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  }