Skip to content
19 changes: 15 additions & 4 deletions Extension/src/LanguageServer/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ export interface Client {
getKnownCompilers(): Thenable<configs.KnownCompiler[] | undefined>;
takeOwnership(document: vscode.TextDocument): void;
sendDidOpen(document: vscode.TextDocument): Promise<void>;
requestSwitchHeaderSource(rootUri: vscode.Uri, fileName: string): Thenable<string>;
requestSwitchHeaderSource(rootUri: vscode.Uri, fileName: string, token: vscode.CancellationToken): Thenable<string>;
updateActiveDocumentTextOptions(): void;
didChangeActiveEditor(editor?: vscode.TextEditor, selection?: Range): Promise<void>;
restartIntelliSenseForFile(document: vscode.TextDocument): Promise<void>;
Expand Down Expand Up @@ -3105,12 +3105,23 @@ export class DefaultClient implements Client {
/**
* requests to the language server
*/
public async requestSwitchHeaderSource(rootUri: vscode.Uri, fileName: string): Promise<string> {
public async requestSwitchHeaderSource(rootUri: vscode.Uri, fileName: string, token: vscode.CancellationToken): Promise<string> {
const params: SwitchHeaderSourceParams = {
switchHeaderSourceFileName: fileName,
workspaceFolderUri: rootUri.toString()
};
return this.enqueue(async () => this.languageClient.sendRequest(SwitchHeaderSourceRequest, params));
return this.enqueue(async () => {
// Don't use withLspCancellationHandling() or withCancellation() here. If the switch target is already known,
// the caller should still be able to use it even if the progress notification was just cancelled.
try {
return await this.languageClient.sendRequest(SwitchHeaderSourceRequest, params, token);
} catch (e: any) {
if (e instanceof ResponseError && (e.code === RequestCancelled || e.code === ServerCancelled)) {
throw new vscode.CancellationError();
}
throw e;
}
});
}

public async requestCompiler(newCompilerPath?: string): Promise<configs.CompilerDefaults> {
Expand Down Expand Up @@ -4461,7 +4472,7 @@ class NullClient implements Client {
getKnownCompilers(): Thenable<configs.KnownCompiler[] | undefined> { return Promise.resolve([]); }
takeOwnership(document: vscode.TextDocument): void { }
sendDidOpen(document: vscode.TextDocument): Promise<void> { return Promise.resolve(); }
requestSwitchHeaderSource(rootUri: vscode.Uri, fileName: string): Thenable<string> { return Promise.resolve(""); }
requestSwitchHeaderSource(rootUri: vscode.Uri, fileName: string, token: vscode.CancellationToken): Thenable<string> { return Promise.resolve(""); }
updateActiveDocumentTextOptions(): void { }
didChangeActiveEditor(editor?: vscode.TextEditor): Promise<void> { return Promise.resolve(); }
restartIntelliSenseForFile(document: vscode.TextDocument): Promise<void> { return Promise.resolve(); }
Expand Down
71 changes: 59 additions & 12 deletions Extension/src/LanguageServer/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,19 +479,66 @@ async function onSwitchHeaderSource(): Promise<void> {
rootUri = vscode.Uri.file(path.dirname(fileName)); // When switching without a folder open.
}

let targetFileName: string = await clients.ActiveClient.requestSwitchHeaderSource(rootUri, fileName);
// If the targetFileName has a path that is a symlink target of a workspace folder,
// then replace the RootRealPath with the RootPath (the symlink path).
let targetFileNameReplaced: boolean = false;
clients.forEach(client => {
if (!targetFileNameReplaced && client.RootRealPath && client.RootPath !== client.RootRealPath
&& targetFileName.startsWith(client.RootRealPath)) {
targetFileName = client.RootPath + targetFileName.substring(client.RootRealPath.length);
targetFileNameReplaced = true;
const switchHeaderSource: (token: vscode.CancellationToken) => Promise<void> = async (token: vscode.CancellationToken) => {
try {
let targetFileName: string = await clients.ActiveClient.requestSwitchHeaderSource(rootUri, fileName, token);
if (!targetFileName) {
return;
}
// If the targetFileName has a path that is a symlink target of a workspace folder,
// then replace the RootRealPath with the RootPath (the symlink path).
let targetFileNameReplaced: boolean = false;
clients.forEach(client => {
if (!targetFileNameReplaced && client.RootRealPath && client.RootPath !== client.RootRealPath
&& targetFileName.startsWith(client.RootRealPath)) {
targetFileName = client.RootPath + targetFileName.substring(client.RootRealPath.length);
targetFileNameReplaced = true;
}
});
const document: vscode.TextDocument = await vscode.workspace.openTextDocument(targetFileName);
await vscode.window.showTextDocument(document).then(undefined, logAndReturn.undefined);
} catch (e) {
if (e instanceof vscode.CancellationError) {
return;
}
throw e;
}
});
const document: vscode.TextDocument = await vscode.workspace.openTextDocument(targetFileName);
void vscode.window.showTextDocument(document).then(undefined, logAndReturn.undefined);
};

const tokenSource: vscode.CancellationTokenSource = new vscode.CancellationTokenSource();
try {
const switchHeaderSourcePromise: Promise<void> = switchHeaderSource(tokenSource.token);
const showProgress: boolean = await new Promise<boolean>((resolve, reject) => {
const timer: NodeJS.Timeout = global.setTimeout(() => resolve(true), 2000);
void switchHeaderSourcePromise.then(() => {
clearTimeout(timer);
resolve(false);
}, (e) => {
clearTimeout(timer);
reject(e);
});
});

if (!showProgress) {
await switchHeaderSourcePromise;
return;
}

await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: localize('switch.header.source', 'Switching Header/Source...'),
cancellable: true
}, async (_progress, token) => {
const cancellationListener: vscode.Disposable = token.onCancellationRequested(() => tokenSource.cancel());
try {
await switchHeaderSourcePromise;
} finally {
cancellationListener.dispose();
}
});
} finally {
tokenSource.dispose();
}
}

/**
Expand Down
Loading