golang.org/x/tools/gopls@v0.15.3/internal/server/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 server 6 7 // This file defines server methods related to initialization, 8 // options, shutdown, and exit. 9 10 import ( 11 "context" 12 "encoding/json" 13 "fmt" 14 "go/build" 15 "log" 16 "os" 17 "path" 18 "path/filepath" 19 "sort" 20 "strings" 21 "sync" 22 23 "golang.org/x/tools/gopls/internal/cache" 24 "golang.org/x/tools/gopls/internal/debug" 25 "golang.org/x/tools/gopls/internal/file" 26 "golang.org/x/tools/gopls/internal/protocol" 27 "golang.org/x/tools/gopls/internal/settings" 28 "golang.org/x/tools/gopls/internal/telemetry" 29 "golang.org/x/tools/gopls/internal/util/bug" 30 "golang.org/x/tools/gopls/internal/util/goversion" 31 "golang.org/x/tools/gopls/internal/util/maps" 32 "golang.org/x/tools/internal/event" 33 "golang.org/x/tools/internal/jsonrpc2" 34 ) 35 36 func (s *server) Initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) { 37 ctx, done := event.Start(ctx, "lsp.Server.initialize") 38 defer done() 39 40 var clientName string 41 if params != nil && params.ClientInfo != nil { 42 clientName = params.ClientInfo.Name 43 } 44 telemetry.RecordClientInfo(clientName) 45 46 s.stateMu.Lock() 47 if s.state >= serverInitializing { 48 defer s.stateMu.Unlock() 49 return nil, fmt.Errorf("%w: initialize called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state) 50 } 51 s.state = serverInitializing 52 s.stateMu.Unlock() 53 54 // For uniqueness, use the gopls PID rather than params.ProcessID (the client 55 // pid). Some clients might start multiple gopls servers, though they 56 // probably shouldn't. 57 pid := os.Getpid() 58 s.tempDir = filepath.Join(os.TempDir(), fmt.Sprintf("gopls-%d.%s", pid, s.session.ID())) 59 err := os.Mkdir(s.tempDir, 0700) 60 if err != nil { 61 // MkdirTemp could fail due to permissions issues. This is a problem with 62 // the user's environment, but should not block gopls otherwise behaving. 63 // All usage of s.tempDir should be predicated on having a non-empty 64 // s.tempDir. 65 event.Error(ctx, "creating temp dir", err) 66 s.tempDir = "" 67 } 68 s.progress.SetSupportsWorkDoneProgress(params.Capabilities.Window.WorkDoneProgress) 69 70 options := s.Options().Clone() 71 // TODO(rfindley): remove the error return from handleOptionResults, and 72 // eliminate this defer. 73 defer func() { s.SetOptions(options) }() 74 75 if err := s.handleOptionResults(ctx, settings.SetOptions(options, params.InitializationOptions)); err != nil { 76 return nil, err 77 } 78 options.ForClientCapabilities(params.ClientInfo, params.Capabilities) 79 80 if options.ShowBugReports { 81 // Report the next bug that occurs on the server. 82 bug.Handle(func(b bug.Bug) { 83 msg := &protocol.ShowMessageParams{ 84 Type: protocol.Error, 85 Message: fmt.Sprintf("A bug occurred on the server: %s\nLocation:%s", b.Description, b.Key), 86 } 87 go func() { 88 if err := s.eventuallyShowMessage(context.Background(), msg); err != nil { 89 log.Printf("error showing bug: %v", err) 90 } 91 }() 92 }) 93 } 94 95 folders := params.WorkspaceFolders 96 if len(folders) == 0 { 97 if params.RootURI != "" { 98 folders = []protocol.WorkspaceFolder{{ 99 URI: string(params.RootURI), 100 Name: path.Base(params.RootURI.Path()), 101 }} 102 } 103 } 104 for _, folder := range folders { 105 if folder.URI == "" { 106 return nil, fmt.Errorf("empty WorkspaceFolder.URI") 107 } 108 if _, err := protocol.ParseDocumentURI(folder.URI); err != nil { 109 return nil, fmt.Errorf("invalid WorkspaceFolder.URI: %v", err) 110 } 111 s.pendingFolders = append(s.pendingFolders, folder) 112 } 113 114 var codeActionProvider interface{} = true 115 if ca := params.Capabilities.TextDocument.CodeAction; len(ca.CodeActionLiteralSupport.CodeActionKind.ValueSet) > 0 { 116 // If the client has specified CodeActionLiteralSupport, 117 // send the code actions we support. 118 // 119 // Using CodeActionOptions is only valid if codeActionLiteralSupport is set. 120 codeActionProvider = &protocol.CodeActionOptions{ 121 CodeActionKinds: s.getSupportedCodeActions(), 122 ResolveProvider: true, 123 } 124 } 125 var renameOpts interface{} = true 126 if r := params.Capabilities.TextDocument.Rename; r != nil && r.PrepareSupport { 127 renameOpts = protocol.RenameOptions{ 128 PrepareProvider: r.PrepareSupport, 129 } 130 } 131 132 versionInfo := debug.VersionInfo() 133 134 goplsVersion, err := json.Marshal(versionInfo) 135 if err != nil { 136 return nil, err 137 } 138 139 return &protocol.InitializeResult{ 140 Capabilities: protocol.ServerCapabilities{ 141 CallHierarchyProvider: &protocol.Or_ServerCapabilities_callHierarchyProvider{Value: true}, 142 CodeActionProvider: codeActionProvider, 143 CodeLensProvider: &protocol.CodeLensOptions{}, // must be non-nil to enable the code lens capability 144 CompletionProvider: &protocol.CompletionOptions{ 145 TriggerCharacters: []string{"."}, 146 }, 147 DefinitionProvider: &protocol.Or_ServerCapabilities_definitionProvider{Value: true}, 148 TypeDefinitionProvider: &protocol.Or_ServerCapabilities_typeDefinitionProvider{Value: true}, 149 ImplementationProvider: &protocol.Or_ServerCapabilities_implementationProvider{Value: true}, 150 DocumentFormattingProvider: &protocol.Or_ServerCapabilities_documentFormattingProvider{Value: true}, 151 DocumentSymbolProvider: &protocol.Or_ServerCapabilities_documentSymbolProvider{Value: true}, 152 WorkspaceSymbolProvider: &protocol.Or_ServerCapabilities_workspaceSymbolProvider{Value: true}, 153 ExecuteCommandProvider: &protocol.ExecuteCommandOptions{ 154 Commands: protocol.NonNilSlice(options.SupportedCommands), 155 }, 156 FoldingRangeProvider: &protocol.Or_ServerCapabilities_foldingRangeProvider{Value: true}, 157 HoverProvider: &protocol.Or_ServerCapabilities_hoverProvider{Value: true}, 158 DocumentHighlightProvider: &protocol.Or_ServerCapabilities_documentHighlightProvider{Value: true}, 159 DocumentLinkProvider: &protocol.DocumentLinkOptions{}, 160 InlayHintProvider: protocol.InlayHintOptions{}, 161 ReferencesProvider: &protocol.Or_ServerCapabilities_referencesProvider{Value: true}, 162 RenameProvider: renameOpts, 163 SelectionRangeProvider: &protocol.Or_ServerCapabilities_selectionRangeProvider{Value: true}, 164 SemanticTokensProvider: protocol.SemanticTokensOptions{ 165 Range: &protocol.Or_SemanticTokensOptions_range{Value: true}, 166 Full: &protocol.Or_SemanticTokensOptions_full{Value: true}, 167 Legend: protocol.SemanticTokensLegend{ 168 TokenTypes: protocol.NonNilSlice(options.SemanticTypes), 169 TokenModifiers: protocol.NonNilSlice(options.SemanticMods), 170 }, 171 }, 172 SignatureHelpProvider: &protocol.SignatureHelpOptions{ 173 TriggerCharacters: []string{"(", ","}, 174 }, 175 TextDocumentSync: &protocol.TextDocumentSyncOptions{ 176 Change: protocol.Incremental, 177 OpenClose: true, 178 Save: &protocol.SaveOptions{ 179 IncludeText: false, 180 }, 181 }, 182 Workspace: &protocol.WorkspaceOptions{ 183 WorkspaceFolders: &protocol.WorkspaceFolders5Gn{ 184 Supported: true, 185 ChangeNotifications: "workspace/didChangeWorkspaceFolders", 186 }, 187 }, 188 }, 189 ServerInfo: &protocol.ServerInfo{ 190 Name: "gopls", 191 Version: string(goplsVersion), 192 }, 193 }, nil 194 } 195 196 func (s *server) Initialized(ctx context.Context, params *protocol.InitializedParams) error { 197 ctx, done := event.Start(ctx, "lsp.Server.initialized") 198 defer done() 199 200 s.stateMu.Lock() 201 if s.state >= serverInitialized { 202 defer s.stateMu.Unlock() 203 return fmt.Errorf("%w: initialized called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state) 204 } 205 s.state = serverInitialized 206 s.stateMu.Unlock() 207 208 for _, not := range s.notifications { 209 s.client.ShowMessage(ctx, not) 210 } 211 s.notifications = nil 212 213 s.addFolders(ctx, s.pendingFolders) 214 215 s.pendingFolders = nil 216 s.checkViewGoVersions() 217 218 var registrations []protocol.Registration 219 options := s.Options() 220 if options.ConfigurationSupported && options.DynamicConfigurationSupported { 221 registrations = append(registrations, protocol.Registration{ 222 ID: "workspace/didChangeConfiguration", 223 Method: "workspace/didChangeConfiguration", 224 }) 225 } 226 if len(registrations) > 0 { 227 if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{ 228 Registrations: registrations, 229 }); err != nil { 230 return err 231 } 232 } 233 234 // Ask (maybe) about enabling telemetry. Do this asynchronously, as it's OK 235 // for users to ignore or dismiss the question. 236 go s.maybePromptForTelemetry(ctx, options.TelemetryPrompt) 237 238 return nil 239 } 240 241 // checkViewGoVersions checks whether any Go version used by a view is too old, 242 // raising a showMessage notification if so. 243 // 244 // It should be called after views change. 245 func (s *server) checkViewGoVersions() { 246 oldestVersion, fromBuild := go1Point(), true 247 for _, view := range s.session.Views() { 248 viewVersion := view.GoVersion() 249 if oldestVersion == -1 || viewVersion < oldestVersion { 250 oldestVersion, fromBuild = viewVersion, false 251 } 252 telemetry.RecordViewGoVersion(viewVersion) 253 } 254 255 if msg, isError := goversion.Message(oldestVersion, fromBuild); msg != "" { 256 mType := protocol.Warning 257 if isError { 258 mType = protocol.Error 259 } 260 s.eventuallyShowMessage(context.Background(), &protocol.ShowMessageParams{ 261 Type: mType, 262 Message: msg, 263 }) 264 } 265 } 266 267 // go1Point returns the x in Go 1.x. If an error occurs extracting the go 268 // version, it returns -1. 269 // 270 // Copied from the testenv package. 271 func go1Point() int { 272 for i := len(build.Default.ReleaseTags) - 1; i >= 0; i-- { 273 var version int 274 if _, err := fmt.Sscanf(build.Default.ReleaseTags[i], "go1.%d", &version); err != nil { 275 continue 276 } 277 return version 278 } 279 return -1 280 } 281 282 // addFolders adds the specified list of "folders" (that's Windows for 283 // directories) to the session. It does not return an error, though it 284 // may report an error to the client over LSP if one or more folders 285 // had problems. 286 func (s *server) addFolders(ctx context.Context, folders []protocol.WorkspaceFolder) { 287 originalViews := len(s.session.Views()) 288 viewErrors := make(map[protocol.URI]error) 289 290 var ndiagnose sync.WaitGroup // number of unfinished diagnose calls 291 if s.Options().VerboseWorkDoneProgress { 292 work := s.progress.Start(ctx, DiagnosticWorkTitle(FromInitialWorkspaceLoad), "Calculating diagnostics for initial workspace load...", nil, nil) 293 defer func() { 294 go func() { 295 ndiagnose.Wait() 296 work.End(ctx, "Done.") 297 }() 298 }() 299 } 300 // Only one view gets to have a workspace. 301 var nsnapshots sync.WaitGroup // number of unfinished snapshot initializations 302 for _, folder := range folders { 303 uri, err := protocol.ParseDocumentURI(folder.URI) 304 if err != nil { 305 viewErrors[folder.URI] = fmt.Errorf("invalid folder URI: %v", err) 306 continue 307 } 308 work := s.progress.Start(ctx, "Setting up workspace", "Loading packages...", nil, nil) 309 snapshot, release, err := s.addView(ctx, folder.Name, uri) 310 if err != nil { 311 if err == cache.ErrViewExists { 312 continue 313 } 314 viewErrors[folder.URI] = err 315 work.End(ctx, fmt.Sprintf("Error loading packages: %s", err)) 316 continue 317 } 318 // Inv: release() must be called once. 319 320 // Initialize snapshot asynchronously. 321 initialized := make(chan struct{}) 322 nsnapshots.Add(1) 323 go func() { 324 snapshot.AwaitInitialized(ctx) 325 work.End(ctx, "Finished loading packages.") 326 nsnapshots.Done() 327 close(initialized) // signal 328 }() 329 330 // Diagnose the newly created view asynchronously. 331 ndiagnose.Add(1) 332 go func() { 333 s.diagnoseSnapshot(snapshot, nil, 0) 334 <-initialized 335 release() 336 ndiagnose.Done() 337 }() 338 } 339 340 // Wait for snapshots to be initialized so that all files are known. 341 // (We don't need to wait for diagnosis to finish.) 342 nsnapshots.Wait() 343 344 // Register for file watching notifications, if they are supported. 345 if err := s.updateWatchedDirectories(ctx); err != nil { 346 event.Error(ctx, "failed to register for file watching notifications", err) 347 } 348 349 // Report any errors using the protocol. 350 if len(viewErrors) > 0 { 351 errMsg := fmt.Sprintf("Error loading workspace folders (expected %v, got %v)\n", len(folders), len(s.session.Views())-originalViews) 352 for uri, err := range viewErrors { 353 errMsg += fmt.Sprintf("failed to load view for %s: %v\n", uri, err) 354 } 355 showMessage(ctx, s.client, protocol.Error, errMsg) 356 } 357 } 358 359 // updateWatchedDirectories compares the current set of directories to watch 360 // with the previously registered set of directories. If the set of directories 361 // has changed, we unregister and re-register for file watching notifications. 362 // updatedSnapshots is the set of snapshots that have been updated. 363 func (s *server) updateWatchedDirectories(ctx context.Context) error { 364 patterns := s.session.FileWatchingGlobPatterns(ctx) 365 366 s.watchedGlobPatternsMu.Lock() 367 defer s.watchedGlobPatternsMu.Unlock() 368 369 // Nothing to do if the set of workspace directories is unchanged. 370 if maps.SameKeys(s.watchedGlobPatterns, patterns) { 371 return nil 372 } 373 374 // If the set of directories to watch has changed, register the updates and 375 // unregister the previously watched directories. This ordering avoids a 376 // period where no files are being watched. Still, if a user makes on-disk 377 // changes before these updates are complete, we may miss them for the new 378 // directories. 379 prevID := s.watchRegistrationCount - 1 380 if err := s.registerWatchedDirectoriesLocked(ctx, patterns); err != nil { 381 return err 382 } 383 if prevID >= 0 { 384 return s.client.UnregisterCapability(ctx, &protocol.UnregistrationParams{ 385 Unregisterations: []protocol.Unregistration{{ 386 ID: watchedFilesCapabilityID(prevID), 387 Method: "workspace/didChangeWatchedFiles", 388 }}, 389 }) 390 } 391 return nil 392 } 393 394 func watchedFilesCapabilityID(id int) string { 395 return fmt.Sprintf("workspace/didChangeWatchedFiles-%d", id) 396 } 397 398 // registerWatchedDirectoriesLocked sends the workspace/didChangeWatchedFiles 399 // registrations to the client and updates s.watchedDirectories. 400 // The caller must not subsequently mutate patterns. 401 func (s *server) registerWatchedDirectoriesLocked(ctx context.Context, patterns map[protocol.RelativePattern]unit) error { 402 if !s.Options().DynamicWatchedFilesSupported { 403 return nil 404 } 405 406 supportsRelativePatterns := s.Options().RelativePatternsSupported 407 408 s.watchedGlobPatterns = patterns 409 watchers := make([]protocol.FileSystemWatcher, 0, len(patterns)) // must be a slice 410 val := protocol.WatchChange | protocol.WatchDelete | protocol.WatchCreate 411 for pattern := range patterns { 412 var value any 413 if supportsRelativePatterns && pattern.BaseURI != "" { 414 value = pattern 415 } else { 416 p := pattern.Pattern 417 if pattern.BaseURI != "" { 418 p = path.Join(filepath.ToSlash(pattern.BaseURI.Path()), p) 419 } 420 value = p 421 } 422 watchers = append(watchers, protocol.FileSystemWatcher{ 423 GlobPattern: protocol.GlobPattern{Value: value}, 424 Kind: &val, 425 }) 426 } 427 428 if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{ 429 Registrations: []protocol.Registration{{ 430 ID: watchedFilesCapabilityID(s.watchRegistrationCount), 431 Method: "workspace/didChangeWatchedFiles", 432 RegisterOptions: protocol.DidChangeWatchedFilesRegistrationOptions{ 433 Watchers: watchers, 434 }, 435 }}, 436 }); err != nil { 437 return err 438 } 439 s.watchRegistrationCount++ 440 return nil 441 } 442 443 // Options returns the current server options. 444 // 445 // The caller must not modify the result. 446 func (s *server) Options() *settings.Options { 447 s.optionsMu.Lock() 448 defer s.optionsMu.Unlock() 449 return s.options 450 } 451 452 // SetOptions sets the current server options. 453 // 454 // The caller must not subsequently modify the options. 455 func (s *server) SetOptions(opts *settings.Options) { 456 s.optionsMu.Lock() 457 defer s.optionsMu.Unlock() 458 s.options = opts 459 } 460 461 func (s *server) newFolder(ctx context.Context, folder protocol.DocumentURI, name string, opts *settings.Options) (*cache.Folder, error) { 462 env, err := cache.FetchGoEnv(ctx, folder, opts) 463 if err != nil { 464 return nil, err 465 } 466 return &cache.Folder{ 467 Dir: folder, 468 Name: name, 469 Options: opts, 470 Env: *env, 471 }, nil 472 } 473 474 // fetchFolderOptions makes a workspace/configuration request for the given 475 // folder, and populates options with the result. 476 // 477 // If folder is "", fetchFolderOptions makes an unscoped request. 478 func (s *server) fetchFolderOptions(ctx context.Context, folder protocol.DocumentURI) (*settings.Options, error) { 479 opts := s.Options() 480 if !opts.ConfigurationSupported { 481 return opts, nil 482 } 483 var scopeURI *string 484 if folder != "" { 485 scope := string(folder) 486 scopeURI = &scope 487 } 488 configs, err := s.client.Configuration(ctx, &protocol.ParamConfiguration{ 489 Items: []protocol.ConfigurationItem{{ 490 ScopeURI: scopeURI, 491 Section: "gopls", 492 }}, 493 }, 494 ) 495 if err != nil { 496 return nil, fmt.Errorf("failed to get workspace configuration from client (%s): %v", folder, err) 497 } 498 499 opts = opts.Clone() 500 for _, config := range configs { 501 if err := s.handleOptionResults(ctx, settings.SetOptions(opts, config)); err != nil { 502 return nil, err 503 } 504 } 505 return opts, nil 506 } 507 508 func (s *server) eventuallyShowMessage(ctx context.Context, msg *protocol.ShowMessageParams) error { 509 s.stateMu.Lock() 510 defer s.stateMu.Unlock() 511 if s.state == serverInitialized { 512 return s.client.ShowMessage(ctx, msg) 513 } 514 s.notifications = append(s.notifications, msg) 515 return nil 516 } 517 518 func (s *server) handleOptionResults(ctx context.Context, results settings.OptionResults) error { 519 var warnings, errors []string 520 for _, result := range results { 521 switch result.Error.(type) { 522 case nil: 523 // nothing to do 524 case *settings.SoftError: 525 warnings = append(warnings, result.Error.Error()) 526 default: 527 errors = append(errors, result.Error.Error()) 528 } 529 } 530 531 // Sort messages, but put errors first. 532 // 533 // Having stable content for the message allows clients to de-duplicate. This 534 // matters because we may send duplicate warnings for clients that support 535 // dynamic configuration: one for the initial settings, and then more for the 536 // individual viewsettings. 537 var msgs []string 538 msgType := protocol.Warning 539 if len(errors) > 0 { 540 msgType = protocol.Error 541 sort.Strings(errors) 542 msgs = append(msgs, errors...) 543 } 544 if len(warnings) > 0 { 545 sort.Strings(warnings) 546 msgs = append(msgs, warnings...) 547 } 548 549 if len(msgs) > 0 { 550 // Settings 551 combined := "Invalid settings: " + strings.Join(msgs, "; ") 552 params := &protocol.ShowMessageParams{ 553 Type: msgType, 554 Message: combined, 555 } 556 return s.eventuallyShowMessage(ctx, params) 557 } 558 559 return nil 560 } 561 562 // fileOf returns the file for a given URI and its snapshot. 563 // On success, the returned function must be called to release the snapshot. 564 func (s *server) fileOf(ctx context.Context, uri protocol.DocumentURI) (file.Handle, *cache.Snapshot, func(), error) { 565 snapshot, release, err := s.session.SnapshotOf(ctx, uri) 566 if err != nil { 567 return nil, nil, nil, err 568 } 569 fh, err := snapshot.ReadFile(ctx, uri) 570 if err != nil { 571 release() 572 return nil, nil, nil, err 573 } 574 return fh, snapshot, release, nil 575 } 576 577 // shutdown implements the 'shutdown' LSP handler. It releases resources 578 // associated with the server and waits for all ongoing work to complete. 579 func (s *server) Shutdown(ctx context.Context) error { 580 ctx, done := event.Start(ctx, "lsp.Server.shutdown") 581 defer done() 582 583 s.stateMu.Lock() 584 defer s.stateMu.Unlock() 585 if s.state < serverInitialized { 586 event.Log(ctx, "server shutdown without initialization") 587 } 588 if s.state != serverShutDown { 589 // drop all the active views 590 s.session.Shutdown(ctx) 591 s.state = serverShutDown 592 if s.tempDir != "" { 593 if err := os.RemoveAll(s.tempDir); err != nil { 594 event.Error(ctx, "removing temp dir", err) 595 } 596 } 597 } 598 return nil 599 } 600 601 func (s *server) Exit(ctx context.Context) error { 602 ctx, done := event.Start(ctx, "lsp.Server.exit") 603 defer done() 604 605 s.stateMu.Lock() 606 defer s.stateMu.Unlock() 607 608 s.client.Close() 609 610 if s.state != serverShutDown { 611 // TODO: We should be able to do better than this. 612 os.Exit(1) 613 } 614 // We don't terminate the process on a normal exit, we just allow it to 615 // close naturally if needed after the connection is closed. 616 return nil 617 }