go: Use delve-dap-shim for spawning delve (#31700)

Piotr Osiewicz created

This allows us to support terminal with go sessions

Closes #ISSUE

Release Notes:

- debugger: Add support for terminal when debugging Go programs

Change summary

crates/dap/src/transport.rs             | 10 +
crates/dap_adapters/src/dap_adapters.rs |  2 
crates/dap_adapters/src/go.rs           | 95 +++++++++++++++++++++-----
3 files changed, 84 insertions(+), 23 deletions(-)

Detailed changes

crates/dap/src/transport.rs 🔗

@@ -658,9 +658,13 @@ impl StdioTransport {
             .stderr(Stdio::piped())
             .kill_on_drop(true);
 
-        let mut process = command
-            .spawn()
-            .with_context(|| "failed to spawn command.")?;
+        let mut process = command.spawn().with_context(|| {
+            format!(
+                "failed to spawn command `{} {}`.",
+                binary.command,
+                binary.arguments.join(" ")
+            )
+        })?;
 
         let stdin = process.stdin.take().context("Failed to open stdin")?;
         let stdout = process.stdout.take().context("Failed to open stdout")?;

crates/dap_adapters/src/dap_adapters.rs 🔗

@@ -37,7 +37,7 @@ pub fn init(cx: &mut App) {
         registry.add_adapter(Arc::from(PhpDebugAdapter::default()));
         registry.add_adapter(Arc::from(JsDebugAdapter::default()));
         registry.add_adapter(Arc::from(RubyDebugAdapter));
-        registry.add_adapter(Arc::from(GoDebugAdapter));
+        registry.add_adapter(Arc::from(GoDebugAdapter::default()));
         registry.add_adapter(Arc::from(GdbDebugAdapter));
 
         #[cfg(any(test, feature = "test-support"))]

crates/dap_adapters/src/go.rs 🔗

@@ -1,22 +1,87 @@
 use anyhow::{Context as _, anyhow, bail};
 use dap::{
     StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
-    adapters::DebugTaskDefinition,
+    adapters::{
+        DebugTaskDefinition, DownloadedFileType, download_adapter_from_github,
+        latest_github_release,
+    },
 };
 
 use gpui::{AsyncApp, SharedString};
 use language::LanguageName;
-use std::{collections::HashMap, ffi::OsStr, path::PathBuf};
+use std::{collections::HashMap, env::consts, ffi::OsStr, path::PathBuf, sync::OnceLock};
 use util;
 
 use crate::*;
 
 #[derive(Default, Debug)]
-pub(crate) struct GoDebugAdapter;
+pub(crate) struct GoDebugAdapter {
+    shim_path: OnceLock<PathBuf>,
+}
 
 impl GoDebugAdapter {
     const ADAPTER_NAME: &'static str = "Delve";
-    const DEFAULT_TIMEOUT_MS: u64 = 60000;
+    async fn fetch_latest_adapter_version(
+        delegate: &Arc<dyn DapDelegate>,
+    ) -> Result<AdapterVersion> {
+        let release = latest_github_release(
+            &"zed-industries/delve-shim-dap",
+            true,
+            false,
+            delegate.http_client(),
+        )
+        .await?;
+
+        let os = match consts::OS {
+            "macos" => "apple-darwin",
+            "linux" => "unknown-linux-gnu",
+            "windows" => "pc-windows-msvc",
+            other => bail!("Running on unsupported os: {other}"),
+        };
+        let suffix = if consts::OS == "windows" {
+            ".zip"
+        } else {
+            ".tar.gz"
+        };
+        let asset_name = format!("delve-shim-dap-{}-{os}{suffix}", consts::ARCH);
+        let asset = release
+            .assets
+            .iter()
+            .find(|asset| asset.name == asset_name)
+            .with_context(|| format!("no asset found matching `{asset_name:?}`"))?;
+
+        Ok(AdapterVersion {
+            tag_name: release.tag_name,
+            url: asset.browser_download_url.clone(),
+        })
+    }
+    async fn install_shim(&self, delegate: &Arc<dyn DapDelegate>) -> anyhow::Result<PathBuf> {
+        if let Some(path) = self.shim_path.get().cloned() {
+            return Ok(path);
+        }
+
+        let asset = Self::fetch_latest_adapter_version(delegate).await?;
+        let ty = if consts::OS == "windows" {
+            DownloadedFileType::Zip
+        } else {
+            DownloadedFileType::GzipTar
+        };
+        download_adapter_from_github(
+            "delve-shim-dap".into(),
+            asset.clone(),
+            ty,
+            delegate.as_ref(),
+        )
+        .await?;
+
+        let path = paths::debug_adapters_dir()
+            .join("delve-shim-dap")
+            .join(format!("delve-shim-dap{}", asset.tag_name))
+            .join("delve-shim-dap");
+        self.shim_path.set(path.clone()).ok();
+
+        Ok(path)
+    }
 }
 
 #[async_trait(?Send)]
@@ -384,16 +449,10 @@ impl DebugAdapter for GoDebugAdapter {
 
             adapter_path.join("dlv").to_string_lossy().to_string()
         };
+        let minidelve_path = self.install_shim(delegate).await?;
+        let tcp_connection = task_definition.tcp_connection.clone().unwrap_or_default();
 
-        let mut tcp_connection = task_definition.tcp_connection.clone().unwrap_or_default();
-
-        if tcp_connection.timeout.is_none()
-            || tcp_connection.timeout.unwrap_or(0) < Self::DEFAULT_TIMEOUT_MS
-        {
-            tcp_connection.timeout = Some(Self::DEFAULT_TIMEOUT_MS);
-        }
-
-        let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
+        let (host, port, _) = crate::configure_tcp_connection(tcp_connection).await?;
 
         let cwd = task_definition
             .config
@@ -404,6 +463,7 @@ impl DebugAdapter for GoDebugAdapter {
 
         let arguments = if cfg!(windows) {
             vec![
+                delve_path,
                 "dap".into(),
                 "--listen".into(),
                 format!("{}:{}", host, port),
@@ -411,6 +471,7 @@ impl DebugAdapter for GoDebugAdapter {
             ]
         } else {
             vec![
+                delve_path,
                 "dap".into(),
                 "--listen".into(),
                 format!("{}:{}", host, port),
@@ -418,15 +479,11 @@ impl DebugAdapter for GoDebugAdapter {
         };
 
         Ok(DebugAdapterBinary {
-            command: delve_path,
+            command: minidelve_path.to_string_lossy().into_owned(),
             arguments,
             cwd: Some(cwd),
             envs: HashMap::default(),
-            connection: Some(adapters::TcpArguments {
-                host,
-                port,
-                timeout,
-            }),
+            connection: None,
             request_args: StartDebuggingRequestArguments {
                 configuration: task_definition.config.clone(),
                 request: self.validate_config(&task_definition.config)?,