iris
Inline completion menu that wraps fish and zsh in its own pty, with zoxide cd suggestions, and how the shells' bindings coexist with it.
iris draws an IntelliSense-style completion menu inline in the terminal. It runs the shell inside its own pty and reads keys before the shell does. fish and zsh start it; bash does not.
| Source (repo) | Target | Notes |
|---|---|---|
dot_config/iris/config.toml |
~/.config/iris/config.toml |
symlink |
iris is the AUR package iris-autocomplete, not in .chezmoidata/packages.yaml. State and history live in ~/.local/share/iris (not in the repo); state.toml remembers the last menu mode.
Startup
config.fish ends with the iris block and .zshrc runs iris init zsh just before zsh-syntax-highlighting; both skip it where iris is not installed. The init script has two halves:
- In an interactive shell without
IRIS_PID, it exportsIRIS_ACTIVE_SHELLandexecsiris, which starts a second copy of that shell inside its pty. That shell runs the whole config again. - Inside iris (
IRIS_PIDandIRIS_FDset), it installs hooks that report the cwd, the current line (zsh), and command start/stop to iris over$IRIS_FD. fish also loses its autosuggestions.
IRIS_RESCUE=1 fish or IRIS_RESCUE=1 zsh skips the exec and gives a plain shell, for when iris itself misbehaves.
Two shell settings change for iris:
| Shell | Setting | Why |
|---|---|---|
| fish | fish_features=no-query-term, exported before exec iris |
fish’s terminal query turns on kitty’s keyboard protocol in kitty, which encodes keys as CSI u escapes; iris cannot parse them, so the menu never opened in kitty (it did in tmux, which lacks the protocol) |
| zsh | unsetopt PROMPT_SP inside iris |
zsh’s partial-line marker pads each prompt with spaces and a carriage return; iris read the echo as a typed space, so prompts opened with a stale query and inserted a stray suggestion (4channels) on the next key |
Key bindings that run programs
iris only knows a command is running from the shell’s preexec/postexec hooks, and programs started by key bindings never fire those. While tv, yazi, or lazygit ran from a binding, iris kept treating the screen as an idle prompt: it swallowed arrows and tab (Down never reached a tv picker) and could draw its menu on top. Every such binding now wraps the program in _iris_busy start / _iris_busy stop, which send IRIS_CMD_START and IRIS_CMD_STOP:0 on $IRIS_FD, the same messages the hooks send (fish, zsh).
The old down → pj binding is gone from fish, zsh, and bash: it opened the pj picker inside iris’s pty and the two fought over the screen. pj stays on ctrl-p.
Config
Every key from iris’s sample config is listed, so iris config show and the file agree. Changes from the defaults:
| Key | Value | Why |
|---|---|---|
core.shell |
"" |
Auto-detect. IRIS_ACTIVE_SHELL, exported by iris init <shell>, decides which shell starts, so one config serves fish and zsh |
core.mode |
"spec" |
Every shell opens with Fig-style completions (subcommands, flags, paths). The default "last" restores whatever mode the last shell ended in, so one stray ctrl-r left every new shell showing history |
core.atuin-history |
0 |
Shell history only; atuin is not used under iris |
updater.check-on-startup |
false |
The package manager owns updates; iris update would replace a pacman-owned binary |
zoxide.extend-cd |
true |
cd <partial> suggests directories from zoxide’s database, not just the current directory |
AI suggestions stay off (ai.enabled = false). The Groq provider reads GROQ_API_KEY from the environment (conf.d/secrets.fish) through api_key_env; no key is stored in the file. debug stays off because iris then logs every keystroke.
Keys
iris consumes these keys; the shell never sees them while iris runs:
| Key | iris action |
|---|---|
| tab | Accept the selected suggestion (replaces the shell’s tab completion) |
| shift-tab | Hide or show the menu for this shell session |
| ctrl-r | Switch between spec and history mode for this shell session (shadows tv init’s history binding) |
| up / down | Move the selection in the menu; on a closed menu, browse iris’s history (navigate-closed = "history", the default) |
| right | Accept ghost text |
Every other key in fish’s and zsh’s bindings (ctrl-a, ctrl-e, ctrl-o, ctrl-p, ctrl-z, ctrl-c, …) and fish vi mode’s esc still reach the shell.
When the menu appears
Like Fig, the menu opens by itself about 20 ms after each keystroke on a non-empty line (ghost-text = 1: menu plus inline ghost text) and closes on an empty line or while a command runs. It only appears in shells that iris started, so shells opened before iris was set up (for example long-running herdr or tmux panes) show nothing until they are reopened.