From 7e59e591a2617a3067db5fe2fe8e903b4c1b10cc Mon Sep 17 00:00:00 2001 From: Rebecca Stambler Date: Mon, 12 Nov 2018 16:53:10 -0500 Subject: [PATCH] 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 Run-TryBot: Rebecca Stambler --- internal/lsp/server.go | 23 +++-- internal/lsp/signature_help.go | 33 +++++++ internal/lsp/source/signature_help.go | 118 ++++++++++++++++++++++++++ 3 files changed, 168 insertions(+), 6 deletions(-) create mode 100644 internal/lsp/signature_help.go create mode 100644 internal/lsp/source/signature_help.go diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 4c4270fba3..35d91915ea 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -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) { diff --git a/internal/lsp/signature_help.go b/internal/lsp/signature_help.go new file mode 100644 index 0000000000..0e54a507d5 --- /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 0000000000..ecccdd9fe9 --- /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 +}