- Rust 83.8%
- Python 11.8%
- Nix 4.4%
The placeholder cell decode (image id from fg + diacritic, tile run inheritance) was a second copy of logic that also lived in felis-render-wgpu, down to a duplicate fg_rgb24. felis-grid now owns it as Grid::placeholder_cells (felis rev b70fcd7), so drop the local decode and fold the shared resolver's cells into per-image bounding boxes. One decoder, used by both the GPU renderer and this ANSI re-encoder. Bump the felis pin to b70fcd7 for the new API. Assisted-by: Claude Code Opus 4.8 |
||
|---|---|---|
| docs | ||
| scripts | ||
| src | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
felis-tui
A felis-native terminal multiplexer and TUI client.
felis is a GPU terminal emulator with a daemon/client split. Its sessions already survive the window closing — the daemon is the persistence layer — and felis deliberately refuses tabs, splits, and panes, delegating layout to the window manager (felis principle 1).
That delegation assumes a first-class WM. On Windows, on macOS, and over
a bare SSH login, you may not have one. felis-tui fills that gap: it
attaches to the felis daemon from any host terminal and provides the
persistence and the splits that felis itself won't — while losing as
little of felis's fidelity as the host terminal allows.
Why not just use tmux?
A multiplexer running inside another terminal can only render what that host terminal supports — that part is unavoidable. The difference is where the fidelity is lost:
- tmux/zellij/dvtm re-parse the PTY byte stream with their own, necessarily incomplete, VT parser. They lose information at the parse step and again at the render step.
- felis-tui consumes felis's already-parsed, structured grid
(
ShadowGrid) over the felis protocol. There is no re-parse. The only fidelity loss is the final re-encode into the host terminal's capabilities — the theoretical minimum.
That is the whole bet, and it is recorded as a hard non-goal in docs/non-goals.md: felis-tui never re-parses a PTY byte stream. The day it wants to, it has become tmux and lost its reason to exist.
Status
Working multiplexer: each pane is its own felis session over its own
connection, mirrored and composited into one host terminal. Panes live
in a binary split tree (split the focused pane horizontally or
vertically; close collapses the split). Keys are encoded through felis's
own key_encode (full Kitty keyboard protocol), and colour is
down-mapped to the host (true-colour passed through, else nearest
xterm-256).
On a Kitty-graphics-capable host, images are re-emitted as Kitty graphics clipped to their pane — the thing tmux/zellij can't do, because felis-tui has the placement geometry as structured data rather than a re-parsed byte stream. On other hosts images are dropped client-side ("the client owns pixels"). Behaviour and config live in docs/roadmap.md.
cargo run # first pane re-attaches an idle session, or spawns a shell
Prefix is Ctrl-A, then:
| key | action |
|---|---|
c |
new pane (spawn a shell) |
o |
focus the next pane |
x |
close (detach) the focused pane — its shell keeps running |
h |
split horizontally (side by side) |
v |
split vertically (stacked) |
d |
detach all and quit |
Ctrl-A |
send a literal Ctrl-A to the focused pane |
Detaching never kills a shell: the session lives in the daemon, so closing felis-tui (or a pane) parks it for the next attach.
Config
Optional, at ~/.config/felis-tui/config.toml (or
$XDG_CONFIG_HOME/felis-tui/config.toml). The [keys] table overlays
the defaults, so you only list what you change. Keys bind to a fixed
action set — there is no scripting, by design (see
docs/non-goals.md).
prefix = "C-a" # the multiplexer prefix chord
[keys] # key -> action (overlays defaults)
h = "split-horizontal"
v = "split-vertical"
o = "focus-next"
x = "close"
d = "detach"
"C-a" = "literal-prefix"
[theme]
separator = 8 # ANSI-256 index for pane separators
Actions: split-horizontal, split-vertical, focus-next, close,
detach, literal-prefix. Key chords are a single character with an
optional C- (Ctrl) or M- (Alt) prefix.
Not yet
- Pixel-level cropping of images that overflow a pane (today an overflowing or natural-size placement is skipped rather than cropped), scrollback-anchored placements, animation, and placeholders on incapable hosts.
- Query-based graphics detection (today it is env markers).
- Interactive per-split resize and layout presets.
- Scrollback viewport, copy mode, search.