273 lines · 11.5 KB · Rust
RawDownload
use anyhow::Result;use async_trait::async_trait;use dap::{    DebugRequest, StartDebuggingRequestArguments,    adapters::{        DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition,    },};use gpui::{AsyncApp, SharedString};use language::LanguageName;use serde_json::json;use std::path::PathBuf;use std::sync::Arc;use task::{DebugScenario, ZedDebugConfig};use util::command::new_smol_command; #[derive(Default)]pub(crate) struct RubyDebugAdapter; impl RubyDebugAdapter {    const ADAPTER_NAME: &'static str = "Ruby";} #[async_trait(?Send)]impl DebugAdapter for RubyDebugAdapter {    fn name(&self) -> DebugAdapterName {        DebugAdapterName(Self::ADAPTER_NAME.into())    }     fn adapter_language_name(&self) -> Option<LanguageName> {        Some(SharedString::new_static("Ruby").into())    }     async fn dap_schema(&self) -> serde_json::Value {        json!({            "oneOf": [                {                    "allOf": [                        {                            "type": "object",                            "required": ["request"],                            "properties": {                                "request": {                                    "type": "string",                                    "enum": ["launch"],                                    "description": "Request to launch a new process"                                }                            }                        },                        {                            "type": "object",                            "required": ["script"],                            "properties": {                                "command": {                                    "type": "string",                                    "description": "Command name (ruby, rake, bin/rails, bundle exec ruby, etc)",                                    "default": "ruby"                                },                                "script": {                                    "type": "string",                                    "description": "Absolute path to a Ruby file."                                },                                "cwd": {                                    "type": "string",                                    "description": "Directory to execute the program in",                                    "default": "${ZED_WORKTREE_ROOT}"                                },                                "args": {                                    "type": "array",                                    "description": "Command line arguments passed to the program",                                    "items": {                                        "type": "string"                                    },                                    "default": []                                },                                "env": {                                    "type": "object",                                    "description": "Additional environment variables to pass to the debugging (and debugged) process",                                    "default": {}                                },                                "showProtocolLog": {                                    "type": "boolean",                                    "description": "Show a log of DAP requests, events, and responses",                                    "default": false                                },                                "useBundler": {                                    "type": "boolean",                                    "description": "Execute Ruby programs with `bundle exec` instead of directly",                                    "default": false                                },                                "bundlePath": {                                    "type": "string",                                    "description": "Location of the bundle executable"                                },                                "rdbgPath": {                                    "type": "string",                                    "description": "Location of the rdbg executable"                                },                                "askParameters": {                                    "type": "boolean",                                    "description": "Ask parameters at first."                                },                                "debugPort": {                                    "type": "string",                                    "description": "UNIX domain socket name or TPC/IP host:port"                                },                                "waitLaunchTime": {                                    "type": "number",                                    "description": "Wait time before connection in milliseconds"                                },                                "localfs": {                                    "type": "boolean",                                    "description": "true if the VSCode and debugger run on a same machine",                                    "default": false                                },                                "useTerminal": {                                    "type": "boolean",                                    "description": "Create a new terminal and then execute commands there",                                    "default": false                                }                            }                        }                    ]                },                {                    "allOf": [                        {                            "type": "object",                            "required": ["request"],                            "properties": {                                "request": {                                    "type": "string",                                    "enum": ["attach"],                                    "description": "Request to attach to an existing process"                                }                            }                        },                        {                            "type": "object",                            "properties": {                                "rdbgPath": {                                    "type": "string",                                    "description": "Location of the rdbg executable"                                },                                "debugPort": {                                    "type": "string",                                    "description": "UNIX domain socket name or TPC/IP host:port"                                },                                "showProtocolLog": {                                    "type": "boolean",                                    "description": "Show a log of DAP requests, events, and responses",                                    "default": false                                },                                "localfs": {                                    "type": "boolean",                                    "description": "true if the VSCode and debugger run on a same machine",                                    "default": false                                },                                "localfsMap": {                                    "type": "string",                                    "description": "Specify pairs of remote root path and local root path like `/remote_dir:/local_dir`. You can specify multiple pairs like `/rem1:/loc1,/rem2:/loc2` by concatenating with `,`."                                },                                "env": {                                    "type": "object",                                    "description": "Additional environment variables to pass to the rdbg process",                                    "default": {}                                }                            }                        }                    ]                }            ]        })    }     fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {        let mut config = serde_json::Map::new();         match &zed_scenario.request {            DebugRequest::Launch(launch) => {                config.insert("request".to_string(), json!("launch"));                config.insert("script".to_string(), json!(launch.program));                config.insert("command".to_string(), json!("ruby"));                 if !launch.args.is_empty() {                    config.insert("args".to_string(), json!(launch.args));                }                 if !launch.env.is_empty() {                    config.insert("env".to_string(), json!(launch.env));                }                 if let Some(cwd) = &launch.cwd {                    config.insert("cwd".to_string(), json!(cwd));                }                 // Ruby stops on entry so there's no need to handle that case            }            DebugRequest::Attach(attach) => {                config.insert("request".to_string(), json!("attach"));                 config.insert("processId".to_string(), json!(attach.process_id));            }        }         Ok(DebugScenario {            adapter: zed_scenario.adapter,            label: zed_scenario.label,            config: serde_json::Value::Object(config),            tcp_connection: None,            build: None,        })    }     async fn get_binary(        &self,        delegate: &Arc<dyn DapDelegate>,        definition: &DebugTaskDefinition,        _user_installed_path: Option<PathBuf>,        _cx: &mut AsyncApp,    ) -> Result<DebugAdapterBinary> {        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()).await {                Some(path) => rdbg_path = path,                None => {                    delegate.output_to_console(                        "rdbg not found on path, trying `gem install debug`".to_string(),                    );                    let output = new_smol_command("gem")                        .arg("install")                        .arg("--no-document")                        .arg("--bindir")                        .arg(adapter_path)                        .arg("debug")                        .output()                        .await?;                    anyhow::ensure!(                        output.status.success(),                        "Failed to install rdbg:\n{}",                        String::from_utf8_lossy(&output.stderr).to_string()                    );                }            }        }         let tcp_connection = definition.tcp_connection.clone().unwrap_or_default();        let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;         let arguments = vec![            "--open".to_string(),            format!("--port={}", port),            format!("--host={}", host),        ];         Ok(DebugAdapterBinary {            command: rdbg_path.to_string_lossy().to_string(),            arguments,            connection: Some(dap::adapters::TcpArguments {                host,                port,                timeout,            }),            cwd: None,            envs: std::collections::HashMap::default(),            request_args: StartDebuggingRequestArguments {                request: self.request_kind(&definition.config)?,                configuration: definition.config.clone(),            },        })    }}