Skip to content
This repository was archived by the owner on Dec 19, 2018. It is now read-only.

Commit 572b556

Browse files
author
N. Taylor Mullen
committed
Refactor completion logic into standalone service.
- Migrated the completion item source provider and the legacy directive completion provider to use the new service. - Cleaned up duplicate tests that were both verifying common completion functionality. - Ensured that the legacy `RazorDirectiveCompletionProvider` did not result in additional Razor assembly loads when in C# scenarios. #2530
1 parent aec88e3 commit 572b556

11 files changed

+495
-512
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.ComponentModel.Composition;
7+
using System.Linq;
8+
using Microsoft.AspNetCore.Razor.Language;
9+
using Microsoft.AspNetCore.Razor.Language.Legacy;
10+
11+
namespace Microsoft.VisualStudio.Editor.Razor
12+
{
13+
[System.Composition.Shared]
14+
[Export(typeof(RazorCompletionFactsService))]
15+
internal class DefaultRazorCompletionFactsService : RazorCompletionFactsService
16+
{
17+
private static readonly IEnumerable<DirectiveDescriptor> DefaultDirectives = new[]
18+
{
19+
CSharpCodeParser.AddTagHelperDirectiveDescriptor,
20+
CSharpCodeParser.RemoveTagHelperDirectiveDescriptor,
21+
CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
22+
};
23+
24+
public override IReadOnlyList<RazorCompletionItem> GetCompletionItems(RazorSyntaxTree syntaxTree, SourceSpan location)
25+
{
26+
var completionItems = new List<RazorCompletionItem>();
27+
28+
if (AtDirectiveCompletionPoint(syntaxTree, location))
29+
{
30+
var directiveCompletions = GetDirectiveCompletionItems(syntaxTree);
31+
completionItems.AddRange(directiveCompletions);
32+
}
33+
34+
return completionItems;
35+
}
36+
37+
// Internal for testing
38+
internal static List<RazorCompletionItem> GetDirectiveCompletionItems(RazorSyntaxTree syntaxTree)
39+
{
40+
var directives = syntaxTree.Options.Directives.Concat(DefaultDirectives);
41+
var completionItems = new List<RazorCompletionItem>();
42+
foreach (var directive in directives)
43+
{
44+
var completionDisplayText = directive.DisplayName ?? directive.Directive;
45+
var completionItem = new RazorCompletionItem(
46+
completionDisplayText,
47+
directive.Directive,
48+
directive.Description,
49+
RazorCompletionItemKind.Directive);
50+
completionItems.Add(completionItem);
51+
}
52+
53+
return completionItems;
54+
}
55+
56+
// Internal for testing
57+
internal static bool AtDirectiveCompletionPoint(RazorSyntaxTree syntaxTree, SourceSpan location)
58+
{
59+
if (syntaxTree == null)
60+
{
61+
return false;
62+
}
63+
64+
var change = new SourceChange(location, string.Empty);
65+
var owner = syntaxTree.Root.LocateOwner(change);
66+
67+
if (owner == null)
68+
{
69+
return false;
70+
}
71+
72+
if (owner.ChunkGenerator is ExpressionChunkGenerator &&
73+
owner.Tokens.All(IsDirectiveCompletableToken) &&
74+
// Do not provide IntelliSense for explicit expressions. Explicit expressions will usually look like:
75+
// [@] [(] [DateTime.Now] [)]
76+
owner.Parent?.Children.Count > 1 &&
77+
owner.Parent.Children[1] == owner)
78+
{
79+
return true;
80+
}
81+
82+
return false;
83+
}
84+
85+
// Internal for testing
86+
internal static bool IsDirectiveCompletableToken(IToken token)
87+
{
88+
if (!(token is CSharpToken csharpToken))
89+
{
90+
return false;
91+
}
92+
93+
return csharpToken.Type == CSharpTokenType.Identifier ||
94+
// Marker symbol
95+
csharpToken.Type == CSharpTokenType.Unknown;
96+
}
97+
}
98+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using Microsoft.AspNetCore.Razor.Language;
6+
7+
namespace Microsoft.VisualStudio.Editor.Razor
8+
{
9+
internal abstract class RazorCompletionFactsService
10+
{
11+
public abstract IReadOnlyList<RazorCompletionItem> GetCompletionItems(RazorSyntaxTree syntaxTree, SourceSpan location);
12+
}
13+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.VisualStudio.Editor.Razor
7+
{
8+
internal sealed class RazorCompletionItem
9+
{
10+
public RazorCompletionItem(
11+
string displayText,
12+
string insertText,
13+
string description,
14+
RazorCompletionItemKind kind)
15+
{
16+
if (displayText == null)
17+
{
18+
throw new ArgumentNullException(nameof(displayText));
19+
}
20+
21+
if (insertText == null)
22+
{
23+
throw new ArgumentNullException(nameof(insertText));
24+
}
25+
26+
if (description == null)
27+
{
28+
throw new ArgumentNullException(nameof(description));
29+
}
30+
31+
DisplayText = displayText;
32+
InsertText = insertText;
33+
Description = description;
34+
Kind = kind;
35+
}
36+
37+
public string DisplayText { get; }
38+
39+
public string InsertText { get; }
40+
41+
public string Description { get; }
42+
43+
public RazorCompletionItemKind Kind { get; }
44+
}
45+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.VisualStudio.Editor.Razor
5+
{
6+
internal enum RazorCompletionItemKind
7+
{
8+
Directive
9+
}
10+
}

src/Microsoft.VisualStudio.Editor.Razor/RazorDirectiveCompletionProvider.cs

Lines changed: 24 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,14 @@ internal class RazorDirectiveCompletionProvider : CompletionProvider
3636
CSharpCodeParser.TagHelperPrefixDirectiveDescriptor,
3737
};
3838
private readonly Lazy<RazorCodeDocumentProvider> _codeDocumentProvider;
39+
private readonly Lazy<RazorCompletionFactsService> _completionFactsService;
3940
private readonly IAsyncCompletionBroker _asyncCompletionBroker;
4041
private readonly RazorTextBufferProvider _textBufferProvider;
4142

4243
[ImportingConstructor]
4344
public RazorDirectiveCompletionProvider(
4445
[Import(typeof(RazorCodeDocumentProvider))] Lazy<RazorCodeDocumentProvider> codeDocumentProvider,
46+
[Import(typeof(RazorCompletionFactsService))] Lazy<RazorCompletionFactsService> completionFactsService,
4547
IAsyncCompletionBroker asyncCompletionBroker,
4648
RazorTextBufferProvider textBufferProvider)
4749
{
@@ -50,6 +52,11 @@ public RazorDirectiveCompletionProvider(
5052
throw new ArgumentNullException(nameof(codeDocumentProvider));
5153
}
5254

55+
if (completionFactsService == null)
56+
{
57+
throw new ArgumentNullException(nameof(completionFactsService));
58+
}
59+
5360
if (asyncCompletionBroker == null)
5461
{
5562
throw new ArgumentNullException(nameof(asyncCompletionBroker));
@@ -61,6 +68,7 @@ public RazorDirectiveCompletionProvider(
6168
}
6269

6370
_codeDocumentProvider = codeDocumentProvider;
71+
_completionFactsService = completionFactsService;
6472
_asyncCompletionBroker = asyncCompletionBroker;
6573
_textBufferProvider = textBufferProvider;
6674
}
@@ -133,70 +141,41 @@ private Task AddCompletionItems(CompletionContext context)
133141
return Task.CompletedTask;
134142
}
135143

136-
if (!AtDirectiveCompletionPoint(syntaxTree, context))
144+
if (!TryGetRazorSnapshotPoint(context, out var razorSnapshotPoint))
137145
{
138-
// Can't have a valid directive at the current location.
146+
// Could not find associated Razor location.
139147
return Task.CompletedTask;
140148
}
141149

142-
var completionItems = GetCompletionItems(syntaxTree);
143-
context.AddItems(completionItems);
150+
var location = new SourceSpan(razorSnapshotPoint.Position, 0);
151+
var razorCompletionItems = _completionFactsService.Value.GetCompletionItems(syntaxTree, location);
144152

145-
return Task.CompletedTask;
146-
}
147-
148-
// Internal virtual for testing
149-
internal virtual IEnumerable<CompletionItem> GetCompletionItems(RazorSyntaxTree syntaxTree)
150-
{
151-
var directives = syntaxTree.Options.Directives.Concat(DefaultDirectives);
152-
var completionItems = new List<CompletionItem>();
153-
foreach (var directive in directives)
153+
foreach (var razorCompletionItem in razorCompletionItems)
154154
{
155-
var propertyDictionary = new Dictionary<string, string>(StringComparer.Ordinal);
155+
if (razorCompletionItem.Kind != RazorCompletionItemKind.Directive)
156+
{
157+
// Don't support any other types of completion kinds other than directives.
158+
continue;
159+
}
156160

157-
if (!string.IsNullOrEmpty(directive.Description))
161+
var propertyDictionary = new Dictionary<string, string>(StringComparer.Ordinal);
162+
if (!string.IsNullOrEmpty(razorCompletionItem.Description))
158163
{
159-
propertyDictionary[DescriptionKey] = directive.Description;
164+
propertyDictionary[DescriptionKey] = razorCompletionItem.Description;
160165
}
161166

162167
var completionItem = CompletionItem.Create(
163-
directive.Directive,
168+
razorCompletionItem.InsertText,
164169
// This groups all Razor directives together
165170
sortText: "_RazorDirective_",
166171
rules: CompletionItemRules.Create(formatOnCommit: false),
167172
tags: ImmutableArray.Create(WellKnownTags.Intrinsic),
168173
properties: propertyDictionary.ToImmutableDictionary());
169-
completionItems.Add(completionItem);
170-
}
171-
172-
return completionItems;
173-
}
174174

175-
// Internal for testing
176-
internal bool AtDirectiveCompletionPoint(RazorSyntaxTree syntaxTree, CompletionContext context)
177-
{
178-
if (TryGetRazorSnapshotPoint(context, out var razorSnapshotPoint))
179-
{
180-
var change = new SourceChange(razorSnapshotPoint.Position, 0, string.Empty);
181-
var owner = syntaxTree.Root.LocateOwner(change);
182-
183-
if (owner == null)
184-
{
185-
return false;
186-
}
187-
188-
if (owner.ChunkGenerator is ExpressionChunkGenerator &&
189-
owner.Tokens.All(IsDirectiveCompletableSymbol) &&
190-
// Do not provide IntelliSense for explicit expressions. Explicit expressions will usually look like:
191-
// [@] [(] [DateTime.Now] [)]
192-
owner.Parent?.Children.Count > 1 &&
193-
owner.Parent.Children[1] == owner)
194-
{
195-
return true;
196-
}
175+
context.AddItem(completionItem);
197176
}
198177

199-
return false;
178+
return Task.CompletedTask;
200179
}
201180

202181
protected virtual bool TryGetRazorSnapshotPoint(CompletionContext context, out SnapshotPoint snapshotPoint)

0 commit comments

Comments
 (0)