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: replacer.replace_value(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            ("file".to_owned(), VariableName::Filename.to_string()), // 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        "rdbg" => "Ruby",
 98        _ => task_type,
 99    }
100    .to_owned()
101    .into()
102}
103
104#[cfg(test)]
105mod tests {
106    use serde_json::json;
107
108    use crate::{DebugScenario, DebugTaskFile, TcpArgumentsTemplate};
109
110    use super::VsCodeDebugTaskFile;
111
112    #[test]
113    fn test_parsing_vscode_launch_json() {
114        let raw = r#"
115            {
116                "version": "0.2.0",
117                "configurations": [
118                    {
119                        "name": "Debug my JS app",
120                        "request": "launch",
121                        "type": "node",
122                        "program": "${workspaceFolder}/xyz.js",
123                        "showDevDebugOutput": false,
124                        "stopOnEntry": true,
125                        "args": ["--foo", "${workspaceFolder}/thing"],
126                        "cwd": "${workspaceFolder}/${env:FOO}/sub",
127                        "env": {
128                            "X": "Y"
129                        },
130                        "port": 17
131                    },
132                ]
133            }
134        "#;
135        let parsed: VsCodeDebugTaskFile =
136            serde_json_lenient::from_str(&raw).expect("deserializing launch.json");
137        let zed = DebugTaskFile::try_from(parsed).expect("converting to Zed debug templates");
138        pretty_assertions::assert_eq!(
139            zed,
140            DebugTaskFile(vec![DebugScenario {
141                label: "Debug my JS app".into(),
142                adapter: "JavaScript".into(),
143                config: json!({
144                    "showDevDebugOutput": false,
145                }),
146                tcp_connection: Some(TcpArgumentsTemplate {
147                    port: Some(17),
148                    host: None,
149                    timeout: None,
150                }),
151                build: None
152            }])
153        );
154    }
155}