From 2d7d42426443588219156d17c455c4bd81434173 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 26 Jul 2026 17:32:48 +0900 Subject: [PATCH 1/2] Differential render by named segments, not by index --- lib/reline/line_editor.rb | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index c8b251f78e..906200414e 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -404,8 +404,8 @@ def calculate_overlay_levels(overlay_levels) end def render_line_differential(old_items, new_items) - old_levels = calculate_overlay_levels(old_items.zip(new_items).each_with_index.map {|((x, w, c), (nx, _nw, nc)), i| [x, w, c == nc && x == nx ? i : -1] if x }.compact) - new_levels = calculate_overlay_levels(new_items.each_with_index.map { |(x, w), i| [x, w, i] if x }.compact).take(screen_width) + old_levels = calculate_overlay_levels(old_items.map { |k, x, w, c| _, nx, _, nc = new_items.assoc(k); [x, w, c == nc && x == nx ? k : :outdated] }).take(screen_width) + new_levels = calculate_overlay_levels(new_items.map { |k, x, w| [x, w, k] }).take(screen_width) base_x = 0 new_levels.zip(old_levels).chunk { |n, o| n == o ? :skip : n || :blank }.each do |level, chunk| width = chunk.size @@ -415,7 +415,7 @@ def render_line_differential(old_items, new_items) Reline::IOGate.move_cursor_column base_x Reline::IOGate.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}" else - x, w, content = new_items[level] + _, x, w, content = new_items.assoc(level) cover_begin = base_x != 0 && new_levels[base_x - 1] == level cover_end = new_levels[base_x + width] == level pos = 0 @@ -474,7 +474,10 @@ def render wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position new_lines = wrapped_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line| prompt_width = Reline::Unicode.calculate_width(prompt, true) - [[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]] + [ + [:builtin_prompt, 0, prompt_width, prompt], + [:builtin_line, prompt_width, Reline::Unicode.calculate_width(line, true), line] + ] end # Add rprompt to the first visible line if set and there's room @@ -482,31 +485,30 @@ def render rprompt_width = Reline::Unicode.calculate_width(@rprompt, true) right_col = screen_width - rprompt_width first_line = new_lines[0] - # Calculate the end of the current content (prompt + input) - content_end = first_line.sum { |_, width, _| width } + content_end = first_line.map { |_, x, width, _| x + width }.max # Only show rprompt if there's at least 1 char gap between content and rprompt if right_col > content_end - first_line << [right_col, rprompt_width, @rprompt] + first_line << [:builtin_rprompt, right_col, rprompt_width, @rprompt] end end if @menu_info @menu_info.lines(screen_width).each do |item| - new_lines << [[0, Reline::Unicode.calculate_width(item), item]] + new_lines << [[:builtin_menu, 0, Reline::Unicode.calculate_width(item), item]] end @menu_info = nil # TODO: do not change state here end - @dialogs.each_with_index do |dialog, index| + @dialogs.each do |dialog| next unless dialog.contents + identifier = :"dialog_#{dialog.name}" x_range, y_range = dialog_range dialog, wrapped_cursor_y - screen_scroll_top y_range.each do |row| next if row < 0 || row >= screen_height dialog_rows = new_lines[row] ||= [] - # index 0 is for prompt, index 1 is for line, index 2 is for rprompt, index 3.. is for dialog - dialog_rows[index + 3] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]] + dialog_rows << [identifier, x_range.begin, dialog.width, dialog.contents[row - y_range.begin]] end end From f466054b695af35596f6e133be3a9ed4eafd268c Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 26 Jul 2026 18:15:25 +0900 Subject: [PATCH 2/2] Update render_line_differential tests to named segment format Co-Authored-By: Claude Fable 5 --- test/reline/test_line_editor.rb | 50 ++++++++++++++++----------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/test/reline/test_line_editor.rb b/test/reline/test_line_editor.rb index 28fcbfa6df..ba521707ec 100644 --- a/test/reline/test_line_editor.rb +++ b/test/reline/test_line_editor.rb @@ -130,107 +130,107 @@ def teardown def test_line_increase_decrease assert_output '[COL_0]bb' do - @line_editor.render_line_differential([[0, 1, 'a']], [[0, 2, 'bb']]) + @line_editor.render_line_differential([[:line, 0, 1, 'a']], [[:line, 0, 2, 'bb']]) end assert_output '[COL_0]b[COL_1][ERASE]' do - @line_editor.render_line_differential([[0, 2, 'aa']], [[0, 1, 'b']]) + @line_editor.render_line_differential([[:line, 0, 2, 'aa']], [[:line, 0, 1, 'b']]) end end def test_dialog_appear_disappear assert_output '[COL_3]dialog' do - @line_editor.render_line_differential([[0, 1, 'a']], [[0, 1, 'a'], [3, 6, 'dialog']]) + @line_editor.render_line_differential([[:line, 0, 1, 'a']], [[:line, 0, 1, 'a'], [:dialog, 3, 6, 'dialog']]) end assert_output '[COL_3]dialog' do - @line_editor.render_line_differential([[0, 10, 'a' * 10]], [[0, 10, 'a' * 10], [3, 6, 'dialog']]) + @line_editor.render_line_differential([[:line, 0, 10, 'a' * 10]], [[:line, 0, 10, 'a' * 10], [:dialog, 3, 6, 'dialog']]) end assert_output '[COL_1][ERASE]' do - @line_editor.render_line_differential([[0, 1, 'a'], [3, 6, 'dialog']], [[0, 1, 'a']]) + @line_editor.render_line_differential([[:line, 0, 1, 'a'], [:dialog, 3, 6, 'dialog']], [[:line, 0, 1, 'a']]) end assert_output '[COL_3]aaaaaa' do - @line_editor.render_line_differential([[0, 10, 'a' * 10], [3, 6, 'dialog']], [[0, 10, 'a' * 10]]) + @line_editor.render_line_differential([[:line, 0, 10, 'a' * 10], [:dialog, 3, 6, 'dialog']], [[:line, 0, 10, 'a' * 10]]) end end def test_dialog_change assert_output '[COL_3]DIALOG' do - @line_editor.render_line_differential([[0, 2, 'a'], [3, 6, 'dialog']], [[0, 2, 'a'], [3, 6, 'DIALOG']]) + @line_editor.render_line_differential([[:line, 0, 2, 'a'], [:dialog, 3, 6, 'dialog']], [[:line, 0, 2, 'a'], [:dialog, 3, 6, 'DIALOG']]) end assert_output '[COL_3]DIALOG' do - @line_editor.render_line_differential([[0, 10, 'a' * 10], [3, 6, 'dialog']], [[0, 10, 'a' * 10], [3, 6, 'DIALOG']]) + @line_editor.render_line_differential([[:line, 0, 10, 'a' * 10], [:dialog, 3, 6, 'dialog']], [[:line, 0, 10, 'a' * 10], [:dialog, 3, 6, 'DIALOG']]) end end def test_update_under_dialog assert_output '[COL_0]b[COL_1] ' do - @line_editor.render_line_differential([[0, 2, 'aa'], [4, 6, 'dialog']], [[0, 1, 'b'], [4, 6, 'dialog']]) + @line_editor.render_line_differential([[:line, 0, 2, 'aa'], [:dialog, 4, 6, 'dialog']], [[:line, 0, 1, 'b'], [:dialog, 4, 6, 'dialog']]) end assert_output '[COL_0]bbb[COL_9]b' do - @line_editor.render_line_differential([[0, 10, 'a' * 10], [3, 6, 'dialog']], [[0, 10, 'b' * 10], [3, 6, 'dialog']]) + @line_editor.render_line_differential([[:line, 0, 10, 'a' * 10], [:dialog, 3, 6, 'dialog']], [[:line, 0, 10, 'b' * 10], [:dialog, 3, 6, 'dialog']]) end assert_output '[COL_0]b[COL_1] [COL_9][ERASE]' do - @line_editor.render_line_differential([[0, 10, 'a' * 10], [3, 6, 'dialog']], [[0, 1, 'b'], [3, 6, 'dialog']]) + @line_editor.render_line_differential([[:line, 0, 10, 'a' * 10], [:dialog, 3, 6, 'dialog']], [[:line, 0, 1, 'b'], [:dialog, 3, 6, 'dialog']]) end end def test_dialog_move assert_output '[COL_3]dialog[COL_9][ERASE]' do - @line_editor.render_line_differential([[0, 1, 'a'], [4, 6, 'dialog']], [[0, 1, 'a'], [3, 6, 'dialog']]) + @line_editor.render_line_differential([[:line, 0, 1, 'a'], [:dialog, 4, 6, 'dialog']], [[:line, 0, 1, 'a'], [:dialog, 3, 6, 'dialog']]) end assert_output '[COL_4] [COL_5]dialog' do - @line_editor.render_line_differential([[0, 1, 'a'], [4, 6, 'dialog']], [[0, 1, 'a'], [5, 6, 'dialog']]) + @line_editor.render_line_differential([[:line, 0, 1, 'a'], [:dialog, 4, 6, 'dialog']], [[:line, 0, 1, 'a'], [:dialog, 5, 6, 'dialog']]) end assert_output '[COL_2]dialog[COL_8]a' do - @line_editor.render_line_differential([[0, 10, 'a' * 10], [3, 6, 'dialog']], [[0, 10, 'a' * 10], [2, 6, 'dialog']]) + @line_editor.render_line_differential([[:line, 0, 10, 'a' * 10], [:dialog, 3, 6, 'dialog']], [[:line, 0, 10, 'a' * 10], [:dialog, 2, 6, 'dialog']]) end assert_output '[COL_2]a[COL_3]dialog' do - @line_editor.render_line_differential([[0, 10, 'a' * 10], [2, 6, 'dialog']], [[0, 10, 'a' * 10], [3, 6, 'dialog']]) + @line_editor.render_line_differential([[:line, 0, 10, 'a' * 10], [:dialog, 2, 6, 'dialog']], [[:line, 0, 10, 'a' * 10], [:dialog, 3, 6, 'dialog']]) end end def test_multibyte - base = [0, 12, '一二三一二三'] - left = [0, 3, 'LLL'] - right = [9, 3, 'RRR'] - front = [3, 6, 'FFFFFF'] + base = [:base, 0, 12, '一二三一二三'] + left = [:left, 0, 3, 'LLL'] + right = [:right, 9, 3, 'RRR'] + front = [:front, 3, 6, 'FFFFFF'] # 一 FFFFFF 三 # 一二三一二三 assert_output '[COL_2]二三一二' do - @line_editor.render_line_differential([base, front], [base, nil]) + @line_editor.render_line_differential([base, front], [base]) end # LLLFFFFFF 三 # LLL 三一二三 assert_output '[COL_3] 三一二' do - @line_editor.render_line_differential([base, left, front], [base, left, nil]) + @line_editor.render_line_differential([base, left, front], [base, left]) end # 一 FFFFFFRRR # 一二三一 RRR assert_output '[COL_2]二三一 ' do - @line_editor.render_line_differential([base, right, front], [base, right, nil]) + @line_editor.render_line_differential([base, right, front], [base, right]) end # LLLFFFFFFRRR # LLL 三一 RRR assert_output '[COL_3] 三一 ' do - @line_editor.render_line_differential([base, left, right, front], [base, left, right, nil]) + @line_editor.render_line_differential([base, left, right, front], [base, left, right]) end end def test_complicated - state_a = [nil, [19, 7, 'bbbbbbb'], [15, 8, 'cccccccc'], [10, 5, 'ddddd'], [18, 4, 'eeee'], [1, 3, 'fff'], [17, 2, 'gg'], [7, 1, 'h']] - state_b = [[5, 9, 'aaaaaaaaa'], nil, [15, 8, 'cccccccc'], nil, [18, 4, 'EEEE'], [25, 4, 'ffff'], [17, 2, 'gg'], [2, 2, 'hh']] + state_a = [[:b, 19, 7, 'bbbbbbb'], [:c, 15, 8, 'cccccccc'], [:d, 10, 5, 'ddddd'], [:e, 18, 4, 'eeee'], [:f, 1, 3, 'fff'], [:g, 17, 2, 'gg'], [:h, 7, 1, 'h']] + state_b = [[:a, 5, 9, 'aaaaaaaaa'], [:c, 15, 8, 'cccccccc'], [:e, 18, 4, 'EEEE'], [:f, 25, 4, 'ffff'], [:g, 17, 2, 'gg'], [:h, 2, 2, 'hh']] # state_a: " fff h dddddccggeeecbbb" # state_b: " hh aaaaaaaaa ccggEEEc ffff"