Skip to content

Commit c342b64

Browse files
committed
feat(extension): add attach-to-process command
1 parent ea3e625 commit c342b64

7 files changed

Lines changed: 172 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ If you have multiple solutions, use the Command Palette action **"VB.NET: Select
131131
For diagnostics, use **"VB.NET: Show Logs"** and **"VB.NET: Toggle LSP Trace"**.
132132
If assets are missing, run **"VB.NET: Restore Workspace"** or **"VB.NET: Restore Project"**.
133133
To force a reanalysis without restarting VS Code, use **"VB.NET: Reload Workspace"**.
134+
For attaching to a running .NET process, use **"VB.NET: Attach Debugger to Process"**.
134135

135136
## Architecture
136137

docs/configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Last Updated: 2026-01-20
3131
- **`VB.NET: Restore Workspace`** — runs `dotnet restore` using the selected solution (or workspace root).
3232
- **`VB.NET: Restore Project`** — picks a `.vbproj` file and runs `dotnet restore` for it.
3333
- **`VB.NET: Reload Workspace`** — requests a workspace reload without restarting the extension.
34+
- **`VB.NET: Attach Debugger to Process`** — picks a running process and starts an attach session.
3435

3536
### Explorer File Nesting Defaults
3637

src/extension/package.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,11 @@
247247
"title": "Reload VB.NET Workspace",
248248
"category": "VB.NET"
249249
},
250+
{
251+
"command": "vbnet.attachToProcess",
252+
"title": "Attach VB.NET Debugger to Process",
253+
"category": "VB.NET"
254+
},
250255
{
251256
"command": "vbnet.showOutputChannel",
252257
"title": "Show VB.NET Output",
@@ -384,6 +389,20 @@
384389
"stopAtEntry": false
385390
}
386391
},
392+
{
393+
"label": "VB.NET: Launch (projectPath inference)",
394+
"description": "Launch using projectPath and let the extension infer the program.",
395+
"body": {
396+
"name": "VB.NET Launch (projectPath)",
397+
"type": "vbnet",
398+
"request": "launch",
399+
"projectPath": "${workspaceFolder}/YourProject.vbproj",
400+
"args": [],
401+
"cwd": "${workspaceFolder}",
402+
"console": "internalConsole",
403+
"stopAtEntry": false
404+
}
405+
},
387406
{
388407
"label": "VB.NET: Attach (netcoredbg)",
389408
"description": "Attach netcoredbg to a running .NET process.",

src/extension/src/extension.ts

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,18 @@ function registerCommands(context: vscode.ExtensionContext): void {
259259
}
260260
})
261261
);
262+
263+
context.subscriptions.push(
264+
vscode.commands.registerCommand('vbnet.attachToProcess', async () => {
265+
try {
266+
await attachToProcess();
267+
} catch (error) {
268+
const message = error instanceof Error ? error.message : String(error);
269+
outputChannel?.appendLine(`Failed to attach to process: ${message}`);
270+
vscode.window.showErrorMessage(`Failed to attach to process: ${message}`);
271+
}
272+
})
273+
);
262274
}
263275

