python.rs

 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};
 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    fn create_scenario(
20        &self,
21        build_config: &TaskTemplate,
22        resolved_label: &str,
23        adapter: DebugAdapterName,
24    ) -> Option<DebugScenario> {
25        if adapter.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 module_specifier_position = build_config
39            .args
40            .iter()
41            .position(|arg| arg == "-m")
42            .map(|position| position + 1);
43        // Skip the -m and module name, get all that's after.
44        let mut rest_of_the_args = module_specifier_position
45            .and_then(|position| build_config.args.get(position..))
46            .into_iter()
47            .flatten()
48            .fuse();
49        let mod_name = rest_of_the_args.next();
50        let args = rest_of_the_args.collect::<Vec<_>>();
51
52        let program_position = mod_name
53            .is_none()
54            .then(|| {
55                build_config
56                    .args
57                    .iter()
58                    .position(|arg| *arg == "\"$ZED_FILE\"")
59            })
60            .flatten();
61        let args = if let Some(position) = program_position {
62            args.into_iter().skip(position).collect::<Vec<_>>()
63        } else {
64            args
65        };
66        if program_position.is_none() && mod_name.is_none() {
67            return None;
68        }
69        let mut config = serde_json::json!({
70            "request": "launch",
71            "python": build_config.command,
72            "args": args,
73            "cwd": build_config.cwd.clone()
74        });
75        if let Some(config_obj) = config.as_object_mut() {
76            if let Some(module) = mod_name {
77                config_obj.insert("module".to_string(), module.clone().into());
78            }
79            if let Some(program) = program_position {
80                config_obj.insert(
81                    "program".to_string(),
82                    build_config.args[program].clone().into(),
83                );
84            }
85        }
86
87        Some(DebugScenario {
88            adapter: adapter.0,
89            label: resolved_label.to_string().into(),
90            build: None,
91            config,
92            tcp_connection: None,
93        })
94    }
95
96    async fn run(&self, _: SpawnInTerminal) -> Result<DebugRequest> {
97        bail!("Python locator should not require DapLocator::run to be ran");
98    }
99}