Module: Terminal

Defined in:
lib/terminal.rb,
lib/terminal/ansi.rb,
lib/terminal/text.rb,
lib/terminal/input.rb,
lib/terminal/shell.rb,
lib/terminal/detect.rb,
lib/terminal/version.rb,
lib/terminal/input/key_event.rb,
lib/terminal/text/char_width.rb,
lib/terminal/ansi/named_colors.rb

Overview

Terminal access with support for ANSI control codes and BBCode-like embedded text attribute syntax (see Ansi.bbcode). It automagically detects whether your terminal supports ANSI features, like coloring (see Terminal.colors) or the CSIu protocol support (see Terminal.read_key_event, Terminal.on_key_event and Terminal.input_mode). It calculates the display width for Unicode chars (see Text.width) and help you to display text with word-wise line breaks (see Text.each_line).

Defined Under Namespace

Modules: Ansi, Text Classes: KeyEvent

Constant Summary collapse

VERSION =

The version number of the gem.

'0.17.1'

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.ansi?true, false (readonly)

Return true if the current terminal supports ANSI control codes for output. In this case all output methods (<<, print, puts) will forward ANSI control codes to the terminal and translate BBCode (see Terminal::Ansi.bbcode).

When false is returned (ANSI is not supported) the output methods will not forward ANSI control codes and BBCodes will be removed. The colors method will just return 2 (two).

Returns:

  • (true, false)

    whether ANSI control codes are supported for output

See Also:



32
# File 'lib/terminal.rb', line 32

def ansi? = @ansi

.applicationSymbol? (readonly)

Terminal application identifier.

The detection supports a wide range of terminal emulators but may return nil if an unsupported terminal was found. These are the supported application IDs: :alacritty, :amiga, :code_edit, :dg_unix, :docker, :fluent, :hpterm, :hyper, :iterm, :macos, :mintty, :ms_terminal, :ncr260, :nsterm, :terminator, :terminology, :termite, :vt100, :warp, :wezterm, :wyse, :xnuppc

Returns:

  • (Symbol, nil)

    the application identifier



67
# File 'lib/terminal.rb', line 67

def application = (@application ||= Detect.application)

.colorsInteger (readonly)

Number of supported colors. The detection checks various conditions to find the correct value. The most common values are

  • 16_777_216 for 24-bit encoding (true_color? return true)
  • 256 for 8-Bit encoding
  • 52 for some Unix terminals with an extended color palette
  • 8 for 3-/4-bit encoding
  • 2 if ANSI is not supported in general (ansi? return false)

Returns:

  • (Integer)

    number of supported colors



81
# File 'lib/terminal.rb', line 81

def colors = (@colors ||= ansi? ? Detect.colors : 2)

.columnsInteger

Screen column count. See size for support and detection details.

Returns:

  • (Integer)

    number of available columns



88
# File 'lib/terminal.rb', line 88

def columns = size[1]

.input_mode:csi_u, ... (readonly)

Supported input mode.

Returns:

  • (:csi_u)

    when CSIu protocol supported

  • (:legacy)

    for standard terminal

  • (:dumb)

    for non-interactive input (pipes etc.)

  • (:error)

    when input device is not avail (closed)



20
21
22
# File 'lib/terminal/input.rb', line 20

def input_mode
  @input_mode ||= find_input_mode
end

.pos[Integer, Integer]?

Current cursor position. This is only available when ANSI is supported (ansi? return true).

Returns:

  • ([Integer, Integer])

    cursor position as rows and columns

  • (nil)

    for incompatible terminals



101
102
103
104
105
# File 'lib/terminal.rb', line 101

def pos
  @con&.cursor
rescue IOError
  @con = nil
end

.rowsInteger

Screen row count. See size for support and detection details.

Returns:

  • (Integer)

    number of available rows



119
# File 'lib/terminal.rb', line 119

def rows = size[0]

.size[Integer, Integer]

Screen size as a tuple of rows and columns.

If the terminal does not support the report of it's dimension or ANSI is not supported in general then environment variables COLUMNS and LINES will be used. If this failed [25, 80] will be returned as default.

Setting the terminal size is not widely supported.

Returns:

  • ([Integer, Integer])

    available screen size as rows and columns

See Also:



140
141
142
143
144
145
# File 'lib/terminal.rb', line 140

def size
  @size ||= @inf&.winsize || _default_size
rescue IOError
  @inf = nil
  @size = _default_size
end

.true_color?true, false (readonly)

Returns whether true colors are supported.

