vscode_debug_format.rs

  1use collections::HashMap;
  2use gpui::SharedString;
  3use serde::Deserialize;
  4use util::ResultExt as _;
  5
  6use crate::{
  7    DebugScenario, DebugTaskFile, EnvVariableReplacer, TcpArgumentsTemplate, VariableName,
  8};
  9
 10#[derive(Clone, Debug, Deserialize, PartialEq)]
 11#[serde(rename_all = "camelCase")]
 12enum Request {
 13    Launch,
 14    Attach,
 15}
 16
 17// TODO support preLaunchTask linkage with other tasks
 18#[derive(Clone, Debug, Deserialize, PartialEq)]
 19#[serde(rename_all = "camelCase")]
 20struct VsCodeDebugTaskDefinition {
 21    r#type: String,
 22    name: String,
 23    request: Request,
 24
 25    #[serde(default)]
 26    program: Option<String>,
 27    #[serde(default)]
 28    args: Vec<String>,
 29    #[serde(default)]
 30    env: HashMap<String, Option<String>>,
 31    // TODO envFile?
 32    #[serde(default)]
 33    cwd: Option<String>,
 34    #[serde(default)]
 35    port: Option<u16>,
 36    #[serde(default)]
 37    stop_on_entry: Option<bool>,
 38    #[serde(flatten)]
 39    other_attributes: serde_json::Value,
 40}
 41
 42impl VsCodeDebugTaskDefinition {
 43    fn try_to_zed(self, replacer: &EnvVariableReplacer) -> anyhow::Result<DebugScenario> {
 44        let label = replacer.replace(&self.name).into();
 45        // TODO based on grep.app results it seems that vscode supports whitespace-splitting this field (ugh)
 46        let definition = DebugScenario {
 47            label,
 48            build: None,
 49            adapter: task_type_to_adapter_name(&self.r#type),
 50            // TODO host?
 51            tcp_connection: self.port.map(|port| TcpArgumentsTemplate {
 52                port: Some(port),
 53                host: None,
 54                timeout: None,
 55            }),
 56            config: self.other_attributes,
 57        };
 58        Ok(definition)
 59    }
 60}
 61
 62#[derive(Clone, Debug, Deserialize, PartialEq)]
 63#[serde(rename_all = "camelCase")]
 64pub struct VsCodeDebugTaskFile {
 65    version: String,
 66    configurations: Vec<VsCodeDebugTaskDefinition>,
 67}
 68
 69impl TryFrom<VsCodeDebugTaskFile> for DebugTaskFile {
 70    type Error = anyhow::Error;
 71
 72    fn try_from(file: VsCodeDebugTaskFile) -> Result<Self, Self::Error> {
 73        let replacer = EnvVariableReplacer::new(HashMap::from_iter([
 74            (
 75                "workspaceFolder".to_owned(),
 76                VariableName::WorktreeRoot.to_string(),
 77            ),
 78            // TODO other interesting variables?
 79        ]));
 80        let templates = file
 81            .configurations
 82            .into_iter()
 83            .filter_map(|config| config.try_to_zed(&replacer).log_err())
 84            .collect::<Vec<_>>();
 85        Ok(DebugTaskFile(templates))
 86    }
 87}
 88
 89// todo(debugger) figure out how to make JsDebugAdapter::ADAPTER_NAME et al available here
 90fn task_type_to_adapter_name(task_type: &str) -> SharedString {
 91    match task_type {
 92        "node" => "JavaScript",
 93        "go" => "Delve",
 94        "php" => "PHP",
 95        "cppdbg" | "lldb" => "CodeLLDB",
 96        "debugpy" => "Debugpy",
 97        _ => task_type,
 98    }
 99    .to_owned()
100    .into()
101}
102
103#[cfg(test)]
104mod tests {
105    use serde_json::json;
106
107    use crate::{DebugScenario, DebugTaskFile, TcpArgumentsTemplate};
108
109    use super::VsCodeDebugTaskFile;
110
111    #[test]
112    fn test_parsing_vscode_launch_json() {
113        let raw = r#"
114            {
115                "version": "0.2.0",
116                "configurations": [
117                    {
118                        "name": "Debug my JS app",
119                        "request": "launch",
120                        "type": "node",
121                        "program": "${workspaceFolder}/xyz.js",
122                        "showDevDebugOutput": false,
123                        "stopOnEntry": true,
124                        "args": ["--foo", "${workspaceFolder}/thing"],
125                        "cwd": "${workspaceFolder}/${env:FOO}/sub",
126                        "env": {
127                            "X": "Y"
128                        },
129                        "port": 17
130                    },
131                ]
132            }
133        "#;
134        let parsed: VsCodeDebugTaskFile =
135            serde_json_lenient::from_str(&raw).expect("deserializing launch.json");
136        let zed = DebugTaskFile::try_from(parsed).expect("converting to Zed debug templates");
137        pretty_assertions::assert_eq!(
138            zed,
139            DebugTaskFile(vec![DebugScenario {
140                label: "Debug my JS app".into(),
141                adapter: "JavaScript".into(),
142                config: json!({
143                    "showDevDebugOutput": false,
144                }),
145                tcp_connection: Some(TcpArgumentsTemplate {
146                    port: Some(17),
147                    host: None,
148                    timeout: None,
149                }),
150                build: None
151            }])
152        );
153    }
154}