|
| 1 | +- Feature Name: signature-based-completions |
| 2 | +- Start Date: 2020-07-25 |
| 3 | +- RFC PR: [nushell/rfcs#0002](https://github.com/nushell/rfcs/pull/2) |
| 4 | +- Nushell Issue: [nushell/nushell#0000](https://github.com/nushell/nushell/issues/0000) |
| 5 | + |
| 6 | +# Summary |
| 7 | + |
| 8 | +Add a completion engine to nushell that can determine how to complete an item at the cursor by |
| 9 | +parsing the line, extracting a signature, and using the information in that signature to determine |
| 10 | +what kind of thing needs to be completed. |
| 11 | + |
| 12 | +# Motivation |
| 13 | + |
| 14 | +We currently use rustyline's completer to do our completions. This completer is unfortunately |
| 15 | +limited, as it focuses on filenames only. We've tried to expand this with command names, but it |
| 16 | +scatters completion code all over the codebase. |
| 17 | + |
| 18 | +We already have incredibly rich information in signatures for commands, and intend to eventually use |
| 19 | +this to describe external commands. By building our completions around signatures, we can take |
| 20 | +advantage of highly contextual information to make nushell's completion system highly contextual, |
| 21 | +powerful, and delightful to use. |
| 22 | + |
| 23 | +# Guide-level explanation |
| 24 | + |
| 25 | +Completion is the act of responding to a user request to find a list of recommendations that fit |
| 26 | +some incomplete information that exists at their cursor. For example, if the user presses tab at the |
| 27 | +end of this line: |
| 28 | + |
| 29 | + > ls dir/ |
| 30 | + |
| 31 | +all path entries under the subdirectory `dir` could be offered as suggestions. Similarly, for |
| 32 | + |
| 33 | + > ls dir/a |
| 34 | + |
| 35 | +we'd expect all path entries under `dir` that start with `a`. It's also possible for the cursor to |
| 36 | +be in the middle of a line, too. For |
| 37 | + |
| 38 | + > ls di<TAB>/a |
| 39 | + |
| 40 | +Here we can show all directories whose name starts with "di". If the user chooses one of the |
| 41 | +suggestions, completion would complete up to the `/`. Note, there are alternative approaches to how |
| 42 | +completion could work here, but this RFC will assume the aforementioned, as it is the simplest. It |
| 43 | +should be easy to switch to a smarter completer in the future though. |
| 44 | + |
| 45 | +Note that this is different from "hinting", which looks at the line before the cursor and provides a |
| 46 | +suggestion for how to complete the rest of the line. The major difference is that hinting will |
| 47 | +replace the entire line, has less context, and is typically based on command history. This RFC does |
| 48 | +not address hinting. |
| 49 | + |
| 50 | +In all of the above examples, there's the concept of a "completion location". The completion |
| 51 | +location scopes the possible completions. Some examples of such locations, emphasized by angle |
| 52 | +brackets: |
| 53 | + |
| 54 | + - Command name: `<command> ...` |
| 55 | + - Flag name: `-<f>` and `--<flag>` |
| 56 | + - Flag value `-f <val>` and `--flag=<val>` |
| 57 | + - Positional arg: `command --flag -s <arg>` |
| 58 | + - Math operator: `= value <op> value` |
| 59 | + - Variable: `$<var>`, `$nu.<var>` |
| 60 | + - Column path: `thing.<path>` |
| 61 | + |
| 62 | +All of these are found in the context of a pipeline, which is just a series of commands connected by |
| 63 | +`|`. Pipelines can be found in many locations: |
| 64 | + |
| 65 | + - `pipeline ; pipeline` |
| 66 | + - `where { pipeline }` |
| 67 | + - `echo $(pipeline)` |
| 68 | + - `` `format string {{$(pipeline)}}` `` |
| 69 | + |
| 70 | +The completion engine should understand the context it is in, and this is easiest if we defer most |
| 71 | +of the work to the parser. The parser should be able to inform us about the location we're in. If |
| 72 | +it's the position for a value, it should even describe the shape needed for the value. For example, |
| 73 | +`where` takes one required positional argument, which has a "math" shape. The completion engine can |
| 74 | +then look for a completer or set of completers that understand how to complete a math shape, and |
| 75 | +collect the suggestions (if any). |
| 76 | + |
| 77 | +Being able to write a completer that only cares about a specific shape is a powerful concept. It |
| 78 | +takes away the need from writing completers that need to understand the entire line (although we can |
| 79 | +still allow them to request the full line). |
| 80 | + |
| 81 | +# Reference-level explanation |
| 82 | + |
| 83 | +The major technical portion of this PR will be to overhaul the parser, so that it can better hint at |
| 84 | +the shape under the cursor. The parser currently is two-phased: lite parse and full parse. The lite |
| 85 | +parse does the basics of pulling out some basic components on a line, but knows nothing about |
| 86 | +signatures. If it fails to parse, an error is returned. We need to extend this error with |
| 87 | +information about what was successfully parsed, because the error may have occurred beyond the |
| 88 | +cursor location. For example: |
| 89 | + |
| 90 | + > ls "some dir/ |
| 91 | + |
| 92 | +doesn't parse successfully, since the string is unterminated. That being said, the parser knows that |
| 93 | +it's attempting to parse a string, so it should be able to convey that to the caller so that they |
| 94 | +can take better action on this error state. |
| 95 | + |
| 96 | +After lite parse, we need to follow up with a full parse to figure out which shape is under the |
| 97 | +cursor. If this succeeds, we have a shape. If it errors, we provide no completion suggestions. |
| 98 | + |
| 99 | +The second technical aspect of this completion engine is to make it store a registry of completers. |
| 100 | +This registry would map completion locations and syntax shapes to a list of completers. For example, |
| 101 | +we may have a registry like this: |
| 102 | + |
| 103 | + | completion location | syntax shape | completers | |
| 104 | + |---------------------|--------------|------------| |
| 105 | + | command | string | filename, internal command, external command | |
| 106 | + | flag/arg value | string | filename | |
| 107 | + | flag/arg value | path | filename | |
| 108 | + | flag name | string | flag name | |
| 109 | + | variable | string | variable name | |
| 110 | + |
| 111 | +A completer would be described by the following trait: |
| 112 | + |
| 113 | +```rust |
| 114 | +trait Completer { |
| 115 | + fn complete(line: &str, span: Span, context: completion::Context) -> Vec<Suggestion>; |
| 116 | +} |
| 117 | +``` |
| 118 | + |
| 119 | +Concrete implementations are expected to take the given span for the line and provide some |
| 120 | +completions based on the partial data. The completion context will allow completers access to the |
| 121 | +nushell context, so they can discover variable names, environment variables, and so on. For example, |
| 122 | +a simple completer may look like this: |
| 123 | + |
| 124 | +```rust |
| 125 | +impl Completer for MyCompleter { |
| 126 | + fn complete(line: &str, span: Span, context: completion::Context) -> Vec<Suggestion> { |
| 127 | + if span.string(line) == "hello" { |
| 128 | + vec![Suggestion::from("hello world")] |
| 129 | + } else { |
| 130 | + Vec::new() |
| 131 | + } |
| 132 | + } |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +Completers don't have to worry about replacement, they simply say what the span should be replaced |
| 137 | +with. It's uncertain whether or not being able to replace a different span is necessary, so the |
| 138 | +completion engine will start with the assumption that it is unnecessary. |
| 139 | + |
| 140 | +The completion engine should deal with special needs of various shapes. For example, a string with |
| 141 | +spaces will need to be quoted. Completers should not have to worry about this, and leave it up to |
| 142 | +the engine. For example, when completing `hello` to `hello world`, like above, the completion engine |
| 143 | +would need to add the appropriate quoting when replacing the span. Continuing with this example, |
| 144 | + |
| 145 | + git co hello |
| 146 | + |
| 147 | +could be completed to |
| 148 | + |
| 149 | + git co "hello world" |
| 150 | + |
| 151 | +The span will never include quoting characters and other syntactical elements that aren't considered |
| 152 | +part of the value. Since completers have access to the full line, they can still consider those |
| 153 | +elements, but ideally completers never have to concern themselves with syntax. |
| 154 | + |
| 155 | +# Drawbacks |
| 156 | + |
| 157 | +I see no reason to not do this. Beyond the effort, this approach offers far too many advantages to |
| 158 | +completion than what we currently have, which doesn't feel idiomatic at all since it does not |
| 159 | +tightly integrate with nushell's structured descriptions of commands. |
| 160 | + |
| 161 | +# Rationale and alternatives |
| 162 | + |
| 163 | +The only real alternative would be to continue doing the line-based approach we currently do. This |
| 164 | +has a huge downside of reproducing a lot of the effort that has went into our parser in terms of |
| 165 | +finding out the position of the cursor in terms of flags, positional arguments, etc. |
| 166 | + |
| 167 | +# Prior art |
| 168 | + |
| 169 | +Fish is a shell known for a great completion system. Fish keeps things simple by offering a |
| 170 | +`complete` command that makes it very easy for someone to provide custom completions: |
| 171 | + |
| 172 | + complete -c myprog -s o -l output -a "yes no" |
| 173 | + |
| 174 | +This provides completions for the command `myprog`. It's pieces are: |
| 175 | + |
| 176 | + - `-s o`, a short flag named "o"; |
| 177 | + - `-l output`, a long flag named "output"; and |
| 178 | + - `-a "yes no"`, positional arguments having either a value of "yes" or "no". |
| 179 | + |
| 180 | +This is similar to what nushell offers, minus the fact that nushell expresses these things through |
| 181 | +signatures (note: we have yet to provide an external format for signatures). |
| 182 | + |
| 183 | +Things gets a little more challenging once we get into subcommands: |
| 184 | + |
| 185 | + complete -c myprog -n "__fish_seen_subcommand_from set-option" -a "(myprog list-options)" |
| 186 | + |
| 187 | +`-n` allows users to configure scripts to determine if the completion should be used. In this case, |
| 188 | +fish has a builtin to determine if a subcommand has been used. In the above example, if the user has |
| 189 | +typed `myprog set-option `, pressing tab will result in completion options being given based on |
| 190 | +whatever `myprog list-options` outputs. |
| 191 | + |
| 192 | +The beauty of a signature based approach in nushell is that subcommands are completely separate from |
| 193 | +their root, so nushell will take care of figuring this out for the user. Each subcommand will have |
| 194 | +its own signature. |
| 195 | + |
| 196 | +# Unresolved questions |
| 197 | + |
| 198 | +None that I can think of. |
| 199 | + |
| 200 | +# Future possibilities |
| 201 | + |
| 202 | +Completions are a huge part of shells, having the potential to provide a ton of productivity to the |
| 203 | +user if sufficiently powerful. The improvements described here will provide a lot of completion |
| 204 | +power to nushell users, but there's likely more room for improvement. |
| 205 | + |
| 206 | +One thing not covered here is how users could specialize a shape. For example, `git checkout <TAB>` |
| 207 | +would make sense to offer various refs - branch names, tags, remote refs, and so on - as completion |
| 208 | +items. We can't include completions for every possible thing, so it would make sense for this to be |
| 209 | +expressed externally somehow. At minimum, any implementation for this PR should keep this in mind. |
| 210 | +The idea of a registry that maps locations/shapes to completers seems to be sufficient to satisfy |
| 211 | +that requirement. |
| 212 | + |
| 213 | +We're also not worried about more intelligent completions. Future work could look at information to |
| 214 | +the left and to the right of the cursor to give even better completions. This RFC is more about |
| 215 | +putting the right foundation in place with a simplistic completion engine, and following up with |
| 216 | +improvements to it. |
0 commit comments