blob: 905a4081ba247c8ae6b385092ea93981c5374309 [file] [log] [blame]
// 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),
gcOptimizationDetails: make(map[span.URI]struct{}),
watchedDirectories: make(map[span.URI]struct{}),
changedFiles: make(map[span.URI]struct{}),
session: session,
client: client,
diagnosticsSema: make(chan struct{}, concurrentAnalyses),
progress: newProgressTracker(client),
debouncer: newDebouncer(),
}
}
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
clientPID int
// notifications generated before serverInitialized
notifications []*protocol.ShowMessageParams
// changedFiles tracks files for which there has been a textDocument/didChange.
changedFilesMu sync.Mutex
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
// watchedDirectories is the set of directories that we have requested that
// the client watch on disk. It will be updated as the set of directories
// that the server should watch changes.
watchedDirectoriesMu sync.Mutex
watchedDirectories map[span.URI]struct{}
watchRegistrationCount uint64
// delivered is a cache of the diagnostics that the server has sent.
deliveredMu sync.Mutex
delivered map[span.URI]sentDiagnostics
// gcOptimizationDetails describes the packages for which we want
// optimization details to be included in the diagnostics. The key is the
// directory of the package.
gcOptimizationDetailsMu sync.Mutex
gcOptimizationDetails map[span.URI]struct{}
// diagnosticsSema limits the concurrency of diagnostics runs, which can be
// expensive.
diagnosticsSema chan struct{}
progress *progressTracker
// debouncer is used for debouncing diagnostics.
debouncer *debouncer
// When the workspace fails to load, we show its status through a progress
// report with an error message.
criticalErrorStatusMu sync.Mutex
criticalErrorStatus *workDone
}
// sentDiagnostics is used to cache diagnostics that have been sent for a given file.
type sentDiagnostics struct {
id source.VersionedFileIdentity
sorted []*source.Diagnostic
includeAnalysis bool
snapshotID uint64
}
func (s *Server) workDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error {
return s.progress.cancel(ctx, params.Token)
}
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, release, err := s.beginFileRequest(ctx, protocol.DocumentURI(file.(string)), source.UnknownKind)
defer release()
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 notImplemented(method string) error {
return errors.Errorf("%w: %q not yet implemented", jsonrpc2.ErrMethodNotFound, method)
}
//go:generate helper/helper -d protocol/tsserver.go -o server_gen.go -u .