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        {
 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}