aboutsummaryrefslogtreecommitdiff
path: root/gopls/internal/lsp/server.go
diff options
context:
space:
mode:
Diffstat (limited to 'gopls/internal/lsp/server.go')
-rw-r--r--gopls/internal/lsp/server.go158
1 files changed, 158 insertions, 0 deletions
diff --git a/gopls/internal/lsp/server.go b/gopls/internal/lsp/server.go
new file mode 100644
index 000000000..13441c40b
--- /dev/null
+++ b/gopls/internal/lsp/server.go
@@ -0,0 +1,158 @@
+// 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.
+
+//go:generate go run ./helper -d protocol/tsserver.go -o server_gen.go -u .
+
+// Package lsp implements LSP for gopls.
+package lsp
+
+import (
+ "context"
+ "fmt"
+ "sync"
+
+ "golang.org/x/tools/gopls/internal/lsp/cache"
+ "golang.org/x/tools/gopls/internal/lsp/progress"
+ "golang.org/x/tools/gopls/internal/lsp/protocol"
+ "golang.org/x/tools/gopls/internal/lsp/source"
+ "golang.org/x/tools/gopls/internal/span"
+ "golang.org/x/tools/internal/jsonrpc2"
+)
+
+const concurrentAnalyses = 1
+
+// NewServer creates an LSP server and binds it to handle incoming client
+// messages on on the supplied stream.
+func NewServer(session *cache.Session, client protocol.ClientCloser) *Server {
+ return &Server{
+ diagnostics: map[span.URI]*fileReports{},
+ gcOptimizationDetails: make(map[source.PackageID]struct{}),
+ watchedGlobPatterns: make(map[string]struct{}),
+ changedFiles: make(map[span.URI]struct{}),
+ session: session,
+ client: client,
+ diagnosticsSema: make(chan struct{}, concurrentAnalyses),
+ progress: progress.NewTracker(client),
+ diagDebouncer: 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.ClientCloser
+
+ stateMu sync.Mutex
+ state serverState
+ // notifications generated before serverInitialized
+ notifications []*protocol.ShowMessageParams
+
+ session *cache.Session
+
+ tempDir string
+
+ // 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
+
+ // watchedGlobPatterns is the set of glob patterns that we have requested
+ // the client watch on disk. It will be updated as the set of directories
+ // that the server should watch changes.
+ watchedGlobPatternsMu sync.Mutex
+ watchedGlobPatterns map[string]struct{}
+ watchRegistrationCount int
+
+ diagnosticsMu sync.Mutex
+ diagnostics map[span.URI]*fileReports
+
+ // gcOptimizationDetails describes the packages for which we want
+ // optimization details to be included in the diagnostics. The key is the
+ // ID of the package.
+ gcOptimizationDetailsMu sync.Mutex
+ gcOptimizationDetails map[source.PackageID]struct{}
+
+ // diagnosticsSema limits the concurrency of diagnostics runs, which can be
+ // expensive.
+ diagnosticsSema chan struct{}
+
+ progress *progress.Tracker
+
+ // diagDebouncer is used for debouncing diagnostics.
+ diagDebouncer *debouncer
+
+ // When the workspace fails to load, we show its status through a progress
+ // report with an error message.
+ criticalErrorStatusMu sync.Mutex
+ criticalErrorStatus *progress.WorkDone
+}
+
+func (s *Server) workDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error {
+ return s.progress.Cancel(params.Token)
+}
+
+func (s *Server) nonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) {
+ switch method {
+ case "gopls/diagnoseFiles":
+ paramMap := params.(map[string]interface{})
+ // TODO(adonovan): opt: parallelize FileDiagnostics(URI...), either
+ // by calling it in multiple goroutines or, better, by making
+ // the relevant APIs accept a set of URIs/packages.
+ 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 fmt.Errorf("%w: %q not yet implemented", jsonrpc2.ErrMethodNotFound, method)
+}