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:
co-authored by
Remco Smits
parent
e033829ef2
commit
56401fc99c
@@ -1,4 +1,5 @@
|
||||
use dap::{DapRegistry, DebugRequest};
|
||||
use futures::channel::oneshot;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{AppContext, DismissEvent, Entity, EventEmitter, Focusable, Render, Task};
|
||||
use gpui::{Subscription, WeakEntity};
|
||||
@@ -9,6 +10,7 @@ use task::ZedDebugConfig;
|
||||
use util::debug_panic;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use sysinfo::{ProcessRefreshKind, RefreshKind, System, UpdateKind};
|
||||
use ui::{Context, Tooltip, prelude::*};
|
||||
use ui::{ListItem, ListItemSpacing};
|
||||
@@ -23,11 +25,16 @@ pub(super) struct Candidate {
|
||||
pub(super) command: Vec<String>,
|
||||
}
|
||||
|
||||
pub(crate) enum ModalIntent {
|
||||
ResolveProcessId(Option<oneshot::Sender<Option<i32>>>),
|
||||
AttachToProcess(ZedDebugConfig),
|
||||
}
|
||||
|
||||
pub(crate) struct AttachModalDelegate {
|
||||
selected_index: usize,
|
||||
matches: Vec<StringMatch>,
|
||||
placeholder_text: Arc<str>,
|
||||
pub(crate) definition: ZedDebugConfig,
|
||||
pub(crate) intent: ModalIntent,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
candidates: Arc<[Candidate]>,
|
||||
}
|
||||
@@ -35,13 +42,13 @@ pub(crate) struct AttachModalDelegate {
|
||||
impl AttachModalDelegate {
|
||||
fn new(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
definition: ZedDebugConfig,
|
||||
intent: ModalIntent,
|
||||
candidates: Arc<[Candidate]>,
|
||||
) -> Self {
|
||||
Self {
|
||||
workspace,
|
||||
definition,
|
||||
candidates,
|
||||
intent,
|
||||
selected_index: 0,
|
||||
matches: Vec::default(),
|
||||
placeholder_text: Arc::from("Select the process you want to attach the debugger to"),
|
||||
@@ -55,8 +62,8 @@ pub struct AttachModal {
|
||||
}
|
||||
|
||||
impl AttachModal {
|
||||
pub fn new(
|
||||
definition: ZedDebugConfig,
|
||||
pub(crate) fn new(
|
||||
intent: ModalIntent,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
modal: bool,
|
||||
@@ -65,7 +72,7 @@ impl AttachModal {
|
||||
) -> Self {
|
||||
let processes_task = get_processes_for_project(&project, cx);
|
||||
|
||||
let modal = Self::with_processes(workspace, definition, Arc::new([]), modal, window, cx);
|
||||
let modal = Self::with_processes(workspace, Arc::new([]), modal, intent, window, cx);
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let processes = processes_task.await;
|
||||
@@ -84,15 +91,15 @@ impl AttachModal {
|
||||
|
||||
pub(super) fn with_processes(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
definition: ZedDebugConfig,
|
||||
processes: Arc<[Candidate]>,
|
||||
modal: bool,
|
||||
intent: ModalIntent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let picker = cx.new(|cx| {
|
||||
Picker::uniform_list(
|
||||
AttachModalDelegate::new(workspace, definition, processes),
|
||||
AttachModalDelegate::new(workspace, intent, processes),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
@@ -207,7 +214,7 @@ impl PickerDelegate for AttachModalDelegate {
|
||||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
fn confirm(&mut self, _secondary: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
let candidate = self
|
||||
.matches
|
||||
.get(self.selected_index())
|
||||
@@ -216,69 +223,86 @@ impl PickerDelegate for AttachModalDelegate {
|
||||
self.candidates.get(ix)
|
||||
});
|
||||
|
||||
let Some(candidate) = candidate else {
|
||||
return cx.emit(DismissEvent);
|
||||
};
|
||||
|
||||
match &mut self.definition.request {
|
||||
DebugRequest::Attach(config) => {
|
||||
config.process_id = Some(candidate.pid);
|
||||
}
|
||||
DebugRequest::Launch(_) => {
|
||||
debug_panic!("Debugger attach modal used on launch debug config");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let workspace = self.workspace.clone();
|
||||
let Some(panel) = workspace
|
||||
.update(cx, |workspace, cx| workspace.panel::<DebugPanel>(cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if secondary {
|
||||
// let Some(id) = worktree_id else { return };
|
||||
// cx.spawn_in(window, async move |_, cx| {
|
||||
// panel
|
||||
// .update_in(cx, |debug_panel, window, cx| {
|
||||
// debug_panel.save_scenario(&debug_scenario, id, window, cx)
|
||||
// })?
|
||||
// .await?;
|
||||
// anyhow::Ok(())
|
||||
// })
|
||||
// .detach_and_log_err(cx);
|
||||
}
|
||||
let Some(adapter) = cx.read_global::<DapRegistry, _>(|registry, _| {
|
||||
registry.adapter(&self.definition.adapter)
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let definition = self.definition.clone();
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let Ok(scenario) = adapter.config_from_zed_format(definition).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
panel
|
||||
.update_in(cx, |panel, window, cx| {
|
||||
panel.start_session(scenario, Default::default(), None, None, window, cx);
|
||||
})
|
||||
.ok();
|
||||
this.update(cx, |_, cx| {
|
||||
match &mut self.intent {
|
||||
ModalIntent::ResolveProcessId(sender) => {
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
|
||||
if let Some(sender) = sender.take() {
|
||||
sender
|
||||
.send(candidate.map(|candidate| candidate.pid as i32))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
ModalIntent::AttachToProcess(definition) => {
|
||||
let Some(candidate) = candidate else {
|
||||
return cx.emit(DismissEvent);
|
||||
};
|
||||
|
||||
match &mut definition.request {
|
||||
DebugRequest::Attach(config) => {
|
||||
config.process_id = Some(candidate.pid);
|
||||
}
|
||||
DebugRequest::Launch(_) => {
|
||||
debug_panic!("Debugger attach modal used on launch debug config");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let workspace = self.workspace.clone();
|
||||
let Some(panel) = workspace
|
||||
.update(cx, |workspace, cx| workspace.panel::<DebugPanel>(cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(adapter) = cx.read_global::<DapRegistry, _>(|registry, _| {
|
||||
registry.adapter(&definition.adapter)
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let definition = definition.clone();
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let Ok(scenario) = adapter.config_from_zed_format(definition).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
panel
|
||||
.update_in(cx, |panel, window, cx| {
|
||||
panel.start_session(
|
||||
scenario,
|
||||
Default::default(),
|
||||
None,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
this.update(cx, |_, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
self.selected_index = 0;
|
||||
|
||||
match &mut self.intent {
|
||||
ModalIntent::ResolveProcessId(sender) => {
|
||||
if let Some(sender) = sender.take() {
|
||||
sender.send(None).ok();
|
||||
}
|
||||
}
|
||||
ModalIntent::AttachToProcess(_) => {}
|
||||
}
|
||||
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
|
||||
@@ -338,7 +362,7 @@ fn get_processes_for_project(project: &Entity<Project>, cx: &mut App) -> Task<Ar
|
||||
|
||||
if let Some(remote_client) = project.remote_client() {
|
||||
let proto_client = remote_client.read(cx).proto_client();
|
||||
cx.spawn(async move |_cx| {
|
||||
cx.background_spawn(async move {
|
||||
let response = proto_client
|
||||
.request(proto::GetProcesses {
|
||||
project_id: proto::REMOTE_SERVER_PROJECT_ID,
|
||||
@@ -389,8 +413,21 @@ fn get_processes_for_project(project: &Entity<Project>, cx: &mut App) -> Task<Ar
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(crate) fn _process_names(modal: &AttachModal, cx: &mut Context<AttachModal>) -> Vec<String> {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn set_candidates(
|
||||
modal: &AttachModal,
|
||||
candidates: Arc<[Candidate]>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<AttachModal>,
|
||||
) {
|
||||
modal.picker.update(cx, |picker, cx| {
|
||||
picker.delegate.candidates = candidates;
|
||||
picker.refresh(window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn process_names(modal: &AttachModal, cx: &mut Context<AttachModal>) -> Vec<String> {
|
||||
modal.picker.read_with(cx, |picker, _| {
|
||||
picker
|
||||
.delegate
|
||||
|
||||
Reference in New Issue
Block a user