github.com/v2fly/tools@v0.100.0/internal/lsp/source/completion/postfix_snippets.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 "context" 9 "fmt" 10 "go/ast" 11 "go/token" 12 "go/types" 13 "log" 14 "reflect" 15 "strings" 16 "sync" 17 "text/template" 18 19 "github.com/v2fly/tools/internal/event" 20 "github.com/v2fly/tools/internal/imports" 21 "github.com/v2fly/tools/internal/lsp/protocol" 22 "github.com/v2fly/tools/internal/lsp/snippet" 23 "github.com/v2fly/tools/internal/lsp/source" 24 errors "golang.org/x/xerrors" 25 ) 26 27 // Postfix snippets are artificial methods that allow the user to 28 // compose common operations in an "argument oriented" fashion. For 29 // example, instead of "sort.Slice(someSlice, ...)" a user can expand 30 // "someSlice.sort!". 31 32 // postfixTmpl represents a postfix snippet completion candidate. 33 type postfixTmpl struct { 34 // label is the completion candidate's label presented to the user. 35 label string 36 37 // details is passed along to the client as the candidate's details. 38 details string 39 40 // body is the template text. See postfixTmplArgs for details on the 41 // facilities available to the template. 42 body string 43 44 tmpl *template.Template 45 } 46 47 // postfixTmplArgs are the template execution arguments available to 48 // the postfix snippet templates. 49 type postfixTmplArgs struct { 50 // StmtOK is true if it is valid to replace the selector with a 51 // statement. For example: 52 // 53 // func foo() { 54 // bar.sort! // statement okay 55 // 56 // someMethod(bar.sort!) // statement not okay 57 // } 58 StmtOK bool 59 60 // X is the textual SelectorExpr.X. For example, when completing 61 // "foo.bar.print!", "X" is "foo.bar". 62 X string 63 64 // Obj is the types.Object of SelectorExpr.X, if any. 65 Obj types.Object 66 67 // Type is the type of "foo.bar" in "foo.bar.print!". 68 Type types.Type 69 70 scope *types.Scope 71 snip snippet.Builder 72 importIfNeeded func(pkgPath string, scope *types.Scope) (name string, edits []protocol.TextEdit, err error) 73 edits []protocol.TextEdit 74 qf types.Qualifier 75 varNames map[string]bool 76 } 77 78 var postfixTmpls = []postfixTmpl{{ 79 label: "sort", 80 details: "sort.Slice()", 81 body: `{{if and (eq .Kind "slice") .StmtOK -}} 82 {{.Import "sort"}}.Slice({{.X}}, func({{.VarName nil "i"}}, {{.VarName nil "j"}} int) bool { 83 {{.Cursor}} 84 }) 85 {{- end}}`, 86 }, { 87 label: "last", 88 details: "s[len(s)-1]", 89 body: `{{if and (eq .Kind "slice") .Obj -}} 90 {{.X}}[len({{.X}})-1] 91 {{- end}}`, 92 }, { 93 label: "reverse", 94 details: "reverse slice", 95 body: `{{if and (eq .Kind "slice") .StmtOK -}} 96 {{$i := .VarName nil "i"}}{{$j := .VarName nil "j" -}} 97 for {{$i}}, {{$j}} := 0, len({{.X}})-1; {{$i}} < {{$j}}; {{$i}}, {{$j}} = {{$i}}+1, {{$j}}-1 { 98 {{.X}}[{{$i}}], {{.X}}[{{$j}}] = {{.X}}[{{$j}}], {{.X}}[{{$i}}] 99 } 100 {{end}}`, 101 }, { 102 label: "range", 103 details: "range over slice", 104 body: `{{if and (eq .Kind "slice") .StmtOK -}} 105 for {{.VarName nil "i"}}, {{.VarName .ElemType "v"}} := range {{.X}} { 106 {{.Cursor}} 107 } 108 {{- end}}`, 109 }, { 110 label: "append", 111 details: "append and re-assign slice", 112 body: `{{if and (eq .Kind "slice") .StmtOK .Obj -}} 113 {{.X}} = append({{.X}}, {{.Cursor}}) 114 {{- end}}`, 115 }, { 116 label: "append", 117 details: "append to slice", 118 body: `{{if and (eq .Kind "slice") (not .StmtOK) -}} 119 append({{.X}}, {{.Cursor}}) 120 {{- end}}`, 121 }, { 122 label: "copy", 123 details: "duplicate slice", 124 body: `{{if and (eq .Kind "slice") .StmtOK .Obj -}} 125 {{$v := (.VarName nil (printf "%sCopy" .X))}}{{$v}} := make([]{{.TypeName .ElemType}}, len({{.X}})) 126 copy({{$v}}, {{.X}}) 127 {{end}}`, 128 }, { 129 label: "range", 130 details: "range over map", 131 body: `{{if and (eq .Kind "map") .StmtOK -}} 132 for {{.VarName .KeyType "k"}}, {{.VarName .ElemType "v"}} := range {{.X}} { 133 {{.Cursor}} 134 } 135 {{- end}}`, 136 }, { 137 label: "clear", 138 details: "clear map contents", 139 body: `{{if and (eq .Kind "map") .StmtOK -}} 140 {{$k := (.VarName .KeyType "k")}}for {{$k}} := range {{.X}} { 141 delete({{.X}}, {{$k}}) 142 } 143 {{end}}`, 144 }, { 145 label: "keys", 146 details: "create slice of keys", 147 body: `{{if and (eq .Kind "map") .StmtOK -}} 148 {{$keysVar := (.VarName nil "keys")}}{{$keysVar}} := make([]{{.TypeName .KeyType}}, 0, len({{.X}})) 149 {{$k := (.VarName .KeyType "k")}}for {{$k}} := range {{.X}} { 150 {{$keysVar}} = append({{$keysVar}}, {{$k}}) 151 } 152 {{end}}`, 153 }, { 154 label: "var", 155 details: "assign to variables", 156 body: `{{if and (eq .Kind "tuple") .StmtOK -}} 157 {{$a := .}}{{range $i, $v := .Tuple}}{{if $i}}, {{end}}{{$a.VarName $v.Type $v.Name}}{{end}} := {{.X}} 158 {{- end}}`, 159 }, { 160 label: "var", 161 details: "assign to variable", 162 body: `{{if and (ne .Kind "tuple") .StmtOK -}} 163 {{.VarName .Type ""}} := {{.X}} 164 {{- end}}`, 165 }, { 166 label: "print", 167 details: "print to stdout", 168 body: `{{if and (ne .Kind "tuple") .StmtOK -}} 169 {{.Import "fmt"}}.Printf("{{.EscapeQuotes .X}}: %v\n", {{.X}}) 170 {{- end}}`, 171 }, { 172 label: "print", 173 details: "print to stdout", 174 body: `{{if and (eq .Kind "tuple") .StmtOK -}} 175 {{.Import "fmt"}}.Println({{.X}}) 176 {{- end}}`, 177 }} 178 179 // Cursor indicates where the client's cursor should end up after the 180 // snippet is done. 181 func (a *postfixTmplArgs) Cursor() string { 182 a.snip.WriteFinalTabstop() 183 return "" 184 } 185 186 // Import makes sure the package corresponding to path is imported, 187 // returning the identifier to use to refer to the package. 188 func (a *postfixTmplArgs) Import(path string) (string, error) { 189 name, edits, err := a.importIfNeeded(path, a.scope) 190 if err != nil { 191 return "", errors.Errorf("couldn't import %q: %w", path, err) 192 } 193 a.edits = append(a.edits, edits...) 194 return name, nil 195 } 196 197 func (a *postfixTmplArgs) EscapeQuotes(v string) string { 198 return strings.ReplaceAll(v, `"`, `\\"`) 199 } 200 201 // ElemType returns the Elem() type of xType, if applicable. 202 func (a *postfixTmplArgs) ElemType() types.Type { 203 if e, _ := a.Type.(interface{ Elem() types.Type }); e != nil { 204 return e.Elem() 205 } 206 return nil 207 } 208 209 // Kind returns the underlying kind of type, e.g. "slice", "struct", 210 // etc. 211 func (a *postfixTmplArgs) Kind() string { 212 t := reflect.TypeOf(a.Type.Underlying()) 213 return strings.ToLower(strings.TrimPrefix(t.String(), "*types.")) 214 } 215 216 // KeyType returns the type of X's key. KeyType panics if X is not a 217 // map. 218 func (a *postfixTmplArgs) KeyType() types.Type { 219 return a.Type.Underlying().(*types.Map).Key() 220 } 221 222 // Tuple returns the tuple result vars if X is a call expression. 223 func (a *postfixTmplArgs) Tuple() []*types.Var { 224 tuple, _ := a.Type.(*types.Tuple) 225 if tuple == nil { 226 return nil 227 } 228 229 typs := make([]*types.Var, 0, tuple.Len()) 230 for i := 0; i < tuple.Len(); i++ { 231 typs = append(typs, tuple.At(i)) 232 } 233 return typs 234 } 235 236 // TypeName returns the textual representation of type t. 237 func (a *postfixTmplArgs) TypeName(t types.Type) (string, error) { 238 if t == nil || t == types.Typ[types.Invalid] { 239 return "", fmt.Errorf("invalid type: %v", t) 240 } 241 return types.TypeString(t, a.qf), nil 242 } 243 244 // VarName returns a suitable variable name for the type t. If t 245 // implements the error interface, "err" is used. If t is not a named 246 // type then nonNamedDefault is used. Otherwise a name is made by 247 // abbreviating the type name. If the resultant name is already in 248 // scope, an integer is appended to make a unique name. 249 func (a *postfixTmplArgs) VarName(t types.Type, nonNamedDefault string) string { 250 if t == nil { 251 t = types.Typ[types.Invalid] 252 } 253 254 var name string 255 if types.Implements(t, errorIntf) { 256 name = "err" 257 } else if _, isNamed := source.Deref(t).(*types.Named); !isNamed { 258 name = nonNamedDefault 259 } 260 261 if name == "" { 262 name = types.TypeString(t, func(p *types.Package) string { 263 return "" 264 }) 265 name = abbreviateTypeName(name) 266 } 267 268 if dot := strings.LastIndex(name, "."); dot > -1 { 269 name = name[dot+1:] 270 } 271 272 uniqueName := name 273 for i := 2; ; i++ { 274 if s, _ := a.scope.LookupParent(uniqueName, token.NoPos); s == nil && !a.varNames[uniqueName] { 275 break 276 } 277 uniqueName = fmt.Sprintf("%s%d", name, i) 278 } 279 280 a.varNames[uniqueName] = true 281 282 return uniqueName 283 } 284 285 func (c *completer) addPostfixSnippetCandidates(ctx context.Context, sel *ast.SelectorExpr) { 286 if !c.opts.postfix { 287 return 288 } 289 290 initPostfixRules() 291 292 if sel == nil || sel.Sel == nil { 293 return 294 } 295 296 selType := c.pkg.GetTypesInfo().TypeOf(sel.X) 297 if selType == nil { 298 return 299 } 300 301 // Skip empty tuples since there is no value to operate on. 302 if tuple, ok := selType.Underlying().(*types.Tuple); ok && tuple == nil { 303 return 304 } 305 306 tokFile := c.snapshot.FileSet().File(c.pos) 307 308 // Only replace sel with a statement if sel is already a statement. 309 var stmtOK bool 310 for i, n := range c.path { 311 if n == sel && i < len(c.path)-1 { 312 switch p := c.path[i+1].(type) { 313 case *ast.ExprStmt: 314 stmtOK = true 315 case *ast.AssignStmt: 316 // In cases like: 317 // 318 // foo.<> 319 // bar = 123 320 // 321 // detect that "foo." makes up the entire statement since the 322 // apparent selector spans lines. 323 stmtOK = tokFile.Line(c.pos) < tokFile.Line(p.TokPos) 324 } 325 break 326 } 327 } 328 329 scope := c.pkg.GetTypes().Scope().Innermost(c.pos) 330 if scope == nil { 331 return 332 } 333 334 // afterDot is the position after selector dot, e.g. "|" in 335 // "foo.|print". 336 afterDot := sel.Sel.Pos() 337 338 // We must detect dangling selectors such as: 339 // 340 // foo.<> 341 // bar 342 // 343 // and adjust afterDot so that we don't mistakenly delete the 344 // newline thinking "bar" is part of our selector. 345 if startLine := tokFile.Line(sel.Pos()); startLine != tokFile.Line(afterDot) { 346 if tokFile.Line(c.pos) != startLine { 347 return 348 } 349 afterDot = c.pos 350 } 351 352 for _, rule := range postfixTmpls { 353 // When completing foo.print<>, "print" is naturally overwritten, 354 // but we need to also remove "foo." so the snippet has a clean 355 // slate. 356 edits, err := c.editText(sel.Pos(), afterDot, "") 357 if err != nil { 358 event.Error(ctx, "error calculating postfix edits", err) 359 return 360 } 361 362 tmplArgs := postfixTmplArgs{ 363 X: source.FormatNode(c.snapshot.FileSet(), sel.X), 364 StmtOK: stmtOK, 365 Obj: exprObj(c.pkg.GetTypesInfo(), sel.X), 366 Type: selType, 367 qf: c.qf, 368 importIfNeeded: c.importIfNeeded, 369 scope: scope, 370 varNames: make(map[string]bool), 371 } 372 373 // Feed the template straight into the snippet builder. This 374 // allows templates to build snippets as they are executed. 375 err = rule.tmpl.Execute(&tmplArgs.snip, &tmplArgs) 376 if err != nil { 377 event.Error(ctx, "error executing postfix template", err) 378 continue 379 } 380 381 if strings.TrimSpace(tmplArgs.snip.String()) == "" { 382 continue 383 } 384 385 score := c.matcher.Score(rule.label) 386 if score <= 0 { 387 continue 388 } 389 390 c.items = append(c.items, CompletionItem{ 391 Label: rule.label + "!", 392 Detail: rule.details, 393 Score: float64(score) * 0.01, 394 Kind: protocol.SnippetCompletion, 395 snippet: &tmplArgs.snip, 396 AdditionalTextEdits: append(edits, tmplArgs.edits...), 397 }) 398 } 399 } 400 401 var postfixRulesOnce sync.Once 402 403 func initPostfixRules() { 404 postfixRulesOnce.Do(func() { 405 var idx int 406 for _, rule := range postfixTmpls { 407 var err error 408 rule.tmpl, err = template.New("postfix_snippet").Parse(rule.body) 409 if err != nil { 410 log.Panicf("error parsing postfix snippet template: %v", err) 411 } 412 postfixTmpls[idx] = rule 413 idx++ 414 } 415 postfixTmpls = postfixTmpls[:idx] 416 }) 417 } 418 419 // importIfNeeded returns the package identifier and any necessary 420 // edits to import package pkgPath. 421 func (c *completer) importIfNeeded(pkgPath string, scope *types.Scope) (string, []protocol.TextEdit, error) { 422 defaultName := imports.ImportPathToAssumedName(pkgPath) 423 424 // Check if file already imports pkgPath. 425 for _, s := range c.file.Imports { 426 if source.ImportPath(s) == pkgPath { 427 if s.Name == nil { 428 return defaultName, nil, nil 429 } 430 if s.Name.Name != "_" { 431 return s.Name.Name, nil, nil 432 } 433 } 434 } 435 436 // Give up if the package's name is already in use by another object. 437 if _, obj := scope.LookupParent(defaultName, token.NoPos); obj != nil { 438 return "", nil, fmt.Errorf("import name %q of %q already in use", defaultName, pkgPath) 439 } 440 441 edits, err := c.importEdits(&importInfo{ 442 importPath: pkgPath, 443 }) 444 if err != nil { 445 return "", nil, err 446 } 447 448 return defaultName, edits, nil 449 }