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}