-
Notifications
You must be signed in to change notification settings - Fork 229
Add the RazorCSharp keywords to completion #12522
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
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,9 @@ | ||
| // 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.Completion; | ||
|
|
||
| internal sealed class CSharpRazorKeywordCompletionDescription(string description) : CompletionDescription | ||
| { | ||
| public override string Description { get; } = string.Format(SR.KeywordDescription, description); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Collections.Generic; | ||
| using System.Collections.Immutable; | ||
| using System.Runtime.InteropServices; | ||
| using Microsoft.AspNetCore.Razor.Language; | ||
| using Microsoft.AspNetCore.Razor.Language.Syntax; | ||
|
|
||
| namespace Microsoft.CodeAnalysis.Razor.Completion; | ||
|
|
||
| internal class CSharpRazorKeywordCompletionItemProvider : IRazorCompletionItemProvider | ||
| { | ||
| internal static readonly ImmutableArray<RazorCommitCharacter> KeywordCommitCharacters = RazorCommitCharacter.CreateArray([" "]); | ||
|
|
||
| // internal for testing | ||
| internal static readonly ImmutableArray<string> CSharpRazorKeywords = | ||
| [ | ||
| "do", "for", "foreach", "if", "lock", "switch", "try", "while" | ||
| ]; | ||
|
|
||
| // Internal for testing | ||
| internal static readonly ImmutableArray<RazorCompletionItem> CSharpRazorKeywordCompletionItems = GetCSharpRazorKeywordCompletionItems(); | ||
|
|
||
| public ImmutableArray<RazorCompletionItem> GetCompletionItems(RazorCompletionContext context) | ||
| { | ||
| return ShouldProvideCompletions(context) | ||
| ? CSharpRazorKeywordCompletionItems | ||
| : []; | ||
| } | ||
|
|
||
| // Internal for testing | ||
| internal static bool ShouldProvideCompletions(RazorCompletionContext context) | ||
| { | ||
| var owner = context.Owner; | ||
| if (owner is null) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| // Do not provide IntelliSense for explicit expressions. Explicit expressions will usually look like: | ||
| // @(DateTime.Now) | ||
| var implicitExpression = owner.FirstAncestorOrSelf<CSharpImplicitExpressionSyntax>(); | ||
| if (implicitExpression is null) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| if (implicitExpression.Width > 2 && context.Reason != CompletionReason.Invoked) | ||
| { | ||
| // We only want to provide razor csharp keyword completions if the implicit expression is empty "@|" or at the beginning of a word "@i|", this ensures | ||
| // we're consistent with how C# typically provides completion items. | ||
| return false; | ||
| } | ||
|
|
||
| if (owner.ChildNodesAndTokens().Any(static x => !x.AsToken(out var token) || !IsCSharpRazorKeywordCompletableToken(token))) | ||
| { | ||
| // Implicit expression contains nodes or tokens that aren't completable by a csharp razor keyword | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| private static bool IsCSharpRazorKeywordCompletableToken(AspNetCore.Razor.Language.Syntax.SyntaxToken token) | ||
| { | ||
| return token is { Kind: SyntaxKind.Identifier or SyntaxKind.Marker or SyntaxKind.Keyword } | ||
| or { Kind: SyntaxKind.Transition, Parent.Kind: SyntaxKind.CSharpTransition }; | ||
| } | ||
|
|
||
| private static ImmutableArray<RazorCompletionItem> GetCSharpRazorKeywordCompletionItems() | ||
| { | ||
| var completionItems = new RazorCompletionItem[CSharpRazorKeywords.Length]; | ||
|
|
||
| for (var i = 0; i < CSharpRazorKeywords.Length; i++) | ||
| { | ||
| var keyword = CSharpRazorKeywords[i]; | ||
|
|
||
| var keywordCompletionItem = RazorCompletionItem.CreateKeyword( | ||
| displayText: keyword, | ||
| insertText: keyword, | ||
| KeywordCommitCharacters); | ||
|
|
||
| completionItems[i] = keywordCompletionItem; | ||
| } | ||
|
|
||
| return ImmutableCollectionsMarshal.AsImmutableArray(completionItems); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,9 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System; | ||
|
|
||
| namespace Microsoft.CodeAnalysis.Razor.Completion; | ||
|
|
||
| internal class DirectiveCompletionDescription : CompletionDescription | ||
| internal sealed class DirectiveCompletionDescription(string description) : CompletionDescription | ||
| { | ||
| public override string Description { get; } | ||
|
|
||
| public DirectiveCompletionDescription(string description) | ||
| { | ||
| if (description is null) | ||
| { | ||
| throw new ArgumentNullException(nameof(description)); | ||
| } | ||
|
|
||
| Description = description; | ||
| } | ||
| public override string Description { get; } = description; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,9 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System; | ||
|
|
||
| namespace Microsoft.CodeAnalysis.Razor.Completion; | ||
|
|
||
| internal class MarkupTransitionCompletionDescription : CompletionDescription | ||
| internal sealed class MarkupTransitionCompletionDescription(string description) : CompletionDescription | ||
| { | ||
| public override string Description { get; } | ||
|
|
||
| public MarkupTransitionCompletionDescription(string description) | ||
| { | ||
| if (description is null) | ||
| { | ||
| throw new ArgumentNullException(nameof(description)); | ||
| } | ||
|
|
||
| Description = description; | ||
| } | ||
| public override string Description { get; } = description; | ||
| } |
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.
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.
If my hypothesis is correct, and this issue is caused by semantic tokens, then I think putting this in the non-cohosting editor would actually result in doubling up of completion items for these, as Roslyn will add them as well, so I think perhaps this line is not desirable.
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.
Can I have you try out cohosting off and see if you see the keywords in completion after typing just '@'? (I'm not seeing it without my changes)
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.
Well now I'm really confused. Using Version: 18.3.0 Insiders [11220.113.main]
Without cohosting:

With cohosting:

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.
If I remember I'll pull this branch down tomorrow and see what I get with these changes
Uh oh!
There was an error while loading. Please reload this page.
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.
Huh, behavior seems to vary across more axes than I tested. I'll do a more comprehensive run through of with/without my changes, at various locations, forced completion versus typed trigger, cohosting on/off and report back probably early tomorrow.