1mod codelldb;
2mod gdb;
3mod go;
4mod javascript;
5mod php;
6mod python;
7
8use std::sync::Arc;
9
10use anyhow::Result;
11use async_trait::async_trait;
12pub use codelldb::CodeLldbDebugAdapter;
13use dap::{
14 DapRegistry,
15 adapters::{
16 self, AdapterVersion, DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName,
17 GithubRepo,
18 },
19 configure_tcp_connection,
20};
21use gdb::GdbDebugAdapter;
22pub use go::GoDebugAdapter;
23use gpui::{App, BorrowAppContext};
24pub use javascript::JsDebugAdapter;
25use php::PhpDebugAdapter;
26pub use python::PythonDebugAdapter;
27use serde_json::json;
28use task::{DebugScenario, ZedDebugConfig};
29
30pub fn init(cx: &mut App) {
31 cx.update_default_global(|registry: &mut DapRegistry, _cx| {
32 registry.add_adapter(Arc::from(CodeLldbDebugAdapter::default()));
33 registry.add_adapter(Arc::from(PythonDebugAdapter::default()));
34 registry.add_adapter(Arc::from(PhpDebugAdapter::default()));
35 registry.add_adapter(Arc::from(JsDebugAdapter::default()));
36 registry.add_adapter(Arc::from(GoDebugAdapter::default()));
37 registry.add_adapter(Arc::from(GdbDebugAdapter));
38
39 #[cfg(any(test, feature = "test-support"))]
40 {
41 registry.add_adapter(Arc::from(dap::FakeAdapter {}));
42 }
43 })
44}
45
46#[cfg(feature = "update-schemas")]
47#[derive(Clone)]
48pub struct UpdateSchemasDapDelegate {
49 client: std::sync::Arc<reqwest_client::ReqwestClient>,
50 fs: std::sync::Arc<fs::RealFs>,
51 executor: gpui::BackgroundExecutor,
52}
53
54#[cfg(feature = "update-schemas")]
55impl UpdateSchemasDapDelegate {
56 pub fn new() -> Self {
57 let executor = gpui::background_executor();
58 let client =
59 Arc::new(reqwest_client::ReqwestClient::user_agent("Zed DAP schema updater").unwrap());
60 let fs = Arc::new(fs::RealFs::new(None, executor.clone()));
61 Self {
62 client,
63 fs,
64 executor,
65 }
66 }
67}
68
69#[cfg(feature = "update-schemas")]
70#[async_trait]
71impl dap::adapters::DapDelegate for UpdateSchemasDapDelegate {
72 fn worktree_id(&self) -> settings::WorktreeId {
73 unreachable!()
74 }
75 fn worktree_root_path(&self) -> &std::path::Path {
76 unreachable!()
77 }
78 fn http_client(&self) -> Arc<dyn dap::adapters::HttpClient> {
79 self.client.clone()
80 }
81 fn node_runtime(&self) -> node_runtime::NodeRuntime {
82 unreachable!()
83 }
84 fn toolchain_store(&self) -> Arc<dyn language::LanguageToolchainStore> {
85 unreachable!()
86 }
87 fn fs(&self) -> Arc<dyn fs::Fs> {
88 self.fs.clone()
89 }
90 fn output_to_console(&self, msg: String) {
91 eprintln!("{msg}")
92 }
93 async fn which(&self, _command: &std::ffi::OsStr) -> Option<std::path::PathBuf> {
94 unreachable!()
95 }
96 async fn read_text_file(&self, _path: std::path::PathBuf) -> Result<String> {
97 unreachable!()
98 }
99 async fn shell_env(&self) -> collections::HashMap<String, String> {
100 unreachable!()
101 }
102}
103
104#[cfg(feature = "update-schemas")]
105#[derive(Debug, serde::Serialize, serde::Deserialize)]
106struct PackageJsonConfigurationAttributes {
107 #[serde(default, skip_serializing_if = "Option::is_none")]
108 launch: Option<serde_json::Value>,
109 #[serde(default, skip_serializing_if = "Option::is_none")]
110 attach: Option<serde_json::Value>,
111}
112
113#[cfg(feature = "update-schemas")]
114#[derive(Debug, serde::Serialize, serde::Deserialize)]
115#[serde(rename_all = "camelCase")]
116struct PackageJsonDebugger {
117 r#type: String,
118 configuration_attributes: PackageJsonConfigurationAttributes,
119}
120
121#[cfg(feature = "update-schemas")]
122#[derive(Debug, serde::Serialize, serde::Deserialize)]
123struct PackageJsonContributes {
124 debuggers: Vec<PackageJsonDebugger>,
125}
126
127#[cfg(feature = "update-schemas")]
128#[derive(Debug, serde::Serialize, serde::Deserialize)]
129struct PackageJson {
130 contributes: PackageJsonContributes,
131}
132
133#[cfg(feature = "update-schemas")]
134fn get_vsix_package_json(
135 temp_dir: &tempfile::TempDir,
136 repo: &str,
137 asset_name: impl FnOnce(&gpui::http_client::github::GithubRelease) -> anyhow::Result<String>,
138 delegate: UpdateSchemasDapDelegate,
139) -> anyhow::Result<(String, Option<String>)> {
140 use anyhow::Context as _;
141 use dap::adapters::DownloadedFileType;
142 use fs::Fs as _;
143
144 let temp_dir = std::fs::canonicalize(temp_dir.path())?;
145 let fs = delegate.fs.clone();
146 let client = delegate.client.clone();
147 let executor = delegate.executor.clone();
148
149 executor.block(async move {
150 let release = adapters::latest_github_release(repo, true, false, client.clone()).await?;
151
152 let asset_name = asset_name(&release)?;
153 let version = AdapterVersion {
154 tag_name: release.tag_name,
155 url: release
156 .assets
157 .iter()
158 .find(|asset| asset.name == asset_name)
159 .with_context(|| format!("no asset found matching {asset_name:?}"))?
160 .browser_download_url
161 .clone(),
162 };
163
164 let path = adapters::download_adapter_from_github(
165 "schemas",
166 version,
167 DownloadedFileType::Vsix,
168 &temp_dir,
169 &delegate,
170 )
171 .await?;
172 let package_json = fs
173 .load(&path.join("extension").join("package.json"))
174 .await?;
175 let package_nls_json = fs
176 .load(&path.join("extension").join("package.nls.json"))
177 .await
178 .ok();
179 anyhow::Ok((package_json, package_nls_json))
180 })
181}
182
183#[cfg(feature = "update-schemas")]
184fn parse_package_json(
185 package_json: String,
186 package_nls_json: Option<String>,
187) -> anyhow::Result<PackageJson> {
188 use collections::HashMap;
189 use task::{EnvVariableReplacer, VariableName};
190
191 let package_nls_json = package_nls_json
192 .map(|package_nls_json| {
193 let package_nls_json =
194 serde_json::from_str::<HashMap<String, serde_json::Value>>(&package_nls_json)?;
195 let package_nls_json = package_nls_json
196 .into_iter()
197 .filter_map(|(k, v)| {
198 let v = v.as_str()?;
199 Some((k, v.to_owned()))
200 })
201 .collect();
202 anyhow::Ok(package_nls_json)
203 })
204 .transpose()?
205 .unwrap_or_default();
206
207 let package_json: serde_json::Value = serde_json::from_str(&package_json)?;
208
209 struct Replacer {
210 package_nls_json: HashMap<String, String>,
211 env: EnvVariableReplacer,
212 }
213
214 impl Replacer {
215 fn replace(&self, input: serde_json::Value) -> serde_json::Value {
216 match input {
217 serde_json::Value::String(s) => {
218 if s.starts_with("%") && s.ends_with("%") {
219 self.package_nls_json
220 .get(s.trim_matches('%'))
221 .map(|s| s.as_str().into())
222 .unwrap_or("(missing)".into())
223 } else {
224 self.env.replace(&s).into()
225 }
226 }
227 serde_json::Value::Array(arr) => {
228 serde_json::Value::Array(arr.into_iter().map(|v| self.replace(v)).collect())
229 }
230 serde_json::Value::Object(obj) => serde_json::Value::Object(
231 obj.into_iter().map(|(k, v)| (k, self.replace(v))).collect(),
232 ),
233 _ => input,
234 }
235 }
236 }
237
238 let env = EnvVariableReplacer::new(HashMap::from_iter([(
239 "workspaceFolder".to_owned(),
240 VariableName::WorktreeRoot.to_string(),
241 )]));
242 let replacer = Replacer {
243 env,
244 package_nls_json,
245 };
246 let package_json = replacer.replace(package_json);
247
248 let package_json: PackageJson = serde_json::from_value(package_json)?;
249 Ok(package_json)
250}
251
252#[cfg(feature = "update-schemas")]
253fn schema_for_configuration_attributes(
254 attrs: PackageJsonConfigurationAttributes,
255) -> serde_json::Value {
256 let conjuncts = attrs
257 .launch
258 .map(|schema| ("launch", schema))
259 .into_iter()
260 .chain(attrs.attach.map(|schema| ("attach", schema)))
261 .map(|(request, schema)| {
262 json!({
263 "if": {
264 "properties": {
265 "request": {
266 "const": request
267 }
268 },
269 "required": ["request"]
270 },
271 "then": schema
272 })
273 })
274 .collect::<Vec<_>>();
275
276 json!({
277 "allOf": conjuncts
278 })
279}