Zsh/Fish fuzzy completion and utility plugin with Deno.
- Insert snippet and abbrev snippet
- Completion with fzf
- Builtin git completion
- User defined completion
- ZLE utilities
- Persistent preprompt prefix (placeholder jump)
- Smart History Selection (global / repository / directory / session scopes, delete, export/import)
zinit ice lucid depth"1" blockf
zinit light yuki-yano/zeno.zsh$ git clone https://github.com/yuki-yano/zeno.zsh.git
$ echo "source /path/to/dir/zeno.zsh" >> ~/.zshrcFish support is experimental. A quick overview is included here; full installation and configuration details are available later in the document.
Require user configuration file
$ gs<Space>
Insert
$ git status --short --branch$ gs<Enter>
Execute
$ git status --short --branch$ git add <Tab>
Git Add Files> ...Use zeno-insert-snippet zle
- Bind a key to
zeno-preprompt(example:bindkey '^xp' zeno-preprompt). - Invoke it with a non-empty buffer to save that text as the next prompt prefix (a space is auto-appended).
- Start typing on the next line with the prefix already inserted; call the widget again with an empty buffer to reset.
- You can embed
{{placeholder}}in the prefix; the first one is replaced and the cursor starts there. Use the existingnext-placeholderwidget to jump through the remaining placeholders. - Use
zeno-preprompt-snippetto pick a configured snippet (via fzf or by passing a snippet name as an argument) and set it as the next prompt prefix.
- Press
Ctrl-Rto open the classic History widget. - Fzf searches the command column; press Enter to paste the selected command into the prompt immediately.
- The classic picker operates directly on your shell history (
HISTFILE) and does not depend on the SQLite subsystem.
- Press
Ctrl-Rto open the Smart History Search widget; press it again to cycle the scope in the orderglobal → repository → directory → session. - Search the command column (fzf ignores the time column), and press Enter to paste the raw command into your prompt.
- Press
Ctrl-Dfor a soft delete (logical delete) orAlt-Dfor a hard delete that also edits yourHISTFILE. - Use
zeno history query|log|delete|export|importto work directly with the SQLite-backed history from scripts. - Configure
history.fzf_command/history.fzf_optionsin your YAML or TypeScript config to override the fzf (or fzf-tmux) command and its options.
Use zeno-ghq-cd zle
zeno loads configuration files from the project and user config directories and
merges them in priority order. If the current workspace has a .zeno/
directory, its contents are loaded first, followed by the user config directory
($ZENO_HOME or ~/.config/zeno/), and finally any XDG config directories.
Within each location, files are merged alphabetically. Both YAML (*.yml,
*.yaml) and TypeScript (*.ts) files are supported, so you can pick the
format that suits your workflow. TypeScript configs can import defineConfig
and types from jsr:@yuki-yano/zeno, giving you access to the full
ConfigContext for dynamic setups.
# if defined load the configuration file from there
# export ZENO_HOME=~/.config/zeno
# if disable deno cache command when plugin loaded
# export ZENO_DISABLE_EXECUTE_CACHE_COMMAND=1
# if enable fzf-tmux
# export ZENO_ENABLE_FZF_TMUX=1
# if setting fzf-tmux options
# export ZENO_FZF_TMUX_OPTIONS="-p"
# by default a unix domain socket is used
# if disable it
# export ZENO_DISABLE_SOCK=1
# if disable builtin completion
# export ZENO_DISABLE_BUILTIN_COMPLETION=1
# default
export ZENO_GIT_CAT="cat"
# git file preview with color
# export ZENO_GIT_CAT="bat --color=always"
# default
export ZENO_GIT_TREE="tree"
# git folder preview with color
# export ZENO_GIT_TREE="eza --tree"
if [[ -n $ZENO_LOADED ]]; then
bindkey ' ' zeno-auto-snippet
# fallback if snippet not matched (default: self-insert)
# export ZENO_AUTO_SNIPPET_FALLBACK=self-insert
# if you use zsh's incremental search
# bindkey -M isearch ' ' self-insert
bindkey '^m' zeno-auto-snippet-and-accept-line
bindkey '^i' zeno-completion
bindkey '^xx' zeno-insert-snippet # open snippet picker (fzf) and insert at cursor
bindkey '^x ' zeno-insert-space
bindkey '^x^m' accept-line
bindkey '^x^z' zeno-toggle-auto-snippet
# preprompt bindings
bindkey '^xp' zeno-preprompt
bindkey '^xs' zeno-preprompt-snippet
# Outside ZLE you can run `zeno-preprompt git {{cmd}}` or `zeno-preprompt-snippet foo`
# to set the next prompt prefix; invoking them with an empty argument resets the state.
bindkey '^r' zeno-history-selection # classic history widget
# bindkey '^r' zeno-smart-history-selection # smart history widget
# fallback if completion not matched
# (default: fzf-completion if exists; otherwise expand-or-complete)
# export ZENO_COMPLETION_FALLBACK=expand-or-complete
fi- git
- add
- diff
- diff file
- checkout
- checkout file
- switch
- reset
- reset file
- restore
- fixup and squash commit
- rebase
- merge
See: src/completion/source/git.ts
The configuration files are discovered and merged in the following order.
- If the detected project root contains a
.zeno/directory, load all.zeno/*.yml/*.yaml/*.ts(A→Z). - If
$ZENO_HOMEis a directory, merge all*.yml/*.yaml/*.tsdirectly under it. - For each path in
$XDG_CONFIG_DIRS, ifzeno/exists, merge allzeno/*.yml/*.yaml/*.ts(directories are processed in the order provided by XDG). - Fallbacks for backward compatibility (used only when no files were found in
the locations above):
$ZENO_HOME/config.yml$XDG_CONFIG_HOME/zeno/config.ymlor~/.config/zeno/config.yml- Find
.../zeno/config.ymlfrom each in$XDG_CONFIG_DIRS
$ touch ~/.config/zeno/config.ymland
snippets:
# snippet and keyword abbrev
- name: git status
keyword: gs
snippet: git status --short --branch
# snippet with placeholder
- name: git commit message
keyword: gcim
snippet: git commit -m '{{commit_message}}'
- name: "null"
keyword: "null"
snippet: ">/dev/null 2>&1"
# auto expand condition
# If not defined, it is only valid at the beginning of a line.
context:
# buffer: ''
lbuffer: '.+\s'
# rbuffer: ''
- name: branch
keyword: B
snippet: git symbolic-ref --short HEAD
context:
lbuffer: '^git\s+checkout\s+'
evaluate: true # eval snippet
completions:
# simple sourceCommand, no callback
- name: kill signal
patterns:
- "^kill -s $"
sourceCommand: "kill -l | tr ' ' '\\n'"
options:
--prompt: "'Kill Signal> '"
# use excludePatterns and callback
- name: kill pid
patterns:
- "^kill( .*)? $"
excludePatterns:
# -l, -n or -s is followed by SIGNAL instead of PID
- " -[lns] $"
sourceCommand: "LANG=C ps -ef | sed 1d"
options:
--multi: true
--prompt: "'Kill Process> '"
callback: "awk '{print $2}'"
# Use null (\0) termination Input / Output
- name: chdir
patterns:
- "^cd $"
sourceCommand: "find . -path '*/.git' -prune -o -maxdepth 5 -type d -print0"
options:
# Added --read0 if null termination is used in `sourceCommand` output.
--read0: true
--prompt: "'Chdir> '"
--preview: "cd {} && ls -a | sed '/^[.]*$/d'"
callback: "cut -z -c 3-"
callbackZero: true # null termination is used in `callback` I/OTypeScript config files can also replace sourceCommand with a sourceFunction
that returns completion candidates programmatically. The function receives the
same ConfigContext as defineConfig and may return a ReadonlyArray<string>
or a Promise of it. Only one of sourceCommand or sourceFunction can be
specified for a completion.
TypeScript configs can be split into multiple files. Each file returns a partial
Settings object and zeno merges them in alphabetical order.
// ~/.config/zeno/10-snippets.ts
import { defineConfig } from "jsr:@yuki-yano/zeno";
export default defineConfig(({ projectRoot, currentDirectory }) => ({
snippets: [
{
name: "git status",
keyword: "gs",
snippet: "git status --short --branch",
},
{
name: "branch",
keyword: "B",
snippet: "git symbolic-ref --short HEAD",
context: { lbuffer: "^git\\s+checkout\\s+" },
evaluate: true,
},
{
name: "null",
keyword: "null",
snippet: ">/dev/null 2>&1",
context: { lbuffer: ".+\\s" },
},
],
}));// ~/.config/zeno/20-completions.ts
import { defineConfig } from "jsr:@yuki-yano/zeno";
import { join } from "jsr:@std/path@^1.0.0/join";
export default defineConfig(({ projectRoot, currentDirectory }) => ({
completions: [
{
name: "kill signal",
patterns: ["^kill -s $"],
sourceCommand: "kill -l | tr ' ' '\\n'",
options: { "--prompt": "'Kill Signal> '" },
},
{
name: "kill pid",
patterns: ["^kill( .*)? $"],
excludePatterns: [" -[lns] $"],
sourceCommand: "LANG=C ps -ef | sed 1d",
options: { "--multi": true, "--prompt": "'Kill Process> '" },
callback: "awk '{print $2}'",
},
{
name: "chdir",
patterns: ["^cd $"],
sourceCommand:
"find . -path '*/.git' -prune -o -maxdepth 5 -type d -print0",
options: {
"--read0": true,
"--prompt": "'Chdir> '",
"--preview": "cd {} && ls -a | sed '/^[.]*$/d'",
},
callback: "cut -z -c 3-",
callbackZero: true,
},
{
name: "npm scripts",
patterns: ["^npm run $"],
sourceFunction: async ({ projectRoot }) => {
try {
const pkgPath = join(projectRoot, "package.json");
const pkg = JSON.parse(
await Deno.readTextFile(pkgPath),
) as { scripts?: Record<string, unknown> };
return Object.keys(pkg.scripts ?? {});
} catch {
return [];
}
},
options: { "--prompt": "'npm scripts> '" },
callback: "npm run {{}}",
},
],
}));Configure the zeno-smart-history-selection and the zeno history … CLI—via a history block in your zeno.config.yml:
history:
defaultScope: global # "global" | "repository" | "directory" | "session"
fzfCommand: fzf-tmux # Override the command that spawns the picker
fzfOptions:
- "-p 50%,50%" # Additional arguments passed to the picker command
redact: [] # Strings to hide from the History view
keymap:
deleteSoft: ctrl-d # Soft delete (logical delete)
deleteHard: alt-d # Hard delete (edits HISTFILE)
toggleScope: ctrl-r # Cycle through scopes within the widget
togglePreview: ? # Toggle the preview windowfisher install yuki-yano/zeno.zshFisher will automatically clone the full repository to ~/.local/share/zeno.zsh
and set up the necessary paths.
git clone https://github.com/yuki-yano/zeno.zsh.git /path/to/zeno.zsh
echo "set -gx ZENO_ROOT /path/to/zeno.zsh" >> ~/.config/fish/config.fish
ln -s /path/to/zeno.zsh/shells/fish/conf.d/zeno.fish ~/.config/fish/conf.d/Note: Setting ZENO_ROOT explicitly is recommended for manual installations to
avoid path resolution issues.
Copy the example key bindings:
cp /path/to/zeno.zsh/shells/fish/conf.d/zeno-keybindings.fish.example ~/.config/fish/conf.d/zeno-keybindings.fishOr manually set up key bindings in your config.fish:
if test "$ZENO_LOADED" = "1"
bind ' ' zeno-auto-snippet
bind \r zeno-auto-snippet-and-accept-line
bind \t zeno-completion
bind \cx\x20 zeno-insert-space
end- Abbrev snippet expansion - Expand abbreviations with Space key
- Auto snippet and accept line - Expand and execute with Enter key
- Fuzzy completion - Tab completion with fzf
- Insert literal space - Insert space without expansion (Ctrl-X Space)
- Snippet placeholder navigation - Navigate through snippet placeholders
- History selection - Fuzzy search command history (Ctrl-R)
- GHQ repository navigation - Navigate to ghq-managed repositories
- Insert snippet - Select and insert snippets from list
- Toggle auto snippet - Enable/disable automatic snippet expansion
- Socket mode is enabled by default (disable with ZENO_DISABLE_SOCK=1)
- Key binding syntax differs from Zsh
Q: zsh-syntax-highlighting does not work well.
A: Use fast-syntax-highlighting instead.
- Preprompt feature inspired by by-binds-yourself