1use std::path::Path;
2
3use anyhow::{Result, bail};
4use async_trait::async_trait;
5use dap::{DapLocator, DebugRequest, adapters::DebugAdapterName};
6use gpui::SharedString;
7
8use task::{DebugScenario, SpawnInTerminal, TaskTemplate, VariableName};
9
10pub(crate) struct PythonLocator;
11
12#[async_trait]
13impl DapLocator for PythonLocator {
14 fn name(&self) -> SharedString {
15 SharedString::new_static("Python")
16 }
17
18 /// Determines whether this locator can generate debug target for given task.
19 async fn create_scenario(
20 &self,
21 build_config: &TaskTemplate,
22 resolved_label: &str,
23 adapter: &DebugAdapterName,
24 ) -> Option<DebugScenario> {
25 if adapter.0.as_ref() != "Debugpy" {
26 return None;
27 }
28 let valid_program = build_config.command.starts_with("$ZED_")
29 || Path::new(&build_config.command)
30 .file_name()
31 .map_or(false, |name| {
32 name.to_str().is_some_and(|path| path.starts_with("python"))
33 });
34 if !valid_program || build_config.args.iter().any(|arg| arg == "-c") {
35 // We cannot debug selections.
36 return None;
37 }
38 let command = if build_config.command
39 == VariableName::Custom("PYTHON_ACTIVE_ZED_TOOLCHAIN".into()).template_value()
40 {
41 VariableName::Custom("PYTHON_ACTIVE_ZED_TOOLCHAIN_RAW".into()).template_value()
42 } else {
43 build_config.command.clone()
44 };
45 let module_specifier_position = build_config
46 .args
47 .iter()
48 .position(|arg| arg == "-m")
49 .map(|position| position + 1);
50 // Skip the -m and module name, get all that's after.
51 let mut rest_of_the_args = module_specifier_position
52 .and_then(|position| build_config.args.get(position..))
53 .into_iter()
54 .flatten()
55 .fuse();
56 let mod_name = rest_of_the_args.next();
57 let args = rest_of_the_args.collect::<Vec<_>>();
58
59 let program_position = mod_name
60 .is_none()
61 .then(|| {
62 build_config
63 .args
64 .iter()
65 .position(|arg| *arg == "\"$ZED_FILE\"")
66 })
67 .flatten();
68 let args = if let Some(position) = program_position {
69 args.into_iter().skip(position).collect::<Vec<_>>()
70 } else {
71 args
72 };
73 if program_position.is_none() && mod_name.is_none() {
74 return None;
75 }
76 let mut config = serde_json::json!({
77 "request": "launch",
78 "python": command,
79 "args": args,
80 "cwd": build_config.cwd.clone()
81 });
82 if let Some(config_obj) = config.as_object_mut() {
83 if let Some(module) = mod_name {
84 config_obj.insert("module".to_string(), module.clone().into());
85 }
86 if let Some(program) = program_position {
87 config_obj.insert(
88 "program".to_string(),
89 build_config.args[program].clone().into(),
90 );
91 }
92 }
93
94 Some(DebugScenario {
95 adapter: adapter.0.clone(),
96 label: resolved_label.to_string().into(),
97 build: None,
98 config,
99 tcp_connection: None,
100 })
101 }
102
103 async fn run(&self, _: SpawnInTerminal) -> Result<DebugRequest> {
104 bail!("Python locator should not require DapLocator::run to be ran");
105 }
106}