Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -9,6 +9,7 @@
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Razor.LanguageServer
{
Expand All @@ -17,13 +18,15 @@ internal class DefaultProjectSnapshotManagerAccessor : ProjectSnapshotManagerAcc
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly IEnumerable<ProjectSnapshotChangeTrigger> _changeTriggers;
private readonly FilePathNormalizer _filePathNormalizer;
private readonly IOptionsMonitor<RazorLSPOptions> _optionsMonitor;
private ProjectSnapshotManagerBase _instance;
private bool _disposed;

public DefaultProjectSnapshotManagerAccessor(
ForegroundDispatcher foregroundDispatcher,
IEnumerable<ProjectSnapshotChangeTrigger> changeTriggers,
FilePathNormalizer filePathNormalizer)
FilePathNormalizer filePathNormalizer,
IOptionsMonitor<RazorLSPOptions> optionsMonitor)
{
if (foregroundDispatcher == null)
{
Expand All @@ -40,9 +43,15 @@ public DefaultProjectSnapshotManagerAccessor(
throw new ArgumentNullException(nameof(filePathNormalizer));
}

if (optionsMonitor is null)
{
throw new ArgumentNullException(nameof(optionsMonitor));
}

_foregroundDispatcher = foregroundDispatcher;
_changeTriggers = changeTriggers;
_filePathNormalizer = filePathNormalizer;
_optionsMonitor = optionsMonitor;
}

public override ProjectSnapshotManagerBase Instance
Expand All @@ -54,7 +63,7 @@ public override ProjectSnapshotManagerBase Instance
var services = AdhocServices.Create(
workspaceServices: new[]
{
new RemoteProjectSnapshotProjectEngineFactory(_filePathNormalizer)
new RemoteProjectSnapshotProjectEngineFactory(_filePathNormalizer, _optionsMonitor)
},
razorLanguageServices: Enumerable.Empty<ILanguageService>());
var workspace = new AdhocWorkspace(services);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.IO;
using System.Text;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.CodeAnalysis.Razor.Editor;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
Expand Down Expand Up @@ -38,43 +37,19 @@ public async override Task<RazorLSPOptions> GetLatestOptionsAsync(CancellationTo
{
try
{
var request = new ConfigurationParams()
{
Items = new[]
{
new ConfigurationItem()
{
Section = "razor"
},
new ConfigurationItem()
{
Section = "html"
},
}
};
var request = GenerateConfigParams();

var response = await _server.SendRequestAsync("workspace/configuration", request);
var result = await response.Returning<JObject[]>(cancellationToken);
if (result == null || result.Length < 2 || result[0] == null)

// LSP spec indicates result should be the same length as the number of ConfigurationItems we pass in.
if (result == null || result.Length != request.Items.Count() || result[0] == null)
{
_logger.LogWarning("Client failed to provide the expected configuration.");
return null;
}

var builder = new ConfigurationBuilder();

var configObject = new JObject
{
{ "razor", result[0] },
{ "html", result[1] }
};
var configJsonString = configObject.ToString();
using var configStream = new MemoryStream(Encoding.UTF8.GetBytes(configJsonString));
builder.AddJsonStream(configStream);

var config = builder.Build();

var instance = BuildOptions(config);
var instance = BuildOptions(result);
return instance;
}
catch (Exception ex)
Expand All @@ -84,25 +59,104 @@ public async override Task<RazorLSPOptions> GetLatestOptionsAsync(CancellationTo
}
}

private RazorLSPOptions BuildOptions(IConfiguration config)
private static ConfigurationParams GenerateConfigParams()
{
var instance = RazorLSPOptions.Default;
// NOTE: Do not change the ordering of sections without updating
Comment thread
allisonchou marked this conversation as resolved.
// the code in the BuildOptions method below.
return new ConfigurationParams()
{
Items = new[]
{
new ConfigurationItem()
{
Section = "razor"
},
new ConfigurationItem()
{
Section = "html"
},
new ConfigurationItem()
{
Section = "vs.editor.razor"
},
}
};
}

// Internal for testing
internal RazorLSPOptions BuildOptions(JObject[] result)
{
ExtractVSCodeOptions(result, out var trace, out var enableFormatting, out var autoClosingTags);
ExtractVSOptions(result, out var insertSpaces, out var tabSize);

Enum.TryParse(config["razor:trace"], out Trace trace);
return new RazorLSPOptions(trace, enableFormatting, autoClosingTags, insertSpaces, tabSize);
}

private void ExtractVSCodeOptions(
JObject[] result,
out Trace trace,
out bool enableFormatting,
out bool autoClosingTags)
{
var razor = result[0];
var html = result[1];

var enableFormatting = instance.EnableFormatting;
if (bool.TryParse(config["razor:format:enable"], out var parsedEnableFormatting))
trace = RazorLSPOptions.Default.Trace;
if (razor.TryGetValue("trace", out var parsedTrace))
Comment thread
allisonchou marked this conversation as resolved.
{
enableFormatting = parsedEnableFormatting;
trace = GetObjectOrDefault(parsedTrace, trace);
}

var autoClosingTags = instance.AutoClosingTags;
if (bool.TryParse(config["html:autoClosingTags"], out var parsedAutoClosingTags))
enableFormatting = RazorLSPOptions.Default.EnableFormatting;
if (razor.TryGetValue("format", out var parsedFormat))
{
autoClosingTags = parsedAutoClosingTags;
if (parsedFormat is JObject jObject &&
jObject.TryGetValue("enable", out var parsedEnableFormatting))
{
enableFormatting = GetObjectOrDefault(parsedEnableFormatting, enableFormatting);
}
}

return new RazorLSPOptions(trace, enableFormatting, autoClosingTags);
autoClosingTags = RazorLSPOptions.Default.AutoClosingTags;
if (html.TryGetValue("autoClosingTags", out var parsedAutoClosingTags))
{
autoClosingTags = GetObjectOrDefault(parsedAutoClosingTags, autoClosingTags);
}
}

private void ExtractVSOptions(
JObject[] result,
out bool insertSpaces,
out int tabSize)
{
var vsEditor = result[2];

insertSpaces = RazorLSPOptions.Default.InsertSpaces;
if (vsEditor.TryGetValue(nameof(EditorSettings.IndentWithTabs), out var parsedInsertTabs))
{
insertSpaces = !GetObjectOrDefault(parsedInsertTabs, insertSpaces);
}

tabSize = RazorLSPOptions.Default.TabSize;
if (vsEditor.TryGetValue(nameof(EditorSettings.IndentSize), out var parsedTabSize))
{
tabSize = GetObjectOrDefault(parsedTabSize, tabSize);
}
}

private T GetObjectOrDefault<T>(JToken token, T defaultValue)
{
try
{
// JToken.ToObject could potentially throw here if the user provides malformed options.
// If this occurs, catch the exception and return the default value.
return token.ToObject<T>() ?? defaultValue;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Malformed option: Token {token} cannot be converted to type {typeof(T)}.");
return defaultValue;
Comment thread
allisonchou marked this conversation as resolved.
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,16 @@ private async Task<Dictionary<int, int>> GetCSharpIndentationCoreAsync(Formattin

// Assuming the C# formatter did the right thing, let's extract the indentation offset from
// the line containing trivia and token that has our attached annotations.
ExtractTriviaAnnotations();
ExtractTokenAnnotations();
ExtractTriviaAnnotations(context, formattedRoot, formattedText, desiredIndentationMap);
Comment thread
allisonchou marked this conversation as resolved.
ExtractTokenAnnotations(context, formattedRoot, formattedText, indentationMap, desiredIndentationMap);

return desiredIndentationMap;

void ExtractTriviaAnnotations()
static void ExtractTriviaAnnotations(
FormattingContext context,
SyntaxNode formattedRoot,
SourceText formattedText,
Dictionary<int, int> desiredIndentationMap)
{
var formattedTriviaList = formattedRoot.GetAnnotatedTrivia(MarkerId);
foreach (var trivia in formattedTriviaList)
Expand All @@ -205,7 +209,12 @@ void ExtractTriviaAnnotations()
}
}

void ExtractTokenAnnotations()
static void ExtractTokenAnnotations(
FormattingContext context,
SyntaxNode formattedRoot,
SourceText formattedText,
Dictionary<int, IndentationMapData> indentationMap,
Dictionary<int, int> desiredIndentationMap)
{
var formattedTokenList = formattedRoot.GetAnnotatedTokens(MarkerId);
foreach (var token in formattedTokenList)
Expand Down Expand Up @@ -317,7 +326,7 @@ private SyntaxNode AttachAnnotations(
return root;
}

private int GetIndentationOffsetFromLine(FormattingContext context, TextLine line)
private static int GetIndentationOffsetFromLine(FormattingContext context, TextLine line)
{
var offset = line.GetFirstNonWhitespaceOffset() ?? 0;
if (!context.Options.InsertSpaces)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer
{
internal class RazorLSPOptions : IEquatable<RazorLSPOptions>
{
public RazorLSPOptions(Trace trace, bool enableFormatting, bool autoClosingTags)
public RazorLSPOptions(Trace trace, bool enableFormatting, bool autoClosingTags, bool insertSpaces, int tabSize)
{
Trace = trace;
EnableFormatting = enableFormatting;
AutoClosingTags = autoClosingTags;
TabSize = tabSize;
InsertSpaces = insertSpaces;
}

public static RazorLSPOptions Default =>
new RazorLSPOptions(trace: default, enableFormatting: true, autoClosingTags: true);
new RazorLSPOptions(trace: default, enableFormatting: true, autoClosingTags: true, insertSpaces: true, tabSize: 4);

public Trace Trace { get; }

Expand All @@ -27,6 +29,10 @@ public RazorLSPOptions(Trace trace, bool enableFormatting, bool autoClosingTags)

public bool AutoClosingTags { get; }

public int TabSize { get; }

public bool InsertSpaces { get; }

public static LogLevel GetLogLevelForTrace(Trace trace)
{
return trace switch
Expand All @@ -44,7 +50,9 @@ public bool Equals(RazorLSPOptions other)
other != null &&
Trace == other.Trace &&
EnableFormatting == other.EnableFormatting &&
AutoClosingTags == other.AutoClosingTags;
AutoClosingTags == other.AutoClosingTags &&
InsertSpaces == other.InsertSpaces &&
Comment thread
allisonchou marked this conversation as resolved.
TabSize == other.TabSize;
}

public override bool Equals(object obj)
Expand All @@ -58,6 +66,8 @@ public override int GetHashCode()
hash.Add(Trace);
hash.Add(EnableFormatting);
hash.Add(AutoClosingTags);
hash.Add(InsertSpaces);
hash.Add(TabSize);
return hash;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Razor.LanguageServer
{
Expand All @@ -14,16 +15,23 @@ internal class RemoteProjectSnapshotProjectEngineFactory : DefaultProjectSnapsho
public static readonly IFallbackProjectEngineFactory FallbackProjectEngineFactory = new FallbackProjectEngineFactory();

private readonly FilePathNormalizer _filePathNormalizer;
private readonly IOptionsMonitor<RazorLSPOptions> _optionsMonitor;

public RemoteProjectSnapshotProjectEngineFactory(FilePathNormalizer filePathNormalizer) :
public RemoteProjectSnapshotProjectEngineFactory(FilePathNormalizer filePathNormalizer, IOptionsMonitor<RazorLSPOptions> optionsMonitor) :
Comment thread
NTaylorMullen marked this conversation as resolved.
base(FallbackProjectEngineFactory, ProjectEngineFactories.Factories)
{
if (filePathNormalizer == null)
if (filePathNormalizer is null)
{
throw new ArgumentNullException(nameof(filePathNormalizer));
}

if (optionsMonitor is null)
{
throw new ArgumentNullException(nameof(optionsMonitor));
}

_filePathNormalizer = filePathNormalizer;
_optionsMonitor = optionsMonitor;
}

public override RazorProjectEngine Create(
Expand All @@ -38,7 +46,37 @@ public override RazorProjectEngine Create(
}

var remoteFileSystem = new RemoteRazorProjectFileSystem(defaultFileSystem.Root, _filePathNormalizer);
return base.Create(configuration, remoteFileSystem, configure);
return base.Create(configuration, remoteFileSystem, Configure);

void Configure(RazorProjectEngineBuilder builder)
{
configure(builder);
builder.Features.Add(new RemoteCodeGenerationOptionsFeature(_optionsMonitor));
}
}

private class RemoteCodeGenerationOptionsFeature : RazorEngineFeatureBase, IConfigureRazorCodeGenerationOptionsFeature
{
private readonly IOptionsMonitor<RazorLSPOptions> _optionsMonitor;

public RemoteCodeGenerationOptionsFeature(IOptionsMonitor<RazorLSPOptions> optionsMonitor)
{
if (optionsMonitor is null)
{
throw new ArgumentNullException(nameof(optionsMonitor));
}

_optionsMonitor = optionsMonitor;
}

public int Order { get; set; }

public void Configure(RazorCodeGenerationOptionsBuilder options)
{
// We don't need to explicitly subscribe to options changing because this method will be run on every parse.
options.IndentSize = _optionsMonitor.CurrentValue.TabSize;
options.IndentWithTabs = !_optionsMonitor.CurrentValue.InsertSpaces;
}
}
}
}
Loading