1use super::DapLocator;
2use anyhow::{Result, anyhow};
3use async_trait::async_trait;
4use dap::DebugAdapterConfig;
5use serde_json::Value;
6use smol::{
7 io::AsyncReadExt,
8 process::{Command, Stdio},
9};
10
11pub(super) struct CargoLocator {}
12
13#[async_trait]
14impl DapLocator for CargoLocator {
15 async fn run_locator(&self, debug_config: &mut DebugAdapterConfig) -> Result<()> {
16 let Some(launch_config) = (match &mut debug_config.request {
17 task::DebugRequestDisposition::UserConfigured(task::DebugRequestType::Launch(
18 launch_config,
19 )) => Some(launch_config),
20 _ => None,
21 }) else {
22 return Err(anyhow!("Couldn't get launch config in locator"));
23 };
24
25 let Some(cwd) = launch_config.cwd.clone() else {
26 return Err(anyhow!(
27 "Couldn't get cwd from debug config which is needed for locators"
28 ));
29 };
30
31 let mut child = Command::new("cargo")
32 .args(&launch_config.args)
33 .arg("--message-format=json")
34 .current_dir(cwd)
35 .stdout(Stdio::piped())
36 .spawn()?;
37
38 let mut output = String::new();
39 if let Some(mut stdout) = child.stdout.take() {
40 stdout.read_to_string(&mut output).await?;
41 }
42
43 let status = child.status().await?;
44 if !status.success() {
45 return Err(anyhow::anyhow!("Cargo command failed"));
46 }
47
48 let Some(executable) = output
49 .lines()
50 .filter(|line| !line.trim().is_empty())
51 .filter_map(|line| serde_json::from_str(line).ok())
52 .find_map(|json: Value| {
53 json.get("executable")
54 .and_then(Value::as_str)
55 .map(String::from)
56 })
57 else {
58 return Err(anyhow!("Couldn't get executable in cargo locator"));
59 };
60
61 launch_config.program = executable;
62 let mut test_name = None;
63
64 if launch_config
65 .args
66 .first()
67 .map_or(false, |arg| arg == "test")
68 {
69 if let Some(package_index) = launch_config
70 .args
71 .iter()
72 .position(|arg| arg == "-p" || arg == "--package")
73 {
74 test_name = launch_config
75 .args
76 .get(package_index + 2)
77 .filter(|name| !name.starts_with("--"))
78 .cloned();
79 }
80 }
81
82 launch_config.args.clear();
83 if let Some(test_name) = test_name {
84 launch_config.args.push(test_name);
85 }
86 Ok(())
87 }
88}