Terminal adapters

Two ship with @textui/terminal, and the interface is small enough that a third is a short file.

The Node adapter

import { createNodeTerminal } from '@textui/terminal';

const terminal = createNodeTerminal({
  stdin: process.stdin,
  stdout: process.stdout,
  capabilities: { /* overrides */ },
  installExitHandlers: true,
});

The virtual adapter

Three cases that turn out to be one: the test harness, an application embedded in a host that owns the real tty, and a browser-hosted terminal such as xterm.js where output goes to a callback rather than a file descriptor.

import { createVirtualTerminal } from '@textui/terminal';

const terminal = createVirtualTerminal({
  width: 80,
  height: 24,
  onWrite: (data) => xterm.write(data),
});

terminal.feed(bytesFromXterm);      // raw input, through the real decoder
terminal.resize(120, 40);
terminal.output();                  // everything written, for assertions

Because it is embedded by default, it does nothing to anyone’s terminal.

Writing an adapter

Implement TerminalAdapter: size, capabilities, setCapabilityOverrides, acquire, release, write, flush, onInput, onResize, and optionally writeClipboard and setTitle. Anything that can deliver bytes and accept bytes can host TextUI - an ssh session, a pty, a websocket.

Input decoding

The decoder is a state machine over a carry buffer rather than a parser over whole strings, because a terminal splits sequences across reads. An incomplete escape sequence stays buffered and waits, which is what stops a fast paste being read as a burst of keys.

It handles the arrow and function key families, xterm modifier parameters, SGR mouse reporting including wheel and drag, bracketed paste as one event, focus in/out, and the Kitty keyboard protocol where available.


Back to top

MIT licensed. Pre-1.0 - the surface is still moving.