264276
interface SolutionPickItem extends vscode.QuickPickItem {
@@ -270,6 +282,16 @@ interface ProjectPickItem extends vscode.QuickPickItem {
270282
projectPath: string;
271283
}
272284

285+
interface ProcessInfo {
286+
pid: number;
287+
name: string;
288+
commandLine?: string;
289+
}
290+
291+
interface ProcessPickItem extends vscode.QuickPickItem {
292+
pid: number;
293+
}
294+
273295
async function selectWorkspaceSolution(): Promise<void> {
274296
const workspaceFolders = vscode.workspace.workspaceFolders;
275297
if (!workspaceFolders || workspaceFolders.length === 0) {
@@ -567,6 +589,122 @@ function fsPathExists(filePath: string): boolean {
567589
}
568590
}
569591

592+
async function attachToProcess(): Promise<void> {
593+
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
594+
const processes = await listProcesses();
595+
596+
if (processes.length === 0) {
597+
vscode.window.showInformationMessage('No running processes found.');
598+
return;
599+
}
600+
601+
processes.sort((a, b) => {
602+
const nameCompare = a.name.localeCompare(b.name);
603+
if (nameCompare !== 0) {
604+
return nameCompare;
605+
}
606+
return a.pid - b.pid;
607+
});
608+
609+
const items: ProcessPickItem[] = processes.map((proc) => ({
610+
label: `${proc.name} (${proc.pid})`,
611+
description: proc.commandLine,
612+
pid: proc.pid
613+
}));
614+
615+
const pick = await vscode.window.showQuickPick(items, {
616+
placeHolder: 'Select a process to attach netcoredbg',
617+
matchOnDescription: true
618+
});
619+
620+
if (!pick) {
621+
return;
622+
}
623+
624+
const attachConfig: vscode.DebugConfiguration = {
625+
name: `VB.NET Attach (${pick.pid})`,
626+
type: 'vbnet',
627+
request: 'attach',
628+
processId: pick.pid
629+
};
630+
631+
const started = await vscode.debug.startDebugging(workspaceFolder, attachConfig);
632+
if (!started) {
633+
vscode.window.showErrorMessage('Failed to start VB.NET attach session.');
634+
}
635+
}
636+
637+
async function listProcesses(): Promise<ProcessInfo[]> {
638+
if (process.platform === 'win32') {
639+
return await listWindowsProcesses();
640+
}
641+
642+
return await listUnixProcesses();
643+
}
644+
645+
async function listWindowsProcesses(): Promise<ProcessInfo[]> {
646+
const output = await execCommand('tasklist', ['/FO', 'CSV', '/NH']);
647+
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
648+
const results: ProcessInfo[] = [];
649+
650+
for (const line of lines) {
651+
const values = line.split('","').map((value) => value.replace(/^"|"$/g, ''));
652+
if (values.length < 2) {
653+
continue;
654+
}
655+
const pid = Number.parseInt(values[1], 10);
656+
if (Number.isNaN(pid)) {
657+
continue;
658+
}
659+
results.push({
660+
pid,
661+
name: values[0],
662+
commandLine: values[0]
663+
});
664+
}
665+
666+
return results;
667+
}
668+
669+
async function listUnixProcesses(): Promise<ProcessInfo[]> {
670+
const output = await execCommand('ps', ['-ax', '-o', 'pid=,comm=,args=']);
671+
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
672+
const results: ProcessInfo[] = [];
673+
674+
for (const line of lines) {
675+
const match = line.match(/^(\d+)\s+(\S+)\s*(.*)$/);
676+
if (!match) {
677+
continue;
678+
}
679+
const pid = Number.parseInt(match[1], 10);
680+
if (Number.isNaN(pid)) {
681+
continue;
682+
}
683+
results.push({
684+
pid,
685+
name: match[2],
686+
commandLine: match[3]
687+
});
688+
}
689+
690+
return results;
691+
}
692+
693+
async function execCommand(command: string, args: string[]): Promise<string> {
694+
return await new Promise((resolve, reject) => {
695+
cp.execFile(command, args, { encoding: 'utf8' }, (error, stdout, stderr) => {
696+
if (error) {
697+
reject(error);
698+
return;
699+
}
700+
if (stderr) {
701+
outputChannel?.appendLine(stderr.trim());
702+
}
703+
resolve(stdout ?? '');
704+
});
705+
});
706+
}
707+
570708
/**
571709
* Extension deactivation.
572710
* Called when the extension is deactivated.

test-explore/TEST_RESULTS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ Optional flags:
4747

4848
## Recent runs
4949

50+
### 2026-01-22 — VS Code harness (VB.NET server, attach command)
51+
52+
Command:
53+
- `cd test-explore\\clients\\vscode; npm test`
54+
55+
Outcome: PASS (14 passing, 5 pending; non-fatal DAP warnings)
56+
Notes:
57+
- DAP warnings: `Failed command 'threads' : 0x80004005`.
58+
- DAP trace: `test-explore/clients/vscode/logs/dap-trace-2026-01-22T202246021Z.log`.
59+
5060
### 2026-01-22 — VS Code harness (VB.NET server, reload command)
5161

5262
Command:

test-explore/clients/vscode/src/suite/vbnet-extension.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ if (skipVbnetSmoke) {
280280
assert.ok(commands.includes("vbnet.restoreWorkspace"), "Restore workspace command not registered.");
281281
assert.ok(commands.includes("vbnet.restoreProject"), "Restore project command not registered.");
282282
assert.ok(commands.includes("vbnet.reloadWorkspace"), "Reload workspace command not registered.");
283+
assert.ok(commands.includes("vbnet.attachToProcess"), "Attach to process command not registered.");
283284
await config.update("trace.server", "verbose", vscode.ConfigurationTarget.Workspace);
284285
await config.update("server.transportType", "namedPipe", vscode.ConfigurationTarget.Workspace);
285286

test/VbNet.Extension.Tests.Vb/ExtensionManifestTests.vb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ Namespace VbNet.Extension.Tests
7171
"vbnet.toggleLspTrace",
7272
"vbnet.restoreWorkspace",
7373
"vbnet.restoreProject",
74-
"vbnet.reloadWorkspace"
74+
"vbnet.reloadWorkspace",
75+
"vbnet.attachToProcess"
7576
}
7677

7778
For Each commandName In required

0 commit comments

Comments
 (0)