From e0e1c39cce95573f6e68c0d9779730a7ac9cbf77 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Tue, 8 May 2018 11:54:08 -0700 Subject: [PATCH] Added a LiveShareWorkspaceProvider to enable location of the remote workspace in live share scenarios. - MEF is the primary means of resolving the new live share provider therefore we allow it to not be registered. - The new contract is in the Editor.Razor binary so the LiveShare bits don't have to take the dependency on the windows binary in Razor (has a lot of baggage). - This is specific to live share but providing a generic way to resolve workspaces didn't seem reasonable given the varying expectations in VS4Mac. If we need to make a more generic solution in the future we'll revisit this; for now this is a straight forward inclusion of live share functionality. - Added tests to validate the new behavior. - This unblocks the live share scenario of resolving the remote workspace. We can't rely on the projection buffers to provide the correct workspace because that workspace is wired up too late in the process of opening a Razor file. #2335 --- .../LiveShareWorkspaceProvider.cs | 13 +++ .../DefaultVisualStudioWorkspaceAccessor.cs | 28 +++++- ...efaultVisualStudioWorkspaceAccessorTest.cs | 91 ++++++++++++++++++- 3 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 src/Microsoft.VisualStudio.Editor.Razor/LiveShareWorkspaceProvider.cs diff --git a/src/Microsoft.VisualStudio.Editor.Razor/LiveShareWorkspaceProvider.cs b/src/Microsoft.VisualStudio.Editor.Razor/LiveShareWorkspaceProvider.cs new file mode 100644 index 0000000000..0eac9d38b6 --- /dev/null +++ b/src/Microsoft.VisualStudio.Editor.Razor/LiveShareWorkspaceProvider.cs @@ -0,0 +1,13 @@ +// 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 Microsoft.CodeAnalysis; +using Microsoft.VisualStudio.Text; + +namespace Microsoft.VisualStudio.Editor.Razor +{ + internal abstract class LiveShareWorkspaceProvider + { + public abstract bool TryGetWorkspace(ITextBuffer textBuffer, out Workspace workspace); + } +} diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultVisualStudioWorkspaceAccessor.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultVisualStudioWorkspaceAccessor.cs index 0e9079b84e..64171020a8 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultVisualStudioWorkspaceAccessor.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/DefaultVisualStudioWorkspaceAccessor.cs @@ -19,12 +19,14 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor private readonly IBufferGraphFactoryService _bufferGraphService; private readonly TextBufferProjectService _projectService; private readonly Workspace _defaultWorkspace; + private readonly LiveShareWorkspaceProvider _liveShareWorkspaceProvider; [ImportingConstructor] public DefaultVisualStudioWorkspaceAccessor( IBufferGraphFactoryService bufferGraphService, TextBufferProjectService projectService, - [Import(typeof(VisualStudioWorkspace))] Workspace defaultWorkspace) + [Import(typeof(VisualStudioWorkspace))] Workspace defaultWorkspace, + [Import(typeof(LiveShareWorkspaceProvider), AllowDefault = true)] LiveShareWorkspaceProvider liveShareWorkspaceProvider) { if (bufferGraphService == null) { @@ -44,6 +46,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor _bufferGraphService = bufferGraphService; _projectService = projectService; _defaultWorkspace = defaultWorkspace; + _liveShareWorkspaceProvider = liveShareWorkspaceProvider; } public override bool TryGetWorkspace(ITextBuffer textBuffer, out Workspace workspace) @@ -56,12 +59,18 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor // We do a best effort approach in this method to get the workspace that belongs to the TextBuffer. // The approaches we take to find the workspace are: // - // 1. Look for a C# projection buffer associated with the Razor buffer. If we can find one we let + // 1. If we have a live share workspace provider, ask it for the workspace. + // 2. Look for a C# projection buffer associated with the Razor buffer. If we can find one we let // Roslyn take control of finding the Workspace (projectionBuffer.GetWorkspace()). If not, // fall back to determining if we can use the default workspace. - // 2. Look to see if this ITextBuffer is associated with a host project. If we find that our Razor + // 3. Look to see if this ITextBuffer is associated with a host project. If we find that our Razor // buffer has a host project, we make the assumption that we should use the default VisualStudioWorkspace. + if (TryGetWorkspaceFromLiveShare(textBuffer, out workspace)) + { + return true; + } + if (TryGetWorkspaceFromProjectionBuffer(textBuffer, out workspace)) { return true; @@ -76,6 +85,19 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor return false; } + // Internal for testing + internal bool TryGetWorkspaceFromLiveShare(ITextBuffer textBuffer, out Workspace workspace) + { + if (_liveShareWorkspaceProvider != null && + _liveShareWorkspaceProvider.TryGetWorkspace(textBuffer, out workspace)) + { + return true; + } + + workspace = null; + return false; + } + // Internal virtual for testing internal virtual bool TryGetWorkspaceFromProjectionBuffer(ITextBuffer textBuffer, out Workspace workspace) { diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DefaultVisualStudioWorkspaceAccessorTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DefaultVisualStudioWorkspaceAccessorTest.cs index 95a0cb7105..4905f8572d 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DefaultVisualStudioWorkspaceAccessorTest.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DefaultVisualStudioWorkspaceAccessorTest.cs @@ -14,6 +14,27 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor { public class DefaultVisualStudioWorkspaceAccessorTest { + private static readonly LiveShareWorkspaceProvider NoLiveShare = null; + + [Fact] + public void TryGetWorkspace_PrioritizesLiveShareWhenResolvingWorkspaces() + { + // Arrange + var expectedWorkspace = TestWorkspace.Create(); + var liveShareWorkspaceProvider = new Mock(); + liveShareWorkspaceProvider.Setup(provider => provider.TryGetWorkspace(It.IsAny(), out expectedWorkspace)) + .Returns(true); + var workspaceAccessor = new TestWorkspaceAccessor(canGetWorkspaceFromProjectionBuffer: true, canGetWorkspaceFromHostProject: true, liveShareWorkspaceProvider.Object); + var textBuffer = Mock.Of(); + + // Act + var result = workspaceAccessor.TryGetWorkspace(textBuffer, out var workspace); + + // Assert + Assert.True(result); + Assert.Same(expectedWorkspace, workspace); + } + [Fact] public void TryGetWorkspace_CanGetWorkspaceFromProjectionBuffersOnly() { @@ -56,6 +77,57 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor Assert.True(result); } + [Fact] + public void TryGetWorkspaceFromLiveShare_NoLiveShareProvider_ReturnsFalse() + { + // Arrange + var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(Mock.Of(), Mock.Of(), TestWorkspace.Create(), NoLiveShare); + var textBuffer = Mock.Of(); + + // Act + var result = workspaceAccessor.TryGetWorkspaceFromLiveShare(textBuffer, out var workspace); + + // Assert + Assert.False(result); + } + + [Fact] + public void TryGetWorkspaceFromLiveShare_CanNotFindWorkspace_ReturnsFalse() + { + // Arrange + Workspace nullWorkspace = null; + var liveShareWorkspaceProvider = new Mock(); + liveShareWorkspaceProvider.Setup(provider => provider.TryGetWorkspace(It.IsAny(), out nullWorkspace)) + .Returns(false); + var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(Mock.Of(), Mock.Of(), TestWorkspace.Create(), liveShareWorkspaceProvider.Object); + var textBuffer = Mock.Of(); + + // Act + var result = workspaceAccessor.TryGetWorkspaceFromLiveShare(textBuffer, out var workspace); + + // Assert + Assert.False(result); + } + + [Fact] + public void TryGetWorkspaceFromLiveShare_CanFindWorkspace_ReturnsTrue() + { + // Arrange + var expectedWorkspace = TestWorkspace.Create(); + var liveShareWorkspaceProvider = new Mock(); + liveShareWorkspaceProvider.Setup(provider => provider.TryGetWorkspace(It.IsAny(), out expectedWorkspace)) + .Returns(true); + var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(Mock.Of(), Mock.Of(), TestWorkspace.Create(), liveShareWorkspaceProvider.Object); + var textBuffer = Mock.Of(); + + // Act + var result = workspaceAccessor.TryGetWorkspaceFromLiveShare(textBuffer, out var workspace); + + // Assert + Assert.True(result); + Assert.Same(expectedWorkspace, workspace); + } + [Fact] public void TryGetWorkspaceFromProjectionBuffer_NoProjectionBuffer_ReturnsFalse() { @@ -66,7 +138,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor var bufferGraphService = new Mock(); bufferGraphService.Setup(service => service.CreateBufferGraph(It.IsAny())) .Returns(bufferGraph.Object); - var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(bufferGraphService.Object, Mock.Of(), TestWorkspace.Create()); + var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(bufferGraphService.Object, Mock.Of(), TestWorkspace.Create(), NoLiveShare); var textBuffer = Mock.Of(); // Act @@ -80,7 +152,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor public void TryGetWorkspaceFromHostProject_NoHostProject_ReturnsFalse() { // Arrange - var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(Mock.Of(), Mock.Of(), TestWorkspace.Create()); + var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(Mock.Of(), Mock.Of(), TestWorkspace.Create(), NoLiveShare); var textBuffer = Mock.Of(); // Act @@ -97,7 +169,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor var textBuffer = Mock.Of(); var projectService = Mock.Of(service => service.GetHostProject(textBuffer) == new object()); var defaultWorkspace = TestWorkspace.Create(); - var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(Mock.Of(), projectService, defaultWorkspace); + var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(Mock.Of(), projectService, defaultWorkspace, NoLiveShare); // Act var result = workspaceAccessor.TryGetWorkspaceFromHostProject(textBuffer, out var workspace); @@ -113,10 +185,19 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor private readonly bool _canGetWorkspaceFromHostProject; internal TestWorkspaceAccessor(bool canGetWorkspaceFromProjectionBuffer, bool canGetWorkspaceFromHostProject) : + this(canGetWorkspaceFromProjectionBuffer, canGetWorkspaceFromHostProject, NoLiveShare) + { + } + + internal TestWorkspaceAccessor( + bool canGetWorkspaceFromProjectionBuffer, + bool canGetWorkspaceFromHostProject, + LiveShareWorkspaceProvider liveShareWorkspaceProvider) : base( Mock.Of(), Mock.Of(), - TestWorkspace.Create()) + TestWorkspace.Create(), + liveShareWorkspaceProvider) { _canGetWorkspaceFromProjectionBuffer = canGetWorkspaceFromProjectionBuffer; _canGetWorkspaceFromHostProject = canGetWorkspaceFromHostProject; @@ -147,4 +228,4 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor } } } -} +} \ No newline at end of file