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}