Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ private bool UseLSPEditor(string filePath)
}

// Otherwise, we just check for the lack of feature flag feature or project capability.
if (_lspEditorFeatureDetector.IsLspEditorEnabled() &&
_lspEditorFeatureDetector.IsLspEditorSupported(filePath))
if (_lspEditorFeatureDetector.IsLspEditorSupported(filePath))
{
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,21 @@
using System;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Internal.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Razor.Extensions;
using Microsoft.VisualStudio.Razor.Logging;
using Microsoft.VisualStudio.Settings;
using Microsoft.VisualStudio.Threading;

namespace Microsoft.VisualStudio.Razor;

[Export(typeof(ILspEditorFeatureDetector))]
internal sealed class LspEditorFeatureDetector : ILspEditorFeatureDetector, IDisposable
[method: ImportingConstructor]
internal sealed class LspEditorFeatureDetector(
IUIContextService uiContextService,
IProjectCapabilityResolver projectCapabilityResolver,
RazorActivityLog activityLog) : ILspEditorFeatureDetector, IDisposable
{
private readonly IUIContextService _uiContextService;
private readonly IProjectCapabilityResolver _projectCapabilityResolver;
private readonly JoinableTaskFactory _jtf;
private readonly RazorActivityLog _activityLog;
private readonly CancellationTokenSource _disposeTokenSource;
private readonly AsyncLazy<bool> _lazyLegacyEditorEnabled;

[ImportingConstructor]
public LspEditorFeatureDetector(
IVsService<SVsSettingsPersistenceManager, ISettingsManager> vsSettingsManagerService,
IUIContextService uiContextService,
IProjectCapabilityResolver projectCapabilityResolver,
JoinableTaskContext joinableTaskContext,
RazorActivityLog activityLog)
{
_uiContextService = uiContextService;
_projectCapabilityResolver = projectCapabilityResolver;
_jtf = joinableTaskContext.Factory;
_activityLog = activityLog;

_disposeTokenSource = new();

_lazyLegacyEditorEnabled = new(() =>
ComputeUseLegacyEditorAsync(vsSettingsManagerService, activityLog, _disposeTokenSource.Token),
_jtf);
}
private readonly IUIContextService _uiContextService = uiContextService;
private readonly IProjectCapabilityResolver _projectCapabilityResolver = projectCapabilityResolver;
private readonly RazorActivityLog _activityLog = activityLog;
private readonly CancellationTokenSource _disposeTokenSource = new();

public void Dispose()
{
Expand All @@ -54,53 +31,14 @@ public void Dispose()
_disposeTokenSource.Dispose();
}

private static async Task<bool> ComputeUseLegacyEditorAsync(
IVsService<SVsSettingsPersistenceManager, ISettingsManager> vsSettingsManagerService,
RazorActivityLog activityLog,
CancellationToken cancellationToken)
{
var settingsManager = await vsSettingsManagerService.GetValueAsync(cancellationToken).ConfigureAwaitRunInline();
var useLegacyEditorSetting = settingsManager.GetValueOrDefault<bool>(WellKnownSettingNames.UseLegacyASPNETCoreEditor);

if (useLegacyEditorSetting)
{
activityLog.LogInfo($"Using legacy editor because the '{WellKnownSettingNames.UseLegacyASPNETCoreEditor}' setting is set to true.");
return true;
}

activityLog.LogInfo($"Using LSP editor.");
return false;
}

public bool IsLspEditorEnabled()
{
// This method is first called by our IFilePathToContentTypeProvider.TryGetContentTypeForFilePath(...) implementations.
// We call AsyncLazy<T>.GetValue() below to get the value. If the work hasn't yet completed, we guard against a hidden
// JTF.Run(...) on a background thread by asserting the UI thread.

if (!_lazyLegacyEditorEnabled.IsValueFactoryCompleted)
{
#pragma warning disable VSTHRD108 // Assert thread affinity unconditionally
_jtf.AssertUIThread();
#pragma warning restore VSTHRD108 // Assert thread affinity unconditionally
}

return !_lazyLegacyEditorEnabled.GetValue(_disposeTokenSource.Token);
return true;
}

public bool IsLspEditorSupported(string documentFilePath)
{
// Regardless of whether the LSP is enabled via tools/options, the document's project
// might not support it. For example, .NET Framework projects don't support the LSP Razor editor.

var useLegacyEditor = _projectCapabilityResolver.CheckCapability(WellKnownProjectCapabilities.LegacyRazorEditor, documentFilePath);

if (useLegacyEditor.HasCapability)
{
_activityLog.LogInfo($"'{documentFilePath}' does not support the LSP editor because it is associated with the '{WellKnownProjectCapabilities.LegacyRazorEditor}' capability.");
return false;
}

// .NET Framework projects don't support the LSP Razor editor.
if (!IsDotNetCoreProject(documentFilePath).HasCapability)
{
_activityLog.LogInfo($"'{documentFilePath}' does not support the LSP editor because it is not associated with the '{WellKnownProjectCapabilities.DotNetCoreCSharp}' capability.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ internal class VisualStudioLanguageServerFeatureOptions : LanguageServerFeatureO
{
private readonly ILspEditorFeatureDetector _lspEditorFeatureDetector;
private readonly Lazy<bool> _showAllCSharpCodeActions;
private readonly Lazy<bool> _useRazorCohostServer;

[ImportingConstructor]
public VisualStudioLanguageServerFeatureOptions(ILspEditorFeatureDetector lspEditorFeatureDetector)
Expand All @@ -27,13 +26,6 @@ public VisualStudioLanguageServerFeatureOptions(ILspEditorFeatureDetector lspEdi
var showAllCSharpCodeActions = featureFlags.IsFeatureEnabled(WellKnownFeatureFlagNames.ShowAllCSharpCodeActions, defaultValue: false);
return showAllCSharpCodeActions;
});

_useRazorCohostServer = new Lazy<bool>(() =>
{
var featureFlags = (IVsFeatureFlags)Package.GetGlobalService(typeof(SVsFeatureFlags));
var useRazorCohostServer = featureFlags.IsFeatureEnabled(WellKnownFeatureFlagNames.UseRazorCohostServer, defaultValue: true);
return useRazorCohostServer;
});
}

// We don't currently support file creation operations on VS Codespaces or VS Liveshare
Expand All @@ -45,5 +37,5 @@ public VisualStudioLanguageServerFeatureOptions(ILspEditorFeatureDetector lspEdi

public override bool ShowAllCSharpCodeActions => _showAllCSharpCodeActions.Value;

public override bool UseRazorCohostServer => _useRazorCohostServer.Value;
public override bool UseRazorCohostServer => true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@ namespace Microsoft.VisualStudio.Razor;
internal static class WellKnownProjectCapabilities
{
public const string DotNetCoreCSharp = "CSharp&CPS";
public const string LegacyRazorEditor = "LegacyRazorEditor";
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,6 @@
"Title"="Show all C# code actions in Razor files (requires restart)"
"PreviewPaneChannels"="IntPreview,int.main"

[$RootKey$\FeatureFlags\Razor\LSP\UseRazorCohostServer]
"Description"="Uses the Razor language server that is cohosted in Roslyn to provide some Razor tooling functionality."
"Value"=dword:00000001
"Title"="Use Roslyn Cohost server for Razor (requires restart)"
"PreviewPaneChannels"="*"

// CacheTag value should be changed when registration file changes
// See https://devdiv.visualstudio.com/DevDiv/_wiki/wikis/DevDiv.wiki/39345/Manifest-Build-Deployment-and-Setup-Authoring-In-Depth?anchor=example-pkgdef-key for more infomation
[$RootKey$\SettingsManifests\{13b72f58-279e-49e0-a56d-296be02f0805}]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
using Microsoft.AspNetCore.Razor.Test.Common;
using Microsoft.AspNetCore.Razor.Test.Common.VisualStudio;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.Internal.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Razor.Logging;
using Microsoft.VisualStudio.Settings;
using Microsoft.VisualStudio.Shell.Interop;
using Moq;
using Xunit;
Expand All @@ -17,46 +15,34 @@ namespace Microsoft.VisualStudio.Razor;

public class LspEditorFeatureDetectorTest(ITestOutputHelper testOutput) : ToolingTestBase(testOutput)
{
public static TheoryData<bool, bool> IsLspEditorEnabledTestData { get; } = new()
{
// legacyEditorSetting, expectedResult
{ false, true },
{ true, false },
};

[UITheory]
[MemberData(nameof(IsLspEditorEnabledTestData))]
public void IsLspEditorEnabled(bool legacyEditorSetting, bool expectedResult)
[Fact]
public void IsLspEditorEnabled()
{
// Arrange
var featureDetector = CreateLspEditorFeatureDetector(legacyEditorSetting);
var featureDetector = CreateLspEditorFeatureDetector();

// Act
var result = featureDetector.IsLspEditorEnabled();

// Assert
Assert.Equal(expectedResult, result);
Assert.True(result);
}

public static TheoryData<bool, bool, bool, bool> IsLspEditorEnabledAndSupportedTestData { get; } = new()
public static TheoryData<bool, bool> IsLspEditorEnabledAndSupportedTestData { get; } = new()
{
// legacyEditorSetting, hasLegacyRazorEditorCapability, hasDotNetCoreCSharpCapability, expectedResult
{ false, true, false, false }, // .Net Framework project - always non-LSP
{ false, false, true, true }, // .Net Core project
{ false, true, true, false }, // .Net Core project opts-in into legacy razor editor (exists in reality?)
{ true, false, true, false }, // .Net Core project but legacy editor via editor option
// hasDotNetCoreCSharpCapability, expectedResult
{ false, false }, // .Net Framework project - always non-LSP
{ true, true }, // .Net Core project
};

[UITheory]
[MemberData(nameof(IsLspEditorEnabledAndSupportedTestData))]
public void IsLspEditorEnabledAndSupported(
bool legacyEditorSetting,
bool hasLegacyRazorEditorCapability,
bool hasDotNetCoreCSharpCapability,
bool expectedResult)
{
// Arrange
var featureDetector = CreateLspEditorFeatureDetector(legacyEditorSetting, hasLegacyRazorEditorCapability, hasDotNetCoreCSharpCapability);
var featureDetector = CreateLspEditorFeatureDetector(hasDotNetCoreCSharpCapability);

// Act
var result = featureDetector.IsLspEditorEnabled() &&
Expand Down Expand Up @@ -119,46 +105,30 @@ public void IsLiveShareHost(bool liveShareHostActive, bool liveShareGuestActive,
}

private ILspEditorFeatureDetector CreateLspEditorFeatureDetector(IUIContextService uiContextService)
=> CreateLspEditorFeatureDetector(legacyEditorSetting: false, uiContextService, hasLegacyRazorEditorCapability: false, hasDotNetCoreCSharpCapability: true);
=> CreateLspEditorFeatureDetector(uiContextService, hasDotNetCoreCSharpCapability: true);

private ILspEditorFeatureDetector CreateLspEditorFeatureDetector(
bool legacyEditorSetting = false,
bool hasLegacyRazorEditorCapability = false,
bool hasDotNetCoreCSharpCapability = true)
{
return CreateLspEditorFeatureDetector(legacyEditorSetting, CreateUIContextService(), hasLegacyRazorEditorCapability, hasDotNetCoreCSharpCapability);
return CreateLspEditorFeatureDetector(CreateUIContextService(), hasDotNetCoreCSharpCapability);
}

private ILspEditorFeatureDetector CreateLspEditorFeatureDetector(
bool legacyEditorSetting,
IUIContextService uiContextService,
bool hasLegacyRazorEditorCapability,
bool hasDotNetCoreCSharpCapability)
{
uiContextService ??= CreateUIContextService();

var featureDetector = new LspEditorFeatureDetector(
CreateVsSettingsManagerService(legacyEditorSetting),
uiContextService,
CreateProjectCapabilityResolver(hasLegacyRazorEditorCapability, hasDotNetCoreCSharpCapability),
JoinableTaskContext,
CreateProjectCapabilityResolver(hasDotNetCoreCSharpCapability),
CreateRazorActivityLog());

AddDisposable(featureDetector);

return featureDetector;
}

private static IVsService<SVsSettingsPersistenceManager, ISettingsManager> CreateVsSettingsManagerService(bool useLegacyEditor)
{
var vsSettingsManagerMock = new StrictMock<ISettingsManager>();
vsSettingsManagerMock
.Setup(x => x.GetValueOrDefault(WellKnownSettingNames.UseLegacyASPNETCoreEditor, It.IsAny<bool>()))
.Returns(useLegacyEditor);

return VsMocks.CreateVsService<SVsSettingsPersistenceManager, ISettingsManager>(vsSettingsManagerMock);
}

private static IUIContextService CreateUIContextService(
bool liveShareHostActive = false,
bool liveShareGuestActive = false,
Expand All @@ -178,13 +148,10 @@ private static IUIContextService CreateUIContextService(
return mock.Object;
}

private static IProjectCapabilityResolver CreateProjectCapabilityResolver(bool hasLegacyRazorEditorCapability, bool hasDotNetCoreCSharpCapability)
private static IProjectCapabilityResolver CreateProjectCapabilityResolver(bool hasDotNetCoreCSharpCapability)
{
var projectCapabilityResolverMock = new StrictMock<IProjectCapabilityResolver>();

projectCapabilityResolverMock
.Setup(x => x.CheckCapability(WellKnownProjectCapabilities.LegacyRazorEditor, It.IsAny<string>()))
.Returns(new CapabilityCheckResult(IsInProject: true, HasCapability: hasLegacyRazorEditorCapability));
projectCapabilityResolverMock
.Setup(x => x.CheckCapability(WellKnownProjectCapabilities.DotNetCoreCSharp, It.IsAny<string>()))
.Returns(new CapabilityCheckResult(IsInProject: true, HasCapability: hasDotNetCoreCSharpCapability));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.Internal.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Settings;
using Microsoft.VisualStudio.Shell;
using Xunit;
using Xunit.Abstractions;
Expand Down Expand Up @@ -67,7 +65,6 @@ public override async Task InitializeAsync()
// Razor extension doesn't launch until a razor file is opened, so wait for it to equalize
await TestServices.Workspace.WaitForProjectSystemAsync(ControlledHangMitigatingCancellationToken);

EnsureLSPEditorEnabled();
await EnsureTextViewRolesAsync(ControlledHangMitigatingCancellationToken);
await EnsureExtensionInstalledAsync(ControlledHangMitigatingCancellationToken);

Expand Down Expand Up @@ -150,15 +147,6 @@ public override async Task DisposeAsync()
await base.DisposeAsync();
}

private static void EnsureLSPEditorEnabled()
{
var settingsManager = (ISettingsManager)ServiceProvider.GlobalProvider.GetService(typeof(SVsSettingsPersistenceManager));
Assumes.Present(settingsManager);

var useLegacyEditor = settingsManager.GetValueOrDefault<bool>(WellKnownSettingNames.UseLegacyASPNETCoreEditor);
Assert.False(useLegacyEditor, "Expected the Legacy Razor Editor to be disabled, but it was enabled");
}

private async Task EnsureTextViewRolesAsync(CancellationToken cancellationToken)
{
var textView = await TestServices.Editor.GetActiveTextViewAsync(cancellationToken);
Expand Down