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#[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}