1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

[ruby/reline] Support for multiple dialog rendering

https://github.com/ruby/reline/commit/f589fab718
This commit is contained in:
aycabta 2021-08-27 14:58:05 +09:00
parent acffb0b068
commit 8d4370b066
2 changed files with 115 additions and 75 deletions

View file

@ -34,7 +34,7 @@ module Reline
auto_indent_proc
pre_input_hook
dig_perfect_match_proc
dialog_proc
dialog_proc_list
).each(&method(:attr_reader))
attr_accessor :config
@ -48,6 +48,7 @@ module Reline
yield self
@completion_quote_character = nil
@bracketed_paste_finished = false
@dialog_proc_list = []
end
def encoding
@ -131,9 +132,10 @@ module Reline
@dig_perfect_match_proc = p
end
def dialog_proc=(p)
def add_dialog_proc(name_sym, p)
raise ArgumentError unless p.respond_to?(:call) or p.nil?
@dialog_proc = p
raise ArgumentError unless name_sym.instance_of?(Symbol)
@dialog_proc_list << [name_sym, p]
end
def input=(val)
@ -206,14 +208,15 @@ module Reline
else
y = 0
end
[Reline::CursorPos.new(x, y), result, pointer]
cursor_pos_to_render = Reline::CursorPos.new(x, y)
[cursor_pos_to_render, result, pointer]
}
def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
unless confirm_multiline_termination
raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
end
@dialog_proc = DEFAULT_DIALOG_PROC_AUTOCOMPLETE
add_dialog_proc(:autocomplete, DEFAULT_DIALOG_PROC_AUTOCOMPLETE)
inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
whole_buffer = line_editor.whole_buffer.dup
@ -269,7 +272,10 @@ module Reline
line_editor.auto_indent_proc = auto_indent_proc
line_editor.dig_perfect_match_proc = dig_perfect_match_proc
line_editor.pre_input_hook = pre_input_hook
line_editor.dialog_proc = dialog_proc
@dialog_proc_list.each do |d|
name_sym, dialog_proc = d
line_editor.add_dialog_proc(name_sym, dialog_proc)
end
unless config.test_mode
config.read

View file

