From 5a920f667ecdb5fe3a2341b0ccc2d24927fe2d32 Mon Sep 17 00:00:00 2001 From: aycabta Date: Fri, 28 Aug 2020 02:09:34 +0900 Subject: [PATCH] Move width calculator methods to Reline::Unicode --- lib/reline/line_editor.rb | 64 ++---------------------------------- lib/reline/unicode.rb | 68 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 62 deletions(-) diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index c9d88edc85..8731666cf1 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -50,12 +50,6 @@ module CompletionState CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer) MenuInfo = Struct.new('MenuInfo', :target, :list) - CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/ - OSC_REGEXP = /\e\]\d+(?:;[^;]+)*\a/ - NON_PRINTING_START = "\1" - NON_PRINTING_END = "\2" - WIDTH_SCANNER = /\G(?:#{NON_PRINTING_START}|#{NON_PRINTING_END}|#{CSI_REGEXP}|#{OSC_REGEXP}|\X)/ - def initialize(config, encoding) @config = config @completion_append_character = '' @@ -235,39 +229,7 @@ def multiline_off end private def split_by_width(prompt, str, max_width) - lines = [String.new(encoding: @encoding)] - height = 1 - width = 0 - rest = "#{prompt}#{str}".encode(Encoding::UTF_8) - in_zero_width = false - rest.scan(WIDTH_SCANNER) do |gc| - case gc - when NON_PRINTING_START - in_zero_width = true - when NON_PRINTING_END - in_zero_width = false - when CSI_REGEXP, OSC_REGEXP - lines.last << gc - else - unless in_zero_width - mbchar_width = Reline::Unicode.get_mbchar_width(gc) - if (width += mbchar_width) > max_width - width = mbchar_width - lines << nil - lines << String.new(encoding: @encoding) - height += 1 - end - end - lines.last << gc - end - end - # The cursor moves to next line in first - if width == max_width - lines << nil - lines << String.new(encoding: @encoding) - height += 1 - end - [lines, height] + Reline::Unicode.split_by_width(prompt, str, max_width, @encoding) end private def scroll_down(val) @@ -1081,29 +1043,7 @@ def finish end private def calculate_width(str, allow_escape_code = false) - if allow_escape_code - width = 0 - rest = str.encode(Encoding::UTF_8) - in_zero_width = false - rest.scan(WIDTH_SCANNER) do |gc| - case gc - when NON_PRINTING_START - in_zero_width = true - when NON_PRINTING_END - in_zero_width = false - when CSI_REGEXP, OSC_REGEXP - else - unless in_zero_width - width += Reline::Unicode.get_mbchar_width(gc) - end - end - end - width - else - str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc| - w + Reline::Unicode.get_mbchar_width(gc) - } - end + Reline::Unicode.calculate_width(str, allow_escape_code) end private def key_delete(key) diff --git a/lib/reline/unicode.rb b/lib/reline/unicode.rb index 4b30f044f3..66f2581d18 100644 --- a/lib/reline/unicode.rb +++ b/lib/reline/unicode.rb @@ -35,6 +35,12 @@ class Reline::Unicode } EscapedChars = EscapedPairs.keys.map(&:chr) + CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/ + OSC_REGEXP = /\e\]\d+(?:;[^;]+)*\a/ + NON_PRINTING_START = "\1" + NON_PRINTING_END = "\2" + WIDTH_SCANNER = /\G(?:#{NON_PRINTING_START}|#{NON_PRINTING_END}|#{CSI_REGEXP}|#{OSC_REGEXP}|\X)/ + def self.get_mbchar_byte_size_by_first_char(c) # Checks UTF-8 character byte size case c.ord @@ -85,6 +91,68 @@ def self.get_mbchar_width(mbchar) end end + def self.calculate_width(str, allow_escape_code = false) + if allow_escape_code + width = 0 + rest = str.encode(Encoding::UTF_8) + in_zero_width = false + rest.scan(WIDTH_SCANNER) do |gc| + case gc + when NON_PRINTING_START + in_zero_width = true + when NON_PRINTING_END + in_zero_width = false + when CSI_REGEXP, OSC_REGEXP + else + unless in_zero_width + width += get_mbchar_width(gc) + end + end + end + width + else + str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc| + w + get_mbchar_width(gc) + } + end + end + + def self.split_by_width(prompt, str, max_width, encoding) + lines = [String.new(encoding: encoding)] + height = 1 + width = 0 + rest = "#{prompt}#{str}".encode(Encoding::UTF_8) + in_zero_width = false + rest.scan(WIDTH_SCANNER) do |gc| + case gc + when NON_PRINTING_START + in_zero_width = true + when NON_PRINTING_END + in_zero_width = false + when CSI_REGEXP, OSC_REGEXP + lines.last << gc + else + unless in_zero_width + mbchar_width = get_mbchar_width(gc) + if (width += mbchar_width) > max_width + width = mbchar_width + lines << nil + lines << String.new(encoding: encoding) + height += 1 + end + end + lines.last << gc + end + end + # The cursor moves to next line in first + if width == max_width + lines << nil + lines << String.new(encoding: encoding) + height += 1 + end + [lines, height] + end + def self.get_next_mbchar_size(line, byte_pointer) grapheme = line.byteslice(byte_pointer..-1).grapheme_clusters.first grapheme ? grapheme.bytesize : 0