diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/IRunningDocumentTableEventListener.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/IRunningDocumentTableEventListener.cs index 5cf8881070c023876b44c8d1eec4e697b1ec355a..99ddfe77ecc15bc569067d097302feaf8716b59b 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/IRunningDocumentTableEventListener.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/IRunningDocumentTableEventListener.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem @@ -10,16 +11,33 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem /// internal interface IRunningDocumentTableEventListener { - void OnCloseDocument(uint docCookie, string moniker); + /// + /// Triggered when a document is opened. + /// + /// the non-null moniker of the opened document. + /// the non-null text buffer of the opened document) + /// the hierarchy of the text buffer if available. + void OnOpenDocument(string moniker, ITextBuffer textBuffer, IVsHierarchy hierarchy); - void OnRefreshDocumentContext(uint docCookie, string moniker); + /// + /// Triggered when a document is closed. + /// + /// the non-null moniker of the closed document. + void OnCloseDocument(string moniker); - void OnReloadDocumentData(uint docCookie, string moniker); + /// + /// Triggered when a document context is refreshed with a new hierarchy. + /// + /// the non-null moniker of the document that changed. + /// the hierarchy of the text buffer if available. + void OnRefreshDocumentContext(string moniker, IVsHierarchy hierarchy); - void OnBeforeOpenDocument(uint docCookie, string moniker, ITextBuffer textBuffer); - - void OnInitializedDocument(uint docCookie, string moniker, ITextBuffer textBuffer); - - void OnRenameDocument(uint docCookie, string newMoniker, string oldMoniker); + /// + /// Triggered when a document moniker changes. + /// + /// the document's new moniker. + /// the document's old moniker. + /// the document's buffer. + void OnRenameDocument(string newMoniker, string oldMoniker, ITextBuffer textBuffer); } } diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MiscellaneousFilesWorkspace.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MiscellaneousFilesWorkspace.cs index 9a1cb38bc096c5df2004d6a1e2abf0bd43d642ff..8614e63fa8f10bceb6512b00268637765fdd684e 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MiscellaneousFilesWorkspace.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/MiscellaneousFilesWorkspace.cs @@ -27,9 +27,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem [Export(typeof(MiscellaneousFilesWorkspace))] internal sealed partial class MiscellaneousFilesWorkspace : Workspace, IRunningDocumentTableEventListener { - private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactoryService; private readonly IMetadataAsSourceFileService _fileTrackingMetadataAsSourceService; - private readonly IVsRunningDocumentTable4 _runningDocumentTable; private readonly IVsTextManager _textManager; private readonly RunningDocumentTableEventTracker _runningDocumentTableEventTracker; @@ -40,13 +38,13 @@ internal sealed partial class MiscellaneousFilesWorkspace : Workspace, IRunningD /// instances for all open buffers being tracked by by this object /// for possible inclusion into this workspace. /// - private IBidirectionalMap _docCookieToWorkspaceRegistration = BidirectionalMap.Empty; + private IBidirectionalMap _monikerToWorkspaceRegistration = BidirectionalMap.Empty; /// - /// The mapping of all doc cookies in the RDT and the of the project and of the open - /// file we have created for that open buffer. An entry should only be in here if it's also already in . + /// The mapping of all monikers in the RDT and the of the project and of the open + /// file we have created for that open buffer. An entry should only be in here if it's also already in . /// - private readonly Dictionary _docCookiesToProjectIdAndContainer = new Dictionary(); + private readonly Dictionary _monikersToProjectIdAndContainer = new Dictionary(); private readonly ImmutableArray _metadataReferences; @@ -65,64 +63,36 @@ internal sealed partial class MiscellaneousFilesWorkspace : Workspace, IRunningD { _foregroundThreadAffinitization = new ForegroundThreadAffinitizedObject(threadingContext, assertIsForeground: true); - _editorAdaptersFactoryService = editorAdaptersFactoryService; _fileTrackingMetadataAsSourceService = fileTrackingMetadataAsSourceService; - _runningDocumentTable = (IVsRunningDocumentTable4)serviceProvider.GetService(typeof(SVsRunningDocumentTable)); _textManager = (IVsTextManager)serviceProvider.GetService(typeof(SVsTextManager)); - _runningDocumentTableEventTracker = new RunningDocumentTableEventTracker(threadingContext, editorAdaptersFactoryService, _runningDocumentTable, this); + var runningDocumentTable = (IVsRunningDocumentTable4)serviceProvider.GetService(typeof(SVsRunningDocumentTable)); + _runningDocumentTableEventTracker = new RunningDocumentTableEventTracker(threadingContext, editorAdaptersFactoryService, runningDocumentTable, this); _metadataReferences = ImmutableArray.CreateRange(CreateMetadataReferences()); saveEventsService.StartSendingSaveEvents(); } - void IRunningDocumentTableEventListener.OnCloseDocument(uint docCookie, string moniker) - { - _foregroundThreadAffinitization.AssertIsForeground(); - TryUntrackClosingDocument(docCookie, moniker); - } - - void IRunningDocumentTableEventListener.OnRefreshDocumentContext(uint docCookie, string moniker) - { - // This event is not relevant to the misc workspace. - } + void IRunningDocumentTableEventListener.OnOpenDocument(string moniker, ITextBuffer textBuffer, IVsHierarchy _) => TrackOpenedDocument(moniker, textBuffer); - void IRunningDocumentTableEventListener.OnReloadDocumentData(uint docCookie, string moniker) - { - if (moniker != null && TryGetLanguageInformation(moniker) != null && !_docCookiesToProjectIdAndContainer.ContainsKey(docCookie)) - { - if (_runningDocumentTableEventTracker.TryGetBuffer(docCookie, out var buffer)) - { - TrackOpenedDocument(docCookie, moniker, buffer); - } - } - } + void IRunningDocumentTableEventListener.OnCloseDocument(string moniker) => TryUntrackClosingDocument(moniker); - void IRunningDocumentTableEventListener.OnBeforeOpenDocument(uint docCookie, string moniker, ITextBuffer textBuffer) + void IRunningDocumentTableEventListener.OnRefreshDocumentContext(string moniker, IVsHierarchy _) { // This event is not relevant to the misc workspace. } - void IRunningDocumentTableEventListener.OnInitializedDocument(uint docCookie, string moniker, ITextBuffer textBuffer) - { - // The document is now initialized, we should try tracking it - TrackOpenedDocument(docCookie, moniker, textBuffer); - } - - void IRunningDocumentTableEventListener.OnRenameDocument(uint docCookie, string newMoniker, string oldMoniker) + void IRunningDocumentTableEventListener.OnRenameDocument(string newMoniker, string oldMoniker, ITextBuffer buffer) { // We want to consider this file to be added in one of two situations: // // 1) the old file already was a misc file, at which point we might just be doing a rename from // one name to another with the same extension // 2) the old file was a different extension that we weren't tracking, which may have now changed - if (TryUntrackClosingDocument(docCookie, oldMoniker) || TryGetLanguageInformation(oldMoniker) == null) + if (TryUntrackClosingDocument(oldMoniker) || TryGetLanguageInformation(oldMoniker) == null) { - if (_runningDocumentTableEventTracker.TryGetBuffer(docCookie, out var buffer)) - { - // Add the new one, if appropriate. - TrackOpenedDocument(docCookie, newMoniker, buffer); - } + // Add the new one, if appropriate. + TrackOpenedDocument(newMoniker, buffer); } } @@ -166,16 +136,10 @@ private IEnumerable CreateMetadataReferences() select manager.CreateMetadataReferenceSnapshot(fullPath, MetadataReferenceProperties.Assembly); } - private void TrackOpenedDocument(uint docCookie, string moniker, ITextBuffer textBuffer) + private void TrackOpenedDocument(string moniker, ITextBuffer textBuffer) { _foregroundThreadAffinitization.AssertIsForeground(); - // As long as the buffer is initialized, then we should see if we should attach - if (textBuffer == null) - { - return; - } - var languageInformation = TryGetLanguageInformation(moniker); if (languageInformation == null) { @@ -185,16 +149,16 @@ private void TrackOpenedDocument(uint docCookie, string moniker, ITextBuffer tex // We don't want to realize the document here unless it's already initialized. Document initialization is watched in // OnAfterAttributeChangeEx and will retrigger this if it wasn't already done. - if (!_docCookieToWorkspaceRegistration.ContainsKey(docCookie)) + if (!_monikerToWorkspaceRegistration.ContainsKey(moniker)) { var registration = Workspace.GetWorkspaceRegistration(textBuffer.AsTextContainer()); registration.WorkspaceChanged += Registration_WorkspaceChanged; - _docCookieToWorkspaceRegistration = _docCookieToWorkspaceRegistration.Add(docCookie, registration); + _monikerToWorkspaceRegistration = _monikerToWorkspaceRegistration.Add(moniker, registration); if (!IsClaimedByAnotherWorkspace(registration)) { - AttachToDocument(docCookie, moniker); + AttachToDocument(moniker, textBuffer); } } } @@ -217,23 +181,21 @@ private void Registration_WorkspaceChanged(object sender, EventArgs e) // Since WorkspaceChanged notifications may be asynchronous and happened on a different thread, // we might have already unsubscribed for this synchronously from the RDT while we were in the process of sending this // request back to the UI thread. - if (!_docCookieToWorkspaceRegistration.TryGetKey(workspaceRegistration, out var docCookie)) + if (!_monikerToWorkspaceRegistration.TryGetKey(workspaceRegistration, out var moniker)) { return; } // It's also theoretically possible that we are getting notified about a workspace change to a document that has // been simultaneously removed from the RDT but we haven't gotten the notification. In that case, also bail. - if (!_runningDocumentTable.IsCookieValid(docCookie)) + if (!_runningDocumentTableEventTracker.IsMonikerValid(moniker)) { return; } - var moniker = _runningDocumentTable.GetDocumentMoniker(docCookie); - if (workspaceRegistration.Workspace == null) { - if (_docCookiesToProjectIdAndContainer.TryGetValue(docCookie, out var projectIdAndSourceTextContainer)) + if (_monikersToProjectIdAndContainer.TryGetValue(moniker, out var projectIdAndSourceTextContainer)) { // The workspace was taken from us and released and we have only asynchronously found out now. // We already have the file open in our workspace, but the global mapping of source text container @@ -248,16 +210,19 @@ private void Registration_WorkspaceChanged(object sender, EventArgs e) // the moniker. Once we observe the rename later in OnAfterAttributeChangeEx we'll completely disconnect. if (TryGetLanguageInformation(moniker) != null) { - AttachToDocument(docCookie, moniker); + if (_runningDocumentTableEventTracker.TryGetBufferFromMoniker(moniker, out var buffer)) + { + AttachToDocument(moniker, buffer); + } } } } else if (IsClaimedByAnotherWorkspace(workspaceRegistration)) { // It's now claimed by another workspace, so we should unclaim it - if (_docCookiesToProjectIdAndContainer.ContainsKey(docCookie)) + if (_monikersToProjectIdAndContainer.ContainsKey(moniker)) { - DetachFromDocument(docCookie, moniker); + DetachFromDocument(moniker); } } } @@ -266,20 +231,22 @@ private void Registration_WorkspaceChanged(object sender, EventArgs e) /// Stops tracking a document in the RDT for whether we should attach to it. /// /// true if we were previously tracking it. - private bool TryUntrackClosingDocument(uint docCookie, string moniker) + private bool TryUntrackClosingDocument(string moniker) { + _foregroundThreadAffinitization.AssertIsForeground(); + bool unregisteredRegistration = false; // Remove our registration changing handler before we call DetachFromDocument. Otherwise, calling DetachFromDocument // causes us to set the workspace to null, which we then respond to as an indication that we should // attach again. - if (_docCookieToWorkspaceRegistration.TryGetValue(docCookie, out var registration)) + if (_monikerToWorkspaceRegistration.TryGetValue(moniker, out var registration)) { registration.WorkspaceChanged -= Registration_WorkspaceChanged; - _docCookieToWorkspaceRegistration = _docCookieToWorkspaceRegistration.RemoveKey(docCookie); + _monikerToWorkspaceRegistration = _monikerToWorkspaceRegistration.RemoveKey(moniker); unregisteredRegistration = true; } - DetachFromDocument(docCookie, moniker); + DetachFromDocument(moniker); return unregisteredRegistration; } @@ -291,15 +258,10 @@ private bool IsClaimedByAnotherWorkspace(WorkspaceRegistration registration) return registration.Workspace != null && registration.Workspace.Kind != WorkspaceKind.MetadataAsSource && registration.Workspace.Kind != WorkspaceKind.MiscellaneousFiles; } - private void AttachToDocument(uint docCookie, string moniker) + private void AttachToDocument(string moniker, ITextBuffer textBuffer) { _foregroundThreadAffinitization.AssertIsForeground(); - // The cast from dynamic to object doesn't change semantics, but avoids loading the dynamic binder - // which saves us JIT time in this method. - var vsTextBuffer = (IVsTextBuffer)(object)_runningDocumentTable.GetDocumentData(docCookie); - var textBuffer = _editorAdaptersFactoryService.GetDocumentBuffer(vsTextBuffer); - if (_fileTrackingMetadataAsSourceService.TryAddDocumentToWorkspace(moniker, textBuffer)) { // We already added it, so we will keep it excluded from the misc files workspace @@ -313,7 +275,7 @@ private void AttachToDocument(uint docCookie, string moniker) var sourceTextContainer = textBuffer.AsTextContainer(); OnDocumentOpened(projectInfo.Documents.Single().Id, sourceTextContainer); - _docCookiesToProjectIdAndContainer.Add(docCookie, (projectInfo.Id, sourceTextContainer)); + _monikersToProjectIdAndContainer.Add(moniker, (projectInfo.Id, sourceTextContainer)); } /// @@ -408,7 +370,7 @@ private ProjectInfo CreateProjectInfoForDocument(string filePath) SourceCodeKind.Script : SourceCodeKind.Regular; } - private void DetachFromDocument(uint docCookie, string moniker) + private void DetachFromDocument(string moniker) { _foregroundThreadAffinitization.AssertIsForeground(); if (_fileTrackingMetadataAsSourceService.TryRemoveDocumentFromWorkspace(moniker)) @@ -416,7 +378,7 @@ private void DetachFromDocument(uint docCookie, string moniker) return; } - if (_docCookiesToProjectIdAndContainer.TryGetValue(docCookie, out var projectIdAndContainer)) + if (_monikersToProjectIdAndContainer.TryGetValue(moniker, out var projectIdAndContainer)) { var document = this.CurrentSolution.GetProject(projectIdAndContainer.projectId).Documents.Single(); @@ -424,7 +386,7 @@ private void DetachFromDocument(uint docCookie, string moniker) OnDocumentClosed(document.Id, new FileTextLoader(document.FilePath, defaultEncoding: null)); OnProjectRemoved(document.Project.Id); - _docCookiesToProjectIdAndContainer.Remove(docCookie); + _monikersToProjectIdAndContainer.Remove(moniker); return; } @@ -450,7 +412,7 @@ public override bool CanApplyChange(ApplyChangesKind feature) protected override void ApplyDocumentTextChanged(DocumentId documentId, SourceText newText) { - foreach (var projectIdAndSourceTextContainer in _docCookiesToProjectIdAndContainer.Values) + foreach (var projectIdAndSourceTextContainer in _monikersToProjectIdAndContainer.Values) { if (projectIdAndSourceTextContainer.projectId == documentId.ProjectId) { diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RunningDocumentTableEventTracker.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RunningDocumentTableEventTracker.cs index 596b5df173507b3e62406984ddeee0d565526ebd..c35b07f23a9dc8bfbba18f9c71ecc46daa1098d9 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RunningDocumentTableEventTracker.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/RunningDocumentTableEventTracker.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Shell.Interop; @@ -49,9 +50,10 @@ public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint d { if (dwReadLocksRemaining + dwEditLocksRemaining == 0) { - if (CheckPreconditions(docCookie)) + _foregroundAffinitization.AssertIsForeground(); + if (_runningDocumentTable.IsDocumentInitialized(docCookie)) { - _listener.OnCloseDocument(docCookie, _runningDocumentTable.GetDocumentMoniker(docCookie)); + _listener.OnCloseDocument(_runningDocumentTable.GetDocumentMoniker(docCookie)); } } @@ -65,7 +67,7 @@ public int OnAfterSave(uint docCookie) public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) { - return VSConstants.S_OK; + return VSConstants.E_NOTIMPL; } public int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarchy pHierOld, uint itemidOld, string pszMkDocumentOld, IVsHierarchy pHierNew, uint itemidNew, string pszMkDocumentNew) @@ -73,34 +75,32 @@ public int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarch // Did we rename? if ((grfAttribs & (uint)__VSRDTATTRIB.RDTA_MkDocument) != 0) { - if (CheckPreconditions(docCookie)) - { - _listener.OnRenameDocument(docCookie, pszMkDocumentNew, pszMkDocumentOld); - } - } - - if ((grfAttribs & (uint)__VSRDTATTRIB3.RDTA_DocumentInitialized) != 0) - { - if (TryGetBuffer(docCookie, out var buffer)) + _foregroundAffinitization.AssertIsForeground(); + if (_runningDocumentTable.IsDocumentInitialized(docCookie) && TryGetBuffer(docCookie, out var buffer)) { - _listener.OnInitializedDocument(docCookie, _runningDocumentTable.GetDocumentMoniker(docCookie), buffer); + _listener.OnRenameDocument(pszMkDocumentNew, pszMkDocumentOld, buffer); } } - // When starting a diff, the RDT doesn't call OnBeforeDocumentWindowShow, but it does call - // OnAfterAttributeChangeEx for the temporary buffer. The native IDE used this even to - // add misc files, so we'll do the same. + // Doc data reloaded is the most reliable way to know when a document has been loaded and may have a text buffer we can get. if ((grfAttribs & (uint)__VSRDTATTRIB.RDTA_DocDataReloaded) != 0) { - if (CheckPreconditions(docCookie)) + _foregroundAffinitization.AssertIsForeground(); + if (_runningDocumentTable.IsDocumentInitialized(docCookie) && TryGetMoniker(docCookie, out var moniker) && TryGetBuffer(docCookie, out var buffer)) { - _listener.OnReloadDocumentData(docCookie, _runningDocumentTable.GetDocumentMoniker(docCookie)); + _runningDocumentTable.GetDocumentHierarchyItem(docCookie, out var hierarchy, out _); + _listener.OnOpenDocument(moniker, buffer, hierarchy); } } if ((grfAttribs & (uint)__VSRDTATTRIB.RDTA_Hierarchy) != 0) { - _listener.OnRefreshDocumentContext(docCookie, _runningDocumentTable.GetDocumentMoniker(docCookie)); + _foregroundAffinitization.AssertIsForeground(); + if (_runningDocumentTable.IsDocumentInitialized(docCookie) && TryGetMoniker(docCookie, out var moniker)) + { + _runningDocumentTable.GetDocumentHierarchyItem(docCookie, out var hierarchy, out _); + _listener.OnRefreshDocumentContext(moniker, hierarchy); + } } return VSConstants.S_OK; @@ -108,15 +108,7 @@ public int OnAfterAttributeChangeEx(uint docCookie, uint grfAttribs, IVsHierarch public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) { - if (fFirstShow != 0) - { - if (TryGetBuffer(docCookie, out var buffer)) - { - _listener.OnBeforeOpenDocument(docCookie, _runningDocumentTable.GetDocumentMoniker(docCookie), buffer); - } - } - - return VSConstants.S_OK; + return VSConstants.E_NOTIMPL; } public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) @@ -129,42 +121,114 @@ public int OnBeforeSave(uint docCookie) return VSConstants.E_NOTIMPL; } + public bool IsMonikerValid(string fileName) => _runningDocumentTable.IsMonikerValid(fileName); + /// - /// Gets the text buffer for a document cookie. - /// Also checks to make sure the document is initialized before returning. + /// Attempts to get a text buffer from the specified moniker. /// - public bool TryGetBuffer(uint docCookie, out ITextBuffer textBuffer) + /// the moniker to retrieve the text buffer for. + /// the output text buffer or null if the moniker is invalid / document is not initialized. + /// true if the buffer was found with a non null value. + public bool TryGetBufferFromMoniker(string moniker, out ITextBuffer textBuffer) { + _foregroundAffinitization.AssertIsForeground(); + textBuffer = null; - if (!CheckPreconditions(docCookie)) + if (!IsMonikerValid(moniker)) { return false; } - if ((object)_runningDocumentTable.GetDocumentData(docCookie) is IVsTextBuffer bufferAdapter) + var cookie = _runningDocumentTable.GetDocumentCookie(moniker); + if (!_runningDocumentTable.IsDocumentInitialized(cookie)) { - textBuffer = _editorAdaptersFactoryService.GetDocumentBuffer(bufferAdapter); - return true; + return false; } - return false; + return TryGetBuffer(cookie, out textBuffer); } /// - /// Checks that we're on the UI thread and that the document has already been initialized. + /// Applies an action to all initialized files in the RDT. /// - private bool CheckPreconditions(uint docCookie) + /// the action to apply. + public void EnumerateDocumentSet(Action enumerateAction) { _foregroundAffinitization.AssertIsForeground(); + foreach (var cookie in GetInitializedRunningDocumentTableCookies()) + { + if (TryGetMoniker(cookie, out var moniker) && TryGetBuffer(cookie, out var buffer)) + { + _runningDocumentTable.GetDocumentHierarchyItem(cookie, out var hierarchy, out _); + enumerateAction(moniker, buffer, hierarchy); + } + } + } - if (!_runningDocumentTable.IsDocumentInitialized(docCookie)) + /// + /// Applies an action to all initialized files in the input set. + /// + /// the action to apply. + /// the file set to apply the action to. + public void EnumerateSpecifiedDocumentSet(Action enumerateAction, IEnumerable fileSet) + { + _foregroundAffinitization.AssertIsForeground(); + foreach (var filename in fileSet) { - // We never want to touch documents that haven't been initialized yet, so immediately bail. Any further - // calls to the RDT might accidentally initialize it. - return false; + if (_runningDocumentTable.IsMonikerValid(filename)) + { + var cookie = _runningDocumentTable.GetDocumentCookie(filename); + if (_runningDocumentTable.IsDocumentInitialized(cookie) && TryGetMoniker(cookie, out var moniker) && TryGetBuffer(cookie, out var buffer)) + { + _runningDocumentTable.GetDocumentHierarchyItem(cookie, out var hierarchy, out _); + enumerateAction(moniker, buffer, hierarchy); + } + } + } + } + + private IEnumerable GetInitializedRunningDocumentTableCookies() + { + // Some methods we need here only exist in IVsRunningDocumentTable and not the IVsRunningDocumentTable4 that we + // hold onto as a field + var runningDocumentTable = (IVsRunningDocumentTable)_runningDocumentTable; + ErrorHandler.ThrowOnFailure(runningDocumentTable.GetRunningDocumentsEnum(out var enumRunningDocuments)); + uint[] cookies = new uint[16]; + + while (ErrorHandler.Succeeded(enumRunningDocuments.Next((uint)cookies.Length, cookies, out var cookiesFetched)) + && cookiesFetched > 0) + { + for (int cookieIndex = 0; cookieIndex < cookiesFetched; cookieIndex++) + { + var cookie = cookies[cookieIndex]; + + if (_runningDocumentTable.IsDocumentInitialized(cookie)) + { + yield return cookie; + } + } + } + } + + private bool TryGetMoniker(uint docCookie, out string moniker) + { + moniker = _runningDocumentTable.GetDocumentMoniker(docCookie); + return !string.IsNullOrEmpty(moniker); + } + + private bool TryGetBuffer(uint docCookie, out ITextBuffer textBuffer) + { + textBuffer = null; + + // The cast from dynamic to object doesn't change semantics, but avoids loading the dynamic binder + // which saves us JIT time in this method. + if ((object)_runningDocumentTable.GetDocumentData(docCookie) is IVsTextBuffer bufferAdapter) + { + textBuffer = _editorAdaptersFactoryService.GetDocumentBuffer(bufferAdapter); + return textBuffer != null; } - return true; + return false; } #region IDisposable Support diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs index 75522f6b392785199250e045872680fc6ac4f1f8..23d788bdd289438e280538de17e2855054f9e2c1 100644 --- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs +++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.OpenFileTracker.cs @@ -31,7 +31,6 @@ public sealed class OpenFileTracker : IRunningDocumentTableEventListener private readonly ForegroundThreadAffinitizedObject _foregroundAffinitization; private readonly VisualStudioWorkspaceImpl _workspace; - private readonly IVsRunningDocumentTable4 _runningDocumentTable; private readonly IAsynchronousOperationListener _asyncOperationListener; private readonly RunningDocumentTableEventTracker _runningDocumentTableEventTracker; @@ -62,11 +61,11 @@ public sealed class OpenFileTracker : IRunningDocumentTableEventListener private readonly ReferenceCountedDisposableCache _hierarchyEventSinkCache = new ReferenceCountedDisposableCache(); /// - /// The IVsHierarchies we have subscribed to to watch for any changes to this document cookie. We track this per document cookie, so + /// The IVsHierarchies we have subscribed to to watch for any changes to this moniker. We track this per moniker, so /// when a document is closed we know what we have to incrementally unsubscribe from rather than having to unsubscribe from everything. /// - private readonly MultiDictionary>> _watchedHierarchiesForDocumentCookie - = new MultiDictionary>>(); + private readonly MultiDictionary>> _watchedHierarchiesForDocumentMoniker + = new MultiDictionary>>(); #endregion @@ -86,32 +85,26 @@ private OpenFileTracker(VisualStudioWorkspaceImpl workspace, IVsRunningDocumentT { _workspace = workspace; _foregroundAffinitization = new ForegroundThreadAffinitizedObject(workspace._threadingContext, assertIsForeground: true); - _runningDocumentTable = runningDocumentTable; _asyncOperationListener = componentModel.GetService().GetListener(FeatureAttribute.Workspace); _runningDocumentTableEventTracker = new RunningDocumentTableEventTracker(workspace._threadingContext, componentModel.GetService(), runningDocumentTable, this); } - void IRunningDocumentTableEventListener.OnCloseDocument(uint docCookie, string moniker) - => TryClosingDocumentsForCookie(docCookie, moniker); + void IRunningDocumentTableEventListener.OnOpenDocument(string moniker, ITextBuffer textBuffer, IVsHierarchy hierarchy) + => TryOpeningDocumentsForMoniker(moniker, textBuffer, hierarchy); - void IRunningDocumentTableEventListener.OnRefreshDocumentContext(uint docCookie, string moniker) - => RefreshContextForRunningDocumentTableCookie(docCookie, moniker); + void IRunningDocumentTableEventListener.OnCloseDocument(string moniker) + => TryClosingDocumentsForMoniker(moniker); - void IRunningDocumentTableEventListener.OnReloadDocumentData(uint docCookie, string moniker) - { - // This event is not relevant to the VS workspace open file tracker. - } - - void IRunningDocumentTableEventListener.OnBeforeOpenDocument(uint docCookie, string moniker, ITextBuffer textBuffer) - => TryOpeningDocumentsForNewCookie(docCookie, moniker, textBuffer); + void IRunningDocumentTableEventListener.OnRefreshDocumentContext(string moniker, IVsHierarchy hierarchy) + => RefreshContextForMoniker(moniker, hierarchy); - void IRunningDocumentTableEventListener.OnInitializedDocument(uint docCookie, string moniker, ITextBuffer textBuffer) - => TryOpeningDocumentsForNewCookie(docCookie, moniker, textBuffer); - - void IRunningDocumentTableEventListener.OnRenameDocument(uint docCookie, string newMoniker, string oldMoniker) + /// + /// The VS open file tracker handles renames through + /// This event is unneeded. + /// + void IRunningDocumentTableEventListener.OnRenameDocument(string newMoniker, string oldMoniker, ITextBuffer buffer) { - // This event is not relevant to the VS workspace open file tracker. } public async static Task CreateAsync(VisualStudioWorkspaceImpl workspace, IAsyncServiceProvider asyncServiceProvider) @@ -122,30 +115,7 @@ public async static Task CreateAsync(VisualStudioWorkspaceImpl return new OpenFileTracker(workspace, runningDocumentTable, componentModel); } - private IEnumerable GetInitializedRunningDocumentTableCookies() - { - // Some methods we need here only exist in IVsRunningDocumentTable and not the IVsRunningDocumentTable4 that we - // hold onto as a field - var runningDocumentTable = ((IVsRunningDocumentTable)_runningDocumentTable); - ErrorHandler.ThrowOnFailure(runningDocumentTable.GetRunningDocumentsEnum(out var enumRunningDocuments)); - uint[] cookies = new uint[16]; - - while (ErrorHandler.Succeeded(enumRunningDocuments.Next((uint)cookies.Length, cookies, out var cookiesFetched)) - && cookiesFetched > 0) - { - for (int cookieIndex = 0; cookieIndex < cookiesFetched; cookieIndex++) - { - var cookie = cookies[cookieIndex]; - - if (_runningDocumentTable.IsDocumentInitialized(cookie)) - { - yield return cookie; - } - } - } - } - - private void TryOpeningDocumentsForNewCookie(uint cookie, string moniker, ITextBuffer textBuffer) + private void TryOpeningDocumentsForMoniker(string moniker, ITextBuffer textBuffer, IVsHierarchy hierarchy) { _foregroundAffinitization.AssertIsForeground(); @@ -170,46 +140,41 @@ private void TryOpeningDocumentsForNewCookie(uint cookie, string moniker, ITextB } else { - activeContextProjectId = GetActiveContextProjectIdAndWatchHierarchies(cookie, documentIds.Select(d => d.ProjectId)); + activeContextProjectId = GetActiveContextProjectIdAndWatchHierarchies(moniker, documentIds.Select(d => d.ProjectId), hierarchy); } - if (textBuffer != null) - { - var textContainer = textBuffer.AsTextContainer(); + var textContainer = textBuffer.AsTextContainer(); - foreach (var documentId in documentIds) + foreach (var documentId in documentIds) + { + if (!w.IsDocumentOpen(documentId) && !_workspace._documentsNotFromFiles.Contains(documentId)) { - if (!w.IsDocumentOpen(documentId) && !_workspace._documentsNotFromFiles.Contains(documentId)) + var isCurrentContext = documentId.ProjectId == activeContextProjectId; + if (w.CurrentSolution.ContainsDocument(documentId)) { - var isCurrentContext = documentId.ProjectId == activeContextProjectId; - if (w.CurrentSolution.ContainsDocument(documentId)) - { - w.OnDocumentOpened(documentId, textContainer, isCurrentContext); - } - else if (w.CurrentSolution.ContainsAdditionalDocument(documentId)) - { - w.OnAdditionalDocumentOpened(documentId, textContainer, isCurrentContext); - } - else - { - Debug.Assert(w.CurrentSolution.ContainsAnalyzerConfigDocument(documentId)); - w.OnAnalyzerConfigDocumentOpened(documentId, textContainer, isCurrentContext); - } + w.OnDocumentOpened(documentId, textContainer, isCurrentContext); + } + else if (w.CurrentSolution.ContainsAdditionalDocument(documentId)) + { + w.OnAdditionalDocumentOpened(documentId, textContainer, isCurrentContext); + } + else + { + Debug.Assert(w.CurrentSolution.ContainsAnalyzerConfigDocument(documentId)); + w.OnAnalyzerConfigDocumentOpened(documentId, textContainer, isCurrentContext); } } } }); } - private ProjectId GetActiveContextProjectIdAndWatchHierarchies(uint cookie, IEnumerable projectIds) + private ProjectId GetActiveContextProjectIdAndWatchHierarchies(string moniker, IEnumerable projectIds, IVsHierarchy hierarchy) { _foregroundAffinitization.AssertIsForeground(); // First clear off any existing IVsHierarchies we are watching. Any ones that still matter we will resubscribe to. // We could be fancy and diff, but the cost is probably neglible. - UnsubscribeFromWatchedHierarchies(cookie); - - _runningDocumentTable.GetDocumentHierarchyItem(cookie, out var hierarchy, out _); + UnsubscribeFromWatchedHierarchies(moniker); if (hierarchy == null) { @@ -220,7 +185,7 @@ private ProjectId GetActiveContextProjectIdAndWatchHierarchies(uint cookie, IEnu void WatchHierarchy(IVsHierarchy hierarchyToWatch) { - _watchedHierarchiesForDocumentCookie.Add(cookie, _hierarchyEventSinkCache.GetOrCreate(hierarchyToWatch, h => new HierarchyEventSink(h, this))); + _watchedHierarchiesForDocumentMoniker.Add(moniker, _hierarchyEventSinkCache.GetOrCreate(hierarchyToWatch, h => new HierarchyEventSink(h, this))); } // Take a snapshot of the immutable data structure here to avoid mutation underneath us @@ -274,19 +239,19 @@ void WatchHierarchy(IVsHierarchy hierarchyToWatch) return projectIds.First(); } - private void UnsubscribeFromWatchedHierarchies(uint cookie) + private void UnsubscribeFromWatchedHierarchies(string moniker) { _foregroundAffinitization.AssertIsForeground(); - foreach (var watchedHierarchy in _watchedHierarchiesForDocumentCookie[cookie]) + foreach (var watchedHierarchy in _watchedHierarchiesForDocumentMoniker[moniker]) { watchedHierarchy.Dispose(); } - _watchedHierarchiesForDocumentCookie.Remove(cookie); + _watchedHierarchiesForDocumentMoniker.Remove(moniker); } - private void RefreshContextForRunningDocumentTableCookie(uint cookie, string moniker) + private void RefreshContextForMoniker(string moniker, IVsHierarchy hierarchy) { _foregroundAffinitization.AssertIsForeground(); @@ -303,7 +268,7 @@ private void RefreshContextForRunningDocumentTableCookie(uint cookie, string mon return; } - var activeProjectId = GetActiveContextProjectIdAndWatchHierarchies(cookie, documentIds.Select(d => d.ProjectId)); + var activeProjectId = GetActiveContextProjectIdAndWatchHierarchies(moniker, documentIds.Select(d => d.ProjectId), hierarchy); w.OnDocumentContextUpdated(documentIds.FirstOrDefault(d => d.ProjectId == activeProjectId)); }); } @@ -314,25 +279,23 @@ private void RefreshContextsForHierarchyPropertyChange(IVsHierarchy hierarchy) // We're going to go through each file that has subscriptions, and update them appropriately. // We have to clone this since we will be modifying it under the covers. - foreach (var cookie in _watchedHierarchiesForDocumentCookie.Keys.ToList()) + foreach (var moniker in _watchedHierarchiesForDocumentMoniker.Keys.ToList()) { - foreach (var subscribedHierarchy in _watchedHierarchiesForDocumentCookie[cookie]) + foreach (var subscribedHierarchy in _watchedHierarchiesForDocumentMoniker[moniker]) { if (subscribedHierarchy.Target.Key == hierarchy) { - var moniker = _runningDocumentTable.GetDocumentMoniker(cookie); - RefreshContextForRunningDocumentTableCookie(cookie, moniker); - break; + RefreshContextForMoniker(moniker, hierarchy); } } } } - private void TryClosingDocumentsForCookie(uint cookie, string moniker) + private void TryClosingDocumentsForMoniker(string moniker) { _foregroundAffinitization.AssertIsForeground(); - UnsubscribeFromWatchedHierarchies(cookie); + UnsubscribeFromWatchedHierarchies(moniker); _workspace.ApplyChangeToWorkspace(w => { @@ -440,27 +403,11 @@ public void ProcessQueuedWorkOnUIThread() if (justEnumerateTheEntireRunningDocumentTable) { - foreach (var cookie in GetInitializedRunningDocumentTableCookies()) - { - if (_runningDocumentTableEventTracker.TryGetBuffer(cookie, out var buffer)) - { - TryOpeningDocumentsForNewCookie(cookie, _runningDocumentTable.GetDocumentMoniker(cookie), buffer); - } - } + _runningDocumentTableEventTracker.EnumerateDocumentSet(TryOpeningDocumentsForMoniker); } else if (fileNamesToCheckForOpenDocuments != null) { - foreach (var filename in fileNamesToCheckForOpenDocuments) - { - if (_runningDocumentTable.IsMonikerValid(filename)) - { - var cookie = _runningDocumentTable.GetDocumentCookie(filename); - if (_runningDocumentTableEventTracker.TryGetBuffer(cookie, out var buffer)) - { - TryOpeningDocumentsForNewCookie(cookie, _runningDocumentTable.GetDocumentMoniker(cookie), buffer); - } - } - } + _runningDocumentTableEventTracker.EnumerateSpecifiedDocumentSet(TryOpeningDocumentsForMoniker, fileNamesToCheckForOpenDocuments); } }