Module: CLI::UI::ANSI

Defined in:
lib/cli/ui/ansi.rb

Constant Summary collapse

ESC =
"\x1b"
CSI_SEQUENCE =
/\x1b\[[\d;:]+[\x20-\x2f]*?[\x40-\x7e]/
OSC_SEQUENCE =

ghostty.org/docs/vt/concepts/sequences#osc-sequences OSC sequences can be terminated with either ST (x1bx5c) or BEL (x07)

/\x1b\][^\x07\x1b]*?(?:\x07|\x1b\x5c)/

Class Method Summary collapse

Class Method Details

.clear_to_end_of_lineObject

: -> String



207
208
209
# File 'lib/cli/ui/ansi.rb', line 207

def clear_to_end_of_line
  control('', 'K')
end

.control(args, cmd) ⇒ Object

Returns an ANSI control sequence

Attributes

  • args - Argument to pass to the ANSI control sequence

  • cmd - ANSI control sequence Command

: (String args, String cmd) -> String



61
62
63
# File 'lib/cli/ui/ansi.rb', line 61

def control(args, cmd)
  ESC + '[' + args + cmd
end

.cursor_back(n = 1) ⇒ Object

Move the cursor back n columns

Attributes

  • n - number of columns by which to move the cursor back

: (?Integer n) -> String



119
120
121
122
123
# File 'lib/cli/ui/ansi.rb', line 119

def cursor_back(n = 1)
  return '' if n.zero?

  control(n.to_s, 'D')
end

.cursor_down(n = 1) ⇒ Object

Move the cursor down n lines

Attributes

  • n - number of lines by which to move the cursor down

: (?Integer n) -> String



93
94
95
96
97
# File 'lib/cli/ui/ansi.rb', line 93

def cursor_down(n = 1)
  return '' if n.zero?

  control(n.to_s, 'B')
end

.cursor_forward(n = 1) ⇒ Object

Move the cursor forward n columns

Attributes

  • n - number of columns by which to move the cursor forward

: (?Integer n) -> String



106
107
108
109
110
# File 'lib/cli/ui/ansi.rb', line 106

def cursor_forward(n = 1)
  return '' if n.zero?

  control(n.to_s, 'C')
end

.cursor_horizontal_absolute(n = 1) ⇒ Object

Move the cursor to a specific column

Attributes

  • n - The column to move to

: (?Integer n) -> String



132
133
134
135
136
# File 'lib/cli/ui/ansi.rb', line 132

def cursor_horizontal_absolute(n = 1)
  cmd = control(n.to_s, 'G')
  cmd += cursor_back if CLI::UI::OS.current.shift_cursor_back_on_horizontal_absolute?
  cmd
end

.cursor_restoreObject

Restore the saved cursor position

: -> String



177
178
179
# File 'lib/cli/ui/ansi.rb', line 177

def cursor_restore
  control('', 'u')
end

.cursor_saveObject

Save the cursor position

: -> String



170
171
172
# File 'lib/cli/ui/ansi.rb', line 170

def cursor_save
  control('', 's')
end

.cursor_up(n = 1) ⇒ Object

Move the cursor up n lines

Attributes

  • n - number of lines by which to move the cursor up

: (?Integer n) -> String



80
81
82
83
84
# File 'lib/cli/ui/ansi.rb', line 80

def cursor_up(n = 1)
  return '' if n.zero?

  control(n.to_s, 'A')
end

.enter_alternate_screenObject

: -> String



139
140
141
# File 'lib/cli/ui/ansi.rb', line 139

def enter_alternate_screen
  control('?1049', 'h')
end

.exit_alternate_screenObject

: -> String



144
145
146
# File 'lib/cli/ui/ansi.rb', line 144

def exit_alternate_screen
  control('?1049', 'l')
end

.hide_cursorObject

Hide the cursor

: -> String



163
164
165
# File 'lib/cli/ui/ansi.rb', line 163

def hide_cursor
  control('', '?25l')
end

.insert_lineObject

: -> String



212
213
214
# File 'lib/cli/ui/ansi.rb', line 212

def insert_line
  insert_lines(1)
end

.insert_lines(n = 1) ⇒ Object

: (?Integer n) -> String



217
218
219
# File 'lib/cli/ui/ansi.rb', line 217

def insert_lines(n = 1)
  control(n.to_s, 'L')
end

.match_alternate_screenObject

: -> Regexp



149
150
151
# File 'lib/cli/ui/ansi.rb', line 149

def match_alternate_screen
  /#{Regexp.escape(control("?1049", ""))}[hl]/
end

.next_lineObject

Move to the next line

: -> String



184
185
186
# File 'lib/cli/ui/ansi.rb', line 184

def next_line
  cursor_down + cursor_horizontal_absolute
end

.previous_lineObject

Move to the previous line

: -> String



191
192
193
# File 'lib/cli/ui/ansi.rb', line 191

def previous_line
  previous_lines(1)
end

.previous_lines(n = 1) ⇒ Object

Move to the previous n lines

Attributes

  • n - number of lines by which to move the cursor up

: (?Integer n) -> String



202
203
204
# File 'lib/cli/ui/ansi.rb', line 202

def previous_lines(n = 1)
  cursor_up(n) + cursor_horizontal_absolute
end

.printing_width(str) ⇒ Object

ANSI escape sequences (like x1b[31m) have zero width. when calculating the padding width, we must exclude them. This also implements a basic version of utf8 character width calculation like we could get for real from something like utf8proc.

: (String str) -> Integer



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/cli/ui/ansi.rb', line 23

def printing_width(str)
  zwj = false #: bool
  strip_codes(str).codepoints.reduce(0) do |acc, cp|
    if zwj
      zwj = false
      next acc
    end
    case cp
    when 0x200d # zero-width joiner
      zwj = true
      acc
    when "\n"
      acc
    else
      acc + 1
    end
  end
end

.sgr(params) ⇒ Object



67
68
69
# File 'lib/cli/ui/ansi.rb', line 67

def sgr(params)
  control(params, 'm')
end

.show_cursorObject

Show the cursor

: -> String



156
157
158
# File 'lib/cli/ui/ansi.rb', line 156

def show_cursor
  control('', '?25h')
end

.strip_codes(str) ⇒ Object

Strips ANSI codes from a str

Attributes

  • str - The string from which to strip codes

: (String str) -> String



49
50
51
# File 'lib/cli/ui/ansi.rb', line 49

def strip_codes(str)
  str.gsub(Regexp.union(CSI_SEQUENCE, OSC_SEQUENCE, /\r/), '')
end