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}