Module: Fatty::Ansi
- Defined in:
- lib/fatty/ansi.rb,
lib/fatty/ansi.rb,
lib/fatty/ansi/renderer.rb
Overview
Parse ANSI escape sequences (primarily SGR, i.e. "\e[...m") into styled text segments. This is intentionally curses-agnostic: the renderer/context decides how to map Style -> terminal attributes / color pairs.
Supported SGR:
- 0 reset
- 1 bold
- 22 normal intensity (clears bold)
- 7 reverse
- 27 reverse off
- 30-37 / 90-97 foreground (16-color)
- 40-47 / 100-107 background (16-color)
- 38;5;n foreground (256-color index)
- 48;5;n background (256-color index)
Everything else is ignored (but does not break parsing).
Defined Under Namespace
Constant Summary collapse
- ESC =
"\e"- CSI =
"#{ESC}["- COMBINING_MARK_RE =
/\p{M}/- ANSI_ESCAPE =
%r{ \e\[ [0-9;?]* [A-Za-z] | # CSI sequences \e\] .*? (?:\a|\e\\) | # OSC sequences \e[@-Z\\-_] # single-char escapes }x
Class Method Summary collapse
- .plain_text(str) ⇒ Object
-
.segment(str, base: nil) ⇒ Object
Public: segment a string into [[text, Style], ...].
-
.strip(text) ⇒ Object
Remove any ANSI escape sequences from text.
- .truncate_visible(text, max_width) ⇒ Object
-
.visible_char?(ch) ⇒ Boolean
Don't count a "combining character" as visible in the sense that it contributes to width.
-
.visible_length(str) ⇒ Object
Return the screen width taken up by an ANSI-encoded sequence, taking into account Unicode combining characters.
Class Method Details
.plain_text(str) ⇒ Object
307 308 309 |
# File 'lib/fatty/ansi.rb', line 307 def self.plain_text(str) segment(str).map { |text, _style| text }.join end |
.segment(str, base: nil) ⇒ Object
Public: segment a string into [[text, Style], ...]
The returned Style objects are independent copies; you can safely mutate them downstream if you want.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/fatty/ansi.rb', line 91 def self.segment(str, base: nil) s = str.to_s style = (base ? base.dup : Style.default).normalize! out = [] buf = +"" i = 0 n = s.bytesize while i < n if s.getbyte(i) == 27 # ESC # Flush buffered text before processing escape unless buf.empty? out << [buf, style.dup] buf = +"" end consumed = consume_escape!(s, i, style) if consumed > 0 i += consumed else # Not a recognized/complete escape; treat ESC as literal. buf << ESC i += 1 end else buf << s.byteslice(i, 1) i += 1 end end out << [buf, style.dup] unless buf.empty? merge_adjacent_segments(out) end |
.strip(text) ⇒ Object
Remove any ANSI escape sequences from text.
83 84 85 |
# File 'lib/fatty/ansi.rb', line 83 def self.strip(text) text.to_s.gsub(ANSI_ESCAPE, "") end |
.truncate_visible(text, max_width) ⇒ Object
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 |
# File 'lib/fatty/ansi.rb', line 326 def self.truncate_visible(text, max_width) max_width = max_width.to_i return "" if max_width <= 0 out = +"" visible = 0 scanner = StringScanner.new(text.to_s) until scanner.eos? || visible >= max_width if (esc = scanner.scan(ANSI_ESCAPE)) out << esc else ch = scanner.getch width = visible_length(ch) break if visible + width > max_width out << ch visible += width end end out end |
.visible_char?(ch) ⇒ Boolean
Don't count a "combining character" as visible in the sense that it contributes to width. It overlays the prior character so it does not add to the visible width.
322 323 324 |
# File 'lib/fatty/ansi.rb', line 322 def self.visible_char?(ch) !ch.match?(COMBINING_MARK_RE) end |
.visible_length(str) ⇒ Object
Return the screen width taken up by an ANSI-encoded sequence, taking into account Unicode combining characters.
313 314 315 316 317 |
# File 'lib/fatty/ansi.rb', line 313 def self.visible_length(str) segment(str.to_s).sum do |segment_text, _style| segment_text.each_char.count { |ch| visible_char?(ch) } end end |