github.com/v2fly/tools@v0.100.0/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/v2fly/tools/internal/event" 14 "github.com/v2fly/tools/internal/imports" 15 "github.com/v2fly/tools/internal/lsp/debug/tag" 16 "github.com/v2fly/tools/internal/lsp/protocol" 17 "github.com/v2fly/tools/internal/lsp/snippet" 18 "github.com/v2fly/tools/internal/lsp/source" 19 "github.com/v2fly/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 if cand.takeSlice { 155 suffix += "[:]" 156 } 157 158 // Add variadic "..." only if snippets if enabled or cand is not a function 159 if cand.variadic && (c.opts.snippets || !cand.expandFuncCall) { 160 suffix += "..." 161 } 162 163 if prefix != "" { 164 // If we are in a selector, add an edit to place prefix before selector. 165 if sel := enclosingSelector(c.path, c.pos); sel != nil { 166 edits, err := c.editText(sel.Pos(), sel.Pos(), prefix) 167 if err != nil { 168 return CompletionItem{}, err 169 } 170 protocolEdits = append(protocolEdits, edits...) 171 } else { 172 // If there is no selector, just stick the prefix at the start. 173 insert = prefix + insert 174 if snip != nil { 175 snip.PrependText(prefix) 176 } 177 } 178 } 179 180 if suffix != "" { 181 insert += suffix 182 if snip != nil { 183 snip.WriteText(suffix) 184 } 185 } 186 187 detail = strings.TrimPrefix(detail, "untyped ") 188 // override computed detail with provided detail, if something is provided. 189 if cand.detail != "" { 190 detail = cand.detail 191 } 192 item := CompletionItem{ 193 Label: label, 194 InsertText: insert, 195 AdditionalTextEdits: protocolEdits, 196 Detail: detail, 197 Kind: kind, 198 Score: cand.score, 199 Depth: len(cand.path), 200 snippet: snip, 201 obj: obj, 202 } 203 // If the user doesn't want documentation for completion items. 204 if !c.opts.documentation { 205 return item, nil 206 } 207 pos := c.snapshot.FileSet().Position(obj.Pos()) 208 209 // We ignore errors here, because some types, like "unsafe" or "error", 210 // may not have valid positions that we can use to get documentation. 211 if !pos.IsValid() { 212 return item, nil 213 } 214 uri := span.URIFromPath(pos.Filename) 215 216 // Find the source file of the candidate, starting from a package 217 // that should have it in its dependencies. 218 searchPkg := c.pkg 219 if cand.imp != nil && cand.imp.pkg != nil { 220 searchPkg = cand.imp.pkg 221 } 222 223 pgf, pkg, err := source.FindPosInPackage(c.snapshot, searchPkg, obj.Pos()) 224 if err != nil { 225 return item, nil 226 } 227 228 posToDecl, err := c.snapshot.PosToDecl(ctx, pgf) 229 if err != nil { 230 return CompletionItem{}, err 231 } 232 decl := posToDecl[obj.Pos()] 233 if decl == nil { 234 return item, nil 235 } 236 237 hover, err := source.HoverInfo(ctx, c.snapshot, pkg, obj, decl) 238 if err != nil { 239 event.Error(ctx, "failed to find Hover", err, tag.URI.Of(uri)) 240 return item, nil 241 } 242 item.Documentation = hover.Synopsis 243 if c.opts.fullDocumentation { 244 item.Documentation = hover.FullDocumentation 245 } 246 // The desired pattern is `^// Deprecated`, but the prefix has been removed 247 if strings.HasPrefix(hover.FullDocumentation, "Deprecated") { 248 if c.snapshot.View().Options().CompletionTags { 249 item.Tags = []protocol.CompletionItemTag{protocol.ComplDeprecated} 250 } else if c.snapshot.View().Options().CompletionDeprecated { 251 item.Deprecated = true 252 } 253 } 254 255 return item, nil 256 } 257 258 // importEdits produces the text edits necessary to add the given import to the current file. 259 func (c *completer) importEdits(imp *importInfo) ([]protocol.TextEdit, error) { 260 if imp == nil { 261 return nil, nil 262 } 263 264 pgf, err := c.pkg.File(span.URIFromPath(c.filename)) 265 if err != nil { 266 return nil, err 267 } 268 269 return source.ComputeOneImportFixEdits(c.snapshot, pgf, &imports.ImportFix{ 270 StmtInfo: imports.ImportInfo{ 271 ImportPath: imp.importPath, 272 Name: imp.name, 273 }, 274 // IdentName is unused on this path and is difficult to get. 275 FixType: imports.AddImport, 276 }) 277 } 278 279 func (c *completer) formatBuiltin(ctx context.Context, cand candidate) (CompletionItem, error) { 280 obj := cand.obj 281 item := CompletionItem{ 282 Label: obj.Name(), 283 InsertText: obj.Name(), 284 Score: cand.score, 285 } 286 switch obj.(type) { 287 case *types.Const: 288 item.Kind = protocol.ConstantCompletion 289 case *types.Builtin: 290 item.Kind = protocol.FunctionCompletion 291 sig, err := source.NewBuiltinSignature(ctx, c.snapshot, obj.Name()) 292 if err != nil { 293 return CompletionItem{}, err 294 } 295 item.Detail = "func" + sig.Format() 296 item.snippet = c.functionCallSnippet(obj.Name(), sig.Params()) 297 case *types.TypeName: 298 if types.IsInterface(obj.Type()) { 299 item.Kind = protocol.InterfaceCompletion 300 } else { 301 item.Kind = protocol.ClassCompletion 302 } 303 case *types.Nil: 304 item.Kind = protocol.VariableCompletion 305 } 306 return item, nil 307 }