terminal_rb 0.19.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +5 -4
- data/bin/bbcode +25 -21
- data/examples/24bit-colors.rb +11 -12
- data/examples/3bit-colors.rb +9 -9
- data/examples/8bit-colors.rb +18 -18
- data/examples/attributes.rb +24 -10
- data/examples/bbcode.rb +23 -19
- data/examples/info.rb +7 -7
- data/examples/key-codes.rb +4 -7
- data/examples/screen_viewer.rb +82 -0
- data/examples/text.rb +12 -28
- data/lib/terminal/ansi/named_colors.rb +1 -0
- data/lib/terminal/ansi/screen_viewer.rb +224 -0
- data/lib/terminal/ansi.rb +502 -484
- data/lib/terminal/detect.rb +1 -0
- data/lib/terminal/input/ansi.rb +18 -16
- data/lib/terminal/input/dumb.rb +5 -8
- data/lib/terminal/input/key_event.rb +131 -75
- data/lib/terminal/input.rb +55 -44
- data/lib/terminal/output/ansi.rb +44 -6
- data/lib/terminal/output/dumb.rb +33 -0
- data/lib/terminal/output.rb +144 -122
- data/lib/terminal/rspec/helper.rb +30 -1
- data/lib/terminal/shell.rb +10 -6
- data/lib/terminal/text/char_width.rb +178 -176
- data/lib/terminal/text/formatter.rb +619 -0
- data/lib/terminal/text.rb +167 -423
- data/lib/terminal/version.rb +1 -1
- data/lib/terminal.rb +79 -75
- metadata +9 -7
- data/terminal_rb.gemspec +0 -36
data/lib/terminal/text.rb
CHANGED
|
@@ -1,135 +1,213 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'ansi'
|
|
4
|
+
require_relative 'text/formatter'
|
|
4
5
|
|
|
5
6
|
module Terminal
|
|
6
|
-
#
|
|
7
|
+
# Unicode-aware text processing utilities for display width calculation,
|
|
8
|
+
# word-wrapping, and formatted text output.
|
|
9
|
+
#
|
|
10
|
+
# All methods correctly handle multi-byte Unicode characters, East Asian
|
|
11
|
+
# wide characters, emoji, combining marks, and ANSI escape codes.
|
|
12
|
+
#
|
|
13
|
+
# @see Terminal::Text::Formatter
|
|
14
|
+
#
|
|
15
|
+
# @example Measure display width
|
|
16
|
+
# Terminal::Text.width('Hello') # => 5
|
|
17
|
+
# Terminal::Text.width("\u{1F600}") # => 2 (emoji)
|
|
18
|
+
#
|
|
19
|
+
# @example Word-wrap text
|
|
20
|
+
# Terminal::Text.lines('Hello World, this is a test', width: 12)
|
|
21
|
+
# # => ["Hello World,", "this is a", "test"]
|
|
7
22
|
#
|
|
8
23
|
module Text
|
|
9
24
|
class << self
|
|
10
|
-
#
|
|
11
|
-
# defined by the Unicode standard.
|
|
12
|
-
# Defaults to one (1).
|
|
25
|
+
# Display width used for East Asian ambiguous-width characters.
|
|
13
26
|
#
|
|
14
|
-
# @
|
|
27
|
+
# @example
|
|
28
|
+
# Terminal::Text.ambiguous_char_width # => 1
|
|
29
|
+
# Terminal::Text.ambiguous_char_width = 2 # for CJK terminals
|
|
15
30
|
#
|
|
16
|
-
# @return [Integer]
|
|
31
|
+
# @return [Integer] default: +1+
|
|
17
32
|
attr_accessor :ambiguous_char_width
|
|
18
33
|
|
|
19
|
-
#
|
|
20
|
-
# argument.
|
|
21
|
-
# It can optionally ignore embedded BBCode.
|
|
34
|
+
# Calculate the display width of a string in terminal columns.
|
|
22
35
|
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
36
|
+
# @example
|
|
37
|
+
# Terminal::Text.width('Hello') # => 5
|
|
38
|
+
# Terminal::Text.width('[bold]Hi[/bold]') # => 2
|
|
39
|
+
# Terminal::Text.width('[bold]Hi[/bold]', bbcode: false) # => 14
|
|
26
40
|
#
|
|
27
|
-
# @param str [#to_s]
|
|
28
|
-
# @param bbcode
|
|
29
|
-
# @
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
width += 1 if sp
|
|
37
|
-
end
|
|
38
|
-
width
|
|
41
|
+
# @param str [#to_s] the string to measure
|
|
42
|
+
# @param bbcode (see Formatter#initialize)
|
|
43
|
+
# @param spaces (see Formatter#initialize)
|
|
44
|
+
# @return [Integer] display width in columns
|
|
45
|
+
def width(str, bbcode: true, spaces: true)
|
|
46
|
+
Formatter
|
|
47
|
+
.new(str, bbcode:, spaces:, ansi: false, eol: false)
|
|
48
|
+
.lines_with_size
|
|
49
|
+
.dig(0, -1) || 0
|
|
39
50
|
end
|
|
40
51
|
|
|
41
|
-
#
|
|
52
|
+
# Split text into lines with their display widths.
|
|
42
53
|
#
|
|
43
|
-
# @example
|
|
44
|
-
# Terminal::Text.
|
|
45
|
-
# # => ["
|
|
54
|
+
# @example
|
|
55
|
+
# Terminal::Text.lines_with_size('Hello World', width: 5)
|
|
56
|
+
# # => [["Hello", 5], ["World", 5]]
|
|
46
57
|
#
|
|
47
|
-
# @param
|
|
48
|
-
#
|
|
49
|
-
# @
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
# @param (see Formatter#initialize)
|
|
59
|
+
# @param (see Formatter#lines_with_size)
|
|
60
|
+
# @return (see Formatter#lines_with_size)
|
|
61
|
+
def lines_with_size(
|
|
62
|
+
*str,
|
|
63
|
+
bbcode: true,
|
|
64
|
+
ansi: true,
|
|
65
|
+
spaces: true,
|
|
66
|
+
eol: true,
|
|
67
|
+
width: nil
|
|
68
|
+
)
|
|
69
|
+
Formatter.new(*str, bbcode:, ansi:, spaces:, eol:).lines_with_size(
|
|
70
|
+
width:
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Split text into lines as strings.
|
|
75
|
+
#
|
|
76
|
+
# @see Formatter#lines
|
|
77
|
+
#
|
|
78
|
+
# @example
|
|
79
|
+
# Terminal::Text.lines('Hello World', width: 5)
|
|
80
|
+
# # => ["Hello", "World"]
|
|
81
|
+
# @example With BBCode
|
|
82
|
+
# Terminal::Text.lines('[bold]Hello[/] World', width: 5)
|
|
83
|
+
# # => ["\e[1mHello\e[m", "World"]
|
|
84
|
+
#
|
|
85
|
+
# @param (see Formatter#initialize)
|
|
86
|
+
# @param (see Formatter#lines)
|
|
87
|
+
# @return (see Formatter#lines)
|
|
88
|
+
def lines(
|
|
89
|
+
*str,
|
|
90
|
+
bbcode: true,
|
|
91
|
+
ansi: true,
|
|
92
|
+
spaces: true,
|
|
93
|
+
eol: true,
|
|
94
|
+
width: nil
|
|
95
|
+
)
|
|
96
|
+
Formatter.new(*str, bbcode:, ansi:, spaces:, eol:).lines(width:)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Format text with alignment, padding, and decorations.
|
|
100
|
+
#
|
|
101
|
+
# @see Formatter.format
|
|
102
|
+
#
|
|
103
|
+
# @param (see Formatter.format)
|
|
104
|
+
# @return (see Formatter.format)
|
|
105
|
+
# @raise (see Formatter.format)
|
|
106
|
+
def format(...) = Formatter.format(...)
|
|
107
|
+
|
|
108
|
+
# @deprecated Use {.lines} and iterate over the result.
|
|
109
|
+
#
|
|
110
|
+
# @comment @param str [Array<#to_s>] text to process
|
|
111
|
+
# @comment @param limit [Integer, nil] maximum line width
|
|
112
|
+
# @comment @param bbcode [true, false] process BBCode markup
|
|
113
|
+
# @comment @param ansi [true, false] recognize ANSI escape codes
|
|
114
|
+
# @comment @param ignore_newline [true, false] treat newlines as spaces
|
|
115
|
+
# @comment @yield [String] each line
|
|
116
|
+
# @comment @return [Enumerator, Object] enumerator or block result
|
|
61
117
|
def each_line(
|
|
62
|
-
*
|
|
118
|
+
*str,
|
|
63
119
|
limit: nil,
|
|
64
120
|
bbcode: true,
|
|
65
121
|
ansi: true,
|
|
66
122
|
ignore_newline: false,
|
|
67
|
-
&
|
|
123
|
+
&
|
|
68
124
|
)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
125
|
+
warn(
|
|
126
|
+
'[DEPRECATED] `each_line` is deprecated – use `.lines.each` instead',
|
|
127
|
+
category: :deprecated,
|
|
128
|
+
uplevel: 1
|
|
129
|
+
)
|
|
130
|
+
lines(
|
|
131
|
+
*str,
|
|
132
|
+
bbcode:,
|
|
133
|
+
ansi:,
|
|
134
|
+
spaces: true,
|
|
135
|
+
eol: !ignore_newline,
|
|
136
|
+
width: limit
|
|
137
|
+
).each(&)
|
|
78
138
|
end
|
|
79
139
|
alias each each_line
|
|
80
140
|
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
# @example Generate word-by-word wrapped text for a limited output width
|
|
84
|
-
# Terminal::Text.each_line_with_size('This is a simple test 😀', limit: 6).to_a
|
|
85
|
-
# # => [["This", 4], ["is a", 4], ["simple", 6], ["test", 4], ["😀", 2]]
|
|
141
|
+
# @deprecated Use {.lines_with_size} and iterate over the result.
|
|
86
142
|
#
|
|
87
|
-
# @param
|
|
88
|
-
# @
|
|
89
|
-
# @
|
|
90
|
-
# @
|
|
143
|
+
# @comment @param str [Array<to_s>] text to process
|
|
144
|
+
# @comment @param limit [Integer, nil] maximum line width
|
|
145
|
+
# @comment @param bbcode [true, false] process BBCode markup
|
|
146
|
+
# @comment @param ansi [true, false] recognize ANSI escape codes
|
|
147
|
+
# @comment @param ignore_newline [true, false] treat newlines as spaces
|
|
148
|
+
# @comment @yield [String, Integer] each line and its display width
|
|
149
|
+
# @comment @return [Enumerator, Object] enumerator or block result
|
|
91
150
|
def each_line_with_size(
|
|
92
|
-
*
|
|
151
|
+
*str,
|
|
93
152
|
limit: nil,
|
|
94
153
|
bbcode: true,
|
|
95
154
|
ansi: true,
|
|
96
155
|
ignore_newline: false,
|
|
97
|
-
&
|
|
156
|
+
&
|
|
98
157
|
)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
158
|
+
warn(
|
|
159
|
+
'[DEPRECATED] `each_line_with_size` is deprecated ' \
|
|
160
|
+
'– use `.lines_width_size.each` instead',
|
|
161
|
+
category: :deprecated,
|
|
162
|
+
uplevel: 1
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
lines_with_size(
|
|
166
|
+
*str,
|
|
167
|
+
bbcode:,
|
|
168
|
+
ansi:,
|
|
169
|
+
spaces: true,
|
|
170
|
+
eol: !ignore_newline,
|
|
171
|
+
width: limit
|
|
172
|
+
).each(&)
|
|
108
173
|
end
|
|
109
174
|
alias each_with_size each_line_with_size
|
|
110
175
|
|
|
111
|
-
#
|
|
176
|
+
# Calculate the maximum display width of any line.
|
|
112
177
|
#
|
|
113
|
-
# @
|
|
114
|
-
#
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
178
|
+
# @example
|
|
179
|
+
# Terminal::Text.max_line_width("short\na longer line")
|
|
180
|
+
# # => 13
|
|
181
|
+
#
|
|
182
|
+
# @param (see Formatter#initialize)
|
|
183
|
+
# @return (see Formatter#max_line_width)
|
|
184
|
+
def max_line_width(
|
|
185
|
+
*str,
|
|
186
|
+
ansi: true,
|
|
187
|
+
bbcode: true,
|
|
188
|
+
spaces: true,
|
|
189
|
+
eol: true,
|
|
190
|
+
**opts
|
|
191
|
+
)
|
|
192
|
+
# backward compatibility:
|
|
193
|
+
if opts.key?(:ignore_newline)
|
|
194
|
+
eol = opts[:ignore_newline] ? false : true
|
|
195
|
+
warn(
|
|
196
|
+
"Parameter ':ignore_newline' will become obsolete - " \
|
|
197
|
+
"use 'eol: #{eol.inspect}' instead",
|
|
198
|
+
category: :deprecated
|
|
199
|
+
)
|
|
120
200
|
end
|
|
121
|
-
ret
|
|
122
|
-
end
|
|
123
201
|
|
|
124
|
-
|
|
202
|
+
Formatter.new(*str, bbcode:, ansi:, spaces:, eol:).max_line_width
|
|
203
|
+
end
|
|
125
204
|
|
|
205
|
+
# @private
|
|
126
206
|
def char_width(char)
|
|
127
207
|
ord = char.ord
|
|
128
208
|
return @ctrlchar_width[ord] if ord < 0x20
|
|
129
209
|
if char.size == 1
|
|
130
|
-
return 1
|
|
131
|
-
width = CharWidth[ord]
|
|
132
|
-
return width < 0 ? @ambiguous_char_width : width
|
|
210
|
+
return ord < 0xa1 ? 1 : CharWidth[ord] || @ambiguous_char_width
|
|
133
211
|
end
|
|
134
212
|
sum = 0
|
|
135
213
|
zwj = false
|
|
@@ -137,346 +215,13 @@ module Terminal
|
|
|
137
215
|
next zwj = false if zwj
|
|
138
216
|
ord = c.ord
|
|
139
217
|
next zwj = true if ord == 0x200d # zero with joiner
|
|
140
|
-
|
|
141
|
-
sum += (width < 0 ? @ambiguous_char_width : width)
|
|
218
|
+
sum += CharWidth[ord] || @ambiguous_char_width
|
|
142
219
|
end
|
|
143
220
|
sum
|
|
144
221
|
end
|
|
145
|
-
|
|
146
|
-
def lim_pairs(snippets, limit)
|
|
147
|
-
line = @empty.dup
|
|
148
|
-
size = 0
|
|
149
|
-
csi = nil
|
|
150
|
-
snippets.each do |snippet|
|
|
151
|
-
if snippet == :space
|
|
152
|
-
next if size == 0
|
|
153
|
-
next line << ' ' if (size += 1) <= limit
|
|
154
|
-
yield(line, size - 1)
|
|
155
|
-
line = "#{csi}"
|
|
156
|
-
next size = 0
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
if snippet == :nl
|
|
160
|
-
line[-1] == ' ' ? yield(line.chop, size - 1) : yield(line, size)
|
|
161
|
-
line = "#{csi}"
|
|
162
|
-
next size = 0
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
if snippet == :hard_nl
|
|
166
|
-
line[-1] == ' ' ? yield(line.chop, size - 1) : yield(line, size)
|
|
167
|
-
line = @empty.dup
|
|
168
|
-
csi = nil
|
|
169
|
-
next size = 0
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
if snippet == CsiEnd
|
|
173
|
-
line << CsiEnd if csi
|
|
174
|
-
next csi = nil
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
next line << (csi = snippet) if Csi === snippet
|
|
178
|
-
next line << snippet if Osc === snippet
|
|
179
|
-
|
|
180
|
-
# Word:
|
|
181
|
-
|
|
182
|
-
if (ns = size + snippet.size) <= limit
|
|
183
|
-
line << snippet
|
|
184
|
-
next size = ns
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
if line[-1] == ' '
|
|
188
|
-
line.chop!
|
|
189
|
-
size -= 1
|
|
190
|
-
end
|
|
191
|
-
yield(line, size) if size != 0
|
|
192
|
-
|
|
193
|
-
if snippet.size <= limit
|
|
194
|
-
line = "#{csi}#{snippet}"
|
|
195
|
-
next size = snippet.size
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
words = snippet.split(limit)
|
|
199
|
-
if words[-1].size <= limit
|
|
200
|
-
snippet = words.pop
|
|
201
|
-
line = "#{csi}#{snippet}"
|
|
202
|
-
size = snippet.size
|
|
203
|
-
else
|
|
204
|
-
line = "#{csi}"
|
|
205
|
-
size = 0
|
|
206
|
-
end
|
|
207
|
-
words.each { yield("#{csi}#{_1}", _1.size) }
|
|
208
|
-
end
|
|
209
|
-
nil
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def pairs(snippets)
|
|
213
|
-
line = @empty.dup
|
|
214
|
-
size = 0
|
|
215
|
-
csi = nil
|
|
216
|
-
snippets.each do |snippet|
|
|
217
|
-
if snippet == :space
|
|
218
|
-
next if size == 0
|
|
219
|
-
line << ' '
|
|
220
|
-
next size += 1
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
if snippet == :nl
|
|
224
|
-
line[-1] == ' ' ? yield(line.chop, size - 1) : yield(line, size)
|
|
225
|
-
line = "#{csi}"
|
|
226
|
-
next size = 0
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
if snippet == :hard_nl
|
|
230
|
-
line[-1] == ' ' ? yield(line.chop, size - 1) : yield(line, size)
|
|
231
|
-
line = @empty.dup
|
|
232
|
-
csi = nil
|
|
233
|
-
next size = 0
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
if snippet == CsiEnd
|
|
237
|
-
line << CsiEnd if csi
|
|
238
|
-
next csi = nil
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
next line << (csi = snippet) if Csi === snippet
|
|
242
|
-
next line << snippet if Osc === snippet
|
|
243
|
-
|
|
244
|
-
# Word:
|
|
245
|
-
size += snippet.size
|
|
246
|
-
line << snippet
|
|
247
|
-
end
|
|
248
|
-
nil
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
def lim_lines(snippets, limit)
|
|
252
|
-
line = @empty.dup
|
|
253
|
-
size = 0
|
|
254
|
-
csi = nil
|
|
255
|
-
snippets.each do |snippet|
|
|
256
|
-
if snippet == :space
|
|
257
|
-
next if size == 0
|
|
258
|
-
next line << ' ' if (size += 1) <= limit
|
|
259
|
-
yield(line)
|
|
260
|
-
line = "#{csi}"
|
|
261
|
-
next size = 0
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
if snippet == :nl
|
|
265
|
-
yield(line[-1] == ' ' ? line.chop : line)
|
|
266
|
-
line = "#{csi}"
|
|
267
|
-
next size = 0
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
if snippet == :hard_nl
|
|
271
|
-
yield(line[-1] == ' ' ? line.chop : line)
|
|
272
|
-
line = @empty.dup
|
|
273
|
-
csi = nil
|
|
274
|
-
next size = 0
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
if snippet == CsiEnd
|
|
278
|
-
line << CsiEnd if csi
|
|
279
|
-
next csi = nil
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
next line << (csi = snippet) if Csi === snippet
|
|
283
|
-
next line << snippet if Osc === snippet
|
|
284
|
-
|
|
285
|
-
# Word:
|
|
286
|
-
|
|
287
|
-
if (ns = size + snippet.size) <= limit
|
|
288
|
-
line << snippet
|
|
289
|
-
next size = ns
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
if line[-1] == ' '
|
|
293
|
-
line.chop!
|
|
294
|
-
size -= 1
|
|
295
|
-
end
|
|
296
|
-
yield(line) if size != 0
|
|
297
|
-
|
|
298
|
-
if snippet.size <= limit
|
|
299
|
-
line = "#{csi}#{snippet}"
|
|
300
|
-
next size = snippet.size
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
words = snippet.split(limit)
|
|
304
|
-
if words[-1].size <= limit
|
|
305
|
-
snippet = words.pop
|
|
306
|
-
line = "#{csi}#{snippet}"
|
|
307
|
-
size = snippet.size
|
|
308
|
-
else
|
|
309
|
-
line = "#{csi}"
|
|
310
|
-
size = 0
|
|
311
|
-
end
|
|
312
|
-
words.each { yield("#{csi}#{_1}") }
|
|
313
|
-
end
|
|
314
|
-
nil
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
def lines(snippets)
|
|
318
|
-
line = @empty.dup
|
|
319
|
-
size = 0
|
|
320
|
-
csi = nil
|
|
321
|
-
snippets.each do |snippet|
|
|
322
|
-
if snippet == :space
|
|
323
|
-
next if size == 0
|
|
324
|
-
line << ' '
|
|
325
|
-
next size += 1
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
if snippet == :nl
|
|
329
|
-
yield(line[-1] == ' ' ? line.chop : line)
|
|
330
|
-
line = "#{csi}"
|
|
331
|
-
next size = 0
|
|
332
|
-
end
|
|
333
|
-
|
|
334
|
-
if snippet == :hard_nl
|
|
335
|
-
yield(line[-1] == ' ' ? line.chop : line)
|
|
336
|
-
line = @empty.dup
|
|
337
|
-
csi = nil
|
|
338
|
-
next size = 0
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
if snippet == CsiEnd
|
|
342
|
-
line << CsiEnd if csi
|
|
343
|
-
next csi = nil
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
next line << (csi = snippet) if Csi === snippet
|
|
347
|
-
next line << snippet if Osc === snippet
|
|
348
|
-
|
|
349
|
-
# Word:
|
|
350
|
-
size += snippet.size
|
|
351
|
-
line << snippet
|
|
352
|
-
end
|
|
353
|
-
nil
|
|
354
|
-
end
|
|
355
|
-
|
|
356
|
-
def as_snippets(text, bbcode, ansi, ignore_newline, word_class)
|
|
357
|
-
ret = []
|
|
358
|
-
last = nil
|
|
359
|
-
to_s = bbcode ? ->(s) { Ansi.bbcode(s) } : :to_s.to_proc
|
|
360
|
-
text.each do |txt|
|
|
361
|
-
if (txt = to_s[txt]).empty?
|
|
362
|
-
next ret[-1] = last = :hard_nl if Symbol === last
|
|
363
|
-
next ret << (last = :hard_nl)
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
txt = txt.encode(@encoding) if txt.encoding != @encoding
|
|
367
|
-
|
|
368
|
-
txt.scan(@scan_snippet) do |nl, csi, osc, space, gc|
|
|
369
|
-
if gc
|
|
370
|
-
next last.add(gc, char_width(gc)) if word_class === last
|
|
371
|
-
next ret << (last = word_class.new(gc, char_width(gc)))
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
next Symbol === last ? nil : ret << (last = :space) if space
|
|
375
|
-
|
|
376
|
-
if nl
|
|
377
|
-
if ignore_newline # handle nl like space
|
|
378
|
-
next Symbol === last ? nil : ret << (last = :space)
|
|
379
|
-
end
|
|
380
|
-
next last == :space ? ret[-1] = last = :nl : ret << (last = :nl)
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
next unless ansi
|
|
384
|
-
|
|
385
|
-
next ret << (last = Osc.new(osc)) if osc
|
|
386
|
-
|
|
387
|
-
if csi == "\e[m" || csi == "\e[0m"
|
|
388
|
-
next last == CsiEnd ? nil : ret << (last = CsiEnd)
|
|
389
|
-
end
|
|
390
|
-
|
|
391
|
-
Csi === last ? last.add(csi) : ret << (last = Csi.new(csi))
|
|
392
|
-
end
|
|
393
|
-
|
|
394
|
-
Symbol === last ? ret[-1] = last = :hard_nl : ret << (last = :hard_nl)
|
|
395
|
-
end
|
|
396
|
-
ret
|
|
397
|
-
end
|
|
398
|
-
end
|
|
399
|
-
|
|
400
|
-
class Osc
|
|
401
|
-
attr_reader :to_str, :size
|
|
402
|
-
alias to_s to_str
|
|
403
|
-
|
|
404
|
-
def initialize(str)
|
|
405
|
-
@to_str = str
|
|
406
|
-
@size = 0
|
|
407
|
-
end
|
|
408
|
-
end
|
|
409
|
-
|
|
410
|
-
class Csi < Osc
|
|
411
|
-
def add(str) = (@to_str << str)
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
module CsiEnd
|
|
415
|
-
class << self
|
|
416
|
-
attr_reader :to_str, :size
|
|
417
|
-
end
|
|
418
|
-
@to_str = "\e[m"
|
|
419
|
-
@size = 0
|
|
420
|
-
end
|
|
421
|
-
|
|
422
|
-
class Word
|
|
423
|
-
attr_reader :to_str, :size
|
|
424
|
-
alias to_s to_str
|
|
425
|
-
|
|
426
|
-
def initialize(char, size)
|
|
427
|
-
@to_str = char.dup
|
|
428
|
-
@size = size
|
|
429
|
-
end
|
|
430
|
-
|
|
431
|
-
def add(char, size)
|
|
432
|
-
@to_str << char
|
|
433
|
-
@size += size
|
|
434
|
-
end
|
|
435
|
-
end
|
|
436
|
-
|
|
437
|
-
class WordEx < Word
|
|
438
|
-
attr_reader :chars
|
|
439
|
-
|
|
440
|
-
def initialize(char, size)
|
|
441
|
-
super
|
|
442
|
-
@chars = [[char, size]]
|
|
443
|
-
end
|
|
444
|
-
|
|
445
|
-
def add(char, size)
|
|
446
|
-
@chars << [char, size]
|
|
447
|
-
super
|
|
448
|
-
end
|
|
449
|
-
|
|
450
|
-
def split(limit)
|
|
451
|
-
chars = @chars.dup
|
|
452
|
-
ret = [last = Word.new(*chars.shift)]
|
|
453
|
-
chars.each do |c, s|
|
|
454
|
-
next ret << (last = Word.new(c, s)) if last.size + s > limit
|
|
455
|
-
last.add(c, s)
|
|
456
|
-
end
|
|
457
|
-
ret
|
|
458
|
-
end
|
|
459
222
|
end
|
|
460
223
|
|
|
461
224
|
@ambiguous_char_width = 1
|
|
462
|
-
@empty = String.new(encoding: @encoding = Encoding::UTF_8).freeze
|
|
463
|
-
|
|
464
|
-
@scan_snippet =
|
|
465
|
-
/\G(?:
|
|
466
|
-
(\r?\n)
|
|
467
|
-
| (\e\[[\x30-\x3f]*[\x20-\x2f]*[a-zA-Z])
|
|
468
|
-
| (\e\]\d+(?:;[^\a\e]+)*(?:\a|\e\\))
|
|
469
|
-
| (\s+)
|
|
470
|
-
| (\X)
|
|
471
|
-
)/x
|
|
472
|
-
|
|
473
|
-
@scan_width =
|
|
474
|
-
/\G(?:
|
|
475
|
-
(?:\e\[[\x30-\x3f]*[\x20-\x2f]*[a-zA-Z])
|
|
476
|
-
| (?:\e\]\d+(?:;[^\a\e]+)*(?:\a|\e\\))
|
|
477
|
-
| (\s+)
|
|
478
|
-
| (\X)
|
|
479
|
-
)/x
|
|
480
225
|
|
|
481
226
|
@ctrlchar_width =
|
|
482
227
|
Hash
|
|
@@ -521,7 +266,6 @@ module Terminal
|
|
|
521
266
|
cw_file = "#{__dir__}/text/char_width.rb"
|
|
522
267
|
autoload :CharWidth, cw_file
|
|
523
268
|
autoload :UNICODE_VERSION, cw_file
|
|
524
|
-
|
|
525
|
-
private_constant :Osc, :Csi, :CsiEnd, :Word, :WordEx, :CharWidth
|
|
269
|
+
private_constant :CharWidth
|
|
526
270
|
end
|
|
527
271
|
end
|
data/lib/terminal/version.rb
CHANGED