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