TermLib
Dart library for terminal applications. Part of termKit.
Features
- Text styling (colors, bold, italic, underlines, etc.)
- RGB/TrueColor with automatic down sampling
- Cursor control, screen clearing, alternate screen
- Keyboard input (including Kitty protocol)
- Mouse events, focus tracking
- Terminal hyperlinking, notifications
- Bracketed paste
- Synchronous updates
Installation
dependencies:
termlib: ^0.1.0
Quick Start
Output and Styling
import 'package:termlib/termlib.dart';
Future<void> main() async {
final term = TermLib();
// Simple output
term.writeln('Hello, terminal!');
// Styled text
term.writeln(term.style('Bold text')..bold());
term.writeln(term.style('Red on blue')..fg(Color.red)..bg(Color.blue));
// RGB colors (auto-downsamples if terminal doesn't support)
term.writeln(term.style('Custom color')..fg(Color.fromString('#ff6600')));
await term.dispose();
await term.flushThenExit(0);
}
Terminal Control
import 'dart:io';
import 'package:termlib/termlib.dart';
Future<void> main() async {
final term = TermLib();
term
..enableAlternateScreen()
..eraseClear()
..cursorHide()
..setTerminalTitle('My App')
..writeAt(5, 5, term.style('Hello!')..fg(Color.cyan));
sleep(const Duration(seconds: 2));
term
..disableAlternateScreen()
..cursorShow();
await term.dispose();
await term.flushThenExit(0);
}
TermRunner
Recommended for apps needing raw mode, alternate screen, or signal handling. Handles setup, cleanup, and error recovery automatically:
import 'package:termlib/termlib.dart';
Future<void> main() async {
await TermRunner(
alternateScreen: true,
rawMode: true,
hideCursor: true,
mouseEvents: true,
bracketedPaste: true,
inBandResize: true,
lineWrapping: true, // disables autowrap for the app, restored on exit
title: 'My App',
).run((term) async {
term.writeln('Press q to quit');
while (true) {
final event = await term.read<KeyEvent>();
if (event is KeyEvent && event.char == 'q') break;
term.writeln('Key: ${event}');
}
return 0; // exit code
});
}
Features:
- Auto cleanup on normal exit, errors, and signals (SIGINT/SIGTERM)
onCleanupcallback for resource cleanuponErrorcallback for custom error handling- Snapshots terminal modes at
build()(after probe seeding) and restores them on every exit path, including signals
Terminal Modes (withModes)
withModes is the scoped save/restore primitive for terminal modes (raw,
alternate screen, mouse, keyboard enhancement, bracketed paste, in-band resize,
line wrapping, cursor visibility). It applies the named modes for the duration
of a callback, then restores each to the state it had at scope entry — so any
component (readline, a widget, a query helper) leaves the terminal exactly as it
found it.
await term.withModes(() async {
// raw on + autowrap off for the duration; restored on return or throw.
return doInteractiveWork();
}, rawMode: true, lineWrapping: false);
Each mode param is three-state:
null(default) — not managed: untouched, inherits the outer scope. An inner scope never clobbers an outer scope's settings (the good-citizen rule).true— ensure on for the duration, restore the prior value after.false— ensure off for the duration, restore the prior value after.
So a readLine() running inside a host that enabled bracketed paste keeps the
host's paste working: readline doesn't request that mode, so it leaves it alone.
Serial-only. Scopes must be strictly nested or sequential — never two
withModescalls concurrently pending. Signal-safe teardown lives only inTermRunner, so apps that must survive Ctrl-C should use it as the outermost harness.
Input Handling
TermLib detects interactive vs piped input automatically.
Interactive Mode (hasTerminal == true)
Events are queued in background. Use poll() (non-blocking) or read() (blocking):
...
// Non-blocking poll (for render loops)
final event = term.poll<KeyEvent>();
if (event is KeyEvent) {
// handle key
}
// Blocking read (for CLI apps)
final event = await term.read<KeyEvent>();
Piped Mode (hasTerminal == false)
Use stdinStream with transformers:
import 'dart:convert';
...
if (!term.hasTerminal) {
await for (final line in term.stdinStream
.transform(utf8.decoder)
.transform(LineSplitter())) {
term.writeln('Line: $line');
}
}
Raw Mode and Ctrl+C
When raw mode is enabled, Ctrl+C does NOT generate SIGINT - it arrives as a KeyEvent and must be handled manually.
Paste Events
When bracketed paste is enabled (term.enableBracketedPaste()), a paste is delivered as a single PasteEvent carrying the full payload — not as a stream of KeyEvents. Listen for it alongside key events:
await for (final event in term.events) {
switch (event) {
case KeyEvent(): /* handle key */
case PasteEvent(): /* handle paste.text */
default: /* ... */
}
}
Rationale:
- Performance: a large paste is one event, not thousands.
- Security: pasted text is never matched against
KeyBinding— a paste containing e.g.ctrl+pcannot trigger thectrl+paction. - Generality: the payload is opaque, leaving room for richer content (mime-typed) in future terminals.
KeyBinding.resolve only matches KeyEvents of type keyPress. It will never fire for paste content, repeat, or release events.
Key Bindings
Map key specs to actions with KeyBinding<A>:
enum AppAction { quit, save, moveLeft }
final bindings = KeyBinding<AppAction>()
..map(['ctrl+q', 'escape'], AppAction.quit) // aliases
..map(['ctrl+s'], AppAction.save)
..map(['left', 'ctrl+b'], AppAction.moveLeft);
final action = bindings.resolve(keyEvent);
if (action != null) {
// dispatch
}
Key specs use the KeyEvent.fromString grammar ('ctrl+a', 'enter', 'shift+ctrl+enter'). keysFor(action) and toGroupedMap() are useful for help screens and config export.
Examples
See the example directory for more:
colors.dart- Color palettes (ANSI, 256, TrueColor)color_table.dart- Downsampling demo from 256 to 16 colorskey_viewer.dart- Interactive key event viewermatrix.dart- Matrix rain effectpiped_input.dart- Processing piped inputstyles.dart- Text styling demosnake.dart- Simple game exampleterm_info.dart- Terminal capabilities info
Acknowledgements
Inspired by dart_console, crossterm, termenv, termwiz, vaxis, mason.
License
MIT
Libraries
- color_util
- termlib
- Dart library that provides a set of utilities for terminal applications. Provides information about the running terminal as well as a set of methods for styling the text output or interacting with the terminal.