extension: Add debug_adapters to extension manifest (#30676)

Piotr Osiewicz created

Also pass worktree to the get_dap_binary.

Release Notes:

- N/A

Change summary

Cargo.lock                                                  |  3 
crates/dap/src/adapters.rs                                  | 12 +
crates/dap_adapters/src/codelldb.rs                         |  6 
crates/dap_adapters/src/gdb.rs                              |  3 
crates/dap_adapters/src/go.rs                               |  3 
crates/dap_adapters/src/javascript.rs                       |  8 
crates/dap_adapters/src/php.rs                              |  8 
crates/dap_adapters/src/python.rs                           | 32 +++--
crates/dap_adapters/src/ruby.rs                             |  8 
crates/debug_adapter_extension/src/extension_dap_adapter.rs | 34 +++++
crates/eval/Cargo.toml                                      |  1 
crates/eval/src/eval.rs                                     |  1 
crates/extension/src/extension.rs                           |  1 
crates/extension/src/extension_manifest.rs                  |  4 
crates/extension_api/src/extension_api.rs                   | 10 +
crates/extension_api/wit/since_v0.6.0/extension.wit         |  4 
crates/extension_host/src/extension_host.rs                 | 12 +
crates/extension_host/src/extension_store_test.rs           |  3 
crates/extension_host/src/wasm_host.rs                      |  4 
crates/extension_host/src/wasm_host/wit.rs                  |  2 
crates/language/src/toolchain.rs                            |  2 
crates/project/src/debugger/dap_store.rs                    | 41 ++++--
crates/remote_server/Cargo.toml                             |  1 
crates/remote_server/src/headless_project.rs                |  1 
crates/zed/Cargo.toml                                       |  1 
crates/zed/src/main.rs                                      |  1 
26 files changed, 147 insertions(+), 59 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4980,6 +4980,7 @@ dependencies = [
  "clap",
  "client",
  "collections",
+ "debug_adapter_extension",
  "dirs 4.0.0",
  "dotenv",
  "env_logger 0.11.8",
@@ -12883,6 +12884,7 @@ dependencies = [
  "clock",
  "dap",
  "dap_adapters",
+ "debug_adapter_extension",
  "env_logger 0.11.8",
  "extension",
  "extension_host",
@@ -19631,6 +19633,7 @@ dependencies = [
  "dap",
  "dap_adapters",
  "db",
+ "debug_adapter_extension",
  "debugger_tools",
  "debugger_ui",
  "diagnostics",

crates/dap/src/adapters.rs 🔗

@@ -32,15 +32,17 @@ pub enum DapStatus {
     Failed { error: String },
 }
 
-#[async_trait(?Send)]
-pub trait DapDelegate {
+#[async_trait]
+pub trait DapDelegate: Send + Sync + 'static {
     fn worktree_id(&self) -> WorktreeId;
+    fn worktree_root_path(&self) -> &Path;
     fn http_client(&self) -> Arc<dyn HttpClient>;
     fn node_runtime(&self) -> NodeRuntime;
     fn toolchain_store(&self) -> Arc<dyn LanguageToolchainStore>;
     fn fs(&self) -> Arc<dyn Fs>;
     fn output_to_console(&self, msg: String);
-    fn which(&self, command: &OsStr) -> Option<PathBuf>;
+    async fn which(&self, command: &OsStr) -> Option<PathBuf>;
+    async fn read_text_file(&self, path: PathBuf) -> Result<String>;
     async fn shell_env(&self) -> collections::HashMap<String, String>;
 }
 
@@ -413,7 +415,7 @@ pub trait DebugAdapter: 'static + Send + Sync {
 
     async fn get_binary(
         &self,
-        delegate: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
         config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         cx: &mut AsyncApp,
@@ -472,7 +474,7 @@ impl DebugAdapter for FakeAdapter {
 
     async fn get_binary(
         &self,
-        _: &dyn DapDelegate,
+        _: &Arc<dyn DapDelegate>,
         config: &DebugTaskDefinition,
         _: Option<PathBuf>,
         _: &mut AsyncApp,

crates/dap_adapters/src/codelldb.rs 🔗

@@ -61,7 +61,7 @@ impl CodeLldbDebugAdapter {
 
     async fn fetch_latest_adapter_version(
         &self,
-        delegate: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
     ) -> Result<AdapterVersion> {
         let release =
             latest_github_release("vadimcn/codelldb", true, false, delegate.http_client()).await?;
@@ -111,7 +111,7 @@ impl DebugAdapter for CodeLldbDebugAdapter {
 
     async fn get_binary(
         &self,
-        delegate: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
         config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         _: &mut AsyncApp,
@@ -129,7 +129,7 @@ impl DebugAdapter for CodeLldbDebugAdapter {
                         self.name(),
                         version.clone(),
                         adapters::DownloadedFileType::Vsix,
-                        delegate,
+                        delegate.as_ref(),
                     )
                     .await?;
                     let version_path =

crates/dap_adapters/src/gdb.rs 🔗

@@ -65,7 +65,7 @@ impl DebugAdapter for GdbDebugAdapter {
 
     async fn get_binary(
         &self,
-        delegate: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
         config: &DebugTaskDefinition,
         user_installed_path: Option<std::path::PathBuf>,
         _: &mut AsyncApp,
@@ -76,6 +76,7 @@ impl DebugAdapter for GdbDebugAdapter {
 
         let gdb_path = delegate
             .which(OsStr::new("gdb"))
+            .await
             .and_then(|p| p.to_str().map(|s| s.to_string()))
             .ok_or(anyhow!("Could not find gdb in path"));
 

crates/dap_adapters/src/go.rs 🔗

@@ -50,13 +50,14 @@ impl DebugAdapter for GoDebugAdapter {
 
     async fn get_binary(
         &self,
-        delegate: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
         config: &DebugTaskDefinition,
         _user_installed_path: Option<PathBuf>,
         _cx: &mut AsyncApp,
     ) -> Result<DebugAdapterBinary> {
         let delve_path = delegate
             .which(OsStr::new("dlv"))
+            .await
             .and_then(|p| p.to_str().map(|p| p.to_string()))
             .ok_or(anyhow!("Dlv not found in path"))?;
 

crates/dap_adapters/src/javascript.rs 🔗

@@ -56,7 +56,7 @@ impl JsDebugAdapter {
 
     async fn fetch_latest_adapter_version(
         &self,
-        delegate: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
     ) -> Result<AdapterVersion> {
         let release = latest_github_release(
             &format!("{}/{}", "microsoft", Self::ADAPTER_NPM_NAME),
@@ -82,7 +82,7 @@ impl JsDebugAdapter {
 
     async fn get_installed_binary(
         &self,
-        delegate: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
         config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         _: &mut AsyncApp,
@@ -139,7 +139,7 @@ impl DebugAdapter for JsDebugAdapter {
 
     async fn get_binary(
         &self,
-        delegate: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
         config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         cx: &mut AsyncApp,
@@ -151,7 +151,7 @@ impl DebugAdapter for JsDebugAdapter {
                     self.name(),
                     version,
                     adapters::DownloadedFileType::GzipTar,
-                    delegate,
+                    delegate.as_ref(),
                 )
                 .await?;
             }

crates/dap_adapters/src/php.rs 🔗

@@ -40,7 +40,7 @@ impl PhpDebugAdapter {
 
     async fn fetch_latest_adapter_version(
         &self,
-        delegate: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
     ) -> Result<AdapterVersion> {
         let release = latest_github_release(
             &format!("{}/{}", "xdebug", Self::ADAPTER_PACKAGE_NAME),
@@ -66,7 +66,7 @@ impl PhpDebugAdapter {
 
     async fn get_installed_binary(
         &self,
-        delegate: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
         config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         _: &mut AsyncApp,
@@ -126,7 +126,7 @@ impl DebugAdapter for PhpDebugAdapter {
 
     async fn get_binary(
         &self,
-        delegate: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
         config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         cx: &mut AsyncApp,
@@ -138,7 +138,7 @@ impl DebugAdapter for PhpDebugAdapter {
                     self.name(),
                     version,
                     adapters::DownloadedFileType::Vsix,
-                    delegate,
+                    delegate.as_ref(),
                 )
                 .await?;
             }

crates/dap_adapters/src/python.rs 🔗

@@ -52,26 +52,26 @@ impl PythonDebugAdapter {
     }
     async fn fetch_latest_adapter_version(
         &self,
-        delegate: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
     ) -> Result<AdapterVersion> {
         let github_repo = GithubRepo {
             repo_name: Self::ADAPTER_PACKAGE_NAME.into(),
             repo_owner: "microsoft".into(),
         };
 
-        adapters::fetch_latest_adapter_version_from_github(github_repo, delegate).await
+        adapters::fetch_latest_adapter_version_from_github(github_repo, delegate.as_ref()).await
     }
 
     async fn install_binary(
         &self,
         version: AdapterVersion,
-        delegate: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
     ) -> Result<()> {
         let version_path = adapters::download_adapter_from_github(
             self.name(),
             version,
             adapters::DownloadedFileType::Zip,
-            delegate,
+            delegate.as_ref(),
         )
         .await?;
 
@@ -93,7 +93,7 @@ impl PythonDebugAdapter {
 
     async fn get_installed_binary(
         &self,
-        delegate: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
         config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         cx: &mut AsyncApp,
@@ -128,14 +128,18 @@ impl PythonDebugAdapter {
         let python_path = if let Some(toolchain) = toolchain {
             Some(toolchain.path.to_string())
         } else {
-            BINARY_NAMES
-                .iter()
-                .filter_map(|cmd| {
-                    delegate
-                        .which(OsStr::new(cmd))
-                        .map(|path| path.to_string_lossy().to_string())
-                })
-                .find(|_| true)
+            let mut name = None;
+
+            for cmd in BINARY_NAMES {
+                name = delegate
+                    .which(OsStr::new(cmd))
+                    .await
+                    .map(|path| path.to_string_lossy().to_string());
+                if name.is_some() {
+                    break;
+                }
+            }
+            name
         };
 
         Ok(DebugAdapterBinary {
@@ -172,7 +176,7 @@ impl DebugAdapter for PythonDebugAdapter {
 
     async fn get_binary(
         &self,
-        delegate: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
         config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         cx: &mut AsyncApp,

crates/dap_adapters/src/ruby.rs 🔗

@@ -8,7 +8,7 @@ use dap::{
 };
 use gpui::{AsyncApp, SharedString};
 use language::LanguageName;
-use std::path::PathBuf;
+use std::{path::PathBuf, sync::Arc};
 use util::command::new_smol_command;
 
 use crate::ToDap;
@@ -32,7 +32,7 @@ impl DebugAdapter for RubyDebugAdapter {
 
     async fn get_binary(
         &self,
-        delegate: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
         definition: &DebugTaskDefinition,
         _user_installed_path: Option<PathBuf>,
         _cx: &mut AsyncApp,
@@ -40,7 +40,7 @@ impl DebugAdapter for RubyDebugAdapter {
         let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());
         let mut rdbg_path = adapter_path.join("rdbg");
         if !delegate.fs().is_file(&rdbg_path).await {
-            match delegate.which("rdbg".as_ref()) {
+            match delegate.which("rdbg".as_ref()).await {
                 Some(path) => rdbg_path = path,
                 None => {
                     delegate.output_to_console(
@@ -76,7 +76,7 @@ impl DebugAdapter for RubyDebugAdapter {
             format!("--port={}", port),
             format!("--host={}", host),
         ];
-        if delegate.which(launch.program.as_ref()).is_some() {
+        if delegate.which(launch.program.as_ref()).await.is_some() {
             arguments.push("--command".to_string())
         }
         arguments.push(launch.program);

crates/debug_adapter_extension/src/extension_dap_adapter.rs 🔗

@@ -5,7 +5,7 @@ use async_trait::async_trait;
 use dap::adapters::{
     DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition,
 };
-use extension::Extension;
+use extension::{Extension, WorktreeDelegate};
 use gpui::AsyncApp;
 
 pub(crate) struct ExtensionDapAdapter {
@@ -25,6 +25,35 @@ impl ExtensionDapAdapter {
     }
 }
 
+/// An adapter that allows an [`dap::adapters::DapDelegate`] to be used as a [`WorktreeDelegate`].
+struct WorktreeDelegateAdapter(pub Arc<dyn DapDelegate>);
+
+#[async_trait]
+impl WorktreeDelegate for WorktreeDelegateAdapter {
+    fn id(&self) -> u64 {
+        self.0.worktree_id().to_proto()
+    }
+
+    fn root_path(&self) -> String {
+        self.0.worktree_root_path().to_string_lossy().to_string()
+    }
+
+    async fn read_text_file(&self, path: PathBuf) -> Result<String> {
+        self.0.read_text_file(path).await
+    }
+
+    async fn which(&self, binary_name: String) -> Option<String> {
+        self.0
+            .which(binary_name.as_ref())
+            .await
+            .map(|path| path.to_string_lossy().to_string())
+    }
+
+    async fn shell_env(&self) -> Vec<(String, String)> {
+        self.0.shell_env().await.into_iter().collect()
+    }
+}
+
 #[async_trait(?Send)]
 impl DebugAdapter for ExtensionDapAdapter {
     fn name(&self) -> DebugAdapterName {
@@ -33,7 +62,7 @@ impl DebugAdapter for ExtensionDapAdapter {
 
     async fn get_binary(
         &self,
-        _: &dyn DapDelegate,
+        delegate: &Arc<dyn DapDelegate>,
         config: &DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
         _cx: &mut AsyncApp,
@@ -43,6 +72,7 @@ impl DebugAdapter for ExtensionDapAdapter {
                 self.debug_adapter_name.clone(),
                 config.clone(),
                 user_installed_path,
+                Arc::new(WorktreeDelegateAdapter(delegate.clone())),
             )
             .await
     }

crates/eval/Cargo.toml 🔗

@@ -30,6 +30,7 @@ chrono.workspace = true
 clap.workspace = true
 client.workspace = true
 collections.workspace = true
+debug_adapter_extension.workspace = true
 dirs.workspace = true
 dotenv.workspace = true
 env_logger.workspace = true

crates/eval/src/eval.rs 🔗

@@ -422,6 +422,7 @@ pub fn init(cx: &mut App) -> Arc<AgentAppState> {
     let extension_host_proxy = ExtensionHostProxy::global(cx);
 
     language::init(cx);
+    debug_adapter_extension::init(extension_host_proxy.clone(), cx);
     language_extension::init(extension_host_proxy.clone(), languages.clone());
     language_model::init(client.clone(), cx);
     language_models::init(user_store.clone(), client.clone(), fs.clone(), cx);

crates/extension/src/extension.rs 🔗

@@ -141,6 +141,7 @@ pub trait Extension: Send + Sync + 'static {
         dap_name: Arc<str>,
         config: DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
+        worktree: Arc<dyn WorktreeDelegate>,
     ) -> Result<DebugAdapterBinary>;
 }
 

crates/extension/src/extension_manifest.rs 🔗

@@ -87,6 +87,8 @@ pub struct ExtensionManifest {
     pub snippets: Option<PathBuf>,
     #[serde(default)]
     pub capabilities: Vec<ExtensionCapability>,
+    #[serde(default)]
+    pub debug_adapters: Vec<Arc<str>>,
 }
 
 impl ExtensionManifest {
@@ -274,6 +276,7 @@ fn manifest_from_old_manifest(
         indexed_docs_providers: BTreeMap::default(),
         snippets: None,
         capabilities: Vec::new(),
+        debug_adapters: vec![],
     }
 }
 
@@ -301,6 +304,7 @@ mod tests {
             indexed_docs_providers: BTreeMap::default(),
             snippets: None,
             capabilities: vec![],
+            debug_adapters: Default::default(),
         }
     }
 

crates/extension_api/src/extension_api.rs 🔗

@@ -19,6 +19,10 @@ pub use wit::{
     KeyValueStore, LanguageServerInstallationStatus, Project, Range, Worktree, download_file,
     make_file_executable,
     zed::extension::context_server::ContextServerConfiguration,
+    zed::extension::dap::{
+        DebugAdapterBinary, DebugRequest, DebugTaskDefinition, StartDebuggingRequestArguments,
+        StartDebuggingRequestArgumentsRequest, TcpArguments, TcpArgumentsTemplate,
+    },
     zed::extension::github::{
         GithubRelease, GithubReleaseAsset, GithubReleaseOptions, github_release_by_tag_name,
         latest_github_release,
@@ -194,6 +198,7 @@ pub trait Extension: Send + Sync {
         _adapter_name: String,
         _config: DebugTaskDefinition,
         _user_provided_path: Option<String>,
+        _worktree: &Worktree,
     ) -> Result<DebugAdapterBinary, String> {
         Err("`get_dap_binary` not implemented".to_string())
     }
@@ -386,8 +391,9 @@ impl wit::Guest for Component {
         adapter_name: String,
         config: DebugTaskDefinition,
         user_installed_path: Option<String>,
-    ) -> Result<DebugAdapterBinary, String> {
-        extension().get_dap_binary(adapter_name, config, user_installed_path)
+        worktree: &Worktree,
+    ) -> Result<wit::DebugAdapterBinary, String> {
+        extension().get_dap_binary(adapter_name, config, user_installed_path, worktree)
     }
 }
 

crates/extension_api/wit/since_v0.6.0/extension.wit 🔗

@@ -11,7 +11,7 @@ world extension {
 
     use common.{env-vars, range};
     use context-server.{context-server-configuration};
-    use dap.{debug-adapter-binary, debug-task-definition};
+    use dap.{debug-adapter-binary, debug-task-definition, debug-request};
     use lsp.{completion, symbol};
     use process.{command};
     use slash-command.{slash-command, slash-command-argument-completion, slash-command-output};
@@ -157,5 +157,5 @@ world extension {
     export index-docs: func(provider-name: string, package-name: string, database: borrow<key-value-store>) -> result<_, string>;
 
     /// Returns a configured debug adapter binary for a given debug task.
-    export get-dap-binary: func(adapter-name: string, config: debug-task-definition, user-installed-path: option<string>) -> result<debug-adapter-binary, string>;
+    export get-dap-binary: func(adapter-name: string, config: debug-task-definition, user-installed-path: option<string>, worktree: borrow<worktree>) -> result<debug-adapter-binary, string>;
 }

crates/extension_host/src/extension_host.rs 🔗

@@ -14,9 +14,10 @@ use collections::{BTreeMap, BTreeSet, HashMap, HashSet, btree_map};
 pub use extension::ExtensionManifest;
 use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder};
 use extension::{
-    ExtensionContextServerProxy, ExtensionEvents, ExtensionGrammarProxy, ExtensionHostProxy,
-    ExtensionIndexedDocsProviderProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy,
-    ExtensionSlashCommandProxy, ExtensionSnippetProxy, ExtensionThemeProxy,
+    ExtensionContextServerProxy, ExtensionDebugAdapterProviderProxy, ExtensionEvents,
+    ExtensionGrammarProxy, ExtensionHostProxy, ExtensionIndexedDocsProviderProxy,
+    ExtensionLanguageProxy, ExtensionLanguageServerProxy, ExtensionSlashCommandProxy,
+    ExtensionSnippetProxy, ExtensionThemeProxy,
 };
 use fs::{Fs, RemoveOptions};
 use futures::{
@@ -1328,6 +1329,11 @@ impl ExtensionStore {
                         this.proxy
                             .register_indexed_docs_provider(extension.clone(), provider_id.clone());
                     }
+
+                    for debug_adapter in &manifest.debug_adapters {
+                        this.proxy
+                            .register_debug_adapter(extension.clone(), debug_adapter.clone());
+                    }
                 }
 
                 this.wasm_extensions.extend(wasm_extensions);

crates/extension_host/src/extension_store_test.rs 🔗

@@ -164,6 +164,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
                         indexed_docs_providers: BTreeMap::default(),
                         snippets: None,
                         capabilities: Vec::new(),
+                        debug_adapters: Default::default(),
                     }),
                     dev: false,
                 },
@@ -193,6 +194,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
                         indexed_docs_providers: BTreeMap::default(),
                         snippets: None,
                         capabilities: Vec::new(),
+                        debug_adapters: Default::default(),
                     }),
                     dev: false,
                 },
@@ -367,6 +369,7 @@ async fn test_extension_store(cx: &mut TestAppContext) {
                 indexed_docs_providers: BTreeMap::default(),
                 snippets: None,
                 capabilities: Vec::new(),
+                debug_adapters: Default::default(),
             }),
             dev: false,
         },

crates/extension_host/src/wasm_host.rs 🔗

@@ -379,11 +379,13 @@ impl extension::Extension for WasmExtension {
         dap_name: Arc<str>,
         config: DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
+        worktree: Arc<dyn WorktreeDelegate>,
     ) -> Result<DebugAdapterBinary> {
         self.call(|extension, store| {
             async move {
+                let resource = store.data_mut().table().push(worktree)?;
                 let dap_binary = extension
-                    .call_get_dap_binary(store, dap_name, config, user_installed_path)
+                    .call_get_dap_binary(store, dap_name, config, user_installed_path, resource)
                     .await?
                     .map_err(|err| anyhow!("{err:?}"))?;
                 let dap_binary = dap_binary.try_into()?;

crates/extension_host/src/wasm_host/wit.rs 🔗

@@ -903,6 +903,7 @@ impl Extension {
         adapter_name: Arc<str>,
         task: DebugTaskDefinition,
         user_installed_path: Option<PathBuf>,
+        resource: Resource<Arc<dyn WorktreeDelegate>>,
     ) -> Result<Result<DebugAdapterBinary, String>> {
         match self {
             Extension::V0_6_0(ext) => {
@@ -912,6 +913,7 @@ impl Extension {
                         &adapter_name,
                         &task.try_into()?,
                         user_installed_path.as_ref().and_then(|p| p.to_str()),
+                        resource,
                     )
                     .await?
                     .map_err(|e| anyhow!("{e:?}"))?;

crates/language/src/toolchain.rs 🔗

@@ -51,7 +51,7 @@ pub trait ToolchainLister: Send + Sync {
 }
 
 #[async_trait(?Send)]
-pub trait LanguageToolchainStore {
+pub trait LanguageToolchainStore: Send + Sync + 'static {
     async fn active_toolchain(
         self: Arc<Self>,
         worktree_id: WorktreeId,

crates/project/src/debugger/dap_store.rs 🔗

@@ -10,13 +10,15 @@ use crate::{
     terminals::{SshCommand, wrap_for_ssh},
     worktree_store::WorktreeStore,
 };
-use anyhow::{Result, anyhow};
+use anyhow::{Context as _, Result, anyhow};
 use async_trait::async_trait;
 use collections::HashMap;
 use dap::{
     Capabilities, CompletionItem, CompletionsArguments, DapRegistry, DebugRequest,
     EvaluateArguments, EvaluateArgumentsContext, EvaluateResponse, Source, StackFrameId,
-    adapters::{DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition, TcpArguments},
+    adapters::{
+        DapDelegate, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition, TcpArguments,
+    },
     client::SessionId,
     inline_value::VariableLookupKind,
     messages::Message,
@@ -488,14 +490,14 @@ impl DapStore {
         worktree: &Entity<Worktree>,
         console: UnboundedSender<String>,
         cx: &mut App,
-    ) -> DapAdapterDelegate {
+    ) -> Arc<dyn DapDelegate> {
         let Some(local_store) = self.as_local() else {
             unimplemented!("Starting session on remote side");
         };
 
-        DapAdapterDelegate::new(
+        Arc::new(DapAdapterDelegate::new(
             local_store.fs.clone(),
-            worktree.read(cx).id(),
+            worktree.read(cx).snapshot(),
             console,
             local_store.node_runtime.clone(),
             local_store.http_client.clone(),
@@ -503,7 +505,7 @@ impl DapStore {
             local_store.environment.update(cx, |env, cx| {
                 env.get_worktree_environment(worktree.clone(), cx)
             }),
-        )
+        ))
     }
 
     pub fn evaluate(
@@ -811,7 +813,7 @@ impl DapStore {
 pub struct DapAdapterDelegate {
     fs: Arc<dyn Fs>,
     console: mpsc::UnboundedSender<String>,
-    worktree_id: WorktreeId,
+    worktree: worktree::Snapshot,
     node_runtime: NodeRuntime,
     http_client: Arc<dyn HttpClient>,
     toolchain_store: Arc<dyn LanguageToolchainStore>,
@@ -821,7 +823,7 @@ pub struct DapAdapterDelegate {
 impl DapAdapterDelegate {
     pub fn new(
         fs: Arc<dyn Fs>,
-        worktree_id: WorktreeId,
+        worktree: worktree::Snapshot,
         status: mpsc::UnboundedSender<String>,
         node_runtime: NodeRuntime,
         http_client: Arc<dyn HttpClient>,
@@ -831,7 +833,7 @@ impl DapAdapterDelegate {
         Self {
             fs,
             console: status,
-            worktree_id,
+            worktree,
             http_client,
             node_runtime,
             toolchain_store,
@@ -840,12 +842,15 @@ impl DapAdapterDelegate {
     }
 }
 
-#[async_trait(?Send)]
+#[async_trait]
 impl dap::adapters::DapDelegate for DapAdapterDelegate {
     fn worktree_id(&self) -> WorktreeId {
-        self.worktree_id
+        self.worktree.id()
     }
 
+    fn worktree_root_path(&self) -> &Path {
+        &self.worktree.abs_path()
+    }
     fn http_client(&self) -> Arc<dyn HttpClient> {
         self.http_client.clone()
     }
@@ -862,7 +867,7 @@ impl dap::adapters::DapDelegate for DapAdapterDelegate {
         self.console.unbounded_send(msg).ok();
     }
 
-    fn which(&self, command: &OsStr) -> Option<PathBuf> {
+    async fn which(&self, command: &OsStr) -> Option<PathBuf> {
         which::which(command).ok()
     }
 
@@ -874,4 +879,16 @@ impl dap::adapters::DapDelegate for DapAdapterDelegate {
     fn toolchain_store(&self) -> Arc<dyn LanguageToolchainStore> {
         self.toolchain_store.clone()
     }
+    async fn read_text_file(&self, path: PathBuf) -> Result<String> {
+        let entry = self
+            .worktree
+            .entry_for_path(&path)
+            .with_context(|| format!("no worktree entry for path {path:?}"))?;
+        let abs_path = self
+            .worktree
+            .absolutize(&entry.path)
+            .with_context(|| format!("cannot absolutize path {path:?}"))?;
+
+        self.fs.load(&abs_path).await
+    }
 }

crates/remote_server/Cargo.toml 🔗

@@ -30,6 +30,7 @@ chrono.workspace = true
 clap.workspace = true
 client.workspace = true
 dap_adapters.workspace = true
+debug_adapter_extension.workspace = true
 env_logger.workspace = true
 extension.workspace = true
 extension_host.workspace = true

crates/remote_server/src/headless_project.rs 🔗

@@ -76,6 +76,7 @@ impl HeadlessProject {
         }: HeadlessAppState,
         cx: &mut Context<Self>,
     ) -> Self {
+        debug_adapter_extension::init(proxy.clone(), cx);
         language_extension::init(proxy.clone(), languages.clone());
         languages::init(languages.clone(), node_runtime.clone(), cx);
 

crates/zed/Cargo.toml 🔗

@@ -45,6 +45,7 @@ dap_adapters.workspace = true
 debugger_ui.workspace = true
 debugger_tools.workspace = true
 db.workspace = true
+debug_adapter_extension.workspace = true
 diagnostics.workspace = true
 editor.workspace = true
 env_logger.workspace = true

crates/zed/src/main.rs 🔗

@@ -419,6 +419,7 @@ fn main() {
         .detach();
         let node_runtime = NodeRuntime::new(client.http_client(), Some(shell_env_loaded_rx), rx);
 
+        debug_adapter_extension::init(extension_host_proxy.clone(), cx);
         language::init(cx);
         language_extension::init(extension_host_proxy.clone(), languages.clone());
         languages::init(languages.clone(), node_runtime.clone(), cx);