diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/DefaultTextBufferProjectService.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/DefaultTextBufferProjectService.cs index fa2b068990..f81d6fa4d5 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/DefaultTextBufferProjectService.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/DefaultTextBufferProjectService.cs @@ -16,6 +16,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor [Export(typeof(TextBufferProjectService))] internal class DefaultTextBufferProjectService : TextBufferProjectService { + private const string DotNetCoreCapability = "(CSharp|VB)&CPS"; + private readonly RunningDocumentTable _documentTable; private readonly ITextDocumentFactoryService _documentFactory; @@ -79,7 +81,20 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor throw new ArgumentNullException(nameof(hierarchy)); } - return hierarchy.IsCapabilityMatch("DotNetCoreWeb"); + try + { + return hierarchy.IsCapabilityMatch(DotNetCoreCapability); + } + catch (NotSupportedException) + { + // IsCapabilityMatch throws a NotSupportedException if it can't create a BooleanSymbolExpressionEvaluator COM object + } + catch (ObjectDisposedException) + { + // IsCapabilityMatch throws an ObjectDisposedException if the underlying hierarchy has been disposed. + } + + return false; } } } diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/RazorTextViewConnectionListener.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/RazorTextViewConnectionListener.cs index 343e6be021..860a8680e1 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/RazorTextViewConnectionListener.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Editor/RazorTextViewConnectionListener.cs @@ -8,6 +8,8 @@ using System.Diagnostics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Razor; using Microsoft.VisualStudio.Editor.Razor; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; @@ -20,14 +22,21 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor internal class RazorTextViewConnectionListener : IWpfTextViewConnectionListener { private readonly ForegroundDispatcher _foregroundDispatcher; + private readonly TextBufferProjectService _projectService; private readonly RazorEditorFactoryService _editorFactoryService; private readonly Workspace _workspace; [ImportingConstructor] public RazorTextViewConnectionListener( + TextBufferProjectService projectService, RazorEditorFactoryService editorFactoryService, [Import(typeof(VisualStudioWorkspace))] Workspace workspace) { + if (projectService == null) + { + throw new ArgumentNullException(nameof(projectService)); + } + if (editorFactoryService == null) { throw new ArgumentNullException(nameof(editorFactoryService)); @@ -38,6 +47,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor throw new ArgumentNullException(nameof(workspace)); } + _projectService = projectService; _editorFactoryService = editorFactoryService; _workspace = workspace; @@ -47,6 +57,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor // This is only for testing. We want to avoid using the actual Roslyn GetService methods in unit tests. internal RazorTextViewConnectionListener( ForegroundDispatcher foregroundDispatcher, + TextBufferProjectService projectService, RazorEditorFactoryService editorFactoryService, [Import(typeof(VisualStudioWorkspace))] Workspace workspace) { @@ -55,6 +66,11 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor throw new ArgumentNullException(nameof(foregroundDispatcher)); } + if (projectService == null) + { + throw new ArgumentNullException(nameof(projectService)); + } + if (editorFactoryService == null) { throw new ArgumentNullException(nameof(editorFactoryService)); @@ -66,6 +82,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor } _foregroundDispatcher = foregroundDispatcher; + _projectService = projectService; _editorFactoryService = editorFactoryService; _workspace = workspace; } @@ -94,6 +111,12 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor continue; } + var hierarchy = _projectService.GetHierarchy(textBuffer); + if (!_projectService.IsSupportedProject(hierarchy)) + { + return; + } + if (!_editorFactoryService.TryGetDocumentTracker(textBuffer, out var documentTracker) || !(documentTracker is DefaultVisualStudioDocumentTracker tracker)) { diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Editor/RazorTextViewConnectionListenerTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Editor/RazorTextViewConnectionListenerTest.cs index f543e741f2..2b727a2139 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Editor/RazorTextViewConnectionListenerTest.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Editor/RazorTextViewConnectionListenerTest.cs @@ -34,12 +34,32 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor private IContentType NonRazorContentType { get; } = Mock.Of(c => c.IsOfType(It.IsAny()) == false); + private TextBufferProjectService SupportedProjectService { get; } = Mock.Of(s => s.IsSupportedProject(It.IsAny()) == true); + + private TextBufferProjectService UnsupportedProjectService { get; } = Mock.Of(s => s.IsSupportedProject(It.IsAny()) == false); + + [ForegroundFact] + public void SubjectBuffersConnected_ForNonRazorCoreProject_DoesNothing() + { + // Arrange + var editorFactoryService = new Mock(MockBehavior.Strict); + var factory = new RazorTextViewConnectionListener(Dispatcher, UnsupportedProjectService, editorFactoryService.Object, Workspace); + var textView = Mock.Of(); + var buffers = new Collection() + { + Mock.Of(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection()), + }; + + // Act & Assert + factory.SubjectBuffersConnected(textView, ConnectionReason.BufferGraphChange, buffers); + } + [ForegroundFact] public void SubjectBuffersConnected_ForNonRazorTextBuffer_DoesNothing() { // Arrange var editorFactoryService = new Mock(MockBehavior.Strict); - var factory = new RazorTextViewConnectionListener(Dispatcher, editorFactoryService.Object, Workspace); + var factory = new RazorTextViewConnectionListener(Dispatcher, SupportedProjectService, editorFactoryService.Object, Workspace); var textView = Mock.Of(); var buffers = new Collection() { @@ -61,7 +81,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor }; VisualStudioDocumentTracker documentTracker = new DefaultVisualStudioDocumentTracker("AFile", ProjectManager, ProjectService, EditorSettingsManager, Workspace, buffers[0]); var editorFactoryService = Mock.Of(factoryService => factoryService.TryGetDocumentTracker(It.IsAny(), out documentTracker) == true); - var textViewListener = new RazorTextViewConnectionListener(Dispatcher, editorFactoryService, Workspace); + var textViewListener = new RazorTextViewConnectionListener(Dispatcher, SupportedProjectService, editorFactoryService, Workspace); // Act textViewListener.SubjectBuffersConnected(textView, ConnectionReason.BufferGraphChange, buffers); @@ -93,7 +113,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor tracker.AddTextView(textView1); tracker.AddTextView(textView2); buffers[1].Properties.AddProperty(typeof(VisualStudioDocumentTracker), tracker); - var textViewListener = new RazorTextViewConnectionListener(Dispatcher, Mock.Of(), Workspace); + var textViewListener = new RazorTextViewConnectionListener(Dispatcher, SupportedProjectService, Mock.Of(), Workspace); // Act textViewListener.SubjectBuffersDisconnected(textView2, ConnectionReason.BufferGraphChange, buffers); @@ -106,11 +126,29 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor Assert.Collection(tracker.TextViews, v => Assert.Same(v, textView1)); } + [ForegroundFact] + public void SubjectBuffersDisconnected_FoNonRazorCoreProject_DoesNothing() + { + // Arrange + var textViewListener = new RazorTextViewConnectionListener(Dispatcher, UnsupportedProjectService, Mock.Of(), Workspace); + var textView = Mock.Of(); + var buffers = new Collection() + { + Mock.Of(b => b.ContentType == RazorContentType && b.Properties == new PropertyCollection()), + }; + + // Act + textViewListener.SubjectBuffersDisconnected(textView, ConnectionReason.BufferGraphChange, buffers); + + // Assert + Assert.False(buffers[0].Properties.ContainsProperty(typeof(VisualStudioDocumentTracker))); + } + [ForegroundFact] public void SubjectBuffersDisconnected_ForAnyTextBufferWithoutTracker_DoesNothing() { // Arrange - var textViewListener = new RazorTextViewConnectionListener(Dispatcher, Mock.Of(), Workspace); + var textViewListener = new RazorTextViewConnectionListener(Dispatcher, SupportedProjectService, Mock.Of(), Workspace); var textView = Mock.Of();