statusbar-quick-actions 0.0.10

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.
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Notification management for StatusBar Quick Actions
3
+ */
4
+
5
+ import * as vscode from "vscode";
6
+ import { NotificationConfig, ExecutionResult } from "./types";
7
+
8
+ export class NotificationManager {
9
+ private config: NotificationConfig;
10
+
11
+ constructor(config: NotificationConfig) {
12
+ this.config = config;
13
+ }
14
+
15
+ /**
16
+ * Show success notification
17
+ */
18
+ public showSuccess(buttonText: string, result: ExecutionResult): void {
19
+ if (!this.config.showSuccess) {
20
+ return;
21
+ }
22
+
23
+ const message = this.getSuccessMessage(buttonText, result);
24
+
25
+ vscode.window
26
+ .showInformationMessage(message, "View Details")
27
+ .then((selection) => {
28
+ if (selection === "View Details") {
29
+ this.showDetails(result);
30
+ }
31
+ });
32
+ }
33
+
34
+ /**
35
+ * Show error notification
36
+ */
37
+ public showError(buttonText: string, error: string | Error): void {
38
+ if (!this.config.showError) {
39
+ return;
40
+ }
41
+
42
+ const message = `${buttonText}: ${error instanceof Error ? error.message : error}`;
43
+
44
+ vscode.window
45
+ .showErrorMessage(message, "View Details")
46
+ .then((selection) => {
47
+ if (selection === "View Details") {
48
+ const errorDetails =
49
+ error instanceof Error ? error.stack || error.message : error;
50
+ vscode.window.showErrorMessage(errorDetails, { modal: true });
51
+ }
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Show progress notification
57
+ */
58
+ public showProgress(buttonText: string): void {
59
+ if (!this.config.showProgress) {
60
+ return;
61
+ }
62
+
63
+ vscode.window.withProgress(
64
+ {
65
+ location: vscode.ProgressLocation.Notification,
66
+ title: `Executing: ${buttonText}`,
67
+ cancellable: false,
68
+ },
69
+ async (_progress) => {
70
+ // Progress will be updated by the caller
71
+ return new Promise<void>((resolve) => {
72
+ // This would be resolved when the command completes
73
+ resolve();
74
+ });
75
+ },
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Show warning notification
81
+ */
82
+ public showWarning(message: string, details?: string): void {
83
+ const fullMessage = details ? `${message}\n${details}` : message;
84
+ vscode.window.showWarningMessage(fullMessage);
85
+ }
86
+
87
+ /**
88
+ * Show info notification
89
+ */
90
+ public showInfo(message: string, details?: string): void {
91
+ const fullMessage = details ? `${message}\n${details}` : message;
92
+ vscode.window.showInformationMessage(fullMessage);
93
+ }
94
+
95
+ /**
96
+ * Show notification with custom actions
97
+ */
98
+ public showCustomNotification(
99
+ type: "info" | "warning" | "error",
100
+ message: string,
101
+ actions: string[],
102
+ ): Thenable<string | undefined> {
103
+ switch (type) {
104
+ case "error":
105
+ return vscode.window.showErrorMessage(message, ...actions);
106
+ case "warning":
107
+ return vscode.window.showWarningMessage(message, ...actions);
108
+ default:
109
+ return vscode.window.showInformationMessage(message, ...actions);
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Update configuration
115
+ */
116
+ public updateConfig(config: NotificationConfig): void {
117
+ this.config = config;
118
+ }
119
+
120
+ /**
121
+ * Get success message
122
+ */
123
+ private getSuccessMessage(
124
+ buttonText: string,
125
+ result: ExecutionResult,
126
+ ): string {
127
+ const duration = result.duration ? ` in ${result.duration}ms` : "";
128
+ const output =
129
+ this.config.includeOutput && result.stdout
130
+ ? `: ${result.stdout.substring(0, 50)}${result.stdout.length > 50 ? "..." : ""}`
131
+ : "";
132
+
133
+ return `✅ ${buttonText} completed successfully${duration}${output}`;
134
+ }
135
+
136
+ /**
137
+ * Show execution details
138
+ */
139
+ private showDetails(result: ExecutionResult): void {
140
+ const output = `Command Output:\n${result.stdout}\n\nErrors:\n${result.stderr}`;
141
+ vscode.window.showInformationMessage(output, { modal: true });
142
+ }
143
+
144
+ /**
145
+ * Show progress update
146
+ */
147
+ public updateProgress(_increment: number, _message?: string): void {
148
+ // This would be called to update an existing progress notification
149
+ // Implementation depends on how progress is tracked
150
+ }
151
+
152
+ /**
153
+ * Dismiss notifications
154
+ */
155
+ public dismissAll(): void {
156
+ // VSCode doesn't provide a way to dismiss specific notifications
157
+ // This would be a no-op for now
158
+ }
159
+
160
+ /**
161
+ * Check if notifications are enabled
162
+ */
163
+ public areNotificationsEnabled(): boolean {
164
+ return (
165
+ this.config.showSuccess ||
166
+ this.config.showError ||
167
+ this.config.showProgress
168
+ );
169
+ }
170
+
171
+ /**
172
+ * Get notification statistics
173
+ */
174
+ public getStats(): {
175
+ successEnabled: boolean;
176
+ errorEnabled: boolean;
177
+ progressEnabled: boolean;
178
+ position: string;
179
+ duration: number;
180
+ } {
181
+ return {
182
+ successEnabled: this.config.showSuccess,
183
+ errorEnabled: this.config.showError,
184
+ progressEnabled: this.config.showProgress,
185
+ position: this.config.position,
186
+ duration: this.config.duration,
187
+ };
188
+ }
189
+ }
@@ -0,0 +1,403 @@
1
+ /**
2
+ * Output panel management for StatusBar Quick Actions
3
+ */
4
+
5
+ import * as vscode from "vscode";
6
+ import { OutputPanelConfig } from "./types";
7
+
8
+ /**
9
+ * Manages output panels for command execution with streaming support
10
+ */
11
+ export class OutputPanelManager {
12
+ private panels: Map<string, vscode.OutputChannel>;
13
+ private sharedPanel: vscode.OutputChannel | null;
14
+ private config: OutputPanelConfig;
15
+ private outputHistory: Map<string, string[]>;
16
+
17
+ constructor(config: OutputPanelConfig) {
18
+ this.config = config;
19
+ this.panels = new Map();
20
+ this.sharedPanel = null;
21
+ this.outputHistory = new Map();
22
+ }
23
+
24
+ /**
25
+ * Get or create an output panel for a button
26
+ */
27
+ public getOrCreatePanel(
28
+ buttonId: string,
29
+ buttonName: string,
30
+ ): vscode.OutputChannel {
31
+ if (this.config.mode === "shared") {
32
+ return this.getSharedPanel();
33
+ }
34
+
35
+ // Per-button mode
36
+ if (!this.panels.has(buttonId)) {
37
+ const panel = vscode.window.createOutputChannel(
38
+ `StatusBar: ${buttonName}`,
39
+ );
40
+ this.panels.set(buttonId, panel);
41
+ }
42
+
43
+ return this.panels.get(buttonId)!;
44
+ }
45
+
46
+ /**
47
+ * Get or create the shared output panel
48
+ */
49
+ private getSharedPanel(): vscode.OutputChannel {
50
+ if (!this.sharedPanel) {
51
+ this.sharedPanel = vscode.window.createOutputChannel(
52
+ "StatusBar Quick Actions",
53
+ );
54
+ }
55
+ return this.sharedPanel;
56
+ }
57
+
58
+ /**
59
+ * Append output to a panel
60
+ */
61
+ public appendOutput(
62
+ buttonId: string,
63
+ data: string,
64
+ type: "stdout" | "stderr",
65
+ ): void {
66
+ const panel =
67
+ this.config.mode === "shared"
68
+ ? this.getSharedPanel()
69
+ : this.panels.get(buttonId);
70
+
71
+ if (!panel) {
72
+ console.warn(`Output panel not found for button ${buttonId}`);
73
+ return;
74
+ }
75
+
76
+ const formattedOutput = this.formatOutput(data, type);
77
+ panel.append(formattedOutput);
78
+
79
+ // Add to history if preservation is enabled
80
+ if (this.config.preserveHistory) {
81
+ this.addToHistory(buttonId, formattedOutput);
82
+ }
83
+
84
+ // Trim panel if max lines exceeded
85
+ this.trimPanelIfNeeded(buttonId);
86
+ }
87
+
88
+ /**
89
+ * Append a line to the output panel
90
+ */
91
+ public appendLine(
92
+ buttonId: string,
93
+ data: string,
94
+ type: "stdout" | "stderr",
95
+ ): void {
96
+ const panel =
97
+ this.config.mode === "shared"
98
+ ? this.getSharedPanel()
99
+ : this.panels.get(buttonId);
100
+
101
+ if (!panel) {
102
+ console.warn(`Output panel not found for button ${buttonId}`);
103
+ return;
104
+ }
105
+
106
+ const formattedOutput = this.formatOutput(data, type);
107
+ panel.appendLine(formattedOutput);
108
+
109
+ // Add to history
110
+ if (this.config.preserveHistory) {
111
+ this.addToHistory(buttonId, formattedOutput + "\n");
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Clear a specific panel
117
+ */
118
+ public clearPanel(buttonId: string): void {
119
+ const panel =
120
+ this.config.mode === "shared"
121
+ ? this.getSharedPanel()
122
+ : this.panels.get(buttonId);
123
+
124
+ if (panel) {
125
+ panel.clear();
126
+ }
127
+
128
+ // Clear history
129
+ this.outputHistory.delete(buttonId);
130
+ }
131
+
132
+ /**
133
+ * Show a panel
134
+ */
135
+ public showPanel(buttonId: string, preserveFocus = false): void {
136
+ const panel =
137
+ this.config.mode === "shared"
138
+ ? this.getSharedPanel()
139
+ : this.panels.get(buttonId);
140
+
141
+ if (panel) {
142
+ panel.show(preserveFocus);
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Hide a panel
148
+ */
149
+ public hidePanel(buttonId: string): void {
150
+ const panel =
151
+ this.config.mode === "shared"
152
+ ? this.getSharedPanel()
153
+ : this.panels.get(buttonId);
154
+
155
+ if (panel) {
156
+ panel.hide();
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Format output based on configuration
162
+ */
163
+ private formatOutput(data: string, type: "stdout" | "stderr"): string {
164
+ let output = data;
165
+
166
+ // Handle formatting mode
167
+ switch (this.config.format) {
168
+ case "raw":
169
+ // Strip ANSI codes
170
+ output = this.stripAnsiCodes(data);
171
+ break;
172
+
173
+ case "formatted":
174
+ // Strip ANSI codes and add timestamp
175
+ output = this.stripAnsiCodes(data);
176
+ if (this.config.showTimestamps) {
177
+ const timestamp = this.getTimestamp();
178
+ // Only add timestamp to non-empty lines
179
+ if (output.trim()) {
180
+ output = `[${timestamp}] ${output}`;
181
+ }
182
+ }
183
+ break;
184
+
185
+ case "ansi":
186
+ // Preserve ANSI codes
187
+ if (this.config.showTimestamps && output.trim()) {
188
+ const timestamp = this.getTimestamp();
189
+ output = `[${timestamp}] ${output}`;
190
+ }
191
+ break;
192
+ }
193
+
194
+ // Add error prefix for stderr
195
+ if (type === "stderr" && output.trim()) {
196
+ output = `[ERROR] ${output}`;
197
+ }
198
+
199
+ return output;
200
+ }
201
+
202
+ /**
203
+ * Strip ANSI color codes from text
204
+ */
205
+ private stripAnsiCodes(text: string): string {
206
+ // eslint-disable-next-line no-control-regex
207
+ return text.replace(/\x1b\[[0-9;]*m/g, "");
208
+ }
209
+
210
+ /**
211
+ * Get formatted timestamp
212
+ */
213
+ private getTimestamp(): string {
214
+ const now = new Date();
215
+ const hours = String(now.getHours()).padStart(2, "0");
216
+ const minutes = String(now.getMinutes()).padStart(2, "0");
217
+ const seconds = String(now.getSeconds()).padStart(2, "0");
218
+ return `${hours}:${minutes}:${seconds}`;
219
+ }
220
+
221
+ /**
222
+ * Add output to history
223
+ */
224
+ private addToHistory(buttonId: string, output: string): void {
225
+ if (!this.outputHistory.has(buttonId)) {
226
+ this.outputHistory.set(buttonId, []);
227
+ }
228
+
229
+ const history = this.outputHistory.get(buttonId)!;
230
+ history.push(output);
231
+
232
+ // Trim history if needed
233
+ if (history.length > this.config.maxLines) {
234
+ history.splice(0, history.length - this.config.maxLines);
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Trim panel content if max lines exceeded
240
+ */
241
+ private trimPanelIfNeeded(buttonId: string): void {
242
+ // Note: VSCode OutputChannel doesn't provide direct line count access
243
+ // This is a placeholder for future optimization if needed
244
+ // The history tracking serves as a workaround for now
245
+
246
+ // Check if history exceeds max lines and trim if needed
247
+ const history = this.outputHistory.get(buttonId);
248
+ if (history && history.length > this.config.maxLines) {
249
+ history.splice(0, history.length - this.config.maxLines);
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Get output history for a button
255
+ */
256
+ public getHistory(buttonId: string): string[] {
257
+ return this.outputHistory.get(buttonId) || [];
258
+ }
259
+
260
+ /**
261
+ * Clear history for a button
262
+ */
263
+ public clearHistory(buttonId: string): void {
264
+ this.outputHistory.delete(buttonId);
265
+ }
266
+
267
+ /**
268
+ * Clear all history
269
+ */
270
+ public clearAllHistory(): void {
271
+ this.outputHistory.clear();
272
+ }
273
+
274
+ /**
275
+ * Update configuration
276
+ */
277
+ public updateConfig(config: OutputPanelConfig): void {
278
+ this.config = config;
279
+
280
+ // If switching modes, clean up existing panels
281
+ if (config.mode === "shared" && this.panels.size > 0) {
282
+ // Dispose per-button panels
283
+ this.panels.forEach((panel) => panel.dispose());
284
+ this.panels.clear();
285
+ } else if (config.mode === "per-button" && this.sharedPanel) {
286
+ // Dispose shared panel
287
+ this.sharedPanel.dispose();
288
+ this.sharedPanel = null;
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Get current configuration
294
+ */
295
+ public getConfig(): OutputPanelConfig {
296
+ return { ...this.config };
297
+ }
298
+
299
+ /**
300
+ * Check if output panel is enabled
301
+ */
302
+ public isEnabled(): boolean {
303
+ return this.config.enabled;
304
+ }
305
+
306
+ /**
307
+ * Get panel for a button (if exists)
308
+ */
309
+ public getPanel(buttonId: string): vscode.OutputChannel | null {
310
+ if (this.config.mode === "shared") {
311
+ return this.sharedPanel;
312
+ }
313
+ return this.panels.get(buttonId) || null;
314
+ }
315
+
316
+ /**
317
+ * Check if a panel exists for a button
318
+ */
319
+ public hasPanel(buttonId: string): boolean {
320
+ if (this.config.mode === "shared") {
321
+ return this.sharedPanel !== null;
322
+ }
323
+ return this.panels.has(buttonId);
324
+ }
325
+
326
+ /**
327
+ * Get all panel IDs
328
+ */
329
+ public getPanelIds(): string[] {
330
+ return Array.from(this.panels.keys());
331
+ }
332
+
333
+ /**
334
+ * Dispose of a specific panel
335
+ */
336
+ public disposePanel(buttonId: string): void {
337
+ const panel = this.panels.get(buttonId);
338
+ if (panel) {
339
+ panel.dispose();
340
+ this.panels.delete(buttonId);
341
+ }
342
+
343
+ // Clear history
344
+ this.outputHistory.delete(buttonId);
345
+ }
346
+
347
+ /**
348
+ * Dispose of all panels
349
+ */
350
+ public dispose(): void {
351
+ // Dispose all per-button panels
352
+ this.panels.forEach((panel) => panel.dispose());
353
+ this.panels.clear();
354
+
355
+ // Dispose shared panel
356
+ if (this.sharedPanel) {
357
+ this.sharedPanel.dispose();
358
+ this.sharedPanel = null;
359
+ }
360
+
361
+ // Clear all history
362
+ this.outputHistory.clear();
363
+ }
364
+
365
+ /**
366
+ * Get statistics about output panels
367
+ */
368
+ public getStatistics(): {
369
+ panelCount: number;
370
+ mode: string;
371
+ totalHistoryLines: number;
372
+ } {
373
+ let totalHistoryLines = 0;
374
+ this.outputHistory.forEach((history) => {
375
+ totalHistoryLines += history.length;
376
+ });
377
+
378
+ return {
379
+ panelCount: this.config.mode === "shared" ? 1 : this.panels.size,
380
+ mode: this.config.mode,
381
+ totalHistoryLines,
382
+ };
383
+ }
384
+
385
+ /**
386
+ * Export history to string
387
+ */
388
+ public exportHistory(buttonId: string): string {
389
+ const history = this.getHistory(buttonId);
390
+ return history.join("");
391
+ }
392
+
393
+ /**
394
+ * Export all history to string
395
+ */
396
+ public exportAllHistory(): Record<string, string> {
397
+ const result: Record<string, string> = {};
398
+ this.outputHistory.forEach((history, buttonId) => {
399
+ result[buttonId] = history.join("");
400
+ });
401
+ return result;
402
+ }
403
+ }