go.rs

  1use anyhow::{Context as _, anyhow};
  2use dap::{
  3    StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
  4    adapters::DebugTaskDefinition,
  5};
  6
  7use gpui::{AsyncApp, SharedString};
  8use language::LanguageName;
  9use std::{collections::HashMap, ffi::OsStr, path::PathBuf};
 10
 11use crate::*;
 12
 13#[derive(Default, Debug)]
 14pub(crate) struct GoDebugAdapter;
 15
 16impl GoDebugAdapter {
 17    const ADAPTER_NAME: &'static str = "Delve";
 18}
 19
 20#[async_trait(?Send)]
 21impl DebugAdapter for GoDebugAdapter {
 22    fn name(&self) -> DebugAdapterName {
 23        DebugAdapterName(Self::ADAPTER_NAME.into())
 24    }
 25
 26    fn adapter_language_name(&self) -> Option<LanguageName> {
 27        Some(SharedString::new_static("Go").into())
 28    }
 29
 30    async fn dap_schema(&self) -> serde_json::Value {
 31        // Create common properties shared between launch and attach
 32        let common_properties = json!({
 33            "debugAdapter": {
 34                "enum": ["legacy", "dlv-dap"],
 35                "description": "Select which debug adapter to use with this configuration.",
 36                "default": "dlv-dap"
 37            },
 38            "stopOnEntry": {
 39                "type": "boolean",
 40                "description": "Automatically stop program after launch or attach.",
 41                "default": false
 42            },
 43            "showLog": {
 44                "type": "boolean",
 45                "description": "Show log output from the delve debugger. Maps to dlv's `--log` flag.",
 46                "default": false
 47            },
 48            "cwd": {
 49                "type": "string",
 50                "description": "Workspace relative or absolute path to the working directory of the program being debugged.",
 51                "default": "${ZED_WORKTREE_ROOT}"
 52            },
 53            "dlvFlags": {
 54                "type": "array",
 55                "description": "Extra flags for `dlv`. See `dlv help` for the full list of supported flags.",
 56                "items": {
 57                    "type": "string"
 58                },
 59                "default": []
 60            },
 61            "port": {
 62                "type": "number",
 63                "description": "Debug server port. For remote configurations, this is where to connect.",
 64                "default": 2345
 65            },
 66            "host": {
 67                "type": "string",
 68                "description": "Debug server host. For remote configurations, this is where to connect.",
 69                "default": "127.0.0.1"
 70            },
 71            "substitutePath": {
 72                "type": "array",
 73                "items": {
 74                    "type": "object",
 75                    "properties": {
 76                        "from": {
 77                            "type": "string",
 78                            "description": "The absolute local path to be replaced."
 79                        },
 80                        "to": {
 81                            "type": "string",
 82                            "description": "The absolute remote path to replace with."
 83                        }
 84                    }
 85                },
 86                "description": "Mappings from local to remote paths for debugging.",
 87                "default": []
 88            },
 89            "trace": {
 90                "type": "string",
 91                "enum": ["verbose", "trace", "log", "info", "warn", "error"],
 92                "default": "error",
 93                "description": "Debug logging level."
 94            },
 95            "backend": {
 96                "type": "string",
 97                "enum": ["default", "native", "lldb", "rr"],
 98                "description": "Backend used by delve. Maps to `dlv`'s `--backend` flag."
 99            },
100            "logOutput": {
101                "type": "string",
102                "enum": ["debugger", "gdbwire", "lldbout", "debuglineerr", "rpc", "dap"],
103                "description": "Components that should produce debug output.",
104                "default": "debugger"
105            },
106            "logDest": {
107                "type": "string",
108                "description": "Log destination for delve."
109            },
110            "stackTraceDepth": {
111                "type": "number",
112                "description": "Maximum depth of stack traces.",
113                "default": 50
114            },
115            "showGlobalVariables": {
116                "type": "boolean",
117                "default": false,
118                "description": "Show global package variables in variables pane."
119            },
120            "showRegisters": {
121                "type": "boolean",
122                "default": false,
123                "description": "Show register variables in variables pane."
124            },
125            "hideSystemGoroutines": {
126                "type": "boolean",
127                "default": false,
128                "description": "Hide system goroutines from call stack view."
129            },
130            "console": {
131                "default": "internalConsole",
132                "description": "Where to launch the debugger.",
133                "enum": ["internalConsole", "integratedTerminal"]
134            },
135            "asRoot": {
136                "default": false,
137                "description": "Debug with elevated permissions (on Unix).",
138                "type": "boolean"
139            }
140        });
141
142        // Create launch-specific properties
143        let launch_properties = json!({
144            "program": {
145                "type": "string",
146                "description": "Path to the program folder or file to debug.",
147                "default": "${ZED_WORKTREE_ROOT}"
148            },
149            "args": {
150                "type": ["array", "string"],
151                "description": "Command line arguments for the program.",
152                "items": {
153                    "type": "string"
154                },
155                "default": []
156            },
157            "env": {
158                "type": "object",
159                "description": "Environment variables for the debugged program.",
160                "default": {}
161            },
162            "envFile": {
163                "type": ["string", "array"],
164                "items": {
165                    "type": "string"
166                },
167                "description": "Path(s) to files with environment variables.",
168                "default": ""
169            },
170            "buildFlags": {
171                "type": ["string", "array"],
172                "items": {
173                    "type": "string"
174                },
175                "description": "Flags for the Go compiler.",
176                "default": []
177            },
178            "output": {
179                "type": "string",
180                "description": "Output path for the binary.",
181                "default": "debug"
182            },
183            "mode": {
184                "enum": [ "debug", "test", "exec", "replay", "core"],
185                "description": "Debug mode for launch configuration.",
186            },
187            "traceDirPath": {
188                "type": "string",
189                "description": "Directory for record trace (for 'replay' mode).",
190                "default": ""
191            },
192            "coreFilePath": {
193                "type": "string",
194                "description": "Path to core dump file (for 'core' mode).",
195                "default": ""
196            }
197        });
198
199        // Create attach-specific properties
200        let attach_properties = json!({
201            "processId": {
202                "anyOf": [
203                    {
204                        "enum": ["${command:pickProcess}", "${command:pickGoProcess}"],
205                        "description": "Use process picker to select a process."
206                    },
207                    {
208                        "type": "string",
209                        "description": "Process name to attach to."
210                    },
211                    {
212                        "type": "number",
213                        "description": "Process ID to attach to."
214                    }
215                ],
216                "default": 0
217            },
218            "mode": {
219                "enum": ["local", "remote"],
220                "description": "Local or remote debugging.",
221                "default": "local"
222            },
223            "remotePath": {
224                "type": "string",
225                "description": "Path to source on remote machine.",
226                "markdownDeprecationMessage": "Use `substitutePath` instead.",
227                "default": ""
228            }
229        });
230
231        // Create the final schema
232        json!({
233            "oneOf": [
234                {
235                    "allOf": [
236                        {
237                            "type": "object",
238                            "required": ["request"],
239                            "properties": {
240                                "request": {
241                                    "type": "string",
242                                    "enum": ["launch"],
243                                    "description": "Request to launch a new process"
244                                }
245                            }
246                        },
247                        {
248                            "type": "object",
249                            "properties": common_properties
250                        },
251                        {
252                            "type": "object",
253                            "required": ["program", "mode"],
254                            "properties": launch_properties
255                        }
256                    ]
257                },
258                {
259                    "allOf": [
260                        {
261                            "type": "object",
262                            "required": ["request"],
263                            "properties": {
264                                "request": {
265                                    "type": "string",
266                                    "enum": ["attach"],
267                                    "description": "Request to attach to an existing process"
268                                }
269                            }
270                        },
271                        {
272                            "type": "object",
273                            "properties": common_properties
274                        },
275                        {
276                            "type": "object",
277                            "required": ["processId", "mode"],
278                            "properties": attach_properties
279                        }
280                    ]
281                }
282            ]
283        })
284    }
285
286    fn validate_config(
287        &self,
288        config: &serde_json::Value,
289    ) -> Result<StartDebuggingRequestArgumentsRequest> {
290        let map = config.as_object().context("Config isn't an object")?;
291
292        let request_variant = map
293            .get("request")
294            .and_then(|val| val.as_str())
295            .context("request argument is not found or invalid")?;
296
297        match request_variant {
298            "launch" => Ok(StartDebuggingRequestArgumentsRequest::Launch),
299            "attach" => Ok(StartDebuggingRequestArgumentsRequest::Attach),
300            _ => Err(anyhow!("request must be either 'launch' or 'attach'")),
301        }
302    }
303
304    fn config_from_zed_format(&self, zed_scenario: ZedDebugConfig) -> Result<DebugScenario> {
305        let mut args = match &zed_scenario.request {
306            dap::DebugRequest::Attach(attach_config) => {
307                json!({
308                    "processId": attach_config.process_id,
309                })
310            }
311            dap::DebugRequest::Launch(launch_config) => json!({
312                "program": launch_config.program,
313                "cwd": launch_config.cwd,
314                "args": launch_config.args,
315                "env": launch_config.env_json()
316            }),
317        };
318
319        let map = args.as_object_mut().unwrap();
320
321        if let Some(stop_on_entry) = zed_scenario.stop_on_entry {
322            map.insert("stopOnEntry".into(), stop_on_entry.into());
323        }
324
325        Ok(DebugScenario {
326            adapter: zed_scenario.adapter,
327            label: zed_scenario.label,
328            build: None,
329            config: args,
330            tcp_connection: None,
331        })
332    }
333
334    async fn get_binary(
335        &self,
336        delegate: &Arc<dyn DapDelegate>,
337        task_definition: &DebugTaskDefinition,
338        _user_installed_path: Option<PathBuf>,
339        _cx: &mut AsyncApp,
340    ) -> Result<DebugAdapterBinary> {
341        let delve_path = delegate
342            .which(OsStr::new("dlv"))
343            .await
344            .and_then(|p| p.to_str().map(|p| p.to_string()))
345            .context("Dlv not found in path")?;
346
347        let tcp_connection = task_definition.tcp_connection.clone().unwrap_or_default();
348        let (host, port, timeout) = crate::configure_tcp_connection(tcp_connection).await?;
349
350        Ok(DebugAdapterBinary {
351            command: delve_path,
352            arguments: vec!["dap".into(), "--listen".into(), format!("{host}:{port}")],
353            cwd: Some(delegate.worktree_root_path().to_path_buf()),
354            envs: HashMap::default(),
355            connection: Some(adapters::TcpArguments {
356                host,
357                port,
358                timeout,
359            }),
360            request_args: StartDebuggingRequestArguments {
361                configuration: task_definition.config.clone(),
362                request: self.validate_config(&task_definition.config)?,
363            },
364        })
365    }
366}