-
Notifications
You must be signed in to change notification settings - Fork 215
Add IntelliSense support for Blazor enhanced navigation and form handling data attributes #12324
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
Open
Copilot
wants to merge
11
commits into
main
Choose a base branch
from
copilot/add-intellisense-support
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+563
−9
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
b64eb0b
Initial plan
Copilot 87f1dc0
Add BlazorDataAttributeCompletionItemProvider with initial tests
Copilot 166159f
Fix BlazorDataAttributeCompletionItemProvider logic and tests
Copilot bbd20bf
Complete BlazorDataAttributeCompletionItemProvider implementation wit…
Copilot 48168f8
Address code review feedback: allow data-enhance-nav on any element a…
Copilot b96489a
Fix VSCode test failures: respect UseVsCodeCompletionCommitCharacters…
Copilot 6f07885
Refactor BlazorDataAttributeCompletionItemProvider: use PooledArrayBu…
Copilot 2751d2b
Create AttributeDescriptionInfo, extract shared completion helpers, a…
Copilot ced7405
Merge remote-tracking branch 'origin/main' into copilot/add-intellise…
Copilot 641b057
Simplify string comparison and move comments inline in CompletionCont…
Copilot e344faa
Simplify string comparison using direct equality operator
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
141 changes: 141 additions & 0 deletions
141
...oft.CodeAnalysis.Razor.Workspaces/Completion/BlazorDataAttributeCompletionItemProvider.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.Collections.Immutable; | ||
using Microsoft.AspNetCore.Razor.Language; | ||
using Microsoft.AspNetCore.Razor.Language.Syntax; | ||
using Microsoft.AspNetCore.Razor.PooledObjects; | ||
using Microsoft.CodeAnalysis.Razor.Tooltip; | ||
using Microsoft.VisualStudio.Editor.Razor; | ||
|
||
namespace Microsoft.CodeAnalysis.Razor.Completion; | ||
|
||
/// <summary> | ||
/// Provides completions for Blazor-specific data-* attributes used for enhanced navigation and form handling. | ||
/// </summary> | ||
internal class BlazorDataAttributeCompletionItemProvider : IRazorCompletionItemProvider | ||
{ | ||
private static readonly ImmutableArray<RazorCommitCharacter> AttributeCommitCharacters = RazorCommitCharacter.CreateArray(["="]); | ||
private static readonly ImmutableArray<RazorCommitCharacter> AttributeSnippetCommitCharacters = RazorCommitCharacter.CreateArray(["="], insert: false); | ||
|
||
// Define the Blazor-specific data attributes | ||
private static readonly ImmutableArray<(string Name, string Description)> s_blazorDataAttributes = | ||
[ | ||
("data-enhance", "Opts in to enhanced form handling for a form element."), | ||
("data-enhance-nav", "Disables enhanced navigation for a link or DOM subtree."), | ||
("data-permanent", "Marks an element to be preserved when handling enhanced navigation or form requests.") | ||
]; | ||
|
||
public ImmutableArray<RazorCompletionItem> GetCompletionItems(RazorCompletionContext context) | ||
{ | ||
// Only provide completions for component files | ||
if (!context.SyntaxTree.Options.FileKind.IsComponent()) | ||
{ | ||
return []; | ||
} | ||
|
||
var owner = CompletionContextHelper.AdjustSyntaxNodeForCompletion(context.Owner); | ||
if (owner is null) | ||
{ | ||
return []; | ||
} | ||
|
||
// Check if we're in an attribute context | ||
if (!HtmlFacts.TryGetAttributeInfo( | ||
owner, | ||
out var containingTagNameToken, | ||
out var prefixLocation, | ||
out var selectedAttributeName, | ||
out var selectedAttributeNameLocation, | ||
out var attributes)) | ||
{ | ||
return []; | ||
} | ||
|
||
// Only provide completions when we're completing an attribute name | ||
if (!CompletionContextHelper.IsAttributeNameCompletionContext( | ||
selectedAttributeName, | ||
selectedAttributeNameLocation, | ||
prefixLocation, | ||
context.AbsoluteIndex)) | ||
{ | ||
return []; | ||
} | ||
|
||
// Don't provide completions if the user is typing a directive attribute (starts with @) | ||
if (selectedAttributeName?.StartsWith('@') == true) | ||
{ | ||
return []; | ||
} | ||
|
||
var containingTagName = containingTagNameToken.Content; | ||
|
||
using var completionItems = new PooledArrayBuilder<RazorCompletionItem>(); | ||
|
||
foreach (var (attributeName, description) in s_blazorDataAttributes) | ||
{ | ||
// Only show data-enhance for form elements | ||
if (attributeName == "data-enhance" && | ||
!string.Equals(containingTagName, "form", System.StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
continue; | ||
} | ||
|
||
// Check if the attribute already exists on the element | ||
davidwengier marked this conversation as resolved.
Show resolved
Hide resolved
|
||
var alreadyExists = false; | ||
foreach (var attribute in attributes) | ||
{ | ||
var existingAttributeName = attribute switch | ||
{ | ||
MarkupAttributeBlockSyntax attributeBlock => attributeBlock.Name.GetContent(), | ||
MarkupMinimizedAttributeBlockSyntax minimizedAttributeBlock => minimizedAttributeBlock.Name.GetContent(), | ||
_ => null | ||
}; | ||
|
||
if (existingAttributeName == attributeName) | ||
{ | ||
alreadyExists = true; | ||
break; | ||
} | ||
} | ||
|
||
if (alreadyExists && selectedAttributeName != attributeName) | ||
{ | ||
// Attribute already exists and is not the one currently being edited | ||
continue; | ||
} | ||
|
||
var insertText = attributeName; | ||
var isSnippet = false; | ||
|
||
// Add snippet text for attribute value if snippets are supported | ||
if (context.Options.SnippetsSupported) | ||
{ | ||
var snippetSuffix = context.Options.AutoInsertAttributeQuotes ? "=\"$0\"" : "=$0"; | ||
insertText = attributeName + snippetSuffix; | ||
isSnippet = true; | ||
} | ||
|
||
// VSCode doesn't use commit characters for attribute completions | ||
var commitCharacters = context.Options.UseVsCodeCompletionCommitCharacters | ||
? ImmutableArray<RazorCommitCharacter>.Empty | ||
: (isSnippet ? AttributeSnippetCommitCharacters : AttributeCommitCharacters); | ||
|
||
var descriptionInfo = new AttributeDescriptionInfo( | ||
Name: attributeName, | ||
Documentation: description); | ||
|
||
var completionItem = RazorCompletionItem.CreateAttribute( | ||
displayText: attributeName, | ||
insertText: insertText, | ||
descriptionInfo: descriptionInfo, | ||
commitCharacters: commitCharacters, | ||
isSnippet: isSnippet); | ||
|
||
completionItems.Add(completionItem); | ||
} | ||
|
||
return completionItems.ToImmutable(); | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Completion/CompletionContextHelper.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using Microsoft.AspNetCore.Razor.Language.Syntax; | ||
using RazorSyntaxNode = Microsoft.AspNetCore.Razor.Language.Syntax.SyntaxNode; | ||
|
||
namespace Microsoft.CodeAnalysis.Razor.Completion; | ||
|
||
internal static class CompletionContextHelper | ||
{ | ||
/// <summary> | ||
/// Adjusts the syntax node owner to find the nearest start or end tag for completion purposes. | ||
/// </summary> | ||
/// <param name="owner">The original syntax node owner.</param> | ||
/// <returns>The adjusted owner node.</returns> | ||
public static RazorSyntaxNode? AdjustSyntaxNodeForCompletion(RazorSyntaxNode? owner) | ||
=> owner switch | ||
{ | ||
// This provider is trying to find the nearest Start or End tag. Most of the time, that's a level up, but if the index the user is typing at | ||
// is a token of a start or end tag directly, we already have the node we want. | ||
MarkupStartTagSyntax or MarkupEndTagSyntax or MarkupTagHelperStartTagSyntax or MarkupTagHelperEndTagSyntax or MarkupTagHelperAttributeSyntax => owner, | ||
// Invoking completion in an empty file will give us RazorDocumentSyntax which always has null parent | ||
RazorDocumentSyntax => owner, | ||
// Either the parent is a context we can handle, or it's not and we shouldn't show completions. | ||
_ => owner?.Parent | ||
}; | ||
|
||
/// <summary> | ||
/// Determines if the absolute index is within an attribute name completion context. | ||
/// </summary> | ||
/// <param name="selectedAttributeName">The currently selected or partially typed attribute name.</param> | ||
/// <param name="selectedAttributeNameLocation">The location of the selected attribute name.</param> | ||
/// <param name="prefixLocation">The location of the attribute prefix (e.g., "@" for directive attributes).</param> | ||
/// <param name="absoluteIndex">The cursor position in the document.</param> | ||
/// <returns>True if the context is appropriate for attribute name completion, false otherwise.</returns> | ||
/// <remarks> | ||
/// To align with HTML completion behavior we only want to provide completion items if we're trying to resolve completion at the | ||
/// beginning of an HTML attribute name or at the end of possible partially written attribute. We do extra checks on prefix locations here in order to rule out malformed cases when the Razor | ||
/// compiler incorrectly parses multi-line attributes while in the middle of typing out an element. For instance: | ||
/// <SurveyPrompt | | ||
/// @code { ... } | ||
/// Will be interpreted as having an `@code` attribute name due to multi-line attributes being a thing. Ultimately this is mostly a | ||
/// heuristic that we have to apply in order to workaround limitations of the Razor compiler. | ||
/// </remarks> | ||
public static bool IsAttributeNameCompletionContext( | ||
string? selectedAttributeName, | ||
Microsoft.CodeAnalysis.Text.TextSpan? selectedAttributeNameLocation, | ||
Microsoft.CodeAnalysis.Text.TextSpan? prefixLocation, | ||
int absoluteIndex) | ||
{ | ||
return selectedAttributeName is null || | ||
selectedAttributeNameLocation?.IntersectsWith(absoluteIndex) == true || | ||
(prefixLocation?.IntersectsWith(absoluteIndex) ?? false); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,4 +12,5 @@ internal enum RazorCompletionItemKind | |
MarkupTransition, | ||
TagHelperElement, | ||
TagHelperAttribute, | ||
Attribute, | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Tooltip/AttributeDescriptionInfo.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
namespace Microsoft.CodeAnalysis.Razor.Tooltip; | ||
|
||
/// <summary> | ||
/// Provides description information for HTML attributes that are not bound to tag helpers. | ||
/// </summary> | ||
/// <param name="Name">The name of the attribute.</param> | ||
/// <param name="Documentation">The documentation text describing the attribute.</param> | ||
internal sealed record AttributeDescriptionInfo(string Name, string Documentation); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.