Module: Architext::Terminal

Defined in:
lib/architext/terminal.rb

Constant Summary collapse

RESET =
"\e[0m"
HIDE_CURSOR =
"\e[?25l"
SHOW_CURSOR =
"\e[?25h"
CLEAR =
"\e[2J"
CLEAR_TO_END =
"\e[J"
CLEAR_LINE =
"\e[2K"
HOME =
"\e[H"
ALT_SCREEN =
"\e[?1049h"
MAIN_SCREEN =
"\e[?1049l"
PALETTE =
{
  ink: "\e[38;2;221;231;239m",
  white: "\e[38;2;255;255;255m",
  dim: "\e[38;2;126;140;155m",
  faint: "\e[38;2;83;96;112m",
  cyan: "\e[38;2;91;220;255m",
  blue: "\e[38;2;98;151;255m",
  green: "\e[38;2;91;232;184m",
  amber: "\e[38;2;245;196;94m",
  red: "\e[38;2;255;104;112m",
  violet: "\e[38;2;190;142;255m",
  bold: "\e[1m",
  inverse: "\e[7m"
}.freeze
TAGS =
PALETTE.transform_keys(&:to_s).merge('/' => RESET).freeze

Class Method Summary collapse

Class Method Details

.alt_screen_supported?(io = $stdout) ⇒ Boolean

Returns:

  • (Boolean)


38
39
40
# File 'lib/architext/terminal.rb', line 38

def alt_screen_supported?(io = $stdout)
  enabled?(io) && ENV['ARCHITEXT_NO_ALT_SCREEN'].nil?
end

.char_display_width(char) ⇒ Object



96
97
98
99
100
101
102
103
# File 'lib/architext/terminal.rb', line 96

def char_display_width(char)
  codepoint = char.ord
  return 0 if codepoint < 32 || codepoint == 0x7F
  return 0 if combining_mark?(codepoint)
  return 2 if wide_codepoint?(codepoint)

  1
end

.combining_mark?(codepoint) ⇒ Boolean

Returns:

  • (Boolean)


105
106
107
108
109
110
111
# File 'lib/architext/terminal.rb', line 105

def combining_mark?(codepoint)
  (0x0300..0x036F).cover?(codepoint) ||
    (0x1AB0..0x1AFF).cover?(codepoint) ||
    (0x1DC0..0x1DFF).cover?(codepoint) ||
    (0x20D0..0x20FF).cover?(codepoint) ||
    (0xFE00..0xFE0F).cover?(codepoint)
end

.display_width(text) ⇒ Object



92
93
94
# File 'lib/architext/terminal.rb', line 92

def display_width(text)
  text.to_s.each_char.sum { |char| char_display_width(char) }
end

.dumb_terminal?Boolean

Returns:

  • (Boolean)


126
127
128
# File 'lib/architext/terminal.rb', line 126

def dumb_terminal?
  ENV.fetch('TERM', '').downcase == 'dumb'
end

.enabled?(io = $stdout) ⇒ Boolean

Returns:

  • (Boolean)


34
35
36
# File 'lib/architext/terminal.rb', line 34

def enabled?(io = $stdout)
  io.respond_to?(:tty?) && io.tty? && ENV['NO_COLOR'].nil? && !dumb_terminal?
end

.paint(text, *styles, enabled: true) ⇒ Object



42
43
44
45
46
# File 'lib/architext/terminal.rb', line 42

def paint(text, *styles, enabled: true)
  return text.to_s unless enabled

  styles.map { |style| PALETTE.fetch(style) }.join + text.to_s + RESET
end

.render(markup, enabled: true) ⇒ Object

Tiny pretext-style renderer: “[cyan]text” keeps visual markup readable.



49
50
51
52
53
# File 'lib/architext/terminal.rb', line 49

def render(markup, enabled: true)
  return strip_markup(markup) unless enabled

  markup.to_s.gsub(%r{\[(/|[a-z_]+)\]}) { TAGS.fetch(Regexp.last_match(1), Regexp.last_match(0)) }
end

.strip_ansi(text) ⇒ Object



71
72
73
# File 'lib/architext/terminal.rb', line 71

def strip_ansi(text)
  text.to_s.gsub(/\e\[[0-9;?]*[A-Za-z]/, '')
end

.strip_markup(markup) ⇒ Object



55
56
57
# File 'lib/architext/terminal.rb', line 55

def strip_markup(markup)
  markup.to_s.gsub(%r{\[(/|[a-z_]+)\]}, '')
end

.truncate(text, width) ⇒ Object



63
64
65
66
67
68
69
# File 'lib/architext/terminal.rb', line 63

def truncate(text, width)
  plain = text.to_s
  return '' if width <= 0
  return plain if visible_length(plain) <= width

  truncate_plain(strip_ansi(plain), width)
end

.truncate_plain(text, width) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/architext/terminal.rb', line 75

def truncate_plain(text, width)
  return '' if width <= 0
  return text.to_s if display_width(text) <= width
  return text.to_s.each_char.first.to_s if width <= 1

  out = +''
  used = 0
  text.to_s.each_char do |char|
    char_width = char_display_width(char)
    break if used + char_width > width - 3

    out << char
    used += char_width
  end
  "#{out}..."
end

.visible_length(text) ⇒ Object



59
60
61
# File 'lib/architext/terminal.rb', line 59

def visible_length(text)
  display_width(strip_ansi(text))
end

.wide_codepoint?(codepoint) ⇒ Boolean

Returns:

  • (Boolean)


113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/architext/terminal.rb', line 113

def wide_codepoint?(codepoint)
  (0x1100..0x115F).cover?(codepoint) ||
    (0x2329..0x232A).cover?(codepoint) ||
    (0x2E80..0xA4CF).cover?(codepoint) ||
    (0xAC00..0xD7A3).cover?(codepoint) ||
    (0xF900..0xFAFF).cover?(codepoint) ||
    (0xFE10..0xFE19).cover?(codepoint) ||
    (0xFE30..0xFE6F).cover?(codepoint) ||
    (0xFF00..0xFF60).cover?(codepoint) ||
    (0xFFE0..0xFFE6).cover?(codepoint) ||
    (0x1F000..0x1FAFF).cover?(codepoint)
end