1use collections::HashMap;
2use serde::Deserialize;
3use util::ResultExt as _;
4
5use crate::{
6 DebugScenario, DebugTaskFile, EnvVariableReplacer, TcpArgumentsTemplate, VariableName,
7};
8
9// TODO support preLaunchTask linkage with other tasks
10#[derive(Clone, Debug, Deserialize, PartialEq)]
11#[serde(rename_all = "camelCase")]
12struct VsCodeDebugTaskDefinition {
13 r#type: String,
14 name: String,
15 #[serde(default)]
16 port: Option<u16>,
17 #[serde(flatten)]
18 other_attributes: serde_json::Value,
19}
20
21impl VsCodeDebugTaskDefinition {
22 fn try_to_zed(mut self, replacer: &EnvVariableReplacer) -> anyhow::Result<DebugScenario> {
23 let label = replacer.replace(&self.name);
24 let mut config = replacer.replace_value(self.other_attributes);
25 let adapter = task_type_to_adapter_name(&self.r#type);
26 if let Some(config) = config.as_object_mut()
27 && adapter == "JavaScript"
28 {
29 config.insert("type".to_owned(), self.r#type.clone().into());
30 if let Some(port) = self.port.take() {
31 config.insert("port".to_owned(), port.into());
32 }
33 }
34 let definition = DebugScenario {
35 label: label.into(),
36 build: None,
37 adapter: adapter.into(),
38 tcp_connection: self.port.map(|port| TcpArgumentsTemplate {
39 port: Some(port),
40 host: None,
41 timeout: None,
42 }),
43 config,
44 };
45 Ok(definition)
46 }
47}
48
49#[derive(Clone, Debug, Deserialize, PartialEq)]
50#[serde(rename_all = "camelCase")]
51pub struct VsCodeDebugTaskFile {
52 #[serde(default)]
53 version: Option<String>,
54 configurations: Vec<VsCodeDebugTaskDefinition>,
55}
56
57impl TryFrom<VsCodeDebugTaskFile> for DebugTaskFile {
58 type Error = anyhow::Error;
59
60 fn try_from(file: VsCodeDebugTaskFile) -> Result<Self, Self::Error> {
61 let replacer = EnvVariableReplacer::new(HashMap::from_iter([
62 (
63 "workspaceFolder".to_owned(),
64 VariableName::WorktreeRoot.to_string(),
65 ),
66 (
67 "relativeFile".to_owned(),
68 VariableName::RelativeFile.to_string(),
69 ),
70 ("file".to_owned(), VariableName::File.to_string()),
71 ]));
72 let templates = file
73 .configurations
74 .into_iter()
75 .filter_map(|config| config.try_to_zed(&replacer).log_err())
76 .collect::<Vec<_>>();
77 Ok(DebugTaskFile(templates))
78 }
79}
80
81fn task_type_to_adapter_name(task_type: &str) -> String {
82 match task_type {
83 "pwa-node" | "node" | "node-terminal" | "chrome" | "pwa-chrome" | "edge" | "pwa-edge"
84 | "msedge" | "pwa-msedge" => "JavaScript",
85 "go" => "Delve",
86 "php" => "Xdebug",
87 "cppdbg" | "lldb" => "CodeLLDB",
88 "debugpy" => "Debugpy",
89 "rdbg" => "rdbg",
90 _ => task_type,
91 }
92 .to_owned()
93}
94
95#[cfg(test)]
96mod tests {
97 use serde_json::json;
98
99 use crate::{DebugScenario, DebugTaskFile};
100
101 use super::VsCodeDebugTaskFile;
102
103 #[test]
104 fn test_parsing_vscode_launch_json() {
105 let raw = r#"
106 {
107 "version": "0.2.0",
108 "configurations": [
109 {
110 "name": "Debug my JS app",
111 "request": "launch",
112 "type": "node",
113 "program": "${workspaceFolder}/xyz.js",
114 "showDevDebugOutput": false,
115 "stopOnEntry": true,
116 "args": ["--foo", "${workspaceFolder}/thing"],
117 "cwd": "${workspaceFolder}/${env:FOO}/sub",
118 "env": {
119 "X": "Y"
120 },
121 "port": 17
122 },
123 ]
124 }
125 "#;
126 let parsed: VsCodeDebugTaskFile =
127 serde_json_lenient::from_str(raw).expect("deserializing launch.json");
128 let zed = DebugTaskFile::try_from(parsed).expect("converting to Zed debug templates");
129 pretty_assertions::assert_eq!(
130 zed,
131 DebugTaskFile(vec![DebugScenario {
132 label: "Debug my JS app".into(),
133 adapter: "JavaScript".into(),
134 config: json!({
135 "request": "launch",
136 "program": "${ZED_WORKTREE_ROOT}/xyz.js",
137 "showDevDebugOutput": false,
138 "stopOnEntry": true,
139 "args": [
140 "--foo",
141 "${ZED_WORKTREE_ROOT}/thing",
142 ],
143 "cwd": "${ZED_WORKTREE_ROOT}/${FOO}/sub",
144 "env": {
145 "X": "Y",
146 },
147 "type": "node",
148 "port": 17,
149 }),
150 tcp_connection: None,
151 build: None
152 }])
153 );
154 }
155}