Detailed changes
@@ -295,7 +295,6 @@ impl DebugPanel {
})
})?
.await?;
-
dap_store
.update(cx, |dap_store, cx| {
dap_store.boot_session(session.clone(), definition, cx)
@@ -547,6 +547,10 @@ impl RunningState {
.for_each(|value| Self::substitute_variables_in_config(value, context));
}
serde_json::Value::String(s) => {
+ // Some built-in zed tasks wrap their arguments in quotes as they might contain spaces.
+ if s.starts_with("\"$ZED_") && s.ends_with('"') {
+ *s = s[1..s.len() - 1].to_string();
+ }
if let Some(substituted) = substitute_variables_in_str(&s, context) {
*s = substituted;
}
@@ -571,6 +575,10 @@ impl RunningState {
.for_each(|value| Self::relativlize_paths(None, value, context));
}
serde_json::Value::String(s) if key == Some("program") || key == Some("cwd") => {
+ // Some built-in zed tasks wrap their arguments in quotes as they might contain spaces.
+ if s.starts_with("\"$ZED_") && s.ends_with('"') {
+ *s = s[1..s.len() - 1].to_string();
+ }
resolve_path(s);
if let Some(substituted) = substitute_variables_in_str(&s, context) {
@@ -413,6 +413,7 @@ impl ContextProvider for PythonContextProvider {
"-c".to_owned(),
VariableName::SelectedText.template_value_with_whitespace(),
],
+ cwd: Some("$ZED_WORKTREE_ROOT".into()),
..TaskTemplate::default()
},
// Execute an entire file
@@ -420,6 +421,7 @@ impl ContextProvider for PythonContextProvider {
label: format!("run '{}'", VariableName::File.template_value()),
command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(),
args: vec![VariableName::File.template_value_with_whitespace()],
+ cwd: Some("$ZED_WORKTREE_ROOT".into()),
..TaskTemplate::default()
},
// Execute a file as module
@@ -430,6 +432,7 @@ impl ContextProvider for PythonContextProvider {
"-m".to_owned(),
PYTHON_MODULE_NAME_TASK_VARIABLE.template_value(),
],
+ cwd: Some("$ZED_WORKTREE_ROOT".into()),
tags: vec!["python-module-main-method".to_owned()],
..TaskTemplate::default()
},
@@ -447,6 +450,7 @@ impl ContextProvider for PythonContextProvider {
"unittest".to_owned(),
VariableName::File.template_value_with_whitespace(),
],
+ cwd: Some("$ZED_WORKTREE_ROOT".into()),
..TaskTemplate::default()
},
// Run test(s) for a specific target within a file
@@ -462,6 +466,7 @@ impl ContextProvider for PythonContextProvider {
"python-unittest-class".to_owned(),
"python-unittest-method".to_owned(),
],
+ cwd: Some("$ZED_WORKTREE_ROOT".into()),
..TaskTemplate::default()
},
]
@@ -477,6 +482,7 @@ impl ContextProvider for PythonContextProvider {
"pytest".to_owned(),
VariableName::File.template_value_with_whitespace(),
],
+ cwd: Some("$ZED_WORKTREE_ROOT".into()),
..TaskTemplate::default()
},
// Run test(s) for a specific target within a file
@@ -488,6 +494,7 @@ impl ContextProvider for PythonContextProvider {
"pytest".to_owned(),
PYTHON_TEST_TARGET_TASK_VARIABLE.template_value_with_whitespace(),
],
+ cwd: Some("$ZED_WORKTREE_ROOT".into()),
tags: vec![
"python-pytest-class".to_owned(),
"python-pytest-method".to_owned(),
@@ -101,7 +101,9 @@ impl DapStore {
pub fn init(client: &AnyProtoClient, cx: &mut App) {
static ADD_LOCATORS: Once = Once::new();
ADD_LOCATORS.call_once(|| {
- DapRegistry::global(cx).add_locator(Arc::new(locators::cargo::CargoLocator {}))
+ let registry = DapRegistry::global(cx);
+ registry.add_locator(Arc::new(locators::cargo::CargoLocator {}));
+ registry.add_locator(Arc::new(locators::python::PythonLocator));
});
client.add_entity_request_handler(Self::handle_run_debug_locator);
client.add_entity_request_handler(Self::handle_get_debug_adapter_binary);
@@ -412,7 +414,6 @@ impl DapStore {
this.get_debug_adapter_binary(definition.clone(), session_id, console, cx)
})?
.await?;
-
session
.update(cx, |session, cx| {
session.boot(binary, worktree, dap_store, cx)
@@ -1 +1,2 @@
pub(crate) mod cargo;
+pub(crate) mod python;
@@ -0,0 +1,99 @@
+use std::path::Path;
+
+use anyhow::{Result, bail};
+use async_trait::async_trait;
+use dap::{DapLocator, DebugRequest, adapters::DebugAdapterName};
+use gpui::SharedString;
+
+use task::{DebugScenario, SpawnInTerminal, TaskTemplate};
+
+pub(crate) struct PythonLocator;
+
+#[async_trait]
+impl DapLocator for PythonLocator {
+ fn name(&self) -> SharedString {
+ SharedString::new_static("Python")
+ }
+
+ /// Determines whether this locator can generate debug target for given task.
+ fn create_scenario(
+ &self,
+ build_config: &TaskTemplate,
+ resolved_label: &str,
+ adapter: DebugAdapterName,
+ ) -> Option<DebugScenario> {
+ if adapter.as_ref() != "Debugpy" {
+ return None;
+ }
+ let valid_program = build_config.command.starts_with("$ZED_")
+ || Path::new(&build_config.command)
+ .file_name()
+ .map_or(false, |name| {
+ name.to_str().is_some_and(|path| path.starts_with("python"))
+ });
+ if !valid_program || build_config.args.iter().any(|arg| arg == "-c") {
+ // We cannot debug selections.
+ return None;
+ }
+ let module_specifier_position = build_config
+ .args
+ .iter()
+ .position(|arg| arg == "-m")
+ .map(|position| position + 1);
+ // Skip the -m and module name, get all that's after.
+ let mut rest_of_the_args = module_specifier_position
+ .and_then(|position| build_config.args.get(position..))
+ .into_iter()
+ .flatten()
+ .fuse();
+ let mod_name = rest_of_the_args.next();
+ let args = rest_of_the_args.collect::<Vec<_>>();
+
+ let program_position = mod_name
+ .is_none()
+ .then(|| {
+ build_config
+ .args
+ .iter()
+ .position(|arg| *arg == "\"$ZED_FILE\"")
+ })
+ .flatten();
+ let args = if let Some(position) = program_position {
+ args.into_iter().skip(position).collect::<Vec<_>>()
+ } else {
+ args
+ };
+ if program_position.is_none() && mod_name.is_none() {
+ return None;
+ }
+ let mut config = serde_json::json!({
+ "request": "launch",
+ "python": build_config.command,
+ "args": args,
+ "cwd": build_config.cwd.clone()
+ });
+ if let Some(config_obj) = config.as_object_mut() {
+ if let Some(module) = mod_name {
+ config_obj.insert("module".to_string(), module.clone().into());
+ }
+ if let Some(program) = program_position {
+ config_obj.insert(
+ "program".to_string(),
+ build_config.args[program].clone().into(),
+ );
+ }
+ }
+
+ Some(DebugScenario {
+ adapter: adapter.0,
+ label: resolved_label.to_string().into(),
+ build: None,
+ config,
+ tcp_connection: None,
+ })
+ }
+
+ async fn run(&self, _: SpawnInTerminal) -> Result<DebugRequest> {
+ bail!("Python locator should not require DapLocator::run to be ran");
+ }
+}