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