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_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                Arc::from("".as_ref()),
 93                language::LanguageName::new(Self::LANGUAGE_NAME),
 94                cx,
 95            )
 96            .await;
 97
 98        let python_path = if let Some(toolchain) = toolchain {
 99            Some(toolchain.path.to_string())
100        } else {
101            BINARY_NAMES
102                .iter()
103                .filter_map(|cmd| {
104                    delegate
105                        .which(OsStr::new(cmd))
106                        .map(|path| path.to_string_lossy().to_string())
107                })
108                .find(|_| true)
109        };
110
111        Ok(DebugAdapterBinary {
112            command: python_path.ok_or(anyhow!("failed to find binary path for python"))?,
113            arguments: Some(vec![
114                debugpy_dir.join(Self::ADAPTER_PATH).into(),
115                format!("--port={}", port).into(),
116                format!("--host={}", host).into(),
117            ]),
118            connection: Some(adapters::TcpArguments {
119                host,
120                port,
121                timeout,
122            }),
123            cwd: None,
124            envs: None,
125        })
126    }
127
128    fn request_args(&self, config: &DebugTaskDefinition) -> Value {
129        let mut args = json!({
130            "request": match config.request {
131                DebugRequestType::Launch(_) => "launch",
132                DebugRequestType::Attach(_) => "attach",
133            },
134            "subProcess": true,
135            "redirectOutput": true,
136        });
137        let map = args.as_object_mut().unwrap();
138        match &config.request {
139            DebugRequestType::Attach(attach) => {
140                map.insert("processId".into(), attach.process_id.into());
141            }
142            DebugRequestType::Launch(launch) => {
143                map.insert("program".into(), launch.program.clone().into());
144                map.insert("args".into(), launch.args.clone().into());
145
146                if let Some(stop_on_entry) = config.stop_on_entry {
147                    map.insert("stopOnEntry".into(), stop_on_entry.into());
148                }
149                if let Some(cwd) = launch.cwd.as_ref() {
150                    map.insert("cwd".into(), cwd.to_string_lossy().into_owned().into());
151                }
152            }
153        }
154        args
155    }
156}