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,530 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for StatusBar Quick Actions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as vscode from "vscode";
|
|
6
|
+
import {
|
|
7
|
+
StatusBarButtonConfig,
|
|
8
|
+
ExtensionConfig,
|
|
9
|
+
ThemeConfig,
|
|
10
|
+
NotificationConfig,
|
|
11
|
+
CommandHistoryEntry,
|
|
12
|
+
PresetConfig,
|
|
13
|
+
PresetApplicationMode,
|
|
14
|
+
} from "./types";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Configuration Manager
|
|
18
|
+
* Handles reading, writing, and watching configuration changes
|
|
19
|
+
*/
|
|
20
|
+
export class ConfigManager {
|
|
21
|
+
private static readonly CONFIG_SECTION = "statusbarQuickActions";
|
|
22
|
+
private static readonly GLOBAL_STATE_KEY = "statusbarQuickActions.history";
|
|
23
|
+
|
|
24
|
+
private context: vscode.ExtensionContext | null = null;
|
|
25
|
+
private onChangeCallbacks: ((config: ExtensionConfig) => void)[] = [];
|
|
26
|
+
private configChangeListener: vscode.Disposable | null = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Initialize the configuration manager
|
|
30
|
+
*/
|
|
31
|
+
public initialize(context: vscode.ExtensionContext): void {
|
|
32
|
+
this.context = context;
|
|
33
|
+
this.setupConfigurationWatching();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the current configuration
|
|
38
|
+
*/
|
|
39
|
+
public getConfig(): ExtensionConfig {
|
|
40
|
+
const config = vscode.workspace.getConfiguration(
|
|
41
|
+
ConfigManager.CONFIG_SECTION,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
buttons: config.get("buttons", []),
|
|
46
|
+
theme: config.get("theme", this.getDefaultThemeConfig()),
|
|
47
|
+
notifications: config.get(
|
|
48
|
+
"notifications",
|
|
49
|
+
this.getDefaultNotificationConfig(),
|
|
50
|
+
),
|
|
51
|
+
history: config.get("history", true),
|
|
52
|
+
autoDetect: config.get("autoDetect", true),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Set a configuration value
|
|
58
|
+
*/
|
|
59
|
+
public async setConfig<T>(key: string, value: T): Promise<void> {
|
|
60
|
+
const config = vscode.workspace.getConfiguration(
|
|
61
|
+
ConfigManager.CONFIG_SECTION,
|
|
62
|
+
);
|
|
63
|
+
await config.update(key, value, vscode.ConfigurationTarget.Workspace);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get a specific configuration value
|
|
68
|
+
*/
|
|
69
|
+
public getConfigValue<T>(key: string, defaultValue: T): T {
|
|
70
|
+
const config = vscode.workspace.getConfiguration(
|
|
71
|
+
ConfigManager.CONFIG_SECTION,
|
|
72
|
+
);
|
|
73
|
+
return config.get(key, defaultValue);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Add a callback for configuration changes
|
|
78
|
+
*/
|
|
79
|
+
public onConfigurationChanged(
|
|
80
|
+
callback: (config: ExtensionConfig) => void,
|
|
81
|
+
): vscode.Disposable {
|
|
82
|
+
this.onChangeCallbacks.push(callback);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
dispose: () => {
|
|
86
|
+
const index = this.onChangeCallbacks.indexOf(callback);
|
|
87
|
+
if (index > -1) {
|
|
88
|
+
this.onChangeCallbacks.splice(index, 1);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Set up configuration change watching
|
|
96
|
+
*/
|
|
97
|
+
private setupConfigurationWatching(): void {
|
|
98
|
+
if (!this.context) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this.configChangeListener = vscode.workspace.onDidChangeConfiguration(
|
|
103
|
+
(event) => {
|
|
104
|
+
if (event.affectsConfiguration(ConfigManager.CONFIG_SECTION)) {
|
|
105
|
+
const newConfig = this.getConfig();
|
|
106
|
+
this.onChangeCallbacks.forEach((callback) => {
|
|
107
|
+
try {
|
|
108
|
+
callback(newConfig);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error("Error in configuration change callback:", error);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (this.context) {
|
|
118
|
+
this.context.subscriptions.push(this.configChangeListener);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get default theme configuration
|
|
124
|
+
*/
|
|
125
|
+
private getDefaultThemeConfig(): ThemeConfig {
|
|
126
|
+
return {
|
|
127
|
+
mode: "auto",
|
|
128
|
+
dark: {
|
|
129
|
+
button: {
|
|
130
|
+
foreground: "#ffffff",
|
|
131
|
+
background: "#6c757d",
|
|
132
|
+
},
|
|
133
|
+
executing: {
|
|
134
|
+
foreground: "#ffffff",
|
|
135
|
+
background: "#007acc",
|
|
136
|
+
},
|
|
137
|
+
error: {
|
|
138
|
+
foreground: "#ffffff",
|
|
139
|
+
background: "#dc3545",
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
light: {
|
|
143
|
+
button: {
|
|
144
|
+
foreground: "#ffffff",
|
|
145
|
+
background: "#6c757d",
|
|
146
|
+
},
|
|
147
|
+
executing: {
|
|
148
|
+
foreground: "#ffffff",
|
|
149
|
+
background: "#007acc",
|
|
150
|
+
},
|
|
151
|
+
error: {
|
|
152
|
+
foreground: "#ffffff",
|
|
153
|
+
background: "#dc3545",
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
highContrast: {
|
|
157
|
+
button: {
|
|
158
|
+
foreground: "#ffffff",
|
|
159
|
+
background: "#000000",
|
|
160
|
+
},
|
|
161
|
+
executing: {
|
|
162
|
+
foreground: "#000000",
|
|
163
|
+
background: "#ffff00",
|
|
164
|
+
},
|
|
165
|
+
error: {
|
|
166
|
+
foreground: "#ffffff",
|
|
167
|
+
background: "#ff0000",
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get default notification configuration
|
|
175
|
+
*/
|
|
176
|
+
private getDefaultNotificationConfig(): NotificationConfig {
|
|
177
|
+
return {
|
|
178
|
+
showSuccess: true,
|
|
179
|
+
showError: true,
|
|
180
|
+
showProgress: true,
|
|
181
|
+
position: "bottom-right",
|
|
182
|
+
duration: 5000,
|
|
183
|
+
includeOutput: false,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Validate configuration
|
|
189
|
+
*/
|
|
190
|
+
public validateConfig(config: ExtensionConfig): {
|
|
191
|
+
isValid: boolean;
|
|
192
|
+
errors: string[];
|
|
193
|
+
} {
|
|
194
|
+
const errors: string[] = [];
|
|
195
|
+
|
|
196
|
+
// Validate buttons
|
|
197
|
+
if (!Array.isArray(config.buttons)) {
|
|
198
|
+
errors.push("Buttons must be an array");
|
|
199
|
+
} else {
|
|
200
|
+
config.buttons.forEach((button, index) => {
|
|
201
|
+
if (!button.id || typeof button.id !== "string") {
|
|
202
|
+
errors.push(`Button ${index}: ID is required and must be a string`);
|
|
203
|
+
}
|
|
204
|
+
if (!button.text && !button.icon) {
|
|
205
|
+
errors.push(`Button ${index}: Either text or icon is required`);
|
|
206
|
+
}
|
|
207
|
+
if (!button.command || typeof button.command !== "object") {
|
|
208
|
+
errors.push(
|
|
209
|
+
`Button ${index}: Command is required and must be an object`,
|
|
210
|
+
);
|
|
211
|
+
} else {
|
|
212
|
+
// Validate command structure
|
|
213
|
+
if (!button.command.type) {
|
|
214
|
+
errors.push(`Button ${index}: Command type is required`);
|
|
215
|
+
}
|
|
216
|
+
// Validate that package manager commands have a script
|
|
217
|
+
if (
|
|
218
|
+
[
|
|
219
|
+
"npm",
|
|
220
|
+
"yarn",
|
|
221
|
+
"pnpm",
|
|
222
|
+
"bun",
|
|
223
|
+
"bunx",
|
|
224
|
+
"npx",
|
|
225
|
+
"pnpx",
|
|
226
|
+
"detect",
|
|
227
|
+
].includes(button.command.type) &&
|
|
228
|
+
!button.command.script
|
|
229
|
+
) {
|
|
230
|
+
console.log(
|
|
231
|
+
`Button ${index}: ${button.command.type} command missing script`,
|
|
232
|
+
button.command,
|
|
233
|
+
);
|
|
234
|
+
errors.push(
|
|
235
|
+
`Button ${index}: Script is required for ${button.command.type} commands`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
// Validate that non-package manager commands have a command string
|
|
239
|
+
if (
|
|
240
|
+
["shell", "github", "vscode", "task"].includes(
|
|
241
|
+
button.command.type,
|
|
242
|
+
) &&
|
|
243
|
+
!button.command.command
|
|
244
|
+
) {
|
|
245
|
+
console.log(
|
|
246
|
+
`Button ${index}: ${button.command.type} command missing command string`,
|
|
247
|
+
button.command,
|
|
248
|
+
);
|
|
249
|
+
errors.push(
|
|
250
|
+
`Button ${index}: Command string is required for ${button.command.type} commands`,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Validate theme configuration
|
|
258
|
+
if (config.theme) {
|
|
259
|
+
if (
|
|
260
|
+
!["auto", "dark", "light", "highContrast"].includes(config.theme.mode)
|
|
261
|
+
) {
|
|
262
|
+
errors.push("Theme mode must be auto, dark, light, or highContrast");
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Validate notification configuration
|
|
267
|
+
if (config.notifications) {
|
|
268
|
+
if (config.notifications.duration && config.notifications.duration < 0) {
|
|
269
|
+
errors.push("Notification duration must be a positive number");
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
isValid: errors.length === 0,
|
|
275
|
+
errors,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Get button configuration by ID
|
|
281
|
+
*/
|
|
282
|
+
public getButtonConfig(buttonId: string): StatusBarButtonConfig | null {
|
|
283
|
+
const config = this.getConfig();
|
|
284
|
+
return config.buttons.find((button) => button.id === buttonId) || null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Update a specific button configuration
|
|
289
|
+
*/
|
|
290
|
+
public async updateButtonConfig(
|
|
291
|
+
buttonId: string,
|
|
292
|
+
updates: Partial<StatusBarButtonConfig>,
|
|
293
|
+
): Promise<void> {
|
|
294
|
+
const config = this.getConfig();
|
|
295
|
+
const buttonIndex = config.buttons.findIndex(
|
|
296
|
+
(button) => button.id === buttonId,
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
if (buttonIndex === -1) {
|
|
300
|
+
throw new Error(`Button with ID '${buttonId}' not found`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const updatedButton = { ...config.buttons[buttonIndex], ...updates };
|
|
304
|
+
config.buttons[buttonIndex] = updatedButton;
|
|
305
|
+
|
|
306
|
+
await this.setConfig("buttons", config.buttons);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Add a new button configuration
|
|
311
|
+
*/
|
|
312
|
+
public async addButtonConfig(button: StatusBarButtonConfig): Promise<void> {
|
|
313
|
+
const config = this.getConfig();
|
|
314
|
+
|
|
315
|
+
// Check for duplicate IDs
|
|
316
|
+
if (config.buttons.some((b) => b.id === button.id)) {
|
|
317
|
+
throw new Error(`Button with ID '${button.id}' already exists`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
config.buttons.push(button);
|
|
321
|
+
await this.setConfig("buttons", config.buttons);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Remove a button configuration
|
|
326
|
+
*/
|
|
327
|
+
public async removeButtonConfig(buttonId: string): Promise<void> {
|
|
328
|
+
const config = this.getConfig();
|
|
329
|
+
const filteredButtons = config.buttons.filter(
|
|
330
|
+
(button) => button.id !== buttonId,
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
if (filteredButtons.length === config.buttons.length) {
|
|
334
|
+
throw new Error(`Button with ID '${buttonId}' not found`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
await this.setConfig("buttons", filteredButtons);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Get command history
|
|
342
|
+
*/
|
|
343
|
+
public getCommandHistory(): CommandHistoryEntry[] {
|
|
344
|
+
if (!this.context) {
|
|
345
|
+
return [];
|
|
346
|
+
}
|
|
347
|
+
return this.context.globalState.get(ConfigManager.GLOBAL_STATE_KEY, []);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Add command to history
|
|
352
|
+
*/
|
|
353
|
+
public async addToHistory(entry: CommandHistoryEntry): Promise<void> {
|
|
354
|
+
if (!this.context) {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const history = this.getCommandHistory();
|
|
359
|
+
history.unshift(entry);
|
|
360
|
+
|
|
361
|
+
// Keep only last 100 entries
|
|
362
|
+
if (history.length > 100) {
|
|
363
|
+
history.splice(100);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
await this.context.globalState.update(
|
|
367
|
+
ConfigManager.GLOBAL_STATE_KEY,
|
|
368
|
+
history,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Clear command history
|
|
374
|
+
*/
|
|
375
|
+
public async clearHistory(): Promise<void> {
|
|
376
|
+
if (!this.context) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
await this.context.globalState.update(ConfigManager.GLOBAL_STATE_KEY, []);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Apply a preset to the current configuration
|
|
385
|
+
*/
|
|
386
|
+
public async applyPreset(
|
|
387
|
+
preset: PresetConfig,
|
|
388
|
+
mode: PresetApplicationMode = "replace",
|
|
389
|
+
): Promise<void> {
|
|
390
|
+
const currentConfig = this.getConfig();
|
|
391
|
+
let newButtons: StatusBarButtonConfig[];
|
|
392
|
+
|
|
393
|
+
switch (mode) {
|
|
394
|
+
case "replace":
|
|
395
|
+
// Replace all buttons with preset buttons
|
|
396
|
+
newButtons = [...preset.buttons];
|
|
397
|
+
break;
|
|
398
|
+
|
|
399
|
+
case "merge":
|
|
400
|
+
// Merge preset buttons, overwriting buttons with same ID
|
|
401
|
+
newButtons = [...currentConfig.buttons];
|
|
402
|
+
preset.buttons.forEach((presetButton) => {
|
|
403
|
+
const existingIndex = newButtons.findIndex(
|
|
404
|
+
(b) => b.id === presetButton.id,
|
|
405
|
+
);
|
|
406
|
+
if (existingIndex >= 0) {
|
|
407
|
+
newButtons[existingIndex] = presetButton;
|
|
408
|
+
} else {
|
|
409
|
+
newButtons.push(presetButton);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
break;
|
|
413
|
+
|
|
414
|
+
case "append":
|
|
415
|
+
// Append preset buttons to existing buttons, ensuring unique IDs
|
|
416
|
+
newButtons = [...currentConfig.buttons];
|
|
417
|
+
preset.buttons.forEach((presetButton) => {
|
|
418
|
+
// Generate new ID if there's a conflict
|
|
419
|
+
let buttonToAdd = presetButton;
|
|
420
|
+
if (newButtons.some((b) => b.id === presetButton.id)) {
|
|
421
|
+
buttonToAdd = {
|
|
422
|
+
...presetButton,
|
|
423
|
+
id: `${presetButton.id}_${Date.now()}`,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
newButtons.push(buttonToAdd);
|
|
427
|
+
});
|
|
428
|
+
break;
|
|
429
|
+
|
|
430
|
+
default:
|
|
431
|
+
throw new Error(`Unknown preset application mode: ${mode}`);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Update buttons configuration
|
|
435
|
+
await this.setConfig("buttons", newButtons);
|
|
436
|
+
|
|
437
|
+
// Apply theme if present in preset
|
|
438
|
+
if (preset.theme) {
|
|
439
|
+
await this.setConfig("theme", preset.theme);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Get buttons that would be affected by preset application
|
|
445
|
+
*/
|
|
446
|
+
public getPresetImpact(
|
|
447
|
+
preset: PresetConfig,
|
|
448
|
+
mode: PresetApplicationMode,
|
|
449
|
+
): {
|
|
450
|
+
added: number;
|
|
451
|
+
modified: number;
|
|
452
|
+
removed: number;
|
|
453
|
+
total: number;
|
|
454
|
+
} {
|
|
455
|
+
const currentConfig = this.getConfig();
|
|
456
|
+
const currentIds = new Set(currentConfig.buttons.map((b) => b.id));
|
|
457
|
+
|
|
458
|
+
let added = 0;
|
|
459
|
+
let modified = 0;
|
|
460
|
+
let removed = 0;
|
|
461
|
+
|
|
462
|
+
switch (mode) {
|
|
463
|
+
case "replace":
|
|
464
|
+
added = preset.buttons.length;
|
|
465
|
+
removed = currentConfig.buttons.length;
|
|
466
|
+
break;
|
|
467
|
+
|
|
468
|
+
case "merge":
|
|
469
|
+
preset.buttons.forEach((pb) => {
|
|
470
|
+
if (currentIds.has(pb.id)) {
|
|
471
|
+
modified++;
|
|
472
|
+
} else {
|
|
473
|
+
added++;
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
break;
|
|
477
|
+
|
|
478
|
+
case "append":
|
|
479
|
+
added = preset.buttons.length;
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
added,
|
|
485
|
+
modified,
|
|
486
|
+
removed,
|
|
487
|
+
total: added + modified + removed,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Validate preset before application
|
|
493
|
+
*/
|
|
494
|
+
public validatePresetApplication(preset: PresetConfig): {
|
|
495
|
+
isValid: boolean;
|
|
496
|
+
errors: string[];
|
|
497
|
+
} {
|
|
498
|
+
const errors: string[] = [];
|
|
499
|
+
|
|
500
|
+
if (!preset.buttons || !Array.isArray(preset.buttons)) {
|
|
501
|
+
errors.push("Preset must contain a buttons array");
|
|
502
|
+
} else {
|
|
503
|
+
preset.buttons.forEach((button, index) => {
|
|
504
|
+
if (!button.id) {
|
|
505
|
+
errors.push(`Button ${index}: ID is required`);
|
|
506
|
+
}
|
|
507
|
+
if (!button.text && !button.icon) {
|
|
508
|
+
errors.push(`Button ${index}: Either text or icon is required`);
|
|
509
|
+
}
|
|
510
|
+
if (!button.command) {
|
|
511
|
+
errors.push(`Button ${index}: Command is required`);
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return {
|
|
517
|
+
isValid: errors.length === 0,
|
|
518
|
+
errors,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Dispose of resources
|
|
524
|
+
*/
|
|
525
|
+
public dispose(): void {
|
|
526
|
+
if (this.configChangeListener) {
|
|
527
|
+
this.configChangeListener.dispose();
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|