-
Notifications
You must be signed in to change notification settings - Fork 199
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
Code action to offer to wrap Html attributes #11422
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT license. See License.txt in the project root for license information. | ||
|
||
using System.Text.Json.Serialization; | ||
|
||
namespace Microsoft.CodeAnalysis.Razor.CodeActions.Models; | ||
|
||
internal sealed class WrapAttributesCodeActionParams | ||
{ | ||
[JsonPropertyName("indentSize")] | ||
public int IndentSize { get; init; } | ||
|
||
[JsonPropertyName("newLinePositions")] | ||
public required int[] NewLinePositions { get; init; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the MIT license. See License.txt in the project root for license information. | ||
|
||
using System.Collections.Immutable; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Razor.Language; | ||
using Microsoft.AspNetCore.Razor.Language.Syntax; | ||
using Microsoft.AspNetCore.Razor.PooledObjects; | ||
using Microsoft.AspNetCore.Razor.Threading; | ||
using Microsoft.CodeAnalysis.Razor.CodeActions.Models; | ||
using Microsoft.CodeAnalysis.Razor.CodeActions.Razor; | ||
using Microsoft.CodeAnalysis.Razor.Protocol; | ||
using Microsoft.CodeAnalysis.Text; | ||
|
||
namespace Microsoft.CodeAnalysis.Razor.CodeActions; | ||
|
||
internal class WrapAttributesCodeActionProvider : IRazorCodeActionProvider | ||
{ | ||
public Task<ImmutableArray<RazorVSInternalCodeAction>> ProvideAsync(RazorCodeActionContext context, CancellationToken cancellationToken) | ||
{ | ||
if (context.HasSelection) | ||
{ | ||
return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>(); | ||
} | ||
|
||
var syntaxTree = context.CodeDocument.GetSyntaxTree(); | ||
if (syntaxTree?.Root is null) | ||
{ | ||
return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>(); | ||
} | ||
|
||
var owner = syntaxTree.Root.FindNode(TextSpan.FromBounds(context.StartAbsoluteIndex, context.EndAbsoluteIndex)); | ||
if (owner is null) | ||
{ | ||
return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>(); | ||
} | ||
|
||
var attributes = FindAttributes(owner); | ||
if (attributes.Count == 0) | ||
{ | ||
return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>(); | ||
} | ||
|
||
var first = true; | ||
var firstAttributeLine = 0; | ||
var indentSize = 0; | ||
var sourceText = context.SourceText; | ||
|
||
using var newLinePositions = new PooledArrayBuilder<int>(attributes.Count); | ||
foreach (var attribute in attributes) | ||
{ | ||
var linePositionSpan = attribute.GetLinePositionSpan(context.CodeDocument.Source); | ||
|
||
if (first) | ||
{ | ||
firstAttributeLine = linePositionSpan.Start.Line; | ||
sourceText.TryGetFirstNonWhitespaceOffset(attribute.Span, out var indentSizeOffset); | ||
indentSize = linePositionSpan.Start.Character + indentSizeOffset; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
first = false; | ||
} | ||
else | ||
{ | ||
if (linePositionSpan.Start.Line != firstAttributeLine) | ||
{ | ||
return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>(); | ||
} | ||
|
||
if (!sourceText.TryGetFirstNonWhitespaceOffset(attribute.Span, out var startOffset)) | ||
{ | ||
continue; | ||
} | ||
|
||
newLinePositions.Add(attribute.SpanStart + startOffset); | ||
} | ||
} | ||
|
||
if (newLinePositions.Count == 0) | ||
{ | ||
return SpecializedTasks.EmptyImmutableArray<RazorVSInternalCodeAction>(); | ||
} | ||
|
||
var data = new WrapAttributesCodeActionParams | ||
{ | ||
IndentSize = indentSize, | ||
NewLinePositions = newLinePositions.ToArray() | ||
}; | ||
|
||
var resolutionParams = new RazorCodeActionResolutionParams() | ||
{ | ||
TextDocument = context.Request.TextDocument, | ||
Action = LanguageServerConstants.CodeActions.WrapAttributes, | ||
Language = RazorLanguageKind.Razor, | ||
DelegatedDocumentUri = context.DelegatedDocumentUri, | ||
Data = data | ||
}; | ||
|
||
var action = RazorCodeActionFactory.CreateWrapAttributes(resolutionParams); | ||
|
||
return Task.FromResult<ImmutableArray<RazorVSInternalCodeAction>>([action]); | ||
} | ||
|
||
private AspNetCore.Razor.Language.Syntax.SyntaxList<RazorSyntaxNode> FindAttributes(AspNetCore.Razor.Language.Syntax.SyntaxNode owner) | ||
{ | ||
foreach (var node in owner.AncestorsAndSelf()) | ||
davidwengier marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
if (node is MarkupStartTagSyntax startTag) | ||
{ | ||
return startTag.Attributes; | ||
} | ||
else if (node is MarkupTagHelperStartTagSyntax tagHelperElement) | ||
{ | ||
return tagHelperElement.Attributes; | ||
} | ||
else if (node is MarkupElementSyntax or MarkupTagHelperElementSyntax) | ||
{ | ||
// If we get as high as the element, we're done looking | ||
break; | ||
} | ||
} | ||
|
||
return []; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// 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.Text.Json; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.AspNetCore.Razor.PooledObjects; | ||
using Microsoft.CodeAnalysis.Razor.CodeActions.Models; | ||
using Microsoft.CodeAnalysis.Razor.Formatting; | ||
using Microsoft.CodeAnalysis.Razor.ProjectSystem; | ||
using Microsoft.CodeAnalysis.Razor.Protocol; | ||
using Microsoft.CodeAnalysis.Text; | ||
using Microsoft.VisualStudio.LanguageServer.Protocol; | ||
|
||
namespace Microsoft.CodeAnalysis.Razor.CodeActions; | ||
|
||
internal class WrapAttributesCodeActionResolver : IRazorCodeActionResolver | ||
{ | ||
public string Action => LanguageServerConstants.CodeActions.WrapAttributes; | ||
|
||
public async Task<WorkspaceEdit?> ResolveAsync(DocumentContext documentContext, JsonElement data, RazorFormattingOptions options, CancellationToken cancellationToken) | ||
{ | ||
var actionParams = data.Deserialize<WrapAttributesCodeActionParams>(); | ||
if (actionParams is null) | ||
{ | ||
return null; | ||
} | ||
|
||
var indentationString = FormattingUtilities.GetIndentationString(actionParams.IndentSize, options.InsertSpaces, options.TabSize); | ||
var sourceText = await documentContext.GetSourceTextAsync(cancellationToken).ConfigureAwait(false); | ||
using var edits = new PooledArrayBuilder<TextEdit>(); | ||
|
||
foreach (var position in actionParams.NewLinePositions) | ||
{ | ||
var start = sourceText.GetLinePosition(FindPreviousNonWhitespacePosition(sourceText, position) + 1); | ||
var end = sourceText.GetLinePosition(position); | ||
edits.Add(VsLspFactory.CreateTextEdit(start, end, Environment.NewLine + indentationString)); | ||
} | ||
|
||
var tde = new TextDocumentEdit | ||
{ | ||
TextDocument = new OptionalVersionedTextDocumentIdentifier() { Uri = documentContext.Uri }, | ||
Edits = edits.ToArray() | ||
}; | ||
|
||
return new WorkspaceEdit | ||
{ | ||
DocumentChanges = new SumType<TextDocumentEdit[], SumType<TextDocumentEdit, CreateFile, RenameFile, DeleteFile>[]>([tde]) | ||
}; | ||
} | ||
|
||
private int FindPreviousNonWhitespacePosition(SourceText sourceText, int position) | ||
{ | ||
for (var i = position - 1; i >= 0; i--) | ||
{ | ||
if (!char.IsWhiteSpace(sourceText[i])) | ||
{ | ||
return i; | ||
} | ||
} | ||
|
||
return 0; | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there cases when attribute span starts with whitespace?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the Razor syntax tree continues to be annoying, so (almost?) all of the attributes start with whitespace. The syntax nodes look a bit like
[<][foo][ class="asdf"][/>]
.