keyboard-shortcut
d

Custom Shell Autocomplete

4 min read

The idea

Autocomplete is AWESOME. Ultimately it is just a function that suggests values. I typically put hard-coded values in first, then update with dynamic values once it is working!

Bash

Define a function (typically a private function with a leading underscore), then register which "command" should use this autocomplete.

_hello_complete() {
  COMPREPLY=( $(compgen -W "world everyone" -- "${COMP_WORDS[COMP_CWORD]}") )
}
complete -F _hello_complete hello

COMPREPLY is the array Bash displays as suggestions. compgen -W filters the words in its list using the current word: the argument currently being typed. COMP_CWORD tells us which item in COMP_WORDS that is, so completion also works when the command has more than one argument.

You can test this by adding the above to your ~/.bashrc file, then type hello <Tab>.

Zsh

Zsh has a different approach.

_hello() {
  _describe 'suggestions' '(world everyone)'
}
compdef _hello hello

_describe displays the supplied choices. suggestions is only a label for the completion group; it can be changed to anything meaningful.

You can test this by adding the above to your ~/.zshrc file, then type hello <Tab>.

Keywords

zsh provides helpers for common cases: _files completes file names, _directories completes directories, and _describe completes a list of named choices. Use compdef to connect a helper function to a command.

Dynamic suggestions

For dynamic suggestions, replace the static list with command output—just ensure it is safely quoted and fast enough to run on every Tab press.

Real world examples

"Notes" folder

notes() {
  local dir="$HOME/notes"

  if (( $# == 0 )); then
    "$EDITOR" "$dir"
  else
    local files=()
    for file in "$@"; do
      files+=("$dir/$file")
    done
    "$EDITOR" "${files[@]}"
  fi
}

_notes_completion() {
  _files -W "$HOME/notes"
}

compdef _notes_completion notes

The command opens the whole folder with no arguments, or selected notes when file names are supplied. _files -W makes Zsh suggest files from that folder.

Switching Git branches

Many developers use a short helper for switching branches. This Zsh version suggests the repository's local branches dynamically:

gco() {
  git switch "$@"
}

_gco() {
  local -a branches
  branches=("${(@f)$(git branch --format='%(refname:short)' --no-merged | awk '$1 != "main" && $1 != "develop"')}")
  _describe 'branches' branches
}
compdef _gco gco

In ${(@f)$(command)}, $(command) captures the command's output, f splits it at newlines, and @ preserves each line as a separate array item. The quotes keep branch names grouped. This is roughly zsh's equivalent of bash's mapfile or readarray.

Now gco <Tab> completes to show unmerged branch names (but excludes main and develop). Add more conditions to the awk expression to exclude other branches. 🚀