debugger beta: Auto download Delve (Go's DAP) & fix grammar errors in docs (#31273)

Anthony Eid and Remco Smits created

Release Notes:

- debugger beta: Go's debug adapter will now automatically download if
not found on user's PATH

Co-authored-by: Remco Smits <djsmits12@gmail.com>

Change summary

crates/dap_adapters/src/codelldb.rs |  3 -
crates/dap_adapters/src/go.rs       | 76 +++++++++++++++++++++++++++---
docs/src/debugger.md                | 14 ++--
3 files changed, 74 insertions(+), 19 deletions(-)

Detailed changes

crates/dap_adapters/src/go.rs 🔗

@@ -1,4 +1,4 @@
-use anyhow::{Context as _, anyhow};
+use anyhow::{Context as _, anyhow, bail};
 use dap::{
     StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
     adapters::DebugTaskDefinition,
@@ -7,6 +7,7 @@ use dap::{
 use gpui::{AsyncApp, SharedString};
 use language::LanguageName;
 use std::{collections::HashMap, ffi::OsStr, path::PathBuf};
+use util;
 
 use crate::*;
 
@@ -15,6 +16,7 @@ pub(crate) struct GoDebugAdapter;
 
 impl GoDebugAdapter {
     const ADAPTER_NAME: &'static str = "Delve";
+    const DEFAULT_TIMEOUT_MS: u64 = 60000;
 }
 
 #[async_trait(?Send)]
@@ -338,19 +340,75 @@ impl DebugAdapter for GoDebugAdapter {
         _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()))
-            .context("Dlv not found in path")?;
+        let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME);
+        let dlv_path = adapter_path.join("dlv");
+
+        let delve_path = if let Some(path) = delegate.which(OsStr::new("dlv")).await {
+            path.to_string_lossy().to_string()
+        } else if delegate.fs().is_file(&dlv_path).await {
+            dlv_path.to_string_lossy().to_string()
+        } else {
+            let go = delegate
+                .which(OsStr::new("go"))
+                .await
+                .context("Go not found in path. Please install Go first, then Dlv will be installed automatically.")?;
+
+            let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME);
+
+            let install_output = util::command::new_smol_command(&go)
+                .env("GO111MODULE", "on")
+                .env("GOBIN", &adapter_path)
+                .args(&["install", "github.com/go-delve/delve/cmd/dlv@latest"])
+                .output()
+                .await?;
+
+            if !install_output.status.success() {
+                bail!(
+                    "failed to install dlv via `go install`. stdout: {:?}, stderr: {:?}\n Please try installing it manually using 'go install github.com/go-delve/delve/cmd/dlv@latest'",
+                    String::from_utf8_lossy(&install_output.stdout),
+                    String::from_utf8_lossy(&install_output.stderr)
+                );
+            }
+
+            adapter_path.join("dlv").to_string_lossy().to_string()
+        };
+
+        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 tcp_connection = task_definition.tcp_connection.clone().unwrap_or_default();
         let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
 
+        let cwd = task_definition
+            .config
+            .get("cwd")
+            .and_then(|s| s.as_str())
+            .map(PathBuf::from)
+            .unwrap_or_else(|| delegate.worktree_root_path().to_path_buf());
+
+        let arguments = if cfg!(windows) {
+            vec![
+                "dap".into(),
+                "--listen".into(),
+                format!("{}:{}", host, port),
+                "--headless".into(),
+            ]
+        } else {
+            vec![
+                "dap".into(),
+                "--listen".into(),
+                format!("{}:{}", host, port),
+            ]
+        };
+
         Ok(DebugAdapterBinary {
             command: delve_path,
-            arguments: vec!["dap".into(), "--listen".into(), format!("{host}:{port}")],
-            cwd: Some(delegate.worktree_root_path().to_path_buf()),
+            arguments,
+            cwd: Some(cwd),
             envs: HashMap::default(),
             connection: Some(adapters::TcpArguments {
                 host,

docs/src/debugger.md 🔗

@@ -28,15 +28,15 @@ These adapters enable Zed to provide a consistent debugging experience across mu
 
 ## Getting Started
 
-For basic debugging you can set up a new configuration by opening up the `New Session Modal` either by the `debugger: start` (default: f4) or clicking the plus icon at the top right of the debug panel. Once the `New Session Modal` is open you can click custom on the bottom left to open a view that allows you to create a custom debug configuration. Once you have created a configuration you can save it to your workspace's `.zed/debug.json` by clicking on the save button on the bottom left.
+For basic debugging you can set up a new configuration by opening the `New Session Modal` either via the `debugger: start` (default: f4) or clicking the plus icon at the top right of the debug panel.
 
-For more advance use cases you can create debug configurations by directly editing the `.zed/debug.json` file in your project root directory. Once you fill out the adapter and label fields completions will show you the available options of the selected debug adapter.
+For more advanced use cases you can create debug configurations by directly editing the `.zed/debug.json` file in your project root directory.
 
 You can then use the `New Session Modal` to select a configuration then start debugging.
 
 ### Configuration
 
-While configuration fields are debug adapter dependent, most adapters support the follow fields.
+While configuration fields are debug adapter dependent, most adapters support the following fields.
 
 ```json
 [
@@ -71,7 +71,7 @@ Zed currently supports these types of breakpoints
 - Conditional Breakpoints: Stop at the breakpoint when it's hit if the condition is met
 - Hit Breakpoints: Stop at the breakpoint when it's hit a certain number of times
 
-Standard breakpoints can be toggled by left clicking on the editor gutter or using the Toggle Breakpoint action. Right clicking on a breakpoint, right clicking on a code runner symbol brings up the breakpoint context menu. That has options for toggling breakpoints and editing log breakpoints.
+Standard breakpoints can be toggled by left clicking on the editor gutter or using the Toggle Breakpoint action. Right clicking on a breakpoint or on a code runner symbol brings up the breakpoint context menu. This has options for toggling breakpoints and editing log breakpoints.
 
 Other kinds of breakpoints can be toggled/edited by right clicking on the breakpoint icon in the gutter and selecting the desired option.
 
@@ -216,10 +216,10 @@ Other kinds of breakpoints can be toggled/edited by right clicking on the breakp
 
 ## Theme
 
-The Debugger supports the following theme options
+The Debugger supports the following theme options:
 
-    /// Color used to accent some of the debuggers elements
-    /// Only accent breakpoint & breakpoint related symbols right now
+    /// Color used to accent some of the debugger's elements
+    /// Only accents breakpoint & breakpoint related symbols right now
 
 **debugger.accent**: Color used to accent breakpoint & breakpoint related symbols
 **editor.debugger_active_line.background**: Background color of active debug line