go.rs

  1use anyhow::Result;
  2use async_trait::async_trait;
  3use collections::FxHashMap;
  4use dap::{DapLocator, DebugRequest, adapters::DebugAdapterName};
  5use gpui::SharedString;
  6use std::path::PathBuf;
  7use task::{
  8    BuildTaskDefinition, DebugScenario, RevealStrategy, RevealTarget, Shell, SpawnInTerminal,
  9    TaskTemplate,
 10};
 11
 12pub(crate) struct GoLocator;
 13
 14#[async_trait]
 15impl DapLocator for GoLocator {
 16    fn name(&self) -> SharedString {
 17        SharedString::new_static("go-debug-locator")
 18    }
 19
 20    fn create_scenario(
 21        &self,
 22        build_config: &TaskTemplate,
 23        resolved_label: &str,
 24        adapter: DebugAdapterName,
 25    ) -> Option<DebugScenario> {
 26        let go_action = build_config.args.first()?;
 27
 28        match go_action.as_str() {
 29            "run" => {
 30                let program = build_config
 31                    .args
 32                    .get(1)
 33                    .cloned()
 34                    .unwrap_or_else(|| ".".to_string());
 35
 36                let build_task = TaskTemplate {
 37                    label: "go build debug".into(),
 38                    command: "go".into(),
 39                    args: vec![
 40                        "build".into(),
 41                        "-gcflags \"all=-N -l\"".into(),
 42                        program.clone(),
 43                    ],
 44                    env: build_config.env.clone(),
 45                    cwd: build_config.cwd.clone(),
 46                    use_new_terminal: false,
 47                    allow_concurrent_runs: false,
 48                    reveal: RevealStrategy::Always,
 49                    reveal_target: RevealTarget::Dock,
 50                    hide: task::HideStrategy::Never,
 51                    shell: Shell::System,
 52                    tags: vec![],
 53                    show_summary: true,
 54                    show_command: true,
 55                };
 56
 57                Some(DebugScenario {
 58                    label: resolved_label.to_string().into(),
 59                    adapter: adapter.0,
 60                    build: Some(BuildTaskDefinition::Template {
 61                        task_template: build_task,
 62                        locator_name: Some(self.name()),
 63                    }),
 64                    config: serde_json::Value::Null,
 65                    tcp_connection: None,
 66                })
 67            }
 68            _ => None,
 69        }
 70    }
 71
 72    async fn run(&self, build_config: SpawnInTerminal) -> Result<DebugRequest> {
 73        if build_config.args.is_empty() {
 74            return Err(anyhow::anyhow!("Invalid Go command"));
 75        }
 76
 77        let go_action = &build_config.args[0];
 78        let cwd = build_config
 79            .cwd
 80            .as_ref()
 81            .map(|p| p.to_string_lossy().to_string())
 82            .unwrap_or_else(|| ".".to_string());
 83
 84        let mut env = FxHashMap::default();
 85        for (key, value) in &build_config.env {
 86            env.insert(key.clone(), value.clone());
 87        }
 88
 89        match go_action.as_str() {
 90            "build" => {
 91                let package = build_config
 92                    .args
 93                    .get(2)
 94                    .cloned()
 95                    .unwrap_or_else(|| ".".to_string());
 96
 97                Ok(DebugRequest::Launch(task::LaunchRequest {
 98                    program: package,
 99                    cwd: Some(PathBuf::from(&cwd)),
100                    args: vec![],
101                    env,
102                }))
103            }
104            _ => Err(anyhow::anyhow!("Unsupported Go command: {}", go_action)),
105        }
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use task::{HideStrategy, RevealStrategy, RevealTarget, Shell, TaskTemplate};
113
114    #[test]
115    fn test_create_scenario_for_go_run() {
116        let locator = GoLocator;
117        let task = TaskTemplate {
118            label: "go run main.go".into(),
119            command: "go".into(),
120            args: vec!["run".into(), "main.go".into()],
121            env: Default::default(),
122            cwd: Some("${ZED_WORKTREE_ROOT}".into()),
123            use_new_terminal: false,
124            allow_concurrent_runs: false,
125            reveal: RevealStrategy::Always,
126            reveal_target: RevealTarget::Dock,
127            hide: HideStrategy::Never,
128            shell: Shell::System,
129            tags: vec![],
130            show_summary: true,
131            show_command: true,
132        };
133
134        let scenario =
135            locator.create_scenario(&task, "test label", DebugAdapterName("Delve".into()));
136
137        assert!(scenario.is_some());
138        let scenario = scenario.unwrap();
139        assert_eq!(scenario.adapter, "Delve");
140        assert_eq!(scenario.label, "test label");
141        assert!(scenario.build.is_some());
142
143        if let Some(BuildTaskDefinition::Template { task_template, .. }) = &scenario.build {
144            assert_eq!(task_template.command, "go");
145            assert!(task_template.args.contains(&"build".into()));
146            assert!(
147                task_template
148                    .args
149                    .contains(&"-gcflags \"all=-N -l\"".into())
150            );
151            assert!(task_template.args.contains(&"main.go".into()));
152        } else {
153            panic!("Expected BuildTaskDefinition::Template");
154        }
155
156        assert!(
157            scenario.config.is_null(),
158            "Initial config should be null to ensure it's invalid"
159        );
160    }
161
162    #[test]
163    fn test_create_scenario_for_go_build() {
164        let locator = GoLocator;
165        let task = TaskTemplate {
166            label: "go build".into(),
167            command: "go".into(),
168            args: vec!["build".into(), ".".into()],
169            env: Default::default(),
170            cwd: Some("${ZED_WORKTREE_ROOT}".into()),
171            use_new_terminal: false,
172            allow_concurrent_runs: false,
173            reveal: RevealStrategy::Always,
174            reveal_target: RevealTarget::Dock,
175            hide: HideStrategy::Never,
176            shell: Shell::System,
177            tags: vec![],
178            show_summary: true,
179            show_command: true,
180        };
181
182        let scenario =
183            locator.create_scenario(&task, "test label", DebugAdapterName("Delve".into()));
184
185        assert!(scenario.is_none());
186    }
187
188    #[test]
189    fn test_skip_non_go_commands_with_non_delve_adapter() {
190        let locator = GoLocator;
191        let task = TaskTemplate {
192            label: "cargo build".into(),
193            command: "cargo".into(),
194            args: vec!["build".into()],
195            env: Default::default(),
196            cwd: Some("${ZED_WORKTREE_ROOT}".into()),
197            use_new_terminal: false,
198            allow_concurrent_runs: false,
199            reveal: RevealStrategy::Always,
200            reveal_target: RevealTarget::Dock,
201            hide: HideStrategy::Never,
202            shell: Shell::System,
203            tags: vec![],
204            show_summary: true,
205            show_command: true,
206        };
207
208        let scenario = locator.create_scenario(
209            &task,
210            "test label",
211            DebugAdapterName("SomeOtherAdapter".into()),
212        );
213        assert!(scenario.is_none());
214
215        let scenario =
216            locator.create_scenario(&task, "test label", DebugAdapterName("Delve".into()));
217        assert!(scenario.is_none());
218    }
219
220    #[test]
221    fn test_skip_unsupported_go_commands() {
222        let locator = GoLocator;
223        let task = TaskTemplate {
224            label: "go clean".into(),
225            command: "go".into(),
226            args: vec!["clean".into()],
227            env: Default::default(),
228            cwd: Some("${ZED_WORKTREE_ROOT}".into()),
229            use_new_terminal: false,
230            allow_concurrent_runs: false,
231            reveal: RevealStrategy::Always,
232            reveal_target: RevealTarget::Dock,
233            hide: HideStrategy::Never,
234            shell: Shell::System,
235            tags: vec![],
236            show_summary: true,
237            show_command: true,
238        };
239
240        let scenario =
241            locator.create_scenario(&task, "test label", DebugAdapterName("Delve".into()));
242        assert!(scenario.is_none());
243    }
244}