[ruby/reline] Separate prompt and input line in rendering

(https://github.com/ruby/reline/pull/652)

* Separate prompt and input line in rendering

Often, only one of prompt and input changes.
Split prompt+input_line to a separate rendering item will improve differential rendering performance.

* Rename method wrapped_prompt_lines to more descriptive name

16d82f1f23
This commit is contained in:
tomoya ishida 2024-04-23 23:45:18 +09:00 committed by git
parent f7d1699f67
commit 53a67efc9a
2 changed files with 19 additions and 12 deletions

View file

@ -295,8 +295,8 @@ class Reline::LineEditor
end
end
private def split_by_width(str, max_width)
Reline::Unicode.split_by_width(str, max_width, @encoding)
private def split_by_width(str, max_width, offset: 0)
Reline::Unicode.split_by_width(str, max_width, @encoding, offset: offset)
end
def current_byte_pointer_cursor
@ -370,7 +370,7 @@ class Reline::LineEditor
@scroll_partial_screen
end
def wrapped_lines
def wrapped_prompt_and_input_lines
with_cache(__method__, @buffer_of_lines.size, modified_lines, prompt_list, screen_width) do |n, lines, prompts, width, prev_cache_key, cached_value|
prev_n, prev_lines, prev_prompts, prev_width = prev_cache_key
cached_wraps = {}
@ -381,9 +381,14 @@ class Reline::LineEditor
end
n.times.map do |i|
prompt = prompts[i]
line = lines[i]
cached_wraps[[prompt, line]] || split_by_width("#{prompt}#{line}", width).first.compact
prompt = prompts[i] || ''
line = lines[i] || ''
if (cached = cached_wraps[[prompt, line]])
next cached
end
*wrapped_prompts, code_line_prompt = split_by_width(prompt, width).first.compact
wrapped_lines = split_by_width(line, width, offset: calculate_width(code_line_prompt)).first.compact
wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] }
end
end
end
@ -426,7 +431,7 @@ class Reline::LineEditor
prompt_width = calculate_width(prompt_list[@line_index], true)
line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
wrapped_line_before_cursor = split_by_width(' ' * prompt_width + line_before_cursor, screen_width).first.compact
wrapped_cursor_y = wrapped_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
[wrapped_cursor_x, wrapped_cursor_y]
end
@ -490,8 +495,9 @@ class Reline::LineEditor
wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
rendered_lines = @rendered_screen.lines
new_lines = wrapped_lines.flatten[screen_scroll_top, screen_height].map do |l|
[[0, Reline::Unicode.calculate_width(l, true), l]]
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]]
end
if @menu_info
@menu_info.lines(screen_width).each do |item|
@ -507,7 +513,8 @@ class Reline::LineEditor
y_range.each do |row|
next if row < 0 || row >= screen_height
dialog_rows = new_lines[row] ||= []
dialog_rows[index + 1] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
# index 0 is for prompt, index 1 is for line, index 2.. is for dialog
dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
end
end

View file

@ -128,10 +128,10 @@ class Reline::Unicode
end
end
def self.split_by_width(str, max_width, encoding = str.encoding)
def self.split_by_width(str, max_width, encoding = str.encoding, offset: 0)
lines = [String.new(encoding: encoding)]
height = 1
width = 0
width = offset
rest = str.encode(Encoding::UTF_8)
in_zero_width = false
seq = String.new(encoding: encoding)