// 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), gcOptimizatonDetails: 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 // 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 gcOptimizatonDetails 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 } // sentDiagnostics is used to cache diagnostics that have been sent for a given file. type sentDiagnostics struct { id source.VersionedFileIdentity sorted []*source.Diagnostic withAnalysis 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 .