Skip to content

Commit d4a0a6c

Browse files
[main] Source code updates from dotnet/razor (#2764)
[main] Source code updates from dotnet/razor
1 parent fbf504e commit d4a0a6c

File tree

19 files changed

+333
-194
lines changed

19 files changed

+333
-194
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: "Copilot Setup Steps"
2+
3+
# Allow testing of the setup steps from your repository's "Actions" tab.
4+
on: workflow_dispatch
5+
6+
jobs:
7+
# The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot.
8+
# See https://docs.github.com/en/copilot/customizing-copilot/customizing-the-development-environment-for-copilot-coding-agent
9+
copilot-setup-steps:
10+
runs-on: 8-core-ubuntu-latest
11+
12+
permissions:
13+
contents: read
14+
15+
# You can define any steps you want, and they will run before the agent starts.
16+
# If you do not check out your code, Copilot will do this for you.
17+
steps:
18+
- uses: actions/checkout@v5
19+
20+
# Install .NET SDK as specified in global.json
21+
- name: Setup .NET
22+
uses: actions/setup-dotnet@v5
23+
with:
24+
global-json-file: global.json
25+
26+
# Restore dependencies - this is critical for Copilot to understand the project structure
27+
- name: Restore dependencies
28+
continue-on-error: true
29+
env:
30+
# Prevent GitInfo errors and other CI-specific issues
31+
CI: false
32+
# Use restore script, but don't fail on errors so Copilot can still attempt to work
33+
run: ./restore.sh
34+
35+
# Diagnostics in the log
36+
- name: Show .NET info
37+
run: dotnet --info
38+
39+
# Show build environment info that might be helpful for debugging
40+
- name: Show environment info
41+
run: |
42+
echo "=== Git Status ==="
43+
git status || true
44+
echo "=== Directory structure ==="
45+
ls -la
46+
echo "=== Build artifacts ==="
47+
ls -la artifacts/ || true
48+
echo "=== Global.json ==="
49+
cat global.json

src/razor/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Completion/DirectiveAttributeCompletionItemProvider.cs

Lines changed: 87 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ internal class DirectiveAttributeCompletionItemProvider : DirectiveAttributeComp
1919
private static ReadOnlyMemory<char> QuotedAttributeValueSnippet => "=\"$0\"".AsMemory();
2020
private static ReadOnlyMemory<char> UnquotedAttributeValueSnippet => "=$0".AsMemory();
2121

22+
private static readonly ImmutableArray<RazorCommitCharacter> EqualsCommitCharacters = [new("=")];
23+
private static readonly ImmutableArray<RazorCommitCharacter> EqualsAndColonCommitCharacters = [new("="), new(":")];
24+
private static readonly ImmutableArray<RazorCommitCharacter> SnippetEqualsCommitCharacters = [new("=", Insert: false)];
25+
private static readonly ImmutableArray<RazorCommitCharacter> SnippetEqualsAndColonCommitCharacters = [new("=", Insert: false), new(":")];
26+
2227
public override ImmutableArray<RazorCompletionItem> GetCompletionItems(RazorCompletionContext context)
2328
{
2429
if (!context.SyntaxTree.Options.FileKind.IsComponent())
@@ -83,7 +88,8 @@ internal static ImmutableArray<RazorCompletionItem> GetAttributeCompletions(
8388
}
8489

8590
// Use ordinal dictionary because attributes are case sensitive when matching
86-
using var _ = StringDictionaryPool<(HashSet<BoundAttributeDescriptionInfo>, HashSet<string>)>.Ordinal.GetPooledObject(out var attributeCompletions);
91+
using var _ = StringDictionaryPool<(ImmutableArray<BoundAttributeDescriptionInfo>, ImmutableArray<RazorCommitCharacter>)>.Ordinal.GetPooledObject(out var attributeCompletions);
92+
var inSnippetContext = InSnippetContext(containingAttribute, razorCompletionOptions);
8793

8894
foreach (var descriptor in descriptorsForTag)
8995
{
@@ -95,7 +101,7 @@ internal static ImmutableArray<RazorCompletionItem> GetAttributeCompletions(
95101
continue;
96102
}
97103

98-
if (!TryAddCompletion(attributeDescriptor.Name, attributeDescriptor, descriptor, razorCompletionOptions) && attributeDescriptor.Parameters.Length > 0)
104+
if (!TryAddCompletion(attributeDescriptor.Name, attributeDescriptor, descriptor, razorCompletionOptions, selectedAttributeName, attributes, inSnippetContext, attributeCompletions) && attributeDescriptor.Parameters.Length > 0)
99105
{
100106
// This attribute has parameters and the base attribute name (@bind) is already satisfied. We need to check if there are any valid
101107
// parameters left to be provided, if so, we need to still represent the base attribute name in the completion list.
@@ -105,15 +111,15 @@ internal static ImmutableArray<RazorCompletionItem> GetAttributeCompletions(
105111
if (!attributes.Any(name => TagHelperMatchingConventions.SatisfiesBoundAttributeWithParameter(parameterDescriptor, name, attributeDescriptor)))
106112
{
107113
// This bound attribute parameter has not had a completion entry added for it, re-represent the base attribute name in the completion list
108-
AddCompletion(attributeDescriptor.Name, attributeDescriptor, descriptor, razorCompletionOptions);
114+
AddCompletion(attributeDescriptor.Name, attributeDescriptor, descriptor, razorCompletionOptions, inSnippetContext, attributeCompletions);
109115
break;
110116
}
111117
}
112118
}
113119

114120
if (!attributeDescriptor.IndexerNamePrefix.IsNullOrEmpty())
115121
{
116-
TryAddCompletion(attributeDescriptor.IndexerNamePrefix + "...", attributeDescriptor, descriptor, razorCompletionOptions);
122+
TryAddCompletion(attributeDescriptor.IndexerNamePrefix + "...", attributeDescriptor, descriptor, razorCompletionOptions, selectedAttributeName, attributes, inSnippetContext, attributeCompletions);
117123
}
118124
}
119125
}
@@ -142,62 +148,62 @@ internal static ImmutableArray<RazorCompletionItem> GetAttributeCompletions(
142148
else
143149
{
144150
// We are trying for snippet text only for non-indexer attributes, e.g. *not* something like "@bind-..."
145-
if (TryGetSnippetText(containingAttribute, insertTextSpan, razorCompletionOptions, out var snippetTextSpan))
151+
if (inSnippetContext)
146152
{
147-
insertTextSpan = snippetTextSpan;
153+
GetSnippetText(insertTextSpan, razorCompletionOptions, out insertTextSpan);
148154
isSnippet = true;
149155
}
150156
}
151157

152-
// Don't create another string annecessarily, even thouth ReadOnlySpan.ToString() special-cases the string to avoid allocation
158+
// Don't create another string unnecessarily, even though ReadOnlySpan.ToString() special-cases the string to avoid allocation
153159
var insertText = insertTextSpan == originalInsertTextSpan ? displayText : insertTextSpan.ToString();
154160

155-
using var razorCommitCharacters = new PooledArrayBuilder<RazorCommitCharacter>(capacity: commitCharacters.Count);
156-
157-
foreach (var c in commitCharacters)
158-
{
159-
razorCommitCharacters.Add(new(c));
160-
}
161-
162161
var razorCompletionItem = RazorCompletionItem.CreateDirectiveAttribute(
163162
displayText,
164163
insertText,
165-
descriptionInfo: new([.. attributeDescriptions]),
166-
commitCharacters: razorCommitCharacters.ToImmutableAndClear(),
164+
descriptionInfo: new(attributeDescriptions),
165+
commitCharacters,
167166
isSnippet);
168167

169168
completionItems.Add(razorCompletionItem);
170169
}
171170

172171
return completionItems.ToImmutableAndClear();
173172

174-
static bool TryGetSnippetText(
173+
static bool InSnippetContext(
175174
RazorSyntaxNode owner,
176-
ReadOnlySpan<char> baseTextSpan,
177-
RazorCompletionOptions razorCompletionOptions,
178-
out ReadOnlySpan<char> snippetTextSpan)
175+
RazorCompletionOptions razorCompletionOptions)
179176
{
180-
if (razorCompletionOptions.SnippetsSupported
177+
return razorCompletionOptions.SnippetsSupported
181178
// Don't create snippet text when attribute is already in the tag and we are trying to replace it
182179
// Otherwise you could have something like @onabort=""=""
183180
&& owner is not (MarkupTagHelperDirectiveAttributeSyntax or MarkupAttributeBlockSyntax)
184-
&& owner.Parent is not (MarkupTagHelperDirectiveAttributeSyntax or MarkupAttributeBlockSyntax))
185-
{
186-
var suffixTextSpan = razorCompletionOptions.AutoInsertAttributeQuotes ? QuotedAttributeValueSnippet : UnquotedAttributeValueSnippet;
181+
&& owner.Parent is not (MarkupTagHelperDirectiveAttributeSyntax or MarkupAttributeBlockSyntax);
182+
}
187183

188-
var buffer = new char[baseTextSpan.Length + suffixTextSpan.Length];
189-
baseTextSpan.CopyTo(buffer);
190-
suffixTextSpan.CopyTo(buffer.AsMemory()[baseTextSpan.Length..]);
184+
static void GetSnippetText(
185+
ReadOnlySpan<char> baseTextSpan,
186+
RazorCompletionOptions razorCompletionOptions,
187+
out ReadOnlySpan<char> snippetTextSpan)
188+
{
189+
var suffixTextSpan = razorCompletionOptions.AutoInsertAttributeQuotes ? QuotedAttributeValueSnippet : UnquotedAttributeValueSnippet;
191190

192-
snippetTextSpan = buffer.AsSpan();
193-
return true;
194-
}
191+
var buffer = new char[baseTextSpan.Length + suffixTextSpan.Length];
192+
baseTextSpan.CopyTo(buffer);
193+
suffixTextSpan.CopyTo(buffer.AsMemory()[baseTextSpan.Length..]);
195194

196-
snippetTextSpan = [];
197-
return false;
195+
snippetTextSpan = buffer.AsSpan();
198196
}
199197

200-
bool TryAddCompletion(string attributeName, BoundAttributeDescriptor boundAttributeDescriptor, TagHelperDescriptor tagHelperDescriptor, RazorCompletionOptions razorCompletionOptions)
198+
static bool TryAddCompletion(
199+
string attributeName,
200+
BoundAttributeDescriptor boundAttributeDescriptor,
201+
TagHelperDescriptor tagHelperDescriptor,
202+
RazorCompletionOptions razorCompletionOptions,
203+
string selectedAttributeName,
204+
ImmutableArray<string> attributes,
205+
bool inSnippetContext,
206+
Dictionary<string, (ImmutableArray<BoundAttributeDescriptionInfo>, ImmutableArray<RazorCommitCharacter>)> attributeCompletions)
201207
{
202208
if (selectedAttributeName != attributeName &&
203209
attributes.Any(attributeName, static (name, attributeName) => name == attributeName))
@@ -207,60 +213,85 @@ bool TryAddCompletion(string attributeName, BoundAttributeDescriptor boundAttrib
207213
return false;
208214
}
209215

210-
AddCompletion(attributeName, boundAttributeDescriptor, tagHelperDescriptor, razorCompletionOptions);
216+
AddCompletion(attributeName, boundAttributeDescriptor, tagHelperDescriptor, razorCompletionOptions, inSnippetContext, attributeCompletions);
211217
return true;
212218
}
213219

214-
void AddCompletion(string attributeName, BoundAttributeDescriptor boundAttributeDescriptor, TagHelperDescriptor tagHelperDescriptor, RazorCompletionOptions razorCompletionOptions)
220+
static void AddCompletion(
221+
string attributeName,
222+
BoundAttributeDescriptor boundAttributeDescriptor,
223+
TagHelperDescriptor tagHelperDescriptor,
224+
RazorCompletionOptions razorCompletionOptions,
225+
bool inSnippetContext,
226+
Dictionary<string, (ImmutableArray<BoundAttributeDescriptionInfo>, ImmutableArray<RazorCommitCharacter>)> attributeCompletions)
215227
{
216228
if (!attributeCompletions.TryGetValue(attributeName, out var attributeDetails))
217229
{
218230
attributeDetails = ([], []);
219-
attributeCompletions[attributeName] = attributeDetails;
220231
}
221232

222233
(var attributeDescriptions, var commitCharacters) = attributeDetails;
223234

224235
var indexerCompletion = attributeName.EndsWith("...", StringComparison.Ordinal);
225236
var tagHelperTypeName = tagHelperDescriptor.TypeName;
226237
var descriptionInfo = BoundAttributeDescriptionInfo.From(boundAttributeDescriptor, isIndexer: indexerCompletion, tagHelperTypeName);
227-
attributeDescriptions.Add(descriptionInfo);
228238

229-
if (indexerCompletion)
239+
if (!attributeDescriptions.Contains(descriptionInfo))
230240
{
231-
// Indexer attribute, we don't want to commit with standard chars
232-
return;
241+
attributeDescriptions = attributeDescriptions.Add(descriptionInfo);
233242
}
234243

235-
if (!razorCompletionOptions.UseVsCodeCompletionCommitCharacters)
244+
// Verify not an indexer attribute, as those don't commit with standard chars
245+
if (!indexerCompletion)
236246
{
237-
// We don't add "=" as a commit character when using VSCode trigger characters.
238-
commitCharacters.Add("=");
239-
}
247+
var equalsAdded = commitCharacters.Any(static c => c.Character == "=");
248+
var spaceAdded = commitCharacters.Any(static c => c.Character == " ");
249+
var colonAdded = commitCharacters.Any(static c => c.Character == ":");
240250

241-
var spaceAdded = commitCharacters.Contains(" ");
242-
var colonAdded = commitCharacters.Contains(":");
251+
// We don't add "=" as a commit character when using VSCode trigger characters.
252+
equalsAdded |= !razorCompletionOptions.UseVsCodeCompletionCommitCharacters;
243253

244-
if (!spaceAdded || !colonAdded)
245-
{
246254
foreach (var boundAttribute in tagHelperDescriptor.BoundAttributes)
247255
{
248-
if (!spaceAdded && boundAttribute.IsBooleanProperty)
256+
spaceAdded |= boundAttribute.IsBooleanProperty;
257+
colonAdded |= boundAttribute.Parameters.Length > 0;
258+
259+
if (spaceAdded && colonAdded)
260+
{
261+
break;
262+
}
263+
}
264+
265+
// Determine if we have a common commit character set
266+
commitCharacters = (equalsAdded, spaceAdded, colonAdded, inSnippetContext) switch
267+
{
268+
(true, false, false, false) => EqualsCommitCharacters,
269+
(true, false, true, false) => EqualsAndColonCommitCharacters,
270+
(true, false, false, true) => SnippetEqualsCommitCharacters,
271+
(true, false, true, true) => SnippetEqualsAndColonCommitCharacters,
272+
_ => []
273+
};
274+
275+
if (commitCharacters.IsEmpty)
276+
{
277+
if (equalsAdded)
249278
{
250-
commitCharacters.Add(" ");
251-
spaceAdded = true;
279+
commitCharacters = commitCharacters.Add(new("=", Insert: !inSnippetContext));
252280
}
253-
else if (!colonAdded && boundAttribute.Parameters.Length > 0)
281+
282+
if (spaceAdded)
254283
{
255-
commitCharacters.Add(":");
256-
colonAdded = true;
284+
commitCharacters = commitCharacters.Add(new(" "));
257285
}
258-
else if (spaceAdded && colonAdded)
286+
287+
if (colonAdded)
259288
{
260-
break;
289+
commitCharacters = commitCharacters.Add(new(":"));
261290
}
262291
}
263292
}
293+
294+
attributeCompletions[attributeName] = (attributeDescriptions, commitCharacters);
264295
}
265296
}
266297
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
using System;
55
using System.Threading;
66

