1use super::DapLocator;
2use anyhow::{Result, anyhow};
3use async_trait::async_trait;
4use dap::DebugAdapterConfig;
5use serde_json::{Value, json};
6use smol::{
7 io::AsyncReadExt,
8 process::{Command, Stdio},
9};
10use util::maybe;
11
12pub(super) struct CargoLocator;
13
14#[async_trait]
15impl DapLocator for CargoLocator {
16 async fn run_locator(&self, debug_config: &mut DebugAdapterConfig) -> Result<()> {
17 let Some(launch_config) = (match &mut debug_config.request {
18 task::DebugRequestDisposition::UserConfigured(task::DebugRequestType::Launch(
19 launch_config,
20 )) => Some(launch_config),
21 _ => None,
22 }) else {
23 return Err(anyhow!("Couldn't get launch config in locator"));
24 };
25
26 let Some(cwd) = launch_config.cwd.clone() else {
27 return Err(anyhow!(
28 "Couldn't get cwd from debug config which is needed for locators"
29 ));
30 };
31
32 let mut child = Command::new("cargo")
33 .args(&launch_config.args)
34 .arg("--message-format=json")
35 .current_dir(cwd)
36 .stdout(Stdio::piped())
37 .spawn()?;
38
39 let mut output = String::new();
40 if let Some(mut stdout) = child.stdout.take() {
41 stdout.read_to_string(&mut output).await?;
42 }
43
44 let status = child.status().await?;
45 if !status.success() {
46 return Err(anyhow::anyhow!("Cargo command failed"));
47 }
48
49 let Some(executable) = output
50 .lines()
51 .filter(|line| !line.trim().is_empty())
52 .filter_map(|line| serde_json::from_str(line).ok())
53 .find_map(|json: Value| {
54 json.get("executable")
55 .and_then(Value::as_str)
56 .map(String::from)
57 })
58 else {
59 return Err(anyhow!("Couldn't get executable in cargo locator"));
60 };
61
62 launch_config.program = executable;
63 let mut test_name = None;
64
65 if launch_config
66 .args
67 .first()
68 .map_or(false, |arg| arg == "test")
69 {
70 if let Some(package_index) = launch_config
71 .args
72 .iter()
73 .position(|arg| arg == "-p" || arg == "--package")
74 {
75 test_name = launch_config
76 .args
77 .get(package_index + 2)
78 .filter(|name| !name.starts_with("--"))
79 .cloned();
80 }
81 }
82
83 if debug_config.adapter == "LLDB" && debug_config.initialize_args.is_none() {
84 // Find Rust pretty-printers in current toolchain's sysroot
85 let cwd = launch_config.cwd.clone();
86 debug_config.initialize_args = maybe!(async move {
87 let cwd = cwd?;
88
89 let output = Command::new("rustc")
90 .arg("--print")
91 .arg("sysroot")
92 .current_dir(cwd)
93 .output()
94 .await
95 .ok()?;
96
97 if !output.status.success() {
98 return None;
99 }
100
101 let sysroot_path = String::from_utf8(output.stdout).ok()?;
102 let sysroot_path = sysroot_path.trim_end();
103 let first_command = format!(
104 r#"command script import "{sysroot_path}/lib/rustlib/etc/lldb_lookup.py"#
105 );
106 let second_command =
107 format!(r#"command source -s 0 '{sysroot_path}/lib/rustlib/etc/lldb_commands"#);
108
109 Some(json!({"initCommands": [first_command, second_command]}))
110 })
111 .await;
112 }
113
114 launch_config.args.clear();
115 if let Some(test_name) = test_name {
116 launch_config.args.push(test_name);
117 }
118 Ok(())
119 }
120}