Add Microsoft.VisualStudio.LiveShare.Razor

- Added LiveShare to the RazorExtension
- Updated dependencies to consume LiveShare nupkg

aspnet/Razor.LiveShare#47
This commit is contained in:
N. Taylor Mullen 2018-12-06 11:46:20 -08:00
Родитель f771e72108
Коммит 599c81f948
49 изменённых файлов: 2945 добавлений и 0 удалений

Просмотреть файл

@ -68,6 +68,7 @@
<MicrosoftVisualStudioEditorPackageVersion>16.0.177-g0ae5fa46a1</MicrosoftVisualStudioEditorPackageVersion>
<MicrosoftVisualStudioLanguagePackageVersion>16.0.177-g0ae5fa46a1</MicrosoftVisualStudioLanguagePackageVersion>
<MicrosoftVisualStudioLanguageIntellisensePackageVersion>16.0.177-g0ae5fa46a1</MicrosoftVisualStudioLanguageIntellisensePackageVersion>
<MicrosoftVisualLiveSharePackageVersion>0.3.959</MicrosoftVisualLiveSharePackageVersion>
<MicrosoftVisualStudioOLEInteropPackageVersion>7.10.6071</MicrosoftVisualStudioOLEInteropPackageVersion>
<MicrosoftVisualStudioProjectSystemAnalyzersPackageVersion>16.0.201-pre-g7d366164d0</MicrosoftVisualStudioProjectSystemAnalyzersPackageVersion>
<MicrosoftVisualStudioProjectSystemManagedVSPackageVersion>3.0.0-beta1-63529-03</MicrosoftVisualStudioProjectSystemManagedVSPackageVersion>

Просмотреть файл

@ -82,6 +82,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.Mac.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.Test.ComponentShim", "test\Microsoft.AspNetCore.Razor.Test.ComponentShim\Microsoft.AspNetCore.Razor.Test.ComponentShim.csproj", "{A9FDA40B-0EE4-4B00-8E24-F1C8B62154BE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VisualStudio.LiveShare.Razor", "src\Microsoft.VisualStudio.LiveShare.Razor\Microsoft.VisualStudio.LiveShare.Razor.csproj", "{CD06AFC8-81C6-4A26-A818-072AB2C44E8C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VisualStudio.LiveShare.Razor.Test", "test\Microsoft.VisualStudio.LiveShare.Razor.Test\Microsoft.VisualStudio.LiveShare.Razor.Test.csproj", "{0C7B8131-1150-4345-837F-6F6AD2CAE606}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -228,6 +232,14 @@ Global
{A9FDA40B-0EE4-4B00-8E24-F1C8B62154BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9FDA40B-0EE4-4B00-8E24-F1C8B62154BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9FDA40B-0EE4-4B00-8E24-F1C8B62154BE}.Release|Any CPU.Build.0 = Release|Any CPU
{CD06AFC8-81C6-4A26-A818-072AB2C44E8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CD06AFC8-81C6-4A26-A818-072AB2C44E8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD06AFC8-81C6-4A26-A818-072AB2C44E8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD06AFC8-81C6-4A26-A818-072AB2C44E8C}.Release|Any CPU.Build.0 = Release|Any CPU
{0C7B8131-1150-4345-837F-6F6AD2CAE606}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0C7B8131-1150-4345-837F-6F6AD2CAE606}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C7B8131-1150-4345-837F-6F6AD2CAE606}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C7B8131-1150-4345-837F-6F6AD2CAE606}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -268,6 +280,8 @@ Global
{D87E5501-B832-46B6-ACD3-EC989E3D14ED} = {92463391-81BE-462B-AC3C-78C6C760741F}
{A34CCC12-687B-4D12-AA0E-F5BE800DE19C} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{A9FDA40B-0EE4-4B00-8E24-F1C8B62154BE} = {92463391-81BE-462B-AC3C-78C6C760741F}
{CD06AFC8-81C6-4A26-A818-072AB2C44E8C} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{0C7B8131-1150-4345-837F-6F6AD2CAE606} = {92463391-81BE-462B-AC3C-78C6C760741F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0035341D-175A-4D05-95E6-F1C2785A1E26}

Просмотреть файл

@ -86,6 +86,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.Mac.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Razor.Test.ComponentShim", "test\Microsoft.AspNetCore.Razor.Test.ComponentShim\Microsoft.AspNetCore.Razor.Test.ComponentShim.csproj", "{5B232E77-F0D3-4298-9A5D-D965788D7A79}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VisualStudio.LiveShare.Razor", "src\Microsoft.VisualStudio.LiveShare.Razor\Microsoft.VisualStudio.LiveShare.Razor.csproj", "{20193C6A-8981-447F-99B3-120DD3B06279}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VisualStudio.LiveShare.Razor.Test", "test\Microsoft.VisualStudio.LiveShare.Razor.Test\Microsoft.VisualStudio.LiveShare.Razor.Test.csproj", "{9A27DD55-E8CD-4C03-A89B-A7348B787660}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -390,6 +394,22 @@ Global
{5B232E77-F0D3-4298-9A5D-D965788D7A79}.Release|Any CPU.Build.0 = Release|Any CPU
{5B232E77-F0D3-4298-9A5D-D965788D7A79}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{5B232E77-F0D3-4298-9A5D-D965788D7A79}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
{20193C6A-8981-447F-99B3-120DD3B06279}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{20193C6A-8981-447F-99B3-120DD3B06279}.Debug|Any CPU.Build.0 = Debug|Any CPU
{20193C6A-8981-447F-99B3-120DD3B06279}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
{20193C6A-8981-447F-99B3-120DD3B06279}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
{20193C6A-8981-447F-99B3-120DD3B06279}.Release|Any CPU.ActiveCfg = Release|Any CPU
{20193C6A-8981-447F-99B3-120DD3B06279}.Release|Any CPU.Build.0 = Release|Any CPU
{20193C6A-8981-447F-99B3-120DD3B06279}.ReleaseNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
{20193C6A-8981-447F-99B3-120DD3B06279}.ReleaseNoVSIX|Any CPU.Build.0 = Debug|Any CPU
{9A27DD55-E8CD-4C03-A89B-A7348B787660}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9A27DD55-E8CD-4C03-A89B-A7348B787660}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A27DD55-E8CD-4C03-A89B-A7348B787660}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
{9A27DD55-E8CD-4C03-A89B-A7348B787660}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
{9A27DD55-E8CD-4C03-A89B-A7348B787660}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A27DD55-E8CD-4C03-A89B-A7348B787660}.Release|Any CPU.Build.0 = Release|Any CPU
{9A27DD55-E8CD-4C03-A89B-A7348B787660}.ReleaseNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
{9A27DD55-E8CD-4C03-A89B-A7348B787660}.ReleaseNoVSIX|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -432,6 +452,8 @@ Global
{23424C44-A86D-4DBD-9274-E358B19DDBA1} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{A34CCC12-687B-4D12-AA0E-F5BE800DE19C} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{5B232E77-F0D3-4298-9A5D-D965788D7A79} = {92463391-81BE-462B-AC3C-78C6C760741F}
{20193C6A-8981-447F-99B3-120DD3B06279} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{9A27DD55-E8CD-4C03-A89B-A7348B787660} = {92463391-81BE-462B-AC3C-78C6C760741F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0035341D-175A-4D05-95E6-F1C2785A1E26}

Просмотреть файл

@ -0,0 +1,10 @@
// 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.
namespace Microsoft.VisualStudio.LiveShare.Razor
{
internal static class Constants
{
internal const string GuestOnlyWorkspaceLayer = "AnyCodeRoslynWorkspace";
}
}

Просмотреть файл

@ -0,0 +1,153 @@
// 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 System.Threading;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.VisualStudio.Threading;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
internal class DefaultProjectSnapshotHandleStore : ProjectSnapshotHandleStore
{
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly JoinableTaskFactory _joinableTaskFactory;
private readonly ProxyAccessor _proxyAccessor;
// Internal for testing
internal JoinableTask TestInitializationTask;
private IReadOnlyList<ProjectSnapshotHandleProxy> _projectHandles;
private bool _triggerChangeAfterInitialize;
public DefaultProjectSnapshotHandleStore(
ForegroundDispatcher foregroundDispatcher,
JoinableTaskFactory joinableTaskFactory,
ProxyAccessor proxyAccessor)
{
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (joinableTaskFactory == null)
{
throw new ArgumentNullException(nameof(joinableTaskFactory));
}
if (proxyAccessor == null)
{
throw new ArgumentNullException(nameof(proxyAccessor));
}
_foregroundDispatcher = foregroundDispatcher;
_joinableTaskFactory = joinableTaskFactory;
_proxyAccessor = proxyAccessor;
}
public override event EventHandler<ProjectProxyChangeEventArgs> Changed;
private bool Initialized => _projectHandles != null;
public override IReadOnlyList<ProjectSnapshotHandleProxy> GetProjectHandles()
{
_foregroundDispatcher.AssertForegroundThread();
if (!Initialized)
{
// Projects requested before initialization
_triggerChangeAfterInitialize = true;
return Array.Empty<ProjectSnapshotHandleProxy>();
}
return _projectHandles;
}
public void InitializeProjects()
{
_foregroundDispatcher.AssertForegroundThread();
// We wire the changed event up early because any changed events that fire will ensure we have the most
// up-to-date state.
var snapshotManagerProxy = _proxyAccessor.GetProjectSnapshotManagerProxy();
snapshotManagerProxy.Changed += HostProxyStateManager_Changed;
TestInitializationTask = _joinableTaskFactory.RunAsync(async () =>
{
var state = await snapshotManagerProxy.GetStateAsync(CancellationToken.None);
await _joinableTaskFactory.SwitchToMainThreadAsync();
if (Initialized)
{
// State was initialized from the changed event firing, that state will be more up-to-date than this.
return;
}
UpdateProjects(state);
if (_triggerChangeAfterInitialize)
{
// Someone requested the set of projects prior to us being initialized. Let listeners know that projects
// have been added. This way we don't have to block on the foreground thread for initialization; however,
// this also assumes that anyone listening re-computes their state when we trigger project changes
// (a correct assumption).
for (var i = 0; i < _projectHandles.Count; i++)
{
var args = new ProjectProxyChangeEventArgs(_projectHandles[i].FilePath, ProjectProxyChangeKind.ProjectAdded);
OnChanged(args);
}
}
});
}
// Internal for testing
internal async void HostProxyStateManager_Changed(object sender, ProjectManagerProxyChangeEventArgs args)
{
if (args == null)
{
throw new ArgumentNullException(nameof(args));
}
if (_foregroundDispatcher.IsForegroundThread)
{
UpdateProjectsAndTriggerChangeForeground();
}
else
{
await _joinableTaskFactory.SwitchToMainThreadAsync();
UpdateProjectsAndTriggerChangeForeground();
}
void UpdateProjectsAndTriggerChangeForeground()
{
_foregroundDispatcher.AssertForegroundThread();
UpdateProjects(args.State);
// We will have already triggered change events to ensure listeners are up-to-date.
_triggerChangeAfterInitialize = false;
OnChanged(args.Change);
}
}
// Internal for testing
internal void UpdateProjects(ProjectSnapshotManagerProxyState state)
{
_foregroundDispatcher.AssertForegroundThread();
_projectHandles = state.ProjectHandles;
}
private void OnChanged(ProjectProxyChangeEventArgs args)
{
_foregroundDispatcher.AssertForegroundThread();
Changed?.Invoke(this, args);
}
}
}

Просмотреть файл

