Skip to content

Replace internal/ansi with charmbracelet/x/vt emulator#9

Merged
schovi merged 1 commit intomainfrom
replace-custom-ansi
Feb 25, 2026
Merged

Replace internal/ansi with charmbracelet/x/vt emulator#9
schovi merged 1 commit intomainfrom
replace-custom-ansi

Conversation

@schovi
Copy link
Owner

@schovi schovi commented Feb 25, 2026

Enhancement

The internal/ansi/ package uses hand-rolled regex stripping, a virtual 2D character grid, heuristic-based TUI frame detection, and a manual terminal responder. This works for common cases but breaks on complex TUI apps with cursor-addressed painting, alt screen interactions, and grapheme clusters. The charmbracelet/x/vt library is a proper VT terminal emulator in pure Go that handles all of this correctly.

Solution

TUI sessions use a vterm.Screen wrapper around charmbracelet/x/vt's thread-safe emulator. PTY output feeds the emulator directly (no raw byte storage). Reads return the current screen state via Render() (ANSI-styled) or String() (plain text). An atomic version counter replaces byte-count change detection. Terminal capability queries (DA1, DA2, DSR) are handled natively by the emulator, with a ReadResponses goroutine bridging responses to the PTY master. Non-TUI ANSI stripping uses a two-path approach: fast regex for simple output, temporary VT emulator for cursor-positioned content.

Changes

New package: internal/vterm/

  • screen.go: VT emulator wrapper with version counter and terminal query response bridge
  • strip.go: ANSI stripping with fast regex path and emulator fallback for cursor sequences
  • screen_test.go, strip_test.go: comprehensive test coverage (60+ strip test cases)

TUI session handling (internal/daemon/server.go):

  • TUI sessions create vterm.Screen instead of FrameDetector + TerminalResponder
  • captureOutput feeds emulator directly for TUI, raw storage for non-TUI
  • handleRead, handleSnapshot, handleSearch, handleSize all branch on TUI mode
  • Snapshot uses resize cycle + version settle (no storage clearing or frame detection)

Wait/settle adaptation (internal/wait/wait.go):

  • FullOutput flag treats output as full screen content rather than growing buffer
  • TUI-aware pattern matching runs against complete screen state

Call site updates (cmd/, internal/mcp/):

  • All ansi.Strip() calls replaced with vterm.StripDefault()
  • InfoResponse includes TUIMode field for client-side TUI detection

Deleted: internal/ansi/ (5 files, ~2900 lines)

  • clear.go: Virtual 2D grid renderer
  • strip.go: Regex-based ANSI stripping
  • responder.go: Manual terminal query responder
  • All corresponding test files

Notes

Testing

Validated against 18 TUI apps across 6 categories (170/171 tests passed):

  • System monitors: btop, htop, glances
  • File managers: ranger, nnn, yazi, vifm
  • Git tools: lazygit, tig
  • Editors: vim, less, micro
  • Network: weechat, irssi
  • Misc: ncdu, newsboat, mc, bat

Net code reduction

20 files changed, +729, -3263 (net -2534 lines)

The hand-rolled ANSI handling (regex stripping, virtual 2D grid, heuristic
frame detection, terminal responder) breaks on complex TUI apps. Replace
the entire internal/ansi package with a proper VT terminal emulator.

- TUI sessions now use vterm.Screen wrapping a thread-safe VT emulator;
  PTY output feeds the emulator directly, no raw byte storage needed
- Non-TUI ANSI stripping uses vterm.Strip with two paths: fast regex for
  simple output, temporary VT emulator for cursor-positioned content
- Terminal query responses (DA1/DA2/DSR) handled by emulator natively
  via ReadResponses bridge, replacing hand-rolled TerminalResponder
- Atomic version counter replaces byte-count change detection for TUI
- wait.ForOutput gains FullOutput flag for TUI-aware pattern matching
- Delete internal/ansi/ entirely (5 files, ~2900 lines removed)
@schovi schovi marked this pull request as ready for review February 25, 2026 19:54
@schovi schovi merged commit 29eacc5 into main Feb 25, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant