| 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 |
| } |