Skip to content

Commit a1943da

Browse files
authored
Fix line wrapped cursor position (#791)
Cursor position calculation was wrong when the input line contains "\1" or CSI escape sequence.
1 parent 6a7e249 commit a1943da

File tree

2 files changed

+30
-1
lines changed

2 files changed

+30
-1
lines changed

lib/reline/line_editor.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ def render_line_differential(old_items, new_items)
436436
# Calculate cursor position in word wrapped content.
437437
def wrapped_cursor_position
438438
prompt_width = calculate_width(prompt_list[@line_index], true)
439-
line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
439+
line_before_cursor = Reline::Unicode.escape_for_print(whole_lines[@line_index].byteslice(0, @byte_pointer))
440440
wrapped_line_before_cursor = split_line_by_width(' ' * prompt_width + line_before_cursor, screen_width)
441441
wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
442442
wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)

test/reline/test_line_editor.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,35 @@ def test_retrieve_completion_quote
5959
end
6060
end
6161

62+
class CursorPositionTest < Reline::TestCase
63+
def setup
64+
@line_editor = Reline::LineEditor.new(nil)
65+
@line_editor.instance_variable_set(:@config, Reline::Config.new)
66+
end
67+
68+
def test_cursor_position_with_escaped_input
69+
@line_editor.instance_variable_set(:@screen_size, [4, 16])
70+
@line_editor.instance_variable_set(:@prompt, "\e[1mprompt\e[0m> ")
71+
@line_editor.instance_variable_set(:@buffer_of_lines, ["\e[1m\0\1\2\3\4\5\6\7abcd"])
72+
@line_editor.instance_variable_set(:@line_index, 0)
73+
# prompt> ^[[1m^@^
74+
# A^B^C^D^E^F^Gabc
75+
# d
76+
@line_editor.instance_variable_set(:@byte_pointer, 0)
77+
assert_equal [8, 0], @line_editor.wrapped_cursor_position
78+
@line_editor.instance_variable_set(:@byte_pointer, 5)
79+
assert_equal [15, 0], @line_editor.wrapped_cursor_position
80+
@line_editor.instance_variable_set(:@byte_pointer, 6)
81+
assert_equal [1, 1], @line_editor.wrapped_cursor_position
82+
@line_editor.instance_variable_set(:@byte_pointer, 14)
83+
assert_equal [15, 1], @line_editor.wrapped_cursor_position
84+
@line_editor.instance_variable_set(:@byte_pointer, 15)
85+
assert_equal [0, 2], @line_editor.wrapped_cursor_position
86+
@line_editor.instance_variable_set(:@byte_pointer, 16)
87+
assert_equal [1, 2], @line_editor.wrapped_cursor_position
88+
end
89+
end
90+
6291
class RenderLineDifferentialTest < Reline::TestCase
6392
class TestIO < Reline::IO
6493
def write(string)

0 commit comments

Comments
 (0)