extension/dap: Add resolve_tcp_template function (#31010)

Piotr Osiewicz created

Extensions cannot look up available port themselves, hence the new API.
With this I'm able to port our Ruby implementation into an extension.

Release Notes:

- N/A

Change summary

crates/dap/src/dap.rs                                   | 18 +++++++
crates/dap/src/registry.rs                              |  4 -
crates/dap_adapters/src/dap_adapters.rs                 | 19 ------
crates/extension_api/src/extension_api.rs               |  1 
crates/extension_api/wit/since_v0.6.0/dap.wit           |  3 +
crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs | 27 +++++++++-
6 files changed, 48 insertions(+), 24 deletions(-)

Detailed changes

crates/dap/src/dap.rs 🔗

@@ -6,6 +6,8 @@ pub mod proto_conversions;
 mod registry;
 pub mod transport;
 
+use std::net::Ipv4Addr;
+
 pub use dap_types::*;
 pub use registry::{DapLocator, DapRegistry};
 pub use task::DebugRequest;
@@ -16,3 +18,19 @@ pub type StackFrameId = u64;
 
 #[cfg(any(test, feature = "test-support"))]
 pub use adapters::FakeAdapter;
+use task::TcpArgumentsTemplate;
+
+pub async fn configure_tcp_connection(
+    tcp_connection: TcpArgumentsTemplate,
+) -> anyhow::Result<(Ipv4Addr, u16, Option<u64>)> {
+    let host = tcp_connection.host();
+    let timeout = tcp_connection.timeout;
+
+    let port = if let Some(port) = tcp_connection.port {
+        port
+    } else {
+        transport::TcpTransport::port(&tcp_connection).await?
+    };
+
+    Ok((host, port, timeout))
+}

crates/dap/src/registry.rs 🔗

@@ -54,10 +54,6 @@ impl DapRegistry {
     pub fn add_adapter(&self, adapter: Arc<dyn DebugAdapter>) {
         let name = adapter.name();
         let _previous_value = self.0.write().adapters.insert(name, adapter);
-        debug_assert!(
-            _previous_value.is_none(),
-            "Attempted to insert a new debug adapter when one is already registered"
-        );
     }
 
     pub fn adapter_language(&self, adapter_name: &str) -> Option<LanguageName> {

crates/dap_adapters/src/dap_adapters.rs 🔗

@@ -6,7 +6,7 @@ mod php;
 mod python;
 mod ruby;
 
-use std::{net::Ipv4Addr, sync::Arc};
+use std::sync::Arc;
 
 use anyhow::{Result, anyhow};
 use async_trait::async_trait;
@@ -17,6 +17,7 @@ use dap::{
         self, AdapterVersion, DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName,
         GithubRepo,
     },
+    configure_tcp_connection,
     inline_value::{PythonInlineValueProvider, RustInlineValueProvider},
 };
 use gdb::GdbDebugAdapter;
@@ -27,7 +28,6 @@ use php::PhpDebugAdapter;
 use python::PythonDebugAdapter;
 use ruby::RubyDebugAdapter;
 use serde_json::{Value, json};
-use task::TcpArgumentsTemplate;
 
 pub fn init(cx: &mut App) {
     cx.update_default_global(|registry: &mut DapRegistry, _cx| {
@@ -45,21 +45,6 @@ pub fn init(cx: &mut App) {
     })
 }
 
-pub(crate) async fn configure_tcp_connection(
-    tcp_connection: TcpArgumentsTemplate,
-) -> Result<(Ipv4Addr, u16, Option<u64>)> {
-    let host = tcp_connection.host();
-    let timeout = tcp_connection.timeout;
-
-    let port = if let Some(port) = tcp_connection.port {
-        port
-    } else {
-        dap::transport::TcpTransport::port(&tcp_connection).await?
-    };
-
-    Ok((host, port, timeout))
-}
-
 trait ToDap {
     fn to_dap(&self) -> dap::StartDebuggingRequestArgumentsRequest;
 }

crates/extension_api/src/extension_api.rs 🔗

@@ -22,6 +22,7 @@ pub use wit::{
     zed::extension::dap::{
         DebugAdapterBinary, DebugRequest, DebugTaskDefinition, StartDebuggingRequestArguments,
         StartDebuggingRequestArgumentsRequest, TcpArguments, TcpArgumentsTemplate,
+        resolve_tcp_template,
     },
     zed::extension::github::{
         GithubRelease, GithubReleaseAsset, GithubReleaseOptions, github_release_by_tag_name,

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

@@ -1,6 +1,9 @@
 interface dap {
     use common.{env-vars};
 
+    /// Resolves a specified TcpArgumentsTemplate into TcpArguments
+    resolve-tcp-template: func(template: tcp-arguments-template) -> result<tcp-arguments, string>;
+
     record launch-request {
         program: string,
         cwd: option<string>,

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

@@ -48,7 +48,7 @@ wasmtime::component::bindgen!({
 pub use self::zed::extension::*;
 
 mod settings {
-    include!(concat!(env!("OUT_DIR"), "/since_v0.5.0/settings.rs"));
+    include!(concat!(env!("OUT_DIR"), "/since_v0.6.0/settings.rs"));
 }
 
 pub type ExtensionWorktree = Arc<dyn WorktreeDelegate>;
@@ -729,8 +729,29 @@ impl slash_command::Host for WasmState {}
 #[async_trait]
 impl context_server::Host for WasmState {}
 
-#[async_trait]
-impl dap::Host for WasmState {}
+impl dap::Host for WasmState {
+    async fn resolve_tcp_template(
+        &mut self,
+        template: TcpArgumentsTemplate,
+    ) -> wasmtime::Result<Result<TcpArguments, String>> {
+        maybe!(async {
+            let (host, port, timeout) =
+                ::dap::configure_tcp_connection(task::TcpArgumentsTemplate {
+                    port: template.port,
+                    host: template.host.map(Ipv4Addr::from_bits),
+                    timeout: template.timeout,
+                })
+                .await?;
+            Ok(TcpArguments {
+                port,
+                host: host.to_bits(),
+                timeout,
+            })
+        })
+        .await
+        .to_wasmtime_result()
+    }
+}
 
 impl ExtensionImports for WasmState {
     async fn get_settings(