diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index a269c099cc..8e1c84083f 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -344,6 +344,7 @@ pub trait DebugAdapter: 'static + Send + Sync { delegate: &Arc, config: &DebugTaskDefinition, user_installed_path: Option, + user_args: Option>, cx: &mut AsyncApp, ) -> Result; @@ -434,6 +435,7 @@ impl DebugAdapter for FakeAdapter { _: &Arc, task_definition: &DebugTaskDefinition, _: Option, + _: Option>, _: &mut AsyncApp, ) -> Result { Ok(DebugAdapterBinary { diff --git a/crates/dap_adapters/src/codelldb.rs b/crates/dap_adapters/src/codelldb.rs index 3158996681..5d14cc8747 100644 --- a/crates/dap_adapters/src/codelldb.rs +++ b/crates/dap_adapters/src/codelldb.rs @@ -329,6 +329,7 @@ impl DebugAdapter for CodeLldbDebugAdapter { delegate: &Arc, config: &DebugTaskDefinition, user_installed_path: Option, + user_args: Option>, _: &mut AsyncApp, ) -> Result { let mut command = user_installed_path @@ -364,10 +365,12 @@ impl DebugAdapter for CodeLldbDebugAdapter { Ok(DebugAdapterBinary { command: Some(command.unwrap()), cwd: Some(delegate.worktree_root_path().to_path_buf()), - arguments: vec![ - "--settings".into(), - json!({"sourceLanguages": ["cpp", "rust"]}).to_string(), - ], + arguments: user_args.unwrap_or_else(|| { + vec![ + "--settings".into(), + json!({"sourceLanguages": ["cpp", "rust"]}).to_string(), + ] + }), request_args: self.request_args(delegate, &config).await?, envs: HashMap::default(), connection: None, diff --git a/crates/dap_adapters/src/gdb.rs b/crates/dap_adapters/src/gdb.rs index e889588359..17b7a65911 100644 --- a/crates/dap_adapters/src/gdb.rs +++ b/crates/dap_adapters/src/gdb.rs @@ -159,6 +159,7 @@ impl DebugAdapter for GdbDebugAdapter { delegate: &Arc, config: &DebugTaskDefinition, user_installed_path: Option, + user_args: Option>, _: &mut AsyncApp, ) -> Result { let user_setting_path = user_installed_path @@ -186,7 +187,7 @@ impl DebugAdapter for GdbDebugAdapter { Ok(DebugAdapterBinary { command: Some(gdb_path), - arguments: vec!["-i=dap".into()], + arguments: user_args.unwrap_or_else(|| vec!["-i=dap".into()]), envs: HashMap::default(), cwd: Some(delegate.worktree_root_path().to_path_buf()), connection: None, diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index afd733b56a..bc3f500745 100644 --- a/crates/dap_adapters/src/go.rs +++ b/crates/dap_adapters/src/go.rs @@ -399,6 +399,7 @@ impl DebugAdapter for GoDebugAdapter { delegate: &Arc, task_definition: &DebugTaskDefinition, user_installed_path: Option, + user_args: Option>, _cx: &mut AsyncApp, ) -> Result { let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME); @@ -470,7 +471,10 @@ impl DebugAdapter for GoDebugAdapter { crate::configure_tcp_connection(TcpArgumentsTemplate::default()).await?; command = Some(minidelve_path.to_string_lossy().into_owned()); connection = None; - arguments = if cfg!(windows) { + arguments = if let Some(mut args) = user_args { + args.insert(0, delve_path); + args + } else if cfg!(windows) { vec![ delve_path, "dap".into(), diff --git a/crates/dap_adapters/src/javascript.rs b/crates/dap_adapters/src/javascript.rs index e59fb101ff..d5d78186ac 100644 --- a/crates/dap_adapters/src/javascript.rs +++ b/crates/dap_adapters/src/javascript.rs @@ -50,6 +50,7 @@ impl JsDebugAdapter { delegate: &Arc, task_definition: &DebugTaskDefinition, user_installed_path: Option, + user_args: Option>, _: &mut AsyncApp, ) -> Result { let adapter_path = if let Some(user_installed_path) = user_installed_path { @@ -109,6 +110,26 @@ impl JsDebugAdapter { .or_insert(true.into()); } + let arguments = if let Some(mut args) = user_args { + args.insert( + 0, + adapter_path + .join(Self::ADAPTER_PATH) + .to_string_lossy() + .to_string(), + ); + args + } else { + vec![ + adapter_path + .join(Self::ADAPTER_PATH) + .to_string_lossy() + .to_string(), + port.to_string(), + host.to_string(), + ] + }; + Ok(DebugAdapterBinary { command: Some( delegate @@ -118,14 +139,7 @@ impl JsDebugAdapter { .to_string_lossy() .into_owned(), ), - arguments: vec![ - adapter_path - .join(Self::ADAPTER_PATH) - .to_string_lossy() - .to_string(), - port.to_string(), - host.to_string(), - ], + arguments, cwd: Some(delegate.worktree_root_path().to_path_buf()), envs: HashMap::default(), connection: Some(adapters::TcpArguments { @@ -464,6 +478,7 @@ impl DebugAdapter for JsDebugAdapter { delegate: &Arc, config: &DebugTaskDefinition, user_installed_path: Option, + user_args: Option>, cx: &mut AsyncApp, ) -> Result { if self.checked.set(()).is_ok() { @@ -481,7 +496,7 @@ impl DebugAdapter for JsDebugAdapter { } } - self.get_installed_binary(delegate, &config, user_installed_path, cx) + self.get_installed_binary(delegate, &config, user_installed_path, user_args, cx) .await } diff --git a/crates/dap_adapters/src/php.rs b/crates/dap_adapters/src/php.rs index 047c744dd9..7d7dee00c9 100644 --- a/crates/dap_adapters/src/php.rs +++ b/crates/dap_adapters/src/php.rs @@ -52,6 +52,7 @@ impl PhpDebugAdapter { delegate: &Arc, task_definition: &DebugTaskDefinition, user_installed_path: Option, + user_args: Option>, _: &mut AsyncApp, ) -> Result { let adapter_path = if let Some(user_installed_path) = user_installed_path { @@ -77,6 +78,25 @@ impl PhpDebugAdapter { .or_insert_with(|| delegate.worktree_root_path().to_string_lossy().into()); } + let arguments = if let Some(mut args) = user_args { + args.insert( + 0, + adapter_path + .join(Self::ADAPTER_PATH) + .to_string_lossy() + .to_string(), + ); + args + } else { + vec![ + adapter_path + .join(Self::ADAPTER_PATH) + .to_string_lossy() + .to_string(), + format!("--server={}", port), + ] + }; + Ok(DebugAdapterBinary { command: Some( delegate @@ -86,13 +106,7 @@ impl PhpDebugAdapter { .to_string_lossy() .into_owned(), ), - arguments: vec![ - adapter_path - .join(Self::ADAPTER_PATH) - .to_string_lossy() - .to_string(), - format!("--server={}", port), - ], + arguments, connection: Some(TcpArguments { port, host, @@ -326,6 +340,7 @@ impl DebugAdapter for PhpDebugAdapter { delegate: &Arc, task_definition: &DebugTaskDefinition, user_installed_path: Option, + user_args: Option>, cx: &mut AsyncApp, ) -> Result { if self.checked.set(()).is_ok() { @@ -341,7 +356,13 @@ impl DebugAdapter for PhpDebugAdapter { } } - self.get_installed_binary(delegate, &task_definition, user_installed_path, cx) - .await + self.get_installed_binary( + delegate, + &task_definition, + user_installed_path, + user_args, + cx, + ) + .await } } diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index 3a8841bb43..43d1246d0c 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -32,29 +32,23 @@ impl PythonDebugAdapter { host: &Ipv4Addr, port: u16, user_installed_path: Option<&Path>, + user_args: Option>, installed_in_venv: bool, ) -> Result> { - if let Some(user_installed_path) = user_installed_path { + let mut args = if let Some(user_installed_path) = user_installed_path { log::debug!( "Using user-installed debugpy adapter from: {}", user_installed_path.display() ); - Ok(vec![ + vec![ user_installed_path .join(Self::ADAPTER_PATH) .to_string_lossy() .to_string(), - format!("--host={}", host), - format!("--port={}", port), - ]) + ] } else if installed_in_venv { log::debug!("Using venv-installed debugpy"); - Ok(vec![ - "-m".to_string(), - "debugpy.adapter".to_string(), - format!("--host={}", host), - format!("--port={}", port), - ]) + vec!["-m".to_string(), "debugpy.adapter".to_string()] } else { let adapter_path = paths::debug_adapters_dir().join(Self::DEBUG_ADAPTER_NAME.as_ref()); let file_name_prefix = format!("{}_", Self::ADAPTER_NAME); @@ -70,15 +64,20 @@ impl PythonDebugAdapter { "Using GitHub-downloaded debugpy adapter from: {}", debugpy_dir.display() ); - Ok(vec![ + vec![ debugpy_dir .join(Self::ADAPTER_PATH) .to_string_lossy() .to_string(), - format!("--host={}", host), - format!("--port={}", port), - ]) - } + ] + }; + + args.extend(if let Some(args) = user_args { + args + } else { + vec![format!("--host={}", host), format!("--port={}", port)] + }); + Ok(args) } async fn request_args( @@ -151,6 +150,7 @@ impl PythonDebugAdapter { delegate: &Arc, config: &DebugTaskDefinition, user_installed_path: Option, + user_args: Option>, toolchain: Option, installed_in_venv: bool, ) -> Result { @@ -182,6 +182,7 @@ impl PythonDebugAdapter { &host, port, user_installed_path.as_deref(), + user_args, installed_in_venv, ) .await?; @@ -595,6 +596,7 @@ impl DebugAdapter for PythonDebugAdapter { delegate: &Arc, config: &DebugTaskDefinition, user_installed_path: Option, + user_args: Option>, cx: &mut AsyncApp, ) -> Result { if let Some(local_path) = &user_installed_path { @@ -603,7 +605,14 @@ impl DebugAdapter for PythonDebugAdapter { local_path.display() ); return self - .get_installed_binary(delegate, &config, Some(local_path.clone()), None, false) + .get_installed_binary( + delegate, + &config, + Some(local_path.clone()), + user_args, + None, + false, + ) .await; } @@ -630,6 +639,7 @@ impl DebugAdapter for PythonDebugAdapter { delegate, &config, None, + user_args, Some(toolchain.clone()), true, ) @@ -647,7 +657,7 @@ impl DebugAdapter for PythonDebugAdapter { } } - self.get_installed_binary(delegate, &config, None, toolchain, false) + self.get_installed_binary(delegate, &config, None, user_args, toolchain, false) .await } } @@ -682,15 +692,21 @@ mod tests { // Case 1: User-defined debugpy path (highest precedence) let user_path = PathBuf::from("/custom/path/to/debugpy"); - let user_args = - PythonDebugAdapter::generate_debugpy_arguments(&host, port, Some(&user_path), false) - .await - .unwrap(); + let user_args = PythonDebugAdapter::generate_debugpy_arguments( + &host, + port, + Some(&user_path), + None, + false, + ) + .await + .unwrap(); // Case 2: Venv-installed debugpy (uses -m debugpy.adapter) - let venv_args = PythonDebugAdapter::generate_debugpy_arguments(&host, port, None, true) - .await - .unwrap(); + let venv_args = + PythonDebugAdapter::generate_debugpy_arguments(&host, port, None, None, true) + .await + .unwrap(); assert!(user_args[0].ends_with("src/debugpy/adapter")); assert_eq!(user_args[1], "--host=127.0.0.1"); @@ -701,6 +717,33 @@ mod tests { assert_eq!(venv_args[2], "--host=127.0.0.1"); assert_eq!(venv_args[3], "--port=5678"); + // The same cases, with arguments overridden by the user + let user_args = PythonDebugAdapter::generate_debugpy_arguments( + &host, + port, + Some(&user_path), + Some(vec!["foo".into()]), + false, + ) + .await + .unwrap(); + let venv_args = PythonDebugAdapter::generate_debugpy_arguments( + &host, + port, + None, + Some(vec!["foo".into()]), + true, + ) + .await + .unwrap(); + + assert!(user_args[0].ends_with("src/debugpy/adapter")); + assert_eq!(user_args[1], "foo"); + + assert_eq!(venv_args[0], "-m"); + assert_eq!(venv_args[1], "debugpy.adapter"); + assert_eq!(venv_args[2], "foo"); + // Note: Case 3 (GitHub-downloaded debugpy) is not tested since this requires mocking the Github API. } } diff --git a/crates/dap_adapters/src/ruby.rs b/crates/dap_adapters/src/ruby.rs index 4e24822f00..28f1fb1f5f 100644 --- a/crates/dap_adapters/src/ruby.rs +++ b/crates/dap_adapters/src/ruby.rs @@ -119,6 +119,7 @@ impl DebugAdapter for RubyDebugAdapter { delegate: &Arc, definition: &DebugTaskDefinition, _user_installed_path: Option, + _user_args: Option>, _cx: &mut AsyncApp, ) -> Result { let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref()); diff --git a/crates/debug_adapter_extension/src/extension_dap_adapter.rs b/crates/debug_adapter_extension/src/extension_dap_adapter.rs index 26b9f2e8ad..b656bed9bc 100644 --- a/crates/debug_adapter_extension/src/extension_dap_adapter.rs +++ b/crates/debug_adapter_extension/src/extension_dap_adapter.rs @@ -88,6 +88,8 @@ impl DebugAdapter for ExtensionDapAdapter { delegate: &Arc, config: &DebugTaskDefinition, user_installed_path: Option, + // TODO support user args in the extension API + _user_args: Option>, _cx: &mut AsyncApp, ) -> Result { self.extension diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index b54c4c1e45..28cfbe4e4d 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -40,7 +40,7 @@ use rpc::{ AnyProtoClient, TypedEnvelope, proto::{self}, }; -use settings::{Settings, WorktreeId}; +use settings::{Settings, SettingsLocation, WorktreeId}; use std::{ borrow::Borrow, collections::BTreeMap, @@ -190,17 +190,23 @@ impl DapStore { return Task::ready(Err(anyhow!("Failed to find a debug adapter"))); }; - let user_installed_path = ProjectSettings::get_global(cx) + let settings_location = SettingsLocation { + worktree_id: worktree.read(cx).id(), + path: Path::new(""), + }; + let dap_settings = ProjectSettings::get(Some(settings_location), cx) .dap - .get(&adapter.name()) - .and_then(|s| s.binary.as_ref().map(PathBuf::from)); + .get(&adapter.name()); + let user_installed_path = + dap_settings.and_then(|s| s.binary.as_ref().map(PathBuf::from)); + let user_args = dap_settings.map(|s| s.args.clone()); let delegate = self.delegate(&worktree, console, cx); let cwd: Arc = worktree.read(cx).abs_path().as_ref().into(); cx.spawn(async move |this, cx| { let mut binary = adapter - .get_binary(&delegate, &definition, user_installed_path, cx) + .get_binary(&delegate, &definition, user_installed_path, user_args, cx) .await?; let env = this diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index e1bf3a46a6..3f584f9697 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -82,6 +82,8 @@ pub struct ProjectSettings { #[serde(rename_all = "snake_case")] pub struct DapSettings { pub binary: Option, + #[serde(default)] + pub args: Vec, } #[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug)]