Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix completion for subcommands of builtins #6249

Merged
merged 1 commit into from Nov 4, 2019

Conversation

krobelus
Copy link
Member

@krobelus krobelus commented Oct 27, 2019

This fixes #2705 - broken subcommand completion for builtins, and #5415 - completions for function.

Back when the complete builtin was introduced, builtins like
builtin, exec, command, not, if, while, or and and could
be handled by the completion engine just like any other command.
Much has changed, now we are using a grammar that contains dedicated
AST nodes for those commands.

The completion engine works on plain_statement nodes and has some
special handling for builtins like exec and builtin. However,
__fish_complete_subcommand passes subcommand tokens to the completion
engine that are based on commandline -op, which includes the
builtins.

Example: type "command git " and hit tab. Git completion does not work.
What happens is that the completion scripts assume that the first
token on the commandline ("command") is the git executable, and the next
token ("git") is the subcommand. Since "command" is ignored, the git completion is called
with garbage input.

This change uses the information provided by the AST to make completion treat the builtins like any other command. In above example the completions for "command" will be called first.
They will remove the "command" token and then call into "git" completions.

Previous approach

6e733d9

This changes commandline -p to only return the plain_statement,
without any decorators to make examples like the above one work. If
anyone needs them to return decorators, then it may be preferable to
maintain compatibility instead. However, I think it makes sense to not
print decorators, consider for example:

false; and builtin A || B     # B is not executed!
       ^

commandline -p would print and builtin A, but in fish's grammar,
and applies to the entire expression builtin A || B.
With this change, it only prints A.

If needed I can make it so it does print the simple decorators
"exec", "command", "builtin" and "not".

Note that we keep completions like share/completions/builtin.fish
around, even though they are handled in C++, because a commandline
like "builtin " is parsed as plain statement
(while "builtin a" is recognised as decorated statement).


TODOs:

  • Tests have been added for regressions fixed
  • User-visible changes noted in CHANGELOG.md

@faho
Copy link
Member

faho commented Oct 27, 2019

I would expect this to be what you want for completions (because the decorator is irrelevant there) but not other uses of commandline -p like bindings.

I think a better solution would be to fake the commandline in completions, like we already do for complete -w. This is what the scripted completions tried to achieve, but the C++ was never cooperative here and I've not found the correct place to change.

@krobelus
Copy link
Member Author

OK I think it's fair to maintain that behavior of commandline -p, so I think I will change the behavior of completion to match that. The machinery to do so is already here - we have completions scripts for all prefixes like and, I just need to make the completion engine not skip over those nodes. It should definitely be doable, by using parse_util_process_extent and then handling the additional node types in completer_t.

@krobelus krobelus changed the title builtin commandline -p: don't print statement decorators (and, not) Fix completion for subcommands of builtins Oct 29, 2019
@krobelus
Copy link
Member Author

krobelus commented Oct 29, 2019

I have pushed a quite different approach and changed the description.
This one uses the information in the AST to extract builtins and complete them like normal commands.

I believe this works fine.

(I was thinking that instead of the AST we could just use the tokenizer here but then we'd probably have to add some hacks that reimplement parsing. for example to handle for <TAB>)

@krobelus krobelus force-pushed the complete-subcommands branch 2 times, most recently from 23f0bc4 to dbbb0a0 Compare October 30, 2019 09:50
@krobelus
Copy link
Member Author

This also makes completions for command and exec use external find, which is simpler to implement, but I could work around that if needed.

@zanchey
Copy link
Member

zanchey commented Oct 30, 2019

find is currently an optional ("preferred") dependency, for what that's worth.

Copy link
Member

@ridiculousfish ridiculousfish left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the idea here is to make completions forget that command, not, etc. are keywords and just pretend everything is a command. This is how fish used to work and in hindsight was better - conceptually simpler. I like this direction.

One downside of moving logic from C++ to script is that we lose autosuggestion support. For example I expect that and will now autosuggest arbitrary file paths instead of only executables. However this is probably acceptable and there's other ways to fix this.

src/complete.cpp Outdated
///
/// The returned nodes are string nodes (keywords for builtins like "if"
/// and "not" and the command string), and a number of argument nodes at the end.
static std::vector<const parse_node_t *> collect_tokens_of_command_at_location(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this is more complicated than necessary. Is all you hope to do collect the string-type nodes? You can do that with a stack, traversing without concern for the type:

std::vector<const parse_node_t *> result;
std::vector<const parse_node_t *> to_visit = {start};
while (! to_visit.empty()) {
           // Append to result if type is parse_token_type_string
           // Pop it off and push all of its children on
 } 

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use tnode_t::descendants() for this:

auto strings = statement.descendants<grammar::tok_string>();

This gives you all the string nodes under a given node (recursively).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I didn't realize the same can be done with one tnode_t::descendants().
But what I am doing now is even simpler, I'm just using the tokenizer because with this change the completion does not need to use the AST, it just needs the string tokens in a process. So this seems good enough for now.

share/functions/__fish_complete_commands.fish Outdated Show resolved Hide resolved
@krobelus krobelus force-pushed the complete-subcommands branch 3 times, most recently from 5cad77d to 4c6e6ca Compare November 4, 2019 06:42
@krobelus
Copy link
Member Author

krobelus commented Nov 4, 2019

One downside of moving logic from C++ to script is that we lose autosuggestion support. For example I expect that and will now autosuggest arbitrary file paths instead of only executables. However this is probably acceptable and there's other ways to fix this.

Yep, I added a small workaround for autosuggestion of builtins in completer_t::perform()

Presently the completion engine ignores builtins that are part of the
fish syntax. This can be a problem when completing a string that was
based on the output of `commandline -p`.  This changes completions to
treat these builtins like any other command.

This also disables generic (filename) completion inside comments and
after strings that do not tokenize.

Additionally, comments are stripped off the output of `commandline -p`.

Fixes fish-shell#5415
Fixes fish-shell#2705
@krobelus krobelus merged commit 6fb7f9b into fish-shell:master Nov 4, 2019
@krobelus krobelus deleted the complete-subcommands branch February 7, 2020 16:53
@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 7, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Tab completion doesn't work with an ; and
4 participants