mirror of
https://github.com/ruby/ruby.git
synced 2025-09-15 16:44:01 +02:00
[ruby/reline] Rewrite dialog rendering
(https://github.com/ruby/reline/pull/492) * Rewrite dialog rendering * Fix failing test of dialog with small screen * Add multiple-dialog rendering test * Add description comments for each part of render_dialog_changes
This commit is contained in:
parent
dd5ba1b725
commit
13dfbcf7bf
3 changed files with 177 additions and 209 deletions
|
@ -94,7 +94,7 @@ class Reline::LineEditor
|
||||||
mode_string
|
mode_string
|
||||||
end
|
end
|
||||||
|
|
||||||
private def check_multiline_prompt(buffer)
|
private def check_multiline_prompt(buffer, force_recalc: false)
|
||||||
if @vi_arg
|
if @vi_arg
|
||||||
prompt = "(arg: #{@vi_arg}) "
|
prompt = "(arg: #{@vi_arg}) "
|
||||||
@rerender_all = true
|
@rerender_all = true
|
||||||
|
@ -104,7 +104,7 @@ class Reline::LineEditor
|
||||||
else
|
else
|
||||||
prompt = @prompt
|
prompt = @prompt
|
||||||
end
|
end
|
||||||
if simplified_rendering?
|
if simplified_rendering? && !force_recalc
|
||||||
mode_string = check_mode_string
|
mode_string = check_mode_string
|
||||||
prompt = mode_string + prompt if mode_string
|
prompt = mode_string + prompt if mode_string
|
||||||
return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
|
return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
|
||||||
|
@ -220,7 +220,7 @@ class Reline::LineEditor
|
||||||
|
|
||||||
def set_signal_handlers
|
def set_signal_handlers
|
||||||
@old_trap = Signal.trap('INT') {
|
@old_trap = Signal.trap('INT') {
|
||||||
clear_dialog
|
clear_dialog(0)
|
||||||
if @scroll_partial_screen
|
if @scroll_partial_screen
|
||||||
move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
|
move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
|
||||||
else
|
else
|
||||||
|
@ -283,6 +283,7 @@ class Reline::LineEditor
|
||||||
@in_pasting = false
|
@in_pasting = false
|
||||||
@auto_indent_proc = nil
|
@auto_indent_proc = nil
|
||||||
@dialogs = []
|
@dialogs = []
|
||||||
|
@previous_rendered_dialog_y = 0
|
||||||
@last_key = nil
|
@last_key = nil
|
||||||
@resized = false
|
@resized = false
|
||||||
reset_line
|
reset_line
|
||||||
|
@ -429,6 +430,7 @@ class Reline::LineEditor
|
||||||
@menu_info = nil
|
@menu_info = nil
|
||||||
end
|
end
|
||||||
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
|
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
|
||||||
|
cursor_column = (prompt_width + @cursor) % @screen_size.last
|
||||||
if @cleared
|
if @cleared
|
||||||
clear_screen_buffer(prompt, prompt_list, prompt_width)
|
clear_screen_buffer(prompt, prompt_list, prompt_width)
|
||||||
@cleared = false
|
@cleared = false
|
||||||
|
@ -446,23 +448,23 @@ class Reline::LineEditor
|
||||||
Reline::IOGate.erase_after_cursor
|
Reline::IOGate.erase_after_cursor
|
||||||
end
|
end
|
||||||
@output.flush
|
@output.flush
|
||||||
clear_dialog
|
clear_dialog(cursor_column)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
|
new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
|
||||||
rendered = false
|
rendered = false
|
||||||
if @add_newline_to_end_of_buffer
|
if @add_newline_to_end_of_buffer
|
||||||
clear_dialog_with_content
|
clear_dialog_with_trap_key(cursor_column)
|
||||||
rerender_added_newline(prompt, prompt_width, prompt_list)
|
rerender_added_newline(prompt, prompt_width, prompt_list)
|
||||||
@add_newline_to_end_of_buffer = false
|
@add_newline_to_end_of_buffer = false
|
||||||
else
|
else
|
||||||
if @just_cursor_moving and not @rerender_all
|
if @just_cursor_moving and not @rerender_all
|
||||||
clear_dialog_with_content
|
clear_dialog_with_trap_key(cursor_column)
|
||||||
rendered = just_move_cursor
|
rendered = just_move_cursor
|
||||||
@just_cursor_moving = false
|
@just_cursor_moving = false
|
||||||
return
|
return
|
||||||
elsif @previous_line_index or new_highest_in_this != @highest_in_this
|
elsif @previous_line_index or new_highest_in_this != @highest_in_this
|
||||||
clear_dialog_with_content
|
clear_dialog_with_trap_key(cursor_column)
|
||||||
rerender_changed_current_line
|
rerender_changed_current_line
|
||||||
@previous_line_index = nil
|
@previous_line_index = nil
|
||||||
rendered = true
|
rendered = true
|
||||||
|
@ -478,7 +480,7 @@ class Reline::LineEditor
|
||||||
# Always rerender on finish because output_modifier_proc may return a different output.
|
# Always rerender on finish because output_modifier_proc may return a different output.
|
||||||
new_lines = whole_lines
|
new_lines = whole_lines
|
||||||
line = modify_lines(new_lines)[@line_index]
|
line = modify_lines(new_lines)[@line_index]
|
||||||
clear_dialog
|
clear_dialog(cursor_column)
|
||||||
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
|
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
|
||||||
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
||||||
move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
|
move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
|
||||||
|
@ -491,7 +493,7 @@ class Reline::LineEditor
|
||||||
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
|
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
|
||||||
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
||||||
end
|
end
|
||||||
render_dialog((prompt_width + @cursor) % @screen_size.last)
|
render_dialog(cursor_column)
|
||||||
end
|
end
|
||||||
@buffer_of_lines[@line_index] = @line
|
@buffer_of_lines[@line_index] = @line
|
||||||
@rest_height = 0 if @scroll_partial_screen
|
@rest_height = 0 if @scroll_partial_screen
|
||||||
|
@ -575,7 +577,7 @@ class Reline::LineEditor
|
||||||
|
|
||||||
class Dialog
|
class Dialog
|
||||||
attr_reader :name, :contents, :width
|
attr_reader :name, :contents, :width
|
||||||
attr_accessor :scroll_top, :pointer, :column, :vertical_offset, :lines_backup, :trap_key
|
attr_accessor :scroll_top, :pointer, :column, :vertical_offset, :trap_key
|
||||||
|
|
||||||
def initialize(name, config, proc_scope)
|
def initialize(name, config, proc_scope)
|
||||||
@name = name
|
@name = name
|
||||||
|
@ -631,9 +633,12 @@ class Reline::LineEditor
|
||||||
|
|
||||||
DIALOG_DEFAULT_HEIGHT = 20
|
DIALOG_DEFAULT_HEIGHT = 20
|
||||||
private def render_dialog(cursor_column)
|
private def render_dialog(cursor_column)
|
||||||
@dialogs.each do |dialog|
|
changes = @dialogs.map do |dialog|
|
||||||
render_each_dialog(dialog, cursor_column)
|
old_dialog = dialog.dup
|
||||||
|
update_each_dialog(dialog, cursor_column)
|
||||||
|
[old_dialog, dialog]
|
||||||
end
|
end
|
||||||
|
render_dialog_changes(changes, cursor_column)
|
||||||
end
|
end
|
||||||
|
|
||||||
private def padding_space_with_escape_sequences(str, width)
|
private def padding_space_with_escape_sequences(str, width)
|
||||||
|
@ -643,9 +648,106 @@ class Reline::LineEditor
|
||||||
str + (' ' * padding_width)
|
str + (' ' * padding_width)
|
||||||
end
|
end
|
||||||
|
|
||||||
private def render_each_dialog(dialog, cursor_column)
|
private def range_subtract(base_ranges, subtract_ranges)
|
||||||
|
indices = base_ranges.flat_map(&:to_a).uniq.sort - subtract_ranges.flat_map(&:to_a)
|
||||||
|
chunks = indices.chunk_while { |a, b| a + 1 == b }
|
||||||
|
chunks.map { |a| a.first...a.last + 1 }
|
||||||
|
end
|
||||||
|
|
||||||
|
private def dialog_range(dialog, dialog_y)
|
||||||
|
x_range = dialog.column...dialog.column + dialog.width
|
||||||
|
y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size
|
||||||
|
[x_range, y_range]
|
||||||
|
end
|
||||||
|
|
||||||
|
private def render_dialog_changes(changes, cursor_column)
|
||||||
|
# Collect x-coordinate range and content of previous and current dialogs for each line
|
||||||
|
old_dialog_ranges = {}
|
||||||
|
new_dialog_ranges = {}
|
||||||
|
new_dialog_contents = {}
|
||||||
|
changes.each do |old_dialog, new_dialog|
|
||||||
|
if old_dialog.contents
|
||||||
|
x_range, y_range = dialog_range(old_dialog, @previous_rendered_dialog_y)
|
||||||
|
y_range.each do |y|
|
||||||
|
(old_dialog_ranges[y] ||= []) << x_range
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if new_dialog.contents
|
||||||
|
x_range, y_range = dialog_range(new_dialog, @first_line_started_from + @started_from)
|
||||||
|
y_range.each do |y|
|
||||||
|
(new_dialog_ranges[y] ||= []) << x_range
|
||||||
|
(new_dialog_contents[y] ||= []) << [x_range, new_dialog.contents[y - y_range.begin]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return if old_dialog_ranges.empty? && new_dialog_ranges.empty?
|
||||||
|
|
||||||
|
# Calculate x-coordinate ranges to restore text that was hidden behind dialogs for each line
|
||||||
|
ranges_to_restore = {}
|
||||||
|
subtract_cache = {}
|
||||||
|
old_dialog_ranges.each do |y, old_x_ranges|
|
||||||
|
new_x_ranges = new_dialog_ranges[y] || []
|
||||||
|
ranges = subtract_cache[[old_x_ranges, new_x_ranges]] ||= range_subtract(old_x_ranges, new_x_ranges)
|
||||||
|
ranges_to_restore[y] = ranges if ranges.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create visual_lines for restoring text hidden behind dialogs
|
||||||
|
if ranges_to_restore.any?
|
||||||
|
lines = whole_lines
|
||||||
|
prompt, _prompt_width, prompt_list = check_multiline_prompt(lines, force_recalc: true)
|
||||||
|
modified_lines = modify_lines(lines, force_recalc: true)
|
||||||
|
visual_lines = []
|
||||||
|
modified_lines.each_with_index { |l, i|
|
||||||
|
pr = prompt_list ? prompt_list[i] : prompt
|
||||||
|
vl, = split_by_width(pr + l, @screen_size.last)
|
||||||
|
vl.compact!
|
||||||
|
visual_lines.concat(vl)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Clear and rerender all dialogs line by line
|
||||||
|
Reline::IOGate.hide_cursor
|
||||||
|
ymin, ymax = (ranges_to_restore.keys + new_dialog_ranges.keys).minmax
|
||||||
|
dialog_y = @first_line_started_from + @started_from
|
||||||
|
cursor_y = dialog_y
|
||||||
|
scroll_down(ymax - cursor_y)
|
||||||
|
move_cursor_up(ymax - cursor_y)
|
||||||
|
(ymin..ymax).each do |y|
|
||||||
|
move_cursor_down(y - cursor_y)
|
||||||
|
cursor_y = y
|
||||||
|
new_x_ranges = new_dialog_ranges[y]
|
||||||
|
restore_ranges = ranges_to_restore[y]
|
||||||
|
# Restore text that was hidden behind dialogs
|
||||||
|
if restore_ranges
|
||||||
|
line = visual_lines[y] || ''
|
||||||
|
restore_ranges.each do |range|
|
||||||
|
col = range.begin
|
||||||
|
width = range.end - range.begin
|
||||||
|
s = padding_space_with_escape_sequences(Reline::Unicode.take_range(line, col, width), width)
|
||||||
|
Reline::IOGate.move_cursor_column(col)
|
||||||
|
@output.write "\e[0m#{s}\e[0m"
|
||||||
|
end
|
||||||
|
max_column = [calculate_width(line, true), new_x_ranges&.map(&:end)&.max || 0].max
|
||||||
|
if max_column < restore_ranges.map(&:end).max
|
||||||
|
Reline::IOGate.move_cursor_column(max_column)
|
||||||
|
Reline::IOGate.erase_after_cursor
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# Render dialog contents
|
||||||
|
new_dialog_contents[y]&.each do |x_range, content|
|
||||||
|
Reline::IOGate.move_cursor_column(x_range.begin)
|
||||||
|
@output.write "\e[0m#{content}\e[0m"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
move_cursor_up(cursor_y - dialog_y)
|
||||||
|
Reline::IOGate.move_cursor_column(cursor_column)
|
||||||
|
Reline::IOGate.show_cursor
|
||||||
|
|
||||||
|
@previous_rendered_dialog_y = dialog_y
|
||||||
|
end
|
||||||
|
|
||||||
|
private def update_each_dialog(dialog, cursor_column)
|
||||||
if @in_pasting
|
if @in_pasting
|
||||||
clear_each_dialog(dialog)
|
|
||||||
dialog.contents = nil
|
dialog.contents = nil
|
||||||
dialog.trap_key = nil
|
dialog.trap_key = nil
|
||||||
return
|
return
|
||||||
|
@ -653,31 +755,20 @@ class Reline::LineEditor
|
||||||
dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
|
dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
|
||||||
dialog_render_info = dialog.call(@last_key)
|
dialog_render_info = dialog.call(@last_key)
|
||||||
if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
|
if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
|
||||||
lines = whole_lines
|
|
||||||
dialog.lines_backup = {
|
|
||||||
unmodified_lines: lines,
|
|
||||||
lines: modify_lines(lines),
|
|
||||||
line_index: @line_index,
|
|
||||||
first_line_started_from: @first_line_started_from,
|
|
||||||
started_from: @started_from,
|
|
||||||
byte_pointer: @byte_pointer
|
|
||||||
}
|
|
||||||
clear_each_dialog(dialog)
|
|
||||||
dialog.contents = nil
|
dialog.contents = nil
|
||||||
dialog.trap_key = nil
|
dialog.trap_key = nil
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
old_dialog = dialog.clone
|
contents = dialog_render_info.contents
|
||||||
dialog.contents = dialog_render_info.contents
|
|
||||||
pointer = dialog.pointer
|
pointer = dialog.pointer
|
||||||
if dialog_render_info.width
|
if dialog_render_info.width
|
||||||
dialog.width = dialog_render_info.width
|
dialog.width = dialog_render_info.width
|
||||||
else
|
else
|
||||||
dialog.width = dialog.contents.map { |l| calculate_width(l, true) }.max
|
dialog.width = contents.map { |l| calculate_width(l, true) }.max
|
||||||
end
|
end
|
||||||
height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
|
height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
|
||||||
height = dialog.contents.size if dialog.contents.size < height
|
height = contents.size if contents.size < height
|
||||||
if dialog.contents.size > height
|
if contents.size > height
|
||||||
if dialog.pointer
|
if dialog.pointer
|
||||||
if dialog.pointer < 0
|
if dialog.pointer < 0
|
||||||
dialog.scroll_top = 0
|
dialog.scroll_top = 0
|
||||||
|
@ -690,13 +781,13 @@ class Reline::LineEditor
|
||||||
else
|
else
|
||||||
dialog.scroll_top = 0
|
dialog.scroll_top = 0
|
||||||
end
|
end
|
||||||
dialog.contents = dialog.contents[dialog.scroll_top, height]
|
contents = contents[dialog.scroll_top, height]
|
||||||
end
|
end
|
||||||
if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
|
if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
|
||||||
bar_max_height = height * 2
|
bar_max_height = height * 2
|
||||||
moving_distance = (dialog_render_info.contents.size - height) * 2
|
moving_distance = (dialog_render_info.contents.size - height) * 2
|
||||||
position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
|
position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
|
||||||
bar_height = (bar_max_height * ((dialog.contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
|
bar_height = (bar_max_height * ((contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
|
||||||
bar_height = MINIMUM_SCROLLBAR_HEIGHT if bar_height < MINIMUM_SCROLLBAR_HEIGHT
|
bar_height = MINIMUM_SCROLLBAR_HEIGHT if bar_height < MINIMUM_SCROLLBAR_HEIGHT
|
||||||
scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
|
scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
|
||||||
else
|
else
|
||||||
|
@ -714,21 +805,13 @@ class Reline::LineEditor
|
||||||
elsif upper_space >= height
|
elsif upper_space >= height
|
||||||
dialog.vertical_offset = dialog_render_info.pos.y - height
|
dialog.vertical_offset = dialog_render_info.pos.y - height
|
||||||
else
|
else
|
||||||
if (@rest_height - dialog_render_info.pos.y) < height
|
|
||||||
scroll_down(height + dialog_render_info.pos.y)
|
|
||||||
move_cursor_up(height + dialog_render_info.pos.y)
|
|
||||||
end
|
|
||||||
dialog.vertical_offset = dialog_render_info.pos.y + 1
|
dialog.vertical_offset = dialog_render_info.pos.y + 1
|
||||||
end
|
end
|
||||||
Reline::IOGate.hide_cursor
|
|
||||||
if dialog.column < 0
|
if dialog.column < 0
|
||||||
dialog.column = 0
|
dialog.column = 0
|
||||||
dialog.width = @screen_size.last
|
dialog.width = @screen_size.last
|
||||||
end
|
end
|
||||||
reset_dialog(dialog, old_dialog)
|
dialog.contents = contents.map.with_index do |item, i|
|
||||||
move_cursor_down(dialog.vertical_offset)
|
|
||||||
Reline::IOGate.move_cursor_column(dialog.column)
|
|
||||||
dialog.contents.each_with_index do |item, i|
|
|
||||||
if i == pointer
|
if i == pointer
|
||||||
fg_color = dialog_render_info.pointer_fg_color
|
fg_color = dialog_render_info.pointer_fg_color
|
||||||
bg_color = dialog_render_info.pointer_bg_color
|
bg_color = dialog_render_info.pointer_bg_color
|
||||||
|
@ -738,183 +821,38 @@ class Reline::LineEditor
|
||||||
end
|
end
|
||||||
str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
|
str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
|
||||||
str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
|
str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
|
||||||
@output.write "\e[#{bg_color}m\e[#{fg_color}m#{str}"
|
colored_content = "\e[#{bg_color}m\e[#{fg_color}m#{str}"
|
||||||
if scrollbar_pos
|
if scrollbar_pos
|
||||||
@output.write "\e[37m"
|
color_seq = "\e[37m"
|
||||||
if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
|
if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
|
||||||
@output.write @full_block
|
colored_content + color_seq + @full_block
|
||||||
elsif scrollbar_pos <= (i * 2) and (i * 2) < (scrollbar_pos + bar_height)
|
elsif scrollbar_pos <= (i * 2) and (i * 2) < (scrollbar_pos + bar_height)
|
||||||
@output.write @upper_half_block
|
colored_content + color_seq + @upper_half_block
|
||||||
elsif scrollbar_pos <= (i * 2 + 1) and (i * 2) < (scrollbar_pos + bar_height)
|
elsif scrollbar_pos <= (i * 2 + 1) and (i * 2) < (scrollbar_pos + bar_height)
|
||||||
@output.write @lower_half_block
|
colored_content + color_seq + @lower_half_block
|
||||||
else
|
else
|
||||||
@output.write ' ' * @block_elem_width
|
colored_content + color_seq + ' ' * @block_elem_width
|
||||||
end
|
end
|
||||||
end
|
|
||||||
@output.write "\e[0m"
|
|
||||||
Reline::IOGate.move_cursor_column(dialog.column)
|
|
||||||
move_cursor_down(1) if i < (dialog.contents.size - 1)
|
|
||||||
end
|
|
||||||
Reline::IOGate.move_cursor_column(cursor_column)
|
|
||||||
move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
|
|
||||||
Reline::IOGate.show_cursor
|
|
||||||
lines = whole_lines
|
|
||||||
dialog.lines_backup = {
|
|
||||||
unmodified_lines: lines,
|
|
||||||
lines: modify_lines(lines),
|
|
||||||
line_index: @line_index,
|
|
||||||
first_line_started_from: @first_line_started_from,
|
|
||||||
started_from: @started_from,
|
|
||||||
byte_pointer: @byte_pointer
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
private def reset_dialog(dialog, old_dialog)
|
|
||||||
return if dialog.lines_backup.nil? or old_dialog.contents.nil?
|
|
||||||
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:unmodified_lines])
|
|
||||||
visual_lines = []
|
|
||||||
visual_start = nil
|
|
||||||
dialog.lines_backup[:lines].each_with_index { |l, i|
|
|
||||||
pr = prompt_list ? prompt_list[i] : prompt
|
|
||||||
vl, _ = split_by_width(pr + l, @screen_size.last)
|
|
||||||
vl.compact!
|
|
||||||
if i == dialog.lines_backup[:line_index]
|
|
||||||
visual_start = visual_lines.size + dialog.lines_backup[:started_from]
|
|
||||||
end
|
|
||||||
visual_lines.concat(vl)
|
|
||||||
}
|
|
||||||
old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
|
|
||||||
y = @first_line_started_from + @started_from
|
|
||||||
y_diff = y - old_y
|
|
||||||
if (old_y + old_dialog.vertical_offset) < (y + dialog.vertical_offset)
|
|
||||||
# rerender top
|
|
||||||
move_cursor_down(old_dialog.vertical_offset - y_diff)
|
|
||||||
start = visual_start + old_dialog.vertical_offset
|
|
||||||
line_num = dialog.vertical_offset - old_dialog.vertical_offset
|
|
||||||
line_num.times do |i|
|
|
||||||
Reline::IOGate.move_cursor_column(old_dialog.column)
|
|
||||||
if visual_lines[start + i].nil?
|
|
||||||
s = ' ' * old_dialog.width
|
|
||||||
else
|
else
|
||||||
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
|
colored_content
|
||||||
s = padding_space_with_escape_sequences(s, old_dialog.width)
|
|
||||||
end
|
end
|
||||||
@output.write "\e[0m#{s}\e[0m"
|
|
||||||
move_cursor_down(1) if i < (line_num - 1)
|
|
||||||
end
|
|
||||||
move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
|
|
||||||
end
|
|
||||||
if (old_y + old_dialog.vertical_offset + old_dialog.contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
|
|
||||||
# rerender bottom
|
|
||||||
move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
|
|
||||||
start = visual_start + dialog.vertical_offset + dialog.contents.size
|
|
||||||
line_num = (old_dialog.vertical_offset + old_dialog.contents.size) - (dialog.vertical_offset + dialog.contents.size)
|
|
||||||
line_num.times do |i|
|
|
||||||
Reline::IOGate.move_cursor_column(old_dialog.column)
|
|
||||||
if visual_lines[start + i].nil?
|
|
||||||
s = ' ' * old_dialog.width
|
|
||||||
else
|
|
||||||
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
|
|
||||||
s = padding_space_with_escape_sequences(s, old_dialog.width)
|
|
||||||
end
|
|
||||||
@output.write "\e[0m#{s}\e[0m"
|
|
||||||
move_cursor_down(1) if i < (line_num - 1)
|
|
||||||
end
|
|
||||||
move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
|
|
||||||
end
|
|
||||||
if old_dialog.column < dialog.column
|
|
||||||
# rerender left
|
|
||||||
move_cursor_down(old_dialog.vertical_offset - y_diff)
|
|
||||||
width = dialog.column - old_dialog.column
|
|
||||||
start = visual_start + old_dialog.vertical_offset
|
|
||||||
line_num = old_dialog.contents.size
|
|
||||||
line_num.times do |i|
|
|
||||||
Reline::IOGate.move_cursor_column(old_dialog.column)
|
|
||||||
if visual_lines[start + i].nil?
|
|
||||||
s = ' ' * width
|
|
||||||
else
|
|
||||||
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
|
|
||||||
s = padding_space_with_escape_sequences(s, dialog.width)
|
|
||||||
end
|
|
||||||
@output.write "\e[0m#{s}\e[0m"
|
|
||||||
move_cursor_down(1) if i < (line_num - 1)
|
|
||||||
end
|
|
||||||
move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
|
|
||||||
end
|
|
||||||
if (old_dialog.column + old_dialog.width) > (dialog.column + dialog.width)
|
|
||||||
# rerender right
|
|
||||||
move_cursor_down(old_dialog.vertical_offset + y_diff)
|
|
||||||
width = (old_dialog.column + old_dialog.width) - (dialog.column + dialog.width)
|
|
||||||
start = visual_start + old_dialog.vertical_offset
|
|
||||||
line_num = old_dialog.contents.size
|
|
||||||
line_num.times do |i|
|
|
||||||
Reline::IOGate.move_cursor_column(old_dialog.column + dialog.width)
|
|
||||||
if visual_lines[start + i].nil?
|
|
||||||
s = ' ' * width
|
|
||||||
else
|
|
||||||
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
|
|
||||||
rerender_width = old_dialog.width - dialog.width
|
|
||||||
s = padding_space_with_escape_sequences(s, rerender_width)
|
|
||||||
end
|
|
||||||
Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
|
|
||||||
@output.write "\e[0m#{s}\e[0m"
|
|
||||||
move_cursor_down(1) if i < (line_num - 1)
|
|
||||||
end
|
|
||||||
move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
|
|
||||||
end
|
|
||||||
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
|
||||||
end
|
|
||||||
|
|
||||||
private def clear_dialog
|
|
||||||
@dialogs.each do |dialog|
|
|
||||||
clear_each_dialog(dialog)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private def clear_dialog_with_content
|
private def clear_dialog(cursor_column)
|
||||||
@dialogs.each do |dialog|
|
changes = @dialogs.map do |dialog|
|
||||||
clear_each_dialog(dialog)
|
old_dialog = dialog.dup
|
||||||
dialog.contents = nil
|
dialog.contents = nil
|
||||||
dialog.trap_key = nil
|
[old_dialog, dialog]
|
||||||
end
|
end
|
||||||
|
render_dialog_changes(changes, cursor_column)
|
||||||
end
|
end
|
||||||
|
|
||||||
private def clear_each_dialog(dialog)
|
private def clear_dialog_with_trap_key(cursor_column)
|
||||||
|
clear_dialog(cursor_column)
|
||||||
|
@dialogs.each do |dialog|
|
||||||
dialog.trap_key = nil
|
dialog.trap_key = nil
|
||||||
return unless dialog.contents
|
|
||||||
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:unmodified_lines])
|
|
||||||
visual_lines = []
|
|
||||||
visual_lines_under_dialog = []
|
|
||||||
visual_start = nil
|
|
||||||
dialog.lines_backup[:lines].each_with_index { |l, i|
|
|
||||||
pr = prompt_list ? prompt_list[i] : prompt
|
|
||||||
vl, _ = split_by_width(pr + l, @screen_size.last)
|
|
||||||
vl.compact!
|
|
||||||
if i == dialog.lines_backup[:line_index]
|
|
||||||
visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
|
|
||||||
end
|
end
|
||||||
visual_lines.concat(vl)
|
|
||||||
}
|
|
||||||
visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
|
|
||||||
visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
|
|
||||||
Reline::IOGate.hide_cursor
|
|
||||||
move_cursor_down(dialog.vertical_offset)
|
|
||||||
dialog_vertical_size = dialog.contents.size
|
|
||||||
dialog_vertical_size.times do |i|
|
|
||||||
if i < visual_lines_under_dialog.size
|
|
||||||
Reline::IOGate.move_cursor_column(dialog.column)
|
|
||||||
str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
|
|
||||||
str = padding_space_with_escape_sequences(str, dialog.width)
|
|
||||||
@output.write "\e[0m#{str}\e[0m"
|
|
||||||
else
|
|
||||||
Reline::IOGate.move_cursor_column(dialog.column)
|
|
||||||
@output.write "\e[0m#{' ' * dialog.width}\e[0m"
|
|
||||||
end
|
|
||||||
move_cursor_down(1) if i < (dialog_vertical_size - 1)
|
|
||||||
end
|
|
||||||
move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
|
|
||||||
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
|
||||||
Reline::IOGate.show_cursor
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
|
private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
|
||||||
|
@ -1224,8 +1162,8 @@ class Reline::LineEditor
|
||||||
height
|
height
|
||||||
end
|
end
|
||||||
|
|
||||||
private def modify_lines(before)
|
private def modify_lines(before, force_recalc: false)
|
||||||
return before if before.nil? || before.empty? || simplified_rendering?
|
return before if !force_recalc && (before.nil? || before.empty? || simplified_rendering?)
|
||||||
|
|
||||||
if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
|
if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
|
||||||
after.lines("\n").map { |l| l.chomp('') }
|
after.lines("\n").map { |l| l.chomp('') }
|
||||||
|
|
13
test/reline/test_line_editor.rb
Normal file
13
test/reline/test_line_editor.rb
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
require_relative 'helper'
|
||||||
|
require 'reline/line_editor'
|
||||||
|
|
||||||
|
class Reline::LineEditor::Test < Reline::TestCase
|
||||||
|
def test_range_subtract
|
||||||
|
dummy_config = nil
|
||||||
|
editor = Reline::LineEditor.new(dummy_config, 'ascii-8bit')
|
||||||
|
base_ranges = [3...5, 4...10, 6...8, 12...15, 15...20]
|
||||||
|
subtract_ranges = [5...7, 8...9, 11...13, 17...18, 18...19]
|
||||||
|
expected_result = [3...5, 7...8, 9...10, 13...17, 19...20]
|
||||||
|
assert_equal expected_result, editor.send(:range_subtract, base_ranges, subtract_ranges)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1138,6 +1138,23 @@ begin
|
||||||
EOC
|
EOC
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_rerender_multiple_dialog
|
||||||
|
start_terminal(20, 60, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete --dialog simple}, startup_message: 'Multiline REPL.')
|
||||||
|
write("if\n abcdef\n 123456\n 456789\nend\C-p\C-p\C-p\C-p Str")
|
||||||
|
write("\t")
|
||||||
|
close
|
||||||
|
assert_screen(<<~'EOC')
|
||||||
|
Multiline REPL.
|
||||||
|
prompt> if String
|
||||||
|
prompt> aStringRuby is...
|
||||||
|
prompt> 1StructA dynamic, open source programming
|
||||||
|
prompt> 456789 language with a focus on simplicity
|
||||||
|
prompt> end and productivity. It has an elegant
|
||||||
|
syntax that is natural to read and
|
||||||
|
easy to write.
|
||||||
|
EOC
|
||||||
|
end
|
||||||
|
|
||||||
def test_autocomplete_long_with_scrollbar
|
def test_autocomplete_long_with_scrollbar
|
||||||
start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete-long}, startup_message: 'Multiline REPL.')
|
start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete-long}, startup_message: 'Multiline REPL.')
|
||||||
write('S')
|
write('S')
|
||||||
|
@ -1343,11 +1360,11 @@ begin
|
||||||
prompt>
|
prompt>
|
||||||
prompt>
|
prompt>
|
||||||
prompt>
|
prompt>
|
||||||
prompt> S
|
|
||||||
prompt> String
|
prompt> String
|
||||||
prompt> Struct
|
prompt> Struct
|
||||||
prompt> enSymbol
|
prompt> Symbol
|
||||||
ScriptError
|
prompt> enScriptError
|
||||||
|
SyntaxError
|
||||||
Signal
|
Signal
|
||||||
EOC
|
EOC
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue