github.com/april1989/origin-go-tools@v0.0.32/internal/lsp/general.go (about) 1 // Copyright 2019 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 6 7 import ( 8 "bytes" 9 "context" 10 "fmt" 11 "io" 12 "os" 13 "path" 14 "path/filepath" 15 "strings" 16 "sync" 17 18 "github.com/april1989/origin-go-tools/internal/event" 19 "github.com/april1989/origin-go-tools/internal/jsonrpc2" 20 "github.com/april1989/origin-go-tools/internal/lsp/debug" 21 "github.com/april1989/origin-go-tools/internal/lsp/debug/tag" 22 "github.com/april1989/origin-go-tools/internal/lsp/protocol" 23 "github.com/april1989/origin-go-tools/internal/lsp/source" 24 "github.com/april1989/origin-go-tools/internal/span" 25 errors "golang.org/x/xerrors" 26 ) 27 28 func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) { 29 s.stateMu.Lock() 30 if s.state >= serverInitializing { 31 defer s.stateMu.Unlock() 32 return nil, errors.Errorf("%w: initialize called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state) 33 } 34 s.state = serverInitializing 35 s.stateMu.Unlock() 36 37 s.progress.supportsWorkDoneProgress = params.Capabilities.Window.WorkDoneProgress 38 39 options := s.session.Options() 40 defer func() { s.session.SetOptions(options) }() 41 42 if err := s.handleOptionResults(ctx, source.SetOptions(&options, params.InitializationOptions)); err != nil { 43 return nil, err 44 } 45 options.ForClientCapabilities(params.Capabilities) 46 47 folders := params.WorkspaceFolders 48 if len(folders) == 0 { 49 if params.RootURI != "" { 50 folders = []protocol.WorkspaceFolder{{ 51 URI: string(params.RootURI), 52 Name: path.Base(params.RootURI.SpanURI().Filename()), 53 }} 54 } 55 } 56 for _, folder := range folders { 57 uri := span.URIFromURI(folder.URI) 58 if !uri.IsFile() { 59 continue 60 } 61 s.pendingFolders = append(s.pendingFolders, folder) 62 } 63 // gopls only supports URIs with a file:// scheme, so if we have no 64 // workspace folders with a supported scheme, fail to initialize. 65 if len(folders) > 0 && len(s.pendingFolders) == 0 { 66 return nil, fmt.Errorf("unsupported URI schemes: %v (gopls only supports file URIs)", folders) 67 } 68 69 var codeActionProvider interface{} = true 70 if ca := params.Capabilities.TextDocument.CodeAction; len(ca.CodeActionLiteralSupport.CodeActionKind.ValueSet) > 0 { 71 // If the client has specified CodeActionLiteralSupport, 72 // send the code actions we support. 73 // 74 // Using CodeActionOptions is only valid if codeActionLiteralSupport is set. 75 codeActionProvider = &protocol.CodeActionOptions{ 76 CodeActionKinds: s.getSupportedCodeActions(), 77 } 78 } 79 var renameOpts interface{} = true 80 if r := params.Capabilities.TextDocument.Rename; r.PrepareSupport { 81 renameOpts = protocol.RenameOptions{ 82 PrepareProvider: r.PrepareSupport, 83 } 84 } 85 86 goplsVer := &bytes.Buffer{} 87 debug.PrintVersionInfo(ctx, goplsVer, true, debug.PlainText) 88 89 return &protocol.InitializeResult{ 90 Capabilities: protocol.ServerCapabilities{ 91 CallHierarchyProvider: true, 92 CodeActionProvider: codeActionProvider, 93 CompletionProvider: protocol.CompletionOptions{ 94 TriggerCharacters: []string{"."}, 95 }, 96 DefinitionProvider: true, 97 TypeDefinitionProvider: true, 98 ImplementationProvider: true, 99 DocumentFormattingProvider: true, 100 DocumentSymbolProvider: true, 101 WorkspaceSymbolProvider: true, 102 ExecuteCommandProvider: protocol.ExecuteCommandOptions{ 103 Commands: options.SupportedCommands, 104 }, 105 FoldingRangeProvider: true, 106 HoverProvider: true, 107 DocumentHighlightProvider: true, 108 DocumentLinkProvider: protocol.DocumentLinkOptions{}, 109 ReferencesProvider: true, 110 RenameProvider: renameOpts, 111 SignatureHelpProvider: protocol.SignatureHelpOptions{ 112 TriggerCharacters: []string{"(", ","}, 113 }, 114 TextDocumentSync: &protocol.TextDocumentSyncOptions{ 115 Change: protocol.Incremental, 116 OpenClose: true, 117 Save: protocol.SaveOptions{ 118 IncludeText: false, 119 }, 120 }, 121 Workspace: protocol.WorkspaceGn{ 122 WorkspaceFolders: protocol.WorkspaceFoldersGn{ 123 Supported: true, 124 ChangeNotifications: "workspace/didChangeWorkspaceFolders", 125 }, 126 }, 127 }, 128 ServerInfo: struct { 129 Name string `json:"name"` 130 Version string `json:"version,omitempty"` 131 }{ 132 Name: "gopls", 133 Version: goplsVer.String(), 134 }, 135 }, nil 136 } 137 138 func (s *Server) initialized(ctx context.Context, params *protocol.InitializedParams) error { 139 s.stateMu.Lock() 140 if s.state >= serverInitialized { 141 defer s.stateMu.Unlock() 142 return errors.Errorf("%w: initalized called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state) 143 } 144 s.state = serverInitialized 145 s.stateMu.Unlock() 146 147 options := s.session.Options() 148 defer func() { s.session.SetOptions(options) }() 149 150 // TODO: this event logging may be unnecessary. 151 // The version info is included in the initialize response. 152 buf := &bytes.Buffer{} 153 debug.PrintVersionInfo(ctx, buf, true, debug.PlainText) 154 event.Log(ctx, buf.String()) 155 156 if err := s.addFolders(ctx, s.pendingFolders); err != nil { 157 return err 158 } 159 s.pendingFolders = nil 160 161 if options.ConfigurationSupported && options.DynamicConfigurationSupported { 162 if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{ 163 Registrations: []protocol.Registration{ 164 { 165 ID: "workspace/didChangeConfiguration", 166 Method: "workspace/didChangeConfiguration", 167 }, 168 { 169 ID: "workspace/didChangeWorkspaceFolders", 170 Method: "workspace/didChangeWorkspaceFolders", 171 }, 172 }, 173 }); err != nil { 174 return err 175 } 176 } 177 return nil 178 } 179 180 func (s *Server) addFolders(ctx context.Context, folders []protocol.WorkspaceFolder) error { 181 originalViews := len(s.session.Views()) 182 viewErrors := make(map[span.URI]error) 183 184 var wg sync.WaitGroup 185 if s.session.Options().VerboseWorkDoneProgress { 186 work := s.progress.start(ctx, DiagnosticWorkTitle(FromInitialWorkspaceLoad), "Calculating diagnostics for initial workspace load...", nil, nil) 187 defer func() { 188 go func() { 189 wg.Wait() 190 work.end("Done.") 191 }() 192 }() 193 } 194 dirsToWatch := map[span.URI]struct{}{} 195 for _, folder := range folders { 196 uri := span.URIFromURI(folder.URI) 197 // Ignore non-file URIs. 198 if !uri.IsFile() { 199 continue 200 } 201 work := s.progress.start(ctx, "Setting up workspace", "Loading packages...", nil, nil) 202 view, snapshot, release, err := s.addView(ctx, folder.Name, uri) 203 if err != nil { 204 viewErrors[uri] = err 205 work.end(fmt.Sprintf("Error loading packages: %s", err)) 206 continue 207 } 208 go func() { 209 view.AwaitInitialized(ctx) 210 work.end("Finished loading packages.") 211 }() 212 213 for _, dir := range snapshot.WorkspaceDirectories(ctx) { 214 dirsToWatch[dir] = struct{}{} 215 } 216 217 // Print each view's environment. 218 buf := &bytes.Buffer{} 219 if err := view.WriteEnv(ctx, buf); err != nil { 220 event.Error(ctx, "failed to write environment", err, tag.Directory.Of(view.Folder().Filename())) 221 continue 222 } 223 event.Log(ctx, buf.String()) 224 225 // Diagnose the newly created view. 226 wg.Add(1) 227 go func() { 228 s.diagnoseDetached(snapshot) 229 release() 230 wg.Done() 231 }() 232 } 233 // Register for file watching notifications, if they are supported. 234 s.watchedDirectoriesMu.Lock() 235 err := s.registerWatchedDirectoriesLocked(ctx, dirsToWatch) 236 s.watchedDirectoriesMu.Unlock() 237 if err != nil { 238 return err 239 } 240 if len(viewErrors) > 0 { 241 errMsg := fmt.Sprintf("Error loading workspace folders (expected %v, got %v)\n", len(folders), len(s.session.Views())-originalViews) 242 for uri, err := range viewErrors { 243 errMsg += fmt.Sprintf("failed to load view for %s: %v\n", uri, err) 244 } 245 return s.client.ShowMessage(ctx, &protocol.ShowMessageParams{ 246 Type: protocol.Error, 247 Message: errMsg, 248 }) 249 } 250 return nil 251 } 252 253 // updateWatchedDirectories compares the current set of directories to watch 254 // with the previously registered set of directories. If the set of directories 255 // has changed, we unregister and re-register for file watching notifications. 256 // updatedSnapshots is the set of snapshots that have been updated. 257 func (s *Server) updateWatchedDirectories(ctx context.Context, updatedSnapshots []source.Snapshot) error { 258 dirsToWatch := map[span.URI]struct{}{} 259 seenViews := map[source.View]struct{}{} 260 261 // Collect all of the workspace directories from the updated snapshots. 262 for _, snapshot := range updatedSnapshots { 263 seenViews[snapshot.View()] = struct{}{} 264 for _, dir := range snapshot.WorkspaceDirectories(ctx) { 265 dirsToWatch[dir] = struct{}{} 266 } 267 } 268 // Not all views were necessarily updated, so check the remaining views. 269 for _, view := range s.session.Views() { 270 if _, ok := seenViews[view]; ok { 271 continue 272 } 273 snapshot, release := view.Snapshot(ctx) 274 for _, dir := range snapshot.WorkspaceDirectories(ctx) { 275 dirsToWatch[dir] = struct{}{} 276 } 277 release() 278 } 279 280 s.watchedDirectoriesMu.Lock() 281 defer s.watchedDirectoriesMu.Unlock() 282 283 // Nothing to do if the set of workspace directories is unchanged. 284 if equalURISet(s.watchedDirectories, dirsToWatch) { 285 return nil 286 } 287 288 // If the set of directories to watch has changed, register the updates and 289 // unregister the previously watched directories. This ordering avoids a 290 // period where no files are being watched. Still, if a user makes on-disk 291 // changes before these updates are complete, we may miss them for the new 292 // directories. 293 if s.watchRegistrationCount > 0 { 294 prevID := s.watchRegistrationCount - 1 295 if err := s.registerWatchedDirectoriesLocked(ctx, dirsToWatch); err != nil { 296 return err 297 } 298 return s.client.UnregisterCapability(ctx, &protocol.UnregistrationParams{ 299 Unregisterations: []protocol.Unregistration{{ 300 ID: watchedFilesCapabilityID(prevID), 301 Method: "workspace/didChangeWatchedFiles", 302 }}, 303 }) 304 } 305 return nil 306 } 307 308 func watchedFilesCapabilityID(id uint64) string { 309 return fmt.Sprintf("workspace/didChangeWatchedFiles-%d", id) 310 } 311 312 func equalURISet(m1, m2 map[span.URI]struct{}) bool { 313 if len(m1) != len(m2) { 314 return false 315 } 316 for k := range m1 { 317 _, ok := m2[k] 318 if !ok { 319 return false 320 } 321 } 322 return true 323 } 324 325 // registerWatchedDirectoriesLocked sends the workspace/didChangeWatchedFiles 326 // registrations to the client and updates s.watchedDirectories. 327 func (s *Server) registerWatchedDirectoriesLocked(ctx context.Context, dirs map[span.URI]struct{}) error { 328 if !s.session.Options().DynamicWatchedFilesSupported { 329 return nil 330 } 331 for k := range s.watchedDirectories { 332 delete(s.watchedDirectories, k) 333 } 334 // Work-around microsoft/vscode#100870 by making sure that we are, 335 // at least, watching the user's entire workspace. This will still be 336 // applied to every folder in the workspace. 337 watchers := []protocol.FileSystemWatcher{{ 338 GlobPattern: "**/*.{go,mod,sum}", 339 Kind: float64(protocol.WatchChange + protocol.WatchDelete + protocol.WatchCreate), 340 }} 341 for dir := range dirs { 342 filename := dir.Filename() 343 // If the directory is within a workspace folder, we're already 344 // watching it via the relative path above. 345 for _, view := range s.session.Views() { 346 if isSubdirectory(view.Folder().Filename(), filename) { 347 continue 348 } 349 } 350 // If microsoft/vscode#100870 is resolved before 351 // microsoft/vscode#104387, we will need a work-around for Windows 352 // drive letter casing. 353 watchers = append(watchers, protocol.FileSystemWatcher{ 354 GlobPattern: fmt.Sprintf("%s/**/*.{go,mod,sum}", filename), 355 Kind: float64(protocol.WatchChange + protocol.WatchDelete + protocol.WatchCreate), 356 }) 357 } 358 if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{ 359 Registrations: []protocol.Registration{{ 360 ID: watchedFilesCapabilityID(s.watchRegistrationCount), 361 Method: "workspace/didChangeWatchedFiles", 362 RegisterOptions: protocol.DidChangeWatchedFilesRegistrationOptions{ 363 Watchers: watchers, 364 }, 365 }}, 366 }); err != nil { 367 return err 368 } 369 s.watchRegistrationCount++ 370 371 for dir := range dirs { 372 s.watchedDirectories[dir] = struct{}{} 373 } 374 return nil 375 } 376 377 func isSubdirectory(root, leaf string) bool { 378 rel, err := filepath.Rel(root, leaf) 379 return err == nil && !strings.HasPrefix(rel, "..") 380 } 381 382 func (s *Server) fetchConfig(ctx context.Context, name string, folder span.URI, o *source.Options) error { 383 if !s.session.Options().ConfigurationSupported { 384 return nil 385 } 386 v := protocol.ParamConfiguration{ 387 ConfigurationParams: protocol.ConfigurationParams{ 388 Items: []protocol.ConfigurationItem{{ 389 ScopeURI: string(folder), 390 Section: "gopls", 391 }, { 392 ScopeURI: string(folder), 393 Section: fmt.Sprintf("gopls-%s", name), 394 }}, 395 }, 396 } 397 configs, err := s.client.Configuration(ctx, &v) 398 if err != nil { 399 return fmt.Errorf("failed to get workspace configuration from client (%s): %v", folder, err) 400 } 401 for _, config := range configs { 402 if err := s.handleOptionResults(ctx, source.SetOptions(o, config)); err != nil { 403 return err 404 } 405 } 406 return nil 407 } 408 409 func (s *Server) handleOptionResults(ctx context.Context, results source.OptionResults) error { 410 for _, result := range results { 411 if result.Error != nil { 412 if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{ 413 Type: protocol.Error, 414 Message: result.Error.Error(), 415 }); err != nil { 416 return err 417 } 418 } 419 switch result.State { 420 case source.OptionUnexpected: 421 if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{ 422 Type: protocol.Error, 423 Message: fmt.Sprintf("unexpected gopls setting %q", result.Name), 424 }); err != nil { 425 return err 426 } 427 case source.OptionDeprecated: 428 msg := fmt.Sprintf("gopls setting %q is deprecated", result.Name) 429 if result.Replacement != "" { 430 msg = fmt.Sprintf("%s, use %q instead", msg, result.Replacement) 431 } 432 if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{ 433 Type: protocol.Warning, 434 Message: msg, 435 }); err != nil { 436 return err 437 } 438 } 439 } 440 return nil 441 } 442 443 // beginFileRequest checks preconditions for a file-oriented request and routes 444 // it to a snapshot. 445 // We don't want to return errors for benign conditions like wrong file type, 446 // so callers should do if !ok { return err } rather than if err != nil. 447 func (s *Server) beginFileRequest(ctx context.Context, pURI protocol.DocumentURI, expectKind source.FileKind) (source.Snapshot, source.VersionedFileHandle, bool, func(), error) { 448 uri := pURI.SpanURI() 449 if !uri.IsFile() { 450 // Not a file URI. Stop processing the request, but don't return an error. 451 return nil, nil, false, func() {}, nil 452 } 453 view, err := s.session.ViewOf(uri) 454 if err != nil { 455 return nil, nil, false, func() {}, err 456 } 457 snapshot, release := view.Snapshot(ctx) 458 fh, err := snapshot.GetFile(ctx, uri) 459 if err != nil { 460 release() 461 return nil, nil, false, func() {}, err 462 } 463 if expectKind != source.UnknownKind && fh.Kind() != expectKind { 464 // Wrong kind of file. Nothing to do. 465 release() 466 return nil, nil, false, func() {}, nil 467 } 468 return snapshot, fh, true, release, nil 469 } 470 471 func (s *Server) shutdown(ctx context.Context) error { 472 s.stateMu.Lock() 473 defer s.stateMu.Unlock() 474 if s.state < serverInitialized { 475 event.Log(ctx, "server shutdown without initialization") 476 } 477 if s.state != serverShutDown { 478 // drop all the active views 479 s.session.Shutdown(ctx) 480 s.state = serverShutDown 481 } 482 return nil 483 } 484 485 func (s *Server) exit(ctx context.Context) error { 486 s.stateMu.Lock() 487 defer s.stateMu.Unlock() 488 489 // TODO: We need a better way to find the conn close method. 490 s.client.(io.Closer).Close() 491 492 if s.state != serverShutDown { 493 // TODO: We should be able to do better than this. 494 os.Exit(1) 495 } 496 // we don't terminate the process on a normal exit, we just allow it to 497 // close naturally if needed after the connection is closed. 498 return nil 499 }