Skip to content

Commit

Permalink
Add "ussing" keyword and "using" snippet, fixup tests
Browse files Browse the repository at this point in the history
  • Loading branch information
alexgav committed Feb 24, 2024
1 parent dbb0fb5 commit b5dbe49
Show file tree
Hide file tree
Showing 25 changed files with 218 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
builder.Description = Resources.AddTagHelperDirective_Description;
});

internal static readonly DirectiveDescriptor UsingDirectiveDescriptor = DirectiveDescriptor.CreateDirective(
SyntaxConstants.CSharp.UsingKeyword,
DirectiveKind.SingleLine,
builder =>
{
builder.Description = Resources.UsingDirective_Description;
});

internal static readonly DirectiveDescriptor RemoveTagHelperDirectiveDescriptor = DirectiveDescriptor.CreateDirective(
SyntaxConstants.CSharp.RemoveTagHelperKeyword,
DirectiveKind.SingleLine,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal static class SyntaxConstants
public static class CSharp
{
public const int UsingKeywordLength = 5;
public const string UsingKeyword = "using";
public const string TagHelperPrefixKeyword = "tagHelperPrefix";
public const string AddTagHelperKeyword = "addTagHelper";
public const string RemoveTagHelperKeyword = "removeTagHelper";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Expand Down Expand Up @@ -595,4 +595,7 @@
<data name="DirectiveExpectsIdentifierOrExpression" xml:space="preserve">
<value>The '{0}' directive expects an identifier or explicit razor expression ("@()").</value>
</data>
</root>
<data name="UsingDirective_Description" xml:space="preserve">
<value>Adds the C# using directive to the generated view</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,30 @@ public RazorCompletionItemResolver(
return null;
}

var associatedRazorCompletion = razorCompletionResolveContext.CompletionItems.FirstOrDefault(completion => string.Equals(completion.DisplayText, completionItem.Label, StringComparison.Ordinal));
var associatedRazorCompletion = razorCompletionResolveContext.CompletionItems.FirstOrDefault(completion =>
{
if (!string.Equals(completion.DisplayText, completionItem.Label, StringComparison.Ordinal))
{
return false;
}

// We may have items of different types with the same label (e.g. snippet and keyword)
if (clientCapabilities is not null)
{
// CompletionItem.Kind and RazorCompletionItem.Kind are not compatible/comparable, so we need to convert
// Razor completion item to VS completion item (as logic to convert just the kind is not easy to separate from
// the rest of the conversion logic) prior to comparing them
RazorCompletionListProvider.TryConvert(completion, clientCapabilities, out var convertedRazorCompletionItem);
if (convertedRazorCompletionItem != null)
{
return completionItem.Kind == convertedRazorCompletionItem.Kind;
}
}

// If display text matches but we couldn't convert razor completion item to VS completion item for some reason,
// do what previous version of the code did and return true.
return true;
});
if (associatedRazorCompletion is null)
{
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ public async Task<VSInternalCompletionItem> HandleRequestAsync(VSInternalComplet

// See if this is the right completion list for this corresponding completion item. We cross-check this based on label only given that
// is what users interact with.
if (cacheEntry.CompletionList.Items.Any(completion => string.Equals(completionItem.Label, completion.Label, StringComparison.Ordinal)))
if (cacheEntry.CompletionList.Items.Any(completion => string.Equals(completionItem.Label, completion.Label, StringComparison.Ordinal) &&
// Check the Kind as well, e.g. we may have a Razor snippet and a C# keyword with the same label, etc.
completionItem.Kind == completion.Kind))
{
originalRequestContext = cacheEntry.Context;
containingCompletionList = cacheEntry.CompletionList;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ internal class DirectiveCompletionItemProvider : IRazorCompletionItemProvider
CSharpCodeParser.AddTagHelperDirectiveDescriptor,
CSharpCodeParser.RemoveTagHelperDirectiveDescriptor,
CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
CSharpCodeParser.UsingDirectiveDescriptor
};

// Test accessor
internal static IEnumerable<DirectiveDescriptor> DefaultDirectives => s_defaultDirectives;

// internal for testing
// Do not forget to update both insert and display text !important
internal static readonly IReadOnlyDictionary<string, (string InsertText, string DisplayText)> s_singleLineDirectiveSnippets = new Dictionary<string, (string InsertText, string DisplayText)>(StringComparer.Ordinal)
Expand All @@ -45,7 +49,8 @@ internal class DirectiveCompletionItemProvider : IRazorCompletionItemProvider
["preservewhitespace"] = ("preservewhitespace ${1:true}$0", "preservewhitespace true"),
["removeTagHelper"] = ("removeTagHelper ${1:*}, ${2:Microsoft.AspNetCore.Mvc.TagHelpers}", "removeTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers"),
["tagHelperPrefix"] = ("tagHelperPrefix ${1:prefix}$0", "tagHelperPrefix prefix"),
["typeparam"] = ("typeparam ${1:T}$0", "typeparam T")
["typeparam"] = ("typeparam ${1:T}$0", "typeparam T"),
["using"] = ("using ${1:MyNamespace}$0", "using MyNamespace")
};

public ImmutableArray<RazorCompletionItem> GetCompletionItems(RazorCompletionContext context)
Expand Down Expand Up @@ -145,6 +150,10 @@ internal static ImmutableArray<RazorCompletionItem> GetDirectiveCompletionItems(
completionDisplayText,
directive.Directive,
RazorCompletionItemKind.Directive,
// Make sort text one less than display text so if there are any delegated completion items
// with the same display text in the combined completion list, they will be sorted below
// our items.
sortText: completionDisplayText[..Math.Max(1, completionDisplayText.Length - 1)],
commitCharacters: commitCharacters,
isSnippet: false);
var completionDescription = new DirectiveCompletionDescription(directive.Description);
Expand All @@ -154,9 +163,11 @@ internal static ImmutableArray<RazorCompletionItem> GetDirectiveCompletionItems(
if (s_singleLineDirectiveSnippets.TryGetValue(directive.Directive, out var snippetTexts))
{
var snippetCompletionItem = new RazorCompletionItem(
$"{completionDisplayText} ...",
$"{completionDisplayText} {SR.Directive}",
snippetTexts.InsertText,
RazorCompletionItemKind.Directive,
// Use the same sort text here as the directive completion item so both items are grouped together
sortText: completionItem.SortText,
commitCharacters: commitCharacters,
isSnippet: true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
<value>Value cannot be null or an empty string.</value>
</data>
<data name="Directive" xml:space="preserve">
<value>directive</value>
</data>
<data name="DirectiveSnippetDescription" xml:space="preserve">
<value>Insert a directive code snippet
[Tab] to navigate between elements, [Enter] to complete</value>
Expand Down

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.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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.Linq;
using Microsoft.CodeAnalysis.Razor.Completion;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Xunit;

namespace Microsoft.AspNetCore.Razor.LanguageServer.Completion;

internal static class DirectiveVerifier
{
private static readonly Action<CompletionItem>[] s_defaultDirectiveCollectionVerifiers;

public static Action<CompletionItem>[] DefaultDirectiveCollectionVerifiers => s_defaultDirectiveCollectionVerifiers;

static DirectiveVerifier()
{
var defaultDirectiveVerifierList = new List<Action<CompletionItem>>(DirectiveCompletionItemProvider.DefaultDirectives.Count() * 2);

foreach (var directive in DirectiveCompletionItemProvider.DefaultDirectives)
{
defaultDirectiveVerifierList.Add(item => Assert.Equal(directive.Directive, item.InsertText));
defaultDirectiveVerifierList.Add(item => AssertDirectiveSnippet(item, directive.Directive));
}

s_defaultDirectiveCollectionVerifiers = defaultDirectiveVerifierList.ToArray();
}

private static void AssertDirectiveSnippet(CompletionItem completionItem, string directive)
{
Assert.StartsWith(directive, completionItem.InsertText);
Assert.Equal(DirectiveCompletionItemProvider.s_singleLineDirectiveSnippets[directive].InsertText, completionItem.InsertText);
Assert.Equal(CompletionItemKind.Snippet, completionItem.Kind);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -396,12 +396,7 @@ public async Task Handle_ProvidesDirectiveCompletionItems()

// These are the default directives that don't need to be separately registered, they should always be part of the completion list.
Assert.Collection(completionList.Items,
item => Assert.Equal("addTagHelper", item.InsertText),
item => AssertDirectiveSnippet(item, "addTagHelper"),
item => Assert.Equal("removeTagHelper", item.InsertText),
item => AssertDirectiveSnippet(item, "removeTagHelper"),
item => Assert.Equal("tagHelperPrefix", item.InsertText),
item => AssertDirectiveSnippet(item, "tagHelperPrefix")
DirectiveVerifier.DefaultDirectiveCollectionVerifiers
);
}

Expand Down Expand Up @@ -440,12 +435,7 @@ public async Task Handle_ProvidesInjectOnIncomplete_KeywordIn()

// Assert
Assert.Collection(completionList.Items,
item => Assert.Equal("addTagHelper", item.InsertText),
item => AssertDirectiveSnippet(item, "addTagHelper"),
item => Assert.Equal("removeTagHelper", item.InsertText),
item => AssertDirectiveSnippet(item, "removeTagHelper"),
item => Assert.Equal("tagHelperPrefix", item.InsertText),
item => AssertDirectiveSnippet(item, "tagHelperPrefix")
DirectiveVerifier.DefaultDirectiveCollectionVerifiers
);
}

Expand Down Expand Up @@ -520,12 +510,7 @@ public async Task Handle_ProvidesInjectOnIncomplete()

// Assert
Assert.Collection(completionList.Items,
item => Assert.Equal("addTagHelper", item.InsertText),
item => AssertDirectiveSnippet(item, "addTagHelper"),
item => Assert.Equal("removeTagHelper", item.InsertText),
item => AssertDirectiveSnippet(item, "removeTagHelper"),
item => Assert.Equal("tagHelperPrefix", item.InsertText),
item => AssertDirectiveSnippet(item, "tagHelperPrefix")
DirectiveVerifier.DefaultDirectiveCollectionVerifiers
);
}

Expand Down
Loading

0 comments on commit b5dbe49

Please sign in to comment.