Skip to content

Commit 0383c5d

Browse files
authored
Don't open closed documents (#7826)
2 parents 5e9cd4a + 1c03a02 commit 0383c5d

12 files changed

+261
-84
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@
3737
}
3838
},
3939
"defaults": {
40-
"roslyn": "4.13.0-3.24604.4",
40+
"roslyn": "4.13.0-3.24605.12",
4141
"omniSharp": "1.39.11",
42-
"razor": "9.0.0-preview.24569.4",
42+
"razor": "9.0.0-preview.24605.1",
4343
"razorOmnisharp": "7.0.0-preview.23363.1",
4444
"xamlTools": "17.13.35606.23"
4545
},

src/lsptoolshost/roslynLanguageServer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ import {
7878
import { registerSourceGeneratedFilesContentProvider } from './sourceGeneratedFilesContentProvider';
7979
import { registerMiscellaneousFileNotifier } from './miscellaneousFileNotifier';
8080
import { TelemetryEventNames } from '../shared/telemetryEventNames';
81+
import { RazorDynamicFileChangedParams } from '../razor/src/dynamicFile/dynamicFileUpdatedParams';
8182

8283
let _channel: vscode.LogOutputChannel;
8384
let _traceChannel: vscode.OutputChannel;
@@ -789,6 +790,11 @@ export class RoslynLanguageServer {
789790
async (notification) =>
790791
vscode.commands.executeCommand(DynamicFileInfoHandler.removeDynamicFileInfoCommand, notification)
791792
);
793+
vscode.commands.registerCommand(
794+
DynamicFileInfoHandler.dynamicFileUpdatedCommand,
795+
async (notification: RazorDynamicFileChangedParams) =>
796+
this.sendNotification<RazorDynamicFileChangedParams>('razor/dynamicFileInfoChanged', notification)
797+
);
792798
}
793799

794800
// eslint-disable-next-line @typescript-eslint/promise-function-async

src/razor/src/csharp/csharpProjectedDocument.ts

