Skip to content

Add snippets for debug configurations #1411

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,11 @@
"title": "Run Until Failure...",
"category": "Swift"
},
{
"command": "swift.pickProcess",
"title": "Pick Process...",
"category": "Swift"
},
{
"command": "swift.runAllTestsParallel",
"title": "Run Tests in Parallel",
Expand Down Expand Up @@ -957,6 +962,10 @@
"command": "swift.reindexProject",
"when": "swift.supportsReindexing"
},
{
"command": "swift.pickProcess",
"when": "false"
},
{
"command": "swift.runAllTestsParallel",
"when": "swift.isActivated"
Expand Down Expand Up @@ -1358,6 +1367,9 @@
{
"type": "swift",
"label": "Swift Debugger",
"variables": {
"pickProcess": "swift.pickProcess"
},
"configurationAttributes": {
"launch": {
"required": [
Expand Down Expand Up @@ -1490,6 +1502,7 @@
},
"pid": {
"type": [
"string",
"number"
],
"description": "System process ID to attach to."
Expand Down Expand Up @@ -1560,7 +1573,42 @@
}
}
}
}
},
"configurationSnippets": [
{
"label": "Swift: Launch",
"description": "",
"body": {
"type": "swift",
"request": "launch",
"name": "${2:Launch Swift Executable}",
"program": "^\"\\${workspaceRoot}/.build/debug/${1:<program>}\"",
"args": [],
"env": {},
"cwd": "^\"\\${workspaceRoot}\""
}
},
{
"label": "Swift: Attach to Process",
"description": "",
"body": {
"type": "swift",
"request": "attach",
"name": "${1:Attach to Swift Executable}",
"pid": "^\"\\${command:pickProcess}\""
}
},
{
"label": "Swift: Attach",
"description": "",
"body": {
"type": "swift",
"request": "attach",
"name": "${2:Attach to Swift Executable}",
"program": "^\"\\${workspaceRoot}/.build/debug/${1:<program>}\""
}
}
]
}
]
},
Expand Down
6 changes: 5 additions & 1 deletion src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { runAllTests } from "./commands/runAllTests";
import { updateDependenciesViewList } from "./commands/dependencies/updateDepViewList";
import { runTask } from "./commands/runTask";
import { TestKind } from "./TestExplorer/TestKind";
import { pickProcess } from "./commands/pickProcess";

/**
* References:
Expand All @@ -65,6 +66,9 @@ export function registerToolchainCommands(
vscode.commands.registerCommand("swift.selectToolchain", () =>
showToolchainSelectionQuickPick(toolchain)
),
vscode.commands.registerCommand("swift.pickProcess", configuration =>
pickProcess(configuration)
),
];
}

Expand Down Expand Up @@ -169,7 +173,7 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] {
return openInExternalEditor(item);
}
}),
vscode.commands.registerCommand("swift.attachDebugger", () => attachDebugger(ctx)),
vscode.commands.registerCommand("swift.attachDebugger", attachDebugger),
vscode.commands.registerCommand("swift.clearDiagnosticsCollection", () =>
ctx.diagnostics.clear()
),
Expand Down
25 changes: 7 additions & 18 deletions src/commands/attachDebugger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
//===----------------------------------------------------------------------===//

import * as vscode from "vscode";
import { WorkspaceContext } from "../WorkspaceContext";
import { getLldbProcess } from "../debugger/lldb";
import { SWIFT_LAUNCH_CONFIG_TYPE } from "../debugger/debugAdapter";

/**
Expand All @@ -29,20 +27,11 @@ import { SWIFT_LAUNCH_CONFIG_TYPE } from "../debugger/debugAdapter";
*
* @throws Will display an error message if no processes are available, or if the debugger fails to attach to the selected process.
*/
export async function attachDebugger(ctx: WorkspaceContext) {
const processPickItems = await getLldbProcess(ctx);
if (processPickItems !== undefined) {
const picked = await vscode.window.showQuickPick(processPickItems, {
placeHolder: "Select Process",
});
if (picked) {
const debugConfig: vscode.DebugConfiguration = {
type: SWIFT_LAUNCH_CONFIG_TYPE,
request: "attach",
name: "Attach",
pid: picked.pid,
};
await vscode.debug.startDebugging(undefined, debugConfig);
}
}
export async function attachDebugger() {
await vscode.debug.startDebugging(undefined, {
type: SWIFT_LAUNCH_CONFIG_TYPE,
request: "attach",
name: "Attach",
pid: "${command:pickProcess}",
});
}
79 changes: 79 additions & 0 deletions src/commands/pickProcess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the VS Code Swift open source project
//
// Copyright (c) 2025 the VS Code Swift project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import * as path from "path";
import * as vscode from "vscode";
import { createProcessList } from "../process-list";

interface ProcessQuickPick extends vscode.QuickPickItem {
processId?: number;
}

/**
* Prompts the user to select a running process.
*
* The return value must be a string so that it is compatible with VS Code's
* string substitution infrastructure. The value will eventually be converted
* to a number by the debug configuration provider.
*
* @param configuration The related debug configuration, if any
* @returns The pid of the process as a string or undefined if cancelled.
*/
export async function pickProcess(
configuration?: vscode.DebugConfiguration
): Promise<string | undefined> {
const processList = createProcessList();
const selectedProcess = await vscode.window.showQuickPick<ProcessQuickPick>(
processList.listAllProcesses().then((processes): ProcessQuickPick[] => {
// Sort by start date in descending order
processes.sort((a, b) => b.start - a.start);
// Filter by program if requested
if (typeof configuration?.program === "string") {
const program = configuration.program;
const programBaseName = path.basename(program);
processes = processes
.filter(proc => path.basename(proc.command) === programBaseName)
.sort((a, b) => {
// Bring exact command matches to the top
const aIsExactMatch = a.command === program ? 1 : 0;
const bIsExactMatch = b.command === program ? 1 : 0;
return bIsExactMatch - aIsExactMatch;
});
// Show a better message if all processes were filtered out
if (processes.length === 0) {
return [
{
label: "No processes matched the debug configuration's program",
},
];
}
}
// Convert to a QuickPickItem
return processes.map(proc => {
return {
processId: proc.id,
label: path.basename(proc.command),
description: proc.id.toString(),
detail: proc.arguments,
} satisfies ProcessQuickPick;
});
}),
{
placeHolder: "Select a process to attach the debugger to",
matchOnDetail: true,
matchOnDescription: true,
}
);
return selectedProcess?.processId?.toString();
}
2 changes: 1 addition & 1 deletion src/debugger/debugAdapterFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration
private outputChannel: SwiftOutputChannel
) {}

