1
0
mirror of https://github.com/golang/go synced 2024-11-18 20:44:45 -07:00
go/internal/lsp/server.go
Rebecca Stambler 57eff0d8ac internal/lsp: add support for running goimports as a code action
This change adds support for goimports as a code action that can be run
on save. However, there do appear to be issues with the propagation of
the context.Only field of the CodeActionParams, so we treat every
codeAction as an organizeImports action - this should be fixed in the
next vscode-languageclient release
(https://github.com/Microsoft/vscode-languageserver-node/issues/442).

Change-Id: I64ca0034c393762248fde6521aba86ed9d41bf70
Reviewed-on: https://go-review.googlesource.com/c/154338
Reviewed-by: Ian Cottrell <iancottrell@google.com>
2018-12-17 20:01:16 +00:00

319 lines
10 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"
"os"
"sync"
"golang.org/x/tools/internal/jsonrpc2"
"golang.org/x/tools/internal/lsp/cache"
"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 *cache.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.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
rootPath, err := source.URI(*params.RootURI).Filename()
if err != nil {
return nil, err
}
s.view = cache.NewView(rootPath)
return &protocol.InitializeResult{
Capabilities: protocol.ServerCapabilities{
CodeActionProvider: true,
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.CacheAndDiagnose(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.CacheAndDiagnose(ctx, params.TextDocument.URI, change.Text)
}
return nil
}
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(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
edits, err := organizeImports(ctx, s.view, params.TextDocument.URI)
if err != nil {
return nil, err
}
return []protocol.CodeAction{
{
Title: "Organize Imports",
Kind: protocol.SourceOrganizeImports,
Edit: protocol.WorkspaceEdit{
Changes: map[protocol.DocumentURI][]protocol.TextEdit{
params.TextDocument.URI: edits,
},
},
},
}, nil
}
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, &params.Range)
}
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)
}