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