Support CSI sequences in prompt

This commit is contained in:
aycabta 2019-05-22 06:48:27 +09:00
Родитель ca435ed04a
Коммит c00d805672
1 изменённых файлов: 61 добавлений и 16 удалений

Просмотреть файл

@ -79,6 +79,8 @@ class Reline::LineEditor
CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
MenuInfo = Struct.new('MenuInfo', :target, :list)
CSI_REGEXP = /\e\[(?:\d+;?)*[ABCDEFGHJKSTfminsuhl]/
def initialize(config)
@config = config
reset_variables
@ -93,7 +95,7 @@ class Reline::LineEditor
def reset_variables(prompt = '', encoding = Encoding.default_external)
@prompt = prompt
@encoding = encoding
@prompt_width = calculate_width(@prompt)
@prompt_width = calculate_width(@prompt, true)
@is_multiline = false
@finished = false
@cleared = false
@ -159,21 +161,47 @@ class Reline::LineEditor
height
end
private def split_by_width(str, max_width)
private def split_by_width(prompt, str, max_width)
lines = [String.new(encoding: @encoding)]
height = 1
width = 0
rest_prompt = prompt.encode(Encoding::UTF_8)
loop do
break if rest_prompt.empty?
if rest_prompt =~ /^#{CSI_REGEXP}/
lines.last << $&
rest_prompt = $'
else
gcs = rest_prompt.grapheme_clusters
gc = gcs.first
rest_prompt = gcs[1..-1].join
mbchar_width = Reline::Unicode.get_mbchar_width(gc)
width += mbchar_width
if width > max_width
width = mbchar_width
lines << nil
lines << String.new(encoding: @encoding)
height += 1
end
lines.last << gc
end
end
lines << :split
lines << String.new(encoding: @encoding)
str.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
mbchar_width = Reline::Unicode.get_mbchar_width(gc)
width += mbchar_width
if width > max_width
width = mbchar_width
lines << nil
lines << String.new(encoding: @encoding)
height += 1
end
lines.last << gc
end
# The cursor moves to next line in first
lines << String.new(encoding: @encoding) if width == max_width
lines
[lines, height]
end
private def scroll_down(val)
@ -367,38 +395,52 @@ class Reline::LineEditor
end
private def render_partial(prompt, prompt_width, line_to_render, with_control = true)
whole_line = prompt + (line_to_render.nil? ? '' : line_to_render)
visual_lines = split_by_width(whole_line, @screen_size.last)
visual_lines, height = split_by_width(prompt, line_to_render.nil? ? '' : line_to_render, @screen_size.last)
if with_control
if visual_lines.size > @highest_in_this
diff = visual_lines.size - @highest_in_this
if height > @highest_in_this
diff = height - @highest_in_this
scroll_down(diff)
@highest_in_all += diff
@highest_in_this = visual_lines.size
@highest_in_this = height
move_cursor_up(1)
end
move_cursor_up(@started_from)
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
end
visual_lines.each_with_index do |line, index|
Reline::IOGate.move_cursor_column(0)
escaped_print line
index = 0
is_prompt = true
Reline::IOGate.move_cursor_column(0)
visual_lines.each do |line|
if line == :split
is_prompt = false
next
end
if line.nil?
Reline::IOGate.erase_after_cursor
move_cursor_down(1)
Reline::IOGate.move_cursor_column(0)
next
end
if is_prompt
@output.print line
else
escaped_print line
end
if @first_prompt
@first_prompt = false
@pre_input_hook&.call
end
Reline::IOGate.erase_after_cursor
move_cursor_down(1) if index < (visual_lines.size - 1)
end
Reline::IOGate.erase_after_cursor
if with_control
if finished?
@output.puts
else
move_cursor_up((visual_lines.size - 1) - @started_from)
move_cursor_up((height - 1) - @started_from)
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
end
end
visual_lines.size
height
end
def editing_mode
@ -759,7 +801,10 @@ class Reline::LineEditor
new_str
end
private def calculate_width(str)
private def calculate_width(str, allow_csi = false)
if allow_csi
str = str.gsub(CSI_REGEXP, '')
end
str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |width, gc|
width + Reline::Unicode.get_mbchar_width(gc)
}