Skip to content

Add SpanMapping for VS Code #8225

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
# 2.76.x

# 2.75.x
* Bump Razor to 10.0.0-preview.25228.4 (PR: [#8225](https://github.com/dotnet/vscode-csharp/pull/8225))
* Don't return null if we couldn't sync the document for breakpoint validation (PR: [#11790](https://github.com/dotnet/razor/pull/11790))
* Add VS Code IMappingService (PR: [#11760](https://github.com/dotnet/razor/pull/11760))
* Fix cases where there is a space in the URI (PR: [#11745](https://github.com/dotnet/razor/pull/11745))
* Bump Roslyn to 5.0.0-1.25224.9 (PR: [#8211](https://github.com/dotnet/vscode-csharp/pull/8211))
* Update ICSharpCode.Decompiler to 9.1.0.7988(PR: [#78270](https://github.com/dotnet/roslyn/pull/78270))
* Reduce allocations in NamespaceSymbol.GetExtensionContainers(PR: [#78243](https://github.com/dotnet/roslyn/pull/78243))
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"defaults": {
"roslyn": "5.0.0-1.25224.9",
"omniSharp": "1.39.12",
"razor": "10.0.0-preview.25228.3",
"razor": "10.0.0-preview.25228.4",
"razorOmnisharp": "7.0.0-preview.23363.1",
"xamlTools": "17.14.36010.33"
},
Expand Down
89 changes: 66 additions & 23 deletions src/lsptoolshost/razor/razorEndpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,51 +24,94 @@ import { DocumentColorHandler } from '../../razor/src/documentColor/documentColo
import { razorOptions } from '../../shared/options';
import { ColorPresentationHandler } from '../../razor/src/colorPresentation/colorPresentationHandler';
import { ColorPresentation } from 'vscode-html-languageservice';
import { DynamicFileInfoHandler } from '../../razor/src/dynamicFile/dynamicFileInfoHandler';
import { ProvideDynamicFileParams } from '../../razor/src/dynamicFile/provideDynamicFileParams';
import { ProvideDynamicFileResponse } from '../../razor/src/dynamicFile/provideDynamicFileResponse';
import { RazorMapSpansParams } from '../../razor/src/mapping/razorMapSpansParams';
import { RazorMapSpansResponse } from '../../razor/src/mapping/razorMapSpansResponse';
import { MappingHandler } from '../../razor/src/mapping/mappingHandler';
import { RazorMapTextChangesParams } from '../../razor/src/mapping/razorMapTextChangesParams';
import { RazorMapTextChangesResponse } from '../../razor/src/mapping/razorMapTextChangesResponse';

export function registerRazorEndpoints(
context: vscode.ExtensionContext,
languageServer: RoslynLanguageServer,
roslynLanguageServer: RoslynLanguageServer,
razorLogger: RazorLogger,
platformInfo: PlatformInformation
) {
const logNotificationType = new NotificationType<LogMessageParams>('razor/log');
languageServer.registerOnNotificationWithParams(logNotificationType, (params) =>
roslynLanguageServer.registerOnNotificationWithParams(logNotificationType, (params) =>
razorLogger.log(params.message, params.type)
);

if (!razorOptions.cohostingEnabled) {
return;
if (razorOptions.cohostingEnabled) {
registerCohostingEndpoints();
} else {
registerNonCohostingEndpoints();
}

const documentManager = new HtmlDocumentManager(platformInfo, razorLogger);
context.subscriptions.push(documentManager.register());
return;

registerRequestHandler<HtmlUpdateParameters, void>('razor/updateHtml', async (params) => {
const uri = UriConverter.deserialize(params.textDocument.uri);
await documentManager.updateDocumentText(uri, params.text);
});
//
// Local Functions
//
function registerCohostingEndpoints() {
const documentManager = new HtmlDocumentManager(platformInfo, razorLogger);
context.subscriptions.push(documentManager.register());

registerRequestHandler<DocumentColorParams, ColorInformation[]>(DocumentColorRequest.method, async (params) => {
const uri = UriConverter.deserialize(params.textDocument.uri);
const document = await documentManager.getDocument(uri);

return await DocumentColorHandler.doDocumentColorRequest(document.uri);
});
registerRequestHandler<HtmlUpdateParameters, void>('razor/updateHtml', async (params) => {
const uri = UriConverter.deserialize(params.textDocument.uri);
await documentManager.updateDocumentText(uri, params.text);
});

registerRequestHandler<ColorPresentationParams, ColorPresentation[]>(
ColorPresentationRequest.method,
async (params) => {
registerRequestHandler<DocumentColorParams, ColorInformation[]>(DocumentColorRequest.method, async (params) => {
const uri = UriConverter.deserialize(params.textDocument.uri);
const document = await documentManager.getDocument(uri);

return await ColorPresentationHandler.doColorPresentationRequest(document.uri, params);
}
);
return await DocumentColorHandler.doDocumentColorRequest(document.uri);
});

registerRequestHandler<ColorPresentationParams, ColorPresentation[]>(
ColorPresentationRequest.method,
async (params) => {
const uri = UriConverter.deserialize(params.textDocument.uri);
const document = await documentManager.getDocument(uri);

return await ColorPresentationHandler.doColorPresentationRequest(document.uri, params);
}
);
}

function registerNonCohostingEndpoints() {
registerRequestHandler<ProvideDynamicFileParams, ProvideDynamicFileResponse>(
'razor/provideDynamicFileInfo',
async (params) =>
vscode.commands.executeCommand(DynamicFileInfoHandler.provideDynamicFileInfoCommand, params)
);

registerRequestHandler<ProvideDynamicFileParams, ProvideDynamicFileResponse>(
'razor/removeDynamicFileInfo',
async (params) =>
vscode.commands.executeCommand(DynamicFileInfoHandler.provideDynamicFileInfoCommand, params)
);
registerRequestHandler<RazorMapSpansParams, RazorMapSpansResponse>('razor/mapSpans', async (params) => {
return await vscode.commands.executeCommand<RazorMapSpansResponse>(MappingHandler.MapSpansCommand, params);
});
registerRequestHandler<RazorMapTextChangesParams, RazorMapTextChangesResponse>(
'razor/mapTextChanges',
async (params) => {
return await vscode.commands.executeCommand<RazorMapTextChangesResponse>(
MappingHandler.MapChangesCommand,
params
);
}
);
}

// Helper method that registers a request handler, and logs errors to the Razor logger.
function registerRequestHandler<Params, Result>(method: string, invocation: (params: Params) => Promise<Result>) {
const requestType = new RequestType<Params, Result, Error>(method);
languageServer.registerOnRequest(requestType, async (params) => {
roslynLanguageServer.registerOnRequest(requestType, async (params) => {
try {
return await invocation(params);
} catch (error) {
Expand Down
12 changes: 12 additions & 0 deletions src/razor/src/document/razorDocumentManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ export class RazorDocumentManager implements IRazorDocumentManager {
return document;
}

public async getDocumentForCSharpUri(csharpUri: vscode.Uri): Promise<IRazorDocument | undefined> {
const csharpPath = csharpUri.fsPath ?? csharpUri.path;

return this.documents.find((document) => {
if (this.platformInfo.isLinux()) {
return document.csharpDocument.path === csharpPath;
}

return document.csharpDocument.path.localeCompare(csharpPath, undefined, { sensitivity: 'base' }) === 0;
});
}

public async getActiveDocument(): Promise<IRazorDocument | null> {
if (!vscode.window.activeTextEditor) {
return null;
Expand Down
28 changes: 16 additions & 12 deletions src/razor/src/dynamicFile/dynamicFileInfoHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { RazorDynamicFileChangedParams } from './dynamicFileUpdatedParams';
import { TextDocumentIdentifier } from 'vscode-languageserver-protocol';
import { razorTextChange } from './razorTextChange';
import { razorTextSpan } from './razorTextSpan';
import { razorOptions } from '../../../shared/options';

// Handles Razor generated doc communication between the Roslyn workspace and Razor.
// didChange behavior for Razor generated docs is handled in the RazorDocumentManager.
Expand All @@ -39,18 +40,21 @@ export class DynamicFileInfoHandler {
await this.removeDynamicFileInfo(request);
}
);
this.documentManager.onChange(async (e) => {
// Ignore any updates without text changes. This is important for perf since sending an update to roslyn does
// a round trip for producing nothing new and causes a lot of churn in solution updates.
if (e.kind == RazorDocumentChangeKind.csharpChanged && !e.document.isOpen && e.changes.length > 0) {
const uriString = UriConverter.serialize(e.document.uri);
const identifier = TextDocumentIdentifier.create(uriString);
await vscode.commands.executeCommand(
DynamicFileInfoHandler.dynamicFileUpdatedCommand,
new RazorDynamicFileChangedParams(identifier)
);
}
});

if (!razorOptions.cohostingEnabled) {
this.documentManager.onChange(async (e) => {
// Ignore any updates without text changes. This is important for perf since sending an update to roslyn does
// a round trip for producing nothing new and causes a lot of churn in solution updates.
if (e.kind == RazorDocumentChangeKind.csharpChanged && !e.document.isOpen && e.changes.length > 0) {
const uriString = UriConverter.serialize(e.document.uri);
const identifier = TextDocumentIdentifier.create(uriString);
await vscode.commands.executeCommand(
DynamicFileInfoHandler.dynamicFileUpdatedCommand,
new RazorDynamicFileChangedParams(identifier)
);
}
});
}
}

// Given Razor document URIs, returns associated generated doc URIs
Expand Down
8 changes: 6 additions & 2 deletions src/razor/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { InlayHintHandler } from './inlayHint/inlayHintHandler';
import { InlayHintResolveHandler } from './inlayHint/inlayHintResolveHandler';
import { getComponentPaths } from '../../lsptoolshost/extensions/builtInComponents';
import { BlazorDebugConfigurationProvider } from './blazorDebug/blazorDebugConfigurationProvider';
import { MappingHandler } from './mapping/mappingHandler';

// We specifically need to take a reference to a particular instance of the vscode namespace,
// otherwise providers attempt to operate on the null extension.
Expand Down Expand Up @@ -123,15 +124,15 @@ export async function activate(
logger
);

const languageServiceClient = new RazorLanguageServiceClient(languageServerClient);

const documentManager = new RazorDocumentManager(
languageServerClient,
logger,
razorTelemetryReporter,
platformInfo
);

const languageServiceClient = new RazorLanguageServiceClient(languageServerClient, documentManager);

const documentSynchronizer = new RazorDocumentSynchronizer(documentManager, logger);
reportTelemetryForDocuments(documentManager, razorTelemetryReporter);
const languageConfiguration = new RazorLanguageConfiguration();
Expand Down Expand Up @@ -261,6 +262,8 @@ export async function activate(
logger
);

const mappingHandler = new MappingHandler(languageServiceClient);

localRegistrations.push(
languageConfiguration.register(),
vscodeType.languages.registerSignatureHelpProvider(RazorLanguage.id, signatureHelpProvider, '(', ','),
Expand Down Expand Up @@ -298,6 +301,7 @@ export async function activate(
completionHandler.register(),
razorSimplifyMethodHandler.register(),
razorFormatNewFileHandler.register(),
mappingHandler.register(),
]);
});

Expand Down
32 changes: 32 additions & 0 deletions src/razor/src/mapping/mappingHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from 'vscode';
import { RazorLanguageServiceClient } from '../razorLanguageServiceClient';
import { RazorMapSpansParams } from './razorMapSpansParams';
import { RazorMapTextChangesParams } from './razorMapTextChangesParams';

export class MappingHandler {
public static readonly MapSpansCommand = 'razor.mapSpansCommand';
public static readonly MapChangesCommand = 'razor.mapChangesCommand';

constructor(private readonly languageServiceClient: RazorLanguageServiceClient) {}

public async register(): Promise<vscode.Disposable> {
return vscode.Disposable.from(
...[
vscode.commands.registerCommand(MappingHandler.MapSpansCommand, async (params: RazorMapSpansParams) => {
return this.languageServiceClient.mapSpans(params);
}),
vscode.commands.registerCommand(
MappingHandler.MapChangesCommand,
async (params: RazorMapTextChangesParams) => {
return this.languageServiceClient.mapTextChanges(params);
}
),
]
);
}
}
2 changes: 1 addition & 1 deletion src/razor/src/mapping/mappingHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { RazorLanguageServiceClient } from '../razorLanguageServiceClient';
import { RazorLogger } from '../razorLogger';

export class MappingHelpers {
public readonly language = 'Razor';
public static readonly language = 'Razor';

public static async remapGeneratedFileWorkspaceEdit(
workspaceEdit: vscode.WorkspaceEdit,
Expand Down
11 changes: 11 additions & 0 deletions src/razor/src/mapping/razorMapSpansParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Range, TextDocumentIdentifier } from 'vscode-languageserver-protocol';

// Matches https://github.com/dotnet/razor/blob/main/src/Razor/src/Microsoft.VisualStudioCode.RazorExtension/Services/RazorMapSpansParams.cs
export class RazorMapSpansParams {
constructor(public readonly csharpDocument: TextDocumentIdentifier, public readonly ranges: Range[]) {}
}
19 changes: 19 additions & 0 deletions src/razor/src/mapping/razorMapSpansResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { TextDocumentIdentifier } from 'vscode-languageserver-types';
import { razorTextSpan } from '../dynamicFile/razorTextSpan';
import { SerializableRange } from '../rpc/serializableRange';

// matches https://github.com/dotnet/razor/blob/main/src/Razor/src/Microsoft.VisualStudioCode.RazorExtension/Services/RazorMapSpansResponse.cs
export class RazorMapSpansResponse {
static empty: RazorMapSpansResponse = new RazorMapSpansResponse([], [], { uri: '' });

constructor(
public readonly ranges: SerializableRange[],
public readonly spans: razorTextSpan[],
public readonly razorDocument: TextDocumentIdentifier
) {}
}
14 changes: 14 additions & 0 deletions src/razor/src/mapping/razorMapTextChangesParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocumentIdentifier } from 'vscode-languageserver-types';
import { razorTextChange } from '../dynamicFile/razorTextChange';

// Matches https://github.com/dotnet/razor/blob/401f6f8632a7e0320bc12804fa7e9659b3b3aeab/src/Razor/src/Microsoft.VisualStudioCode.RazorExtension/Services/RazorMapTextChangesParams.cs
export class RazorMapTextChangesParams {
constructor(
public readonly csharpDocument: TextDocumentIdentifier,
public readonly textChanges: razorTextChange[]
) {}
}
15 changes: 15 additions & 0 deletions src/razor/src/mapping/razorMapTextChangesResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocumentIdentifier } from 'vscode-languageserver-types';
import { razorTextChange } from '../dynamicFile/razorTextChange';

// Matches https://github.com/dotnet/razor/blob/401f6f8632a7e0320bc12804fa7e9659b3b3aeab/src/Razor/src/Microsoft.VisualStudioCode.RazorExtension/Services/RazorMapTextChangesResponse.cs
export class RazorMapTextChangesResponse {
static empty: RazorMapTextChangesResponse | PromiseLike<RazorMapTextChangesResponse>;
constructor(
public readonly razorDocument: TextDocumentIdentifier,
public readonly textChanges: razorTextChange[]
) {}
}
16 changes: 16 additions & 0 deletions src/razor/src/mapping/razorMapToDocumentEditsParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Uri } from 'vscode';
import { razorTextChange } from '../dynamicFile/razorTextChange';
import { LanguageKind } from '../rpc/languageKind';

// Matches https://github.com/dotnet/razor/blob/401f6f8632a7e0320bc12804fa7e9659b3b3aeab/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/DocumentMapping/RazorMapToDocumentEditsParams.cs
export class RazorMapToDocumentEditsParams {
constructor(
public readonly kind: LanguageKind,
public readonly razorDocumentUri: Uri,
public readonly textChanges: razorTextChange[]
) {}
}
10 changes: 10 additions & 0 deletions src/razor/src/mapping/razorMapToDocumentEditsResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { razorTextChange } from '../dynamicFile/razorTextChange';

// Matches https://github.com/dotnet/razor/blob/401f6f8632a7e0320bc12804fa7e9659b3b3aeab/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Protocol/DocumentMapping/RazorMapToDocumentEditsResponse.cs
export class RazorMapToDocumentEditsResponse {
constructor(public readonly textChanges: razorTextChange[], public readonly hostDocumentVersion: number) {}
}
Loading