internal/lsp: add some keyword completions

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>
This commit is contained in:
Sebastian Chlopecki 2019-09-20 15:01:33 -05:00 committed by Rebecca Stambler
parent ab6dbf99d1
commit 25e800de08
5 changed files with 184 additions and 2 deletions

View File

@ -515,6 +515,9 @@ func Completion(ctx context.Context, view View, f File, pos protocol.Position, o
if err := c.lexical(); err != nil {
return nil, nil, err
}
if err := c.keyword(); err != nil {
return nil, nil, err
}
// The function name hasn't been typed yet, but the parens are there:
// recv.‸(arg)

View File

@ -0,0 +1,105 @@
package source
import (
"go/ast"
"golang.org/x/tools/internal/lsp/protocol"
errors "golang.org/x/xerrors"
)
const (
BREAK = "break"
CASE = "case"
CHAN = "chan"
CONST = "const"
CONTINUE = "continue"
DEFAULT = "default"
DEFER = "defer"
ELSE = "else"
FALLTHROUGH = "fallthrough"
FOR = "for"
FUNC = "func"
GO = "go"
GOTO = "goto"
IF = "if"
IMPORT = "import"
INTERFACE = "interface"
MAP = "map"
PACKAGE = "package"
RANGE = "range"
RETURN = "return"
SELECT = "select"
STRUCT = "struct"
SWITCH = "switch"
TYPE = "type"
VAR = "var"
)
// keyword looks at the current scope of an *ast.Ident and recommends keywords
func (c *completer) keyword() error {
if _, ok := c.path[0].(*ast.Ident); !ok {
// TODO(golang/go#34009): Support keyword completion in any context
return errors.Errorf("keywords are currently only recommended for identifiers")
}
// Track which keywords we've already determined are in a valid scope
// Use score to order keywords by how close we are to where they are useful
valid := make(map[string]float64)
// only suggest keywords at the begnning of a statement
switch c.path[1].(type) {
case *ast.BlockStmt, *ast.CommClause, *ast.CaseClause, *ast.ExprStmt:
default:
return nil
}
// Filter out keywords depending on scope
// Skip the first one because we want to look at the enclosing scopes
for _, n := range c.path[1:] {
switch node := n.(type) {
case *ast.CaseClause:
// only recommend "fallthrough" and "break" within the bodies of a case clause
if c.pos > node.Colon {
valid[BREAK] = stdScore
// TODO: "fallthrough" is only valid in switch statements
valid[FALLTHROUGH] = stdScore
}
case *ast.CommClause:
if c.pos > node.Colon {
valid[BREAK] = stdScore
}
case *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.SwitchStmt:
valid[CASE] = stdScore + lowScore
valid[DEFAULT] = stdScore + lowScore
case *ast.ForStmt:
valid[BREAK] = stdScore
valid[CONTINUE] = stdScore
// This is a bit weak, functions allow for many keywords
case *ast.FuncDecl:
if node.Body != nil && c.pos > node.Body.Lbrace {
valid[DEFER] = stdScore - lowScore
valid[RETURN] = stdScore - lowScore
valid[FOR] = stdScore - lowScore
valid[GO] = stdScore - lowScore
valid[SWITCH] = stdScore - lowScore
valid[SELECT] = stdScore - lowScore
valid[IF] = stdScore - lowScore
valid[ELSE] = stdScore - lowScore
valid[VAR] = stdScore - lowScore
valid[CONST] = stdScore - lowScore
}
}
}
for ident, score := range valid {
if c.matcher.Score(ident) > 0 {
c.items = append(c.items, CompletionItem{
Label: ident,
Kind: protocol.KeywordCompletion,
InsertText: ident,
Score: score,
})
}
}
return nil
}

View File

@ -0,0 +1,74 @@
package keywords
func _() {
var test int
var tChan chan int
switch test {
case 1: // TODO: trying to complete case here will break because the parser wont return *ast.Ident
b //@complete(" //", break)
case 2:
f //@complete(" //", fallthrough, for)
r //@complete(" //", return)
d //@complete(" //", default, defer)
c //@complete(" //", case, const)
}
switch test.(type) {
case int:
b //@complete(" //", break)
case int32:
f //@complete(" //", fallthrough, for)
d //@complete(" //", default, defer)
r //@complete(" //", return)
c //@complete(" //", case, const)
}
select {
case <-tChan:
b //@complete(" //", break)
c //@complete(" //", case, const)
}
for index := 0; index < test; index++ {
c //@complete(" //", continue, const)
b //@complete(" //", break)
}
// Test function level keywords
//Using 2 characters to test because map output order is random
sw //@complete(" //", switch)
se //@complete(" //", select)
f //@complete(" //", for)
d //@complete(" //", defer)
g //@complete(" //", go)
r //@complete(" //", return)
i //@complete(" //", if)
e //@complete(" //", else)
v //@complete(" //", var)
c //@complete(" //", const)
}
/* package */ //@item(package, "package", "", "keyword")
/* import */ //@item(import, "import", "", "keyword")
/* func */ //@item(func, "func", "", "keyword")
/* type */ //@item(type, "type", "", "keyword")
/* var */ //@item(var, "var", "", "keyword")
/* const */ //@item(const, "const", "", "keyword")
/* break */ //@item(break, "break", "", "keyword")
/* default */ //@item(default, "default", "", "keyword")
/* case */ //@item(case, "case", "", "keyword")
/* defer */ //@item(defer, "defer", "", "keyword")
/* go */ //@item(go, "go", "", "keyword")
/* for */ //@item(for, "for", "", "keyword")
/* if */ //@item(if, "if", "", "keyword")
/* else */ //@item(else, "else", "", "keyword")
/* switch */ //@item(switch, "switch", "", "keyword")
/* select */ //@item(select, "select", "", "keyword")
/* fallthrough */ //@item(fallthrough, "fallthrough", "", "keyword")
/* continue */ //@item(continue, "continue", "", "keyword")
/* return */ //@item(return, "return", "", "keyword")
/* var */ //@item(var, "var", "", "keyword")
/* const */ //@item(const, "const", "", "keyword")

View File

@ -6,6 +6,6 @@ func _() {
switch interface{}(pear).(type) {
case b: //@complete(":", basket, banana)
b //@complete(" //", banana, basket)
b //@complete(" //", banana, basket, break)
}
}

View File

@ -1,5 +1,5 @@
-- summary --
CompletionsCount = 185
CompletionsCount = 209
CompletionSnippetCount = 39
UnimportedCompletionsCount = 1
DeepCompletionsCount = 5