github.com/jd-ly/tools@v0.5.7/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/types" 11 "strings" 12 13 "github.com/jd-ly/tools/internal/event" 14 "github.com/jd-ly/tools/internal/imports" 15 "github.com/jd-ly/tools/internal/lsp/debug/tag" 16 "github.com/jd-ly/tools/internal/lsp/protocol" 17 "github.com/jd-ly/tools/internal/lsp/snippet" 18 "github.com/jd-ly/tools/internal/lsp/source" 19 "github.com/jd-ly/tools/internal/span" 20 errors "golang.org/x/xerrors" 21 ) 22 23 // item formats a candidate to a CompletionItem. 24 func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, error) { 25 obj := cand.obj 26 27 // if the object isn't a valid match against the surrounding, return early. 28 matchScore := c.matcher.Score(cand.name) 29 if matchScore <= 0 { 30 return CompletionItem{}, errors.New("not a surrounding match") 31 } 32 cand.score *= float64(matchScore) 33 34 // Ignore deep candidates that wont be in the MaxDeepCompletions anyway. 35 if len(cand.path) != 0 && !c.deepState.isHighScore(cand.score) { 36 return CompletionItem{}, errors.New("not a high scoring candidate") 37 } 38 39 // Handle builtin types separately. 40 if obj.Parent() == types.Universe { 41 return c.formatBuiltin(ctx, cand) 42 } 43 44 var ( 45 label = cand.name 46 detail = types.TypeString(obj.Type(), c.qf) 47 insert = label 48 kind = protocol.TextCompletion 49 snip *snippet.Builder 50 protocolEdits []protocol.TextEdit 51 ) 52 if obj.Type() == nil { 53 detail = "" 54 } 55 56 // expandFuncCall mutates the completion label, detail, and snippet 57 // to that of an invocation of sig. 58 expandFuncCall := func(sig *types.Signature) { 59 s := source.NewSignature(ctx, c.snapshot, c.pkg, sig, nil, c.qf) 60 snip = c.functionCallSnippet(label, s.Params()) 61 detail = "func" + s.Format() 62 } 63 64 switch obj := obj.(type) { 65 case *types.TypeName: 66 detail, kind = source.FormatType(obj.Type(), c.qf) 67 case *types.Const: 68 kind = protocol.ConstantCompletion 69 case *types.Var: 70 if _, ok := obj.Type().(*types.Struct); ok { 71 detail = "struct{...}" // for anonymous structs 72 } else if obj.IsField() { 73 detail = source.FormatVarType(ctx, c.snapshot, c.pkg, obj, c.qf) 74 } 75 if obj.IsField() { 76 kind = protocol.FieldCompletion 77 snip = c.structFieldSnippet(cand, label, detail) 78 } else { 79 kind = protocol.VariableCompletion 80 } 81 if obj.Type() == nil { 82 break 83 } 84 85 if sig, ok := obj.Type().Underlying().(*types.Signature); ok && cand.expandFuncCall { 86 expandFuncCall(sig) 87 } 88 case *types.Func: 89 sig, ok := obj.Type().Underlying().(*types.Signature) 90 if !ok { 91 break 92 } 93 kind = protocol.FunctionCompletion 94 if sig != nil && sig.Recv() != nil { 95 kind = protocol.MethodCompletion 96 } 97 98 if cand.expandFuncCall { 99 expandFuncCall(sig) 100 } 101 case *types.PkgName: 102 kind = protocol.ModuleCompletion 103 detail = fmt.Sprintf("%q", obj.Imported().Path()) 104 case *types.Label: 105 kind = protocol.ConstantCompletion 106 detail = "label" 107 } 108 109 // If this candidate needs an additional import statement, 110 // add the additional text edits needed. 111 if cand.imp != nil { 112 addlEdits, err := c.importEdits(cand.imp) 113 if err != nil { 114 return CompletionItem{}, err 115 } 116 117 protocolEdits = append(protocolEdits, addlEdits...) 118 if kind != protocol.ModuleCompletion { 119 if detail != "" { 120 detail += " " 121 } 122 detail += fmt.Sprintf("(from %q)", cand.imp.importPath) 123 } 124 } 125 126 var prefix, suffix string 127 128 // Prepend "&" or "*" operator as appropriate. 129 if cand.takeAddress { 130 prefix = "&" 131 } else if cand.makePointer { 132 prefix = "*" 133 } else if cand.dereference > 0 { 134 prefix = strings.Repeat("*", cand.dereference) 135 } 136 137 // Include "*" and "&" prefixes in the label. 138 label = prefix + label 139 140 if cand.convertTo != nil { 141 typeName := types.TypeString(cand.convertTo, c.qf) 142 143 switch cand.convertTo.(type) { 144 // We need extra parens when casting to these types. For example, 145 // we need "(*int)(foo)", not "*int(foo)". 146 case *types.Pointer, *types.Signature: 147 typeName = "(" + typeName + ")" 148 } 149 150 prefix = typeName + "(" + prefix 151 suffix = ")" 152 } 153 154 // Add variadic "..." if we are filling in a variadic param. 155 if cand.variadic { 156 suffix += "..." 157 } 158 159 if prefix != "" { 160 // If we are in a selector, add an edit to place prefix before selector. 161 if sel := enclosingSelector(c.path, c.pos); sel != nil { 162 edits, err := prependEdit(c.snapshot.FileSet(), c.mapper, sel, prefix) 163 if err != nil { 164 return CompletionItem{}, err 165 } 166 protocolEdits = append(protocolEdits, edits...) 167 } else { 168 // If there is no selector, just stick the prefix at the start. 169 insert = prefix + insert 170 if snip != nil { 171 snip.PrependText(prefix) 172 } 173 } 174 } 175 176 if suffix != "" { 177 insert += suffix 178 if snip != nil { 179 snip.WriteText(suffix) 180 } 181 } 182 183 detail = strings.TrimPrefix(detail, "untyped ") 184 // override computed detail with provided detail, if something is provided. 185 if cand.detail != "" { 186 detail = cand.detail 187 } 188 item := CompletionItem{ 189 Label: label, 190 InsertText: insert, 191 AdditionalTextEdits: protocolEdits, 192 Detail: detail, 193 Kind: kind, 194 Score: cand.score, 195 Depth: len(cand.path), 196 snippet: snip, 197 obj: obj, 198 } 199 // If the user doesn't want documentation for completion items. 200 if !c.opts.documentation { 201 return item, nil 202 } 203 pos := c.snapshot.FileSet().Position(obj.Pos()) 204 205 // We ignore errors here, because some types, like "unsafe" or "error", 206 // may not have valid positions that we can use to get documentation. 207 if !pos.IsValid() { 208 return item, nil 209 } 210 uri := span.URIFromPath(pos.Filename) 211 212 // Find the source file of the candidate, starting from a package 213 // that should have it in its dependencies. 214 searchPkg := c.pkg 215 if cand.imp != nil && cand.imp.pkg != nil { 216 searchPkg = cand.imp.pkg 217 } 218 219 pgf, pkg, err := source.FindPosInPackage(c.snapshot, searchPkg, obj.Pos()) 220 if err != nil { 221 return item, nil 222 } 223 224 posToDecl, err := c.snapshot.PosToDecl(ctx, pgf) 225 if err != nil { 226 return CompletionItem{}, err 227 } 228 decl := posToDecl[obj.Pos()] 229 if decl == nil { 230 return item, nil 231 } 232 233 hover, err := source.HoverInfo(ctx, pkg, obj, decl) 234 if err != nil { 235 event.Error(ctx, "failed to find Hover", err, tag.URI.Of(uri)) 236 return item, nil 237 } 238 item.Documentation = hover.Synopsis 239 if c.opts.fullDocumentation { 240 item.Documentation = hover.FullDocumentation 241 } 242 243 return item, nil 244 } 245 246 // importEdits produces the text edits necessary to add the given import to the current file. 247 func (c *completer) importEdits(imp *importInfo) ([]protocol.TextEdit, error) { 248 if imp == nil { 249 return nil, nil 250 } 251 252 pgf, err := c.pkg.File(span.URIFromPath(c.filename)) 253 if err != nil { 254 return nil, err 255 } 256 257 return source.ComputeOneImportFixEdits(c.snapshot, pgf, &imports.ImportFix{ 258 StmtInfo: imports.ImportInfo{ 259 ImportPath: imp.importPath, 260 Name: imp.name, 261 }, 262 // IdentName is unused on this path and is difficult to get. 263 FixType: imports.AddImport, 264 }) 265 } 266 267 func (c *completer) formatBuiltin(ctx context.Context, cand candidate) (CompletionItem, error) { 268 obj := cand.obj 269 item := CompletionItem{ 270 Label: obj.Name(), 271 InsertText: obj.Name(), 272 Score: cand.score, 273 } 274 switch obj.(type) { 275 case *types.Const: 276 item.Kind = protocol.ConstantCompletion 277 case *types.Builtin: 278 item.Kind = protocol.FunctionCompletion 279 sig, err := source.NewBuiltinSignature(ctx, c.snapshot, obj.Name()) 280 if err != nil { 281 return CompletionItem{}, err 282 } 283 item.Detail = "func" + sig.Format() 284 item.snippet = c.functionCallSnippet(obj.Name(), sig.Params()) 285 case *types.TypeName: 286 if types.IsInterface(obj.Type()) { 287 item.Kind = protocol.InterfaceCompletion 288 } else { 289 item.Kind = protocol.ClassCompletion 290 } 291 case *types.Nil: 292 item.Kind = protocol.VariableCompletion 293 } 294 return item, nil 295 }