提交 6726b73d 编写于 作者: D David Barbet

Combine events and remove needing references to actual table in implementations.

上级 cdc32b73
// 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
/// </summary>
internal interface IRunningDocumentTableEventListener
{
void OnCloseDocument(uint docCookie, string moniker);
/// <summary>
/// Triggered when a document is opened.
/// </summary>
/// <param name="moniker">the non-null moniker of the opened document.</param>
/// <param name="textBuffer">the non-null text buffer of the opened document)</param>
/// <param name="hierarchy">the hierarchy of the text buffer if available.</param>
void OnOpenDocument(string moniker, ITextBuffer textBuffer, IVsHierarchy hierarchy);
void OnRefreshDocumentContext(uint docCookie, string moniker);
/// <summary>
/// Triggered when a document is closed.
/// </summary>
/// <param name="moniker">the non-null moniker of the closed document.</param>
void OnCloseDocument(string moniker);
void OnReloadDocumentData(uint docCookie, string moniker);
/// <summary>
/// Triggered when a document context is refreshed with a new hierarchy.
/// </summary>
/// <param name="moniker">the non-null moniker of the document that changed.</param>
/// <param name="hierarchy">the hierarchy of the text buffer if available.</param>
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);
/// <summary>
/// Triggered when a document moniker changes.
/// </summary>
/// <param name="newMoniker">the document's new moniker.</param>
/// <param name="oldMoniker">the document's old moniker.</param>
/// <param name="textBuffer">the document's buffer.</param>
void OnRenameDocument(string newMoniker, string oldMoniker, ITextBuffer textBuffer);
}
}
......@@ -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
/// <see cref="WorkspaceRegistration"/> instances for all open buffers being tracked by by this object
/// for possible inclusion into this workspace.
/// </summary>
private IBidirectionalMap<uint, WorkspaceRegistration> _docCookieToWorkspaceRegistration = BidirectionalMap<uint, WorkspaceRegistration>.Empty;
private IBidirectionalMap<string, WorkspaceRegistration> _monikerToWorkspaceRegistration = BidirectionalMap<string, WorkspaceRegistration>.Empty;
/// <summary>
/// The mapping of all doc cookies in the RDT and the <see cref="ProjectId"/> of the project and <see cref="SourceTextContainer"/> of the open
/// file we have created for that open buffer. An entry should only be in here if it's also already in <see cref="_docCookieToWorkspaceRegistration"/>.
/// The mapping of all monikers in the RDT and the <see cref="ProjectId"/> of the project and <see cref="SourceTextContainer"/> of the open
/// file we have created for that open buffer. An entry should only be in here if it's also already in <see cref="_monikerToWorkspaceRegistration"/>.
/// </summary>
private readonly Dictionary<uint, (ProjectId projectId, SourceTextContainer textContainer)> _docCookiesToProjectIdAndContainer = new Dictionary<uint, (ProjectId, SourceTextContainer)>();
private readonly Dictionary<string, (ProjectId projectId, SourceTextContainer textContainer)> _monikersToProjectIdAndContainer = new Dictionary<string, (ProjectId, SourceTextContainer)>();
private readonly ImmutableArray<MetadataReference> _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<MetadataReference> 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.
/// </summary>
/// <returns>true if we were previously tracking it.</returns>
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));
}
/// <summary>
......@@ -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)
{
......
// 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);
/// <summary>
/// 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.
/// </summary>
public bool TryGetBuffer(uint docCookie, out ITextBuffer textBuffer)
/// <param name="moniker">the moniker to retrieve the text buffer for.</param>
/// <param name="textBuffer">the output text buffer or null if the moniker is invalid / document is not initialized.</param>
/// <returns>true if the buffer was found with a non null value.</returns>
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);
}
/// <summary>
/// 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.
/// </summary>
private bool CheckPreconditions(uint docCookie)
/// <param name="enumerateAction">the action to apply.</param>
public void EnumerateDocumentSet(Action<string, ITextBuffer, IVsHierarchy> 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))
/// <summary>
/// Applies an action to all initialized files in the input set.
/// </summary>
/// <param name="enumerateAction">the action to apply.</param>
/// <param name="fileSet">the file set to apply the action to.</param>
public void EnumerateSpecifiedDocumentSet(Action<string, ITextBuffer, IVsHierarchy> enumerateAction, IEnumerable<string> 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<uint> 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
......
......@@ -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<IVsHierarchy, HierarchyEventSink> _hierarchyEventSinkCache = new ReferenceCountedDisposableCache<IVsHierarchy, HierarchyEventSink>();
/// <summary>
/// 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.
/// </summary>
private readonly MultiDictionary<uint, IReferenceCountedDisposable<ICacheEntry<IVsHierarchy, HierarchyEventSink>>> _watchedHierarchiesForDocumentCookie
= new MultiDictionary<uint, IReferenceCountedDisposable<ICacheEntry<IVsHierarchy, HierarchyEventSink>>>();
private readonly MultiDictionary<string, IReferenceCountedDisposable<ICacheEntry<IVsHierarchy, HierarchyEventSink>>> _watchedHierarchiesForDocumentMoniker
= new MultiDictionary<string, IReferenceCountedDisposable<ICacheEntry<IVsHierarchy, HierarchyEventSink>>>();
#endregion
......@@ -86,32 +85,26 @@ private OpenFileTracker(VisualStudioWorkspaceImpl workspace, IVsRunningDocumentT
{
_workspace = workspace;
_foregroundAffinitization = new ForegroundThreadAffinitizedObject(workspace._threadingContext, assertIsForeground: true);
_runningDocumentTable = runningDocumentTable;
_asyncOperationListener = componentModel.GetService<IAsynchronousOperationListenerProvider>().GetListener(FeatureAttribute.Workspace);
_runningDocumentTableEventTracker = new RunningDocumentTableEventTracker(workspace._threadingContext,
componentModel.GetService<IVsEditorAdaptersFactoryService>(), 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)
/// <summary>
/// The VS open file tracker handles renames through <see cref="QueueCheckForFilesBeingOpen"/>
/// This event is unneeded.
/// </summary>
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<OpenFileTracker> CreateAsync(VisualStudioWorkspaceImpl workspace, IAsyncServiceProvider asyncServiceProvider)
......@@ -122,30 +115,7 @@ public async static Task<OpenFileTracker> CreateAsync(VisualStudioWorkspaceImpl
return new OpenFileTracker(workspace, runningDocumentTable, componentModel);
}
private IEnumerable<uint> 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<ProjectId> projectIds)
private ProjectId GetActiveContextProjectIdAndWatchHierarchies(string moniker, IEnumerable<ProjectId> 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);
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册