mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
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:
parent
e033829ef2
commit
56401fc99c
6 changed files with 413 additions and 90 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -29,10 +29,13 @@ use ui::{
|
|||
KeyBinding, ListItem, ListItemSpacing, ToggleButtonGroup, ToggleButtonSimple, ToggleState,
|
||||
Tooltip, prelude::*,
|
||||
};
|
||||
use util::{ResultExt, rel_path::RelPath, shell::ShellKind};
|
||||
use util::{ResultExt, debug_panic, rel_path::RelPath, shell::ShellKind};
|
||||
use workspace::{ModalView, Workspace, notifications::DetachAndPromptErr, pane};
|
||||
|
||||
use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel};
|
||||
use crate::{
|
||||
attach_modal::{AttachModal, ModalIntent},
|
||||
debugger_panel::DebugPanel,
|
||||
};
|
||||
|
||||
pub(super) struct NewProcessModal {
|
||||
workspace: WeakEntity<Workspace>,
|
||||
|
|
@ -395,8 +398,15 @@ impl NewProcessModal {
|
|||
|
||||
this.attach_picker.update(cx, |this, cx| {
|
||||
this.picker.update(cx, |this, cx| {
|
||||
this.delegate.definition.adapter = adapter.0.clone();
|
||||
this.focus(window, cx);
|
||||
match &mut this.delegate.intent {
|
||||
ModalIntent::AttachToProcess(definition) => {
|
||||
definition.adapter = adapter.0.clone();
|
||||
this.focus(window, cx);
|
||||
},
|
||||
ModalIntent::ResolveProcessId(_) => {
|
||||
debug_panic!("Attach picker attempted to update config when in resolve Process ID mode");
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
|
@ -942,7 +952,14 @@ impl AttachMode {
|
|||
stop_on_entry: Some(false),
|
||||
};
|
||||
let attach_picker = cx.new(|cx| {
|
||||
let modal = AttachModal::new(definition.clone(), workspace, project, false, window, cx);
|
||||
let modal = AttachModal::new(
|
||||
ModalIntent::AttachToProcess(definition.clone()),
|
||||
workspace,
|
||||
project,
|
||||
false,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
window.focus(&modal.focus_handle(cx));
|
||||
|
||||
modal
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
use crate::{attach_modal::Candidate, tests::start_debug_session_with, *};
|
||||
use crate::{
|
||||
attach_modal::{Candidate, ModalIntent},
|
||||
tests::start_debug_session_with,
|
||||
*,
|
||||
};
|
||||
use attach_modal::AttachModal;
|
||||
use dap::{FakeAdapter, adapters::DebugTaskDefinition};
|
||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||
|
|
@ -98,12 +102,6 @@ async fn test_show_attach_modal_and_select_process(
|
|||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
AttachModal::with_processes(
|
||||
workspace_handle,
|
||||
task::ZedDebugConfig {
|
||||
adapter: FakeAdapter::ADAPTER_NAME.into(),
|
||||
request: dap::DebugRequest::Attach(AttachRequest::default()),
|
||||
label: "attach example".into(),
|
||||
stop_on_entry: None,
|
||||
},
|
||||
vec![
|
||||
Candidate {
|
||||
pid: 0,
|
||||
|
|
@ -124,6 +122,12 @@ async fn test_show_attach_modal_and_select_process(
|
|||
.into_iter()
|
||||
.collect(),
|
||||
true,
|
||||
ModalIntent::AttachToProcess(task::ZedDebugConfig {
|
||||
adapter: FakeAdapter::ADAPTER_NAME.into(),
|
||||
request: dap::DebugRequest::Attach(AttachRequest::default()),
|
||||
label: "attach example".into(),
|
||||
stop_on_entry: None,
|
||||
}),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
|
|
@ -138,8 +142,7 @@ async fn test_show_attach_modal_and_select_process(
|
|||
// 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));
|
||||
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| {
|
||||
|
|
@ -153,8 +156,7 @@ async fn test_show_attach_modal_and_select_process(
|
|||
// assert we got the expected processes
|
||||
workspace
|
||||
.update(cx, |_, _, cx| {
|
||||
let names =
|
||||
attach_modal.update(cx, |modal, cx| attach_modal::_process_names(modal, cx));
|
||||
let names = attach_modal.update(cx, |modal, cx| attach_modal::process_names(modal, cx));
|
||||
// Initially all processes are visible.
|
||||
assert_eq!(2, names.len());
|
||||
})
|
||||
|
|
@ -171,3 +173,139 @@ async fn test_show_attach_modal_and_select_process(
|
|||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_attach_with_pick_pid_variable(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(executor.clone());
|
||||
|
||||
fs.insert_tree(
|
||||
path!("/project"),
|
||||
json!({
|
||||
"main.rs": "First line\nSecond line\nThird line\nFourth line",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
|
||||
let workspace = init_test_workspace(&project, cx).await;
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
let _initialize_subscription =
|
||||
project::debugger::test::intercept_debug_sessions(cx, |client| {
|
||||
client.on_request::<dap::requests::Attach, _>(move |_, args| {
|
||||
let raw = &args.raw;
|
||||
assert_eq!(raw["request"], "attach");
|
||||
assert_eq!(
|
||||
raw["process_id"], "42",
|
||||
"verify process id has been replaced"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
});
|
||||
});
|
||||
|
||||
let pick_pid_placeholder = task::VariableName::PickProcessId.template_value();
|
||||
workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
workspace.start_debug_session(
|
||||
DebugTaskDefinition {
|
||||
adapter: FakeAdapter::ADAPTER_NAME.into(),
|
||||
label: "attach with picker".into(),
|
||||
config: json!({
|
||||
"request": "attach",
|
||||
"process_id": pick_pid_placeholder,
|
||||
}),
|
||||
tcp_connection: None,
|
||||
}
|
||||
.to_scenario(),
|
||||
task::TaskContext::default(),
|
||||
None,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
let attach_modal = workspace
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
workspace.active_modal::<AttachModal>(cx)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
attach_modal.is_some(),
|
||||
"Attach modal should open when config contains ZED_PICK_PID"
|
||||
);
|
||||
|
||||
let attach_modal = attach_modal.unwrap();
|
||||
|
||||
workspace
|
||||
.update(cx, |_, window, cx| {
|
||||
attach_modal.update(cx, |modal, cx| {
|
||||
attach_modal::set_candidates(
|
||||
modal,
|
||||
vec![
|
||||
Candidate {
|
||||
pid: 10,
|
||||
name: "process-1".into(),
|
||||
command: vec![],
|
||||
},
|
||||
Candidate {
|
||||
pid: 42,
|
||||
name: "target-process".into(),
|
||||
command: vec![],
|
||||
},
|
||||
Candidate {
|
||||
pid: 99,
|
||||
name: "process-3".into(),
|
||||
command: vec![],
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
workspace
|
||||
.update(cx, |_, window, cx| {
|
||||
attach_modal.update(cx, |modal, cx| {
|
||||
modal.picker.update(cx, |picker, cx| {
|
||||
picker.set_query("target", window, cx);
|
||||
})
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
workspace
|
||||
.update(cx, |_, _, cx| {
|
||||
let names = attach_modal.update(cx, |modal, cx| attach_modal::process_names(modal, cx));
|
||||
assert_eq!(names.len(), 1);
|
||||
assert_eq!(names[0], " 42 target-process");
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cx.dispatch_action(Confirm);
|
||||
cx.run_until_parked();
|
||||
|
||||
workspace
|
||||
.update(cx, |workspace, _window, cx| {
|
||||
assert!(
|
||||
workspace.active_modal::<AttachModal>(cx).is_none(),
|
||||
"Attach modal should be dismissed after selection"
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,6 +173,9 @@ pub enum VariableName {
|
|||
SelectedText,
|
||||
/// The symbol selected by the symbol tagging system, specifically the @run capture in a runnables.scm
|
||||
RunnableSymbol,
|
||||
/// Open a Picker to select a process ID to use in place
|
||||
/// Can only be used to debug configurations
|
||||
PickProcessId,
|
||||
/// Custom variable, provided by the plugin or other external source.
|
||||
/// Will be printed with `CUSTOM_` prefix to avoid potential conflicts with other variables.
|
||||
Custom(Cow<'static, str>),
|
||||
|
|
@ -240,6 +243,7 @@ impl std::fmt::Display for VariableName {
|
|||
Self::Column => write!(f, "{ZED_VARIABLE_NAME_PREFIX}COLUMN"),
|
||||
Self::SelectedText => write!(f, "{ZED_VARIABLE_NAME_PREFIX}SELECTED_TEXT"),
|
||||
Self::RunnableSymbol => write!(f, "{ZED_VARIABLE_NAME_PREFIX}RUNNABLE_SYMBOL"),
|
||||
Self::PickProcessId => write!(f, "{ZED_VARIABLE_NAME_PREFIX}PICK_PID"),
|
||||
Self::Custom(s) => write!(
|
||||
f,
|
||||
"{ZED_VARIABLE_NAME_PREFIX}{ZED_CUSTOM_VARIABLE_NAME_PREFIX}{s}"
|
||||
|
|
@ -346,15 +350,28 @@ pub fn shell_to_proto(shell: Shell) -> proto::Shell {
|
|||
}
|
||||
|
||||
type VsCodeEnvVariable = String;
|
||||
type VsCodeCommand = String;
|
||||
type ZedEnvVariable = String;
|
||||
|
||||
struct EnvVariableReplacer {
|
||||
variables: HashMap<VsCodeEnvVariable, ZedEnvVariable>,
|
||||
commands: HashMap<VsCodeCommand, ZedEnvVariable>,
|
||||
}
|
||||
|
||||
impl EnvVariableReplacer {
|
||||
fn new(variables: HashMap<VsCodeEnvVariable, ZedEnvVariable>) -> Self {
|
||||
Self { variables }
|
||||
Self {
|
||||
variables,
|
||||
commands: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_commands(
|
||||
mut self,
|
||||
commands: impl IntoIterator<Item = (VsCodeCommand, ZedEnvVariable)>,
|
||||
) -> Self {
|
||||
self.commands = commands.into_iter().collect();
|
||||
self
|
||||
}
|
||||
|
||||
fn replace_value(&self, input: serde_json::Value) -> serde_json::Value {
|
||||
|
|
@ -380,7 +397,13 @@ impl EnvVariableReplacer {
|
|||
if left == "env" && !right.is_empty() {
|
||||
let variable_name = &right[1..];
|
||||
return Some(format!("${{{variable_name}}}"));
|
||||
} else if left == "command" && !right.is_empty() {
|
||||
let command_name = &right[1..];
|
||||
if let Some(replacement_command) = self.commands.get(command_name) {
|
||||
return Some(format!("${{{replacement_command}}}"));
|
||||
}
|
||||
}
|
||||
|
||||
let (variable_name, default) = (left, right);
|
||||
let append_previous_default = |ret: &mut String| {
|
||||
if !default.is_empty() {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,11 @@ impl TryFrom<VsCodeDebugTaskFile> for DebugTaskFile {
|
|||
VariableName::RelativeFile.to_string(),
|
||||
),
|
||||
("file".to_owned(), VariableName::File.to_string()),
|
||||
]));
|
||||
]))
|
||||
.with_commands([(
|
||||
"pickMyProcess".to_owned(),
|
||||
VariableName::PickProcessId.to_string(),
|
||||
)]);
|
||||
let templates = file
|
||||
.configurations
|
||||
.into_iter()
|
||||
|
|
@ -96,7 +100,7 @@ fn task_type_to_adapter_name(task_type: &str) -> String {
|
|||
mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{DebugScenario, DebugTaskFile};
|
||||
use crate::{DebugScenario, DebugTaskFile, VariableName};
|
||||
|
||||
use super::VsCodeDebugTaskFile;
|
||||
|
||||
|
|
@ -152,4 +156,39 @@ mod tests {
|
|||
}])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_command_pickmyprocess_replacement() {
|
||||
let raw = r#"
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Attach to Process",
|
||||
"request": "attach",
|
||||
"type": "cppdbg",
|
||||
"processId": "${command:pickMyProcess}"
|
||||
}
|
||||
]
|
||||
}
|
||||
"#;
|
||||
let parsed: VsCodeDebugTaskFile =
|
||||
serde_json_lenient::from_str(raw).expect("deserializing launch.json");
|
||||
let zed = DebugTaskFile::try_from(parsed).expect("converting to Zed debug templates");
|
||||
|
||||
let expected_placeholder = format!("${{{}}}", VariableName::PickProcessId);
|
||||
pretty_assertions::assert_eq!(
|
||||
zed,
|
||||
DebugTaskFile(vec![DebugScenario {
|
||||
label: "Attach to Process".into(),
|
||||
adapter: "CodeLLDB".into(),
|
||||
config: json!({
|
||||
"request": "attach",
|
||||
"processId": expected_placeholder,
|
||||
}),
|
||||
tcp_connection: None,
|
||||
build: None
|
||||
}])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue