Skip to content

Commit 8c228dc

Browse files
author
Jonathan Turner
authored
Merge pull request rust-lang#2 from thegedge/add-signature-based-completions-rfc
Signature-based completions
2 parents b66cee4 + ac6c585 commit 8c228dc

File tree

1 file changed

+216
-0
lines changed

1 file changed

+216
-0
lines changed
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
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

Comments
 (0)