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