Returns:

  • (true, false)

    whether true colors are supported

See Also:



157
# File 'lib/terminal.rb', line 157

def true_color? = (colors == 16_777_216)

.tui?true, false (readonly)

Return true if the current terminal supports ANSI control codes for input and output. In this case not only all output methods (<<, print, puts) will forward ANSI control codes to the terminal and translate BBCode (see Terminal::Ansi.bbcode). But also the input methods read_key_event and on_key_event will support extended key codes, mouse and focus events.

Returns:

  • (true, false)

    whether ANSI control codes are supported for input and output

See Also:



46
47
48
49
50
51
52
53
# File 'lib/terminal.rb', line 46

def tui?
  case input_mode
  when :csi_u, :legacy
    @ansi
  else
    false
  end
end

Class Method Details

.<<(object) ⇒ Terminal

Writes the given object to the terminal. Interprets embedded BBCode.

Parameters:

  • object (#to_s)

    object to write

Returns:



212
213
214
215
216
217
218
# File 'lib/terminal.rb', line 212

def <<(object)
  @out.write(Ansi.bbcode(object)) if @out && object != nil
  self
rescue IOError
  @out = nil
  self
end

.hide_alt_screenTerminal

Hide the alternate screen. Will not send the control code if the alternate screen is not used.

When you called show_alt_screen n-times you need to call hide_alt_screen n-times to show the default screen again.

Returns:



202
203
204
205
# File 'lib/terminal.rb', line 202

def hide_alt_screen
  raw_write(Ansi::SCREEN_ALTERNATE_OFF) if @as > 0 && (@as -= 1).zero?
  self
end

.hide_cursorTerminal

Hide the cursor. Will not send the control code if the cursor is already hidden.

When you called hide_cursor n-times you need to call show_cursor n-times to show the cursor again.

Returns:



166
167
168
169
# File 'lib/terminal.rb', line 166

def hide_cursor
  raw_write(Ansi::CURSOR_HIDE) if ansi? && (@cc += 1) == 1
  self
end

.on_key_event(mouse: false, mouse_move: false, focus: false) {|event| ... } ⇒ true, ...

Event loop for key and mouse events.

Parameters:

  • mouse (true, false) (defaults to: false)

    whether mouse buttons should be reported

  • mouse_move (true, false) (defaults to: false)

    whether mouse movement should be reported

  • focus (true, false) (defaults to: false)

    whether focus/unfocus of terminal window should be reported

Yield Parameters:

Yield Returns:

  • (true, false)

    whether the loop should be continued

Returns:

  • (true)

    when the loop was started

  • (false)

    when the current input device is not available

  • (nil)

    when no block was given



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/terminal/input.rb', line 83

def on_key_event(mouse: false, mouse_move: false, focus: false, &block)
  return unless block
  prevent_input_recursion do
    case input_mode
    when :dumb
      on_bumb_key_event(&block)
      true
    when :csi_u, :legacy
      on_tty_key_event(mouse_option(mouse, mouse_move, focus), &block)
      true
    else
      false
    end
  end
end

Writes the given objects to the terminal. Optionally interprets embedded BBCode.

Parameters:

  • objects (Array<#to_s>)

    any number of objects to write

  • bbcode (true|false) (defaults to: true)

    whether to interpret embedded BBCode

Returns:

  • (nil)


226
227
228
229
230
231
232
233
# File 'lib/terminal.rb', line 226

def print(*objects, bbcode: true)
  return unless @out
  return @out.print(*objects) unless bbcode
  objects.each { @out.write(Ansi.bbcode(_1)) }
  nil
rescue IOError
  @out = nil
end

.puts(*objects, bbcode: true) ⇒ nil

Writes the given objects to the terminal. Writes a newline after each object that does not already end with a newline sequence in it's String represenation. If called without any arguments, writes a newline only.

Optionally interprets embedded BBCode.

Parameters:

  • objects (Array<#to_s>)

    any number of objects to write

  • bbcode (true|false) (defaults to: true)

    whether to interpret embedded BBCode

Returns:

  • (nil)


244
245
246
247
248
249
250
251
252
# File 'lib/terminal.rb', line 244

def puts(*objects, bbcode: true)
  return unless @out
  return @out.puts if objects.empty?
  return @out.puts(objects) unless bbcode
  objects.flatten!
  objects.empty? ? @out.puts : @out.puts(objects.map! { Ansi.bbcode(_1) })
rescue IOError
  @out = nil
end

.read_key(mode: :named) ⇒ String, ...

Deprecated.

Use rather read_key_event for better input handling.

Read next keyboard input.

The input will be returned as named key codes like "Ctrl+c" by default. This can be changed by mode.

Parameters:

  • mode (:named, :raw, :both) (defaults to: :named)

    modifies the result

Returns:

  • (String)

    key code ("as is") in :raw mode

  • (String)

    key name in :named mode

  • ([String, String])

    key code and key name in :both mode

  • (nil)

    in error case



36
37
38
39
40
41
42
43
44
45
# File 'lib/terminal/input.rb', line 36

def read_key(mode: :named)
  warn(
    'Terminal.read_key is deprecaded; use Terminal.read_key_event instead.',
    uplevel: 1
  )
  event = read_key_event or return
  return event.raw if mode == :raw
  key, name = event
  mode == :both ? [key, name] : name || key
end

.read_key_eventKeyEvent?

Read next KeyEvent from standard input.

Returns:

  • (KeyEvent)

    next event

  • (nil)

    in error case



51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/terminal/input.rb', line 51

def read_key_event
  prevent_input_recursion do
    case input_mode
    when :dumb
      raw = read_dumb or return
      opts = @dumb_keys[raw.ord] if raw.size == 1
      KeyEvent.new(raw, *opts)
    when :csi_u, :legacy
      raw = read_tty or return
      KeyEvent[raw]
    end
  end
end

.sh(*cmd, **options) ⇒ [Process::Status, output, error]? .sh(*cmd, **options) {|line, type| ... } ⇒ Process::Status?

Execute a command and report command output line by line or capture the output.

Examples:

Execute a command wih arguments

ret, out, = Terminal.sh('curl', '--version')
raise('command not found - curl') unless ret
puts out

Execute shell commands and print error

ret, _, err = Terminal.sh("mkdir '/test' && cd '/test'")
raise('unable to execute') unless ret
puts("error #{ret.exitstatus}: #{err.join}") unless ret.success?

Copy a file content to clipboard

command = ENV.fetch('CLIP_COPY', 'pbcopy')
File.open(__FILE__) do |file|
  ret, = Terminal.sh(*command, input: file)
  raise("command not found - #{command}") unless ret
end

Overloads:

  • .sh(*cmd, **options) ⇒ [Process::Status, output, error]?

    Returns:

    • ([Process::Status, output, error])

      the status of the process successfully executed, the captured output, the captured error output,

    • (nil)

      when the command was not executable

  • .sh(*cmd, **options) {|line, type| ... } ⇒ Process::Status?

    Yield Parameters:

    • line (String)

      next received line from the process

    • type (:output, :error)

      whether the received line is standard output or error

    Returns:

    • (Process::Status)

      the status of the process successfully executed

    • (nil)

      when the command was not executable

Parameters:

  • cmd (Array<#to_s>)

    command line or command with arguments

  • options (Hash)

    options how to execute the new process; see Process.spawn for default execution options provided by Ruby

Options Hash (**options):

  • :env (Hash<String,String>)

    key/value pairs added to the ENV of the process; with option :unsetenv_others set to true it replaces the ENV

  • :shell (true, false)

    whether to create a seperate shell or not

  • :input (#readpartial, #to_io, Array, #each, #to_a, #to_s)

    piped to the command as input

Raises:

  • (ArgumentError)

    when command is missing



305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/terminal.rb', line 305

def sh(*cmd, **options, &block)
  raise(ArgumentError, 'command expected') if cmd.empty?
  return Shell.exec(cmd, **options, &block) if block_given?
  out = []
  err = []
  [
    Shell.exec(cmd, **options) do |line, type|
      (type == :error ? err : out) << line
    end,
    out,
    err
  ]
end

.show_alt_screenTerminal

Show the alternate screen. Will not send the control code if the alternate screen is already used.

When you called show_alt_screen n-times you need to call hide_alt_screen n-times to show the default screen again.

Returns:



190
191
192
193
# File 'lib/terminal.rb', line 190

def show_alt_screen
  raw_write(Ansi::SCREEN_ALTERNATE) if ansi? && (@as += 1) == 1
  self
end

.show_cursorTerminal

Show the cursor. Will not send the control code if the cursor is not hidden.

When you called hide_cursor n-times you need to call show_cursor n-times to show the cursor again.

Returns:



178
179
180
181
# File 'lib/terminal.rb', line 178

def show_cursor
  raw_write(Ansi::CURSOR_SHOW) if @cc > 0 && (@cc -= 1).zero?
  self
end