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



205
206
207
# File 'lib/cli/ui/ansi.rb', line 205

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



59
60
61
# File 'lib/cli/ui/ansi.rb', line 59

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



117
118
119
120
121
# File 'lib/cli/ui/ansi.rb', line 117

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



91
92
93
94
95
# File 'lib/cli/ui/ansi.rb', line 91

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



104
105
106
107
108
# File 'lib/cli/ui/ansi.rb', line 104

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



130
131
132
133
134
# File 'lib/cli/ui/ansi.rb', line 130

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



175
176
177
# File 'lib/cli/ui/ansi.rb', line 175

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

.cursor_saveObject

Save the cursor position

: -> String



168
169
170
# File 'lib/cli/ui/ansi.rb', line 168

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



78
79
80
81
82
# File 'lib/cli/ui/ansi.rb', line 78

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

  control(n.to_s, 'A')
end

.enter_alternate_screenObject

: -> String



137
138
139
# File 'lib/cli/ui/ansi.rb', line 137

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

.exit_alternate_screenObject

: -> String



142
143
144
# File 'lib/cli/ui/ansi.rb', line 142

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

.hide_cursorObject

Hide the cursor

: -> String



161
162
163
# File 'lib/cli/ui/ansi.rb', line 161

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

.insert_lineObject

: -> String



210
211
212
# File 'lib/cli/ui/ansi.rb', line 210

def insert_line
  insert_lines(1)
end

.insert_lines(n = 1) ⇒ Object

: (?Integer n) -> String



215
216
217
# File 'lib/cli/ui/ansi.rb', line 215

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

.match_alternate_screenObject

: -> Regexp



147
148
149
# File 'lib/cli/ui/ansi.rb', line 147

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

.next_lineObject

Move to the next line

: -> String



182
183
184
# File 'lib/cli/ui/ansi.rb', line 182

def next_line
  cursor_down + cursor_horizontal_absolute
end

.previous_lineObject

Move to the previous line

: -> String



189
190
191
# File 'lib/cli/ui/ansi.rb', line 189

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



200
201
202
# File 'lib/cli/ui/ansi.rb', line 200

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



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

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



65
66
67
# File 'lib/cli/ui/ansi.rb', line 65

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

.show_cursorObject

Show the cursor

: -> String



154
155
156
# File 'lib/cli/ui/ansi.rb', line 154

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



47
48
49
# File 'lib/cli/ui/ansi.rb', line 47

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