Skip to content

Commit 5f697ad

Browse files
committed
hot exit - turn progress on shutdown to a modal dialog (microsoft#122774)
1 parent 8b2f022 commit 5f697ad

File tree

4 files changed

+53
-25
lines changed

4 files changed

+53
-25
lines changed

src/vs/platform/progress/common/progress.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export interface IProgressService {
1919
readonly _serviceBrand: undefined;
2020

2121
withProgress<R>(
22-
options: IProgressOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions,
22+
options: IProgressOptions | IProgressDialogOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions,
2323
task: (progress: IProgress<IProgressStep>) => Promise<R>,
2424
onDidCancel?: (choice?: number) => void
2525
): Promise<R>;
@@ -66,6 +66,11 @@ export interface IProgressNotificationOptions extends IProgressOptions {
6666
readonly silent?: boolean;
6767
}
6868

69+
export interface IProgressDialogOptions extends IProgressOptions {
70+
readonly delay?: number;
71+
readonly detail?: string;
72+
}
73+
6974
export interface IProgressWindowOptions extends IProgressOptions {
7075
readonly location: ProgressLocation.Window;
7176
readonly command?: string;
@@ -134,7 +139,7 @@ export class UnmanagedProgress extends Disposable {
134139
private lastStep?: IProgressStep;
135140

136141
constructor(
137-
options: IProgressOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions,
142+
options: IProgressOptions | IProgressDialogOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions,
138143
@IProgressService progressService: IProgressService,
139144
) {
140145
super();
@@ -159,7 +164,6 @@ export class UnmanagedProgress extends Disposable {
159164
}
160165
}
161166

162-
163167
export class LongRunningOperation extends Disposable {
164168
private currentOperationId = 0;
165169
private readonly currentOperationDisposables = this._register(new DisposableStore());

src/vs/workbench/services/progress/browser/progressService.ts

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import 'vs/css!./media/progressService';
77

88
import { localize } from 'vs/nls';
99
import { IDisposable, dispose, DisposableStore, Disposable, toDisposable } from 'vs/base/common/lifecycle';
10-
import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions, IProgressRunner, IProgressIndicator, IProgressWindowOptions } from 'vs/platform/progress/common/progress';
10+
import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions, IProgressRunner, IProgressIndicator, IProgressWindowOptions, IProgressDialogOptions } from 'vs/platform/progress/common/progress';
1111
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
1212
import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar';
13-
import { timeout } from 'vs/base/common/async';
13+
import { RunOnceScheduler, timeout } from 'vs/base/common/async';
1414
import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity';
1515
import { INotificationService, Severity, INotificationHandle } from 'vs/platform/notification/common/notification';
1616
import { Action } from 'vs/base/common/actions';
@@ -438,7 +438,7 @@ export class ProgressService extends Disposable implements IProgressService {
438438
return promise;
439439
}
440440

441-
private showOnActivityBar<P extends Promise<R>, R = unknown>(viewletId: string, options: IProgressCompositeOptions, promise: P) {
441+
private showOnActivityBar<P extends Promise<R>, R = unknown>(viewletId: string, options: IProgressCompositeOptions, promise: P): void {
442442
let activityProgress: IDisposable;
443443
let delayHandle: any = setTimeout(() => {
444444
delayHandle = undefined;
@@ -501,8 +501,9 @@ export class ProgressService extends Disposable implements IProgressService {
501501
return promise;
502502
}
503503

504-
private withDialogProgress<P extends Promise<R>, R = unknown>(options: IProgressOptions, task: (progress: IProgress<IProgressStep>) => P, onDidCancel?: (choice?: number) => void): P {
504+
private withDialogProgress<P extends Promise<R>, R = unknown>(options: IProgressDialogOptions, task: (progress: IProgress<IProgressStep>) => P, onDidCancel?: (choice?: number) => void): P {
505505
const disposables = new DisposableStore();
506+
506507
const allowableCommands = [
507508
'workbench.action.quit',
508509
'workbench.action.reloadWindow',
@@ -515,7 +516,6 @@ export class ProgressService extends Disposable implements IProgressService {
515516
let dialog: Dialog;
516517

517518
const createDialog = (message: string) => {
518-
519519
const buttons = options.buttons || [];
520520
buttons.push(options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss"));
521521

@@ -525,6 +525,7 @@ export class ProgressService extends Disposable implements IProgressService {
525525
buttons,
526526
{
527527
type: 'pending',
528+
detail: options.detail,
528529
cancelId: buttons.length - 1,
529530
keyEventProcessor: (event: StandardKeyboardEvent) => {
530531
const resolved = this.keybindingService.softDispatch(event, this.layoutService.container);
@@ -540,22 +541,37 @@ export class ProgressService extends Disposable implements IProgressService {
540541
disposables.add(dialog);
541542
disposables.add(attachDialogStyler(dialog, this.themeService));
542543

543-
dialog.show().then((dialogResult) => {
544-
if (typeof onDidCancel === 'function') {
545-
onDidCancel(dialogResult.button);
546-
}
544+
dialog.show().then(dialogResult => {
545+
onDidCancel?.(dialogResult.button);
547546

548547
dispose(dialog);
549548
});
550549

551550
return dialog;
552551
};
553552

554-
const updateDialog = (message?: string) => {
555-
if (message && !dialog) {
556-
dialog = createDialog(message);
557-
} else if (message) {
558-
dialog.updateMessage(message);
553+
// In order to support the `delay` option, we use a scheduler
554+
// that will guard each access to the dialog behind a delay
555+
// that is either the original delay for one invocation and
556+
// otherwise runs without delay.
557+
let delay = options.delay ?? 0;
558+
let latestMessage: string | undefined = undefined;
559+
const scheduler = disposables.add(new RunOnceScheduler(() => {
560+
delay = 0; // since we have run once, we reset the delay
561+
562+
if (latestMessage && !dialog) {
563+
dialog = createDialog(latestMessage);
564+
} else if (latestMessage) {
565+
dialog.updateMessage(latestMessage);
566+
}
567+
}, 0));
568+
569+
const updateDialog = function (message?: string): void {
570+
latestMessage = message;
571+
572+
// Make sure to only run one dialog update and not multiple
573+
if (!scheduler.isScheduled()) {
574+
scheduler.schedule(delay);
559575
}
560576
};
561577

@@ -569,6 +585,10 @@ export class ProgressService extends Disposable implements IProgressService {
569585
dispose(disposables);
570586
});
571587

588+
if (options.title) {
589+
updateDialog(options.title);
590+
}
591+
572592
return promise;
573593
}
574594
}

src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,10 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp
224224
} catch (backupError) {
225225
error = backupError;
226226
}
227-
}, localize('backupBeforeShutdown', "Waiting for dirty editors to backup..."));
227+
},
228+
localize('backupBeforeShutdownMessage', "Waiting for dirty editors to backup..."),
229+
localize('backupBeforeShutdownDetail', "Click 'Cancel' skip backups and save or revert dirty editors.")
230+
);
228231

229232
return { backups, error };
230233
}
@@ -313,14 +316,15 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp
313316
}, localize('revertBeforeShutdown', "Waiting for dirty editors to revert..."));
314317
}
315318

316-
private withProgressAndCancellation(promiseFactory: (token: CancellationToken) => Promise<void>, title: string): Promise<void> {
319+
private withProgressAndCancellation(promiseFactory: (token: CancellationToken) => Promise<void>, title: string, detail?: string): Promise<void> {
317320
const cts = new CancellationTokenSource();
318321

319322
return this.progressService.withProgress({
320-
location: ProgressLocation.Notification,
321-
cancellable: true, // for issues such as https://github.com/microsoft/vscode/issues/112278
322-
delay: 800, // delay notification so that it only appears when operation takes a long time
323-
title
323+
location: ProgressLocation.Dialog, // use a dialog to prevent the user from making any more changes now (https://github.com/microsoft/vscode/issues/122774)
324+
cancellable: true, // allow to cancel (https://github.com/microsoft/vscode/issues/112278)
325+
delay: 800, // delay notification so that it only appears when operation takes a long time
326+
title,
327+
detail
324328
}, () => raceCancellation(promiseFactory(cts.token), cts.token), () => cts.dispose(true));
325329
}
326330

src/vs/workbench/test/browser/workbenchTestServices.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
8383
import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel';
8484
import { IPathService } from 'vs/workbench/services/path/common/pathService';
8585
import { Direction } from 'vs/base/browser/ui/grid/grid';
86-
import { IProgressService, IProgressOptions, IProgressWindowOptions, IProgressNotificationOptions, IProgressCompositeOptions, IProgress, IProgressStep, Progress } from 'vs/platform/progress/common/progress';
86+
import { IProgressService, IProgressOptions, IProgressWindowOptions, IProgressNotificationOptions, IProgressCompositeOptions, IProgress, IProgressStep, Progress, IProgressDialogOptions } from 'vs/platform/progress/common/progress';
8787
import { IWorkingCopyFileService, WorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
8888
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
8989
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
@@ -411,7 +411,7 @@ export class TestProgressService implements IProgressService {
411411
declare readonly _serviceBrand: undefined;
412412

413413
withProgress(
414-
options: IProgressOptions | IProgressWindowOptions | IProgressNotificationOptions | IProgressCompositeOptions,
414+
options: IProgressOptions | IProgressDialogOptions | IProgressWindowOptions | IProgressNotificationOptions | IProgressCompositeOptions,
415415
task: (progress: IProgress<IProgressStep>) => Promise<any>,
416416
onDidCancel?: ((choice?: number | undefined) => void) | undefined
417417
): Promise<any> {

0 commit comments

Comments
 (0)