github.com/v2fly/tools@v0.100.0/internal/lsp/source/signature_help.go (about)

     1  // Copyright 2018 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 source
     6  
     7  import (
     8  	"context"
     9  	"go/ast"
    10  	"go/token"
    11  	"go/types"
    12  
    13  	"github.com/v2fly/tools/go/ast/astutil"
    14  	"github.com/v2fly/tools/internal/event"
    15  	"github.com/v2fly/tools/internal/lsp/protocol"
    16  	errors "golang.org/x/xerrors"
    17  )
    18  
    19  func SignatureHelp(ctx context.Context, snapshot Snapshot, fh FileHandle, pos protocol.Position) (*protocol.SignatureInformation, int, error) {
    20  	ctx, done := event.Start(ctx, "source.SignatureHelp")
    21  	defer done()
    22  
    23  	pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
    24  	if err != nil {
    25  		return nil, 0, errors.Errorf("getting file for SignatureHelp: %w", err)
    26  	}
    27  	spn, err := pgf.Mapper.PointSpan(pos)
    28  	if err != nil {
    29  		return nil, 0, err
    30  	}
    31  	rng, err := spn.Range(pgf.Mapper.Converter)
    32  	if err != nil {
    33  		return nil, 0, err
    34  	}
    35  	// Find a call expression surrounding the query position.
    36  	var callExpr *ast.CallExpr
    37  	path, _ := astutil.PathEnclosingInterval(pgf.File, rng.Start, rng.Start)
    38  	if path == nil {
    39  		return nil, 0, errors.Errorf("cannot find node enclosing position")
    40  	}
    41  FindCall:
    42  	for _, node := range path {
    43  		switch node := node.(type) {
    44  		case *ast.CallExpr:
    45  			if rng.Start >= node.Lparen && rng.Start <= node.Rparen {
    46  				callExpr = node
    47  				break FindCall
    48  			}
    49  		case *ast.FuncLit, *ast.FuncType:
    50  			// The user is within an anonymous function,
    51  			// which may be the parameter to the *ast.CallExpr.
    52  			// Don't show signature help in this case.
    53  			return nil, 0, errors.Errorf("no signature help within a function declaration")
    54  		}
    55  	}
    56  	if callExpr == nil || callExpr.Fun == nil {
    57  		return nil, 0, errors.Errorf("cannot find an enclosing function")
    58  	}
    59  
    60  	qf := Qualifier(pgf.File, pkg.GetTypes(), pkg.GetTypesInfo())
    61  
    62  	// Get the object representing the function, if available.
    63  	// There is no object in certain cases such as calling a function returned by
    64  	// a function (e.g. "foo()()").
    65  	var obj types.Object
    66  	switch t := callExpr.Fun.(type) {
    67  	case *ast.Ident:
    68  		obj = pkg.GetTypesInfo().ObjectOf(t)
    69  	case *ast.SelectorExpr:
    70  		obj = pkg.GetTypesInfo().ObjectOf(t.Sel)
    71  	}
    72  
    73  	// Handle builtin functions separately.
    74  	if obj, ok := obj.(*types.Builtin); ok {
    75  		return builtinSignature(ctx, snapshot, callExpr, obj.Name(), rng.Start)
    76  	}
    77  
    78  	// Get the type information for the function being called.
    79  	sigType := pkg.GetTypesInfo().TypeOf(callExpr.Fun)
    80  	if sigType == nil {
    81  		return nil, 0, errors.Errorf("cannot get type for Fun %[1]T (%[1]v)", callExpr.Fun)
    82  	}
    83  
    84  	sig, _ := sigType.Underlying().(*types.Signature)
    85  	if sig == nil {
    86  		return nil, 0, errors.Errorf("cannot find signature for Fun %[1]T (%[1]v)", callExpr.Fun)
    87  	}
    88  
    89  	activeParam := activeParameter(callExpr, sig.Params().Len(), sig.Variadic(), rng.Start)
    90  
    91  	var (
    92  		name    string
    93  		comment *ast.CommentGroup
    94  	)
    95  	if obj != nil {
    96  		node, err := objToDecl(ctx, snapshot, pkg, obj)
    97  		if err != nil {
    98  			return nil, 0, err
    99  		}
   100  		rng, err := objToMappedRange(snapshot, pkg, obj)
   101  		if err != nil {
   102  			return nil, 0, err
   103  		}
   104  		decl := Declaration{
   105  			obj:  obj,
   106  			node: node,
   107  		}
   108  		decl.MappedRange = append(decl.MappedRange, rng)
   109  		d, err := HoverInfo(ctx, snapshot, pkg, decl.obj, decl.node)
   110  		if err != nil {
   111  			return nil, 0, err
   112  		}
   113  		name = obj.Name()
   114  		comment = d.comment
   115  	} else {
   116  		name = "func"
   117  	}
   118  	s := NewSignature(ctx, snapshot, pkg, sig, comment, qf)
   119  	paramInfo := make([]protocol.ParameterInformation, 0, len(s.params))
   120  	for _, p := range s.params {
   121  		paramInfo = append(paramInfo, protocol.ParameterInformation{Label: p})
   122  	}
   123  	return &protocol.SignatureInformation{
   124  		Label:         name + s.Format(),
   125  		Documentation: s.doc,
   126  		Parameters:    paramInfo,
   127  	}, activeParam, nil
   128  }
   129  
   130  func builtinSignature(ctx context.Context, snapshot Snapshot, callExpr *ast.CallExpr, name string, pos token.Pos) (*protocol.SignatureInformation, int, error) {
   131  	sig, err := NewBuiltinSignature(ctx, snapshot, name)
   132  	if err != nil {
   133  		return nil, 0, err
   134  	}
   135  	paramInfo := make([]protocol.ParameterInformation, 0, len(sig.params))
   136  	for _, p := range sig.params {
   137  		paramInfo = append(paramInfo, protocol.ParameterInformation{Label: p})
   138  	}
   139  	activeParam := activeParameter(callExpr, len(sig.params), sig.variadic, pos)
   140  	return &protocol.SignatureInformation{
   141  		Label:         sig.name + sig.Format(),
   142  		Documentation: sig.doc,
   143  		Parameters:    paramInfo,
   144  	}, activeParam, nil
   145  
   146  }
   147  
   148  func activeParameter(callExpr *ast.CallExpr, numParams int, variadic bool, pos token.Pos) (activeParam int) {
   149  	if len(callExpr.Args) == 0 {
   150  		return 0
   151  	}
   152  	// First, check if the position is even in the range of the arguments.
   153  	start, end := callExpr.Lparen, callExpr.Rparen
   154  	if !(start <= pos && pos <= end) {
   155  		return 0
   156  	}
   157  	for _, expr := range callExpr.Args {
   158  		if start == token.NoPos {
   159  			start = expr.Pos()
   160  		}
   161  		end = expr.End()
   162  		if start <= pos && pos <= end {
   163  			break
   164  		}
   165  		// Don't advance the active parameter for the last parameter of a variadic function.
   166  		if !variadic || activeParam < numParams-1 {
   167  			activeParam++
   168  		}
   169  		start = expr.Pos() + 1 // to account for commas
   170  	}
   171  	return activeParam
   172  }