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, 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}