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

Support CSI sequences in prompt

This commit is contained in:
aycabta 2019-05-22 06:48:27 +09:00
parent ca435ed04a
commit c00d805672

View file

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