@maccesar/aiskills 1.11.0 → 1.15.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 +43 -8
- package/lib/cleanup.js +42 -0
- package/lib/commands/skills.js +110 -8
- package/lib/config.js +17 -12
- package/lib/installer.js +5 -3
- package/lib/platform.js +1 -1
- package/lib/symlink.js +45 -3
- package/lib/utils.js +41 -0
- package/package.json +1 -1
- package/skills/audit-codebase/SKILL.md +70 -0
- package/skills/audit-codebase/agents/openai.yaml +4 -0
- package/skills/audit-codebase/references/comprehensive-audit.md +220 -0
- package/skills/audit-codebase/references/report-format.md +119 -0
- package/skills/humaniza/SKILL.md +55 -4
- package/skills/humaniza/references/ai-patterns-es.md +40 -0
- package/skills/humaniza/references/checklist.md +9 -0
- package/skills/humaniza/references/examples.md +16 -0
- package/skills/humaniza/references/lexicon-es-mx.md +18 -0
- package/skills/humaniza/references/structures-es.md +132 -0
- package/skills/humaniza/scripts/check_ai_patterns.py +216 -0
- package/skills/refactoring-ui/SKILL.md +65 -29
- package/skills/refactoring-ui/references/05-motion.md +124 -0
- package/skills/refactoring-ui/references/06-dark-mode.md +117 -0
- package/skills/refactoring-ui/references/07-component-patterns.md +181 -0
- package/skills/stitch-showcase/SKILL.md +24 -232
- package/skills/stitch-showcase/references/07-theme-system.md +12 -0
- package/skills/stitch-showcase/references/08-type-detection.md +9 -1
- package/skills/stitch-showcase/references/10-component-standardization.md +25 -0
- package/skills/stitch-showcase/references/12-video-embedding.md +113 -0
- package/skills/stitch-showcase/references/13-language-detection.md +82 -0
- package/skills/stitch-showcase/references/14-troubleshooting-known-issues.md +122 -0
- package/skills/stitch-showcase/references/15-build-flags.md +71 -0
- package/skills/stitch-showcase/references/16-design-md-format.md +107 -0
- package/skills/stitch-showcase/references/index.html +25 -19
- package/skills/stitch-showcase/references/viewer.html +24 -12
- package/skills/stitch-showcase/scripts/__pycache__/build_showcase.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/component_utils.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/detect_components.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/extract_catalog.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/extract_text.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/extract_zips.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/parse_design_md.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/__pycache__/slug_demangle.cpython-314.pyc +0 -0
- package/skills/stitch-showcase/scripts/build_showcase.py +150 -10
- package/skills/stitch-showcase/scripts/parse_design_md.py +145 -12
- package/skills/stitch-showcase/scripts/slug_demangle.py +209 -0
- package/skills/vscode-extension-dev/SKILL.md +90 -41
- package/skills/vscode-extension-dev/references/api-additional.md +168 -0
- package/skills/vscode-extension-dev/references/api-progress.md +55 -0
- package/skills/vscode-extension-dev/references/api-quickpick.md +75 -0
- package/skills/vscode-extension-dev/references/api-secretstorage.md +57 -0
- package/skills/vscode-extension-dev/references/api-statusbar.md +38 -0
- package/skills/vscode-extension-dev/references/api-treeview.md +78 -0
- package/skills/vscode-extension-dev/references/api-webview.md +149 -0
- package/skills/vscode-extension-dev/references/architecture.md +67 -0
- package/skills/vscode-extension-dev/references/debugger.md +179 -0
- package/skills/vscode-extension-dev/references/lsp.md +175 -0
- package/skills/vscode-extension-dev/references/notebooks.md +208 -0
- package/skills/vscode-extension-dev/references/testing.md +208 -0
- package/skills/vscode-extension-dev/references/api-patterns.md +0 -625
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# QuickPick
|
|
2
|
+
|
|
3
|
+
Modal list with filtering, multi-select, and async items.
|
|
4
|
+
|
|
5
|
+
## Simple QuickPick
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
const items: vscode.QuickPickItem[] = [
|
|
9
|
+
{ label: 'Item 1', description: 'First item', detail: 'Additional details' },
|
|
10
|
+
{ label: 'Item 2', description: 'Second item', picked: true },
|
|
11
|
+
{ label: 'Item 3', description: 'Third item' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const selected = await vscode.window.showQuickPick(items, {
|
|
15
|
+
placeHolder: 'Select an item',
|
|
16
|
+
canPickMany: false,
|
|
17
|
+
matchOnDescription: true,
|
|
18
|
+
matchOnDetail: true,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (selected) {
|
|
22
|
+
vscode.window.showInformationMessage(`Selected: ${selected.label}`);
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## QuickPick with Async Loading
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
async function showAsyncQuickPick(): Promise<void> {
|
|
30
|
+
const qp = vscode.window.createQuickPick<vscode.QuickPickItem>();
|
|
31
|
+
qp.placeholder = 'Search items...';
|
|
32
|
+
qp.matchOnDescription = true;
|
|
33
|
+
|
|
34
|
+
// Debounced search
|
|
35
|
+
let searchTimer: ReturnType<typeof setTimeout> | undefined;
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
|
|
38
|
+
qp.onDidChangeValue((value) => {
|
|
39
|
+
if (searchTimer) {
|
|
40
|
+
clearTimeout(searchTimer);
|
|
41
|
+
}
|
|
42
|
+
searchTimer = setTimeout(async () => {
|
|
43
|
+
qp.busy = true;
|
|
44
|
+
try {
|
|
45
|
+
const results = await fetchItems(value, controller.signal);
|
|
46
|
+
qp.items = results.map((r) => ({
|
|
47
|
+
label: r.name,
|
|
48
|
+
description: r.description,
|
|
49
|
+
}));
|
|
50
|
+
} catch (err) {
|
|
51
|
+
if (err instanceof Error && err.name !== 'AbortError') {
|
|
52
|
+
vscode.window.showErrorMessage(`Search failed: ${err.message}`);
|
|
53
|
+
}
|
|
54
|
+
} finally {
|
|
55
|
+
qp.busy = false;
|
|
56
|
+
}
|
|
57
|
+
}, 300);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
qp.onDidAccept(() => {
|
|
61
|
+
const selected = qp.selectedItems[0];
|
|
62
|
+
if (selected) {
|
|
63
|
+
vscode.window.showInformationMessage(`Selected: ${selected.label}`);
|
|
64
|
+
}
|
|
65
|
+
qp.dispose();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
qp.onDidHide(() => {
|
|
69
|
+
controller.abort();
|
|
70
|
+
qp.dispose();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
qp.show();
|
|
74
|
+
}
|
|
75
|
+
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# SecretStorage
|
|
2
|
+
|
|
3
|
+
Secure credential storage using the OS keychain.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
class CredentialManager {
|
|
7
|
+
private static readonly TOKEN_KEY = 'myExt.apiToken';
|
|
8
|
+
|
|
9
|
+
constructor(private readonly secrets: vscode.SecretStorage) {}
|
|
10
|
+
|
|
11
|
+
async getToken(): Promise<string | undefined> {
|
|
12
|
+
return this.secrets.get(CredentialManager.TOKEN_KEY);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async setToken(token: string): Promise<void> {
|
|
16
|
+
await this.secrets.store(CredentialManager.TOKEN_KEY, token);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async deleteToken(): Promise<void> {
|
|
20
|
+
await this.secrets.delete(CredentialManager.TOKEN_KEY);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
onDidChange(callback: (e: vscode.SecretStorageChangeEvent) => void): vscode.Disposable {
|
|
24
|
+
return this.secrets.onDidChange(callback);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// In activate():
|
|
29
|
+
export function activate(context: vscode.ExtensionContext) {
|
|
30
|
+
const credentials = new CredentialManager(context.secrets);
|
|
31
|
+
|
|
32
|
+
context.subscriptions.push(
|
|
33
|
+
vscode.commands.registerCommand('myExt.login', async () => {
|
|
34
|
+
const token = await vscode.window.showInputBox({
|
|
35
|
+
prompt: 'Enter your API token',
|
|
36
|
+
password: true,
|
|
37
|
+
ignoreFocusOut: true,
|
|
38
|
+
});
|
|
39
|
+
if (token) {
|
|
40
|
+
await credentials.setToken(token);
|
|
41
|
+
vscode.window.showInformationMessage('Token saved securely.');
|
|
42
|
+
}
|
|
43
|
+
}),
|
|
44
|
+
|
|
45
|
+
vscode.commands.registerCommand('myExt.logout', async () => {
|
|
46
|
+
await credentials.deleteToken();
|
|
47
|
+
vscode.window.showInformationMessage('Token removed.');
|
|
48
|
+
}),
|
|
49
|
+
|
|
50
|
+
credentials.onDidChange((e) => {
|
|
51
|
+
if (e.key === 'myExt.apiToken') {
|
|
52
|
+
// React to credential change
|
|
53
|
+
}
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# StatusBarItem
|
|
2
|
+
|
|
3
|
+
Persistent info in the status bar with click action.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
export function activate(context: vscode.ExtensionContext) {
|
|
7
|
+
const statusBar = vscode.window.createStatusBarItem(
|
|
8
|
+
vscode.StatusBarAlignment.Right,
|
|
9
|
+
100, // priority — higher = more to the left
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
statusBar.text = '$(sync) My Extension';
|
|
13
|
+
statusBar.tooltip = 'Click to refresh';
|
|
14
|
+
statusBar.command = 'myExt.refresh';
|
|
15
|
+
statusBar.backgroundColor = undefined; // use new vscode.ThemeColor('statusBarItem.warningBackground') for warnings
|
|
16
|
+
statusBar.show();
|
|
17
|
+
|
|
18
|
+
context.subscriptions.push(statusBar);
|
|
19
|
+
|
|
20
|
+
// Update dynamically
|
|
21
|
+
function updateStatus(count: number): void {
|
|
22
|
+
statusBar.text = `$(check) ${count} items`;
|
|
23
|
+
statusBar.tooltip = `${count} items synced`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Codicon Icons in StatusBar
|
|
29
|
+
|
|
30
|
+
Use `$(icon-name)` syntax. Common icons:
|
|
31
|
+
|
|
32
|
+
- `$(sync~spin)` — spinning sync (loading)
|
|
33
|
+
- `$(check)` — checkmark
|
|
34
|
+
- `$(warning)` — warning triangle
|
|
35
|
+
- `$(error)` — error circle
|
|
36
|
+
- `$(info)` — info circle
|
|
37
|
+
- `$(cloud-upload)` — upload
|
|
38
|
+
- Full list: https://code.visualstudio.com/api/references/icons-in-labels
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# TreeDataProvider
|
|
2
|
+
|
|
3
|
+
Provides data for a TreeView in the sidebar or panel.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import * as vscode from 'vscode';
|
|
7
|
+
|
|
8
|
+
interface TreeItem {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
children?: TreeItem[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class MyTreeProvider implements vscode.TreeDataProvider<TreeItem> {
|
|
15
|
+
private _onDidChangeTreeData = new vscode.EventEmitter<TreeItem | undefined>();
|
|
16
|
+
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
|
|
17
|
+
|
|
18
|
+
private items: TreeItem[] = [];
|
|
19
|
+
|
|
20
|
+
refresh(): void {
|
|
21
|
+
this._onDidChangeTreeData.fire(undefined);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getTreeItem(element: TreeItem): vscode.TreeItem {
|
|
25
|
+
const treeItem = new vscode.TreeItem(
|
|
26
|
+
element.label,
|
|
27
|
+
element.children?.length
|
|
28
|
+
? vscode.TreeItemCollapsibleState.Collapsed
|
|
29
|
+
: vscode.TreeItemCollapsibleState.None
|
|
30
|
+
);
|
|
31
|
+
treeItem.id = element.id;
|
|
32
|
+
treeItem.contextValue = element.children ? 'parent' : 'leaf';
|
|
33
|
+
treeItem.iconPath = new vscode.ThemeIcon('symbol-file');
|
|
34
|
+
// Make leaf items clickable
|
|
35
|
+
if (!element.children) {
|
|
36
|
+
treeItem.command = {
|
|
37
|
+
command: 'myExt.openItem',
|
|
38
|
+
title: 'Open Item',
|
|
39
|
+
arguments: [element],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return treeItem;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getChildren(element?: TreeItem): TreeItem[] {
|
|
46
|
+
if (!element) {
|
|
47
|
+
return this.items;
|
|
48
|
+
}
|
|
49
|
+
return element.children ?? [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
setItems(items: TreeItem[]): void {
|
|
53
|
+
this.items = items;
|
|
54
|
+
this.refresh();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Registering the TreeView
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
export function activate(context: vscode.ExtensionContext) {
|
|
63
|
+
const treeProvider = new MyTreeProvider();
|
|
64
|
+
|
|
65
|
+
const treeView = vscode.window.createTreeView('myTreeView', {
|
|
66
|
+
treeDataProvider: treeProvider,
|
|
67
|
+
showCollapseAll: true,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
context.subscriptions.push(
|
|
71
|
+
treeView,
|
|
72
|
+
vscode.commands.registerCommand('myExt.refresh', () => treeProvider.refresh()),
|
|
73
|
+
vscode.commands.registerCommand('myExt.openItem', (item: TreeItem) => {
|
|
74
|
+
vscode.window.showInformationMessage(`Opened: ${item.label}`);
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Webview Panel
|
|
2
|
+
|
|
3
|
+
Full HTML rendering with CSP and bidirectional messaging.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import * as vscode from 'vscode';
|
|
7
|
+
|
|
8
|
+
class MyWebviewPanel {
|
|
9
|
+
public static readonly viewType = 'myExt.webview';
|
|
10
|
+
private readonly _panel: vscode.WebviewPanel;
|
|
11
|
+
private readonly _extensionUri: vscode.Uri;
|
|
12
|
+
private _disposables: vscode.Disposable[] = [];
|
|
13
|
+
|
|
14
|
+
public static create(extensionUri: vscode.Uri): MyWebviewPanel {
|
|
15
|
+
const panel = vscode.window.createWebviewPanel(
|
|
16
|
+
MyWebviewPanel.viewType,
|
|
17
|
+
'My Panel',
|
|
18
|
+
vscode.ViewColumn.One,
|
|
19
|
+
{
|
|
20
|
+
enableScripts: true,
|
|
21
|
+
retainContextWhenHidden: false, // saves memory; set true if state is expensive
|
|
22
|
+
localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'media')],
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
return new MyWebviewPanel(panel, extensionUri);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
|
|
30
|
+
this._panel = panel;
|
|
31
|
+
this._extensionUri = extensionUri;
|
|
32
|
+
|
|
33
|
+
this._panel.webview.html = this._getHtml(this._panel.webview);
|
|
34
|
+
|
|
35
|
+
// Handle messages FROM the webview
|
|
36
|
+
this._panel.webview.onDidReceiveMessage(
|
|
37
|
+
(message: { command: string; data?: unknown }) => {
|
|
38
|
+
switch (message.command) {
|
|
39
|
+
case 'save':
|
|
40
|
+
this._handleSave(message.data);
|
|
41
|
+
return;
|
|
42
|
+
case 'requestData':
|
|
43
|
+
this._sendData();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
null,
|
|
48
|
+
this._disposables,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Send data TO the webview */
|
|
55
|
+
public sendMessage(command: string, data: unknown): void {
|
|
56
|
+
this._panel.webview.postMessage({ command, data });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private _handleSave(data: unknown): void {
|
|
60
|
+
vscode.window.showInformationMessage('Data saved!');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private _sendData(): void {
|
|
64
|
+
this.sendMessage('loadData', { items: ['a', 'b', 'c'] });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private _getHtml(webview: vscode.Webview): string {
|
|
68
|
+
const styleUri = webview.asWebviewUri(
|
|
69
|
+
vscode.Uri.joinPath(this._extensionUri, 'media', 'style.css'),
|
|
70
|
+
);
|
|
71
|
+
const scriptUri = webview.asWebviewUri(
|
|
72
|
+
vscode.Uri.joinPath(this._extensionUri, 'media', 'main.js'),
|
|
73
|
+
);
|
|
74
|
+
const nonce = getNonce();
|
|
75
|
+
|
|
76
|
+
return /*html*/ `<!DOCTYPE html>
|
|
77
|
+
<html lang="en">
|
|
78
|
+
<head>
|
|
79
|
+
<meta charset="UTF-8">
|
|
80
|
+
<meta http-equiv="Content-Security-Policy"
|
|
81
|
+
content="default-src 'none';
|
|
82
|
+
style-src ${webview.cspSource};
|
|
83
|
+
script-src 'nonce-${nonce}';
|
|
84
|
+
img-src ${webview.cspSource} https:;
|
|
85
|
+
font-src ${webview.cspSource};">
|
|
86
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
87
|
+
<link href="${styleUri}" rel="stylesheet">
|
|
88
|
+
<title>My Panel</title>
|
|
89
|
+
</head>
|
|
90
|
+
<body>
|
|
91
|
+
<div id="app"></div>
|
|
92
|
+
<script nonce="${nonce}" src="${scriptUri}"></script>
|
|
93
|
+
</body>
|
|
94
|
+
</html>`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public dispose(): void {
|
|
98
|
+
this._panel.dispose();
|
|
99
|
+
while (this._disposables.length) {
|
|
100
|
+
const d = this._disposables.pop();
|
|
101
|
+
d?.dispose();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getNonce(): string {
|
|
107
|
+
let text = '';
|
|
108
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
109
|
+
for (let i = 0; i < 32; i++) {
|
|
110
|
+
text += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
111
|
+
}
|
|
112
|
+
return text;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Webview Script (media/main.js)
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
// Inside the webview — communicates with the extension via postMessage
|
|
120
|
+
(function () {
|
|
121
|
+
// @ts-ignore
|
|
122
|
+
const vscode = acquireVsCodeApi();
|
|
123
|
+
|
|
124
|
+
// Send message TO the extension
|
|
125
|
+
function save(data) {
|
|
126
|
+
vscode.postMessage({ command: 'save', data });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Receive messages FROM the extension
|
|
130
|
+
window.addEventListener('message', (event) => {
|
|
131
|
+
const message = event.data;
|
|
132
|
+
switch (message.command) {
|
|
133
|
+
case 'loadData':
|
|
134
|
+
renderData(message.data);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
function renderData(data) {
|
|
140
|
+
const app = document.getElementById('app');
|
|
141
|
+
if (app) {
|
|
142
|
+
app.textContent = JSON.stringify(data, null, 2);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Request initial data
|
|
147
|
+
vscode.postMessage({ command: 'requestData' });
|
|
148
|
+
})();
|
|
149
|
+
```
|
|
@@ -285,3 +285,70 @@ export function deactivate(): void {
|
|
|
285
285
|
// Disposables in context.subscriptions are auto-disposed
|
|
286
286
|
}
|
|
287
287
|
```
|
|
288
|
+
|
|
289
|
+
## Extension Host Runtime
|
|
290
|
+
|
|
291
|
+
The extension host is a **single Node.js process shared by every extension** in the window. Two consequences shape the code you write:
|
|
292
|
+
|
|
293
|
+
- **Synchronous file I/O blocks the UI.** Calls like `fs.readFileSync` or `fs.writeFileSync` stall the host event loop, which freezes responses from every extension AND the editor's command handling. Always prefer async APIs:
|
|
294
|
+
- Node: `import { readFile } from 'node:fs/promises'` — `await readFile(path, 'utf8')`
|
|
295
|
+
- VS Code abstraction: `await vscode.workspace.fs.readFile(uri)` — works across remote, virtual, and local file systems
|
|
296
|
+
- **CPU-heavy work in the host blocks too.** For long parsing/analysis, move work to a worker thread (`node:worker_threads`) or a language server (LSP), not a tight loop on the host.
|
|
297
|
+
- The Webview runs in a separate process; messaging is async by construction. Don't try to "call" the host synchronously from the webview.
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
// BAD — blocks the extension host
|
|
301
|
+
import * as fs from 'node:fs';
|
|
302
|
+
const text = fs.readFileSync('/tmp/big-file.txt', 'utf8');
|
|
303
|
+
|
|
304
|
+
// GOOD — async, yields back to the event loop
|
|
305
|
+
import { readFile } from 'node:fs/promises';
|
|
306
|
+
const text = await readFile('/tmp/big-file.txt', 'utf8');
|
|
307
|
+
|
|
308
|
+
// GOOD — workspace-aware (preferred when reading user files)
|
|
309
|
+
const uri = vscode.Uri.file('/tmp/big-file.txt');
|
|
310
|
+
const bytes = await vscode.workspace.fs.readFile(uri);
|
|
311
|
+
const text2 = new TextDecoder('utf-8').decode(bytes);
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Workspace Folders (`rootPath` is deprecated)
|
|
315
|
+
|
|
316
|
+
`vscode.workspace.rootPath` is **deprecated** and returns only the *first* folder in multi-root workspaces, silently dropping the rest. Use `vscode.workspace.workspaceFolders` instead — it returns `readonly WorkspaceFolder[] | undefined`.
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
// BAD — deprecated, fails silently in multi-root workspaces
|
|
320
|
+
const root = vscode.workspace.rootPath;
|
|
321
|
+
|
|
322
|
+
// GOOD — iterate all workspace folders
|
|
323
|
+
const folders = vscode.workspace.workspaceFolders;
|
|
324
|
+
if (!folders || folders.length === 0) {
|
|
325
|
+
vscode.window.showWarningMessage('Open a folder first.');
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
for (const folder of folders) {
|
|
329
|
+
// folder.uri.fsPath is the absolute path
|
|
330
|
+
// folder.name is the display name
|
|
331
|
+
// folder.index is its position in the list
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// GOOD — find the folder that owns a given file
|
|
335
|
+
const fileUri = vscode.window.activeTextEditor?.document.uri;
|
|
336
|
+
if (fileUri) {
|
|
337
|
+
const owner = vscode.workspace.getWorkspaceFolder(fileUri);
|
|
338
|
+
// owner is the WorkspaceFolder containing the file, or undefined
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// GOOD — react to folders being added or removed
|
|
342
|
+
context.subscriptions.push(
|
|
343
|
+
vscode.workspace.onDidChangeWorkspaceFolders((event) => {
|
|
344
|
+
for (const added of event.added) {
|
|
345
|
+
// ...
|
|
346
|
+
}
|
|
347
|
+
for (const removed of event.removed) {
|
|
348
|
+
// ...
|
|
349
|
+
}
|
|
350
|
+
}),
|
|
351
|
+
);
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Relative paths in extension code should resolve against a specific `WorkspaceFolder`, never against `process.cwd()` (which is unrelated to the user's workspace).
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Debugger Extensions (Debug Adapter Protocol)
|
|
2
|
+
|
|
3
|
+
Add a new debugger by implementing the **Debug Adapter Protocol** (DAP). The debug adapter is a separate process that translates between VS Code's generic debug UI and the language/runtime's actual debugging facilities.
|
|
4
|
+
|
|
5
|
+
Official guide: https://code.visualstudio.com/api/extension-guides/debugger-extension
|
|
6
|
+
Protocol spec: https://microsoft.github.io/debug-adapter-protocol/
|
|
7
|
+
|
|
8
|
+
## Three Components
|
|
9
|
+
|
|
10
|
+
| Component | Job | Where |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| Debug adapter | Speaks DAP over stdio/socket, drives the underlying debugger | Separate process (any language) |
|
|
13
|
+
| `DebugAdapterDescriptorFactory` | Tells VS Code how to launch the adapter | In the extension |
|
|
14
|
+
| `DebugConfigurationProvider` | Resolves/validates `launch.json` entries | In the extension |
|
|
15
|
+
|
|
16
|
+
Plus declarations in `package.json` under `contributes.debuggers`.
|
|
17
|
+
|
|
18
|
+
## package.json Manifest
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
"contributes": {
|
|
22
|
+
"debuggers": [
|
|
23
|
+
{
|
|
24
|
+
"type": "mydebug",
|
|
25
|
+
"label": "My Debugger",
|
|
26
|
+
"languages": ["mylang"],
|
|
27
|
+
"configurationAttributes": {
|
|
28
|
+
"launch": {
|
|
29
|
+
"required": ["program"],
|
|
30
|
+
"properties": {
|
|
31
|
+
"program": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "Path to program to debug"
|
|
34
|
+
},
|
|
35
|
+
"stopOnEntry": {
|
|
36
|
+
"type": "boolean",
|
|
37
|
+
"default": true
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"initialConfigurations": [
|
|
43
|
+
{
|
|
44
|
+
"type": "mydebug",
|
|
45
|
+
"request": "launch",
|
|
46
|
+
"name": "Launch program",
|
|
47
|
+
"program": "${workspaceFolder}/main.mylang",
|
|
48
|
+
"stopOnEntry": true
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- `type` is the debugger identifier — referenced in user `launch.json`
|
|
57
|
+
- `configurationAttributes` powers IntelliSense inside `launch.json`
|
|
58
|
+
- `initialConfigurations` is what "Add Configuration" inserts
|
|
59
|
+
|
|
60
|
+
## Descriptor Factory
|
|
61
|
+
|
|
62
|
+
Tells VS Code how to start the adapter process for a debug session.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import * as vscode from 'vscode';
|
|
66
|
+
|
|
67
|
+
class MyDebugAdapterDescriptorFactory
|
|
68
|
+
implements vscode.DebugAdapterDescriptorFactory {
|
|
69
|
+
|
|
70
|
+
createDebugAdapterDescriptor(
|
|
71
|
+
_session: vscode.DebugSession,
|
|
72
|
+
_executable: vscode.DebugAdapterExecutable | undefined,
|
|
73
|
+
): vscode.ProviderResult<vscode.DebugAdapterDescriptor> {
|
|
74
|
+
// Option 1: launch a separate Node executable
|
|
75
|
+
return new vscode.DebugAdapterExecutable('node', [
|
|
76
|
+
this._extensionPath + '/dist/debug-adapter.js',
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
// Option 2: run the adapter inline in the extension host
|
|
80
|
+
// return new vscode.DebugAdapterInlineImplementation(new MyDebugSession());
|
|
81
|
+
|
|
82
|
+
// Option 3: connect to a server on a TCP port (for development)
|
|
83
|
+
// return new vscode.DebugAdapterServer(4711);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
constructor(private readonly _extensionPath: string) {}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function activate(context: vscode.ExtensionContext) {
|
|
90
|
+
context.subscriptions.push(
|
|
91
|
+
vscode.debug.registerDebugAdapterDescriptorFactory(
|
|
92
|
+
'mydebug',
|
|
93
|
+
new MyDebugAdapterDescriptorFactory(context.extensionPath),
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Choose the right form**:
|
|
100
|
+
|
|
101
|
+
- `DebugAdapterExecutable` — production. Adapter is a separate Node script.
|
|
102
|
+
- `DebugAdapterInlineImplementation` — testing, or very simple debuggers. Runs in extension host (so it shares the event loop — beware blocking).
|
|
103
|
+
- `DebugAdapterServer` — development convenience. Run the adapter in a debugger yourself, point VS Code at the port.
|
|
104
|
+
|
|
105
|
+
## Configuration Provider
|
|
106
|
+
|
|
107
|
+
Resolves user `launch.json` BEFORE the session starts. Useful for filling in defaults, validating, or generating configs dynamically.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
class MyDebugConfigurationProvider
|
|
111
|
+
implements vscode.DebugConfigurationProvider {
|
|
112
|
+
|
|
113
|
+
resolveDebugConfiguration(
|
|
114
|
+
folder: vscode.WorkspaceFolder | undefined,
|
|
115
|
+
config: vscode.DebugConfiguration,
|
|
116
|
+
_token?: vscode.CancellationToken,
|
|
117
|
+
): vscode.ProviderResult<vscode.DebugConfiguration> {
|
|
118
|
+
// Called when "Debug" is hit with no launch.json — supply a default
|
|
119
|
+
if (!config.type && !config.request && !config.name) {
|
|
120
|
+
const editor = vscode.window.activeTextEditor;
|
|
121
|
+
if (editor && editor.document.languageId === 'mylang') {
|
|
122
|
+
config.type = 'mydebug';
|
|
123
|
+
config.request = 'launch';
|
|
124
|
+
config.name = 'Launch';
|
|
125
|
+
config.program = '${file}';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!config.program) {
|
|
130
|
+
return vscode.window
|
|
131
|
+
.showInformationMessage('Cannot find a program to debug')
|
|
132
|
+
.then(() => undefined);
|
|
133
|
+
}
|
|
134
|
+
return config;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
context.subscriptions.push(
|
|
139
|
+
vscode.debug.registerDebugConfigurationProvider(
|
|
140
|
+
'mydebug',
|
|
141
|
+
new MyDebugConfigurationProvider(),
|
|
142
|
+
),
|
|
143
|
+
);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## DAP — What the Adapter Has to Implement
|
|
147
|
+
|
|
148
|
+
Minimum lifecycle:
|
|
149
|
+
|
|
150
|
+
| Request | Response | When |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `initialize` | capabilities (e.g., `supportsConfigurationDoneRequest`) | Session start |
|
|
153
|
+
| `launch` or `attach` | empty success | After initialize |
|
|
154
|
+
| `setBreakpoints` | array of verified breakpoints | When breakpoints change |
|
|
155
|
+
| `configurationDone` | empty success | After initial setup |
|
|
156
|
+
| `threads` | list of thread ids/names | UI refresh |
|
|
157
|
+
| `stackTrace` | call frames | Thread is stopped |
|
|
158
|
+
| `scopes` | scopes per frame | Frame selected |
|
|
159
|
+
| `variables` | variables in a scope | Scope expanded |
|
|
160
|
+
| `continue` / `next` / `stepIn` / `stepOut` | empty success | User clicks step button |
|
|
161
|
+
| `disconnect` | empty success | Session ends |
|
|
162
|
+
|
|
163
|
+
Events the adapter must SEND to the UI:
|
|
164
|
+
|
|
165
|
+
- `initialized` (after responding to `initialize`)
|
|
166
|
+
- `stopped` (with reason: `breakpoint`, `step`, `exception`, etc.)
|
|
167
|
+
- `terminated` (debugging finished)
|
|
168
|
+
- `output` (console output for the Debug Console)
|
|
169
|
+
- `thread` (thread started/exited)
|
|
170
|
+
|
|
171
|
+
Use the `vscode-debugadapter` npm package to handle the protocol plumbing — implement just the verbs you support.
|
|
172
|
+
|
|
173
|
+
## Anti-Patterns
|
|
174
|
+
|
|
175
|
+
- ❌ Sending `stopped` without a previous `initialized` event — UI shows nothing
|
|
176
|
+
- ❌ Returning ad-hoc thread/frame ids — must be stable integers within the session
|
|
177
|
+
- ❌ Sending `output` events without a `category` (`stdout` | `stderr` | `console` | `important`)
|
|
178
|
+
- ❌ Long-running synchronous work in `DebugAdapterInlineImplementation` — blocks the extension host
|
|
179
|
+
- ❌ Forgetting to send `terminated` — session never closes and "Stop" hangs
|