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.
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
- # Fast ANSI control code and BBCode processing.
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
- # Supported attribute names.
10
- #
11
- # @see []
23
+ # All known text attribute names (bold, italic, underline, etc.).
12
24
  #
13
25
  # @attribute [r] attributes
14
- # @return [Array<Symbol>] all attribute names
26
+ # @return [Array<Symbol>]
15
27
  def attributes = @attributes.dup
16
28
 
17
- # Supported 3/4-bit color names.
18
- #
19
- # @see []
29
+ # All known basic color names.
20
30
  #
21
31
  # @attribute [r] colors
22
- # @return [Array<Symbol>] all color names
32
+ # @return [Array<Symbol>]
23
33
  def colors = @colors.dup
24
34
 
25
- # Supported basic 24-bit (Kitty compatible) color names.
26
- #
27
- # @see []
35
+ # All CSS/X11 named colors.
28
36
  #
29
37
  # @attribute [r] named_colors
30
- # @return [Array<Symbol>] all basic named_colors names
38
+ # @return [Array<Symbol>]
31
39
  def named_colors = NAMED_COLORS.keys.map!(&:to_sym)
32
40
 
33
41
  #
34
- # @!group ANSI control code generator functions
35
- #
36
-
37
- # Combine given {attributes}, {colors}, {named_colors} and color codes to
38
- # an ANSI control code sequence.
39
- #
40
- # Colors can specified by their name for ANSI 3-bit and 4-bit colors.
41
- # For 8-bit ANSI colors use 2-digit hexadecimal values `00`...`ff`.
42
- #
43
- # To use RGB ANSI colors (24-bit colors) specify 3-digit or 6-digit
44
- # hexadecimal values `000`...`fff` or `000000`...`ffffff`.
45
- # This represent the `RRGGBB` values (or `RGB` for short version) like you
46
- # may known from CSS color notation.
47
- #
48
- # To use a color as background color prefix the color attribute with `bg_`
49
- # or `on_`.
50
- # To use a color as underline color prefix the color attribute with `ul_`.
51
- # To clarify that a color attribute have to be used as foreground
52
- # color use the prefix `fg_`.
53
- #
54
- # @example Valid Foreground Color Attributes
55
- # Terminal::Ansi[:yellow]
56
- # Terminal::Ansi[:fg_fab]
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
- # Test if given String contains ANSI control codes.
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
- # 'Hello World!',
130
- # :bold, :italic, :bright_white, :on_00c
131
- # )
132
- # # => "\e[1;3;97;48;2;0;0;204mHello World!\e[m"
133
- #
134
- # @see []
135
- # @see undecorate
136
- #
137
- # @param str [#to_s] object to be decorated
138
- # @param attributes [Array<Symbol, String>] attribute names to be used
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 functions, attributes and colors from given string.
108
+ # Remove all ANSI escape codes from a string.
147
109
  #
148
110
  # @example
149
- # Terminal::Ansi.undecorate("\e[1;3;97;48;2;0;0;204mHello World!\e[m")
150
- # # => "Hello World!"
111
+ # Terminal::Ansi.undecorate("\e[1;31mHello\e[m")
112
+ # # => "Hello"
151
113
  #
152
- # @see decorate
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 combine given ANSI attributes and colors.
161
- # The attributes and colors have to be separated by given `separator``.
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
- # @see []
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] attributes separated by given `separator`
174
- # @param separator [String] attribute separator char
175
- # @return [String] combined ANSI attributes
176
- # @return [nil] when string does not contain valid attributes
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
- "\e[#{attributes.map! { @attr_map[_1] || return }.join(';')}m"
134
+ attributes.uniq!
135
+ "\e[#{attributes.map! { @attr_map[it] || return }.join(';')}m"
181
136
  end
182
137
 
183
- # Test if all given attributes are valid.
138
+ # Generate rainbow-colored text using true color (24-bit) ANSI
139
+ # codes.
184
140
  #
185
- # @see []
141
+ # @example
142
+ # puts Terminal::Ansi.rainbow('Hello, World!')
186
143
  #
