mirror of
https://github.com/golang/go
synced 2024-11-18 18:54:42 -07:00
62e1d13d53
This change adds a very simple implementation of hovering. It doesn't show any documentation, just the object string for the given object. Also, this change sets the prefix for composite literals, making sure we don't insert duplicate text. Change-Id: Ib706ec821a9e459a6c61c10f5dd28d1798944fa3 Reviewed-on: https://go-review.googlesource.com/c/152599 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
367 lines
12 KiB
Go
367 lines
12 KiB
Go
// 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 (
|
|
"context"
|
|
"go/token"
|
|
"os"
|
|
"sync"
|
|
|
|
"golang.org/x/tools/internal/jsonrpc2"
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
"golang.org/x/tools/internal/lsp/source"
|
|
)
|
|
|
|
// RunServer starts an LSP server on the supplied stream, and waits until the
|
|
// stream is closed.
|
|
func RunServer(ctx context.Context, stream jsonrpc2.Stream, opts ...interface{}) error {
|
|
s := &server{}
|
|
conn, client := protocol.RunServer(ctx, stream, s, opts...)
|
|
s.client = client
|
|
return conn.Wait(ctx)
|
|
}
|
|
|
|
type server struct {
|
|
client protocol.Client
|
|
|
|
initializedMu sync.Mutex
|
|
initialized bool // set once the server has received "initialize" request
|
|
|
|
signatureHelpEnabled bool
|
|
snippetsSupported bool
|
|
|
|
view *source.View
|
|
}
|
|
|
|
func (s *server) Initialize(ctx context.Context, params *protocol.InitializeParams) (*protocol.InitializeResult, error) {
|
|
s.initializedMu.Lock()
|
|
defer s.initializedMu.Unlock()
|
|
if s.initialized {
|
|
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server already initialized")
|
|
}
|
|
s.view = source.NewView()
|
|
s.initialized = true // mark server as initialized now
|
|
|
|
// Check if the client supports snippets in completion items.
|
|
s.snippetsSupported = params.Capabilities.TextDocument.Completion.CompletionItem.SnippetSupport
|
|
s.signatureHelpEnabled = true
|
|
|
|
return &protocol.InitializeResult{
|
|
Capabilities: protocol.ServerCapabilities{
|
|
CompletionProvider: protocol.CompletionOptions{
|
|
TriggerCharacters: []string{"."},
|
|
},
|
|
DefinitionProvider: true,
|
|
DocumentFormattingProvider: true,
|
|
DocumentRangeFormattingProvider: true,
|
|
HoverProvider: true,
|
|
SignatureHelpProvider: protocol.SignatureHelpOptions{
|
|
TriggerCharacters: []string{"(", ","},
|
|
},
|
|
TextDocumentSync: protocol.TextDocumentSyncOptions{
|
|
Change: float64(protocol.Full), // full contents of file sent on each update
|
|
OpenClose: true,
|
|
},
|
|
TypeDefinitionProvider: true,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (s *server) Initialized(context.Context, *protocol.InitializedParams) error {
|
|
return nil // ignore
|
|
}
|
|
|
|
func (s *server) Shutdown(context.Context) error {
|
|
s.initializedMu.Lock()
|
|
defer s.initializedMu.Unlock()
|
|
if !s.initialized {
|
|
return jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server not initialized")
|
|
}
|
|
s.initialized = false
|
|
return nil
|
|
}
|
|
|
|
func (s *server) Exit(ctx context.Context) error {
|
|
if s.initialized {
|
|
os.Exit(1)
|
|
}
|
|
os.Exit(0)
|
|
return nil
|
|
}
|
|
|
|
func (s *server) DidChangeWorkspaceFolders(context.Context, *protocol.DidChangeWorkspaceFoldersParams) error {
|
|
return notImplemented("DidChangeWorkspaceFolders")
|
|
}
|
|
|
|
func (s *server) DidChangeConfiguration(context.Context, *protocol.DidChangeConfigurationParams) error {
|
|
return notImplemented("DidChangeConfiguration")
|
|
}
|
|
|
|
func (s *server) DidChangeWatchedFiles(context.Context, *protocol.DidChangeWatchedFilesParams) error {
|
|
return notImplemented("DidChangeWatchedFiles")
|
|
}
|
|
|
|
func (s *server) Symbols(context.Context, *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) {
|
|
return nil, notImplemented("Symbols")
|
|
}
|
|
|
|
func (s *server) ExecuteCommand(context.Context, *protocol.ExecuteCommandParams) (interface{}, error) {
|
|
return nil, notImplemented("ExecuteCommand")
|
|
}
|
|
|
|
func (s *server) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
|
|
s.cacheAndDiagnoseFile(ctx, params.TextDocument.URI, params.TextDocument.Text)
|
|
return nil
|
|
}
|
|
|
|
func (s *server) DidChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error {
|
|
if len(params.ContentChanges) < 1 {
|
|
return jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "no content changes provided")
|
|
}
|
|
// We expect the full content of file, i.e. a single change with no range.
|
|
if change := params.ContentChanges[0]; change.RangeLength == 0 {
|
|
s.cacheAndDiagnoseFile(ctx, params.TextDocument.URI, change.Text)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *server) cacheAndDiagnoseFile(ctx context.Context, uri protocol.DocumentURI, text string) {
|
|
f := s.view.GetFile(source.URI(uri))
|
|
f.SetContent([]byte(text))
|
|
go func() {
|
|
f := s.view.GetFile(source.URI(uri))
|
|
reports, err := source.Diagnostics(ctx, s.view, f)
|
|
if err != nil {
|
|
return // handle error?
|
|
}
|
|
for filename, diagnostics := range reports {
|
|
s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
|
|
URI: protocol.DocumentURI(source.ToURI(filename)),
|
|
Diagnostics: toProtocolDiagnostics(s.view, diagnostics),
|
|
})
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (s *server) WillSave(context.Context, *protocol.WillSaveTextDocumentParams) error {
|
|
return notImplemented("WillSave")
|
|
}
|
|
|
|
func (s *server) WillSaveWaitUntil(context.Context, *protocol.WillSaveTextDocumentParams) ([]protocol.TextEdit, error) {
|
|
return nil, notImplemented("WillSaveWaitUntil")
|
|
}
|
|
|
|
func (s *server) DidSave(context.Context, *protocol.DidSaveTextDocumentParams) error {
|
|
// TODO(rstambler): Should we clear the cache here?
|
|
return nil // ignore
|
|
}
|
|
|
|
func (s *server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error {
|
|
s.view.GetFile(source.URI(params.TextDocument.URI)).SetContent(nil)
|
|
return nil
|
|
}
|
|
|
|
func (s *server) Completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, 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)
|
|
items, prefix, err := source.Completion(ctx, f, pos)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &protocol.CompletionList{
|
|
IsIncomplete: false,
|
|
Items: toProtocolCompletionItems(items, prefix, params.Position, s.snippetsSupported, s.signatureHelpEnabled),
|
|
}, nil
|
|
}
|
|
|
|
func (s *server) CompletionResolve(context.Context, *protocol.CompletionItem) (*protocol.CompletionItem, error) {
|
|
return nil, notImplemented("CompletionResolve")
|
|
}
|
|
|
|
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) {
|
|
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) {
|
|
f := s.view.GetFile(source.URI(params.TextDocument.URI))
|
|
tok, err := f.GetToken()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pos := fromProtocolPosition(tok, params.Position)
|
|
r, err := source.Definition(ctx, f, pos)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []protocol.Location{toProtocolLocation(s.view.Config.Fset, r)}, nil
|
|
}
|
|
|
|
func (s *server) TypeDefinition(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.Location, 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)
|
|
r, err := source.TypeDefinition(ctx, f, pos)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return []protocol.Location{toProtocolLocation(s.view.Config.Fset, r)}, nil
|
|
}
|
|
|
|
func (s *server) Implementation(context.Context, *protocol.TextDocumentPositionParams) ([]protocol.Location, error) {
|
|
return nil, notImplemented("Implementation")
|
|
}
|
|
|
|
func (s *server) References(context.Context, *protocol.ReferenceParams) ([]protocol.Location, error) {
|
|
return nil, notImplemented("References")
|
|
}
|
|
|
|
func (s *server) DocumentHighlight(context.Context, *protocol.TextDocumentPositionParams) ([]protocol.DocumentHighlight, error) {
|
|
return nil, notImplemented("DocumentHighlight")
|
|
}
|
|
|
|
func (s *server) DocumentSymbol(context.Context, *protocol.DocumentSymbolParams) ([]protocol.DocumentSymbol, error) {
|
|
return nil, notImplemented("DocumentSymbol")
|
|
}
|
|
|
|
func (s *server) CodeAction(context.Context, *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
|
|
return nil, notImplemented("CodeAction")
|
|
}
|
|
|
|
func (s *server) CodeLens(context.Context, *protocol.CodeLensParams) ([]protocol.CodeLens, error) {
|
|
return nil, nil // ignore
|
|
}
|
|
|
|
func (s *server) CodeLensResolve(context.Context, *protocol.CodeLens) (*protocol.CodeLens, error) {
|
|
return nil, notImplemented("CodeLensResolve")
|
|
}
|
|
|
|
func (s *server) DocumentLink(context.Context, *protocol.DocumentLinkParams) ([]protocol.DocumentLink, error) {
|
|
return nil, nil // ignore
|
|
}
|
|
|
|
func (s *server) DocumentLinkResolve(context.Context, *protocol.DocumentLink) (*protocol.DocumentLink, error) {
|
|
return nil, notImplemented("DocumentLinkResolve")
|
|
}
|
|
|
|
func (s *server) DocumentColor(context.Context, *protocol.DocumentColorParams) ([]protocol.ColorInformation, error) {
|
|
return nil, notImplemented("DocumentColor")
|
|
}
|
|
|
|
func (s *server) ColorPresentation(context.Context, *protocol.ColorPresentationParams) ([]protocol.ColorPresentation, error) {
|
|
return nil, notImplemented("ColorPresentation")
|
|
}
|
|
|
|
func (s *server) Formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) {
|
|
return formatRange(ctx, s.view, params.TextDocument.URI, nil)
|
|
}
|
|
|
|
func (s *server) RangeFormatting(ctx context.Context, params *protocol.DocumentRangeFormattingParams) ([]protocol.TextEdit, error) {
|
|
return formatRange(ctx, s.view, params.TextDocument.URI, ¶ms.Range)
|
|
}
|
|
|
|
// formatRange formats a document with a given range.
|
|
func formatRange(ctx context.Context, v *source.View, uri protocol.DocumentURI, rng *protocol.Range) ([]protocol.TextEdit, error) {
|
|
f := v.GetFile(source.URI(uri))
|
|
tok, err := f.GetToken()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var r source.Range
|
|
if rng == nil {
|
|
r.Start = tok.Pos(0)
|
|
r.End = tok.Pos(tok.Size())
|
|
} else {
|
|
r = fromProtocolRange(tok, *rng)
|
|
}
|
|
content, err := f.Read()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
edits, err := source.Format(ctx, f, r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return toProtocolEdits(tok, content, edits), nil
|
|
}
|
|
|
|
func toProtocolEdits(tok *token.File, content []byte, edits []source.TextEdit) []protocol.TextEdit {
|
|
if edits == nil {
|
|
return nil
|
|
}
|
|
// When a file ends with an empty line, the newline character is counted
|
|
// as part of the previous line. This causes the formatter to insert
|
|
// another unnecessary newline on each formatting. We handle this case by
|
|
// checking if the file already ends with a newline character.
|
|
hasExtraNewline := content[len(content)-1] == '\n'
|
|
result := make([]protocol.TextEdit, len(edits))
|
|
for i, edit := range edits {
|
|
rng := toProtocolRange(tok, edit.Range)
|
|
// If the edit ends at the end of the file, add the extra line.
|
|
if hasExtraNewline && tok.Offset(edit.Range.End) == len(content) {
|
|
rng.End.Line++
|
|
rng.End.Character = 0
|
|
}
|
|
result[i] = protocol.TextEdit{
|
|
Range: rng,
|
|
NewText: edit.NewText,
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (s *server) OnTypeFormatting(context.Context, *protocol.DocumentOnTypeFormattingParams) ([]protocol.TextEdit, error) {
|
|
return nil, notImplemented("OnTypeFormatting")
|
|
}
|
|
|
|
func (s *server) Rename(context.Context, *protocol.RenameParams) ([]protocol.WorkspaceEdit, error) {
|
|
return nil, notImplemented("Rename")
|
|
}
|
|
|
|
func (s *server) FoldingRanges(context.Context, *protocol.FoldingRangeRequestParam) ([]protocol.FoldingRange, error) {
|
|
return nil, notImplemented("FoldingRanges")
|
|
}
|
|
|
|
func notImplemented(method string) *jsonrpc2.Error {
|
|
return jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not yet implemented", method)
|
|
}
|