@ -249,10 +249,7 @@ class Reline::LineEditor
@drop_terminate_spaces = false
@in_pasting = false
@auto_indent_proc = nil
@dialog_column = nil
@dialog_vertical_offset = nil
@dialog_contents = nil
@dialog_lines_backup = nil
@dialogs = []
reset_line
end
@ -521,75 +518,98 @@ class Reline::LineEditor
end
end
def dialog_proc=(p)
@dialog_proc_scope = DialogProcScope.new(self, p)
@dialog_proc = p
class Dialog
attr_reader :name
attr_accessor :column, :vertical_offset, :contents, :lines_backup
def initialize(name, proc_scope)
@name = name
@proc_scope = proc_scope
end
DIALOG_HEIGHT = 5
def set_cursor_pos(col, row)
@proc_scope.set_cursor_pos(col, row)
end
def call
@proc_scope.call
end
end
def add_dialog_proc(name, p)
return if @dialogs.any? { |d| d.name == name }
@dialogs << Dialog.new(name, DialogProcScope.new(self, p))
end
DIALOG_HEIGHT = 20
DIALOG_WIDTH = 40
private def render_dialog(cursor_column)
return if @dialog_proc_scope.nil?
@dialogs.each do |dialog|
render_each_dialog(dialog, cursor_column)
end
end
private def render_each_dialog(dialog, cursor_column)
if @in_pasting
@dialog_contents = nil
dialog.contents = nil
return
end
@dialog_proc_scope.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
pos, result, pointer = @dialog_proc_scope.call
old_dialog_contents = @dialog_contents
old_dialog_column = @dialog_column
old_dialog_vertical_offset = @dialog_vertical_offset
dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
pos, result, pointer = dialog.call
old_dialog_contents = dialog.contents
old_dialog_column = dialog.column
old_dialog_vertical_offset = dialog.vertical_offset
if result and not result.empty?
@dialog_contents = result
@dialog_contents = @dialog_contents[0...DIALOG_HEIGHT] if @dialog_contents.size > DIALOG_HEIGHT
dialog.contents = result
dialog.contents = dialog.contents[0...DIALOG_HEIGHT] if dialog.contents.size > DIALOG_HEIGHT
else
@dialog_lines_backup = {
dialog.lines_backup = {
lines: modify_lines(whole_lines),
line_index: @line_index,
first_line_started_from: @first_line_started_from,
started_from: @started_from,
byte_pointer: @byte_pointer
}
clear_dialog
@dialog_contents = nil
clear_each_dialog(dialog)
dialog.contents = nil
return
end
upper_space = @first_line_started_from - @started_from
lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
@dialog_column = pos.x
diff = (@dialog_column + DIALOG_WIDTH) - (@screen_size.last - 1)
dialog.column = pos.x
diff = (dialog.column + DIALOG_WIDTH) - (@screen_size.last - 1)
if diff > 0
@dialog_column -= diff
dialog.column -= diff
end
if (lower_space + @rest_height) >= DIALOG_HEIGHT
@dialog_vertical_offset = pos.y + 1
dialog.vertical_offset = pos.y + 1
elsif upper_space >= DIALOG_HEIGHT
@dialog_vertical_offset = pos.y + -(DIALOG_HEIGHT + 1)
dialog.vertical_offset = pos.y + -(DIALOG_HEIGHT + 1)
else
if (lower_space + @rest_height) < DIALOG_HEIGHT
scroll_down(DIALOG_HEIGHT)
move_cursor_up(DIALOG_HEIGHT)
end
@dialog_vertical_offset = pos.y + 1
dialog.vertical_offset = pos.y + 1
end
Reline::IOGate.hide_cursor
reset_dialog(old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
move_cursor_down(@dialog_vertical_offset)
Reline::IOGate.move_cursor_column(@dialog_column)
@dialog_contents.each_with_index do |item, i|
reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
move_cursor_down(dialog.vertical_offset)
Reline::IOGate.move_cursor_column(dialog.column)
dialog.contents.each_with_index do |item, i|
if i == pointer
bg_color = '45'
else
bg_color = '46'
end
@output.write "\e[#{bg_color}m%-#{DIALOG_WIDTH}s\e[49m" % item.slice(0, DIALOG_WIDTH)
Reline::IOGate.move_cursor_column(@dialog_column)
move_cursor_down(1) if i < (@dialog_contents.size - 1)
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)
move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
Reline::IOGate.show_cursor
@dialog_lines_backup = {
dialog.lines_backup = {
lines: modify_lines(whole_lines),
line_index: @line_index,
first_line_started_from: @first_line_started_from,
@ -598,53 +618,61 @@ class Reline::LineEditor
}
end
private def reset_dialog(old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
return if @dialog_lines_backup.nil? or old_dialog_contents.nil?
prompt, prompt_width, prompt_list = check_multiline_prompt(@dialog_lines_backup[:lines], prompt)
private def reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
return if dialog.lines_backup.nil? or old_dialog_contents.nil?
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
visual_lines = []
visual_start = nil
@dialog_lines_backup[:lines].each_with_index { |l, i|
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]
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]
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)
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 = dialog.vertical_offset - old_dialog_vertical_offset
line_num.times do |i|
Reline::IOGate.move_cursor_column(0)
@output.write "\e[39m\e[49m#{visual_lines[start + i]}\e[39m\e[49m"
Reline::IOGate.erase_after_cursor
Reline::IOGate.move_cursor_column(old_dialog_column)
if visual_lines[start + i].nil?
s = ' ' * DIALOG_WIDTH
else
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
end
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
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)
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)
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(0)
@output.write "\e[39m\e[49m#{visual_lines[start + i]}\e[39m\e[49m"
Reline::IOGate.erase_after_cursor
Reline::IOGate.move_cursor_column(old_dialog_column)
if visual_lines[start + i].nil?
s = ' ' * DIALOG_WIDTH
else
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
end
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
move_cursor_down(1) if i < (line_num - 1)
end
move_cursor_up(@dialog_vertical_offset + @dialog_contents.size + line_num - 1 - y_diff)
move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
end
if old_dialog_column < @dialog_column
if old_dialog_column < dialog.column
# rerender left
move_cursor_down(old_dialog_vertical_offset - y_diff)
width = @dialog_column - old_dialog_column
width = dialog.column - old_dialog_column
start = visual_start + old_dialog_vertical_offset
line_num = old_dialog_contents.size
line_num.times do |i|
@ -659,10 +687,10 @@ class Reline::LineEditor
end
move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
end
if (old_dialog_column + DIALOG_WIDTH) > (@dialog_column + DIALOG_WIDTH)
if (old_dialog_column + DIALOG_WIDTH) > (dialog.column + DIALOG_WIDTH)
# rerender right
move_cursor_down(old_dialog_vertical_offset + y_diff)
width = (old_dialog_column + DIALOG_WIDTH) - (@dialog_column + DIALOG_WIDTH)
width = (old_dialog_column + DIALOG_WIDTH) - (dialog.column + DIALOG_WIDTH)
start = visual_start + old_dialog_vertical_offset
line_num = old_dialog_contents.size
line_num.times do |i|
@ -672,7 +700,7 @@ class Reline::LineEditor
else
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column + DIALOG_WIDTH, width)
end
Reline::IOGate.move_cursor_column(@dialog_column + DIALOG_WIDTH)
Reline::IOGate.move_cursor_column(dialog.column + DIALOG_WIDTH)
@output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
move_cursor_down(1) if i < (line_num - 1)
end
@ -682,36 +710,42 @@ class Reline::LineEditor
end
private def clear_dialog
return unless @dialog_contents
prompt, prompt_width, prompt_list = check_multiline_prompt(@dialog_lines_backup[:lines], prompt)
@dialogs.each do |dialog|
clear_each_dialog(dialog)
end
end
private def clear_each_dialog(dialog)
return unless dialog.contents
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
visual_lines = []
visual_lines_under_dialog = []
visual_start = nil
@dialog_lines_backup[:lines].each_with_index { |l, i|
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
if i == dialog.lines_backup[:line_index]
visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
end
visual_lines.concat(vl)
}
visual_lines_under_dialog = visual_lines[visual_start, @dialog_contents.size]
visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
Reline::IOGate.hide_cursor
move_cursor_down(@dialog_vertical_offset)
dialog_vertical_size = @dialog_contents.size
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(0)
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % visual_lines_under_dialog[i]
else
Reline::IOGate.move_cursor_column(@dialog_column)
Reline::IOGate.move_cursor_column(dialog.column)
@output.write "\e[39m\e[49m#{' ' * DIALOG_WIDTH}\e[39m\e[49m"
end
Reline::IOGate.erase_after_cursor
move_cursor_down(1) if i < (dialog_vertical_size - 1)
end
move_cursor_up(dialog_vertical_size - 1 + @dialog_vertical_offset)
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