github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/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 "context" 9 "fmt" 10 "go/ast" 11 "go/doc" 12 "go/types" 13 "strings" 14 15 "github.com/powerman/golang-tools/internal/event" 16 "github.com/powerman/golang-tools/internal/imports" 17 "github.com/powerman/golang-tools/internal/lsp/debug/tag" 18 "github.com/powerman/golang-tools/internal/lsp/protocol" 19 "github.com/powerman/golang-tools/internal/lsp/snippet" 20 "github.com/powerman/golang-tools/internal/lsp/source" 21 "github.com/powerman/golang-tools/internal/span" 22 "github.com/powerman/golang-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 += source.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.TypeParams(), 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.FindHoverContext(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 if c.opts.fullDocumentation { 255 item.Documentation = hover.Comment.Text() 256 } else { 257 item.Documentation = doc.Synopsis(hover.Comment.Text()) 258 } 259 // The desired pattern is `^// Deprecated`, but the prefix has been removed 260 if strings.HasPrefix(hover.Comment.Text(), "Deprecated") { 261 if c.snapshot.View().Options().CompletionTags { 262 item.Tags = []protocol.CompletionItemTag{protocol.ComplDeprecated} 263 } else if c.snapshot.View().Options().CompletionDeprecated { 264 item.Deprecated = true 265 } 266 } 267 268 return item, nil 269 } 270 271 // importEdits produces the text edits necessary to add the given import to the current file. 272 func (c *completer) importEdits(imp *importInfo) ([]protocol.TextEdit, error) { 273 if imp == nil { 274 return nil, nil 275 } 276 277 pgf, err := c.pkg.File(span.URIFromPath(c.filename)) 278 if err != nil { 279 return nil, err 280 } 281 282 return source.ComputeOneImportFixEdits(c.snapshot, pgf, &imports.ImportFix{ 283 StmtInfo: imports.ImportInfo{ 284 ImportPath: imp.importPath, 285 Name: imp.name, 286 }, 287 // IdentName is unused on this path and is difficult to get. 288 FixType: imports.AddImport, 289 }) 290 } 291 292 func (c *completer) formatBuiltin(ctx context.Context, cand candidate) (CompletionItem, error) { 293 obj := cand.obj 294 item := CompletionItem{ 295 Label: obj.Name(), 296 InsertText: obj.Name(), 297 Score: cand.score, 298 } 299 switch obj.(type) { 300 case *types.Const: 301 item.Kind = protocol.ConstantCompletion 302 case *types.Builtin: 303 item.Kind = protocol.FunctionCompletion 304 sig, err := source.NewBuiltinSignature(ctx, c.snapshot, obj.Name()) 305 if err != nil { 306 return CompletionItem{}, err 307 } 308 item.Detail = "func" + sig.Format() 309 item.snippet = &snippet.Builder{} 310 c.functionCallSnippet(obj.Name(), sig.TypeParams(), sig.Params(), item.snippet) 311 case *types.TypeName: 312 if types.IsInterface(obj.Type()) { 313 item.Kind = protocol.InterfaceCompletion 314 } else { 315 item.Kind = protocol.ClassCompletion 316 } 317 case *types.Nil: 318 item.Kind = protocol.VariableCompletion 319 } 320 return item, nil 321 } 322 323 // decide if the type params (if any) should be part of the completion 324 // which only possible for types.Named and types.Signature 325 // (so far, only in receivers, e.g.; func (s *GENERIC[K, V])..., which is a types.Named) 326 func (c *completer) wantTypeParams() bool { 327 // Need to be lexically in a receiver, and a child of an IndexListExpr 328 // (but IndexListExpr only exists with go1.18) 329 start := c.path[0].Pos() 330 for i, nd := range c.path { 331 if fd, ok := nd.(*ast.FuncDecl); ok { 332 if i > 0 && fd.Recv != nil && start < fd.Recv.End() { 333 return true 334 } else { 335 return false 336 } 337 } 338 } 339 return false 340 }