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