diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index 9fbe7e80facca993f4e34f281bb5e22871e39a50..2bcfbf6a9efded79634e221b116a832c9ca701a4 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -357,8 +357,34 @@ export class SyntaxRoutingTsServer extends Disposable implements ITypeScriptServ return this.syntaxServer.executeImpl(command, args, executeInfo); } else if (SyntaxRoutingTsServer.sharedCommands.has(command)) { // Dispatch to both server but only return from syntax one - this.semanticServer.executeImpl(command, args, executeInfo); - return this.syntaxServer.executeImpl(command, args, executeInfo); + + // Also make sure we never cancel requests to just one server + let hasCompletedSyntax = false; + let hasCompletedSemantic = false; + let token: vscode.CancellationToken | undefined = undefined; + if (executeInfo.token) { + const source = new vscode.CancellationTokenSource(); + executeInfo.token.onCancellationRequested(() => { + if (hasCompletedSyntax && !hasCompletedSemantic || hasCompletedSemantic && !hasCompletedSyntax) { + // Don't cancel. + // One of the servers completed this request so we don't want to leave the other + // in a different state + return; + } + source.cancel(); + }); + token = source.token; + } + + const semanticRequest = this.semanticServer.executeImpl(command, args, { ...executeInfo, token }); + if (semanticRequest) { + semanticRequest.finally(() => { hasCompletedSemantic = true; }); + } + const syntaxRequest = this.syntaxServer.executeImpl(command, args, { ...executeInfo, token }); + if (syntaxRequest) { + syntaxRequest.finally(() => { hasCompletedSyntax = true; }); + } + return syntaxRequest; } else { return this.semanticServer.executeImpl(command, args, executeInfo); }