187
- # @param attributes [Array<Symbol, String>] attribute names to be used
188
- # @return [true, false] whether if all given attributes are valid
189
- def valid?(*attributes)
190
- attributes.all? do |arg|
191
- case arg
192
- when String
193
- @attr_map[arg]
194
- when Symbol
195
- @attrs_map[arg]
196
- when (0..767)
197
- true
198
- end
199
- end
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 related functions
166
+ # @!group BBcode Generation
206
167
  #
207
168
 
208
- # Replace embedded BBCode-like attributes with ANSI control codes.
169
+ # Convert BBCode markup to ANSI escape codes.
209
170
  #
210
- # @example
211
- # Terminal::Ansi.bbcode "[b]Bold[/b] Text"
212
- # # => "\e[1mBold\e[22m Text"
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
- # @param str [#to_s] string to be modified
218
- # @return [String] string with ANSI attributes
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 "[#{match[1..]}]" if match[0] == '\\'
225
- try_convert(match) || match_str
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 embedded BBCode-like attributes.
197
+ # Remove BBCode tags from a string, keeping the enclosed text.
230
198
  #
231
- # @example
232
- # Terminal::Ansi.unbbcode "[b]Bold[/b] Text"
233
- # # => "Bold Text"
199
+ # @see .bbcode
234
200
  #
235
- # @see bbcode
201
+ # @example
202
+ # Terminal::Ansi.unbbcode('[bold]Hello[/bold]') # => "Hello"
236
203
  #
237
- # @param str [#to_s] string to be modified
238
- # @return [String] string without BBCode
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
- next "[#{match[1..]}]" if match[0] == '\\'
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[_1] }
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 Other tool functions
248
+ # @!group Tool Functions
255
249
  #
256
250
 
257
- # Remove any BBCode-like and/or ANSI attributes.
251
+ # Test whether a string contains ANSI escape codes.
258
252
  #
259
- # @see undecorate
260
- # @see unbbcode
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 be modified
263
- # @return [String] string without BBCode and ANSI control codes.
264
- def plain(str)
265
- unless (str = str.to_s).index('[')
266
- return str.index("\e") ? str.gsub(@re_test, '') : str.dup
267
- end
268
- str =
269
- str.gsub(@re_bbcode) do |match_str|
270
- match = Regexp.last_match(1) or next match_str
271
- next "[#{match[1..]}]" if match[0] == '\\'
272
- next match_str if (match = match.split).empty?
273
- next if match.all? { @attr_map[_1] }
274
- match_str
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
- str.index("\e") ? str.gsub!(@re_test, '') : str
280
+ end
277
281
  end
278
282
 
279
- # Create nice colored text.
283
+ # Remove both BBCode tags and ANSI escape codes, returning plain
284
+ # text.
280
285
  #
281
- # @param str [#to_s] string to enrich with color
282
- # @param frequency [Float] color change frequency
283
- # @param spread [Float] number of chars with same color
284
- # @param seed [Float] start index on sinus curve
285
- # @return [String] fancy text
286
- def rainbow(str, frequency: 0.3, spread: 0.8, seed: 1.1)
287
- pos = -1
288
- @pi2_third ||= 2.0 * Math::PI / 3.0
289
- @pi4_third ||= 4.0 * Math::PI / 3.0
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 Cursor manipulation
301
+ # @!group ANSI Control Code Generation
304
302
  #
305
303
 
306
- # Move cursor given lines up.
304
+ # Move cursor up.
307
305
  #
308
- # @param lines [Integer] number of lines to move
309
- # @return [String] ANSI control code
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 given lines down.
310
+ # Move cursor down.
313
311
  #
314
- # @param (see cursor_up)
315
- # @return (see cursor_up)
312
+ # @param (see .cursor_up)
313
+ # @return (see .[])
316
314
  def cursor_down(lines = 1) = "\e[#{lines}B"
317
315
 
318
- # Move cursor given columns forward.
316
+ # Move cursor right.
319
317
  #
320
- # @param columns [Integer] number of columns to move
321
- # @return (see cursor_up)
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 given columns back.
322
+ # Move cursor left.
325
323
  #
