Skip to content

Commit e0f9531

Browse files
Add snippets for debug configurations (#1411)
1 parent fabf4b3 commit e0f9531

16 files changed

+524
-300
lines changed

package.json

+49-1
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,11 @@
271271
"title": "Run Until Failure...",
272272
"category": "Swift"
273273
},
274+
{
275+
"command": "swift.pickProcess",
276+
"title": "Pick Process...",
277+
"category": "Swift"
278+
},
274279
{
275280
"command": "swift.runAllTestsParallel",
276281
"title": "Run Tests in Parallel",
@@ -957,6 +962,10 @@
957962
"command": "swift.reindexProject",
958963
"when": "swift.supportsReindexing"
959964
},
965+
{
966+
"command": "swift.pickProcess",
967+
"when": "false"
968+
},
960969
{
961970
"command": "swift.runAllTestsParallel",
962971
"when": "swift.isActivated"
@@ -1358,6 +1367,9 @@
13581367
{
13591368
"type": "swift",
13601369
"label": "Swift Debugger",
1370+
"variables": {
1371+
"pickProcess": "swift.pickProcess"
1372+
},
13611373
"configurationAttributes": {
13621374
"launch": {
13631375
"required": [
@@ -1490,6 +1502,7 @@
14901502
},
14911503
"pid": {
14921504
"type": [
1505+
"string",
14931506
"number"
14941507
],
14951508
"description": "System process ID to attach to."
@@ -1560,7 +1573,42 @@
15601573
}
15611574
}
15621575
}
1563-
}
1576+
},
1577+
"configurationSnippets": [
1578+
{
1579+
"label": "Swift: Launch",
1580+
"description": "",
1581+
"body": {
1582+
"type": "swift",
1583+
"request": "launch",
1584+
"name": "${2:Launch Swift Executable}",
1585+
"program": "^\"\\${workspaceRoot}/.build/debug/${1:<program>}\"",
1586+
"args": [],
1587+
"env": {},
1588+
"cwd": "^\"\\${workspaceRoot}\""
1589+
}
1590+
},
1591+
{
1592+
"label": "Swift: Attach to Process",
1593+
"description": "",
1594+
"body": {
1595+
"type": "swift",
1596+
"request": "attach",
1597+
"name": "${1:Attach to Swift Executable}",
1598+
"pid": "^\"\\${command:pickProcess}\""
1599+
}
1600+
},
1601+
{
1602+
"label": "Swift: Attach",
1603+
"description": "",
1604+
"body": {
1605+
"type": "swift",
1606+
"request": "attach",
1607+
"name": "${2:Attach to Swift Executable}",
1608+
"program": "^\"\\${workspaceRoot}/.build/debug/${1:<program>}\""
1609+
}
1610+
}
1611+
]
15641612
}
15651613
]
15661614
},

src/commands.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { runAllTests } from "./commands/runAllTests";
4343
import { updateDependenciesViewList } from "./commands/dependencies/updateDepViewList";
4444
import { runTask } from "./commands/runTask";
4545
import { TestKind } from "./TestExplorer/TestKind";
46+
import { pickProcess } from "./commands/pickProcess";
4647

