Module: Terminal::Ansi

Defined in:
lib/terminal/ansi.rb,
lib/terminal/ansi/named_colors.rb,
lib/terminal/ansi/screen_viewer.rb

Overview

ANSI escape code generation and BBCode-to-ANSI conversion.

Provides methods for text decoration, color, cursor control, screen manipulation, hyperlinks, notifications, and progress indicators. All methods return ANSI escape sequence strings — they do not write to the terminal directly.

Colors can be specified as named symbols, 256-color indices, hex strings, or CSS/X11 color names.

Examples:

Generate ANSI codes

Terminal::Ansi[:bold, :red]   # => "\e[1;31m"
Terminal::Ansi[:on_blue]      # => "\e[44m"

BBCode to ANSI

Terminal::Ansi.bbcode('[bold]Hello[/bold]') # => "\e[1mHello\e[m"

Defined Under Namespace

Classes: ScreenViewer

Class Attribute Summary collapse

ANSI Control Code Generation collapse

BBcode Generation collapse

Tool Functions collapse

Class Attribute Details

.attributesArray<Symbol> (readonly)

All known text attribute names (bold, italic, underline, etc.).

Returns:

  • (Array<Symbol>)


27
# File 'lib/terminal/ansi.rb', line 27

def attributes = @attributes.dup

.colorsArray<Symbol> (readonly)

All known basic color names.

Returns:

  • (Array<Symbol>)


33
# File 'lib/terminal/ansi.rb', line 33

def colors = @colors.dup

.named_colorsArray<Symbol> (readonly)

All CSS/X11 named colors.

Returns:

  • (Array<Symbol>)


39
# File 'lib/terminal/ansi.rb', line 39

def named_colors = NAMED_COLORS.keys.map!(&:to_sym)

Class Method Details

.[](*attributes) ⇒ String

Generate an ANSI escape sequence from attribute names or color indices.

Accepts symbols (:bold, :red), strings ("bold", "#ff0000"), or integers for 256-color palette (0-255 foreground, 256-511 background, 512-767 underline color).

Examples:

Named attributes

Terminal::Ansi[:bold, :italic, :green] # => "\e[1;3;32m"

256-color palette

Terminal::Ansi[196]       # foreground color 196
Terminal::Ansi[256 + 21]  # background color 21

Hex colors

Terminal::Ansi[:ff8800] # => "\e[38;2;255;136;0m"

Named CSS colors

Terminal::Ansi[:coral] # => "\e[38;2;255;127;80m"

Parameters:

  • attributes (Array<String, Symbol, Integer>)

    ANSI attributes

Returns:

  • (String)

    ANSI escape sequence

Raises:

  • (ArgumentError)

    for unknown attributes



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/terminal/ansi.rb', line 65

def [](*attributes)
  return +'' if attributes.empty?
  "\e[#{
    attributes
      .map! do |arg|
        case arg
        when String
          @attr_str_map[arg] || _invalid(arg)
        when Symbol
          @attr_sym_map[arg] || _invalid(arg)
        when (0..255)
          "38;5;#{arg}"
        when (256..511)
          "48;5;#{arg - 256}"
        when (512..767)
          "58;5;#{arg - 512}"
        else
          _invalid(arg)
        end
      end
      .join(';')
  }m"
end

.ansi?(str) ⇒ true, false

Test whether a string contains ANSI escape codes.

Examples:

Terminal::Ansi.ansi?("\e[1mHi\e[m") # => true
Terminal::Ansi.ansi?("Hello")       # => false

