revert(shell): move iris integration to the iris branch

Reverts 43dfe48, 0692ae3, e0cdcd6 and the cable-description half of
098da70 (the star-prompt stats stay). master returns to the pre-iris
shell setup: atuin in fish/zsh/bash, the down -> pj binding with
PJ_QUIET, and the fish/zsh key bindings as they were. The iris work
lives on the `iris` branch.
This commit is contained in:
Prad Nukala committed 2026-10-05 10:41:51 -04:00
1 parent 098da702fc
commit 2049f580f0
27 files changed
+237 -408

No files matched your search

-1
View File
@@ -52,7 +52,6 @@ The repo is a [chezmoi](https://chezmoi.io) source directory. It lives at `~/.lo
- herdr-nvim/ - herdr-nvim/
- hunk/ - hunk/
- hypr/ - hypr/
- iris/
- kitty/ - kitty/
- lazydocker/ - lazydocker/
- lazygit/ - lazygit/
+40 -7
View File
@@ -1,9 +1,9 @@
--- ---
title: atuin title: atuin
description: Shell history search for bash with a daemon, directory-scoped up-arrow, tmux popups, and a cyberdream theme. description: Shell history search with a daemon, directory-scoped up-arrow, tmux popups, a cyberdream theme, and the atuin AI inline command generator.
--- ---
[atuin](https://atuin.sh) replaces shell history search in bash. fish and zsh do not use it: [iris](/shell/iris) wraps both and takes <kbd>up</kbd>, <kbd>down</kbd>, and <kbd>ctrl-r</kbd> itself, so the two fought over the same keys. [atuin](https://atuin.sh) replaces shell history search in fish, bash, and zsh. It also provides `atuin ai inline`, a natural-language command generator that the fish config binds to keys.
| Source (repo) | Target | Notes | | Source (repo) | Target | Notes |
| --- | --- | --- | | --- | --- | --- |
@@ -15,9 +15,12 @@ chezmoi runs in symlink mode, but `private_` files are written as real files, so
## Installation ## Installation
`atuin` is listed in `.chezmoidata/packages.yaml` for pacman and Homebrew. bash also expects atuin's standalone installer layout in `~/.atuin/bin`: it runs `. "$HOME/.atuin/bin/env"` just before `atuin init`. `atuin` is listed in `.chezmoidata/packages.yaml` for pacman and Homebrew. The shells also expect atuin's standalone installer layout in `~/.atuin/bin`:
That line does not check that the file exists, so a machine without `~/.atuin/bin` prints a sourcing error at shell start. - fish: `conf.d/atuin.env.fish` sources `~/.atuin/bin/env.fish`.
- bash and zsh: `. "$HOME/.atuin/bin/env"` just before `atuin init`.
Neither line checks that the file exists, so a machine without `~/.atuin/bin` prints a sourcing error at shell start.
### atuin-receipt.json ### atuin-receipt.json
@@ -37,21 +40,51 @@ The receipt that atuin's [cargo-dist](https://opensource.axo.dev/cargo-dist/) in
| Shell | Where | What | | Shell | Where | What |
| --- | --- | --- | | --- | --- | --- |
| fish | `config.fish`, interactive only | `atuin init fish \| source`, `atuin ai init fish \| source`, and the `?` binding |
| fish | `functions/fish_user_key_bindings.fish` | <kbd>ctrl-w</kbd> runs `atuin ai inline` |
| bash | end of `~/.bashrc` | `eval "$(atuin init bash)"` | | bash | end of `~/.bashrc` | `eval "$(atuin init bash)"` |
| zsh | end of `~/.zshrc` | `eval "$(atuin init zsh)"` |
In fish, `40-tools.fish` sources hishtory before atuin, but atuin's init runs later in `config.fish` and takes <kbd>ctrl-r</kbd>. zsh also loads `suv init zsh` (suvadu) if installed.
### Key bindings ### Key bindings
| Key | Shells | Action | | Key | Shells | Action |
| --- | --- | --- | | --- | --- | --- |
| <kbd>ctrl-r</kbd> | bash | Interactive search (`_atuin_search`) | | <kbd>ctrl-r</kbd> | all | Interactive search (`_atuin_search`) |
| <kbd>up</kbd> | bash | Search opened from the up arrow. `filter_mode_shell_up_key_binding = "directory"` limits it to history from the current directory | | <kbd>up</kbd> | all | Search opened from the up arrow. `filter_mode_shell_up_key_binding = "directory"` limits it to history from the current directory |
| <kbd>?</kbd> | fish, vi normal mode | On an empty buffer, runs `atuin ai inline --hook` |
| <kbd>ctrl-w</kbd> | fish, insert and normal mode | `atuin ai inline`, then repaint |
| <kbd>ctrl+shift+p</kbd> | kitty | [`atuin-pick`](/shell/scripts#atuin-pick): directory-filtered search in a split, running the pick in a fresh fish | | <kbd>ctrl+shift+p</kbd> | kitty | [`atuin-pick`](/shell/scripts#atuin-pick): directory-filtered search in a split, running the pick in a fresh fish |
Under tmux, atuin's `[tmux]` settings open the search in a tmux popup (80% wide, 60% tall) instead of inline. Under tmux, atuin's `[tmux]` settings open the search in a tmux popup (80% wide, 60% tall) instead of inline.
## AI inline ## AI inline
`atuin ai` is enabled in the config (`[ai] enabled = true`), but no shell loads `atuin ai init` or binds a key to it any more: the fish integration (<kbd>?</kbd> and <kbd>ctrl-w</kbd>) was removed with the move to iris. Run `atuin ai inline` by hand to use it. Commit `a82921c` ("add Atuin AI inline integration and keybinding") added the <kbd>ctrl-w</kbd> bind to `fish_user_key_bindings.fish` and the following to `config.fish`:
1. `atuin ai init fish | source`, in the interactive block next to `atuin init fish`.
2. A copy of the `_atuin_ai_question_mark` function that `atuin ai init fish` prints, and `bind "?" _atuin_ai_question_mark`.
The config also sets `[ai] enabled = true`.
`_atuin_ai_question_mark` works like this:
- **Buffer empty (or just `?`):** clears the line, prints OSC `133;C` to `/dev/tty` to close the terminal's semantic prompt zone (so shell-integrated terminals do not erase the TUI during a prompt reflow), then runs `atuin ai inline --hook` with stdout and stderr swapped. The TUI draws on the terminal, and the result comes back tagged:
| Output prefix | Effect |
| --- | --- |
| `__atuin_ai_print__:` | Print the text, repaint |
| `__atuin_ai_cancel__` | Repaint only |
| `__atuin_ai_execute__:` | Put the command on the line and execute it |
| `__atuin_ai_insert__:` | Put the command on the line for editing |
| anything else, non-empty | Put it on the line for editing |
- **Buffer not empty:** inserts a literal `?` under emacs bindings. Under vi or hybrid bindings, which this config uses, it does nothing.
`bind "?"` in `config.fish` has no `-M` flag, so it applies to fish's `default` mode, which is vi **normal** mode here. In insert mode <kbd>?</kbd> types a question mark. To ask for a command, press <kbd>Esc</kbd> and then <kbd>?</kbd> on an empty line, or press <kbd>ctrl-w</kbd> from either mode. The <kbd>ctrl-w</kbd> bind calls `atuin ai inline` without `--hook` and replaces fish's default <kbd>ctrl-w</kbd> word deletion.
bash and zsh do not load `atuin ai init`.
## Configuration ## Configuration
+14 -18
View File
@@ -1,9 +1,9 @@
--- ---
title: bash and zsh title: bash and zsh
description: Secondary shells that share fish's aliases, starship prompt, and cd-style helper functions; zsh also runs under iris like fish. description: Secondary shells that share fish's aliases, starship prompt, atuin history, and cd-style helper functions.
--- ---
bash and zsh are set up so a shell other than fish feels the same: the same aliases, the same starship prompt, ports of the fish helpers that have to change the shell's own directory (`zsh/functions/` also ports `czs` and `y`), and the same picker key bindings. zsh matches fish most closely: it runs under [iris](/shell/iris), activates mise and zoxide, and has the <kbd>ctrl-z</kbd> job picker. bash keeps atuin for history. bash and zsh are set up so a shell other than fish feels the same: the same aliases, the same starship prompt, atuin history, ports of the fish helpers that have to change the shell's own directory, and the same picker key bindings.
| Source (repo) | Target | | Source (repo) | Target |
| --- | --- | | --- | --- |
@@ -38,30 +38,24 @@ Interactive shells only (`[[ $- != *i* ]] && return`). In order:
## ~/.zshrc ## ~/.zshrc
1. **History.** `HISTFILE=~/.zsh_history`, `HISTSIZE=10000`, `SAVEHIST=10000`, `setopt SHARE_HISTORY HIST_IGNORE_DUPS HIST_IGNORE_SPACE`. `HIST_IGNORE_SPACE` keeps lines that start with a space out of history, as fish does; the <kbd>ctrl-z</kbd> binding's hidden ` __jobs_bind_fg` relies on it. 1. **History.** `HISTFILE=~/.zsh_history`, `HISTSIZE=10000`, `SAVEHIST=10000`, `setopt SHARE_HISTORY HIST_IGNORE_DUPS`.
2. **Completion.** `autoload -Uz compinit && compinit`. 2. **Completion.** `autoload -Uz compinit && compinit`.
3. The same PATH, Go, Android, and `GITHUB_TOKEN` blocks as `.bashrc`, including `GHQ_ROOT` and `CODE_ROOT`, plus `eval "$(mise activate zsh)"` if mise is installed (fish activates mise in `40-tools.fish`; the Go block's `GOROOT` comes from it). 3. The same PATH, Go, Android, and `GITHUB_TOKEN` blocks as `.bashrc`, including `GHQ_ROOT` and `CODE_ROOT`.
4. Sources `~/.config/shell/aliases.sh`. 4. Sources `~/.config/shell/aliases.sh`.
5. Sources every `~/.config/zsh/functions/*.zsh`. 5. Sources every `~/.config/zsh/functions/*.zsh`.
6. Sources `~/.config/zsh/keybinds.zsh`. 6. Sources `~/.config/zsh/keybinds.zsh`.
7. `eval "$(suv init zsh)"` if suvadu is installed. 7. `eval "$(suv init zsh)"` if suvadu is installed.
8. Sources `~/.config/envman/load.sh` if it is non-empty. 8. Sources `~/.config/envman/load.sh` if it is non-empty.
9. `eval "$(starship init zsh)"` if starship is installed. 9. `eval "$(starship init zsh)"` if starship is installed.
10. Prints quickshell's wallpaper-generated terminal color sequences (`~/.local/state/quickshell/user/generated/terminal/sequences.txt`) if present, as fish does. 10. `eval "$(tv init zsh)"` if `tv` is installed (<kbd>ctrl-t</kbd> smart autocomplete).
11. `eval "$(zoxide init zsh)"` if zoxide is installed (`z`, `zi`), alongside the zoxide fallback in `functions/cd.zsh`. 11. **Syntax highlighting.** Sources the first `zsh-syntax-highlighting.zsh` found in `/usr/share/zsh/plugins/zsh-syntax-highlighting/`, `/opt/homebrew/share/zsh-syntax-highlighting/`, or `/usr/local/share/zsh-syntax-highlighting/`. The comment says this must stay last, because it wraps every zle widget defined before it.
12. `eval "$(tv init zsh)"` if `tv` is installed (<kbd>ctrl-t</kbd> smart autocomplete). 12. **atuin.** Sources `~/.atuin/bin/env` and runs `eval "$(atuin init zsh)"`. These lines come after the syntax-highlighting block, so despite the comment, syntax highlighting is not actually last. atuin loads after tv, so it keeps <kbd>ctrl-r</kbd>.
13. **iris.** If iris is installed, `eval "$(iris init zsh)"`: in an interactive zsh on a tty it `exec`s iris, which starts a second zsh in its pty that runs `.zshrc` again; there the init installs iris's line, cwd, and command hooks. Inside iris (`IRIS_PID` set) it also runs `unsetopt PROMPT_SP`: zsh's partial-line marker pads the line with spaces and a carriage return before each prompt, and iris read that echo as a typed space, so every prompt opened with a stale query and inserted a stray first suggestion on the next key. `IRIS_RESCUE=1 zsh` starts a plain zsh. See [iris](/shell/iris).
14. **Syntax highlighting.** Sources the first `zsh-syntax-highlighting.zsh` found in `/usr/share/zsh/plugins/zsh-syntax-highlighting/`, `/opt/homebrew/share/zsh-syntax-highlighting/`, or `/usr/local/share/zsh-syntax-highlighting/`. It stays last, because it wraps every zle widget defined before it.
atuin is not used in zsh: under iris it fought for <kbd>up</kbd> and <kbd>ctrl-r</kbd>, as in fish.
## zsh key bindings ## zsh key bindings
`keybinds.zsh` ports `fish_user_key_bindings.fish` to zle. Unlike fish, zsh uses the **emacs** keymap: `bindkey -e` pins it so that `$EDITOR=nvim` does not switch zsh into vi mode. `unsetopt flow_control` frees <kbd>ctrl-q</kbd> and <kbd>ctrl-s</kbd> from the terminal. `keybinds.zsh` ports `fish_user_key_bindings.fish` to zle. Unlike fish, zsh uses the **emacs** keymap: `bindkey -e` pins it so that `$EDITOR=nvim` does not switch zsh into vi mode. `unsetopt flow_control` frees <kbd>ctrl-q</kbd> and <kbd>ctrl-s</kbd> from the terminal.
A generic widget `_zle-run` is registered as `run-<cmd>` for `purple`, `clip-last`, `omp-session`, `yy`, and `lazygit`. It clears the pending display (`zle -I`), runs the command with the tty on stdin, then `zle reset-prompt`. Widgets run in the shell process, so directory changes made by `gho` and `yy` persist. A generic widget `_zle-run` is registered as `run-<cmd>` for `purple`, `clip-last`, `omp`, `yy`, and `lazygit`. It clears the pending display (`zle -I`), runs the command with the tty on stdin, then `zle reset-prompt`. Widgets run in the shell process, so directory changes made by `gho` and `yy` persist.
Widgets never fire `preexec`/`precmd`, so every widget that runs a program wraps it in `_iris_busy start` / `_iris_busy stop`, which send iris the same `IRIS_CMD_START` / `IRIS_CMD_STOP` messages its own hooks do (a no-op outside iris). Without them iris kept handling keys as if the prompt were idle while tv, yazi, or lazygit owned the screen, swallowing arrows and <kbd>tab</kbd>. See [fish](/shell/fish#key-bindings) for the same helper there.
The tv pickers have their own widgets, because `tv --inline` opens on the line below the prompt and leaves the cursor there on exit. `zle -I` followed by `zle reset-prompt` redraws the prompt where zle last drew it, wiping anything printed below; `zle -I` alone draws a fresh prompt under the output. The tv pickers have their own widgets, because `tv --inline` opens on the line below the prompt and leaves the cursor there on exit. `zle -I` followed by `zle reset-prompt` redraws the prompt where zle last drew it, wiping anything printed below; `zle -I` alone draws a fresh prompt under the output.
@@ -69,8 +63,7 @@ The tv pickers have their own widgets, because `tv --inline` opens on the line b
| --- | --- | --- | | --- | --- | --- |
| `tv-git-files` | `tv git-files --inline` | `\033[A`, `reset-prompt` | | `tv-git-files` | `tv git-files --inline` | `\033[A`, `reset-prompt` |
| `gho-inline` | `gho` | cwd unchanged: `\033[A`, `reset-prompt`. After a cd: nothing, so the new prompt lands below onefetch | | `gho-inline` | `gho` | cwd unchanged: `\033[A`, `reset-prompt`. After a cd: nothing, so the new prompt lands below onefetch |
| `pj-inline` | `_zle-pj`: `pj` with `PJ_STATE` set to a temp file | `open` (cancelled): `\033[A`, `reset-prompt`. `ran`: nothing, so the task output stays. Empty (no task sources): `reset-prompt` | | `pj-inline`, `pj-down` | `_zle-pj`: `pj` with `PJ_STATE` set to a temp file (`PJ_QUIET=1` for `pj-down`) | `open` (cancelled): `\033[A`, `reset-prompt`. `ran`: nothing, so the task output stays. Empty (no task sources): `reset-prompt` |
| `jobs-fg` | `_zle-jobs`: with one job, that job; with several, `tv jobs --inline` with `TV_JOBS` set to a temp file holding the job table built from `$jobstates`/`$jobtexts` | Cancelled: `\033[A`, `reset-prompt`. Picked: `zle push-input`, then the line ` __jobs_bind_fg <id>` is accepted; `__jobs_bind_fg` erases that echoed line and runs `fg` quietly. Typed text returns at the next prompt |
| Key | Action | | Key | Action |
| --- | --- | | --- | --- |
@@ -81,7 +74,7 @@ The tv pickers have their own widgets, because `tv --inline` opens on the line b
| <kbd>ctrl-o</kbd> | `gho-inline` (`ghq-repos` cable, cd) | | <kbd>ctrl-o</kbd> | `gho-inline` (`ghq-repos` cable, cd) |
| <kbd>ctrl-p</kbd> | `pj-inline` (`pj` cable) | | <kbd>ctrl-p</kbd> | `pj-inline` (`pj` cable) |
| <kbd>ctrl-t</kbd> | tv smart autocomplete (from `tv init zsh`) | | <kbd>ctrl-t</kbd> | tv smart autocomplete (from `tv init zsh`) |
| <kbd>ctrl-z</kbd> | `jobs-fg`: `fg` the only background job, or pick one from the `jobs` cable ([television](/tools/television#jobs)); nothing with no jobs. While a job runs, ctrl-z is the tty's suspend, so the key toggles | | <kbd>down</kbd> | `pj-down`: `PJ_QUIET=1 pj` on an empty prompt, otherwise `down-line-or-history` (bound to `^[[B` and `^[OB`) |
| <kbd>ctrl-a</kbd> | `omp-session`: resume or start omp for the current repo ([Scripts](/shell/scripts#omp-session)) | | <kbd>ctrl-a</kbd> | `omp-session`: resume or start omp for the current repo ([Scripts](/shell/scripts#omp-session)) |
| <kbd>ctrl-g</kbd> | `lazygit` | | <kbd>ctrl-g</kbd> | `lazygit` |
| <kbd>ctrl-q</kbd> | `exit` | | <kbd>ctrl-q</kbd> | `exit` |
@@ -89,7 +82,7 @@ The tv pickers have their own widgets, because `tv --inline` opens on the line b
<kbd>ctrl-c</kbd> is the tty interrupt character, so `bindkey '^C'` would never fire. The binding is a `TRAPINT` function instead. At the prompt it runs `zle .kill-buffer`, prints the clear sequence, and runs `zle .reset-prompt`. While a command is running, SIGINT keeps its normal behavior (returns `128+SIGINT`). <kbd>ctrl-c</kbd> is the tty interrupt character, so `bindkey '^C'` would never fire. The binding is a `TRAPINT` function instead. At the prompt it runs `zle .kill-buffer`, prints the clear sequence, and runs `zle .reset-prompt`. While a command is running, SIGINT keeps its normal behavior (returns `128+SIGINT`).
<kbd>ctrl-h/j/k/l</kbd> belong to kitty ([kitty](/terminal/kitty)) and are not bound here. Under iris, <kbd>tab</kbd>, <kbd>shift-tab</kbd>, <kbd>ctrl-r</kbd>, <kbd>up</kbd>, and <kbd>down</kbd> belong to iris; <kbd>up</kbd>/<kbd>down</kbd> keep zsh's defaults outside it. The cables are described on the [television](/tools/television) page. The fish-only binds <kbd>ctrl-w</kbd> (atuin AI) and <kbd>?</kbd> have no zsh equivalent. <kbd>ctrl-h/j/k/l</kbd> belong to kitty ([kitty](/terminal/kitty)) and are not bound here. The cables are described on the [television](/tools/television) page.
## bash key bindings ## bash key bindings
@@ -100,6 +93,7 @@ The tv pickers have their own widgets, because `tv --inline` opens on the line b
| <kbd>ctrl-o</kbd> | Macro `\C-x1\C-x2\C-x3\C-x0` (below): pick from the `ghq-repos` cable and cd | | <kbd>ctrl-o</kbd> | Macro `\C-x1\C-x2\C-x3\C-x0` (below): pick from the `ghq-repos` cable and cd |
| <kbd>ctrl-e</kbd> | `_kb_git_files`: `tv git-files --inline`, then `_kb_up` | | <kbd>ctrl-e</kbd> | `_kb_git_files`: `tv git-files --inline`, then `_kb_up` |
| <kbd>ctrl-p</kbd> | `_kb_pj`: `pj` with `PJ_STATE` set to a temp file; `_kb_up` only when the state is `open` (cancelled) | | <kbd>ctrl-p</kbd> | `_kb_pj`: `pj` with `PJ_STATE` set to a temp file; `_kb_up` only when the state is `open` (cancelled) |
| <kbd>down</kbd> | `PJ_QUIET=1 _kb_pj` on an empty prompt, otherwise `history-search-forward` |
| <kbd>ctrl-t</kbd> | `_kb_tv_autocomplete`: runs `tv --autocomplete-prompt … --inline` on the text left of the cursor, `_kb_up`, and inserts the picks there, each `printf %q`-quoted | | <kbd>ctrl-t</kbd> | `_kb_tv_autocomplete`: runs `tv --autocomplete-prompt … --inline` on the text left of the cursor, `_kb_up`, and inserts the picks there, each `printf %q`-quoted |
`bind -x` runs in the shell process, so a `cd` persists, but readline keeps drawing the prompt it started with. <kbd>ctrl-o</kbd> is therefore a macro: `bind -x` runs in the shell process, so a `cd` persists, but readline keeps drawing the prompt it started with. <kbd>ctrl-o</kbd> is therefore a macro:
@@ -109,6 +103,8 @@ The tv pickers have their own widgets, because `tv --inline` opens on the line b
3. `\C-x3` runs `_kb_gho_show` (`clear`, then `onefetch` in git repos); readline redraws the prompt below the output. 3. `\C-x3` runs `_kb_gho_show` (`clear`, then `onefetch` in git repos); readline redraws the prompt below the output.
4. `\C-x0` runs `_kb_restore`, which puts the typed text back. 4. `\C-x0` runs `_kb_restore`, which puts the typed text back.
<kbd>down</kbd> (`\e[B` and `\eOB`) is the macro `\C-x4\C-x5`. `bind -x` cannot call readline functions, so `_kb_down` (`\C-x4`) rebinds `\C-x5` to `history-search-forward` when the line is non-empty, or runs `PJ_QUIET=1 _kb_pj` and rebinds `\C-x5` to `redraw-current-line`.
## Shared aliases: aliases.sh ## Shared aliases: aliases.sh
`~/.config/shell/aliases.sh` is rendered from `.chezmoidata/aliases.yaml`. Each YAML `section` becomes a comment, and each item becomes `alias <name>='<cmd>'`. The template then appends shell-specific extras that are not in the YAML: `~/.config/shell/aliases.sh` is rendered from `.chezmoidata/aliases.yaml`. Each YAML `section` becomes a comment, and each item becomes `alias <name>='<cmd>'`. The template then appends shell-specific extras that are not in the YAML:
+24 -26
View File
@@ -3,7 +3,7 @@ title: fish
description: The primary interactive shell — conf.d layout, environment, vi-mode key bindings, the cyberdream theme, completions, and every autoloaded function. description: The primary interactive shell — conf.d layout, environment, vi-mode key bindings, the cyberdream theme, completions, and every autoloaded function.
--- ---
fish is the primary interactive shell. The config is split into autoloaded files: `conf.d/` for startup snippets, `functions/` for one function per file, `completions/` for completion scripts, and `themes/` for the color theme. `config.fish` runs last and holds the prompt setup and the [iris](/shell/iris) launch. fish is the primary interactive shell. The config is split into autoloaded files: `conf.d/` for startup snippets, `functions/` for one function per file, `completions/` for completion scripts, and `themes/` for the color theme. `config.fish` runs last and holds the prompt and atuin setup.
| Source (repo) | Target | | Source (repo) | Target |
| --- | --- | | --- | --- |
@@ -19,7 +19,7 @@ None of these files are templates. chezmoi runs in symlink mode, so each target
## Installation ## Installation
`fish` is in `.chezmoidata/packages.yaml` under `packages.arch.pacman` and `packages.darwin.brew`. `.chezmoiscripts/run_onchange_before_10-packages.sh.tmpl` installs it with pacman on Arch and Homebrew on macOS. The tools that fish initializes (starship, zoxide, fzf, eza, gum, yazi) come from the same manifest. mise, television, hishtory, envman, iris, and the Bun, Go, Android, and Foundry toolchains are not in the manifest; fish initializes each one only if it is present. `fish` is in `.chezmoidata/packages.yaml` under `packages.arch.pacman` and `packages.darwin.brew`. `.chezmoiscripts/run_onchange_before_10-packages.sh.tmpl` installs it with pacman on Arch and Homebrew on macOS. The tools that fish initializes (starship, zoxide, atuin, fzf, eza, gum, yazi) come from the same manifest. mise, television, hishtory, envman, and the Bun, Go, Android, and Foundry toolchains are not in the manifest; fish initializes each one only if it is present.
### Plugins ### Plugins
@@ -43,6 +43,7 @@ fish sources `conf.d/*.fish` in name order and then `config.fish`. The numbered
| `30-ssh-agent.fish` | One ssh-agent shared across shells | | `30-ssh-agent.fish` | One ssh-agent shared across shells |
| `40-tools.fish` | mise, television, starship, zoxide, entire, hishtory, unsloth | | `40-tools.fish` | mise, television, starship, zoxide, entire, hishtory, unsloth |
| `50-terminal-title.fish` | tmux/screen window name from `$PWD` | | `50-terminal-title.fish` | tmux/screen window name from `$PWD` |
| `atuin.env.fish` | Sources `~/.atuin/bin/env.fish` (puts atuin's install dir on `PATH`) |
| `inir-env.fish` | `INIR_VENV` and `ILLOGICAL_IMPULSE_VIRTUAL_ENV` → `~/.local/state/quickshell/.venv` | | `inir-env.fish` | `INIR_VENV` and `ILLOGICAL_IMPULSE_VIRTUAL_ENV` → `~/.local/state/quickshell/.venv` |
| `inir-path.fish` | Prepends `~/.local/bin` to `PATH` if missing | | `inir-path.fish` | Prepends `~/.local/bin` to `PATH` if missing |
| `nvm.fish` | nvm.fish plugin events and default-version activation | | `nvm.fish` | nvm.fish plugin events and default-version activation |
@@ -132,7 +133,7 @@ Interactive shells without `SSH_AUTH_SOCK` source `~/.ssh/agent.env.fish` and pr
5. Source `~/.hishtory/config.fish` if present. 5. Source `~/.hishtory/config.fish` if present.
6. Activate the unsloth studio venv (`~/.unsloth/studio/unsloth_studio/bin/activate.fish`) if present. 6. Activate the unsloth studio venv (`~/.unsloth/studio/unsloth_studio/bin/activate.fish`) if present.
Under [iris](/shell/iris), <kbd>ctrl-r</kbd> never reaches fish: iris takes it to switch its menu between spec and history mode. atuin is initialized later, in `config.fish`, and binds <kbd>ctrl-r</kbd> itself.
### 50-terminal-title.fish ### 50-terminal-title.fish
@@ -168,14 +169,10 @@ The header comment says the file should stay empty, but it holds the following:
2. **Bottom-pinned prompt.** In interactive shells with `FISH_BOTTOM_PROMPT=1`, `tput cup $LINES 0` moves the cursor to the last row. The comment calls `1` the default, but `10-env.fish` sets `0`, so the prompt stays at the top unless you export `FISH_BOTTOM_PROMPT=1`. 2. **Bottom-pinned prompt.** In interactive shells with `FISH_BOTTOM_PROMPT=1`, `tput cup $LINES 0` moves the cursor to the last row. The comment calls `1` the default, but `10-env.fish` sets `0`, so the prompt stays at the top unless you export `FISH_BOTTOM_PROMPT=1`.
3. `fish_add_path ~/.grok/bin` (grok installer block). 3. `fish_add_path ~/.grok/bin` (grok installer block).
4. Sources `~/.config/envman/load.fish` if it is non-empty. 4. Sources `~/.config/envman/load.fish` if it is non-empty.
5. `fish_add_path ~/.cmux/bin` and `~/.cache/.bun/bin`. 5. **atuin.** Interactive shells run `atuin init fish | source` and `atuin ai init fish | source`. See [atuin](/shell/atuin).
6. Sources the first readable `~/.config/herdr/plugins/github/herdr-automatic-rename-*/shell/hook.fish`. This is herdr's automatic pane-rename hook ([herdr](/terminal/herdr)). 6. `fish_add_path ~/.cmux/bin` and `~/.cache/.bun/bin`.
7. `brew shellenv fish` for `/home/linuxbrew/.linuxbrew`. 7. Sources the first readable `~/.config/herdr/plugins/github/herdr-automatic-rename-*/shell/hook.fish`. This is herdr's automatic pane-rename hook ([herdr](/terminal/herdr)).
8. **iris.** If iris is installed: in an interactive fish that iris is not already running (no `IRIS_PID`, no `IRIS_RESCUE`), export `fish_features=no-query-term`, then `iris init fish | source`. The first half of the init `exec`s iris from an interactive fish; iris starts a second fish inside its pty, which runs this whole config again, and there the second half installs iris's cwd and command hooks and turns off fish autosuggestions. `IRIS_RESCUE=1 fish` starts a plain fish. See [iris](/shell/iris). 8. A copy of the `_atuin_ai_question_mark` function that `atuin ai init fish` emits, followed by `bind "?" _atuin_ai_question_mark`.
`no-query-term` turns off fish's terminal capability query. In kitty that query switches on kitty's keyboard protocol, which sends keys as `CSI u` escape sequences iris cannot parse, so its menu never opened. Feature flags are read at startup, so the export has to happen before `exec iris`; the fish iris starts inherits it.
atuin is not used in fish: it fought iris for <kbd>up</kbd> and <kbd>ctrl-r</kbd>. bash still initializes it ([atuin](/shell/atuin)).
## Key bindings ## Key bindings
@@ -184,14 +181,16 @@ atuin is not used in fish: it fought iris for <kbd>up</kbd> and <kbd>ctrl-r</kbd
| Key | Action | | Key | Action |
| --- | --- | | --- | --- |
| <kbd>ctrl-e</kbd> | `_tv_inline git-files`, then repaint: pick a file in the repo and open it in nvim ([television](/tools/television#git-files)) | | <kbd>ctrl-e</kbd> | `_tv_inline git-files`, then repaint: pick a file in the repo and open it in nvim ([television](/tools/television#git-files)) |
| <kbd>ctrl-f</kbd> | `_bind_run yy`: yazi, then cd to the directory yazi exited in | | <kbd>ctrl-f</kbd> | `yy`: yazi, then cd to the directory yazi exited in |
| <kbd>ctrl-b</kbd> | `_bind_run purple` (an external command; this repo does not provide it) | | <kbd>ctrl-b</kbd> | `purple`, then repaint (an external command; this repo does not provide it) |
| <kbd>ctrl-y</kbd> | `_bind_run clip-last`: re-run the last command and copy its output | | <kbd>ctrl-y</kbd> | `clip-last`: re-run the last command and copy its output |
| <kbd>ctrl-o</kbd> | `_gho_bind`: `gho` (pick a ghq repo or Obsidian vault from the `ghq-repos` cable and cd into it), then repaint | | <kbd>ctrl-o</kbd> | `_gho_bind`: `gho` (pick a ghq repo or Obsidian vault from the `ghq-repos` cable and cd into it), then repaint |
| <kbd>ctrl-p</kbd> | `_pj_bind`: `pj`, the task runner on the `pj` cable ([Scripts](/shell/scripts#pj)), then repaint | | <kbd>ctrl-p</kbd> | `_pj_bind`: `pj`, the task runner on the `pj` cable ([Scripts](/shell/scripts#pj)), then repaint |
| <kbd>ctrl-a</kbd> | `_bind_run omp-session`: resume or start omp for the current repo by branch and session age, opening the `omp-sessions` cable only when nothing qualifies ([Scripts](/shell/scripts#omp-session)) | | <kbd>ctrl-a</kbd> | `omp-session`, then repaint: resume or start omp for the current repo by branch and session age, opening the `omp-sessions` cable only when nothing qualifies ([Scripts](/shell/scripts#omp-session)) |
| <kbd>ctrl-w</kbd> | `atuin ai inline`: natural-language command overlay |
| <kbd>ctrl-z</kbd> | `_jobs_bind`: with one background job, `fg` it; with several, pick one from the `jobs` cable ([television](/tools/television#jobs)) and `fg` that; with none, nothing. While a job holds the terminal, ctrl-z is still the tty's suspend, so the key toggles suspend and resume | | <kbd>ctrl-z</kbd> | `_jobs_bind`: with one background job, `fg` it; with several, pick one from the `jobs` cable ([television](/tools/television#jobs)) and `fg` that; with none, nothing. While a job holds the terminal, ctrl-z is still the tty's suspend, so the key toggles suspend and resume |
| <kbd>ctrl-g</kbd> | `_bind_run lazygit` | | <kbd>ctrl-g</kbd> | `lazygit` |
| <kbd>down</kbd> | `_pj_bind_down`: opens the `pj` cable on an idle, empty prompt, otherwise fish's `down-or-search` |
| <kbd>ctrl-c</kbd> | Clears the command line, runs `clear` (screen and kitty scrollback), repaints | | <kbd>ctrl-c</kbd> | Clears the command line, runs `clear` (screen and kitty scrollback), repaints |
| <kbd>ctrl-q</kbd> | `exit` | | <kbd>ctrl-q</kbd> | `exit` |
@@ -199,12 +198,12 @@ Bindings added by tool init scripts:
| Key | Mode | Action | Source | | Key | Mode | Action | Source |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| <kbd>up</kbd> / <kbd>down</kbd> | default and insert | fish presets (`up-or-search`, `down-or-search`); under iris the keys never reach fish | fish | | <kbd>ctrl-r</kbd> | default and insert | `_atuin_search` | `atuin init fish` |
| <kbd>up</kbd> | default and insert | `_atuin_bind_up` (atuin history, directory filter) | `atuin init fish` |
| <kbd>?</kbd> | default (vi normal) | `_atuin_ai_question_mark`: on an empty buffer, opens `atuin ai inline` | `config.fish` |
| <kbd>ctrl-t</kbd> | insert | `tv_smart_autocomplete` | `tv init fish` | | <kbd>ctrl-t</kbd> | insert | `tv_smart_autocomplete` | `tv init fish` |
Under iris, <kbd>tab</kbd>, <kbd>shift-tab</kbd>, <kbd>ctrl-r</kbd>, <kbd>up</kbd>, and <kbd>down</kbd> belong to iris and never reach fish, so fish's tab completion, history keys, and the `tv init fish` <kbd>ctrl-r</kbd> history binding are shadowed. Every other key above reaches fish. `_pj_bind_down` hands the key to `down-or-search` when the buffer is non-empty, the completion pager is open, or history search is active. Otherwise it runs `_pj_bind --quiet` (`PJ_QUIET=1`), so pressing down outside a project does nothing and prints no error.
A program started by a key binding never fires `fish_preexec`/`fish_postexec`, so iris would keep handling keys as if the prompt were idle while tv, yazi, or lazygit owned the screen: it swallowed arrows and <kbd>tab</kbd> and could draw its menu over them. Every binding that runs a program therefore tells iris with `_iris_busy start` / `_iris_busy stop`, the same `IRIS_CMD_START` / `IRIS_CMD_STOP` messages iris's own hooks send (a no-op outside iris). `_bind_run CMD` wraps a command that way and repaints; `_tv_inline`, `_gho_bind`, and `_pj_bind` call `_iris_busy` themselves.
The pickers run `tv --inline`, which opens on the line below the prompt and leaves the cursor there when it exits. The wrappers put the prompt back before `commandline -f repaint`, writing the cursor moves to `/dev/tty`: The pickers run `tv --inline`, which opens on the line below the prompt and leaves the cursor there when it exits. The wrappers put the prompt back before `commandline -f repaint`, writing the cursor moves to `/dev/tty`:
@@ -212,14 +211,14 @@ The pickers run `tv --inline`, which opens on the line below the prompt and leav
| --- | --- | --- | | --- | --- | --- |
| `_tv_inline ARGS` | `tv ARGS --inline` | `\033[A` (cursor up), returns tv's status | | `_tv_inline ARGS` | `tv ARGS --inline` | `\033[A` (cursor up), returns tv's status |
| `_gho_bind` | `gho` | `\033[A` if the cwd did not change; `\n` after a cd, so the prompt lands below onefetch | | `_gho_bind` | `gho` | `\033[A` if the cwd did not change; `\n` after a cd, so the prompt lands below onefetch |
| `_pj_bind` | `pj` with `PJ_STATE` set to a temp file | `\033[A` for `open` (cancelled) or empty (no task sources); `\n` for `ran` (the task printed output) | | `_pj_bind [--quiet]` | `pj` with `PJ_STATE` set to a temp file | `\033[A` for `open` (cancelled) or empty (no task sources); `\n` for `ran` (the task printed output) |
| `_jobs_bind` | `fg` of the only job, or `jobs` with `TV_JOBS` set to a temp file holding the job table | `\033[A` (via `_tv_inline`) when the picker is cancelled | | `_jobs_bind` | `fg` of the only job, or `jobs` with `TV_JOBS` set to a temp file holding the job table | `\033[A` (via `_tv_inline`) when the picker is cancelled |
`_jobs_bind` runs `fg` by replacing the command line with ` __jobs_bind_fg <id>` (leading space: kept out of history) and executing it, not from inside the binding. A binding runs while fish's reader still owns the terminal with extended keys on, so a job started with `&` that had never held the terminal got ctrl-z and ctrl-c as escape sequences instead of signals. `__jobs_bind_fg` erases the echoed prompt line (`\033[A\033[2K` on `/dev/tty`) and runs `fg` with its "Send job … to foreground" message sent to `/dev/null`, so resuming leaves nothing on screen. Text already typed is put back at the next prompt by a one-shot `fish_prompt` handler. `_jobs_bind` runs `fg` by replacing the command line with ` __jobs_bind_fg <id>` (leading space: kept out of history) and executing it, not from inside the binding. A binding runs while fish's reader still owns the terminal with extended keys on, so a job started with `&` that had never held the terminal got ctrl-z and ctrl-c as escape sequences instead of signals. `__jobs_bind_fg` erases the echoed prompt line (`\033[A\033[2K` on `/dev/tty`) and runs `fg` with its "Send job … to foreground" message sent to `/dev/null`, so resuming leaves nothing on screen. Text already typed is put back at the next prompt by a one-shot `fish_prompt` handler.
`omp-session` (<kbd>ctrl-a</kbd>) does its own `\033[A` when its `omp-sessions` picker is cancelled, so the binding only repaints. `omp-session` (<kbd>ctrl-a</kbd>) does its own `\033[A` when its `omp-sessions` picker is cancelled, so the binding only repaints.
zsh and bash bind <kbd>ctrl-o</kbd>, <kbd>ctrl-e</kbd>, <kbd>ctrl-p</kbd>, and <kbd>ctrl-t</kbd> the same way, and zsh also <kbd>ctrl-z</kbd> ([bash and zsh](/shell/bash-zsh)). Inside television, <kbd>ctrl-e</kbd> runs the channel's `edit` action and <kbd>ctrl-h</kbd> its `goto_parent_dir` action (`television/config.toml`). The config and cables are on the [television](/tools/television) page. zsh and bash bind <kbd>ctrl-o</kbd>, <kbd>ctrl-e</kbd>, <kbd>ctrl-p</kbd>, <kbd>down</kbd>, and <kbd>ctrl-t</kbd> the same way ([bash and zsh](/shell/bash-zsh)). Inside television, <kbd>ctrl-e</kbd> runs the channel's `edit` action and <kbd>ctrl-h</kbd> its `goto_parent_dir` action (`television/config.toml`). The config and cables are on the [television](/tools/television) page.
:::note :::note
Under kitty, <kbd>ctrl-g</kbd> (lazygit overlay), <kbd>ctrl-y</kbd> (yazi split), and <kbd>ctrl-h/j/k/l</kbd> (split navigation) are mapped in `kitty.conf` and never reach fish. The fish binds for those keys only apply in other terminals. See [kitty](/terminal/kitty). Under kitty, <kbd>ctrl-g</kbd> (lazygit overlay), <kbd>ctrl-y</kbd> (yazi split), and <kbd>ctrl-h/j/k/l</kbd> (split navigation) are mapped in `kitty.conf` and never reach fish. The fish binds for those keys only apply in other terminals. See [kitty](/terminal/kitty).
@@ -272,10 +271,9 @@ Every file in `functions/` is autoloaded by name. Several also exist as POSIX sc
| `_nvm_version_activate` | nvm.fish internal: sets `nvm_current_version` and prepends its `bin` to `PATH` | | `_nvm_version_activate` | nvm.fish internal: sets `nvm_current_version` and prepends its `bin` to `PATH` |
| `_nvm_version_deactivate` | nvm.fish internal: removes a version's `bin` from `PATH` | | `_nvm_version_deactivate` | nvm.fish internal: removes a version's `bin` from `PATH` |
| `_gho_bind` | <kbd>ctrl-o</kbd> handler: `gho`, then cursor fix and repaint (see [Key bindings](#key-bindings)) | | `_gho_bind` | <kbd>ctrl-o</kbd> handler: `gho`, then cursor fix and repaint (see [Key bindings](#key-bindings)) |
| `_pj_bind` | <kbd>ctrl-p</kbd> handler: `pj` with `PJ_STATE`, then cursor fix and repaint | | `_pj_bind` | <kbd>ctrl-p</kbd> handler: `pj` with `PJ_STATE`, then cursor fix and repaint. `--quiet` sets `PJ_QUIET=1` |
| `_jobs_bind` | <kbd>ctrl-z</kbd> handler: `fg` the only job or pick one from the `jobs` cable (see [Key bindings](#key-bindings)) | | `_jobs_bind` | <kbd>ctrl-z</kbd> handler: `fg` the only job or pick one from the `jobs` cable (see [Key bindings](#key-bindings)) |
| `_bind_run` | Runs `$argv` between `_iris_busy start` and `stop`, then repaints. Used by the <kbd>ctrl-a</kbd>, <kbd>ctrl-b</kbd>, <kbd>ctrl-f</kbd>, <kbd>ctrl-g</kbd>, and <kbd>ctrl-y</kbd> bindings | | `_pj_bind_down` | Down-arrow handler: `_pj_bind --quiet` on an empty idle prompt, else `down-or-search` |
| `_iris_busy` | `start`/`stop`: writes `IRIS_CMD_START` / `IRIS_CMD_STOP:0` to `$IRIS_FD` so iris stands down while a binding's program runs. No-op outside iris |
| `_tv_inline` | `tv $argv --inline`, then moves the cursor up one line on `/dev/tty` | | `_tv_inline` | `tv $argv --inline`, then moves the cursor up one line on `/dev/tty` |
| `ai` | `gum choose` among `claude`, `opencode`, `codex`, `hermes`, `fractal`. `claude` runs with `--dangerously-skip-permissions` | | `ai` | `gum choose` among `claude`, `opencode`, `codex`, `hermes`, `fractal`. `claude` runs with `--dangerously-skip-permissions` |
| `c` | `clear` with no argument, `cd <dir>` with one | | `c` | `clear` with no argument, `cd <dir>` with one |
@@ -304,7 +302,7 @@ Every file in `functions/` is autoloaded by name. Several also exist as POSIX sc
| `y` | yazi with `--cwd-file`, then cd to the directory yazi exited in | | `y` | yazi with `--cwd-file`, then cd to the directory yazi exited in |
| `yy` | Same as `y`. Used by the <kbd>ctrl-f</kbd> bind | | `yy` | Same as `y`. Used by the <kbd>ctrl-f</kbd> bind |
Functions defined inside `conf.d/` and `config.fish` rather than `functions/`: `q` (20-aliases), `terminal_title` (50-terminal-title), the `pnpmf*` helpers and `__pnpm_pick_pkg` (pnpm-filter), the `_nvm_install`/`_nvm_update`/`_nvm_uninstall` event handlers (nvm), and `starship_transient_prompt_func` and `starship_transient_rprompt_func` (config.fish). Under iris, `iris init fish` adds `_iris_fish_prompt`, `_iris_fish_preexec`, and `_iris_fish_postexec`. Functions defined inside `conf.d/` and `config.fish` rather than `functions/`: `q` (20-aliases), `terminal_title` (50-terminal-title), the `pnpmf*` helpers and `__pnpm_pick_pkg` (pnpm-filter), the `_nvm_install`/`_nvm_update`/`_nvm_uninstall` event handlers (nvm), and `starship_transient_prompt_func`, `starship_transient_rprompt_func`, and `_atuin_ai_question_mark` (config.fish).
:::warning[gh-sonr-org-migrate] :::warning[gh-sonr-org-migrate]
`functions/gh-sonr-org-migrate.fish` is a script, not a function definition. Running `gh-sonr-org-migrate` in fish autoloads the file, which runs the whole fish migration flow. Because the file defines no function of that name, fish then falls through to the bash port at `~/.local/bin/gh-sonr-org-migrate` and starts a second run. Cancel the second run at its first `gum` prompt, or call the bash script by its full path. `functions/gh-sonr-org-migrate.fish` is a script, not a function definition. Running `gh-sonr-org-migrate` in fish autoloads the file, which runs the whole fish migration flow. Because the file defines no function of that name, fish then falls through to the bash port at `~/.local/bin/gh-sonr-org-migrate` and starts a second run. Cancel the second run at its first `gum` prompt, or call the bash script by its full path.
+6 -8
View File
@@ -1,11 +1,11 @@
--- ---
title: Shell title: Shell
description: fish, bash, and zsh with shared aliases, the starship prompt, iris and atuin, and the ~/.local/bin scripts. description: fish, bash, and zsh with shared aliases, the starship prompt, atuin history, and the ~/.local/bin scripts.
sidebar: sidebar:
label: Overview label: Overview
--- ---
fish is the main interactive shell. fish and zsh both run under [iris](/shell/iris) for an inline completion menu; bash uses atuin for history. All three share aliases, the prompt, and ports of the cd-style helper functions. General-purpose tools are POSIX or bash scripts in `~/.local/bin`, so each has a single implementation that all three shells call. fish is the main interactive shell. bash and zsh are set up to feel the same: they share aliases, the prompt, atuin history, and ports of the cd-style helper functions. General-purpose tools are POSIX or bash scripts in `~/.local/bin`, so each has a single implementation that all three shells call.
| Page | Covers | | Page | Covers |
| --- | --- | | --- | --- |
@@ -14,8 +14,7 @@ fish is the main interactive shell. fish and zsh both run under [iris](/shell/ir
| [Aliases](/shell/aliases) | Every alias from `.chezmoidata/aliases.yaml` plus the fish-only and shell-specific ones | | [Aliases](/shell/aliases) | Every alias from `.chezmoidata/aliases.yaml` plus the fish-only and shell-specific ones |
| [Scripts](/shell/scripts) | Every general-purpose executable in `~/.local/bin` (`pj`, `ghn`, `gop`, `mtime-sort`, `atuin-pick`, …) | | [Scripts](/shell/scripts) | Every general-purpose executable in `~/.local/bin` (`pj`, `ghn`, `gop`, `mtime-sort`, `atuin-pick`, …) |
| [Starship](/shell/starship) | `~/.config/starship.toml`: format, modules, symbols, cyberdream palette | | [Starship](/shell/starship) | `~/.config/starship.toml`: format, modules, symbols, cyberdream palette |
| [iris](/shell/iris) | `~/.config/iris/config.toml`: the inline completion menu that wraps fish and zsh, zoxide `cd`, key ownership | | [atuin](/shell/atuin) | `~/.config/atuin`: history search settings, AI inline integration, cyberdream theme |
| [atuin](/shell/atuin) | `~/.config/atuin`: bash history search settings, cyberdream theme |
## Shared across shells ## Shared across shells
@@ -23,11 +22,10 @@ fish is the main interactive shell. fish and zsh both run under [iris](/shell/ir
| --- | --- | --- | --- | | --- | --- | --- | --- |
| Aliases | `conf.d/20-aliases.fish` (hand-maintained) | `~/.config/shell/aliases.sh` (rendered from YAML) | same as bash | | Aliases | `conf.d/20-aliases.fish` (hand-maintained) | `~/.config/shell/aliases.sh` (rendered from YAML) | same as bash |
| Prompt | starship + transient prompt | starship | starship | | Prompt | starship + transient prompt | starship | starship |
| History | fish's own history, browsed through iris (hishtory also sourced if installed) | atuin | zsh's own history, browsed through iris, plus suvadu if installed | | History | atuin (hishtory also sourced if installed) | atuin | atuin, plus suvadu if installed |
| Completion menu | iris | bash completion | iris | | Key bindings | vi mode plus custom binds | atuin defaults only | emacs keymap plus ports of the fish binds |
| Key bindings | vi mode plus custom binds | atuin defaults plus picker keys | emacs keymap plus ports of the fish binds |
| cd-style helpers | `functions/*.fish` | `~/.config/bash/functions/*.bash` | `~/.config/zsh/functions/*.zsh` | | cd-style helpers | `functions/*.fish` | `~/.config/bash/functions/*.bash` | `~/.config/zsh/functions/*.zsh` |
| Directory jumping | `cd` wrapper with zoxide fallback, `zoxide init` | `cd` wrapper | `cd` wrapper with zoxide fallback, `zoxide init` | | Directory jumping | `cd` wrapper with zoxide fallback, `zoxide init` | `cd` wrapper | `cd` wrapper |
## Theme ## Theme
-66
View File
@@ -1,66 +0,0 @@
---
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 <kbd>tab</kbd> (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 <kbd>down</kbd> → `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 <kbd>ctrl-p</kbd>.
## 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 <kbd>ctrl-r</kbd> 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 |
| --- | --- |
| <kbd>tab</kbd> | Accept the selected suggestion (replaces the shell's tab completion) |
| <kbd>shift-tab</kbd> | Hide or show the menu for this shell session |
| <kbd>ctrl-r</kbd> | Switch between spec and history mode for this shell session (shadows `tv init`'s history binding) |
| <kbd>up</kbd> / <kbd>down</kbd> | Move the selection in the menu; on a closed menu, browse iris's history (`navigate-closed = "history"`, the default) |
| <kbd>right</kbd> | Accept ghost text |
Every other key in [fish's](/shell/fish#key-bindings) and [zsh's](/shell/bash-zsh#zsh-key-bindings) bindings (<kbd>ctrl-a</kbd>, <kbd>ctrl-e</kbd>, <kbd>ctrl-o</kbd>, <kbd>ctrl-p</kbd>, <kbd>ctrl-z</kbd>, <kbd>ctrl-c</kbd>, …) and fish vi mode's <kbd>esc</kbd> 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.
+1 -1
View File
@@ -3,5 +3,5 @@ import { defineMeta } from "blume";
export default defineMeta({ export default defineMeta({
title: "Shell", title: "Shell",
icon: "terminal", icon: "terminal",
pages: ["index", "fish", "bash-zsh", "aliases", "scripts", "starship", "iris", "atuin"], pages: ["index", "fish", "bash-zsh", "aliases", "scripts", "starship", "atuin"],
}); });
+2 -2
View File
@@ -314,9 +314,9 @@ A task runner that searches every task source in the project and lets you pick o
**Run.** cds into the task's directory and `exec`s the runner. Extra arguments given to `pj` travel to the task through `$PJ_EXTRA` (shell-quoted), with `--` inserted for `npm run`, `mise`, and `devbox`. Tasks named `dev`, `start`, `serve`, `watch`, or namespaced variants like `dev:web` are wrapped in `openlogs` (found on `PATH`, via `mise which`, or in mise's node installs) for a live log tail. Without openlogs they run unwrapped, with a note. **Run.** cds into the task's directory and `exec`s the runner. Extra arguments given to `pj` travel to the task through `$PJ_EXTRA` (shell-quoted), with `--` inserted for `npm run`, `mise`, and `devbox`. Tasks named `dev`, `start`, `serve`, `watch`, or namespaced variants like `dev:web` are wrapped in `openlogs` (found on `PATH`, via `mise which`, or in mise's node installs) for a live log tail. Without openlogs they run unwrapped, with a note.
**Env.** `PJ_STATE=FILE` makes `pj` write `open` to `FILE` before launching the picker, and `pj --run` writes `ran`; the file stays empty when there are no task sources. The key bindings read it to tell no tasks, a cancelled picker, and a task that printed output apart and redraw the prompt to match. `pj` itself does no cursor handling, so typing `pj` behaves like any command. **Env.** `PJ_QUIET=1` suppresses the "no task sources found" error (exit status stays 1). The <kbd>down</kbd> bindings use it. `PJ_STATE=FILE` makes `pj` write `open` to `FILE` before launching the picker, and `pj --run` writes `ran`; the file stays empty when there are no task sources. The key bindings read it to tell no tasks, a cancelled picker, and a task that printed output apart and redraw the prompt to match. `pj` itself does no cursor handling, so typing `pj` behaves like any command.
**Launched by:** <kbd>ctrl-p</kbd> in fish, zsh, and bash. **Launched by:** <kbd>ctrl-p</kbd>, and <kbd>down</kbd> on an empty prompt, in fish, zsh, and bash.
**Needs:** `bash`, `tv`, `mtime-sort`, `jq`, `python3` 3.11+ (for `tomllib`), plus whichever runners the project uses. **Needs:** `bash`, `tv`, `mtime-sort`, `jq`, `python3` 3.11+ (for `tomllib`), plus whichever runners the project uses.
+5 -5
View File
@@ -70,14 +70,14 @@ Every custom cable except `agents` (a fixed list, see [agents](#agents)), `omp-s
| --- | --- | --- | | --- | --- | --- |
| [`ghq-repos`](#ghq-repos) | `cable/ghq-repos.toml` | <kbd>ctrl-o</kbd> (`gho`) | | [`ghq-repos`](#ghq-repos) | `cable/ghq-repos.toml` | <kbd>ctrl-o</kbd> (`gho`) |
| [`git-files`](#git-files) | `cable/git-files.toml` | <kbd>ctrl-e</kbd> | | [`git-files`](#git-files) | `cable/git-files.toml` | <kbd>ctrl-e</kbd> |
| [`pj`](#pj) | `cable/pj.toml` | <kbd>ctrl-p</kbd> (`pj`) | | [`pj`](#pj) | `cable/pj.toml` | <kbd>ctrl-p</kbd>, <kbd>down</kbd> on an empty prompt (`pj`) |
| [`files`](#files) | `cable/files.toml` | `tv`, <kbd>ctrl-t</kbd> autocomplete | | [`files`](#files) | `cable/files.toml` | `tv`, <kbd>ctrl-t</kbd> autocomplete |
| [`obsidian`](#obsidian) | `cable/obsidian.toml` | `tv obsidian` | | [`obsidian`](#obsidian) | `cable/obsidian.toml` | `tv obsidian` |
| [`agents`](#agents) | `cable/agents.toml` | `tv agents` | | [`agents`](#agents) | `cable/agents.toml` | `tv agents` |
| [`omp-sessions`](#omp-sessions) | `cable/omp-sessions.toml` | `tv omp-sessions`, <kbd>ctrl-a</kbd> (`omp-session`) when it has nothing to resume | | [`omp-sessions`](#omp-sessions) | `cable/omp-sessions.toml` | `tv omp-sessions`, <kbd>ctrl-a</kbd> (`omp-session`) when it has nothing to resume |
| [`jobs`](#jobs) | `cable/jobs.toml` | <kbd>ctrl-z</kbd> in fish (`_jobs_bind`) and zsh (`jobs-fg`) with two or more background jobs | | [`jobs`](#jobs) | `cable/jobs.toml` | <kbd>ctrl-z</kbd> in fish (`_jobs_bind`) with two or more background jobs |
The keys are bound in fish (`fish_user_key_bindings.fish`), zsh (`keybinds.zsh`), and bash (`keybinds.bash`); bash has no <kbd>ctrl-a</kbd> or <kbd>ctrl-z</kbd> binding. See [fish](/shell/fish#key-bindings) and [bash and zsh](/shell/bash-zsh). Under [iris](/shell/iris), the bindings tell iris a picker is running (`_iris_busy`), or iris would swallow the arrow keys meant for tv. The keys are bound in fish (`fish_user_key_bindings.fish`), zsh (`keybinds.zsh`), and bash (`keybinds.bash`); bash has no <kbd>ctrl-a</kbd> binding, and <kbd>ctrl-z</kbd> is fish only. See [fish](/shell/fish#key-bindings) and [bash and zsh](/shell/bash-zsh).
### ghq-repos ### ghq-repos
@@ -161,7 +161,7 @@ The preview runs `omp-session --preview`: title, last-write time and cwd, then t
### jobs ### jobs
The current shell's background jobs, stopped or running, newest first. tv runs as a child process and cannot read the shell's job table, so the <kbd>ctrl-z</kbd> binding (`_jobs_bind` in [fish](/shell/fish#key-bindings), `jobs-fg` in [zsh](/shell/bash-zsh#zsh-key-bindings)) writes it to a temp file named by `$TV_JOBS` and the source just `cat`s that file; opened any other way the list is empty. Entries are `%<id> <state> <command><TAB><id><TAB><pgid>`; <kbd>enter</kbd> prints the id and the binding runs `fg %<id>`. The current fish shell's background jobs, stopped or running, newest first. tv runs as a child process and cannot read the shell's job table, so `_jobs_bind` ([fish](/shell/fish#key-bindings)) writes it to a temp file named by `$TV_JOBS` and the source just `cat`s that file; opened any other way the list is empty. Entries are `%<id> <state> <command><TAB><id><TAB><pgid>`; <kbd>enter</kbd> prints the id and the binding runs `fg %<id>`.
The preview finds the job's processes by process group (procps `ps -g` matches sessions, so it filters `ps -e -o pgid,pid`) and shows the first process's cwd and start time, then `ps --forest` with PID, STAT (`T` = stopped), elapsed time, CPU, RSS, and arguments. The preview script avoids `{`/`}` because tv would parse them as templates and leave `{split:…}` unexpanded. The preview finds the job's processes by process group (procps `ps -g` matches sessions, so it filters `ps -e -o pgid,pid`) and shows the first process's cwd and start time, then `ps --forest` with PID, STAT (`T` = stopped), elapsed time, CPU, RSS, and arguments. The preview script avoids `{`/`}` because tv would parse them as templates and leave `{split:…}` unexpanded.
@@ -170,4 +170,4 @@ The preview finds the job's processes by process group (procps `ps -g` matches s
| <kbd>enter</kbd> | Print the job id; `_jobs_bind` runs `fg` on it | | <kbd>enter</kbd> | Print the job id; `_jobs_bind` runs `fg` on it |
| <kbd>ctrl-d</kbd> | `kill` then `reload_source`: SIGTERM to the job's process group, then SIGCONT so a stopped job receives it. Once the group is gone (polled up to 1 s) the row is removed from `$TV_JOBS`, so the reloaded list drops it; a job that ignores SIGTERM stays listed. Fish reports the termination at the next prompt | | <kbd>ctrl-d</kbd> | `kill` then `reload_source`: SIGTERM to the job's process group, then SIGCONT so a stopped job receives it. Once the group is gone (polled up to 1 s) the row is removed from `$TV_JOBS`, so the reloaded list drops it; a job that ignores SIGTERM stays listed. Fish reports the termination at the next prompt |
**Needs:** `ps`, fish or zsh. **Needs:** `ps`, fish.
+19 -4
View File
@@ -1,7 +1,7 @@
# ~/.config/bash/keybinds.bash — readline ports of the television keys in # ~/.config/bash/keybinds.bash — readline ports of the television keys in
# fish_user_key_bindings.fish / zsh keybinds.zsh: # fish_user_key_bindings.fish / zsh keybinds.zsh:
# ctrl-o → gho (ghq-repos cable, cd) ctrl-e → tv git-files (nvim) # ctrl-o → gho (ghq-repos cable, cd) ctrl-e → tv git-files (nvim)
# ctrl-p → pj (pj cable) # ctrl-p → pj (pj cable) down → pj on an empty prompt
# ctrl-t → tv smart autocomplete (tv init bash ships no ctrl-t) # ctrl-t → tv smart autocomplete (tv init bash ships no ctrl-t)
# Keys owned by kitty.conf never reach the shell: ctrl-h/j/k/l (pass_keys.py). # Keys owned by kitty.conf never reach the shell: ctrl-h/j/k/l (pass_keys.py).
# #
@@ -19,12 +19,12 @@ _kb_git_files() {
} }
bind -x '"\C-e": _kb_git_files' bind -x '"\C-e": _kb_git_files'
# ctrl-p. pj reports through $PJ_STATE: `open` = cancelled, `ran` = a task # ctrl-p, and down on an empty prompt. pj reports through $PJ_STATE: `open`
# printed output, empty = no task sources. # = cancelled, `ran` = a task printed output, empty = no task sources.
_kb_pj() { _kb_pj() {
local state local state
state=$(mktemp) || return state=$(mktemp) || return
PJ_STATE=$state pj </dev/tty PJ_STATE=$state pj "$@" </dev/tty
[ "$(<"$state")" = open ] && _kb_up [ "$(<"$state")" = open ] && _kb_up
rm -f "$state" rm -f "$state"
} }
@@ -66,6 +66,21 @@ bind -x '"\C-x1": _kb_gho'
bind -x '"\C-x0": _kb_restore' bind -x '"\C-x0": _kb_restore'
bind '"\C-o": "\C-x1\C-x2\C-x3\C-x0"' bind '"\C-o": "\C-x1\C-x2\C-x3\C-x0"'
# down: the pj cable on an idle, empty prompt, otherwise history search
# (fish's _pj_bind_down). bind -x can't call readline functions, so _kb_down
# rebinds \C-x5, which the macro sends right after it.
_kb_down() {
if [ -n "$READLINE_LINE" ]; then
bind '"\C-x5": history-search-forward'
return
fi
PJ_QUIET=1 _kb_pj
bind '"\C-x5": redraw-current-line'
}
bind -x '"\C-x4": _kb_down'
bind '"\e[B": "\C-x4\C-x5"'
bind '"\eOB": "\C-x4\C-x5"'
# ctrl-t: pick with the channel tv guesses from the text left of the cursor # ctrl-t: pick with the channel tv guesses from the text left of the cursor
# (shell_integration.channel_triggers) and insert the picks there, quoted. # (shell_integration.channel_triggers) and insert the picks there, quoted.
_kb_tv_autocomplete() { _kb_tv_autocomplete() {
+2
View File
@@ -0,0 +1,2 @@
source "$HOME/.atuin/bin/env.fish"
+52 -14
View File
@@ -33,6 +33,11 @@ fish_add_path $HOME/.grok/bin
# Generated for envman. Do not edit. # Generated for envman. Do not edit.
test -s ~/.config/envman/load.fish; and source ~/.config/envman/load.fish test -s ~/.config/envman/load.fish; and source ~/.config/envman/load.fish
if status is-interactive
atuin init fish | source
atuin ai init fish | source
end
# cmux # cmux
fish_add_path "/home/prad/.cmux/bin" fish_add_path "/home/prad/.cmux/bin"
fish_add_path "/home/prad/.cache/.bun/bin" fish_add_path "/home/prad/.cache/.bun/bin"
@@ -41,20 +46,53 @@ for _f in $HOME/.config/herdr/plugins/github/herdr-automatic-rename-*/shell/hook
test -r "$_f"; and source "$_f"; and break test -r "$_f"; and source "$_f"; and break
end end
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv fish)"
# iris (~/.config/iris/config.toml). The init's first half `exec`s iris from an # Question mark at start of line - natural language mode
# interactive fish, which then starts this config again inside its pty; the function _atuin_ai_question_mark
# second half installs the cwd/command hooks there. Skipped where iris is not set -l buf (commandline -b)
# installed. `IRIS_RESCUE=1 fish` opens a plain fish (e.g. if iris breaks).
if command -q iris # If buffer is empty or just contains '?', trigger natural language mode
# fish's `query-term` feature asks the terminal for its capabilities and, if test -z "$buf" -o "$buf" = "?"
# in kitty, turns on the kitty keyboard protocol (keys as CSI-u escapes). commandline -r ""
# iris reads raw key bytes and cannot parse those, so its menu never
# opened in kitty. Feature flags are read at startup, so export the opt-out # Close the semantic prompt zone (OSC 133 C) so terminals with
# before `exec iris`: the fish iris starts inherits it. # shell integration don't erase the TUI's output during their
if status is-interactive; and not set -q IRIS_PID; and not set -q IRIS_RESCUE # resize-time prompt reflow.
set -gx fish_features no-query-term printf '\033]133;C\007' > /dev/tty
# Run atuin ai inline, swapping stdout and stderr
set -l output (atuin ai inline --hook 3>&1 1>&2 2>&3 | string collect)
if string match --quiet '__atuin_ai_print__:*' "$output"
echo (string replace "__atuin_ai_print__:" "" -- "$output" | string collect)
commandline -f repaint
else if test "$output" = "__atuin_ai_cancel__"
commandline -f repaint
else if string match --quiet '__atuin_ai_execute__:*' "$output"
# Execute the command immediately
set -l cmd (string replace "__atuin_ai_execute__:" "" -- "$output" | string collect)
commandline -r "$cmd"
commandline -f repaint
commandline -f execute
else if string match --quiet '__atuin_ai_insert__:*' "$output"
# Insert the command for editing
set -l cmd (string replace "__atuin_ai_insert__:" "" -- "$output" | string collect)
commandline -r "$cmd"
commandline -f repaint
else if test -n "$output"
# Default: insert for editing
commandline -r "$output"
commandline -f repaint
else
commandline -f repaint
end
else if not contains -- "$fish_key_bindings" fish_vi_key_bindings fish_hybrid_key_bindings
# Not at empty prompt, just insert the question mark
commandline -i "?"
end end
iris init fish | source
end end
# Set up keybindings
bind "?" _atuin_ai_question_mark
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv fish)"
-6
View File
@@ -1,6 +0,0 @@
function _bind_run --description 'run a command from a key binding (iris-aware), then repaint'
_iris_busy start
$argv
_iris_busy stop
commandline -f repaint
end
-2
View File
@@ -1,8 +1,6 @@
function _gho_bind --description 'ctrl-o: gho (tv --inline), then redraw the prompt in the right place' function _gho_bind --description 'ctrl-o: gho (tv --inline), then redraw the prompt in the right place'
set -l before $PWD set -l before $PWD
_iris_busy start
gho gho
_iris_busy stop
if test "$PWD" = "$before" if test "$PWD" = "$before"
# Cancelled (or the nvim action ran): tv --inline left the cursor on # Cancelled (or the nvim action ran): tv --inline left the cursor on
# the line below the prompt; step back so repaint redraws in place. # the line below the prompt; step back so repaint redraws in place.
-15
View File
@@ -1,15 +0,0 @@
function _iris_busy --description 'tell iris a key binding is running a program (start|stop)'
# iris (~/.config/iris) learns a command is running from fish_preexec /
# fish_postexec, which never fire for programs a key binding starts. Without
# this, iris keeps treating the screen as an idle prompt while tv, yazi or
# lazygit run: it swallows up/down/tab and can draw its menu over them.
# Same messages as iris's own hooks; a no-op outside iris.
set -q IRIS_FD; or return 0
switch "$argv[1]"
case start
printf 'IRIS_CMD_START\x00' >&$IRIS_FD 2>/dev/null
case stop
printf 'IRIS_CMD_STOP:0\x00' >&$IRIS_FD 2>/dev/null
end
return 0
end
+4 -3
View File
@@ -1,13 +1,14 @@
function _pj_bind --description 'ctrl-p: pj (tv --inline), then redraw the prompt in the right place' function _pj_bind --description 'ctrl-p / down: pj (tv --inline), then redraw the prompt in the right place'
# pj reports through $PJ_STATE: `open` = the picker was cancelled and # pj reports through $PJ_STATE: `open` = the picker was cancelled and
# left the cursor on the line below the prompt; empty = no task sources, # left the cursor on the line below the prompt; empty = no task sources,
# nothing drawn — but fish's repaint after an external command still # nothing drawn — but fish's repaint after an external command still
# lands one line low, so both step back up. `ran` = a task printed # lands one line low, so both step back up. `ran` = a task printed
# output: new line, prompt below it. # output: new line, prompt below it.
set -lx PJ_STATE (mktemp) set -lx PJ_STATE (mktemp)
_iris_busy start if contains -- --quiet $argv
set -lx PJ_QUIET 1
end
pj pj
_iris_busy stop
set -l state (cat $PJ_STATE) set -l state (cat $PJ_STATE)
switch "$state" switch "$state"
case open '' case open ''
@@ -0,0 +1,21 @@
function _pj_bind_down --description "down arrow: pj tv cable on an idle empty prompt, else fish's down-or-search"
# Mirror of atuin's up binding (atuin owns up, this owns down). Everything
# that legitimately needs the arrow keys keeps them:
# - non-empty buffer → multiline cursor movement / forward history search
# - paging mode → tab-completion pager navigation (tab on an empty
# line opens the pager with an empty buffer)
# - search mode → history-search navigation
# down-or-search is fish's own input function, called as a command exactly
# the way atuin's _atuin_bind_up calls up-or-search.
set -l buf (commandline | string collect)
if test -n "$buf"; or commandline --paging-mode; or commandline --search-mode
down-or-search
return
end
# _pj_bind runs ~/.local/bin/pj (also ctrl-p), which opens the `pj` tv
# cable. --quiet (PJ_QUIET) keeps it silent when the tree has no task
# source at all, so down is a no-op outside projects instead of printing
# an error per press.
_pj_bind --quiet
end
@@ -3,10 +3,8 @@ function _tv_inline --description 'tv --inline below the prompt, cursor back on
# its own first row when it exits; step back up so the following # its own first row when it exits; step back up so the following
# `commandline -f repaint` redraws the prompt in place instead of one line # `commandline -f repaint` redraws the prompt in place instead of one line
# lower. /dev/tty keeps the move out of callers that capture stdout (gho). # lower. /dev/tty keeps the move out of callers that capture stdout (gho).
_iris_busy start
tv $argv --inline tv $argv --inline
set -l st $status set -l st $status
_iris_busy stop
printf '\033[A' >/dev/tty printf '\033[A' >/dev/tty
return $st return $st
end end
@@ -10,29 +10,33 @@ function fish_user_key_bindings
# Keys owned by kitty.conf never reach fish and are NOT bound here: # Keys owned by kitty.conf never reach fish and are NOT bound here:
# ctrl-g → lazygit overlay, ctrl-y → yazi vsplit, # ctrl-g → lazygit overlay, ctrl-y → yazi vsplit,
# ctrl-h/j/k/l → vim-aware split nav (pass_keys.py). # ctrl-h/j/k/l → vim-aware split nav (pass_keys.py).
# ctrl-e: tv git-files (bat preview, enter opens nvim). ctrl-o and # ctrl-e: tv git-files (bat preview, enter opens nvim). ctrl-o, ctrl-p
# ctrl-p also open tv cables — same keys in zsh and bash. tv runs # and down also open tv cables — same keys in zsh and bash. tv runs
# --inline below the prompt; _tv_inline/_gho_bind/_pj_bind put the # --inline below the prompt; _tv_inline/_gho_bind/_pj_bind put the
# prompt back in place (or below any output) afterwards. ctrl-a: # prompt back in place (or below any output) afterwards. ctrl-a:
# omp-session resumes or starts omp for the repo (same in zsh); when it # omp-session resumes or starts omp for the repo (same in zsh); when it
# opens the omp-sessions cable and it is cancelled, it steps back up. # opens the omp-sessions cable and it is cancelled, it steps back up.
# Every binding that runs a program tells iris via _iris_busy
# (_bind_run, _tv_inline, _gho_bind, _pj_bind), or iris would keep
# its prompt key handling while tv/yazi/lazygit own the screen.
bind -M $mode ctrl-e '_tv_inline git-files; commandline -f repaint' bind -M $mode ctrl-e '_tv_inline git-files; commandline -f repaint'
bind -M $mode ctrl-b '_bind_run purple' bind -M $mode ctrl-b purple repaint
bind -M $mode ctrl-y '_bind_run clip-last' bind -M $mode ctrl-y clip-last
bind -M $mode ctrl-o _gho_bind bind -M $mode ctrl-o _gho_bind
bind -M $mode ctrl-p _pj_bind bind -M $mode ctrl-p _pj_bind
bind -M $mode ctrl-a '_bind_run omp-session' bind -M $mode ctrl-a 'omp-session; commandline -f repaint'
bind -M $mode ctrl-w 'atuin ai inline; commandline -f repaint'
# ctrl-z at the prompt: fg the only background job, or pick one from # ctrl-z at the prompt: fg the only background job, or pick one from
# the jobs tv cable when there are several (_jobs_bind). # the jobs tv cable when there are several (_jobs_bind).
bind -M $mode ctrl-z _jobs_bind bind -M $mode ctrl-z _jobs_bind
# Arrow keys: atuin owns up (bound from config.fish, survives the
# fish_vi_key_bindings reset above because that only erases presets),
# down opens the pj tv cable — but only on an idle, empty prompt; see
# _pj_bind_down.
bind -M $mode down _pj_bind_down
# ctrl-f: yazi (cd's to yazi's exit dir on quit). # ctrl-f: yazi (cd's to yazi's exit dir on quit).
bind -M $mode ctrl-f '_bind_run yy' bind -M $mode ctrl-f 'yy; commandline -f repaint'
bind -M $mode ctrl-g '_bind_run lazygit' bind -M $mode ctrl-g 'lazygit; commandline -f repaint'
# ctrl-c routes through the clear wrapper (kitty scrollback + prompt # ctrl-c routes through the clear wrapper (kitty scrollback + prompt
# pinned to the bottom) AND wipes any text typed on the command line. # pinned to the bottom) AND wipes any text typed on the command line.
-73
View File
@@ -1,73 +0,0 @@
# iris (https://github.com/versenilvis/iris): inline completion menu that wraps
# the shell in its own pty. Started from fish and zsh (`iris init <shell>` in
# config.fish / .zshrc). `iris config show` prints the merged result; missing
# keys fall back to iris's defaults.
[core]
version = 1 # config schema version
shell = "" # auto-detect; `iris init <shell>` exports IRIS_ACTIVE_SHELL, which wins anyway
shell-login = false # run shell as a login shell (also: iris --shell-login)
# Every shell opens in spec mode: Fig-style completions (subcommands, flags,
# paths) pop up as you type. ctrl+r still flips to history for that session;
# "last" would carry a stray ctrl+r into every later shell.
mode = "spec" # "last", "spec", or "history"
debug = false # verbose logging to iris.log (also: iris -d); logs every keystroke
expand-alias = true # expand aliases before matching
auto-execute = false # run suggestion immediately instead of inserting it
atuin-history = 0 # shell history only: atuin is not used under iris
atuin-db-path = "" # path to atuin's history.db, empty = use default
cobra-probe-enabled = true # fall back to probing cobra binaries for completions
# Menu closed: up/down browse iris's own history (Fig-style). No shell binds
# up/down; handing them to the shell ran the old down -> pj picker inside
# iris's pty, and the two fought over the screen.
navigate-closed = "history"
[ui]
style = "modern" # "modern" or "classic"
ghost-text = 1 # 0 = off, 1 = menu + ghost text, 2 = ghost text only
hidden-files = false # include dotfiles in suggestions
max-suggestions = 100 # max suggestions ranked before display
max-height = 6 # max visible rows in the menu
max-width = 0 # menu width: columns (80) or a share ("80%"), 0 = default
nerd-fonts = true # use nerd-font icons (Victor Mono Nerd Font)
# ctrl+r, tab, shift+tab, up and down never reach the shell while iris runs;
# tv's own ctrl-r history binding (`tv init`) is shadowed by toggle-mode.
[keybindings]
toggle-mode = "ctrl+r" # switch spec/history mode
toggle-menu = "shift+tab" # show or hide the suggestion menu
select = "tab" # accept the selected suggestion
navigate-up = "up" # select up (menu open)
navigate-down = "down" # select down (menu open)
navigate-right = "right" # accept ghost text
[git]
filter-active-branch = true # exclude current branch from suggestions
deduplicate-branches = true # merge same-name local/remote branches
# Installed from the AUR (iris-autocomplete): pacman/paru owns updates, so iris
# neither checks nor installs on its own.
[updater]
check-on-startup = false
channel = "stable"
check-interval = "24h"
auto-update = 0
[zoxide]
extend-cd = true # complete `cd` from zoxide's database (conf.d/40-tools.fish runs `zoxide init fish`)
[ai]
enabled = false
provider = "groq" # "groq" or "ollama"
debounce_ms = 400 # ms to wait before querying the AI
[ai.providers.groq]
endpoint = "https://api.groq.com/openai/v1/chat/completions"
api_key_env = "GROQ_API_KEY" # read from the environment, never stored here
model = "llama-3.3-70b-versatile"
timeout_ms = 3000
[ai.providers.ollama]
endpoint = "http://localhost:11434/v1/chat/completions"
model = "qwen2.5-coder"
timeout_ms = 5000
-35
View File
@@ -1,35 +0,0 @@
# czs: chezmoi re-add drifted files, then commit + push the source repo.
# Port of functions/czs.fish. Optional targets scope the status view and the
# re-add (e.g. `czs ~/.config/fish`); no args = all managed files.
czs() {
local drift src ahead
drift=$(chezmoi status "$@") || return
if [[ -n $drift ]]; then
print -r -- $drift
# re-add takes the local (destination) contents as truth for modified
# files; templates are never overwritten, non-file entries are ignored.
chezmoi re-add "$@" || return
else
echo "chs: in sync — nothing to re-add"
fi
# Commit + sync the source repo (~/.local/share/chezmoi).
src=$(chezmoi source-path)
if [[ -n $(git -C $src status --porcelain) ]]; then
git -C $src add -A &&
git -C $src commit --quiet -m 'chs: re-add drifted files' || return
echo "chs: committed $(git -C $src rev-parse --short HEAD)"
fi
# Push anything unpushed: this commit and leftovers from a failed push.
# --autostash tolerates post-commit hooks re-dirtying the tree mid-sync.
ahead=$(git -C $src rev-list --count @{upstream}..HEAD 2>/dev/null || echo 0)
if (( ahead > 0 )); then
git -C $src pull --rebase --autostash --quiet &&
git -C $src push --quiet &&
echo "chs: pushed $ahead commit(s) to $(git -C $src remote get-url origin)"
else
echo "chs: remote up to date"
fi
}
-2
View File
@@ -1,2 +0,0 @@
# y: same as yy (fish has both names) — yazi, then cd to its exit directory.
y() { yy "$@"; }
+16 -68
View File
@@ -5,20 +5,6 @@
bindkey -e # pin emacs keymap ($EDITOR containing "vi" would pick viins) bindkey -e # pin emacs keymap ($EDITOR containing "vi" would pick viins)
unsetopt flow_control # free ^Q/^S in zle so the ctrl-q exit bind fires unsetopt flow_control # free ^Q/^S in zle so the ctrl-q exit bind fires
# iris (~/.config/iris) learns a command is running from the preexec/precmd
# hooks, which never fire for programs a widget starts. Without this, iris
# keeps treating the screen as an idle prompt while tv, yazi or lazygit run:
# it swallows up/down/tab and can draw its menu over them. Same messages as
# iris's own hooks; a no-op outside iris. fish: functions/_iris_busy.fish.
_iris_busy() {
[[ -n $IRIS_FD ]] || return 0
case $1 in
start) print -u $IRIS_FD -N -r -- IRIS_CMD_START 2>/dev/null ;;
stop) print -u $IRIS_FD -N -r -- IRIS_CMD_STOP:0 2>/dev/null ;;
esac
return 0
}
# Generic runner: widget `run-<cmd>` executes <cmd> (function or binary) with # Generic runner: widget `run-<cmd>` executes <cmd> (function or binary) with
# the tty as stdin, then repaints — fish's `commandline -f repaint`. # the tty as stdin, then repaints — fish's `commandline -f repaint`.
# cwd changes from gho/yy persist: widgets run in the shell process. # cwd changes from gho/yy persist: widgets run in the shell process.
@@ -27,9 +13,7 @@ _iris_busy() {
_zle-run() { _zle-run() {
local cmd=${WIDGET#run-} local cmd=${WIDGET#run-}
zle -I zle -I
_iris_busy start
$cmd </dev/tty $cmd </dev/tty
_iris_busy stop
zle reset-prompt zle reset-prompt
} }
for _cmd in purple clip-last omp-session yy lazygit; do for _cmd in purple clip-last omp-session yy lazygit; do
@@ -47,9 +31,7 @@ unset _cmd
# cursor to tv's line). # cursor to tv's line).
_zle-tv-git-files() { _zle-tv-git-files() {
zle -I zle -I
_iris_busy start
tv git-files --inline </dev/tty tv git-files --inline </dev/tty
_iris_busy stop
printf '\033[A' >/dev/tty printf '\033[A' >/dev/tty
zle reset-prompt zle reset-prompt
} }
@@ -60,9 +42,7 @@ zle -N tv-git-files _zle-tv-git-files
_zle-gho() { _zle-gho() {
local before=$PWD local before=$PWD
zle -I zle -I
_iris_busy start
gho </dev/tty gho </dev/tty
_iris_busy stop
if [[ $PWD == "$before" ]]; then if [[ $PWD == "$before" ]]; then
printf '\033[A' >/dev/tty printf '\033[A' >/dev/tty
zle reset-prompt zle reset-prompt
@@ -70,15 +50,23 @@ _zle-gho() {
} }
zle -N gho-inline _zle-gho zle -N gho-inline _zle-gho
# ctrl-p: pj. pj reports through $PJ_STATE: `open` = cancelled, `ran` = a task # ctrl-p, and down on an idle empty prompt (fish's _pj_bind_down; otherwise
# printed output, empty = no task sources. # normal history / multiline movement). pj reports through $PJ_STATE: `open`
# = cancelled, `ran` = a task printed output, empty = no task sources
# (PJ_QUIET keeps down silent outside projects).
_zle-pj() { _zle-pj() {
if [[ $WIDGET == pj-down && -n $BUFFER ]]; then
zle down-line-or-history
return
fi
local state local state
state=$(mktemp) || return state=$(mktemp) || return
zle -I zle -I
_iris_busy start if [[ $WIDGET == pj-down ]]; then
PJ_STATE=$state pj </dev/tty PJ_STATE=$state PJ_QUIET=1 pj </dev/tty
_iris_busy stop else
PJ_STATE=$state pj </dev/tty
fi
case $(<$state) in case $(<$state) in
open) printf '\033[A' >/dev/tty; zle reset-prompt ;; open) printf '\033[A' >/dev/tty; zle reset-prompt ;;
ran) ;; ran) ;;
@@ -87,48 +75,7 @@ _zle-pj() {
rm -f $state rm -f $state
} }
zle -N pj-inline _zle-pj zle -N pj-inline _zle-pj
zle -N pj-down _zle-pj
# ctrl-z at the prompt (fish's _jobs_bind): fg the only job, or pick one from
# the `jobs` tv cable, which reads the job table from $TV_JOBS (records:
# "%<id> <state> <command>", id, process group). While a job holds the
# terminal ctrl-z is still the tty's suspend, so the key toggles.
# fg runs as a real command line (push-input keeps typed text for the next
# prompt); __jobs_bind_fg erases that echoed line and fg's "[1] continued"
# notice. The leading space keeps it out of history (HIST_IGNORE_SPACE).
__jobs_bind_fg() {
printf '\033[A\033[2K\r' >/dev/tty
fg %$1 2>/dev/null
}
_zle-jobs() {
local -a ids recs
local id pick file
ids=(${(On)${(k)jobstates}})
(( $#ids )) || return 0
for id in $ids; do
recs+=("$(printf '%%%s %-9s %s\t%s\t%s' $id ${jobstates[$id]%%:*} \
"${jobtexts[$id]}" $id ${${jobstates[$id]#*:*:}%%=*})")
done
if (( $#ids == 1 )); then
pick=$ids[1]
else
file=$(mktemp) || return
print -rl -- $recs >$file
zle -I
_iris_busy start
pick=$(TV_JOBS=$file tv jobs --inline </dev/tty)
_iris_busy stop
printf '\033[A' >/dev/tty
rm -f $file
if [[ -z $pick ]]; then
zle reset-prompt
return
fi
fi
zle push-input
BUFFER=" __jobs_bind_fg $pick"
zle accept-line
}
zle -N jobs-fg _zle-jobs
bindkey '^E' tv-git-files bindkey '^E' tv-git-files
bindkey '^B' run-purple bindkey '^B' run-purple
@@ -141,7 +88,8 @@ bindkey '^A' run-omp-session
# ctrl-f: yazi; yy cd's to yazi's exit dir on quit. # ctrl-f: yazi; yy cd's to yazi's exit dir on quit.
bindkey '^F' run-yy bindkey '^F' run-yy
bindkey '^G' run-lazygit bindkey '^G' run-lazygit
bindkey '^Z' jobs-fg bindkey '^[[B' pj-down
bindkey '^[OB' pj-down
# ctrl-q mirrors ctrl-d's preset (`exit`) — quit the shell. # ctrl-q mirrors ctrl-d's preset (`exit`) — quit the shell.
_zle-exit() { exit } _zle-exit() { exit }
+3 -1
View File
@@ -14,6 +14,8 @@
# Usage: pj [extra args…] extra args are forwarded to the picked task # Usage: pj [extra args…] extra args are forwarded to the picked task
# #
# Env: # Env:
# PJ_QUIET=1 suppress the "no task sources" error — the down-arrow
# bindings use this, where an empty tree is the normal case.
# PJ_STATE=FILE key bindings pass a path; pj writes `open` to it when the # PJ_STATE=FILE key bindings pass a path; pj writes `open` to it when the
# picker opens and `pj --run` writes `ran`, so the binding # picker opens and `pj --run` writes `ran`, so the binding
# can tell "no tasks" / "cancelled" / "task printed output" # can tell "no tasks" / "cancelled" / "task printed output"
@@ -390,7 +392,7 @@ while :; do
done done
if [ ${#recs[@]} -eq 0 ]; then if [ ${#recs[@]} -eq 0 ]; then
echo "pj: no task sources found in this tree" >&2 [ -n "${PJ_QUIET-}" ] || echo "pj: no task sources found in this tree" >&2
exit 1 exit 1
fi fi
+7 -32
View File
@@ -4,9 +4,7 @@
HISTFILE="$HOME/.zsh_history" HISTFILE="$HOME/.zsh_history"
HISTSIZE=10000 HISTSIZE=10000
SAVEHIST=10000 SAVEHIST=10000
# HIST_IGNORE_SPACE: a leading space keeps a line out of history, as in fish setopt SHARE_HISTORY HIST_IGNORE_DUPS
# (the ctrl-z binding's hidden ` __jobs_bind_fg` relies on it).
setopt SHARE_HISTORY HIST_IGNORE_DUPS HIST_IGNORE_SPACE
# Completion # Completion
autoload -Uz compinit && compinit autoload -Uz compinit && compinit
@@ -22,10 +20,6 @@ export GOPATH="$HOME/.local/share/go"
export GOBIN="$GOPATH/bin" export GOBIN="$GOPATH/bin"
case ":$PATH:" in *":$GOBIN:"*) ;; *) PATH="$GOBIN:$PATH" ;; esac case ":$PATH:" in *":$GOBIN:"*) ;; *) PATH="$GOBIN:$PATH" ;; esac
# mise: tool shims and env (GOROOT above comes from it), as fish's
# conf.d/40-tools.fish does for every fish.
command -v mise >/dev/null 2>&1 && eval "$(mise activate zsh)"
# Repo roots (ghq) — same as fish's conf.d/10-env.fish; the ghq-repos tv # Repo roots (ghq) — same as fish's conf.d/10-env.fish; the ghq-repos tv
# cable (ctrl-o) lists this root. # cable (ctrl-o) lists this root.
export GHQ_ROOT="$HOME/Code" export GHQ_ROOT="$HOME/Code"
@@ -71,33 +65,10 @@ command -v suv >/dev/null 2>&1 && eval "$(suv init zsh)"
# Prompt — starship, consistent with fish # Prompt — starship, consistent with fish
command -v starship >/dev/null 2>&1 && eval "$(starship init zsh)" command -v starship >/dev/null 2>&1 && eval "$(starship init zsh)"
# Material You terminal colors generated from the wallpaper (quickshell), # television: ctrl-t smart autocomplete (its ctrl-r is taken over by atuin
# replayed like fish's conf.d/40-tools.fish. # below, as in fish)
_qs_seq="$HOME/.local/state/quickshell/user/generated/terminal/sequences.txt"
[ -f "$_qs_seq" ] && cat "$_qs_seq"
unset _qs_seq
# zoxide: `z` / `zi`, plus the cd wrapper's fallback (functions/cd.zsh).
command -v zoxide >/dev/null 2>&1 && eval "$(zoxide init zsh)"
# television: ctrl-t smart autocomplete (its ctrl-r is shadowed by iris's
# mode toggle, as in fish)
command -v tv >/dev/null 2>&1 && eval "$(tv init zsh)" command -v tv >/dev/null 2>&1 && eval "$(tv init zsh)"
# iris (~/.config/iris/config.toml): inline completion menu, same as fish.
# The init's first half `exec`s iris from an interactive zsh on a tty; iris
# starts a second zsh in its pty, which runs this file again, and there the
# second half installs the line/cwd/command hooks. `IRIS_RESCUE=1 zsh` skips
# it. Placed before syntax highlighting, which must stay last.
if command -v iris >/dev/null 2>&1; then
eval "$(iris init zsh)"
# Inside iris: PROMPT_SP pads the line with spaces + CR before each prompt
# (the inverse `%` partial-line marker). iris reads that echo as a typed
# space, so every prompt opened with a stale query and a stray first
# suggestion (`4channels`) got inserted on the next key.
[[ -n $IRIS_PID ]] && unsetopt PROMPT_SP
fi
# Fish-style syntax highlighting (zsh-users/zsh-syntax-highlighting). # Fish-style syntax highlighting (zsh-users/zsh-syntax-highlighting).
# MUST stay last in .zshrc: it wraps every zle widget defined above # MUST stay last in .zshrc: it wraps every zle widget defined above
# (keybinds, compinit, suv/starship hooks). # (keybinds, compinit, suv/starship hooks).
@@ -109,6 +80,10 @@ for _zsh_hl in \
done done
unset _zsh_hl unset _zsh_hl
. "$HOME/.atuin/bin/env"
eval "$(atuin init zsh)"
# >>> termium >>> # >>> termium >>>
case ":$PATH:" in *:'/home/prad/.local/bin':*) ;; *) export PATH='/home/prad/.local/bin':"$PATH" ;; esac case ":$PATH:" in *:'/home/prad/.local/bin':*) ;; *) export PATH='/home/prad/.local/bin':"$PATH" ;; esac
# <<< termium <<< # <<< termium <<<
+5 -5
View File
@@ -1,11 +1,11 @@
[metadata] [metadata]
name = "jobs" name = "jobs"
description = """ description = """
The current shell's background jobs (stopped or running). tv runs in a child The current fish shell's background jobs (stopped or running). tv runs in a
process and cannot see the shell's job table, so the ctrl-z binding (fish child process and cannot see the shell's job table, so the ctrl-z binding
_jobs_bind, zsh jobs-fg) writes it to the file named by $TV_JOBS; enter prints (_jobs_bind) writes it to the file named by $TV_JOBS; enter prints the job id
the job id and the binding runs `fg %<id>`; ctrl-d kills the job. Opened any and the binding runs `fg %<id>`; ctrl-d kills the job. Opened any other way,
other way, the list is empty. the list is empty.
""" """
requirements = ["ps"] requirements = ["ps"]
+2 -2
View File
@@ -4,8 +4,8 @@ description = """
Project tasks from every task source between the cwd and the git toplevel Project tasks from every task source between the cwd and the git toplevel
(package.json, Makefile, justfile, mise, devbox, pyproject), tasks of the most (package.json, Makefile, justfile, mise, devbox, pyproject), tasks of the most
recently modified manifest first. Enter runs the task. Discovery, preview and recently modified manifest first. Enter runs the task. Discovery, preview and
run live in ~/.local/bin/pj; `pj` (ctrl-p in fish, zsh and bash) opens this run live in ~/.local/bin/pj; `pj` (ctrl-p, and down on an empty prompt in fish,
cable with its own scan. zsh and bash) opens this cable with its own scan.
""" """
requirements = ["pj", "mtime-sort"] requirements = ["pj", "mtime-sort"]