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/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,155 @@ 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
|
-
str = str.to_s
|
|
221
|
-
return str.dup unless str.index('[')
|
|
187
|
+
return str.dup unless (str = str.to_s).index('[')
|
|
222
188
|
str.gsub(@re_bbcode) do |match_str|
|
|
223
189
|
match = Regexp.last_match(1) or next match_str
|
|
224
|
-
next
|
|
225
|
-
|
|
190
|
+
next try_convert(match) || match_str if match[0] != '\\'
|
|
191
|
+
match[0] = ''
|
|
192
|
+
"[#{match}]"
|
|
226
193
|
end
|
|
227
194
|
end
|
|
228
195
|
|
|
229
|
-
# Remove
|
|
196
|
+
# Remove BBCode tags from a string, keeping the enclosed text.
|
|
230
197
|
#
|
|
231
|
-
# @
|
|
232
|
-
# Terminal::Ansi.unbbcode "[b]Bold[/b] Text"
|
|
233
|
-
# # => "Bold Text"
|
|
198
|
+
# @see .bbcode
|
|
234
199
|
#
|
|
235
|
-
# @
|
|
200
|
+
# @example
|
|
201
|
+
# Terminal::Ansi.unbbcode('[bold]Hello[/bold]') # => "Hello"
|
|
236
202
|
#
|
|
237
|
-
# @param str [#to_s]
|
|
238
|
-
# @return [String]
|
|
203
|
+
# @param str [#to_s] text with BBCode markup
|
|
204
|
+
# @return [String] text with tags removed
|
|
239
205
|
def unbbcode(str)
|
|
240
|
-
str = str.to_s
|
|
241
|
-
return str.dup unless str.index('[')
|
|
206
|
+
return str.dup unless (str = str.to_s).index('[')
|
|
242
207
|
str.gsub(@re_bbcode) do |match_str|
|
|
243
208
|
match = Regexp.last_match(1) or next match_str
|
|
244
|
-
|
|
209
|
+
if match[0] == '\\'
|
|
210
|
+
match[0] = ''
|
|
211
|
+
next "[#{match}]"
|
|
212
|
+
end
|
|
245
213
|
next match_str if (match = match.split).empty?
|
|
246
|
-
next if match.all? { @attr_map[
|
|
214
|
+
next if match.all? { @attr_map[it] }
|
|
215
|
+
match_str
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Escape BBCode tags so they render as literal text after
|
|
220
|
+
# {.bbcode} processing.
|
|
221
|
+
#
|
|
222
|
+
# @see .bbcode
|
|
223
|
+
#
|
|
224
|
+
# @example
|
|
225
|
+
# Terminal::Ansi.escape_bbcode('[bold]Hi[/bold]')
|
|
226
|
+
# # => "[\\bold]Hi[\\/bold]"
|
|
227
|
+
#
|
|
228
|
+
# @param str [#to_s] text with BBCode markup
|
|
229
|
+
# @return [String] text with BBCode tags escaped
|
|
230
|
+
def escape_bbcode(str)
|
|
231
|
+
return str.dup unless (str = str.to_s).index('[')
|
|
232
|
+
str.gsub(@re_bbcode) do |match_str|
|
|
233
|
+
fc = match_str[1]
|
|
234
|
+
next match_str if fc == '\\' || fc == ']'
|
|
235
|
+
match = Regexp.last_match(1) or next match_str
|
|
236
|
+
next match_str if (parts = match.split).empty?
|
|
237
|
+
next "[\\#{match}]" if parts.all? { @attr_map[it] }
|
|
247
238
|
match_str
|
|
248
239
|
end
|
|
249
240
|
end
|
|
@@ -251,120 +242,124 @@ module Terminal
|
|
|
251
242
|
#
|
|
252
243
|
# @!endgroup
|
|
253
244
|
#
|
|
254
|
-
# @!group
|
|
245
|
+
# @!group Tool Functions
|
|
246
|
+
#
|
|
247
|
+
|
|
248
|
+
# Test whether a string contains ANSI escape codes.
|
|
249
|
+
#
|
|
250
|
+
# @example
|
|
251
|
+
# Terminal::Ansi.ansi?("\e[1mHi\e[m") # => true
|
|
252
|
+
# Terminal::Ansi.ansi?("Hello") # => false
|
|
255
253
|
#
|
|
254
|
+
# @param str [#to_s] the string to test
|
|
255
|
+
# @return [true, false]
|
|
256
|
+
def ansi?(str) = @re_test.match?(str.to_s)
|
|
256
257
|
|
|
257
|
-
#
|
|
258
|
+
# Check whether all given attributes are valid.
|
|
258
259
|
#
|
|
259
|
-
# @
|
|
260
|
-
#
|
|
260
|
+
# @example
|
|
261
|
+
# Terminal::Ansi.valid?(:bold, :red) # => true
|
|
262
|
+
# Terminal::Ansi.valid?(:nope) # => false
|
|
261
263
|
#
|
|
262
|
-
# @param
|
|
263
|
-
#
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
match_str
|
|
264
|
+
# @param attributes [Array<String, Symbol, Integer>] attributes to
|
|
265
|
+
# validate
|
|
266
|
+
# @return [true, false]
|
|
267
|
+
def valid?(*attributes)
|
|
268
|
+
attributes.all? do |arg|
|
|
269
|
+
case arg
|
|
270
|
+
when String
|
|
271
|
+
@attr_map[arg]
|
|
272
|
+
when Symbol
|
|
273
|
+
@attrs_map[arg]
|
|
274
|
+
when (0..767)
|
|
275
|
+
true
|
|
275
276
|
end
|
|
276
|
-
|
|
277
|
+
end
|
|
277
278
|
end
|
|
278
279
|
|
|
279
|
-
#
|
|
280
|
+
# Remove both BBCode tags and ANSI escape codes, returning plain
|
|
281
|
+
# text.
|
|
280
282
|
#
|
|
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
|
|
283
|
+
# @example
|
|
284
|
+
# Terminal::Ansi.plain('[bold]Hello[/bold]') # => "Hello"
|
|
285
|
+
# Terminal::Ansi.plain("\e[1mHello\e[m") # => "Hello"
|
|
286
|
+
#
|
|
287
|
+
# @param str [#to_s] text with BBCode and/or ANSI codes
|
|
288
|
+
# @return [String] plain text
|
|
289
|
+
def plain(str)
|
|
290
|
+
str.gsub!(@re_test, '') if (str = unbbcode(str)).index("\e")
|
|
291
|
+
str
|
|
298
292
|
end
|
|
299
293
|
|
|
300
294
|
#
|
|
301
295
|
# @!endgroup
|
|
302
296
|
#
|
|
303
|
-
# @!group
|
|
297
|
+
# @!group ANSI Control Code Generation
|
|
304
298
|
#
|
|
305
299
|
|
|
306
|
-
# Move cursor
|
|
300
|
+
# Move cursor up.
|
|
307
301
|
#
|
|
308
|
-
# @param lines [Integer] number of lines
|
|
309
|
-
# @return [
|
|
302
|
+
# @param lines [Integer] number of lines
|
|
303
|
+
# @return (see .[])
|
|
310
304
|
def cursor_up(lines = 1) = "\e[#{lines}A"
|
|
311
305
|
|
|
312
|
-
# Move cursor
|
|
306
|
+
# Move cursor down.
|
|
313
307
|
#
|
|
314
|
-
# @param (see cursor_up)
|
|
315
|
-
# @return (see
|
|
308
|
+
# @param (see .cursor_up)
|
|
309
|
+
# @return (see .[])
|
|
316
310
|
def cursor_down(lines = 1) = "\e[#{lines}B"
|
|
317
311
|
|
|
318
|
-
# Move cursor
|
|
312
|
+
# Move cursor right.
|
|
319
313
|
#
|
|
320
|
-
# @param columns [Integer] number of columns
|
|
321
|
-
# @return (see
|
|
314
|
+
# @param columns [Integer] number of columns
|
|
315
|
+
# @return (see .[])
|
|
322
316
|
def cursor_forward(columns = 1) = "\e[#{columns}C"
|
|
323
317
|
|
|
324
|
-
# Move cursor
|
|
318
|
+
# Move cursor left.
|
|
325
319
|
#
|
|
326
|
-
# @param (see
|
|
327
|
-
# @return (see
|
|
320
|
+
# @param (see .cursor_forward)
|
|
321
|
+
# @return (see .[])
|
|
328
322
|
def cursor_back(columns = 1) = "\e[#{columns}D"
|
|
329
323
|
|
|
330
|
-
# Move cursor to
|
|
324
|
+
# Move cursor to beginning of line, N lines down.
|
|
331
325
|
#
|
|
332
|
-
# @param (see
|
|
333
|
-
# @return (see
|
|
326
|
+
# @param (see .cursor_up)
|
|
327
|
+
# @return (see .[])
|
|
334
328
|
def cursor_next_line(lines = 1) = "\e[#{lines}E"
|
|
335
329
|
|
|
336
|
-
# Move cursor to
|
|
330
|
+
# Move cursor to beginning of line, N lines up.
|
|
337
331
|
#
|
|
338
|
-
# @param (see
|
|
339
|
-
# @return (see
|
|
332
|
+
# @param (see .cursor_up)
|
|
333
|
+
# @return (see .[])
|
|
340
334
|
def cursor_prev_line(lines = 1) = "\e[#{lines}F"
|
|
341
335
|
|
|
342
|
-
# Move cursor to
|
|
336
|
+
# Move cursor to absolute column.
|
|
343
337
|
#
|
|
344
|
-
# @param column [Integer] column
|
|
345
|
-
# @return (see
|
|
338
|
+
# @param column [Integer] column number
|
|
339
|
+
# @return (see .[])
|
|
346
340
|
def cursor_column(column = 1) = "\e[#{column}G"
|
|
347
341
|
|
|
348
|
-
# Move cursor
|
|
349
|
-
# position.
|
|
350
|
-
# (Skip some columns.)
|
|
342
|
+
# Move cursor right by relative columns.
|
|
351
343
|
#
|
|
352
|
-
# @param
|
|
353
|
-
# @return (see
|
|
344
|
+
# @param column [Integer] number of columns
|
|
345
|
+
# @return (see .[])
|
|
354
346
|
def cursor_column_rel(column = 1) = "\e[#{column}a"
|
|
355
347
|
|
|
356
|
-
# Move cursor
|
|
357
|
-
# (Skip some rows.)
|
|
348
|
+
# Move cursor down by relative rows.
|
|
358
349
|
#
|
|
359
|
-
# @param row [Integer]
|
|
360
|
-
# @return (see
|
|
350
|
+
# @param row [Integer] number of rows
|
|
351
|
+
# @return (see .[])
|
|
361
352
|
def cursor_row_rel(row = 1) = "\e[#{row}e"
|
|
362
353
|
|
|
363
|
-
# Move to
|
|
354
|
+
# Move cursor to absolute position.
|
|
355
|
+
#
|
|
356
|
+
# @example
|
|
357
|
+
# Terminal::Ansi.cursor_pos(1, 1) # home position
|
|
358
|
+
# Terminal::Ansi.cursor_pos(10, 5) # row 10, column 5
|
|
364
359
|
#
|
|
365
|
-
# @param row [Integer] row
|
|
366
|
-
# @param column [Integer] column
|
|
367
|
-
# @return (see
|
|
360
|
+
# @param row [Integer, nil] row number; +nil+ moves to home
|
|
361
|
+
# @param column [Integer, nil] column number
|
|
362
|
+
# @return (see .[])
|
|
368
363
|
def cursor_pos(row, column = nil)
|
|
369
364
|
return column ? "\e[;#{column}H" : "\e[H" unless row
|
|
370
365
|
column ? "\e[#{row};#{column}H" : "\e[#{row}H"
|
|
@@ -372,142 +367,162 @@ module Terminal
|
|
|
372
367
|
|
|
373
368
|
# Show cursor.
|
|
374
369
|
#
|
|
375
|
-
# @return (see
|
|
370
|
+
# @return (see .[])
|
|
376
371
|
def cursor_show = +CURSOR_SHOW
|
|
377
372
|
|
|
378
373
|
# Hide cursor.
|
|
379
374
|
#
|
|
380
|
-
# @return (see
|
|
375
|
+
# @return (see .[])
|
|
381
376
|
def cursor_hide = +CURSOR_HIDE
|
|
382
377
|
|
|
383
378
|
# Save current cursor position.
|
|
384
379
|
#
|
|
385
|
-
# @
|
|
386
|
-
def cursor_save_pos = +CURSOR_POS_SAVE
|
|
387
|
-
|
|
388
|
-
# Restore saved cursor position.
|
|
380
|
+
# @see .cursor_restore_pos
|
|
389
381
|
#
|
|
390
|
-
# @return (see
|
|
391
|
-
def
|
|
382
|
+
# @return (see .[])
|
|
383
|
+
def cursor_save_pos = +CURSOR_POS_SAVE
|
|
392
384
|
|
|
385
|
+
# Restore previously saved cursor position.
|
|
393
386
|
#
|
|
394
|
-
#
|
|
395
|
-
#
|
|
396
|
-
# @!group Screen manipulation
|
|
387
|
+
# @see .cursor_save_pos
|
|
397
388
|
#
|
|
389
|
+
# @return (see .[])
|
|
390
|
+
def cursor_restore_pos = +CURSOR_POS_RESTORE
|
|
398
391
|
|
|
399
|
-
# Erase screen
|
|
392
|
+
# Erase part of the screen.
|
|
400
393
|
#
|
|
401
|
-
# @param part [
|
|
402
|
-
#
|
|
394
|
+
# @param part [Symbol] area to erase:
|
|
395
|
+
# +:all+, +:below+, +:above+, or +:scrollback+
|
|
396
|
+
# @return (see .[])
|
|
403
397
|
def screen_erase(part = :all) = "\e[#{@screen_erase[part]}J"
|
|
404
398
|
|
|
405
|
-
#
|
|
399
|
+
# Save screen state.
|
|
400
|
+
#
|
|
401
|
+
# @see .screen_restore
|
|
406
402
|
#
|
|
407
|
-
# @return (see
|
|
403
|
+
# @return (see .[])
|
|
408
404
|
def screen_save = +SCREEN_SAVE
|
|
409
405
|
|
|
410
|
-
# Restore
|
|
406
|
+
# Restore previously saved screen state.
|
|
411
407
|
#
|
|
412
|
-
# @
|
|
408
|
+
# @see .screen_save
|
|
409
|
+
#
|
|
410
|
+
# @return (see .[])
|
|
413
411
|
def screen_restore = +SCREEN_RESTORE
|
|
414
412
|
|
|
415
|
-
#
|
|
413
|
+
# Switch to the alternate screen buffer.
|
|
414
|
+
#
|
|
415
|
+
# @see .screen_alternate_off
|
|
416
416
|
#
|
|
417
|
-
# @return (see
|
|
417
|
+
# @return (see .[])
|
|
418
418
|
def screen_alternate = +SCREEN_ALTERNATE
|
|
419
419
|
|
|
420
|
-
#
|
|
420
|
+
# Switch back from the alternate screen buffer.
|
|
421
|
+
#
|
|
422
|
+
# @see .screen_alternate
|
|
421
423
|
#
|
|
422
|
-
# @return (see
|
|
424
|
+
# @return (see .[])
|
|
423
425
|
def screen_alternate_off = +SCREEN_ALTERNATE_OFF
|
|
424
426
|
|
|
425
|
-
# Scroll
|
|
427
|
+
# Scroll the screen up.
|
|
426
428
|
#
|
|
427
|
-
# @param
|
|
428
|
-
# @return (see
|
|
429
|
+
# @param (see .cursor_up)
|
|
430
|
+
# @return (see .[])
|
|
429
431
|
def screen_scroll_up(lines = 1) = "\e[#{lines}S"
|
|
430
432
|
|
|
431
|
-
# Scroll
|
|
433
|
+
# Scroll the screen down.
|
|
432
434
|
#
|
|
433
|
-
# @param (see
|
|
434
|
-
# @return (see
|
|
435
|
+
# @param (see .cursor_up)
|
|
436
|
+
# @return (see .[])
|
|
435
437
|
def screen_scroll_down(lines = 1) = "\e[#{lines}T"
|
|
436
438
|
|
|
439
|
+
# Repeat the last printed character.
|
|
437
440
|
#
|
|
438
|
-
#
|
|
441
|
+
# @param count [Integer] number of repetitions
|
|
442
|
+
# @return (see .[])
|
|
443
|
+
def char_repeat(count = 1) = "\e[#{count}b"
|
|
444
|
+
|
|
445
|
+
# Erase part of the current line.
|
|
446
|
+
#
|
|
447
|
+
# @param part [Symbol] area to erase:
|
|
448
|
+
# +:all+, +:to_end+, or +:to_start+
|
|
449
|
+
# @return (see .[])
|
|
450
|
+
def line_erase(part = :all) = "\e[#{@line_erase[part]}K"
|
|
451
|
+
|
|
452
|
+
# Set the terminal window title.
|
|
453
|
+
# @note Supported by Hyper, iTerm2, Kitty, macOS Terminal, Tabby,
|
|
454
|
+
# WezTerm.
|
|
439
455
|
#
|
|
440
|
-
#
|
|
456
|
+
# @example
|
|
457
|
+
# Terminal.raw_write(Terminal::Ansi.title('My App'))
|
|
441
458
|
#
|
|
459
|
+
# @param title [String] the title text
|
|
460
|
+
# @return (see .[])
|
|
461
|
+
def title(title) = "\e]0;#{title}\a"
|
|
442
462
|
|
|
443
|
-
#
|
|
463
|
+
# Start a hyperlink (OSC 8).
|
|
444
464
|
#
|
|
445
|
-
# @
|
|
446
|
-
#
|
|
447
|
-
|
|
465
|
+
# @note Supported by Ghostty, iTerm2, Kitty, Rio, Tabby, WezTerm.
|
|
466
|
+
#
|
|
467
|
+
# @see .link
|
|
468
|
+
# @see .link_end
|
|
469
|
+
#
|
|
470
|
+
# @param url [#to_s] the link URL
|
|
471
|
+
# @param params [Hash] optional link parameters (e.g., +id:+)
|
|
472
|
+
# @return (see .[])
|
|
473
|
+
def link_start(url, **params)
|
|
474
|
+
"\e]8;#{params.map { it.join('=') }.join(':')};#{url}\a"
|
|
475
|
+
end
|
|
448
476
|
|
|
449
|
-
#
|
|
477
|
+
# End a hyperlink.
|
|
450
478
|
#
|
|
451
|
-
# @
|
|
452
|
-
#
|
|
453
|
-
|
|
479
|
+
# @see .link_start
|
|
480
|
+
#
|
|
481
|
+
# @return (see .[])
|
|
482
|
+
def link_end = +LINK_END
|
|
454
483
|
|
|
455
|
-
#
|
|
456
|
-
#
|
|
457
|
-
#
|
|
458
|
-
#
|
|
459
|
-
#
|
|
460
|
-
#
|
|
461
|
-
#
|
|
462
|
-
#
|
|
463
|
-
#
|
|
464
|
-
# @param
|
|
465
|
-
# @return (see
|
|
466
|
-
def
|
|
484
|
+
# Create a complete hyperlink (OSC 8).
|
|
485
|
+
#
|
|
486
|
+
# @see .link_start
|
|
487
|
+
# @see .link_end
|
|
488
|
+
#
|
|
489
|
+
# @example
|
|
490
|
+
# Terminal::Ansi.link('https://example.com', 'Example')
|
|
491
|
+
#
|
|
492
|
+
# @param (see .link_start)
|
|
493
|
+
# @param text [String] the visible link text
|
|
494
|
+
# @return (see .[])
|
|
495
|
+
def link(url, text, **params)
|
|
496
|
+
"#{link_start(url, **params)}#{text}#{LINK_END}"
|
|
497
|
+
end
|
|
467
498
|
|
|
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)
|
|
499
|
+
# Send a desktop notification via the terminal.
|
|
500
|
+
# @note Supported by Ghostty, iTerm2, Kitty, WezTerm.
|
|
501
|
+
#
|
|
502
|
+
# @example
|
|
503
|
+
# Terminal.raw_write(Terminal::Ansi.notify('Build complete!'))
|
|
504
|
+
#
|
|
505
|
+
# @param text [to_s] the notification text
|
|
506
|
+
# @return (see .[])
|
|
491
507
|
def notify(text) = "\e]9;#{text}\a"
|
|
492
508
|
|
|
493
|
-
#
|
|
494
|
-
#
|
|
495
|
-
#
|
|
496
|
-
# iTerm2,
|
|
497
|
-
#
|
|
498
|
-
#
|
|
499
|
-
#
|
|
500
|
-
#
|
|
501
|
-
#
|
|
502
|
-
#
|
|
503
|
-
#
|
|
504
|
-
# - `:error
|
|
505
|
-
#
|
|
506
|
-
#
|
|
507
|
-
#
|
|
508
|
-
#
|
|
509
|
-
#
|
|
510
|
-
# @return (see cursor_up)
|
|
509
|
+
# Show or update a progress indicator in the terminal tab/title
|
|
510
|
+
# bar.
|
|
511
|
+
#
|
|
512
|
+
# @note Supported by Ghostty, iTerm2, Kitty.
|
|
513
|
+
#
|
|
514
|
+
# @example
|
|
515
|
+
# Terminal.raw_write(Terminal::Ansi.progress(50)) # 50%
|
|
516
|
+
# Terminal.raw_write(Terminal::Ansi.progress(:error)) # error state
|
|
517
|
+
# Terminal.raw_write(Terminal::Ansi.progress(nil)) # hide
|
|
518
|
+
#
|
|
519
|
+
# @param state [Symbol, Numeric, Boolean] progress state:
|
|
520
|
+
# - +:show+ or +true+ to show, `:err`/`:error`, `:warn`/`:warning`,
|
|
521
|
+
# +:indeterminate+, a +Numeric+ (0-100) to set percentage, or
|
|
522
|
+
# any other value to hide
|
|
523
|
+
# @param percent [Integer] progress percentage (0-100, used with
|
|
524
|
+
# +:show+ state)
|
|
525
|
+
# @return (see .[])
|
|
511
526
|
def progress(state, percent = 0)
|
|
512
527
|
case state
|
|
513
528
|
when :show, true
|
|
@@ -526,32 +541,28 @@ module Terminal
|
|
|
526
541
|
"\e]9;4;#{state};#{percent.to_i.clamp(0, 100)}\a"
|
|
527
542
|
end
|
|
528
543
|
|
|
529
|
-
#
|
|
530
|
-
# It uses the
|
|
544
|
+
# Scale text using the
|
|
531
545
|
# [text sizing protocol](https://sw.kovidgoyal.net/kitty/text-sizing-protocol).
|
|
532
|
-
#
|
|
546
|
+
#
|
|
547
|
+
# @note Only supported by Kitty.
|
|
533
548
|
#
|
|
534
549
|
# @example Double-height Greeting
|
|
535
550
|
# Terminal::Ansi.scale('Hello Ruby!', scale: 2)
|
|
536
551
|
#
|
|
537
552
|
# @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)
|
|
553
|
+
# Terminal::Ansi.scale('Hello Ruby!', fracn: 1, fracd: 2, vertical: :middle)
|
|
554
|
+
#
|
|
555
|
+
# @param text [#to_s] text to scale
|
|
556
|
+
# @param scale [Integer, nil] scale multiplier (1-7)
|
|
557
|
+
# @param width [Integer, nil] width mode (0-7)
|
|
558
|
+
# @param fracn [Integer, nil] fractional numerator (0-15)
|
|
559
|
+
# @param fracd [Integer, nil] fractional denominator
|
|
560
|
+
# (must be > fracn, max 15)
|
|
561
|
+
# @param vertical [Symbol, Integer, nil] vertical alignment:
|
|
562
|
+
# +:top+, +:bottom+, +:middle+
|
|
563
|
+
# @param horizontal [Symbol, Integer, nil] horizontal alignment:
|
|
564
|
+
# +:left+, +:right+, +:center+
|
|
565
|
+
# @return (see .[])
|
|
555
566
|
def scale(
|
|
556
567
|
text,
|
|
557
568
|
scale: nil,
|
|
@@ -571,7 +582,7 @@ module Terminal
|
|
|
571
582
|
opts << 'v=0'
|
|
572
583
|
when 1, :bottom
|
|
573
584
|
opts << 'v=1'
|
|
574
|
-
when 2, :
|
|
585
|
+
when 2, :middle
|
|
575
586
|
opts << 'v=2'
|
|
576
587
|
end
|
|
577
588
|
case horizontal
|
|
@@ -579,7 +590,7 @@ module Terminal
|
|
|
579
590
|
opts << 'h=0'
|
|
580
591
|
when 1, :right
|
|
581
592
|
opts << 'h=1'
|
|
582
|
-
when 2, :
|
|
593
|
+
when 2, :center
|
|
583
594
|
opts << 'h=2'
|
|
584
595
|
end
|
|
585
596
|
end
|
|
@@ -601,10 +612,6 @@ module Terminal
|
|
|
601
612
|
end
|
|
602
613
|
end
|
|
603
614
|
|
|
604
|
-
@cbase = { 'bg' => '48', 'on' => '48', 'ul' => '58' }
|
|
605
|
-
@cbase.default = '38'
|
|
606
|
-
@cbase.freeze
|
|
607
|
-
|
|
608
615
|
@re_test =
|
|
609
616
|
/
|
|
610
617
|
(?:\e\[[\x30-\x3f]*[\x20-\x2f]*[a-zA-Z])
|
|
@@ -616,139 +623,128 @@ module Terminal
|
|
|
616
623
|
|
|
617
624
|
clr_map = {
|
|
618
625
|
# foreground
|
|
619
|
-
'
|
|
620
|
-
'
|
|
621
|
-
'
|
|
622
|
-
'
|
|
623
|
-
'
|
|
624
|
-
'
|
|
625
|
-
'
|
|
626
|
-
'
|
|
627
|
-
'
|
|
626
|
+
'black' => '30',
|
|
627
|
+
'red' => '31',
|
|
628
|
+
'green' => '32',
|
|
629
|
+
'yellow' => '33',
|
|
630
|
+
'blue' => '34',
|
|
631
|
+
'magenta' => '35',
|
|
632
|
+
'cyan' => '36',
|
|
633
|
+
'white' => '37',
|
|
634
|
+
'default' => '39',
|
|
628
635
|
# background
|
|
629
|
-
'
|
|
630
|
-
'
|
|
631
|
-
'
|
|
632
|
-
'
|
|
633
|
-
'
|
|
634
|
-
'
|
|
635
|
-
'
|
|
636
|
-
'
|
|
637
|
-
'
|
|
636
|
+
'on_black' => '40',
|
|
637
|
+
'on_red' => '41',
|
|
638
|
+
'on_green' => '42',
|
|
639
|
+
'on_yellow' => '43',
|
|
640
|
+
'on_blue' => '44',
|
|
641
|
+
'on_magenta' => '45',
|
|
642
|
+
'on_cyan' => '46',
|
|
643
|
+
'on_white' => '47',
|
|
644
|
+
'on_default' => '49',
|
|
638
645
|
# 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
|
-
'
|
|
646
|
+
'ul_black' => '58;2;0;0;0',
|
|
647
|
+
'ul_blue' => '58;2;0;0;128',
|
|
648
|
+
'ul_bright_blue' => '58;2;0;0;255',
|
|
649
|
+
'ul_green' => '58;2;0;128;0',
|
|
650
|
+
'ul_cyan' => '58;2;0;128;128',
|
|
651
|
+
'ul_bright_green' => '58;2;0;255;0',
|
|
652
|
+
'ul_bright_cyan' => '58;2;0;255;255',
|
|
653
|
+
'ul_red' => '58;2;128;0;0',
|
|
654
|
+
'ul_magenta' => '58;2;128;0;128',
|
|
655
|
+
'ul_yellow' => '58;2;128;128;0',
|
|
656
|
+
'ul_white' => '58;2;128;128;128',
|
|
657
|
+
'ul_bright_red' => '58;2;255;0;0',
|
|
658
|
+
'ul_bright_magenta' => '58;2;255;0;255',
|
|
659
|
+
'ul_bright_yellow' => '58;2;255;255;0',
|
|
660
|
+
'ul_bright_white' => '58;2;255;255;255',
|
|
661
|
+
'ul_bright_black' => '58;2;64;64;64',
|
|
662
|
+
'ul_default' => '59',
|
|
656
663
|
# bright foreground
|
|
657
|
-
'
|
|
658
|
-
'
|
|
659
|
-
'
|
|
660
|
-
'
|
|
661
|
-
'
|
|
662
|
-
'
|
|
663
|
-
'
|
|
664
|
-
'
|
|
664
|
+
'bright_black' => '90',
|
|
665
|
+
'bright_red' => '91',
|
|
666
|
+
'bright_green' => '92',
|
|
667
|
+
'bright_yellow' => '93',
|
|
668
|
+
'bright_blue' => '94',
|
|
669
|
+
'bright_magenta' => '95',
|
|
670
|
+
'bright_cyan' => '96',
|
|
671
|
+
'bright_white' => '97',
|
|
665
672
|
# 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']
|
|
673
|
+
'on_bright_black' => '100',
|
|
674
|
+
'on_bright_red' => '101',
|
|
675
|
+
'on_bright_green' => '102',
|
|
676
|
+
'on_bright_yellow' => '103',
|
|
677
|
+
'on_bright_blue' => '104',
|
|
678
|
+
'on_bright_magenta' => '105',
|
|
679
|
+
'on_bright_cyan' => '106',
|
|
680
|
+
'on_bright_white' => '107'
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
@colors = clr_map.keys.map!(&:to_sym).sort!.freeze
|
|
685
684
|
|
|
686
685
|
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
|
-
'
|
|
686
|
+
'reset' => '',
|
|
687
|
+
'bold' => '1',
|
|
688
|
+
'faint' => '2',
|
|
689
|
+
'italic' => '3',
|
|
690
|
+
'underline' => '4',
|
|
691
|
+
'blink' => '5',
|
|
692
|
+
'rapid_blink' => '6',
|
|
693
|
+
'invert' => '7',
|
|
694
|
+
'hide' => '8',
|
|
695
|
+
'strike' => '9',
|
|
696
|
+
'primary_font' => '10',
|
|
697
|
+
'font1' => '11',
|
|
698
|
+
'font2' => '12',
|
|
699
|
+
'font3' => '13',
|
|
700
|
+
'font4' => '14',
|
|
701
|
+
'font5' => '15',
|
|
702
|
+
'font6' => '16',
|
|
703
|
+
'font7' => '17',
|
|
704
|
+
'font8' => '18',
|
|
705
|
+
'font9' => '19',
|
|
706
|
+
'fraktur' => '20',
|
|
707
|
+
'double_underline' => '21',
|
|
708
|
+
'bold_off' => '22',
|
|
709
|
+
'faint_off' => '22',
|
|
710
|
+
'italic_off' => '23',
|
|
711
|
+
'fraktur_off' => '23',
|
|
712
|
+
'underline_off' => '24',
|
|
713
|
+
'double_underline_off' => '24',
|
|
714
|
+
'blink_off' => '25',
|
|
715
|
+
'rapid_blink_off' => '25',
|
|
716
|
+
'proportional' => '26',
|
|
717
|
+
'invert_off' => '27',
|
|
718
|
+
'hide_off' => '28',
|
|
719
|
+
'strike_off' => '29',
|
|
720
|
+
# ...
|
|
721
|
+
'proportional_off' => '50',
|
|
722
|
+
'framed' => '51',
|
|
723
|
+
'encircled' => '52',
|
|
724
|
+
'overlined' => '53',
|
|
725
|
+
'framed_off' => '54',
|
|
726
|
+
'encircled_off' => '54',
|
|
727
|
+
'overlined_off' => '55',
|
|
724
728
|
# ...
|
|
725
|
-
'
|
|
726
|
-
'
|
|
727
|
-
'
|
|
729
|
+
'superscript' => '73',
|
|
730
|
+
'subscript' => '74',
|
|
731
|
+
'superscript_off' => '75',
|
|
732
|
+
'subscript_off' => '75',
|
|
728
733
|
# special underline
|
|
729
|
-
'
|
|
730
|
-
'
|
|
731
|
-
'
|
|
732
|
-
'
|
|
733
|
-
|
|
734
|
-
|
|
734
|
+
'curly_underline' => '4:3',
|
|
735
|
+
'dotted_underline' => '4:4',
|
|
736
|
+
'dashed_underline' => '4:5',
|
|
737
|
+
'curly_underline_off' => '4:0',
|
|
738
|
+
'dotted_underline_off' => '4:0',
|
|
739
|
+
'dashed_underline_off' => '4:0'
|
|
740
|
+
}
|
|
735
741
|
|
|
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']
|
|
742
|
+
attr_alias = ->(t, s) { attr_map[t] = attr_map[s] }
|
|
744
743
|
|
|
745
|
-
#
|
|
744
|
+
# aliases:
|
|
746
745
|
attr_alias['off', 'reset']
|
|
747
746
|
attr_alias['dim', 'faint']
|
|
748
747
|
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
748
|
attr_alias['spacing', 'proportional']
|
|
753
749
|
attr_alias['spacing_off', 'proportional_off']
|
|
754
750
|
|
|
@@ -761,20 +757,26 @@ module Terminal
|
|
|
761
757
|
attr_alias['h', 'hide']
|
|
762
758
|
attr_alias['s', 'strike']
|
|
763
759
|
attr_alias['uu', 'double_underline']
|
|
764
|
-
attr_alias['ovr', 'overlined']
|
|
765
|
-
attr_alias['sup', 'superscript']
|
|
766
|
-
attr_alias['sub', 'subscript']
|
|
767
760
|
attr_alias['cu', 'curly_underline']
|
|
768
761
|
attr_alias['dau', 'dashed_underline']
|
|
769
762
|
attr_alias['dou', 'dotted_underline']
|
|
763
|
+
attr_alias['ovr', 'overlined']
|
|
764
|
+
attr_alias['sup', 'superscript']
|
|
765
|
+
attr_alias['sub', 'subscript']
|
|
770
766
|
|
|
771
|
-
@colors = clr_map.keys.map!(&:to_sym).sort!.freeze
|
|
772
767
|
@attributes = attr_map.keys.map!(&:to_sym).sort!.freeze
|
|
773
768
|
|
|
774
769
|
# shortcuts disable:
|
|
775
770
|
attr_map.keys.each do |n|
|
|
776
771
|
attr_alias["/#{n.delete_suffix('_off')}", n] if n.end_with?('_off')
|
|
777
772
|
end
|
|
773
|
+
|
|
774
|
+
# additional shortcuts disable:
|
|
775
|
+
attr_alias['/', 'reset']
|
|
776
|
+
attr_map['/bg'] = clr_map['on_default']
|
|
777
|
+
attr_map['/fg'] = clr_map['default']
|
|
778
|
+
attr_map['/ul'] = clr_map['ul_default']
|
|
779
|
+
|
|
778
780
|
attr_alias['/b', 'bold_off']
|
|
779
781
|
attr_alias['/d', 'dim_off']
|
|
780
782
|
attr_alias['/i', 'italic_off']
|
|
@@ -783,26 +785,24 @@ module Terminal
|
|
|
783
785
|
attr_alias['/h', 'hide_off']
|
|
784
786
|
attr_alias['/s', 'strike_off']
|
|
785
787
|
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
788
|
attr_alias['/cu', 'curly_underline_off']
|
|
790
789
|
attr_alias['/dau', 'dashed_underline_off']
|
|
791
790
|
attr_alias['/dou', 'dotted_underline_off']
|
|
791
|
+
attr_alias['/ovr', 'overlined_off']
|
|
792
|
+
attr_alias['/sup', 'superscript_off']
|
|
793
|
+
attr_alias['/sub', 'subscript_off']
|
|
792
794
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
attr_map['/bg'] = clr_map['on_default']
|
|
797
|
-
attr_map['/ul'] = clr_map['ul_default']
|
|
795
|
+
@cbase = { 'on' => '48', 'ul' => '58' }
|
|
796
|
+
@cbase.default = '38'
|
|
797
|
+
@cbase.freeze
|
|
798
798
|
|
|
799
799
|
@attr_map =
|
|
800
800
|
Hash.new do |_, str|
|
|
801
|
-
b, v = /\A(
|
|
801
|
+
b, v = /\A(on|ul)?_?#?([[:xdigit:]]{1,6})\z/.match(str)&.captures
|
|
802
802
|
unless v
|
|
803
|
-
b = /\A(
|
|
804
|
-
|
|
805
|
-
next "#{@cbase[b[1]]};#{
|
|
803
|
+
b = /\A(on|ul)?_?([a-z]{3,}[0-9]{0,3})\z/.match(str) or next
|
|
804
|
+
v = NAMED_COLORS[b[2]] or next
|
|
805
|
+
next "#{@cbase[b[1]]};#{v}"
|
|
806
806
|
end
|
|
807
807
|
case v.size
|
|
808
808
|
when 1, 2
|
|
@@ -813,7 +813,7 @@ module Terminal
|
|
|
813
813
|
"#{@cbase[b]};2;#{v[0, 2].hex};#{v[2, 2].hex};#{v[4, 2].hex}"
|
|
814
814
|
end
|
|
815
815
|
end
|
|
816
|
-
attr_map.merge!(clr_map).keys.sort!.each { @attr_map[
|
|
816
|
+
attr_map.merge!(clr_map).keys.sort!.each { @attr_map[it] = attr_map[it] }
|
|
817
817
|
@attr_map.freeze
|
|
818
818
|
|
|
819
819
|
@attrs_map = @attr_map.transform_keys(&:to_sym)
|
|
@@ -828,12 +828,15 @@ module Terminal
|
|
|
828
828
|
@line_erase.default = '2'
|
|
829
829
|
@line_erase.compare_by_identity.freeze
|
|
830
830
|
|
|
831
|
-
autoload :NAMED_COLORS, "#{__dir__}/ansi/named_colors.rb"
|
|
832
|
-
private_constant :NAMED_COLORS
|
|
833
|
-
|
|
834
831
|
# @private
|
|
835
832
|
RESET = -self[:reset]
|
|
836
833
|
|
|
834
|
+
# @private
|
|
835
|
+
RESET_FG = -self[:default]
|
|
836
|
+
|
|
837
|
+
# @private
|
|
838
|
+
RESET_BG = -self[:on_default]
|
|
839
|
+
|
|
837
840
|
# @private
|
|
838
841
|
FULL_RESET = "\ec"
|
|
839
842
|
|
|
@@ -843,6 +846,13 @@ module Terminal
|
|
|
843
846
|
CURSOR_FIRST_ROW = -cursor_pos(1)
|
|
844
847
|
# @private
|
|
845
848
|
CURSOR_FIRST_COLUMN = -cursor_column(1)
|
|
849
|
+
# @private
|
|
850
|
+
CURSOR_NEXT_LINE = -cursor_next_line(nil)
|
|
851
|
+
|
|
852
|
+
# @private
|
|
853
|
+
CURSOR_BACK = -cursor_back(nil)
|
|
854
|
+
# @private
|
|
855
|
+
CURSOR_FORWARD = -cursor_forward(nil)
|
|
846
856
|
|
|
847
857
|
# @private
|
|
848
858
|
CURSOR_SHOW = "\e[?25h"
|
|
@@ -893,6 +903,9 @@ module Terminal
|
|
|
893
903
|
# @private
|
|
894
904
|
LINE_ERASE_PREV = -"#{cursor_prev_line(nil)}#{LINE_ERASE}"
|
|
895
905
|
|
|
906
|
+
# @private
|
|
907
|
+
LINK_END = "\e]8;;\a"
|
|
908
|
+
|
|
896
909
|
# @private
|
|
897
910
|
PROGRESS_HIDE = "\e]9;4;0;0\a"
|
|
898
911
|
# @private
|
|
@@ -929,5 +942,10 @@ module Terminal
|
|
|
929
942
|
# https://sw.kovidgoyal.net/kitty/color-stack
|
|
930
943
|
# https://sw.kovidgoyal.net/kitty/deccara
|
|
931
944
|
# https://sw.kovidgoyal.net/kitty/clipboard
|
|
945
|
+
|
|
946
|
+
dir = "#{__dir__}/ansi"
|
|
947
|
+
autoload :NAMED_COLORS, "#{dir}/named_colors.rb"
|
|
948
|
+
autoload :ScreenViewer, "#{dir}/screen_viewer.rb"
|
|
949
|
+
private_constant :NAMED_COLORS
|
|
932
950
|
end
|
|
933
951
|
end
|