typr — Terminal UI Toolkit

typr is a Ruby library for building interactive, keyboard-driven terminal user interfaces. It is built from data-object based components: grids for arrays, a pager for text, a file picker, and lines for user interaction — each of which renders itself, handles its own keymap, and can be combined into full applications.

typr is designed for speed: every interactive choice is one keystroke away. Selectable rows and columns carry number hints, popups open in / search mode so long lists can be distilled down and picked from in a couple of keystrokes. It is the toolkit behind the Filecon file manager.

typr's terminal layer is self-contained: it ships a precompiled terminfo key table and a MIME-type database inside the gem, so it has no ncurses dependency and works on any raw, ANSI-capable terminal — including Android/Termux.

Features

Components

  • Grid — sortable, filterable table with formatted columns, per-cell colors, and row selection. Columns can be :min/:max/fixed-width, with built-in processors for dates, file sizes, and file trees. Grid#pick column: adds live / search
  • Text — scrollable text viewer with word/row picking, / search with n/p navigation and match highlighting, and borders
  • Browser — file-system directory browser with MIME-type detection (bundled database, optional magic detection), per-type coloring, and an interactive picker
  • Line — interactive prompt/input with colored questions and answers, configurable bindings, and strict ask question types (Array, :key, :line)

Keyboard interaction

Navigation uses hint numbers displayed next to selectable items — rows in grids, words in text, and other elements. Press the number to select that item directly. When more items are visible than fit in one set of hints (1–9, 0), press Tab to cycle through hint groups. Arrow keys, Page Up/Down, Home/End work as expected, and all keys are dispatched through a configurable keymap.

Live / search is built into listings and pickers: popups open in search mode, so you start typing to distill the list down to the matches, then press a number hint to pick from the few remaining results. Text adds less-style search (/, n, p) with smart-case matching and wrap-around.

Mouse support

typr reports and decodes mouse input. Typr.init enables mouse reporting and Typr.exit disables it; Typr.read_key decodes SGR and legacy X10 reports into a Typr::Mouse struct (button, x, y, action, modifiers) with press?/release?/wheel?/left?/middle?/right? predicates. Pickers accept the mouse directly: a left click acts like pressing the row's hint digit, and the wheel scrolls the listing.

Terminal layer

The default terminal frontend is pure ANSI on raw TTY input — no ncurses. Key sequences and keypad escapes are precompiled into a bundled terminfo table (share/terminfo), and MIME types come from a bundled database (share/mimetypes); both ship with the gem, so typr runs anywhere a raw, ANSI-capable terminal exists. The only runtime dependency is unicode-display_width.

Colors

Named colors (basic, shades, and dark_*), grey shades grey0grey100, the terminal's 8-bit palette, and RGB triplets for true-color terminals. Every component takes a per-part colors: hash (columns, headers, hints, search matches, …).

Frontends

  • Terminal (require 'typr') — the default: raw terminal IO, no ncurses, works over SSH and on Termux
  • Graphical (require 'typr/graphical') — experimental: SDL2-based windows

All components work identically across frontends.

Installation

gem install typr

Or in your Gemfile:

gem 'typr', '~> 1.2'

Usage

Grid (table)

require 'typr'

Typr.init

@grid = Typr::Grid.new(
  input:   [
    ["Alice",   30, "engineer"],
    ["Bob",     25, "designer"],
    ["Carol",   35, "manager"]
  ],
  header:  ["Name", "Age", "Role"],
  format:  [:min, :right, :max],
  colors:  { columns: [:yellow, :cyan, :magenta] }
)

Typr.clear
@grid.show
key = Typr.read_key   # reads a single keypress
@grid.reset            # reset grid to default view
Typr.exit

Text viewer

require 'typr'

Typr.init

@text = Typr::Text.new(
  input:  IO.popen(%w[cat /usr/share/dict/words]),
  header: "Words",
  top:    2, left: 0.1, right: 0.9, bottom: -4,
  border: :round,
  colors: { header: [:yellow, :grey30] }
)

