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}