summaryrefslogtreecommitdiff
path: root/lib/reline/unicode.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/reline/unicode.rb')
-rw-r--r--lib/reline/unicode.rb149
1 files changed, 89 insertions, 60 deletions
diff --git a/lib/reline/unicode.rb b/lib/reline/unicode.rb
index 6000c9f82a..82c9ec427c 100644
--- a/lib/reline/unicode.rb
+++ b/lib/reline/unicode.rb
@@ -38,33 +38,8 @@ class Reline::Unicode
NON_PRINTING_START = "\1"
NON_PRINTING_END = "\2"
CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/
- OSC_REGEXP = /\e\]\d+(?:;[^;]+)*\a/
+ OSC_REGEXP = /\e\]\d+(?:;[^;\a\e]+)*(?:\a|\e\\)/
WIDTH_SCANNER = /\G(?:(#{NON_PRINTING_START})|(#{NON_PRINTING_END})|(#{CSI_REGEXP})|(#{OSC_REGEXP})|(\X))/o
- NON_PRINTING_START_INDEX = 0
- NON_PRINTING_END_INDEX = 1
- CSI_REGEXP_INDEX = 2
- OSC_REGEXP_INDEX = 3
- GRAPHEME_CLUSTER_INDEX = 4
-
- def self.get_mbchar_byte_size_by_first_char(c)
- # Checks UTF-8 character byte size
- case c.ord
- # 0b0xxxxxxx
- when ->(code) { (code ^ 0b10000000).allbits?(0b10000000) } then 1
- # 0b110xxxxx
- when ->(code) { (code ^ 0b00100000).allbits?(0b11100000) } then 2
- # 0b1110xxxx
- when ->(code) { (code ^ 0b00010000).allbits?(0b11110000) } then 3
- # 0b11110xxx
- when ->(code) { (code ^ 0b00001000).allbits?(0b11111000) } then 4
- # 0b111110xx
- when ->(code) { (code ^ 0b00000100).allbits?(0b11111100) } then 5
- # 0b1111110x
- when ->(code) { (code ^ 0b00000010).allbits?(0b11111110) } then 6
- # successor of mbchar
- else 0
- end
- end
def self.escape_for_print(str)
str.chars.map! { |gr|
@@ -132,15 +107,14 @@ class Reline::Unicode
width = 0
rest = str.encode(Encoding::UTF_8)
in_zero_width = false
- rest.scan(WIDTH_SCANNER) do |gc|
+ rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc|
case
- when gc[NON_PRINTING_START_INDEX]
+ when non_printing_start
in_zero_width = true
- when gc[NON_PRINTING_END_INDEX]
+ when non_printing_end
in_zero_width = false
- when gc[CSI_REGEXP_INDEX], gc[OSC_REGEXP_INDEX]
- when gc[GRAPHEME_CLUSTER_INDEX]
- gc = gc[GRAPHEME_CLUSTER_INDEX]
+ when csi, osc
+ when gc
unless in_zero_width
width += get_mbchar_width(gc)
end
@@ -154,30 +128,40 @@ 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
- rest.scan(WIDTH_SCANNER) do |gc|
+ seq = String.new(encoding: encoding)
+ rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc|
case
- when gc[NON_PRINTING_START_INDEX]
+ when non_printing_start
in_zero_width = true
- when gc[NON_PRINTING_END_INDEX]
+ lines.last << NON_PRINTING_START
+ when non_printing_end
in_zero_width = false
- when gc[CSI_REGEXP_INDEX]
- lines.last << gc[CSI_REGEXP_INDEX]
- when gc[OSC_REGEXP_INDEX]
- lines.last << gc[OSC_REGEXP_INDEX]
- when gc[GRAPHEME_CLUSTER_INDEX]
- gc = gc[GRAPHEME_CLUSTER_INDEX]
+ lines.last << NON_PRINTING_END
+ when csi
+ lines.last << csi
+ unless in_zero_width
+ if csi == -"\e[m" || csi == -"\e[0m"
+ seq.clear
+ else
+ seq << csi
+ end
+ end
+ when osc
+ lines.last << osc
+ seq << osc
+ when gc
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)
+ lines << seq.dup
height += 1
end
end
@@ -194,34 +178,79 @@ class Reline::Unicode
end
# Take a chunk of a String cut by width with escape sequences.
- def self.take_range(str, start_col, max_width, encoding = str.encoding)
- chunk = String.new(encoding: encoding)
+ def self.take_range(str, start_col, max_width)
+ take_mbchar_range(str, start_col, max_width).first
+ end
+
+ def self.take_mbchar_range(str, start_col, width, cover_begin: false, cover_end: false, padding: false)
+ chunk = String.new(encoding: str.encoding)
+
+ end_col = start_col + width
total_width = 0
rest = str.encode(Encoding::UTF_8)
in_zero_width = false
- rest.scan(WIDTH_SCANNER) do |gc|
+ chunk_start_col = nil
+ chunk_end_col = nil
+ has_csi = false
+ rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc|
case
- when gc[NON_PRINTING_START_INDEX]
+ when non_printing_start
in_zero_width = true
- when gc[NON_PRINTING_END_INDEX]
+ chunk << NON_PRINTING_START
+ when non_printing_end
in_zero_width = false
- when gc[CSI_REGEXP_INDEX]
- chunk << gc[CSI_REGEXP_INDEX]
- when gc[OSC_REGEXP_INDEX]
- chunk << gc[OSC_REGEXP_INDEX]
- when gc[GRAPHEME_CLUSTER_INDEX]
- gc = gc[GRAPHEME_CLUSTER_INDEX]
+ chunk << NON_PRINTING_END
+ when csi
+ has_csi = true
+ chunk << csi
+ when osc
+ chunk << osc
+ when gc
if in_zero_width
chunk << gc
+ next
+ end
+
+ mbchar_width = get_mbchar_width(gc)
+ prev_width = total_width
+ total_width += mbchar_width
+
+ if (cover_begin || padding ? total_width <= start_col : prev_width < start_col)
+ # Current character haven't reached start_col yet
+ next
+ elsif padding && !cover_begin && prev_width < start_col && start_col < total_width
+ # Add preceding padding. This padding might have background color.
+ chunk << ' '
+ chunk_start_col ||= start_col
+ chunk_end_col = total_width
+ next
+ elsif (cover_end ? prev_width < end_col : total_width <= end_col)
+ # Current character is in the range
+ chunk << gc
+ chunk_start_col ||= prev_width
+ chunk_end_col = total_width
+ break if total_width >= end_col
else
- mbchar_width = get_mbchar_width(gc)
- total_width += mbchar_width
- break if (start_col + max_width) < total_width
- chunk << gc if start_col < total_width
+ # Current character exceeds end_col
+ if padding && end_col < total_width
+ # Add succeeding padding. This padding might have background color.
+ chunk << ' '
+ chunk_start_col ||= prev_width
+ chunk_end_col = end_col
+ end
+ break
end
end
end
- chunk
+ chunk_start_col ||= start_col
+ chunk_end_col ||= start_col
+ if padding && chunk_end_col < end_col
+ # Append padding. This padding should not include background color.
+ chunk << "\e[0m" if has_csi
+ chunk << ' ' * (end_col - chunk_end_col)
+ chunk_end_col = end_col
+ end
+ [chunk, chunk_start_col, chunk_end_col - chunk_start_col]
end
def self.get_next_mbchar_size(line, byte_pointer)