From 61e010f82b48356c144a15822304c74eb6f24ed5 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 6 May 2025 18:26:02 +1000 Subject: [PATCH 1/4] Inform the server when a Razor document is closed --- src/lsptoolshost/razor/htmlDocumentManager.ts | 20 +++++++++++++++++-- src/lsptoolshost/razor/razorEndpoints.ts | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/lsptoolshost/razor/htmlDocumentManager.ts b/src/lsptoolshost/razor/htmlDocumentManager.ts index 2e9a54e5ac..5bd4adb612 100644 --- a/src/lsptoolshost/razor/htmlDocumentManager.ts +++ b/src/lsptoolshost/razor/htmlDocumentManager.ts @@ -10,12 +10,22 @@ import { getUriPath } from '../../razor/src/uriPaths'; import { virtualHtmlSuffix } from '../../razor/src/razorConventions'; import { HtmlDocumentContentProvider } from './htmlDocumentContentProvider'; import { HtmlDocument } from './htmlDocument'; +import { RoslynLanguageServer } from '../server/roslynLanguageServer'; +import { RequestType, TextDocumentIdentifier } from 'vscode-languageserver-protocol'; export class HtmlDocumentManager { private readonly htmlDocuments: { [hostDocumentPath: string]: HtmlDocument } = {}; private readonly contentProvider: HtmlDocumentContentProvider; - constructor(private readonly platformInfo: PlatformInformation, private readonly logger: RazorLogger) { + private readonly razorDocumentClosedRequest: RequestType = new RequestType( + 'razor/documentClosed' + ); + + constructor( + private readonly platformInfo: PlatformInformation, + private readonly roslynLanguageServer: RoslynLanguageServer, + private readonly logger: RazorLogger + ) { this.contentProvider = new HtmlDocumentContentProvider(this, this.logger); } @@ -37,7 +47,13 @@ export class HtmlDocumentManager { await this.closeDocument(document.uri); - // TODO: Send a notification back to the server so it can cancel any pending sync requests and clear its cache. + // We don't care about the response, but Razor cohosting can't currently do notifications with documents + // so making it a request means the logs end up in the right place. + await this.roslynLanguageServer.sendRequest( + this.razorDocumentClosedRequest, + TextDocumentIdentifier.create(getUriPath(document.uri)), + new vscode.CancellationTokenSource().token + ); } }); diff --git a/src/lsptoolshost/razor/razorEndpoints.ts b/src/lsptoolshost/razor/razorEndpoints.ts index 002c6cdc16..b683661806 100644 --- a/src/lsptoolshost/razor/razorEndpoints.ts +++ b/src/lsptoolshost/razor/razorEndpoints.ts @@ -76,7 +76,7 @@ export function registerRazorEndpoints( // Local Functions // function registerCohostingEndpoints() { - const documentManager = new HtmlDocumentManager(platformInfo, razorLogger); + const documentManager = new HtmlDocumentManager(platformInfo, roslynLanguageServer, razorLogger); const reportIssueCommand = new ReportIssueCommand(vscode, undefined, documentManager, razorLogger); context.subscriptions.push(documentManager.register()); context.subscriptions.push(reportIssueCommand.register()); From ca4dfa3a33364ddd47d6dd9887d58bc9253ae2aa Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 6 May 2025 22:38:33 +1000 Subject: [PATCH 2/4] Validate checksum for requests --- src/lsptoolshost/razor/htmlDocument.ts | 11 ++- src/lsptoolshost/razor/htmlDocumentManager.ts | 44 ++++++---- .../razor/htmlForwardedRequest.ts | 13 +++ .../razor/htmlUpdateParameters.ts | 6 +- src/lsptoolshost/razor/razorEndpoints.ts | 80 +++++++------------ .../src/diagnostics/reportIssueCreator.ts | 7 +- 6 files changed, 90 insertions(+), 71 deletions(-) create mode 100644 src/lsptoolshost/razor/htmlForwardedRequest.ts diff --git a/src/lsptoolshost/razor/htmlDocument.ts b/src/lsptoolshost/razor/htmlDocument.ts index e2dbd7f4a5..eecf19d905 100644 --- a/src/lsptoolshost/razor/htmlDocument.ts +++ b/src/lsptoolshost/razor/htmlDocument.ts @@ -9,16 +9,23 @@ import { getUriPath } from '../../razor/src/uriPaths'; export class HtmlDocument { public readonly path: string; private content = ''; + private checksum = ''; - public constructor(public readonly uri: vscode.Uri) { + public constructor(public readonly uri: vscode.Uri, checksum: string) { this.path = getUriPath(uri); + this.checksum = checksum; } public getContent() { return this.content; } - public setContent(content: string) { + public getChecksum() { + return this.checksum; + } + + public setContent(checksum: string, content: string) { + this.checksum = checksum; this.content = content; } } diff --git a/src/lsptoolshost/razor/htmlDocumentManager.ts b/src/lsptoolshost/razor/htmlDocumentManager.ts index 5bd4adb612..53323a48ee 100644 --- a/src/lsptoolshost/razor/htmlDocumentManager.ts +++ b/src/lsptoolshost/razor/htmlDocumentManager.ts @@ -65,12 +65,22 @@ export class HtmlDocumentManager { return vscode.Disposable.from(didCloseRegistration, providerRegistration); } - public async updateDocumentText(uri: vscode.Uri, text: string) { - const document = await this.getDocument(uri); + public async updateDocumentText(uri: vscode.Uri, checksum: string, text: string) { + // We don't pass the checksum in here, because we'd be comparing the new one against the old one. + let document = await this.findDocument(uri); - this.logger.logTrace(`New content for '${uri}', updating '${document.path}'.`); + if (!document) { + this.logger.logTrace( + `File '${uri}' didn't exist in the Razor document list, so adding it with checksum '${checksum}'.` + ); + document = this.addDocument(uri, checksum); + } + + this.logger.logTrace(`New content for '${uri}', updating '${document.path}', checksum '${checksum}'.`); + + await vscode.workspace.openTextDocument(document.uri); - document.setContent(text); + document.setContent(checksum, text); this.contentProvider.fireDidChange(document.uri); } @@ -85,30 +95,36 @@ export class HtmlDocumentManager { } } - public async getDocument(uri: vscode.Uri): Promise { - let document = this.findDocument(uri); + public async getDocument(uri: vscode.Uri, checksum?: string): Promise { + const document = this.findDocument(uri); - // This might happen in the case that a file is opened outside the workspace if (!document) { + this.logger.logTrace(`File '${uri}' didn't exist in the Razor document list. Doing nothing.`); + return undefined; + } + + if (checksum && document.getChecksum() !== checksum) { this.logger.logInfo( - `File '${uri}' didn't exist in the Razor document list. This is likely because it's from outside the workspace.` + `Found '${uri}' in the Razor document list, but the checksum '${document.getChecksum()}' doesn't match '${checksum}'.` ); - document = this.addDocument(uri); + return undefined; } + // No checksum, just give them the latest document and hope they know what to do with it. + await vscode.workspace.openTextDocument(document.uri); - return document!; + return document; } - private addDocument(uri: vscode.Uri): HtmlDocument { + private addDocument(uri: vscode.Uri, checksum: string): HtmlDocument { let document = this.findDocument(uri); if (document) { this.logger.logInfo(`Skipping document creation for '${document.path}' because it already exists.`); return document; } - document = this.createDocument(uri); + document = this.createDocument(uri, checksum); this.htmlDocuments[document.path] = document; return document; @@ -131,12 +147,12 @@ export class HtmlDocumentManager { ); } - private createDocument(uri: vscode.Uri) { + private createDocument(uri: vscode.Uri, checksum: string) { uri = uri.with({ scheme: HtmlDocumentContentProvider.scheme, path: `${uri.path}${virtualHtmlSuffix}`, }); - const projectedDocument = new HtmlDocument(uri); + const projectedDocument = new HtmlDocument(uri, checksum); return projectedDocument; } diff --git a/src/lsptoolshost/razor/htmlForwardedRequest.ts b/src/lsptoolshost/razor/htmlForwardedRequest.ts new file mode 100644 index 0000000000..f49413f00f --- /dev/null +++ b/src/lsptoolshost/razor/htmlForwardedRequest.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; + +export class HtmlForwardedRequest { + constructor( + public readonly textDocument: TextDocumentIdentifier, + public readonly checksum: string, + public readonly request: Params + ) {} +} diff --git a/src/lsptoolshost/razor/htmlUpdateParameters.ts b/src/lsptoolshost/razor/htmlUpdateParameters.ts index 11d7a8f8f2..dc4ae49930 100644 --- a/src/lsptoolshost/razor/htmlUpdateParameters.ts +++ b/src/lsptoolshost/razor/htmlUpdateParameters.ts @@ -6,5 +6,9 @@ import { TextDocumentIdentifier } from 'vscode-languageserver-protocol'; export class HtmlUpdateParameters { - constructor(public readonly textDocument: TextDocumentIdentifier, public readonly text: string) {} + constructor( + public readonly textDocument: TextDocumentIdentifier, + public readonly checksum: string, + public readonly text: string + ) {} } diff --git a/src/lsptoolshost/razor/razorEndpoints.ts b/src/lsptoolshost/razor/razorEndpoints.ts index b683661806..2ca105d4bc 100644 --- a/src/lsptoolshost/razor/razorEndpoints.ts +++ b/src/lsptoolshost/razor/razorEndpoints.ts @@ -50,6 +50,8 @@ import { RazorMapTextChangesParams } from '../../razor/src/mapping/razorMapTextC import { RazorMapTextChangesResponse } from '../../razor/src/mapping/razorMapTextChangesResponse'; import { FormattingHandler } from '../../razor/src/formatting/formattingHandler'; import { ReportIssueCommand } from '../../razor/src/diagnostics/reportIssueCommand'; +import { HtmlDocument } from './htmlDocument'; +import { HtmlForwardedRequest } from './htmlForwardedRequest'; export function registerRazorEndpoints( context: vscode.ExtensionContext, @@ -83,27 +85,18 @@ export function registerRazorEndpoints( registerMethodHandler('razor/updateHtml', async (params) => { const uri = UriConverter.deserialize(params.textDocument.uri); - await documentManager.updateDocumentText(uri, params.text); + await documentManager.updateDocumentText(uri, params.checksum, params.text); }); - registerRequestHandler(DocumentColorRequest.type, async (params) => { - const uri = UriConverter.deserialize(params.textDocument.uri); - const document = await documentManager.getDocument(uri); - + registerCohostHandler(DocumentColorRequest.type, documentManager, async (document) => { return await DocumentColorHandler.doDocumentColorRequest(document.uri); }); - registerRequestHandler(ColorPresentationRequest.type, async (params) => { - const uri = UriConverter.deserialize(params.textDocument.uri); - const document = await documentManager.getDocument(uri); - + registerCohostHandler(ColorPresentationRequest.type, documentManager, async (document, params) => { return await ColorPresentationHandler.doColorPresentationRequest(document.uri, params); }); - registerRequestHandler(FoldingRangeRequest.type, async (params) => { - const uri = UriConverter.deserialize(params.textDocument.uri); - const document = await documentManager.getDocument(uri); - + registerCohostHandler(FoldingRangeRequest.type, documentManager, async (document) => { const results = await vscode.commands.executeCommand( 'vscode.executeFoldingRangeProvider', document.uri @@ -112,10 +105,7 @@ export function registerRazorEndpoints( return FoldingRangeHandler.convertFoldingRanges(results, razorLogger); }); - registerRequestHandler(HoverRequest.type, async (params) => { - const uri = UriConverter.deserialize(params.textDocument.uri); - const document = await documentManager.getDocument(uri); - + registerCohostHandler(HoverRequest.type, documentManager, async (document, params) => { const results = await vscode.commands.executeCommand( 'vscode.executeHoverProvider', document.uri, @@ -126,10 +116,7 @@ export function registerRazorEndpoints( return rewriteHover(applicableHover); }); - registerRequestHandler(DocumentHighlightRequest.type, async (params) => { - const uri = UriConverter.deserialize(params.textDocument.uri); - const document = await documentManager.getDocument(uri); - + registerCohostHandler(DocumentHighlightRequest.type, documentManager, async (document, params) => { const results = await vscode.commands.executeCommand( 'vscode.executeDocumentHighlights', document.uri, @@ -139,10 +126,7 @@ export function registerRazorEndpoints( return rewriteHighlight(results); }); - registerRequestHandler(CompletionRequest.type, async (params) => { - const uri = UriConverter.deserialize(params.textDocument.uri); - const document = await documentManager.getDocument(uri); - + registerCohostHandler(CompletionRequest.type, documentManager, async (document, params) => { return CompletionHandler.provideVscodeCompletions( document.uri, params.position, @@ -150,10 +134,7 @@ export function registerRazorEndpoints( ); }); - registerRequestHandler(ReferencesRequest.type, async (params) => { - const uri = UriConverter.deserialize(params.textDocument.uri); - const document = await documentManager.getDocument(uri); - + registerCohostHandler(ReferencesRequest.type, documentManager, async (document, params) => { const results = await vscode.commands.executeCommand( 'vscode.executeReferenceProvider', document.uri, @@ -163,10 +144,7 @@ export function registerRazorEndpoints( return rewriteLocations(results); }); - registerRequestHandler(ImplementationRequest.type, async (params) => { - const uri = UriConverter.deserialize(params.textDocument.uri); - const document = await documentManager.getDocument(uri); - + registerCohostHandler(ImplementationRequest.type, documentManager, async (document, params) => { const results = await vscode.commands.executeCommand( 'vscode.executeImplementationProvider', document.uri, @@ -176,10 +154,7 @@ export function registerRazorEndpoints( return rewriteLocations(results); }); - registerRequestHandler(DefinitionRequest.type, async (params) => { - const uri = UriConverter.deserialize(params.textDocument.uri); - const document = await documentManager.getDocument(uri); - + registerCohostHandler(DefinitionRequest.type, documentManager, async (document, params) => { const results = await vscode.commands.executeCommand( 'vscode.executeDefinitionProvider', document.uri, @@ -189,10 +164,7 @@ export function registerRazorEndpoints( return rewriteLocations(results); }); - registerRequestHandler(SignatureHelpRequest.type, async (params) => { - const uri = UriConverter.deserialize(params.textDocument.uri); - const document = await documentManager.getDocument(uri); - + registerCohostHandler(SignatureHelpRequest.type, documentManager, async (document, params) => { const results = await vscode.commands.executeCommand( 'vscode.executeSignatureHelpProvider', document.uri, @@ -206,10 +178,7 @@ export function registerRazorEndpoints( return rewriteSignatureHelp(results); }); - registerRequestHandler(DocumentFormattingRequest.type, async (params) => { - const uri = UriConverter.deserialize(params.textDocument.uri); - const document = await documentManager.getDocument(uri); - + registerCohostHandler(DocumentFormattingRequest.type, documentManager, async (document, params) => { const content = document.getContent(); const options = params.options; @@ -217,10 +186,7 @@ export function registerRazorEndpoints( return response?.edits; }); - registerRequestHandler(DocumentOnTypeFormattingRequest.type, async (params) => { - const uri = UriConverter.deserialize(params.textDocument.uri); - const document = await documentManager.getDocument(uri); - + registerCohostHandler(DocumentOnTypeFormattingRequest.type, documentManager, async (document, params) => { const content = document.getContent(); const options = params.options; @@ -262,11 +228,21 @@ export function registerRazorEndpoints( } // Helper method that registers a request handler, and logs errors to the Razor logger. - function registerRequestHandler( + function registerCohostHandler( type: RequestType, - invocation: (params: Params) => Promise + documentManager: HtmlDocumentManager, + invocation: (document: HtmlDocument, request: Params) => Promise ) { - return registerMethodHandler(type.method, invocation); + return registerMethodHandler, Result | undefined>(type.method, async (params) => { + const uri = UriConverter.deserialize(params.textDocument.uri); + const document = await documentManager.getDocument(uri, params.checksum); + + if (!document) { + return undefined; + } + + return invocation(document, params.request); + }); } function registerMethodHandler(method: string, invocation: (params: Params) => Promise) { diff --git a/src/razor/src/diagnostics/reportIssueCreator.ts b/src/razor/src/diagnostics/reportIssueCreator.ts index 1651f72cd3..356d0a9b62 100644 --- a/src/razor/src/diagnostics/reportIssueCreator.ts +++ b/src/razor/src/diagnostics/reportIssueCreator.ts @@ -37,9 +37,12 @@ export class ReportIssueCreator { csharpContent = await this.getProjectedCSharp(razorDocument); htmlContent = await this.getProjectedHtml(razorDocument); } else if (this.cohostingDocumentManager) { - const htmlDocument = await this.cohostingDocumentManager.getDocument(collectionResult.document.uri); csharpContent = vscode.l10n.t('Cohosting is on, client has no access to CSharp content'); - htmlContent = htmlDocument.getContent(); + + const htmlDocument = await this.cohostingDocumentManager.getDocument(collectionResult.document.uri); + if (htmlDocument) { + htmlContent = htmlDocument.getContent(); + } } } From ff962d9d6a33928ddc64ab88eb11275b87dcb458 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 7 May 2025 15:47:35 +1000 Subject: [PATCH 3/4] Bump Razor --- CHANGELOG.md | 5 ++++- package.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57020aae20..a233b236cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,10 @@ - Debug from .csproj and .sln [#5876](https://github.com/dotnet/vscode-csharp/issues/5876) # 2.77.x -* Bump Razor to 10.0.0-preview.25255.4 (PR: [#8249](https://github.com/dotnet/vscode-csharp/pull/8249)) +* Bump Razor to 10.0.0-preview.25256.6 (PR: [#8259](https://github.com/dotnet/vscode-csharp/pull/8259)) + * Improve document handling in VS Code and cohosting (#11825) (PR: [#11825](https://github.com/dotnet/razor/pull/11825)) + * Use new select and order method (#11826) (PR: [#11826](https://github.com/dotnet/razor/pull/11826)) + * Make it so validation happens after filtering and normalization (#11811) (PR: [#11811](https://github.com/dotnet/razor/pull/11811)) * Port remaining cohosting endpoints to VS Code (#11815) (PR: [#11815](https://github.com/dotnet/razor/pull/11815)) * Fix cohost semantic tokens in VS Code (#11816) (PR: [#11816](https://github.com/dotnet/razor/pull/11816)) * Fix some hardcoded values in cohosting (#11817) (PR: [#11817](https://github.com/dotnet/razor/pull/11817)) diff --git a/package.json b/package.json index 50b132ce45..9bb25590d4 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "defaults": { "roslyn": "5.0.0-1.25256.5", "omniSharp": "1.39.12", - "razor": "10.0.0-preview.25255.4", + "razor": "10.0.0-preview.25256.6", "razorOmnisharp": "7.0.0-preview.23363.1", "xamlTools": "17.14.36010.33" }, From 3106f0adcb83293b1f36b71e1ea3faef51179337 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 7 May 2025 16:17:45 +1000 Subject: [PATCH 4/4] FIx changelog --- CHANGELOG.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a233b236cf..b4910c9710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,15 +12,13 @@ * Fix cohost semantic tokens in VS Code (#11816) (PR: [#11816](https://github.com/dotnet/razor/pull/11816)) * Fix some hardcoded values in cohosting (#11817) (PR: [#11817](https://github.com/dotnet/razor/pull/11817)) * Add new shared SelectXXXAsArray helpers (#11796) (PR: [#11796](https://github.com/dotnet/razor/pull/11796)) -* Bump Roslyn to 5.0.0-1.25255.4 (PR: [#8249](https://github.com/dotnet/vscode-csharp/pull/8249)) - * Ensure hover markdown for supported platforms uses non-breaking spaces for indentation (#78405) (PR: [#78405](https://github.com/dotnet/roslyn/pull/78405)) - * Change O(n) + O(lg n) search in SolutionState.SortedProjectStates to just O(lg n) (#78427) (PR: [#78427](https://github.com/dotnet/roslyn/pull/78427)) - * Fix syntax tree creation when modifying source generated documents (#78343) (PR: [#78343](https://github.com/dotnet/roslyn/pull/78343)) # 2.76.x * Bump Roslyn to 5.0.0-1.25256.5 (PR: [#8254](https://github.com/dotnet/vscode-csharp/pull/8254)) * Do not parse URIs during LSP serialization/deserialization(PR: [#76691](https://github.com/dotnet/roslyn/pull/76691)) * Ensure hover markdown for supported platforms uses non-breaking spaces for indentation(PR: [#78405](https://github.com/dotnet/roslyn/pull/78405)) + * Change O(n) + O(lg n) search in SolutionState.SortedProjectStates to just O(lg n) (#78427) (PR: [#78427](https://github.com/dotnet/roslyn/pull/78427)) + * Fix syntax tree creation when modifying source generated documents (#78343) (PR: [#78343](https://github.com/dotnet/roslyn/pull/78343)) * Bump Razor to 10.0.0-preview.25252.1 (PR: [#8239](https://github.com/dotnet/vscode-csharp/pull/8239)) * If SupportDiagnostics is false then dynamic files don't report diagnostics... (PR: #11807) * Client settings tweaks, and implement VS Code configuration monitoring (PR: #11800)