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 withn/pnavigation 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
askquestion 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 grey0–grey100, 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