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.
15 "golang.org/x/tools/internal/event"
16 "golang.org/x/tools/internal/gocommand"
17 "golang.org/x/tools/internal/imports"
18 "golang.org/x/tools/internal/lsp/source"
19 "golang.org/x/tools/internal/span"
20 "golang.org/x/tools/internal/xcontext"
21 errors "golang.org/x/xerrors"
29 options *source.Options
33 viewMap map[span.URI]*View
36 overlays map[span.URI]*overlay
38 // gocmdRunner guards go command calls from concurrency errors.
39 gocmdRunner *gocommand.Runner
50 // saved is true if a file matches the state on disk,
51 // and therefore does not need to be part of the overlay sent to go/packages.
55 func (o *overlay) Read() ([]byte, error) {
59 func (o *overlay) FileIdentity() source.FileIdentity {
60 return source.FileIdentity{
67 func (o *overlay) VersionedFileIdentity() source.VersionedFileIdentity {
68 return source.VersionedFileIdentity{
70 SessionID: o.session.id,
75 func (o *overlay) Kind() source.FileKind {
79 func (o *overlay) URI() span.URI {
83 func (o *overlay) Version() float64 {
87 func (o *overlay) Session() string {
91 func (o *overlay) Saved() bool {
95 // closedFile implements LSPFile for a file that the editor hasn't told us about.
96 type closedFile struct {
100 func (c *closedFile) VersionedFileIdentity() source.VersionedFileIdentity {
101 return source.VersionedFileIdentity{
102 URI: c.FileHandle.URI(),
108 func (c *closedFile) Saved() bool {
112 func (c *closedFile) Session() string {
116 func (c *closedFile) Version() float64 {
120 func (s *Session) ID() string { return s.id }
121 func (s *Session) String() string { return s.id }
123 func (s *Session) Options() *source.Options {
125 defer s.optionsMu.Unlock()
129 func (s *Session) SetOptions(options *source.Options) {
131 defer s.optionsMu.Unlock()
135 func (s *Session) Shutdown(ctx context.Context) {
137 defer s.viewMu.Unlock()
138 for _, view := range s.views {
143 event.Log(ctx, "Shutdown session", KeyShutdownSession.Of(s))
146 func (s *Session) Cache() interface{} {
150 func (s *Session) NewView(ctx context.Context, name string, folder, tempWorkspace span.URI, options *source.Options) (source.View, source.Snapshot, func(), error) {
152 defer s.viewMu.Unlock()
153 view, snapshot, release, err := s.createView(ctx, name, folder, tempWorkspace, options, 0)
155 return nil, nil, func() {}, err
157 s.views = append(s.views, view)
158 // we always need to drop the view map
159 s.viewMap = make(map[span.URI]*View)
160 return view, snapshot, release, nil
163 func (s *Session) createView(ctx context.Context, name string, folder, tempWorkspace span.URI, options *source.Options, snapshotID uint64) (*View, *snapshot, func(), error) {
164 index := atomic.AddInt64(&viewIndex, 1)
166 if s.cache.options != nil {
167 s.cache.options(options)
170 // Set the module-specific information.
171 ws, err := s.getWorkspaceInformation(ctx, folder, options)
173 return nil, nil, func() {}, err
176 if options.ExpandWorkspaceToModule {
177 root, err = findWorkspaceRoot(ctx, root, s, pathExcludedByFilterFunc(options), options.ExperimentalWorkspaceModule)
179 return nil, nil, func() {}, err
183 // Build the gopls workspace, collecting active modules in the view.
184 workspace, err := newWorkspace(ctx, root, s, pathExcludedByFilterFunc(options), ws.userGo111Module == off, options.ExperimentalWorkspaceModule)
186 return nil, nil, func() {}, err
189 // We want a true background context and not a detached context here
190 // the spans need to be unrelated and no tag values should pollute it.
191 baseCtx := event.Detach(xcontext.Detach(ctx))
192 backgroundCtx, cancel := context.WithCancel(baseCtx)
196 initialWorkspaceLoad: make(chan struct{}),
197 initializationSema: make(chan struct{}, 1),
198 id: strconv.FormatInt(index, 10),
203 filesByURI: make(map[span.URI]*fileBase),
204 filesByBase: make(map[string][]*fileBase),
206 workspaceInformation: *ws,
207 tempWorkspace: tempWorkspace,
209 v.importsState = &importsState{
211 processEnv: &imports.ProcessEnv{
212 GocmdRunner: s.gocmdRunner,
215 v.snapshot = &snapshot{
218 backgroundCtx: backgroundCtx,
220 initializeOnce: &sync.Once{},
221 generation: s.cache.store.Generation(generationName(v, 0)),
222 packages: make(map[packageKey]*packageHandle),
223 ids: make(map[span.URI][]packageID),
224 metadata: make(map[packageID]*metadata),
225 files: make(map[span.URI]source.VersionedFileHandle),
226 goFiles: make(map[parseKey]*parseGoHandle),
227 importedBy: make(map[packageID][]packageID),
228 actions: make(map[actionKey]*actionHandle),
229 workspacePackages: make(map[packageID]packagePath),
230 unloadableFiles: make(map[span.URI]struct{}),
231 parseModHandles: make(map[span.URI]*parseModHandle),
232 modTidyHandles: make(map[span.URI]*modTidyHandle),
233 modUpgradeHandles: make(map[span.URI]*modUpgradeHandle),
234 modWhyHandles: make(map[span.URI]*modWhyHandle),
235 workspace: workspace,
238 // Initialize the view without blocking.
239 initCtx, initCancel := context.WithCancel(xcontext.Detach(ctx))
240 v.initCancelFirstAttempt = initCancel
241 snapshot := v.snapshot
242 release := snapshot.generation.Acquire(initCtx)
244 snapshot.initialize(initCtx, true)
245 if v.tempWorkspace != "" {
247 if err = os.Mkdir(v.tempWorkspace.Filename(), 0700); err == nil {
249 wsdir, err = snapshot.getWorkspaceDir(initCtx)
251 err = copyWorkspace(v.tempWorkspace, wsdir)
255 event.Error(initCtx, "creating workspace dir", err)
260 return v, snapshot, snapshot.generation.Acquire(ctx), nil
263 // View returns the view by name.
264 func (s *Session) View(name string) source.View {
266 defer s.viewMu.Unlock()
267 for _, view := range s.views {
268 if view.Name() == name {
275 // ViewOf returns a view corresponding to the given URI.
276 // If the file is not already associated with a view, pick one using some heuristics.
277 func (s *Session) ViewOf(uri span.URI) (source.View, error) {
281 func (s *Session) viewOf(uri span.URI) (*View, error) {
283 defer s.viewMu.Unlock()
285 // Check if we already know this file.
286 if v, found := s.viewMap[uri]; found {
289 // Pick the best view for this file and memoize the result.
290 if len(s.views) == 0 {
291 return nil, fmt.Errorf("no views in session")
293 s.viewMap[uri] = bestViewForURI(uri, s.views)
294 return s.viewMap[uri], nil
297 func (s *Session) viewsOf(uri span.URI) []*View {
299 defer s.viewMu.Unlock()
302 for _, view := range s.views {
303 if source.InDir(view.folder.Filename(), uri.Filename()) {
304 views = append(views, view)
310 func (s *Session) Views() []source.View {
312 defer s.viewMu.Unlock()
313 result := make([]source.View, len(s.views))
314 for i, v := range s.views {
320 // bestViewForURI returns the most closely matching view for the given URI
321 // out of the given set of views.
322 func bestViewForURI(uri span.URI, views []*View) *View {
323 // we need to find the best view for this file
325 for _, view := range views {
326 if longest != nil && len(longest.Folder()) > len(view.Folder()) {
329 if view.contains(uri) {
336 // Try our best to return a view that knows the file.
337 for _, view := range views {
338 if view.knownFile(uri) {
342 // TODO: are there any more heuristics we can use?
346 func (s *Session) removeView(ctx context.Context, view *View) error {
348 defer s.viewMu.Unlock()
349 i, err := s.dropView(ctx, view)
353 // delete this view... we don't care about order but we do want to make
354 // sure we can garbage collect the view
355 s.views[i] = s.views[len(s.views)-1]
356 s.views[len(s.views)-1] = nil
357 s.views = s.views[:len(s.views)-1]
361 func (s *Session) updateView(ctx context.Context, view *View, options *source.Options) (*View, error) {
363 defer s.viewMu.Unlock()
364 i, err := s.dropView(ctx, view)
368 // Preserve the snapshot ID if we are recreating the view.
369 view.snapshotMu.Lock()
370 snapshotID := view.snapshot.id
371 view.snapshotMu.Unlock()
372 v, _, release, err := s.createView(ctx, view.name, view.folder, view.tempWorkspace, options, snapshotID)
375 // we have dropped the old view, but could not create the new one
376 // this should not happen and is very bad, but we still need to clean
377 // up the view array if it happens
378 s.views[i] = s.views[len(s.views)-1]
379 s.views[len(s.views)-1] = nil
380 s.views = s.views[:len(s.views)-1]
383 // substitute the new view into the array where the old view was
388 func (s *Session) dropView(ctx context.Context, v *View) (int, error) {
389 // we always need to drop the view map
390 s.viewMap = make(map[span.URI]*View)
391 for i := range s.views {
393 // we found the view, drop it and return the index it was found at
399 return -1, errors.Errorf("view %s for %v not found", v.Name(), v.Folder())
402 func (s *Session) ModifyFiles(ctx context.Context, changes []source.FileModification) error {
403 _, releases, err := s.DidModifyFiles(ctx, changes)
404 for _, release := range releases {
410 type fileChange struct {
413 fileHandle source.VersionedFileHandle
416 func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModification) (map[source.Snapshot][]span.URI, []func(), error) {
417 views := make(map[*View]map[span.URI]*fileChange)
418 affectedViews := map[span.URI][]*View{}
420 overlays, err := s.updateOverlays(ctx, changes)
424 var forceReloadMetadata bool
425 for _, c := range changes {
426 if c.Action == source.InvalidateMetadata {
427 forceReloadMetadata = true
430 // Build the list of affected views.
431 var changedViews []*View
432 for _, view := range s.views {
433 // Don't propagate changes that are outside of the view's scope
435 if !view.relevantChange(c) {
438 changedViews = append(changedViews, view)
440 // If the change is not relevant to any view, but the change is
441 // happening in the editor, assign it the most closely matching view.
442 if len(changedViews) == 0 {
446 bestView, err := s.viewOf(c.URI)
450 changedViews = append(changedViews, bestView)
452 affectedViews[c.URI] = changedViews
454 // Apply the changes to all affected views.
455 for _, view := range changedViews {
456 // Make sure that the file is added to the view.
457 if _, err := view.getFile(c.URI); err != nil {
460 if _, ok := views[view]; !ok {
461 views[view] = make(map[span.URI]*fileChange)
463 if fh, ok := overlays[c.URI]; ok {
464 views[view][c.URI] = &fileChange{
470 fsFile, err := s.cache.getFile(ctx, c.URI)
474 content, err := fsFile.Read()
475 fh := &closedFile{fsFile}
476 views[view][c.URI] = &fileChange{
485 var releases []func()
486 viewToSnapshot := map[*View]*snapshot{}
487 for view, changed := range views {
488 snapshot, release := view.invalidateContent(ctx, changed, forceReloadMetadata)
489 releases = append(releases, release)
490 viewToSnapshot[view] = snapshot
493 // We only want to diagnose each changed file once, in the view to which
494 // it "most" belongs. We do this by picking the best view for each URI,
495 // and then aggregating the set of snapshots and their URIs (to avoid
496 // diagnosing the same snapshot multiple times).
497 snapshotURIs := map[source.Snapshot][]span.URI{}
498 for _, mod := range changes {
499 viewSlice, ok := affectedViews[mod.URI]
500 if !ok || len(viewSlice) == 0 {
503 view := bestViewForURI(mod.URI, viewSlice)
504 snapshot, ok := viewToSnapshot[view]
506 panic(fmt.Sprintf("no snapshot for view %s", view.Folder()))
508 snapshotURIs[snapshot] = append(snapshotURIs[snapshot], mod.URI)
510 return snapshotURIs, releases, nil
513 func (s *Session) ExpandModificationsToDirectories(ctx context.Context, changes []source.FileModification) []source.FileModification {
514 var snapshots []*snapshot
515 for _, v := range s.views {
516 snapshot, release := v.getSnapshot(ctx)
518 snapshots = append(snapshots, snapshot)
520 knownDirs := knownDirectories(ctx, snapshots)
521 var result []source.FileModification
522 for _, c := range changes {
523 if _, ok := knownDirs[c.URI]; !ok {
524 result = append(result, c)
527 affectedFiles := knownFilesInDir(ctx, snapshots, c.URI)
528 var fileChanges []source.FileModification
529 for uri := range affectedFiles {
530 fileChanges = append(fileChanges, source.FileModification{
535 // changes to directories cannot include text or versions
538 result = append(result, fileChanges...)
543 // knownDirectories returns all of the directories known to the given
544 // snapshots, including workspace directories and their subdirectories.
545 func knownDirectories(ctx context.Context, snapshots []*snapshot) map[span.URI]struct{} {
546 result := map[span.URI]struct{}{}
547 for _, snapshot := range snapshots {
548 dirs := snapshot.workspace.dirs(ctx, snapshot)
549 for _, dir := range dirs {
550 result[dir] = struct{}{}
552 subdirs := snapshot.allKnownSubdirs(ctx)
553 for dir := range subdirs {
554 result[dir] = struct{}{}
560 // knownFilesInDir returns the files known to the snapshots in the session.
561 // It does not respect symlinks.
562 func knownFilesInDir(ctx context.Context, snapshots []*snapshot, dir span.URI) map[span.URI]struct{} {
563 files := map[span.URI]struct{}{}
565 for _, snapshot := range snapshots {
566 for _, uri := range snapshot.knownFilesInDir(ctx, dir) {
567 files[uri] = struct{}{}
573 func (s *Session) updateOverlays(ctx context.Context, changes []source.FileModification) (map[span.URI]*overlay, error) {
575 defer s.overlayMu.Unlock()
577 for _, c := range changes {
578 // Don't update overlays for metadata invalidations.
579 if c.Action == source.InvalidateMetadata {
583 o, ok := s.overlays[c.URI]
585 // If the file is not opened in an overlay and the change is on disk,
586 // there's no need to update an overlay. If there is an overlay, we
587 // may need to update the overlay's saved value.
592 // Determine the file kind on open, otherwise, assume it has been cached.
593 var kind source.FileKind
596 kind = source.DetectLanguage(c.LanguageID, c.URI.Filename())
599 return nil, errors.Errorf("updateOverlays: modifying unopened overlay %v", c.URI)
603 if kind == source.UnknownKind {
604 return nil, errors.Errorf("updateOverlays: unknown file kind for %s", c.URI)
607 // Closing a file just deletes its overlay.
608 if c.Action == source.Close {
609 delete(s.overlays, c.URI)
613 // If the file is on disk, check if its content is the same as in the
614 // overlay. Saves and on-disk file changes don't come with the file's
617 if text == nil && (c.Action == source.Save || c.OnDisk) {
619 return nil, fmt.Errorf("no known content for overlay for %s", c.Action)
623 // On-disk changes don't come with versions.
628 hash := hashContents(text)
629 var sameContentOnDisk bool
632 // Do nothing. sameContentOnDisk should be false.
634 // Make sure the version and content (if present) is the same.
635 if o.version != version {
636 return nil, errors.Errorf("updateOverlays: saving %s at version %v, currently at %v", c.URI, c.Version, o.version)
638 if c.Text != nil && o.hash != hash {
639 return nil, errors.Errorf("updateOverlays: overlay %s changed on save", c.URI)
641 sameContentOnDisk = true
643 fh, err := s.cache.getFile(ctx, c.URI)
647 _, readErr := fh.Read()
648 sameContentOnDisk = (readErr == nil && fh.FileIdentity().Hash == hash)
657 saved: sameContentOnDisk,
659 s.overlays[c.URI] = o
662 // Get the overlays for each change while the session's overlay map is
664 overlays := make(map[span.URI]*overlay)
665 for _, c := range changes {
666 if o, ok := s.overlays[c.URI]; ok {
673 func (s *Session) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
674 if overlay := s.readOverlay(uri); overlay != nil {
677 // Fall back to the cache-level file system.
678 return s.cache.getFile(ctx, uri)
681 func (s *Session) readOverlay(uri span.URI) *overlay {
683 defer s.overlayMu.Unlock()
685 if overlay, ok := s.overlays[uri]; ok {
691 func (s *Session) Overlays() []source.Overlay {
693 defer s.overlayMu.Unlock()
695 overlays := make([]source.Overlay, 0, len(s.overlays))
696 for _, overlay := range s.overlays {
697 overlays = append(overlays, overlay)
702 func (s *Session) FileWatchingGlobPatterns(ctx context.Context) map[string]struct{} {
703 patterns := map[string]struct{}{}
704 for _, view := range s.views {
705 snapshot, release := view.getSnapshot(ctx)
706 for k, v := range snapshot.fileWatchingGlobPatterns(ctx) {