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.
- package/.github/FUNDING.yml +3 -0
- package/.vscodeignore +11 -0
- package/CLAUDE.md +230 -0
- package/LICENSE +21 -0
- package/README.md +529 -0
- package/assets/icon.png +0 -0
- package/bun.lock +908 -0
- package/docs/PERFORMANCE_OPTIMIZATIONS.md +240 -0
- package/docs/PRESET_AND_DYNAMIC_LABELS.md +536 -0
- package/docs/SAMPLE-CONFIGURATIONS.md +973 -0
- package/eslint.config.mjs +41 -0
- package/package.json +605 -0
- package/src/config-cli.ts +1287 -0
- package/src/configuration.ts +530 -0
- package/src/dynamic-label.ts +360 -0
- package/src/executor.ts +406 -0
- package/src/extension.ts +1754 -0
- package/src/history.ts +175 -0
- package/src/material-icons.ts +388 -0
- package/src/notifications.ts +189 -0
- package/src/output-panel.ts +403 -0
- package/src/preset-manager.ts +406 -0
- package/src/theme.ts +318 -0
- package/src/types.ts +368 -0
- package/src/utils/debounce.ts +91 -0
- package/src/visibility.ts +283 -0
- package/tsconfig.dev.json +10 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preset Management for StatusBar Quick Actions
|
|
3
|
+
* Handles preset storage, CRUD operations, and application
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as vscode from "vscode";
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import { PresetConfig, PresetApplicationMode, ExtensionConfig } from "./types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Preset Manager
|
|
12
|
+
* Manages configuration presets for quick button setup
|
|
13
|
+
*/
|
|
14
|
+
export class PresetManager {
|
|
15
|
+
private static readonly PRESET_STORAGE_KEY = "statusbarQuickActions.presets";
|
|
16
|
+
private context: vscode.ExtensionContext | null = null;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Initialize the preset manager
|
|
20
|
+
*/
|
|
21
|
+
public initialize(context: vscode.ExtensionContext): void {
|
|
22
|
+
this.context = context;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get all presets
|
|
27
|
+
*/
|
|
28
|
+
public getAllPresets(): PresetConfig[] {
|
|
29
|
+
if (!this.context) {
|
|
30
|
+
console.error("PresetManager not initialized");
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const presetsJson = this.context.globalState.get<string>(
|
|
36
|
+
PresetManager.PRESET_STORAGE_KEY,
|
|
37
|
+
"[]",
|
|
38
|
+
);
|
|
39
|
+
const presets = JSON.parse(presetsJson) as PresetConfig[];
|
|
40
|
+
|
|
41
|
+
// Convert date strings back to Date objects
|
|
42
|
+
return presets.map((preset) => ({
|
|
43
|
+
...preset,
|
|
44
|
+
metadata: preset.metadata
|
|
45
|
+
? {
|
|
46
|
+
...preset.metadata,
|
|
47
|
+
created: new Date(preset.metadata.created),
|
|
48
|
+
modified: new Date(preset.metadata.modified),
|
|
49
|
+
}
|
|
50
|
+
: undefined,
|
|
51
|
+
}));
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error("Failed to load presets:", error);
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get a preset by ID
|
|
60
|
+
*/
|
|
61
|
+
public getPreset(presetId: string): PresetConfig | null {
|
|
62
|
+
const presets = this.getAllPresets();
|
|
63
|
+
return presets.find((p) => p.id === presetId) || null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Save a preset
|
|
68
|
+
*/
|
|
69
|
+
public async savePreset(preset: PresetConfig): Promise<void> {
|
|
70
|
+
if (!this.context) {
|
|
71
|
+
throw new Error("PresetManager not initialized");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const presets = this.getAllPresets();
|
|
76
|
+
const existingIndex = presets.findIndex((p) => p.id === preset.id);
|
|
77
|
+
|
|
78
|
+
const now = new Date();
|
|
79
|
+
const presetWithMetadata: PresetConfig = {
|
|
80
|
+
...preset,
|
|
81
|
+
metadata: {
|
|
82
|
+
created: preset.metadata?.created || now,
|
|
83
|
+
modified: now,
|
|
84
|
+
author: preset.metadata?.author,
|
|
85
|
+
tags: preset.metadata?.tags,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
if (existingIndex >= 0) {
|
|
90
|
+
// Update existing preset
|
|
91
|
+
presets[existingIndex] = presetWithMetadata;
|
|
92
|
+
} else {
|
|
93
|
+
// Add new preset
|
|
94
|
+
presets.push(presetWithMetadata);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await this.context.globalState.update(
|
|
98
|
+
PresetManager.PRESET_STORAGE_KEY,
|
|
99
|
+
JSON.stringify(presets, null, 2),
|
|
100
|
+
);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
const errorMessage =
|
|
103
|
+
error instanceof Error ? error.message : String(error);
|
|
104
|
+
throw new Error(`Failed to save preset: ${errorMessage}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Delete a preset
|
|
110
|
+
*/
|
|
111
|
+
public async deletePreset(presetId: string): Promise<void> {
|
|
112
|
+
if (!this.context) {
|
|
113
|
+
throw new Error("PresetManager not initialized");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const presets = this.getAllPresets();
|
|
118
|
+
const filteredPresets = presets.filter((p) => p.id !== presetId);
|
|
119
|
+
|
|
120
|
+
if (filteredPresets.length === presets.length) {
|
|
121
|
+
throw new Error(`Preset with ID '${presetId}' not found`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
await this.context.globalState.update(
|
|
125
|
+
PresetManager.PRESET_STORAGE_KEY,
|
|
126
|
+
JSON.stringify(filteredPresets, null, 2),
|
|
127
|
+
);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
const errorMessage =
|
|
130
|
+
error instanceof Error ? error.message : String(error);
|
|
131
|
+
throw new Error(`Failed to delete preset: ${errorMessage}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Create a preset from current configuration
|
|
137
|
+
*/
|
|
138
|
+
public async createPresetFromConfig(
|
|
139
|
+
name: string,
|
|
140
|
+
description: string,
|
|
141
|
+
currentConfig: ExtensionConfig,
|
|
142
|
+
tags?: string[],
|
|
143
|
+
): Promise<PresetConfig> {
|
|
144
|
+
const preset: PresetConfig = {
|
|
145
|
+
id: `preset_${Date.now()}`,
|
|
146
|
+
name,
|
|
147
|
+
description,
|
|
148
|
+
buttons: currentConfig.buttons,
|
|
149
|
+
theme: currentConfig.theme,
|
|
150
|
+
metadata: {
|
|
151
|
+
created: new Date(),
|
|
152
|
+
modified: new Date(),
|
|
153
|
+
tags,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
await this.savePreset(preset);
|
|
158
|
+
return preset;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Apply a preset to current configuration
|
|
163
|
+
*/
|
|
164
|
+
public applyPreset(
|
|
165
|
+
preset: PresetConfig,
|
|
166
|
+
currentConfig: ExtensionConfig,
|
|
167
|
+
mode: PresetApplicationMode = "replace",
|
|
168
|
+
): ExtensionConfig {
|
|
169
|
+
let newConfig: ExtensionConfig;
|
|
170
|
+
|
|
171
|
+
switch (mode) {
|
|
172
|
+
case "replace":
|
|
173
|
+
// Replace all buttons with preset buttons
|
|
174
|
+
newConfig = {
|
|
175
|
+
...currentConfig,
|
|
176
|
+
buttons: [...preset.buttons],
|
|
177
|
+
};
|
|
178
|
+
break;
|
|
179
|
+
|
|
180
|
+
case "merge": {
|
|
181
|
+
// Merge preset buttons, overwriting buttons with same ID
|
|
182
|
+
const mergedButtons = [...currentConfig.buttons];
|
|
183
|
+
preset.buttons.forEach((presetButton) => {
|
|
184
|
+
const existingIndex = mergedButtons.findIndex(
|
|
185
|
+
(b) => b.id === presetButton.id,
|
|
186
|
+
);
|
|
187
|
+
if (existingIndex >= 0) {
|
|
188
|
+
mergedButtons[existingIndex] = presetButton;
|
|
189
|
+
} else {
|
|
190
|
+
mergedButtons.push(presetButton);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
newConfig = {
|
|
194
|
+
...currentConfig,
|
|
195
|
+
buttons: mergedButtons,
|
|
196
|
+
};
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
case "append": {
|
|
201
|
+
// Append preset buttons to existing buttons
|
|
202
|
+
const appendedButtons = [...currentConfig.buttons, ...preset.buttons];
|
|
203
|
+
newConfig = {
|
|
204
|
+
...currentConfig,
|
|
205
|
+
buttons: appendedButtons,
|
|
206
|
+
};
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
default:
|
|
211
|
+
throw new Error(`Unknown preset application mode: ${mode}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Apply theme if present in preset
|
|
215
|
+
if (preset.theme) {
|
|
216
|
+
newConfig.theme = preset.theme;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return newConfig;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Export preset to JSON file
|
|
224
|
+
*/
|
|
225
|
+
public async exportPreset(preset: PresetConfig): Promise<void> {
|
|
226
|
+
const uri = await vscode.window.showSaveDialog({
|
|
227
|
+
filters: { JSON: ["json"] },
|
|
228
|
+
defaultUri: vscode.Uri.file(`${preset.name}.preset.json`),
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (!uri) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
fs.writeFileSync(uri.fsPath, JSON.stringify(preset, null, 2));
|
|
237
|
+
vscode.window.showInformationMessage(
|
|
238
|
+
`✅ Preset "${preset.name}" exported to ${uri.fsPath}`,
|
|
239
|
+
);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
const errorMessage =
|
|
242
|
+
error instanceof Error ? error.message : String(error);
|
|
243
|
+
throw new Error(`Failed to export preset: ${errorMessage}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Import preset from JSON file
|
|
249
|
+
*/
|
|
250
|
+
public async importPreset(): Promise<PresetConfig | null> {
|
|
251
|
+
const uri = await vscode.window.showOpenDialog({
|
|
252
|
+
filters: { JSON: ["json"] },
|
|
253
|
+
canSelectMany: false,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
if (!uri || uri.length === 0) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const content = fs.readFileSync(uri[0].fsPath, "utf8");
|
|
262
|
+
const preset = JSON.parse(content) as PresetConfig;
|
|
263
|
+
|
|
264
|
+
// Validate preset structure
|
|
265
|
+
if (!preset.id || !preset.name || !preset.buttons) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
"Invalid preset file: missing required fields (id, name, buttons)",
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Generate new ID to avoid conflicts
|
|
272
|
+
preset.id = `preset_${Date.now()}`;
|
|
273
|
+
preset.metadata = {
|
|
274
|
+
created: new Date(),
|
|
275
|
+
modified: new Date(),
|
|
276
|
+
author: preset.metadata?.author,
|
|
277
|
+
tags: preset.metadata?.tags,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
await this.savePreset(preset);
|
|
281
|
+
vscode.window.showInformationMessage(
|
|
282
|
+
`✅ Preset "${preset.name}" imported successfully`,
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
return preset;
|
|
286
|
+
} catch (error) {
|
|
287
|
+
const errorMessage =
|
|
288
|
+
error instanceof Error ? error.message : String(error);
|
|
289
|
+
vscode.window.showErrorMessage(
|
|
290
|
+
`Failed to import preset: ${errorMessage}`,
|
|
291
|
+
);
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Duplicate a preset
|
|
298
|
+
*/
|
|
299
|
+
public async duplicatePreset(presetId: string): Promise<PresetConfig | null> {
|
|
300
|
+
const original = this.getPreset(presetId);
|
|
301
|
+
if (!original) {
|
|
302
|
+
throw new Error(`Preset with ID '${presetId}' not found`);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const duplicate: PresetConfig = {
|
|
306
|
+
...original,
|
|
307
|
+
id: `preset_${Date.now()}`,
|
|
308
|
+
name: `${original.name} (Copy)`,
|
|
309
|
+
metadata: {
|
|
310
|
+
created: new Date(),
|
|
311
|
+
modified: new Date(),
|
|
312
|
+
author: original.metadata?.author,
|
|
313
|
+
tags: original.metadata?.tags,
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
await this.savePreset(duplicate);
|
|
318
|
+
return duplicate;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Search presets by name or tags
|
|
323
|
+
*/
|
|
324
|
+
public searchPresets(query: string): PresetConfig[] {
|
|
325
|
+
const presets = this.getAllPresets();
|
|
326
|
+
const lowerQuery = query.toLowerCase();
|
|
327
|
+
|
|
328
|
+
return presets.filter(
|
|
329
|
+
(preset) =>
|
|
330
|
+
preset.name.toLowerCase().includes(lowerQuery) ||
|
|
331
|
+
preset.description?.toLowerCase().includes(lowerQuery) ||
|
|
332
|
+
preset.metadata?.tags?.some((tag) =>
|
|
333
|
+
tag.toLowerCase().includes(lowerQuery),
|
|
334
|
+
),
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get preset count
|
|
340
|
+
*/
|
|
341
|
+
public getPresetCount(): number {
|
|
342
|
+
return this.getAllPresets().length;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Clear all presets (with confirmation)
|
|
347
|
+
*/
|
|
348
|
+
public async clearAllPresets(): Promise<void> {
|
|
349
|
+
if (!this.context) {
|
|
350
|
+
throw new Error("PresetManager not initialized");
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
await this.context.globalState.update(
|
|
354
|
+
PresetManager.PRESET_STORAGE_KEY,
|
|
355
|
+
"[]",
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Validate preset configuration
|
|
361
|
+
*/
|
|
362
|
+
public validatePreset(preset: PresetConfig): {
|
|
363
|
+
isValid: boolean;
|
|
364
|
+
errors: string[];
|
|
365
|
+
} {
|
|
366
|
+
const errors: string[] = [];
|
|
367
|
+
|
|
368
|
+
if (!preset.id || typeof preset.id !== "string") {
|
|
369
|
+
errors.push("Preset ID is required and must be a string");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (!preset.name || typeof preset.name !== "string") {
|
|
373
|
+
errors.push("Preset name is required and must be a string");
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (!Array.isArray(preset.buttons)) {
|
|
377
|
+
errors.push("Preset buttons must be an array");
|
|
378
|
+
} else {
|
|
379
|
+
preset.buttons.forEach((button, index) => {
|
|
380
|
+
if (!button.id || typeof button.id !== "string") {
|
|
381
|
+
errors.push(`Button ${index}: ID is required and must be a string`);
|
|
382
|
+
}
|
|
383
|
+
if (!button.text && !button.icon) {
|
|
384
|
+
errors.push(`Button ${index}: Either text or icon is required`);
|
|
385
|
+
}
|
|
386
|
+
if (!button.command || typeof button.command !== "object") {
|
|
387
|
+
errors.push(
|
|
388
|
+
`Button ${index}: Command is required and must be an object`,
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
isValid: errors.length === 0,
|
|
396
|
+
errors,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Dispose of resources
|
|
402
|
+
*/
|
|
403
|
+
public dispose(): void {
|
|
404
|
+
// Cleanup if needed
|
|
405
|
+
}
|
|
406
|
+
}
|
package/src/theme.ts
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme management for StatusBar Quick Actions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as vscode from "vscode";
|
|
6
|
+
import { ThemeConfig } from "./types";
|
|
7
|
+
|
|
8
|
+
export class ThemeManager {
|
|
9
|
+
private context: vscode.ExtensionContext | null = null;
|
|
10
|
+
private currentTheme: ThemeConfig | null = null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Initialize the theme manager
|
|
14
|
+
*/
|
|
15
|
+
public async initialize(context: vscode.ExtensionContext): Promise<void> {
|
|
16
|
+
this.context = context;
|
|
17
|
+
await this.loadTheme();
|
|
18
|
+
this.setupThemeWatching();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load theme from configuration
|
|
23
|
+
*/
|
|
24
|
+
private async loadTheme(): Promise<void> {
|
|
25
|
+
try {
|
|
26
|
+
const config = vscode.workspace.getConfiguration(
|
|
27
|
+
"statusbarQuickActions.settings",
|
|
28
|
+
);
|
|
29
|
+
const themeConfig = config.get<ThemeConfig>("theme");
|
|
30
|
+
|
|
31
|
+
if (themeConfig) {
|
|
32
|
+
this.currentTheme = themeConfig as ThemeConfig;
|
|
33
|
+
} else {
|
|
34
|
+
// Use default theme if not configured
|
|
35
|
+
this.currentTheme = this.getDefaultTheme();
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error("Error loading theme:", error);
|
|
39
|
+
this.currentTheme = this.getDefaultTheme();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Apply theme to a statusbar item
|
|
45
|
+
*/
|
|
46
|
+
public applyThemeToStatusBarItem(item: vscode.StatusBarItem): void {
|
|
47
|
+
if (!this.currentTheme) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const colors = this.getCurrentThemeColors();
|
|
53
|
+
if (colors.foreground) {
|
|
54
|
+
item.color = new vscode.ThemeColor(colors.foreground);
|
|
55
|
+
}
|
|
56
|
+
if (colors.background) {
|
|
57
|
+
item.backgroundColor = new vscode.ThemeColor(colors.background);
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error("Error applying theme to statusbar item:", error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get current theme colors
|
|
66
|
+
*/
|
|
67
|
+
public getCurrentThemeColors(): { foreground?: string; background?: string } {
|
|
68
|
+
if (!this.currentTheme) {
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const themeType = this.getCurrentThemeType();
|
|
74
|
+
const theme = this.currentTheme[themeType];
|
|
75
|
+
if (!theme || typeof theme !== "object") {
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
foreground: theme.button?.foreground,
|
|
81
|
+
background: theme.button?.background,
|
|
82
|
+
};
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error("Error getting current theme colors:", error);
|
|
85
|
+
return {};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get current theme type (dark/light/highContrast)
|
|
91
|
+
*/
|
|
92
|
+
private getCurrentThemeType(): "dark" | "light" | "highContrast" {
|
|
93
|
+
if (!this.currentTheme) {
|
|
94
|
+
return "dark";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const mode = this.currentTheme.mode;
|
|
98
|
+
if (mode === "auto") {
|
|
99
|
+
// Detect from VSCode theme
|
|
100
|
+
const colorTheme = vscode.workspace
|
|
101
|
+
.getConfiguration()
|
|
102
|
+
.get("workbench.colorTheme");
|
|
103
|
+
const isDark =
|
|
104
|
+
colorTheme?.toString().toLowerCase().includes("dark") ||
|
|
105
|
+
colorTheme?.toString().toLowerCase().includes("black") ||
|
|
106
|
+
colorTheme?.toString().toLowerCase().includes("dimmed");
|
|
107
|
+
|
|
108
|
+
// Check for high contrast
|
|
109
|
+
const isHighContrast =
|
|
110
|
+
vscode.workspace
|
|
111
|
+
.getConfiguration()
|
|
112
|
+
.get("accessibility.verbosityNotifications") === "verbose";
|
|
113
|
+
|
|
114
|
+
return isHighContrast ? "highContrast" : isDark ? "dark" : "light";
|
|
115
|
+
} else if (mode === "highContrast") {
|
|
116
|
+
return "highContrast";
|
|
117
|
+
} else if (mode === "dark") {
|
|
118
|
+
return "dark";
|
|
119
|
+
} else {
|
|
120
|
+
return "light";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Set up theme change watching
|
|
126
|
+
*/
|
|
127
|
+
private setupThemeWatching(): void {
|
|
128
|
+
if (!this.context) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Watch for theme changes
|
|
133
|
+
const disposable = vscode.workspace.onDidChangeConfiguration((event) => {
|
|
134
|
+
if (
|
|
135
|
+
event.affectsConfiguration("workbench.colorTheme") ||
|
|
136
|
+
event.affectsConfiguration("accessibility")
|
|
137
|
+
) {
|
|
138
|
+
this.updateTheme();
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
this.context.subscriptions.push(disposable);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Update current theme
|
|
147
|
+
*/
|
|
148
|
+
private updateTheme(): void {
|
|
149
|
+
// Reload theme from configuration
|
|
150
|
+
this.loadTheme();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get theme for executing state
|
|
155
|
+
*/
|
|
156
|
+
public getExecutingThemeColors(): {
|
|
157
|
+
foreground?: string;
|
|
158
|
+
background?: string;
|
|
159
|
+
} {
|
|
160
|
+
if (!this.currentTheme) {
|
|
161
|
+
return {};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const themeType = this.getCurrentThemeType();
|
|
166
|
+
const theme = this.currentTheme[themeType];
|
|
167
|
+
if (!theme || typeof theme !== "object") {
|
|
168
|
+
return {};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
foreground: theme.executing?.foreground,
|
|
173
|
+
background: theme.executing?.background,
|
|
174
|
+
};
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error("Error getting executing theme colors:", error);
|
|
177
|
+
return {};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get theme for error state
|
|
183
|
+
*/
|
|
184
|
+
public getErrorThemeColors(): { foreground?: string; background?: string } {
|
|
185
|
+
if (!this.currentTheme) {
|
|
186
|
+
return {};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const themeType = this.getCurrentThemeType();
|
|
191
|
+
const theme = this.currentTheme[themeType];
|
|
192
|
+
if (!theme || typeof theme !== "object") {
|
|
193
|
+
return {};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
foreground: theme.error?.foreground,
|
|
198
|
+
background: theme.error?.background,
|
|
199
|
+
};
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.error("Error getting error theme colors:", error);
|
|
202
|
+
return {};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Export theme configuration
|
|
208
|
+
*/
|
|
209
|
+
public exportTheme(): ThemeConfig | null {
|
|
210
|
+
return this.currentTheme;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Import theme configuration
|
|
215
|
+
*/
|
|
216
|
+
public async importTheme(theme: ThemeConfig): Promise<void> {
|
|
217
|
+
this.currentTheme = theme;
|
|
218
|
+
// Trigger theme update
|
|
219
|
+
this.updateTheme();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Reset to default theme
|
|
224
|
+
*/
|
|
225
|
+
public async resetToDefault(): Promise<void> {
|
|
226
|
+
this.currentTheme = this.getDefaultTheme();
|
|
227
|
+
this.updateTheme();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Get default theme configuration
|
|
232
|
+
*/
|
|
233
|
+
private getDefaultTheme(): ThemeConfig {
|
|
234
|
+
return {
|
|
235
|
+
mode: "auto",
|
|
236
|
+
dark: {
|
|
237
|
+
button: {
|
|
238
|
+
foreground: "#ffffff",
|
|
239
|
+
background: "#6c757d",
|
|
240
|
+
},
|
|
241
|
+
executing: {
|
|
242
|
+
foreground: "#ffffff",
|
|
243
|
+
background: "#007acc",
|
|
244
|
+
},
|
|
245
|
+
error: {
|
|
246
|
+
foreground: "#ffffff",
|
|
247
|
+
background: "#dc3545",
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
light: {
|
|
251
|
+
button: {
|
|
252
|
+
foreground: "#ffffff",
|
|
253
|
+
background: "#6c757d",
|
|
254
|
+
},
|
|
255
|
+
executing: {
|
|
256
|
+
foreground: "#ffffff",
|
|
257
|
+
background: "#007acc",
|
|
258
|
+
},
|
|
259
|
+
error: {
|
|
260
|
+
foreground: "#ffffff",
|
|
261
|
+
background: "#dc3545",
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
highContrast: {
|
|
265
|
+
button: {
|
|
266
|
+
foreground: "#ffffff",
|
|
267
|
+
background: "#000000",
|
|
268
|
+
},
|
|
269
|
+
executing: {
|
|
270
|
+
foreground: "#000000",
|
|
271
|
+
background: "#ffff00",
|
|
272
|
+
},
|
|
273
|
+
error: {
|
|
274
|
+
foreground: "#ffffff",
|
|
275
|
+
background: "#ff0000",
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Check if high contrast mode is enabled
|
|
283
|
+
*/
|
|
284
|
+
public isHighContrastMode(): boolean {
|
|
285
|
+
return this.getCurrentThemeType() === "highContrast";
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get recommended color scheme for accessibility
|
|
290
|
+
*/
|
|
291
|
+
public getAccessibilityColors(): { foreground: string; background: string } {
|
|
292
|
+
const themeType = this.getCurrentThemeType();
|
|
293
|
+
|
|
294
|
+
if (themeType === "highContrast") {
|
|
295
|
+
return {
|
|
296
|
+
foreground: "#ffffff",
|
|
297
|
+
background: "#000000",
|
|
298
|
+
};
|
|
299
|
+
} else if (themeType === "dark") {
|
|
300
|
+
return {
|
|
301
|
+
foreground: "#ffffff",
|
|
302
|
+
background: "#333333",
|
|
303
|
+
};
|
|
304
|
+
} else {
|
|
305
|
+
return {
|
|
306
|
+
foreground: "#000000",
|
|
307
|
+
background: "#cccccc",
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Dispose of resources
|
|
314
|
+
*/
|
|
315
|
+
public dispose(): void {
|
|
316
|
+
this.context = null;
|
|
317
|
+
}
|
|
318
|
+
}
|