Skip to content

Commit f589fab

Browse files
committed
Support for multiple dialog rendering
1 parent 46aa269 commit f589fab

File tree

2 files changed

+115
-75
lines changed

2 files changed

+115
-75
lines changed

lib/reline.rb

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class Core
3434
auto_indent_proc
3535
pre_input_hook
3636
dig_perfect_match_proc
37-
dialog_proc
37+
dialog_proc_list
3838
).each(&method(:attr_reader))
3939

4040
attr_accessor :config
@@ -48,6 +48,7 @@ def initialize
4848
yield self
4949
@completion_quote_character = nil
5050
@bracketed_paste_finished = false
51+
@dialog_proc_list = []
5152
end
5253

5354
def encoding
@@ -131,9 +132,10 @@ def dig_perfect_match_proc=(p)
131132
@dig_perfect_match_proc = p
132133
end
133134

134-
def dialog_proc=(p)
135+
def add_dialog_proc(name_sym, p)
135136
raise ArgumentError unless p.respond_to?(:call) or p.nil?
136-
@dialog_proc = p
137+
raise ArgumentError unless name_sym.instance_of?(Symbol)
138+
@dialog_proc_list << [name_sym, p]
137139
end
138140

139141
def input=(val)
@@ -206,14 +208,15 @@ def get_screen_size
206208
else
207209
y = 0
208210
end
209-
[Reline::CursorPos.new(x, y), result, pointer]
211+
cursor_pos_to_render = Reline::CursorPos.new(x, y)
212+
[cursor_pos_to_render, result, pointer]
210213
}
211214

212215
def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
213216
unless confirm_multiline_termination
214217
raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
215218
end
216-
@dialog_proc = DEFAULT_DIALOG_PROC_AUTOCOMPLETE
219+
add_dialog_proc(:autocomplete, DEFAULT_DIALOG_PROC_AUTOCOMPLETE)
217220
inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
218221

219222
whole_buffer = line_editor.whole_buffer.dup
@@ -269,7 +272,10 @@ def readline(prompt = '', add_hist = false)
269272
line_editor.auto_indent_proc = auto_indent_proc
270273
line_editor.dig_perfect_match_proc = dig_perfect_match_proc
271274
line_editor.pre_input_hook = pre_input_hook
272-
line_editor.dialog_proc = dialog_proc
275+
@dialog_proc_list.each do |d|
276+
name_sym, dialog_proc = d
277+
line_editor.add_dialog_proc(name_sym, dialog_proc)
278+
end
273279

274280
unless config.test_mode
275281
config.read

lib/reline/line_editor.rb

Lines changed: 103 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,7 @@ def reset_variables(prompt = '', encoding:)
249249
@drop_terminate_spaces = false
250250
@in_pasting = false
251251
@auto_indent_proc = nil
252-
@dialog_column = nil
253-
@dialog_vertical_offset = nil
254-
@dialog_contents = nil
255-
@dialog_lines_backup = nil
252+
@dialogs = []
256253
reset_line
257254
end
258255

@@ -521,75 +518,98 @@ def call
521518
end
522519
end
523520

524-
def dialog_proc=(p)
525-
@dialog_proc_scope = DialogProcScope.new(self, p)
526-
@dialog_proc = p
521+
class Dialog
522+
attr_reader :name
523+
attr_accessor :column, :vertical_offset, :contents, :lines_backup
524+
525+
def initialize(name, proc_scope)
526+
@name = name
527+
@proc_scope = proc_scope
528+
end
529+
530+
def set_cursor_pos(col, row)
531+
@proc_scope.set_cursor_pos(col, row)
532+
end
533+
534+
def call
535+
@proc_scope.call
536+
end
527537
end
528538

