TTY

Terminal UI module for raw input events, styled output, and layout utilities.

This module only works inside terminal context. You cannot use it from regular functions—only from processes running on a Terminal Host.

Loading

local tty = require("tty")

Input Loop

Start the raw input reader, subscribe to events, and process them in a loop:

local tty = require("tty")
local io = require("io")

local function handler()
    tty.start()
    local events = tty.events()

    while true do
        local ev = events:receive()
        if not ev then break end

        if ev.type == "key" then
            if ev.key == "q" or (ev.ctrl and ev.key == "c") then
                break
            end
            io.print("Key: " .. ev.key)

        elseif ev.type == "resize" then
            io.print("Size: " .. ev.width .. "x" .. ev.height)
        end
    end

    tty.stop()
end

Input Control

tty.start()

Enable raw terminal input mode. The terminal switches to raw mode and begins emitting events.

local ok, err = tty.start()

Returns: boolean, error

tty.stop()

Disable raw input and restore the terminal to normal mode.

local ok, err = tty.stop()

Returns: boolean, error

tty.events()

Subscribe to terminal events and return a channel. Events are delivered as tables with a type field.

local events = tty.events()

Returns: EventChannel

tty.screen_size()

Query current terminal dimensions.

local width, height, err = tty.screen_size()

Returns: number, number, error

tty.mouse(enable)

Enable or disable mouse event tracking.

local ok, err = tty.mouse(true)
Parameter Type Description
enable boolean true to enable, false to disable

Returns: boolean, error

Event Types

Events are tables with a type field that determines which other fields are present.

Key Event

{
    type = "key",
    key = "a",           -- printable character or key name
    key_type = "runes",  -- "runes" for printable, or special key name
    action = "press",    -- "press" or "release"
    alt = false,
    ctrl = false,
    shift = false
}

Mouse Event

Requires tty.mouse(true).

{
    type = "mouse",
    action = "press",    -- "press", "release", "motion", "wheel"
    button = "left",     -- button name
    x = 10,
    y = 5,
    alt = false,
    ctrl = false,
    shift = false
}

Resize Event

{type = "resize", width = 120, height = 40}

Start Event

Emitted once after tty.start() with initial dimensions.

{type = "start", width = 120, height = 40}

Focus Event

{type = "focus", focused = true}

Paste Event

{type = "paste", text = "pasted content"}

Key Bindings

Create reusable key bindings that match against key events:

local quit = tty.bind({
    keys = {"q", "ctrl+c"},
    help = {key = "q/ctrl+c", desc = "quit"}
})

-- In event loop
if quit:matches(ev) then
    break
end

tty.bind(config)

Field Type Description
keys string[] Key patterns to match (e.g. "a", "ctrl+c", "enter")
help table Optional. {key = "...", desc = "..."} for help text

Returns: KeyBinding

KeyBinding Methods

Method Returns Description
matches(event) boolean Test if a key event matches this binding
set_enabled(bool) self Enable or disable the binding
is_enabled() boolean Check if the binding is enabled
help() table Returns {key, desc} help info

Styles

Create styled text output using lipgloss-based styling. All style methods return a new style (immutable).

local tty = require("tty")
local io = require("io")

local title = tty.style()
    :bold()
    :foreground("#FF0000")
    :padding(0, 1)

local box = tty.style()
    :border(tty.borders.ROUNDED)
    :border_foreground("#00FF00")
    :width(40)
    :padding(1, 2)

io.print(box:render(title:render("Hello"), "World"))

tty.style()

Create a new empty style.

Returns: Style

Style Methods

All methods return a new Style and can be chained.

Text Decoration

Method Parameter Description
foreground(color) string Text color (hex "#FF0000", ANSI "9", or name)
background(color) string Background color
bold(enable?) boolean Bold text (default: true)
italic(enable?) boolean Italic text
underline(enable?) boolean Underline text
strikethrough(enable?) boolean Strikethrough text
faint(enable?) boolean Dimmed text
blink(enable?) boolean Blinking text
reverse(enable?) boolean Swap foreground/background

Layout

Method Parameter Description
width(n) number Fixed width
height(n) number Fixed height
max_width(n) number Maximum width
max_height(n) number Maximum height
padding(...) numbers Padding (CSS-style: top, right, bottom, left)
margin(...) numbers Margin (CSS-style)
align(pos) number Horizontal alignment
align_vertical(pos) number Vertical alignment
inline(enable?) boolean Inline rendering mode

Borders

Method Parameter Description
border(name, ...) string, booleans Border style, optional per-side toggles
border_foreground(...) strings Border color(s)
border_background(...) strings Border background color(s)

Other

Method Description
render(...) Render strings with this style applied
copy() Create a copy of this style

Border Constants

tty.borders.NORMAL
tty.borders.ROUNDED
tty.borders.THICK
tty.borders.DOUBLE
tty.borders.HIDDEN

Alignment Constants

tty.align.LEFT    -- 0
tty.align.CENTER  -- 0.5
tty.align.RIGHT   -- 1

Text Utilities

Layout and measurement functions for styled text. Available under tty.text.

Measurement

local w = tty.text.width("hello")         -- printable width (ANSI-aware)
local h = tty.text.height("a\nb\nc")      -- line count
local w, h = tty.text.size("hello\nworld") -- both

Joining

-- Join side by side, aligned at top
local row = tty.text.join_horizontal(tty.text.position.TOP, left, right)

-- Stack vertically, centered
local col = tty.text.join_vertical(tty.text.position.CENTER, top, bottom)

Max Dimensions

local w = tty.text.max_width({"short", "a longer string"})   -- widest
local h = tty.text.max_height({"one\ntwo", "single"})         -- tallest

Placement

Place a string within a box of given dimensions:

-- Center in a 80x24 box
local out = tty.text.place(80, 24, tty.text.position.CENTER, tty.text.position.CENTER, content)

-- Horizontal only
local out = tty.text.place_horizontal(80, tty.text.position.RIGHT, content)

-- Vertical only
local out = tty.text.place_vertical(24, tty.text.position.BOTTOM, content)

Position Constants

tty.text.position.TOP      -- 0
tty.text.position.LEFT     -- 0
tty.text.position.CENTER   -- 0.5
tty.text.position.BOTTOM   -- 1
tty.text.position.RIGHT    -- 1

See Also