7-
namespace Microsoft.VisualStudio.Razor.Logging;
7+
namespace Microsoft.CodeAnalysis.Razor.Logging;
88

9-
internal partial class MemoryLoggerProvider
9+
internal partial class AbstractMemoryLoggerProvider
1010
{
1111
/// <summary>
1212
/// A circular in memory buffer to store logs in memory.
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5-
using Microsoft.CodeAnalysis.Razor.Logging;
65

7-
namespace Microsoft.VisualStudio.Razor.Logging;
6+
namespace Microsoft.CodeAnalysis.Razor.Logging;
87

9-
internal partial class MemoryLoggerProvider
8+
internal partial class AbstractMemoryLoggerProvider
109
{
1110
private class Logger(Buffer buffer, string categoryName) : ILogger
1211
{
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.CodeAnalysis.Razor.Logging;
5+
6+
internal abstract partial class AbstractMemoryLoggerProvider : ILoggerProvider
7+
{
8+
// How many messages will the buffer contain
9+
private const int BufferSize = 5000;
10+
private readonly Buffer _buffer = new(BufferSize);
11+
12+
public ILogger CreateLogger(string categoryName)
13+
=> new Logger(_buffer, categoryName);
14+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.CodeAnalysis.Razor.Logging;
5+
6+
namespace Microsoft.CodeAnalysis.Remote.Razor.Logging;
7+
8+
internal sealed class MemoryLoggerProvider : AbstractMemoryLoggerProvider
9+
{
10+
}

src/razor/src/Razor/src/Microsoft.CodeAnalysis.Remote.Razor/Logging/TraceSourceLoggerFactory.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77
namespace Microsoft.CodeAnalysis.Remote.Razor.Logging;
88

9-
internal sealed partial class TraceSourceLoggerFactory(TraceSource traceSource) : AbstractLoggerFactory([new LoggerProvider(traceSource)])
9+
internal sealed partial class TraceSourceLoggerFactory(TraceSource traceSource)
10+
: AbstractLoggerFactory([
11+
new LoggerProvider(traceSource),
12+
new MemoryLoggerProvider()])
1013
{
1114
public TraceSource TraceSource { get; } = traceSource;
1215
}

0 commit comments

Comments
 (0)