Module: CLI::UI::Truncater

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

Overview

Truncater truncates a string to a provided printable width.

Constant Summary collapse

PARSE_ROOT =
:root
PARSE_ANSI =
:ansi
PARSE_ESC =
:esc
PARSE_ZWJ =
:zwj
ESC =
0x1b
LEFT_SQUARE_BRACKET =
0x5b
ZWJ =

emojipedia.org/emoji-zwj-sequences

0x200d
SEMICOLON =
0x3b
EMOJI_RANGE =

EMOJI_RANGE in particular is super inaccurate. This is best-effort. If you need this to be more accurate, we'll almost certainly accept a PR improving it.

0x1f300..0x1f5ff
NUMERIC_RANGE =
0x30..0x39
LC_ALPHA_RANGE =
0x40..0x5a
UC_ALPHA_RANGE =
0x60..0x71
TRUNCATED =
"\x1b[0m…"

Class Method Summary collapse

Class Method Details

.call(text, printing_width) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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/cli/ui/truncater.rb', line 30

def call(text, printing_width)
  return text if text.size <= printing_width

  width            = 0
  mode             = PARSE_ROOT
  truncation_index = nil

  codepoints = text.codepoints
  codepoints.each.with_index do |cp, index|
    case mode
    when PARSE_ROOT
      case cp
      when ESC # non-printable, followed by some more non-printables.
        mode = PARSE_ESC
      when ZWJ # non-printable, followed by another non-printable.
        mode = PARSE_ZWJ
      else
        width += width(cp)
        if width >= printing_width
          truncation_index ||= index
          # it looks like we could break here but we still want the
          # width calculation for the rest of the characters.
        end
      end
    when PARSE_ESC
      case cp
      when LEFT_SQUARE_BRACKET
        mode = PARSE_ANSI
      else
        mode = PARSE_ROOT
      end
    when PARSE_ANSI
      # ANSI escape codes preeeetty much have the format of:
      # \x1b[0-9;]+[A-Za-z]
      case cp
      when NUMERIC_RANGE, SEMICOLON
      when LC_ALPHA_RANGE, UC_ALPHA_RANGE
        mode = PARSE_ROOT
      else
        # unexpected. let's just go back to the root state I guess?
        mode = PARSE_ROOT
      end
    when PARSE_ZWJ
      # consume any character and consider it as having no width
      # width(x+ZWJ+y) = width(x).
      mode = PARSE_ROOT
    end
  end

  # Without the `width <= printing_width` check, we truncate
  # "foo\x1b[0m" for a width of 3, but it should not be truncated.
  # It's specifically for the case where we decided "Yes, this is the
  # point at which we'd have to add a truncation!" but it's actually
  # the end of the string.
  return text if !truncation_index || width <= printing_width

  codepoints[0...truncation_index].pack("U*") + TRUNCATED
end