4748
/**
4849
* References:
@@ -65,6 +66,9 @@ export function registerToolchainCommands(
6566
vscode.commands.registerCommand("swift.selectToolchain", () =>
6667
showToolchainSelectionQuickPick(toolchain)
6768
),
69+
vscode.commands.registerCommand("swift.pickProcess", configuration =>
70+
pickProcess(configuration)
71+
),
6872
];
6973
}
7074

@@ -169,7 +173,7 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] {
169173
return openInExternalEditor(item);
170174
}
171175
}),
172-
vscode.commands.registerCommand("swift.attachDebugger", () => attachDebugger(ctx)),
176+
vscode.commands.registerCommand("swift.attachDebugger", attachDebugger),
173177
vscode.commands.registerCommand("swift.clearDiagnosticsCollection", () =>
174178
ctx.diagnostics.clear()
175179
),

src/commands/attachDebugger.ts

+7-18
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import * as vscode from "vscode";
16-
import { WorkspaceContext } from "../WorkspaceContext";
17-
import { getLldbProcess } from "../debugger/lldb";
1816
import { SWIFT_LAUNCH_CONFIG_TYPE } from "../debugger/debugAdapter";
1917

2018
/**
@@ -29,20 +27,11 @@ import { SWIFT_LAUNCH_CONFIG_TYPE } from "../debugger/debugAdapter";
2927
*
3028
* @throws Will display an error message if no processes are available, or if the debugger fails to attach to the selected process.
3129
*/
32-
export async function attachDebugger(ctx: WorkspaceContext) {
33-
const processPickItems = await getLldbProcess(ctx);
34-
if (processPickItems !== undefined) {
35-
const picked = await vscode.window.showQuickPick(processPickItems, {
36-
placeHolder: "Select Process",
37-
});
38-
if (picked) {
39-
const debugConfig: vscode.DebugConfiguration = {
40-
type: SWIFT_LAUNCH_CONFIG_TYPE,
41-
request: "attach",
42-
name: "Attach",
43-
pid: picked.pid,
44-
};
45-
await vscode.debug.startDebugging(undefined, debugConfig);
46-
}
47-
}
30+
export async function attachDebugger() {
31+
await vscode.debug.startDebugging(undefined, {
32+
type: SWIFT_LAUNCH_CONFIG_TYPE,
33+
request: "attach",
34+
name: "Attach",
35+
pid: "${command:pickProcess}",
36+
});
4837
}

src/commands/pickProcess.ts

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the VS Code Swift open source project
4+
//
5+
// Copyright (c) 2025 the VS Code Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import * as path from "path";
16+
import * as vscode from "vscode";
17+
import { createProcessList } from "../process-list";
18+
19+
interface ProcessQuickPick extends vscode.QuickPickItem {
20+
processId?: number;
21+
}
22+
23+
/**
24+
* Prompts the user to select a running process.
25+
*
26+
* The return value must be a string so that it is compatible with VS Code's
27+
* string substitution infrastructure. The value will eventually be converted
28+
* to a number by the debug configuration provider.
29+
*
30+
* @param configuration The related debug configuration, if any
31+
* @returns The pid of the process as a string or undefined if cancelled.
32+
*/
33+
export async function pickProcess(
34+
configuration?: vscode.DebugConfiguration
35+
): Promise<string | undefined> {
36+
const processList = createProcessList();
37+
const selectedProcess = await vscode.window.showQuickPick<ProcessQuickPick>(
38+
processList.listAllProcesses().then((processes): ProcessQuickPick[] => {
39+
// Sort by start date in descending order
40+
processes.sort((a, b) => b.start - a.start);
41+
// Filter by program if requested
42+
if (typeof configuration?.program === "string") {
43+
const program = configuration.program;
44+
const programBaseName = path.basename(program);
45+
processes = processes
46+
.filter(proc => path.basename(proc.command) === programBaseName)
47+
.sort((a, b) => {
48+
// Bring exact command matches to the top
49+
const aIsExactMatch = a.command === program ? 1 : 0;
50+
const bIsExactMatch = b.command === program ? 1 : 0;
51+
return bIsExactMatch - aIsExactMatch;
52+
});
53+
// Show a better message if all processes were filtered out
54+
if (processes.length === 0) {
55+
return [
56+
{
57+
label: "No processes matched the debug configuration's program",
58+
},
59+
];
60+
}
61+
}
62+
// Convert to a QuickPickItem
63+
return processes.map(proc => {
64+
return {
65+
processId: proc.id,
66+
label: path.basename(proc.command),
67+
description: proc.id.toString(),
68+
detail: proc.arguments,
69+
} satisfies ProcessQuickPick;
70+
});
71+
}),
72+
{
73+
placeHolder: "Select a process to attach the debugger to",
74+
matchOnDetail: true,
75+
matchOnDescription: true,
76+
}
77+
);
78+
return selectedProcess?.processId?.toString();
79+
}

src/debugger/debugAdapterFactory.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration
7474
private outputChannel: SwiftOutputChannel
7575
) {}
7676

