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);
}
}