ruby.rs

  1use anyhow::Result;
  2use async_trait::async_trait;
  3use dap::{
  4    DebugRequest, StartDebuggingRequestArguments,
  5    adapters::{
  6        DapDelegate, DebugAdapter, DebugAdapterBinary, DebugAdapterName, DebugTaskDefinition,
  7    },
  8};
  9use gpui::{AsyncApp, SharedString};
 10use language::LanguageName;
 11use serde_json::json;
 12use std::path::PathBuf;
 13use std::sync::Arc;
 14use task::{DebugScenario, ZedDebugConfig};
 15use util::command::new_smol_command;
 16
 17#[derive(Default)]
 18pub(crate) struct RubyDebugAdapter;
 19
 20impl RubyDebugAdapter {
 21    const ADAPTER_NAME: &'static str = "Ruby";
 22}
 23
 24#[async_trait(?Send)]
 25impl DebugAdapter for RubyDebugAdapter {
 26    fn name(&self) -> DebugAdapterName {
 27        DebugAdapterName(Self::ADAPTER_NAME.into())
 28    }
 29
 30    fn adapter_language_name(&self) -> Option<LanguageName> {
 31        Some(SharedString::new_static("Ruby").into())
 32    }
 33
 34    async fn dap_schema(&self) -> serde_json::Value {
 35        json!({
 36            "oneOf": [
 37                {
 38                    "allOf": [
 39                        {
 40                            "type": "object",
 41                            "required": ["request"],
 42                            "properties": {
 43                                "request": {
 44                                    "type": "string",
 45                                    "enum": ["launch"],
 46                                    "description": "Request to launch a new process"
 47                                }
 48                            }
 49                        },
 50                        {
 51                            "type": "object",
 52                            "required": ["script"],
 53                            "properties": {
 54                                "command": {
 55                                    "type": "string",
 56                                    "description": "Command name (ruby, rake, bin/rails, bundle exec ruby, etc)",
 57                                    "default": "ruby"
 58                                },
 59                                "script": {
 60                                    "type": "string",
 61                                    "description": "Absolute path to a Ruby file."
 62                                },
 63                                "cwd": {
 64                                    "type": "string",
 65                                    "description": "Directory to execute the program in",
 66                                    "default": "${ZED_WORKTREE_ROOT}"
 67                                },
 68                                "args": {
 69                                    "type": "array",
 70                                    "description": "Command line arguments passed to the program",
 71                                    "items": {
 72                                        "type": "string"
 73                                    },
 74                                    "default": []
 75                                },
 76                                "env": {
 77                                    "type": "object",
 78                                    "description": "Additional environment variables to pass to the debugging (and debugged) process",
 79                                    "default": {}
 80                                },
 81                                "showProtocolLog": {
 82                                    "type": "boolean",
 83                                    "description": "Show a log of DAP requests, events, and responses",
 84                                    "default": false
 85                                },
 86                                "useBundler": {
 87                                    "type": "boolean",
 88                                    "description": "Execute Ruby programs with `bundle exec` instead of directly",
 89                                    "default": false
 90                                },
 91                                "bundlePath": {
 92                                    "type": "string",
 93                                    "description": "Location of the bundle executable"
 94                                },
 95                                "rdbgPath": {
 96                                    "type": "string",
 97                                    "description": "Location of the rdbg executable"
 98                                },
 99                                "askParameters": {
100                                    "type": "boolean",
101                                    "description": "Ask parameters at first."
102                                },
103                                "debugPort": {
104                                    "type": "string",
105                                    "description": "UNIX domain socket name or TPC/IP host:port"
106                                },
107                                "waitLaunchTime": {
108                                    "type": "number",
109                                    "description": "Wait time before connection in milliseconds"
110                                },
111                                "localfs": {
112                                    "type": "boolean",
113                                    "description": "true if the VSCode and debugger run on a same machine",
114                                    "default": false
115                                },
116                                "useTerminal": {
117                                    "type": "boolean",
118                                    "description": "Create a new terminal and then execute commands there",
119                                    "default": false
120                                }
121                            }
122                        }
123                    ]
124                },
125                {
126                    "allOf": [
127                        {
128                            "type": "object",
129                            "required": ["request"],
130                            "properties": {
131                                "request": {
132                                    "type": "string",
133                                    "enum": ["attach"],
134                                    "description": "Request to attach to an existing process"
135                                }
136                            }
137                        },
138                        {
139                            "type": "object",
140                            "properties": {
141                                "rdbgPath": {
142                                    "type": "string",
143                                    "description": "Location of the rdbg executable"
144                                },
145                                "debugPort": {
146                                    "type": "string",
147                                    "description": "UNIX domain socket name or TPC/IP host:port"
148                                },
149                                "showProtocolLog": {
150                                    "type": "boolean",
151                                    "description": "Show a log of DAP requests, events, and responses",
152                                    "default": false
153                                },
154                                "localfs": {
155                                    "type": "boolean",
156                                    "description": "true if the VSCode and debugger run on a same machine",
157                                    "default": false
158                                },
159                                "localfsMap": {
160                                    "type": "string",
161                                    "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 `,`."
162                                },
163                                "env": {
164                                    "type": "object",
165                                    "description": "Additional environment variables to pass to the rdbg process",
166                                    "default": {}
167                                }
168                            }
169                        }
170                    ]
171                }
172            ]
173        })
174    }
175
176    fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
177        let mut config = serde_json::Map::new();
178
179        match &zed_scenario.request {
180            DebugRequest::Launch(launch) => {
181                config.insert("request".to_string(), json!("launch"));
182                config.insert("script".to_string(), json!(launch.program));
183                config.insert("command".to_string(), json!("ruby"));
184
185                if !launch.args.is_empty() {
186                    config.insert("args".to_string(), json!(launch.args));
187                }
188
189                if !launch.env.is_empty() {
190                    config.insert("env".to_string(), json!(launch.env));
191                }
192
193                if let Some(cwd) = &launch.cwd {
194                    config.insert("cwd".to_string(), json!(cwd));
195                }
196
197                // Ruby stops on entry so there's no need to handle that case
198            }
199            DebugRequest::Attach(attach) => {
200                config.insert("request".to_string(), json!("attach"));
201
202                config.insert("processId".to_string(), json!(attach.process_id));
203            }
204        }
205
206        Ok(DebugScenario {
207            adapter: zed_scenario.adapter,
208            label: zed_scenario.label,
209            config: serde_json::Value::Object(config),
210            tcp_connection: None,
211            build: None,
212        })
213    }
214
215    async fn get_binary(
216        &self,
217        delegate: &Arc<dyn DapDelegate>,
218        definition: &DebugTaskDefinition,
219        _user_installed_path: Option<PathBuf>,
220        _cx: &mut AsyncApp,
221    ) -> Result<DebugAdapterBinary> {
222        let adapter_path = paths::debug_adapters_dir().join(self.name().as_ref());
223        let mut rdbg_path = adapter_path.join("rdbg");
224        if !delegate.fs().is_file(&rdbg_path).await {
225            match delegate.which("rdbg".as_ref()).await {
226                Some(path) => rdbg_path = path,
227                None => {
228                    delegate.output_to_console(
229                        "rdbg not found on path, trying `gem install debug`".to_string(),
230                    );
231                    let output = new_smol_command("gem")
232                        .arg("install")
233                        .arg("--no-document")
234                        .arg("--bindir")
235                        .arg(adapter_path)
236                        .arg("debug")
237                        .output()
238                        .await?;
239                    anyhow::ensure!(
240                        output.status.success(),
241                        "Failed to install rdbg:\n{}",
242                        String::from_utf8_lossy(&output.stderr).to_string()
243                    );
244                }
245            }
246        }
247
248        let tcp_connection = definition.tcp_connection.clone().unwrap_or_default();
249        let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
250
251        let arguments = vec![
252            "--open".to_string(),
253            format!("--port={}", port),
254            format!("--host={}", host),
255        ];
256
257        Ok(DebugAdapterBinary {
258            command: rdbg_path.to_string_lossy().to_string(),
259            arguments,
260            connection: Some(dap::adapters::TcpArguments {
261                host,
262                port,
263                timeout,
264            }),
265            cwd: None,
266            envs: std::collections::HashMap::default(),
267            request_args: StartDebuggingRequestArguments {
268                request: self.request_kind(&definition.config)?,
269                configuration: definition.config.clone(),
270            },
271        })
272    }
273}