Skip to main content

A small contribution to Nushell: when completion should also look at the description

Submitted by Lennart on

I recently made a small contribution to Nushell — the open source shell I use daily. It's a tiny improvement in how custom completers work, but it solves a specific friction I encountered every single time I had to use my own terminal tool. Here's what it's about.

The Problem

In Nushell, you can write your own "completers" — small functions that suggest values when you press tab. A completion has two fields: a value (what gets inserted) and a description (what is displayed next to it). For example, a list of airports:

def "nu-complete airports" [] {
    [
        { value: "LAX", description: "Los Angeles" }
        { value: "JFK", description: "New York — John F. Kennedy" }
        { value: "CPH", description: "Copenhagen" }
        { value: "LHR", description: "London Heathrow" }
        { value: "NRT", description: "Tokyo Narita" }
    ]
}

Until now, Nushell only matched against the value. If you typed LAX<tab>, you would get LAX — Los Angeles. Fine. But if you couldn't remember the IATA code and just typed Los<tab>, you got nothing. The description was decoration, not something that could be searched.

It's annoying when it's your own brain that can't remember that Narita is NRT but can easily remember it's Tokyo.

The Contribution

Custom completers can now choose to match the user's prefix against both value and description, by setting match_description: true in options:

def "nu-complete airports" [] {
    {
        options: {
            case_sensitive: false,
            completion_algorithm: "substring",
            match_description: true,
        },
        completions: [
            { value: "LAX", description: "Los Angeles" }
            { value: "JFK", description: "New York — John F. Kennedy" }
            { value: "CPH", description: "Copenhagen" }
            { value: "LHR", description: "London Heathrow" }
            { value: "NRT", description: "Tokyo Narita" }
        ]
    }
}

With match_description: true enabled, you can now type Tokyo<tab> and get NRT inserted. Or Copenhagen<tab> and get CPH. What's inserted is still always the value — the description is only for finding the right suggestion, not for ending up on the command line.

Why it's a Small Thing with a Big Impact

I came up with it myself because I have a send.nu command that sends emails to contacts from my address book. Previously, I had to remember the email addresses. Now, I can type the recipient's name, press tab, and get the address inserted:

def "nu-complete recipients" [] {
    {
        options: {
            case_sensitive: false,
            completion_algorithm: fuzzy,
            match_description: true,
        },
        completions: (contacts | each {|c| { value: $c.EMAIL, description: $c.FN }})
    }
}

The brain remembers people by name, not by email string. The shell should meet the brain where it is.

What I'm Taking Away

Two things.

First: open source is still the easiest way to get rid of a specific annoyance. The total time from "this should work" to "this works in everyone's installation in a few weeks" is manageable if the project is well-organized — and Nushell is.

Second: good tools meet you where you are. It's the same mindset I use when advising companies about AI. A system that requires the user to translate their own knowledge into the system's language (IATA codes, customer numbers, internal IDs) before it will help, is a system with a design flaw. Let the machine do the translation — it's better at it than we are.