mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
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:
parent
ab6dbf99d1
commit
25e800de08
@ -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)
|
||||
|
105
internal/lsp/source/completion_keywords.go
Normal file
105
internal/lsp/source/completion_keywords.go
Normal 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
|
||||
}
|
74
internal/lsp/testdata/keywords/keywords.go
vendored
Normal file
74
internal/lsp/testdata/keywords/keywords.go
vendored
Normal 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")
|
@ -6,6 +6,6 @@ func _() {
|
||||
|
||||
switch interface{}(pear).(type) {
|
||||
case b: //@complete(":", basket, banana)
|
||||
b //@complete(" //", banana, basket)
|
||||
b //@complete(" //", banana, basket, break)
|
||||
}
|
||||
}
|
||||
|
2
internal/lsp/testdata/summary.txt.golden
vendored
2
internal/lsp/testdata/summary.txt.golden
vendored
@ -1,5 +1,5 @@
|
||||
-- summary --
|
||||
CompletionsCount = 185
|
||||
CompletionsCount = 209
|
||||
CompletionSnippetCount = 39
|
||||
UnimportedCompletionsCount = 1
|
||||
DeepCompletionsCount = 5
|
||||
|
Loading…
x
Reference in New Issue
Block a user