terminal_rb 0.20.0 → 1.0.4
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 +3 -3
- 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 -4
- 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 -480
- data/lib/terminal/detect.rb +1 -0
- data/lib/terminal/input/ansi.rb +9 -7
- data/lib/terminal/input/dumb.rb +5 -8
- data/lib/terminal/input/key_event.rb +131 -75
- data/lib/terminal/input.rb +49 -40
- data/lib/terminal/output/ansi.rb +39 -5
- data/lib/terminal/output/dumb.rb +33 -0
- data/lib/terminal/output.rb +139 -125
- 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 +614 -0
- data/lib/terminal/text.rb +168 -444
- 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/ansi.rb
CHANGED
|
@@ -1,97 +1,67 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Terminal
|
|
4
|
+
# ANSI escape code generation and BBCode-to-ANSI conversion.
|
|
4
5
|
#
|
|
5
|
-
#
|
|
6
|
+
# Provides methods for text decoration, color, cursor control, screen
|
|
7
|
+
# manipulation, hyperlinks, notifications, and progress indicators.
|
|
8
|
+
# All methods return ANSI escape sequence strings — they do not write
|
|
9
|
+
# to the terminal directly.
|
|
10
|
+
#
|
|
11
|
+
# Colors can be specified as named symbols, 256-color indices, hex
|
|
12
|
+
# strings, or CSS/X11 color names.
|
|
13
|
+
#
|
|
14
|
+
# @example Generate ANSI codes
|
|
15
|
+
# Terminal::Ansi[:bold, :red] # => "\e[1;31m"
|
|
16
|
+
# Terminal::Ansi[:on_blue] # => "\e[44m"
|
|
17
|
+
#
|
|
18
|
+
# @example BBCode to ANSI
|
|
19
|
+
# Terminal::Ansi.bbcode('[bold]Hello[/bold]') # => "\e[1mHello\e[m"
|
|
6
20
|
#
|
|
7
21
|
module Ansi
|
|
8
22
|
class << self
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# @see []
|
|
23
|
+
# All known text attribute names (bold, italic, underline, etc.).
|
|
12
24
|
#
|
|
13
25
|
# @attribute [r] attributes
|
|
14
|
-
# @return [Array<Symbol>]
|
|
26
|
+
# @return [Array<Symbol>]
|
|
15
27
|
def attributes = @attributes.dup
|
|
16
28
|
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# @see []
|
|
29
|
+
# All known basic color names.
|
|
20
30
|
#
|
|
21
31
|
# @attribute [r] colors
|
|
22
|
-
# @return [Array<Symbol>]
|
|
32
|
+
# @return [Array<Symbol>]
|
|
23
33
|
def colors = @colors.dup
|
|
24
34
|
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# @see []
|
|
35
|
+
# All CSS/X11 named colors.
|
|
28
36
|
#
|
|
29
37
|
# @attribute [r] named_colors
|
|
30
|
-
# @return [Array<Symbol>]
|
|
38
|
+
# @return [Array<Symbol>]
|
|
31
39
|
def named_colors = NAMED_COLORS.keys.map!(&:to_sym)
|
|
32
40
|
|
|
33
41
|
#
|
|
34
|
-
# @!group ANSI
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
# @
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
# Terminal::Ansi[:fg_00aa00]
|
|
58
|
-
# Terminal::Ansi[:af]
|
|
59
|
-
# Terminal::Ansi[:fg_af]
|
|
60
|
-
# Terminal::Ansi['#fab']
|
|
61
|
-
# Terminal::Ansi['#00aa00']
|
|
62
|
-
# Terminal::Ansi['lightblue']
|
|
63
|
-
#
|
|
64
|
-
# @example Valid Background Color Attributes
|
|
65
|
-
# Terminal::Ansi[:bg_yellow]
|
|
66
|
-
# Terminal::Ansi[:bg_fab]
|
|
67
|
-
# Terminal::Ansi[:bg_00aa00]
|
|
68
|
-
# Terminal::Ansi[:bg_af]
|
|
69
|
-
# Terminal::Ansi['bg#00aa00']
|
|
70
|
-
# Terminal::Ansi['bg_lightblue']
|
|
71
|
-
#
|
|
72
|
-
# Terminal::Ansi[:on_yellow]
|
|
73
|
-
# Terminal::Ansi[:on_fab]
|
|
74
|
-
# Terminal::Ansi[:on_00aa00]
|
|
75
|
-
# Terminal::Ansi[:on_af]
|
|
76
|
-
# Terminal::Ansi['on#00aa00']
|
|
77
|
-
# Terminal::Ansi['on_lightblue']
|
|
78
|
-
#
|
|
79
|
-
# @example Valid Underline Color Attributes
|
|
80
|
-
# Terminal::Ansi[:underline, :ul_yellow]
|
|
81
|
-
# Terminal::Ansi[:underline, :ul_fab]
|
|
82
|
-
# Terminal::Ansi[:underline, :ul_00aa00]
|
|
83
|
-
# Terminal::Ansi[:underline, :ul_fa]
|
|
84
|
-
# Terminal::Ansi[:underline, :ul_bright_yellow]
|
|
85
|
-
# Terminal::Ansi[:underline, 'ul#00aa00']
|
|
86
|
-
# Terminal::Ansi['underline', 'ul_lightblue']
|
|
87
|
-
#
|
|
88
|
-
# @example Combined attributes:
|
|
89
|
-
# Terminal::Ansi[:bold, :italic, :bright_white, :on_0000cc]
|
|
90
|
-
#
|
|
91
|
-
# @see valid?
|
|
92
|
-
#
|
|
93
|
-
# @param attributes [Array<Symbol, String>] attribute names to be used
|
|
94
|
-
# @return [String] combined ANSI attributes
|
|
42
|
+
# @!group ANSI Control Code Generation
|
|
43
|
+
#
|
|
44
|
+
|
|
45
|
+
# Generate an ANSI escape sequence from attribute names or color
|
|
46
|
+
# indices.
|
|
47
|
+
#
|
|
48
|
+
# Accepts symbols (`:bold`, +:red+), strings (`"bold"`, +"#ff0000"+),
|
|
49
|
+
# or integers for 256-color palette (0-255 foreground, 256-511
|
|
50
|
+
# background, 512-767 underline color).
|
|
51
|
+
#
|
|
52
|
+
# @example Named attributes
|
|
53
|
+
# Terminal::Ansi[:bold, :italic, :green] # => "\e[1;3;32m"
|
|
54
|
+
# @example 256-color palette
|
|
55
|
+
# Terminal::Ansi[196] # foreground color 196
|
|
56
|
+
# Terminal::Ansi[256 + 21] # background color 21
|
|
57
|
+
# @example Hex colors
|
|
58
|
+
# Terminal::Ansi[:ff8800] # => "\e[38;2;255;136;0m"
|
|
59
|
+
# @example Named CSS colors
|
|
60
|
+
# Terminal::Ansi[:coral] # => "\e[38;2;255;127;80m"
|
|
61
|
+
#
|
|
62
|
+
# @param attributes [Array<String, Symbol, Integer>] ANSI attributes
|
|
63
|
+
# @return [String] ANSI escape sequence
|
|
64
|
+
# @raise [ArgumentError] for unknown attributes
|
|
95
65
|
def [](*attributes)
|
|
96
66
|
return +'' if attributes.empty?
|
|
97
67
|
"\e[#{
|
|
@@ -116,134 +86,158 @@ module Terminal
|
|
|
116
86
|
}m"
|
|
117
87
|
end
|
|
118
88
|
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
# @param str [#to_s] object to be tested
|
|
122
|
-
# @return [true, false] whether if attributes are found
|
|
123
|
-
def ansi?(str) = @re_test.match?(str.to_s)
|
|
124
|
-
|
|
125
|
-
# Decorate given argument with ANSI attributes and colors.
|
|
89
|
+
# Wrap a string with ANSI escape codes.
|
|
126
90
|
#
|
|
127
91
|
# @example
|
|
128
|
-
# Terminal::Ansi.decorate(
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
# )
|
|
132
|
-
# # => "\e[
|
|
133
|
-
#
|
|
134
|
-
# @
|
|
135
|
-
# @
|
|
136
|
-
#
|
|
137
|
-
# @param
|
|
138
|
-
# @
|
|
139
|
-
# @param reset [true, false] whether to include reset code for ANSI attributes
|
|
140
|
-
# @return [String] `str` converted and decorated with the ANSI `attributes`
|
|
92
|
+
# Terminal::Ansi.decorate('Hello', :bold, :red)
|
|
93
|
+
# # => "\e[1;31mHello\e[m"
|
|
94
|
+
# @example Without reset
|
|
95
|
+
# Terminal::Ansi.decorate('Hello', :bold, reset: false)
|
|
96
|
+
# # => "\e[1mHello"
|
|
97
|
+
#
|
|
98
|
+
# @param str [#to_s] the string to decorate
|
|
99
|
+
# @param attributes [Array<String, Symbol, Integer>] ANSI attributes
|
|
100
|
+
# to apply
|
|
101
|
+
# @param reset [true, false] append a reset code at the end
|
|
102
|
+
# @return [String] the decorated string
|
|
141
103
|
def decorate(str, *attributes, reset: true)
|
|
142
104
|
attributes = self[*attributes]
|
|
143
105
|
attributes.empty? ? "#{str}" : "#{attributes}#{str}#{"\e[m" if reset}"
|
|
144
106
|
end
|
|
145
107
|
|
|
146
|
-
# Remove ANSI
|
|
108
|
+
# Remove all ANSI escape codes from a string.
|
|
147
109
|
#
|
|
148
110
|
# @example
|
|
149
|
-
# Terminal::Ansi.undecorate("\e[1;
|
|
150
|
-
# # => "Hello
|
|
111
|
+
# Terminal::Ansi.undecorate("\e[1;31mHello\e[m")
|
|
112
|
+
# # => "Hello"
|
|
151
113
|
#
|
|
152
|
-
# @
|
|
153
|
-
#
|
|
154
|
-
# @param str [#to_s] string to be modified
|
|
155
|
-
# @return [String] string without ANSI attributes
|
|
114
|
+
# @param str [#to_s] the string to clean
|
|
115
|
+
# @return [String] a copy with all ANSI codes stripped
|
|
156
116
|
def undecorate(str)
|
|
157
117
|
(str = str.to_s).index("\e") ? str.gsub(@re_test, '') : str.dup
|
|
158
118
|
end
|
|
159
119
|
|
|
160
|
-
# Try to
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
# @example Valid Attribute String
|
|
164
|
-
# Terminal::Ansi.try_convert('bold italic blink red on#00ff00')
|
|
165
|
-
# # => "\e[1;3;5;31;48;2;0;255;0m"
|
|
166
|
-
#
|
|
167
|
-
# @example Invalid Attribute String
|
|
168
|
-
# Terminal::Ansi.try_convert('cool bold on green')
|
|
169
|
-
# # => nil
|
|
120
|
+
# Try to convert a space-separated attribute string to an ANSI
|
|
121
|
+
# escape sequence.
|
|
170
122
|
#
|
|
171
|
-
# @
|
|
123
|
+
# @example
|
|
124
|
+
# Terminal::Ansi.try_convert('bold red') # => "\e[1;31m"
|
|
125
|
+
# Terminal::Ansi.try_convert('invalid') # => nil
|
|
172
126
|
#
|
|
173
|
-
# @param attributes [#to_s]
|
|
174
|
-
# @param separator [String]
|
|
175
|
-
# @return [String]
|
|
176
|
-
#
|
|
127
|
+
# @param attributes [#to_s] space-separated attribute names
|
|
128
|
+
# @param separator [String] delimiter for splitting
|
|
129
|
+
# @return [String, nil] ANSI escape sequence, or +nil+ if any
|
|
130
|
+
# attribute is invalid
|
|
177
131
|
def try_convert(attributes, separator: ' ')
|
|
178
132
|
return unless attributes
|
|
179
133
|
return if (attributes = attributes.to_s.split(separator)).empty?
|
|
180
|
-
|
|
134
|
+
attributes.uniq!
|
|
135
|
+
"\e[#{attributes.map! { @attr_map[it] || return }.join(';')}m"
|
|
181
136
|
end
|
|
182
137
|
|
|
183
|
-
#
|
|
138
|
+
# Generate rainbow-colored text using true color (24-bit) ANSI
|
|
139
|
+
# codes.
|
|
184
140
|
#
|
|
185
|
-
# @
|
|
141
|
+
# @example
|
|
142
|
+
# puts Terminal::Ansi.rainbow('Hello, World!')
|
|
186
143
|
#
|
|
187
|
-
# @param
|
|
188
|
-
# @
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
144
|
+
# @param str [#to_s] text to colorize
|
|
145
|
+
# @param frequency [Float] color wave frequency
|
|
146
|
+
# @param spread [Float] color wave spread
|
|
147
|
+
# @param seed [Float] starting position in the color cycle
|
|
148
|
+
# @return [String] text with per-character RGB foreground colors
|
|
149
|
+
def rainbow(str, frequency: 0.3, spread: 0.8, seed: 1.1)
|
|
150
|
+
pos = -1
|
|
151
|
+
@pi2_third ||= 2.0 * Math::PI / 3.0
|
|
152
|
+
@pi4_third ||= 4.0 * Math::PI / 3.0
|
|
153
|
+
(
|
|
154
|
+
str.to_s.chars.map! do |char|
|
|
155
|
+
i = (seed + ((pos += 1) / spread)) * frequency
|
|
156
|
+
"\e[38;2;#{(Math.sin(i) * 255).round.abs};#{
|
|
157
|
+
(Math.sin(i + @pi2_third) * 255).round.abs
|
|
158
|
+
};#{(Math.sin(i + @pi4_third) * 255).round.abs}m#{char}"
|
|
159
|
+
end << RESET_FG
|
|
160
|
+
).join
|
|
200
161
|
end
|
|
201
162
|
|
|
202
163
|
#
|
|
203
164
|
# @!endgroup
|
|
204
165
|
#
|
|
205
|
-
# @!group BBcode
|
|
166
|
+
# @!group BBcode Generation
|
|
206
167
|
#
|
|
207
168
|
|
|
208
|
-
#
|
|
169
|
+
# Convert BBCode markup to ANSI escape codes.
|
|
209
170
|
#
|
|
210
|
-
#
|
|
211
|
-
#
|
|
212
|
-
#
|
|
171
|
+
# Tags like +[bold]+ are converted to their ANSI equivalents;
|
|
172
|
+
# +[/bold]+ or +[/]+ resets. Unknown tags are left unchanged.
|
|
173
|
+
# Escape a tag with a backslash: +[\\bold]+ renders as +[bold]+.
|
|
213
174
|
#
|
|
214
|
-
# @see unbbcode
|
|
215
|
-
# @see
|
|
175
|
+
# @see .unbbcode
|
|
176
|
+
# @see .plain
|
|
216
177
|
#
|
|
217
|
-
# @
|
|
218
|
-
#
|
|
178
|
+
# @example
|
|
179
|
+
# Terminal::Ansi.bbcode('[bold red]Hello[/] World')
|
|
180
|
+
# # => "\e[1;31mHello\e[m World"
|
|
181
|
+
# @example Escaped tag
|
|
182
|
+
# Terminal::Ansi.bbcode('[\\bold]') # => "[bold]"
|
|
183
|
+
#
|
|
184
|
+
# @param str [#to_s] text with BBCode markup
|
|
185
|
+
# @return [String] text with ANSI escape codes
|
|
219
186
|
def bbcode(str)
|
|
220
187
|
str = str.to_s
|
|
221
188
|
return str.dup unless str.index('[')
|
|
222
189
|
str.gsub(@re_bbcode) do |match_str|
|
|
223
190
|
match = Regexp.last_match(1) or next match_str
|
|
224
|
-
next
|
|
225
|
-
|
|
191
|
+
next try_convert(match) || match_str if match[0] != '\\'
|
|
192
|
+
match[0] = ''
|
|
193
|
+
"[#{match}]"
|
|
226
194
|
end
|
|
227
195
|
end
|
|
228
196
|
|
|
229
|
-
# Remove
|
|
197
|
+
# Remove BBCode tags from a string, keeping the enclosed text.
|
|
230
198
|
#
|
|
231
|
-
# @
|
|
232
|
-
# Terminal::Ansi.unbbcode "[b]Bold[/b] Text"
|
|
233
|
-
# # => "Bold Text"
|
|
199
|
+
# @see .bbcode
|
|
234
200
|
#
|
|
235
|
-
# @
|
|
201
|
+
# @example
|
|
202
|
+
# Terminal::Ansi.unbbcode('[bold]Hello[/bold]') # => "Hello"
|
|
236
203
|
#
|
|
237
|
-
# @param str [#to_s]
|
|
238
|
-
# @return [String]
|
|
204
|
+
# @param str [#to_s] text with BBCode markup
|
|
205
|
+
# @return [String] text with tags removed
|
|
239
206
|
def unbbcode(str)
|
|
240
207
|
str = str.to_s
|
|
241
208
|
return str.dup unless str.index('[')
|
|
242
209
|
str.gsub(@re_bbcode) do |match_str|
|
|
243
210
|
match = Regexp.last_match(1) or next match_str
|
|
244
|
-
|
|
211
|
+
if match[0] == '\\'
|
|
212
|
+
match[0] = ''
|
|
213
|
+
next "[#{match}]"
|
|
214
|
+
end
|
|
245
215
|
next match_str if (match = match.split).empty?
|
|
246
|
-
next if match.all? { @attr_map[
|
|
216
|
+
next if match.all? { @attr_map[it] }
|
|
217
|
+
match_str
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Escape BBCode tags so they render as literal text after
|
|
222
|
+
# {.bbcode} processing.
|
|
223
|
+
#
|
|
224
|
+
# @see .bbcode
|
|
225
|
+
#
|
|
226
|
+
# @example
|
|
227
|
+
# Terminal::Ansi.escape_bbcode('[bold]Hi[/bold]')
|
|
228
|
+
# # => "[\\bold]Hi[\\/bold]"
|
|
229
|
+
#
|
|
230
|
+
# @param str [#to_s] text with BBCode markup
|
|
231
|
+
# @return [String] text with BBCode tags escaped
|
|
232
|
+
def escape_bbcode(str)
|
|
233
|
+
str = str.to_s
|
|
234
|
+
return str.dup unless str.index('[')
|
|
235
|
+
str.gsub(@re_bbcode) do |match_str|
|
|
236
|
+
fc = match_str[1]
|
|
237
|
+
next match_str if fc == '\\' || fc == ']'
|
|
238
|
+
match = Regexp.last_match(1) or next match_str
|
|
239
|
+
next match_str if (parts = match.split).empty?
|
|
240
|
+
next "[\\#{match}]" if parts.all? { @attr_map[it] }
|
|
247
241
|
match_str
|
|
248
242
|
end
|
|
249
243
|
end
|
|
@@ -251,120 +245,125 @@ module Terminal
|
|
|
251
245
|
#
|
|
252
246
|
# @!endgroup
|
|
253
247
|
#
|
|
254
|
-
# @!group
|
|
248
|
+
# @!group Tool Functions
|
|
255
249
|
#
|
|
256
250
|
|
|
257
|
-
#
|
|
251
|
+
# Test whether a string contains ANSI escape codes.
|
|
258
252
|
#
|
|
259
|
-
# @
|
|
260
|
-
#
|
|
253
|
+
# @example
|
|
254
|
+
# Terminal::Ansi.ansi?("\e[1mHi\e[m") # => true
|
|
255
|
+
# Terminal::Ansi.ansi?("Hello") # => false
|
|
261
256
|
#
|
|
262
|
-
# @param str [#to_s] string to
|
|
263
|
-
# @return [
|
|
264
|
-
def
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
257
|
+
# @param str [#to_s] the string to test
|
|
258
|
+
# @return [true, false]
|
|
259
|
+
def ansi?(str) = @re_test.match?(str.to_s)
|
|
260
|
+
|
|
261
|
+
# Check whether all given attributes are valid.
|
|
262
|
+
#
|
|
263
|
+
# @example
|
|
264
|
+
# Terminal::Ansi.valid?(:bold, :red) # => true
|
|
265
|
+
# Terminal::Ansi.valid?(:nope) # => false
|
|
266
|
+
#
|
|
267
|
+
# @param attributes [Array<String, Symbol, Integer>] attributes to
|
|
268
|
+
# validate
|
|
269
|
+
# @return [true, false]
|
|
270
|
+
def valid?(*attributes)
|
|
271
|
+
attributes.all? do |arg|
|
|
272
|
+
case arg
|
|
273
|
+
when String
|
|
274
|
+
@attr_map[arg]
|
|
275
|
+
when Symbol
|
|
276
|
+
@attrs_map[arg]
|
|
277
|
+
when (0..767)
|
|
278
|
+
true
|
|
275
279
|
end
|
|
276
|
-
|
|
280
|
+
end
|
|
277
281
|
end
|
|
278
282
|
|
|
279
|
-
#
|
|
283
|
+
# Remove both BBCode tags and ANSI escape codes, returning plain
|
|
284
|
+
# text.
|
|
280
285
|
#
|
|
281
|
-
# @
|
|
282
|
-
#
|
|
283
|
-
#
|
|
284
|
-
#
|
|
285
|
-
# @
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
@
|
|
290
|
-
|
|
291
|
-
str.to_s.chars.map! do |char|
|
|
292
|
-
i = (seed + ((pos += 1) / spread)) * frequency
|
|
293
|
-
"\e[38;2;#{(Math.sin(i) * 255).to_i.abs};" \
|
|
294
|
-
"#{(Math.sin(i + @pi2_third) * 255).to_i.abs};" \
|
|
295
|
-
"#{(Math.sin(i + @pi4_third) * 255).to_i.abs}m#{char}"
|
|
296
|
-
end << RESET
|
|
297
|
-
).join
|
|
286
|
+
# @example
|
|
287
|
+
# Terminal::Ansi.plain('[bold]Hello[/bold]') # => "Hello"
|
|
288
|
+
# Terminal::Ansi.plain("\e[1mHello\e[m") # => "Hello"
|
|
289
|
+
#
|
|
290
|
+
# @param str [#to_s] text with BBCode and/or ANSI codes
|
|
291
|
+
# @return [String] plain text
|
|
292
|
+
def plain(str)
|
|
293
|
+
str = unbbcode(str)
|
|
294
|
+
str.gsub!(@re_test, '') if str.index("\e")
|
|
295
|
+
str
|
|
298
296
|
end
|
|
299
297
|
|
|
300
298
|
#
|
|
301
299
|
# @!endgroup
|
|
302
300
|
#
|
|
303
|
-
# @!group
|
|
301
|
+
# @!group ANSI Control Code Generation
|
|
304
302
|
#
|
|
305
303
|
|
|
306
|
-
# Move cursor
|
|
304
|
+
# Move cursor up.
|
|
307
305
|
#
|
|
308
|
-
# @param lines [Integer] number of lines
|
|
309
|
-
# @return [
|
|
306
|
+
# @param lines [Integer] number of lines
|
|
307
|
+
# @return (see .[])
|
|
310
308
|
def cursor_up(lines = 1) = "\e[#{lines}A"
|
|
311
309
|
|
|
312
|
-
# Move cursor
|
|
310
|
+
# Move cursor down.
|
|
313
311
|
#
|
|
314
|
-
# @param (see cursor_up)
|
|
315
|
-
# @return (see
|
|
312
|
+
# @param (see .cursor_up)
|
|
313
|
+
# @return (see .[])
|
|
316
314
|
def cursor_down(lines = 1) = "\e[#{lines}B"
|
|
317
315
|
|
|
318
|
-
# Move cursor
|
|
316
|
+
# Move cursor right.
|
|
319
317
|
#
|
|
320
|
-
# @param columns [Integer] number of columns
|
|
321
|
-
# @return (see
|
|
318
|
+
# @param columns [Integer] number of columns
|
|
319
|
+
# @return (see .[])
|
|
322
320
|
def cursor_forward(columns = 1) = "\e[#{columns}C"
|
|
323
321
|
|
|
324
|
-
# Move cursor
|
|
322
|
+
# Move cursor left.
|
|
325
323
|
#
|
|
326
|
-
# @param (see
|
|
327
|
-
# @return (see
|
|
324
|
+
# @param (see .cursor_forward)
|
|
325
|
+
# @return (see .[])
|
|
328
326
|
def cursor_back(columns = 1) = "\e[#{columns}D"
|
|
329
327
|
|
|
330
|
-
# Move cursor to
|
|
328
|
+
# Move cursor to beginning of line, N lines down.
|
|
331
329
|
#
|
|
332
|
-
# @param (see
|
|
333
|
-
# @return (see
|
|
330
|
+
# @param (see .cursor_up)
|
|
331
|
+
# @return (see .[])
|
|
334
332
|
def cursor_next_line(lines = 1) = "\e[#{lines}E"
|
|
335
333
|
|
|
336
|
-
# Move cursor to
|
|
334
|
+
# Move cursor to beginning of line, N lines up.
|
|
337
335
|
#
|
|
338
|
-
# @param (see
|
|
339
|
-
# @return (see
|
|
336
|
+
# @param (see .cursor_up)
|
|
337
|
+
# @return (see .[])
|
|
340
338
|
def cursor_prev_line(lines = 1) = "\e[#{lines}F"
|
|
341
339
|
|
|
342
|
-
# Move cursor to
|
|
340
|
+
# Move cursor to absolute column.
|
|
343
341
|
#
|
|
344
|
-
# @param column [Integer] column
|
|
345
|
-
# @return (see
|
|
342
|
+
# @param column [Integer] column number
|
|
343
|
+
# @return (see .[])
|
|
346
344
|
def cursor_column(column = 1) = "\e[#{column}G"
|
|
347
345
|
|
|
348
|
-
# Move cursor
|
|
349
|
-
# position.
|
|
350
|
-
# (Skip some columns.)
|
|
346
|
+
# Move cursor right by relative columns.
|
|
351
347
|
#
|
|
352
|
-
# @param
|
|
353
|
-
# @return (see
|
|
348
|
+
# @param column [Integer] number of columns
|
|
349
|
+
# @return (see .[])
|
|
354
350
|
def cursor_column_rel(column = 1) = "\e[#{column}a"
|
|
355
351
|
|
|
356
|
-
# Move cursor
|
|
357
|
-
# (Skip some rows.)
|
|
352
|
+
# Move cursor down by relative rows.
|
|
358
353
|
#
|
|
359
|
-
# @param row [Integer]
|
|
360
|
-
# @return (see
|
|
354
|
+
# @param row [Integer] number of rows
|
|
355
|
+
# @return (see .[])
|
|
361
356
|
def cursor_row_rel(row = 1) = "\e[#{row}e"
|
|
362
357
|
|
|
363
|
-
# Move to
|
|
358
|
+
# Move cursor to absolute position.
|
|
364
359
|
#
|
|
365
|
-
# @
|
|
366
|
-
#
|
|
367
|
-
#
|
|
360
|
+
# @example
|
|
361
|
+
# Terminal::Ansi.cursor_pos(1, 1) # home position
|
|
362
|
+
# Terminal::Ansi.cursor_pos(10, 5) # row 10, column 5
|
|
363
|
+
#
|
|
364
|
+
# @param row [Integer, nil] row number; +nil+ moves to home
|
|
365
|
+
# @param column [Integer, nil] column number
|
|
366
|
+
# @return (see .[])
|
|
368
367
|
def cursor_pos(row, column = nil)
|
|
369
368
|
return column ? "\e[;#{column}H" : "\e[H" unless row
|
|
370
369
|
column ? "\e[#{row};#{column}H" : "\e[#{row}H"
|
|
@@ -372,142 +371,162 @@ module Terminal
|
|
|
372
371
|
|
|
373
372
|
# Show cursor.
|
|
374
373
|
#
|
|
375
|
-
# @return (see
|
|
374
|
+
# @return (see .[])
|
|
376
375
|
def cursor_show = +CURSOR_SHOW
|
|
377
376
|
|
|
378
377
|
# Hide cursor.
|
|
379
378
|
#
|
|
380
|
-
# @return (see
|
|
379
|
+
# @return (see .[])
|
|
381
380
|
def cursor_hide = +CURSOR_HIDE
|
|
382
381
|
|
|
383
382
|
# Save current cursor position.
|
|
384
383
|
#
|
|
385
|
-
# @
|
|
386
|
-
def cursor_save_pos = +CURSOR_POS_SAVE
|
|
387
|
-
|
|
388
|
-
# Restore saved cursor position.
|
|
384
|
+
# @see .cursor_restore_pos
|
|
389
385
|
#
|
|
390
|
-
# @return (see
|
|
391
|
-
def
|
|
386
|
+
# @return (see .[])
|
|
387
|
+
def cursor_save_pos = +CURSOR_POS_SAVE
|
|
392
388
|
|
|
389
|
+
# Restore previously saved cursor position.
|
|
393
390
|
#
|
|
394
|
-
#
|
|
395
|
-
#
|
|
396
|
-
# @!group Screen manipulation
|
|
391
|
+
# @see .cursor_save_pos
|
|
397
392
|
#
|
|
393
|
+
# @return (see .[])
|
|
394
|
+
def cursor_restore_pos = +CURSOR_POS_RESTORE
|
|
398
395
|
|
|
399
|
-
# Erase screen
|
|
396
|
+
# Erase part of the screen.
|
|
400
397
|
#
|
|
401
|
-
# @param part [
|
|
402
|
-
#
|
|
398
|
+
# @param part [Symbol] area to erase:
|
|
399
|
+
# +:all+, +:below+, +:above+, or +:scrollback+
|
|
400
|
+
# @return (see .[])
|
|
403
401
|
def screen_erase(part = :all) = "\e[#{@screen_erase[part]}J"
|
|
404
402
|
|
|
405
|
-
#
|
|
403
|
+
# Save screen state.
|
|
406
404
|
#
|
|
407
|
-
# @
|
|
405
|
+
# @see .screen_restore
|
|
406
|
+
#
|
|
407
|
+
# @return (see .[])
|
|
408
408
|
def screen_save = +SCREEN_SAVE
|
|
409
409
|
|
|
410
|
-
# Restore
|
|
410
|
+
# Restore previously saved screen state.
|
|
411
|
+
#
|
|
412
|
+
# @see .screen_save
|
|
411
413
|
#
|
|
412
|
-
# @return (see
|
|
414
|
+
# @return (see .[])
|
|
413
415
|
def screen_restore = +SCREEN_RESTORE
|
|
414
416
|
|
|
415
|
-
#
|
|
417
|
+
# Switch to the alternate screen buffer.
|
|
418
|
+
#
|
|
419
|
+
# @see .screen_alternate_off
|
|
416
420
|
#
|
|
417
|
-
# @return (see
|
|
421
|
+
# @return (see .[])
|
|
418
422
|
def screen_alternate = +SCREEN_ALTERNATE
|
|
419
423
|
|
|
420
|
-
#
|
|
424
|
+
# Switch back from the alternate screen buffer.
|
|
421
425
|
#
|
|
422
|
-
# @
|
|
426
|
+
# @see .screen_alternate
|
|
427
|
+
#
|
|
428
|
+
# @return (see .[])
|
|
423
429
|
def screen_alternate_off = +SCREEN_ALTERNATE_OFF
|
|
424
430
|
|
|
425
|
-
# Scroll
|
|
431
|
+
# Scroll the screen up.
|
|
426
432
|
#
|
|
427
|
-
# @param
|
|
428
|
-
# @return (see
|
|
433
|
+
# @param (see .cursor_up)
|
|
434
|
+
# @return (see .[])
|
|
429
435
|
def screen_scroll_up(lines = 1) = "\e[#{lines}S"
|
|
430
436
|
|
|
431
|
-
# Scroll
|
|
437
|
+
# Scroll the screen down.
|
|
432
438
|
#
|
|
433
|
-
# @param (see
|
|
434
|
-
# @return (see
|
|
439
|
+
# @param (see .cursor_up)
|
|
440
|
+
# @return (see .[])
|
|
435
441
|
def screen_scroll_down(lines = 1) = "\e[#{lines}T"
|
|
436
442
|
|
|
443
|
+
# Repeat the last printed character.
|
|
437
444
|
#
|
|
438
|
-
#
|
|
445
|
+
# @param count [Integer] number of repetitions
|
|
446
|
+
# @return (see .[])
|
|
447
|
+
def char_repeat(count = 1) = "\e[#{count}b"
|
|
448
|
+
|
|
449
|
+
# Erase part of the current line.
|
|
450
|
+
#
|
|
451
|
+
# @param part [Symbol] area to erase:
|
|
452
|
+
# +:all+, +:to_end+, or +:to_start+
|
|
453
|
+
# @return (see .[])
|
|
454
|
+
def line_erase(part = :all) = "\e[#{@line_erase[part]}K"
|
|
455
|
+
|
|
456
|
+
# Set the terminal window title.
|
|
457
|
+
# @note Supported by Hyper, iTerm2, Kitty, macOS Terminal, Tabby,
|
|
458
|
+
# WezTerm.
|
|
439
459
|
#
|
|
440
|
-
#
|
|
460
|
+
# @example
|
|
461
|
+
# Terminal.raw_write(Terminal::Ansi.title('My App'))
|
|
441
462
|
#
|
|
463
|
+
# @param title [String] the title text
|
|
464
|
+
# @return (see .[])
|
|
465
|
+
def title(title) = "\e]0;#{title}\a"
|
|
442
466
|
|
|
443
|
-
#
|
|
467
|
+
# Start a hyperlink (OSC 8).
|
|
444
468
|
#
|
|
445
|
-
# @
|
|
446
|
-
#
|
|
447
|
-
|
|
469
|
+
# @note Supported by Ghostty, iTerm2, Kitty, Rio, Tabby, WezTerm.
|
|
470
|
+
#
|
|
471
|
+
# @see .link
|
|
472
|
+
# @see .link_end
|
|
473
|
+
#
|
|
474
|
+
# @param url [#to_s] the link URL
|
|
475
|
+
# @param params [Hash] optional link parameters (e.g., +id:+)
|
|
476
|
+
# @return (see .[])
|
|
477
|
+
def link_start(url, **params)
|
|
478
|
+
"\e]8;#{params.map { it.join('=') }.join(':')};#{url}\a"
|
|
479
|
+
end
|
|
448
480
|
|
|
449
|
-
#
|
|
481
|
+
# End a hyperlink.
|
|
450
482
|
#
|
|
451
|
-
# @
|
|
452
|
-
#
|
|
453
|
-
|
|
483
|
+
# @see .link_start
|
|
484
|
+
#
|
|
485
|
+
# @return (see .[])
|
|
486
|
+
def link_end = +LINK_END
|
|
454
487
|
|
|
455
|
-
#
|
|
456
|
-
#
|
|
457
|
-
#
|
|
458
|
-
#
|
|
459
|
-
#
|
|
460
|
-
#
|
|
461
|
-
#
|
|
462
|
-
#
|
|
463
|
-
#
|
|
464
|
-
# @param
|
|
465
|
-
# @return (see
|
|
466
|
-
def
|
|
488
|
+
# Create a complete hyperlink (OSC 8).
|
|
489
|
+
#
|
|
490
|
+
# @see .link_start
|
|
491
|
+
# @see .link_end
|
|
492
|
+
#
|
|
493
|
+
# @example
|
|
494
|
+
# Terminal::Ansi.link('https://example.com', 'Example')
|
|
495
|
+
#
|
|
496
|
+
# @param (see .link_start)
|
|
497
|
+
# @param text [String] the visible link text
|
|
498
|
+
# @return (see .[])
|
|
499
|
+
def link(url, text, **params)
|
|
500
|
+
"#{link_start(url, **params)}#{text}#{LINK_END}"
|
|
501
|
+
end
|
|
467
502
|
|
|
468
|
-
#
|
|
469
|
-
#
|
|
470
|
-
#
|
|
471
|
-
#
|
|
472
|
-
#
|
|
473
|
-
#
|
|
474
|
-
#
|
|
475
|
-
#
|
|
476
|
-
#
|
|
477
|
-
# @param url [#to_s] URL to link to
|
|
478
|
-
# @param text [#to_s] text to display for the link
|
|
479
|
-
# @return (see cursor_up)
|
|
480
|
-
def link(url, text) = "\e]8;;#{url}\a#{text}\e]8;;\a"
|
|
481
|
-
|
|
482
|
-
# Show a simple notification.
|
|
483
|
-
# This is not widely supported; works for
|
|
484
|
-
# Ghostty,
|
|
485
|
-
# iTerm2,
|
|
486
|
-
# Kitty,
|
|
487
|
-
# WezTerm.
|
|
488
|
-
#
|
|
489
|
-
# @param text [#to_s] text to display
|
|
490
|
-
# @return (see cursor_up)
|
|
503
|
+
# Send a desktop notification via the terminal.
|
|
504
|
+
# @note Supported by Ghostty, iTerm2, Kitty, WezTerm.
|
|
505
|
+
#
|
|
506
|
+
# @example
|
|
507
|
+
# Terminal.raw_write(Terminal::Ansi.notify('Build complete!'))
|
|
508
|
+
#
|
|
509
|
+
# @param text [to_s] the notification text
|
|
510
|
+
# @return (see .[])
|
|
491
511
|
def notify(text) = "\e]9;#{text}\a"
|
|
492
512
|
|
|
493
|
-
#
|
|
494
|
-
#
|
|
495
|
-
#
|
|
496
|
-
# iTerm2,
|
|
497
|
-
#
|
|
498
|
-
#
|
|
499
|
-
#
|
|
500
|
-
#
|
|
501
|
-
#
|
|
502
|
-
#
|
|
503
|
-
#
|
|
504
|
-
# - `:error
|
|
505
|
-
#
|
|
506
|
-
#
|
|
507
|
-
#
|
|
508
|
-
#
|
|
509
|
-
#
|
|
510
|
-
# @return (see cursor_up)
|
|
513
|
+
# Show or update a progress indicator in the terminal tab/title
|
|
514
|
+
# bar.
|
|
515
|
+
#
|
|
516
|
+
# @note Supported by Ghostty, iTerm2, Kitty.
|
|
517
|
+
#
|
|
518
|
+
# @example
|
|
519
|
+
# Terminal.raw_write(Terminal::Ansi.progress(50)) # 50%
|
|
520
|
+
# Terminal.raw_write(Terminal::Ansi.progress(:error)) # error state
|
|
521
|
+
# Terminal.raw_write(Terminal::Ansi.progress(nil)) # hide
|
|
522
|
+
#
|
|
523
|
+
# @param state [Symbol, Numeric, Boolean] progress state:
|
|
524
|
+
# - +:show+ or +true+ to show, `:err`/`:error`, `:warn`/`:warning`,
|
|
525
|
+
# +:indeterminate+, a +Numeric+ (0-100) to set percentage, or
|
|
526
|
+
# any other value to hide
|
|
527
|
+
# @param percent [Integer] progress percentage (0-100, used with
|
|
528
|
+
# +:show+ state)
|
|
529
|
+
# @return (see .[])
|
|
511
530
|
def progress(state, percent = 0)
|
|
512
531
|
case state
|
|
513
532
|
when :show, true
|
|
@@ -526,32 +545,28 @@ module Terminal
|
|
|
526
545
|
"\e]9;4;#{state};#{percent.to_i.clamp(0, 100)}\a"
|
|
527
546
|
end
|
|
528
547
|
|
|
529
|
-
#
|
|
530
|
-
# It uses the
|
|
548
|
+
# Scale text using the
|
|
531
549
|
# [text sizing protocol](https://sw.kovidgoyal.net/kitty/text-sizing-protocol).
|
|
532
|
-
#
|
|
550
|
+
#
|
|
551
|
+
# @note Only supported by Kitty.
|
|
533
552
|
#
|
|
534
553
|
# @example Double-height Greeting
|
|
535
554
|
# Terminal::Ansi.scale('Hello Ruby!', scale: 2)
|
|
536
555
|
#
|
|
537
556
|
# @example Half-height Greeting
|
|
538
|
-
# Terminal::Ansi.scale('Hello Ruby!', fracn: 1, fracd: 2, vertical: :
|
|
539
|
-
#
|
|
540
|
-
# @param text [#to_s]
|
|
541
|
-
#
|
|
542
|
-
# @param
|
|
543
|
-
#
|
|
544
|
-
# @param
|
|
545
|
-
#
|
|
546
|
-
# @param
|
|
547
|
-
#
|
|
548
|
-
# @param
|
|
549
|
-
#
|
|
550
|
-
# @
|
|
551
|
-
# vertical alignment to use for fractionally scaled text
|
|
552
|
-
# @param horizontal [:left, :right, :centered, nil]
|
|
553
|
-
# horizontal alignment to use for fractionally scaled text
|
|
554
|
-
# @return (see cursor_up)
|
|
557
|
+
# Terminal::Ansi.scale('Hello Ruby!', fracn: 1, fracd: 2, vertical: :middle)
|
|
558
|
+
#
|
|
559
|
+
# @param text [#to_s] text to scale
|
|
560
|
+
# @param scale [Integer, nil] scale multiplier (1-7)
|
|
561
|
+
# @param width [Integer, nil] width mode (0-7)
|
|
562
|
+
# @param fracn [Integer, nil] fractional numerator (0-15)
|
|
563
|
+
# @param fracd [Integer, nil] fractional denominator
|
|
564
|
+
# (must be > fracn, max 15)
|
|
565
|
+
# @param vertical [Symbol, Integer, nil] vertical alignment:
|
|
566
|
+
# +:top+, +:bottom+, +:middle+
|
|
567
|
+
# @param horizontal [Symbol, Integer, nil] horizontal alignment:
|
|
568
|
+
# +:left+, +:right+, +:center+
|
|
569
|
+
# @return (see .[])
|
|
555
570
|
def scale(
|
|
556
571
|
text,
|
|
557
572
|
scale: nil,
|
|
@@ -571,7 +586,7 @@ module Terminal
|
|
|
571
586
|
opts << 'v=0'
|
|
572
587
|
when 1, :bottom
|
|
573
588
|
opts << 'v=1'
|
|
574
|
-
when 2, :
|
|
589
|
+
when 2, :middle
|
|
575
590
|
opts << 'v=2'
|
|
576
591
|
end
|
|
577
592
|
case horizontal
|
|
@@ -579,7 +594,7 @@ module Terminal
|
|
|
579
594
|
opts << 'h=0'
|
|
580
595
|
when 1, :right
|
|
581
596
|
opts << 'h=1'
|
|
582
|
-
when 2, :
|
|
597
|
+
when 2, :center
|
|
583
598
|
opts << 'h=2'
|
|
584
599
|
end
|
|
585
600
|
end
|
|
@@ -601,10 +616,6 @@ module Terminal
|
|
|
601
616
|
end
|
|
602
617
|
end
|
|
603
618
|
|
|
604
|
-
@cbase = { 'bg' => '48', 'on' => '48', 'ul' => '58' }
|
|
605
|
-
@cbase.default = '38'
|
|
606
|
-
@cbase.freeze
|
|
607
|
-
|
|
608
619
|
@re_test =
|
|
609
620
|
/
|
|
610
621
|
(?:\e\[[\x30-\x3f]*[\x20-\x2f]*[a-zA-Z])
|
|
@@ -616,139 +627,128 @@ module Terminal
|
|
|
616
627
|
|
|
617
628
|
clr_map = {
|
|
618
629
|
# foreground
|
|
619
|
-
'
|
|
620
|
-
'
|
|
621
|
-
'
|
|
622
|
-
'
|
|
623
|
-
'
|
|
624
|
-
'
|
|
625
|
-
'
|
|
626
|
-
'
|
|
627
|
-
'
|
|
630
|
+
'black' => '30',
|
|
631
|
+
'red' => '31',
|
|
632
|
+
'green' => '32',
|
|
633
|
+
'yellow' => '33',
|
|
634
|
+
'blue' => '34',
|
|
635
|
+
'magenta' => '35',
|
|
636
|
+
'cyan' => '36',
|
|
637
|
+
'white' => '37',
|
|
638
|
+
'default' => '39',
|
|
628
639
|
# background
|
|
629
|
-
'
|
|
630
|
-
'
|
|
631
|
-
'
|
|
632
|
-
'
|
|
633
|
-
'
|
|
634
|
-
'
|
|
635
|
-
'
|
|
636
|
-
'
|
|
637
|
-
'
|
|
640
|
+
'on_black' => '40',
|
|
641
|
+
'on_red' => '41',
|
|
642
|
+
'on_green' => '42',
|
|
643
|
+
'on_yellow' => '43',
|
|
644
|
+
'on_blue' => '44',
|
|
645
|
+
'on_magenta' => '45',
|
|
646
|
+
'on_cyan' => '46',
|
|
647
|
+
'on_white' => '47',
|
|
648
|
+
'on_default' => '49',
|
|
638
649
|
# underline
|
|
639
|
-
'58;2;0;0;0'
|
|
640
|
-
'58;2;0;0;128'
|
|
641
|
-
'58;2;0;0;255'
|
|
642
|
-
'58;2;0;128;0'
|
|
643
|
-
'58;2;0;128;128'
|
|
644
|
-
'58;2;0;255;0'
|
|
645
|
-
'58;2;0;255;255'
|
|
646
|
-
'58;2;128;0;0'
|
|
647
|
-
'58;2;128;0;128'
|
|
648
|
-
'58;2;128;128;0'
|
|
649
|
-
'58;2;128;128;128'
|
|
650
|
-
'58;2;255;0;0'
|
|
651
|
-
'58;2;255;0;255'
|
|
652
|
-
'58;2;255;255;0'
|
|
653
|
-
'58;2;255;255;255'
|
|
654
|
-
'58;2;64;64;64'
|
|
655
|
-
'
|
|
650
|
+
'ul_black' => '58;2;0;0;0',
|
|
651
|
+
'ul_blue' => '58;2;0;0;128',
|
|
652
|
+
'ul_bright_blue' => '58;2;0;0;255',
|
|
653
|
+
'ul_green' => '58;2;0;128;0',
|
|
654
|
+
'ul_cyan' => '58;2;0;128;128',
|
|
655
|
+
'ul_bright_green' => '58;2;0;255;0',
|
|
656
|
+
'ul_bright_cyan' => '58;2;0;255;255',
|
|
657
|
+
'ul_red' => '58;2;128;0;0',
|
|
658
|
+
'ul_magenta' => '58;2;128;0;128',
|
|
659
|
+
'ul_yellow' => '58;2;128;128;0',
|
|
660
|
+
'ul_white' => '58;2;128;128;128',
|
|
661
|
+
'ul_bright_red' => '58;2;255;0;0',
|
|
662
|
+
'ul_bright_magenta' => '58;2;255;0;255',
|
|
663
|
+
'ul_bright_yellow' => '58;2;255;255;0',
|
|
664
|
+
'ul_bright_white' => '58;2;255;255;255',
|
|
665
|
+
'ul_bright_black' => '58;2;64;64;64',
|
|
666
|
+
'ul_default' => '59',
|
|
656
667
|
# bright foreground
|
|
657
|
-
'
|
|
658
|
-
'
|
|
659
|
-
'
|
|
660
|
-
'
|
|
661
|
-
'
|
|
662
|
-
'
|
|
663
|
-
'
|
|
664
|
-
'
|
|
668
|
+
'bright_black' => '90',
|
|
669
|
+
'bright_red' => '91',
|
|
670
|
+
'bright_green' => '92',
|
|
671
|
+
'bright_yellow' => '93',
|
|
672
|
+
'bright_blue' => '94',
|
|
673
|
+
'bright_magenta' => '95',
|
|
674
|
+
'bright_cyan' => '96',
|
|
675
|
+
'bright_white' => '97',
|
|
665
676
|
# bright background
|
|
666
|
-
'
|
|
667
|
-
'
|
|
668
|
-
'
|
|
669
|
-
'
|
|
670
|
-
'
|
|
671
|
-
'
|
|
672
|
-
'
|
|
673
|
-
'
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
%w[black red green yellow blue magenta cyan white].each do |name|
|
|
678
|
-
clr_alias["fg_#{name}", name]
|
|
679
|
-
clr_alias["bg_#{name}", "on_#{name}"]
|
|
680
|
-
clr_alias["fg_bright_#{name}", "bright_#{name}"]
|
|
681
|
-
clr_alias["bg_bright_#{name}", "on_bright_#{name}"]
|
|
682
|
-
end
|
|
683
|
-
clr_alias['fg_default', 'default']
|
|
684
|
-
clr_alias['bg_default', 'on_default']
|
|
677
|
+
'on_bright_black' => '100',
|
|
678
|
+
'on_bright_red' => '101',
|
|
679
|
+
'on_bright_green' => '102',
|
|
680
|
+
'on_bright_yellow' => '103',
|
|
681
|
+
'on_bright_blue' => '104',
|
|
682
|
+
'on_bright_magenta' => '105',
|
|
683
|
+
'on_bright_cyan' => '106',
|
|
684
|
+
'on_bright_white' => '107'
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
@colors = clr_map.keys.map!(&:to_sym).sort!.freeze
|
|
685
688
|
|
|
686
689
|
attr_map = {
|
|
687
|
-
'' => '
|
|
688
|
-
'
|
|
689
|
-
'
|
|
690
|
-
'
|
|
691
|
-
'
|
|
692
|
-
'
|
|
693
|
-
'
|
|
694
|
-
'
|
|
695
|
-
'
|
|
696
|
-
'
|
|
697
|
-
'
|
|
698
|
-
'
|
|
699
|
-
'
|
|
700
|
-
'
|
|
701
|
-
'
|
|
702
|
-
'
|
|
703
|
-
'
|
|
704
|
-
'
|
|
705
|
-
'
|
|
706
|
-
'
|
|
707
|
-
'
|
|
708
|
-
'
|
|
709
|
-
'
|
|
710
|
-
'
|
|
711
|
-
'
|
|
712
|
-
'
|
|
713
|
-
'
|
|
714
|
-
'
|
|
715
|
-
'
|
|
716
|
-
'
|
|
717
|
-
|
|
718
|
-
'
|
|
719
|
-
'
|
|
720
|
-
'
|
|
721
|
-
|
|
722
|
-
'
|
|
723
|
-
'
|
|
690
|
+
'reset' => '',
|
|
691
|
+
'bold' => '1',
|
|
692
|
+
'faint' => '2',
|
|
693
|
+
'italic' => '3',
|
|
694
|
+
'underline' => '4',
|
|
695
|
+
'blink' => '5',
|
|
696
|
+
'rapid_blink' => '6',
|
|
697
|
+
'invert' => '7',
|
|
698
|
+
'hide' => '8',
|
|
699
|
+
'strike' => '9',
|
|
700
|
+
'primary_font' => '10',
|
|
701
|
+
'font1' => '11',
|
|
702
|
+
'font2' => '12',
|
|
703
|
+
'font3' => '13',
|
|
704
|
+
'font4' => '14',
|
|
705
|
+
'font5' => '15',
|
|
706
|
+
'font6' => '16',
|
|
707
|
+
'font7' => '17',
|
|
708
|
+
'font8' => '18',
|
|
709
|
+
'font9' => '19',
|
|
710
|
+
'fraktur' => '20',
|
|
711
|
+
'double_underline' => '21',
|
|
712
|
+
'bold_off' => '22',
|
|
713
|
+
'faint_off' => '22',
|
|
714
|
+
'italic_off' => '23',
|
|
715
|
+
'fraktur_off' => '23',
|
|
716
|
+
'underline_off' => '24',
|
|
717
|
+
'double_underline_off' => '24',
|
|
718
|
+
'blink_off' => '25',
|
|
719
|
+
'rapid_blink_off' => '25',
|
|
720
|
+
'proportional' => '26',
|
|
721
|
+
'invert_off' => '27',
|
|
722
|
+
'hide_off' => '28',
|
|
723
|
+
'strike_off' => '29',
|
|
724
|
+
# ...
|
|
725
|
+
'proportional_off' => '50',
|
|
726
|
+
'framed' => '51',
|
|
727
|
+
'encircled' => '52',
|
|
728
|
+
'overlined' => '53',
|
|
729
|
+
'framed_off' => '54',
|
|
730
|
+
'encircled_off' => '54',
|
|
731
|
+
'overlined_off' => '55',
|
|
724
732
|
# ...
|
|
725
|
-
'
|
|
726
|
-
'
|
|
727
|
-
'
|
|
733
|
+
'superscript' => '73',
|
|
734
|
+
'subscript' => '74',
|
|
735
|
+
'superscript_off' => '75',
|
|
736
|
+
'subscript_off' => '75',
|
|
728
737
|
# special underline
|
|
729
|
-
'
|
|
730
|
-
'
|
|
731
|
-
'
|
|
732
|
-
'
|
|
733
|
-
|
|
734
|
-
|
|
738
|
+
'curly_underline' => '4:3',
|
|
739
|
+
'dotted_underline' => '4:4',
|
|
740
|
+
'dashed_underline' => '4:5',
|
|
741
|
+
'curly_underline_off' => '4:0',
|
|
742
|
+
'dotted_underline_off' => '4:0',
|
|
743
|
+
'dashed_underline_off' => '4:0'
|
|
744
|
+
}
|
|
735
745
|
|
|
736
|
-
attr_alias
|
|
737
|
-
attr_alias['fraktur_off', 'italic_off']
|
|
738
|
-
attr_alias['double_underline_off', 'underline_off']
|
|
739
|
-
attr_alias['rapid_blink_off', 'blink_off']
|
|
740
|
-
attr_alias['encircled_off', 'framed_off']
|
|
741
|
-
attr_alias['subscript_off', 'superscript_off']
|
|
742
|
-
attr_alias['dotted_underline_off', 'curly_underline_off']
|
|
743
|
-
attr_alias['dashed_underline_off', 'curly_underline_off']
|
|
746
|
+
attr_alias = ->(t, s) { attr_map[t] = attr_map[s] }
|
|
744
747
|
|
|
745
|
-
#
|
|
748
|
+
# aliases:
|
|
746
749
|
attr_alias['off', 'reset']
|
|
747
750
|
attr_alias['dim', 'faint']
|
|
748
751
|
attr_alias['dim_off', 'faint_off']
|
|
749
|
-
attr_alias['conceal', 'hide']
|
|
750
|
-
attr_alias['conceal_off', 'hide_off']
|
|
751
|
-
attr_alias['reveal', 'hide_off']
|
|
752
752
|
attr_alias['spacing', 'proportional']
|
|
753
753
|
attr_alias['spacing_off', 'proportional_off']
|
|
754
754
|
|
|
@@ -761,20 +761,26 @@ module Terminal
|
|
|
761
761
|
attr_alias['h', 'hide']
|
|
762
762
|
attr_alias['s', 'strike']
|
|
763
763
|
attr_alias['uu', 'double_underline']
|
|
764
|
-
attr_alias['ovr', 'overlined']
|
|
765
|
-
attr_alias['sup', 'superscript']
|
|
766
|
-
attr_alias['sub', 'subscript']
|
|
767
764
|
attr_alias['cu', 'curly_underline']
|
|
768
765
|
attr_alias['dau', 'dashed_underline']
|
|
769
766
|
attr_alias['dou', 'dotted_underline']
|
|
767
|
+
attr_alias['ovr', 'overlined']
|
|
768
|
+
attr_alias['sup', 'superscript']
|
|
769
|
+
attr_alias['sub', 'subscript']
|
|
770
770
|
|
|
771
|
-
@colors = clr_map.keys.map!(&:to_sym).sort!.freeze
|
|
772
771
|
@attributes = attr_map.keys.map!(&:to_sym).sort!.freeze
|
|
773
772
|
|
|
774
773
|
# shortcuts disable:
|
|
775
774
|
attr_map.keys.each do |n|
|
|
776
775
|
attr_alias["/#{n.delete_suffix('_off')}", n] if n.end_with?('_off')
|
|
777
776
|
end
|
|
777
|
+
|
|
778
|
+
# additional shortcuts disable:
|
|
779
|
+
attr_alias['/', 'reset']
|
|
780
|
+
attr_map['/bg'] = clr_map['on_default']
|
|
781
|
+
attr_map['/fg'] = clr_map['default']
|
|
782
|
+
attr_map['/ul'] = clr_map['ul_default']
|
|
783
|
+
|
|
778
784
|
attr_alias['/b', 'bold_off']
|
|
779
785
|
attr_alias['/d', 'dim_off']
|
|
780
786
|
attr_alias['/i', 'italic_off']
|
|
@@ -783,26 +789,24 @@ module Terminal
|
|
|
783
789
|
attr_alias['/h', 'hide_off']
|
|
784
790
|
attr_alias['/s', 'strike_off']
|
|
785
791
|
attr_alias['/uu', 'double_underline_off']
|
|
786
|
-
attr_alias['/ovr', 'overlined_off']
|
|
787
|
-
attr_alias['/sup', 'superscript_off']
|
|
788
|
-
attr_alias['/sub', 'subscript_off']
|
|
789
792
|
attr_alias['/cu', 'curly_underline_off']
|
|
790
793
|
attr_alias['/dau', 'dashed_underline_off']
|
|
791
794
|
attr_alias['/dou', 'dotted_underline_off']
|
|
795
|
+
attr_alias['/ovr', 'overlined_off']
|
|
796
|
+
attr_alias['/sup', 'superscript_off']
|
|
797
|
+
attr_alias['/sub', 'subscript_off']
|
|
792
798
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
attr_map['/bg'] = clr_map['on_default']
|
|
797
|
-
attr_map['/ul'] = clr_map['ul_default']
|
|
799
|
+
@cbase = { 'on' => '48', 'ul' => '58' }
|
|
800
|
+
@cbase.default = '38'
|
|
801
|
+
@cbase.freeze
|
|
798
802
|
|
|
799
803
|
@attr_map =
|
|
800
804
|
Hash.new do |_, str|
|
|
801
|
-
b, v = /\A(
|
|
805
|
+
b, v = /\A(on|ul)?_?#?([[:xdigit:]]{1,6})\z/.match(str)&.captures
|
|
802
806
|
unless v
|
|
803
|
-
b = /\A(
|
|
804
|
-
|
|
805
|
-
next "#{@cbase[b[1]]};#{
|
|
807
|
+
b = /\A(on|ul)?_?([a-z]{3,}[0-9]{0,3})\z/.match(str) or next
|
|
808
|
+
v = NAMED_COLORS[b[2]] or next
|
|
809
|
+
next "#{@cbase[b[1]]};#{v}"
|
|
806
810
|
end
|
|
807
811
|
case v.size
|
|
808
812
|
when 1, 2
|
|
@@ -813,7 +817,7 @@ module Terminal
|
|
|
813
817
|
"#{@cbase[b]};2;#{v[0, 2].hex};#{v[2, 2].hex};#{v[4, 2].hex}"
|
|
814
818
|
end
|
|
815
819
|
end
|
|
816
|
-
attr_map.merge!(clr_map).keys.sort!.each { @attr_map[
|
|
820
|
+
attr_map.merge!(clr_map).keys.sort!.each { @attr_map[it] = attr_map[it] }
|
|
817
821
|
@attr_map.freeze
|
|
818
822
|
|
|
819
823
|
@attrs_map = @attr_map.transform_keys(&:to_sym)
|
|
@@ -828,12 +832,15 @@ module Terminal
|
|
|
828
832
|
@line_erase.default = '2'
|
|
829
833
|
@line_erase.compare_by_identity.freeze
|
|
830
834
|
|
|
831
|
-
autoload :NAMED_COLORS, "#{__dir__}/ansi/named_colors.rb"
|
|
832
|
-
private_constant :NAMED_COLORS
|
|
833
|
-
|
|
834
835
|
# @private
|
|
835
836
|
RESET = -self[:reset]
|
|
836
837
|
|
|
838
|
+
# @private
|
|
839
|
+
RESET_FG = -self[:default]
|
|
840
|
+
|
|
841
|
+
# @private
|
|
842
|
+
RESET_BG = -self[:on_default]
|
|
843
|
+
|
|
837
844
|
# @private
|
|
838
845
|
FULL_RESET = "\ec"
|
|
839
846
|
|
|
@@ -843,6 +850,13 @@ module Terminal
|
|
|
843
850
|
CURSOR_FIRST_ROW = -cursor_pos(1)
|
|
844
851
|
# @private
|
|
845
852
|
CURSOR_FIRST_COLUMN = -cursor_column(1)
|
|
853
|
+
# @private
|
|
854
|
+
CURSOR_NEXT_LINE = -cursor_next_line(nil)
|
|
855
|
+
|
|
856
|
+
# @private
|
|
857
|
+
CURSOR_BACK = -cursor_back(nil)
|
|
858
|
+
# @private
|
|
859
|
+
CURSOR_FORWARD = -cursor_forward(nil)
|
|
846
860
|
|
|
847
861
|
# @private
|
|
848
862
|
CURSOR_SHOW = "\e[?25h"
|
|
@@ -893,6 +907,9 @@ module Terminal
|
|
|
893
907
|
# @private
|
|
894
908
|
LINE_ERASE_PREV = -"#{cursor_prev_line(nil)}#{LINE_ERASE}"
|
|
895
909
|
|
|
910
|
+
# @private
|
|
911
|
+
LINK_END = "\e]8;;\a"
|
|
912
|
+
|
|
896
913
|
# @private
|
|
897
914
|
PROGRESS_HIDE = "\e]9;4;0;0\a"
|
|
898
915
|
# @private
|
|
@@ -929,5 +946,10 @@ module Terminal
|
|
|
929
946
|
# https://sw.kovidgoyal.net/kitty/color-stack
|
|
930
947
|
# https://sw.kovidgoyal.net/kitty/deccara
|
|
931
948
|
# https://sw.kovidgoyal.net/kitty/clipboard
|
|
949
|
+
|
|
950
|
+
dir = "#{__dir__}/ansi"
|
|
951
|
+
autoload :NAMED_COLORS, "#{dir}/named_colors.rb"
|
|
952
|
+
autoload :ScreenViewer, "#{dir}/screen_viewer.rb"
|
|
953
|
+
private_constant :NAMED_COLORS
|
|
932
954
|
end
|
|
933
955
|
end
|