Skip to content

Commit 7e92f4c

Browse files
authored
Ensure only one reload extension dialog can be shown at a time (#1473)
If `showReloadExtensionNotification` is in flight and being awaited then return the promise being awaited instead of showing another dialog. Also, debounce `showReloadExtensionNotification` with a 5 second window because its possible for users to close the first dialog very fast and have another one appear immediately if we update several settings at a time that require an extension restart. Issue: #1471
1 parent 3039bb3 commit 7e92f4c

File tree

2 files changed

+61
-12
lines changed

2 files changed

+61
-12
lines changed

src/ui/ReloadExtension.ts

+37-11
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,49 @@
1414

1515
import * as vscode from "vscode";
1616
import { Workbench } from "../utilities/commands";
17+
// eslint-disable-next-line @typescript-eslint/no-require-imports
18+
import debounce = require("lodash.debounce");
1719

1820
/**
1921
* Prompts the user to reload the extension in cases where we are unable to do
20-
* so automatically.
22+
* so automatically. Only one of these prompts will be shown at a time.
2123
*
2224
* @param message the warning message to display to the user
2325
* @param items extra buttons to display
2426
* @returns the selected button or undefined if cancelled
2527
*/
26-
export async function showReloadExtensionNotification<T extends string>(
27-
message: string,
28-
...items: T[]
29-
): Promise<"Reload Extensions" | T | undefined> {
30-
const buttons: ("Reload Extensions" | T)[] = ["Reload Extensions", ...items];
31-
const selected = await vscode.window.showWarningMessage(message, ...buttons);
32-
if (selected === "Reload Extensions") {
33-
await vscode.commands.executeCommand(Workbench.ACTION_RELOADWINDOW);
34-
}
35-
return selected;
28+
export function showReloadExtensionNotificationInstance<T extends string>() {
29+
let inFlight: Promise<"Reload Extensions" | T | undefined> | null = null;
30+
31+
return async function (
32+
message: string,
33+
...items: T[]
34+
): Promise<"Reload Extensions" | T | undefined> {
35+
if (inFlight) {
36+
return inFlight;
37+
}
38+
39+
const buttons: ("Reload Extensions" | T)[] = ["Reload Extensions", ...items];
40+
inFlight = (async () => {
41+
try {
42+
const selected = await vscode.window.showWarningMessage(message, ...buttons);
43+
if (selected === "Reload Extensions") {
44+
await vscode.commands.executeCommand(Workbench.ACTION_RELOADWINDOW);
45+
}
46+
return selected;
47+
} finally {
48+
inFlight = null;
49+
}
50+
})();
51+
52+
return inFlight;
53+
};
3654
}
55+
56+
// In case the user closes the dialog immediately we want to debounce showing it again
57+
// for 10 seconds to prevent another popup perhaps immediately appearing.
58+
export const showReloadExtensionNotification = debounce(
59+
showReloadExtensionNotificationInstance(),
60+
10_000,
61+
{ leading: true }
62+
);

test/unit-tests/ui/ReloadExtension.test.ts

+24-1
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,24 @@
1111
// SPDX-License-Identifier: Apache-2.0
1212
//
1313
//===----------------------------------------------------------------------===//
14+
import { beforeEach } from "mocha";
1415
import { expect } from "chai";
1516
import { mockGlobalObject } from "../../MockUtils";
1617
import * as vscode from "vscode";
17-
import { showReloadExtensionNotification } from "../../../src/ui/ReloadExtension";
18+
import { showReloadExtensionNotificationInstance } from "../../../src/ui/ReloadExtension";
1819
import { Workbench } from "../../../src/utilities/commands";
1920

2021
suite("showReloadExtensionNotification()", async function () {
2122
const mockedVSCodeWindow = mockGlobalObject(vscode, "window");
2223
const mockedVSCodeCommands = mockGlobalObject(vscode, "commands");
24+
let showReloadExtensionNotification: (
25+
message: string,
26+
...items: string[]
27+
) => Promise<string | undefined>;
28+
29+
beforeEach(() => {
30+
showReloadExtensionNotification = showReloadExtensionNotificationInstance();
31+
});
2332

2433
test("displays a warning message asking the user if they would like to reload the window", async () => {
2534
mockedVSCodeWindow.showWarningMessage.resolves(undefined);
@@ -57,4 +66,18 @@ suite("showReloadExtensionNotification()", async function () {
5766
);
5867
expect(mockedVSCodeCommands.executeCommand).to.not.have.been.called;
5968
});
69+
70+
test("only shows one dialog at a time", async () => {
71+
mockedVSCodeWindow.showWarningMessage.resolves(undefined);
72+
73+
await Promise.all([
74+
showReloadExtensionNotification("Want to reload?"),
75+
showReloadExtensionNotification("Want to reload?"),
76+
]);
77+
78+
expect(mockedVSCodeWindow.showWarningMessage).to.have.been.calledOnceWithExactly(
79+
"Want to reload?",
80+
"Reload Extensions"
81+
);
82+
});
6083
});

0 commit comments

Comments
 (0)