mirror of
https://github.com/golang/go
synced 2024-11-05 15:06:09 -07:00
5ea363182e
Our approach to commands and their arguments has been ad-hoc until this point. This CL creates a standard way of defining and passing the arguments to different commands. The arguments to a command are now json.RawMessages, so that we don't have to double encode. This also allows us to check the expected number of arguments without defining a struct for every command. Change-Id: Ic765c9b059e8ec3e1985046d13bf321be21f16ab Reviewed-on: https://go-review.googlesource.com/c/tools/+/242697 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
161 lines
4.5 KiB
Go
161 lines
4.5 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 implements LSP for gopls.
|
|
package lsp
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"golang.org/x/tools/internal/jsonrpc2"
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
"golang.org/x/tools/internal/lsp/source"
|
|
"golang.org/x/tools/internal/span"
|
|
errors "golang.org/x/xerrors"
|
|
)
|
|
|
|
const concurrentAnalyses = 1
|
|
|
|
// NewServer creates an LSP server and binds it to handle incoming client
|
|
// messages on on the supplied stream.
|
|
func NewServer(session source.Session, client protocol.Client) *Server {
|
|
return &Server{
|
|
delivered: make(map[span.URI]sentDiagnostics),
|
|
session: session,
|
|
client: client,
|
|
diagnosticsSema: make(chan struct{}, concurrentAnalyses),
|
|
}
|
|
}
|
|
|
|
type serverState int
|
|
|
|
const (
|
|
serverCreated = serverState(iota)
|
|
serverInitializing // set once the server has received "initialize" request
|
|
serverInitialized // set once the server has received "initialized" request
|
|
serverShutDown
|
|
)
|
|
|
|
func (s serverState) String() string {
|
|
switch s {
|
|
case serverCreated:
|
|
return "created"
|
|
case serverInitializing:
|
|
return "initializing"
|
|
case serverInitialized:
|
|
return "initialized"
|
|
case serverShutDown:
|
|
return "shutDown"
|
|
}
|
|
return fmt.Sprintf("(unknown state: %d)", int(s))
|
|
}
|
|
|
|
// Server implements the protocol.Server interface.
|
|
type Server struct {
|
|
client protocol.Client
|
|
|
|
stateMu sync.Mutex
|
|
state serverState
|
|
|
|
session source.Session
|
|
|
|
// changedFiles tracks files for which there has been a textDocument/didChange.
|
|
changedFiles map[span.URI]struct{}
|
|
|
|
// folders is only valid between initialize and initialized, and holds the
|
|
// set of folders to build views for when we are ready
|
|
pendingFolders []protocol.WorkspaceFolder
|
|
|
|
// delivered is a cache of the diagnostics that the server has sent.
|
|
deliveredMu sync.Mutex
|
|
delivered map[span.URI]sentDiagnostics
|
|
|
|
// diagnosticsSema limits the concurrency of diagnostics runs, which can be expensive.
|
|
diagnosticsSema chan struct{}
|
|
|
|
// supportsWorkDoneProgress is set in the initializeRequest
|
|
// to determine if the client can support progress notifications
|
|
supportsWorkDoneProgress bool
|
|
inProgressMu sync.Mutex
|
|
inProgress map[string]*WorkDone
|
|
}
|
|
|
|
// sentDiagnostics is used to cache diagnostics that have been sent for a given file.
|
|
type sentDiagnostics struct {
|
|
version float64
|
|
identifier string
|
|
sorted []*source.Diagnostic
|
|
withAnalysis bool
|
|
snapshotID uint64
|
|
}
|
|
|
|
func (s *Server) nonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) {
|
|
paramMap := params.(map[string]interface{})
|
|
if method == "gopls/diagnoseFiles" {
|
|
for _, file := range paramMap["files"].([]interface{}) {
|
|
snapshot, fh, ok, err := s.beginFileRequest(ctx, protocol.DocumentURI(file.(string)), source.UnknownKind)
|
|
if !ok {
|
|
return nil, err
|
|
}
|
|
|
|
fileID, diagnostics, err := source.FileDiagnostics(ctx, snapshot, fh.URI())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
|
|
URI: protocol.URIFromSpanURI(fh.URI()),
|
|
Diagnostics: toProtocolDiagnostics(diagnostics),
|
|
Version: fileID.Version,
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
|
|
URI: "gopls://diagnostics-done",
|
|
}); err != nil {
|
|
return nil, err
|
|
}
|
|
return struct{}{}, nil
|
|
}
|
|
return nil, notImplemented(method)
|
|
}
|
|
|
|
func (s *Server) workDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error {
|
|
token, ok := params.Token.(string)
|
|
if !ok {
|
|
return errors.Errorf("expected params.Token to be string but got %T", params.Token)
|
|
}
|
|
s.inProgressMu.Lock()
|
|
defer s.inProgressMu.Unlock()
|
|
wd, ok := s.inProgress[token]
|
|
if !ok {
|
|
return errors.Errorf("token %q not found in progress", token)
|
|
}
|
|
if wd.cancel == nil {
|
|
return errors.Errorf("work %q is not cancellable", token)
|
|
}
|
|
wd.cancel()
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) addInProgress(wd *WorkDone) {
|
|
s.inProgressMu.Lock()
|
|
s.inProgress[wd.token] = wd
|
|
s.inProgressMu.Unlock()
|
|
}
|
|
|
|
func (s *Server) removeInProgress(token string) {
|
|
s.inProgressMu.Lock()
|
|
delete(s.inProgress, token)
|
|
s.inProgressMu.Unlock()
|
|
}
|
|
|
|
func notImplemented(method string) error {
|
|
return fmt.Errorf("%w: %q not yet implemented", jsonrpc2.ErrMethodNotFound, method)
|
|
}
|
|
|
|
//go:generate helper/helper -d protocol/tsserver.go -o server_gen.go -u .
|