Visual Studio Code Progress Cancelled by Async Task
Posted: November 26, 2023 | | Categories: Developer Tools
I'm working on my first Visual Studio Code extension and as part of the extension's work, it calls a long-running external API that could take 30 seconds or more to complete. I started looking for way to display a progress dialog and discovered the vscode.window.withProgress. As I looked through the different examples I found out there, they all showed how the code that displays and manages the progress item decides when to close the window.
For my project, I had a separate process deciding when to close the progress window and it took me some time to sort out how to make everything work. You can find the complete Visual Studio Code extension code in Visual Studio Extension Progress Demo.
Asynchronous Task
I didn't want to pull over the complete code from my other extension project, so I created a dummy asynchronous task that basically sits for a specific number of seconds (in this case 10), then cancels the progress window; I'll explain the customCancellationToken
object later.
const delayValue = 10; // seconds
// this `taskRunner` executes for delayValue seconds to simulate a long running task
async function taskRunner(customCancellationToken: vscode.CancellationTokenSource) {
setTimeout(function () {
console.log('taskRunner completed');
// tell the progress bar to stop
customCancellationToken.cancel();
}, delayValue * 1000);
}
Change the value in the delayValue
constant to modify how long the progress window appears before closing.
Progress Window
To create a progress window, use the following code as a framework:
vscode.window.withProgress({
title: extensionTitle,
location: vscode.ProgressLocation.Notification,
cancellable: false
},
async (progress, token) => {
return new Promise((async (resolve) => {
// The code you put here manages the lifecycle of the progress window
}));
});
Notice that the progress window is cancellable
; to trigger cancellation, you need a CancellationTokenSource
:
var customCancellationToken: vscode.CancellationTokenSource | null = new vscode.CancellationTokenSource();
It's the cancel()
method on the customCancellationToken
object that the asynchronous task calls when it's done waiting.
Next, you create an event handler for the cancellation event:
customCancellationToken.token.onCancellationRequested(() => {
// do other stuff you need to do here
customCancellationToken?.dispose();
customCancellationToken = null;
resolve(null);
return;
});
The code essentially destroys the cancellation token and resolves the promise keeping everything running up to this point.
Next, you need something to update the progress window periodically so the user can tell that something's happening. The following code uses a JavaScript interval to upgrade the progress window every second. In this example, the code adds a period to the end of the status message, resetting after 5 periods.
var loopCounter = 0;
interval = setInterval(() => {
console.log('Waiting');
loopCounter++;
if (loopCounter > 5) { loopCounter = 1; } // reset the loop counter
progress.report({ message: 'working' + '.'.repeat(loopCounter) });
}, 1000);
Putting this all together, here's the complete progress window code:
vscode.window.withProgress({
title: extensionTitle,
location: vscode.ProgressLocation.Notification,
cancellable: false
},
async (progress, token) => {
return new Promise((async (resolve) => {
var interval: any;
// setup a process to handle progress bar cancellation
var customCancellationToken: vscode.CancellationTokenSource | null = new vscode.CancellationTokenSource();
customCancellationToken.token.onCancellationRequested(() => {
console.log('Clearing progress bar');
interval = clearInterval(interval);
customCancellationToken?.dispose();
customCancellationToken = null;
resolve(null);
return;
});
taskRunner(customCancellationToken);
var loopCounter = 0;
interval = setInterval(() => {
console.log('Waiting');
loopCounter++;
if (loopCounter > 5) { loopCounter = 1; } // reset the loop counter
progress.report({ message: 'working' + '.'.repeat(loopCounter) });
}, 1000);
}));
});
The code:
- Creates the cancellation handler
- Starts the
taskRunner
task - Kicks off
setInterval
to loop infinitely until the task runner tells it to cancel.
Unlike the other examples I found online, this example cancels the progress window from an external task.
Full Code
Here's the complete code for the extension.
import * as vscode from 'vscode';
const extensionTitle = 'Progress Demo';
const delayValue = 10; // seconds
export function activate(context: vscode.ExtensionContext) {
console.log('Extension activated');
let disposable = vscode.commands.registerCommand('progress-demo.go', () => {
// this `taskRunner` executes for delayValue seconds to simulate a long running task
async function taskRunner(customCancellationToken: vscode.CancellationTokenSource) {
setTimeout(function () {
console.log('taskRunner completed');
// tell the progress bar to stop
customCancellationToken.cancel();
}, delayValue * 1000);
}
// creates a progress bar that runs until the `taskRunner` tells it to stop
vscode.window.withProgress({
title: extensionTitle,
location: vscode.ProgressLocation.Notification,
cancellable: false
},
async (progress, token) => {
return new Promise((async (resolve) => {
var interval: any;
// setup a process to handle progress bar cancellation
var customCancellationToken: vscode.CancellationTokenSource | null = new vscode.CancellationTokenSource();
customCancellationToken.token.onCancellationRequested(() => {
console.log('Clearing progress bar');
interval = clearInterval(interval);
customCancellationToken?.dispose();
customCancellationToken = null;
resolve(null);
return;
});
taskRunner(customCancellationToken);
var loopCounter = 0;
interval = setInterval(() => {
console.log('Waiting');
loopCounter++;
if (loopCounter > 5) { loopCounter = 1; } // reset the loop counter
progress.report({ message: 'working' + '.'.repeat(loopCounter) });
}, 1000);
}));
});
});
context.subscriptions.push(disposable);
}
export function deactivate() {
console.log('Extension deactivated');
}
To run this code in Visual Studio Code:
- Clone the Visual Studio Extension Progress Demo repository.
- Open the cloned project in Visual Studio Code
- In a vscode terminal window, execute
npm install
- Press the F5 key to start debugging the extension.
- In the new Visual Studio Code window that opens, open the Command Palette and type select the Progress Demo option.
Next Post: zx for Simplified Node Process Spawning
Previous Post: Pumpkin Controller Native Apps for Windows
If this content helps you in some way, please consider buying me a coffee.
Header image: Photo by Ashim D’Silva on Unsplash.