326
- # @param (see forward)
327
- # @return (see cursor_up)
324
+ # @param (see .cursor_forward)
325
+ # @return (see .[])
328
326
  def cursor_back(columns = 1) = "\e[#{columns}D"
329
327
 
330
- # Move cursor to the beginning of the given next line.
328
+ # Move cursor to beginning of line, N lines down.
331
329
  #
332
- # @param (see up)
333
- # @return (see cursor_up)
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 the beginning of the given previous line.
334
+ # Move cursor to beginning of line, N lines up.
337
335
  #
338
- # @param (see up)
339
- # @return (see cursor_up)
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 given column in the current row.
340
+ # Move cursor to absolute column.
343
341
  #
344
- # @param column [Integer] column index
345
- # @return (see cursor_up)
342
+ # @param column [Integer] column number
343
+ # @return (see .[])
346
344
  def cursor_column(column = 1) = "\e[#{column}G"
347
345
 
348
- # Move cursor to given column in the current row relative to the current
349
- # position.
350
- # (Skip some columns.)
346
+ # Move cursor right by relative columns.
351
347
  #
352
- # @param (see cursor_column)
353
- # @return (see cursor_up)
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 to given row relative to the current position.
357
- # (Skip some rows.)
352
+ # Move cursor down by relative rows.
358
353
  #
359
- # @param row [Integer] row index
360
- # @return (see cursor_up)
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 given row and column.
358
+ # Move cursor to absolute position.
364
359
  #
365
- # @param row [Integer] row index
366
- # @param column [Integer] column index
367
- # @return (see cursor_up)
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 cursor_up)
374
+ # @return (see .[])
376
375
  def cursor_show = +CURSOR_SHOW
377
376
 
378
377
  # Hide cursor.
379
378
  #
380
- # @return (see cursor_up)
379
+ # @return (see .[])
381
380
  def cursor_hide = +CURSOR_HIDE
382
381
 
383
382
  # Save current cursor position.
384
383
  #
385
- # @return (see cursor_up)
386
- def cursor_save_pos = +CURSOR_POS_SAVE
387
-
388
- # Restore saved cursor position.
384
+ # @see .cursor_restore_pos
389
385
  #
390
- # @return (see cursor_up)
391
- def cursor_restore_pos = +CURSOR_POS_RESTORE
386
+ # @return (see .[])
387
+ def cursor_save_pos = +CURSOR_POS_SAVE
392
388
 
389
+ # Restore previously saved cursor position.
393
390
  #
394
- # @!endgroup
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 part.
396
+ # Erase part of the screen.
400
397
  #
401
- # @param part [:below, :above, :all, :scrollback] screen part to erase
402
- # @return (see cursor_up)
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
- # Safe current screen.
403
+ # Save screen state.
406
404
  #
407
- # @return (see cursor_up)
405
+ # @see .screen_restore
406
+ #
407
+ # @return (see .[])
408
408
  def screen_save = +SCREEN_SAVE
409
409
 
410
- # Restore current screen.
410
+ # Restore previously saved screen state.
411
+ #
412
+ # @see .screen_save
411
413
  #
412
- # @return (see cursor_up)
414
+ # @return (see .[])
413
415
  def screen_restore = +SCREEN_RESTORE
414
416
 
415
- # Use alternative screen buffer.
417
+ # Switch to the alternate screen buffer.
418
+ #
419
+ # @see .screen_alternate_off
416
420
  #
417
- # @return (see cursor_up)
421
+ # @return (see .[])
418
422
  def screen_alternate = +SCREEN_ALTERNATE
419
423
 
420
- # Do not longer use alternative screen buffer.
424
+ # Switch back from the alternate screen buffer.
421
425
  #
422
- # @return (see cursor_up)
426
+ # @see .screen_alternate
427
+ #
428
+ # @return (see .[])
423
429
  def screen_alternate_off = +SCREEN_ALTERNATE_OFF
424
430
 
425
- # Scroll window given lines up.
431
+ # Scroll the screen up.
426
432
  #
427
- # @param lines [Integer] number of lines to scroll
428
- # @return (see cursor_up)
433
+ # @param (see .cursor_up)
434
+ # @return (see .[])
429
435
  def screen_scroll_up(lines = 1) = "\e[#{lines}S"
