dap_adapters.rs

  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}