python.rs

  1use crate::*;
  2use anyhow::bail;
  3use dap::DebugRequestType;
  4use gpui::AsyncApp;
  5use std::{ffi::OsStr, path::PathBuf};
  6use task::DebugTaskDefinition;
  7
  8#[derive(Default)]
  9pub(crate) struct PythonDebugAdapter;
 10
 11impl PythonDebugAdapter {
 12    const ADAPTER_NAME: &'static str = "Debugpy";
 13    const ADAPTER_PACKAGE_NAME: &'static str = "debugpy";
 14    const ADAPTER_PATH: &'static str = "src/debugpy/adapter";
 15    const LANGUAGE_NAME: &'static str = "Python";
 16}
 17
 18#[async_trait(?Send)]
 19impl DebugAdapter for PythonDebugAdapter {
 20    fn name(&self) -> DebugAdapterName {
 21        DebugAdapterName(Self::ADAPTER_NAME.into())
 22    }
 23
 24    async fn fetch_latest_adapter_version(
 25        &self,
 26        delegate: &dyn DapDelegate,
 27    ) -> Result<AdapterVersion> {
 28        let github_repo = GithubRepo {
 29            repo_name: Self::ADAPTER_PACKAGE_NAME.into(),
 30            repo_owner: "microsoft".into(),
 31        };
 32
 33        adapters::fetch_latest_adapter_version_from_github(github_repo, delegate).await
 34    }
 35
 36    async fn install_binary(
 37        &self,
 38        version: AdapterVersion,
 39        delegate: &dyn DapDelegate,
 40    ) -> Result<()> {
 41        let version_path = adapters::download_adapter_from_github(
 42            self.name(),
 43            version,
 44            adapters::DownloadedFileType::Zip,
 45            delegate,
 46        )
 47        .await?;
 48
 49        // only needed when you install the latest version for the first time
 50        if let Some(debugpy_dir) =
 51            util::fs::find_file_name_in_dir(version_path.as_path(), |file_name| {
 52                file_name.starts_with("microsoft-debugpy-")
 53            })
 54            .await
 55        {
 56            // TODO Debugger: Rename folder instead of moving all files to another folder
 57            // We're doing unnecessary IO work right now
 58            util::fs::move_folder_files_to_folder(debugpy_dir.as_path(), version_path.as_path())
 59                .await?;
 60        }
 61
 62        Ok(())
 63    }
 64
 65    async fn get_installed_binary(
 66        &self,
 67        delegate: &dyn DapDelegate,
 68        config: &DebugAdapterConfig,
 69        user_installed_path: Option<PathBuf>,
 70        cx: &mut AsyncApp,
 71    ) -> Result<DebugAdapterBinary> {
 72        const BINARY_NAMES: [&str; 3] = ["python3", "python", "py"];
 73        let Some(tcp_connection) = config.tcp_connection.clone() else {
 74            bail!("Python Debug Adapter expects tcp connection arguments to be provided");
 75        };
 76        let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
 77
 78        let debugpy_dir = if let Some(user_installed_path) = user_installed_path {
 79            user_installed_path
 80        } else {
 81            let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());
 82            let file_name_prefix = format!("{}_", Self::ADAPTER_PACKAGE_NAME);
 83
 84            util::fs::find_file_name_in_dir(adapter_path.as_path(), |file_name| {
 85                file_name.starts_with(&file_name_prefix)
 86            })
 87            .await
 88            .ok_or_else(|| anyhow!("Debugpy directory not found"))?
 89        };
 90
 91        let toolchain = delegate
 92            .toolchain_store()
 93            .active_toolchain(
 94                delegate.worktree_id(),
 95                language::LanguageName::new(Self::LANGUAGE_NAME),
 96                cx,
 97            )
 98            .await;
 99
100        let python_path = if let Some(toolchain) = toolchain {
101            Some(toolchain.path.to_string())
102        } else {
103            BINARY_NAMES
104                .iter()
105                .filter_map(|cmd| {
106                    delegate
107                        .which(OsStr::new(cmd))
108                        .map(|path| path.to_string_lossy().to_string())
109                })
110                .find(|_| true)
111        };
112
113        Ok(DebugAdapterBinary {
114            command: python_path.ok_or(anyhow!("failed to find binary path for python"))?,
115            arguments: Some(vec![
116                debugpy_dir.join(Self::ADAPTER_PATH).into(),
117                format!("--port={}", port).into(),
118                format!("--host={}", host).into(),
119            ]),
120            connection: Some(adapters::TcpArguments {
121                host,
122                port,
123                timeout,
124            }),
125            cwd: None,
126            envs: None,
127        })
128    }
129
130    fn request_args(&self, config: &DebugTaskDefinition) -> Value {
131        match &config.request {
132            DebugRequestType::Launch(launch_config) => {
133                json!({
134                    "program": launch_config.program,
135                    "subProcess": true,
136                    "cwd": launch_config.cwd,
137                    "redirectOutput": true,
138                })
139            }
140            dap::DebugRequestType::Attach(attach_config) => {
141                json!({
142                    "subProcess": true,
143                    "redirectOutput": true,
144                    "processId": attach_config.process_id
145                })
146            }
147        }
148    }
149}