📢 Update
Nushell 0.116 reworked the Completion API. In the latest version, external completers now natively support alias resolution, so you no longer need to write your own logic to expand or complete aliases.
This post has been updated accordingly:
- Updated the Completion API configuration syntax;
- Removed the now-redundant section on alias handling.
Nushell is a modern shell built with Rust. Unlike Fish, which only supports UNIX platforms, Nushell runs across platforms, so you can use the same shell on different systems. It also has an active community.
Fish has a more mature completion ecosystem, though. For example, it natively supports completion for Claude Code, while Nushell community support for it arrived a little later.
The good news is that Nushell offers two approaches to completion, and they work well together:
- Custom Completions: hand-written completion scripts. See the Nushell community script repository.
- External Completers: bridges to external completion tools such as Fish, Carapace, and Zoxide.
This post focuses on the second approach: reusing Fish’s completion capabilities in Nushell.
Overall Architecture
In completions.nu, we split the completion system into two core modules:
- Bridge: connects to external completion systems (Fish / Carapace).
- Dispatcher: selects the most suitable completer for each command.
Step 1. Bridge Fish and Carapace
Fish is a mature shell with an extensive completion ecosystem. It also provides a complete --do-complete interface that lets us call its completion logic directly from the command line:# Call Fish's completion logic to get candidates for `git switch or`$ fish --command "complete --do-complete 'git switch or'"origin/HEAD Remote Branchorigin/dev Remote Branchorigin/main Remote Branch
Carapace is another general-purpose, cross-platform completion engine that provides a common interface for multiple shells. Call it with carapace <command> <shell> <args...> to get completion data directly in JSON format:# Call Carapace to get JSON completion output for `git switch or` in Nushell# Carapace outputs JSON on a single line by default; jq formats it here for readability$ carapace git nushell git switch or | jq[ { "value": "origin/head ", "display": "origin/head", "description": "remote branch" }, { "value": "origin/dev ", "display": "origin/dev", "description": "remote branch" }, { "value": "origin/main ", "display": "origin/main", "description": "remote branch" }]
With Nushell’s External Completer API, we can integrate these capabilities into Nushell:let fish_completer = {|place| fish --command $"complete '--do-complete=($place.command | str replace --all "'" "\\'" | str join ' ')'" | from tsv --flexible --noheaders --no-infer | rename value description | update value {|row| let value = $row.value let need_quote = ['\' ',' '[' ']' '(' ')' ' ' '\t' "'" '"' "`"] | any {$in in $value} if ($need_quote and ($value | path exists)) { let expanded_path = if ($value starts-with '~') {$value | path expand --no-symlink} else {$value} $'"($expanded_path | str replace --all "\"" "\\\"")"' } else {$value} }}let carapace_completer = {|place| CARAPACE_LENIENT=1 carapace $place.command.0 nushell ...$place.command | from json}
Step 2. Dispatch to a Completer
Completion quality varies by tool and completer:
- Some tools have the most complete support in Fish, such as
gitandbun. - Others are better served by Carapace.
So we use a dispatch strategy: send selected commands to Fish and everything else to Carapace.
With Nushell 0.116, the dispatcher can work directly with the command spans it receives, without any code to look up or expand aliases:let external_completer = {|place| match $place.command.0 { nu | tv | bun | git | rclone => $fish_completer _ => $carapace_completer } | do $in $place}
Step 3. Enable the External Completer
Finally, enable external completion in the Nushell configuration:$env.config.completions = { case_sensitive: false quick: true partial: true algorithm: "prefix" external: { enable: true completer: $external_completer } use_ls_colors: true}
You can also configure menu and keybindings for a smoother experience. See my configuration repository, Efterklang/dotfiles.
Demo
Once configured, type ssh and press Tab to automatically list remote hosts from ~/.ssh/config. Completion for git is fully available too:
