python.rs

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