diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index 85244cbb45..1669bb6011 100644 --- a/crates/dap_adapters/src/go.rs +++ b/crates/dap_adapters/src/go.rs @@ -1,4 +1,3 @@ -use anyhow::bail; use gpui::AsyncApp; use std::{ffi::OsStr, path::PathBuf}; use task::DebugTaskDefinition; @@ -63,9 +62,7 @@ impl DebugAdapter for GoDebugAdapter { .and_then(|p| p.to_str().map(|p| p.to_string())) .ok_or(anyhow!("Dlv not found in path"))?; - let Some(tcp_connection) = config.tcp_connection.clone() else { - bail!("Go Debug Adapter expects tcp connection arguments to be provided"); - }; + let tcp_connection = config.tcp_connection.clone().unwrap_or_default(); let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; Ok(DebugAdapterBinary { diff --git a/crates/dap_adapters/src/javascript.rs b/crates/dap_adapters/src/javascript.rs index 0506c96781..4a4b110f88 100644 --- a/crates/dap_adapters/src/javascript.rs +++ b/crates/dap_adapters/src/javascript.rs @@ -78,11 +78,7 @@ impl DebugAdapter for JsDebugAdapter { .ok_or_else(|| anyhow!("Couldn't find JavaScript dap directory"))? }; - let Some(tcp_connection) = config.tcp_connection.clone() else { - anyhow::bail!( - "Javascript Debug Adapter expects tcp connection arguments to be provided" - ); - }; + let tcp_connection = config.tcp_connection.clone().unwrap_or_default(); let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; Ok(DebugAdapterBinary { diff --git a/crates/dap_adapters/src/php.rs b/crates/dap_adapters/src/php.rs index c576d90c26..4ac8b93d76 100644 --- a/crates/dap_adapters/src/php.rs +++ b/crates/dap_adapters/src/php.rs @@ -1,5 +1,4 @@ use adapters::latest_github_release; -use anyhow::bail; use dap::adapters::TcpArguments; use gpui::AsyncApp; use std::path::PathBuf; @@ -69,9 +68,7 @@ impl DebugAdapter for PhpDebugAdapter { .ok_or_else(|| anyhow!("Couldn't find PHP dap directory"))? }; - let Some(tcp_connection) = config.tcp_connection.clone() else { - bail!("PHP Debug Adapter expects tcp connection arguments to be provided"); - }; + let tcp_connection = config.tcp_connection.clone().unwrap_or_default(); let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; Ok(DebugAdapterBinary { diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index d94648b210..afc77bdf15 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -1,5 +1,4 @@ use crate::*; -use anyhow::bail; use dap::DebugRequestType; use gpui::AsyncApp; use std::{ffi::OsStr, path::PathBuf}; @@ -70,9 +69,7 @@ impl DebugAdapter for PythonDebugAdapter { cx: &mut AsyncApp, ) -> Result { const BINARY_NAMES: [&str; 3] = ["python3", "python", "py"]; - let Some(tcp_connection) = config.tcp_connection.clone() else { - bail!("Python Debug Adapter expects tcp connection arguments to be provided"); - }; + let tcp_connection = config.tcp_connection.clone().unwrap_or_default(); let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?; let debugpy_dir = if let Some(user_installed_path) = user_installed_path { diff --git a/crates/debugger_ui/src/session/inert.rs b/crates/debugger_ui/src/session/inert.rs index dcbaa13522..4c3861176f 100644 --- a/crates/debugger_ui/src/session/inert.rs +++ b/crates/debugger_ui/src/session/inert.rs @@ -159,6 +159,8 @@ impl Render for InertState { }), tcp_connection: Some(TCPHost::default()), initialize_args: None, + args: Default::default(), + locator: None, }, }); } else { @@ -319,6 +321,8 @@ impl InertState { adapter: kind, request: DebugRequestType::Attach(task::AttachConfig { process_id: None }), initialize_args: None, + args: Default::default(), + locator: None, tcp_connection: Some(TCPHost::default()), }; diff --git a/crates/debugger_ui/src/tests/attach_modal.rs b/crates/debugger_ui/src/tests/attach_modal.rs index 0d6c7a3bbd..66f402706b 100644 --- a/crates/debugger_ui/src/tests/attach_modal.rs +++ b/crates/debugger_ui/src/tests/attach_modal.rs @@ -89,6 +89,8 @@ async fn test_show_attach_modal_and_select_process( label: "attach example".into(), initialize_args: None, tcp_connection: Some(TCPHost::default()), + locator: None, + args: Default::default(), }, vec![ Candidate { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cd0464e671..df3b3afc10 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4850,6 +4850,7 @@ impl Editor { } else { return None; }; + let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item); let action = actions_menu.actions.get(action_ix)?; let title = action.label(); @@ -4858,17 +4859,48 @@ impl Editor { match action { CodeActionsItem::Task(task_source_kind, resolved_task) => { - workspace.update(cx, |workspace, cx| { - workspace::tasks::schedule_resolved_task( - workspace, - task_source_kind, - resolved_task, - false, - cx, - ); + match resolved_task.task_type() { + task::TaskType::Script => workspace.update(cx, |workspace, cx| { + workspace::tasks::schedule_resolved_task( + workspace, + task_source_kind, + resolved_task, + false, + cx, + ); - Some(Task::ready(Ok(()))) - }) + Some(Task::ready(Ok(()))) + }), + task::TaskType::Debug(debug_args) => { + if debug_args.locator.is_some() { + workspace.update(cx, |workspace, cx| { + workspace::tasks::schedule_resolved_task( + workspace, + task_source_kind, + resolved_task, + false, + cx, + ); + }); + + return Some(Task::ready(Ok(()))); + } + + if let Some(project) = self.project.as_ref() { + project + .update(cx, |project, cx| { + project.start_debug_session( + resolved_task.resolved_debug_adapter_config().unwrap(), + cx, + ) + }) + .detach_and_log_err(cx); + Some(Task::ready(Ok(()))) + } else { + Some(Task::ready(Ok(()))) + } + } + } } CodeActionsItem::CodeAction { excerpt_id, @@ -8600,7 +8632,7 @@ impl Editor { let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row)); let anchor_end = snapshot .buffer_snapshot - .anchor_before(Point::new(row, line_len)); + .anchor_after(Point::new(row, line_len)); let bp = self .breakpoint_store diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index 146803c481..c4d0c2ada0 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -17,7 +17,7 @@ use std::{ path::{Path, PathBuf}, sync::{Arc, LazyLock}, }; -use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName}; +use task::{TaskTemplate, TaskTemplates, TaskType, TaskVariables, VariableName}; use util::{fs::remove_matching, maybe, ResultExt}; use crate::language_settings::language_settings; @@ -574,11 +574,16 @@ impl ContextProvider for RustContextProvider { .variables .get(CUSTOM_TARGET_DIR) .cloned(); - let run_task_args = if let Some(package_to_run) = package_to_run { + let run_task_args = if let Some(package_to_run) = package_to_run.clone() { vec!["run".into(), "-p".into(), package_to_run] } else { vec!["run".into()] }; + let debug_task_args = if let Some(package_to_run) = package_to_run { + vec!["build".into(), "-p".into(), package_to_run] + } else { + vec!["build".into()] + }; let mut task_templates = vec![ TaskTemplate { label: format!( @@ -620,6 +625,31 @@ impl ContextProvider for RustContextProvider { cwd: Some("$ZED_DIRNAME".to_owned()), ..TaskTemplate::default() }, + TaskTemplate { + label: format!( + "Debug Test '{}' (package: {})", + RUST_TEST_NAME_TASK_VARIABLE.template_value(), + RUST_PACKAGE_TASK_VARIABLE.template_value(), + ), + task_type: TaskType::Debug(task::DebugArgs { + adapter: "LLDB".to_owned(), + request: task::DebugArgsRequest::Launch, + locator: Some("cargo".into()), + tcp_connection: None, + initialize_args: None, + }), + command: "cargo".into(), + args: vec![ + "test".into(), + "-p".into(), + RUST_PACKAGE_TASK_VARIABLE.template_value(), + RUST_TEST_NAME_TASK_VARIABLE.template_value(), + "--no-run".into(), + ], + tags: vec!["rust-test".to_owned()], + cwd: Some("$ZED_DIRNAME".to_owned()), + ..TaskTemplate::default() + }, TaskTemplate { label: format!( "Doc test '{}' (package: {})", @@ -697,6 +727,21 @@ impl ContextProvider for RustContextProvider { cwd: Some("$ZED_DIRNAME".to_owned()), ..TaskTemplate::default() }, + TaskTemplate { + label: "Debug".into(), + cwd: Some("$ZED_DIRNAME".to_owned()), + command: "cargo".into(), + task_type: TaskType::Debug(task::DebugArgs { + request: task::DebugArgsRequest::Launch, + adapter: "LLDB".to_owned(), + initialize_args: None, + locator: Some("cargo".into()), + tcp_connection: None, + }), + args: debug_task_args, + tags: vec!["rust-main".to_owned()], + ..TaskTemplate::default() + }, TaskTemplate { label: "Clean".into(), command: "cargo".into(), diff --git a/crates/project/src/debugger.rs b/crates/project/src/debugger.rs index 695d5196ec..a01f63606a 100644 --- a/crates/project/src/debugger.rs +++ b/crates/project/src/debugger.rs @@ -14,4 +14,5 @@ pub mod breakpoint_store; pub mod dap_command; pub mod dap_store; +mod locator_store; pub mod session; diff --git a/crates/project/src/debugger/breakpoint_store.rs b/crates/project/src/debugger/breakpoint_store.rs index 5dbcd061e2..478e50b78c 100644 --- a/crates/project/src/debugger/breakpoint_store.rs +++ b/crates/project/src/debugger/breakpoint_store.rs @@ -12,7 +12,7 @@ use rpc::{ AnyProtoClient, TypedEnvelope, }; use std::{hash::Hash, ops::Range, path::Path, sync::Arc}; -use text::{Point, PointUtf16}; +use text::PointUtf16; use crate::{buffer_store::BufferStore, worktree_store::WorktreeStore, Project, ProjectPath}; @@ -494,7 +494,7 @@ impl BreakpointStore { this.update(cx, |_, cx| BreakpointsInFile::new(buffer, cx))?; for bp in bps { - let position = snapshot.anchor_after(Point::new(bp.row, 0)); + let position = snapshot.anchor_after(PointUtf16::new(bp.row, 0)); breakpoints_for_file.breakpoints.push(( position, Breakpoint { diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index 25bc25a75a..2d9b7db4ca 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -1,11 +1,6 @@ use super::{ breakpoint_store::BreakpointStore, - // Will need to uncomment this once we implement rpc message handler again - // dap_command::{ - // ContinueCommand, DapCommand, DisconnectCommand, NextCommand, PauseCommand, RestartCommand, - // RestartStackFrameCommand, StepBackCommand, StepCommand, StepInCommand, StepOutCommand, - // TerminateCommand, TerminateThreadsCommand, VariablesCommand, - // }, + locator_store::LocatorStore, session::{self, Session}, }; use crate::{debugger, worktree_store::WorktreeStore, ProjectEnvironment}; @@ -87,6 +82,7 @@ pub struct LocalDapStore { language_registry: Arc, debug_adapters: Arc, toolchain_store: Arc, + locator_store: Arc, start_debugging_tx: futures::channel::mpsc::UnboundedSender<(SessionId, Message)>, _start_debugging_task: Task<()>, } @@ -179,6 +175,7 @@ impl DapStore { debug_adapters, start_debugging_tx, _start_debugging_task, + locator_store: Arc::from(LocatorStore::new()), next_session_id: Default::default(), }), downstream_client: None, @@ -324,7 +321,7 @@ impl DapStore { pub fn new_session( &mut self, - config: DebugAdapterConfig, + mut config: DebugAdapterConfig, worktree: &Entity, parent_session: Option>, cx: &mut Context, @@ -354,22 +351,39 @@ impl DapStore { } let (initialized_tx, initialized_rx) = oneshot::channel(); + let locator_store = local_store.locator_store.clone(); + let debug_adapters = local_store.debug_adapters.clone(); - let start_client_task = Session::local( - self.breakpoint_store.clone(), - session_id, - parent_session, - delegate, - config, - local_store.start_debugging_tx.clone(), - initialized_tx, - local_store.debug_adapters.clone(), - cx, - ); + let start_debugging_tx = local_store.start_debugging_tx.clone(); + + let task = cx.spawn(async move |this, cx| { + if config.locator.is_some() { + locator_store.resolve_debug_config(&mut config).await?; + } + + let start_client_task = this.update(cx, |this, cx| { + Session::local( + this.breakpoint_store.clone(), + session_id, + parent_session, + delegate, + config, + start_debugging_tx.clone(), + initialized_tx, + debug_adapters, + cx, + ) + })?; + + this.update(cx, |_, cx| { + create_new_session(session_id, initialized_rx, start_client_task, cx) + })? + .await + }); - let task = create_new_session(session_id, initialized_rx, start_client_task, cx); (session_id, task) } + #[cfg(any(test, feature = "test-support"))] pub fn new_fake_session( &mut self, @@ -456,7 +470,10 @@ impl DapStore { request: DebugRequestDisposition::ReverseRequest(args), initialize_args: config.initialize_args.clone(), tcp_connection: config.tcp_connection.clone(), + locator: None, + args: Default::default(), }; + #[cfg(any(test, feature = "test-support"))] let new_session_task = { let caps = parent_session.read(cx).capabilities.clone(); diff --git a/crates/project/src/debugger/locator_store.rs b/crates/project/src/debugger/locator_store.rs new file mode 100644 index 0000000000..474c729725 --- /dev/null +++ b/crates/project/src/debugger/locator_store.rs @@ -0,0 +1,39 @@ +use anyhow::{anyhow, Result}; +use cargo::CargoLocator; +use collections::HashMap; +use dap::DebugAdapterConfig; +use gpui::SharedString; +use locators::DapLocator; + +mod cargo; +mod locators; + +pub(super) struct LocatorStore { + locators: HashMap>, +} + +impl LocatorStore { + pub(super) fn new() -> Self { + let locators = HashMap::from_iter([( + SharedString::new("cargo"), + Box::new(CargoLocator {}) as Box, + )]); + Self { locators } + } + + pub(super) async fn resolve_debug_config( + &self, + debug_config: &mut DebugAdapterConfig, + ) -> Result<()> { + let Some(ref locator_name) = &debug_config.locator else { + log::debug!("Attempted to resolve debug config without a locator field"); + return Ok(()); + }; + + if let Some(locator) = self.locators.get(locator_name as &str) { + locator.run_locator(debug_config).await + } else { + Err(anyhow!("Couldn't find locator {}", locator_name)) + } + } +} diff --git a/crates/project/src/debugger/locator_store/cargo.rs b/crates/project/src/debugger/locator_store/cargo.rs new file mode 100644 index 0000000000..06307efe74 --- /dev/null +++ b/crates/project/src/debugger/locator_store/cargo.rs @@ -0,0 +1,65 @@ +use super::DapLocator; +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use dap::DebugAdapterConfig; +use serde_json::Value; +use smol::{ + io::AsyncReadExt, + process::{Command, Stdio}, +}; + +pub(super) struct CargoLocator {} + +#[async_trait] +impl DapLocator for CargoLocator { + async fn run_locator(&self, debug_config: &mut DebugAdapterConfig) -> Result<()> { + let Some(launch_config) = (match &mut debug_config.request { + task::DebugRequestDisposition::UserConfigured(task::DebugRequestType::Launch( + launch_config, + )) => Some(launch_config), + _ => None, + }) else { + return Err(anyhow!("Couldn't get launch config in locator")); + }; + + let Some(cwd) = launch_config.cwd.clone() else { + return Err(anyhow!( + "Couldn't get cwd from debug config which is needed for locators" + )); + }; + + let mut child = Command::new("cargo") + .args(&debug_config.args) + .arg("--message-format=json") + .current_dir(cwd) + .stdout(Stdio::piped()) + .spawn()?; + + let mut output = String::new(); + if let Some(mut stdout) = child.stdout.take() { + stdout.read_to_string(&mut output).await?; + } + + let status = child.status().await?; + if !status.success() { + return Err(anyhow::anyhow!("Cargo command failed")); + } + + let Some(executable) = output + .lines() + .filter(|line| !line.trim().is_empty()) + .filter_map(|line| serde_json::from_str(line).ok()) + .find_map(|json: Value| { + json.get("executable") + .and_then(Value::as_str) + .map(String::from) + }) + else { + return Err(anyhow!("Couldn't get executable in cargo locator")); + }; + + launch_config.program = executable; + debug_config.args.clear(); + Ok(()) + } +} diff --git a/crates/project/src/debugger/locator_store/locators.rs b/crates/project/src/debugger/locator_store/locators.rs new file mode 100644 index 0000000000..5629998bbb --- /dev/null +++ b/crates/project/src/debugger/locator_store/locators.rs @@ -0,0 +1,8 @@ +use anyhow::Result; +use async_trait::async_trait; +use dap::DebugAdapterConfig; + +#[async_trait] +pub(super) trait DapLocator { + async fn run_locator(&self, debug_config: &mut DebugAdapterConfig) -> Result<()>; +} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index d766b30ec2..5284e8ebb5 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -165,6 +165,7 @@ pub struct Project { languages: Arc, debug_adapters: Arc, dap_store: Entity, + breakpoint_store: Entity, client: Arc, join_project_response_message_id: u32, @@ -952,6 +953,7 @@ impl Project { ssh_client: None, breakpoint_store, dap_store, + buffers_needing_diff: Default::default(), git_diff_debouncer: DebouncedDelay::new(), terminals: Terminals { @@ -1450,6 +1452,12 @@ impl Project { } } + pub fn queue_debug_session(&mut self, config: DebugAdapterConfig, cx: &mut Context) { + if config.locator.is_none() { + self.start_debug_session(config, cx).detach_and_log_err(cx); + } + } + pub fn start_debug_session( &mut self, config: DebugAdapterConfig, @@ -1490,6 +1498,8 @@ impl Project { request: DebugRequestDisposition::UserConfigured(request), initialize_args: None, tcp_connection: None, + locator: None, + args: Default::default(), }; let caps = caps.unwrap_or(Capabilities { supports_step_back: Some(false), diff --git a/crates/task/src/debug_format.rs b/crates/task/src/debug_format.rs index b4554ee3f0..0a41cfc2bc 100644 --- a/crates/task/src/debug_format.rs +++ b/crates/task/src/debug_format.rs @@ -5,7 +5,7 @@ use std::net::Ipv4Addr; use std::path::PathBuf; use util::ResultExt; -use crate::{TaskTemplate, TaskTemplates, TaskType}; +use crate::{task_template::DebugArgs, TaskTemplate, TaskTemplates, TaskType}; impl Default for DebugConnectionType { fn default() -> Self { @@ -102,6 +102,10 @@ pub struct DebugAdapterConfig { /// spawning a new process. This is useful for connecting to a debug adapter /// that is already running or is started by another process. pub tcp_connection: Option, + /// What Locator to use to configure the debug task + pub locator: Option, + /// Args to pass to a debug adapter (only used in locator right now) + pub args: Vec, } impl From for DebugAdapterConfig { @@ -112,6 +116,8 @@ impl From for DebugAdapterConfig { request: DebugRequestDisposition::UserConfigured(def.request), initialize_args: def.initialize_args, tcp_connection: def.tcp_connection, + locator: def.locator, + args: def.args, } } } @@ -130,6 +136,8 @@ impl TryFrom for DebugTaskDefinition { request, initialize_args: def.initialize_args, tcp_connection: def.tcp_connection, + locator: def.locator, + args: def.args, }) } } @@ -137,18 +145,30 @@ impl TryFrom for DebugTaskDefinition { impl DebugTaskDefinition { /// Translate from debug definition to a task template pub fn to_zed_format(self) -> anyhow::Result { - let command = "".to_string(); - - let cwd = if let DebugRequestType::Launch(ref launch) = self.request { - launch - .cwd - .as_ref() - .map(|path| path.to_string_lossy().into_owned()) - } else { - None + let (command, cwd, request) = match self.request { + DebugRequestType::Launch(launch_config) => ( + launch_config.program, + launch_config + .cwd + .map(|cwd| cwd.to_string_lossy().to_string()), + crate::task_template::DebugArgsRequest::Launch, + ), + DebugRequestType::Attach(attach_config) => ( + "".to_owned(), + None, + crate::task_template::DebugArgsRequest::Attach(attach_config), + ), }; + + let task_type = TaskType::Debug(DebugArgs { + adapter: self.adapter, + request, + initialize_args: self.initialize_args, + locator: self.locator, + tcp_connection: self.tcp_connection, + }); + let label = self.label.clone(); - let task_type = TaskType::Debug(self); Ok(TaskTemplate { label, @@ -189,6 +209,12 @@ pub struct DebugTaskDefinition { /// spawning a new process. This is useful for connecting to a debug adapter /// that is already running or is started by another process. pub tcp_connection: Option, + /// Locator to use + /// -- cargo + pub locator: Option, + /// Args to pass to a debug adapter (only used in locator right now) + #[serde(skip)] + pub args: Vec, } /// A group of Debug Tasks defined in a JSON file. diff --git a/crates/task/src/lib.rs b/crates/task/src/lib.rs index 5716bbbf4b..bb96f32af9 100644 --- a/crates/task/src/lib.rs +++ b/crates/task/src/lib.rs @@ -19,7 +19,8 @@ pub use debug_format::{ DebugRequestType, DebugTaskDefinition, DebugTaskFile, LaunchConfig, TCPHost, }; pub use task_template::{ - HideStrategy, RevealStrategy, TaskModal, TaskTemplate, TaskTemplates, TaskType, + DebugArgs, DebugArgsRequest, HideStrategy, RevealStrategy, TaskModal, TaskTemplate, + TaskTemplates, TaskType, }; pub use vscode_format::VsCodeTaskFile; pub use zed_actions::RevealTarget; @@ -61,8 +62,6 @@ pub struct SpawnInTerminal { pub hide: HideStrategy, /// Which shell to use when spawning the task. pub shell: Shell, - /// Tells debug tasks which program to debug - pub program: Option, /// Whether to show the task summary line in the task output (sucess/failure). pub show_summary: bool, /// Whether to show the command line in the task output. @@ -104,24 +103,50 @@ impl ResolvedTask { } /// Get the configuration for the debug adapter that should be used for this task. - pub fn resolved_debug_adapter_config(&self) -> Option { + pub fn resolved_debug_adapter_config(&self) -> Option { match self.original_task.task_type.clone() { - TaskType::Script => None, - TaskType::Debug(mut adapter_config) => { - if let Some(resolved) = &self.resolved { - adapter_config.label = resolved.label.clone(); - if let DebugRequestType::Launch(ref mut launch) = adapter_config.request { - if let Some(program) = resolved.program.clone() { - launch.program = program; - } - if let Some(cwd) = resolved.cwd.clone() { - launch.cwd = Some(cwd); - } - } - } + TaskType::Debug(debug_args) if self.resolved.is_some() => { + let resolved = self + .resolved + .as_ref() + .expect("We just checked if this was some"); - Some(adapter_config) + let args = resolved + .args + .iter() + .cloned() + .map(|arg| { + if arg.starts_with("$") { + arg.strip_prefix("$") + .and_then(|arg| resolved.env.get(arg).map(ToOwned::to_owned)) + .unwrap_or_else(|| arg) + } else { + arg + } + }) + .collect(); + + Some(DebugAdapterConfig { + label: resolved.label.clone(), + adapter: debug_args.adapter.clone(), + request: DebugRequestDisposition::UserConfigured(match debug_args.request { + crate::task_template::DebugArgsRequest::Launch => { + DebugRequestType::Launch(LaunchConfig { + program: resolved.command.clone(), + cwd: resolved.cwd.clone(), + }) + } + crate::task_template::DebugArgsRequest::Attach(attach_config) => { + DebugRequestType::Attach(attach_config) + } + }), + initialize_args: debug_args.initialize_args, + tcp_connection: debug_args.tcp_connection, + args, + locator: debug_args.locator.clone(), + }) } + _ => None, } } diff --git a/crates/task/src/task_template.rs b/crates/task/src/task_template.rs index 7c423db1f4..a94dccab65 100644 --- a/crates/task/src/task_template.rs +++ b/crates/task/src/task_template.rs @@ -9,8 +9,8 @@ use sha2::{Digest, Sha256}; use util::{truncate_and_remove_front, ResultExt}; use crate::{ - DebugRequestType, DebugTaskDefinition, ResolvedTask, RevealTarget, Shell, SpawnInTerminal, - TaskContext, TaskId, VariableName, ZED_VARIABLE_NAME_PREFIX, + AttachConfig, ResolvedTask, RevealTarget, Shell, SpawnInTerminal, TCPHost, TaskContext, TaskId, + VariableName, ZED_VARIABLE_NAME_PREFIX, }; /// A template definition of a Zed task to run. @@ -75,62 +75,39 @@ pub struct TaskTemplate { pub show_command: bool, } +#[derive(Deserialize, Eq, PartialEq, Clone, Debug)] +/// Use to represent debug request type +pub enum DebugArgsRequest { + /// launch (program, cwd) are stored in TaskTemplate as (command, cwd) + Launch, + /// Attach + Attach(AttachConfig), +} + +#[derive(Deserialize, Eq, PartialEq, Clone, Debug)] +/// This represents the arguments for the debug task. +pub struct DebugArgs { + /// The launch type + pub request: DebugArgsRequest, + /// Adapter choice + pub adapter: String, + /// TCP connection to make with debug adapter + pub tcp_connection: Option, + /// Args to send to debug adapter + pub initialize_args: Option, + /// the locator to use + pub locator: Option, +} + /// Represents the type of task that is being ran -#[derive(Default, Deserialize, Serialize, Eq, PartialEq, JsonSchema, Clone, Debug)] -#[serde(rename_all = "snake_case", tag = "type")] +#[derive(Default, Eq, PartialEq, Clone, Debug)] #[allow(clippy::large_enum_variant)] pub enum TaskType { /// Act like a typically task that runs commands #[default] Script, /// This task starts the debugger for a language - Debug(DebugTaskDefinition), -} - -#[cfg(test)] -mod deserialization_tests { - use crate::LaunchConfig; - - use super::*; - use serde_json::json; - - #[test] - fn deserialize_task_type_script() { - let json = json!({"type": "script"}); - - let task_type: TaskType = - serde_json::from_value(json).expect("Failed to deserialize TaskType::Script"); - assert_eq!(task_type, TaskType::Script); - } - - #[test] - fn deserialize_task_type_debug() { - let adapter_config = DebugTaskDefinition { - label: "test config".into(), - adapter: "Debugpy".into(), - request: crate::DebugRequestType::Launch(LaunchConfig { - program: "main".to_string(), - cwd: None, - }), - initialize_args: None, - tcp_connection: None, - }; - let json = json!({ - "label": "test config", - "type": "debug", - "adapter": "Debugpy", - "program": "main", - "supports_attach": false, - }); - - let task_type: TaskType = - serde_json::from_value(json).expect("Failed to deserialize TaskType::Debug"); - if let TaskType::Debug(config) = task_type { - assert_eq!(config, adapter_config); - } else { - panic!("Expected TaskType::Debug"); - } - } + Debug(DebugArgs), } #[derive(Clone, Debug, PartialEq, Eq)] @@ -270,22 +247,6 @@ impl TaskTemplate { &mut substituted_variables, )?; - let program = match &self.task_type { - TaskType::Script => None, - TaskType::Debug(adapter_config) => { - if let DebugRequestType::Launch(ref launch) = &adapter_config.request { - Some(substitute_all_template_variables_in_str( - &launch.program, - &task_variables, - &variable_names, - &mut substituted_variables, - )?) - } else { - None - } - } - }; - let task_hash = to_hex_hash(self) .context("hashing task template") .log_err()?; @@ -341,7 +302,6 @@ impl TaskTemplate { reveal_target: self.reveal_target, hide: self.hide, shell: self.shell.clone(), - program, show_summary: self.show_summary, show_command: self.show_command, show_rerun: true, diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index 7831456ba7..f058582ec4 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -10,7 +10,8 @@ use gpui::{ use picker::{highlighted_match_with_paths::HighlightedMatch, Picker, PickerDelegate}; use project::{task_store::TaskStore, TaskSourceKind}; use task::{ - DebugRequestType, ResolvedTask, RevealTarget, TaskContext, TaskModal, TaskTemplate, TaskType, + DebugRequestType, DebugTaskDefinition, ResolvedTask, RevealTarget, TaskContext, TaskModal, + TaskTemplate, TaskType, }; use ui::{ div, h_flex, v_flex, ActiveTheme, Button, ButtonCommon, ButtonSize, Clickable, Color, @@ -320,15 +321,11 @@ impl PickerDelegate for TasksModalDelegate { self.workspace .update(cx, |workspace, cx| { match task.task_type() { - TaskType::Script => schedule_resolved_task( - workspace, - task_source_kind, - task, - omit_history_entry, - cx, - ), - TaskType::Debug(_) => { - let Some(config) = task.resolved_debug_adapter_config() else { + TaskType::Debug(config) if config.locator.is_none() => { + let Some(config): Option = task + .resolved_debug_adapter_config() + .and_then(|config| config.try_into().ok()) + else { return; }; let project = workspace.project().clone(); @@ -355,6 +352,13 @@ impl PickerDelegate for TasksModalDelegate { } } } + _ => schedule_resolved_task( + workspace, + task_source_kind, + task, + omit_history_entry, + cx, + ), }; }) .ok(); @@ -517,16 +521,30 @@ impl PickerDelegate for TasksModalDelegate { omit_history_entry, cx, ), - // TODO: Should create a schedule_resolved_debug_task function + // todo(debugger): Should create a schedule_resolved_debug_task function // This would allow users to access to debug history and other issues - TaskType::Debug(_) => workspace.project().update(cx, |project, cx| { - project - .start_debug_session( - task.resolved_debug_adapter_config().unwrap().into(), + TaskType::Debug(debug_args) => { + let Some(debug_config) = task.resolved_debug_adapter_config() else { + // todo(debugger) log an error, this should never happen + return; + }; + + if debug_args.locator.is_some() { + schedule_resolved_task( + workspace, + task_source_kind, + task, + omit_history_entry, cx, - ) - .detach_and_log_err(cx); - }), + ); + } else { + workspace.project().update(cx, |project, cx| { + project + .start_debug_session(debug_config, cx) + .detach_and_log_err(cx); + }); + } + } }; }) .ok(); diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index a58ed2b4b0..bcbbbf89b4 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -110,6 +110,7 @@ pub enum Event { SelectionsChanged, NewNavigationTarget(Option), Open(MaybeNavigationTarget), + TaskLocatorReady { task_id: TaskId, success: bool }, } #[derive(Clone, Debug)] @@ -1899,6 +1900,11 @@ impl Terminal { unsafe { append_text_to_term(&mut self.term.lock(), &lines_to_show) }; } + cx.emit(Event::TaskLocatorReady { + task_id: task.id.clone(), + success: finished_successfully, + }); + match task.hide { HideStrategy::Never => {} HideStrategy::Always => { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 20619f73fd..bd0093c4d1 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -982,6 +982,15 @@ fn subscribe_for_terminal_events( window.invalidate_character_coordinates(); cx.emit(SearchEvent::ActiveMatchChanged) } + Event::TaskLocatorReady { task_id, success } => { + if *success { + workspace + .update(cx, |workspace, cx| { + workspace.debug_task_ready(task_id, cx); + }) + .log_err(); + } + } }, ); vec![terminal_subscription, terminal_events_subscription] diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 8ad2994480..3ee57c5bde 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -1440,7 +1440,6 @@ impl ShellExec { reveal_target: RevealTarget::Dock, hide: HideStrategy::Never, shell, - program: None, show_summary: false, show_command: false, show_rerun: false, diff --git a/crates/workspace/src/tasks.rs b/crates/workspace/src/tasks.rs index 2b6c5c13dd..21adb4ede5 100644 --- a/crates/workspace/src/tasks.rs +++ b/crates/workspace/src/tasks.rs @@ -46,7 +46,15 @@ pub fn schedule_resolved_task( omit_history: bool, cx: &mut Context, ) { + let debug_config = resolved_task.resolved_debug_adapter_config(); + if let Some(spawn_in_terminal) = resolved_task.resolved.take() { + if let Some(debug_config) = debug_config { + workspace + .debug_task_queue + .insert(resolved_task.id.clone(), debug_config); + } + if !omit_history { resolved_task.resolved = Some(spawn_in_terminal.clone()); workspace.project().update(cx, |project, cx| { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 612002e977..c1e5dae2da 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -94,7 +94,7 @@ use std::{ sync::{atomic::AtomicUsize, Arc, LazyLock, Weak}, time::Duration, }; -use task::SpawnInTerminal; +use task::{DebugAdapterConfig, SpawnInTerminal, TaskId}; use theme::{ActiveTheme, SystemAppearance, ThemeSettings}; pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; pub use ui; @@ -874,6 +874,7 @@ pub struct Workspace { serialized_ssh_project: Option, _items_serializer: Task>, session_id: Option, + debug_task_queue: HashMap, } impl EventEmitter for Workspace {} @@ -1185,6 +1186,7 @@ impl Workspace { _items_serializer, session_id: Some(session_id), serialized_ssh_project: None, + debug_task_queue: Default::default(), } } @@ -5191,6 +5193,16 @@ impl Workspace { .update(cx, |_, window, _| window.activate_window()) .ok(); } + + pub fn debug_task_ready(&mut self, task_id: &TaskId, cx: &mut App) { + if let Some(debug_config) = self.debug_task_queue.remove(task_id) { + self.project.update(cx, |project, cx| { + project + .start_debug_session(debug_config, cx) + .detach_and_log_err(cx); + }) + } + } } fn leader_border_for_pane(