Skip to content

Instantly share code, notes, and snippets.

@dbluhm
Created April 23, 2025 19:59
Show Gist options
  • Save dbluhm/823c11e107dc7a6267e76eef15399fcf to your computer and use it in GitHub Desktop.
Save dbluhm/823c11e107dc7a6267e76eef15399fcf to your computer and use it in GitHub Desktop.
zk zsh completion

zk zsh Completion

This gist contains a zsh completion script for the zk (Zettelkasten) CLI, plus instructions for installation and customization.


Installation

  1. Create a completion directory (if you haven’t already):

    mkdir -p ~/.zsh/completion
  2. Copy the completion file (_zk) into that directory:

    cp _zk ~/.zsh/completion/
  3. Add the directory to your $fpath and initialize completions in your ~/.zshrc:

    # ~/.zshrc
    fpath=(~/.zsh/completion $fpath)
    autoload -U compinit
    compinit
  4. Reload your shell or source your rc file:

    source ~/.zshrc
  5. Test!

    zk <TAB>         # should list commands & aliases
    zk new -<TAB>    # should show new-note flags
    zk list --by-<TAB> # should complete "--by-tag"

Usage

  • Commands & subcommands will auto-complete after zk and zk <command> .
  • Flags are available contextually per subcommand.
  • Your custom zk aliases (defined in ~/.zk/config.toml) are also registered for tab-completion.

Writing Your Own Completions for Custom Aliases

If you define extra aliases in your .zk/config.toml (e.g. ls = "zk list --quiet $@"), you can teach zsh to autocomplete them:

  1. Open the _zk file in ~/.zsh/completion/.
  2. Add your alias name to the commands=(...) array at the top, with a short description:
    local -a commands
    commands=(
      'init:Create a new notebook'
      'myalias:My custom alias for listing starred notes'
      # … other entries …
    )
  3. Dispatch flags under the case $words[2] in … esac section. For example, if myalias should accept the same flags as list:
    (myalias|list)
      _arguments \
        '-q[Quiet: no total count]' \
        '-t=[Tags to filter]:tag' \
        '-n=[Limit results]:limit'
      ;;
  4. Reload your completions:
    autoload -U compinit
    compinit
  5. Verify that zk myalias -<TAB> offers the intended flags and options.

Tips

  • Use _describe, _values, or compadd to supply dynamic lists (e.g., tags from zk tags).
  • Fallback to _files to complete file/directory arguments.
  • Consult Zsh Completion System Documentation for advanced patterns.

Troubleshooting

  • No completions? Verify that ~/.zsh/completion is in $fpath before any other entries.
  • Stale completions? Run autoload -U compinit; compinit again or restart your terminal.
  • Syntax errors? Open _zk in an editor with zsh syntax highlighting to locate typos.
