// 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.ObjectModel; using System.ComponentModel.Composition; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Razor; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Razor { [ContentType(RazorLanguage.ContentType)] [TextViewRole(PredefinedTextViewRoles.Editable)] //[Export(typeof(IWpfTextViewConnectionListener))] - we're not exporting this interface because we only want it to be hooked up // in developer mode. [Export(typeof(TextViewRazorDocumentTrackerService))] internal class DefaultTextViewRazorDocumentTrackerService : TextViewRazorDocumentTrackerService, IWpfTextViewConnectionListener { private readonly ITextDocumentFactoryService _documentFactory; private readonly IServiceProvider _services; private readonly Workspace _workspace; private RunningDocumentTable _runningDocumentTable; private IVsSolution _solution; [ImportingConstructor] public DefaultTextViewRazorDocumentTrackerService( [Import(typeof(SVsServiceProvider))] IServiceProvider services, ITextDocumentFactoryService documentFactory, [Import(typeof(VisualStudioWorkspace))] Workspace workspace) { if (services == null) { throw new ArgumentNullException(nameof(services)); } if (documentFactory == null) { throw new ArgumentNullException(nameof(documentFactory)); } if (workspace == null) { throw new ArgumentNullException(nameof(workspace)); } _services = services; _documentFactory = documentFactory; _workspace = workspace; } // Lazy to avoid needing this in unit tests. private RunningDocumentTable RunningDocumentTable { get { if (_runningDocumentTable == null) { _runningDocumentTable = new RunningDocumentTable(_services); } return _runningDocumentTable; } } // Lazy to avoid needing this in unit tests. private IVsSolution Solution { get { if (_solution == null) { _solution = (IVsSolution)_services.GetService(typeof(SVsSolution)); } return _solution; } } public Workspace Workspace => _workspace; public override RazorDocumentTracker CreateTracker(ITextView textView) { if (textView == null) { throw new ArgumentNullException(nameof(textView)); } if (!textView.Properties.TryGetProperty(typeof(RazorDocumentTracker), out var tracker)) { tracker = new DefaultTextViewRazorDocumentTracker(this, textView); textView.Properties.AddProperty(typeof(RazorDocumentTracker), tracker); } return tracker; } public void SubjectBuffersConnected(IWpfTextView textView, ConnectionReason reason, Collection subjectBuffers) { if (textView == null) { throw new ArgumentException(nameof(textView)); } if (subjectBuffers == null) { throw new ArgumentNullException(nameof(subjectBuffers)); } // This means a Razor buffer has been attached to this ITextView or the ITextView is just opening with Razor content. // // Call CreateTracker just for the side effect. The tracker will do all of the real work after it's initialized. CreateTracker(textView); } public void SubjectBuffersDisconnected(IWpfTextView textView, ConnectionReason reason, Collection subjectBuffers) { if (textView == null) { throw new ArgumentException(nameof(textView)); } if (subjectBuffers == null) { throw new ArgumentNullException(nameof(subjectBuffers)); } // This means a Razor buffer has be detached from this ITextView or the ITextView is closing. // // Do nothing, the tracker will update itself. } public virtual IVsHierarchy GetProject(ITextBuffer textBuffer) { if (textBuffer == null) { throw new ArgumentNullException(nameof(textBuffer)); } // If there's no document we can't find the filename, or look for a matching hierarchy. if (!_documentFactory.TryGetTextDocument(textBuffer, out var textDocument)) { return null; } RunningDocumentTable.FindDocument(textDocument.FilePath, out var hierarchy, out uint itemId, out uint cookie); // We don't currently try to look a Roslyn ProjectId at this point, we just want to know some // basic things. // See https://github.com/dotnet/roslyn/blob/4e3db2b7a0732d45a720e9ed00c00cd22ab67a14/src/VisualStudio/Core/SolutionExplorerShim/HierarchyItemToProjectIdMap.cs#L47 // for a more complete implementation. return hierarchy; } public virtual bool IsSupportedProject(IVsHierarchy project) { if (project == null) { throw new ArgumentNullException(nameof(project)); } return project.IsCapabilityMatch("DotNetCoreWeb"); } } }