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

x/tools/gopls: autocomplete keywords #34009

Closed
stamblerre opened this issue Sep 1, 2019 · 20 comments
Closed

x/tools/gopls: autocomplete keywords #34009

stamblerre opened this issue Sep 1, 2019 · 20 comments
Labels
FrozenDueToAge gopls Issues related to the Go language server, gopls. NeedsFix The path to resolution is known, but the work has not been done. Tools This label describes issues relating to any tools in the x/tools repository.

Comments

@stamblerre
Copy link
Contributor

They probably should be ranked even lower than builtins.

@stamblerre stamblerre added help wanted NeedsFix The path to resolution is known, but the work has not been done. gopls Issues related to the Go language server, gopls. labels Sep 1, 2019
@gopherbot gopherbot added this to the Unreleased milestone Sep 1, 2019
@sebsebmc
Copy link

sebsebmc commented Sep 4, 2019

Hi, I am interested in helping with this.

@stamblerre stamblerre added the Tools This label describes issues relating to any tools in the x/tools repository. label Sep 9, 2019
@stamblerre
Copy link
Contributor Author

@sebsebmc: Awesome! A good place to start looking is https://github.com/golang/tools/blob/master/internal/lsp/source/completion.go. Feel free to reach out to me here or on the Gophers Slack if you need help.

Some things to consider:

  1. How should keywords be ranked? My thinking is that they should probably be prioritized lower than lexical completions, but higher than deep completions (what do you think, @muirrn?).

  2. We should only suggest keywords if they are reasonable in the context of the code. For example, we shouldn't suggest continue outside of a loop.

@muirdm
Copy link

muirdm commented Sep 10, 2019

I think if we do a good job with 2), we can probably just start with the "stdScore" (assuming they are naturally ordered after lexical completions). My guess is typing "cont<>" and getting some weird fuzzy match instead of "continue" will be a more common annoyance than typing "cont<>" and getting "continue" instead of a lexical candidate.

One other thought: most keywords are statements, so we should also limit those to beginning of statements. For example:

for {
  breakDance := 123
  actuate(brea<>) // don't offer "break" completion because it isn't start of statement
}

@stamblerre
Copy link
Contributor Author

