github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/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/powerman/golang-tools/internal/event" 20 "github.com/powerman/golang-tools/internal/imports" 21 "github.com/powerman/golang-tools/internal/lsp/protocol" 22 "github.com/powerman/golang-tools/internal/lsp/snippet" 23 "github.com/powerman/golang-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 label: "split", 179 details: "split string", 180 body: `{{if (eq (.TypeName .Type) "string") -}} 181 {{.Import "strings"}}.Split({{.X}}, "{{.Cursor}}") 182 {{- end}}`, 183 }, { 184 label: "join", 185 details: "join string slice", 186 body: `{{if and (eq .Kind "slice") (eq (.TypeName .ElemType) "string") -}} 187 {{.Import "strings"}}.Join({{.X}}, "{{.Cursor}}") 188 {{- end}}`, 189 }} 190 191 // Cursor indicates where the client's cursor should end up after the 192 // snippet is done. 193 func (a *postfixTmplArgs) Cursor() string { 194 a.snip.WriteFinalTabstop() 195 return "" 196 } 197 198 // Import makes sure the package corresponding to path is imported, 199 // returning the identifier to use to refer to the package. 200 func (a *postfixTmplArgs) Import(path string) (string, error) { 201 name, edits, err := a.importIfNeeded(path, a.scope) 202 if err != nil { 203 return "", errors.Errorf("couldn't import %q: %w", path, err) 204 } 205 a.edits = append(a.edits, edits...) 206 return name, nil 207 } 208 209 func (a *postfixTmplArgs) EscapeQuotes(v string) string { 210 return strings.ReplaceAll(v, `"`, `\\"`) 211 } 212 213 // ElemType returns the Elem() type of xType, if applicable. 214 func (a *postfixTmplArgs) ElemType() types.Type { 215 if e, _ := a.Type.(interface{ Elem() types.Type }); e != nil { 216 return e.Elem() 217 } 218 return nil 219 } 220 221 // Kind returns the underlying kind of type, e.g. "slice", "struct", 222 // etc. 223 func (a *postfixTmplArgs) Kind() string { 224 t := reflect.TypeOf(a.Type.Underlying()) 225 return strings.ToLower(strings.TrimPrefix(t.String(), "*types.")) 226 } 227 228 // KeyType returns the type of X's key. KeyType panics if X is not a 229 // map. 230 func (a *postfixTmplArgs) KeyType() types.Type { 231 return a.Type.Underlying().(*types.Map).Key() 232 } 233 234 // Tuple returns the tuple result vars if X is a call expression. 235 func (a *postfixTmplArgs) Tuple() []*types.Var { 236 tuple, _ := a.Type.(*types.Tuple) 237 if tuple == nil { 238 return nil 239 } 240 241 typs := make([]*types.Var, 0, tuple.Len()) 242 for i := 0; i < tuple.Len(); i++ { 243 typs = append(typs, tuple.At(i)) 244 } 245 return typs 246 } 247 248 // TypeName returns the textual representation of type t. 249 func (a *postfixTmplArgs) TypeName(t types.Type) (string, error) { 250 if t == nil || t == types.Typ[types.Invalid] { 251 return "", fmt.Errorf("invalid type: %v", t) 252 } 253 return types.TypeString(t, a.qf), nil 254 } 255 256 // VarName returns a suitable variable name for the type t. If t 257 // implements the error interface, "err" is used. If t is not a named 258 // type then nonNamedDefault is used. Otherwise a name is made by 259 // abbreviating the type name. If the resultant name is already in 260 // scope, an integer is appended to make a unique name. 261 func (a *postfixTmplArgs) VarName(t types.Type, nonNamedDefault string) string { 262 if t == nil { 263 t = types.Typ[types.Invalid] 264 } 265 266 var name string 267 if types.Implements(t, errorIntf) { 268 name = "err" 269 } else if _, isNamed := source.Deref(t).(*types.Named); !isNamed { 270 name = nonNamedDefault 271 } 272 273 if name == "" { 274 name = types.TypeString(t, func(p *types.Package) string { 275 return "" 276 }) 277 name = abbreviateTypeName(name) 278 } 279 280 if dot := strings.LastIndex(name, "."); dot > -1 { 281 name = name[dot+1:] 282 } 283 284 uniqueName := name 285 for i := 2; ; i++ { 286 if s, _ := a.scope.LookupParent(uniqueName, token.NoPos); s == nil && !a.varNames[uniqueName] { 287 break 288 } 289 uniqueName = fmt.Sprintf("%s%d", name, i) 290 } 291 292 a.varNames[uniqueName] = true 293 294 return uniqueName 295 } 296 297 func (c *completer) addPostfixSnippetCandidates(ctx context.Context, sel *ast.SelectorExpr) { 298 if !c.opts.postfix { 299 return 300 } 301 302 initPostfixRules() 303 304 if sel == nil || sel.Sel == nil { 305 return 306 } 307 308 selType := c.pkg.GetTypesInfo().TypeOf(sel.X) 309 if selType == nil { 310 return 311 } 312 313 // Skip empty tuples since there is no value to operate on. 314 if tuple, ok := selType.Underlying().(*types.Tuple); ok && tuple == nil { 315 return 316 } 317 318 tokFile := c.snapshot.FileSet().File(c.pos) 319 320 // Only replace sel with a statement if sel is already a statement. 321 var stmtOK bool 322 for i, n := range c.path { 323 if n == sel && i < len(c.path)-1 { 324 switch p := c.path[i+1].(type) { 325 case *ast.ExprStmt: 326 stmtOK = true 327 case *ast.AssignStmt: 328 // In cases like: 329 // 330 // foo.<> 331 // bar = 123 332 // 333 // detect that "foo." makes up the entire statement since the 334 // apparent selector spans lines. 335 stmtOK = tokFile.Line(c.pos) < tokFile.Line(p.TokPos) 336 } 337 break 338 } 339 } 340 341 scope := c.pkg.GetTypes().Scope().Innermost(c.pos) 342 if scope == nil { 343 return 344 } 345 346 // afterDot is the position after selector dot, e.g. "|" in 347 // "foo.|print". 348 afterDot := sel.Sel.Pos() 349 350 // We must detect dangling selectors such as: 351 // 352 // foo.<> 353 // bar 354 // 355 // and adjust afterDot so that we don't mistakenly delete the 356 // newline thinking "bar" is part of our selector. 357 if startLine := tokFile.Line(sel.Pos()); startLine != tokFile.Line(afterDot) { 358 if tokFile.Line(c.pos) != startLine { 359 return 360 } 361 afterDot = c.pos 362 } 363 364 for _, rule := range postfixTmpls { 365 // When completing foo.print<>, "print" is naturally overwritten, 366 // but we need to also remove "foo." so the snippet has a clean 367 // slate. 368 edits, err := c.editText(sel.Pos(), afterDot, "") 369 if err != nil { 370 event.Error(ctx, "error calculating postfix edits", err) 371 return 372 } 373 374 tmplArgs := postfixTmplArgs{ 375 X: source.FormatNode(c.snapshot.FileSet(), sel.X), 376 StmtOK: stmtOK, 377 Obj: exprObj(c.pkg.GetTypesInfo(), sel.X), 378 Type: selType, 379 qf: c.qf, 380 importIfNeeded: c.importIfNeeded, 381 scope: scope, 382 varNames: make(map[string]bool), 383 } 384 385 // Feed the template straight into the snippet builder. This 386 // allows templates to build snippets as they are executed. 387 err = rule.tmpl.Execute(&tmplArgs.snip, &tmplArgs) 388 if err != nil { 389 event.Error(ctx, "error executing postfix template", err) 390 continue 391 } 392 393 if strings.TrimSpace(tmplArgs.snip.String()) == "" { 394 continue 395 } 396 397 score := c.matcher.Score(rule.label) 398 if score <= 0 { 399 continue 400 } 401 402 c.items = append(c.items, CompletionItem{ 403 Label: rule.label + "!", 404 Detail: rule.details, 405 Score: float64(score) * 0.01, 406 Kind: protocol.SnippetCompletion, 407 snippet: &tmplArgs.snip, 408 AdditionalTextEdits: append(edits, tmplArgs.edits...), 409 }) 410 } 411 } 412 413 var postfixRulesOnce sync.Once 414 415 func initPostfixRules() { 416 postfixRulesOnce.Do(func() { 417 var idx int 418 for _, rule := range postfixTmpls { 419 var err error 420 rule.tmpl, err = template.New("postfix_snippet").Parse(rule.body) 421 if err != nil { 422 log.Panicf("error parsing postfix snippet template: %v", err) 423 } 424 postfixTmpls[idx] = rule 425 idx++ 426 } 427 postfixTmpls = postfixTmpls[:idx] 428 }) 429 } 430 431 // importIfNeeded returns the package identifier and any necessary 432 // edits to import package pkgPath. 433 func (c *completer) importIfNeeded(pkgPath string, scope *types.Scope) (string, []protocol.TextEdit, error) { 434 defaultName := imports.ImportPathToAssumedName(pkgPath) 435 436 // Check if file already imports pkgPath. 437 for _, s := range c.file.Imports { 438 if source.ImportPath(s) == pkgPath { 439 if s.Name == nil { 440 return defaultName, nil, nil 441 } 442 if s.Name.Name != "_" { 443 return s.Name.Name, nil, nil 444 } 445 } 446 } 447 448 // Give up if the package's name is already in use by another object. 449 if _, obj := scope.LookupParent(defaultName, token.NoPos); obj != nil { 450 return "", nil, fmt.Errorf("import name %q of %q already in use", defaultName, pkgPath) 451 } 452 453 edits, err := c.importEdits(&importInfo{ 454 importPath: pkgPath, 455 }) 456 if err != nil { 457 return "", nil, err 458 } 459 460 return defaultName, edits, nil 461 }