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(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            if adapter == "JavaScript" {
 35                config.insert("type".to_owned(), self.r#type.clone().into());
 36            }
 37        }
 38        let definition = DebugScenario {
 39            label: label.into(),
 40            build: None,
 41            adapter: adapter.into(),
 42            tcp_connection: self.port.map(|port| TcpArgumentsTemplate {
 43                port: Some(port),
 44                host: None,
 45                timeout: None,
 46            }),
 47            config,
 48        };
 49        Ok(definition)
 50    }
 51}
 52
 53#[derive(Clone, Debug, Deserialize, PartialEq)]
 54#[serde(rename_all = "camelCase")]
 55pub struct VsCodeDebugTaskFile {
 56    #[serde(default)]
 57    version: Option<String>,
 58    configurations: Vec<VsCodeDebugTaskDefinition>,
 59}
 60
 61impl TryFrom<VsCodeDebugTaskFile> for DebugTaskFile {
 62    type Error = anyhow::Error;
 63
 64    fn try_from(file: VsCodeDebugTaskFile) -> Result<Self, Self::Error> {
 65        let replacer = EnvVariableReplacer::new(HashMap::from_iter([
 66            (
 67                "workspaceFolder".to_owned(),
 68                VariableName::WorktreeRoot.to_string(),
 69            ),
 70            (
 71                "relativeFile".to_owned(),
 72                VariableName::RelativeFile.to_string(),
 73            ),
 74            ("file".to_owned(), VariableName::File.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" | "chrome" | "pwa-chrome" | "edge" | "pwa-edge" | "msedge"
 88        | "pwa-msedge" => "JavaScript",
 89        "go" => "Delve",
 90        "php" => "PHP",
 91        "cppdbg" | "lldb" => "CodeLLDB",
 92        "debugpy" => "Debugpy",
 93        "rdbg" => "Ruby",
 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, TcpArgumentsTemplate};
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                }),
153                tcp_connection: Some(TcpArgumentsTemplate {
154                    port: Some(17),
155                    host: None,
156                    timeout: None,
157                }),
158                build: None
159            }])
160        );
161    }
162}