TauTUI is a Swift 6 port of @mariozechner/pi-tui: the same differential renderer, bracketed paste handling, autocomplete, and component set—implemented with Swift concurrency, value semantics, and native terminal plumbing for macOS + Linux.
- Differential Rendering – Three rendering strategies (first frame, resize/full clear, partial diff) wrapped in CSI 2026 synchronized output for flicker‑free updates.
- Bracketed Paste Mode – Handles large pastes via
[paste #n ...]markers and replaces them on submit, just like pi-tui. - Component-based API –
Componentprotocol withrender(width:)/handle(input:), plusContainerfor composition. - Built-in Components –
Text,MarkdownComponent,Input,Editor,SelectList,Loader,Spacer,TruncatedText. - Autocomplete – Slash-command + filesystem completion via
CombinedAutocompleteProvider, including@attachment filtering. - Terminal Implementations –
ProcessTerminal(raw mode, modifier-aware key parsing) andVirtualTerminal(test harness).
Add TauTUI to your Package.swift:
// swift-tools-version: 6.2
import PackageDescription
let package = Package(
name: "Demo",
platforms: [.macOS(.v13)],
dependencies: [
.package(url: "https://github.com/steipete/TauTUI.git", branch: "main"),
],
targets: [
.executableTarget(
name: "Demo",
dependencies: [
.product(name: "TauTUI", package: "TauTUI"),
]),
])import TauTUI
@main
struct DemoApp {
static func main() throws {
let terminal = ProcessTerminal()
let tui = TUI(terminal: terminal)
let text = Text(text: "Welcome to TauTUI!", paddingX: 1, paddingY: 1)
tui.addChild(text)
let editor = Editor()
editor.onSubmit = { value in
tui.addChild(MarkdownComponent(text: value))
tui.requestRender()
}
tui.addChild(editor)
tui.setFocus(editor)
try tui.start()
RunLoop.main.run()
}
}let tui = TUI(terminal: ProcessTerminal())
tui.addChild(component)
tui.removeChild(component)
tui.requestRender()
try tui.start()
tui.stop()Every component conforms to:
public protocol Component: AnyObject {
func render(width: Int) -> [String]
func handle(input: TerminalInput)
}Container is a convenience component that renders its children sequentially.
let text = Text(text: "Hello", paddingX: 2, paddingY: 1)
text.background = Text.Background(red: 30, green: 30, blue: 40)Word-wrapped text with optional RGB background and padding, cached per width.
let md = MarkdownComponent(
text: "# Heading",
padding: .init(horizontal: 1, vertical: 1),
theme: .default,
defaultTextStyle: .init(
color: AnsiStyling.rgb(230, 230, 230),
background: .rgb(52, 53, 65)))Renders headings, lists, tables, blockquotes, and fenced code via swift-markdown, with RGB background + foreground tints.
Single-line editor with fake cursor and horizontal scrolling:
let input = Input()
input.onSubmit = { print($0) }
input.setValue("Initial text")Full multiline editor with autocomplete, modifiers, paste markers:
let editor = Editor()
editor.onChange = { print("Changed", $0) }
editor.onSubmit = { print("Submitted", $0) }
editor.setAutocompleteProvider(CombinedAutocompleteProvider(commands: [DemoCommand()]))Key bindings include Enter for submit, Shift/Ctrl/Alt+Enter for newlines (Alt is the most reliable across terminals), Ctrl+K/U/W/A/E, Option word motion, Tab for autocomplete, Escape to cancel, etc.
let list = SelectList(items: [
SelectItem(value: "clear", label: "Clear", description: "Remove messages"),
SelectItem(value: "delete", label: "Delete", description: "Delete last"),
])
list.onSelect = { print($0.value) }
list.onCancel = { print("cancel") }Scrollable list with arrow navigation, Enter selection, Escape cancel.
let truncated = TruncatedText(text: "A very long title that will be trimmed…", paddingX: 1)Renders only the first line, truncates with a reset + ellipsis, and pads to the viewport width.
Loader renders the Braille spinner and notifies its TUI/closure every tick; Spacer inserts empty lines for layout.
Theme-aware components accept injected themes; defaults keep the classic look. To change colors globally:
var palette = ThemePalette.dark()
palette.editor = .init(borderColor: AnsiStyling.color(36), selectList: .default)
palette.markdown = .default // customize fields as needed
palette.textBackground = .init(red: 24, green: 26, blue: 32)
tui.apply(theme: palette) // pushes themes to children and re-rendersYou can still pass per-instance themes when constructing components; apply(theme:) refreshes any theme-aware component already on screen.
CombinedAutocompleteProvider supports slash commands, inline command items, file paths, and attachment filtering.
let provider = CombinedAutocompleteProvider(
commands: [DemoCommand()],
staticCommands: [AutocompleteItem(value: "clear", label: "/clear")],
basePath: FileManager.default.currentDirectoryPath)
struct DemoCommand: SlashCommand {
let name = "clear"
let description: String? = "Clear all messages"
func argumentCompletions(prefix: String) -> [AutocompleteItem] { [] }
}Features:
- Type
/for commands; Tab auto-completes. - Tab also forces file completion (
./,~/,../, relative paths). @prefix restricts suggestions to attachable files (text + common images).- Provider exposes
forceFileSuggestions/shouldTriggerFileCompletionso components can request hints explicitly.
TauTUI mirrors pi-tui’s renderer:
- First render – emit all lines inside
CSI ?2026 h ... lwith no clears. - Resize / change above viewport –
CSI 3J,CSI 2J, full redraw. - Diff – compute first/last changed lines, move cursor, clear to end, rewrite modified lines only.
VisibleWidth.measure strips ANSI and normalizes tabs (3 spaces) so layout matches the TypeScript implementation.
public protocol Terminal: AnyObject {
func start(onInput: @escaping (TerminalInput) -> Void, onResize: @escaping () -> Void) throws
func stop()
func write(_ data: String)
var columns: Int { get }
var rows: Int { get }
func moveBy(lines: Int)
func hideCursor()
func showCursor()
func clearLine()
func clearFromCursor()
func clearScreen()
}ProcessTerminalputs stdin in raw mode, normalizes modifier encodings (CSI params, ESC-prefix meta), turns bracketed paste on/off, and emitsTerminalInputevents (.key,.raw,.paste).VirtualTerminalrecords writes for tests, tracks viewport/scrollback, and exposes helper methods (flush(),getViewport(),getScrollBuffer(),getCursorPosition()).
swift run ChatDemo– Swift rewrite oftest/chat-simple.ts: Markdown chat messages, autocomplete for/clear+/delete, loader spinner.swift run KeyTester– Swift version oftest/key-tester.ts: logs raw hex/codes for every key and paste event.
swift build
swift test
swift run ChatDemo
swift run KeyTesterTauTUI stays aligned with pi-tui—bug fixes or new components in the TypeScript project are mirrored here using Swift conventions. Contributions are welcome; see docs/spec.md for the tracking plan.
All credit for the design goes to Mario Zechner and the pi-tui contributors. TauTUI simply brings the same experience to Swift with a native API surface.
License: MIT • Peter Steinberger (steipete)
