docs(docs): add television documentation and update shell docs for TV integration

This commit is contained in:
Prad Nukala committed 2026-09-30 11:14:18 -04:00
1 parent 3c4af515fa
commit 7c786f9349
11 files changed
+198 -57

No files matched your search

+37 -14
View File
@@ -3,13 +3,14 @@ title: bash and zsh
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, atuin history, and ports of the fish helpers that have to change the shell's own directory. zsh also gets ports of the fish key bindings.
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 |
| --- | --- |
| `dot_bashrc` | `~/.bashrc` |
| `dot_zshrc` | `~/.zshrc` |
| `dot_config/zsh/keybinds.zsh` | `~/.config/zsh/keybinds.zsh` |
| `dot_config/bash/keybinds.bash` | `~/.config/bash/keybinds.bash` |
| `dot_config/shell/aliases.sh.tmpl` | `~/.config/shell/aliases.sh` (template) |
| `dot_config/bash/functions/*.bash` | `~/.config/bash/functions/` |
| `dot_config/zsh/functions/*.zsh` | `~/.config/zsh/functions/` |
@@ -24,7 +25,7 @@ bash is part of the base system. zsh itself is not listed in `.chezmoidata/packa
Interactive shells only (`[[ $- != *i* ]] && return`). In order:
1. **PATH.** Prepends `~/.local/bin` if missing, sources `~/.cargo/env` if present, and appends `~/.maestro/bin` if it exists.
1. **PATH.** Prepends `~/.local/bin` if missing, sources `~/.cargo/env` if present, and appends `~/.maestro/bin` if it exists. Exports `GHQ_ROOT` and `CODE_ROOT` as `~/Code`.
2. **Go.** `GOPATH=~/.local/share/go`, `GOBIN=$GOPATH/bin`, and `GOBIN` prepended to `PATH`. mise provides `GOROOT`.
3. **Android SDK.** `ANDROID_HOME` and `ANDROID_SDK_ROOT` set to `~/.local/share/android`. `platform-tools`, `cmdline-tools/latest/bin`, and `emulator` are appended if they exist.
4. **Aliases.** Sources `~/.config/shell/aliases.sh`.
@@ -32,38 +33,40 @@ Interactive shells only (`[[ $- != *i* ]] && return`). In order:
6. **Flyline.** If `~/.local/lib/libflyline.so` exists, loads it with `enable -f … flyline` for richer line editing.
7. Sources `~/.config/envman/load.sh` if it is non-empty.
8. **Prompt.** `eval "$(starship init bash)"` if starship is installed.
9. **atuin.** Sources `~/.atuin/bin/env`, then `eval "$(atuin init bash)"`.
bash has no custom key bindings. atuin's init binds <kbd>ctrl-r</kbd> and <kbd>up</kbd>.
9. **television.** `eval "$(tv init bash)"` if `tv` is installed (the `_tv` completion), then sources `~/.config/bash/keybinds.bash`.
10. **atuin.** Sources `~/.atuin/bin/env`, then `eval "$(atuin init bash)"`. atuin comes after tv, so it keeps <kbd>ctrl-r</kbd> and <kbd>up</kbd>.
## ~/.zshrc
1. **History.** `HISTFILE=~/.zsh_history`, `HISTSIZE=10000`, `SAVEHIST=10000`, `setopt SHARE_HISTORY HIST_IGNORE_DUPS`.
2. **Completion.** `autoload -Uz compinit && compinit`.
3. The same PATH, Go, and Android blocks as `.bashrc`.
3. The same PATH, Go, and Android blocks as `.bashrc`, including `GHQ_ROOT` and `CODE_ROOT`.
4. Sources `~/.config/shell/aliases.sh`.
5. Sources every `~/.config/zsh/functions/*.zsh`.
6. Sources `~/.config/zsh/keybinds.zsh`.
7. `eval "$(suv init zsh)"` if suvadu is installed.
8. Sources `~/.config/envman/load.sh` if it is non-empty.
9. `eval "$(starship init zsh)"` if starship is installed.
10. **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.
11. **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.
10. `eval "$(tv init zsh)"` if `tv` is installed (<kbd>ctrl-t</kbd> smart autocomplete).
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. **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>.
## 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.
A generic widget `_zle-run` is registered as `run-<cmd>` for `purple`, `clip-last`, `gho`, `pj`, `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.
A generic widget `_zle-run` is registered as `run-<cmd>` for `purple`, `clip-last`, `gho`, `pj`, `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. `tv-git-files` runs `tv git-files` the same way, and `pj-down` handles <kbd>down</kbd>.
| Key | Action |
| --- | --- |
| <kbd>ctrl-e</kbd> | `yy` (yazi, cd to its exit dir) |
| <kbd>ctrl-e</kbd> | `tv git-files` (pick a file, open it in nvim) |
| <kbd>ctrl-f</kbd> | `yy` |
| <kbd>ctrl-b</kbd> | `purple` (an external command; this repo does not provide it) |
| <kbd>ctrl-y</kbd> | `clip-last` |
| <kbd>ctrl-o</kbd> | `gho` (repo or vault picker, cd) |
| <kbd>ctrl-p</kbd> | `pj` (task runner) |
| <kbd>ctrl-o</kbd> | `gho` (`ghq-repos` cable, cd) |
| <kbd>ctrl-p</kbd> | `pj` (`pj` cable) |
| <kbd>ctrl-t</kbd> | tv smart autocomplete (from `tv init zsh`) |
| <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` |
| <kbd>ctrl-g</kbd> | `lazygit` |
| <kbd>ctrl-q</kbd> | `exit` |
@@ -71,7 +74,27 @@ A generic widget `_zle-run` is registered as `run-<cmd>` for `purple`, `clip-las
<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`).
The fish-only binds <kbd>ctrl-w</kbd> (atuin AI), <kbd>down</kbd> (`pj`), 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 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
`keybinds.bash` gives bash the same picker keys as fish and zsh. bash keeps readline's default emacs keymap.
| Key | Action |
| --- | --- |
| <kbd>ctrl-o</kbd> | `gho` (`ghq-repos` cable, cd) |
| <kbd>ctrl-e</kbd> | `tv git-files` |
| <kbd>ctrl-p</kbd> | `pj` (`pj` cable) |
| <kbd>down</kbd> | `pj` cable on an empty prompt (`PJ_QUIET=1`), otherwise `history-search-forward` |
| <kbd>ctrl-t</kbd> | `_kb_tv_autocomplete`: runs `tv --autocomplete-prompt` on the text left of the cursor and inserts the picks there, each `printf %q`-quoted |
Each picker key is a readline macro `\C-xN \C-m \C-x0` built by `_kb_bind KEY SLOT COMMAND…`:
1. `\C-xN` is a `bind -x` for `_kb_run`, which stashes `READLINE_LINE` and runs the command in the shell process, so the `cd` done by `gho` persists.
2. `\C-m` accepts the now-empty line, so bash prints a fresh prompt for the new cwd. Nothing reaches history.
3. `\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` and rebinds `\C-x5` to `redraw-current-line`. The slots used are `\C-x0` through `\C-x5`.
## Shared aliases: aliases.sh
@@ -118,7 +141,7 @@ Each directory holds one function per file. bash and zsh source every file at st
| `clip-last` | Re-runs the previous history entry (`fc -ln -1 -1`, skipping itself if the line editor already recorded `clip-last`), pipes the output to `wl-copy` or `pbcopy`, and sends a `notify-send -a shell` notification if available |
| `ghg <repo>` | `ghg-clone "$@"`, cd into the printed path, `clear && onefetch` |
| `ghn <name>` | `command ghn "$@"`, cd into the printed path |
| `gho` | `command gho`, cd into the printed path, `clear`, and `onefetch` if it is a git repo |
| `gho` | `dir=$(tv ghq-repos)`, return if empty, cd into it, `clear`, and `onefetch` if it is a git repo |
| `yy [args]` | `yazi --cwd-file=<tmp>`, then cd to the directory written there if it changed |
The `ghg` helper script is named `ghg-clone` because an unrelated cargo binary already uses the name `ghg`. The [Scripts](/shell/scripts#why-some-helpers-are-shell-functions) page explains why these helpers are shell functions and not scripts.