--- /dev/null
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package completion
+
+import (
+ "go/ast"
+
+ "golang.org/x/tools/internal/lsp/protocol"
+ "golang.org/x/tools/internal/lsp/source"
+)
+
+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() && c.inference.objType == nil {
+ // If we want a type name but don't have an expected obj type,
+ // 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 && source.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),
+ })
+ }
+ }
+}