Skip to content

Commit 0251396

Browse files
committed
Add client side support for refreshing source generated files
1 parent 5fc720a commit 0251396

File tree

4 files changed

+101
-11
lines changed

4 files changed

+101
-11
lines changed

l10n/bundle.l10n.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@
163163
"Fix all issues": "Fix all issues",
164164
"Select fix all action": "Select fix all action",
165165
"Test run already in progress": "Test run already in progress",
166+
"Generated document not found": "Generated document not found",
166167
"Server stopped": "Server stopped",
167168
"Workspace projects": "Workspace projects",
168169
"Your workspace has multiple Visual Studio Solution files; please select one to get full IntelliSense.": "Your workspace has multiple Visual Studio Solution files; please select one to get full IntelliSense.",

src/lsptoolshost/roslynLanguageServer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
CancellationToken,
3030
RequestHandler,
3131
ResponseError,
32+
NotificationHandler0,
3233
} from 'vscode-languageclient/node';
3334
import { PlatformInformation } from '../shared/platform';
3435
import { readConfigurations } from './configurationMiddleware';
@@ -423,6 +424,10 @@ export class RoslynLanguageServer {
423424
this._languageClient.addDisposable(this._languageClient.onRequest(type, handler));
424425
}
425426

427+
public registerOnNotification(method: string, handler: NotificationHandler0) {
428+
this._languageClient.addDisposable(this._languageClient.onNotification(method, handler));
429+
}
430+
426431
public async registerSolutionSnapshot(token: vscode.CancellationToken): Promise<SolutionSnapshotId> {
427432
const response = await this.sendRequest0(RoslynProtocol.RegisterSolutionSnapshotRequest.type, token);
428433
if (response) {

src/lsptoolshost/roslynProtocol.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,10 +233,12 @@ export interface CopilotRelatedDocumentsReport {
233233

234234
export interface SourceGeneratorGetRequestParams {
235235
textDocument: lsp.TextDocumentIdentifier;
236+
resultId?: string;
236237
}
237238

238239
export interface SourceGeneratedDocumentText {
239-
text: string;
240+
text?: string;
241+
resultId?: string;
240242
}
241243

242244
export namespace WorkspaceDebugConfigurationRequest {
@@ -366,3 +368,9 @@ export namespace SourceGeneratorGetTextRequest {
366368
export const messageDirection: lsp.MessageDirection = lsp.MessageDirection.clientToServer;
367369
export const type = new lsp.RequestType<SourceGeneratorGetRequestParams, SourceGeneratedDocumentText, void>(method);
368370
}
371+
372+
export namespace RefreshSourceGeneratedDocumentNotification {
373+
export const method = 'workspace/refreshSourceGeneratedDocument';
374+
export const messageDirection: lsp.MessageDirection = lsp.MessageDirection.serverToClient;
375+
export const type = new lsp.NotificationType(method);
376+
}

src/lsptoolshost/sourceGeneratedFilesContentProvider.ts

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as RoslynProtocol from './roslynProtocol';
88
import { RoslynLanguageServer } from './roslynLanguageServer';
99
import { UriConverter } from './uriConverter';
1010
import * as lsp from 'vscode-languageserver-protocol';
11+
import { IDisposable } from '@microsoft/servicehub-framework';
1112

1213
export function registerSourceGeneratedFilesContentProvider(
1314
context: vscode.ExtensionContext,
@@ -16,16 +17,91 @@ export function registerSourceGeneratedFilesContentProvider(
1617
context.subscriptions.push(
1718
vscode.workspace.registerTextDocumentContentProvider(
1819
'roslyn-source-generated',
19-
new (class implements vscode.TextDocumentContentProvider {
20-
async provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): Promise<string> {
21-
const result = await languageServer.sendRequest(
22-
RoslynProtocol.SourceGeneratorGetTextRequest.type,
23-
{ textDocument: lsp.TextDocumentIdentifier.create(UriConverter.serialize(uri)) },
24-
token
25-
);
26-
return result.text;
27-
}
28-
})()
20+
new RoslynSourceGeneratedContentProvider(languageServer)
2921
)
3022
);
3123
}
24+
25+
class RoslynSourceGeneratedContentProvider implements vscode.TextDocumentContentProvider, IDisposable {
26+
private _onDidChangeEmitter: vscode.EventEmitter<vscode.Uri> = new vscode.EventEmitter<vscode.Uri>();
27+
28+
// Stores all the source generated documents that we have opened so far and their up to date content.
29+
private _openedDocuments: Map<vscode.Uri, RoslynProtocol.SourceGeneratedDocumentText> = new Map();
30+
31+
// Since we could potentially have multiple refresh notifications in flight at the same time,
32+
// we use a simple queue to ensure that updates to our state map only happen serially.
33+
private _updateQueue?: Promise<RoslynProtocol.SourceGeneratedDocumentText>;
34+
35+
private _cancellationSource = new vscode.CancellationTokenSource();
36+
37+
constructor(private languageServer: RoslynLanguageServer) {
38+
languageServer.registerOnNotification(
39+
RoslynProtocol.RefreshSourceGeneratedDocumentNotification.method,
40+
async () => {
41+
this._openedDocuments.forEach(async (_, key) => {
42+
await this.enqueueDocumentUpdateAsync(key, this._cancellationSource.token);
43+
this._onDidChangeEmitter.fire(key);
44+
});
45+
}
46+
);
47+
vscode.workspace.onDidCloseTextDocument((document) => {
48+
const openedDoc = this._openedDocuments.get(document.uri);
49+
if (openedDoc !== undefined) {
50+
this._openedDocuments.delete(document.uri);
51+
}
52+
});
53+
}
54+
55+
public onDidChange: vscode.Event<vscode.Uri> = this._onDidChangeEmitter.event;
56+
57+
async provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): Promise<string> {
58+
let content = this._openedDocuments.get(uri);
59+
60+
if (!content) {
61+
// We're being asked about this document for the first time, so we need to fetch it from the server.
62+
content = await this.enqueueDocumentUpdateAsync(uri, token);
63+
}
64+
65+
return content.text ?? vscode.l10n.t('Generated document not found');
66+
}
67+
68+
private async enqueueDocumentUpdateAsync(
69+
uri: vscode.Uri,
70+
token: vscode.CancellationToken
71+
): Promise<RoslynProtocol.SourceGeneratedDocumentText> {
72+
if (!this._updateQueue) {
73+
this._updateQueue = this.updateDocumentAsync(uri, token);
74+
} else {
75+
this._updateQueue = this._updateQueue.then(async () => await this.updateDocumentAsync(uri, token));
76+
}
77+
78+
return await this._updateQueue;
79+
}
80+
81+
private async updateDocumentAsync(
82+
uri: vscode.Uri,
83+
token: vscode.CancellationToken
84+
): Promise<RoslynProtocol.SourceGeneratedDocumentText> {
85+
const currentContent = this._openedDocuments.get(uri);
86+
const newContent = await this.languageServer.sendRequest(
87+
RoslynProtocol.SourceGeneratorGetTextRequest.type,
88+
{
89+
textDocument: lsp.TextDocumentIdentifier.create(UriConverter.serialize(uri)),
90+
resultId: currentContent?.resultId,
91+
},
92+
token
93+
);
94+
95+
// If we had no content before, or the resultId has changed, update the content
96+
if (!currentContent || newContent.resultId !== currentContent?.resultId) {
97+
this._openedDocuments.set(uri, newContent);
98+
return newContent;
99+
}
100+
101+
return currentContent;
102+
}
103+
104+
dispose(): void {
105+
this._cancellationSource.cancel();
106+
}
107+
}

0 commit comments

Comments
 (0)