zj-pane-finder
A floating fuzzy finder for every pane in the session. Lists panes across all tabs, filters as you type, previews the highlighted one, and jumps to it.
It lists every pane in the session, including ones you cannot see: members of a
stack hidden behind the visible one, and panes in other tabs. Selecting a hidden
stack member swaps it into view; selecting a pane in another tab switches to that
tab first. Both come free from the host's FocusPaneWithId, which resolves the
owning tab through suppressed panes and hands hidden stack members to the fork's
stack-list logic.
Suppressed plugin panes are the exception — zellij:link and friends are
deliberately invisible infrastructure, and focusing one shows an empty pane.
Filtering matches pane metadata (tab, title, command, cwd) and, failing
that, the visible contents of each pane — so you can find a pane by
something you can see in it rather than only by what it is called. Content
matches are marked ≡ in the list, and the preview scrolls to the matching
line with the matched characters highlighted.
Why it lives here
It path-depends on this checkout's zellij-tile, so the plugin API and its
protobuf assets always match the binary built beside it. Keeping the two in one
repo means they cannot drift.
It is deliberately its own workspace root rather than a member of zellij's:
a non-root member has its [profile] ignored, and opt-level = "z" + lto +
strip are what keep the wasm small — which matters because the keybinding
loads it with skip_plugin_cache, recompiling on every launch. Staying
independent also means carrying this crate touches no upstream-tracked file.
Sizing
On a roomy terminal it opens as a centred float with a frame. Below
COMPACT_BELOW_COLS x COMPACT_BELOW_ROWS (100x30) it takes the whole
viewport and drops its frame instead.
The float's margins cost 16% of the columns and 28% of the rows, plus a border, plus this fork's pane gap — on an 80x24 terminal that leaves roughly 63x14 to work with rather than 80x24. Rows are the scarce resource in a list, so most of the results were going to decoration.
It re-evaluates on TabUpdate, so resizing the terminal switches modes live.
Only applied when the decision actually flips, so moving or resizing the pane by
hand is left alone.
Build and install
./install.sh
Builds for wasm32-wasip1, copies to ~/.config/zellij/plugins/, and
hot-reloads it in every running session — zellij caches a plugin's compiled
module in the server's memory for the life of the session, so overwriting the
.wasm alone is not enough.
Note it does not build into ./target: this checkout's .cargo/config.toml
sets a shared build.target-dir, so install.sh asks cargo metadata where
the artifact actually landed.
Binding it
Something used this often wants a single chord rather than a mode dance. In a
config with default_mode "locked", put it in a block shared by the modes you
actually sit in:
shared_among "normal" "locked" {
bind "Ctrl ;" {
LaunchOrFocusPlugin "file:~/.config/zellij/plugins/pane-finder.wasm" {
floating true
move_to_focused_tab true
skip_plugin_cache true
};
SwitchToMode "Locked"
}
}
Ctrl ; is deliberately obscure: ; has no control-code meaning, readline does
not bind it, and vim binds only bare ; (repeat f/t), never Ctrl ;. Plain
; keeps typing normally.
It does need the kitty keyboard protocol to be distinguishable from a plain
; — zellij enables it and kitty speaks it, but on a terminal without it the
binding simply never fires. Ctrl \, Ctrl ] and Ctrl _ are real control
codes and work anywhere, but they collide with SIGQUIT, vim's tag jump and
readline's undo respectively.
Keys
type filter · Up/Down, Ctrl-p/Ctrl-n, Tab move · Enter jump
Ctrl-w clear word · Ctrl-u clear · Esc/Ctrl-c close
