diff --git a/internal/lsp/server.go b/internal/lsp/server.go index b8b09525..30c74178 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -57,6 +57,7 @@ func (s *server) Initialize(ctx context.Context, params *protocol.InitializePara DefinitionProvider: true, DocumentFormattingProvider: true, DocumentRangeFormattingProvider: true, + HoverProvider: true, SignatureHelpProvider: protocol.SignatureHelpOptions{ TriggerCharacters: []string{"(", ","}, }, @@ -184,8 +185,24 @@ func (s *server) CompletionResolve(context.Context, *protocol.CompletionItem) (* return nil, notImplemented("CompletionResolve") } -func (s *server) Hover(context.Context, *protocol.TextDocumentPositionParams) (*protocol.Hover, error) { - return nil, notImplemented("Hover") +func (s *server) Hover(ctx context.Context, params *protocol.TextDocumentPositionParams) (*protocol.Hover, 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) + contents, rng, err := source.Hover(ctx, f, pos) + if err != nil { + return nil, err + } + return &protocol.Hover{ + Contents: protocol.MarkupContent{ + Kind: protocol.Markdown, + Value: contents, + }, + Range: toProtocolRange(tok, rng), + }, nil } func (s *server) SignatureHelp(ctx context.Context, params *protocol.TextDocumentPositionParams) (*protocol.SignatureHelp, error) { diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go index 5f8e3d80..5524de21 100644 --- a/internal/lsp/source/completion.go +++ b/internal/lsp/source/completion.go @@ -101,8 +101,8 @@ func completions(file *ast.File, pos token.Pos, fset *token.FileSet, pkg *types. } // The position is within a composite literal. - if items, ok := complit(path, pos, pkg, info, found); ok { - return items, "", nil + if items, prefix, ok := complit(path, pos, pkg, info, found); ok { + return items, prefix, nil } switch n := path[0].(type) { case *ast.Ident: @@ -250,7 +250,7 @@ func lexical(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Inf // complit finds completions for field names inside a composite literal. // It reports whether the node was handled as part of a composite literal. -func complit(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Info, found finder) (items []CompletionItem, ok bool) { +func complit(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Info, found finder) (items []CompletionItem, prefix string, ok bool) { var lit *ast.CompositeLit // First, determine if the pos is within a composite literal. @@ -286,6 +286,8 @@ func complit(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Inf } } case *ast.Ident: + prefix = n.Name[:pos-n.Pos()] + // If the enclosing node is an identifier, it can either be an identifier that is // part of a composite literal (e.g. &x{fo<>}), or it can be an identifier that is // part of a key-value expression, which is part of a composite literal (e.g. &x{foo: ba<>). @@ -311,7 +313,7 @@ func complit(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Inf } // We are not in a composite literal. if lit == nil { - return nil, false + return nil, prefix, false } // Mark fields of the composite literal that have already been set, // except for the current field. @@ -351,10 +353,10 @@ func complit(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Inf if !hasKeys && structPkg == pkg { items = append(items, lexical(path, pos, pkg, info, found)...) } - return items, true + return items, prefix, true } } - return items, false + return items, prefix, false } // formatCompletion creates a completion item for a given types.Object. diff --git a/internal/lsp/source/definition.go b/internal/lsp/source/definition.go index b4f05f48..7990325e 100644 --- a/internal/lsp/source/definition.go +++ b/internal/lsp/source/definition.go @@ -30,7 +30,7 @@ func Definition(ctx context.Context, f *File, pos token.Pos) (Range, error) { return Range{}, err } if i.ident == nil { - return Range{}, fmt.Errorf("definition was not a valid identifier") + return Range{}, fmt.Errorf("not a valid identifier") } obj := pkg.TypesInfo.ObjectOf(i.ident) if obj == nil { diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go new file mode 100644 index 00000000..2c04a884 --- /dev/null +++ b/internal/lsp/source/hover.go @@ -0,0 +1,50 @@ +// 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/token" + "go/types" +) + +func Hover(ctx context.Context, f *File, pos token.Pos) (string, Range, error) { + fAST, err := f.GetAST() + if err != nil { + return "", Range{}, err + } + pkg, err := f.GetPackage() + if err != nil { + return "", Range{}, err + } + i, err := findIdentifier(fAST, pos) + if err != nil { + return "", Range{}, err + } + if i.ident == nil { + return "", Range{}, fmt.Errorf("not a valid identifier") + } + obj := pkg.TypesInfo.ObjectOf(i.ident) + if obj == nil { + return "", Range{}, fmt.Errorf("no object") + } + if i.wasEmbeddedField { + // the original position was on the embedded field declaration + // so we try to dig out the type and jump to that instead + if v, ok := obj.(*types.Var); ok { + if n, ok := v.Type().(*types.Named); ok { + obj = n.Obj() + } + } + } + // TODO(rstambler): Add documentation and improve quality of object string. + content := types.ObjectString(obj, qualifier(fAST, pkg.Types, pkg.TypesInfo)) + markdown := "```go\n" + content + "\n```" + return markdown, Range{ + Start: i.ident.Pos(), + End: i.ident.End(), + }, nil +}