Parameters:

  • str (#to_s)

    the string to test

Returns:

  • (true, false)


264
# File 'lib/terminal/ansi.rb', line 264

def ansi?(str) = @re_test.match?(str.to_s)

.bbcode(str) ⇒ String

Convert BBCode markup to ANSI escape codes.

Tags like [bold] are converted to their ANSI equivalents; [/bold] or [/] resets. Unknown tags are left unchanged. Escape a tag with a backslash: [\\bold] renders as [bold].

Examples:

Terminal::Ansi.bbcode('[bold red]Hello[/] World')
# => "\e[1;31mHello\e[m World"

Escaped tag

Terminal::Ansi.bbcode('[\\bold]') # => "[bold]"

Parameters:

  • str (#to_s)

    text with BBCode markup

Returns:

  • (String)

    text with ANSI escape codes

See Also:



191
192
193
194
195
196
197
198
199
200
# File 'lib/terminal/ansi.rb', line 191

def bbcode(str)
  str = str.to_s
  return str.dup unless str.index('[')
  str.gsub(@re_bbcode) do |match_str|
    match = Regexp.last_match(1) or next match_str
    next try_convert(match) || match_str if match[0] != '\\'
    match[0] = ''
    "[#{match}]"
  end
end

.char_repeat(count = 1) ⇒ String

Repeat the last printed character.

Parameters:

  • count (Integer) (defaults to: 1)

    number of repetitions

Returns:

  • (String)

    ANSI escape sequence



452
# File 'lib/terminal/ansi.rb', line 452

def char_repeat(count = 1) = "\e[#{count}b"

.cursor_back(columns = 1) ⇒ String

Move cursor left.

Parameters:

  • columns (Integer) (defaults to: 1)

    number of columns

Returns:

  • (String)

    ANSI escape sequence



331
# File 'lib/terminal/ansi.rb', line 331

def cursor_back(columns = 1) = "\e[#{columns}D"

.cursor_column(column = 1) ⇒ String

Move cursor to absolute column.

Parameters:

  • column (Integer) (defaults to: 1)

    column number

Returns:

  • (String)

    ANSI escape sequence



349
# File 'lib/terminal/ansi.rb', line 349

def cursor_column(column = 1) = "\e[#{column}G"

.cursor_column_rel(column = 1) ⇒ String

Move cursor right by relative columns.

Parameters:

  • column (Integer) (defaults to: 1)

    number of columns

Returns:

  • (String)

    ANSI escape sequence



355
# File 'lib/terminal/ansi.rb', line 355

def cursor_column_rel(column = 1) = "\e[#{column}a"

.cursor_down(lines = 1) ⇒ String

Move cursor down.

Parameters:

  • lines (Integer) (defaults to: 1)

    number of lines

Returns:

  • (String)

    ANSI escape sequence



319
# File 'lib/terminal/ansi.rb', line 319

def cursor_down(lines = 1) = "\e[#{lines}B"

.cursor_forward(columns = 1) ⇒ String

Move cursor right.

Parameters:

  • columns (Integer) (defaults to: 1)

    number of columns

Returns:

  • (String)

    ANSI escape sequence



325
# File 'lib/terminal/ansi.rb', line 325

def cursor_forward(columns = 1) = "\e[#{columns}C"

.cursor_hideString

Hide cursor.

Returns:

  • (String)

    ANSI escape sequence



385
# File 'lib/terminal/ansi.rb', line 385

def cursor_hide = +CURSOR_HIDE

.cursor_next_line(lines = 1) ⇒ String

Move cursor to beginning of line, N lines down.

Parameters:

  • lines (Integer) (defaults to: 1)

    number of lines

Returns:

  • (String)

    ANSI escape sequence



337
# File 'lib/terminal/ansi.rb', line 337

def cursor_next_line(lines = 1) = "\e[#{lines}E"

.cursor_pos(row, column = nil) ⇒ String

Move cursor to absolute position.

Examples:

Terminal::Ansi.cursor_pos(1, 1)  # home position
Terminal::Ansi.cursor_pos(10, 5) # row 10, column 5

Parameters:

  • row (Integer, nil)

    row number; nil moves to home

  • column (Integer, nil) (defaults to: nil)

    column number

Returns:

  • (String)

    ANSI escape sequence



372
373
374
375
# File 'lib/terminal/ansi.rb', line 372

def cursor_pos(row, column = nil)
  return column ? "\e[;#{column}H" : "\e[H" unless row
  column ? "\e[#{row};#{column}H" : "\e[#{row}H"
end

.cursor_prev_line(lines = 1) ⇒ String

Move cursor to beginning of line, N lines up.

Parameters:

  • lines (Integer) (defaults to: 1)

    number of lines

Returns:

  • (String)

    ANSI escape sequence



343
# File 'lib/terminal/ansi.rb', line 343

def cursor_prev_line(lines = 1) = "\e[#{lines}F"

.cursor_restore_posString

Restore previously saved cursor position.

Returns:

  • (String)

    ANSI escape sequence

See Also:



399
# File 'lib/terminal/ansi.rb', line 399

def cursor_restore_pos = +CURSOR_POS_RESTORE

.cursor_row_rel(row = 1) ⇒ String

Move cursor down by relative rows.

Parameters:

  • row (Integer) (defaults to: 1)

    number of rows

Returns:

  • (String)

    ANSI escape sequence



361
# File 'lib/terminal/ansi.rb', line 361

def cursor_row_rel(row = 1) = "\e[#{row}e"

.cursor_save_posString

Save current cursor position.

Returns:

  • (String)

    ANSI escape sequence

See Also:



392
# File 'lib/terminal/ansi.rb', line 392

def cursor_save_pos = +CURSOR_POS_SAVE

.cursor_showString

Show cursor.

Returns:

  • (String)

    ANSI escape sequence



380
# File 'lib/terminal/ansi.rb', line 380

def cursor_show = +CURSOR_SHOW

.cursor_up(lines = 1) ⇒ String

Move cursor up.

Parameters:

  • lines (Integer) (defaults to: 1)

    number of lines

Returns:

  • (String)

    ANSI escape sequence



313
# File 'lib/terminal/ansi.rb', line 313

def cursor_up(lines = 1) = "\e[#{lines}A"

.decorate(str, *attributes, reset: true) ⇒ String

Wrap a string with ANSI escape codes.

Examples:

Terminal::Ansi.decorate('Hello', :bold, :red)
# => "\e[1;31mHello\e[m"

Without reset

Terminal::Ansi.decorate('Hello', :bold, reset: false)
# => "\e[1mHello"

Parameters:

  • str (#to_s)

    the string to decorate

  • attributes (Array<String, Symbol, Integer>)

    ANSI attributes to apply

  • reset (true, false) (defaults to: true)

    append a reset code at the end

Returns:

  • (String)

    the decorated string



103
104
105
106
# File 'lib/terminal/ansi.rb', line 103

def decorate(str, *attributes, reset: true)
  attributes = self[*attributes]
  attributes.empty? ? "#{str}" : "#{attributes}#{str}#{"\e[m" if reset}"
end

.escape_bbcode(str) ⇒ String

Escape BBCode tags so they render as literal text after bbcode processing.

Examples:

Terminal::Ansi.escape_bbcode('[bold]Hi[/bold]')
# => "[\\bold]Hi[\\/bold]"

Parameters:

  • str (#to_s)

    text with BBCode markup

Returns:

  • (String)

    text with BBCode tags escaped

See Also:



237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/terminal/ansi.rb', line 237

def escape_bbcode(str)
  str = str.to_s
  return str.dup unless str.index('[')
  str.gsub(@re_bbcode) do |match_str|
    fc = match_str[1]
    next match_str if fc == '\\' || fc == ']'
    match = Regexp.last_match(1) or next match_str
    next match_str if (parts = match.split).empty?
    next "[\\#{match}]" if parts.all? { @attr_str_map[it] }
    match_str
  end
end

.line_erase(part = :all) ⇒ String

Erase part of the current line.

Parameters:

  • part (Symbol) (defaults to: :all)

    area to erase: :all, :to_end, or :to_start

Returns:

  • (String)

    ANSI escape sequence



459
# File 'lib/terminal/ansi.rb', line 459

def line_erase(part = :all) = "\e[#{@line_erase[part]}K"

Create a complete hyperlink (OSC 8).

Examples:

Terminal::Ansi.link('https://example.com', 'Example')

Parameters:

  • text (String)

    the visible link text

  • url (#to_s)

    the link URL

  • params (Hash)

    optional link parameters (e.g., id:)

Returns:

  • (String)

    ANSI escape sequence

See Also:



504
505
506
# File 'lib/terminal/ansi.rb', line 504

def link(url, text, **params)
  "#{link_start(url, **params)}#{text}#{LINK_END}"
end

End a hyperlink.

Returns:

  • (String)

    ANSI escape sequence

See Also:



491
# File 'lib/terminal/ansi.rb', line 491

def link_end = +LINK_END
Note:

Supported by Ghostty, iTerm2, Kitty, Rio, Tabby, WezTerm.

Start a hyperlink (OSC 8).

Parameters:

  • url (#to_s)

    the link URL

  • params (Hash)

    optional link parameters (e.g., id:)

Returns:

  • (String)

    ANSI escape sequence

See Also:



482
483
484
# File 'lib/terminal/ansi.rb', line 482

def link_start(url, **params)
  "\e]8;#{params.map { it.join('=') }.join(':')};#{url}\a"
end

.notify(text) ⇒ String

Note:

Supported by Ghostty, iTerm2, Kitty, WezTerm.

Send a desktop notification via the terminal.

Examples:

Terminal.raw_write(Terminal::Ansi.notify('Build complete!'))

Parameters:

  • text (to_s)

    the notification text

Returns:

  • (String)

    ANSI escape sequence



516
# File 'lib/terminal/ansi.rb', line 516

def notify(text) = "\e]9;#{text}\a"

.plain(str) ⇒ String

Remove both BBCode tags and ANSI escape codes, returning plain text.

Examples:

Terminal::Ansi.plain('[bold]Hello[/bold]')     # => "Hello"
Terminal::Ansi.plain("\e[1mHello\e[m")         # => "Hello"

Parameters:

  • str (#to_s)

    text with BBCode and/or ANSI codes

Returns:

  • (String)

    plain text



297
298
299
300
301
# File 'lib/terminal/ansi.rb', line 297

def plain(str)
  str = unbbcode(str)
  str.gsub!(@re_test, '') if str.index("\e")
  str
end

.progress(state, percent = 0) ⇒ String

Note:

Supported by Ghostty, iTerm2, Kitty.

Show or update a progress indicator in the terminal tab/title bar.

Examples:

Terminal.raw_write(Terminal::Ansi.progress(50))     # 50%
Terminal.raw_write(Terminal::Ansi.progress(:error)) # error state
Terminal.raw_write(Terminal::Ansi.progress(nil))    # hide

Parameters:

  • state (Symbol, Numeric, true, false)

    progress state:

    • :show or true to show, :err/:error, :warn/:warning, :indeterminate, a Numeric (0-100) to set percentage, or any other value to hide
  • percent (#to_i) (defaults to: 0)

    progress percentage (0-100, used with :show state)

Returns:

  • (String)

    ANSI escape sequence



535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
# File 'lib/terminal/ansi.rb', line 535

def progress(state, percent = 0)
  case state
  when :show, true
    state = 1
  when :err, :error
    state = 2
  when :warn, :warning
    state = 4
  when Numeric
    percent, state = state, 1
  when :indeterminate
    return +PROGRESS_SHOW_INDETERMINATE
  else
    return +PROGRESS_HIDE
  end
  "\e]9;4;#{state};#{percent.to_i.clamp(0, 100)}\a"
end

.rainbow(str, frequency: 0.3, spread: 0.8, seed: 1.1) ⇒ String

Generate rainbow-colored text using true color (24-bit) ANSI codes.

Examples:

puts Terminal::Ansi.rainbow('Hello, World!')

Parameters:

  • str (#to_s)

    text to colorize

  • frequency (Float) (defaults to: 0.3)

    color wave frequency

  • spread (Float) (defaults to: 0.8)

    color wave spread

  • seed (Float) (defaults to: 1.1)

    starting position in the color cycle

Returns:

  • (String)

    text with per-character RGB foreground colors



154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/terminal/ansi.rb', line 154

def rainbow(str, frequency: 0.3, spread: 0.8, seed: 1.1)
  pos = -1
  @pi2_third ||= 2.0 * Math::PI / 3.0
  @pi4_third ||= 4.0 * Math::PI / 3.0
  (
    str.to_s.chars.map! do |char|
      i = (seed + ((pos += 1) / spread)) * frequency
      "\e[38;2;#{(Math.sin(i) * 255).round.abs};#{
        (Math.sin(i + @pi2_third) * 255).round.abs
      };#{(Math.sin(i + @pi4_third) * 255).round.abs}m#{char}"
    end << RESET_FG
  ).join
end

.scale(text, scale: nil, width: nil, fracn: nil, fracd: nil, vertical: nil, horizontal: nil) ⇒ String

Note:

Only supported by Kitty.

Scale text using the text sizing protocol.

Examples:

Double-height Greeting

Terminal::Ansi.scale('Hello Ruby!', scale: 2)

Half-height Greeting

Terminal::Ansi.scale('Hello Ruby!', fracn: 1, fracd: 2, vertical: :middle)

Parameters:

  • text (#to_s)

    text to scale

  • scale (Integer, nil) (defaults to: nil)

    scale multiplier (1-7)

  • width (Integer, nil) (defaults to: nil)

    width mode (0-7)

  • fracn (Integer, nil) (defaults to: nil)

    fractional numerator (0-15)

  • fracd (Integer, nil) (defaults to: nil)

    fractional denominator (must be > fracn, max 15)

  • vertical (Symbol, Integer, nil) (defaults to: nil)

    vertical alignment: :top, :bottom, :middle

  • horizontal (Symbol, Integer, nil) (defaults to: nil)

    horizontal alignment: :left, :right, :center

Returns:

  • (String)

    ANSI escape sequence



575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
# File 'lib/terminal/ansi.rb', line 575

def scale(
  text,
  scale: nil,
  width: nil,
  fracn: nil,
  fracd: nil,
  vertical: nil,
  horizontal: nil
)
  opts = scale ? ["s=#{scale.clamp(1, 7)}"] : []
  opts << "w=#{width.clamp(0, 7)}" if width
  if fracn
    opts << "n=#{fracn = fracn.clamp(0, 15)}"
    opts << "d=#{fracd.clamp(fracn + 1, 15)}" if fracd
    case vertical
    when 0, :top
      opts << 'v=0'
    when 1, :bottom
      opts << 'v=1'
    when 2, :middle
      opts << 'v=2'
    end
    case horizontal
    when 0, :left
      opts << 'h=0'
    when 1, :right
      opts << 'h=1'
    when 2, :center
      opts << 'h=2'
    end
  end
  "\e]66;#{opts.join(':')};#{text}\a"
end

.screen_alternateString

Switch to the alternate screen buffer.

Returns:

  • (String)

    ANSI escape sequence

See Also:



427
# File 'lib/terminal/ansi.rb', line 427

def screen_alternate = +SCREEN_ALTERNATE

.screen_alternate_offString

Switch back from the alternate screen buffer.

Returns:

  • (String)

    ANSI escape sequence

See Also:



434
# File 'lib/terminal/ansi.rb', line 434

def screen_alternate_off = +SCREEN_ALTERNATE_OFF

.screen_erase(part = :all) ⇒ String

Erase part of the screen.

Parameters:

  • part (Symbol) (defaults to: :all)

    area to erase: :all, :below, :above, or :scrollback

Returns:

  • (String)

    ANSI escape sequence



406
# File 'lib/terminal/ansi.rb', line 406

def screen_erase(part = :all) = "\e[#{@screen_erase[part]}J"

.screen_restoreString

Restore previously saved screen state.

Returns:

  • (String)

    ANSI escape sequence

See Also:



420
# File 'lib/terminal/ansi.rb', line 420

def screen_restore = +SCREEN_RESTORE

.screen_saveString

Save screen state.

Returns:

  • (String)

    ANSI escape sequence

See Also:



413
# File 'lib/terminal/ansi.rb', line 413

def screen_save = +SCREEN_SAVE

.screen_scroll_down(lines = 1) ⇒ String

Scroll the screen down.

Parameters:

  • lines (Integer) (defaults to: 1)

    number of lines

Returns:

  • (String)

    ANSI escape sequence



446
# File 'lib/terminal/ansi.rb', line 446

def screen_scroll_down(lines = 1) = "\e[#{lines}T"

.screen_scroll_up(lines = 1) ⇒ String

Scroll the screen up.

Parameters:

  • lines (Integer) (defaults to: 1)

    number of lines

Returns:

  • (String)

    ANSI escape sequence



440
# File 'lib/terminal/ansi.rb', line 440

def screen_scroll_up(lines = 1) = "\e[#{lines}S"

.title(title) ⇒ String

Note:

Supported by Hyper, iTerm2, Kitty, macOS Terminal, Tabby, WezTerm.

Set the terminal window title.

Examples:

Terminal.raw_write(Terminal::Ansi.title('My App'))

Parameters:

  • title (String)

    the title text

Returns:

  • (String)

    ANSI escape sequence



470
# File 'lib/terminal/ansi.rb', line 470

def title(title) = "\e]0;#{title}\a"

.try_convert(attributes, separator: ' ') ⇒ String?

Try to convert a space-separated attribute string to an ANSI escape sequence.

Examples:

Terminal::Ansi.try_convert('bold red')  # => "\e[1;31m"
Terminal::Ansi.try_convert('invalid')   # => nil

Parameters:

  • attributes (#to_s)

    space-separated attribute names

  • separator (String) (defaults to: ' ')

    delimiter for splitting

Returns:

  • (String, nil)

    ANSI escape sequence, or nil if any attribute is invalid



131
132
133
134
135
136
137
138
139
140
141
# File 'lib/terminal/ansi.rb', line 131

def try_convert(attributes, separator: ' ')
  return unless attributes
  cache = @convert_cache[separator]
  hit = cache[key = attributes.to_s] and return hit
  cache.clear if cache.size > 1024 # cache limit
  cache[key] = unless (attributes = key.split(separator)).empty?
    # attributes.uniq!
    codes = attributes.map { @attr_str_map[it] || break }
    "\e[#{codes.join(';')}m" if codes
  end
end

.unbbcode(str) ⇒ String

Remove BBCode tags from a string, keeping the enclosed text.

Examples:

Terminal::Ansi.unbbcode('[bold]Hello[/bold]') # => "Hello"

Parameters:

  • str (#to_s)

    text with BBCode markup

Returns:

  • (String)

    text with tags removed

See Also:



211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/terminal/ansi.rb', line 211

def unbbcode(str)
  str = str.to_s
  return str.dup unless str.index('[')
  str.gsub(@re_bbcode) do |match_str|
    match = Regexp.last_match(1) or next match_str
    if match[0] == '\\'
      match[0] = ''
      next "[#{match}]"
    end
    next match_str if (match = match.split).empty?
    next if match.all? { @attr_str_map[it] }
    match_str
  end
end

.undecorate(str) ⇒ String

Remove all ANSI escape codes from a string.

Examples:

Terminal::Ansi.undecorate("\e[1;31mHello\e[m")
# => "Hello"

Parameters:

  • str (#to_s)

    the string to clean

Returns:

  • (String)

    a copy with all ANSI codes stripped



116
117
118
# File 'lib/terminal/ansi.rb', line 116

def undecorate(str)
  (str = str.to_s).index("\e") ? str.gsub(@re_test, '') : str.dup
end

.valid?(*attributes) ⇒ true, false

Check whether all given attributes are valid.

Examples:

Terminal::Ansi.valid?(:bold, :red)  # => true
Terminal::Ansi.valid?(:nope)        # => false

Parameters:

  • attributes (Array<String, Symbol, Integer>)

    attributes to validate

Returns:

  • (true, false)


275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/terminal/ansi.rb', line 275

def valid?(*attributes)
  attributes.all? do |arg|
    case arg
    when String
      @attr_str_map[arg]
    when Symbol
      @attr_sym_map[arg]
    when (0..767)
      true
    end
  end
end