430
436
 
431
- # Scroll window given lines down.
437
+ # Scroll the screen down.
432
438
  #
433
- # @param (see scroll_up)
434
- # @return (see cursor_up)
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
- # @!endgroup
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
- # @!group Other ANSI control functions
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
- # Repeat last char.
467
+ # Start a hyperlink (OSC 8).
444
468
  #
445
- # @param count [Integer] repeat count
446
- # @return (see cursor_up)
447
- def char_repeat(count = 1) = "\e[#{count}b"
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
- # Erase part of line.
481
+ # End a hyperlink.
450
482
  #
451
- # @param part [:to_end, :to_start, :all] line part to erase
452
- # @return (see cursor_up)
453
- def line_erase(part = :all) = "\e[#{@line_erase[part]}K"
483
+ # @see .link_start
484
+ #
485
+ # @return (see .[])
486
+ def link_end = +LINK_END
454
487
 
455
- # Set (tab) title.
456
- # This is not widely supported; works for
457
- # Hyper,
458
- # iTerm2,
459
- # Kitty,
460
- # MacOS Terminal,
461
- # Tabby,
462
- # WezTerm.
463
- #
464
- # @param title [#to_s] text
465
- # @return (see cursor_up)
466
- def title(title) = "\e]0;#{title}\a"
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
- # Create a hyperlink.
469
- # This is not widely supported; works for
470
- # Ghostty,
471
- # iTerm2,
472
- # Kitty,
473
- # Rio,
474
- # Tabby,
475
- # WezTerm.
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
- # Set task progress state.
494
- # This is not widely supported; works for
495
- # Ghostty,
496
- # iTerm2,
497
- # Kitty.
498
- #
499
- # @param state [:show, :warning, :error, :indeterminate, :hide]
500
- # - `:show`
501
- # show progress indicator with given percent value in default mode
502
- # - `:warning`
503
- # show progress with given percent value in warning mode
504
- # - `:error`
505
- # show progress with given percent value in error mode
506
- # - `:indeterminate`
507
- # show progress in indeterminate state (percent value is ignored)
508
- # - `:hide`
509
- # hide progress indicator (percent value is ignored)
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
- # Create scaled text.
530
- # It uses the
548
+ # Scale text using the
531
549
  # [text sizing protocol](https://sw.kovidgoyal.net/kitty/text-sizing-protocol).
