cuelang.org/go@v0.13.0/internal/golangorgx/gopls/cache/session.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 cache 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "path/filepath" 12 "strconv" 13 "strings" 14 "sync" 15 "sync/atomic" 16 "time" 17 18 "cuelang.org/go/cue/build" 19 "cuelang.org/go/internal/golangorgx/gopls/cache/metadata" 20 "cuelang.org/go/internal/golangorgx/gopls/cache/typerefs" 21 "cuelang.org/go/internal/golangorgx/gopls/file" 22 "cuelang.org/go/internal/golangorgx/gopls/protocol" 23 "cuelang.org/go/internal/golangorgx/gopls/util/bug" 24 "cuelang.org/go/internal/golangorgx/gopls/util/persistent" 25 "cuelang.org/go/internal/golangorgx/tools/event" 26 "cuelang.org/go/internal/golangorgx/tools/memoize" 27 "cuelang.org/go/internal/golangorgx/tools/xcontext" 28 ) 29 30 // NewSession creates a new gopls session with the given cache. 31 func NewSession(ctx context.Context, c *Cache) *Session { 32 index := atomic.AddInt64(&sessionIndex, 1) 33 s := &Session{ 34 id: strconv.FormatInt(index, 10), 35 cache: c, 36 overlayFS: newOverlayFS(c), 37 parseCache: newParseCache(1 * time.Minute), // keep recently parsed files for a minute, to optimize typing CPU 38 viewMap: make(map[protocol.DocumentURI]*View), 39 } 40 event.Log(ctx, "New session", KeyCreateSession.Of(s)) 41 return s 42 } 43 44 // A Session holds the state (views, file contents, parse cache, 45 // memoized computations) of a gopls server process. 46 // 47 // It implements the file.Source interface. 48 type Session struct { 49 // Unique identifier for this session. 50 id string 51 52 // Immutable attributes shared across views. 53 cache *Cache // shared cache 54 55 viewMu sync.Mutex 56 views []*View 57 viewMap map[protocol.DocumentURI]*View // file->best view; nil after shutdown 58 59 // snapshots is a counting semaphore that records the number 60 // of unreleased snapshots associated with this session. 61 // Shutdown waits for it to fall to zero. 62 snapshotWG sync.WaitGroup 63 64 parseCache *parseCache 65 66 *overlayFS 67 } 68 69 // ID returns the unique identifier for this session on this server. 70 func (s *Session) ID() string { return s.id } 71 func (s *Session) String() string { return s.id } 72 73 // Shutdown the session and all views it has created. 74 func (s *Session) Shutdown(ctx context.Context) { 75 var views []*View 76 s.viewMu.Lock() 77 views = append(views, s.views...) 78 s.views = nil 79 s.viewMap = nil 80 s.viewMu.Unlock() 81 for _, view := range views { 82 view.shutdown() 83 } 84 s.parseCache.stop() 85 s.snapshotWG.Wait() // wait for all work on associated snapshots to finish 86 event.Log(ctx, "Shutdown session", KeyShutdownSession.Of(s)) 87 } 88 89 // Cache returns the cache that created this session, for debugging only. 90 func (s *Session) Cache() *Cache { 91 return s.cache 92 } 93 94 // NewView creates a new View, returning it and its first snapshot. If a 95 // non-empty tempWorkspace directory is provided, the View will record a copy 96 // of its gopls workspace module in that directory, so that client tooling 97 // can execute in the same main module. On success it also returns a release 98 // function that must be called when the Snapshot is no longer needed. 99 func (s *Session) NewView(ctx context.Context, folder *Folder) (*View, *Snapshot, func(), error) { 100 s.viewMu.Lock() 101 defer s.viewMu.Unlock() 102 103 // TODO(myitcv): when we shift to support multiple WorkspaceFolders, we 104 // might need to introduce logic here that determines if we have an existing 105 // view for a WorkspaceFolder we are adding. 106 107 def, err := defineView(ctx, s, folder, nil) 108 if err != nil { 109 return nil, nil, nil, err 110 } 111 view, snapshot, release := s.createView(ctx, def) 112 s.views = append(s.views, view) 113 // we always need to drop the view map 114 s.viewMap = make(map[protocol.DocumentURI]*View) 115 return view, snapshot, release, nil 116 } 117 118 // createView creates a new view, with an initial snapshot that retains the 119 // supplied context, detached from events and cancelation. 120 // 121 // The caller is responsible for calling the release function once. 122 func (s *Session) createView(ctx context.Context, def *viewDefinition) (*View, *Snapshot, func()) { 123 index := atomic.AddInt64(&viewIndex, 1) 124 125 // We want a true background context and not a detached context here 126 // the spans need to be unrelated and no tag values should pollute it. 127 baseCtx := event.Detach(xcontext.Detach(ctx)) 128 backgroundCtx, cancel := context.WithCancel(baseCtx) 129 130 v := &View{ 131 id: strconv.FormatInt(index, 10), 132 initialWorkspaceLoad: make(chan struct{}), 133 initializationSema: make(chan struct{}, 1), 134 baseCtx: baseCtx, 135 parseCache: s.parseCache, 136 fs: s.overlayFS, 137 viewDefinition: def, 138 } 139 140 s.snapshotWG.Add(1) 141 v.snapshot = &Snapshot{ 142 view: v, 143 backgroundCtx: backgroundCtx, 144 cancel: cancel, 145 store: s.cache.store, 146 refcount: 1, // Snapshots are born referenced. 147 done: s.snapshotWG.Done, 148 packages: new(persistent.Map[ImportPath, *packageHandle]), 149 meta: new(metadata.Graph), 150 files: newFileMap(), 151 activePackages: new(persistent.Map[ImportPath, *build.Instance]), 152 symbolizeHandles: new(persistent.Map[protocol.DocumentURI, *memoize.Promise]), 153 shouldLoad: new(persistent.Map[ImportPath, []PackagePath]), 154 unloadableFiles: new(persistent.Set[protocol.DocumentURI]), 155 pkgIndex: typerefs.NewPackageIndex(), 156 } 157 158 // Snapshots must observe all open files, as there are some caching 159 // heuristics that change behavior depending on open files. 160 for _, o := range s.overlayFS.Overlays() { 161 _, _ = v.snapshot.ReadFile(ctx, o.URI()) 162 } 163 164 // Record the environment of the newly created view in the log. 165 event.Log(ctx, viewEnv(v)) 166 167 // Initialize the view without blocking. 168 initCtx, initCancel := context.WithCancel(xcontext.Detach(ctx)) 169 v.cancelInitialWorkspaceLoad = initCancel 170 snapshot := v.snapshot 171 172 // Pass a second reference to the background goroutine. 173 bgRelease := snapshot.Acquire() 174 go func() { 175 defer bgRelease() 176 snapshot.initialize(initCtx, true) 177 }() 178 179 // Return a third reference to the caller. 180 return v, snapshot, snapshot.Acquire() 181 } 182 183 // RemoveView removes from the session the view rooted at the specified directory. 184 // It reports whether a view of that directory was removed. 185 func (s *Session) RemoveView(dir protocol.DocumentURI) bool { 186 s.viewMu.Lock() 187 defer s.viewMu.Unlock() 188 for _, view := range s.views { 189 if view.folder.Dir == dir { 190 i := s.dropView(view) 191 if i == -1 { 192 return false // can't happen 193 } 194 // delete this view... we don't care about order but we do want to make 195 // sure we can garbage collect the view 196 s.views = removeElement(s.views, i) 197 return true 198 } 199 } 200 return false 201 } 202 203 // View returns the view with a matching id, if present. 204 func (s *Session) View(id string) (*View, error) { 205 s.viewMu.Lock() 206 defer s.viewMu.Unlock() 207 for _, view := range s.views { 208 if view.ID() == id { 209 return view, nil 210 } 211 } 212 return nil, fmt.Errorf("no view with ID %q", id) 213 } 214 215 // SnapshotOf returns a Snapshot corresponding to the given URI. 216 // 217 // In the case where the file can be can be associated with a View by 218 // bestViewForURI (based on directory information alone, without package 219 // metadata), SnapshotOf returns the current Snapshot for that View. Otherwise, 220 // it awaits loading package metadata and returns a Snapshot for the first View 221 // containing a real (=not command-line-arguments) package for the file. 222 // 223 // If that also fails to find a View, SnapshotOf returns a Snapshot for the 224 // first view in s.views that is not shut down (i.e. s.views[0] unless we lose 225 // a race), for determinism in tests and so that we tend to aggregate the 226 // resulting command-line-arguments packages into a single view. 227 // 228 // SnapshotOf returns an error if a failure occurs along the way (most likely due 229 // to context cancellation), or if there are no Views in the Session. 230 // 231 // On success, the caller must call the returned function to release the snapshot. 232 func (s *Session) SnapshotOf(ctx context.Context, uri protocol.DocumentURI) (*Snapshot, func(), error) { 233 // Fast path: if the uri has a static association with a view, return it. 234 s.viewMu.Lock() 235 v, err := s.viewOfLocked(ctx, uri) 236 s.viewMu.Unlock() 237 238 if err != nil { 239 return nil, nil, err 240 } 241 242 if v != nil { 243 snapshot, release, err := v.Snapshot() 244 if err == nil { 245 return snapshot, release, nil 246 } 247 // View is shut down. Forget this association. 248 s.viewMu.Lock() 249 if s.viewMap[uri] == v { 250 delete(s.viewMap, uri) 251 } 252 s.viewMu.Unlock() 253 } 254 255 // Fall-back: none of the views could be associated with uri based on 256 // directory information alone. 257 // 258 // Don't memoize the view association in viewMap, as it is not static: Views 259 // may change as metadata changes. 260 // 261 // TODO(rfindley): we could perhaps optimize this case by peeking at existing 262 // metadata before awaiting the load (after all, a load only adds metadata). 263 // But that seems potentially tricky, when in the common case no loading 264 // should be required. 265 views := s.Views() 266 267 for _, v := range views { 268 snapshot, release, err := v.Snapshot() 269 if err != nil { 270 continue 271 } 272 _ = snapshot.awaitLoaded(ctx) // ignore error 273 return snapshot, release, nil // first valid snapshot 274 } 275 return nil, nil, errNoViews 276 } 277 278 // errNoViews is sought by orphaned file diagnostics, to detect the case where 279 // we have no view containing a file. 280 var errNoViews = errors.New("no views") 281 282 // viewOfLocked wraps bestViewForURI, memoizing its result. 283 // 284 // Precondition: caller holds s.viewMu lock. 285 // 286 // May return (nil, nil). 287 func (s *Session) viewOfLocked(ctx context.Context, uri protocol.DocumentURI) (*View, error) { 288 v, hit := s.viewMap[uri] 289 if !hit { 290 // Cache miss: compute (and memoize) the best view. 291 fh, err := s.ReadFile(ctx, uri) 292 if err != nil { 293 return nil, err 294 } 295 v, err = bestView(ctx, s, fh, s.views) 296 if err != nil { 297 return nil, err 298 } 299 if s.viewMap == nil { 300 return nil, errors.New("session is shut down") 301 } 302 s.viewMap[uri] = v 303 } 304 return v, nil 305 } 306 307 func (s *Session) Views() []*View { 308 s.viewMu.Lock() 309 defer s.viewMu.Unlock() 310 result := make([]*View, len(s.views)) 311 copy(result, s.views) 312 return result 313 } 314 315 // selectViewDefs constructs the best set of views covering the provided workspace 316 // folders and open files. 317 // 318 // This implements the zero-config algorithm of golang/go#57979. 319 func selectViewDefs(ctx context.Context, fs file.Source, folders []*Folder, openFiles []protocol.DocumentURI) ([]*viewDefinition, error) { 320 var defs []*viewDefinition 321 322 // First, compute a default view for each workspace folder. 323 // TODO(golang/go#57979): technically, this is path dependent, since 324 // DidChangeWorkspaceFolders could introduce a path-dependent ordering on 325 // folders. We should keep folders sorted, or sort them here. 326 for _, folder := range folders { 327 def, err := defineView(ctx, fs, folder, nil) 328 if err != nil { 329 return nil, err 330 } 331 defs = append(defs, def) 332 } 333 334 // Next, ensure that the set of views covers all open files contained in a 335 // workspace folder. 336 // 337 // We only do this for files contained in a workspace folder, because other 338 // open files are most likely the result of jumping to a definition from a 339 // workspace file; we don't want to create additional views in those cases: 340 // they should be resolved after initialization. 341 342 folderForFile := func(uri protocol.DocumentURI) *Folder { 343 var longest *Folder 344 for _, folder := range folders { 345 if (longest == nil || len(folder.Dir) > len(longest.Dir)) && folder.Dir.Encloses(uri) { 346 longest = folder 347 } 348 } 349 return longest 350 } 351 352 checkFiles: 353 for _, uri := range openFiles { 354 folder := folderForFile(uri) 355 if folder == nil || !folder.Options.ZeroConfig { 356 continue // only guess views for open files 357 } 358 fh, err := fs.ReadFile(ctx, uri) 359 if err != nil { 360 return nil, err 361 } 362 def, err := bestView(ctx, fs, fh, defs) 363 if err != nil { 364 // We should never call selectViewDefs with a cancellable context, so 365 // this should never fail. 366 return nil, bug.Errorf("failed to find best view for open file: %v", err) 367 } 368 if def != nil { 369 continue // file covered by an existing view 370 } 371 def, err = defineView(ctx, fs, folder, fh) 372 if err != nil { 373 // We should never call selectViewDefs with a cancellable context, so 374 // this should never fail. 375 return nil, bug.Errorf("failed to define view for open file: %v", err) 376 } 377 // It need not strictly be the case that the best view for a file is 378 // distinct from other views, as the logic of getViewDefinition and 379 // bestViewForURI does not align perfectly. This is not necessarily a bug: 380 // there may be files for which we can't construct a valid view. 381 // 382 // Nevertheless, we should not create redundant views. 383 for _, alt := range defs { 384 if viewDefinitionsEqual(alt, def) { 385 continue checkFiles 386 } 387 } 388 defs = append(defs, def) 389 } 390 391 return defs, nil 392 } 393 394 // The viewDefiner interface allows the bestView algorithm to operate on both 395 // Views and viewDefinitions. 396 type viewDefiner interface{ definition() *viewDefinition } 397 398 // bestView returns the best View or viewDefinition that contains the 399 // given file, or (nil, nil) if no matching view is found. 400 // 401 // bestView only returns an error in the event of context cancellation. 402 // 403 // Making this function generic is convenient so that we can avoid mapping view 404 // definitions back to views inside Session.DidModifyFiles, where performance 405 // matters. It is, however, not the cleanest application of generics. 406 // 407 // Note: keep this function in sync with defineView. 408 func bestView[V viewDefiner](ctx context.Context, fs file.Source, fh file.Handle, views []V) (V, error) { 409 var zero V 410 411 // Given current limitations of cue lsp (exactly one workspace folder 412 // supported), and that we create a single view per workspace folder, we 413 // should assert here that we have a single view. Otherwise we have problems 414 if len(views) != 1 { 415 return zero, fmt.Errorf("expected exactly 1 view; saw %d", len(views)) 416 } 417 418 v := views[0] 419 420 dirURI := fh.URI().Dir() 421 moduleCue, err := findRootPattern(ctx, dirURI, filepath.FromSlash("cue.mod/module.cue"), fs) 422 if err != nil { 423 return zero, err 424 } 425 426 // Only if the module root corresponds to that of the view (workspace folder) 427 // do we match. 428 root := moduleCue.Dir().Dir() 429 if root == v.definition().root { 430 return v, nil 431 } 432 433 // TODO(myitcv): in the case of a nested module, we could prompt the user to 434 // do something here when we add support for multiple workspace folders. For 435 // now we simply return the zero view. 436 if v.definition().folder.Dir.Encloses(fh.URI()) { 437 return zero, nil 438 } 439 440 // Perhaps a random file opened by the user? 441 return zero, nil 442 } 443 444 // updateViewLocked recreates the view with the given options. 445 // 446 // If the resulting error is non-nil, the view may or may not have already been 447 // dropped from the session. 448 func (s *Session) updateViewLocked(ctx context.Context, view *View, def *viewDefinition) (*View, error) { 449 i := s.dropView(view) 450 if i == -1 { 451 return nil, fmt.Errorf("view %q not found", view.id) 452 } 453 454 view, _, release := s.createView(ctx, def) 455 defer release() 456 457 // substitute the new view into the array where the old view was 458 s.views[i] = view 459 s.viewMap = make(map[protocol.DocumentURI]*View) 460 return view, nil 461 } 462 463 // removeElement removes the ith element from the slice replacing it with the last element. 464 // TODO(adonovan): generics, someday. 465 func removeElement(slice []*View, index int) []*View { 466 last := len(slice) - 1 467 slice[index] = slice[last] 468 slice[last] = nil // aid GC 469 return slice[:last] 470 } 471 472 // dropView removes v from the set of views for the receiver s and calls 473 // v.shutdown, returning the index of v in s.views (if found), or -1 if v was 474 // not found. s.viewMu must be held while calling this function. 475 func (s *Session) dropView(v *View) int { 476 // we always need to drop the view map 477 s.viewMap = make(map[protocol.DocumentURI]*View) 478 for i := range s.views { 479 if v == s.views[i] { 480 // we found the view, drop it and return the index it was found at 481 s.views[i] = nil 482 v.shutdown() 483 return i 484 } 485 } 486 // TODO(rfindley): it looks wrong that we don't shutdown v in this codepath. 487 // We should never get here. 488 bug.Reportf("tried to drop nonexistent view %q", v.id) 489 return -1 490 } 491 492 // ResetView resets the best view for the given URI. 493 func (s *Session) ResetView(ctx context.Context, uri protocol.DocumentURI) (*View, error) { 494 s.viewMu.Lock() 495 defer s.viewMu.Unlock() 496 v, err := s.viewOfLocked(ctx, uri) 497 if err != nil { 498 return nil, err 499 } 500 return s.updateViewLocked(ctx, v, v.viewDefinition) 501 } 502 503 // DidModifyFiles reports a file modification to the session. It returns 504 // the new snapshots after the modifications have been applied, paired with 505 // the affected file URIs for those snapshots. 506 // On success, it returns a release function that 507 // must be called when the snapshots are no longer needed. 508 // 509 // TODO(rfindley): what happens if this function fails? It must leave us in a 510 // broken state, which we should surface to the user, probably as a request to 511 // restart gopls. 512 func (s *Session) DidModifyFiles(ctx context.Context, modifications []file.Modification) (map[*View][]protocol.DocumentURI, error) { 513 s.viewMu.Lock() 514 defer s.viewMu.Unlock() 515 516 // Update overlays. 517 // 518 // This is done while holding viewMu because the set of open files affects 519 // the set of views, and to prevent views from seeing updated file content 520 // before they have processed invalidations. 521 _, err := s.updateOverlays(ctx, modifications) 522 if err != nil { 523 return nil, err 524 } 525 526 // checkViews controls whether the set of views needs to be recomputed, for 527 // example because a go.mod file was created or deleted, or a go.work file 528 // changed on disk. 529 checkViews := false 530 531 changed := make(map[protocol.DocumentURI]file.Handle) 532 for _, c := range modifications { 533 fh := mustReadFile(ctx, s, c.URI) 534 changed[c.URI] = fh 535 } 536 537 // We only want to run fast-path diagnostics (i.e. diagnoseChangedFiles) once 538 // for each changed file, in its best view. 539 viewsToDiagnose := map[*View][]protocol.DocumentURI{} 540 for _, mod := range modifications { 541 v, err := s.viewOfLocked(ctx, mod.URI) 542 if err != nil { 543 // bestViewForURI only returns an error in the event of context 544 // cancellation. Since state changes should occur on an uncancellable 545 // context, an error here is a bug. 546 bug.Reportf("finding best view for change: %v", err) 547 continue 548 } 549 if v != nil { 550 viewsToDiagnose[v] = append(viewsToDiagnose[v], mod.URI) 551 } 552 } 553 554 // ...but changes may be relevant to other views, for example if they are 555 // changes to a shared package. 556 for _, v := range s.views { 557 _, release, needsDiagnosis := s.invalidateViewLocked(ctx, v, StateChange{Modifications: modifications, Files: changed}) 558 release() 559 560 if needsDiagnosis || checkViews { 561 if _, ok := viewsToDiagnose[v]; !ok { 562 viewsToDiagnose[v] = nil 563 } 564 } 565 } 566 567 return viewsToDiagnose, nil 568 } 569 570 // ExpandModificationsToDirectories returns the set of changes with the 571 // directory changes removed and expanded to include all of the files in 572 // the directory. 573 func (s *Session) ExpandModificationsToDirectories(ctx context.Context, changes []file.Modification) []file.Modification { 574 var snapshots []*Snapshot 575 s.viewMu.Lock() 576 for _, v := range s.views { 577 snapshot, release, err := v.Snapshot() 578 if err != nil { 579 continue // view is shut down; continue with others 580 } 581 defer release() 582 snapshots = append(snapshots, snapshot) 583 } 584 s.viewMu.Unlock() 585 586 // Expand the modification to any file we could care about, which we define 587 // to be any file observed by any of the snapshots. 588 // 589 // There may be other files in the directory, but if we haven't read them yet 590 // we don't need to invalidate them. 591 var result []file.Modification 592 for _, c := range changes { 593 expanded := make(map[protocol.DocumentURI]bool) 594 for _, snapshot := range snapshots { 595 for _, uri := range snapshot.filesInDir(c.URI) { 596 expanded[uri] = true 597 } 598 } 599 if len(expanded) == 0 { 600 result = append(result, c) 601 } else { 602 for uri := range expanded { 603 result = append(result, file.Modification{ 604 URI: uri, 605 Action: c.Action, 606 LanguageID: "", 607 OnDisk: c.OnDisk, 608 // changes to directories cannot include text or versions 609 }) 610 } 611 } 612 } 613 return result 614 } 615 616 // updateOverlays updates the set of overlays and returns a map of any existing 617 // overlay values that were replaced. 618 // 619 // Precondition: caller holds s.viewMu lock. 620 // TODO(rfindley): move this to fs_overlay.go. 621 func (fs *overlayFS) updateOverlays(ctx context.Context, changes []file.Modification) (map[protocol.DocumentURI]*overlay, error) { 622 fs.mu.Lock() 623 defer fs.mu.Unlock() 624 625 replaced := make(map[protocol.DocumentURI]*overlay) 626 for _, c := range changes { 627 o, ok := fs.overlays[c.URI] 628 if ok { 629 replaced[c.URI] = o 630 } 631 632 // If the file is not opened in an overlay and the change is on disk, 633 // there's no need to update an overlay. If there is an overlay, we 634 // may need to update the overlay's saved value. 635 if !ok && c.OnDisk { 636 continue 637 } 638 639 // Determine the file kind on open, otherwise, assume it has been cached. 640 var kind file.Kind 641 switch c.Action { 642 case file.Open: 643 kind = file.KindForLang(c.LanguageID) 644 default: 645 if !ok { 646 return nil, fmt.Errorf("updateOverlays: modifying unopened overlay %v", c.URI) 647 } 648 kind = o.kind 649 } 650 651 // Closing a file just deletes its overlay. 652 if c.Action == file.Close { 653 delete(fs.overlays, c.URI) 654 continue 655 } 656 657 // If the file is on disk, check if its content is the same as in the 658 // overlay. Saves and on-disk file changes don't come with the file's 659 // content. 660 text := c.Text 661 if text == nil && (c.Action == file.Save || c.OnDisk) { 662 if !ok { 663 return nil, fmt.Errorf("no known content for overlay for %s", c.Action) 664 } 665 text = o.content 666 } 667 // On-disk changes don't come with versions. 668 version := c.Version 669 if c.OnDisk || c.Action == file.Save { 670 version = o.version 671 } 672 hash := file.HashOf(text) 673 var sameContentOnDisk bool 674 switch c.Action { 675 case file.Delete: 676 // Do nothing. sameContentOnDisk should be false. 677 case file.Save: 678 // Make sure the version and content (if present) is the same. 679 if c.Text != nil && o.hash != hash { 680 return nil, fmt.Errorf("updateOverlays: overlay %s changed on save", c.URI) 681 } 682 sameContentOnDisk = true 683 default: 684 fh := mustReadFile(ctx, fs.delegate, c.URI) 685 _, readErr := fh.Content() 686 sameContentOnDisk = (readErr == nil && fh.Identity().Hash == hash) 687 } 688 o = &overlay{ 689 uri: c.URI, 690 version: version, 691 content: text, 692 kind: kind, 693 hash: hash, 694 saved: sameContentOnDisk, 695 } 696 697 // NOTE: previous versions of this code checked here that the overlay had a 698 // view and file kind (but we don't know why). 699 700 fs.overlays[c.URI] = o 701 } 702 703 return replaced, nil 704 } 705 706 func mustReadFile(ctx context.Context, fs file.Source, uri protocol.DocumentURI) file.Handle { 707 ctx = xcontext.Detach(ctx) 708 fh, err := fs.ReadFile(ctx, uri) 709 if err != nil { 710 // ReadFile cannot fail with an uncancellable context. 711 bug.Reportf("reading file failed unexpectedly: %v", err) 712 return brokenFile{uri, err} 713 } 714 return fh 715 } 716 717 // A brokenFile represents an unexpected failure to read a file. 718 type brokenFile struct { 719 uri protocol.DocumentURI 720 err error 721 } 722 723 func (b brokenFile) URI() protocol.DocumentURI { return b.uri } 724 func (b brokenFile) Identity() file.Identity { return file.Identity{URI: b.uri} } 725 func (b brokenFile) SameContentsOnDisk() bool { return false } 726 func (b brokenFile) Version() int32 { return 0 } 727 func (b brokenFile) Content() ([]byte, error) { return nil, b.err } 728 729 // FileWatchingGlobPatterns returns a set of glob patterns that the client is 730 // required to watch for changes, and notify the server of them, in order to 731 // keep the server's state up to date. 732 // 733 // This set includes 734 // 1. all go.mod and go.work files in the workspace; and 735 // 2. for each Snapshot, its modules (or directory for ad-hoc views). In 736 // module mode, this is the set of active modules (and for VS Code, all 737 // workspace directories within them, due to golang/go#42348). 738 // 739 // The watch for workspace go.work and go.mod files in (1) is sufficient to 740 // capture changes to the repo structure that may affect the set of views. 741 // Whenever this set changes, we reload the workspace and invalidate memoized 742 // files. 743 // 744 // The watch for workspace directories in (2) should keep each View up to date, 745 // as it should capture any newly added/modified/deleted Go files. 746 // 747 // Patterns are returned as a set of protocol.RelativePatterns, since they can 748 // always be later translated to glob patterns (i.e. strings) if the client 749 // lacks relative pattern support. By convention, any pattern returned with 750 // empty baseURI should be served as a glob pattern. 751 // 752 // In general, we prefer to serve relative patterns, as they work better on 753 // most clients that support both, and do not have issues with Windows driver 754 // letter casing: 755 // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#relativePattern 756 // 757 // TODO(golang/go#57979): we need to reset the memoizedFS when a view changes. 758 // Consider the case where we incidentally read a file, then it moved outside 759 // of an active module, and subsequently changed: we would still observe the 760 // original file state. 761 func (s *Session) FileWatchingGlobPatterns(ctx context.Context) map[protocol.RelativePattern]unit { 762 s.viewMu.Lock() 763 defer s.viewMu.Unlock() 764 765 // Always watch files that may change the set of views. 766 patterns := map[protocol.RelativePattern]unit{ 767 {Pattern: "**/*.{mod,work}"}: {}, 768 } 769 770 for _, view := range s.views { 771 snapshot, release, err := view.Snapshot() 772 if err != nil { 773 continue // view is shut down; continue with others 774 } 775 for k, v := range snapshot.fileWatchingGlobPatterns() { 776 patterns[k] = v 777 } 778 release() 779 } 780 return patterns 781 } 782 783 // OrphanedFileDiagnostics reports diagnostics describing why open files have 784 // no packages or have only command-line-arguments packages. 785 // 786 // If the resulting diagnostic is nil, the file is either not orphaned or we 787 // can't produce a good diagnostic. 788 // 789 // The caller must not mutate the result. 790 func (s *Session) OrphanedFileDiagnostics(ctx context.Context) (map[protocol.DocumentURI][]*Diagnostic, error) { 791 // Note: diagnostics holds a slice for consistency with other diagnostic 792 // funcs. 793 diagnostics := make(map[protocol.DocumentURI][]*Diagnostic) 794 795 byView := make(map[*View][]*overlay) 796 for _, o := range s.Overlays() { 797 uri := o.URI() 798 snapshot, release, err := s.SnapshotOf(ctx, uri) 799 if err != nil { 800 // TODO(golang/go#57979): we have to use the .go suffix as an approximation for 801 // file kind here, because we don't have access to Options if no View was 802 // matched. 803 // 804 // But Options are really a property of Folder, not View, and we could 805 // match a folder here. 806 // 807 // Refactor so that Folders are tracked independently of Views, and use 808 // the correct options here to get the most accurate file kind. 809 // 810 // TODO(golang/go#57979): once we switch entirely to the zeroconfig 811 // logic, we should use this diagnostic for the fallback case of 812 // s.views[0] in the ViewOf logic. 813 if errors.Is(err, errNoViews) { 814 if strings.HasSuffix(string(uri), ".go") { 815 if _, rng, ok := orphanedFileDiagnosticRange(ctx, s.parseCache, o); ok { 816 diagnostics[uri] = []*Diagnostic{{ 817 URI: uri, 818 Range: rng, 819 Severity: protocol.SeverityWarning, 820 Source: ListError, 821 Message: fmt.Sprintf("No active builds contain %s: consider opening a new workspace folder containing it", uri.Path()), 822 }} 823 } 824 } 825 continue 826 } 827 return nil, err 828 } 829 v := snapshot.View() 830 release() 831 byView[v] = append(byView[v], o) 832 } 833 834 return diagnostics, nil 835 }