statusbar-quick-actions 0.0.10 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/config-cli.js +905 -0
- package/out/config-cli.js.map +1 -0
- package/out/configuration.js +366 -0
- package/out/configuration.js.map +1 -0
- package/out/dynamic-label.js +272 -0
- package/out/dynamic-label.js.map +1 -0
- package/out/executor.js +352 -0
- package/out/executor.js.map +1 -0
- package/out/extension.js +1166 -0
- package/out/extension.js.map +1 -0
- package/out/history.js +98 -0
- package/out/history.js.map +1 -0
- package/out/material-icons.js +303 -0
- package/out/material-icons.js.map +1 -0
- package/out/notifications.js +135 -0
- package/out/notifications.js.map +1 -0
- package/out/output-panel.js +253 -0
- package/out/output-panel.js.map +1 -0
- package/out/preset-manager.js +293 -0
- package/out/preset-manager.js.map +1 -0
- package/out/theme.js +267 -0
- package/out/theme.js.map +1 -0
- package/out/types.js +2 -0
- package/out/types.js.map +1 -0
- package/out/utils/debounce.js +49 -0
- package/out/utils/debounce.js.map +1 -0
- package/out/visibility.js +195 -0
- package/out/visibility.js.map +1 -0
- package/package.json +2 -2
package/out/extension.js
ADDED
|
@@ -0,0 +1,1166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.StatusBarQuickActionsExtension = void 0;
|
|
37
|
+
exports.activate = activate;
|
|
38
|
+
exports.deactivate = deactivate;
|
|
39
|
+
const vscode = __importStar(require("vscode"));
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const configuration_1 = require("./configuration");
|
|
42
|
+
const executor_1 = require("./executor");
|
|
43
|
+
const theme_1 = require("./theme");
|
|
44
|
+
const visibility_1 = require("./visibility");
|
|
45
|
+
const material_icons_1 = require("./material-icons");
|
|
46
|
+
const output_panel_1 = require("./output-panel");
|
|
47
|
+
const preset_manager_1 = require("./preset-manager");
|
|
48
|
+
const dynamic_label_1 = require("./dynamic-label");
|
|
49
|
+
class StatusBarQuickActionsExtension {
|
|
50
|
+
context;
|
|
51
|
+
configManager;
|
|
52
|
+
commandExecutor;
|
|
53
|
+
themeManager;
|
|
54
|
+
visibilityManager;
|
|
55
|
+
materialIconManager;
|
|
56
|
+
outputPanelManager;
|
|
57
|
+
presetManager;
|
|
58
|
+
dynamicLabelManager;
|
|
59
|
+
buttonStates = new Map();
|
|
60
|
+
disposables = [];
|
|
61
|
+
editorChangeListener = null;
|
|
62
|
+
isActivated = false;
|
|
63
|
+
debugMode = false;
|
|
64
|
+
constructor(context) {
|
|
65
|
+
this.context = context;
|
|
66
|
+
this.configManager = new configuration_1.ConfigManager();
|
|
67
|
+
this.commandExecutor = new executor_1.CommandExecutor();
|
|
68
|
+
this.themeManager = new theme_1.ThemeManager();
|
|
69
|
+
this.debugMode = vscode.workspace
|
|
70
|
+
.getConfiguration("statusbarQuickActions.settings")
|
|
71
|
+
.get("debug", false);
|
|
72
|
+
}
|
|
73
|
+
debugLog(...args) {
|
|
74
|
+
if (this.debugMode) {
|
|
75
|
+
console.log("[StatusBar Quick Actions]", ...args);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async activate() {
|
|
79
|
+
if (this.isActivated) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
await this.initializeManagers();
|
|
84
|
+
this.registerCommands();
|
|
85
|
+
this.setupConfigurationWatching();
|
|
86
|
+
await this.loadConfiguration();
|
|
87
|
+
this.isActivated = true;
|
|
88
|
+
this.debugLog("Extension activated successfully");
|
|
89
|
+
setImmediate(() => {
|
|
90
|
+
this.showWelcomeMessageIfNeeded().catch((error) => {
|
|
91
|
+
this.debugLog("Failed to show welcome message:", error);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.error("Failed to activate StatusBar Quick Actions extension:", error);
|
|
97
|
+
vscode.window.showErrorMessage(`Failed to activate StatusBar Quick Actions: ${error}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async showWelcomeMessageIfNeeded() {
|
|
101
|
+
if (!this.context.globalState.get("hasBeenActivated")) {
|
|
102
|
+
await this.showWelcomeMessage();
|
|
103
|
+
await this.context.globalState.update("hasBeenActivated", true);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
deactivate() {
|
|
107
|
+
if (!this.isActivated) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
this.disposables.forEach((disposable) => disposable.dispose());
|
|
111
|
+
this.buttonStates.clear();
|
|
112
|
+
if (this.outputPanelManager) {
|
|
113
|
+
this.outputPanelManager.dispose();
|
|
114
|
+
}
|
|
115
|
+
if (this.visibilityManager) {
|
|
116
|
+
this.visibilityManager.dispose();
|
|
117
|
+
}
|
|
118
|
+
if (this.presetManager) {
|
|
119
|
+
this.presetManager.dispose();
|
|
120
|
+
}
|
|
121
|
+
if (this.dynamicLabelManager) {
|
|
122
|
+
this.dynamicLabelManager.dispose();
|
|
123
|
+
}
|
|
124
|
+
this.isActivated = false;
|
|
125
|
+
this.debugLog("Extension deactivated");
|
|
126
|
+
}
|
|
127
|
+
async initializeManagers() {
|
|
128
|
+
try {
|
|
129
|
+
this.configManager.initialize(this.context);
|
|
130
|
+
this.debugLog("ConfigManager initialized");
|
|
131
|
+
const outputConfig = this.configManager.getConfigValue("settings.output", this.getDefaultOutputConfig());
|
|
132
|
+
const performanceConfig = this.configManager.getConfigValue("settings.performance", this.getDefaultPerformanceConfig());
|
|
133
|
+
await Promise.all([
|
|
134
|
+
this.themeManager.initialize(this.context).then(() => {
|
|
135
|
+
this.debugLog("ThemeManager initialized");
|
|
136
|
+
}),
|
|
137
|
+
(async () => {
|
|
138
|
+
this.dynamicLabelManager = new dynamic_label_1.DynamicLabelManager();
|
|
139
|
+
await this.dynamicLabelManager.initialize();
|
|
140
|
+
this.dynamicLabelManager.onLabelRefresh = (buttonId) => {
|
|
141
|
+
this.refreshButtonLabel(buttonId);
|
|
142
|
+
};
|
|
143
|
+
this.debugLog("DynamicLabelManager initialized");
|
|
144
|
+
})(),
|
|
145
|
+
]);
|
|
146
|
+
this.materialIconManager = new material_icons_1.MaterialIconManager();
|
|
147
|
+
this.outputPanelManager = new output_panel_1.OutputPanelManager(outputConfig);
|
|
148
|
+
this.visibilityManager = new visibility_1.VisibilityManager(performanceConfig.visibilityDebounceMs);
|
|
149
|
+
this.presetManager = new preset_manager_1.PresetManager();
|
|
150
|
+
this.presetManager.initialize(this.context);
|
|
151
|
+
this.debugLog("All synchronous managers initialized");
|
|
152
|
+
this.setupEditorChangeListener();
|
|
153
|
+
this.debugLog("Managers initialization complete");
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
157
|
+
console.error("Failed to initialize managers:", errorMessage);
|
|
158
|
+
vscode.window.showErrorMessage(`StatusBar Quick Actions: Failed to initialize - ${errorMessage}`);
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
registerCommands() {
|
|
163
|
+
this.disposables.push(vscode.commands.registerCommand("statusbarQuickActions.editButton", this.editButton.bind(this)));
|
|
164
|
+
this.disposables.push(vscode.commands.registerCommand("statusbarQuickActions.viewHistory", this.viewHistory.bind(this)));
|
|
165
|
+
this.disposables.push(vscode.commands.registerCommand("statusbarQuickActions.clearHistory", this.clearHistory.bind(this)));
|
|
166
|
+
this.disposables.push(vscode.commands.registerCommand("statusbarQuickActions.managePresets", this.managePresets.bind(this)));
|
|
167
|
+
this.disposables.push(vscode.commands.registerCommand("statusbarQuickActions.applyPreset", this.applyPresetCommand.bind(this)));
|
|
168
|
+
this.disposables.push(vscode.commands.registerCommand("statusbarQuickActions.saveAsPreset", this.saveAsPreset.bind(this)));
|
|
169
|
+
this.registerButtonCommands();
|
|
170
|
+
}
|
|
171
|
+
registerButtonCommands() {
|
|
172
|
+
const config = this.configManager.getConfig();
|
|
173
|
+
config.buttons.forEach((button) => {
|
|
174
|
+
const commandId = `statusbarQuickActions.execute_${button.id}`;
|
|
175
|
+
this.disposables.push(vscode.commands.registerCommand(commandId, () => this.executeButton(button.id)));
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
setupConfigurationWatching() {
|
|
179
|
+
this.disposables.push(this.configManager.onConfigurationChanged(async (newConfig) => {
|
|
180
|
+
this.debugMode = vscode.workspace
|
|
181
|
+
.getConfiguration("statusbarQuickActions.settings")
|
|
182
|
+
.get("debug", false);
|
|
183
|
+
this.debugLog("Configuration changed, updating buttons");
|
|
184
|
+
await this.updateConfiguration(newConfig);
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
async loadConfiguration() {
|
|
188
|
+
const config = this.configManager.getConfig();
|
|
189
|
+
await this.updateConfiguration(config);
|
|
190
|
+
}
|
|
191
|
+
async updateConfiguration(config) {
|
|
192
|
+
this.debugLog("Updating configuration with buttons:", config.buttons.length);
|
|
193
|
+
if (this.debugMode) {
|
|
194
|
+
config.buttons.forEach((button, index) => {
|
|
195
|
+
this.debugLog(`Button ${index}: ${button.id} - ${button.text || "no text"}`, button);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const validation = this.configManager.validateConfig(config);
|
|
199
|
+
if (!validation.isValid) {
|
|
200
|
+
const errorMessage = `Invalid button configuration:\n${validation.errors.join("\n")}`;
|
|
201
|
+
console.error(errorMessage);
|
|
202
|
+
vscode.window.showErrorMessage(`StatusBar Quick Actions: Configuration validation failed. Check console for details.`);
|
|
203
|
+
const outputChannel = vscode.window.createOutputChannel("StatusBar Quick Actions - Errors");
|
|
204
|
+
outputChannel.appendLine("Configuration Validation Errors:");
|
|
205
|
+
validation.errors.forEach((error) => outputChannel.appendLine(` - ${error}`));
|
|
206
|
+
outputChannel.show(true);
|
|
207
|
+
}
|
|
208
|
+
this.buttonStates.forEach((state) => {
|
|
209
|
+
state.item.dispose();
|
|
210
|
+
});
|
|
211
|
+
this.buttonStates.clear();
|
|
212
|
+
let createdCount = 0;
|
|
213
|
+
let failedCount = 0;
|
|
214
|
+
let disabledCount = 0;
|
|
215
|
+
const buttonCreationPromises = config.buttons.map(async (buttonConfig) => {
|
|
216
|
+
if (buttonConfig.enabled === false) {
|
|
217
|
+
this.debugLog(`Button ${buttonConfig.id} is disabled, skipping`);
|
|
218
|
+
disabledCount++;
|
|
219
|
+
return { created: false, disabled: true };
|
|
220
|
+
}
|
|
221
|
+
const created = await this.createStatusBarItem(buttonConfig);
|
|
222
|
+
return { created, disabled: false };
|
|
223
|
+
});
|
|
224
|
+
const results = await Promise.all(buttonCreationPromises);
|
|
225
|
+
results.forEach((result) => {
|
|
226
|
+
if (result.created) {
|
|
227
|
+
createdCount++;
|
|
228
|
+
}
|
|
229
|
+
else if (!result.disabled) {
|
|
230
|
+
failedCount++;
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
this.debugLog(`Created ${createdCount} buttons, ${failedCount} failed, ${disabledCount} disabled`);
|
|
234
|
+
if (createdCount === 0 && config.buttons.length > 0) {
|
|
235
|
+
vscode.window.showWarningMessage(`StatusBar Quick Actions: No buttons could be created. Check the output panel for errors.`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async createStatusBarItem(buttonConfig) {
|
|
239
|
+
try {
|
|
240
|
+
this.debugLog(`Creating status bar item for button: ${buttonConfig.id}`);
|
|
241
|
+
if (!buttonConfig.id) {
|
|
242
|
+
const error = `Button ${buttonConfig.id || "unknown"} missing ID`;
|
|
243
|
+
this.debugLog(error);
|
|
244
|
+
throw new Error("Button ID is required");
|
|
245
|
+
}
|
|
246
|
+
if (!buttonConfig.text && !buttonConfig.icon) {
|
|
247
|
+
const error = `Button ${buttonConfig.id} missing both text and icon`;
|
|
248
|
+
this.debugLog(error);
|
|
249
|
+
throw new Error("Either button text or icon is required");
|
|
250
|
+
}
|
|
251
|
+
if (!buttonConfig.command) {
|
|
252
|
+
const error = `Button ${buttonConfig.id} missing command`;
|
|
253
|
+
this.debugLog(error);
|
|
254
|
+
throw new Error("Button command is required");
|
|
255
|
+
}
|
|
256
|
+
const alignment = buttonConfig.alignment === "left"
|
|
257
|
+
? vscode.StatusBarAlignment.Left
|
|
258
|
+
: vscode.StatusBarAlignment.Right;
|
|
259
|
+
const priority = buttonConfig.priority ?? 100;
|
|
260
|
+
const statusBarItem = vscode.window.createStatusBarItem(alignment, priority);
|
|
261
|
+
this.debugLog(`Created status bar item for ${buttonConfig.id} (alignment: ${alignment}, priority: ${priority})`);
|
|
262
|
+
const displayText = this.getButtonDisplayText(buttonConfig);
|
|
263
|
+
this.debugLog(`Button ${buttonConfig.id} display text: "${displayText}"`);
|
|
264
|
+
if (!displayText || displayText.trim() === "") {
|
|
265
|
+
const error = `Button ${buttonConfig.id} has empty display text`;
|
|
266
|
+
this.debugLog(error);
|
|
267
|
+
throw new Error("Button display text cannot be empty");
|
|
268
|
+
}
|
|
269
|
+
statusBarItem.text = displayText;
|
|
270
|
+
statusBarItem.tooltip =
|
|
271
|
+
buttonConfig.tooltip || buttonConfig.text || "Quick Action";
|
|
272
|
+
statusBarItem.command = `statusbarQuickActions.execute_${buttonConfig.id}`;
|
|
273
|
+
this.debugLog(`Button ${buttonConfig.id} command: ${statusBarItem.command}`);
|
|
274
|
+
this.themeManager.applyThemeToStatusBarItem(statusBarItem);
|
|
275
|
+
statusBarItem.accessibilityInformation = {
|
|
276
|
+
label: buttonConfig.tooltip ||
|
|
277
|
+
buttonConfig.text ||
|
|
278
|
+
`Button ${buttonConfig.id}`,
|
|
279
|
+
role: "button",
|
|
280
|
+
};
|
|
281
|
+
const buttonState = {
|
|
282
|
+
item: statusBarItem,
|
|
283
|
+
config: buttonConfig,
|
|
284
|
+
isExecuting: false,
|
|
285
|
+
history: [],
|
|
286
|
+
accessibility: {
|
|
287
|
+
role: "button",
|
|
288
|
+
ariaLabel: buttonConfig.tooltip ||
|
|
289
|
+
buttonConfig.text ||
|
|
290
|
+
`Button ${buttonConfig.id}`,
|
|
291
|
+
focusOrder: priority,
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
this.buttonStates.set(buttonConfig.id, buttonState);
|
|
295
|
+
this.disposables.push(statusBarItem);
|
|
296
|
+
statusBarItem.show();
|
|
297
|
+
setImmediate(() => {
|
|
298
|
+
this.loadHistoryAsync(buttonConfig.id).catch((error) => {
|
|
299
|
+
this.debugLog(`Failed to load history for ${buttonConfig.id}:`, error);
|
|
300
|
+
});
|
|
301
|
+
if (buttonConfig.dynamicLabel) {
|
|
302
|
+
this.refreshButtonLabel(buttonConfig.id).catch((error) => {
|
|
303
|
+
this.debugLog(`Failed to refresh label for ${buttonConfig.id}:`, error);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
this.debugLog(`Button ${buttonConfig.id} created and shown`);
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
312
|
+
console.error(`Failed to create statusbar item for button ${buttonConfig.id}:`, errorMessage);
|
|
313
|
+
vscode.window.showErrorMessage(`Failed to create button "${buttonConfig.text || buttonConfig.id}": ${errorMessage}`);
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
getButtonDisplayText(buttonConfig) {
|
|
318
|
+
if (buttonConfig.icon) {
|
|
319
|
+
const resolvedIconId = this.materialIconManager.resolveIcon(buttonConfig.icon);
|
|
320
|
+
const iconPrefix = buttonConfig.icon.animation === "spin"
|
|
321
|
+
? `$(${resolvedIconId}~spin)`
|
|
322
|
+
: buttonConfig.icon.animation === "pulse"
|
|
323
|
+
? `$(${resolvedIconId}~pulse)`
|
|
324
|
+
: `$(${resolvedIconId})`;
|
|
325
|
+
return iconPrefix;
|
|
326
|
+
}
|
|
327
|
+
return buttonConfig.text;
|
|
328
|
+
}
|
|
329
|
+
async executeButton(buttonId) {
|
|
330
|
+
const buttonState = this.buttonStates.get(buttonId);
|
|
331
|
+
if (!buttonState || buttonState.isExecuting) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const config = buttonState.config;
|
|
335
|
+
try {
|
|
336
|
+
buttonState.isExecuting = true;
|
|
337
|
+
this.updateButtonState(buttonId, buttonState);
|
|
338
|
+
if (config.execution?.showProgress) {
|
|
339
|
+
this.showProgress(buttonId);
|
|
340
|
+
}
|
|
341
|
+
const executionOptions = {
|
|
342
|
+
workingDirectory: config.workingDirectory,
|
|
343
|
+
environment: config.environment,
|
|
344
|
+
};
|
|
345
|
+
if (config.execution?.timeout) {
|
|
346
|
+
executionOptions.timeout = config.execution.timeout;
|
|
347
|
+
}
|
|
348
|
+
const outputConfig = this.outputPanelManager.getConfig();
|
|
349
|
+
if (outputConfig.enabled) {
|
|
350
|
+
this.outputPanelManager.getOrCreatePanel(buttonId, config.text);
|
|
351
|
+
if (outputConfig.clearOnRun) {
|
|
352
|
+
this.outputPanelManager.clearPanel(buttonId);
|
|
353
|
+
}
|
|
354
|
+
executionOptions.streaming = {
|
|
355
|
+
enabled: true,
|
|
356
|
+
onStdout: (data) => {
|
|
357
|
+
this.outputPanelManager.appendOutput(buttonId, data, "stdout");
|
|
358
|
+
},
|
|
359
|
+
onStderr: (data) => {
|
|
360
|
+
this.outputPanelManager.appendOutput(buttonId, data, "stderr");
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
this.outputPanelManager.showPanel(buttonId, true);
|
|
364
|
+
}
|
|
365
|
+
const result = await this.commandExecutor.execute(config.command, executionOptions);
|
|
366
|
+
buttonState.isExecuting = false;
|
|
367
|
+
buttonState.lastResult = result;
|
|
368
|
+
if (config.history?.enabled !== false) {
|
|
369
|
+
await this.addToHistory(buttonId, result);
|
|
370
|
+
}
|
|
371
|
+
this.updateButtonState(buttonId, buttonState);
|
|
372
|
+
await this.showExecutionResult(buttonId, result);
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
buttonState.isExecuting = false;
|
|
376
|
+
this.updateButtonState(buttonId, buttonState);
|
|
377
|
+
const errorResult = {
|
|
378
|
+
code: -1,
|
|
379
|
+
stdout: "",
|
|
380
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
381
|
+
duration: 0,
|
|
382
|
+
timestamp: new Date(),
|
|
383
|
+
command: "Unknown",
|
|
384
|
+
};
|
|
385
|
+
buttonState.lastResult = errorResult;
|
|
386
|
+
this.updateButtonState(buttonId, buttonState);
|
|
387
|
+
await this.showExecutionError(buttonId, error);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
updateButtonState(buttonId, buttonState) {
|
|
391
|
+
const config = buttonState.config;
|
|
392
|
+
if (buttonState.isExecuting) {
|
|
393
|
+
if (config.icon?.animation) {
|
|
394
|
+
buttonState.item.text =
|
|
395
|
+
config.icon.animation === "spin"
|
|
396
|
+
? "$(sync~spin)"
|
|
397
|
+
: config.icon.animation === "pulse"
|
|
398
|
+
? "$(sync~pulse)"
|
|
399
|
+
: this.getButtonDisplayText(config);
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
buttonState.item.text = `$(sync~spin) ${config.text}`;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
buttonState.item.text = this.getButtonDisplayText(config);
|
|
407
|
+
}
|
|
408
|
+
if (buttonState.lastResult) {
|
|
409
|
+
const result = buttonState.lastResult;
|
|
410
|
+
const status = result.code === 0 ? "✅" : "❌";
|
|
411
|
+
const duration = result.duration ? ` (${result.duration}ms)` : "";
|
|
412
|
+
buttonState.item.tooltip = `${config.tooltip || config.text}\n${status} Last run: ${result.timestamp.toLocaleTimeString()}${duration}`;
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
buttonState.item.tooltip = config.tooltip || config.text;
|
|
416
|
+
}
|
|
417
|
+
buttonState.item.show();
|
|
418
|
+
}
|
|
419
|
+
showProgress(buttonId) {
|
|
420
|
+
const buttonState = this.buttonStates.get(buttonId);
|
|
421
|
+
if (!buttonState) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
vscode.window.withProgress({
|
|
425
|
+
location: vscode.ProgressLocation.Notification,
|
|
426
|
+
title: `Executing: ${buttonState.config.text}`,
|
|
427
|
+
cancellable: false,
|
|
428
|
+
}, async (progress) => {
|
|
429
|
+
for (let i = 0; i <= 100; i += 10) {
|
|
430
|
+
if (!buttonState.isExecuting) {
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
progress.report({ increment: 10, message: `${i}%` });
|
|
434
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
async showExecutionResult(buttonId, result) {
|
|
439
|
+
const buttonState = this.buttonStates.get(buttonId);
|
|
440
|
+
if (!buttonState) {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const config = buttonState.config;
|
|
444
|
+
if (result.code === 0) {
|
|
445
|
+
const message = this.getResultMessage(result);
|
|
446
|
+
vscode.window
|
|
447
|
+
.showInformationMessage(`✅ ${config.text}: ${message}`, "View Output")
|
|
448
|
+
.then((selection) => {
|
|
449
|
+
if (selection === "View Output") {
|
|
450
|
+
this.showOutput(result);
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
async showExecutionError(buttonId, error) {
|
|
456
|
+
const buttonState = this.buttonStates.get(buttonId);
|
|
457
|
+
if (!buttonState) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const config = buttonState.config;
|
|
461
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
462
|
+
vscode.window
|
|
463
|
+
.showErrorMessage(`❌ ${config.text}: ${errorMessage}`, "View Details")
|
|
464
|
+
.then((selection) => {
|
|
465
|
+
if (selection === "View Details") {
|
|
466
|
+
vscode.window.showErrorMessage(errorMessage, { modal: true });
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
getResultMessage(result) {
|
|
471
|
+
const showTime = true;
|
|
472
|
+
const timeStr = showTime && result.duration ? ` in ${result.duration}ms` : "";
|
|
473
|
+
if (result.stdout && result.stdout.trim()) {
|
|
474
|
+
const output = result.stdout.trim().split("\n")[0];
|
|
475
|
+
return output.length > 100
|
|
476
|
+
? `${output.substring(0, 100)}...${timeStr}`
|
|
477
|
+
: `${output}${timeStr}`;
|
|
478
|
+
}
|
|
479
|
+
return `Completed successfully${timeStr}`;
|
|
480
|
+
}
|
|
481
|
+
showOutput(result) {
|
|
482
|
+
const output = `Command Output:\n${result.stdout}\n\nErrors:\n${result.stderr}`;
|
|
483
|
+
vscode.window.showInformationMessage(output, { modal: true });
|
|
484
|
+
}
|
|
485
|
+
async editButton() {
|
|
486
|
+
const mainMenuItems = [
|
|
487
|
+
{
|
|
488
|
+
label: "$(add) Add New Button",
|
|
489
|
+
description: "Create a new status bar button",
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
label: "$(edit) Edit Existing Button",
|
|
493
|
+
description: "Modify an existing button configuration",
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
label: "$(trash) Delete Button",
|
|
497
|
+
description: "Remove a button from the status bar",
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
label: "$(copy) Duplicate Button",
|
|
501
|
+
description: "Create a copy of an existing button",
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
label: "$(sync) Toggle Button",
|
|
505
|
+
description: "Enable or disable a button",
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
label: "$(archive) Manage Presets",
|
|
509
|
+
description: "Save, load, or manage configuration presets",
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
label: "$(settings-gear) Open Full Settings",
|
|
513
|
+
description: "Open VS Code settings page",
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
label: "$(export) Export Configuration",
|
|
517
|
+
description: "Export all button configurations to a file",
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
label: "$(import) Import Configuration",
|
|
521
|
+
description: "Import button configurations from a file",
|
|
522
|
+
},
|
|
523
|
+
];
|
|
524
|
+
const selected = await vscode.window.showQuickPick(mainMenuItems, {
|
|
525
|
+
placeHolder: "StatusBar Quick Actions - Settings",
|
|
526
|
+
matchOnDescription: true,
|
|
527
|
+
});
|
|
528
|
+
if (!selected) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
switch (selected.label) {
|
|
532
|
+
case "$(add) Add New Button":
|
|
533
|
+
await this.addNewButton();
|
|
534
|
+
break;
|
|
535
|
+
case "$(edit) Edit Existing Button":
|
|
536
|
+
await this.selectAndEditButton();
|
|
537
|
+
break;
|
|
538
|
+
case "$(trash) Delete Button":
|
|
539
|
+
await this.deleteButton();
|
|
540
|
+
break;
|
|
541
|
+
case "$(copy) Duplicate Button":
|
|
542
|
+
await this.duplicateButton();
|
|
543
|
+
break;
|
|
544
|
+
case "$(sync) Toggle Button":
|
|
545
|
+
await this.toggleButton();
|
|
546
|
+
break;
|
|
547
|
+
case "$(archive) Manage Presets":
|
|
548
|
+
await this.managePresets();
|
|
549
|
+
break;
|
|
550
|
+
case "$(settings-gear) Open Full Settings":
|
|
551
|
+
vscode.commands.executeCommand("workbench.action.openSettings", "@ext:involvex.statusbar-quick-actions");
|
|
552
|
+
break;
|
|
553
|
+
case "$(export) Export Configuration":
|
|
554
|
+
await this.exportConfiguration();
|
|
555
|
+
break;
|
|
556
|
+
case "$(import) Import Configuration":
|
|
557
|
+
await this.importConfiguration();
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
async addNewButton() {
|
|
562
|
+
const text = await vscode.window.showInputBox({
|
|
563
|
+
prompt: "Enter button text (supports emojis)",
|
|
564
|
+
placeHolder: "e.g., Build 🔨",
|
|
565
|
+
validateInput: (value) => (value ? null : "Button text is required"),
|
|
566
|
+
});
|
|
567
|
+
if (!text) {
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const commandTypes = [
|
|
571
|
+
{ label: "npm", description: "Run npm script" },
|
|
572
|
+
{ label: "yarn", description: "Run yarn script" },
|
|
573
|
+
{ label: "pnpm", description: "Run pnpm script" },
|
|
574
|
+
{ label: "bun", description: "Run bun script" },
|
|
575
|
+
{ label: "shell", description: "Run shell command" },
|
|
576
|
+
{ label: "vscode", description: "Run VS Code command" },
|
|
577
|
+
{ label: "task", description: "Run VS Code task" },
|
|
578
|
+
{ label: "github", description: "Run GitHub CLI command" },
|
|
579
|
+
{ label: "npx", description: "Run npx command" },
|
|
580
|
+
{ label: "pnpx", description: "Run pnpx command" },
|
|
581
|
+
{ label: "bunx", description: "Run bunx command" },
|
|
582
|
+
{ label: "detect", description: "Auto-detect package manager" },
|
|
583
|
+
];
|
|
584
|
+
const commandType = await vscode.window.showQuickPick(commandTypes, {
|
|
585
|
+
placeHolder: "Select command type",
|
|
586
|
+
});
|
|
587
|
+
if (!commandType) {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const command = await vscode.window.showInputBox({
|
|
591
|
+
prompt: commandType.label === "npm" ||
|
|
592
|
+
commandType.label === "yarn" ||
|
|
593
|
+
commandType.label === "pnpm" ||
|
|
594
|
+
commandType.label === "bun" ||
|
|
595
|
+
commandType.label === "npx" ||
|
|
596
|
+
commandType.label === "pnpx" ||
|
|
597
|
+
commandType.label === "bunx" ||
|
|
598
|
+
commandType.label === "detect"
|
|
599
|
+
? "Enter script name"
|
|
600
|
+
: "Enter command",
|
|
601
|
+
placeHolder: commandType.label === "npm" ? "e.g., build" : 'e.g., echo "Hello"',
|
|
602
|
+
validateInput: (value) => (value ? null : "Command is required"),
|
|
603
|
+
});
|
|
604
|
+
if (!command) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const id = `button_${Date.now()}`;
|
|
608
|
+
const newButton = {
|
|
609
|
+
id,
|
|
610
|
+
text,
|
|
611
|
+
tooltip: text,
|
|
612
|
+
command: {
|
|
613
|
+
type: commandType.label,
|
|
614
|
+
script: [
|
|
615
|
+
"npm",
|
|
616
|
+
"yarn",
|
|
617
|
+
"pnpm",
|
|
618
|
+
"bun",
|
|
619
|
+
"bunx",
|
|
620
|
+
"npx",
|
|
621
|
+
"pnpx",
|
|
622
|
+
"detect",
|
|
623
|
+
].includes(commandType.label)
|
|
624
|
+
? command
|
|
625
|
+
: undefined,
|
|
626
|
+
command: ![
|
|
627
|
+
"npm",
|
|
628
|
+
"yarn",
|
|
629
|
+
"pnpm",
|
|
630
|
+
"bun",
|
|
631
|
+
"bunx",
|
|
632
|
+
"npx",
|
|
633
|
+
"pnpx",
|
|
634
|
+
"detect",
|
|
635
|
+
].includes(commandType.label)
|
|
636
|
+
? command
|
|
637
|
+
: undefined,
|
|
638
|
+
},
|
|
639
|
+
enabled: true,
|
|
640
|
+
alignment: "left",
|
|
641
|
+
priority: 100,
|
|
642
|
+
};
|
|
643
|
+
const config = this.configManager.getConfig();
|
|
644
|
+
config.buttons.push(newButton);
|
|
645
|
+
await this.configManager.setConfig("buttons", config.buttons);
|
|
646
|
+
vscode.window.showInformationMessage(`✅ Button "${text}" added successfully!`);
|
|
647
|
+
}
|
|
648
|
+
async selectAndEditButton() {
|
|
649
|
+
const config = this.configManager.getConfig();
|
|
650
|
+
if (config.buttons.length === 0) {
|
|
651
|
+
vscode.window.showInformationMessage("No buttons configured yet.");
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
const items = config.buttons.map((button) => ({
|
|
655
|
+
label: button.text,
|
|
656
|
+
description: button.command.type,
|
|
657
|
+
detail: button.tooltip,
|
|
658
|
+
button: button,
|
|
659
|
+
}));
|
|
660
|
+
const selected = await vscode.window.showQuickPick(items, {
|
|
661
|
+
placeHolder: "Select a button to edit",
|
|
662
|
+
});
|
|
663
|
+
if (selected) {
|
|
664
|
+
vscode.commands.executeCommand("workbench.action.openSettings", `@ext:involvex.statusbar-quick-actions buttons`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
async deleteButton() {
|
|
668
|
+
const config = this.configManager.getConfig();
|
|
669
|
+
if (config.buttons.length === 0) {
|
|
670
|
+
vscode.window.showInformationMessage("No buttons configured yet.");
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const items = config.buttons.map((button) => ({
|
|
674
|
+
label: button.text,
|
|
675
|
+
description: button.command.type,
|
|
676
|
+
detail: button.id,
|
|
677
|
+
}));
|
|
678
|
+
const selected = await vscode.window.showQuickPick(items, {
|
|
679
|
+
placeHolder: "Select a button to delete",
|
|
680
|
+
});
|
|
681
|
+
if (!selected) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
const confirm = await vscode.window.showWarningMessage(`Delete button "${selected.label}"?`, { modal: true }, "Yes, Delete", "No");
|
|
685
|
+
if (confirm === "Yes, Delete") {
|
|
686
|
+
const updatedButtons = config.buttons.filter((b) => b.id !== selected.detail);
|
|
687
|
+
await this.configManager.setConfig("buttons", updatedButtons);
|
|
688
|
+
vscode.window.showInformationMessage(`✅ Button "${selected.label}" deleted`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
async duplicateButton() {
|
|
692
|
+
const config = this.configManager.getConfig();
|
|
693
|
+
if (config.buttons.length === 0) {
|
|
694
|
+
vscode.window.showInformationMessage("No buttons configured yet.");
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
const items = config.buttons.map((button) => ({
|
|
698
|
+
label: button.text,
|
|
699
|
+
description: button.command.type,
|
|
700
|
+
button: button,
|
|
701
|
+
}));
|
|
702
|
+
const selected = await vscode.window.showQuickPick(items, {
|
|
703
|
+
placeHolder: "Select a button to duplicate",
|
|
704
|
+
});
|
|
705
|
+
if (!selected) {
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
const newButton = {
|
|
709
|
+
...selected.button,
|
|
710
|
+
id: `button_${Date.now()}`,
|
|
711
|
+
text: `${selected.button.text} (Copy)`,
|
|
712
|
+
};
|
|
713
|
+
config.buttons.push(newButton);
|
|
714
|
+
await this.configManager.setConfig("buttons", config.buttons);
|
|
715
|
+
vscode.window.showInformationMessage(`✅ Button duplicated successfully!`);
|
|
716
|
+
}
|
|
717
|
+
async toggleButton() {
|
|
718
|
+
const config = this.configManager.getConfig();
|
|
719
|
+
if (config.buttons.length === 0) {
|
|
720
|
+
vscode.window.showInformationMessage("No buttons configured yet.");
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const items = config.buttons.map((button) => ({
|
|
724
|
+
label: button.text,
|
|
725
|
+
description: button.enabled ? "$(check) Enabled" : "$(x) Disabled",
|
|
726
|
+
button: button,
|
|
727
|
+
}));
|
|
728
|
+
const selected = await vscode.window.showQuickPick(items, {
|
|
729
|
+
placeHolder: "Select a button to toggle",
|
|
730
|
+
});
|
|
731
|
+
if (!selected) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
const buttonIndex = config.buttons.findIndex((b) => b.id === selected.button.id);
|
|
735
|
+
config.buttons[buttonIndex].enabled = !config.buttons[buttonIndex].enabled;
|
|
736
|
+
await this.configManager.setConfig("buttons", config.buttons);
|
|
737
|
+
const status = config.buttons[buttonIndex].enabled ? "enabled" : "disabled";
|
|
738
|
+
vscode.window.showInformationMessage(`✅ Button "${selected.label}" ${status}`);
|
|
739
|
+
}
|
|
740
|
+
async exportConfiguration() {
|
|
741
|
+
const config = this.configManager.getConfig();
|
|
742
|
+
const uri = await vscode.window.showSaveDialog({
|
|
743
|
+
filters: { JSON: ["json"] },
|
|
744
|
+
defaultUri: vscode.Uri.file("statusbar-quick-actions-config.json"),
|
|
745
|
+
});
|
|
746
|
+
if (uri) {
|
|
747
|
+
fs.writeFileSync(uri.fsPath, JSON.stringify(config, null, 2));
|
|
748
|
+
vscode.window.showInformationMessage(`✅ Configuration exported to ${uri.fsPath}`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
async importConfiguration() {
|
|
752
|
+
const uri = await vscode.window.showOpenDialog({
|
|
753
|
+
filters: { JSON: ["json"] },
|
|
754
|
+
canSelectMany: false,
|
|
755
|
+
});
|
|
756
|
+
if (uri && uri[0]) {
|
|
757
|
+
try {
|
|
758
|
+
const content = fs.readFileSync(uri[0].fsPath, "utf8");
|
|
759
|
+
const importedConfig = JSON.parse(content);
|
|
760
|
+
const merge = await vscode.window.showQuickPick(["Replace All", "Merge with Existing"], { placeHolder: "Import mode" });
|
|
761
|
+
if (!merge) {
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (merge === "Replace All") {
|
|
765
|
+
await this.configManager.setConfig("buttons", importedConfig.buttons || []);
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
const config = this.configManager.getConfig();
|
|
769
|
+
const mergedButtons = [
|
|
770
|
+
...config.buttons,
|
|
771
|
+
...(importedConfig.buttons || []),
|
|
772
|
+
];
|
|
773
|
+
await this.configManager.setConfig("buttons", mergedButtons);
|
|
774
|
+
}
|
|
775
|
+
vscode.window.showInformationMessage("✅ Configuration imported successfully!");
|
|
776
|
+
}
|
|
777
|
+
catch (error) {
|
|
778
|
+
vscode.window.showErrorMessage(`Failed to import configuration: ${error}`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
async addToHistory(buttonId, result) {
|
|
783
|
+
try {
|
|
784
|
+
const historyKey = `history_${buttonId}`;
|
|
785
|
+
const history = this.context.globalState.get(historyKey, []);
|
|
786
|
+
history.unshift(result);
|
|
787
|
+
const buttonState = this.buttonStates.get(buttonId);
|
|
788
|
+
const maxEntries = buttonState?.config.history?.maxEntries || 20;
|
|
789
|
+
while (history.length > maxEntries) {
|
|
790
|
+
history.pop();
|
|
791
|
+
}
|
|
792
|
+
await this.context.globalState.update(historyKey, history);
|
|
793
|
+
if (buttonState) {
|
|
794
|
+
buttonState.history = history;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
catch (error) {
|
|
798
|
+
console.error(`Failed to add execution to history for button ${buttonId}:`, error);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
async loadHistory(buttonId) {
|
|
802
|
+
try {
|
|
803
|
+
const historyKey = `history_${buttonId}`;
|
|
804
|
+
return this.context.globalState.get(historyKey, []);
|
|
805
|
+
}
|
|
806
|
+
catch (error) {
|
|
807
|
+
console.error(`Failed to load history for button ${buttonId}:`, error);
|
|
808
|
+
return [];
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
async loadHistoryAsync(buttonId) {
|
|
812
|
+
const buttonState = this.buttonStates.get(buttonId);
|
|
813
|
+
if (!buttonState) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
const history = await this.loadHistory(buttonId);
|
|
817
|
+
buttonState.history = history;
|
|
818
|
+
this.debugLog(`History loaded for ${buttonId}: ${history.length} entries`);
|
|
819
|
+
}
|
|
820
|
+
async getAllHistory() {
|
|
821
|
+
const allHistory = new Map();
|
|
822
|
+
for (const [buttonId] of this.buttonStates) {
|
|
823
|
+
const history = await this.loadHistory(buttonId);
|
|
824
|
+
if (history.length > 0) {
|
|
825
|
+
allHistory.set(buttonId, history);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
return allHistory;
|
|
829
|
+
}
|
|
830
|
+
async viewHistory() {
|
|
831
|
+
const allHistory = await this.getAllHistory();
|
|
832
|
+
if (allHistory.size === 0) {
|
|
833
|
+
vscode.window.showInformationMessage("No command history available yet.");
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
const items = [];
|
|
837
|
+
for (const [buttonId, history] of allHistory) {
|
|
838
|
+
const buttonState = this.buttonStates.get(buttonId);
|
|
839
|
+
const buttonName = buttonState?.config.text || buttonId;
|
|
840
|
+
items.push({
|
|
841
|
+
label: `$(inbox) ${buttonName}`,
|
|
842
|
+
kind: vscode.QuickPickItemKind.Separator,
|
|
843
|
+
});
|
|
844
|
+
history.forEach((entry) => {
|
|
845
|
+
const status = entry.code === 0 ? "$(check)" : "$(error)";
|
|
846
|
+
const time = entry.timestamp.toLocaleString();
|
|
847
|
+
const duration = entry.duration ? ` (${entry.duration}ms)` : "";
|
|
848
|
+
items.push({
|
|
849
|
+
label: `${status} ${entry.command}`,
|
|
850
|
+
description: `${time}${duration}`,
|
|
851
|
+
detail: entry.stderr || entry.stdout?.substring(0, 100),
|
|
852
|
+
buttons: [
|
|
853
|
+
{
|
|
854
|
+
iconPath: new vscode.ThemeIcon("output"),
|
|
855
|
+
tooltip: "View Full Output",
|
|
856
|
+
},
|
|
857
|
+
],
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
const selected = await vscode.window.showQuickPick(items, {
|
|
862
|
+
placeHolder: "Command Execution History",
|
|
863
|
+
matchOnDescription: true,
|
|
864
|
+
matchOnDetail: true,
|
|
865
|
+
});
|
|
866
|
+
if (selected && selected.detail) {
|
|
867
|
+
const doc = await vscode.workspace.openTextDocument({
|
|
868
|
+
content: `Command: ${selected.label}\nTime: ${selected.description}\n\nOutput:\n${selected.detail}`,
|
|
869
|
+
language: "text",
|
|
870
|
+
});
|
|
871
|
+
await vscode.window.showTextDocument(doc);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
async clearHistory() {
|
|
875
|
+
const confirm = await vscode.window.showWarningMessage("Are you sure you want to clear all command history?", { modal: true }, "Yes, Clear History", "No");
|
|
876
|
+
if (confirm === "Yes, Clear History") {
|
|
877
|
+
try {
|
|
878
|
+
for (const [buttonId, buttonState] of this.buttonStates) {
|
|
879
|
+
const historyKey = `history_${buttonId}`;
|
|
880
|
+
await this.context.globalState.update(historyKey, []);
|
|
881
|
+
buttonState.history = [];
|
|
882
|
+
}
|
|
883
|
+
vscode.window.showInformationMessage("✅ Command history cleared successfully");
|
|
884
|
+
}
|
|
885
|
+
catch (error) {
|
|
886
|
+
vscode.window.showErrorMessage(`Failed to clear history: ${error}`);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
async showWelcomeMessage() {
|
|
891
|
+
const config = this.configManager.getConfig();
|
|
892
|
+
if (config.buttons.length === 0) {
|
|
893
|
+
vscode.window
|
|
894
|
+
.showInformationMessage("👋 Welcome to StatusBar Quick Actions! Configure your first button in Settings.", "Open Settings")
|
|
895
|
+
.then((selection) => {
|
|
896
|
+
if (selection === "Open Settings") {
|
|
897
|
+
vscode.commands.executeCommand("workbench.action.openSettings", "@ext:statusbar-quick-actions");
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
getDefaultOutputConfig() {
|
|
903
|
+
return {
|
|
904
|
+
enabled: true,
|
|
905
|
+
mode: "per-button",
|
|
906
|
+
format: "formatted",
|
|
907
|
+
clearOnRun: false,
|
|
908
|
+
showTimestamps: true,
|
|
909
|
+
preserveHistory: true,
|
|
910
|
+
maxLines: 1000,
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
getDefaultPerformanceConfig() {
|
|
914
|
+
return {
|
|
915
|
+
visibilityDebounceMs: 300,
|
|
916
|
+
enableVirtualization: false,
|
|
917
|
+
cacheResults: true,
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
setupEditorChangeListener() {
|
|
921
|
+
this.editorChangeListener = vscode.window.onDidChangeActiveTextEditor(() => {
|
|
922
|
+
this.buttonStates.forEach((buttonState, buttonId) => {
|
|
923
|
+
if (buttonState.config.visibility) {
|
|
924
|
+
const customDebounce = buttonState.config.visibility.debounceMs;
|
|
925
|
+
this.visibilityManager.checkVisibilityDebounced(buttonId, buttonState.config.visibility, customDebounce, (isVisible) => {
|
|
926
|
+
if (isVisible) {
|
|
927
|
+
buttonState.item.show();
|
|
928
|
+
}
|
|
929
|
+
else {
|
|
930
|
+
buttonState.item.hide();
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
});
|
|
936
|
+
this.disposables.push(this.editorChangeListener);
|
|
937
|
+
}
|
|
938
|
+
async managePresets() {
|
|
939
|
+
const items = [
|
|
940
|
+
{
|
|
941
|
+
label: "$(add) Create New Preset",
|
|
942
|
+
description: "Save current configuration as a preset",
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
label: "$(archive) Apply Preset",
|
|
946
|
+
description: "Load a saved preset",
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
label: "$(list-unordered) View All Presets",
|
|
950
|
+
description: "Browse and manage saved presets",
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
label: "$(export) Export Preset",
|
|
954
|
+
description: "Export a preset to a file",
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
label: "$(import) Import Preset",
|
|
958
|
+
description: "Import a preset from a file",
|
|
959
|
+
},
|
|
960
|
+
];
|
|
961
|
+
const selected = await vscode.window.showQuickPick(items, {
|
|
962
|
+
placeHolder: "Preset Management",
|
|
963
|
+
matchOnDescription: true,
|
|
964
|
+
});
|
|
965
|
+
if (!selected) {
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
switch (selected.label) {
|
|
969
|
+
case "$(add) Create New Preset":
|
|
970
|
+
await this.saveAsPreset();
|
|
971
|
+
break;
|
|
972
|
+
case "$(archive) Apply Preset":
|
|
973
|
+
await this.applyPresetCommand();
|
|
974
|
+
break;
|
|
975
|
+
case "$(list-unordered) View All Presets":
|
|
976
|
+
await this.viewAllPresets();
|
|
977
|
+
break;
|
|
978
|
+
case "$(export) Export Preset":
|
|
979
|
+
await this.exportPresetCommand();
|
|
980
|
+
break;
|
|
981
|
+
case "$(import) Import Preset":
|
|
982
|
+
await this.importPresetCommand();
|
|
983
|
+
break;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
async saveAsPreset() {
|
|
987
|
+
const name = await vscode.window.showInputBox({
|
|
988
|
+
prompt: "Enter preset name",
|
|
989
|
+
placeHolder: "e.g., My Development Setup",
|
|
990
|
+
validateInput: (value) => (value ? null : "Name is required"),
|
|
991
|
+
});
|
|
992
|
+
if (!name) {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
const description = await vscode.window.showInputBox({
|
|
996
|
+
prompt: "Enter preset description (optional)",
|
|
997
|
+
placeHolder: "e.g., Standard buttons for Node.js development",
|
|
998
|
+
});
|
|
999
|
+
try {
|
|
1000
|
+
const currentConfig = this.configManager.getConfig();
|
|
1001
|
+
await this.presetManager.createPresetFromConfig(name, description || "", currentConfig);
|
|
1002
|
+
vscode.window.showInformationMessage(`✅ Preset "${name}" created successfully!`);
|
|
1003
|
+
}
|
|
1004
|
+
catch (error) {
|
|
1005
|
+
vscode.window.showErrorMessage(`Failed to create preset: ${error}`);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
async applyPresetCommand() {
|
|
1009
|
+
const presets = this.presetManager.getAllPresets();
|
|
1010
|
+
if (presets.length === 0) {
|
|
1011
|
+
vscode.window.showInformationMessage("No presets available yet.");
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
const items = presets.map((preset) => ({
|
|
1015
|
+
label: preset.name,
|
|
1016
|
+
description: preset.description,
|
|
1017
|
+
detail: `${preset.buttons.length} buttons · Created ${preset.metadata?.created.toLocaleDateString()}`,
|
|
1018
|
+
preset,
|
|
1019
|
+
}));
|
|
1020
|
+
const selected = await vscode.window.showQuickPick(items, {
|
|
1021
|
+
placeHolder: "Select a preset to apply",
|
|
1022
|
+
matchOnDescription: true,
|
|
1023
|
+
matchOnDetail: true,
|
|
1024
|
+
});
|
|
1025
|
+
if (!selected) {
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
const modeItems = [
|
|
1029
|
+
{
|
|
1030
|
+
label: "Replace",
|
|
1031
|
+
description: "Replace all current buttons with preset buttons",
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
label: "Merge",
|
|
1035
|
+
description: "Merge preset buttons with current buttons (overwrite duplicates)",
|
|
1036
|
+
},
|
|
1037
|
+
{
|
|
1038
|
+
label: "Append",
|
|
1039
|
+
description: "Add preset buttons to current buttons",
|
|
1040
|
+
},
|
|
1041
|
+
];
|
|
1042
|
+
const modeSelected = await vscode.window.showQuickPick(modeItems, {
|
|
1043
|
+
placeHolder: "How should the preset be applied?",
|
|
1044
|
+
});
|
|
1045
|
+
if (!modeSelected) {
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
const mode = modeSelected.label.toLowerCase();
|
|
1049
|
+
const impact = this.configManager.getPresetImpact(selected.preset, mode);
|
|
1050
|
+
const confirm = await vscode.window.showWarningMessage(`Apply preset "${selected.preset.name}"?\n\nImpact:\n• ${impact.added} buttons added\n• ${impact.modified} buttons modified\n• ${impact.removed} buttons removed`, { modal: true }, "Yes, Apply", "No");
|
|
1051
|
+
if (confirm !== "Yes, Apply") {
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
try {
|
|
1055
|
+
await this.configManager.applyPreset(selected.preset, mode);
|
|
1056
|
+
vscode.window.showInformationMessage(`✅ Preset "${selected.preset.name}" applied successfully!`);
|
|
1057
|
+
}
|
|
1058
|
+
catch (error) {
|
|
1059
|
+
vscode.window.showErrorMessage(`Failed to apply preset: ${error}`);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
async viewAllPresets() {
|
|
1063
|
+
const presets = this.presetManager.getAllPresets();
|
|
1064
|
+
if (presets.length === 0) {
|
|
1065
|
+
vscode.window.showInformationMessage("No presets available yet.");
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
const items = presets.map((preset) => ({
|
|
1069
|
+
label: preset.name,
|
|
1070
|
+
description: `${preset.buttons.length} buttons`,
|
|
1071
|
+
detail: preset.description,
|
|
1072
|
+
buttons: [
|
|
1073
|
+
{
|
|
1074
|
+
iconPath: new vscode.ThemeIcon("edit"),
|
|
1075
|
+
tooltip: "Rename Preset",
|
|
1076
|
+
},
|
|
1077
|
+
{
|
|
1078
|
+
iconPath: new vscode.ThemeIcon("copy"),
|
|
1079
|
+
tooltip: "Duplicate Preset",
|
|
1080
|
+
},
|
|
1081
|
+
{
|
|
1082
|
+
iconPath: new vscode.ThemeIcon("trash"),
|
|
1083
|
+
tooltip: "Delete Preset",
|
|
1084
|
+
},
|
|
1085
|
+
],
|
|
1086
|
+
preset,
|
|
1087
|
+
}));
|
|
1088
|
+
const selected = await vscode.window.showQuickPick(items, {
|
|
1089
|
+
placeHolder: "Manage Presets",
|
|
1090
|
+
matchOnDescription: true,
|
|
1091
|
+
matchOnDetail: true,
|
|
1092
|
+
});
|
|
1093
|
+
if (selected) {
|
|
1094
|
+
await this.applyPresetCommand();
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
async exportPresetCommand() {
|
|
1098
|
+
const presets = this.presetManager.getAllPresets();
|
|
1099
|
+
if (presets.length === 0) {
|
|
1100
|
+
vscode.window.showInformationMessage("No presets available to export.");
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
const items = presets.map((preset) => ({
|
|
1104
|
+
label: preset.name,
|
|
1105
|
+
description: `${preset.buttons.length} buttons`,
|
|
1106
|
+
preset,
|
|
1107
|
+
}));
|
|
1108
|
+
const selected = await vscode.window.showQuickPick(items, {
|
|
1109
|
+
placeHolder: "Select a preset to export",
|
|
1110
|
+
});
|
|
1111
|
+
if (!selected) {
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
try {
|
|
1115
|
+
await this.presetManager.exportPreset(selected.preset);
|
|
1116
|
+
}
|
|
1117
|
+
catch (error) {
|
|
1118
|
+
vscode.window.showErrorMessage(`Failed to export preset: ${error}`);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
async importPresetCommand() {
|
|
1122
|
+
try {
|
|
1123
|
+
const preset = await this.presetManager.importPreset();
|
|
1124
|
+
if (preset) {
|
|
1125
|
+
const apply = await vscode.window.showInformationMessage(`Preset "${preset.name}" imported. Apply it now?`, "Yes", "No");
|
|
1126
|
+
if (apply === "Yes") {
|
|
1127
|
+
await this.configManager.applyPreset(preset, "replace");
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
catch (error) {
|
|
1132
|
+
vscode.window.showErrorMessage(`Failed to import preset: ${error}`);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
async refreshButtonLabel(buttonId) {
|
|
1136
|
+
const buttonState = this.buttonStates.get(buttonId);
|
|
1137
|
+
if (!buttonState || !buttonState.config.dynamicLabel) {
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
try {
|
|
1141
|
+
const newLabel = await this.dynamicLabelManager.evaluateLabel(buttonId, buttonState.config.dynamicLabel);
|
|
1142
|
+
if (buttonState.config.icon) {
|
|
1143
|
+
const iconText = this.getButtonDisplayText(buttonState.config);
|
|
1144
|
+
buttonState.item.text = `${iconText} ${newLabel}`;
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
buttonState.item.text = newLabel;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
catch (error) {
|
|
1151
|
+
console.error(`Failed to refresh label for ${buttonId}:`, error);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
exports.StatusBarQuickActionsExtension = StatusBarQuickActionsExtension;
|
|
1156
|
+
function activate(context) {
|
|
1157
|
+
console.log("Activating StatusBar Quick Actions extension...");
|
|
1158
|
+
const extension = new StatusBarQuickActionsExtension(context);
|
|
1159
|
+
context.subscriptions.push({
|
|
1160
|
+
dispose: () => extension.deactivate(),
|
|
1161
|
+
});
|
|
1162
|
+
extension.activate();
|
|
1163
|
+
}
|
|
1164
|
+
function deactivate() {
|
|
1165
|
+
console.log("Deactivating StatusBar Quick Actions extension...");
|
|
1166
|
+
}
|