@ -0,0 +1,56 @@
// 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.Composition;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.VisualStudio.Threading;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
[ExportLanguageServiceFactory(typeof(ProjectSnapshotHandleStore), RazorLanguage.Name, Constants.GuestOnlyWorkspaceLayer)]
internal class DefaultProjectSnapshotHandleStoreFactory : ILanguageServiceFactory
{
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly JoinableTaskContext _joinableTaskContext;
[ImportingConstructor]
public DefaultProjectSnapshotHandleStoreFactory(
ForegroundDispatcher foregroundDispatcher,
JoinableTaskContext joinableTaskContext)
{
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (joinableTaskContext == null)
{
throw new ArgumentNullException(nameof(joinableTaskContext));
}
_foregroundDispatcher = foregroundDispatcher;
_joinableTaskContext = joinableTaskContext;
}
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
if (languageServices == null)
{
throw new ArgumentNullException(nameof(languageServices));
}
var proxyAccessor = languageServices.GetRequiredService<ProxyAccessor>();
var snapshotStore = new DefaultProjectSnapshotHandleStore(
_foregroundDispatcher,
_joinableTaskContext.Factory,
proxyAccessor);
snapshotStore.InitializeProjects();
return snapshotStore;
}
}
}

Просмотреть файл

@ -0,0 +1,65 @@
// 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 Microsoft.VisualStudio.Threading;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
public class DefaultProxyAccessor : ProxyAccessor
{
private readonly LiveShareClientProvider _liveShareClientProvider;
private readonly JoinableTaskFactory _joinableTaskFactory;
private IProjectSnapshotManagerProxy _projectSnapshotManagerProxy;
private IProjectHierarchyProxy _projectHierarchyProxy;
public DefaultProxyAccessor(
LiveShareClientProvider liveShareClientProvider,
JoinableTaskFactory joinableTaskFactory)
{
if (liveShareClientProvider == null)
{
throw new ArgumentNullException(nameof(liveShareClientProvider));
}
if (joinableTaskFactory == null)
{
throw new ArgumentNullException(nameof(joinableTaskFactory));
}
_liveShareClientProvider = liveShareClientProvider;
_joinableTaskFactory = joinableTaskFactory;
}
// Testing constructor
private protected DefaultProxyAccessor()
{
}
public override IProjectSnapshotManagerProxy GetProjectSnapshotManagerProxy()
{
if (_projectSnapshotManagerProxy == null)
{
_projectSnapshotManagerProxy = CreateServiceProxy<IProjectSnapshotManagerProxy>();
}
return _projectSnapshotManagerProxy;
}
public override IProjectHierarchyProxy GetProjectHierarchyProxy()
{
if (_projectHierarchyProxy == null)
{
_projectHierarchyProxy = CreateServiceProxy<IProjectHierarchyProxy>();
}
return _projectHierarchyProxy;
}
// Internal virtual for testing
internal virtual TProxy CreateServiceProxy<TProxy>() where TProxy : class
{
return _joinableTaskFactory.Run(() => _liveShareClientProvider.CreateServiceProxyAsync<TProxy>());
}
}
}

Просмотреть файл

@ -0,0 +1,50 @@
// 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.Composition;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.VisualStudio.Threading;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
[Shared]
[ExportLanguageServiceFactory(typeof(ProxyAccessor), RazorLanguage.Name, Constants.GuestOnlyWorkspaceLayer)]
public class DefaultProxyAccessorFactory : ILanguageServiceFactory
{
private readonly LiveShareClientProvider _liveShareClientProvider;
private readonly JoinableTaskContext _joinableTaskContext;
[ImportingConstructor]
public DefaultProxyAccessorFactory(
LiveShareClientProvider liveShareClientProvider,
JoinableTaskContext joinableTaskContext)
{
if (liveShareClientProvider == null)
{
throw new ArgumentNullException(nameof(liveShareClientProvider));
}
if (joinableTaskContext == null)
{
throw new ArgumentNullException(nameof(joinableTaskContext));
}
_liveShareClientProvider = liveShareClientProvider;
_joinableTaskContext = joinableTaskContext;
}
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
if (languageServices == null)
{
throw new ArgumentNullException(nameof(languageServices));
}
var proxyAccessor = new DefaultProxyAccessor(_liveShareClientProvider, _joinableTaskContext.Factory);
return proxyAccessor;
}
}
}

Просмотреть файл

@ -0,0 +1,97 @@
// 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.Threading;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.VisualStudio.Editor.Razor;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Threading;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
internal class GuestProjectPathProvider : ProjectPathProvider
{
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly JoinableTaskFactory _joinableTaskFactory;
private readonly ITextDocumentFactoryService _textDocumentFactory;
private readonly ProxyAccessor _proxyAccessor;
private readonly LiveShareClientProvider _liveShareClientProvider;
public GuestProjectPathProvider(
ForegroundDispatcher foregroundDispatcher,
JoinableTaskFactory joinableTaskFactory,
ITextDocumentFactoryService textDocumentFactory,
ProxyAccessor proxyAccessor,
LiveShareClientProvider liveShareClientProvider)
{
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (joinableTaskFactory == null)
{
throw new ArgumentNullException(nameof(joinableTaskFactory));
}
if (textDocumentFactory == null)
{
throw new ArgumentNullException(nameof(textDocumentFactory));
}
if (proxyAccessor == null)
{
throw new ArgumentNullException(nameof(proxyAccessor));
}
if (liveShareClientProvider == null)
{
throw new ArgumentNullException(nameof(liveShareClientProvider));
}
_foregroundDispatcher = foregroundDispatcher;
_joinableTaskFactory = joinableTaskFactory;
_textDocumentFactory = textDocumentFactory;
_proxyAccessor = proxyAccessor;
_liveShareClientProvider = liveShareClientProvider;
}
public override bool TryGetProjectPath(ITextBuffer textBuffer, out string filePath)
{
if (!_textDocumentFactory.TryGetTextDocument(textBuffer, out var textDocument))
{
filePath = null;
return false;
}
var hostProjectPath = GetHostProjectPath(textDocument);
if (hostProjectPath == null)
{
filePath = null;
return false;
}
// Host always responds with a host-based path, convert back to a guest one.
filePath = _liveShareClientProvider.ConvertToLocalPath(hostProjectPath);
return true;
}
// Internal virtual for testing
internal virtual Uri GetHostProjectPath(ITextDocument textDocument)
{
// The path we're given is from the guest so following other patterns we always ask the host information in its own form (aka convert on guest instead of on host).
var ownerPath = _liveShareClientProvider.ConvertToSharedUri(textDocument.FilePath);
var hostProjectPath = _joinableTaskFactory.Run(() =>
{
var projectHierarchyProxy = _proxyAccessor.GetProjectHierarchyProxy();
// We need to block the foreground thread to get a proper project path. However, this is only done once on opening thedocument.
return projectHierarchyProxy.GetProjectPathAsync(ownerPath, CancellationToken.None);
});
return hostProjectPath;
}
}
}

Просмотреть файл

@ -0,0 +1,77 @@
// 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.Composition;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.VisualStudio.Editor.Razor;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Threading;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
[Shared]
[ExportWorkspaceServiceFactory(typeof(ProjectPathProvider), Constants.GuestOnlyWorkspaceLayer)]
internal class GuestProjectPathProviderFactory : IWorkspaceServiceFactory
{
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly JoinableTaskContext _joinableTaskContext;
private readonly ITextDocumentFactoryService _textDocumentFactory;
private readonly LiveShareClientProvider _liveShareClientProvider;
[ImportingConstructor]
public GuestProjectPathProviderFactory(
ForegroundDispatcher foregroundDispatcher,
JoinableTaskContext joinableTaskContext,
ITextDocumentFactoryService textDocumentFactory,
LiveShareClientProvider liveShareClientProvider)
{
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (joinableTaskContext == null)
{
throw new ArgumentNullException(nameof(joinableTaskContext));
}
if (textDocumentFactory == null)
{
throw new ArgumentNullException(nameof(textDocumentFactory));
}
if (liveShareClientProvider == null)
{
throw new ArgumentNullException(nameof(liveShareClientProvider));
}
_foregroundDispatcher = foregroundDispatcher;
_joinableTaskContext = joinableTaskContext;
_textDocumentFactory = textDocumentFactory;
_liveShareClientProvider = liveShareClientProvider;
}
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
if (workspaceServices == null)
{
throw new ArgumentNullException(nameof(workspaceServices));
}
var languageServices = workspaceServices.GetLanguageServices(RazorLanguage.Name);
var proxyAccessor = languageServices.GetRequiredService<ProxyAccessor>();
var projectPathProvider = new GuestProjectPathProvider(
_foregroundDispatcher,
_joinableTaskContext.Factory,
_textDocumentFactory,
proxyAccessor,
_liveShareClientProvider);
return projectPathProvider;
}
}
}

Просмотреть файл

@ -0,0 +1,60 @@
// 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 System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.CodeAnalysis.Razor.Workspaces.ProjectSystem;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
internal class GuestProjectSnapshot : LiveShareProjectSnapshotBase
{
private readonly DefaultProjectSnapshot _innerProjectSnapshot;
private readonly IReadOnlyList<TagHelperDescriptor> _tagHelpers;
public GuestProjectSnapshot(ProjectState projectState, IReadOnlyList<TagHelperDescriptor> tagHelpers)
{
if (projectState == null)
{
throw new ArgumentNullException(nameof(projectState));
}
if (tagHelpers == null)
{
throw new ArgumentNullException(nameof(tagHelpers));
}
var snapshot = new DefaultProjectSnapshot(projectState);
_innerProjectSnapshot = new DefaultProjectSnapshot(projectState);
_tagHelpers = tagHelpers;
}
public override RazorConfiguration Configuration => _innerProjectSnapshot.Configuration;
public override IEnumerable<string> DocumentFilePaths => _innerProjectSnapshot.DocumentFilePaths;
public override string FilePath => _innerProjectSnapshot.FilePath;
public override bool IsInitialized => true;
public override VersionStamp Version => _innerProjectSnapshot.Version;
public override Project WorkspaceProject => null;
public override DocumentSnapshot GetDocument(string filePath) => _innerProjectSnapshot.GetDocument(filePath);
public override RazorProjectEngine GetProjectEngine() => _innerProjectSnapshot.GetProjectEngine();
public override Task<IReadOnlyList<TagHelperDescriptor>> GetTagHelpersAsync() => Task.FromResult(_tagHelpers);
public override bool TryGetTagHelpers(out IReadOnlyList<TagHelperDescriptor> result)
{
result = _tagHelpers;
return true;
}
}
}

Просмотреть файл

@ -0,0 +1,47 @@
// 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 Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
internal class GuestProjectSnapshotFactory : ProjectSnapshotFactory
{
private readonly Workspace _workspace;
private readonly LiveShareClientProvider _liveShareClientProvider;
public GuestProjectSnapshotFactory(
Workspace workspace,
LiveShareClientProvider liveShareClientProvider)
{
if (workspace == null)
{
throw new ArgumentNullException(nameof(workspace));
}
if (liveShareClientProvider == null)
{
throw new ArgumentNullException(nameof(liveShareClientProvider));
}
_workspace = workspace;
_liveShareClientProvider = liveShareClientProvider;
}
public override ProjectSnapshot Create(ProjectSnapshotHandleProxy projectHandle)
{
if (projectHandle == null)
{
throw new ArgumentNullException(nameof(projectHandle));
}
var filePath = _liveShareClientProvider.ConvertToLocalPath(projectHandle.FilePath);
var hostProject = new HostProject(filePath, projectHandle.Configuration);
var projectState = ProjectState.Create(_workspace.Services, hostProject);
var snapshot = new GuestProjectSnapshot(projectState, projectHandle.TagHelpers);
return snapshot;
}
}
}

Просмотреть файл

