From 2f4b51cb85b00cec50393d6f36abefc5243956ee Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Mon, 25 Nov 2024 17:48:56 -0800 Subject: [PATCH 1/7] Update dynamic documents to store edits for closed documents --- src/lsptoolshost/roslynLanguageServer.ts | 6 ++ .../src/csharp/csharpProjectedDocument.ts | 46 ++++++++++++--- src/razor/src/document/IRazorDocument.ts | 1 + src/razor/src/document/razorDocument.ts | 32 +++++++++++ .../src/document/razorDocumentFactory.ts | 12 +--- .../src/document/razorDocumentManager.ts | 57 ++++++------------- .../src/dynamicFile/dynamicFileInfoHandler.ts | 54 ++++++++++++------ .../dynamicFile/dynamicFileUpdatedParams.ts | 10 ++++ .../dynamicFile/provideDynamicFileResponse.ts | 6 +- 9 files changed, 150 insertions(+), 74 deletions(-) create mode 100644 src/razor/src/document/razorDocument.ts create mode 100644 src/razor/src/dynamicFile/dynamicFileUpdatedParams.ts diff --git a/src/lsptoolshost/roslynLanguageServer.ts b/src/lsptoolshost/roslynLanguageServer.ts index 3b43487dc..e391bdf2d 100644 --- a/src/lsptoolshost/roslynLanguageServer.ts +++ b/src/lsptoolshost/roslynLanguageServer.ts @@ -78,6 +78,7 @@ import { import { registerSourceGeneratedFilesContentProvider } from './sourceGeneratedFilesContentProvider'; import { registerMiscellaneousFileNotifier } from './miscellaneousFileNotifier'; import { TelemetryEventNames } from '../shared/telemetryEventNames'; +import { RazorDynamicFileChangedParams } from '../razor/src/dynamicFile/dynamicFileUpdatedParams'; let _channel: vscode.LogOutputChannel; let _traceChannel: vscode.OutputChannel; @@ -789,6 +790,11 @@ export class RoslynLanguageServer { async (notification) => vscode.commands.executeCommand(DynamicFileInfoHandler.removeDynamicFileInfoCommand, notification) ); + vscode.commands.registerCommand( + DynamicFileInfoHandler.dynamicFileUpdatedCommand, + async (notification: RazorDynamicFileChangedParams) => + this.sendNotification('razor/dynamicFileInfoChanged', notification) + ); } // eslint-disable-next-line @typescript-eslint/promise-function-async diff --git a/src/razor/src/csharp/csharpProjectedDocument.ts b/src/razor/src/csharp/csharpProjectedDocument.ts index d74c0bb25..6e90a1de7 100644 --- a/src/razor/src/csharp/csharpProjectedDocument.ts +++ b/src/razor/src/csharp/csharpProjectedDocument.ts @@ -19,6 +19,7 @@ export class CSharpProjectedDocument implements IProjectedDocument { private resolveProvisionalEditAt: number | undefined; private ProvisionalDotPosition: Position | undefined; private hostDocumentVersion: number | null = null; + private edits: ServerTextChange[] | null = null; public constructor(public readonly uri: vscode.Uri) { this.path = getUriPath(uri); @@ -37,21 +38,38 @@ export class CSharpProjectedDocument implements IProjectedDocument { } public update(edits: ServerTextChange[], hostDocumentVersion: number) { + // Apply any stored edits if needed + if (this.edits) { + edits = this.edits.concat(edits); + this.edits = null; + } + this.removeProvisionalDot(); this.hostDocumentVersion = hostDocumentVersion; - if (edits.length === 0) { - return; + this.updateContent(edits); + } + + public storeEdits(edits: ServerTextChange[], hostDocumentVersion: number) { + this.hostDocumentVersion = hostDocumentVersion; + if (this.edits) { + this.edits = this.edits.concat(edits); + } else { + this.edits = edits; } + } - let content = this.content; - for (const edit of edits.reverse()) { - // TODO: Use a better data structure to represent the content, string concatenation is slow. - content = this.getEditedContent(edit.newText, edit.span.start, edit.span.start + edit.span.length, content); + public getAndApplyEdits() { + const edits = this.edits; + + // Make sure the internal representation of the content is updated + if (edits) { + this.updateContent(edits); } - this.setContent(content); + this.edits = null; + return edits; } public getContent() { @@ -150,4 +168,18 @@ export class CSharpProjectedDocument implements IProjectedDocument { private setContent(content: string) { this.content = content; } + + private updateContent(edits: ServerTextChange[]) { + if (edits.length === 0) { + return; + } + + let content = this.content; + for (const edit of edits.reverse()) { + // TODO: Use a better data structure to represent the content, string concatenation is slow. + content = this.getEditedContent(edit.newText, edit.span.start, edit.span.start + edit.span.length, content); + } + + this.setContent(content); + } } diff --git a/src/razor/src/document/IRazorDocument.ts b/src/razor/src/document/IRazorDocument.ts index 95557df8f..6fcd07a26 100644 --- a/src/razor/src/document/IRazorDocument.ts +++ b/src/razor/src/document/IRazorDocument.ts @@ -11,4 +11,5 @@ export interface IRazorDocument { readonly uri: vscode.Uri; readonly csharpDocument: IProjectedDocument; readonly htmlDocument: IProjectedDocument; + readonly isOpen: boolean; } diff --git a/src/razor/src/document/razorDocument.ts b/src/razor/src/document/razorDocument.ts new file mode 100644 index 000000000..1c42dce6a --- /dev/null +++ b/src/razor/src/document/razorDocument.ts @@ -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 { CSharpProjectedDocument } from '../csharp/csharpProjectedDocument'; +import { HtmlProjectedDocument } from '../html/htmlProjectedDocument'; +import { getUriPath } from '../uriPaths'; +import { IRazorDocument } from './IRazorDocument'; + +export class RazorDocument implements IRazorDocument { + public readonly path: string; + + constructor( + readonly uri: vscode.Uri, + readonly csharpDocument: CSharpProjectedDocument, + readonly htmlDocument: HtmlProjectedDocument + ) { + this.path = getUriPath(uri); + } + + public get isOpen(): boolean { + for (const textDocument of vscode.workspace.textDocuments) { + if (textDocument.uri.fsPath == this.uri.fsPath) { + return true; + } + } + + return false; + } +} diff --git a/src/razor/src/document/razorDocumentFactory.ts b/src/razor/src/document/razorDocumentFactory.ts index 18b8b919f..1b3f2c5db 100644 --- a/src/razor/src/document/razorDocumentFactory.ts +++ b/src/razor/src/document/razorDocumentFactory.ts @@ -11,18 +11,12 @@ import { HtmlProjectedDocumentContentProvider } from '../html/htmlProjectedDocum import { virtualCSharpSuffix, virtualHtmlSuffix } from '../razorConventions'; import { getUriPath } from '../uriPaths'; import { IRazorDocument } from './IRazorDocument'; +import { RazorDocument } from './razorDocument'; -export function createDocument(uri: vscode.Uri) { +export function createDocument(uri: vscode.Uri): IRazorDocument { const csharpDocument = createProjectedCSharpDocument(uri); const htmlDocument = createProjectedHtmlDocument(uri); - const path = getUriPath(uri); - - const document: IRazorDocument = { - uri, - path, - csharpDocument, - htmlDocument, - }; + const document = new RazorDocument(uri, csharpDocument, htmlDocument); return document; } diff --git a/src/razor/src/document/razorDocumentManager.ts b/src/razor/src/document/razorDocumentManager.ts index 066073f40..50ee8dd71 100644 --- a/src/razor/src/document/razorDocumentManager.ts +++ b/src/razor/src/document/razorDocumentManager.ts @@ -50,21 +50,12 @@ export class RazorDocumentManager implements IRazorDocumentManager { return Object.values(this.razorDocuments); } - public async getDocument(uri: vscode.Uri) { + public async getDocument(uri: vscode.Uri): Promise { const document = this._getDocument(uri); - - // VS Code closes virtual documents after some timeout if they are not open in the IDE. Since our generated C# and Html - // documents are never open in the IDE, we need to ensure that VS Code considers them open so that requests against them - // succeed. Without this, even a simple diagnostics request will fail in Roslyn if the user just opens a .razor document - // and leaves it open past the timeout. - if (this.razorDocumentGenerationInitialized) { - await this.ensureDocumentAndProjectedDocumentsOpen(document); - } - return document; } - public async getActiveDocument() { + public async getActiveDocument(): Promise { if (!vscode.window.activeTextEditor) { return null; } @@ -147,7 +138,7 @@ export class RazorDocumentManager implements IRazorDocumentManager { return vscode.Disposable.from(watcher, didCreateRegistration, didOpenRegistration, didCloseRegistration); } - private _getDocument(uri: vscode.Uri) { + private _getDocument(uri: vscode.Uri): IRazorDocument { const path = getUriPath(uri); let document = this.findDocument(path); @@ -159,7 +150,7 @@ export class RazorDocumentManager implements IRazorDocumentManager { document = this.addDocument(uri); } - return document; + return document!; } private async openDocument(uri: vscode.Uri) { @@ -182,10 +173,6 @@ export class RazorDocumentManager implements IRazorDocumentManager { await vscode.commands.executeCommand(razorInitializeCommand, pipeName); await this.serverClient.connectNamedPipe(pipeName); - for (const document of this.documents) { - await this.ensureDocumentAndProjectedDocumentsOpen(document); - } - this.onRazorInitializedEmitter.fire(); } } @@ -205,7 +192,7 @@ export class RazorDocumentManager implements IRazorDocumentManager { this.notifyDocumentChange(document, RazorDocumentChangeKind.closed); } - private addDocument(uri: vscode.Uri) { + private addDocument(uri: vscode.Uri): IRazorDocument { const path = getUriPath(uri); let document = this.findDocument(path); if (document) { @@ -261,10 +248,6 @@ export class RazorDocumentManager implements IRazorDocumentManager { ) { // We allow re-setting of the updated content from the same doc sync version in the case // of project or file import changes. - - // Make sure the document is open, because updating will cause a didChange event to fire. - await vscode.workspace.openTextDocument(document.csharpDocument.uri); - const csharpProjectedDocument = projectedDocument as CSharpProjectedDocument; // If the language server is telling us that the previous document was empty, then we should clear @@ -275,7 +258,17 @@ export class RazorDocumentManager implements IRazorDocumentManager { csharpProjectedDocument.clear(); } - csharpProjectedDocument.update(updateBufferRequest.changes, updateBufferRequest.hostDocumentVersion); + if (document.isOpen) { + // Make sure the document is open, because updating will cause a didChange event to fire. + await vscode.workspace.openTextDocument(document.csharpDocument.uri); + + csharpProjectedDocument.update(updateBufferRequest.changes, updateBufferRequest.hostDocumentVersion); + } else { + csharpProjectedDocument.storeEdits( + updateBufferRequest.changes, + updateBufferRequest.hostDocumentVersion + ); + } this.notifyDocumentChange(document, RazorDocumentChangeKind.csharpChanged); } else { @@ -342,22 +335,4 @@ export class RazorDocumentManager implements IRazorDocumentManager { this.onChangeEmitter.fire(args); } - - private async ensureDocumentAndProjectedDocumentsOpen(document: IRazorDocument) { - // vscode.workspace.openTextDocument may send a textDocument/didOpen - // request to the C# language server. We need to keep track of - // this to make sure we don't send a duplicate request later on. - const razorUri = vscode.Uri.file(document.path); - if (!this.isRazorDocumentOpenInCSharpWorkspace(razorUri)) { - this.didOpenRazorCSharpDocument(razorUri); - - // Need to tell the Razor server that the document is open, or it won't generate C# code - // for it, and our projected document will always be empty, until the user manually - // opens the razor file. - await vscode.workspace.openTextDocument(razorUri); - } - - await vscode.workspace.openTextDocument(document.csharpDocument.uri); - await vscode.workspace.openTextDocument(document.htmlDocument.uri); - } } diff --git a/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts b/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts index 9a91d1375..2d86a926c 100644 --- a/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts +++ b/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts @@ -4,19 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { DocumentUri } from 'vscode-languageclient/node'; import { UriConverter } from '../../../lsptoolshost/uriConverter'; import { RazorDocumentManager } from '../document/razorDocumentManager'; import { RazorLogger } from '../razorLogger'; import { ProvideDynamicFileParams } from './provideDynamicFileParams'; import { ProvideDynamicFileResponse } from './provideDynamicFileResponse'; import { RemoveDynamicFileParams } from './removeDynamicFileParams'; +import { CSharpProjectedDocument } from '../csharp/csharpProjectedDocument'; +import { RazorDocumentChangeKind } from '../document/razorDocumentChangeKind'; +import { RazorDynamicFileChangedParams } from './dynamicFileUpdatedParams'; +import { TextDocumentIdentifier } from 'vscode-languageserver-protocol'; // Handles Razor generated doc communication between the Roslyn workspace and Razor. // didChange behavior for Razor generated docs is handled in the RazorDocumentManager. export class DynamicFileInfoHandler { public static readonly provideDynamicFileInfoCommand = 'razor.provideDynamicFileInfo'; public static readonly removeDynamicFileInfoCommand = 'razor.removeDynamicFileInfo'; + public static readonly dynamicFileUpdatedCommand = 'razor.dynamicFileUpdated'; constructor(private readonly documentManager: RazorDocumentManager, private readonly logger: RazorLogger) {} @@ -33,39 +37,57 @@ export class DynamicFileInfoHandler { await this.removeDynamicFileInfo(request); } ); + this.documentManager.onChange(async (e) => { + if (e.kind == RazorDocumentChangeKind.csharpChanged && !e.document.isOpen) { + 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 private async provideDynamicFileInfo( request: ProvideDynamicFileParams ): Promise { - let virtualUri: DocumentUri | null = null; + this.documentManager.roslynActivated = true; + const vscodeUri = vscode.Uri.parse(request.razorDocument.uri, true); + + // Normally we start receiving dynamic info after Razor is initialized, but if the user had a .razor file open + // when they started VS Code, the order is the other way around. This no-ops if Razor is already initialized. + await this.documentManager.ensureRazorInitialized(); + + const razorDocument = await this.documentManager.getDocument(vscodeUri); try { - const vscodeUri = vscode.Uri.parse(request.razorDocument.uri, true); - const razorDocument = await this.documentManager.getDocument(vscodeUri); if (razorDocument === undefined) { this.logger.logWarning( `Could not find Razor document ${vscodeUri.fsPath}; adding null as a placeholder in URI array.` ); - } else { - // Retrieve generated doc URIs for each Razor URI we are given - const virtualCsharpUri = UriConverter.serialize(razorDocument.csharpDocument.uri); - virtualUri = virtualCsharpUri; + + return null; } - this.documentManager.roslynActivated = true; + const virtualCsharpUri = UriConverter.serialize(razorDocument.csharpDocument.uri); - // Normally we start receiving dynamic info after Razor is initialized, but if the user had a .razor file open - // when they started VS Code, the order is the other way around. This no-ops if Razor is already initialized. - await this.documentManager.ensureRazorInitialized(); + if (this.documentManager.isRazorDocumentOpenInCSharpWorkspace(vscodeUri)) { + // Open documents have didOpen/didChange to update the csharp buffer. Razor + // does not send edits and instead lets vscode handle them. + return new ProvideDynamicFileResponse({ uri: virtualCsharpUri }, null); + } else { + // Closed documents provide edits since the last time they were requested since + // there is no open buffer in vscode corresponding to the csharp content. + const csharpDocument = razorDocument.csharpDocument as CSharpProjectedDocument; + const edits = csharpDocument.getAndApplyEdits(); + + return new ProvideDynamicFileResponse({ uri: virtualCsharpUri }, edits ?? null); + } } catch (error) { this.logger.logWarning(`${DynamicFileInfoHandler.provideDynamicFileInfoCommand} failed with ${error}`); } - if (virtualUri) { - return new ProvideDynamicFileResponse({ uri: virtualUri }); - } - return null; } diff --git a/src/razor/src/dynamicFile/dynamicFileUpdatedParams.ts b/src/razor/src/dynamicFile/dynamicFileUpdatedParams.ts new file mode 100644 index 000000000..3a5f4c397 --- /dev/null +++ b/src/razor/src/dynamicFile/dynamicFileUpdatedParams.ts @@ -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 { TextDocumentIdentifier } from 'vscode-languageserver-protocol'; + +export class RazorDynamicFileChangedParams { + constructor(public readonly razorDocument: TextDocumentIdentifier) {} +} diff --git a/src/razor/src/dynamicFile/provideDynamicFileResponse.ts b/src/razor/src/dynamicFile/provideDynamicFileResponse.ts index 7be8d1f01..42aa48022 100644 --- a/src/razor/src/dynamicFile/provideDynamicFileResponse.ts +++ b/src/razor/src/dynamicFile/provideDynamicFileResponse.ts @@ -4,8 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { TextDocumentIdentifier } from 'vscode-languageclient/node'; +import { ServerTextChange } from '../rpc/serverTextChange'; // matches https://github.com/dotnet/roslyn/blob/9e91ca6590450e66e0041ee3135bbf044ac0687a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/RazorDynamicFileInfoProvider.cs#L28 export class ProvideDynamicFileResponse { - constructor(public readonly csharpDocument: TextDocumentIdentifier | null) {} + constructor( + public readonly csharpDocument: TextDocumentIdentifier | null, + public readonly edits: ServerTextChange[] | null + ) {} } From 212e71030ad98e7ef70364a4779ead8f2bed0b1e Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Tue, 26 Nov 2024 18:21:13 -0800 Subject: [PATCH 2/7] Encapsulate each set of changes in an Update class --- .../src/csharp/csharpProjectedDocument.ts | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/razor/src/csharp/csharpProjectedDocument.ts b/src/razor/src/csharp/csharpProjectedDocument.ts index 6e90a1de7..ec4cabe13 100644 --- a/src/razor/src/csharp/csharpProjectedDocument.ts +++ b/src/razor/src/csharp/csharpProjectedDocument.ts @@ -19,7 +19,7 @@ export class CSharpProjectedDocument implements IProjectedDocument { private resolveProvisionalEditAt: number | undefined; private ProvisionalDotPosition: Position | undefined; private hostDocumentVersion: number | null = null; - private edits: ServerTextChange[] | null = null; + private updates: Update[] | null = null; public constructor(public readonly uri: vscode.Uri) { this.path = getUriPath(uri); @@ -38,38 +38,45 @@ export class CSharpProjectedDocument implements IProjectedDocument { } public update(edits: ServerTextChange[], hostDocumentVersion: number) { + this.removeProvisionalDot(); + // Apply any stored edits if needed - if (this.edits) { - edits = this.edits.concat(edits); - this.edits = null; - } + if (this.updates) { + for (const update of this.updates) { + this.updateContent(update.changes); + } - this.removeProvisionalDot(); + this.updates = null; + } this.hostDocumentVersion = hostDocumentVersion; - this.updateContent(edits); } public storeEdits(edits: ServerTextChange[], hostDocumentVersion: number) { this.hostDocumentVersion = hostDocumentVersion; - if (this.edits) { - this.edits = this.edits.concat(edits); + if (this.updates) { + this.updates = this.updates.concat(new Update(edits)); } else { - this.edits = edits; + this.updates = [new Update(edits)]; } } public getAndApplyEdits() { - const edits = this.edits; + const updates = this.updates; + this.updates = null; + + if (updates) { + let changes: ServerTextChange[] = []; + for (const update of updates) { + this.updateContent(update.changes); + changes = changes.concat(update.changes); + } - // Make sure the internal representation of the content is updated - if (edits) { - this.updateContent(edits); + return changes; } - this.edits = null; - return edits; + return null; } public getContent() { @@ -158,8 +165,8 @@ export class CSharpProjectedDocument implements IProjectedDocument { } private getEditedContent(newText: string, start: number, end: number, content: string) { - const before = content.substr(0, start); - const after = content.substr(end); + const before = content.substring(0, start); + const after = content.substring(end); content = `${before}${newText}${after}`; return content; @@ -183,3 +190,7 @@ export class CSharpProjectedDocument implements IProjectedDocument { this.setContent(content); } } + +class Update { + constructor(public readonly changes: ServerTextChange[]) {} +} From 86d43c59cf676f4f8d2d5888c6cf3c25101645d0 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Mon, 2 Dec 2024 19:32:12 -0800 Subject: [PATCH 3/7] Add checksum and encoding information to publish data --- .../src/csharp/csharpProjectedDocument.ts | 61 +++++++++++++------ .../src/document/razorDocumentManager.ts | 27 ++++---- .../src/dynamicFile/dynamicFileInfoHandler.ts | 16 ++++- .../dynamicFile/provideDynamicFileResponse.ts | 5 +- src/razor/src/html/htmlProjectedDocument.ts | 26 +++++++- .../src/projection/IProjectedDocument.ts | 3 + src/razor/src/rpc/updateBufferRequest.ts | 5 +- 7 files changed, 108 insertions(+), 35 deletions(-) diff --git a/src/razor/src/csharp/csharpProjectedDocument.ts b/src/razor/src/csharp/csharpProjectedDocument.ts index ec4cabe13..3ed95805f 100644 --- a/src/razor/src/csharp/csharpProjectedDocument.ts +++ b/src/razor/src/csharp/csharpProjectedDocument.ts @@ -20,6 +20,9 @@ export class CSharpProjectedDocument implements IProjectedDocument { private ProvisionalDotPosition: Position | undefined; private hostDocumentVersion: number | null = null; private updates: Update[] | null = null; + private _checksum: Uint8Array = new Uint8Array(); + private _checksumAlgorithm: number = 0; + private _encodingCodePage: number | null = null; public constructor(public readonly uri: vscode.Uri) { this.path = getUriPath(uri); @@ -33,33 +36,55 @@ export class CSharpProjectedDocument implements IProjectedDocument { return this.content.length; } + public get checksum(): Uint8Array { + return this._checksum; + } + + public get checksumAlgorithm(): number { + return this._checksumAlgorithm; + } + + public get encodingCodePage(): number | null { + return this._encodingCodePage; + } + public clear() { this.setContent(''); } - public update(edits: ServerTextChange[], hostDocumentVersion: number) { - this.removeProvisionalDot(); - - // Apply any stored edits if needed - if (this.updates) { - for (const update of this.updates) { - this.updateContent(update.changes); + public update( + hostDocumentIsOpen: boolean, + edits: ServerTextChange[], + hostDocumentVersion: number, + checksum: Uint8Array, + checksumAlgorithm: number, + encodingCodePage: number | null + ) { + if (hostDocumentIsOpen) { + this.removeProvisionalDot(); + + // Apply any stored edits if needed + if (this.updates) { + for (const update of this.updates) { + this.updateContent(update.changes); + } + + this.updates = null; } - this.updates = null; + this.updateContent(edits); + } else { + if (this.updates) { + this.updates = this.updates.concat(new Update(edits)); + } else { + this.updates = [new Update(edits)]; + } } + this._checksum = checksum; + this._checksumAlgorithm = checksumAlgorithm; + this._encodingCodePage = encodingCodePage; this.hostDocumentVersion = hostDocumentVersion; - this.updateContent(edits); - } - - public storeEdits(edits: ServerTextChange[], hostDocumentVersion: number) { - this.hostDocumentVersion = hostDocumentVersion; - if (this.updates) { - this.updates = this.updates.concat(new Update(edits)); - } else { - this.updates = [new Update(edits)]; - } } public getAndApplyEdits() { diff --git a/src/razor/src/document/razorDocumentManager.ts b/src/razor/src/document/razorDocumentManager.ts index 50ee8dd71..115cf81b4 100644 --- a/src/razor/src/document/razorDocumentManager.ts +++ b/src/razor/src/document/razorDocumentManager.ts @@ -258,17 +258,14 @@ export class RazorDocumentManager implements IRazorDocumentManager { csharpProjectedDocument.clear(); } - if (document.isOpen) { - // Make sure the document is open, because updating will cause a didChange event to fire. - await vscode.workspace.openTextDocument(document.csharpDocument.uri); - - csharpProjectedDocument.update(updateBufferRequest.changes, updateBufferRequest.hostDocumentVersion); - } else { - csharpProjectedDocument.storeEdits( - updateBufferRequest.changes, - updateBufferRequest.hostDocumentVersion - ); - } + csharpProjectedDocument.update( + document.isOpen, + updateBufferRequest.changes, + updateBufferRequest.hostDocumentVersion, + updateBufferRequest.checksum, + updateBufferRequest.checksumAlgorithm, + updateBufferRequest.encodingCodePage + ); this.notifyDocumentChange(document, RazorDocumentChangeKind.csharpChanged); } else { @@ -311,7 +308,13 @@ export class RazorDocumentManager implements IRazorDocumentManager { htmlProjectedDocument.clear(); } - htmlProjectedDocument.update(updateBufferRequest.changes, updateBufferRequest.hostDocumentVersion); + htmlProjectedDocument.update( + updateBufferRequest.changes, + updateBufferRequest.hostDocumentVersion, + updateBufferRequest.checksum, + updateBufferRequest.checksumAlgorithm, + updateBufferRequest.encodingCodePage + ); this.notifyDocumentChange(document, RazorDocumentChangeKind.htmlChanged); } else { diff --git a/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts b/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts index 2d86a926c..2fedb9337 100644 --- a/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts +++ b/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts @@ -75,14 +75,26 @@ export class DynamicFileInfoHandler { if (this.documentManager.isRazorDocumentOpenInCSharpWorkspace(vscodeUri)) { // Open documents have didOpen/didChange to update the csharp buffer. Razor // does not send edits and instead lets vscode handle them. - return new ProvideDynamicFileResponse({ uri: virtualCsharpUri }, null); + return new ProvideDynamicFileResponse( + { uri: virtualCsharpUri }, + null, + razorDocument.csharpDocument.checksum, + razorDocument.csharpDocument.checksumAlgorithm, + razorDocument.csharpDocument.encodingCodePage + ); } else { // Closed documents provide edits since the last time they were requested since // there is no open buffer in vscode corresponding to the csharp content. const csharpDocument = razorDocument.csharpDocument as CSharpProjectedDocument; const edits = csharpDocument.getAndApplyEdits(); - return new ProvideDynamicFileResponse({ uri: virtualCsharpUri }, edits ?? null); + return new ProvideDynamicFileResponse( + { uri: virtualCsharpUri }, + edits ?? null, + razorDocument.csharpDocument.checksum, + razorDocument.csharpDocument.checksumAlgorithm, + razorDocument.csharpDocument.encodingCodePage + ); } } catch (error) { this.logger.logWarning(`${DynamicFileInfoHandler.provideDynamicFileInfoCommand} failed with ${error}`); diff --git a/src/razor/src/dynamicFile/provideDynamicFileResponse.ts b/src/razor/src/dynamicFile/provideDynamicFileResponse.ts index 42aa48022..0cee9f476 100644 --- a/src/razor/src/dynamicFile/provideDynamicFileResponse.ts +++ b/src/razor/src/dynamicFile/provideDynamicFileResponse.ts @@ -10,6 +10,9 @@ import { ServerTextChange } from '../rpc/serverTextChange'; export class ProvideDynamicFileResponse { constructor( public readonly csharpDocument: TextDocumentIdentifier | null, - public readonly edits: ServerTextChange[] | null + public readonly edits: ServerTextChange[] | null, + public readonly checksum: Uint8Array, + public readonly checksumAlgorithm: number, + public readonly encodingCodePage: number | null ) {} } diff --git a/src/razor/src/html/htmlProjectedDocument.ts b/src/razor/src/html/htmlProjectedDocument.ts index 1ec11bc5d..6c9671079 100644 --- a/src/razor/src/html/htmlProjectedDocument.ts +++ b/src/razor/src/html/htmlProjectedDocument.ts @@ -12,6 +12,9 @@ export class HtmlProjectedDocument implements IProjectedDocument { public readonly path: string; private content = ''; private hostDocumentVersion: number | null = null; + private _checksum: Uint8Array = new Uint8Array(); + private _checksumAlgorithm: number = 0; + private _encodingCodePage: number | null = null; public constructor(public readonly uri: vscode.Uri) { this.path = getUriPath(uri); @@ -29,7 +32,13 @@ export class HtmlProjectedDocument implements IProjectedDocument { this.setContent(''); } - public update(edits: ServerTextChange[], hostDocumentVersion: number) { + public update( + edits: ServerTextChange[], + hostDocumentVersion: number, + checksum: Uint8Array, + checksumAlgorithm: number, + encodingCodePage: number | null + ) { this.hostDocumentVersion = hostDocumentVersion; if (edits.length === 0) { @@ -43,12 +52,27 @@ export class HtmlProjectedDocument implements IProjectedDocument { } this.setContent(content); + this._checksum = checksum; + this._checksumAlgorithm = checksumAlgorithm; + this._encodingCodePage = encodingCodePage; } public getContent() { return this.content; } + public get checksum(): Uint8Array { + return this._checksum; + } + + public get checksumAlgorithm(): number { + return this._checksumAlgorithm; + } + + public get encodingCodePage(): number | null { + return this._encodingCodePage; + } + private getEditedContent(newText: string, start: number, end: number, content: string) { const before = content.substr(0, start); const after = content.substr(end); diff --git a/src/razor/src/projection/IProjectedDocument.ts b/src/razor/src/projection/IProjectedDocument.ts index f16ce6248..87256d1c8 100644 --- a/src/razor/src/projection/IProjectedDocument.ts +++ b/src/razor/src/projection/IProjectedDocument.ts @@ -11,4 +11,7 @@ export interface IProjectedDocument { readonly hostDocumentSyncVersion: number | null; readonly length: number; getContent(): string; + checksum: Uint8Array; + checksumAlgorithm: number; + encodingCodePage: number | null; } diff --git a/src/razor/src/rpc/updateBufferRequest.ts b/src/razor/src/rpc/updateBufferRequest.ts index 619886720..c7f3aacd4 100644 --- a/src/razor/src/rpc/updateBufferRequest.ts +++ b/src/razor/src/rpc/updateBufferRequest.ts @@ -10,6 +10,9 @@ export class UpdateBufferRequest { public readonly hostDocumentVersion: number, public readonly hostDocumentFilePath: string, public readonly changes: ServerTextChange[], - public readonly previousWasEmpty: boolean + public readonly previousWasEmpty: boolean, + public readonly checksum: Uint8Array, + public readonly checksumAlgorithm: number, + public readonly encodingCodePage: number | null ) {} } From fbfe1dc81bf7863f1230d4ea635ced84a7310462 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Tue, 3 Dec 2024 18:20:56 -0800 Subject: [PATCH 4/7] Use correct checksum values. Use base64 checksum --- .../src/csharp/csharpProjectedDocument.ts | 66 ++++++++++++------- .../src/document/razorDocumentManager.ts | 8 +-- .../src/dynamicFile/dynamicFileInfoHandler.ts | 40 ++++++++--- .../dynamicFile/provideDynamicFileParams.ts | 2 +- .../dynamicFile/provideDynamicFileResponse.ts | 2 +- src/razor/src/html/htmlProjectedDocument.ts | 26 +------- .../src/projection/IProjectedDocument.ts | 3 - src/razor/src/rpc/updateBufferRequest.ts | 2 +- 8 files changed, 80 insertions(+), 69 deletions(-) diff --git a/src/razor/src/csharp/csharpProjectedDocument.ts b/src/razor/src/csharp/csharpProjectedDocument.ts index 3ed95805f..e31ad27e6 100644 --- a/src/razor/src/csharp/csharpProjectedDocument.ts +++ b/src/razor/src/csharp/csharpProjectedDocument.ts @@ -19,9 +19,9 @@ export class CSharpProjectedDocument implements IProjectedDocument { private resolveProvisionalEditAt: number | undefined; private ProvisionalDotPosition: Position | undefined; private hostDocumentVersion: number | null = null; - private updates: Update[] | null = null; - private _checksum: Uint8Array = new Uint8Array(); - private _checksumAlgorithm: number = 0; + private updates: CSharpDocumentUpdate[] | null = null; + private _checksum: string = ''; + private _checksumAlgorithm: number = 1; // Default to Sha1 private _encodingCodePage: number | null = null; public constructor(public readonly uri: vscode.Uri) { @@ -36,7 +36,11 @@ export class CSharpProjectedDocument implements IProjectedDocument { return this.content.length; } - public get checksum(): Uint8Array { + public clear() { + this.setContent(''); + } + + public get checksum(): string { return this._checksum; } @@ -48,15 +52,11 @@ export class CSharpProjectedDocument implements IProjectedDocument { return this._encodingCodePage; } - public clear() { - this.setContent(''); - } - public update( hostDocumentIsOpen: boolean, edits: ServerTextChange[], hostDocumentVersion: number, - checksum: Uint8Array, + checksum: string, checksumAlgorithm: number, encodingCodePage: number | null ) { @@ -73,35 +73,45 @@ export class CSharpProjectedDocument implements IProjectedDocument { } this.updateContent(edits); + this._checksum = checksum; + this._checksumAlgorithm = checksumAlgorithm; + this._encodingCodePage = encodingCodePage; } else { + const update = new CSharpDocumentUpdate(edits, checksum, checksumAlgorithm, encodingCodePage); + if (this.updates) { - this.updates = this.updates.concat(new Update(edits)); + this.updates = this.updates.concat(update); } else { - this.updates = [new Update(edits)]; + this.updates = [update]; } } - this._checksum = checksum; - this._checksumAlgorithm = checksumAlgorithm; - this._encodingCodePage = encodingCodePage; this.hostDocumentVersion = hostDocumentVersion; } - public getAndApplyEdits() { + public applyEdits(): ApplyEditsResponse { const updates = this.updates; this.updates = null; + const originalChecksum = this._checksum; + const originalChecksumAlgorithm = this._checksumAlgorithm; + const originalEncodingCodePage = this._encodingCodePage; + if (updates) { - let changes: ServerTextChange[] = []; for (const update of updates) { this.updateContent(update.changes); - changes = changes.concat(update.changes); + this._checksum = update.checksum; + this._checksumAlgorithm = update.checksumAlgorithm; + this._encodingCodePage = update.encodingCodePage; } - - return changes; } - return null; + return { + edits: updates, + originalChecksum: originalChecksum, + originalChecksumAlgorithm: originalChecksumAlgorithm, + originalEncodingCodePage: originalEncodingCodePage, + }; } public getContent() { @@ -216,6 +226,18 @@ export class CSharpProjectedDocument implements IProjectedDocument { } } -class Update { - constructor(public readonly changes: ServerTextChange[]) {} +export class CSharpDocumentUpdate { + constructor( + public readonly changes: ServerTextChange[], + public readonly checksum: string, + public readonly checksumAlgorithm: number, + public readonly encodingCodePage: number | null + ) {} +} + +export interface ApplyEditsResponse { + edits: CSharpDocumentUpdate[] | null; + originalChecksum: string; + originalChecksumAlgorithm: number; + originalEncodingCodePage: number | null; } diff --git a/src/razor/src/document/razorDocumentManager.ts b/src/razor/src/document/razorDocumentManager.ts index 115cf81b4..b90e303fd 100644 --- a/src/razor/src/document/razorDocumentManager.ts +++ b/src/razor/src/document/razorDocumentManager.ts @@ -308,13 +308,7 @@ export class RazorDocumentManager implements IRazorDocumentManager { htmlProjectedDocument.clear(); } - htmlProjectedDocument.update( - updateBufferRequest.changes, - updateBufferRequest.hostDocumentVersion, - updateBufferRequest.checksum, - updateBufferRequest.checksumAlgorithm, - updateBufferRequest.encodingCodePage - ); + htmlProjectedDocument.update(updateBufferRequest.changes, updateBufferRequest.hostDocumentVersion); this.notifyDocumentChange(document, RazorDocumentChangeKind.htmlChanged); } else { diff --git a/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts b/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts index 2fedb9337..430c5dabe 100644 --- a/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts +++ b/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts @@ -14,6 +14,7 @@ import { CSharpProjectedDocument } from '../csharp/csharpProjectedDocument'; import { RazorDocumentChangeKind } from '../document/razorDocumentChangeKind'; import { RazorDynamicFileChangedParams } from './dynamicFileUpdatedParams'; import { TextDocumentIdentifier } from 'vscode-languageserver-protocol'; +import { ServerTextChange } from '../rpc/serverTextChange'; // Handles Razor generated doc communication between the Roslyn workspace and Razor. // didChange behavior for Razor generated docs is handled in the RazorDocumentManager. @@ -70,6 +71,27 @@ export class DynamicFileInfoHandler { return null; } + const csharpDocument = razorDocument.csharpDocument as CSharpProjectedDocument; + if (request.fullText) { + // The server asked for a full replace so the newtext is the important + // thing here, the span doesn't matter. + const change: ServerTextChange = { + newText: razorDocument.csharpDocument.getContent(), + span: { + start: 0, + length: 0, + }, + }; + + return new ProvideDynamicFileResponse( + request.razorDocument, + [change], + csharpDocument.checksum, + csharpDocument.checksumAlgorithm, + csharpDocument.encodingCodePage + ); + } + const virtualCsharpUri = UriConverter.serialize(razorDocument.csharpDocument.uri); if (this.documentManager.isRazorDocumentOpenInCSharpWorkspace(vscodeUri)) { @@ -78,22 +100,22 @@ export class DynamicFileInfoHandler { return new ProvideDynamicFileResponse( { uri: virtualCsharpUri }, null, - razorDocument.csharpDocument.checksum, - razorDocument.csharpDocument.checksumAlgorithm, - razorDocument.csharpDocument.encodingCodePage + csharpDocument.checksum, + csharpDocument.checksumAlgorithm, + csharpDocument.encodingCodePage ); } else { // Closed documents provide edits since the last time they were requested since // there is no open buffer in vscode corresponding to the csharp content. - const csharpDocument = razorDocument.csharpDocument as CSharpProjectedDocument; - const edits = csharpDocument.getAndApplyEdits(); + const response = csharpDocument.applyEdits(); + const changes = response.edits?.map((e) => e.changes).reduce((a, b) => a.concat(b)) ?? null; return new ProvideDynamicFileResponse( { uri: virtualCsharpUri }, - edits ?? null, - razorDocument.csharpDocument.checksum, - razorDocument.csharpDocument.checksumAlgorithm, - razorDocument.csharpDocument.encodingCodePage + changes, + response.originalChecksum, + response.originalChecksumAlgorithm, + response.originalEncodingCodePage ); } } catch (error) { diff --git a/src/razor/src/dynamicFile/provideDynamicFileParams.ts b/src/razor/src/dynamicFile/provideDynamicFileParams.ts index 58241b99b..1993f45e5 100644 --- a/src/razor/src/dynamicFile/provideDynamicFileParams.ts +++ b/src/razor/src/dynamicFile/provideDynamicFileParams.ts @@ -7,5 +7,5 @@ import { TextDocumentIdentifier } from 'vscode-languageserver-protocol'; // matches https://github.com/dotnet/roslyn/blob/9e91ca6590450e66e0041ee3135bbf044ac0687a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/HostWorkspace/RazorDynamicFileInfoProvider.cs#L22 export class ProvideDynamicFileParams { - constructor(public readonly razorDocument: TextDocumentIdentifier) {} + constructor(public readonly razorDocument: TextDocumentIdentifier, public readonly fullText: boolean) {} } diff --git a/src/razor/src/dynamicFile/provideDynamicFileResponse.ts b/src/razor/src/dynamicFile/provideDynamicFileResponse.ts index 0cee9f476..affb5356b 100644 --- a/src/razor/src/dynamicFile/provideDynamicFileResponse.ts +++ b/src/razor/src/dynamicFile/provideDynamicFileResponse.ts @@ -11,7 +11,7 @@ export class ProvideDynamicFileResponse { constructor( public readonly csharpDocument: TextDocumentIdentifier | null, public readonly edits: ServerTextChange[] | null, - public readonly checksum: Uint8Array, + public readonly checksum: string, public readonly checksumAlgorithm: number, public readonly encodingCodePage: number | null ) {} diff --git a/src/razor/src/html/htmlProjectedDocument.ts b/src/razor/src/html/htmlProjectedDocument.ts index 6c9671079..1ec11bc5d 100644 --- a/src/razor/src/html/htmlProjectedDocument.ts +++ b/src/razor/src/html/htmlProjectedDocument.ts @@ -12,9 +12,6 @@ export class HtmlProjectedDocument implements IProjectedDocument { public readonly path: string; private content = ''; private hostDocumentVersion: number | null = null; - private _checksum: Uint8Array = new Uint8Array(); - private _checksumAlgorithm: number = 0; - private _encodingCodePage: number | null = null; public constructor(public readonly uri: vscode.Uri) { this.path = getUriPath(uri); @@ -32,13 +29,7 @@ export class HtmlProjectedDocument implements IProjectedDocument { this.setContent(''); } - public update( - edits: ServerTextChange[], - hostDocumentVersion: number, - checksum: Uint8Array, - checksumAlgorithm: number, - encodingCodePage: number | null - ) { + public update(edits: ServerTextChange[], hostDocumentVersion: number) { this.hostDocumentVersion = hostDocumentVersion; if (edits.length === 0) { @@ -52,27 +43,12 @@ export class HtmlProjectedDocument implements IProjectedDocument { } this.setContent(content); - this._checksum = checksum; - this._checksumAlgorithm = checksumAlgorithm; - this._encodingCodePage = encodingCodePage; } public getContent() { return this.content; } - public get checksum(): Uint8Array { - return this._checksum; - } - - public get checksumAlgorithm(): number { - return this._checksumAlgorithm; - } - - public get encodingCodePage(): number | null { - return this._encodingCodePage; - } - private getEditedContent(newText: string, start: number, end: number, content: string) { const before = content.substr(0, start); const after = content.substr(end); diff --git a/src/razor/src/projection/IProjectedDocument.ts b/src/razor/src/projection/IProjectedDocument.ts index 87256d1c8..f16ce6248 100644 --- a/src/razor/src/projection/IProjectedDocument.ts +++ b/src/razor/src/projection/IProjectedDocument.ts @@ -11,7 +11,4 @@ export interface IProjectedDocument { readonly hostDocumentSyncVersion: number | null; readonly length: number; getContent(): string; - checksum: Uint8Array; - checksumAlgorithm: number; - encodingCodePage: number | null; } diff --git a/src/razor/src/rpc/updateBufferRequest.ts b/src/razor/src/rpc/updateBufferRequest.ts index c7f3aacd4..2bf3ad74e 100644 --- a/src/razor/src/rpc/updateBufferRequest.ts +++ b/src/razor/src/rpc/updateBufferRequest.ts @@ -11,7 +11,7 @@ export class UpdateBufferRequest { public readonly hostDocumentFilePath: string, public readonly changes: ServerTextChange[], public readonly previousWasEmpty: boolean, - public readonly checksum: Uint8Array, + public readonly checksum: string, public readonly checksumAlgorithm: number, public readonly encodingCodePage: number | null ) {} From 0b09369280a6fc8779a6276d908297e9ede64209 Mon Sep 17 00:00:00 2001 From: "Andrew Hall (METAL)" Date: Thu, 5 Dec 2024 12:37:32 -0800 Subject: [PATCH 5/7] update packages --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 872b217e4..e3a258394 100644 --- a/package.json +++ b/package.json @@ -37,9 +37,9 @@ } }, "defaults": { - "roslyn": "4.13.0-3.24577.4", + "roslyn": "4.13.0-3.24605.1", "omniSharp": "1.39.11", - "razor": "9.0.0-preview.24569.4", + "razor": "9.0.0-preview.24605.1", "razorOmnisharp": "7.0.0-preview.23363.1", "xamlTools": "17.13.35527.19" }, From 5b3e0eb3ecc60d87286e632dd7c9a22aeb897c30 Mon Sep 17 00:00:00 2001 From: "Andrew Hall (METAL)" Date: Thu, 5 Dec 2024 13:28:45 -0800 Subject: [PATCH 6/7] Pass multiple updates as sets of edits --- src/razor/src/dynamicFile/dynamicFileInfoHandler.ts | 10 ++++++---- .../src/dynamicFile/provideDynamicFileResponse.ts | 6 +++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts b/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts index 430c5dabe..62300f0f5 100644 --- a/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts +++ b/src/razor/src/dynamicFile/dynamicFileInfoHandler.ts @@ -8,7 +8,7 @@ import { UriConverter } from '../../../lsptoolshost/uriConverter'; import { RazorDocumentManager } from '../document/razorDocumentManager'; import { RazorLogger } from '../razorLogger'; import { ProvideDynamicFileParams } from './provideDynamicFileParams'; -import { ProvideDynamicFileResponse } from './provideDynamicFileResponse'; +import { ProvideDynamicFileResponse, DynamicFileUpdate } from './provideDynamicFileResponse'; import { RemoveDynamicFileParams } from './removeDynamicFileParams'; import { CSharpProjectedDocument } from '../csharp/csharpProjectedDocument'; import { RazorDocumentChangeKind } from '../document/razorDocumentChangeKind'; @@ -83,9 +83,11 @@ export class DynamicFileInfoHandler { }, }; + const update = new DynamicFileUpdate([change]); + return new ProvideDynamicFileResponse( request.razorDocument, - [change], + [update], csharpDocument.checksum, csharpDocument.checksumAlgorithm, csharpDocument.encodingCodePage @@ -108,11 +110,11 @@ export class DynamicFileInfoHandler { // Closed documents provide edits since the last time they were requested since // there is no open buffer in vscode corresponding to the csharp content. const response = csharpDocument.applyEdits(); - const changes = response.edits?.map((e) => e.changes).reduce((a, b) => a.concat(b)) ?? null; + const updates = response.edits?.map((e) => new DynamicFileUpdate(e.changes)) ?? null; return new ProvideDynamicFileResponse( { uri: virtualCsharpUri }, - changes, + updates, response.originalChecksum, response.originalChecksumAlgorithm, response.originalEncodingCodePage diff --git a/src/razor/src/dynamicFile/provideDynamicFileResponse.ts b/src/razor/src/dynamicFile/provideDynamicFileResponse.ts index affb5356b..d1ff807bc 100644 --- a/src/razor/src/dynamicFile/provideDynamicFileResponse.ts +++ b/src/razor/src/dynamicFile/provideDynamicFileResponse.ts @@ -10,9 +10,13 @@ import { ServerTextChange } from '../rpc/serverTextChange'; export class ProvideDynamicFileResponse { constructor( public readonly csharpDocument: TextDocumentIdentifier | null, - public readonly edits: ServerTextChange[] | null, + public readonly updates: DynamicFileUpdate[] | null, public readonly checksum: string, public readonly checksumAlgorithm: number, public readonly encodingCodePage: number | null ) {} } + +export class DynamicFileUpdate { + constructor(public readonly edits: ServerTextChange[]) {} +} From 505add12049b964e9d8deb58897dddb52b682dcf Mon Sep 17 00:00:00 2001 From: "Andrew Hall (METAL)" Date: Fri, 6 Dec 2024 13:39:16 -0800 Subject: [PATCH 7/7] Update roslyn version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e3a258394..a80260071 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ } }, "defaults": { - "roslyn": "4.13.0-3.24605.1", + "roslyn": "4.13.0-3.24605.12", "omniSharp": "1.39.11", "razor": "9.0.0-preview.24605.1", "razorOmnisharp": "7.0.0-preview.23363.1",