@maccesar/aiskills 1.7.0
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/README.md +531 -0
- package/bin/aiskills.js +76 -0
- package/lib/cache.js +49 -0
- package/lib/cleanup.js +77 -0
- package/lib/commands/auto-update.js +131 -0
- package/lib/commands/doctor.js +139 -0
- package/lib/commands/list.js +77 -0
- package/lib/commands/skills.js +263 -0
- package/lib/commands/status.js +94 -0
- package/lib/commands/uninstall.js +182 -0
- package/lib/commands/update.js +149 -0
- package/lib/config.js +90 -0
- package/lib/downloader.js +110 -0
- package/lib/hooks.js +74 -0
- package/lib/installer.js +114 -0
- package/lib/platform.js +112 -0
- package/lib/prompts/checkboxCancel.js +264 -0
- package/lib/prompts/selectCancel.js +204 -0
- package/lib/symlink.js +154 -0
- package/lib/utils.js +49 -0
- package/package.json +61 -0
- package/skills/humaniza/SKILL.md +51 -0
- package/skills/humaniza/agents/openai.yaml +4 -0
- package/skills/humaniza/references/ai-patterns-es.md +51 -0
- package/skills/humaniza/references/checklist.md +9 -0
- package/skills/humaniza/references/examples.md +17 -0
- package/skills/humaniza/references/lexicon-es-mx.md +36 -0
- package/skills/humaniza/references/modes-es-mx.md +41 -0
- package/skills/humaniza/references/voice-es-mx.md +24 -0
- package/skills/refactoring-ui/SKILL.md +59 -0
- package/skills/refactoring-ui/references/01-design-process.md +72 -0
- package/skills/refactoring-ui/references/02-visual-hierarchy.md +84 -0
- package/skills/refactoring-ui/references/03-layout-spacing.md +69 -0
- package/skills/refactoring-ui/references/04-typography.md +70 -0
- package/skills/refactoring-ui/references/05-color.md +96 -0
- package/skills/refactoring-ui/references/06-depth-shadows.md +74 -0
- package/skills/refactoring-ui/references/07-images.md +75 -0
- package/skills/refactoring-ui/references/08-finishing-touches.md +91 -0
- package/skills/stitch-showcase/SKILL.md +411 -0
- package/skills/stitch-showcase/references/01-navbar.md +52 -0
- package/skills/stitch-showcase/references/02-hero.md +56 -0
- package/skills/stitch-showcase/references/03-design-system.md +102 -0
- package/skills/stitch-showcase/references/04-screen-gallery.md +102 -0
- package/skills/stitch-showcase/references/05-viewer-web.md +105 -0
- package/skills/stitch-showcase/references/06-viewer-mobile.md +104 -0
- package/skills/stitch-showcase/references/07-theme-system.md +77 -0
- package/skills/stitch-showcase/references/08-type-detection.md +81 -0
- package/skills/stitch-showcase/references/09-quality-standards.md +126 -0
- package/skills/stitch-showcase/references/10-component-standardization.md +40 -0
- package/skills/stitch-showcase/references/11-component-catalog.md +70 -0
- package/skills/stitch-showcase/references/catalog-template.html +841 -0
- package/skills/stitch-showcase/references/index.html +299 -0
- package/skills/stitch-showcase/references/viewer.html +412 -0
- package/skills/stitch-showcase/scripts/__pycache__/build_showcase.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/component_utils.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/detect_components.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/extract_catalog.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/extract_text.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/extract_zips.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/parse_design_md.cpython-313.pyc +0 -0
- package/skills/stitch-showcase/scripts/apply_canonical.py +238 -0
- package/skills/stitch-showcase/scripts/build_showcase.py +2103 -0
- package/skills/stitch-showcase/scripts/component_utils.py +398 -0
- package/skills/stitch-showcase/scripts/detect_components.py +284 -0
- package/skills/stitch-showcase/scripts/extract_catalog.py +913 -0
- package/skills/stitch-showcase/scripts/extract_text.py +268 -0
- package/skills/stitch-showcase/scripts/extract_zips.py +178 -0
- package/skills/stitch-showcase/scripts/parse_design_md.py +397 -0
- package/skills/vscode-extension-dev/SKILL.md +114 -0
- package/skills/vscode-extension-dev/references/api-patterns.md +625 -0
- package/skills/vscode-extension-dev/references/architecture.md +287 -0
- package/skills/vscode-extension-dev/references/package-json-schema.md +345 -0
- package/skills/vscode-extension-dev/references/publishing.md +251 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
# VS Code API Patterns
|
|
2
|
+
|
|
3
|
+
Working TypeScript implementations for common VS Code extension patterns.
|
|
4
|
+
|
|
5
|
+
## TreeDataProvider
|
|
6
|
+
|
|
7
|
+
Provides data for a TreeView in the sidebar or panel.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import * as vscode from 'vscode';
|
|
11
|
+
|
|
12
|
+
interface TreeItem {
|
|
13
|
+
id: string;
|
|
14
|
+
label: string;
|
|
15
|
+
children?: TreeItem[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
class MyTreeProvider implements vscode.TreeDataProvider<TreeItem> {
|
|
19
|
+
private _onDidChangeTreeData = new vscode.EventEmitter<TreeItem | undefined>();
|
|
20
|
+
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
|
|
21
|
+
|
|
22
|
+
private items: TreeItem[] = [];
|
|
23
|
+
|
|
24
|
+
refresh(): void {
|
|
25
|
+
this._onDidChangeTreeData.fire(undefined);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getTreeItem(element: TreeItem): vscode.TreeItem {
|
|
29
|
+
const treeItem = new vscode.TreeItem(
|
|
30
|
+
element.label,
|
|
31
|
+
element.children?.length
|
|
32
|
+
? vscode.TreeItemCollapsibleState.Collapsed
|
|
33
|
+
: vscode.TreeItemCollapsibleState.None
|
|
34
|
+
);
|
|
35
|
+
treeItem.id = element.id;
|
|
36
|
+
treeItem.contextValue = element.children ? 'parent' : 'leaf';
|
|
37
|
+
treeItem.iconPath = new vscode.ThemeIcon('symbol-file');
|
|
38
|
+
// Make leaf items clickable
|
|
39
|
+
if (!element.children) {
|
|
40
|
+
treeItem.command = {
|
|
41
|
+
command: 'myExt.openItem',
|
|
42
|
+
title: 'Open Item',
|
|
43
|
+
arguments: [element],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return treeItem;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getChildren(element?: TreeItem): TreeItem[] {
|
|
50
|
+
if (!element) {
|
|
51
|
+
return this.items;
|
|
52
|
+
}
|
|
53
|
+
return element.children ?? [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setItems(items: TreeItem[]): void {
|
|
57
|
+
this.items = items;
|
|
58
|
+
this.refresh();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Registering the TreeView
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
export function activate(context: vscode.ExtensionContext) {
|
|
67
|
+
const treeProvider = new MyTreeProvider();
|
|
68
|
+
|
|
69
|
+
const treeView = vscode.window.createTreeView('myTreeView', {
|
|
70
|
+
treeDataProvider: treeProvider,
|
|
71
|
+
showCollapseAll: true,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
context.subscriptions.push(
|
|
75
|
+
treeView,
|
|
76
|
+
vscode.commands.registerCommand('myExt.refresh', () => treeProvider.refresh()),
|
|
77
|
+
vscode.commands.registerCommand('myExt.openItem', (item: TreeItem) => {
|
|
78
|
+
vscode.window.showInformationMessage(`Opened: ${item.label}`);
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Webview Panel
|
|
85
|
+
|
|
86
|
+
Full HTML rendering with CSP and bidirectional messaging.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import * as vscode from 'vscode';
|
|
90
|
+
|
|
91
|
+
class MyWebviewPanel {
|
|
92
|
+
public static readonly viewType = 'myExt.webview';
|
|
93
|
+
private readonly _panel: vscode.WebviewPanel;
|
|
94
|
+
private readonly _extensionUri: vscode.Uri;
|
|
95
|
+
private _disposables: vscode.Disposable[] = [];
|
|
96
|
+
|
|
97
|
+
public static create(extensionUri: vscode.Uri): MyWebviewPanel {
|
|
98
|
+
const panel = vscode.window.createWebviewPanel(
|
|
99
|
+
MyWebviewPanel.viewType,
|
|
100
|
+
'My Panel',
|
|
101
|
+
vscode.ViewColumn.One,
|
|
102
|
+
{
|
|
103
|
+
enableScripts: true,
|
|
104
|
+
retainContextWhenHidden: false, // saves memory; set true if state is expensive
|
|
105
|
+
localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'media')],
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
return new MyWebviewPanel(panel, extensionUri);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
|
|
113
|
+
this._panel = panel;
|
|
114
|
+
this._extensionUri = extensionUri;
|
|
115
|
+
|
|
116
|
+
this._panel.webview.html = this._getHtml(this._panel.webview);
|
|
117
|
+
|
|
118
|
+
// Handle messages FROM the webview
|
|
119
|
+
this._panel.webview.onDidReceiveMessage(
|
|
120
|
+
(message: { command: string; data?: unknown }) => {
|
|
121
|
+
switch (message.command) {
|
|
122
|
+
case 'save':
|
|
123
|
+
this._handleSave(message.data);
|
|
124
|
+
return;
|
|
125
|
+
case 'requestData':
|
|
126
|
+
this._sendData();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
null,
|
|
131
|
+
this._disposables,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Send data TO the webview */
|
|
138
|
+
public sendMessage(command: string, data: unknown): void {
|
|
139
|
+
this._panel.webview.postMessage({ command, data });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private _handleSave(data: unknown): void {
|
|
143
|
+
vscode.window.showInformationMessage('Data saved!');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private _sendData(): void {
|
|
147
|
+
this.sendMessage('loadData', { items: ['a', 'b', 'c'] });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private _getHtml(webview: vscode.Webview): string {
|
|
151
|
+
const styleUri = webview.asWebviewUri(
|
|
152
|
+
vscode.Uri.joinPath(this._extensionUri, 'media', 'style.css'),
|
|
153
|
+
);
|
|
154
|
+
const scriptUri = webview.asWebviewUri(
|
|
155
|
+
vscode.Uri.joinPath(this._extensionUri, 'media', 'main.js'),
|
|
156
|
+
);
|
|
157
|
+
const nonce = getNonce();
|
|
158
|
+
|
|
159
|
+
return /*html*/ `<!DOCTYPE html>
|
|
160
|
+
<html lang="en">
|
|
161
|
+
<head>
|
|
162
|
+
<meta charset="UTF-8">
|
|
163
|
+
<meta http-equiv="Content-Security-Policy"
|
|
164
|
+
content="default-src 'none';
|
|
165
|
+
style-src ${webview.cspSource};
|
|
166
|
+
script-src 'nonce-${nonce}';
|
|
167
|
+
img-src ${webview.cspSource} https:;
|
|
168
|
+
font-src ${webview.cspSource};">
|
|
169
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
170
|
+
<link href="${styleUri}" rel="stylesheet">
|
|
171
|
+
<title>My Panel</title>
|
|
172
|
+
</head>
|
|
173
|
+
<body>
|
|
174
|
+
<div id="app"></div>
|
|
175
|
+
<script nonce="${nonce}" src="${scriptUri}"></script>
|
|
176
|
+
</body>
|
|
177
|
+
</html>`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public dispose(): void {
|
|
181
|
+
this._panel.dispose();
|
|
182
|
+
while (this._disposables.length) {
|
|
183
|
+
const d = this._disposables.pop();
|
|
184
|
+
d?.dispose();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function getNonce(): string {
|
|
190
|
+
let text = '';
|
|
191
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
192
|
+
for (let i = 0; i < 32; i++) {
|
|
193
|
+
text += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
194
|
+
}
|
|
195
|
+
return text;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Webview Script (media/main.js)
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
// Inside the webview — communicates with the extension via postMessage
|
|
203
|
+
(function () {
|
|
204
|
+
// @ts-ignore
|
|
205
|
+
const vscode = acquireVsCodeApi();
|
|
206
|
+
|
|
207
|
+
// Send message TO the extension
|
|
208
|
+
function save(data) {
|
|
209
|
+
vscode.postMessage({ command: 'save', data });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Receive messages FROM the extension
|
|
213
|
+
window.addEventListener('message', (event) => {
|
|
214
|
+
const message = event.data;
|
|
215
|
+
switch (message.command) {
|
|
216
|
+
case 'loadData':
|
|
217
|
+
renderData(message.data);
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
function renderData(data) {
|
|
223
|
+
const app = document.getElementById('app');
|
|
224
|
+
if (app) {
|
|
225
|
+
app.textContent = JSON.stringify(data, null, 2);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Request initial data
|
|
230
|
+
vscode.postMessage({ command: 'requestData' });
|
|
231
|
+
})();
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## QuickPick
|
|
235
|
+
|
|
236
|
+
Modal list with filtering, multi-select, and async items.
|
|
237
|
+
|
|
238
|
+
### Simple QuickPick
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
const items: vscode.QuickPickItem[] = [
|
|
242
|
+
{ label: 'Item 1', description: 'First item', detail: 'Additional details' },
|
|
243
|
+
{ label: 'Item 2', description: 'Second item', picked: true },
|
|
244
|
+
{ label: 'Item 3', description: 'Third item' },
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
const selected = await vscode.window.showQuickPick(items, {
|
|
248
|
+
placeHolder: 'Select an item',
|
|
249
|
+
canPickMany: false,
|
|
250
|
+
matchOnDescription: true,
|
|
251
|
+
matchOnDetail: true,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
if (selected) {
|
|
255
|
+
vscode.window.showInformationMessage(`Selected: ${selected.label}`);
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### QuickPick with Async Loading
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
async function showAsyncQuickPick(): Promise<void> {
|
|
263
|
+
const qp = vscode.window.createQuickPick<vscode.QuickPickItem>();
|
|
264
|
+
qp.placeholder = 'Search items...';
|
|
265
|
+
qp.matchOnDescription = true;
|
|
266
|
+
|
|
267
|
+
// Debounced search
|
|
268
|
+
let searchTimer: ReturnType<typeof setTimeout> | undefined;
|
|
269
|
+
const controller = new AbortController();
|
|
270
|
+
|
|
271
|
+
qp.onDidChangeValue((value) => {
|
|
272
|
+
if (searchTimer) {
|
|
273
|
+
clearTimeout(searchTimer);
|
|
274
|
+
}
|
|
275
|
+
searchTimer = setTimeout(async () => {
|
|
276
|
+
qp.busy = true;
|
|
277
|
+
try {
|
|
278
|
+
const results = await fetchItems(value, controller.signal);
|
|
279
|
+
qp.items = results.map((r) => ({
|
|
280
|
+
label: r.name,
|
|
281
|
+
description: r.description,
|
|
282
|
+
}));
|
|
283
|
+
} catch (err) {
|
|
284
|
+
if (err instanceof Error && err.name !== 'AbortError') {
|
|
285
|
+
vscode.window.showErrorMessage(`Search failed: ${err.message}`);
|
|
286
|
+
}
|
|
287
|
+
} finally {
|
|
288
|
+
qp.busy = false;
|
|
289
|
+
}
|
|
290
|
+
}, 300);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
qp.onDidAccept(() => {
|
|
294
|
+
const selected = qp.selectedItems[0];
|
|
295
|
+
if (selected) {
|
|
296
|
+
vscode.window.showInformationMessage(`Selected: ${selected.label}`);
|
|
297
|
+
}
|
|
298
|
+
qp.dispose();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
qp.onDidHide(() => {
|
|
302
|
+
controller.abort();
|
|
303
|
+
qp.dispose();
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
qp.show();
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## StatusBarItem
|
|
311
|
+
|
|
312
|
+
Persistent info in the status bar with click action.
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
export function activate(context: vscode.ExtensionContext) {
|
|
316
|
+
const statusBar = vscode.window.createStatusBarItem(
|
|
317
|
+
vscode.StatusBarAlignment.Right,
|
|
318
|
+
100, // priority — higher = more to the left
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
statusBar.text = '$(sync) My Extension';
|
|
322
|
+
statusBar.tooltip = 'Click to refresh';
|
|
323
|
+
statusBar.command = 'myExt.refresh';
|
|
324
|
+
statusBar.backgroundColor = undefined; // use new vscode.ThemeColor('statusBarItem.warningBackground') for warnings
|
|
325
|
+
statusBar.show();
|
|
326
|
+
|
|
327
|
+
context.subscriptions.push(statusBar);
|
|
328
|
+
|
|
329
|
+
// Update dynamically
|
|
330
|
+
function updateStatus(count: number): void {
|
|
331
|
+
statusBar.text = `$(check) ${count} items`;
|
|
332
|
+
statusBar.tooltip = `${count} items synced`;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Codicon Icons in StatusBar
|
|
338
|
+
|
|
339
|
+
Use `$(icon-name)` syntax. Common icons:
|
|
340
|
+
- `$(sync~spin)` — spinning sync (loading)
|
|
341
|
+
- `$(check)` — checkmark
|
|
342
|
+
- `$(warning)` — warning triangle
|
|
343
|
+
- `$(error)` — error circle
|
|
344
|
+
- `$(info)` — info circle
|
|
345
|
+
- `$(cloud-upload)` — upload
|
|
346
|
+
- Full list: https://code.visualstudio.com/api/references/icons-in-labels
|
|
347
|
+
|
|
348
|
+
## SecretStorage
|
|
349
|
+
|
|
350
|
+
Secure credential storage using the OS keychain.
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
class CredentialManager {
|
|
354
|
+
private static readonly TOKEN_KEY = 'myExt.apiToken';
|
|
355
|
+
|
|
356
|
+
constructor(private readonly secrets: vscode.SecretStorage) {}
|
|
357
|
+
|
|
358
|
+
async getToken(): Promise<string | undefined> {
|
|
359
|
+
return this.secrets.get(CredentialManager.TOKEN_KEY);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async setToken(token: string): Promise<void> {
|
|
363
|
+
await this.secrets.store(CredentialManager.TOKEN_KEY, token);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async deleteToken(): Promise<void> {
|
|
367
|
+
await this.secrets.delete(CredentialManager.TOKEN_KEY);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
onDidChange(callback: (e: vscode.SecretStorageChangeEvent) => void): vscode.Disposable {
|
|
371
|
+
return this.secrets.onDidChange(callback);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// In activate():
|
|
376
|
+
export function activate(context: vscode.ExtensionContext) {
|
|
377
|
+
const credentials = new CredentialManager(context.secrets);
|
|
378
|
+
|
|
379
|
+
context.subscriptions.push(
|
|
380
|
+
vscode.commands.registerCommand('myExt.login', async () => {
|
|
381
|
+
const token = await vscode.window.showInputBox({
|
|
382
|
+
prompt: 'Enter your API token',
|
|
383
|
+
password: true,
|
|
384
|
+
ignoreFocusOut: true,
|
|
385
|
+
});
|
|
386
|
+
if (token) {
|
|
387
|
+
await credentials.setToken(token);
|
|
388
|
+
vscode.window.showInformationMessage('Token saved securely.');
|
|
389
|
+
}
|
|
390
|
+
}),
|
|
391
|
+
|
|
392
|
+
vscode.commands.registerCommand('myExt.logout', async () => {
|
|
393
|
+
await credentials.deleteToken();
|
|
394
|
+
vscode.window.showInformationMessage('Token removed.');
|
|
395
|
+
}),
|
|
396
|
+
|
|
397
|
+
credentials.onDidChange((e) => {
|
|
398
|
+
if (e.key === 'myExt.apiToken') {
|
|
399
|
+
// React to credential change
|
|
400
|
+
}
|
|
401
|
+
}),
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## withProgress
|
|
407
|
+
|
|
408
|
+
Show progress for long-running operations.
|
|
409
|
+
|
|
410
|
+
### Notification Progress
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
async function longRunningTask(): Promise<void> {
|
|
414
|
+
await vscode.window.withProgress(
|
|
415
|
+
{
|
|
416
|
+
location: vscode.ProgressLocation.Notification,
|
|
417
|
+
title: 'Processing items',
|
|
418
|
+
cancellable: true,
|
|
419
|
+
},
|
|
420
|
+
async (progress, token) => {
|
|
421
|
+
const items = await getItems();
|
|
422
|
+
const total = items.length;
|
|
423
|
+
|
|
424
|
+
for (let i = 0; i < total; i++) {
|
|
425
|
+
// Check for cancellation
|
|
426
|
+
if (token.isCancellationRequested) {
|
|
427
|
+
vscode.window.showWarningMessage('Operation cancelled.');
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
progress.report({
|
|
432
|
+
increment: 100 / total,
|
|
433
|
+
message: `(${i + 1}/${total}) ${items[i].name}`,
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
await processItem(items[i]);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
vscode.window.showInformationMessage(`Processed ${total} items.`);
|
|
440
|
+
},
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Status Bar Progress
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
await vscode.window.withProgress(
|
|
449
|
+
{
|
|
450
|
+
location: vscode.ProgressLocation.Window,
|
|
451
|
+
title: 'Indexing files...',
|
|
452
|
+
},
|
|
453
|
+
async (progress) => {
|
|
454
|
+
progress.report({ message: 'scanning...' });
|
|
455
|
+
await scanFiles();
|
|
456
|
+
progress.report({ message: 'building index...' });
|
|
457
|
+
await buildIndex();
|
|
458
|
+
},
|
|
459
|
+
);
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
## FileSystemWatcher
|
|
463
|
+
|
|
464
|
+
React to file changes in the workspace.
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
export function activate(context: vscode.ExtensionContext) {
|
|
468
|
+
const watcher = vscode.workspace.createFileSystemWatcher(
|
|
469
|
+
'**/*.json', // glob pattern
|
|
470
|
+
false, // ignoreCreateEvents
|
|
471
|
+
false, // ignoreChangeEvents
|
|
472
|
+
false, // ignoreDeleteEvents
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
context.subscriptions.push(
|
|
476
|
+
watcher,
|
|
477
|
+
watcher.onDidCreate((uri) => {
|
|
478
|
+
console.log(`Created: ${uri.fsPath}`);
|
|
479
|
+
}),
|
|
480
|
+
watcher.onDidChange((uri) => {
|
|
481
|
+
console.log(`Changed: ${uri.fsPath}`);
|
|
482
|
+
}),
|
|
483
|
+
watcher.onDidDelete((uri) => {
|
|
484
|
+
console.log(`Deleted: ${uri.fsPath}`);
|
|
485
|
+
}),
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
## Disposable Cleanup Pattern
|
|
491
|
+
|
|
492
|
+
The standard pattern for managing extension lifecycle.
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
import * as vscode from 'vscode';
|
|
496
|
+
|
|
497
|
+
let outputChannel: vscode.OutputChannel | undefined;
|
|
498
|
+
|
|
499
|
+
export function activate(context: vscode.ExtensionContext) {
|
|
500
|
+
// Output channel for logging
|
|
501
|
+
outputChannel = vscode.window.createOutputChannel('My Extension');
|
|
502
|
+
context.subscriptions.push(outputChannel);
|
|
503
|
+
|
|
504
|
+
// All registrations go into context.subscriptions
|
|
505
|
+
context.subscriptions.push(
|
|
506
|
+
vscode.commands.registerCommand('myExt.run', run),
|
|
507
|
+
vscode.workspace.onDidSaveTextDocument(onDocSaved),
|
|
508
|
+
vscode.window.onDidChangeActiveTextEditor(onEditorChanged),
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
outputChannel.appendLine('Extension activated');
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export function deactivate(): void {
|
|
515
|
+
// Only needed for async cleanup like:
|
|
516
|
+
// - Closing network connections
|
|
517
|
+
// - Stopping child processes
|
|
518
|
+
// - Flushing buffers
|
|
519
|
+
// Disposables in context.subscriptions are auto-disposed.
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function run(): void {
|
|
523
|
+
outputChannel?.appendLine('Command executed');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function onDocSaved(doc: vscode.TextDocument): void {
|
|
527
|
+
outputChannel?.appendLine(`Saved: ${doc.fileName}`);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function onEditorChanged(editor: vscode.TextEditor | undefined): void {
|
|
531
|
+
outputChannel?.appendLine(`Active editor: ${editor?.document.fileName ?? 'none'}`);
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
## Diagnostic Collection
|
|
536
|
+
|
|
537
|
+
Report problems (errors, warnings) in the Problems panel.
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
const diagnostics = vscode.languages.createDiagnosticCollection('myExt');
|
|
541
|
+
context.subscriptions.push(diagnostics);
|
|
542
|
+
|
|
543
|
+
function validateDocument(doc: vscode.TextDocument): void {
|
|
544
|
+
const issues: vscode.Diagnostic[] = [];
|
|
545
|
+
|
|
546
|
+
for (let i = 0; i < doc.lineCount; i++) {
|
|
547
|
+
const line = doc.lineAt(i);
|
|
548
|
+
if (line.text.includes('TODO')) {
|
|
549
|
+
issues.push(
|
|
550
|
+
new vscode.Diagnostic(
|
|
551
|
+
line.range,
|
|
552
|
+
'TODO comment found',
|
|
553
|
+
vscode.DiagnosticSeverity.Warning,
|
|
554
|
+
),
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
diagnostics.set(doc.uri, issues);
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
## Output Channel and Logging
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
// Simple output channel
|
|
567
|
+
const output = vscode.window.createOutputChannel('My Extension');
|
|
568
|
+
output.appendLine('Info message');
|
|
569
|
+
output.show(true); // true = preserve focus
|
|
570
|
+
|
|
571
|
+
// Log output channel (structured, with log levels — VS Code 1.74+)
|
|
572
|
+
const log = vscode.window.createOutputChannel('My Extension', { log: true });
|
|
573
|
+
log.info('Started');
|
|
574
|
+
log.warn('Something looks off');
|
|
575
|
+
log.error('Something failed', new Error('details'));
|
|
576
|
+
log.debug('Debug data', { key: 'value' });
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
## Context Keys (When Clauses)
|
|
580
|
+
|
|
581
|
+
Set custom context keys to control menu/command visibility.
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
// Set a context key
|
|
585
|
+
vscode.commands.executeCommand('setContext', 'myExt.isConnected', true);
|
|
586
|
+
|
|
587
|
+
// Use in package.json when clauses:
|
|
588
|
+
// "when": "myExt.isConnected"
|
|
589
|
+
// "when": "myExt.isConnected && editorLangId == typescript"
|
|
590
|
+
|
|
591
|
+
// Clear it
|
|
592
|
+
vscode.commands.executeCommand('setContext', 'myExt.isConnected', false);
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
## TextDocumentContentProvider
|
|
596
|
+
|
|
597
|
+
Provide virtual read-only documents.
|
|
598
|
+
|
|
599
|
+
```typescript
|
|
600
|
+
class MyContentProvider implements vscode.TextDocumentContentProvider {
|
|
601
|
+
private _onDidChange = new vscode.EventEmitter<vscode.Uri>();
|
|
602
|
+
readonly onDidChange = this._onDidChange.event;
|
|
603
|
+
|
|
604
|
+
provideTextDocumentContent(uri: vscode.Uri): string {
|
|
605
|
+
const query = new URLSearchParams(uri.query);
|
|
606
|
+
const id = query.get('id') ?? 'unknown';
|
|
607
|
+
return `Content for: ${id}\nGenerated at: ${new Date().toISOString()}`;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
refresh(uri: vscode.Uri): void {
|
|
611
|
+
this._onDidChange.fire(uri);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Register
|
|
616
|
+
const provider = new MyContentProvider();
|
|
617
|
+
context.subscriptions.push(
|
|
618
|
+
vscode.workspace.registerTextDocumentContentProvider('myScheme', provider),
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
// Open a virtual document
|
|
622
|
+
const uri = vscode.Uri.parse('myScheme:item?id=123');
|
|
623
|
+
const doc = await vscode.workspace.openTextDocument(uri);
|
|
624
|
+
await vscode.window.showTextDocument(doc);
|
|
625
|
+
```
|