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 }