Lines changed: 102 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ export class CSharpProjectedDocument implements IProjectedDocument {
1919
private resolveProvisionalEditAt: number | undefined;
2020
private ProvisionalDotPosition: Position | undefined;
2121
private hostDocumentVersion: number | null = null;
22+
private updates: CSharpDocumentUpdate[] | null = null;
23+
private _checksum: string = '';
24+
private _checksumAlgorithm: number = 1; // Default to Sha1
25+
private _encodingCodePage: number | null = null;
2226

2327
public constructor(public readonly uri: vscode.Uri) {
2428
this.path = getUriPath(uri);
@@ -36,22 +40,78 @@ export class CSharpProjectedDocument implements IProjectedDocument {
3640
this.setContent('');
3741
}
3842

39-
public update(edits: ServerTextChange[], hostDocumentVersion: number) {
40-
this.removeProvisionalDot();
43+
public get checksum(): string {
44+
return this._checksum;
45+
}
4146

42-
this.hostDocumentVersion = hostDocumentVersion;
47+
public get checksumAlgorithm(): number {
48+
return this._checksumAlgorithm;
49+
}
4350

44-
if (edits.length === 0) {
45-
return;
51+
public get encodingCodePage(): number | null {
52+
return this._encodingCodePage;
53+
}
54+
55+
public update(
56+
hostDocumentIsOpen: boolean,
57+
edits: ServerTextChange[],
58+
hostDocumentVersion: number,
59+
checksum: string,
60+
checksumAlgorithm: number,
61+
encodingCodePage: number | null
62+
) {
63+
if (hostDocumentIsOpen) {
64+
this.removeProvisionalDot();
65+
66+
// Apply any stored edits if needed
67+
if (this.updates) {
68+
for (const update of this.updates) {
69+
this.updateContent(update.changes);
70+
}
71+
72+
this.updates = null;
73+
}
74+
75+
this.updateContent(edits);
76+
this._checksum = checksum;
77+
this._checksumAlgorithm = checksumAlgorithm;
78+
this._encodingCodePage = encodingCodePage;
79+
} else {
80+
const update = new CSharpDocumentUpdate(edits, checksum, checksumAlgorithm, encodingCodePage);
81+
82+
if (this.updates) {
83+
this.updates = this.updates.concat(update);
84+
} else {
85+
this.updates = [update];
86+
}
4687
}
4788

48-
let content = this.content;
49-
for (const edit of edits.reverse()) {
50-
// TODO: Use a better data structure to represent the content, string concatenation is slow.
51-
content = this.getEditedContent(edit.newText, edit.span.start, edit.span.start + edit.span.length, content);
89+
this.hostDocumentVersion = hostDocumentVersion;
90+
}
91+
92+
public applyEdits(): ApplyEditsResponse {
93+
const updates = this.updates;
94+
this.updates = null;
95+
96+
const originalChecksum = this._checksum;
97+
const originalChecksumAlgorithm = this._checksumAlgorithm;
98+
const originalEncodingCodePage = this._encodingCodePage;
99+
100+
if (updates) {
101+
for (const update of updates) {
102+
this.updateContent(update.changes);
103+
this._checksum = update.checksum;
104+
this._checksumAlgorithm = update.checksumAlgorithm;
105+
this._encodingCodePage = update.encodingCodePage;
106+
}
52107
}
53108

54-
this.setContent(content);
109+
return {
110+
edits: updates,
111+
originalChecksum: originalChecksum,
112+
originalChecksumAlgorithm: originalChecksumAlgorithm,
113+
originalEncodingCodePage: originalEncodingCodePage,
114+
};
55115
}
56116

57117
public getContent() {
@@ -140,8 +200,8 @@ export class CSharpProjectedDocument implements IProjectedDocument {
140200
}
141201

142202
private getEditedContent(newText: string, start: number, end: number, content: string) {
143-
const before = content.substr(0, start);
144-
const after = content.substr(end);
203+
const before = content.substring(0, start);
204+
const after = content.substring(end);
145205
content = `${before}${newText}${after}`;
146206

147207
return content;
@@ -150,4 +210,34 @@ export class CSharpProjectedDocument implements IProjectedDocument {
150210
private setContent(content: string) {
151211
this.content = content;
152212
}
213+
214+
private updateContent(edits: ServerTextChange[]) {
215+
if (edits.length === 0) {
216+
return;
217+
}
218+
219+
let content = this.content;
220+
for (const edit of edits.reverse()) {
221+
// TODO: Use a better data structure to represent the content, string concatenation is slow.
222+
content = this.getEditedContent(edit.newText, edit.span.start, edit.span.start + edit.span.length, content);
223+
}
224+
225+
this.setContent(content);
226+
}
227+
}
228+
229+
export class CSharpDocumentUpdate {
230+
constructor(
231+
public readonly changes: ServerTextChange[],
232+
public readonly checksum: string,
233+
public readonly checksumAlgorithm: number,
234+
public readonly encodingCodePage: number | null
235+
) {}
236+
}
237+
238+
export interface ApplyEditsResponse {
239+
edits: CSharpDocumentUpdate[] | null;
240+
originalChecksum: string;
241+
originalChecksumAlgorithm: number;
242+
originalEncodingCodePage: number | null;
153243
}

src/razor/src/document/IRazorDocument.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ export interface IRazorDocument {
1111
readonly uri: vscode.Uri;
1212
readonly csharpDocument: IProjectedDocument;
1313
readonly htmlDocument: IProjectedDocument;
14+
readonly isOpen: boolean;
1415
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import * as vscode from 'vscode';
7+
import { CSharpProjectedDocument } from '../csharp/csharpProjectedDocument';
8+
import { HtmlProjectedDocument } from '../html/htmlProjectedDocument';
9+
import { getUriPath } from '../uriPaths';
10+
import { IRazorDocument } from './IRazorDocument';
11+
12+
export class RazorDocument implements IRazorDocument {
13+
public readonly path: string;
14+
15+
constructor(
16+
readonly uri: vscode.Uri,
17+
readonly csharpDocument: CSharpProjectedDocument,
18+
readonly htmlDocument: HtmlProjectedDocument
19+
) {
20+
this.path = getUriPath(uri);
21+
}
22+
23+
public get isOpen(): boolean {
24+
for (const textDocument of vscode.workspace.textDocuments) {
25+
if (textDocument.uri.fsPath == this.uri.fsPath) {
26+
return true;
27+
}
28+
}
29+
30+
return false;
31+
}
32+
}

src/razor/src/document/razorDocumentFactory.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,12 @@ import { HtmlProjectedDocumentContentProvider } from '../html/htmlProjectedDocum
1111
import { virtualCSharpSuffix, virtualHtmlSuffix } from '../razorConventions';
1212
import { getUriPath } from '../uriPaths';
1313
import { IRazorDocument } from './IRazorDocument';
14+
import { RazorDocument } from './razorDocument';
1415

15-
export function createDocument(uri: vscode.Uri) {
16+
export function createDocument(uri: vscode.Uri): IRazorDocument {
1617
const csharpDocument = createProjectedCSharpDocument(uri);
1718
const htmlDocument = createProjectedHtmlDocument(uri);
18-
const path = getUriPath(uri);
19-
20-
const document: IRazorDocument = {
21-
uri,
22-
path,
23-
csharpDocument,
24-
htmlDocument,
25-
};
19+
const document = new RazorDocument(uri, csharpDocument, htmlDocument);
2620

2721
return document;
2822
}

src/razor/src/document/razorDocumentManager.ts

Lines changed: 13 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,12 @@ export class RazorDocumentManager implements IRazorDocumentManager {
5050
return Object.values(this.razorDocuments);
5151
}
5252

53-
public async getDocument(uri: vscode.Uri) {
53+
public async getDocument(uri: vscode.Uri): Promise<IRazorDocument> {
5454
const document = this._getDocument(uri);
55-
56-
// VS Code closes virtual documents after some timeout if they are not open in the IDE. Since our generated C# and Html
57-
// documents are never open in the IDE, we need to ensure that VS Code considers them open so that requests against them
58-
// succeed. Without this, even a simple diagnostics request will fail in Roslyn if the user just opens a .razor document
59-
// and leaves it open past the timeout.
60-
if (this.razorDocumentGenerationInitialized) {
61-
await this.ensureDocumentAndProjectedDocumentsOpen(document);
62-
}
63-
6455
return document;
6556
}
6657

67-
public async getActiveDocument() {
58+
public async getActiveDocument(): Promise<IRazorDocument | null> {
6859
if (!vscode.window.activeTextEditor) {
6960
return null;
7061
}
@@ -147,7 +138,7 @@ export class RazorDocumentManager implements IRazorDocumentManager {
147138
return vscode.Disposable.from(watcher, didCreateRegistration, didOpenRegistration, didCloseRegistration);
148139
}
149140

150-
private _getDocument(uri: vscode.Uri) {
141+
private _getDocument(uri: vscode.Uri): IRazorDocument {
151142
const path = getUriPath(uri);
152143
let document = this.findDocument(path);
153144

@@ -159,7 +150,7 @@ export class RazorDocumentManager implements IRazorDocumentManager {
159150
document = this.addDocument(uri);
160151
}
161152

162-
return document;
153+
return document!;
163154
}
164155

165156
private async openDocument(uri: vscode.Uri) {
@@ -182,10 +173,6 @@ export class RazorDocumentManager implements IRazorDocumentManager {
182173
await vscode.commands.executeCommand(razorInitializeCommand, pipeName);
183174
await this.serverClient.connectNamedPipe(pipeName);
184175

185-
for (const document of this.documents) {
186-
await this.ensureDocumentAndProjectedDocumentsOpen(document);
187-
}
188-
189176
this.onRazorInitializedEmitter.fire();
190177
}
191178
}
@@ -205,7 +192,7 @@ export class RazorDocumentManager implements IRazorDocumentManager {
205192
this.notifyDocumentChange(document, RazorDocumentChangeKind.closed);
206193
}
207194

208-
private addDocument(uri: vscode.Uri) {
195+
private addDocument(uri: vscode.Uri): IRazorDocument {
209196
const path = getUriPath(uri);
210197
let document = this.findDocument(path);
211198
if (document) {
@@ -261,10 +248,6 @@ export class RazorDocumentManager implements IRazorDocumentManager {
261248
) {
262249
// We allow re-setting of the updated content from the same doc sync version in the case
263250
// of project or file import changes.
264-
265-
// Make sure the document is open, because updating will cause a didChange event to fire.
266-
await vscode.workspace.openTextDocument(document.csharpDocument.uri);
267-
268251
const csharpProjectedDocument = projectedDocument as CSharpProjectedDocument;
269252

270253
// If the language server is telling us that the previous document was empty, then we should clear
@@ -275,7 +258,14 @@ export class RazorDocumentManager implements IRazorDocumentManager {
275258
csharpProjectedDocument.clear();
276259
}
277260

278-
csharpProjectedDocument.update(updateBufferRequest.changes, updateBufferRequest.hostDocumentVersion);
261+
csharpProjectedDocument.update(
262+
document.isOpen,
263+
updateBufferRequest.changes,
264+
updateBufferRequest.hostDocumentVersion,
265+
updateBufferRequest.checksum,
266+
updateBufferRequest.checksumAlgorithm,
267+
updateBufferRequest.encodingCodePage
268+
);
279269

280270
this.notifyDocumentChange(document, RazorDocumentChangeKind.csharpChanged);
281271
} else {
@@ -342,22 +332,4 @@ export class RazorDocumentManager implements IRazorDocumentManager {
342332

343333
this.onChangeEmitter.fire(args);
344334
}
345-
346-
private async ensureDocumentAndProjectedDocumentsOpen(document: IRazorDocument) {
347-
// vscode.workspace.openTextDocument may send a textDocument/didOpen
348-
// request to the C# language server. We need to keep track of
349-
// this to make sure we don't send a duplicate request later on.
350-
const razorUri = vscode.Uri.file(document.path);
351-
if (!this.isRazorDocumentOpenInCSharpWorkspace(razorUri)) {
352-
this.didOpenRazorCSharpDocument(razorUri);
353-
354-
// Need to tell the Razor server that the document is open, or it won't generate C# code
355-
// for it, and our projected document will always be empty, until the user manually
356-
// opens the razor file.
357-
await vscode.workspace.openTextDocument(razorUri);
358-
}
359-
360-
await vscode.workspace.openTextDocument(document.csharpDocument.uri);
361-
await vscode.workspace.openTextDocument(document.htmlDocument.uri);
362-
}
363335
}

0 commit comments

Comments
 (0)