// Copyright (c) .NET Foundation. 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; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor { internal class DefaultVisualStudioDocumentTracker : VisualStudioDocumentTracker { private readonly ProjectSnapshotManager _projectManager; private readonly TextBufferProjectService _projectService; private readonly ITextBuffer _textBuffer; private readonly List _textViews; private readonly Workspace _workspace; private bool _isSupportedProject; private ProjectSnapshot _project; private string _projectPath; public override event EventHandler ContextChanged; public DefaultVisualStudioDocumentTracker( ProjectSnapshotManager projectManager, TextBufferProjectService projectService, Workspace workspace, ITextBuffer textBuffer) { if (projectManager == null) { throw new ArgumentNullException(nameof(projectManager)); } if (projectService == null) { throw new ArgumentNullException(nameof(projectService)); } if (workspace == null) { throw new ArgumentNullException(nameof(workspace)); } if (textBuffer == null) { throw new ArgumentNullException(nameof(textBuffer)); } _projectManager = projectManager; _projectService = projectService; _textBuffer = textBuffer; _workspace = workspace; // For now we assume that the workspace is the always default VS workspace. _textViews = new List(); } internal override ProjectExtensibilityConfiguration Configuration => _project.Configuration; public override bool IsSupportedProject => _isSupportedProject; public override Project Project => _workspace.CurrentSolution.GetProject(_project.UnderlyingProject.Id); public override ITextBuffer TextBuffer => _textBuffer; public override IReadOnlyList TextViews => _textViews; public IList TextViewsInternal => _textViews; public override Workspace Workspace => _workspace; public void Subscribe() { // Fundamentally we have a Razor half of the world as as soon as the document is open - and then later // the C# half of the world will be initialized. This code is in general pretty tolerant of // unexpected /impossible states. // // We also want to successfully shut down if the buffer is something other than .cshtml. IVsHierarchy hierarchy = null; string projectPath = null; var isSupportedProject = false; if (_textBuffer.ContentType.IsOfType(RazorLanguage.ContentType) && // We expect the document to have a hierarchy even if it's not a real 'project'. // However the hierarchy can be null when the document is in the process of closing. (hierarchy = _projectService.GetHierarchy(_textBuffer)) != null) { projectPath = _projectService.GetProjectPath(hierarchy); isSupportedProject = _projectService.IsSupportedProject(hierarchy); } if (!isSupportedProject || projectPath == null) { return; } _isSupportedProject = isSupportedProject; _projectPath = projectPath; _project = _projectManager.GetProjectWithFilePath(projectPath); _projectManager.Changed += ProjectManager_Changed; OnContextChanged(_project); } public void Unsubscribe() { _projectManager.Changed -= ProjectManager_Changed; } private void OnContextChanged(ProjectSnapshot project) { _project = project; var handler = ContextChanged; if (handler != null) { handler(this, EventArgs.Empty); } } private void ProjectManager_Changed(object sender, ProjectChangeEventArgs e) { if (_projectPath != null && string.Equals(_projectPath, e.Project.UnderlyingProject.FilePath, StringComparison.OrdinalIgnoreCase)) { OnContextChanged(e.Project); } } } }