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  }