placeholderNushell Command Completion Guide

Nushell Command Completion Guide

Configure Carapace and Fish as Nushell external completers, with alias normalization and dispatcher logic for smarter CLI completion.

Nushell is a modern shell built with Rust. Compared with Fish, which is only available on UNIX platforms, Nushell is naturally cross-platform across Windows, Linux, and macOS. Sharing one configuration across systems is very convenient, and the community is active and iterates quickly.

But what is the cost? Fish has a more mature completion ecosystem. For example, it natively supports completion for Claude Code, while Nushell community support for it arrived slightly later.

I Want It All.webp

The good news is that in Nushell we can implement completion through two approaches, and they coexist perfectly:

  1. Custom Completions: hand-written completion scripts. See the Nushell community script repository.
  2. External Completers: bridge external completion tools such as Fish, Carapace, and Zoxide.

This post mainly introduces the second approach: reusing Fish’s completion capability inside Nushell.

Overall Architecture

In completions.nu, I split the completion system into three modules:

  1. Bridge: connects external completion systems, such as Fish and Carapace.
  2. Normalization: expands aliases back to real commands.
  3. Dispatcher: chooses the most suitable completer according to command type.

Step 1. Bridge Fish and Carapace

Many mature command-line tools already provide high-quality completions for other shells:

  • Fish’s complete --do-complete
  • Carapace’s cross-shell completion interface
$ fish --command "complete --do-complete 'git switch or'"origin/HEAD    Remote Branchorigin/dev     Remote Branchorigin/main    Remote Branch$ carapace git nushell git switch ''[{"value":"switch ","display":"switch","description":"Switch branches","style":{"fg":"blue"}}]

With Nushell’s External Completer API, we can integrate these capabilities into Nushell:

let fish_completer = {|spans|    fish --command $"complete '--do-complete=($spans | 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 = {|spans: list<string>|    CARAPACE_LENIENT=1 carapace $spans.0 nushell ...$spans | from json}

Step 2. Handle Aliases

Suppose you define:

alias g = git

Most completers do not recognize g, so Git completion will not trigger. We can query the current command with scope aliases; if it is an alias, expand it back to the real command before passing it to the completer.

For example, g switch will be converted to git switch, then completion runs. The concrete implementation is in the dispatcher in the next step.

Step 3. Dispatcher

Completion quality varies by tool and completer:

  • Some tools have the most complete completion in Fish, such as git and bun.
  • Other tools are better handled by Carapace.

So I use a dispatch strategy: specified commands go through Fish, while the rest go through Carapace.

The dispatcher below implements both Step 2 alias normalization and Step 3 dispatch logic:

let external_completer = {|spans|    let expanded_alias = scope aliases    | where name == $spans.0    | get -o 0.expansion    let spans = if $expanded_alias != null {        $spans        | skip 1        | prepend ($expanded_alias | split row ' ' | take 1)    } else {        $spans    }    match $spans.0 {        nu | tv | bun | git | rclone => $fish_completer        _ => $carapace_completer    } | do $in $spans}

Step 4. 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 further configure menu and keybindings for a smoother experience. See my configuration repository Efterklang/dotfiles.

Demo

After configuration, typing ssh and pressing Tab automatically lists remote hosts from ~/.ssh/config, and git completion is also fully available:

nu_completion
nu_completion