I actually just ran into the continue case a few minutes ago (VSCode offers keyword completions on its own, so they don't work at all with our rankings), but that's exactly why I mentioned the placement between standard and deep completions. I agree, there are enough cases where we can exclude keywords that the ranking system should be fine.

@sebsebmc
Copy link

Hi, I've been looking at completions.go already, I'll be sure to reach out with any questions.

@sebsebmc
Copy link

Would it be alright to submit my in-progress changes and then mark it as work-in-progress (is that the purpose of DNR) somehow?

@sebsebmc
Copy link

So currently I have reduced the keywords into roughly 2 categories: those that the parser thinks might be an identifier, and those that it doesn't. I've been focusing on the *ast.Ident case and trying to return keywords based on the context and erring on the side of not recommending a keyword.

Currently, the scoring is very rudimentary and there still needs to be some more work on restricting the keywords to beginnings of statements. This is partially being done right now by bailing on keyword suggestion if the path contains certain nodes like: FieldSet, ValueSpec, TypeSwitchExpr... These node types mean we are inside a statement where a keyword is unlikely to be valid.

For nodes that aren't being marked as an identifier, the parser is typically marking them as a *ast.BadDecl because it only wants valid declaration keywords at the file level scope. A partially typed keyword here is invalid and the token needs to be retrieved from the file to be able to prefix match on it.

I plan on submitting a patch for the first case once I have it flushed out a bit more and then based on that we can think about how to handle the second case. (Or if there's a different approach altogether that would be better)

@muirdm
Copy link

muirdm commented Sep 17, 2019

there still needs to be some more work on restricting the keywords to beginnings of statements.

Not tested, but to detect if we are at the start of a statement I would start by checking if path[0] or path[1] is an *ast.BlockStmt. Maybe you already tried something like that.

the parser is typically marking them as a *ast.BadDecl because it only wants valid declaration keywords at the file level scope

I assume you are talking about cases like this:

package foo
co<> // want to complete to "const".

I had a failed CL that might have helped with this case. The CL would replace the leaf BadDecl in path with an *ast.Ident to facilitate completion. If that helps you then maybe there is a case to reopen it. On the other hand, maybe not offering completions for file level keywords is fine.

@sebsebmc
Copy link

I'm not sure that the BadDecl needs to be replaced, it would likely be sufficient to be able to get the co in your example and match that against the keywords used in declarations (like var, const, etc.)

checking if path[0] or path[1] is an *ast.BlockStmt

The problem are nodes like CaseClause that don't have BlockStmt as bodies.

@gopherbot
Copy link

Change https://golang.org/cl/196664 mentions this issue: internal/lsp: add some keyword completions

@sebsebmc
Copy link

Additional work is needed to add support for keywords used in declarations at the file level. Currently any non-comment text results in *ast.BadDecl and so there is no text to match against to provide completions.

The keywords that we want to complete here are: package, import, const, var, type, and func. We can special case things like suggesting a package snippet if the document doesn't yet have an *ast.Package node, or we can fix the AST like suggested in #34332 (comment)

Note: I don't believe testing package statements is possible right now because of packagetest not running tests that don't contain a valid package statement.

Another issue is that inside of switch/select statements when there is no case or default statement the the parser expects a right brace so when typing the first case/default keyword users will not get completions.

gopherbot pushed a commit to golang/tools that referenced this issue Oct 22, 2019
For *ast.Ident completion requests, this checks the parent node to
see if the token begins a statement and then based on the path adds
possible keyword completion candidates. The test lists some cases where
this approach cannot provide completion candidates.

The biggest thing missing is keywords for file level declarations

Updates golang/go#34009

Change-Id: I9d9c0c1eb88e362613feca66d0eea6b88705b9b0
Reviewed-on: https://go-review.googlesource.com/c/tools/+/196664
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
@stamblerre
Copy link
Contributor Author

It seems that keyword completions are not being proposed in some of the expected cases. It will require a bit more investigation to see what's going on, but I just wanted to note that here.

@gopherbot
Copy link

Change https://golang.org/cl/217500 mentions this issue: internal/lsp/source: improve completions at file scope

gopherbot pushed a commit to golang/tools that referenced this issue Feb 20, 2020
Add support for var/func/const/type/import keywords at the file scope.
I left out "package" because, currently, if you are completing
something that means you must already have a package declaration. The
main hurdle was that anything other than a decl keyword shows up as
an *ast.BadDecl at the file scope. To properly detect the prefix we
manually scan for the token containing the position.

I also made a couple small drive-by improvements:
 - Also suggest "goto" and "type" keywords in functions.
 - Allow completing directly before a comment, e.g. "foo<>//". I
   needed this for a test that would have been annoying to write
   otherwise.

Updates golang/go#34009.

Change-Id: I290e7bdda9e66a16f996cdc291985a54bf375231
Reviewed-on: https://go-review.googlesource.com/c/tools/+/217500
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
@gopherbot
Copy link

Change https://golang.org/cl/220503 mentions this issue: internal/lsp/source: complete keywords as types

@gopherbot
Copy link

Change https://golang.org/cl/220504 mentions this issue: internal/lsp/source: support completing "range" keyword

@gopherbot
Copy link

Change https://golang.org/cl/220580 mentions this issue: internal/lsp/source: suppress keyword completions in "case" statement

@gopherbot
Copy link

Change https://golang.org/cl/220579 mentions this issue: internal/lsp/source: fix completion in empty switch statements

gopherbot pushed a commit to golang/tools that referenced this issue Mar 8, 2020
Offer "struct", "interface", "map", "chan", and "func" keywords when
we expect a type. For example "var foo i<>" will offer "interface".

Because "struct" and "interface" are more often used when declaring
named types, they get a higher score in type declarations. Otherwise,
"map", "chan" and "func" get a higher score.

I also got rid of the special keyword scoring. Now keywords just use
stdScore and highScore. This makes the interplay with other types of
candidates more predictable. Keywords are offered in pretty limited
contexts, so I don't think they will be annoying.

Finally, keyword candidate score is now to be scaled properly based on
how well they match the prefix. Previously they weren't penalized for
not matching well, so there were probably some situations where
keywords were ranked too high.

Updates golang/go#34009.

Change-Id: I0b659c00a8503cd72da28853dfe54fcb67f734ae
Reviewed-on: https://go-review.googlesource.com/c/tools/+/220503
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
gopherbot pushed a commit to golang/tools that referenced this issue Mar 8, 2020
We now offer "range" keyword in the for loop init statement:

for i := r<> // offer "range" completion candidate

Updates golang/go#34009.

Change-Id: I2e4c1db11c37127406c78191681c39b9dd3439f7
Reviewed-on: https://go-review.googlesource.com/c/tools/+/220504
Run-TryBot: Muir Manders <muir@mnd.rs>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
gopherbot pushed a commit to golang/tools that referenced this issue Mar 11, 2020
Now we properly offer "case" and "default" keyword completion in cases
like:

    switch {
      <>
    }

First I had to add an AST fix for empty switch statements to move the
closing brace down. For example, say the user is completing:

    switch {
    ca<>
    }

This gets parsed as:

    switch {
    }

Even if we manually recover the "ca" token, "<>" is not positioned
inside the switch statement anymore, so we don't know to offer "case"
and "default" candidates. To work around this, we move the closing
brace down one line yielding:

    switch {

    }

Second I had to add logic to manually extract the completion prefix
inside empty switch statements, and finally some logic to offer (only)
"case" and "default" candidates in empty switch statements.

Updates golang/go#34009.

Change-Id: I624f17da1c5e73faf91fe5f69e872d86f1cf5482
Reviewed-on: https://go-review.googlesource.com/c/tools/+/220579
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
gopherbot pushed a commit to golang/tools that referenced this issue Mar 11, 2020
Now we no longer offer keyword completions inside "case" statements:

    select {
    case fo<>: // don't offer "for"
    }

Updates golang/go#34009.

Change-Id: I908eda32af01fd1912fb587ce59efef1798b6741
Reviewed-on: https://go-review.googlesource.com/c/tools/+/220580
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
@muirdm
Copy link

muirdm commented Mar 11, 2020

@stamblerre do you have any other keyword TODOs or can this be closed? I'm ignoring "package" since that will probably be a can of worms.

@stamblerre
Copy link
Contributor Author

One issue I have noticed is this (internal/lsp/cache/view.go:679):

Screen Shot 2020-03-11 at 12 54 29 PM

@gopherbot
Copy link

Change https://golang.org/cl/223120 mentions this issue: internal/lsp/source: offer loop keyword completions in range stmt

@golang golang locked and limited conversation to collaborators Mar 12, 2021
@rsc rsc unassigned muirdm Jun 23, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge gopls Issues related to the Go language server, gopls. NeedsFix The path to resolution is known, but the work has not been done. Tools This label describes issues relating to any tools in the x/tools repository.
Projects
None yet
Development

No branches or pull requests

4 participants