1
0
mirror of https://github.com/golang/go synced 2024-10-01 05:18:33 -06:00
go/internal/lsp/source/completion_keywords.go
Heschi Kreinick 3f7dfa39cf internal/lsp: sort by label after score
I want to stop sorting unimported completions. We still want to show
users something reasonable, so use label as a tiebreaker for score in
the higher level completion function.

To maintain the current sorting, we need to adjust scores by search
depth (height?) for lexical completions. A few tests are really ties,
and need sorting in the test case.

Change-Id: Ie2d09fdcbebf6fda4ab33a2f16c579d12b0f26ad
Reviewed-on: https://go-review.googlesource.com/c/tools/+/212633
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-12-30 21:11:21 +00:00

115 lines
3.2 KiB
Go

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 {
keywordScore := float64(0.9)
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
path := c.path[1:]
for i, n := range path {
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] = keywordScore
// "fallthrough" is only valid in switch statements.
// A case clause is always nested within a block statement in a switch statement,
// that block statement is nested within either a TypeSwitchStmt or a SwitchStmt.
if i+2 >= len(path) {
continue
}
if _, ok := path[i+2].(*ast.SwitchStmt); ok {
valid[FALLTHROUGH] = keywordScore
}
}
case *ast.CommClause:
if c.pos > node.Colon {
valid[BREAK] = keywordScore
}
case *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.SwitchStmt:
valid[CASE] = keywordScore + lowScore
valid[DEFAULT] = keywordScore + lowScore
case *ast.ForStmt:
valid[BREAK] = keywordScore
valid[CONTINUE] = keywordScore
// This is a bit weak, functions allow for many keywords
case *ast.FuncDecl:
if node.Body != nil && c.pos > node.Body.Lbrace {
valid[DEFER] = keywordScore - lowScore
valid[RETURN] = keywordScore - lowScore
valid[FOR] = keywordScore - lowScore
valid[GO] = keywordScore - lowScore
valid[SWITCH] = keywordScore - lowScore
valid[SELECT] = keywordScore - lowScore
valid[IF] = keywordScore - lowScore
valid[ELSE] = keywordScore - lowScore
valid[VAR] = keywordScore - lowScore
valid[CONST] = keywordScore - 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
}