vscode_debug_format.rs

  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        .with_commands([(
 73            "pickMyProcess".to_owned(),
 74            VariableName::PickProcessId.to_string(),
 75        )]);
 76        let templates = file
 77            .configurations
 78            .into_iter()
 79            .filter_map(|config| config.try_to_zed(&replacer).log_err())
 80            .collect::<Vec<_>>();
 81        Ok(DebugTaskFile(templates))
 82    }
 83}
 84
 85fn task_type_to_adapter_name(task_type: &str) -> String {
 86    match task_type {
 87        "pwa-node" | "node" | "node-terminal" | "chrome" | "pwa-chrome" | "edge" | "pwa-edge"
 88        | "msedge" | "pwa-msedge" => "JavaScript",
 89        "go" => "Delve",
 90        "php" => "Xdebug",
 91        "cppdbg" | "lldb" => "CodeLLDB",
 92        "debugpy" => "Debugpy",
 93        "rdbg" => "rdbg",
 94        _ => task_type,
 95    }
 96    .to_owned()
 97}
 98
 99#[cfg(test)]
100mod tests {
101    use serde_json::json;
102
103    use crate::{DebugScenario, DebugTaskFile, VariableName};
104
105    use super::VsCodeDebugTaskFile;
106
107    #[test]
108    fn test_parsing_vscode_launch_json() {
109        let raw = r#"
110            {
111                "version": "0.2.0",
112                "configurations": [
113                    {
114                        "name": "Debug my JS app",
115                        "request": "launch",
116                        "type": "node",
117                        "program": "${workspaceFolder}/xyz.js",
118                        "showDevDebugOutput": false,
119                        "stopOnEntry": true,
120                        "args": ["--foo", "${workspaceFolder}/thing"],
121                        "cwd": "${workspaceFolder}/${env:FOO}/sub",
122                        "env": {
123                            "X": "Y"
124                        },
125                        "port": 17
126                    },
127                ]
128            }
129        "#;
130        let parsed: VsCodeDebugTaskFile =
131            serde_json_lenient::from_str(raw).expect("deserializing launch.json");
132        let zed = DebugTaskFile::try_from(parsed).expect("converting to Zed debug templates");
133        pretty_assertions::assert_eq!(
134            zed,
135            DebugTaskFile(vec![DebugScenario {
136                label: "Debug my JS app".into(),
137                adapter: "JavaScript".into(),
138                config: json!({
139                    "request": "launch",
140                    "program": "${ZED_WORKTREE_ROOT}/xyz.js",
141                    "showDevDebugOutput": false,
142                    "stopOnEntry": true,
143                    "args": [
144                        "--foo",
145                        "${ZED_WORKTREE_ROOT}/thing",
146                    ],
147                    "cwd": "${ZED_WORKTREE_ROOT}/${FOO}/sub",
148                    "env": {
149                        "X": "Y",
150                    },
151                    "type": "node",
152                    "port": 17,
153                }),
154                tcp_connection: None,
155                build: None
156            }])
157        );
158    }
159
160    #[test]
161    fn test_command_pickmyprocess_replacement() {
162        let raw = r#"
163            {
164                "version": "0.2.0",
165                "configurations": [
166                    {
167                        "name": "Attach to Process",
168                        "request": "attach",
169                        "type": "cppdbg",
170                        "processId": "${command:pickMyProcess}"
171                    }
172                ]
173            }
174        "#;
175        let parsed: VsCodeDebugTaskFile =
176            serde_json_lenient::from_str(raw).expect("deserializing launch.json");
177        let zed = DebugTaskFile::try_from(parsed).expect("converting to Zed debug templates");
178
179        let expected_placeholder = format!("${{{}}}", VariableName::PickProcessId);
180        pretty_assertions::assert_eq!(
181            zed,
182            DebugTaskFile(vec![DebugScenario {
183                label: "Attach to Process".into(),
184                adapter: "CodeLLDB".into(),
185                config: json!({
186                    "request": "attach",
187                    "processId": expected_placeholder,
188                }),
189                tcp_connection: None,
190                build: None
191            }])
192        );
193    }
194}