@ -0,0 +1,40 @@
// 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.Composition;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Razor;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
[Shared]
[ExportLanguageServiceFactory(typeof(ProjectSnapshotFactory), RazorLanguage.Name, Constants.GuestOnlyWorkspaceLayer)]
internal class GuestProjectSnapshotFactoryFactory : ILanguageServiceFactory
{
private readonly LiveShareClientProvider _liveShareClientProvider;
[ImportingConstructor]
public GuestProjectSnapshotFactoryFactory(LiveShareClientProvider liveShareClientProvider)
{
if (liveShareClientProvider == null)
{
throw new ArgumentNullException(nameof(liveShareClientProvider));
}
_liveShareClientProvider = liveShareClientProvider;
}
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
if (languageServices == null)
{
throw new ArgumentNullException(nameof(languageServices));
}
var projectSnapshotFactory = new GuestProjectSnapshotFactory(languageServices.WorkspaceServices.Workspace, _liveShareClientProvider);
return projectSnapshotFactory;
}
}
}

Просмотреть файл

@ -0,0 +1,204 @@
// 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 System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
internal class GuestProjectSnapshotManager : ProjectSnapshotManager
{
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly HostWorkspaceServices _services;
private readonly ProjectSnapshotHandleStore _projectSnapshotHandleStore;
private readonly ProjectSnapshotFactory _projectSnapshotFactory;
private readonly LiveShareClientProvider _liveShareClientProvider;
private readonly Workspace _workspace;
private Dictionary<string, ProjectSnapshot> _projects;
public GuestProjectSnapshotManager(
ForegroundDispatcher foregroundDispatcher,
HostWorkspaceServices services,
ProjectSnapshotHandleStore projectSnapshotStore,
ProjectSnapshotFactory projectSnapshotFactory,
LiveShareClientProvider liveShareClientProvider,
Workspace workspace)
{
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (projectSnapshotStore == null)
{
throw new ArgumentNullException(nameof(projectSnapshotStore));
}
if (projectSnapshotFactory == null)
{
throw new ArgumentNullException(nameof(projectSnapshotFactory));
}
if (liveShareClientProvider == null)
{
throw new ArgumentNullException(nameof(liveShareClientProvider));
}
if (workspace == null)
{
throw new ArgumentNullException(nameof(workspace));
}
_foregroundDispatcher = foregroundDispatcher;
_services = services;
_projectSnapshotHandleStore = projectSnapshotStore;
_projectSnapshotFactory = projectSnapshotFactory;
_liveShareClientProvider = liveShareClientProvider;
_workspace = workspace;
_projectSnapshotHandleStore.Changed += ProjectSnapshotStore_Changed;
}
// Internal for testing
internal GuestProjectSnapshotManager(
ForegroundDispatcher foregroundDispatcher,
HostWorkspaceServices services,
ProjectSnapshotHandleStore projectSnapshotHandleStore,
ProjectSnapshotFactory projectSnapshotFactory,
Workspace workspace)
{
_foregroundDispatcher = foregroundDispatcher;
_services = services;
_projectSnapshotHandleStore = projectSnapshotHandleStore;
_projectSnapshotFactory = projectSnapshotFactory;
_workspace = workspace;
_projectSnapshotHandleStore.Changed += ProjectSnapshotStore_Changed;
}
public override IReadOnlyList<ProjectSnapshot> Projects
{
get
{
_foregroundDispatcher.AssertForegroundThread();
EnsureProjects();
return _projects.Values.ToList();
}
}
public override event EventHandler<ProjectChangeEventArgs> Changed;
public override ProjectSnapshot GetLoadedProject(string filePath)
{
if (filePath == null)
{
throw new ArgumentNullException(nameof(filePath));
}
_foregroundDispatcher.AssertForegroundThread();
EnsureProjects();
if (_projects.TryGetValue(filePath, out var snapshot))
{
return snapshot;
}
return null;
}
public override ProjectSnapshot GetOrCreateProject(string filePath)
{
if (filePath == null)
{
throw new ArgumentNullException(nameof(filePath));
}
_foregroundDispatcher.AssertForegroundThread();
EnsureProjects();
return GetLoadedProject(filePath) ?? new EphemeralProjectSnapshot(_services, filePath);
}
public override bool IsDocumentOpen(string documentFilePath)
{
if (documentFilePath == null)
{
throw new ArgumentNullException(nameof(documentFilePath));
}
_foregroundDispatcher.AssertForegroundThread();
// On the guest side we don't currently support open document tracking.
return false;
}
// Internal for testing
internal void ProjectSnapshotStore_Changed(object sender, ProjectProxyChangeEventArgs args)
{
if (args == null)
{
throw new ArgumentNullException(nameof(args));
}
_foregroundDispatcher.AssertForegroundThread();
var guestPath = ResolveGuestPath(args.ProjectFilePath);
var oldProject = GetLoadedProject(guestPath);
// Reset projects cache so they'll be re-calculated.
_projects = null;
var newProject = GetLoadedProject(guestPath);
var changeArgs = new ProjectChangeEventArgs(oldProject, newProject, (ProjectChangeKind)args.Kind);
OnChanged(changeArgs);
}
private void OnChanged(ProjectChangeEventArgs args)
{
_foregroundDispatcher.AssertForegroundThread();
Changed?.Invoke(this, args);
}
private void EnsureProjects()
{
_foregroundDispatcher.AssertForegroundThread();
if (_projects == null)
{
UpdateProjects();
}
}
private void UpdateProjects()
{
_foregroundDispatcher.AssertForegroundThread();
var projectHandles = _projectSnapshotHandleStore.GetProjectHandles();
_projects = projectHandles
.Select(_projectSnapshotFactory.Create)
.ToDictionary(project => project.FilePath, FilePathComparer.Instance);
}
// Internal virtual for testing
internal virtual string ResolveGuestPath(Uri filePath)
{
var guestPath = _liveShareClientProvider.ConvertToLocalPath(filePath);
return guestPath;
}
}
}

Просмотреть файл

@ -0,0 +1,59 @@
// 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.Composition;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
// This overrides the default Razor implementation of the ProjectSnapshotManager
[ExportLanguageServiceFactory(typeof(ProjectSnapshotManager), RazorLanguage.Name, Constants.GuestOnlyWorkspaceLayer)]
internal class GuestProjectSnapshotManagerFactory : ILanguageServiceFactory
{
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly LiveShareClientProvider _liveShareClientProvider;
[ImportingConstructor]
public GuestProjectSnapshotManagerFactory(
ForegroundDispatcher foregroundDispatcher,
LiveShareClientProvider liveShareClientProvider)
{
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (liveShareClientProvider == null)
{
throw new ArgumentNullException(nameof(liveShareClientProvider));
}
_foregroundDispatcher = foregroundDispatcher;
_liveShareClientProvider = liveShareClientProvider;
}
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
if (languageServices == null)
{
throw new ArgumentNullException(nameof(languageServices));
}
var projectSnapshotStore = languageServices.GetRequiredService<ProjectSnapshotHandleStore>();
var projectSnapshotFactory = languageServices.GetRequiredService<ProjectSnapshotFactory>();
var snapshotManager = new GuestProjectSnapshotManager(
_foregroundDispatcher,
languageServices.WorkspaceServices,
projectSnapshotStore,
projectSnapshotFactory,
_liveShareClientProvider,
languageServices.WorkspaceServices.Workspace);
return snapshotManager;
}
}
}

Просмотреть файл

@ -0,0 +1,86 @@
// 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.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.LiveShare.Razor.Serialization;
using Newtonsoft.Json;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
[Export]
[ExportCollaborationService(typeof(ICollaborationService), Scope = SessionScope.Guest)]
public class LiveShareClientProvider : ICollaborationServiceFactory
{
private LiveShareClientService _liveShareClientService;
public Task<ICollaborationService> CreateServiceAsync(CollaborationSession session, CancellationToken cancellationToken)
{
var serializer = (JsonSerializer)session.GetService(typeof(JsonSerializer));
serializer.Converters.RegisterRazorLiveShareConverters();
var liveShareClientService = new LiveShareClientService(session);
liveShareClientService.Disposed += (s, e) =>
{
_liveShareClientService = null;
};
_liveShareClientService = liveShareClientService;
return Task.FromResult<ICollaborationService>(liveShareClientService);
}
internal Task<TProxy> CreateServiceProxyAsync<TProxy>() where TProxy : class
{
if (_liveShareClientService == null)
{
return Task.FromResult<TProxy>(null);
}
return _liveShareClientService.CreateServiceProxyAsync<TProxy>();
}
internal string ConvertToLocalPath(Uri sharedUri)
{
return _liveShareClientService?.ConvertToLocalPath(sharedUri);
}
internal Uri ConvertToSharedUri(string localPath)
{
return _liveShareClientService?.ConvertToSharedUri(localPath);
}
private class LiveShareClientService : ICollaborationService, IDisposable
{
private CollaborationSession _session;
public LiveShareClientService(CollaborationSession session)
{
_session = session;
}
public event EventHandler Disposed;
public void Dispose()
{
Disposed?.Invoke(this, null);
}
internal Task<TProxy> CreateServiceProxyAsync<TProxy>() where TProxy : class
{
return _session.GetRemoteServiceAsync<TProxy>(typeof(TProxy).Name, CancellationToken.None);
}
internal string ConvertToLocalPath(Uri sharedUri)
{
return _session.ConvertSharedUriToLocalPath(sharedUri);
}
internal Uri ConvertToSharedUri(string localPath)
{
return _session.ConvertLocalPathToSharedUri(localPath);
}
}
}
}

Просмотреть файл

@ -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.Host;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
internal abstract class ProjectSnapshotFactory : ILanguageService
{
public abstract ProjectSnapshot Create(ProjectSnapshotHandleProxy projectHandle);
}
}

Просмотреть файл

@ -0,0 +1,16 @@
// 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.Host;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
internal abstract class ProjectSnapshotHandleStore : ILanguageService
{
public abstract IReadOnlyList<ProjectSnapshotHandleProxy> GetProjectHandles();
public abstract event EventHandler<ProjectProxyChangeEventArgs> Changed;
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// 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.Host;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
public abstract class ProxyAccessor : ILanguageService
{
public abstract IProjectSnapshotManagerProxy GetProjectSnapshotManagerProxy();
public abstract IProjectHierarchyProxy GetProjectHierarchyProxy();
}
}

Просмотреть файл

@ -0,0 +1,91 @@
//// 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 System.ComponentModel.Composition;
//using System.Threading;
//using System.Threading.Tasks;
//using Microsoft.Cascade.Contracts;
//using Microsoft.VisualStudio.Cascade.Contracts;
//namespace Microsoft.VisualStudio.LiveShare.Razor.Host
//{
// [ExportCollaborationService(typeof(ICollaborationService), Scope = SessionScope.Guest)]
// internal class RazorGuestInitializationService : ICollaborationServiceFactory
// {
// private const string ViewImportsFileName = "_ViewImports.cshtml";
// private readonly IVsRemoteWorkspaceManager _remoteWorkspaceManager;
// [ImportingConstructor]
// public RazorGuestInitializationService(IVsRemoteWorkspaceManager remoteWorkspaceManager)
// {
// if (remoteWorkspaceManager == null)
// {
// throw new ArgumentNullException(nameof(remoteWorkspaceManager));
// }
// _remoteWorkspaceManager = remoteWorkspaceManager;
// }
// public async Task<ICollaborationService> CreateServiceAsync(CollaborationSession sessionContext, CancellationToken cancellationToken)
// {
// if (sessionContext == null)
// {
// throw new ArgumentNullException(nameof(sessionContext));
// }
// await EnsureViewImportsCopiedAsync(cancellationToken);
// // Our services don't actually have any state that needs to be disposed.
// return null;
// }
// // Today we ensure that all _ViewImports in the shared project exist on the guest because we don't currently track import documents
// // in a manner that would allow us to retrieve/monitor that data across the wire. Once the Razor sub-system is moved to use
// // DocumentSnapshots we'll be able to rely on that API to more properly manage files that impact parsing of Razor documents.
// private async Task EnsureViewImportsCopiedAsync(CancellationToken cancellationToken)
// {
// var fileListOptions = new FileListOptions()
// {
// RecurseMode = FileRecurseMode.AllDescendants,
// ExcludePatterns = new[]
// {
// "*.cs",
// "*.js",
// "*.css",
// "*.html",
// "*.json",
// "*.csproj",
// "*.sln",
// "bin",
// "obj",
// }
// };
// var files = await _remoteWorkspaceManager.FileService.ListAsync(new[] { "/" }, fileListOptions, cancellationToken);
// var copyTasks = new List<Task>();
// StartViewImportsCopy(files, copyTasks, cancellationToken);
// await Task.WhenAll(copyTasks);
// }
// private void StartViewImportsCopy(FileInfo[] files, List<Task> copyTasks, CancellationToken cancellationToken)
// {
// foreach (var file in files)
// {
// if (file.IsDirectory)
// {
// StartViewImportsCopy(file.Children, copyTasks, cancellationToken);
// }
// else if (file.Path.EndsWith(ViewImportsFileName, StringComparison.OrdinalIgnoreCase))
// {
// // A _ViewImports.cshtml file, need to ensure it's copied to the guest.
// var filePath = _remoteWorkspaceManager.ConvertToJoinerPath(file.Path, cancellationToken);
// var copyTask = _remoteWorkspaceManager.WorkspaceFileService.EnsureFileExistsAsync(filePath, cancellationToken);
// copyTasks.Add(copyTask);
// }
// }
// }
// }
//}

