Skip to content

Commit 6a0c0fd

Browse files
authored
feat(status-bar): improve status bar display and actions (#32)
- The status bar will always show the LS instance status (running, stopping, etc) if the LS CLI is installed — it doesn’t need to have the full setup complete - The status bar commands will always allow starting and stopping LS if the LS CLI is installed — it doesn’t need to have the full setup complete - The status bar setup wizard command will be displayed on top of the rest of the commands - The status bar will be red if any part of the setup is incomplete - The status bar text now uses a colon as a separator, instead of parentheses
1 parent fc7cdaa commit 6a0c0fd

File tree

6 files changed

+100
-78
lines changed

6 files changed

+100
-78
lines changed

src/plugins/logs.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import { spawn } from "node:child_process";
22
import type { ChildProcess } from "node:child_process";
33

4+
import { commands } from "vscode";
5+
46
import { createPlugin } from "../plugins.ts";
57
import { pipeToLogOutputChannel } from "../utils/spawn.ts";
68

79
export default createPlugin(
810
"logs",
911
({ context, outputChannel, containerStatusTracker }) => {
12+
context.subscriptions.push(
13+
commands.registerCommand("localstack.viewLogs", () => {
14+
outputChannel.show(true);
15+
}),
16+
);
17+
1018
let logsProcess: ChildProcess | undefined;
1119

1220
const startLogging = () => {

src/plugins/manage.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,6 @@ import {
1010
export default createPlugin(
1111
"manage",
1212
({ context, outputChannel, telemetry, localStackStatusTracker }) => {
13-
context.subscriptions.push(
14-
commands.registerCommand("localstack.viewLogs", () => {
15-
outputChannel.show(true);
16-
}),
17-
);
18-
1913
context.subscriptions.push(
2014
commands.registerCommand("localstack.start", async () => {
2115
if (localStackStatusTracker.status() !== "stopped") {

src/plugins/status-bar.ts

Lines changed: 66 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,70 +2,69 @@ import { commands, QuickPickItemKind, ThemeColor, window } from "vscode";
22
import type { QuickPickItem } from "vscode";
33

44
import { createPlugin } from "../plugins.ts";
5-
import { checkIsProfileConfigured } from "../utils/configure-aws.ts";
5+
import { checkLocalstackInstalled } from "../utils/install.ts";
66

77
export default createPlugin(
88
"status-bar",
9-
({ context, statusBarItem, localStackStatusTracker, setupStatusTracker }) => {
9+
({
10+
context,
11+
statusBarItem,
12+
localStackStatusTracker,
13+
setupStatusTracker,
14+
outputChannel,
15+
}) => {
1016
context.subscriptions.push(
1117
commands.registerCommand("localstack.showCommands", async () => {
12-
const getCommands = async () => {
18+
const shouldShowLocalStackStart = () =>
19+
setupStatusTracker.statuses().isInstalled &&
20+
localStackStatusTracker.status() === "stopped";
21+
const shouldShowLocalStackStop = () =>
22+
setupStatusTracker.statuses().isInstalled &&
23+
localStackStatusTracker.status() === "running";
24+
const shouldShowRunSetupWizard = () =>
25+
setupStatusTracker.status() === "setup_required";
26+
27+
const getCommands = () => {
1328
const commands: (QuickPickItem & { command: string })[] = [];
29+
1430
commands.push({
15-
label: "Manage",
31+
label: "Configure",
1632
command: "",
1733
kind: QuickPickItemKind.Separator,
1834
});
19-
const setupStatus = setupStatusTracker.status();
20-
21-
if (setupStatus === "ok") {
22-
if (localStackStatusTracker.status() === "stopped") {
23-
commands.push({
24-
label: "Start LocalStack",
25-
command: "localstack.start",
26-
});
27-
} else {
28-
commands.push({
29-
label: "Stop LocalStack",
30-
command: "localstack.stop",
31-
});
32-
}
35+
36+
if (shouldShowRunSetupWizard()) {
37+
commands.push({
38+
label: "Run LocalStack Setup Wizard",
39+
command: "localstack.setup",
40+
});
3341
}
3442

3543
commands.push({
36-
label: "Configure",
44+
label: "Manage",
3745
command: "",
3846
kind: QuickPickItemKind.Separator,
3947
});
4048

41-
if (setupStatus === "setup_required") {
49+
if (shouldShowLocalStackStart()) {
4250
commands.push({
43-
label: "Run LocalStack setup Wizard",
44-
command: "localstack.setup",
51+
label: "Start LocalStack",
52+
command: "localstack.start",
4553
});
46-
47-
// show start command if stopped or stop command when running, even if setup_required (in authentication, or profile)
48-
if (localStackStatusTracker.status() === "stopped") {
49-
commands.push({
50-
label: "Start LocalStack",
51-
command: "localstack.start",
52-
});
53-
} else if (localStackStatusTracker.status() === "running") {
54-
commands.push({
55-
label: "Stop LocalStack",
56-
command: "localstack.stop",
57-
});
58-
}
5954
}
6055

61-
const isProfileConfigured = await checkIsProfileConfigured();
62-
if (!isProfileConfigured) {
56+
if (shouldShowLocalStackStop()) {
6357
commands.push({
64-
label: "Configure AWS Profiles",
65-
command: "localstack.configureAwsProfiles",
58+
label: "Stop LocalStack",
59+
command: "localstack.stop",
6660
});
6761
}
6862

63+
commands.push({
64+
label: "View Logs",
65+
command: "localstack.viewLogs",
66+
});
67+
6968
return commands;
7069
};
7170

@@ -74,38 +73,38 @@ export default createPlugin(
7473
});
7574

7675
if (selected) {
77-
commands.executeCommand(selected.command);
76+
void commands.executeCommand(selected.command);
7877
}
7978
}),
8079
);
8180

8281
context.subscriptions.push(
8382
commands.registerCommand("localstack.refreshStatusBar", () => {
8483
const setupStatus = setupStatusTracker.status();
85-
86-
if (setupStatus === "setup_required") {
87-
statusBarItem.command = "localstack.showCommands";
88-
statusBarItem.text = "$(error) LocalStack";
89-
statusBarItem.backgroundColor = new ThemeColor(
90-
"statusBarItem.errorBackground",
91-
);
92-
} else {
93-
statusBarItem.command = "localstack.showCommands";
94-
statusBarItem.backgroundColor = undefined;
95-
const localStackStatus = localStackStatusTracker.status();
96-
if (
97-
localStackStatus === "starting" ||
98-
localStackStatus === "stopping"
99-
) {
100-
statusBarItem.text = `$(sync~spin) LocalStack (${localStackStatus})`;
101-
} else if (
102-
localStackStatus === "running" ||
103-
localStackStatus === "stopped"
104-
) {
105-
statusBarItem.text = `$(localstack-logo) LocalStack (${localStackStatus})`;
106-
}
107-
}
108-
84+
const localStackStatus = localStackStatusTracker.status();
85+
const localStackInstalled = setupStatusTracker.statuses().isInstalled;
86+
87+
statusBarItem.command = "localstack.showCommands";
88+
statusBarItem.backgroundColor =
89+
setupStatus === "setup_required"
90+
? new ThemeColor("statusBarItem.errorBackground")
91+
: undefined;
92+
93+
const shouldSpin =
94+
localStackStatus === "starting" || localStackStatus === "stopping";
95+
const icon =
96+
setupStatus === "setup_required"
97+
? "$(error)"
98+
: shouldSpin
99+
? "$(sync~spin)"
100+
: "$(localstack-logo)";
101+
102+
const statusText = localStackInstalled
103+
? `${localStackStatus}`
104+
: "not installed";
105+
statusBarItem.text = `${icon} LocalStack: ${statusText}`;
106+
107+
statusBarItem.tooltip = "Show LocalStack commands";
109108
statusBarItem.show();
110109
}),
111110
);
@@ -119,6 +118,7 @@ export default createPlugin(
119118
});
120119
}
121120
};
121+
122122
context.subscriptions.push({
123123
dispose() {
124124
clearImmediate(refreshStatusBarImmediateId);
@@ -128,10 +128,12 @@ export default createPlugin(
128128
refreshStatusBarImmediate();
129129

130130
localStackStatusTracker.onChange(() => {
131+
outputChannel.trace("[status-bar]: localStackStatusTracker changed");
131132
refreshStatusBarImmediate();
132133
});
133134

134135
setupStatusTracker.onChange(() => {
136+
outputChannel.trace("[status-bar]: setupStatusTracker changed");
135137
refreshStatusBarImmediate();
136138
});
137139
},

src/utils/promises.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,8 @@ export function minDelay<T>(
3232
MIN_TIME_BETWEEN_STEPS_MS,
3333
);
3434
}
35+
36+
/**
37+
* Extracts the resolved type from a Promise.
38+
*/
39+
export type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

src/utils/setup-status.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import ms from "ms";
22
import type { Disposable, LogOutputChannel } from "vscode";
33

44
import { createEmitter } from "./emitter.ts";
5-
import { checkIsSetupRequired } from "./setup.ts";
5+
import type { UnwrapPromise } from "./promises.ts";
6+
import { checkSetupStatus } from "./setup.ts";
67
import type { TimeTracker } from "./time-tracker.ts";
78

89
export type SetupStatus = "ok" | "setup_required";
910

1011
export interface SetupStatusTracker extends Disposable {
1112
status(): SetupStatus;
13+
statuses(): UnwrapPromise<ReturnType<typeof checkSetupStatus>>;
1214
onChange(callback: (status: SetupStatus) => void): void;
1315
}
1416

@@ -20,6 +22,7 @@ export async function createSetupStatusTracker(
2022
timeTracker: TimeTracker,
2123
): Promise<SetupStatusTracker> {
2224
const start = Date.now();
25+
let statuses: UnwrapPromise<ReturnType<typeof checkSetupStatus>> | undefined;
2326
let status: SetupStatus | undefined;
2427
const emitter = createEmitter<SetupStatus>(outputChannel);
2528
const end = Date.now();
@@ -29,13 +32,18 @@ export async function createSetupStatusTracker(
2932

3033
let timeout: NodeJS.Timeout | undefined;
3134
const startChecking = async () => {
32-
const setupRequired = await checkIsSetupRequired(outputChannel);
35+
statuses = await checkSetupStatus(outputChannel);
36+
37+
const setupRequired = Object.values(statuses).some(
38+
(check) => check === false,
39+
);
3340
const newStatus = setupRequired ? "setup_required" : "ok";
3441
if (status !== newStatus) {
3542
status = newStatus;
3643
await emitter.emit(status);
3744
}
3845

46+
// TODO: Find a smarter way to check the status (e.g. watch for changes in AWS credentials or LocalStack installation)
3947
timeout = setTimeout(() => void startChecking(), 1_000);
4048
};
4149

@@ -48,6 +56,10 @@ export async function createSetupStatusTracker(
4856
// biome-ignore lint/style/noNonNullAssertion: false positive
4957
return status!;
5058
},
59+
statuses() {
60+
// biome-ignore lint/style/noNonNullAssertion: false positive
61+
return statuses!;
62+
},
5163
onChange(callback) {
5264
emitter.on(callback);
5365
if (status) {

src/utils/setup.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ import { checkIsProfileConfigured } from "./configure-aws.ts";
55
import { checkLocalstackInstalled } from "./install.ts";
66
import { checkIsLicenseValid } from "./license.ts";
77

8-
export async function checkIsSetupRequired(
9-
outputChannel: LogOutputChannel,
10-
): Promise<boolean> {
8+
export async function checkSetupStatus(outputChannel: LogOutputChannel) {
119
const [isInstalled, isAuthenticated, isLicenseValid, isProfileConfigured] =
1210
await Promise.all([
1311
checkLocalstackInstalled(outputChannel),
@@ -16,7 +14,10 @@ export async function checkIsSetupRequired(
1614
checkIsProfileConfigured(),
1715
]);
1816

19-
return (
20-
!isInstalled || !isAuthenticated || !isLicenseValid || !isProfileConfigured
21-
);
17+
return {
18+
isInstalled,
19+
isAuthenticated,
20+
isLicenseValid,
21+
isProfileConfigured,
22+
};
2223
}

0 commit comments

Comments
 (0)