532
- # This is not widely supported; works for Kitty.
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: :centered)
539
- #
540
- # @param text [#to_s]
541
- # text to scale
542
- # @param scale [Integer, nil]
543
- # overall scale size, range 1..7
544
- # @param width [Integer, nil]
545
- # with in cells, range 0..7
546
- # @param fracn [Integer, nil]
547
- # numerator for the fractional scale, range: 0..15
548
- # @param fracd [Integer, nil]
549
- # denominator for the fractional scale, range: 0..15, > fracn
550
- # @param vertical [:top, :bottom, :centered, nil]
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, :centered
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, :centered
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
- '30' => 'black',
620
- '31' => 'red',
621
- '32' => 'green',
622
- '33' => 'yellow',
623
- '34' => 'blue',
624
- '35' => 'magenta',
625
- '36' => 'cyan',
626
- '37' => 'white',
627
- '39' => 'default', # reset
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
- '40' => 'on_black',
630
- '41' => 'on_red',
631
- '42' => 'on_green',
632
- '43' => 'on_yellow',
633
- '44' => 'on_blue',
634
- '45' => 'on_magenta',
635
- '46' => 'on_cyan',
636
- '47' => 'on_white',
637
- '49' => 'on_default', # reset
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' => 'ul_black',
640
- '58;2;0;0;128' => 'ul_blue',
641
- '58;2;0;0;255' => 'ul_bright_blue',
642
- '58;2;0;128;0' => 'ul_green',
643
- '58;2;0;128;128' => 'ul_cyan',
644
- '58;2;0;255;0' => 'ul_bright_green',
645
- '58;2;0;255;255' => 'ul_bright_cyan',
646
- '58;2;128;0;0' => 'ul_red',
647
- '58;2;128;0;128' => 'ul_magenta',
648
- '58;2;128;128;0' => 'ul_yellow',
649
- '58;2;128;128;128' => 'ul_white',
650
- '58;2;255;0;0' => 'ul_bright_red',
651
- '58;2;255;0;255' => 'ul_bright_magenta',
652
- '58;2;255;255;0' => 'ul_bright_yellow',
653
- '58;2;255;255;255' => 'ul_bright_white',
654
- '58;2;64;64;64' => 'ul_bright_black',
655
- '59' => 'ul_default', # reset
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
- '90' => 'bright_black',
658
- '91' => 'bright_red',
659
- '92' => 'bright_green',
660
- '93' => 'bright_yellow',
661
- '94' => 'bright_blue',
662
- '95' => 'bright_magenta',
663
- '96' => 'bright_cyan',
664
- '97' => 'bright_white',
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
- '100' => 'on_bright_black',
667
- '101' => 'on_bright_red',
668
- '102' => 'on_bright_green',
669
- '103' => 'on_bright_yellow',
670
- '104' => 'on_bright_blue',
671
- '105' => 'on_bright_magenta',
672
- '106' => 'on_bright_cyan',
673
- '107' => 'on_bright_white'
674
- }.invert
675
- clr_alias = ->(t, s) { clr_map[t] = clr_map[s] }
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
- '' => 'reset',
688
- '1' => 'bold',
689
- '2' => 'faint',
690
- '3' => 'italic',
691
- '4' => 'underline',
692
- '5' => 'blink',
693
- '6' => 'rapid_blink',
694
- '7' => 'invert',
695
- '8' => 'hide',
696
- '9' => 'strike',
697
- '10' => 'primary_font',
698
- '11' => 'font1',
699
- '12' => 'font2',
700
- '13' => 'font3',
701
- '14' => 'font4',
702
- '15' => 'font5',
703
- '16' => 'font6',
704
- '17' => 'font7',
705
- '18' => 'font8',
706
- '19' => 'font9',
707
- '20' => 'fraktur',
708
- '21' => 'double_underline',
709
- '22' => 'bold_off', # faint_off
710
- '23' => 'italic_off', # fraktur_off
711
- '24' => 'underline_off', # double_underline_off
712
- '25' => 'blink_off', # rapid_blink_off
713
- '26' => 'proportional',
714
- '27' => 'invert_off',
715
- '28' => 'hide_off',
716
- '29' => 'strike_off',
717
- # colors ...
718
- '50' => 'proportional_off',
719
- '51' => 'framed',
720
- '52' => 'encircled',
721
- '53' => 'overlined',
722
- '54' => 'framed_off', # encircled_off
723
- '55' => 'overlined_off',
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
- '73' => 'superscript',
726
- '74' => 'subscript',
727
- '75' => 'superscript_off', # subscript_off
733
+ 'superscript' => '73',
734
+ 'subscript' => '74',
735
+ 'superscript_off' => '75',
736
+ 'subscript_off' => '75',
728
737
  # special underline
729
- '4:3' => 'curly_underline',
730
- '4:4' => 'dotted_underline',
731
- '4:5' => 'dashed_underline',
732
- '4:0' => 'curly_underline_off' # dotted_underline_off, dashed_underline_off
733
- }.invert
734
- attr_alias = ->(t, s) { attr_map[t] = attr_map[s] }
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['faint_off', 'bold_off']
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
- # extra aliases:
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
- # additional shortcuts disable:
794
- attr_alias['/', 'reset']
795
- attr_map['/fg'] = clr_map['default']
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(fg|bg|on|ul)?_?#?([[:xdigit:]]{1,6})\z/.match(str)&.captures
805
+ b, v = /\A(on|ul)?_?#?([[:xdigit:]]{1,6})\z/.match(str)&.captures
802
806
  unless v
803
- b = /\A(fg|bg|on|ul)?_?([a-z]{3,}[0-9]{0,3})\z/.match(str) or next
804
- name = NAMED_COLORS[b[2]] or next
805
- next "#{@cbase[b[1]]};#{name}"
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[_1] = attr_map[_1] }
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