debugger: Allow users to include PickProcessId in debug tasks and resolve Pid (#42913)

Closes #33286

This PR adds support for Zed's `$ZED_PICK_PID` command in debug
configurations, which allows users to select a process to attach to at
debug time. When this variable is present in a debug configuration, Zed
automatically opens a process picker modal.

Follow up for this will be integrating this variable in the task system
instead of just the debug configuration system.

Release Notes:

- Added `$ZED_PICK_PID` variable for debug configurations, allowing
users to select which process to attach the debugger to at runtime

---------

Co-authored-by: Remco Smits <djsmits12@gmail.com>
This commit is contained in:
Anthony Eid
2025-11-20 10:12:59 -05:00
committed by GitHub
co-authored by Remco Smits
parent e033829ef2
commit 56401fc99c
6 changed files with 413 additions and 90 deletions
+71 -2
View File
@@ -5,16 +5,23 @@ pub(crate) mod memory_view;
pub(crate) mod module_list;
pub mod stack_frame_list;
pub mod variable_list;
use std::{any::Any, ops::ControlFlow, path::PathBuf, sync::Arc, time::Duration};
use std::{
any::Any,
ops::ControlFlow,
path::PathBuf,
sync::{Arc, LazyLock},
time::Duration,
};
use crate::{
ToggleExpandItem,
attach_modal::{AttachModal, ModalIntent},
new_process_modal::resolve_path,
persistence::{self, DebuggerPaneItem, SerializedLayout},
session::running::memory_view::MemoryView,
};
use anyhow::{Context as _, Result, anyhow};
use anyhow::{Context as _, Result, anyhow, bail};
use breakpoint_list::BreakpointList;
use collections::{HashMap, IndexMap};
use console::Console;
@@ -56,6 +63,9 @@ use workspace::{
Workspace, item::TabContentParams, move_item, pane::Event,
};
static PROCESS_ID_PLACEHOLDER: LazyLock<String> =
LazyLock::new(|| task::VariableName::PickProcessId.template_value());
pub struct RunningState {
session: Entity<Session>,
thread_id: Option<ThreadId>,
@@ -653,6 +663,40 @@ impl RunningState {
}
}
pub(crate) fn contains_substring(config: &serde_json::Value, substring: &str) -> bool {
match config {
serde_json::Value::Object(obj) => obj
.values()
.any(|value| Self::contains_substring(value, substring)),
serde_json::Value::Array(array) => array
.iter()
.any(|value| Self::contains_substring(value, substring)),
serde_json::Value::String(s) => s.contains(substring),
_ => false,
}
}
pub(crate) fn substitute_process_id_in_config(config: &mut serde_json::Value, process_id: i32) {
match config {
serde_json::Value::Object(obj) => {
obj.values_mut().for_each(|value| {
Self::substitute_process_id_in_config(value, process_id);
});
}
serde_json::Value::Array(array) => {
array.iter_mut().for_each(|value| {
Self::substitute_process_id_in_config(value, process_id);
});
}
serde_json::Value::String(s) => {
if s.contains(PROCESS_ID_PLACEHOLDER.as_str()) {
*s = s.replace(PROCESS_ID_PLACEHOLDER.as_str(), &process_id.to_string());
}
}
_ => {}
}
}
pub(crate) fn relativize_paths(
key: Option<&str>,
config: &mut serde_json::Value,
@@ -955,6 +999,31 @@ impl RunningState {
Self::relativize_paths(None, &mut config, &task_context);
Self::substitute_variables_in_config(&mut config, &task_context);
if Self::contains_substring(&config, PROCESS_ID_PLACEHOLDER.as_str()) || label.as_ref().contains(PROCESS_ID_PLACEHOLDER.as_str()) {
let (tx, rx) = futures::channel::oneshot::channel::<Option<i32>>();
let weak_workspace_clone = weak_workspace.clone();
weak_workspace.update_in(cx, |workspace, window, cx| {
let project = workspace.project().clone();
workspace.toggle_modal(window, cx, |window, cx| {
AttachModal::new(
ModalIntent::ResolveProcessId(Some(tx)),
weak_workspace_clone,
project,
true,
window,
cx,
)
});
}).ok();
let Some(process_id) = rx.await.ok().flatten() else {
bail!("No process selected with config that contains {}", PROCESS_ID_PLACEHOLDER.as_str())
};
Self::substitute_process_id_in_config(&mut config, process_id);
}
let request_type = match dap_registry
.adapter(&adapter)
.with_context(|| format!("{}: is not a valid adapter name", &adapter)) {