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