internal void UpdateContextHierarchyIfContainsDocument(IVsHierarchy sharedHierarchy, DocumentId documentId) { // TODO: This is a very roundabout way to update the context // The sharedHierarchy passed in has a new context, but we don't know what it is. // The documentId passed in is associated with this sharedHierarchy, and this method // will be called once for each such documentId. During this process, one of these // documentIds will actually belong to the new SharedItemContextHierarchy. Once we // find that one, we can map back to the open buffer and set its active context to // the appropriate project. var contextHierarchy = LinkedFileUtilities.GetSharedItemContextHierarchy(sharedHierarchy); // TODO: linear search var matchingProject = CurrentSolution.Projects.FirstOrDefault(p => GetHostProject(p.Id).Hierarchy == contextHierarchy); if (matchingProject == null) { // How? return; } if (!matchingProject.ContainsDocument(documentId)) { // While this documentId is associated with one of the head projects for this // sharedHierarchy, it is not associated with the new context hierarchy. Another // documentId will be passed to this method and update the context. return; } // This documentId belongs to the new SharedItemContextHierarchy. Update the associated // buffer. OnDocumentContextUpdated(documentId); }
public VisualStudioProjectTracker(IThreadingContext threadingContext, IServiceProvider serviceProvider, Workspace workspace, LinkedFileUtilities linkedFileUtilities) : base(threadingContext, assertIsForeground: true) { _projectMap = new Dictionary <ProjectId, AbstractProject>(); _projectPathToIdMap = new Dictionary <string, ProjectId>(StringComparer.OrdinalIgnoreCase); _serviceProvider = serviceProvider; _workspace = workspace; WorkspaceServices = workspace.Services; _linkedFileUtilities = linkedFileUtilities; _vsSolution = (IVsSolution)serviceProvider.GetService(typeof(SVsSolution)); _runningDocumentTable = (IVsRunningDocumentTable4)serviceProvider.GetService(typeof(SVsRunningDocumentTable)); // It's possible that we're loading after the solution has already fully loaded, so see if we missed the event var shellMonitorSelection = (IVsMonitorSelection)serviceProvider.GetService(typeof(SVsShellMonitorSelection)); if (ErrorHandler.Succeeded(shellMonitorSelection.GetCmdUIContextCookie(VSConstants.UICONTEXT.SolutionExistsAndFullyLoaded_guid, out var fullyLoadedContextCookie))) { if (ErrorHandler.Succeeded(shellMonitorSelection.IsCmdUIContextActive(fullyLoadedContextCookie, out var fActive)) && fActive != 0) { _solutionLoadComplete = true; } } }
internal override void SetDocumentContext(DocumentId documentId) { var hostDocument = GetHostDocument(documentId); var hierarchy = hostDocument.Project.Hierarchy; var itemId = hostDocument.GetItemId(); if (itemId == (uint)VSConstants.VSITEMID.Nil) { // the document has been removed from the solution return; } var sharedHierarchy = LinkedFileUtilities.GetSharedHierarchyForItem(hierarchy, itemId); if (sharedHierarchy != null) { // Universal Project shared files // Change the SharedItemContextHierarchy of the project's parent hierarchy, then // hierarchy events will trigger the workspace to update. var hr = sharedHierarchy.SetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID7.VSHPROPID_SharedItemContextHierarchy, hierarchy); } else { // Regular linked files // Transfer the item (open buffer) to the new hierarchy, and then hierarchy events // will trigger the workspace to update. var vsproj = hierarchy as IVsProject3; var hr = vsproj.TransferItem(hostDocument.FilePath, hostDocument.FilePath, punkWindowFrame: null); } }
private void SubscribeToSharedHierarchyEvents(DocumentId documentId) { // Todo: maybe avoid double alerts. var hostDocument = _workspace.GetHostDocument(documentId); if (hostDocument == null) { return; } var hierarchy = hostDocument.Project.Hierarchy; var itemId = hostDocument.GetItemId(); if (itemId == (uint)VSConstants.VSITEMID.Nil) { // the document has been removed from the solution return; } var sharedHierarchy = LinkedFileUtilities.GetSharedHierarchyForItem(hierarchy, itemId); if (sharedHierarchy != null) { uint cookie; var eventSink = new HierarchyEventsSink(_workspace, sharedHierarchy, documentId); var hr = sharedHierarchy.AdviseHierarchyEvents(eventSink, out cookie); if (hr == VSConstants.S_OK && !_documentIdToHierarchyEventsCookieMap.ContainsKey(documentId)) { _documentIdToHierarchyEventsCookieMap.Add(documentId, cookie); } } }
private void UnsubscribeFromSharedHierarchyEvents(DocumentId documentId) { var hostDocument = _workspace.GetHostDocument(documentId); var hierarchy = hostDocument.Project.Hierarchy; var itemId = hostDocument.GetItemId(); if (itemId == (uint)VSConstants.VSITEMID.Nil) { // the document has been removed from the solution return; } var sharedHierarchy = LinkedFileUtilities.GetSharedHierarchyForItem(hierarchy, itemId); if (sharedHierarchy != null) { uint cookie; if (_documentIdToHierarchyEventsCookieMap.TryGetValue(documentId, out cookie)) { var hr = sharedHierarchy.UnadviseHierarchyEvents(cookie); _documentIdToHierarchyEventsCookieMap.Remove(documentId); } } }
/// <summary> /// Creates a document provider. /// </summary> /// <param name="serviceProvider">Service provider</param> /// <param name="documentTrackingService">An optional <see cref="VisualStudioDocumentTrackingService"/> to track active and visible documents.</param> public DocumentProvider( VisualStudioProjectTracker projectTracker, IServiceProvider serviceProvider, VisualStudioDocumentTrackingService documentTrackingService, LinkedFileUtilities linkedFileUtilities) : base(projectTracker.ThreadingContext) { var componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); _projectTracker = projectTracker; this._documentTrackingServiceOpt = documentTrackingService; this._runningDocumentTable = (IVsRunningDocumentTable4)serviceProvider.GetService(typeof(SVsRunningDocumentTable)); this._editorAdaptersFactoryService = componentModel.GetService <IVsEditorAdaptersFactoryService>(); this._contentTypeRegistryService = componentModel.GetService <IContentTypeRegistryService>(); _linkedFileUtilities = linkedFileUtilities; _textManager = (IVsTextManager)serviceProvider.GetService(typeof(SVsTextManager)); _fileChangeService = (IVsFileChangeEx)serviceProvider.GetService(typeof(SVsFileChangeEx)); var shell = (IVsShell)serviceProvider.GetService(typeof(SVsShell)); if (shell == null) { // This can happen only in tests, bail out. return; } var runningDocumentTableForEvents = (IVsRunningDocumentTable)_runningDocumentTable; Marshal.ThrowExceptionForHR(runningDocumentTableForEvents.AdviseRunningDocTableEvents(new RunningDocTableEventsSink(this), out _runningDocumentTableEventCookie)); }
private bool IsCurrentContext(DocumentKey documentKey) { AssertIsForeground(); var document = documentKey.HostProject.GetCurrentDocumentFromPath(documentKey.Moniker); return(document != null && LinkedFileUtilities.IsCurrentContextHierarchy(document, _runningDocumentTable)); }
internal void UpdateDocumentContextIfContainsDocument(IVsHierarchy sharedHierarchy, DocumentId documentId) { // TODO: This is a very roundabout way to update the context // The sharedHierarchy passed in has a new context, but we don't know what it is. // The documentId passed in is associated with this sharedHierarchy, and this method // will be called once for each such documentId. During this process, one of these // documentIds will actually belong to the new SharedItemContextHierarchy. Once we // find that one, we can map back to the open buffer and set its active context to // the appropriate project. // Note that if there is a single head project and it's in the process of being unloaded // there might not be a host project. var hostProject = LinkedFileUtilities.GetContextHostProject(sharedHierarchy, DeferredState.ProjectTracker); if (hostProject?.Hierarchy == sharedHierarchy) { return; } if (hostProject.Id != documentId.ProjectId) { // While this documentId is associated with one of the head projects for this // sharedHierarchy, it is not associated with the new context hierarchy. Another // documentId will be passed to this method and update the context. return; } // This documentId belongs to the new SharedItemContextHierarchy. Update the associated // buffer. OnDocumentContextUpdated(documentId); }
private bool IsCurrentContext(uint docCookie, DocumentKey documentKey) { AssertIsForeground(); _runningDocumentTable.GetDocumentHierarchyItem(docCookie, out var hierarchy, out var itemid); // If it belongs to a Shared Code or ASP.NET 5 project, then find the correct host project var hostProject = LinkedFileUtilities.GetContextHostProject(hierarchy, _projectContainer); return(documentKey.HostProject == hostProject); }
/// <summary> /// Finds the <see cref="DocumentId"/> related to the given <see cref="DocumentId"/> that /// is in the current context. For regular files (non-shared and non-linked) and closed /// linked files, this is always the provided <see cref="DocumentId"/>. For open linked /// files and open shared files, the active context is already tracked by the /// <see cref="Workspace"/> and can be looked up directly. For closed shared files, the /// document in the shared project's <see cref="__VSHPROPID7.VSHPROPID_SharedItemContextHierarchy"/> /// is preferred. /// </summary> internal override DocumentId GetDocumentIdInCurrentContext(DocumentId documentId) { // If the document is open, then the Workspace knows the current context for both // linked and shared files if (IsDocumentOpen(documentId)) { return(base.GetDocumentIdInCurrentContext(documentId)); } var hostDocument = GetHostDocument(documentId); if (hostDocument == null) { // This can happen if the document was temporary and has since been closed/deleted. return(base.GetDocumentIdInCurrentContext(documentId)); } var itemId = hostDocument.GetItemId(); if (itemId == (uint)VSConstants.VSITEMID.Nil) { // An itemid is required to determine whether the file belongs to a Shared Project return(base.GetDocumentIdInCurrentContext(documentId)); } // If this is a regular document or a closed linked (non-shared) document, then use the // default logic for determining current context. var sharedHierarchy = LinkedFileUtilities.GetSharedHierarchyForItem(hostDocument.Project.Hierarchy, itemId); if (sharedHierarchy == null) { return(base.GetDocumentIdInCurrentContext(documentId)); } // This is a closed shared document, so we must determine the correct context. var hostProject = LinkedFileUtilities.GetContextHostProject(sharedHierarchy, DeferredState.ProjectTracker); var matchingProject = CurrentSolution.GetProject(hostProject.Id); if (matchingProject == null || hostProject.Hierarchy == sharedHierarchy) { return(base.GetDocumentIdInCurrentContext(documentId)); } if (matchingProject.ContainsDocument(documentId)) { // The provided documentId is in the current context project return(documentId); } // The current context document is from another project. var linkedDocumentIds = CurrentSolution.GetDocument(documentId).GetLinkedDocumentIds(); var matchingDocumentId = linkedDocumentIds.FirstOrDefault(id => id.ProjectId == matchingProject.Id); return(matchingDocumentId ?? base.GetDocumentIdInCurrentContext(documentId)); }
public void AddAdditionalFile(string additionalFilePath) { var document = this.DocumentProvider.TryGetDocumentForFile(this, (uint)VSConstants.VSITEMID.Nil, filePath: additionalFilePath, sourceCodeKind: SourceCodeKind.Regular, canUseTextBuffer: (b) => true); if (document == null) { return; } AddAdditionalDocument(document, isCurrentContext: document.Project.Hierarchy == LinkedFileUtilities.GetContextHierarchy(document, RunningDocumentTable)); }
private bool IsCurrentContext(uint docCookie, DocumentKey documentKey) { IVsHierarchy hierarchy; uint itemid; RunningDocumentTable.GetDocumentHierarchyItem(docCookie, out hierarchy, out itemid); // If it belongs to a Shared Code project, then the current context is determined // by the SharedItemContextHierarchy. var hierarchyOwningDocument = LinkedFileUtilities.GetSharedItemContextHierarchy(hierarchy) ?? hierarchy; return(documentKey.HostProject.Hierarchy == hierarchyOwningDocument); }
public StandardTextDocument( DocumentProvider documentProvider, IVisualStudioHostProject project, DocumentKey documentKey, uint itemId, SourceCodeKind sourceCodeKind, ITextBufferFactoryService textBufferFactoryService, ITextUndoHistoryRegistry textUndoHistoryRegistry, IVsFileChangeEx fileChangeService, ITextBuffer openTextBuffer, DocumentId id) { Contract.ThrowIfNull(documentProvider); Contract.ThrowIfNull(textBufferFactoryService); this.Project = project; this.Id = id ?? DocumentId.CreateNewId(project.Id, documentKey.Moniker); this.Folders = project.GetFolderNames(itemId); // TODO: // this one doesn't work for asynchronous project load situation where shared projects is loaded after one uses shared file. // we need to figure out what to do on those case. but this works for project k case. // opened an issue to track this issue - https://github.com/dotnet/roslyn/issues/1859 this.SharedHierarchy = project.Hierarchy == null ? null : LinkedFileUtilities.GetSharedHierarchyForItem(project.Hierarchy, itemId); _documentProvider = documentProvider; this.Key = documentKey; this.SourceCodeKind = sourceCodeKind; _itemMoniker = documentKey.Moniker; _textBufferFactoryService = textBufferFactoryService; _textUndoHistoryRegistry = textUndoHistoryRegistry; _fileChangeTracker = new FileChangeTracker(fileChangeService, this.FilePath); _fileChangeTracker.UpdatedOnDisk += OnUpdatedOnDisk; _openTextBuffer = openTextBuffer; _snapshotTracker = new ReiteratedVersionSnapshotTracker(openTextBuffer); // The project system does not tell us the CodePage specified in the proj file, so // we use null to auto-detect. _doNotAccessDirectlyLoader = new FileTextLoader(documentKey.Moniker, defaultEncoding: null); // If we aren't already open in the editor, then we should create a file change notification if (openTextBuffer == null) { _fileChangeTracker.StartFileChangeListeningAsync(); } }
public StandardTextDocument( DocumentProvider documentProvider, IVisualStudioHostProject project, DocumentKey documentKey, uint itemId, SourceCodeKind sourceCodeKind, ITextBufferFactoryService textBufferFactoryService, ITextUndoHistoryRegistry textUndoHistoryRegistry, IVsFileChangeEx fileChangeService, ITextBuffer openTextBuffer, DocumentId id) { Contract.ThrowIfNull(documentProvider); Contract.ThrowIfNull(textBufferFactoryService); this.Project = project; this.Id = id ?? DocumentId.CreateNewId(project.Id, documentKey.Moniker); this.Folders = project.GetFolderNames(itemId); this.SharedHierarchy = project.Hierarchy == null ? null : LinkedFileUtilities.GetSharedHierarchyForItem(project.Hierarchy, itemId); _documentProvider = documentProvider; this.Key = documentKey; this.SourceCodeKind = sourceCodeKind; _itemMoniker = documentKey.Moniker; _textBufferFactoryService = textBufferFactoryService; _textUndoHistoryRegistry = textUndoHistoryRegistry; _fileChangeTracker = new FileChangeTracker(fileChangeService, this.FilePath); _fileChangeTracker.UpdatedOnDisk += OnUpdatedOnDisk; _openTextBuffer = openTextBuffer; _snapshotTracker = new ReiteratedVersionSnapshotTracker(openTextBuffer); // The project system does not tell us the CodePage specified in the proj file, so // we use null to auto-detect. _doNotAccessDirectlyLoader = new FileTextLoader(documentKey.Moniker, defaultEncoding: null); // If we aren't already open in the editor, then we should create a file change notification if (openTextBuffer == null) { _fileChangeTracker.StartFileChangeListeningAsync(); } }
internal void StartPushingToWorkspaceAndNotifyOfOpenDocuments( IEnumerable <AbstractProject> projects) { AssertIsForeground(); // If the workspace host isn't actually ready yet, we shouldn't do anything. // Also, if the solution is closing we shouldn't do anything either, because all of our state is // in the process of going away. This can happen if we receive notification that a document has // opened in the middle of the solution close operation. if (!this.HostReadyForEvents || _tracker._solutionIsClosing) { return; } // We need to push these projects and any project dependencies we already know about. Therefore, compute the // transitive closure of the projects that haven't already been pushed, keeping them in appropriate order. var visited = new HashSet <AbstractProject>(); var inOrderToPush = new List <AbstractProject>(); foreach (var project in projects) { AddToPushListIfNeeded(project, inOrderToPush, visited); } var projectInfos = inOrderToPush.Select(p => p.CreateProjectInfoForCurrentState()).ToImmutableArray(); // We need to enable projects to start pushing changes to workspace hosts even before we add the solution/project to the host. // This is required because between the point we capture the project info for current state and the point where we start pushing to workspace hosts, // project system may send new events on the AbstractProject on a background thread, and these won't get pushed over to the workspace hosts as we didn't set the _pushingChangesToWorkspaceHost flag on the AbstractProject. // By invoking StartPushingToWorkspaceHosts upfront, any project state changes on the background thread will enqueue notifications to workspace hosts on foreground scheduled tasks. foreach (var project in inOrderToPush) { project.StartPushingToWorkspaceHosts(); } if (!_solutionAdded) { string solutionFilePath = null; VersionStamp?version = default(VersionStamp?); // Figure out the solution version if (ErrorHandler.Succeeded(_tracker._vsSolution.GetSolutionInfo(out var solutionDirectory, out var solutionFileName, out var userOptsFile)) && solutionFileName != null) { solutionFilePath = Path.Combine(solutionDirectory, solutionFileName); if (File.Exists(solutionFilePath)) { version = VersionStamp.Create(File.GetLastWriteTimeUtc(solutionFilePath)); } } if (version == null) { version = VersionStamp.Create(); } var id = SolutionId.CreateNewId(string.IsNullOrWhiteSpace(solutionFileName) ? null : solutionFileName); _tracker.RegisterSolutionProperties(id); var solutionInfo = SolutionInfo.Create(id, version.Value, solutionFilePath, projects: projectInfos); this.Host.OnSolutionAdded(solutionInfo); _solutionAdded = true; } else { // The solution is already added, so we'll just do project added notifications from here foreach (var projectInfo in projectInfos) { this.Host.OnProjectAdded(projectInfo); } } foreach (var project in inOrderToPush) { _pushedProjects.Add(project); foreach (var document in project.GetCurrentDocuments()) { if (document.IsOpen) { this.Host.OnDocumentOpened( document.Id, document.GetOpenTextBuffer(), isCurrentContext: LinkedFileUtilities.IsCurrentContextHierarchy(document, _tracker._runningDocumentTable)); } } } }
/// <summary> /// Starts pushing events from the given projects to the workspace hosts and notifies about open documents. /// </summary> /// <remarks>This method must be called on the foreground thread.</remarks> internal void StartPushingToWorkspaceAndNotifyOfOpenDocuments(IEnumerable <AbstractProject> projects) { AssertIsForeground(); // If the solution is closing we shouldn't do anything, because all of our state is // in the process of going away. This can happen if we receive notification that a document has // opened in the middle of the solution close operation. if (_solutionIsClosing) { return; } // We need to push these projects and any project dependencies we already know about. Therefore, compute the // transitive closure of the projects that haven't already been pushed, keeping them in appropriate order. var visited = new HashSet <AbstractProject>(); var inOrderToPush = new List <AbstractProject>(); void addToInOrderToPush(AbstractProject project) { Contract.ThrowIfFalse(ContainsProject(project)); // Bail out if any of the following is true: // 1. We have already started pushing changes for this project OR // 2. We have already visited this project in a prior recursive call if (_pushedProjects.Contains(project) || !visited.Add(project)) { return; } foreach (var projectReference in project.GetCurrentProjectReferences()) { addToInOrderToPush(GetProject(projectReference.ProjectId)); } inOrderToPush.Add(project); } foreach (var project in projects) { addToInOrderToPush(project); } var projectInfos = inOrderToPush.Select(p => p.CreateProjectInfoForCurrentState()).ToImmutableArray(); // We need to enable projects to start pushing changes to the workspace even before we add the solution/project to the host. // This is required because between the point we capture the project info for current state and the point where we start pushing to the workspace, // project system may send new events on the AbstractProject on a background thread, and these won't get pushed over to the workspace hosts as we didn't set the _pushingChangesToWorkspaceHost flag on the AbstractProject. // By invoking StartPushingToWorkspaceHosts upfront, any project state changes on the background thread will enqueue notifications to workspace hosts on foreground scheduled tasks. foreach (var project in inOrderToPush) { project.PushingChangesToWorkspace = true; Logger.Log(FunctionId.AbstractProject_PushedToWorkspace, KeyValueLogMessage.Create(LogType.Trace, m => { m[AbstractProject.ProjectGuidPropertyName] = project.Guid; })); } using (WorkspaceServices.GetService <IGlobalOperationNotificationService>()?.Start("Add Project to Workspace")) { if (!_solutionAdded) { string solutionFilePath = null; VersionStamp?version = default; // Figure out the solution version if (ErrorHandler.Succeeded(_vsSolution.GetSolutionInfo(out var solutionDirectory, out var solutionFileName, out var userOptsFile)) && solutionFileName != null) { solutionFilePath = Path.Combine(solutionDirectory, solutionFileName); if (File.Exists(solutionFilePath)) { version = VersionStamp.Create(File.GetLastWriteTimeUtc(solutionFilePath)); } } if (version == null) { version = VersionStamp.Create(); } var id = SolutionId.CreateNewId(string.IsNullOrWhiteSpace(solutionFileName) ? null : solutionFileName); var solutionInfo = SolutionInfo.Create(id, version.Value, solutionFilePath, projects: projectInfos); NotifyWorkspace(workspace => workspace.OnSolutionAdded(solutionInfo)); _solutionAdded = true; var persistenceService = WorkspaceServices.GetRequiredService <IPersistentStorageLocationService>() as VisualStudioPersistentStorageLocationService; persistenceService?.UpdateForVisualStudioWorkspace(_workspace); } else { // The solution is already added, so we'll just do project added notifications from here foreach (var projectInfo in projectInfos) { NotifyWorkspace(workspace => workspace.OnProjectAdded(projectInfo)); } } foreach (var project in inOrderToPush) { _pushedProjects.Add(project); foreach (var document in project.GetCurrentDocuments()) { if (document.IsOpen) { NotifyWorkspace(workspace => { workspace.OnDocumentOpened( document.Id, document.GetOpenTextBuffer().AsTextContainer(), isCurrentContext: LinkedFileUtilities.IsCurrentContextHierarchy(document, _runningDocumentTable)); (workspace as VisualStudioWorkspaceImpl)?.ConnectToSharedHierarchyEvents(document); }); } } } } }
public DeferredInitializationState(IThreadingContext threadingContext, VisualStudioWorkspaceImpl workspace, IServiceProvider serviceProvider, LinkedFileUtilities linkedFileUtilities) : base(threadingContext, assertIsForeground: true) { ServiceProvider = serviceProvider; ShellOpenDocumentService = (IVsUIShellOpenDocument)serviceProvider.GetService(typeof(SVsUIShellOpenDocument)); ProjectTracker = new VisualStudioProjectTracker(threadingContext, serviceProvider, workspace, linkedFileUtilities); // Ensure the document tracking service is initialized on the UI thread var documentTrackingService = (VisualStudioDocumentTrackingService)workspace.Services.GetService <IDocumentTrackingService>(); var documentProvider = new DocumentProvider(ProjectTracker, serviceProvider, documentTrackingService, linkedFileUtilities); var metadataReferenceProvider = workspace.Services.GetService <VisualStudioMetadataReferenceManager>(); var ruleSetFileProvider = workspace.Services.GetService <VisualStudioRuleSetManager>(); ProjectTracker.InitializeProviders(documentProvider, metadataReferenceProvider, ruleSetFileProvider); var componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); var saveEventsService = componentModel.GetService <SaveEventsService>(); saveEventsService.StartSendingSaveEvents(); VisualStudioProjectCacheHostServiceFactory.ConnectProjectCacheServiceToDocumentTracking(workspace.Services, (ProjectCacheService)workspace.CurrentSolution.Services.CacheService); // Ensure the options factory services are initialized on the UI thread workspace.Services.GetService <IOptionService>(); }