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