1
0
mirror of https://github.com/golang/go synced 2024-09-30 20:28:32 -06:00

internal/lsp: implement signature help

Add SignatureHelp functionality to source package. Tests will be added
in a subsequent change.

Change-Id: Ia43280946d96a984c5741273a00c12255d637b2a
Reviewed-on: https://go-review.googlesource.com/c/149177
Reviewed-by: Ian Cottrell <iancottrell@google.com>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Rebecca Stambler 2018-11-12 16:53:10 -05:00
parent 0a8e63141f
commit 7e59e591a2
3 changed files with 168 additions and 6 deletions

View File

@ -43,14 +43,15 @@ func (s *server) Initialize(ctx context.Context, params *protocol.InitializePara
s.initialized = true
return &protocol.InitializeResult{
Capabilities: protocol.ServerCapabilities{
CompletionProvider: protocol.CompletionOptions{},
DefinitionProvider: true,
DocumentFormattingProvider: true,
DocumentRangeFormattingProvider: true,
SignatureHelpProvider: protocol.SignatureHelpOptions{},
TextDocumentSync: protocol.TextDocumentSyncOptions{
Change: float64(protocol.Full), // full contents of file sent on each update
OpenClose: true,
},
DocumentFormattingProvider: true,
DocumentRangeFormattingProvider: true,
CompletionProvider: protocol.CompletionOptions{},
DefinitionProvider: true,
},
}, nil
}
@ -174,8 +175,18 @@ func (s *server) Hover(context.Context, *protocol.TextDocumentPositionParams) (*
return nil, notImplemented("Hover")
}
func (s *server) SignatureHelp(context.Context, *protocol.TextDocumentPositionParams) (*protocol.SignatureHelp, error) {
return nil, notImplemented("SignatureHelp")
func (s *server) SignatureHelp(ctx context.Context, params *protocol.TextDocumentPositionParams) (*protocol.SignatureHelp, error) {
f := s.view.GetFile(source.URI(params.TextDocument.URI))
tok, err := f.GetToken()
if err != nil {
return nil, err
}
pos := fromProtocolPosition(tok, params.Position)
info, err := source.SignatureHelp(ctx, f, pos)
if err != nil {
return nil, err
}
return toProtocolSignatureHelp(info), nil
}
func (s *server) Definition(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.Location, error) {

View File

@ -0,0 +1,33 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package lsp
import (
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
)
func toProtocolSignatureHelp(info *source.SignatureInformation) *protocol.SignatureHelp {
return &protocol.SignatureHelp{
ActiveParameter: float64(info.ActiveParameter),
ActiveSignature: 0, // there is only ever one possible signature
Signatures: []protocol.SignatureInformation{
{
Label: info.Label,
Parameters: toProtocolParameterInformation(info.Parameters),
},
},
}
}
func toProtocolParameterInformation(info []source.ParameterInformation) []protocol.ParameterInformation {
var result []protocol.ParameterInformation
for _, p := range info {
result = append(result, protocol.ParameterInformation{
Label: p.Label,
})
}
return result
}

View File

@ -0,0 +1,118 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package source
import (
"context"
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/ast/astutil"
)
type SignatureInformation struct {
Label string
Parameters []ParameterInformation
ActiveParameter int
}
type ParameterInformation struct {
Label string
}
func SignatureHelp(ctx context.Context, f *File, pos token.Pos) (*SignatureInformation, error) {
fAST, err := f.GetAST()
if err != nil {
return nil, err
}
pkg, err := f.GetPackage()
if err != nil {
return nil, err
}
// Find a call expression surrounding the query position.
var callExpr *ast.CallExpr
path, _ := astutil.PathEnclosingInterval(fAST, pos, pos)
if path == nil {
return nil, fmt.Errorf("cannot find node enclosing position")
}
for _, node := range path {
if c, ok := node.(*ast.CallExpr); ok {
callExpr = c
break
}
}
if callExpr == nil || callExpr.Fun == nil {
return nil, fmt.Errorf("cannot find an enclosing function")
}
// Get the type information for the function corresponding to the call expression.
var obj types.Object
switch t := callExpr.Fun.(type) {
case *ast.Ident:
obj = pkg.TypesInfo.ObjectOf(t)
case *ast.SelectorExpr:
obj = pkg.TypesInfo.ObjectOf(t.Sel)
default:
return nil, fmt.Errorf("the enclosing function is malformed")
}
if obj == nil {
return nil, fmt.Errorf("cannot resolve %s", callExpr.Fun)
}
// Find the signature corresponding to the object.
var sig *types.Signature
switch obj.(type) {
case *types.Var:
if underlying, ok := obj.Type().Underlying().(*types.Signature); ok {
sig = underlying
}
case *types.Func:
sig = obj.Type().(*types.Signature)
}
if sig == nil {
return nil, fmt.Errorf("no function signatures found for %s", obj.Name())
}
pkgStringer := qualifier(fAST, pkg.Types, pkg.TypesInfo)
var paramInfo []ParameterInformation
for i := 0; i < sig.Params().Len(); i++ {
param := sig.Params().At(i)
label := types.TypeString(param.Type(), pkgStringer)
if param.Name() != "" {
label = fmt.Sprintf("%s %s", param.Name(), label)
}
paramInfo = append(paramInfo, ParameterInformation{
Label: label,
})
}
// Determine the query position relative to the number of parameters in the function.
var activeParam int
var start, end token.Pos
for i, expr := range callExpr.Args {
if start == token.NoPos {
start = expr.Pos()
}
end = expr.End()
if i < len(callExpr.Args)-1 {
end = callExpr.Args[i+1].Pos() - 1 // comma
}
if start <= pos && pos <= end {
break
}
activeParam++
start = expr.Pos() + 1 // to account for commas
}
// Label for function, qualified by package name.
label := obj.Name()
if pkg := pkgStringer(obj.Pkg()); pkg != "" {
label = pkg + "." + label
}
return &SignatureInformation{
Label: label + formatParams(sig.Params(), sig.Variadic(), pkgStringer),
Parameters: paramInfo,
ActiveParameter: activeParam,
}, nil
}