146 lines · 4.9 KB · Rust
RawDownload
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, VariableName}; 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.    async fn create_scenario(        &self,        build_config: &TaskTemplate,        resolved_label: &str,        adapter: &DebugAdapterName,    ) -> Option<DebugScenario> {        if adapter.0.as_ref() != "Debugpy" {            return None;        }        let valid_program = build_config.command.starts_with("$ZED_")            || Path::new(&build_config.command)                .file_name()                .is_some_and(|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 command = build_config.command.clone();        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(|| {                let zed_file = VariableName::File.template_value_with_whitespace();                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": 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.clone(),            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");    }} #[cfg(test)]mod test {    use serde_json::json;     use super::*;     #[gpui::test]    async fn test_python_locator() {        let adapter = DebugAdapterName("Debugpy".into());        let build_task = TaskTemplate {            label: "run module '$ZED_FILE'".into(),            command: "$ZED_CUSTOM_PYTHON_ACTIVE_ZED_TOOLCHAIN".into(),            args: vec!["-m".into(), "$ZED_CUSTOM_PYTHON_MODULE_NAME".into()],            env: Default::default(),            cwd: Some("$ZED_WORKTREE_ROOT".into()),            use_new_terminal: false,            allow_concurrent_runs: false,            reveal: task::RevealStrategy::Always,            reveal_target: task::RevealTarget::Dock,            hide: task::HideStrategy::Never,            tags: vec!["python-module-main-method".into()],            shell: task::Shell::System,            show_summary: false,            show_command: false,        };         let expected_scenario = DebugScenario {            adapter: "Debugpy".into(),            label: "run module 'main.py'".into(),            build: None,            config: json!({                "request": "launch",                "python": "$ZED_CUSTOM_PYTHON_ACTIVE_ZED_TOOLCHAIN",                "args": [],                "cwd": "$ZED_WORKTREE_ROOT",                "module": "$ZED_CUSTOM_PYTHON_MODULE_NAME",            }),            tcp_connection: None,        };         assert_eq!(            PythonLocator                .create_scenario(&build_task, "run module 'main.py'", &adapter)                .await                .expect("Failed to create a scenario"),            expected_scenario        );    }}