debugger: Add locator for Python tasks (#31533)
Closes #ISSUE Release Notes: - debugger: Python tests/main functions can now we debugged from the gutter. --------- Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
parent
15d59fcda9
commit
c0a5ace8b8
6 changed files with 118 additions and 3 deletions
|
@ -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;
|
||||
|
|
99
crates/project/src/debugger/locators/python.rs
Normal file
99
crates/project/src/debugger/locators/python.rs
Normal file
|
@ -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");
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue