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_map[arg] || _invalid(arg)
        when Symbol
          @attrs_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)


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

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:



186
187
188
189
190
191
192
193
194
# File 'lib/terminal/ansi.rb', line 186

def bbcode(str)
  return str.dup unless (str = str.to_s).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



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

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



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

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



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

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



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

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



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

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



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

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

.cursor_hideString

Hide cursor.

Returns:

  • (String)

    ANSI escape sequence



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

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



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

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



363
364
365
366
# File 'lib/terminal/ansi.rb', line 363

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



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

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

.cursor_restore_posString

Restore previously saved cursor position.

Returns:

  • (String)

    ANSI escape sequence

See Also:



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

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



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

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

.cursor_save_posString

Save current cursor position.

Returns:

  • (String)

    ANSI escape sequence

See Also:



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

def cursor_save_pos = +CURSOR_POS_SAVE

.cursor_showString

Show cursor.

Returns:

  • (String)

    ANSI escape sequence



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

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



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

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:



230
231
232
233
234
235
236
237
238
239
240
# File 'lib/terminal/ansi.rb', line 230

def escape_bbcode(str)
  return str.dup unless (str = str.to_s).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_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



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

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:



495
496
497
# File 'lib/terminal/ansi.rb', line 495

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

End a hyperlink.

Returns:

  • (String)

    ANSI escape sequence

See Also:



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

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:



473
474
475
# File 'lib/terminal/ansi.rb', line 473

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



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

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



289
290
291
292
# File 'lib/terminal/ansi.rb', line 289

def plain(str)
  str.gsub!(@re_test, '') if (str = unbbcode(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, Boolean)

    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 (Integer) (defaults to: 0)

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

Returns:

  • (String)

    ANSI escape sequence



526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
# File 'lib/terminal/ansi.rb', line 526

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



149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/terminal/ansi.rb', line 149

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



566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
# File 'lib/terminal/ansi.rb', line 566

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:



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

def screen_alternate = +SCREEN_ALTERNATE

.screen_alternate_offString

Switch back from the alternate screen buffer.

Returns:

  • (String)

    ANSI escape sequence

See Also:



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

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



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

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

.screen_restoreString

Restore previously saved screen state.

Returns:

  • (String)

    ANSI escape sequence

See Also:



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

def screen_restore = +SCREEN_RESTORE

.screen_saveString

Save screen state.

Returns:

  • (String)

    ANSI escape sequence

See Also:



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

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



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

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



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

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



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

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
# File 'lib/terminal/ansi.rb', line 131

def try_convert(attributes, separator: ' ')
  return unless attributes
  return if (attributes = attributes.to_s.split(separator)).empty?
  attributes.uniq!
  "\e[#{attributes.map! { @attr_map[it] || return }.join(';')}m"
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:



205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/terminal/ansi.rb', line 205

def unbbcode(str)
  return str.dup unless (str = str.to_s).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_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)


267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/terminal/ansi.rb', line 267

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