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  }