github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/server.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package lsp implements LSP for gopls. 6 package lsp 7 8 import ( 9 "context" 10 "fmt" 11 "sync" 12 13 "github.com/jhump/golang-x-tools/internal/jsonrpc2" 14 "github.com/jhump/golang-x-tools/internal/lsp/progress" 15 "github.com/jhump/golang-x-tools/internal/lsp/protocol" 16 "github.com/jhump/golang-x-tools/internal/lsp/source" 17 "github.com/jhump/golang-x-tools/internal/span" 18 errors "golang.org/x/xerrors" 19 ) 20 21 const concurrentAnalyses = 1 22 23 // NewServer creates an LSP server and binds it to handle incoming client 24 // messages on on the supplied stream. 25 func NewServer(session source.Session, client protocol.ClientCloser) *Server { 26 tracker := progress.NewTracker(client) 27 session.SetProgressTracker(tracker) 28 return &Server{ 29 diagnostics: map[span.URI]*fileReports{}, 30 gcOptimizationDetails: make(map[string]struct{}), 31 watchedGlobPatterns: make(map[string]struct{}), 32 changedFiles: make(map[span.URI]struct{}), 33 session: session, 34 client: client, 35 diagnosticsSema: make(chan struct{}, concurrentAnalyses), 36 progress: tracker, 37 diagDebouncer: newDebouncer(), 38 watchedFileDebouncer: newDebouncer(), 39 } 40 } 41 42 type serverState int 43 44 const ( 45 serverCreated = serverState(iota) 46 serverInitializing // set once the server has received "initialize" request 47 serverInitialized // set once the server has received "initialized" request 48 serverShutDown 49 ) 50 51 func (s serverState) String() string { 52 switch s { 53 case serverCreated: 54 return "created" 55 case serverInitializing: 56 return "initializing" 57 case serverInitialized: 58 return "initialized" 59 case serverShutDown: 60 return "shutDown" 61 } 62 return fmt.Sprintf("(unknown state: %d)", int(s)) 63 } 64 65 // Server implements the protocol.Server interface. 66 type Server struct { 67 client protocol.ClientCloser 68 69 stateMu sync.Mutex 70 state serverState 71 // notifications generated before serverInitialized 72 notifications []*protocol.ShowMessageParams 73 74 session source.Session 75 76 tempDir string 77 78 // changedFiles tracks files for which there has been a textDocument/didChange. 79 changedFilesMu sync.Mutex 80 changedFiles map[span.URI]struct{} 81 82 // folders is only valid between initialize and initialized, and holds the 83 // set of folders to build views for when we are ready 84 pendingFolders []protocol.WorkspaceFolder 85 86 // watchedGlobPatterns is the set of glob patterns that we have requested 87 // the client watch on disk. It will be updated as the set of directories 88 // that the server should watch changes. 89 watchedGlobPatternsMu sync.Mutex 90 watchedGlobPatterns map[string]struct{} 91 watchRegistrationCount int 92 93 diagnosticsMu sync.Mutex 94 diagnostics map[span.URI]*fileReports 95 96 // gcOptimizationDetails describes the packages for which we want 97 // optimization details to be included in the diagnostics. The key is the 98 // ID of the package. 99 gcOptimizationDetailsMu sync.Mutex 100 gcOptimizationDetails map[string]struct{} 101 102 // diagnosticsSema limits the concurrency of diagnostics runs, which can be 103 // expensive. 104 diagnosticsSema chan struct{} 105 106 progress *progress.Tracker 107 108 // diagDebouncer is used for debouncing diagnostics. 109 diagDebouncer *debouncer 110 111 // watchedFileDebouncer is used for batching didChangeWatchedFiles notifications. 112 watchedFileDebouncer *debouncer 113 fileChangeMu sync.Mutex 114 pendingOnDiskChanges []*pendingModificationSet 115 116 // When the workspace fails to load, we show its status through a progress 117 // report with an error message. 118 criticalErrorStatusMu sync.Mutex 119 criticalErrorStatus *progress.WorkDone 120 } 121 122 type pendingModificationSet struct { 123 diagnoseDone chan struct{} 124 changes []source.FileModification 125 } 126 127 func (s *Server) workDoneProgressCancel(ctx context.Context, params *protocol.WorkDoneProgressCancelParams) error { 128 return s.progress.Cancel(ctx, params.Token) 129 } 130 131 func (s *Server) nonstandardRequest(ctx context.Context, method string, params interface{}) (interface{}, error) { 132 switch method { 133 case "gopls/diagnoseFiles": 134 paramMap := params.(map[string]interface{}) 135 for _, file := range paramMap["files"].([]interface{}) { 136 snapshot, fh, ok, release, err := s.beginFileRequest(ctx, protocol.DocumentURI(file.(string)), source.UnknownKind) 137 defer release() 138 if !ok { 139 return nil, err 140 } 141 142 fileID, diagnostics, err := source.FileDiagnostics(ctx, snapshot, fh.URI()) 143 if err != nil { 144 return nil, err 145 } 146 if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{ 147 URI: protocol.URIFromSpanURI(fh.URI()), 148 Diagnostics: toProtocolDiagnostics(diagnostics), 149 Version: fileID.Version, 150 }); err != nil { 151 return nil, err 152 } 153 } 154 if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{ 155 URI: "gopls://diagnostics-done", 156 }); err != nil { 157 return nil, err 158 } 159 return struct{}{}, nil 160 } 161 return nil, notImplemented(method) 162 } 163 164 func notImplemented(method string) error { 165 return errors.Errorf("%w: %q not yet implemented", jsonrpc2.ErrMethodNotFound, method) 166 } 167 168 //go:generate helper/helper -d protocol/tsserver.go -o server_gen.go -u .