debugger: Always show process list in attach (#28685)

Closes #ISSUE

Release Notes:

- N/A
This commit is contained in:
Piotr Osiewicz 2025-04-15 14:13:19 +02:00 committed by GitHub
parent d4a985a6e3
commit 98d001bad5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 78 additions and 112 deletions

2
Cargo.lock generated
View file

@ -4001,7 +4001,6 @@ dependencies = [
"node_runtime",
"parking_lot",
"paths",
"regex",
"schemars",
"serde",
"serde_json",
@ -4033,7 +4032,6 @@ dependencies = [
"gpui",
"language",
"paths",
"regex",
"serde",
"serde_json",
"task",

View file

@ -39,7 +39,6 @@ log.workspace = true
node_runtime.workspace = true
parking_lot.workspace = true
paths.workspace = true
regex.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true

View file

@ -20,7 +20,7 @@ use std::{
net::Ipv4Addr,
ops::Deref,
path::PathBuf,
sync::{Arc, LazyLock},
sync::Arc,
};
use task::{DebugAdapterConfig, DebugTaskDefinition};
use util::ResultExt;
@ -291,14 +291,7 @@ pub trait DebugAdapter: 'static + Send + Sync {
/// Should return base configuration to make the debug adapter work
fn request_args(&self, config: &DebugTaskDefinition) -> Value;
fn attach_processes_filter(&self) -> regex::Regex {
EMPTY_REGEX.clone()
}
}
static EMPTY_REGEX: LazyLock<regex::Regex> =
LazyLock::new(|| regex::Regex::new("").expect("Regex compilation to succeed"));
#[cfg(any(test, feature = "test-support"))]
pub struct FakeAdapter {}
@ -375,10 +368,4 @@ impl DebugAdapter for FakeAdapter {
},
})
}
fn attach_processes_filter(&self) -> regex::Regex {
static REGEX: LazyLock<regex::Regex> =
LazyLock::new(|| regex::Regex::new("^fake-binary").unwrap());
REGEX.clone()
}
}

View file

@ -8,7 +8,7 @@ struct DapRegistryState {
adapters: BTreeMap<DebugAdapterName, Arc<dyn DebugAdapter>>,
}
#[derive(Default)]
#[derive(Clone, Default)]
/// Stores available debug adapters.
pub struct DapRegistry(Arc<RwLock<DapRegistryState>>);

View file

@ -27,7 +27,6 @@ dap.workspace = true
gpui.workspace = true
language.workspace = true
paths.workspace = true
regex.workspace = true
serde.workspace = true
serde_json.workspace = true
task.workspace = true

View file

@ -31,7 +31,7 @@ pub fn init(registry: Arc<DapRegistry>) {
registry.add_adapter(Arc::from(CodeLldbDebugAdapter::default()));
registry.add_adapter(Arc::from(PythonDebugAdapter));
registry.add_adapter(Arc::from(PhpDebugAdapter));
registry.add_adapter(Arc::from(JsDebugAdapter::default()));
registry.add_adapter(Arc::from(JsDebugAdapter));
registry.add_adapter(Arc::from(LldbDebugAdapter));
registry.add_adapter(Arc::from(GoDebugAdapter));
registry.add_adapter(Arc::from(GdbDebugAdapter));

View file

@ -1,24 +1,13 @@
use adapters::latest_github_release;
use gpui::AsyncApp;
use regex::Regex;
use std::path::PathBuf;
use task::{DebugRequestType, DebugTaskDefinition};
use crate::*;
#[derive(Debug)]
pub(crate) struct JsDebugAdapter {
attach_processes: Regex,
}
pub(crate) struct JsDebugAdapter;
impl Default for JsDebugAdapter {
fn default() -> Self {
Self {
attach_processes: Regex::new(r"(?i)^(?:node|bun|iojs)(?:$|\b)")
.expect("Regex compilation to succeed"),
}
}
}
impl JsDebugAdapter {
const ADAPTER_NAME: &'static str = "JavaScript";
const ADAPTER_NPM_NAME: &'static str = "vscode-js-debug";
@ -149,8 +138,4 @@ impl DebugAdapter for JsDebugAdapter {
}
args
}
fn attach_processes_filter(&self) -> Regex {
self.attach_processes.clone()
}
}

View file

