Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add formatting option to force open brace onto the next line after a @code or @functions block #10018

Merged
merged 5 commits into from
Mar 5, 2024
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 @@ -95,22 +95,30 @@ internal RazorLSPOptions BuildOptions(JObject[] result)
}
else
{
ExtractVSCodeOptions(result, out var enableFormatting, out var autoClosingTags, out var commitElementsWithSpace);
return new RazorLSPOptions(enableFormatting, autoClosingTags, commitElementsWithSpace, ClientSettings.Default);
ExtractVSCodeOptions(result, out var enableFormatting, out var autoClosingTags, out var commitElementsWithSpace, out var codeBlockBraceOnNextLine);
return RazorLSPOptions.Default with
{
EnableFormatting = enableFormatting,
AutoClosingTags = autoClosingTags,
CommitElementsWithSpace = commitElementsWithSpace,
CodeBlockBraceOnNextLine = codeBlockBraceOnNextLine
};
}
}

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

enableFormatting = RazorLSPOptions.Default.EnableFormatting;
autoClosingTags = RazorLSPOptions.Default.AutoClosingTags;
codeBlockBraceOnNextLine = RazorLSPOptions.Default.CodeBlockBraceOnNextLine;
// Deliberately not using the "default" here because we want a different default for VS Code, as
// this matches VS Code's html servers commit behaviour
commitElementsWithSpace = false;
Expand All @@ -119,10 +127,17 @@ private void ExtractVSCodeOptions(
{
if (razor.TryGetValue("format", out var parsedFormat))
{
if (parsedFormat is JObject jObject &&
jObject.TryGetValue("enable", out var parsedEnableFormatting))
if (parsedFormat is JObject jObject)
{
enableFormatting = GetObjectOrDefault(parsedEnableFormatting, enableFormatting);
if (jObject.TryGetValue("enable", out var parsedEnableFormatting))
{
enableFormatting = GetObjectOrDefault(parsedEnableFormatting, enableFormatting);
}

if (jObject.TryGetValue("codeBlockBraceOnNextLine", out var parsedCodeBlockBraceOnNextLine))
{
codeBlockBraceOnNextLine = GetObjectOrDefault(parsedCodeBlockBraceOnNextLine, codeBlockBraceOnNextLine);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Components;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Syntax;
using Microsoft.AspNetCore.Razor.LanguageServer.Extensions;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.Workspaces.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.VisualStudio.LanguageServer.Protocol;

namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting;

internal class RazorFormattingPass : FormattingPassBase
internal class RazorFormattingPass(
IRazorDocumentMappingService documentMappingService,
IClientConnection clientConnection,
IOptionsMonitor<RazorLSPOptions> optionsMonitor,
IRazorLoggerFactory loggerFactory)
: FormattingPassBase(documentMappingService, clientConnection)
{
private readonly ILogger _logger;

public RazorFormattingPass(
IRazorDocumentMappingService documentMappingService,
IClientConnection clientConnection,
IRazorLoggerFactory loggerFactory)
: base(documentMappingService, clientConnection)
{
if (loggerFactory is null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}

_logger = loggerFactory.CreateLogger<RazorFormattingPass>();
}
private readonly ILogger _logger = loggerFactory.CreateLogger<RazorFormattingPass>();
private readonly IOptionsMonitor<RazorLSPOptions> _optionsMonitor = optionsMonitor;

// Run after the C# formatter pass.
public override int Order => DefaultOrder - 4;
Expand Down Expand Up @@ -76,7 +69,7 @@ public async override Task<FormattingResult> ExecuteAsync(FormattingContext cont
return new FormattingResult(finalEdits);
}

private static IEnumerable<TextEdit> FormatRazor(FormattingContext context, RazorSyntaxTree syntaxTree)
private IEnumerable<TextEdit> FormatRazor(FormattingContext context, RazorSyntaxTree syntaxTree)
{
var edits = new List<TextEdit>();
var source = syntaxTree.Source;
Expand All @@ -93,7 +86,7 @@ private static IEnumerable<TextEdit> FormatRazor(FormattingContext context, Razo
return edits;
}

private static void TryFormatBlocks(FormattingContext context, List<TextEdit> edits, RazorSourceDocument source, SyntaxNode node)
private void TryFormatBlocks(FormattingContext context, List<TextEdit> edits, RazorSourceDocument source, SyntaxNode node)
{
// We only want to run one of these
_ = TryFormatFunctionsBlock(context, edits, source, node) ||
Expand Down Expand Up @@ -122,8 +115,8 @@ directiveCode.Children is [RazorDirectiveSyntax directive] &&
if (TryGetWhitespace(children, out var whitespaceBeforeSectionName, out var whitespaceAfterSectionName))
{
// For whitespace we normalize it differently depending on if its multi-line or not
FormatWhitespaceBetweenDirectiveAndBrace(whitespaceBeforeSectionName, directive, edits, source, context);
FormatWhitespaceBetweenDirectiveAndBrace(whitespaceAfterSectionName, directive, edits, source, context);
FormatWhitespaceBetweenDirectiveAndBrace(whitespaceBeforeSectionName, directive, edits, source, context, forceNewLine: false);
FormatWhitespaceBetweenDirectiveAndBrace(whitespaceAfterSectionName, directive, edits, source, context, forceNewLine: false);

return true;
}
Expand Down Expand Up @@ -178,8 +171,7 @@ private static bool TryFormatFunctionsBlock(FormattingContext context, IList<Tex
// }
if (node is CSharpCodeBlockSyntax { Children: [RazorDirectiveSyntax { Body: RazorDirectiveBodySyntax body } directive] })
{
var keywordContent = body.Keyword.GetContent();
if (keywordContent != "functions" && keywordContent != "code")
if (!IsCodeOrFunctionsBlock(body.Keyword))
{
return false;
}
Expand Down Expand Up @@ -265,7 +257,7 @@ private static bool TryFormatHtmlInCSharp(FormattingContext context, IList<TextE
return false;
}

private static void TryFormatCSharpBlockStructure(FormattingContext context, List<TextEdit> edits, RazorSourceDocument source, SyntaxNode node)
private void TryFormatCSharpBlockStructure(FormattingContext context, List<TextEdit> edits, RazorSourceDocument source, SyntaxNode node)
{
// We're looking for a code block like this:
//
Expand All @@ -284,11 +276,16 @@ private static void TryFormatCSharpBlockStructure(FormattingContext context, Lis
!directive.ContainsDiagnostics &&
directive.DirectiveDescriptor?.Kind == DirectiveKind.CodeBlock)
{
// If we're formatting a @code or @functions directive, the user might have indicated they always want a newline
var forceNewLine = _optionsMonitor.CurrentValue.CodeBlockBraceOnNextLine &&
directive.Body is RazorDirectiveBodySyntax { Keyword: { } keyword } &&
IsCodeOrFunctionsBlock(keyword);

var children = code.Children;
if (TryGetLeadingWhitespace(children, out var whitespace))
{
// For whitespace we normalize it differently depending on if its multi-line or not
FormatWhitespaceBetweenDirectiveAndBrace(whitespace, directive, edits, source, context);
FormatWhitespaceBetweenDirectiveAndBrace(whitespace, directive, edits, source, context, forceNewLine);
}
else if (children.TryGetOpenBraceToken(out var brace))
{
Expand All @@ -297,7 +294,9 @@ private static void TryFormatCSharpBlockStructure(FormattingContext context, Lis
var edit = new TextEdit
{
Range = new Range { Start = start, End = start },
NewText = " "
NewText = forceNewLine
? context.NewLineString + FormattingUtilities.GetIndentationString(directive.GetLinePositionSpan(source).Start.Character, context.Options.InsertSpaces, context.Options.TabSize)
: " "
};
edits.Add(edit);
}
Expand Down Expand Up @@ -355,9 +354,9 @@ static bool IsSingleLineDirective(SyntaxNode node, [NotNullWhen(true)] out Synta
}
}

private static void FormatWhitespaceBetweenDirectiveAndBrace(SyntaxNode node, RazorDirectiveSyntax directive, List<TextEdit> edits, RazorSourceDocument source, FormattingContext context)
private static void FormatWhitespaceBetweenDirectiveAndBrace(SyntaxNode node, RazorDirectiveSyntax directive, List<TextEdit> edits, RazorSourceDocument source, FormattingContext context, bool forceNewLine)
{
if (node.ContainsOnlyWhitespace(includingNewLines: false))
if (node.ContainsOnlyWhitespace(includingNewLines: false) && !forceNewLine)
{
ShrinkToSingleSpace(node, edits, source);
}
Expand Down Expand Up @@ -393,6 +392,7 @@ private static bool FormatBlock(FormattingContext context, RazorSourceDocument s

var openBraceRange = openBraceNode.GetRangeWithoutWhitespace(source);
var codeRange = codeNode.GetRangeWithoutWhitespace(source);

if (openBraceRange is not null &&
codeRange is not null &&
openBraceRange.End.Line == codeRange.Start.Line &&
Expand Down Expand Up @@ -536,4 +536,11 @@ static int GetTrailingWhitespaceLength(SyntaxNode node, FormattingContext contex
}
}
}

private static bool IsCodeOrFunctionsBlock(RazorSyntaxNode keyword)
{
var keywordContent = keyword.GetContent();
return keywordContent == FunctionsDirective.Directive.Directive ||
keywordContent == ComponentCodeDirective.Directive.Directive;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,9 @@ internal record RazorLSPOptions(
bool FormatOnType,
bool AutoInsertAttributeQuotes,
bool ColorBackground,
bool CodeBlockBraceOnNextLine,
bool CommitElementsWithSpace)
{
public RazorLSPOptions(bool enableFormatting, bool autoClosingTags, bool commitElementsWithSpace, ClientSettings settings)
: this(enableFormatting,
autoClosingTags,
!settings.ClientSpaceSettings.IndentWithTabs,
settings.ClientSpaceSettings.IndentSize,
settings.ClientCompletionSettings.AutoShowCompletion,
settings.ClientCompletionSettings.AutoListParams,
settings.AdvancedSettings.FormatOnType,
settings.AdvancedSettings.AutoInsertAttributeQuotes,
settings.AdvancedSettings.ColorBackground,
commitElementsWithSpace)
{
}

public readonly static RazorLSPOptions Default = new(EnableFormatting: true,
AutoClosingTags: true,
AutoListParams: true,
Expand All @@ -40,15 +27,23 @@ public RazorLSPOptions(bool enableFormatting, bool autoClosingTags, bool commitE
FormatOnType: true,
AutoInsertAttributeQuotes: true,
ColorBackground: false,
CodeBlockBraceOnNextLine: false,
CommitElementsWithSpace: true);

/// <summary>
/// Initializes the LSP options with the settings from the passed in client settings, and default values for anything
/// not defined in client settings.
/// </summary>
internal static RazorLSPOptions From(ClientSettings clientSettings)
internal static RazorLSPOptions From(ClientSettings settings)
davidwengier marked this conversation as resolved.
Show resolved Hide resolved
=> new(Default.EnableFormatting,
clientSettings.AdvancedSettings.AutoClosingTags,
clientSettings.AdvancedSettings.CommitElementsWithSpace,
clientSettings);
settings.AdvancedSettings.AutoClosingTags,
!settings.ClientSpaceSettings.IndentWithTabs,
settings.ClientSpaceSettings.IndentSize,
settings.ClientCompletionSettings.AutoShowCompletion,
settings.ClientCompletionSettings.AutoListParams,
settings.AdvancedSettings.FormatOnType,
settings.AdvancedSettings.AutoInsertAttributeQuotes,
settings.AdvancedSettings.ColorBackground,
settings.AdvancedSettings.CodeBlockBraceOnNextLine,
settings.AdvancedSettings.CommitElementsWithSpace);
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ internal sealed record ClientSpaceSettings(bool IndentWithTabs, int IndentSize)
public int IndentSize { get; } = IndentSize >= 0 ? IndentSize : throw new ArgumentOutOfRangeException(nameof(IndentSize));
}

internal sealed record ClientAdvancedSettings(bool FormatOnType, bool AutoClosingTags, bool AutoInsertAttributeQuotes, bool ColorBackground, bool CommitElementsWithSpace, SnippetSetting SnippetSetting, LogLevel LogLevel)
internal sealed record ClientAdvancedSettings(bool FormatOnType, bool AutoClosingTags, bool AutoInsertAttributeQuotes, bool ColorBackground, bool CodeBlockBraceOnNextLine, bool CommitElementsWithSpace, SnippetSetting SnippetSetting, LogLevel LogLevel)
{
public static readonly ClientAdvancedSettings Default = new(FormatOnType: true, AutoClosingTags: true, AutoInsertAttributeQuotes: true, ColorBackground: false, CommitElementsWithSpace: true, SnippetSetting.All, LogLevel.Warning);
public static readonly ClientAdvancedSettings Default = new(FormatOnType: true, AutoClosingTags: true, AutoInsertAttributeQuotes: true, ColorBackground: false, CodeBlockBraceOnNextLine: false, CommitElementsWithSpace: true, SnippetSetting.All, LogLevel.Warning);
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ public bool ColorBackground
set => SetBool(SettingsNames.ColorBackground.LegacyName, value);
}

public bool CodeBlockBraceOnNextLine
{
get => GetBool(SettingsNames.CodeBlockBraceOnNextLine.LegacyName, defaultValue: false);
set => SetBool(SettingsNames.CodeBlockBraceOnNextLine.LegacyName, value);
}

public bool CommitElementsWithSpace
{
get => GetBool(SettingsNames.CommitElementsWithSpace.LegacyName, defaultValue: true);
Expand Down Expand Up @@ -101,7 +107,7 @@ public async Task OnChangedAsync(Action<ClientAdvancedSettings> changed)

private EventHandler<ClientAdvancedSettingsChangedEventArgs>? _changed;

public ClientAdvancedSettings GetAdvancedSettings() => new(FormatOnType, AutoClosingTags, AutoInsertAttributeQuotes, ColorBackground, CommitElementsWithSpace, Snippets, LogLevel);
public ClientAdvancedSettings GetAdvancedSettings() => new(FormatOnType, AutoClosingTags, AutoInsertAttributeQuotes, ColorBackground, CodeBlockBraceOnNextLine, CommitElementsWithSpace, Snippets, LogLevel);

public bool GetBool(string name, bool defaultValue)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public record Setting(string LegacyName, string UnifiedName);
public static readonly Setting AutoClosingTags = new("AutoClosingTags", UnifiedCollection + ".autoClosingTags");
public static readonly Setting AutoInsertAttributeQuotes = new("AutoInsertAttributeQuotes", UnifiedCollection + ".autoInsertAttributeQuotes");
public static readonly Setting ColorBackground = new("ColorBackground", UnifiedCollection + ".colorBackground");
public static readonly Setting CodeBlockBraceOnNextLine = new("CodeBlockBraceOnNextLine", UnifiedCollection + ".codeBlockBraceOnNextLine");
public static readonly Setting CommitElementsWithSpace = new("CommitElementsWithSpace", UnifiedCollection + ".commitCharactersWithSpace");
public static readonly Setting Snippets = new("Snippets", UnifiedCollection + ".snippets");
public static readonly Setting LogLevel = new("LogLevel", UnifiedCollection + ".logLevel");
Expand All @@ -24,6 +25,7 @@ public record Setting(string LegacyName, string UnifiedName);
AutoClosingTags,
AutoInsertAttributeQuotes,
ColorBackground,
CodeBlockBraceOnNextLine,
CommitElementsWithSpace,
Snippets,
LogLevel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internal class AdvancedOptionPage : DialogPage
private bool? _autoClosingTags;
private bool? _autoInsertAttributeQuotes;
private bool? _colorBackground;
private bool? _codeBlockBraceOnNextLine;
private bool? _commitElementsWithSpace;
private SnippetSetting? _snippets;
private LogLevel? _logLevel;
Expand Down Expand Up @@ -81,6 +82,15 @@ public bool ColorBackground
set => _colorBackground = value;
}

[LocCategory(nameof(VSPackage.Formatting))]
[LocDescription(nameof(VSPackage.Setting_CodeBlockBraceOnNextLineDescription))]
[LocDisplayName(nameof(VSPackage.Setting_CodeBlockBraceOnNextLineDisplayName))]
public bool CodeBlockBraceOnNextLine
{
get => _codeBlockBraceOnNextLine ?? _optionsStorage.Value.CodeBlockBraceOnNextLine;
set => _codeBlockBraceOnNextLine = value;
}

[LocCategory(nameof(VSPackage.Completion))]
[LocDescription(nameof(VSPackage.Setting_SnippetsDescription))]
[LocDisplayName(nameof(VSPackage.Setting_SnippetsDisplayName))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@
}
}
},
"textEditor.razor.advanced.codeBlockBraceOnNextLine": {
"type": "boolean",
"default": false,
"title": "@Setting_CodeBlockBraceOnNextLinedDisplayName;{13b72f58-279e-49e0-a56d-296be02f0805}",
"description": "@Setting_CodeBlockBraceOnNextLinedDescription;{13b72f58-279e-49e0-a56d-296be02f0805}",
"migration": {
"pass": {
"input": {
"store": "VsUserSettingsRegistry",
"path": "Razor\\CodeBlockBraceOnNextLined"
}
}
}
},
"textEditor.razor.advanced.commitElementsWithSpace": {
"type": "boolean",
"default": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@
<data name="Setting_AutoInsertAttributeQuotesDisplayName" xml:space="preserve">
<value>Auto Insert Attribute Quotes</value>
</data>
<data name="Setting_CodeBlockBraceOnNextLineDescription" xml:space="preserve">
<value>Forces the open brace after an @code or @functions directive to be on the following line</value>
</data>
<data name="Setting_CodeBlockBraceOnNextLineDisplayName" xml:space="preserve">
<value>Code/Functions block open brace on next line</value>
</data>
<data name="Setting_ColorBackgroundDescription" xml:space="preserve">
<value>If true, gives C# code in Razor files a background color</value>
</data>
Expand Down
Loading