---
title: iris
description: 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](https://github.com/versenilvis/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:
1. In an interactive shell without `IRIS_PID`, it exports `IRIS_ACTIVE_SHELL` and `exec`s `iris`, which starts a second copy of that shell inside its pty. That shell runs the whole config again.
2. Inside iris (`IRIS_PID` and `IRIS_FD` set), 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](/shell/fish#key-bindings), [zsh](/shell/bash-zsh#zsh-key-bindings)).
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 `, 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 ` 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](/shell/fish#key-bindings) and [zsh's](/shell/bash-zsh#zsh-key-bindings) 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.