Nushell Auto-Completion

Nushell knows three ways to extend its auto-completion.

Natively, pressing tab gives you file path auto-completion. This serves as the fallback when no other completers are used.

Custom Completions

Custom Completions documents how you can extend a command argument with completion data with @:

> def animals [] { ["cat", "dog", "eel" ] }
> def my-command [animal: string@animals] { print $animal }
>| my-command
cat                 dog                 eel

Beyond static lists, it can also offer completions according to context.

They can also be used on extern commands (external commands) to integrate non-nushell commands into the shell with auto-completions.

Completions can be simple value lists, like in other shells, or include a description to values, for example to include descriptions you would otherwise have to look up in --help argument and flag documentation.

External Completions

External Completions is intended for calling external completers to integrate them.

Integrating external completers can be useful for integrating completion frameworks as well as programs that offer their own completions.

For example, dotnet and winget offer their own completion interfaces with dotnet complete and winget complete.

You can also integrate other shells completions that may already have numerous command auto-completions defined like Fish shell, or you can integrate tools that specifically exist for generalized completion.

Concrete Completions and Caveats

Given that dotnet and winget natively offer completion it makes sense to integrate them with external completions, right?

Well, turns out they only return value lists. Which works, and works like in other shells, but misses out on Nushells capabilities of describing options.

Which is why the nu_scripts repository (official community utils and resources repository) includes Custom Completions for winget and for dotnet.

The first is a custom implementation whereas the second is generated from existing fish shell definitions.

nu ❯ | dotnet
dotnet run           Run the application from source
dotnet sln           Modify Visual Studio solution files
dotnet add           Add a package/reference
dotnet new           Create a new .NET project
[…]

Adding External Completions

I struggled quite a bit trying to create external completions integration. But eventually I got it to work and understand it, how the passed spans differ between tab on last argument or spaced-after last argument, and how it cascades into further closures.

I also found what I think is a bug and crashes the process: External Completer Call Can Panic

config-external-completers.nu:

# https://www.nushell.sh/book/custom_completions.html#external-completions
# https://www.nushell.sh/cookbook/external_completers.html#carapace-completer

let multiple_completers = {|spans|
    match $spans.0 {
        # dotnet - nu_scripts has a completion which has item descriptions
        # dotnet https://learn.microsoft.com/en-us/dotnet/core/tools/enable-tab-autocomplete#nushell
        # `dotnet complete`: arg: args[1..], result: lines
        #dotnet => {|| dotnet complete ($spans | skip 1 | str join " ") | lines }
        dotnet => { dotnet complete ($spans | skip 1 | str join " ") | lines }

        # winget - nu_scripts has a completion which has item descriptions
        # winget https://learn.microsoft.com/en-us/windows/package-manager/winget/tab-completion
        # winget https://github.com/microsoft/winget-cli/blob/master/doc/Completion.md
        # winget - nushell has `commandline get-cursor` we could use, but word identification would be missing -> inconsistency breakage
        winget => { winget complete --commandline ($spans | str join " ") --word ($spans | last) --position 999 | lines }
        #winget => {|| dotnet-suggest get --executable winget -- ($spans | skip 1 | str join " ") | lines | skip 1 }

        # dotnet System.CommandLine https://learn.microsoft.com/en-us/dotnet/standard/commandline/tab-completion
        #_ => { dotnet-suggest get --executable ($spans | first) -- ($spans | skip 1 | str join " ") }

        #aa => {|| [{value: ($in | length), description:'in count'}] }
        #aa => {|| [{value: ($in), description:'in content'}] }
        ##aa => {|| [{value: ($spans | length), description:'spans count'}] }
        ##aa => {|| [{value: ($spans | str join "_" | collect), description:'spans joined'} {value:'second'}] }
        #aa => {|| $in | str join "," | {|span| {value:$spans} } }
        #aa => {|| $in | each {|span| {value:$spans} } }
        #a => { $spans | skip 1 | each {|span| {value: $span, description:'ye'} } }
        #_ => {|spans| [{value:'default'}] }
    }
}

$env.config.completions.external.completer = $multiple_completers