Skip to content
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
b7318df
Track declaration Razor C# documents
davidwengier Jun 9, 2026
e8b0e11
Unwrap Razor CodeLens resolve data
davidwengier Jun 9, 2026
286c492
Use declaration document for Razor CodeLens
davidwengier Jun 9, 2026
a88a999
Add legacy Razor CodeLens tests
davidwengier Jun 9, 2026
aef113b
Simplify Razor semantic tokens
davidwengier Jun 9, 2026
17a367c
Make Razor document symbols SONIC-aware
davidwengier Jun 9, 2026
f0a760c
Add Razor document symbol SONIC coverage
davidwengier Jun 9, 2026
47254eb
Make Razor selection ranges SONIC-aware
davidwengier Jun 9, 2026
8d16ffc
Add Razor selection range SONIC coverage
davidwengier Jun 9, 2026
7c5c384
Fix CI issues
davidwengier Jun 9, 2026
427e60e
Make Razor spell check SONIC-aware
davidwengier Jun 9, 2026
9f61e1d
Add SONIC spell check tests
davidwengier Jun 9, 2026
b546c73
Make Razor inline completion SONIC-aware
davidwengier Jun 9, 2026
3f2934d
Add SONIC inline completion tests
davidwengier Jun 9, 2026
e363e6a
Make Razor type hierarchy SONIC-aware
davidwengier Jun 9, 2026
e8c348a
Add SONIC type hierarchy tests
davidwengier Jun 9, 2026
5413ad3
Make Razor go to implementation SONIC-aware
davidwengier Jun 9, 2026
495fb29
Add SONIC go to implementation tests
davidwengier Jun 9, 2026
8d540df
Make Razor find all references SONIC-aware
davidwengier Jun 9, 2026
bfc7b72
Add SONIC find all references tests
davidwengier Jun 9, 2026
b171b00
Move document and edit mapping code up to Remote.Razor
davidwengier Jun 10, 2026
844035f
Move resource
davidwengier Jun 10, 2026
c9b000f
Delete unused file
davidwengier Jun 10, 2026
db79a4d
Move MEF exports to the real classes, and remove indirection
davidwengier Jun 10, 2026
43d93eb
Rename file
davidwengier Jun 10, 2026
1e8434a
Strongly type and simplify logic, now that there is no abstraction to…
davidwengier Jun 10, 2026
9fa0075
Make edit service and mapping service handle multiple C# documents
davidwengier Jun 10, 2026
db803ac
Move document position info code
davidwengier Jun 10, 2026
b41ce6b
Remove unused Razor using directive
davidwengier Jun 10, 2026
6b65bbb
Move formatting files up to Remote.Razor
davidwengier Jun 10, 2026
3796b77
Move almost all of FormattingUtilities
davidwengier Jun 10, 2026
d60bd03
Move C# formatting options helper back a to shared location
davidwengier Jun 10, 2026
e4059b1
Move trigger characters to where they're actually used
davidwengier Jun 10, 2026
b63641b
Fix namespaces and usings
davidwengier Jun 10, 2026
8f4a32d
Add IVT
davidwengier Jun 10, 2026
e46ed32
Move resources
davidwengier Jun 10, 2026
51db216
Update tests
davidwengier Jun 10, 2026
24d5d26
Move MEF attributes and remote unnecessary indirection
davidwengier Jun 10, 2026
74d6760
Unskip tests
davidwengier Jun 11, 2026
62c0956
Make formatting sonic compatible
davidwengier Jun 11, 2026
df11094
Update dependent services so they at least compile
davidwengier Jun 11, 2026
a4948b7
Fix "Show Formatting Document" feature
davidwengier Jun 11, 2026
4ec51a3
Unskip tests (and add one)
davidwengier Jun 11, 2026
f1cbb12
Remove stale comment
davidwengier Jun 11, 2026
e384ef0
Fix benchmark
davidwengier Jun 11, 2026
cffd735
Make Razor inlay hints SONIC-aware
davidwengier Jun 11, 2026
e5affc8
Add SONIC inlay hint tests
davidwengier Jun 11, 2026
d758621
Make Razor folding ranges SONIC-aware
davidwengier Jun 11, 2026
2057517
Add SONIC folding range tests
davidwengier Jun 11, 2026
3eb3b12
Move files from Workspaces to Remote and unify namespaces
davidwengier Jun 11, 2026
c9d98ae
Move MEF export
davidwengier Jun 11, 2026
bff73ca
Rename file
davidwengier Jun 11, 2026
577e795
Make Razor go to definition SONIC-aware
davidwengier Jun 11, 2026
4c73cf3
Correctness
davidwengier Jun 11, 2026
87ff5ca
[Sonic] Inlay hints, folding ranges, and go to definition (#84102)
davidwengier Jun 12, 2026
11d5cb6
[Sonic] Support multiple C# documents in formatting (#84097)
davidwengier Jun 12, 2026
d00caf2
[Sonic] Move edit service up to Remote and make it sonic aware (#84086)
davidwengier Jun 12, 2026
f877407
[Sonic] Make even more Razor features to be sonic compatible (#84075)
davidwengier Jun 12, 2026
6f75f9b
Fix grammatical error
davidwengier Jun 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ protected override RazorCodeDocument ExecuteCore(RazorCodeDocument codeDocument,
// The decl writer suppresses diagnostic collection because every diagnostic on the
// decl tree is also reachable from the original tree the impl half lowers from;
// letting both writers report would surface every Razor-detected issue twice.
var declDocument = RazorCSharpDocumentWriter.Write(declDocNode, codeDocument, reportDiagnostics: false, cancellationToken);
var declDocument = RazorCSharpDocumentWriter.Write(declDocNode, codeDocument, reportDiagnostics: false, isDeclarationDocument: true, cancellationToken: cancellationToken);

return codeDocument.WithDeclCSharpDocument(declDocument);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ public sealed class RazorCSharpDocument
public ImmutableArray<SourceMapping> SourceMappingsSortedByGenerated { get; }
public ImmutableArray<SourceMapping> SourceMappingsSortedByOriginal { get; }
public ImmutableArray<LinePragma> LinePragmas { get; }
public bool IsDeclarationDocument { get; }

public RazorCSharpDocument(
RazorCodeDocument codeDocument,
SourceText text,
ImmutableArray<RazorDiagnostic> diagnostics,
ImmutableArray<SourceMapping> sourceMappings = default,
ImmutableArray<LinePragma> linePragmas = default)
ImmutableArray<LinePragma> linePragmas = default,
bool isDeclarationDocument = false)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bool isDeclarationDocument = false

I assume we don't see there being any additional differences needed between decl/impl docs? If there were to be, it might be worth considering whether this could be an abstract base class, having impl and decl derivations.

@davidwengier davidwengier Jun 9, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if anything, we could probably simplify this a lot more. The SourceText property used to be the only way to get the C# text, but with cohosting we can get it from the Roslyn solution snapshot. The diagnostics property is weird in sonic, since currently both documents duplicate all diagnostics, so I could see those moving up to RazorCodeDocument. I'm pretty sure linePragmas is only used for tests. Which means a RazorCSharpDocument could become a simple record that just stores source mappings and whether its a declaration document. In fact, I could see an argument for source mappings to store isDeclarationDocument, and have them just be a property on RazorCodeDocument and make this unnecessary altogether. Though I've no idea what impact that would have on the compiler, I'm not as familiar with that codebase.

I definitely still want to improve the architecture here, along with the DocumentContext and DocumentSnapshot stuff, but the longer the sonic feature branch lives, the more stressed I become about conflicts.

Comment thread
davidwengier marked this conversation as resolved.
Resolved
{
Comment thread
davidwengier marked this conversation as resolved.
Resolved
ArgHelper.ThrowIfNull(codeDocument);
ArgHelper.ThrowIfNull(text);
Expand All @@ -48,5 +50,6 @@ public RazorCSharpDocument(

SourceMappingsSortedByOriginal = SourceMappingsSortedByGenerated.Sort(static (m1, m2) => m1.OriginalSpan.CompareByStartThenLength(m2.OriginalSpan));
LinePragmas = linePragmas.NullToEmpty();
IsDeclarationDocument = isDeclarationDocument;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public static RazorCSharpDocument Write(
DocumentIntermediateNode documentNode,
RazorCodeDocument codeDocument,
bool reportDiagnostics = true,
bool isDeclarationDocument = false,
CancellationToken cancellationToken = default)
{
ArgHelper.ThrowIfNull(codeDocument);
Expand All @@ -44,7 +45,8 @@ public static RazorCSharpDocument Write(
text,
context.GetDiagnostics(),
context.GetSourceMappings(),
context.GetLinePragmas());
context.GetLinePragmas(),
isDeclarationDocument);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ public void Setup()
hostServicesProvider.SetWorkspaceProvider(new WorkspaceProvider(_workspace));

var clientSettingsManager = new RemoteClientSettingsManager();
var documentMappingService = new RemoteDocumentMappingService(filePathService, snapshotManager, EmptyLoggerFactory.Instance);
var razorEditService = new RemoteRazorEditService(documentMappingService, clientSettingsManager, filePathService, snapshotManager, NoOpTelemetryReporter.Instance);
var documentMappingService = new DocumentMappingService(filePathService, snapshotManager, EmptyLoggerFactory.Instance);
var razorEditService = new RazorEditService(documentMappingService, clientSettingsManager, filePathService, snapshotManager, NoOpTelemetryReporter.Instance);

_formattingService = new RemoteRazorFormattingService(
_formattingService = new RazorFormattingService(
documentMappingService,
razorEditService,
hostServicesProvider,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie

protected override Task<TextEdit[]?> HandleRequestAsync(DocumentFormattingParams request, TextDocument razorDocument, CancellationToken cancellationToken)
{
var csharpSyntaxFormattingOptions = CSharpFormatter.GetCSharpSyntaxFormattingOptions(razorDocument.Project.Solution.Services, csharpSyntaxFormattingOptions: null);
var csharpSyntaxFormattingOptions = CSharpFormattingOptionsHelper.GetCSharpSyntaxFormattingOptions(razorDocument.Project.Solution.Services, csharpSyntaxFormattingOptions: null);
return HandleRequestAsync(request, razorDocument, csharpSyntaxFormattingOptions, cancellationToken);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.CodeAnalysis.Razor.Workspaces.Settings;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Frozen;
using System;

namespace Microsoft.VisualStudio.Razor.LanguageClient.Cohost;

Expand All @@ -33,6 +35,10 @@ internal sealed class CohostOnTypeFormattingEndpoint(
ILoggerFactory loggerFactory)
: AbstractCohostDocumentEndpoint<DocumentOnTypeFormattingParams, TextEdit[]?>(incompatibleProjectService), IDynamicRegistrationProvider
{
internal const string FirstTriggerCharacter = "}";
internal static readonly string[] MoreTriggerCharacters = [";", "\n", "{"];
internal static readonly FrozenSet<string> AllTriggerCharacterSet = FrozenSet.ToFrozenSet([FirstTriggerCharacter, .. MoreTriggerCharacters], StringComparer.Ordinal);

private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker;
private readonly IHtmlRequestInvoker _requestInvoker = requestInvoker;
private readonly IClientSettingsManager _clientSettingsManager = clientSettingsManager;
Expand All @@ -51,8 +57,8 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
Method = Methods.TextDocumentOnTypeFormattingName,
RegisterOptions = new DocumentOnTypeFormattingRegistrationOptions()
{
FirstTriggerCharacter = RazorFormattingService.FirstTriggerCharacter,
MoreTriggerCharacter = RazorFormattingService.MoreTriggerCharacters
FirstTriggerCharacter = FirstTriggerCharacter,
MoreTriggerCharacter = MoreTriggerCharacters
}
}];
}
Expand All @@ -72,7 +78,7 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
return null;
}

if (!RazorFormattingService.AllTriggerCharacterSet.Contains(request.Character))
if (!AllTriggerCharacterSet.Contains(request.Character))
{
_logger.LogWarning($"Unexpected trigger character '{request.Character}'.");
return null;
Expand Down Expand Up @@ -112,7 +118,7 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
htmlChanges = htmlEdits.SelectAsArray(sourceText.GetTextChange);
}

var csharpSyntaxFormattingOptions = CSharpFormatter.GetCSharpSyntaxFormattingOptions(razorDocument.Project.Solution.Services, csharpSyntaxFormattingOptions: null);
var csharpSyntaxFormattingOptions = CSharpFormattingOptionsHelper.GetCSharpSyntaxFormattingOptions(razorDocument.Project.Solution.Services, csharpSyntaxFormattingOptions: null);
var options = RazorFormattingOptions.From(request.Options, clientSettings.AdvancedSettings.CodeBlockBraceOnNextLine, clientSettings.AdvancedSettings.AttributeIndentStyle, csharpSyntaxFormattingOptions);

_logger.LogDebug($"Calling OOP with the {htmlChanges.Length} html edits, so it can fill in the rest");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public ImmutableArray<Registration> GetRegistrations(VSInternalClientCapabilitie
var sourceText = await razorDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
var htmlChanges = htmlEdits.SelectAsArray(sourceText.GetTextChange);

var csharpSyntaxFormattingOptions = CSharpFormatter.GetCSharpSyntaxFormattingOptions(razorDocument.Project.Solution.Services, csharpSyntaxFormattingOptions: null);
var csharpSyntaxFormattingOptions = CSharpFormattingOptionsHelper.GetCSharpSyntaxFormattingOptions(razorDocument.Project.Solution.Services, csharpSyntaxFormattingOptions: null);
var options = RazorFormattingOptions.From(
request.Options,
_clientSettingsManager.GetClientSettings().AdvancedSettings.CodeBlockBraceOnNextLine,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ internal class CohostInlayHintResolveEndpoint(

var hint = await _remoteServiceInvoker.TryInvokeAsync<IRemoteInlayHintService, InlayHint>(
razorDocument.Project.Solution,
(service, solutionInfo, cancellationToken) => service.ResolveHintAsync(solutionInfo, razorDocument.Id, request, cancellationToken),
(service, solutionInfo, cancellationToken) => service.ResolveHintAsync(solutionInfo, razorDocument.Id, request, razorData.InDeclDocument, cancellationToken),
cancellationToken).ConfigureAwait(false);

if (hint is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ static string GetArgumentTypesLogString(VSInternalCompletionItem resolvedComplet
var formattedTextChange = await formattingService.TryGetCSharpSnippetFormattingEditAsync(
documentContext,
changes,
declarationDocument: false, // PROTOTYPE(sonic): Pass in the right value to this
options,
cancellationToken).ConfigureAwait(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ internal interface IRazorEditService
Task<ImmutableArray<RazorTextChange>> MapCSharpEditsAsync(
ImmutableArray<RazorTextChange> textChanges,
IDocumentSnapshot snapshot,
bool declarationDocument,
bool includeCSharpLanguageFeatureEdits,
Func<RazorTextChange, bool>? directlyMappedEditFilter,
CancellationToken cancellationToken);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Text;

Expand Down Expand Up @@ -48,23 +50,28 @@ public static SourceText GetCSharpSourceText(this RazorCodeDocument document)
/// keeps the behavior conservative.
/// </remarks>
public static RazorCSharpDocument GetCSharpDocumentForHintName(this RazorCodeDocument document, string hintName)
{
return GetCSharpDocumentForHintName(document, hintName, out _);
}

public static RazorCSharpDocument GetCSharpDocumentForHintName(this RazorCodeDocument document, string hintName, out bool isDeclDocument)
{
if (hintName.EndsWith(".decl.g.cs", System.StringComparison.Ordinal) &&
document.GetCSharpDocument(declarationDocument: true) is { } declDocument)
{
isDeclDocument = true;
return declDocument;
}

isDeclDocument = false;
return document.GetRequiredCSharpDocument(declarationDocument: false);
}

public static bool TryGetCSharpDocumentForGeneratedUri(this RazorCodeDocument codeDocument, Solution solution, Uri generatedDocumentUri, [NotNullWhen(true)] out RazorCSharpDocument? csharpDocument)
{
if (solution.TryGetSourceGeneratedDocumentIdentity(generatedDocumentUri, out var identity))
{
csharpDocument = GetCSharpDocumentForHintName(codeDocument, identity.HintName);
return true;
}

csharpDocument = null;
return false;
}

public static SourceText GetHtmlSourceText(this RazorCodeDocument document, CancellationToken cancellationToken)
=> GetCachedData(document).GetOrComputeHtmlDocument(cancellationToken).Text;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,4 +370,33 @@ public static ImmutableArray<TextChange> GetTextChangesArray(this SourceText new

return list.ToImmutableArray();
}

/// <summary>
/// Sometimes the Html language server will send back an edit that contains a tilde, because the generated
/// document we send them has lots of tildes. In those cases, we need to do some extra work to compute the
/// minimal text edits
/// </summary>
public static TextEdit[] FixHtmlTextEdits(this SourceText htmlSourceText, TextEdit[] edits)
{
// Avoid computing a minimal diff if we don't need to
if (!edits.Any(static e => e.NewText.Contains('~')))
return edits;

var changes = edits.SelectAsArray(htmlSourceText.GetTextChange);

var fixedChanges = htmlSourceText.MinimizeTextChanges(changes);
return fixedChanges.SelectAsPlainArray(htmlSourceText.GetTextEdit);
}

public static SumType<TextEdit, AnnotatedTextEdit>[] FixHtmlTextEdits(this SourceText htmlSourceText, SumType<TextEdit, AnnotatedTextEdit>[] edits)
{
// Avoid computing a minimal diff if we don't need to
if (!edits.Any(static e => ((TextEdit)e).NewText.Contains('~')))
return edits;

var changes = edits.SelectAsArray(e => htmlSourceText.GetTextChange((TextEdit)e));

var fixedChanges = htmlSourceText.MinimizeTextChanges(changes);
return fixedChanges.SelectAsPlainArray<TextChange, SumType<TextEdit, AnnotatedTextEdit>>(c => htmlSourceText.GetTextEdit(c));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.CodeAnalysis.CSharp.Formatting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;

namespace Microsoft.CodeAnalysis.Razor.Formatting;

internal static class CSharpFormattingOptionsHelper
{
internal static CSharpSyntaxFormattingOptions GetCSharpSyntaxFormattingOptions(
SolutionServices services,
CSharpSyntaxFormattingOptions? csharpSyntaxFormattingOptions)
{
csharpSyntaxFormattingOptions
??= (CSharpSyntaxFormattingOptions)(services.GetService<ILegacyGlobalOptionsWorkspaceService>()?.GetSyntaxFormattingOptions(services.GetLanguageServices(LanguageNames.CSharp))
?? CSharpSyntaxFormattingOptions.Default);

return csharpSyntaxFormattingOptions;
}

internal static CSharpSyntaxFormattingOptions GetResolvedCSharpSyntaxFormattingOptions(
SolutionServices services,
RazorFormattingOptions options,
CSharpSyntaxFormattingOptions? csharpSyntaxFormattingOptions = null)
{
csharpSyntaxFormattingOptions = GetCSharpSyntaxFormattingOptions(
services,
csharpSyntaxFormattingOptions ?? options.CSharpSyntaxFormattingOptions);

return csharpSyntaxFormattingOptions with
{
LineFormatting = csharpSyntaxFormattingOptions.LineFormatting with
{
UseTabs = !options.InsertSpaces,
TabSize = options.TabSize,
IndentationSize = options.TabSize,
NewLine = CSharpSyntaxFormattingOptions.Default.NewLine
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,27 @@ Task<ImmutableArray<TextChange>> GetCSharpOnTypeFormattingChangesAsync(
RazorFormattingOptions options,
int hostDocumentIndex,
char triggerCharacter,
bool declarationDocument,
CancellationToken cancellationToken);

Task<TextChange?> TryGetSingleCSharpEditAsync(
DocumentContext documentContext,
TextChange csharpEdit,
bool declarationDocument,
RazorFormattingOptions options,
CancellationToken cancellationToken);

Task<TextChange?> TryGetCSharpCodeActionEditAsync(
DocumentContext documentContext,
ImmutableArray<TextChange> csharpEdits,
bool declarationDocument,
RazorFormattingOptions options,
CancellationToken cancellationToken);

Task<TextChange?> TryGetCSharpSnippetFormattingEditAsync(
DocumentContext documentContext,
ImmutableArray<TextChange> csharpEdits,
bool declarationDocument,
RazorFormattingOptions options,
CancellationToken cancellationToken);

Expand Down
Loading
Loading