Typr.clear
loop do
  @text.show
  key = Typr.read_key
  case key
  when ?q then break
  else     @text.send(key)  # spacebar scrolls, j/k move up/down, etc.
  end
end
Typr.exit

Browser (file picker)

require 'typr'

Typr.init

@browser = Typr::Browser.new(
  directory: "/tmp",
  right: -1, bottom: -1,
  colors: { hints: [:white, :black] }
)

selected = @browser.pick   # interactive file picker, returns the absolute path
puts "You selected: #{selected}"
Typr.exit

Combined example (Grid + prompt line)

require 'typr'

class MyApp
  include Typr

  def initialize
    Typr.init
    @grid = Typr::Grid.new(
      input: [
        ["Item A", 10, true],
        ["Item B", 5,  false],
        ["Item C", 20, true]
      ],
      header:  ["Name", "Qty", "Active"],
      format:  [:max, :right, :min],
      colors:  { columns: [:yellow, :cyan, :green] }
    )
    @prompt = Typr::Line.new(
      top: -1,
      default: "(s)ort  (f)ilter  [esc] quit"
    )
  end

  def run
    Typr.clear
    loop do
      @grid.show
      @prompt.show
      key = Typr.read_key
      case key
      when ?s then @grid.sort_by(@prompt.ask("sort column": [@grid, :column]))
      when ?f then @grid.add_filter(*@prompt.ask("filter column": [@grid, :column], with: :line))
      when KEY_ESCAPE then break
      end
    end
  end

  def shutdown; Typr.exit; end
end

app = MyApp.new
begin
  app.run
ensure
  app.shutdown
end

API Reference

Typr.init / Typr.exit

Initialize and tear down the terminal UI (hides the cursor, enables key-mode and mouse reporting; restores them on exit). Always call these at the start and end of your program.

Typr.clear([scope])

Clears the full screen or a single line (:line).

Typr.read_key

Reads a single keypress in raw mode. Returns the key as a String (KEY_ESCAPE, ?q, arrow escape sequences), a Typr::Mouse struct for mouse reports, or nil when input is unavailable.

Typr.read_line(prompt, ...)

The shared interactive line editor. Renders a prompt and query at any position with arrow keys, ctrl-arrow word jumps, Home/End/Delete/Backspace; Enter confirms, Escape aborts (nil). An optional block runs after each keypress and may short-circuit with a result:

Typr.read_line "/" do |key, query, cursor|
  filter query
  nil
end

Line#ask

Strict question types:

  • Array — pick a row from an object/column (hint-driven)
  • :key — read a single key
  • :line — read a line of text

The question and answer are rendered in @colors[:question]/@colors[:answer].

Layout properties (all components)

  • top, bottom, left, right — placement. Integers for absolute offsets, floats (0-1) for relative sizing. Negative values position from the opposite edge. (bottom: -1 = last row)

Grid options

Option Description
input Array of arrays (data rows)
header Column titles array (symbols define Typr::NAME-style constants)
format Per-column format: :min, :max, or a number for fixed width
procs Per-column processors (built-ins: :datetime, :magnitudes, :filetree, :convert)
colors { columns: [:yellow, :cyan], header: [:white, :black] } etc.
.sort_by Sort the grid on a given column (toggles direction)
.add_filter Add a substring/regex filter on a column (or :all)
.pick :row, column: Interactive row picker with live / search and number hints

Text options

Option Description
input String or IO-like stream to display
header Header bar text
border Border style: :round, " ", etc.
.search Search the text (/); .search_next/.search_prev (n/p)

Browser options

Option Description
directory Root directory to start browsing
.pick([type]) Interactive file picker, returns the absolute path; type filters by MIME family ("image", "audio", "video", "text", "application", "inode")

License

MIT