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>
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index 4c4270f..35d9191 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -43,14 +43,15 @@
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 @@
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) {
diff --git a/internal/lsp/signature_help.go b/internal/lsp/signature_help.go
new file mode 100644
index 0000000..0e54a50
--- /dev/null
+++ b/internal/lsp/signature_help.go
@@ -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
+}
diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go
new file mode 100644
index 0000000..ecccdd9
--- /dev/null
+++ b/internal/lsp/source/signature_help.go
@@ -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
+}