529-
DIALOG_HEIGHT = 5
539+
def add_dialog_proc(name, p)
540+
return if @dialogs.any? { |d| d.name == name }
541+
@dialogs << Dialog.new(name, DialogProcScope.new(self, p))
542+
end
543+
544+
DIALOG_HEIGHT = 20
530545
DIALOG_WIDTH = 40
531546
private def render_dialog(cursor_column)
532-
return if @dialog_proc_scope.nil?
547+
@dialogs.each do |dialog|
548+
render_each_dialog(dialog, cursor_column)
549+
end
550+
end
551+
552+
private def render_each_dialog(dialog, cursor_column)
533553
if @in_pasting
534-
@dialog_contents = nil
554+
dialog.contents = nil
535555
return
536556
end
537-
@dialog_proc_scope.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
538-
pos, result, pointer = @dialog_proc_scope.call
539-
old_dialog_contents = @dialog_contents
540-
old_dialog_column = @dialog_column
541-
old_dialog_vertical_offset = @dialog_vertical_offset
557+
dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
558+
pos, result, pointer = dialog.call
559+
old_dialog_contents = dialog.contents
560+
old_dialog_column = dialog.column
561+
old_dialog_vertical_offset = dialog.vertical_offset
542562
if result and not result.empty?
543-
@dialog_contents = result
544-
@dialog_contents = @dialog_contents[0...DIALOG_HEIGHT] if @dialog_contents.size > DIALOG_HEIGHT
563+
dialog.contents = result
564+
dialog.contents = dialog.contents[0...DIALOG_HEIGHT] if dialog.contents.size > DIALOG_HEIGHT
545565
else
546-
@dialog_lines_backup = {
566+
dialog.lines_backup = {
547567
lines: modify_lines(whole_lines),
548568
line_index: @line_index,
549569
first_line_started_from: @first_line_started_from,
550570
started_from: @started_from,
551571
byte_pointer: @byte_pointer
552572
}
553-
clear_dialog
554-
@dialog_contents = nil
573+
clear_each_dialog(dialog)
574+
dialog.contents = nil
555575
return
556576
end
557577
upper_space = @first_line_started_from - @started_from
558578
lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
559-
@dialog_column = pos.x
560-
diff = (@dialog_column + DIALOG_WIDTH) - (@screen_size.last - 1)
579+
dialog.column = pos.x
580+
diff = (dialog.column + DIALOG_WIDTH) - (@screen_size.last - 1)
561581
if diff > 0
562-
@dialog_column -= diff
582+
dialog.column -= diff
563583
end
564584
if (lower_space + @rest_height) >= DIALOG_HEIGHT
565-
@dialog_vertical_offset = pos.y + 1
585+
dialog.vertical_offset = pos.y + 1
566586
elsif upper_space >= DIALOG_HEIGHT
567-
@dialog_vertical_offset = pos.y + -(DIALOG_HEIGHT + 1)
587+
dialog.vertical_offset = pos.y + -(DIALOG_HEIGHT + 1)
568588
else
569589
if (lower_space + @rest_height) < DIALOG_HEIGHT
570590
scroll_down(DIALOG_HEIGHT)
571591
move_cursor_up(DIALOG_HEIGHT)
572592
end
573-
@dialog_vertical_offset = pos.y + 1
593+
dialog.vertical_offset = pos.y + 1
574594
end
575595
Reline::IOGate.hide_cursor
576-
reset_dialog(old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
577-
move_cursor_down(@dialog_vertical_offset)
578-
Reline::IOGate.move_cursor_column(@dialog_column)
579-
@dialog_contents.each_with_index do |item, i|
596+
reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
597+
move_cursor_down(dialog.vertical_offset)
598+
Reline::IOGate.move_cursor_column(dialog.column)
599+
dialog.contents.each_with_index do |item, i|
580600
if i == pointer
581601
bg_color = '45'
582602
else
583603
bg_color = '46'
584604
end
585605
@output.write "\e[#{bg_color}m%-#{DIALOG_WIDTH}s\e[49m" % item.slice(0, DIALOG_WIDTH)
586-
Reline::IOGate.move_cursor_column(@dialog_column)
587-
move_cursor_down(1) if i < (@dialog_contents.size - 1)
606+
Reline::IOGate.move_cursor_column(dialog.column)
607+
move_cursor_down(1) if i < (dialog.contents.size - 1)
588608
end
589609
Reline::IOGate.move_cursor_column(cursor_column)
590-
move_cursor_up(@dialog_vertical_offset + @dialog_contents.size - 1)
610+
move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
591611
Reline::IOGate.show_cursor
592-
@dialog_lines_backup = {
612+
dialog.lines_backup = {
593613
lines: modify_lines(whole_lines),
594614
line_index: @line_index,
595615
first_line_started_from: @first_line_started_from,
@@ -598,53 +618,61 @@ def dialog_proc=(p)
598618
}
599619
end
600620

601-
private def reset_dialog(old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
602-
return if @dialog_lines_backup.nil? or old_dialog_contents.nil?
603-
prompt, prompt_width, prompt_list = check_multiline_prompt(@dialog_lines_backup[:lines], prompt)
621+
private def reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
622+
return if dialog.lines_backup.nil? or old_dialog_contents.nil?
623+
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
604624
visual_lines = []
605625
visual_start = nil
606-
@dialog_lines_backup[:lines].each_with_index { |l, i|
626+
dialog.lines_backup[:lines].each_with_index { |l, i|
607627
pr = prompt_list ? prompt_list[i] : prompt
608628
vl, _ = split_by_width(pr + l, @screen_size.last)
609629
vl.compact!
610-
if i == @dialog_lines_backup[:line_index]
611-
visual_start = visual_lines.size + @dialog_lines_backup[:started_from]
630+
if i == dialog.lines_backup[:line_index]
631+
visual_start = visual_lines.size + dialog.lines_backup[:started_from]
612632
end
613633
visual_lines.concat(vl)
614634
}
615-
old_y = @dialog_lines_backup[:first_line_started_from] + @dialog_lines_backup[:started_from]
635+
old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
616636
y = @first_line_started_from + @started_from
617637
y_diff = y - old_y
618-
if (old_y + old_dialog_vertical_offset) < (y + @dialog_vertical_offset)
638+
if (old_y + old_dialog_vertical_offset) < (y + dialog.vertical_offset)
619639
# rerender top
620640
move_cursor_down(old_dialog_vertical_offset - y_diff)
621641
start = visual_start + old_dialog_vertical_offset
622-
line_num = @dialog_vertical_offset - old_dialog_vertical_offset
642+
line_num = dialog.vertical_offset - old_dialog_vertical_offset
623643
line_num.times do |i|
624-
Reline::IOGate.move_cursor_column(0)
625-
@output.write "\e[39m\e[49m#{visual_lines[start + i]}\e[39m\e[49m"
626-
Reline::IOGate.erase_after_cursor
644+
Reline::IOGate.move_cursor_column(old_dialog_column)
645+
if visual_lines[start + i].nil?
646+
s = ' ' * DIALOG_WIDTH
647+
else
648+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
649+
end
650+
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
627651
move_cursor_down(1) if i < (line_num - 1)
628652
end
629653
move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
630654
end
631-
if (old_y + old_dialog_vertical_offset + old_dialog_contents.size) > (y + @dialog_vertical_offset + @dialog_contents.size)
655+
if (old_y + old_dialog_vertical_offset + old_dialog_contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
632656
# rerender bottom
633-
move_cursor_down(@dialog_vertical_offset + @dialog_contents.size - y_diff)
634-
start = visual_start + @dialog_vertical_offset + @dialog_contents.size
635-
line_num = (old_dialog_vertical_offset + old_dialog_contents.size) - (@dialog_vertical_offset + @dialog_contents.size)
657+
move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
658+
start = visual_start + dialog.vertical_offset + dialog.contents.size
659+
line_num = (old_dialog_vertical_offset + old_dialog_contents.size) - (dialog.vertical_offset + dialog.contents.size)
636660
line_num.times do |i|
637-
Reline::IOGate.move_cursor_column(0)
638-
@output.write "\e[39m\e[49m#{visual_lines[start + i]}\e[39m\e[49m"
639-
Reline::IOGate.erase_after_cursor
661+
Reline::IOGate.move_cursor_column(old_dialog_column)
662+
if visual_lines[start + i].nil?
663+
s = ' ' * DIALOG_WIDTH
664+
else
665+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
666+
end
667+
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
640668
move_cursor_down(1) if i < (line_num - 1)
641669
end
642-
move_cursor_up(@dialog_vertical_offset + @dialog_contents.size + line_num - 1 - y_diff)
670+
move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
643671
end
644-
if old_dialog_column < @dialog_column
672+
if old_dialog_column < dialog.column
645673
# rerender left
646674
move_cursor_down(old_dialog_vertical_offset - y_diff)
647-
width = @dialog_column - old_dialog_column
675+
width = dialog.column - old_dialog_column
648676
start = visual_start + old_dialog_vertical_offset
649677
line_num = old_dialog_contents.size
650678
line_num.times do |i|
@@ -659,10 +687,10 @@ def dialog_proc=(p)
659687
end
660688
move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
661689
end
662-
if (old_dialog_column + DIALOG_WIDTH) > (@dialog_column + DIALOG_WIDTH)
690+
if (old_dialog_column + DIALOG_WIDTH) > (dialog.column + DIALOG_WIDTH)
663691
# rerender right
664692
move_cursor_down(old_dialog_vertical_offset + y_diff)
665-
width = (old_dialog_column + DIALOG_WIDTH) - (@dialog_column + DIALOG_WIDTH)
693+
width = (old_dialog_column + DIALOG_WIDTH) - (dialog.column + DIALOG_WIDTH)
666694
start = visual_start + old_dialog_vertical_offset
667695
line_num = old_dialog_contents.size
668696
line_num.times do |i|
@@ -672,7 +700,7 @@ def dialog_proc=(p)
672700
else
673701
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column + DIALOG_WIDTH, width)
674702
end
675-
Reline::IOGate.move_cursor_column(@dialog_column + DIALOG_WIDTH)
703+
Reline::IOGate.move_cursor_column(dialog.column + DIALOG_WIDTH)
676704
@output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
677705
move_cursor_down(1) if i < (line_num - 1)
678706
end
@@ -682,36 +710,42 @@ def dialog_proc=(p)
682710
end
683711

684712
private def clear_dialog
685-
return unless @dialog_contents
686-
prompt, prompt_width, prompt_list = check_multiline_prompt(@dialog_lines_backup[:lines], prompt)
713+
@dialogs.each do |dialog|
714+
clear_each_dialog(dialog)
715+
end
716+
end
717+
718+
private def clear_each_dialog(dialog)
719+
return unless dialog.contents
720+
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
687721
visual_lines = []
688722
visual_lines_under_dialog = []
689723
visual_start = nil
690-
@dialog_lines_backup[:lines].each_with_index { |l, i|
724+
dialog.lines_backup[:lines].each_with_index { |l, i|
691725
pr = prompt_list ? prompt_list[i] : prompt
692726
vl, _ = split_by_width(pr + l, @screen_size.last)
693727
vl.compact!
694-
if i == @dialog_lines_backup[:line_index]
695-
visual_start = visual_lines.size + @dialog_lines_backup[:started_from] + @dialog_vertical_offset
728+
if i == dialog.lines_backup[:line_index]
729+
visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
696730
end
697731
visual_lines.concat(vl)
698732
}
699-
visual_lines_under_dialog = visual_lines[visual_start, @dialog_contents.size]
733+
visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
700734
Reline::IOGate.hide_cursor
701-
move_cursor_down(@dialog_vertical_offset)
702-
dialog_vertical_size = @dialog_contents.size
735+
move_cursor_down(dialog.vertical_offset)
736+
dialog_vertical_size = dialog.contents.size
703737
dialog_vertical_size.times do |i|
704738
if i < visual_lines_under_dialog.size
705739
Reline::IOGate.move_cursor_column(0)
706740
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % visual_lines_under_dialog[i]
707741
else
708-
Reline::IOGate.move_cursor_column(@dialog_column)
742+
Reline::IOGate.move_cursor_column(dialog.column)
709743
@output.write "\e[39m\e[49m#{' ' * DIALOG_WIDTH}\e[39m\e[49m"
710744
end
711745
Reline::IOGate.erase_after_cursor
712746
move_cursor_down(1) if i < (dialog_vertical_size - 1)
713747
end
714-
move_cursor_up(dialog_vertical_size - 1 + @dialog_vertical_offset)
748+
move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
715749
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
716750
Reline::IOGate.show_cursor
717751
end

0 commit comments

Comments
 (0)