@@ -16,44 +16,34 @@ internal class DirectiveAttributeCompletionItemProvider : DirectiveAttributeComp
16
16
{
17
17
public override ImmutableArray < RazorCompletionItem > GetCompletionItems ( RazorCompletionContext context )
18
18
{
19
- if ( context is null )
20
- {
21
- throw new ArgumentNullException ( nameof ( context ) ) ;
22
- }
23
-
24
- if ( context . TagHelperDocumentContext is null )
25
- {
26
- throw new ArgumentNullException ( nameof ( context . TagHelperDocumentContext ) ) ;
27
- }
28
-
29
19
if ( ! FileKinds . IsComponent ( context . SyntaxTree . Options . FileKind ) )
30
20
{
31
21
// Directive attributes are only supported in components
32
- return ImmutableArray < RazorCompletionItem > . Empty ;
22
+ return [ ] ;
33
23
}
34
24
35
25
var owner = context . Owner ;
36
26
if ( owner is null )
37
27
{
38
- return ImmutableArray < RazorCompletionItem > . Empty ;
28
+ return [ ] ;
39
29
}
40
30
41
31
if ( ! TryGetAttributeInfo ( owner , out _ , out var attributeName , out var attributeNameLocation , out _ , out _ ) )
42
32
{
43
33
// Either we're not in an attribute or the attribute is so malformed that we can't provide proper completions.
44
- return ImmutableArray < RazorCompletionItem > . Empty ;
34
+ return [ ] ;
45
35
}
46
36
47
37
if ( ! attributeNameLocation . IntersectsWith ( context . AbsoluteIndex ) )
48
38
{
49
39
// We're trying to retrieve completions on a portion of the name that is not supported (such as a parameter).
50
- return ImmutableArray < RazorCompletionItem > . Empty ;
40
+ return [ ] ;
51
41
}
52
42
53
43
if ( ! TryGetElementInfo ( owner . Parent . Parent , out var containingTagName , out var attributes ) )
54
44
{
55
45
// This should never be the case, it means that we're operating on an attribute that doesn't have a tag.
56
- return ImmutableArray < RazorCompletionItem > . Empty ;
46
+ return [ ] ;
57
47
}
58
48
59
49
// At this point we've determined that completions have been requested for the name portion of the selected attribute.
@@ -63,16 +53,16 @@ public override ImmutableArray<RazorCompletionItem> GetCompletionItems(RazorComp
63
53
// We don't provide Directive Attribute completions when we're in the middle of
64
54
// another unrelated (doesn't start with @) partially completed attribute.
65
55
// <svg xml:| ></svg> (attributeName = "xml:") should not get any directive attribute completions.
66
- if ( string . IsNullOrWhiteSpace ( attributeName ) || attributeName . StartsWith ( "@" , StringComparison . Ordinal ) )
56
+ if ( attributeName . IsNullOrWhiteSpace ( ) || attributeName . StartsWith ( '@' ) )
67
57
{
68
58
return completionItems ;
69
59
}
70
60
71
- return ImmutableArray < RazorCompletionItem > . Empty ;
61
+ return [ ] ;
72
62
}
73
63
74
64
// Internal for testing
75
- internal ImmutableArray < RazorCompletionItem > GetAttributeCompletions (
65
+ internal static ImmutableArray < RazorCompletionItem > GetAttributeCompletions (
76
66
string selectedAttributeName ,
77
67
string containingTagName ,
78
68
ImmutableArray < string > attributes ,
@@ -82,11 +72,11 @@ internal ImmutableArray<RazorCompletionItem> GetAttributeCompletions(
82
72
if ( descriptorsForTag . Length == 0 )
83
73
{
84
74
// If the current tag has no possible descriptors then we can't have any directive attributes.
85
- return ImmutableArray < RazorCompletionItem > . Empty ;
75
+ return [ ] ;
86
76
}
87
77
88
- // Attributes are case sensitive when matching
89
- var attributeCompletions = new Dictionary < string , ( HashSet < BoundAttributeDescriptionInfo > , HashSet < string > ) > ( StringComparer . Ordinal ) ;
78
+ // Use ordinal dictionary because attributes are case sensitive when matching
79
+ using var _ = StringDictionaryPool < ( HashSet < BoundAttributeDescriptionInfo > , HashSet < string > ) > . Ordinal . GetPooledObject ( out var attributeCompletions ) ;
90
80
91
81
foreach ( var descriptor in descriptorsForTag )
92
82
{
@@ -114,7 +104,7 @@ internal ImmutableArray<RazorCompletionItem> GetAttributeCompletions(
114
104
}
115
105
}
116
106
117
- if ( ! string . IsNullOrEmpty ( attributeDescriptor . IndexerNamePrefix ) )
107
+ if ( ! attributeDescriptor . IndexerNamePrefix . IsNullOrEmpty ( ) )
118
108
{
119
109
TryAddCompletion ( attributeDescriptor . IndexerNamePrefix + "..." , attributeDescriptor , descriptor ) ;
120
110
}
@@ -123,39 +113,36 @@ internal ImmutableArray<RazorCompletionItem> GetAttributeCompletions(
123
113
124
114
using var completionItems = new PooledArrayBuilder < RazorCompletionItem > ( capacity : attributeCompletions . Count ) ;
125
115
126
- foreach ( var completion in attributeCompletions )
116
+ foreach ( var ( displayText , ( attributeDescriptions , commitCharacters ) ) in attributeCompletions )
127
117
{
128
- var insertText = completion . Key ;
129
- if ( insertText . EndsWith ( "..." , StringComparison . Ordinal ) )
130
- {
131
- // Indexer attribute, we don't want to insert with the triple dot.
132
- insertText = insertText [ .. ^ 3 ] ;
133
- }
118
+ var insertText = displayText ;
119
+
120
+ // Strip off the @ from the insertion text. This change is here to align the insertion text with the
121
+ // completion hooks into VS and VSCode. Basically, completion triggers when `@` is typed so we don't
122
+ // want to insert `@bind` because `@` already exists.
123
+ var startIndex = insertText . StartsWith ( '@' ) ? 1 : 0 ;
134
124
135
- if ( insertText . StartsWith ( "@" , StringComparison . Ordinal ) )
125
+ // Indexer attribute, we don't want to insert with the triple dot.
126
+ var endIndex = insertText . EndsWith ( "..." , StringComparison . Ordinal ) ? ^ 3 : ^ 0 ;
127
+
128
+ // Don't allocate a new string unless we need to make a change.
129
+ if ( startIndex > 0 || endIndex . Value > 0 )
136
130
{
137
- // Strip off the @ from the insertion text. This change is here to align the insertion text with the
138
- // completion hooks into VS and VSCode. Basically, completion triggers when `@` is typed so we don't
139
- // want to insert `@bind` because `@` already exists.
140
- insertText = insertText [ 1 ..] ;
131
+ insertText = insertText [ startIndex ..endIndex ] ;
141
132
}
142
133
143
- var ( attributeDescriptionInfos , commitCharacters ) = completion . Value ;
144
-
145
134
using var razorCommitCharacters = new PooledArrayBuilder < RazorCommitCharacter > ( capacity : commitCharacters . Count ) ;
146
135
147
136
foreach ( var c in commitCharacters )
148
137
{
149
138
razorCommitCharacters . Add ( new ( c ) ) ;
150
139
}
151
140
152
- var razorCompletionItem = new RazorCompletionItem (
153
- completion . Key ,
141
+ var razorCompletionItem = RazorCompletionItem . CreateDirectiveAttribute (
142
+ displayText ,
154
143
insertText ,
155
- RazorCompletionItemKind . DirectiveAttribute ,
144
+ descriptionInfo : new ( [ .. attributeDescriptions ] ) ,
156
145
commitCharacters : razorCommitCharacters . DrainToImmutable ( ) ) ;
157
- var completionDescription = new AggregateBoundAttributeDescription ( attributeDescriptionInfos . ToImmutableArray ( ) ) ;
158
- razorCompletionItem . SetAttributeCompletionDescription ( completionDescription ) ;
159
146
160
147
completionItems . Add ( razorCompletionItem ) ;
161
148
}
@@ -164,8 +151,8 @@ internal ImmutableArray<RazorCompletionItem> GetAttributeCompletions(
164
151
165
152
bool TryAddCompletion ( string attributeName , BoundAttributeDescriptor boundAttributeDescriptor , TagHelperDescriptor tagHelperDescriptor )
166
153
{
167
- if ( attributes . Any ( name => string . Equals ( name , attributeName , StringComparison . Ordinal ) ) &&
168
- ! string . Equals ( selectedAttributeName , attributeName , StringComparison . Ordinal ) )
154
+ if ( selectedAttributeName != attributeName &&
155
+ attributes . Any ( attributeName , static ( name , attributeName ) => name == attributeName ) )
169
156
{
170
157
// Attribute is already present on this element and it is not the selected attribute.
171
158
// It shouldn't exist in the completion list.
@@ -180,16 +167,16 @@ void AddCompletion(string attributeName, BoundAttributeDescriptor boundAttribute
180
167
{
181
168
if ( ! attributeCompletions . TryGetValue ( attributeName , out var attributeDetails ) )
182
169
{
183
- attributeDetails = ( new HashSet < BoundAttributeDescriptionInfo > ( ) , new HashSet < string > ( ) ) ;
170
+ attributeDetails = ( [ ] , [ ] ) ;
184
171
attributeCompletions [ attributeName ] = attributeDetails ;
185
172
}
186
173
187
- ( var attributeDescriptionInfos , var commitCharacters ) = attributeDetails ;
174
+ ( var attributeDescriptions , var commitCharacters ) = attributeDetails ;
188
175
189
176
var indexerCompletion = attributeName . EndsWith ( "..." , StringComparison . Ordinal ) ;
190
177
var tagHelperTypeName = tagHelperDescriptor . GetTypeName ( ) ;
191
178
var descriptionInfo = BoundAttributeDescriptionInfo . From ( boundAttributeDescriptor , isIndexer : indexerCompletion , tagHelperTypeName ) ;
192
- attributeDescriptionInfos . Add ( descriptionInfo ) ;
179
+ attributeDescriptions . Add ( descriptionInfo ) ;
193
180
194
181
if ( indexerCompletion )
195
182
{
@@ -199,14 +186,28 @@ void AddCompletion(string attributeName, BoundAttributeDescriptor boundAttribute
199
186
200
187
commitCharacters . Add ( "=" ) ;
201
188
202
- if ( tagHelperDescriptor . BoundAttributes . Any ( b => b . IsBooleanProperty ) )
203
- {
204
- commitCharacters . Add ( " " ) ;
205
- }
189
+ var spaceAdded = commitCharacters . Contains ( " " ) ;
190
+ var colonAdded = commitCharacters . Contains ( ":" ) ;
206
191
207
- if ( tagHelperDescriptor . BoundAttributes . Any ( b => b . Parameters . Length > 0 ) )
192
+ if ( ! spaceAdded || ! colonAdded )
208
193
{
209
- commitCharacters . Add ( ":" ) ;
194
+ foreach ( var boundAttribute in tagHelperDescriptor . BoundAttributes )
195
+ {
196
+ if ( ! spaceAdded && boundAttribute . IsBooleanProperty )
197
+ {
198
+ commitCharacters . Add ( " " ) ;
199
+ spaceAdded = true ;
200
+ }
201
+ else if ( ! colonAdded && boundAttribute . Parameters . Length > 0 )
202
+ {
203
+ commitCharacters . Add ( ":" ) ;
204
+ colonAdded = true ;
205
+ }
206
+ else if ( spaceAdded && colonAdded )
207
+ {
208
+ break ;
209
+ }
210
+ }
210
211
}
211
212
}
212
213
}
0 commit comments