Skip to content

Commit ce999a5

Browse files
committed
Wait for a didChange rather than polling
1 parent 82537a4 commit ce999a5

File tree

3 files changed

+71
-26
lines changed

3 files changed

+71
-26
lines changed

src/lsptoolshost/razor/htmlDocument.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ export class HtmlDocument {
1010
public readonly path: string;
1111
private content = '';
1212
private checksum = '';
13-
private previousVersion = -1;
1413

1514
public constructor(public readonly uri: vscode.Uri, checksum: string) {
1615
this.path = getUriPath(uri);
@@ -25,21 +24,8 @@ export class HtmlDocument {
2524
return this.checksum;
2625
}
2726

28-
public async setContent(checksum: string, content: string) {
29-
const document = await vscode.workspace.openTextDocument(this.uri);
30-
// Capture the version _before_ the change, so we can know for sure if it's been seen
31-
this.previousVersion = document.version;
27+
public setContent(checksum: string, content: string) {
3228
this.checksum = checksum;
3329
this.content = content;
3430
}
35-
36-
public async waitForBufferUpdate() {
37-
const document = await vscode.workspace.openTextDocument(this.uri);
38-
39-
// Wait for VS Code to process any previous content change. We don't care about finding
40-
// a specific version, just that it's moved on from the previous one.
41-
while (document.version === this.previousVersion) {
42-
await new Promise((resolve) => setTimeout(resolve, 5));
43-
}
44-
}
4531
}

src/lsptoolshost/razor/htmlDocumentManager.ts

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ import { UriConverter } from '../utils/uriConverter';
1717
export class HtmlDocumentManager {
1818
private readonly htmlDocuments: { [hostDocumentPath: string]: HtmlDocument } = {};
1919
private readonly contentProvider: HtmlDocumentContentProvider;
20+
private readonly pendingUpdates: {
21+
[documentPath: string]: {
22+
promise: Promise<void>;
23+
resolve: () => void;
24+
reject: (error: any) => void;
25+
};
26+
} = {};
2027

2128
private readonly razorDocumentClosedRequest: RequestType<TextDocumentIdentifier, void, Error> = new RequestType(
2229
'razor/documentClosed'
@@ -35,6 +42,20 @@ export class HtmlDocumentManager {
3542
}
3643

3744
public register() {
45+
const didChangeRegistration = vscode.workspace.onDidChangeTextDocument((e) => {
46+
// Check if this document is being monitored for updates
47+
if (e.document.uri.scheme === HtmlDocumentContentProvider.scheme) {
48+
const documentPath = getUriPath(e.document.uri);
49+
const pendingUpdate = this.pendingUpdates[documentPath];
50+
51+
if (pendingUpdate) {
52+
// Document has been updated, resolve the promise
53+
pendingUpdate.resolve();
54+
delete this.pendingUpdates[documentPath];
55+
}
56+
}
57+
});
58+
3859
const didCloseRegistration = vscode.workspace.onDidCloseTextDocument(async (document) => {
3960
// We log when a virtual document is closed just in case it helps track down future bugs
4061
if (document.uri.scheme === HtmlDocumentContentProvider.scheme) {
@@ -63,7 +84,7 @@ export class HtmlDocumentManager {
6384
this.contentProvider
6485
);
6586

66-
return vscode.Disposable.from(didCloseRegistration, providerRegistration);
87+
return vscode.Disposable.from(didChangeRegistration, didCloseRegistration, providerRegistration);
6788
}
6889

6990
public async updateDocumentText(uri: vscode.Uri, checksum: string, text: string) {
@@ -79,7 +100,20 @@ export class HtmlDocumentManager {
79100

80101
this.logger.logTrace(`New content for '${uri}', updating '${document.path}', checksum '${checksum}'.`);
81102

82-
await document.setContent(checksum, text);
103+
// Create a promise for this document update
104+
let resolve: () => void;
105+
let reject: (error: any) => void;
106+
const updatePromise = new Promise<void>((res, rej) => {
107+
resolve = res;
108+
reject = rej;
109+
});
110+
111+
this.pendingUpdates[document.path] = { promise: updatePromise, resolve: resolve!, reject: reject! };
112+
113+
// Now update the document and fire the change event so VS Code will inform the Html language client.
114+
await vscode.workspace.openTextDocument(document.uri);
115+
116+
document.setContent(checksum, text);
83117

84118
this.contentProvider.fireDidChange(document.uri);
85119
}
@@ -90,6 +124,13 @@ export class HtmlDocumentManager {
90124
if (document) {
91125
this.logger.logTrace(`Removing '${document.uri}' from the document manager.`);
92126

127+
// Clean up any pending update promises for this document
128+
const pendingUpdate = this.pendingUpdates[document.path];
129+
if (pendingUpdate) {
130+
pendingUpdate.reject(new Error('Document was closed before update completed'));
131+
delete this.pendingUpdates[document.path];
132+
}
133+
93134
delete this.htmlDocuments[document.path];
94135
}
95136
}
@@ -109,10 +150,35 @@ export class HtmlDocumentManager {
109150
return undefined;
110151
}
111152

112-
// No checksum, just give them the latest document and hope they know what to do with it.
113-
114153
await vscode.workspace.openTextDocument(document.uri);
115154

155+
if (checksum) {
156+
// If checksum is supplied, that means we're getting this document because we're about to call an LSP method
157+
// on it. We know that we've got the right document, and we've been told about the right content by the server,
158+
// but all we can be sure of at this point is that we've fired the change event for it. The event firing
159+
// is async, and the didChange notification that it would generate is a notification, so doesn't necessarily
160+
// block. Before we actually make a call to the Html server, we should at least make sure that the document
161+
// update has been seen by VS Code. We can't get access to the Html language client specifically to check if it
162+
// has seen it, but we can trust that ordering will be preserved at least.
163+
const pendingUpdate = this.pendingUpdates[document.path];
164+
if (pendingUpdate) {
165+
try {
166+
// Wait for the update promise with a 5 second timeout
167+
await Promise.race([
168+
pendingUpdate.promise,
169+
new Promise((_, reject) =>
170+
setTimeout(() => reject(new Error('Document update timeout')), 5000)
171+
),
172+
]);
173+
} catch (error) {
174+
this.logger.logWarning(`Failed to wait for document update: ${error}`);
175+
} finally {
176+
// Clean up the promise reference
177+
delete this.pendingUpdates[document.path];
178+
}
179+
}
180+
}
181+
116182
return document;
117183
}
118184

src/lsptoolshost/razor/razorEndpoints.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -252,13 +252,6 @@ export function registerRazorEndpoints(
252252
return undefined;
253253
}
254254

255-
// We know that we've got the right document, and we've been told about the right content by the server,
256-
// but all we can be sure of at this point is that we've fired the change event for it. The event firing
257-
// is async, and the didChange notification that it would generate is a notification, so doesn't necessarily
258-
// block. Before we actually make a call to the Html server, we should at least make sure that the document
259-
// version is not still the same as before we updated the content.
260-
await document.waitForBufferUpdate();
261-
262255
return invocation(document, params.request);
263256
});
264257
}

0 commit comments

Comments
 (0)