627 lines · 21.5 KB · Rust
RawDownload
use anyhow::{Context as _, bail};use collections::HashMap;use dap::{    StartDebuggingRequestArguments,    adapters::{        DebugTaskDefinition, DownloadedFileType, TcpArguments, download_adapter_from_github,        latest_github_release,    },};use fs::Fs;use futures::StreamExt;use gpui::{AsyncApp, SharedString};use language::LanguageName;use log::warn;use serde_json::{Map, Value};use task::TcpArgumentsTemplate;use util; use std::{    env::consts,    ffi::OsStr,    path::{Path, PathBuf},    str::FromStr,    sync::OnceLock,}; use crate::*; #[derive(Default, Debug)]pub(crate) struct GoDebugAdapter {    shim_path: OnceLock<PathBuf>,} impl GoDebugAdapter {    const ADAPTER_NAME: &'static str = "Delve";    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 adapter_dir = paths::debug_adapters_dir().join("delve-shim-dap");         match Self::fetch_latest_adapter_version(delegate).await {            Ok(asset) => {                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 = adapter_dir                    .join(format!("delve-shim-dap_{}", asset.tag_name))                    .join(format!("delve-shim-dap{}", consts::EXE_SUFFIX));                self.shim_path.set(path.clone()).ok();                 Ok(path)            }            Err(error) => {                let binary_name = format!("delve-shim-dap{}", consts::EXE_SUFFIX);                let mut cached = None;                if let Ok(mut entries) = delegate.fs().read_dir(&adapter_dir).await {                    while let Some(entry) = entries.next().await {                        if let Ok(version_dir) = entry {                            let candidate = version_dir.join(&binary_name);                            if delegate                                .fs()                                .metadata(&candidate)                                .await                                .is_ok_and(|m| m.is_some())                            {                                cached = Some(candidate);                                break;                            }                        }                    }                }                 if let Some(path) = cached {                    warn!("Failed to fetch latest delve-shim-dap, using cached version: {error:#}");                    self.shim_path.set(path.clone()).ok();                    Ok(path)                } else {                    Err(error)                }            }        }    }} #[async_trait(?Send)]impl DebugAdapter for GoDebugAdapter {    fn name(&self) -> DebugAdapterName {        DebugAdapterName(Self::ADAPTER_NAME.into())    }     fn adapter_language_name(&self) -> Option<LanguageName> {        Some(SharedString::new_static("Go").into())    }     fn dap_schema(&self) -> serde_json::Value {        // Create common properties shared between launch and attach        let common_properties = json!({            "debugAdapter": {                "enum": ["legacy", "dlv-dap"],                "description": "Select which debug adapter to use with this configuration.",                "default": "dlv-dap"            },            "stopOnEntry": {                "type": "boolean",                "description": "Automatically stop program after launch or attach.",                "default": false            },            "showLog": {                "type": "boolean",                "description": "Show log output from the delve debugger. Maps to dlv's `--log` flag.",                "default": false            },            "cwd": {                "type": "string",                "description": "Workspace relative or absolute path to the working directory of the program being debugged.",                "default": "${ZED_WORKTREE_ROOT}"            },            "dlvFlags": {                "type": "array",                "description": "Extra flags for `dlv`. See `dlv help` for the full list of supported flags.",                "items": {                    "type": "string"                },                "default": []            },            "port": {                "type": "number",                "description": "Debug server port. For remote configurations, this is where to connect.",                "default": 2345            },            "host": {                "type": "string",                "description": "Debug server host. For remote configurations, this is where to connect.",                "default": "127.0.0.1"            },            "substitutePath": {                "type": "array",                "items": {                    "type": "object",                    "properties": {                        "from": {                            "type": "string",                            "description": "The absolute local path to be replaced."                        },                        "to": {                            "type": "string",                            "description": "The absolute remote path to replace with."                        }                    }                },                "description": "Mappings from local to remote paths for debugging.",                "default": []            },            "trace": {                "type": "string",                "enum": ["verbose", "trace", "log", "info", "warn", "error"],                "default": "error",                "description": "Debug logging level."            },            "backend": {                "type": "string",                "enum": ["default", "native", "lldb", "rr"],                "description": "Backend used by delve. Maps to `dlv`'s `--backend` flag."            },            "logOutput": {                "type": "string",                "enum": ["debugger", "gdbwire", "lldbout", "debuglineerr", "rpc", "dap"],                "description": "Components that should produce debug output.",                "default": "debugger"            },            "logDest": {                "type": "string",                "description": "Log destination for delve."            },            "stackTraceDepth": {                "type": "number",                "description": "Maximum depth of stack traces.",                "default": 50            },            "showGlobalVariables": {                "type": "boolean",                "default": false,                "description": "Show global package variables in variables pane."            },            "showRegisters": {                "type": "boolean",                "default": false,                "description": "Show register variables in variables pane."            },            "hideSystemGoroutines": {                "type": "boolean",                "default": false,                "description": "Hide system goroutines from call stack view."            },            "console": {                "default": "internalConsole",                "description": "Where to launch the debugger.",                "enum": ["internalConsole", "integratedTerminal"]            },            "asRoot": {                "default": false,                "description": "Debug with elevated permissions (on Unix).",                "type": "boolean"            }        });         // Create launch-specific properties        let launch_properties = json!({            "program": {                "type": "string",                "description": "Path to the program folder or file to debug.",                "default": "${ZED_WORKTREE_ROOT}"            },            "args": {                "type": ["array", "string"],                "description": "Command line arguments for the program.",                "items": {                    "type": "string"                },                "default": []            },            "env": {                "type": "object",                "description": "Environment variables for the debugged program.",                "default": {}            },            "envFile": {                "type": ["string", "array"],                "items": {                    "type": "string"                },                "description": "Path(s) to files with environment variables.",                "default": ""            },            "buildFlags": {                "type": ["string", "array"],                "items": {                    "type": "string"                },                "description": "Flags for the Go compiler.",                "default": []            },            "output": {                "type": "string",                "description": "Output path for the binary.",                "default": "debug"            },            "mode": {                "enum": [ "debug", "test", "exec", "replay", "core"],                "description": "Debug mode for launch configuration.",            },            "traceDirPath": {                "type": "string",                "description": "Directory for record trace (for 'replay' mode).",                "default": ""            },            "coreFilePath": {                "type": "string",                "description": "Path to core dump file (for 'core' mode).",                "default": ""            }        });         // Create attach-specific properties        let attach_properties = json!({            "processId": {                "anyOf": [                    {                        "enum": ["${command:pickProcess}", "${command:pickGoProcess}"],                        "description": "Use process picker to select a process."                    },                    {                        "type": "string",                        "description": "Process name to attach to."                    },                    {                        "type": "number",                        "description": "Process ID to attach to."                    }                ],                "default": 0            },            "mode": {                "enum": ["local", "remote"],                "description": "Local or remote debugging.",                "default": "local"            },            "remotePath": {                "type": "string",                "description": "Path to source on remote machine.",                "markdownDeprecationMessage": "Use `substitutePath` instead.",                "default": ""            }        });         // Create the final schema        json!({            "oneOf": [                {                    "allOf": [                        {                            "type": "object",                            "required": ["request"],                            "properties": {                                "request": {                                    "type": "string",                                    "enum": ["launch"],                                    "description": "Request to launch a new process"                                }                            }                        },                        {                            "type": "object",                            "properties": common_properties                        },                        {                            "type": "object",                            "required": ["program", "mode"],                            "properties": launch_properties                        }                    ]                },                {                    "allOf": [                        {                            "type": "object",                            "required": ["request"],                            "properties": {                                "request": {                                    "type": "string",                                    "enum": ["attach"],                                    "description": "Request to attach to an existing process"                                }                            }                        },                        {                            "type": "object",                            "properties": common_properties                        },                        {                            "type": "object",                            "required": ["mode"],                            "properties": attach_properties                        }                    ]                }            ]        })    }     async fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {        let mut args = match &zed_scenario.request {            dap::DebugRequest::Attach(attach_config) => {                json!({                    "request": "attach",                    "mode": "local",                    "processId": attach_config.process_id,                })            }            dap::DebugRequest::Launch(launch_config) => {                let mode = if launch_config.program != "." {                    "exec"                } else {                    "debug"                };                 json!({                    "request": "launch",                    "mode": mode,                    "program": launch_config.program,                    "cwd": launch_config.cwd,                    "args": launch_config.args,                    "env": launch_config.env_json()                })            }        };         let map = args.as_object_mut().unwrap();         if let Some(stop_on_entry) = zed_scenario.stop_on_entry {            map.insert("stopOnEntry".into(), stop_on_entry.into());        }         Ok(DebugScenario {            adapter: zed_scenario.adapter,            label: zed_scenario.label,            build: None,            config: args,            tcp_connection: None,        })    }     async fn get_binary(        &self,        delegate: &Arc<dyn DapDelegate>,        task_definition: &DebugTaskDefinition,        user_installed_path: Option<PathBuf>,        user_args: Option<Vec<String>>,        user_env: Option<HashMap<String, String>>,        _cx: &mut AsyncApp,    ) -> Result<DebugAdapterBinary> {        let adapter_path = paths::debug_adapters_dir().join(&Self::ADAPTER_NAME);        let dlv_binary = format!("dlv{}", consts::EXE_SUFFIX);        let dlv_path = adapter_path.join(&dlv_binary);         let delve_path = if let Some(path) = user_installed_path {            path.to_string_lossy().into_owned()        } else if let Some(path) = delegate.which(OsStr::new("dlv")).await {            path.to_string_lossy().into_owned()        } else if delegate.fs().is_file(&dlv_path).await {            dlv_path.to_string_lossy().into_owned()        } 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_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_binary)                .to_string_lossy()                .into_owned()        };         let cwd = Some(            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;        let command;        let connection;         let mut configuration = task_definition.config.clone();

Lines 1–500 of 627.