Sync extension debuggers to remote host (#33876)

Ryan Hawkins created

Closes #33835

Release Notes:

- Fixed debugger extensions not working in remote projects.

Change summary

crates/extension/src/extension_builder.rs   | 11 ++----
crates/extension/src/extension_manifest.rs  | 33 +++++++++++++++++++++++
crates/extension_host/src/extension_host.rs | 17 +++++++++++
crates/extension_host/src/headless_host.rs  | 27 ++++++++++++++++--
4 files changed, 77 insertions(+), 11 deletions(-)

Detailed changes

crates/extension/src/extension_builder.rs 🔗

@@ -1,5 +1,6 @@
 use crate::{
-    ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry, parse_wasm_extension_version,
+    ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry, build_debug_adapter_schema_path,
+    parse_wasm_extension_version,
 };
 use anyhow::{Context as _, Result, bail};
 use async_compression::futures::bufread::GzipDecoder;
@@ -99,12 +100,8 @@ impl ExtensionBuilder {
         }
 
         for (debug_adapter_name, meta) in &mut extension_manifest.debug_adapters {
-            let debug_adapter_relative_schema_path =
-                meta.schema_path.clone().unwrap_or_else(|| {
-                    Path::new("debug_adapter_schemas")
-                        .join(Path::new(debug_adapter_name.as_ref()).with_extension("json"))
-                });
-            let debug_adapter_schema_path = extension_dir.join(debug_adapter_relative_schema_path);
+            let debug_adapter_schema_path =
+                extension_dir.join(build_debug_adapter_schema_path(debug_adapter_name, meta));
 
             let debug_adapter_schema = fs::read_to_string(&debug_adapter_schema_path)
                 .with_context(|| {

crates/extension/src/extension_manifest.rs 🔗

@@ -132,6 +132,16 @@ impl ExtensionManifest {
     }
 }
 
+pub fn build_debug_adapter_schema_path(
+    adapter_name: &Arc<str>,
+    meta: &DebugAdapterManifestEntry,
+) -> PathBuf {
+    meta.schema_path.clone().unwrap_or_else(|| {
+        Path::new("debug_adapter_schemas")
+            .join(Path::new(adapter_name.as_ref()).with_extension("json"))
+    })
+}
+
 /// A capability for an extension.
 #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
 #[serde(tag = "kind")]
@@ -320,6 +330,29 @@ mod tests {
         }
     }
 
+    #[test]
+    fn test_build_adapter_schema_path_with_schema_path() {
+        let adapter_name = Arc::from("my_adapter");
+        let entry = DebugAdapterManifestEntry {
+            schema_path: Some(PathBuf::from("foo/bar")),
+        };
+
+        let path = build_debug_adapter_schema_path(&adapter_name, &entry);
+        assert_eq!(path, PathBuf::from("foo/bar"));
+    }
+
+    #[test]
+    fn test_build_adapter_schema_path_without_schema_path() {
+        let adapter_name = Arc::from("my_adapter");
+        let entry = DebugAdapterManifestEntry { schema_path: None };
+
+        let path = build_debug_adapter_schema_path(&adapter_name, &entry);
+        assert_eq!(
+            path,
+            PathBuf::from("debug_adapter_schemas").join("my_adapter.json")
+        );
+    }
+
     #[test]
     fn test_allow_exact_match() {
         let manifest = ExtensionManifest {

crates/extension_host/src/extension_host.rs 🔗

@@ -1639,6 +1639,23 @@ impl ExtensionStore {
                 }
             }
 
+            for (adapter_name, meta) in loaded_extension.manifest.debug_adapters.iter() {
+                let schema_path = &extension::build_debug_adapter_schema_path(adapter_name, meta);
+
+                if fs.is_file(&src_dir.join(schema_path)).await {
+                    match schema_path.parent() {
+                        Some(parent) => fs.create_dir(&tmp_dir.join(parent)).await?,
+                        None => {}
+                    }
+                    fs.copy_file(
+                        &src_dir.join(schema_path),
+                        &tmp_dir.join(schema_path),
+                        fs::CopyOptions::default(),
+                    )
+                    .await?
+                }
+            }
+
             Ok(())
         })
     }

crates/extension_host/src/headless_host.rs 🔗

@@ -4,8 +4,8 @@ use anyhow::{Context as _, Result};
 use client::{TypedEnvelope, proto};
 use collections::{HashMap, HashSet};
 use extension::{
-    Extension, ExtensionHostProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy,
-    ExtensionManifest,
+    Extension, ExtensionDebugAdapterProviderProxy, ExtensionHostProxy, ExtensionLanguageProxy,
+    ExtensionLanguageServerProxy, ExtensionManifest,
 };
 use fs::{Fs, RemoveOptions, RenameOptions};
 use gpui::{App, AppContext as _, AsyncApp, Context, Entity, Task, WeakEntity};
@@ -169,8 +169,9 @@ impl HeadlessExtensionStore {
             return Ok(());
         }
 
-        let wasm_extension: Arc<dyn Extension> =
-            Arc::new(WasmExtension::load(extension_dir, &manifest, wasm_host.clone(), &cx).await?);
+        let wasm_extension: Arc<dyn Extension> = Arc::new(
+            WasmExtension::load(extension_dir.clone(), &manifest, wasm_host.clone(), &cx).await?,
+        );
 
         for (language_server_id, language_server_config) in &manifest.language_servers {
             for language in language_server_config.languages() {
@@ -186,6 +187,24 @@ impl HeadlessExtensionStore {
                     );
                 })?;
             }
+            for (debug_adapter, meta) in &manifest.debug_adapters {
+                let schema_path = extension::build_debug_adapter_schema_path(debug_adapter, meta);
+
+                this.update(cx, |this, _cx| {
+                    this.proxy.register_debug_adapter(
+                        wasm_extension.clone(),
+                        debug_adapter.clone(),
+                        &extension_dir.join(schema_path),
+                    );
+                })?;
+            }
+
+            for debug_adapter in manifest.debug_locators.keys() {
+                this.update(cx, |this, _cx| {
+                    this.proxy
+                        .register_debug_locator(wasm_extension.clone(), debug_adapter.clone());
+                })?;
+            }
         }
 
         Ok(())