github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/source/completion/format.go (about) 1 // Copyright 2019 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 "bytes" 9 "context" 10 "fmt" 11 "go/ast" 12 "go/types" 13 "strings" 14 15 "github.com/jhump/golang-x-tools/internal/event" 16 "github.com/jhump/golang-x-tools/internal/imports" 17 "github.com/jhump/golang-x-tools/internal/lsp/debug/tag" 18 "github.com/jhump/golang-x-tools/internal/lsp/protocol" 19 "github.com/jhump/golang-x-tools/internal/lsp/snippet" 20 "github.com/jhump/golang-x-tools/internal/lsp/source" 21 "github.com/jhump/golang-x-tools/internal/span" 22 "github.com/jhump/golang-x-tools/internal/typeparams" 23 errors "golang.org/x/xerrors" 24 ) 25 26 var ( 27 errNoMatch = errors.New("not a surrounding match") 28 errLowScore = errors.New("not a high scoring candidate") 29 ) 30 31 // item formats a candidate to a CompletionItem. 32 func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, error) { 33 obj := cand.obj 34 35 // if the object isn't a valid match against the surrounding, return early. 36 matchScore := c.matcher.Score(cand.name) 37 if matchScore <= 0 { 38 return CompletionItem{}, errNoMatch 39 } 40 cand.score *= float64(matchScore) 41 42 // Ignore deep candidates that wont be in the MaxDeepCompletions anyway. 43 if len(cand.path) != 0 && !c.deepState.isHighScore(cand.score) { 44 return CompletionItem{}, errLowScore 45 } 46 47 // Handle builtin types separately. 48 if obj.Parent() == types.Universe { 49 return c.formatBuiltin(ctx, cand) 50 } 51 52 var ( 53 label = cand.name 54 detail = types.TypeString(obj.Type(), c.qf) 55 insert = label 56 kind = protocol.TextCompletion 57 snip snippet.Builder 58 protocolEdits []protocol.TextEdit 59 ) 60 if obj.Type() == nil { 61 detail = "" 62 } 63 if isTypeName(obj) && c.wantTypeParams() { 64 x := cand.obj.(*types.TypeName) 65 if named, ok := x.Type().(*types.Named); ok { 66 tp := typeparams.ForNamed(named) 67 label += string(formatTypeParams(tp)) 68 insert = label // maintain invariant above (label == insert) 69 } 70 } 71 72 snip.WriteText(insert) 73 74 switch obj := obj.(type) { 75 case *types.TypeName: 76 detail, kind = source.FormatType(obj.Type(), c.qf) 77 case *types.Const: 78 kind = protocol.ConstantCompletion 79 case *types.Var: 80 if _, ok := obj.Type().(*types.Struct); ok { 81 detail = "struct{...}" // for anonymous structs 82 } else if obj.IsField() { 83 detail = source.FormatVarType(ctx, c.snapshot, c.pkg, obj, c.qf) 84 } 85 if obj.IsField() { 86 kind = protocol.FieldCompletion 87 c.structFieldSnippet(cand, detail, &snip) 88 } else { 89 kind = protocol.VariableCompletion 90 } 91 if obj.Type() == nil { 92 break 93 } 94 case *types.Func: 95 sig, ok := obj.Type().Underlying().(*types.Signature) 96 if !ok { 97 break 98 } 99 kind = protocol.FunctionCompletion 100 if sig != nil && sig.Recv() != nil { 101 kind = protocol.MethodCompletion 102 } 103 case *types.PkgName: 104 kind = protocol.ModuleCompletion 105 detail = fmt.Sprintf("%q", obj.Imported().Path()) 106 case *types.Label: 107 kind = protocol.ConstantCompletion 108 detail = "label" 109 } 110 111 var prefix string 112 for _, mod := range cand.mods { 113 switch mod { 114 case reference: 115 prefix = "&" + prefix 116 case dereference: 117 prefix = "*" + prefix 118 case chanRead: 119 prefix = "<-" + prefix 120 } 121 } 122 123 var ( 124 suffix string 125 funcType = obj.Type() 126 ) 127 Suffixes: 128 for _, mod := range cand.mods { 129 switch mod { 130 case invoke: 131 if sig, ok := funcType.Underlying().(*types.Signature); ok { 132 s := source.NewSignature(ctx, c.snapshot, c.pkg, sig, nil, c.qf) 133 c.functionCallSnippet("", s.Params(), &snip) 134 if sig.Results().Len() == 1 { 135 funcType = sig.Results().At(0).Type() 136 } 137 detail = "func" + s.Format() 138 } 139 140 if !c.opts.snippets { 141 // Without snippets the candidate will not include "()". Don't 142 // add further suffixes since they will be invalid. For 143 // example, with snippets "foo()..." would become "foo..." 144 // without snippets if we added the dotDotDot. 145 break Suffixes 146 } 147 case takeSlice: 148 suffix += "[:]" 149 case takeDotDotDot: 150 suffix += "..." 151 case index: 152 snip.WriteText("[") 153 snip.WritePlaceholder(nil) 154 snip.WriteText("]") 155 } 156 } 157 158 // If this candidate needs an additional import statement, 159 // add the additional text edits needed. 160 if cand.imp != nil { 161 addlEdits, err := c.importEdits(cand.imp) 162 163 if err != nil { 164 return CompletionItem{}, err 165 } 166 167 protocolEdits = append(protocolEdits, addlEdits...) 168 if kind != protocol.ModuleCompletion { 169 if detail != "" { 170 detail += " " 171 } 172 detail += fmt.Sprintf("(from %q)", cand.imp.importPath) 173 } 174 } 175 176 if cand.convertTo != nil { 177 typeName := types.TypeString(cand.convertTo, c.qf) 178 179 switch cand.convertTo.(type) { 180 // We need extra parens when casting to these types. For example, 181 // we need "(*int)(foo)", not "*int(foo)". 182 case *types.Pointer, *types.Signature: 183 typeName = "(" + typeName + ")" 184 } 185 186 prefix = typeName + "(" + prefix 187 suffix = ")" 188 } 189 190 if prefix != "" { 191 // If we are in a selector, add an edit to place prefix before selector. 192 if sel := enclosingSelector(c.path, c.pos); sel != nil { 193 edits, err := c.editText(sel.Pos(), sel.Pos(), prefix) 194 if err != nil { 195 return CompletionItem{}, err 196 } 197 protocolEdits = append(protocolEdits, edits...) 198 } else { 199 // If there is no selector, just stick the prefix at the start. 200 insert = prefix + insert 201 snip.PrependText(prefix) 202 } 203 } 204 205 if suffix != "" { 206 insert += suffix 207 snip.WriteText(suffix) 208 } 209 210 detail = strings.TrimPrefix(detail, "untyped ") 211 // override computed detail with provided detail, if something is provided. 212 if cand.detail != "" { 213 detail = cand.detail 214 } 215 item := CompletionItem{ 216 Label: label, 217 InsertText: insert, 218 AdditionalTextEdits: protocolEdits, 219 Detail: detail, 220 Kind: kind, 221 Score: cand.score, 222 Depth: len(cand.path), 223 snippet: &snip, 224 obj: obj, 225 } 226 // If the user doesn't want documentation for completion items. 227 if !c.opts.documentation { 228 return item, nil 229 } 230 pos := c.snapshot.FileSet().Position(obj.Pos()) 231 232 // We ignore errors here, because some types, like "unsafe" or "error", 233 // may not have valid positions that we can use to get documentation. 234 if !pos.IsValid() { 235 return item, nil 236 } 237 uri := span.URIFromPath(pos.Filename) 238 239 // Find the source file of the candidate. 240 pkg, err := source.FindPackageFromPos(ctx, c.snapshot, obj.Pos()) 241 if err != nil { 242 return item, nil 243 } 244 245 decl, err := c.snapshot.PosToDecl(ctx, pkg, obj.Pos()) 246 if err != nil { 247 return CompletionItem{}, err 248 } 249 hover, err := source.HoverInfo(ctx, c.snapshot, pkg, obj, decl, nil) 250 if err != nil { 251 event.Error(ctx, "failed to find Hover", err, tag.URI.Of(uri)) 252 return item, nil 253 } 254 item.Documentation = hover.Synopsis 255 if c.opts.fullDocumentation { 256 item.Documentation = hover.FullDocumentation 257 } 258 // The desired pattern is `^// Deprecated`, but the prefix has been removed 259 if strings.HasPrefix(hover.FullDocumentation, "Deprecated") { 260 if c.snapshot.View().Options().CompletionTags { 261 item.Tags = []protocol.CompletionItemTag{protocol.ComplDeprecated} 262 } else if c.snapshot.View().Options().CompletionDeprecated { 263 item.Deprecated = true 264 } 265 } 266 267 return item, nil 268 } 269 270 // importEdits produces the text edits necessary to add the given import to the current file. 271 func (c *completer) importEdits(imp *importInfo) ([]protocol.TextEdit, error) { 272 if imp == nil { 273 return nil, nil 274 } 275 276 pgf, err := c.pkg.File(span.URIFromPath(c.filename)) 277 if err != nil { 278 return nil, err 279 } 280 281 return source.ComputeOneImportFixEdits(c.snapshot, pgf, &imports.ImportFix{ 282 StmtInfo: imports.ImportInfo{ 283 ImportPath: imp.importPath, 284 Name: imp.name, 285 }, 286 // IdentName is unused on this path and is difficult to get. 287 FixType: imports.AddImport, 288 }) 289 } 290 291 func (c *completer) formatBuiltin(ctx context.Context, cand candidate) (CompletionItem, error) { 292 obj := cand.obj 293 item := CompletionItem{ 294 Label: obj.Name(), 295 InsertText: obj.Name(), 296 Score: cand.score, 297 } 298 switch obj.(type) { 299 case *types.Const: 300 item.Kind = protocol.ConstantCompletion 301 case *types.Builtin: 302 item.Kind = protocol.FunctionCompletion 303 sig, err := source.NewBuiltinSignature(ctx, c.snapshot, obj.Name()) 304 if err != nil { 305 return CompletionItem{}, err 306 } 307 item.Detail = "func" + sig.Format() 308 item.snippet = &snippet.Builder{} 309 c.functionCallSnippet(obj.Name(), sig.Params(), item.snippet) 310 case *types.TypeName: 311 if types.IsInterface(obj.Type()) { 312 item.Kind = protocol.InterfaceCompletion 313 } else { 314 item.Kind = protocol.ClassCompletion 315 } 316 case *types.Nil: 317 item.Kind = protocol.VariableCompletion 318 } 319 return item, nil 320 } 321 322 // decide if the type params (if any) should be part of the completion 323 // which only possible for types.Named and types.Signature 324 // (so far, only in receivers, e.g.; func (s *GENERIC[K, V])..., which is a types.Named) 325 func (c *completer) wantTypeParams() bool { 326 // Need to be lexically in a receiver, and a child of an IndexListExpr 327 // (but IndexListExpr only exists with go1.18) 328 start := c.path[0].Pos() 329 for i, nd := range c.path { 330 if fd, ok := nd.(*ast.FuncDecl); ok { 331 if i > 0 && fd.Recv != nil && start < fd.Recv.End() { 332 return true 333 } else { 334 return false 335 } 336 } 337 } 338 return false 339 } 340 341 func formatTypeParams(tp *typeparams.TypeParamList) []byte { 342 var buf bytes.Buffer 343 if tp == nil || tp.Len() == 0 { 344 return nil 345 } 346 buf.WriteByte('[') 347 for i := 0; i < tp.Len(); i++ { 348 if i > 0 { 349 buf.WriteString(", ") 350 } 351 buf.WriteString(tp.At(i).Obj().Name()) 352 } 353 buf.WriteByte(']') 354 return buf.Bytes() 355 }