77-
async resolveDebugConfiguration(
77+
async resolveDebugConfigurationWithSubstitutedVariables(
7878
_folder: vscode.WorkspaceFolder | undefined,
7979
launchConfig: vscode.DebugConfiguration
8080
): Promise<vscode.DebugConfiguration | undefined | null> {

src/debugger/lldb.ts

+1-46
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717

1818
import * as path from "path";
1919
import * as fs from "fs/promises";
20-
import * as vscode from "vscode";
21-
import { WorkspaceContext } from "../WorkspaceContext";
22-
import { execFile, getErrorDescription } from "../utilities/utilities";
20+
import { execFile } from "../utilities/utilities";
2321
import { Result } from "../utilities/result";
2422
import { SwiftToolchain } from "../toolchain/toolchain";
2523

@@ -114,46 +112,3 @@ export async function findFileByPattern(path: string, pattern: RegExp): Promise<
114112
}
115113
return null;
116114
}
117-
118-
/**
119-
* Retrieves a list of LLDB processes from the system using LLDB.
120-
*
121-
* This function executes an LLDB command to list all processes on the system,
122-
* including their arguments, and returns them in an array of objects where each
123-
* object contains the `pid` and a `label` describing the process.
124-
*
125-
* @param {WorkspaceContext} ctx - The workspace context, which includes the toolchain needed to run LLDB.
126-
* @returns {Promise<Array<{ pid: number; label: string }> | undefined>}
127-
* A promise that resolves to an array of processes, where each process is represented by an object with a `pid` and a `label`.
128-
* If an error occurs or no processes are found, it returns `undefined`.
129-
*
130-
* @throws Will display an error message in VS Code if the LLDB command fails.
131-
*/
132-
export async function getLldbProcess(
133-
ctx: WorkspaceContext
134-
): Promise<Array<{ pid: number; label: string }> | undefined> {
135-
try {
136-
// use LLDB to get list of processes
137-
const lldb = await ctx.toolchain.getLLDB();
138-
const { stdout } = await execFile(lldb, [
139-
"--batch",
140-
"--no-lldbinit",
141-
"--one-line",
142-
"platform process list --show-args --all-users",
143-
]);
144-
const entries = stdout.split("\n");
145-
const processes = entries.flatMap(line => {
146-
const match = /^(\d+)\s+\d+\s+\S+\s+\S+\s+(.+)$/.exec(line);
147-
if (match) {
148-
return [{ pid: parseInt(match[1]), label: `${match[1]}: ${match[2]}` }];
149-
} else {
150-
return [];
151-
}
152-
});
153-
return processes;
154-
} catch (error) {
155-
const errorMessage = `Failed to run LLDB: ${getErrorDescription(error)}`;
156-
ctx.outputChannel.log(errorMessage);
157-
vscode.window.showErrorMessage(errorMessage);
158-
}
159-
}

src/process-list/BaseProcessList.ts

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the VS Code Swift open source project
4+
//
5+
// Copyright (c) 2025 the VS Code Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import * as util from "util";
16+
import * as child_process from "child_process";
17+
import { Process, ProcessList } from ".";
18+
19+
const exec = util.promisify(child_process.execFile);
20+
21+
/** Parses process information from a given line of process output. */
22+
export type ProcessListParser = (line: string) => Process | undefined;
23+
24+
/**
25+
* Implements common behavior between the different {@link ProcessList} implementations.
26+
*/
27+
export abstract class BaseProcessList implements ProcessList {
28+
/**
29+
* Get the command responsible for collecting all processes on the system.
30+
*/
31+
protected abstract getCommand(): string;
32+
33+
/**
34+
* Get the list of arguments used to launch the command.
35+
*/
36+
protected abstract getCommandArguments(): string[];
37+
38+
/**
39+
* Create a new parser that can read the process information from stdout of the process
40+
* spawned by {@link spawnProcess spawnProcess()}.
41+
*/
42+
protected abstract createParser(): ProcessListParser;
43+
44+
async listAllProcesses(): Promise<Process[]> {
45+
const execCommand = exec(this.getCommand(), this.getCommandArguments());
46+
const parser = this.createParser();
47+
return (await execCommand).stdout.split("\n").flatMap(line => {
48+
const process = parser(line.toString());
49+
if (!process || process.id === execCommand.child.pid) {
50+
return [];
51+
}
52+
return [process];
53+
});
54+
}
55+
}

0 commit comments

Comments
 (0)