async resolveDebugConfiguration(
async resolveDebugConfigurationWithSubstitutedVariables(
_folder: vscode.WorkspaceFolder | undefined,
launchConfig: vscode.DebugConfiguration
): Promise<vscode.DebugConfiguration | undefined | null> {
Expand Down
47 changes: 1 addition & 46 deletions src/debugger/lldb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@

import * as path from "path";
import * as fs from "fs/promises";
import * as vscode from "vscode";
import { WorkspaceContext } from "../WorkspaceContext";
import { execFile, getErrorDescription } from "../utilities/utilities";
import { execFile } from "../utilities/utilities";
import { Result } from "../utilities/result";
import { SwiftToolchain } from "../toolchain/toolchain";

Expand Down Expand Up @@ -114,46 +112,3 @@ export async function findFileByPattern(path: string, pattern: RegExp): Promise<
}
return null;
}

/**
* Retrieves a list of LLDB processes from the system using LLDB.
*
* This function executes an LLDB command to list all processes on the system,
* including their arguments, and returns them in an array of objects where each
* object contains the `pid` and a `label` describing the process.
*
* @param {WorkspaceContext} ctx - The workspace context, which includes the toolchain needed to run LLDB.
* @returns {Promise<Array<{ pid: number; label: string }> | undefined>}
* A promise that resolves to an array of processes, where each process is represented by an object with a `pid` and a `label`.
* If an error occurs or no processes are found, it returns `undefined`.
*
* @throws Will display an error message in VS Code if the LLDB command fails.
*/
export async function getLldbProcess(
ctx: WorkspaceContext
): Promise<Array<{ pid: number; label: string }> | undefined> {
try {
// use LLDB to get list of processes
const lldb = await ctx.toolchain.getLLDB();
const { stdout } = await execFile(lldb, [
"--batch",
"--no-lldbinit",
"--one-line",
"platform process list --show-args --all-users",
]);
const entries = stdout.split("\n");
const processes = entries.flatMap(line => {
const match = /^(\d+)\s+\d+\s+\S+\s+\S+\s+(.+)$/.exec(line);
if (match) {
return [{ pid: parseInt(match[1]), label: `${match[1]}: ${match[2]}` }];
} else {
return [];
}
});
return processes;
} catch (error) {
const errorMessage = `Failed to run LLDB: ${getErrorDescription(error)}`;
ctx.outputChannel.log(errorMessage);
vscode.window.showErrorMessage(errorMessage);
}
}
55 changes: 55 additions & 0 deletions src/process-list/BaseProcessList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the VS Code Swift open source project
//
// Copyright (c) 2025 the VS Code Swift project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import * as util from "util";
import * as child_process from "child_process";
import { Process, ProcessList } from ".";

const exec = util.promisify(child_process.execFile);

/** Parses process information from a given line of process output. */
export type ProcessListParser = (line: string) => Process | undefined;

/**
* Implements common behavior between the different {@link ProcessList} implementations.
*/
export abstract class BaseProcessList implements ProcessList {
/**
* Get the command responsible for collecting all processes on the system.
*/
protected abstract getCommand(): string;

/**
* Get the list of arguments used to launch the command.
*/
protected abstract getCommandArguments(): string[];

/**
* Create a new parser that can read the process information from stdout of the process
* spawned by {@link spawnProcess spawnProcess()}.
*/
protected abstract createParser(): ProcessListParser;

async listAllProcesses(): Promise<Process[]> {
const execCommand = exec(this.getCommand(), this.getCommandArguments());
const parser = this.createParser();
return (await execCommand).stdout.split("\n").flatMap(line => {
const process = parser(line.toString());
if (!process || process.id === execCommand.child.pid) {
return [];
}
return [process];
});
}
}
Loading
Loading