debugger: Support passing custom arguments to debug adapters (#33251)

Custom arguments replace any arguments that we normally pass to the DAP.
For interpreted languages, they are passed to the interpreter after the
DAP path or module. They can be combined with a custom binary, or you
can omit `dap.binary` and just customize the arguments to the DAPs we
download.

This doesn't take care of updating the extension API to support custom
arguments.

Release Notes:

- debugger: Implemented support for passing custom arguments to a debug
adapter binary using the `dap.args` setting.
- debugger: Fixed not being able to use the `dap` setting in
`.zed/settings.json`.
This commit is contained in:
Cole Miller 2025-06-23 13:06:48 -04:00 committed by GitHub
parent a067c16c82
commit c9bd409732
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 154 additions and 54 deletions

View file

@ -329,6 +329,7 @@ impl DebugAdapter for CodeLldbDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
config: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
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,

View file

@ -159,6 +159,7 @@ impl DebugAdapter for GdbDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
config: &DebugTaskDefinition,
user_installed_path: Option<std::path::PathBuf>,
user_args: Option<Vec<String>>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
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,

View file

@ -399,6 +399,7 @@ impl DebugAdapter for GoDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
task_definition: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
_cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
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(),

View file

@ -50,6 +50,7 @@ impl JsDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
task_definition: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
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<dyn DapDelegate>,
config: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
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
}

View file

@ -52,6 +52,7 @@ impl PhpDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
task_definition: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
_: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
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<dyn DapDelegate>,
task_definition: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
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
}
}

View file

@ -32,29 +32,23 @@ impl PythonDebugAdapter {
host: &Ipv4Addr,
port: u16,
user_installed_path: Option<&Path>,
user_args: Option<Vec<String>>,
installed_in_venv: bool,
) -> Result<Vec<String>> {
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<dyn DapDelegate>,
config: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
toolchain: Option<Toolchain>,
installed_in_venv: bool,
) -> Result<DebugAdapterBinary> {
@ -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<dyn DapDelegate>,
config: &DebugTaskDefinition,
user_installed_path: Option<PathBuf>,
user_args: Option<Vec<String>>,
cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
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.
}
}

View file

@ -119,6 +119,7 @@ impl DebugAdapter for RubyDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
definition: &DebugTaskDefinition,
_user_installed_path: Option<PathBuf>,
_user_args: Option<Vec<String>>,
_cx: &mut AsyncApp,
) -> Result<DebugAdapterBinary> {
let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());