@ -4,7 +4,6 @@ use gpui::Subscription;
use gpui::{DismissEvent, Entity, EventEmitter, Focusable, Render};
use picker::{Picker, PickerDelegate};
use std::cell::LazyCell;
use std::sync::Arc;
use sysinfo::System;
use ui::{Context, Tooltip, prelude::*};
@ -24,7 +23,7 @@ pub(crate) struct AttachModalDelegate {
matches: Vec<StringMatch>,
placeholder_text: Arc<str>,
project: Entity<project::Project>,
debug_config: task::DebugTaskDefinition,
pub(crate) debug_config: task::DebugTaskDefinition,
candidates: Arc<[Candidate]>,
}
@ -58,7 +57,7 @@ impl AttachModal {
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let mut processes: Vec<_> = System::new_all()
let mut processes: Box<[_]> = System::new_all()
.processes()
.values()
.map(|process| {
@ -75,30 +74,18 @@ impl AttachModal {
})
.collect();
processes.sort_by_key(|k| k.name.clone());
let processes = processes.into_iter().collect();
Self::with_processes(project, debug_config, processes, modal, window, cx)
}
pub(super) fn with_processes(
project: Entity<project::Project>,
debug_config: task::DebugTaskDefinition,
processes: Vec<Candidate>,
processes: Arc<[Candidate]>,
modal: bool,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let adapter = project
.read(cx)
.debug_adapters()
.adapter(&debug_config.adapter);
let filter = LazyCell::new(|| adapter.map(|adapter| adapter.attach_processes_filter()));
let processes = processes
.into_iter()
.filter(|process| {
filter
.as_ref()
.map_or(false, |filter| filter.is_match(&process.name))
})
.collect();
let picker = cx.new(|cx| {
Picker::uniform_list(
AttachModalDelegate::new(project, debug_config, processes),
@ -117,9 +104,10 @@ impl AttachModal {
}
impl Render for AttachModal {
fn render(&mut self, _window: &mut Window, _: &mut Context<Self>) -> impl ui::IntoElement {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
v_flex()
.key_context("AttachModal")
.track_focus(&self.focus_handle(cx))
.w(rems(34.))
.child(self.picker.clone())
}

View file

@ -11,6 +11,7 @@ use gpui::{
App, AppContext, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, TextStyle,
WeakEntity,
};
use project::Project;
use settings::Settings;
use task::{DebugTaskDefinition, LaunchConfig};
use theme::ThemeSettings;
@ -59,7 +60,7 @@ impl NewSessionModal {
debug_panel: WeakEntity<DebugPanel>,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut App,
cx: &mut Context<Self>,
) -> Self {
let debugger = past_debug_definition
.as_ref()
@ -171,25 +172,13 @@ impl NewSessionModal {
attach.update(cx, |this, cx| {
if selected_debugger != this.debug_definition.adapter {
this.debug_definition.adapter = selected_debugger.into();
if let Some(project) = this
.workspace
.read_with(cx, |workspace, _| workspace.project().clone())
.ok()
{
this.attach_picker = Some(cx.new(|cx| {
let modal = AttachModal::new(
project,
this.debug_definition.clone(),
false,
window,
cx,
);
window.focus(&modal.focus_handle(cx));
modal
}));
}
this.attach_picker.update(cx, |this, cx| {
this.picker.update(cx, |this, cx| {
this.delegate.debug_config.adapter = selected_debugger.into();
this.focus(window, cx);
})
});
}
cx.notify();
@ -256,7 +245,6 @@ impl NewSessionModal {
ContextMenu::build(window, cx, move |mut menu, _, cx| {
let setter_for_name = |task: DebugTaskDefinition| {
let weak = weak.clone();
let workspace = workspace.clone();
move |window: &mut Window, cx: &mut App| {
weak.update(cx, |this, cx| {
this.last_selected_profile_name = Some(SharedString::from(&task.label));
@ -271,12 +259,19 @@ impl NewSessionModal {
);
}
DebugRequestType::Attach(_) => {
let Ok(project) = this
.workspace
.read_with(cx, |this, _| this.project().clone())
else {
return;
};
this.mode = NewSessionMode::attach(
this.debugger.clone(),
workspace.clone(),
project,
window,
cx,
);
this.mode.focus_handle(cx).focus(window);
if let Some((debugger, attach)) =
this.debugger.as_ref().zip(this.mode.as_attach())
{
@ -365,18 +360,16 @@ impl LaunchMode {
#[derive(Clone)]
struct AttachMode {
workspace: WeakEntity<Workspace>,
debug_definition: DebugTaskDefinition,
attach_picker: Option<Entity<AttachModal>>,
focus_handle: FocusHandle,
attach_picker: Entity<AttachModal>,
}
impl AttachMode {
fn new(
debugger: Option<SharedString>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
window: &mut Window,
cx: &mut App,
cx: &mut Context<NewSessionModal>,
) -> Entity<Self> {
let debug_definition = DebugTaskDefinition {
label: "Attach New Session Setup".into(),
@ -387,27 +380,15 @@ impl AttachMode {
initialize_args: None,
stop_on_entry: Some(false),
};
let attach_picker = cx.new(|cx| {
let modal = AttachModal::new(project, debug_definition.clone(), false, window, cx);
window.focus(&modal.focus_handle(cx));
let attach_picker = if let Some(project) = debugger.and(
workspace
.read_with(cx, |workspace, _| workspace.project().clone())
.ok(),
) {
Some(cx.new(|cx| {
let modal = AttachModal::new(project, debug_definition.clone(), false, window, cx);
window.focus(&modal.focus_handle(cx));
modal
}))
} else {
None
};
cx.new(|cx| Self {
workspace,
modal
});
cx.new(|_| Self {
debug_definition,
attach_picker,
focus_handle: cx.focus_handle(),
})
}
fn debug_task(&self) -> task::AttachConfig {
@ -444,7 +425,7 @@ impl Focusable for NewSessionMode {
fn focus_handle(&self, cx: &App) -> FocusHandle {
match &self {
NewSessionMode::Launch(entity) => entity.read(cx).program.focus_handle(cx),
NewSessionMode::Attach(entity) => entity.read(cx).focus_handle.clone(),
NewSessionMode::Attach(entity) => entity.read(cx).attach_picker.focus_handle(cx),
}
}
}
@ -476,8 +457,11 @@ impl RenderOnce for LaunchMode {
}
impl RenderOnce for AttachMode {
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
v_flex().w_full().children(self.attach_picker.clone())
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
v_flex()
.w_full()
.track_focus(&self.attach_picker.focus_handle(cx))
.child(self.attach_picker.clone())
}
}
@ -497,13 +481,17 @@ impl RenderOnce for NewSessionMode {
impl NewSessionMode {
fn attach(
debugger: Option<SharedString>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
window: &mut Window,
cx: &mut App,
cx: &mut Context<NewSessionModal>,
) -> Self {
Self::Attach(AttachMode::new(debugger, workspace, window, cx))
Self::Attach(AttachMode::new(debugger, project, window, cx))
}
fn launch(past_launch_config: Option<LaunchConfig>, window: &mut Window, cx: &mut App) -> Self {
fn launch(
past_launch_config: Option<LaunchConfig>,
window: &mut Window,
cx: &mut Context<NewSessionModal>,
) -> Self {
Self::Launch(LaunchMode::new(past_launch_config, window, cx))
}
}
@ -592,18 +580,25 @@ impl Render for NewSessionModal {
.toggle_state(matches!(self.mode, NewSessionMode::Attach(_)))
.style(ui::ButtonStyle::Subtle)
.on_click(cx.listener(|this, _, window, cx| {
let Ok(project) = this
.workspace
.read_with(cx, |this, _| this.project().clone())
else {
return;
};
this.mode = NewSessionMode::attach(
this.debugger.clone(),
this.workspace.clone(),
project,
window,
cx,
);
this.mode.focus_handle(cx).focus(window);
if let Some((debugger, attach)) =
this.debugger.as_ref().zip(this.mode.as_attach())
{
Self::update_attach_picker(&attach, &debugger, window, cx);
}
this.mode.focus_handle(cx).focus(window);
cx.notify();
}))
.last(),

View file

@ -100,7 +100,7 @@ async fn test_show_attach_modal_and_select_process(
},
Candidate {
pid: 3,
name: "non-fake-binary-1".into(),
name: "real-binary-1".into(),
command: vec![],
},
Candidate {
@ -108,7 +108,9 @@ async fn test_show_attach_modal_and_select_process(
name: "fake-binary-2".into(),
command: vec![],
},
],
]
.into_iter()
.collect(),
true,
window,
cx,
@ -121,17 +123,30 @@ async fn test_show_attach_modal_and_select_process(
cx.run_until_parked();
// assert we got the expected processes
workspace
.update(cx, |_, window, cx| {
let names =
attach_modal.update(cx, |modal, cx| attach_modal::_process_names(&modal, cx));
// Initially all processes are visible.
assert_eq!(3, names.len());
attach_modal.update(cx, |this, cx| {
this.picker.update(cx, |this, cx| {
this.set_query("fakb", window, cx);
})
})
})
.unwrap();
cx.run_until_parked();
// assert we got the expected processes
workspace
.update(cx, |_, _, cx| {
let names =
attach_modal.update(cx, |modal, cx| attach_modal::_process_names(&modal, cx));
// we filtered out all processes that are not starting with `fake-binary`
// Initially all processes are visible.
assert_eq!(2, names.len());
})
.unwrap();
// select the only existing process
cx.dispatch_action(Confirm);