dap_adapters.rs

  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}