diff --git a/extensions/typescript-language-features/src/features/bufferSyncSupport.ts b/extensions/typescript-language-features/src/features/bufferSyncSupport.ts index ce73a59ac69..3aafeb61e60 100644 --- a/extensions/typescript-language-features/src/features/bufferSyncSupport.ts +++ b/extensions/typescript-language-features/src/features/bufferSyncSupport.ts @@ -337,6 +337,9 @@ export default class BufferSyncSupport extends Disposable { private readonly _onDelete = this._register(new vscode.EventEmitter()); public readonly onDelete = this._onDelete.event; + private readonly _onWillChange = this._register(new vscode.EventEmitter()); + public readonly onWillChange = this._onWillChange.event; + public listen(): void { if (this.listening) { return; @@ -456,6 +459,8 @@ export default class BufferSyncSupport extends Disposable { return; } + this._onWillChange.fire(syncedBuffer.resource); + syncedBuffer.onContentChanged(e.contentChanges); const didTrigger = this.requestDiagnostic(syncedBuffer); diff --git a/extensions/typescript-language-features/src/features/implementationsCodeLens.ts b/extensions/typescript-language-features/src/features/implementationsCodeLens.ts index eccb5433e76..0ab2ae8719c 100644 --- a/extensions/typescript-language-features/src/features/implementationsCodeLens.ts +++ b/extensions/typescript-language-features/src/features/implementationsCodeLens.ts @@ -24,7 +24,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip const codeLens = inputCodeLens as ReferencesCodeLens; const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start); - const response = await this.client.execute('implementation', args, token, { lowPriority: true }); + const response = await this.client.execute('implementation', args, token, { lowPriority: true, cancelOnResourceChange: codeLens.document }); if (response.type !== 'response' || !response.body) { codeLens.command = response.type === 'cancelled' ? TypeScriptBaseCodeLensProvider.cancelledCommand diff --git a/extensions/typescript-language-features/src/features/referencesCodeLens.ts b/extensions/typescript-language-features/src/features/referencesCodeLens.ts index 25b8d117b9c..b926f6058ac 100644 --- a/extensions/typescript-language-features/src/features/referencesCodeLens.ts +++ b/extensions/typescript-language-features/src/features/referencesCodeLens.ts @@ -19,7 +19,7 @@ class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvide public async resolveCodeLens(inputCodeLens: vscode.CodeLens, token: vscode.CancellationToken): Promise { const codeLens = inputCodeLens as ReferencesCodeLens; const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start); - const response = await this.client.execute('references', args, token, { lowPriority: true }); + const response = await this.client.execute('references', args, token, { lowPriority: true, cancelOnResourceChange: codeLens.document }); if (response.type !== 'response' || !response.body) { codeLens.command = response.type === 'cancelled' ? TypeScriptBaseCodeLensProvider.cancelledCommand diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index 741fee0e94b..cd2d50e93ea 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -78,6 +78,7 @@ export type TypeScriptRequests = StandardTsServerRequests & NoResponseTsServerRe export type ExecConfig = { readonly lowPriority?: boolean; readonly nonRecoverable?: boolean; + readonly cancelOnResourceChange?: vscode.Uri }; export interface ITypeScriptServiceClient { diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index e1a2b10270f..bb640ad80d3 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -35,6 +35,11 @@ export interface TsDiagnostics { readonly diagnostics: Proto.Diagnostic[]; } +interface ToCancelOnResourceChanged { + readonly resource: vscode.Uri; + cancel(): void; +} + namespace ServerState { export const enum Type { None, @@ -60,6 +65,8 @@ namespace ServerState { public tsserverVersion: string | undefined, public langaugeServiceEnabled: boolean, ) { } + + public readonly toCancelOnResourceChange = new Set(); } export class Errored { @@ -129,9 +136,14 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.diagnosticsManager = new DiagnosticsManager('typescript'); this.bufferSyncSupport.onDelete(resource => { + this.cancelInflightRequestsForResource(resource); this.diagnosticsManager.delete(resource); }, null, this._disposables); + this.bufferSyncSupport.onWillChange(resource => { + this.cancelInflightRequestsForResource(resource); + }); + vscode.workspace.onDidChangeConfiguration(() => { const oldConfiguration = this._configuration; this._configuration = TypeScriptServiceConfiguration.loadFromWorkspace(); @@ -173,6 +185,18 @@ export default class TypeScriptServiceClient extends Disposable implements IType })); } + private cancelInflightRequestsForResource(resource: vscode.Uri): void { + if (this.serverState.type !== ServerState.Type.Running) { + return; + } + + for (const request of this.serverState.toCancelOnResourceChange) { + if (request.resource.toString() === resource.toString()) { + request.cancel(); + } + } + } + public get configuration() { return this._configuration; } @@ -609,12 +633,37 @@ export default class TypeScriptServiceClient extends Disposable implements IType } public execute(command: keyof TypeScriptRequests, args: any, token: vscode.CancellationToken, config?: ExecConfig): Promise> { - const execution = this.executeImpl(command, args, { - isAsync: false, - token, - expectsResult: true, - lowPriority: config?.lowPriority - }); + let execution: Promise>; + + if (config?.cancelOnResourceChange) { + const runningServerState = this.service(); + + const source = new vscode.CancellationTokenSource(); + token.onCancellationRequested(() => source.cancel()); + + const inFlight: ToCancelOnResourceChanged = { + resource: config.cancelOnResourceChange, + cancel: () => source.cancel(), + }; + runningServerState.toCancelOnResourceChange.add(inFlight); + + execution = this.executeImpl(command, args, { + isAsync: false, + token: source.token, + expectsResult: true, + ...config, + }).finally(() => { + runningServerState.toCancelOnResourceChange.delete(inFlight); + source.dispose(); + }); + } else { + execution = this.executeImpl(command, args, { + isAsync: false, + token, + expectsResult: true, + ...config, + }); + } if (config?.nonRecoverable) { execution.catch(() => this.fatalError(command));