Просмотреть файл

@ -0,0 +1,74 @@
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Threading;
namespace Microsoft.VisualStudio.LiveShare.Razor.Host
{
internal class DefaultProjectHierarchyProxy : IProjectHierarchyProxy, ICollaborationService
{
private readonly CollaborationSession _session;
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly JoinableTaskFactory _joinableTaskFactory;
private IVsUIShellOpenDocument _openDocumentShell;
public DefaultProjectHierarchyProxy(
CollaborationSession session,
ForegroundDispatcher foregroundDispatcher,
JoinableTaskFactory joinableTaskFactory)
{
if (session == null)
{
throw new ArgumentNullException(nameof(session));
}
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (joinableTaskFactory == null)
{
throw new ArgumentNullException(nameof(joinableTaskFactory));
}
_session = session;
_foregroundDispatcher = foregroundDispatcher;
_joinableTaskFactory = joinableTaskFactory;
}
public async Task<Uri> GetProjectPathAsync(Uri documentFilePath, CancellationToken cancellationToken)
{
if (documentFilePath == null)
{
throw new ArgumentNullException(nameof(documentFilePath));
}
_foregroundDispatcher.AssertBackgroundThread();
await _joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
if (_openDocumentShell == null)
{
_openDocumentShell = ServiceProvider.GlobalProvider.GetService(typeof(SVsUIShellOpenDocument)) as IVsUIShellOpenDocument;
}
var hostDocumentFilePath = _session.ConvertSharedUriToLocalPath(documentFilePath);
var hr = _openDocumentShell.IsDocumentInAProject(hostDocumentFilePath, out var hierarchy, out _, out _, out _);
if (ErrorHandler.Succeeded(hr) && hierarchy != null)
{
ErrorHandler.ThrowOnFailure(((IVsProject)hierarchy).GetMkDocument((uint)VSConstants.VSITEMID.Root, out var path), VSConstants.E_NOTIMPL);
return _session.ConvertLocalPathToSharedUri(path);
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,53 @@
// 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.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.VisualStudio.Threading;
namespace Microsoft.VisualStudio.LiveShare.Razor.Host
{
[ExportCollaborationService(
typeof(IProjectHierarchyProxy),
Name = nameof(IProjectHierarchyProxy),
Scope = SessionScope.Host,
Role = ServiceRole.RemoteService)]
internal class DefaultProjectHierarchyProxyFactory : ICollaborationServiceFactory
{
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly JoinableTaskContext _joinableTaskContext;
[ImportingConstructor]
public DefaultProjectHierarchyProxyFactory(
ForegroundDispatcher foregroundDispatcher,
JoinableTaskContext joinableTaskContext)
{
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (joinableTaskContext == null)
{
throw new ArgumentNullException(nameof(joinableTaskContext));
}
_foregroundDispatcher = foregroundDispatcher;
_joinableTaskContext = joinableTaskContext;
}
public Task<ICollaborationService> CreateServiceAsync(CollaborationSession session, CancellationToken cancellationToken)
{
if (session == null)
{
throw new ArgumentNullException(nameof(session));
}
var service = new DefaultProjectHierarchyProxy(session, _foregroundDispatcher, _joinableTaskContext.Factory);
return Task.FromResult<ICollaborationService>(service);
}
}
}

Просмотреть файл

@ -0,0 +1,161 @@
// 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 System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.Threading;
namespace Microsoft.VisualStudio.LiveShare.Razor.Host
{
internal class DefaultProjectSnapshotManagerProxy : IProjectSnapshotManagerProxy, ICollaborationService, IDisposable
{
private readonly CollaborationSession _session;
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly ProjectSnapshotManager _projectSnapshotManager;
private readonly JoinableTaskFactory _joinableTaskFactory;
private readonly AsyncSemaphore _latestStateSemaphore;
private bool _disposed;
private ProjectSnapshotManagerProxyState _latestState;
// Internal for testing
internal JoinableTask _processingChangedEventTestTask;
public DefaultProjectSnapshotManagerProxy(
CollaborationSession session,
ForegroundDispatcher foregroundDispatcher,
ProjectSnapshotManager projectSnapshotManager,
JoinableTaskFactory joinableTaskFactory)
{
if (session == null)
{
throw new ArgumentNullException(nameof(session));
}
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (projectSnapshotManager == null)
{
throw new ArgumentNullException(nameof(projectSnapshotManager));
}
if (joinableTaskFactory == null)
{
throw new ArgumentNullException(nameof(joinableTaskFactory));
}
_session = session;
_foregroundDispatcher = foregroundDispatcher;
_projectSnapshotManager = projectSnapshotManager;
_joinableTaskFactory = joinableTaskFactory;
_latestStateSemaphore = new AsyncSemaphore(initialCount: 1);
_projectSnapshotManager.Changed += ProjectSnapshotManager_Changed;
}
public event EventHandler<ProjectManagerProxyChangeEventArgs> Changed;
public async Task<ProjectSnapshotManagerProxyState> GetStateAsync(CancellationToken cancellationToken)
{
using (await _latestStateSemaphore.EnterAsync().ConfigureAwait(false))
{
if (_latestState != null)
{
return _latestState;
}
}
var projects = await GetLatestProjectsAsync();
var state = await CalculateUpdatedStateAsync(projects);
return state;
}
public void Dispose()
{
_foregroundDispatcher.AssertForegroundThread();
_projectSnapshotManager.Changed -= ProjectSnapshotManager_Changed;
_disposed = true;
}
// Internal for testing
internal async Task<IReadOnlyList<ProjectSnapshot>> GetLatestProjectsAsync()
{
if (!_foregroundDispatcher.IsForegroundThread)
{
await _joinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None);
}
return _projectSnapshotManager.Projects.ToArray();
}
// Internal for testing
internal async Task<ProjectSnapshotManagerProxyState> CalculateUpdatedStateAsync(IReadOnlyList<ProjectSnapshot> projects)
{
using (await _latestStateSemaphore.EnterAsync().ConfigureAwait(false))
{
var projectHandles = new List<ProjectSnapshotHandleProxy>();
foreach (var project in projects)
{
var tagHelpers = await project.GetTagHelpersAsync().ConfigureAwait(false);
var projectHandle = new ProjectSnapshotHandleProxy(_session.ConvertLocalPathToSharedUri(project.FilePath), tagHelpers, project.Configuration);
projectHandles.Add(projectHandle);
}
_latestState = new ProjectSnapshotManagerProxyState(projectHandles);
return _latestState;
}
}
private void ProjectSnapshotManager_Changed(object sender, ProjectChangeEventArgs args)
{
_foregroundDispatcher.AssertForegroundThread();
if (_disposed)
{
return;
}
if (args.Kind == ProjectChangeKind.DocumentAdded ||
args.Kind == ProjectChangeKind.DocumentRemoved ||
args.Kind == ProjectChangeKind.DocumentChanged)
{
// Razor LiveShare doesn't currently support document based notifications over the wire.
return;
}
_processingChangedEventTestTask = _joinableTaskFactory.RunAsync(async () =>
{
var projects = await GetLatestProjectsAsync();
var state = await CalculateUpdatedStateAsync(projects);
await _joinableTaskFactory.SwitchToMainThreadAsync();
var proxyArgs = new ProjectProxyChangeEventArgs(_session.ConvertLocalPathToSharedUri(args.ProjectFilePath), (ProjectProxyChangeKind)args.Kind);
var remoteProjectChangeArgs = new ProjectManagerProxyChangeEventArgs(proxyArgs, state);
OnChanged(remoteProjectChangeArgs);
});
}
private void OnChanged(ProjectManagerProxyChangeEventArgs args)
{
_foregroundDispatcher.AssertForegroundThread();
if (_disposed)
{
return;
}
Changed?.Invoke(this, args);
}
}
}

Просмотреть файл

@ -0,0 +1,73 @@
// 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.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.LanguageServices;
using Microsoft.VisualStudio.LiveShare.Razor.Serialization;
using Microsoft.VisualStudio.Threading;
using Newtonsoft.Json;
namespace Microsoft.VisualStudio.LiveShare.Razor.Host
{
[ExportCollaborationService(
typeof(IProjectSnapshotManagerProxy),
Name = nameof(IProjectSnapshotManagerProxy),
Scope = SessionScope.Host,
Role = ServiceRole.RemoteService)]
internal class DefaultProjectSnapshotManagerProxyFactory : ICollaborationServiceFactory
{
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly JoinableTaskContext _joinableTaskContext;
private readonly Workspace _workspace;
[ImportingConstructor]
public DefaultProjectSnapshotManagerProxyFactory(
ForegroundDispatcher foregroundDispatcher,
JoinableTaskContext joinableTaskContext,
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
{
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (joinableTaskContext == null)
{
throw new ArgumentNullException(nameof(joinableTaskContext));
}
if (workspace == null)
{
throw new ArgumentNullException(nameof(workspace));
}
_foregroundDispatcher = foregroundDispatcher;
_joinableTaskContext = joinableTaskContext;
_workspace = workspace;
}
public Task<ICollaborationService> CreateServiceAsync(CollaborationSession session, CancellationToken cancellationToken)
{
if (session == null)
{
throw new ArgumentNullException(nameof(session));
}
var serializer = (JsonSerializer)session.GetService(typeof(JsonSerializer));
serializer.Converters.RegisterRazorLiveShareConverters();
var razorLanguageServices = _workspace.Services.GetLanguageServices(RazorLanguage.Name);
var projectSnapshotManager = razorLanguageServices.GetRequiredService<ProjectSnapshotManager>();
var service = new DefaultProjectSnapshotManagerProxy(session, _foregroundDispatcher, projectSnapshotManager, _joinableTaskContext.Factory);
return Task.FromResult<ICollaborationService>(service);
}
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// 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.Threading;
using System.Threading.Tasks;
namespace Microsoft.VisualStudio.LiveShare.Razor
{
public interface IProjectHierarchyProxy
{
Task<Uri> GetProjectPathAsync(Uri documentFilePath, CancellationToken cancellationToken);
}
}

Просмотреть файл

@ -0,0 +1,16 @@
// 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.Threading;
using System.Threading.Tasks;
namespace Microsoft.VisualStudio.LiveShare.Razor
{
public interface IProjectSnapshotManagerProxy
{
event EventHandler<ProjectManagerProxyChangeEventArgs> Changed;
Task<ProjectSnapshotManagerProxyState> GetStateAsync(CancellationToken cancellationToken);
}
}

Просмотреть файл

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net472</TargetFrameworks>
<Description>Razor is a markup syntax for adding server-side logic to web pages. This package contains the Visual Studio Live Share Razor infrastructure.</Description>
<EnableApiCheck>false</EnableApiCheck>
<CodeAnalysisRuleSet>Microsoft.VisualStudio.LiveShare.Razor.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Microsoft.VisualStudio.LanguageServices.Razor\Serialization\*.cs">
<Link>Serialization\%(FileName)%(Extension)</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.VisualStudio.Editor.Razor\Microsoft.VisualStudio.Editor.Razor.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.LiveShare" Version="$(MicrosoftVisualStudioLiveSharePackageVersion)" />
<PackageReference Include="Microsoft.VisualStudio.LanguageServices" Version="$(VSIX_MicrosoftVisualStudioLanguageServicesPackageVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Shell.15.0" Version="$(MicrosoftVisualStudioShell150PackageVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="$(MicrosoftVisualStudioThreadingPackageVersion)" />
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Microsoft Managed Recommended Rules" Description="These rules focus on the most critical problems in your code, including potential security holes, application crashes, and other important logic and design errors. It is recommended to include this rule set in any custom rule set you create for your projects." ToolsVersion="15.0">
<Localization ResourceAssembly="Microsoft.VisualStudio.CodeAnalysis.RuleSets.Strings.dll" ResourceBaseName="Microsoft.VisualStudio.CodeAnalysis.RuleSets.Strings.Localized">
<Name Resource="MinimumRecommendedRules_Name" />
<Description Resource="MinimumRecommendedRules_Description" />
</Localization>
<Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed">
<Rule Id="CA1001" Action="Warning" />
<Rule Id="CA1009" Action="Warning" />
<Rule Id="CA1016" Action="Warning" />
<Rule Id="CA1033" Action="Warning" />
<Rule Id="CA1049" Action="Warning" />
<Rule Id="CA1060" Action="Warning" />
<Rule Id="CA1061" Action="Warning" />
<Rule Id="CA1063" Action="Warning" />
<Rule Id="CA1065" Action="Warning" />
<Rule Id="CA1301" Action="Warning" />
<Rule Id="CA1400" Action="Warning" />
<Rule Id="CA1401" Action="Warning" />
<Rule Id="CA1403" Action="Warning" />
<Rule Id="CA1404" Action="Warning" />
<Rule Id="CA1405" Action="Warning" />
<Rule Id="CA1410" Action="Warning" />
<Rule Id="CA1415" Action="Warning" />
<Rule Id="CA1821" Action="Warning" />
<Rule Id="CA1900" Action="Warning" />
<Rule Id="CA1901" Action="Warning" />
<Rule Id="CA2002" Action="Warning" />
<Rule Id="CA2100" Action="Warning" />
<Rule Id="CA2101" Action="Warning" />
<Rule Id="CA2108" Action="Warning" />
<Rule Id="CA2111" Action="Warning" />
<Rule Id="CA2112" Action="Warning" />
<Rule Id="CA2114" Action="Warning" />
<Rule Id="CA2116" Action="Warning" />
<Rule Id="CA2117" Action="Warning" />
<Rule Id="CA2122" Action="Warning" />
<Rule Id="CA2123" Action="Warning" />
<Rule Id="CA2124" Action="Warning" />
<Rule Id="CA2126" Action="Warning" />
<Rule Id="CA2131" Action="Warning" />
<Rule Id="CA2132" Action="Warning" />
<Rule Id="CA2133" Action="Warning" />
<Rule Id="CA2134" Action="Warning" />
<Rule Id="CA2137" Action="Warning" />
<Rule Id="CA2138" Action="Warning" />
<Rule Id="CA2140" Action="Warning" />
<Rule Id="CA2141" Action="Warning" />
<Rule Id="CA2146" Action="Warning" />
<Rule Id="CA2147" Action="Warning" />
<Rule Id="CA2149" Action="Warning" />
<Rule Id="CA2200" Action="Warning" />
<Rule Id="CA2202" Action="Warning" />
<Rule Id="CA2207" Action="Warning" />
<Rule Id="CA2212" Action="Warning" />
<Rule Id="CA2213" Action="Warning" />
<Rule Id="CA2214" Action="Warning" />
<Rule Id="CA2216" Action="Warning" />
<Rule Id="CA2220" Action="Warning" />
<Rule Id="CA2229" Action="Warning" />
<Rule Id="CA2231" Action="Warning" />
<Rule Id="CA2232" Action="Warning" />
<Rule Id="CA2235" Action="Warning" />
<Rule Id="CA2236" Action="Warning" />
<Rule Id="CA2237" Action="Warning" />
<Rule Id="CA2238" Action="Warning" />
<Rule Id="CA2240" Action="Warning" />
<Rule Id="CA2241" Action="Warning" />
<Rule Id="CA2242" Action="Warning" />
</Rules>
<Rules AnalyzerId="Microsoft.VisualStudio.Threading.Analyzers" RuleNamespace="Microsoft.VisualStudio.Threading.Analyzers">
<Rule Id="VSTHRD001" Action="Info" />
<Rule Id="VSTHRD002" Action="Info" />
<Rule Id="VSTHRD003" Action="Info" />
<Rule Id="VSTHRD004" Action="Info" />
<Rule Id="VSTHRD010" Action="Info" />
<Rule Id="VSTHRD011" Action="Info" />
<Rule Id="VSTHRD012" Action="Info" />
<Rule Id="VSTHRD100" Action="Info" />
<Rule Id="VSTHRD101" Action="Info" />
<Rule Id="VSTHRD102" Action="Info" />
<Rule Id="VSTHRD103" Action="Info" />
<Rule Id="VSTHRD104" Action="Info" />
<Rule Id="VSTHRD105" Action="Info" />
<Rule Id="VSTHRD106" Action="Info" />
<Rule Id="VSTHRD107" Action="Info" />
<Rule Id="VSTHRD108" Action="Info" />
<Rule Id="VSTHRD109" Action="Info" />
<Rule Id="VSTHRD110" Action="Info" />
<Rule Id="VSTHRD111" Action="Info" />
<Rule Id="VSTHRD200" Action="Info" />
</Rules>
</RuleSet>

Просмотреть файл

@ -0,0 +1,32 @@
// 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;
namespace Microsoft.VisualStudio.LiveShare.Razor
{
public sealed class ProjectManagerProxyChangeEventArgs : EventArgs
{
public ProjectManagerProxyChangeEventArgs(
ProjectProxyChangeEventArgs change,
ProjectSnapshotManagerProxyState state)
{
if (change == null)
{
throw new ArgumentNullException(nameof(change));
}
if (state == null)
{
throw new ArgumentNullException(nameof(state));
}
Change = change;
State = state;
}
public ProjectProxyChangeEventArgs Change { get; }
public ProjectSnapshotManagerProxyState State { get; }
}
}

Просмотреть файл

@ -0,0 +1,22 @@
// 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;
namespace Microsoft.VisualStudio.LiveShare.Razor
{
public sealed class ProjectProxyChangeEventArgs
{
public ProjectProxyChangeEventArgs(
Uri projectFilePath,
ProjectProxyChangeKind kind)
{
ProjectFilePath = projectFilePath;
Kind = kind;
}
public Uri ProjectFilePath { get; }
public ProjectProxyChangeKind Kind { get; }
}
}

Просмотреть файл

@ -0,0 +1,12 @@
// 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.
namespace Microsoft.VisualStudio.LiveShare.Razor
{
public enum ProjectProxyChangeKind
{
ProjectAdded,
ProjectRemoved,
ProjectChanged,
}
}

Просмотреть файл

@ -0,0 +1,38 @@
// 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.AspNetCore.Razor.Language;
namespace Microsoft.VisualStudio.LiveShare.Razor
{
public sealed class ProjectSnapshotHandleProxy
{
public ProjectSnapshotHandleProxy(
Uri filePath,
IReadOnlyList<TagHelperDescriptor> tagHelpers,
RazorConfiguration configuration)
{
if (filePath == null)
{
throw new ArgumentNullException(nameof(filePath));
}
if (tagHelpers == null)
{
throw new ArgumentNullException(nameof(tagHelpers));
}
FilePath = filePath;
TagHelpers = tagHelpers;
Configuration = configuration;
}
public RazorConfiguration Configuration { get; }
public IReadOnlyList<TagHelperDescriptor> TagHelpers { get; }
public Uri FilePath { get; }
}
}

Просмотреть файл

@ -0,0 +1,23 @@
// 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;
namespace Microsoft.VisualStudio.LiveShare.Razor
{
public sealed class ProjectSnapshotManagerProxyState
{
public ProjectSnapshotManagerProxyState(IReadOnlyList<ProjectSnapshotHandleProxy> projectHandles)
{
if (projectHandles == null)
{
throw new ArgumentNullException(nameof(projectHandles));
}
ProjectHandles = projectHandles;
}
public IReadOnlyList<ProjectSnapshotHandleProxy> ProjectHandles { get; }
}
}

Просмотреть файл

@ -0,0 +1,6 @@
// 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.LiveShare.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]

Просмотреть файл

@ -0,0 +1,29 @@
// 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 Microsoft.CodeAnalysis.Razor;
using Newtonsoft.Json;
namespace Microsoft.VisualStudio.LiveShare.Razor.Serialization
{
internal static class LiveShareJsonConverterCollectionExtensions
{
public static void RegisterRazorLiveShareConverters(this JsonConverterCollection collection)
{
if (collection == null)
{
throw new ArgumentNullException(nameof(collection));
}
if (collection.Contains(ProjectSnapshotHandleProxyJsonConverter.Instance))
{
// Already registered.
return;
}
collection.Add(ProjectSnapshotHandleProxyJsonConverter.Instance);
collection.RegisterRazorConverters();
}
}
}

Просмотреть файл

@ -0,0 +1,62 @@
// 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.AspNetCore.Razor.Language;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.VisualStudio.LiveShare.Razor.Serialization
{
internal class ProjectSnapshotHandleProxyJsonConverter : JsonConverter
{
public static readonly ProjectSnapshotHandleProxyJsonConverter Instance = new ProjectSnapshotHandleProxyJsonConverter();
public override bool CanConvert(Type objectType)
{
return typeof(ProjectSnapshotHandleProxy).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.StartObject)
{
return null;
}
var obj = JObject.Load(reader);
var filePath = obj[nameof(ProjectSnapshotHandleProxy.FilePath)].ToObject<Uri>(serializer);
var tagHelpers = obj[nameof(ProjectSnapshotHandleProxy.TagHelpers)].ToObject<List<TagHelperDescriptor>>(serializer);
var configuration = obj[nameof(ProjectSnapshotHandleProxy.Configuration)].ToObject<RazorConfiguration>(serializer);
return new ProjectSnapshotHandleProxy(filePath, tagHelpers, configuration);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var handle = (ProjectSnapshotHandleProxy)value;
writer.WriteStartObject();
writer.WritePropertyName(nameof(ProjectSnapshotHandleProxy.FilePath));
writer.WriteValue(handle.FilePath);
writer.WritePropertyName(nameof(ProjectSnapshotHandleProxy.TagHelpers));
serializer.Serialize(writer, handle.TagHelpers);
if (handle.Configuration == null)
{
writer.WritePropertyName(nameof(ProjectSnapshotHandleProxy.Configuration));
writer.WriteNull();
}
else
{
writer.WritePropertyName(nameof(ProjectSnapshotHandleProxy.Configuration));
serializer.Serialize(writer, handle.Configuration);
}
writer.WriteEndObject();
}
}
}

Просмотреть файл

@ -93,6 +93,7 @@
<ProjectReference Include="..\Microsoft.CodeAnalysis.Remote.Razor\Microsoft.CodeAnalysis.Remote.Razor.csproj" />
<ProjectReference Include="..\Microsoft.VisualStudio.Editor.Razor\Microsoft.VisualStudio.Editor.Razor.csproj" />
<ProjectReference Include="..\Microsoft.VisualStudio.LanguageServices.Razor\Microsoft.VisualStudio.LanguageServices.Razor.csproj" />
<ProjectReference Include="..\Microsoft.VisualStudio.LiveShare.Razor\Microsoft.VisualStudio.LiveShare.Razor.csproj" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -38,5 +38,7 @@
<Asset Type="Microsoft.VisualStudio.MefComponent" Path="Microsoft.VisualStudio.LanguageServices.Razor.dll" />
<Asset Type="Microsoft.VisualStudio.Assembly" Path="Microsoft.VisualStudio.Editor.Razor.dll" />
<Asset Type="Microsoft.VisualStudio.MefComponent" Path="Microsoft.VisualStudio.Editor.Razor.dll" />
<Asset Type="Microsoft.VisualStudio.Assembly" Path="Microsoft.VisualStudio.LiveShare.Razor.dll" />
<Asset Type="Microsoft.VisualStudio.MefComponent" Path="Microsoft.VisualStudio.LiveShare.Razor.dll" />
</Assets>
</PackageManifest>

Просмотреть файл

@ -0,0 +1,172 @@
// 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 System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.LiveShare.Razor.Test;
using Microsoft.VisualStudio.Threading;
using Moq;
using Xunit;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
public class DefaultProjectSnapshotHandleStoreTest : ForegroundDispatcherTestBase
{
public DefaultProjectSnapshotHandleStoreTest()
{
var joinableTaskContext = new JoinableTaskContextNode(new JoinableTaskContext());
JoinableTaskFactory = new JoinableTaskFactory(joinableTaskContext.Context);
}
public JoinableTaskFactory JoinableTaskFactory { get; }
[Fact]
public void GetProjectHandles_ReturnsCurrentProjects()
{
// Arrange
var snapshotStore = new DefaultProjectSnapshotHandleStore(Dispatcher, JoinableTaskFactory, Mock.Of<ProxyAccessor>());
snapshotStore.UpdateProjects(
new ProjectSnapshotManagerProxyState(new[]
{
TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/other/project.csproj")),
TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/path/project.csproj")),
}));
// Act
var projects = snapshotStore.GetProjectHandles();
// Assert
Assert.Equal(2, projects.Count);
}
[Fact]
public void GetProjectHandles_ReturnsEmptyArrayIfNotInitialized()
{
// Arrange
var snapshotStore = new DefaultProjectSnapshotHandleStore(Dispatcher, JoinableTaskFactory, Mock.Of<ProxyAccessor>());
// Act
var projects = snapshotStore.GetProjectHandles();
// Assert
Assert.Empty(projects);
}
[Fact]
public void RemoteProxyStateManager_UpdatesStateAndRaisesChangedEvent()
{
// Arrange
var expectedChangeEventArgs = new ProjectProxyChangeEventArgs(new Uri("vsls:/some/path/project.csproj"), ProjectProxyChangeKind.ProjectChanged);
var expectedProjectManagerState = new ProjectSnapshotManagerProxyState(new[] { TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/path/project.csproj")) });
var setupArgs = new ProjectManagerProxyChangeEventArgs(expectedChangeEventArgs, expectedProjectManagerState);
var snapshotStore = new DefaultProjectSnapshotHandleStore(Dispatcher, JoinableTaskFactory, Mock.Of<ProxyAccessor>());
var called = false;
snapshotStore.Changed += (sender, args) =>
{
called = true;
Assert.Same(expectedChangeEventArgs, args);
};
// Act
snapshotStore.HostProxyStateManager_Changed(null, setupArgs);
// Assert
Assert.True(called);
Assert.Single(snapshotStore.GetProjectHandles());
}
[Fact]
public async Task InitializeProjects_RaisesChangeEventForEachProjectIfGetProjectHandlesWasCalledBeforeInitialization()
{
// Arrange
var proxy = new Mock<IProjectSnapshotManagerProxy>();
proxy.Setup(p => p.GetStateAsync(CancellationToken.None))
.Returns(Task.FromResult(new ProjectSnapshotManagerProxyState(new[]
{
TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/path/project.csproj")),
TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/other/project.csproj")),
})));
var accessor = Mock.Of<ProxyAccessor>(a => a.GetProjectSnapshotManagerProxy() == proxy.Object);
var snapshotStore = new DefaultProjectSnapshotHandleStore(Dispatcher, JoinableTaskFactory, accessor);
snapshotStore.GetProjectHandles();
var calledWith = new List<ProjectProxyChangeEventArgs>();
snapshotStore.Changed += (sender, args) => calledWith.Add(args);
// Act
snapshotStore.InitializeProjects();
await snapshotStore.TestInitializationTask;
// Assert
Assert.Collection(calledWith,
args =>
{
Assert.Equal(ProjectProxyChangeKind.ProjectAdded, args.Kind);
Assert.Equal("vsls:/some/path/project.csproj", args.ProjectFilePath.ToString());
},
args =>
{
Assert.Equal(ProjectProxyChangeKind.ProjectAdded, args.Kind);
Assert.Equal("vsls:/some/other/project.csproj", args.ProjectFilePath.ToString());
});
}
[Fact]
public async Task InitializeProjects_UpdatesProjectStateWithoutTriggeringChange()
{
// Arrange
var proxy = new Mock<IProjectSnapshotManagerProxy>();
proxy.Setup(p => p.GetStateAsync(CancellationToken.None))
.Returns(Task.FromResult(new ProjectSnapshotManagerProxyState(new[] { TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/path/project.csproj")) })));
var accessor = Mock.Of<ProxyAccessor>(a => a.GetProjectSnapshotManagerProxy() == proxy.Object);
var snapshotStore = new DefaultProjectSnapshotHandleStore(Dispatcher, JoinableTaskFactory, accessor);
snapshotStore.Changed += (sender, args) => throw new InvalidOperationException("This should not be called.");
// Act
snapshotStore.InitializeProjects();
await snapshotStore.TestInitializationTask;
// Assert
Assert.Single(snapshotStore.GetProjectHandles());
}
[Fact]
public async Task InitializeProjects_DoesNotUpdateProjectsIfInitialized()
{
// Arrange
var proxy = new Mock<IProjectSnapshotManagerProxy>();
proxy.Setup(p => p.GetStateAsync(CancellationToken.None))
.Returns(Task.FromResult(new ProjectSnapshotManagerProxyState(Array.Empty<ProjectSnapshotHandleProxy>())));
var accessor = Mock.Of<ProxyAccessor>(a => a.GetProjectSnapshotManagerProxy() == proxy.Object);
var snapshotStore = new DefaultProjectSnapshotHandleStore(Dispatcher, JoinableTaskFactory, accessor);
// Initialize the initial project state to a project list. This is pretending to be an update event from the proxy
// that has newer information than initialization.
snapshotStore.UpdateProjects(new ProjectSnapshotManagerProxyState(new[] { TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/path/project.csproj")) }));
// Act
snapshotStore.InitializeProjects();
await snapshotStore.TestInitializationTask;
// Assert
Assert.Single(snapshotStore.GetProjectHandles());
}
[Fact]
public void UpdateProjects_UpdatesProjectCollection()
{
// Arrange
var snapshotStore = new DefaultProjectSnapshotHandleStore(Dispatcher, JoinableTaskFactory, Mock.Of<ProxyAccessor>());
var expectedProject = TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/path/project.csproj"));
var remoteProjectSnapshotManagerState = new ProjectSnapshotManagerProxyState(new List<ProjectSnapshotHandleProxy>() { expectedProject });
// Act
snapshotStore.UpdateProjects(remoteProjectSnapshotManagerState);
// Assert
var project = Assert.Single(snapshotStore.GetProjectHandles());
Assert.Same(expectedProject, project);
}
}
}

Просмотреть файл

@ -0,0 +1,62 @@
// 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 Moq;
using Xunit;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
public class DefaultProxyAccessorTest
{
[Fact]
public void GetProjectSnapshotManagerProxy_Caches()
{
// Arrange
var proxy = Mock.Of<IProjectSnapshotManagerProxy>();
var proxyAccessor = new TestProxyAccessor<IProjectSnapshotManagerProxy>(proxy);
// Act
var proxy1 = proxyAccessor.GetProjectSnapshotManagerProxy();
var proxy2 = proxyAccessor.GetProjectSnapshotManagerProxy();
// Assert
Assert.Same(proxy1, proxy2);
}
[Fact]
public void GetProjectHierarchyProxy_Caches()
{
// Arrange
var proxy = Mock.Of<IProjectHierarchyProxy>();
var proxyAccessor = new TestProxyAccessor<IProjectHierarchyProxy>(proxy);
// Act
var proxy1 = proxyAccessor.GetProjectHierarchyProxy();
var proxy2 = proxyAccessor.GetProjectHierarchyProxy();
// Assert
Assert.Same(proxy1, proxy2);
}
private class TestProxyAccessor<TTestProxy> : DefaultProxyAccessor where TTestProxy : class
{
private readonly TTestProxy _proxy;
public TestProxyAccessor(TTestProxy proxy)
{
_proxy = proxy;
}
internal override TProxy CreateServiceProxy<TProxy>()
{
if (typeof(TProxy) == typeof(TTestProxy))
{
return _proxy as TProxy;
}
throw new InvalidOperationException("The proxy accessor was called with unexpected arguments.");
}
}
}
}

Просмотреть файл

@ -0,0 +1,147 @@
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.VisualStudio.LiveShare.Razor.Test;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Threading;
using Moq;
using Xunit;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
public class GuestProjectPathProviderTest : ForegroundDispatcherTestBase
{
public GuestProjectPathProviderTest()
{
var joinableTaskContext = new JoinableTaskContextNode(new JoinableTaskContext());
JoinableTaskFactory = new JoinableTaskFactory(joinableTaskContext.Context);
}
public JoinableTaskFactory JoinableTaskFactory { get; }
[Fact]
public void TryGetProjectPath_NoTextDocument_ReturnsFalse()
{
// Arrange
var textBuffer = Mock.Of<ITextBuffer>();
var projectPathProvider = new GuestProjectPathProvider(
Dispatcher,
JoinableTaskFactory,
Mock.Of<ITextDocumentFactoryService>(),
Mock.Of<ProxyAccessor>(),
Mock.Of<LiveShareClientProvider>());
// Act
var result = projectPathProvider.TryGetProjectPath(textBuffer, out var filePath);
// Assert
Assert.False(result);
Assert.Null(filePath);
}
[Fact]
public void TryGetProjectPath_NullHostProjectPath_ReturnsFalse()
{
// Arrange
var textBuffer = Mock.Of<ITextBuffer>();
var textDocument = Mock.Of<ITextDocument>();
var textDocumentFactory = Mock.Of<ITextDocumentFactoryService>(factory => factory.TryGetTextDocument(textBuffer, out textDocument) == true);
var projectPathProvider = new TestGuestProjectPathProvider(
null,
Dispatcher,
JoinableTaskFactory,
textDocumentFactory,
Mock.Of<ProxyAccessor>(),
Mock.Of<LiveShareClientProvider>());
// Act
var result = projectPathProvider.TryGetProjectPath(textBuffer, out var filePath);
// Assert
Assert.False(result);
Assert.Null(filePath);
}
[Fact]
public async Task TryGetProjectPath_ValidHostProjectPath_ReturnsTrueWithGuestNormalizedPathAsync()
{
// Arrange
var textBuffer = Mock.Of<ITextBuffer>();
var textDocument = Mock.Of<ITextDocument>();
var textDocumentFactory = Mock.Of<ITextDocumentFactoryService>(factory => factory.TryGetTextDocument(textBuffer, out textDocument) == true);
var expectedProjectPath = "/guest/path/project.csproj";
var collabSession = new TestCollaborationSession(isHost: false);
var liveShareClientProvider = new LiveShareClientProvider();
await liveShareClientProvider.CreateServiceAsync(collabSession, CancellationToken.None);
var projectPathProvider = new TestGuestProjectPathProvider(
new Uri("vsls:/path/project.csproj"),
Dispatcher,
JoinableTaskFactory,
textDocumentFactory,
Mock.Of<ProxyAccessor>(),
liveShareClientProvider);
// Act
var result = projectPathProvider.TryGetProjectPath(textBuffer, out var filePath);
// Assert
Assert.True(result);
Assert.Equal(expectedProjectPath, filePath);
}
[Fact]
public async Task GetHostProjectPath_AsksProxyForProjectPathAsync()
{
// Arrange
var expectedGuestFilePath = "/guest/path/index.cshtml";
var expectedHostFilePath = new Uri("vsls:/path/index.cshtml");
var expectedHostProjectPath = new Uri("vsls:/path/project.csproj");
var collabSession = new TestCollaborationSession(isHost: true);
var liveShareClientProvider = new LiveShareClientProvider();
await liveShareClientProvider.CreateServiceAsync(collabSession, CancellationToken.None);
var proxy = Mock.Of<IProjectHierarchyProxy>(p => p.GetProjectPathAsync(expectedHostFilePath, CancellationToken.None) == Task.FromResult(expectedHostProjectPath));
var proxyAccessor = Mock.Of<ProxyAccessor>(accessor => accessor.GetProjectHierarchyProxy() == proxy);
var textDocument = Mock.Of<ITextDocument>(document => document.FilePath == expectedGuestFilePath);
var projectPathProvider = new GuestProjectPathProvider(
Dispatcher,
JoinableTaskFactory,
Mock.Of<ITextDocumentFactoryService>(),
proxyAccessor,
liveShareClientProvider);
// Act
var hostProjectPath = projectPathProvider.GetHostProjectPath(textDocument);
// Assert
Assert.Equal(expectedHostProjectPath, hostProjectPath);
}
private class TestGuestProjectPathProvider : GuestProjectPathProvider
{
private readonly Uri _hostProjectPath;
public TestGuestProjectPathProvider(
Uri hostProjectPath,
ForegroundDispatcher foregroundDispatcher,
JoinableTaskFactory joinableTaskFactory,
ITextDocumentFactoryService textDocumentFactory,
ProxyAccessor proxyAccessor,
LiveShareClientProvider remoteWorkspaceManager)
: base(foregroundDispatcher, joinableTaskFactory, textDocumentFactory, proxyAccessor, remoteWorkspaceManager)
{
_hostProjectPath = hostProjectPath;
}
internal override Uri GetHostProjectPath(ITextDocument textDocument) => _hostProjectPath;
}
}
}

Просмотреть файл

@ -0,0 +1,41 @@
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.VisualStudio.LiveShare.Razor.Test;
using Xunit;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
public class GuestProjectSnapshotFactoryTest
{
[Fact]
public async Task Create_ConvertsFromHandleToSnapshotAsync()
{
// Arrange
var configuration = RazorConfiguration.Create(RazorLanguageVersion.Version_1_1, "TestConfiguration", Enumerable.Empty<RazorExtension>());
var expectedTagHelpers = new[] { TagHelperDescriptorBuilder.Create("test1", "TestAssembly1").Build() };
var projectHandle = new ProjectSnapshotHandleProxy(new Uri("vsls:/path/project.csproj"), expectedTagHelpers, configuration);
var collabSession = new TestCollaborationSession(isHost: false);
var liveShareClientProvider = new LiveShareClientProvider();
await liveShareClientProvider.CreateServiceAsync(collabSession, CancellationToken.None);
var workspace = TestWorkspace.Create();
var factory = new GuestProjectSnapshotFactory(workspace, liveShareClientProvider);
// Act
var project = factory.Create(projectHandle);
// Assert
Assert.Empty(project.DocumentFilePaths);
Assert.Equal(configuration, project.Configuration);
Assert.Equal("/guest/path/project.csproj", project.FilePath);
Assert.True(project.TryGetTagHelpers(out var tagHelpers));
Assert.Equal(expectedTagHelpers, tagHelpers);
}
}
}

Просмотреть файл

@ -0,0 +1,171 @@
// 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.Host;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.LiveShare.Razor.Test;
using Xunit;
namespace Microsoft.VisualStudio.LiveShare.Razor.Guest
{
public class GuestProjectSnapshotManagerTest : ForegroundDispatcherTestBase
{
public Workspace Workspace { get; } = TestWorkspace.Create();
[Fact]
public void Projects_ReturnsCurrentProjects()
{
// Arrange
var snapshotStore = CreateSnapshotStore(
TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/other/project.csproj")),
TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/path/project.csproj")));
var snapshotManager = CreateSnapshotManager(snapshotStore, Workspace);
// Act & Assert
Assert.Equal(2, snapshotManager.Projects.Count);
}
[Fact]
public void GetLoadedProject_ReturnsNullIfCanNotFindProject()
{
// Arrange
var snapshotStore = CreateSnapshotStore(
TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/other/project.csproj")),
TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/path/project.csproj")));
var snapshotManager = CreateSnapshotManager(snapshotStore, Workspace);
// Act
var project = snapshotManager.GetLoadedProject("/some/random/path/project.csproj");
// Assert
Assert.Null(project);
}
[Fact]
public void GetLoadedProject_ReturnsExistingProject()
{
// Arrange
var expectedProject = TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/path/project.csproj"));
var snapshotStore = CreateSnapshotStore(
TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/other/project.csproj")),
expectedProject);
var snapshotManager = CreateSnapshotManager(snapshotStore, Workspace);
// Act
var project = snapshotManager.GetLoadedProject(expectedProject.FilePath.ToString());
// Assert
Assert.IsType<TestProjectSnapshot>(project);
Assert.Equal(expectedProject.FilePath.ToString(), project.FilePath);
}
[Fact]
public void GetOrCreateProject_CreatesNewProject()
{
// Arrange
var snapshotStore = CreateSnapshotStore(TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/path/project.csproj")));
var workspace = TestWorkspace.Create();
var snapshotManager = CreateSnapshotManager(snapshotStore, workspace);
var expectedNewProjectPath = "/some/random/path/project.csproj";
// Act
var project = snapshotManager.GetOrCreateProject(expectedNewProjectPath);
// Assert
Assert.IsType<EphemeralProjectSnapshot>(project);
Assert.Equal(expectedNewProjectPath, project.FilePath);
}
[Fact]
public void GetOrCreateProject_ReturnsExistingProject()
{
// Arrange
var expectedProject = TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/path/project.csproj"));
var snapshotStore = CreateSnapshotStore(
TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/other/project.csproj")),
expectedProject);
var snapshotManager = CreateSnapshotManager(snapshotStore, Workspace);
// Act
var project = snapshotManager.GetOrCreateProject(expectedProject.FilePath.ToString());
// Assert
Assert.IsType<TestProjectSnapshot>(project);
Assert.Equal(expectedProject.FilePath.ToString(), project.FilePath);
}
[Fact]
public void ProjectSnapshotStore_Changed_UpdatesStateAndRaisesChangedEvent()
{
// Arrange
var expectedFilePath = new Uri("vsls:/some/path/project.csproj");
var storeArgs = new ProjectProxyChangeEventArgs(expectedFilePath, ProjectProxyChangeKind.ProjectChanged);
var snapshotStore = new TestSnapshotStore();
snapshotStore.ProjectHandles.Add(TestProjectSnapshotHandleProxy.Create(new Uri("vsls:/some/path/project.csproj")));
var snapshotManager = CreateSnapshotManager(snapshotStore, Workspace);
var initialProjects = snapshotManager.Projects;
snapshotStore.ProjectHandles.Clear();
var called = false;
snapshotManager.Changed += (sender, args) =>
{
called = true;
Assert.Equal(expectedFilePath.ToString(), args.ProjectFilePath);
Assert.Equal(ProjectChangeKind.ProjectChanged, args.Kind);
};
// Act
snapshotManager.ProjectSnapshotStore_Changed(null, storeArgs);
// Assert
Assert.True(called);
Assert.NotEqual(snapshotManager.Projects.Count, initialProjects.Count);
}
private static ProjectSnapshotHandleStore CreateSnapshotStore(params ProjectSnapshotHandleProxy[] snapshotProxies)
{
var snapshotStore = new TestSnapshotStore();
snapshotStore.ProjectHandles.AddRange(snapshotProxies);
return snapshotStore;
}
private GuestProjectSnapshotManager CreateSnapshotManager(ProjectSnapshotHandleStore projectSnapshotHandleStore, Workspace workspace)
{
return new TestProjectSnapshotManager(Dispatcher, workspace.Services, projectSnapshotHandleStore, workspace);
}
private class TestSnapshotStore : ProjectSnapshotHandleStore
{
public override event EventHandler<ProjectProxyChangeEventArgs> Changed
{
add { }
remove { }
}
public List<ProjectSnapshotHandleProxy> ProjectHandles { get; } = new List<ProjectSnapshotHandleProxy>();
public override IReadOnlyList<ProjectSnapshotHandleProxy> GetProjectHandles() => ProjectHandles;
}
private class TestProjectSnapshotManager : GuestProjectSnapshotManager
{
internal TestProjectSnapshotManager(ForegroundDispatcher foregroundDispatcher, HostWorkspaceServices services, ProjectSnapshotHandleStore projectSnapshotHandleStore, Workspace workspace)
: base(foregroundDispatcher, services, projectSnapshotHandleStore, new TestProjectSnapshotFactory(), workspace)
{
}
internal override string ResolveGuestPath(Uri filePath)
{
return filePath.ToString();
}
}
private class TestProjectSnapshotFactory : ProjectSnapshotFactory
{
public override ProjectSnapshot Create(ProjectSnapshotHandleProxy projectHandle) => new TestProjectSnapshot(projectHandle.FilePath.ToString());
}
}
}

Просмотреть файл

@ -0,0 +1,225 @@
// 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 System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.LiveShare.Razor.Test;
using Microsoft.VisualStudio.Threading;
using Moq;
using Xunit;
namespace Microsoft.VisualStudio.LiveShare.Razor.Host
{
public class DefaultProjectSnapshotManagerProxyTest : ForegroundDispatcherTestBase
{
public DefaultProjectSnapshotManagerProxyTest()
{
var joinableTaskContext = new JoinableTaskContextNode(new JoinableTaskContext());
JoinableTaskFactory = new JoinableTaskFactory(joinableTaskContext.Context);
}
public JoinableTaskFactory JoinableTaskFactory { get; }
[Fact]
public async Task CalculateUpdatedStateAsync_ReturnsStateForAllProjects()
{
// Arrange
var tagHelper1 = TagHelperDescriptorBuilder.Create("test1", "TestAssembly1").Build();
var project1 = new TestProjectSnapshot("/host/path/to/project1.csproj", tagHelper1);
var tagHelper2 = TagHelperDescriptorBuilder.Create("test2", "TestAssembly2").Build();
var project2 = new TestProjectSnapshot("/host/path/to/project2.csproj", tagHelper2);
var projects = new ProjectSnapshot[] { project1, project2 };
var projectSnapshotManager = new TestProjectSnapshotManager(projects);
var proxy = new DefaultProjectSnapshotManagerProxy(
new TestCollaborationSession(true),
Dispatcher,
projectSnapshotManager,
JoinableTaskFactory);
// Act
var state = await JoinableTaskFactory.RunAsync(() => proxy.CalculateUpdatedStateAsync(projects));
// Assert
Assert.Collection(
state.ProjectHandles,
handle =>
{
Assert.Equal("vsls:/path/to/project1.csproj", handle.FilePath.ToString());
var tagHelper = Assert.Single(handle.TagHelpers);
Assert.Equal(tagHelper1, tagHelper);
},
handle =>
{
Assert.Equal("vsls:/path/to/project2.csproj", handle.FilePath.ToString());
var tagHelper = Assert.Single(handle.TagHelpers);
Assert.Equal(tagHelper2, tagHelper);
});
}
[Fact]
public async Task Changed_TriggersOnSnapshotManagerChanged()
{
// Arrange
var projectSnapshotManager = new TestProjectSnapshotManager(new TestProjectSnapshot("/host/path/to/project1.csproj"));
var proxy = new DefaultProjectSnapshotManagerProxy(
new TestCollaborationSession(true),
Dispatcher,
projectSnapshotManager,
JoinableTaskFactory);
var snapshot = new TestProjectSnapshot("/host/path/to/project1.csproj");
var changedArgs = new ProjectChangeEventArgs(snapshot, snapshot, ProjectChangeKind.ProjectChanged);
var called = false;
proxy.Changed += (sender, args) =>
{
called = true;
Assert.Equal($"vsls:/path/to/project1.csproj", args.Change.ProjectFilePath.ToString());
Assert.Equal(ProjectProxyChangeKind.ProjectChanged, args.Change.Kind);
var handle = Assert.Single(args.State.ProjectHandles);
Assert.Equal("vsls:/path/to/project1.csproj", handle.FilePath.ToString());
Assert.Empty(handle.TagHelpers);
};
// Act
projectSnapshotManager.TriggerChanged(changedArgs);
await proxy._processingChangedEventTestTask.JoinAsync();
// Assert
Assert.True(called);
}
[Fact]
public void Changed_NoopsIfProxyDisposed()
{
// Arrange
var projectSnapshotManager = new TestProjectSnapshotManager(new TestProjectSnapshot("/host/path/to/project1.csproj"));
var proxy = new DefaultProjectSnapshotManagerProxy(
new TestCollaborationSession(true),
Dispatcher,
projectSnapshotManager,
JoinableTaskFactory);
var snapshot = new TestProjectSnapshot("/host/path/to/project1.csproj");
var changedArgs = new ProjectChangeEventArgs(snapshot, snapshot, ProjectChangeKind.ProjectChanged);
proxy.Changed += (sender, args) => throw new InvalidOperationException("Should not have been called.");
proxy.Dispose();
// Act
projectSnapshotManager.TriggerChanged(changedArgs);
// Assert
Assert.Null(proxy._processingChangedEventTestTask);
}
[Fact]
public async Task GetLatestProjectsAsync_ReturnsSnapshotManagerProjects()
{
// Arrange
var expectedProject = new TestProjectSnapshot("/host/path/to/project1.csproj");
var projectSnapshotManager = new TestProjectSnapshotManager(expectedProject);
var proxy = new DefaultProjectSnapshotManagerProxy(
new TestCollaborationSession(true),
Dispatcher,
projectSnapshotManager,
JoinableTaskFactory);
// Act
var projects = await proxy.GetLatestProjectsAsync();
// Assert
var project = Assert.Single(projects);
Assert.Same(expectedProject, project);
}
[Fact]
public async Task GetStateAsync_ReturnsProjectState()
{
// Arrange
var tagHelper1 = TagHelperDescriptorBuilder.Create("test1", "TestAssembly1").Build();
var project1 = new TestProjectSnapshot("/host/path/to/project1.csproj", tagHelper1);
var tagHelper2 = TagHelperDescriptorBuilder.Create("test2", "TestAssembly2").Build();
var project2 = new TestProjectSnapshot("/host/path/to/project2.csproj", tagHelper2);
var projects = new ProjectSnapshot[] { project1, project2 };
var projectSnapshotManager = new TestProjectSnapshotManager(projects);
var proxy = new DefaultProjectSnapshotManagerProxy(
new TestCollaborationSession(true),
Dispatcher,
projectSnapshotManager,
JoinableTaskFactory);
// Act
var state = await JoinableTaskFactory.RunAsync(() => proxy.GetStateAsync(CancellationToken.None));
// Assert
Assert.Collection(
state.ProjectHandles,
handle =>
{
Assert.Equal("vsls:/path/to/project1.csproj", handle.FilePath.ToString());
var tagHelper = Assert.Single(handle.TagHelpers);
Assert.Equal(tagHelper1, tagHelper);
},
handle =>
{
Assert.Equal("vsls:/path/to/project2.csproj", handle.FilePath.ToString());
var tagHelper = Assert.Single(handle.TagHelpers);
Assert.Equal(tagHelper2, tagHelper);
});
}
[Fact]
public async Task GetStateAsync_CachesState()
{
// Arrange
var project = new TestProjectSnapshot("/host/path/to/project2.csproj");
var projectSnapshotManager = new TestProjectSnapshotManager(project);
var proxy = new DefaultProjectSnapshotManagerProxy(
new TestCollaborationSession(true),
Dispatcher,
projectSnapshotManager,
JoinableTaskFactory);
// Act
var state1 = await JoinableTaskFactory.RunAsync(() => proxy.GetStateAsync(CancellationToken.None));
var state2 = await JoinableTaskFactory.RunAsync(() => proxy.GetStateAsync(CancellationToken.None));
// Assert
Assert.Same(state1, state2);
}
private class TestProjectSnapshotManager : ProjectSnapshotManager
{
public TestProjectSnapshotManager(params ProjectSnapshot[] projects)
{
Projects = projects;
}
public override IReadOnlyList<ProjectSnapshot> Projects { get; }
public override event EventHandler<ProjectChangeEventArgs> Changed;
public void TriggerChanged(ProjectChangeEventArgs args)
{
Changed?.Invoke(this, args);
}
public override ProjectSnapshot GetLoadedProject(string filePath)
{
throw new NotImplementedException();
}
public override ProjectSnapshot GetOrCreateProject(string filePath)
{
throw new NotImplementedException();
}
public override bool IsDocumentOpen(string documentFilePath)
{
throw new NotImplementedException();
}
}
}
}

Просмотреть файл

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<CodeAnalysisRuleSet>..\..\src\Microsoft.VisualStudio.LiveShare.Razor\Microsoft.VisualStudio.LiveShare.Razor.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.VisualStudio.LiveShare.Razor\Microsoft.VisualStudio.LiveShare.Razor.csproj" />
<ProjectReference Include="..\Microsoft.VisualStudio.Editor.Razor.Test.Common\Microsoft.VisualStudio.Editor.Razor.Test.Common.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Moq" Version="$(MoqPackageVersion)" />
<PackageReference Include="xunit.analyzers" Version="$(XunitAnalyzersPackageVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,43 @@
// 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.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.VisualStudio.LiveShare.Razor.Serialization;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.VisualStudio.LiveShare.Razor
{
public class SerializationTest
{
[Fact]
public void ProjectSnapshotHandleProxy_RoundTripsProperly()
{
// Arrange
var tagHelpers = new[]
{
TagHelperDescriptorBuilder.Create("TestTagHelper", "TestAssembly").Build(),
TagHelperDescriptorBuilder.Create("TestTagHelper2", "TestAssembly2").Build(),
};
var expectedConfiguration = RazorConfiguration.Default;
var handle = new ProjectSnapshotHandleProxy(new Uri("vsls://some/path/project.csproj"), tagHelpers, RazorConfiguration.Default);
var converterCollection = new JsonConverterCollection();
converterCollection.RegisterRazorLiveShareConverters();
var converters = converterCollection.ToArray();
var serializedHandle = JsonConvert.SerializeObject(handle, converters);
// Act
var deserializedHandle = JsonConvert.DeserializeObject<ProjectSnapshotHandleProxy>(serializedHandle, converters);
// Assert
Assert.Equal("vsls://some/path/project.csproj", deserializedHandle.FilePath.ToString());
Assert.Equal(tagHelpers, deserializedHandle.TagHelpers);
Assert.Equal(expectedConfiguration.ConfigurationName, deserializedHandle.Configuration.ConfigurationName);
Assert.Equal(expectedConfiguration.Extensions.Count, deserializedHandle.Configuration.Extensions.Count);
Assert.Equal(expectedConfiguration.LanguageVersion, deserializedHandle.Configuration.LanguageVersion);
}
}
}

Просмотреть файл

@ -0,0 +1,69 @@
// 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 System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Microsoft.VisualStudio.LiveShare.Razor.Test
{
class TestCollaborationSession : CollaborationSession
{
private readonly bool _isHost;
public TestCollaborationSession(bool isHost)
{
_isHost = isHost;
}
/// <summary>
/// Assumes that host paths are prefixed with /host and guest paths are prefixed with /guest.
/// Converts such paths to vsls: uris
/// </summary>
public override Uri ConvertLocalPathToSharedUri(string localPath)
{
var path = localPath.Replace("/host", "").Replace("/guest", "");
return new Uri($"vsls:{path}");
}
/// <summary>
/// Assumes that host paths are prefixed with /host and guest paths are prefixed with /guest.
/// Converts vsls: uris to such paths.
/// </summary>
public override string ConvertSharedUriToLocalPath(Uri uri)
{
var path = uri.ToString().Replace("vsls:", "");
return _isHost ? $"/host{path}" : $"/guest{path}";
}
public override string SessionId => throw new NotImplementedException();
public override IReadOnlyCollection<Peer> Peers => throw new NotImplementedException();
public override IReadOnlyCollection<string> RemoteServiceNames => throw new NotImplementedException();
public override int PeerNumber => throw new NotImplementedException();
public override PeerIdentity Identity => throw new NotImplementedException();
public override PeerRole Role => throw new NotImplementedException();
public override PeerAccess Access => throw new NotImplementedException();
public override Task<string> DownloadFileAsync(Uri uri, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public override Task<TService> GetRemoteServiceAsync<TService>(string name, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public override object GetService(Type serviceType)
{
if (serviceType.Name == "JsonSerializer")
{
return new JsonSerializer();
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,67 @@
// 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 System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.VisualStudio.LiveShare.Razor.Test
{
internal class TestProjectSnapshot : ProjectSnapshot
{
private readonly TagHelperDescriptor[] _tagHelpers;
public TestProjectSnapshot(string filePath, params TagHelperDescriptor[] tagHelpers)
{
FilePath = filePath;
_tagHelpers = tagHelpers;
}
public override RazorConfiguration Configuration => RazorConfiguration.Default;
public override IEnumerable<string> DocumentFilePaths => throw new NotImplementedException();
public override string FilePath { get; }
public override bool IsInitialized => throw new NotImplementedException();
public override VersionStamp Version => throw new NotImplementedException();
public override Project WorkspaceProject => throw new NotImplementedException();
public override DocumentSnapshot GetDocument(string filePath)
{
throw new NotImplementedException();
}
public override RazorProjectEngine GetProjectEngine()
{
throw new NotImplementedException();
}
public override IEnumerable<DocumentSnapshot> GetRelatedDocuments(DocumentSnapshot document)
{
throw new NotImplementedException();
}
public override bool IsImportDocument(DocumentSnapshot document)
{
throw new NotImplementedException();
}
public override Task<IReadOnlyList<TagHelperDescriptor>> GetTagHelpersAsync()
{
return Task.FromResult((IReadOnlyList<TagHelperDescriptor>)_tagHelpers);
}
public override bool TryGetTagHelpers(out IReadOnlyList<TagHelperDescriptor> results)
{
results = _tagHelpers;
return true;
}
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// 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 Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
namespace Microsoft.VisualStudio.LiveShare.Razor.Test
{
internal static class TestProjectSnapshotHandleProxy
{
public static ProjectSnapshotHandleProxy Create(Uri filePath) => new ProjectSnapshotHandleProxy(filePath, Array.Empty<TagHelperDescriptor>(), RazorConfiguration.Default);
}
}