python.rs

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