1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
// 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{
diagnostics: map[span.URI]*fileReports{},
gcOptimizationDetails: make(map[span.URI]struct{}),
watchedGlobPatterns: make(map[string]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
// 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
// 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
}
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) {
switch method {
case "gopls/diagnoseFiles":
paramMap := params.(map[string]interface{})
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 .
|