#compdef zk
# Zettelkasten (zk) zsh completion file
# This includes my aliases but you can follow the same pattern to add your own.
# Primary commands and aliases
local -a commands
commands=(
'init:Create a new notebook in the given directory'
'index:Index the notes to be searchable'
'new:Create a new note in the given notebook directory'
'list:List notes matching the given criteria'
'graph:Produce a graph of the notes matching the given criteria'
'edit:Edit notes matching the given criteria'
'tag:Manage the note tags'
# zk aliases
'ls:Alias for zk list -t "NOT comment, NOT daily"'
'ed:Alias for zk edit -t "NOT comment, NOT daily" --interactive'
'n:Alias for zk new'
'editlast:Alias for editing the last modified note'
'recent:Alias for editing recent notes interactively'
'hist:Alias for viewing git history of notes'
'conf:Alias for editing the zk config file'
'inline:Alias for listing inline paths'
'daily:Alias for creating a daily note'
'past:Alias for editing past daily notes'
'last:Alias for editing the last daily note'
)
# Global flags available for all commands
local -a global_opts
global_opts=(
'-h[Show context-sensitive help]'
'--help[Show context-sensitive help]'
'--notebook-dir=[Notebook directory]:dir:_files'
'-W[Run as if zk was started in <PATH> instead of cwd]:dir:_files'
'--working-dir=[Run as if zk was started in <PATH> instead of cwd]:dir:_files'
'--no-input[Never prompt or ask for confirmation]'
)
# Main entry
_arguments \
$global_opts \
'1:command:->commands' \
'*::args:->args'
# Dispatch based on selected command
case $state in
commands)
_describe 'zk commands' commands
;;
args)
case $words[2] in
(init)
_arguments \
'[1:directory:directory:_files]'
;;
(index)
_arguments \
'-f[Force indexing all notes]' \
'-v[Print detailed information]' \
'-q[Do not print statistics nor progress]'
;;
(new)
_arguments \
'-i[Read contents from stdin]' \
'-t=[Title of the new note]:title' \
'--title=[Title of the new note]:title' \
'--date=[Set the current date]:date' \
'-g=[Name of the config group]:group' \
'--group=[Name of the config group]:group' \
'--extra=[Extra variables passed to templates]:key=val' \
'--template=[Custom template path]:file:_files' \
'-p[Print the path of the created note]' \
'-n[Dry run: print content to stdout]' \
'--id=[Use provided id]:id' \
'[1:directory:directory:_files]'
;;
(list|ls)
_arguments \
'[1:paths...:path:_files]' \
'-f=[Format]:format:(oneline short medium long full json jsonl)' \
'--format=[Format]:format:(oneline short medium long full json jsonl)' \
'--header=[Text at start of list]:header' \
'--footer=[Text at end of list]:footer' \
'-d=[Separator]:delimiter' \
'-0[Use ASCII NUL as delimiter]' \
'-P[Do not pipe output into a pager]' \
'-q[Quiet: no total count]' \
'-i[Select interactively with fzf]' \
'-n=[Limit number of notes]:limit' \
'-m=[Search terms]:match' \
'-M=[Matching strategy]:strategy:(fts re exact)' \
'-x=[Exclude paths]:exclude' \
'-t=[Tags to filter]:tag' \
'--mention=[Mention filter]:mention' \
'--mentioned-by=[Mentioned-by filter]:mentioned-by' \
'-l=[Link-to filter]:link-to' \
'--no-link-to=[No link-to filter]:no-link-to' \
'-L=[Linked-by filter]:linked-by' \
'--no-linked-by=[No linked-by filter]:no-linked-by' \
'--orphan[Find notes with no incoming links]' \
'--tagless[Find notes with no tags]' \
'--related=[Find related notes]:related' \
'--max-distance=[Max link distance]:distance' \
'-r[Recursive link traversal]' \
'--created=[Created on date]:created' \
'--created-before=[Created before date]:created-before' \
'--created-after=[Created after date]:created-after' \
'--modified=[Modified on date]:modified' \
'--modified-before=[Modified before date]:modified-before' \
'--modified-after=[Modified after date]:modified-after' \
'-s=[Sort by criterion]:sort'
;;
(graph)
_arguments \
'-f=[Format of graph]:format:(json)' \
'--format=[Format of graph]:format:(json)' \
'-q[Quiet: no total count]' \
'-i[Select interactively with fzf]' \
'-n=[Limit number of notes]:limit' \
'-m=[Search terms]:match' \
'-M=[Matching strategy]:strategy:(fts re exact)' \
'-x=[Exclude paths]:exclude' \
'-t=[Tags to filter]:tag' \
'--mention=[Mention filter]:mention' \
'--mentioned-by=[Mentioned-by filter]:mentioned-by' \
'-l=[Link-to filter]:link-to' \
'--no-link-to=[No link-to filter]:no-link-to' \
'-L=[Linked-by filter]:linked-by' \
'--no-linked-by=[No linked-by filter]:no-linked-by' \
'--orphan' \
'--tagless' \
'--related=[Find related notes]:related' \
'--max-distance=[Max link distance]:distance' \
'-r' \
'--created' \
'--created-before' \
'--created-after' \
'--modified' \
'--modified-before' \
'--modified-after' \
'-s=[Sort by criterion]:sort' \
'[2:paths...:path:_files]'
;;
(edit|ed)
_arguments \
'[1:paths...:path:_files]' \
'-f[Force: no confirmation]' \
'-i[Select interactively with fzf]' \
'-n=[Limit number of notes]:limit' \
'-m=[Search terms]:match' \
'-M=[Matching strategy]:strategy:(fts re exact)' \
'-x=[Exclude paths]:exclude' \
'-t=[Tags to filter]:tag' \
'--mention=[Mention filter]:mention' \
'--mentioned-by=[Mentioned-by filter]:mentioned-by' \
'-l=[Link-to filter]:link-to' \
'--no-link-to=[No link-to filter]:no-link-to' \
'-L=[Linked-by filter]:linked-by' \
'--no-linked-by=[No linked-by filter]:no-linked-by' \
'--orphan' \
'--tagless' \
'--related=[Find related notes]:related' \
'--max-distance=[Max link distance]:distance' \
'-r[Recursive link traversal]' \
'--created' \
'--created-before' \
'--created-after' \
'--modified' \
'--modified-before' \
'--modified-after' \
'-s=[Sort by criterion]:sort'
;;
(tag)
_arguments '1:tag subcommand:->tagcmds'
;;
esac
;;
tagcmds)
local -a tagcmds
tagcmds=( 'list:List all the note tags' )
_describe 'zk tag commands' tagcmds
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment