python.rs

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