forked from golang/tools
/
completion_keywords.go
149 lines (136 loc) · 4.33 KB
/
completion_keywords.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package source
import (
"go/ast"
"golang.org/x/tools/internal/lsp/protocol"
)
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"
)
// addKeywordCompletions offers keyword candidates appropriate at the position.
func (c *completer) addKeywordCompletions() {
seen := make(map[string]bool)
if c.wantTypeName() {
// If we expect a type name, include "interface", "struct",
// "func", "chan", and "map".
// "interface" and "struct" are more common declaring named types.
// Give them a higher score if we are in a type declaration.
structIntf, funcChanMap := stdScore, highScore
if len(c.path) > 1 {
if _, namedDecl := c.path[1].(*ast.TypeSpec); namedDecl {
structIntf, funcChanMap = highScore, stdScore
}
}
c.addKeywordItems(seen, structIntf, STRUCT, INTERFACE)
c.addKeywordItems(seen, funcChanMap, FUNC, CHAN, MAP)
}
// If we are at the file scope, only offer decl keywords. We don't
// get *ast.Idents at the file scope because non-keyword identifiers
// turn into *ast.BadDecl, not *ast.Ident.
if len(c.path) == 1 || isASTFile(c.path[1]) {
c.addKeywordItems(seen, stdScore, TYPE, CONST, VAR, FUNC, IMPORT)
return
} else if _, ok := c.path[0].(*ast.Ident); !ok {
// Otherwise only offer keywords if the client is completing an identifier.
return
}
if len(c.path) > 2 {
// Offer "range" if we are in ast.ForStmt.Init. This is what the
// AST looks like before "range" is typed, e.g. "for i := r<>".
if loop, ok := c.path[2].(*ast.ForStmt); ok && nodeContains(loop.Init, c.pos) {
c.addKeywordItems(seen, stdScore, RANGE)
}
}
// Only suggest keywords if we are beginning a statement.
switch n := c.path[1].(type) {
case *ast.BlockStmt, *ast.ExprStmt:
// OK - our ident must be at beginning of statement.
case *ast.CommClause:
// Make sure we aren't in the Comm statement.
if !n.Colon.IsValid() || c.pos <= n.Colon {
return
}
case *ast.CaseClause:
// Make sure we aren't in the case List.
if !n.Colon.IsValid() || c.pos <= n.Colon {
return
}
default:
return
}
// 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 {
c.addKeywordItems(seen, stdScore, BREAK)
// "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 {
c.addKeywordItems(seen, stdScore, FALLTHROUGH)
}
}
case *ast.CommClause:
if c.pos > node.Colon {
c.addKeywordItems(seen, stdScore, BREAK)
}
case *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.SwitchStmt:
c.addKeywordItems(seen, stdScore, CASE, DEFAULT)
case *ast.ForStmt, *ast.RangeStmt:
c.addKeywordItems(seen, stdScore, BREAK, CONTINUE)
// This is a bit weak, functions allow for many keywords
case *ast.FuncDecl:
if node.Body != nil && c.pos > node.Body.Lbrace {
c.addKeywordItems(seen, stdScore, DEFER, RETURN, FOR, GO, SWITCH, SELECT, IF, ELSE, VAR, CONST, GOTO, TYPE)
}
}
}
}
// addKeywordItems dedupes and adds completion items for the specified
// keywords with the specified score.
func (c *completer) addKeywordItems(seen map[string]bool, score float64, kws ...string) {
for _, kw := range kws {
if seen[kw] {
continue
}
seen[kw] = true
if matchScore := c.matcher.Score(kw); matchScore > 0 {
c.items = append(c.items, CompletionItem{
Label: kw,
Kind: protocol.KeywordCompletion,
InsertText: kw,
Score: score * float64(matchScore),
})
}
}
}