summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authoraycabta <aycabta@gmail.com>2020-12-13 11:30:05 +0900
committeraycabta <aycabta@gmail.com>2020-12-17 20:14:59 +0900
commit2b8fa7817643a9ac7632b66ffed7cb5fd3753966 (patch)
treed2b2ebb3c75fda15964ce1cd11b0d10f49962c74 /lib
parent9908177857a28633d6279c43a1ad4dfedcb98596 (diff)
[ruby/reline] Support longer than screen height
https://github.com/ruby/reline/commit/e83a3de9ed
Diffstat (limited to 'lib')
-rw-r--r--lib/reline/line_editor.rb221
1 files changed, 162 insertions, 59 deletions
diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
index 65878fe9f3..b80a184fc6 100644
--- a/lib/reline/line_editor.rb
+++ b/lib/reline/line_editor.rb
@@ -123,6 +123,7 @@ class Reline::LineEditor
def reset(prompt = '', encoding:)
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
@screen_size = Reline::IOGate.get_screen_size
+ @screen_height = @screen_size.first
reset_variables(prompt, encoding: encoding)
@old_trap = Signal.trap('SIGINT') {
@old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
@@ -132,6 +133,7 @@ class Reline::LineEditor
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
old_screen_size = @screen_size
@screen_size = Reline::IOGate.get_screen_size
+ @screen_height = @screen_size.first
if old_screen_size.last < @screen_size.last # columns increase
@rerender_all = true
rerender
@@ -202,6 +204,7 @@ class Reline::LineEditor
@prompt_cache_time = nil
@eof = false
@continuous_insertion_buffer = String.new(encoding: @encoding)
+ @scroll_partial_screen = nil
reset_line
end
@@ -287,28 +290,28 @@ class Reline::LineEditor
end
end
- private def calculate_nearest_cursor
- @cursor_max = calculate_width(line)
+ private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
+ new_cursor_max = calculate_width(line_to_calc)
new_cursor = 0
new_byte_pointer = 0
height = 1
max_width = @screen_size.last
if @config.editing_mode_is?(:vi_command)
- last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @line.bytesize)
+ last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
if last_byte_size > 0
- last_mbchar = @line.byteslice(@line.bytesize - last_byte_size, last_byte_size)
+ last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size)
last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
- cursor_max = @cursor_max - last_width
+ end_of_line_cursor = new_cursor_max - last_width
else
- cursor_max = @cursor_max
+ end_of_line_cursor = new_cursor_max
end
else
- cursor_max = @cursor_max
+ end_of_line_cursor = new_cursor_max
end
- @line.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
+ line_to_calc.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
mbchar_width = Reline::Unicode.get_mbchar_width(gc)
now = new_cursor + mbchar_width
- if now > cursor_max or now > @cursor
+ if now > end_of_line_cursor or now > cursor
break
end
new_cursor += mbchar_width
@@ -317,9 +320,15 @@ class Reline::LineEditor
end
new_byte_pointer += gc.bytesize
end
- @started_from = height - 1
- @cursor = new_cursor
- @byte_pointer = new_byte_pointer
+ new_started_from = height - 1
+ if update
+ @cursor = new_cursor
+ @cursor_max = new_cursor_max
+ @started_from = new_started_from
+ @byte_pointer = new_byte_pointer
+ else
+ [new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
+ end
end
def rerender_all
@@ -349,33 +358,37 @@ class Reline::LineEditor
if @add_newline_to_end_of_buffer
rerender_added_newline
@add_newline_to_end_of_buffer = false
- elsif @just_cursor_moving and not @rerender_all
- just_move_cursor
- @just_cursor_moving = false
- return
- elsif @previous_line_index or new_highest_in_this != @highest_in_this
- rerender_changed_current_line
- @previous_line_index = nil
- rendered = true
- elsif @rerender_all
- rerender_all_lines
- @rerender_all = false
- rendered = true
+ else
+ if @just_cursor_moving and not @rerender_all
+ rendered = just_move_cursor
+ @just_cursor_moving = false
+ return
+ elsif @previous_line_index or new_highest_in_this != @highest_in_this
+ rerender_changed_current_line
+ @previous_line_index = nil
+ rendered = true
+ elsif @rerender_all
+ rerender_all_lines
+ @rerender_all = false
+ rendered = true
+ else
+ end
end
line = modify_lines(whole_lines)[@line_index]
if @is_multiline
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
if finished?
# Always rerender on finish because output_modifier_proc may return a different output.
- render_partial(prompt, prompt_width, line)
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
scroll_down(1)
Reline::IOGate.move_cursor_column(0)
Reline::IOGate.erase_after_cursor
elsif not rendered
- render_partial(prompt, prompt_width, line)
+ render_partial(prompt, prompt_width, line, @first_line_started_from)
end
+ @buffer_of_lines[@line_index] = @line
else
- render_partial(prompt, prompt_width, line)
+ render_partial(prompt, prompt_width, line, 0)
if finished?
scroll_down(1)
Reline::IOGate.move_cursor_column(0)
@@ -384,13 +397,46 @@ class Reline::LineEditor
end
end
+ private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
+ if @screen_height < highest_in_all
+ old_scroll_partial_screen = @scroll_partial_screen
+ if cursor_y == 0
+ @scroll_partial_screen = 0
+ elsif cursor_y == (highest_in_all - 1)
+ @scroll_partial_screen = highest_in_all - @screen_height
+ else
+ if @scroll_partial_screen
+ if cursor_y <= @scroll_partial_screen
+ @scroll_partial_screen = cursor_y
+ elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
+ @scroll_partial_screen = cursor_y - (@screen_height - 1)
+ end
+ else
+ if cursor_y > (@screen_height - 1)
+ @scroll_partial_screen = cursor_y - (@screen_height - 1)
+ else
+ @scroll_partial_screen = 0
+ end
+ end
+ end
+ if @scroll_partial_screen != old_scroll_partial_screen
+ @rerender_all = true
+ end
+ else
+ if @scroll_partial_screen
+ @rerender_all = true
+ end
+ @scroll_partial_screen = nil
+ end
+ end
+
private def rerender_added_newline
scroll_down(1)
new_lines = whole_lines(index: @previous_line_index, line: @line)
prompt, prompt_width, = check_multiline_prompt(new_lines, prompt)
@buffer_of_lines[@previous_line_index] = @line
@line = @buffer_of_lines[@line_index]
- render_partial(prompt, prompt_width, @line, false)
+ render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
@cursor = @cursor_max = calculate_width(@line)
@byte_pointer = @line.bytesize
@highest_in_all += @highest_in_this
@@ -409,14 +455,25 @@ class Reline::LineEditor
else
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
end
- @line = @buffer_of_lines[@line_index]
- move_cursor_down(new_first_line_started_from - @first_line_started_from)
- @first_line_started_from = new_first_line_started_from
- calculate_nearest_cursor
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
- move_cursor_down(@started_from)
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
+ first_line_diff = new_first_line_started_from - @first_line_started_from
+ new_cursor, _, new_started_from, _ = calculate_nearest_cursor(@line, @cursor, @started_from, @byte_pointer, false)
+ new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
+ calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
@previous_line_index = nil
+ if @rerender_all
+ @line = @buffer_of_lines[@line_index]
+ rerender_all_lines
+ @rerender_all = false
+ true
+ else
+ @line = @buffer_of_lines[@line_index]
+ @first_line_started_from = new_first_line_started_from
+ @started_from = new_started_from
+ @cursor = new_cursor
+ move_cursor_down(first_line_diff + @started_from)
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
+ false
+ end
end
private def rerender_changed_current_line
@@ -479,35 +536,42 @@ class Reline::LineEditor
height = calculate_height_by_width(width)
back += height
end
- if back > @highest_in_all
+ old_highest_in_all = @highest_in_all
+ if @line_index.zero?
+ new_first_line_started_from = 0
+ else
+ new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
+ end
+ new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
+ if back > old_highest_in_all
scroll_down(back - 1)
move_cursor_up(back - 1)
- elsif back < @highest_in_all
+ elsif back < old_highest_in_all
scroll_down(back)
Reline::IOGate.erase_after_cursor
- (@highest_in_all - back - 1).times do
+ (old_highest_in_all - back - 1).times do
scroll_down(1)
Reline::IOGate.erase_after_cursor
end
- move_cursor_up(@highest_in_all - 1)
+ move_cursor_up(old_highest_in_all - 1)
end
+ calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
- move_cursor_up(back - 1)
if @prompt_proc
prompt = prompt_list[@line_index]
prompt_width = calculate_width(prompt, true)
end
- @highest_in_all = back
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
- @first_line_started_from =
- if @line_index.zero?
- 0
- else
- calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
- end
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
- move_cursor_down(@first_line_started_from + @started_from)
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
+ @highest_in_all = back
+ @first_line_started_from = new_first_line_started_from
+ @started_from = new_started_from
+ if @scroll_partial_screen
+ Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
+ else
+ move_cursor_down(@first_line_started_from + @started_from - back + 1)
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
+ end
end
private def render_whole_lines(lines, prompt, prompt_width)
@@ -519,9 +583,15 @@ class Reline::LineEditor
else
line_prompt = prompt
end
- height = render_partial(line_prompt, prompt_width, line, false)
+ height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
if index < (lines.size - 1)
- scroll_down(1)
+ if @scroll_partial_screen
+ if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
+ move_cursor_down(1)
+ end
+ else
+ scroll_down(1)
+ end
rendered_height += height
else
rendered_height += height - 1
@@ -530,8 +600,34 @@ class Reline::LineEditor
rendered_height
end
- private def render_partial(prompt, prompt_width, line_to_render, with_control = true)
+ private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
+ cursor_up_from_last_line = 0
+ # TODO: This logic would be sometimes buggy if this logical line isn't the current @line_index.
+ if @scroll_partial_screen
+ last_visual_line = this_started_from + (height - 1)
+ last_screen_line = @scroll_partial_screen + (@screen_height - 1)
+ if (@scroll_partial_screen - this_started_from) >= height
+ # Render nothing because this line is before the screen.
+ visual_lines = []
+ elsif this_started_from > last_screen_line
+ # Render nothing because this line is after the screen.
+ visual_lines = []
+ else
+ deleted_lines_before_screen = []
+ if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
+ # A part of visual lines are before the screen.
+ deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
+ deleted_lines_before_screen.compact!
+ end
+ if this_started_from <= last_screen_line and last_screen_line < last_visual_line
+ # A part of visual lines are after the screen.
+ visual_lines.pop((last_visual_line - last_screen_line) * 2)
+ end
+ move_cursor_up(deleted_lines_before_screen.size - @started_from)
+ cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
+ end
+ end
if with_control
if height > @highest_in_this
diff = height - @highest_in_this
@@ -545,10 +641,14 @@ class Reline::LineEditor
@highest_in_this = height
end
move_cursor_up(@started_from)
+ cursor_up_from_last_line = height - 1 - @started_from
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
end
- Reline::IOGate.move_cursor_column(0)
+ if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
+ @output.write "\e[0m" # clear character decorations
+ end
visual_lines.each_with_index do |line, index|
+ Reline::IOGate.move_cursor_column(0)
if line.nil?
if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
# reaches the end of line
@@ -580,15 +680,18 @@ class Reline::LineEditor
@pre_input_hook&.call
end
end
- Reline::IOGate.erase_after_cursor
- Reline::IOGate.move_cursor_column(0)
+ unless visual_lines.empty?
+ Reline::IOGate.erase_after_cursor
+ Reline::IOGate.move_cursor_column(0)
+ end
if with_control
# Just after rendring, so the cursor is on the last line.
if finished?
Reline::IOGate.move_cursor_column(0)
else
# Moves up from bottom of lines to the cursor position.
- move_cursor_up(height - 1 - @started_from)
+ move_cursor_up(cursor_up_from_last_line)
+ # This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
end
end
@@ -624,9 +727,9 @@ class Reline::LineEditor
modify_lines(whole_lines).each_with_index do |line, index|
if @prompt_proc
pr = prompt_list[index]
- height = render_partial(pr, calculate_width(pr), line, false)
+ height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
else
- height = render_partial(prompt, prompt_width, line, false)
+ height = render_partial(prompt, prompt_width, line, back, with_control: false)
end
if index < (@buffer_of_lines.size - 1)
move_cursor_down(height)