debug_format.rs

  1use anyhow::Result;
  2use collections::FxHashMap;
  3use gpui::SharedString;
  4use schemars::{JsonSchema, r#gen::SchemaSettings};
  5use serde::{Deserialize, Serialize};
  6use std::path::PathBuf;
  7use std::{net::Ipv4Addr, path::Path};
  8
  9/// Represents the host information of the debug adapter
 10#[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
 11pub struct TcpArgumentsTemplate {
 12    /// The port that the debug adapter is listening on
 13    ///
 14    /// Default: We will try to find an open port
 15    pub port: Option<u16>,
 16    /// The host that the debug adapter is listening too
 17    ///
 18    /// Default: 127.0.0.1
 19    pub host: Option<Ipv4Addr>,
 20    /// The max amount of time in milliseconds to connect to a tcp DAP before returning an error
 21    ///
 22    /// Default: 2000ms
 23    pub timeout: Option<u64>,
 24}
 25
 26impl TcpArgumentsTemplate {
 27    /// Get the host or fallback to the default host
 28    pub fn host(&self) -> Ipv4Addr {
 29        self.host.unwrap_or_else(|| Ipv4Addr::new(127, 0, 0, 1))
 30    }
 31
 32    pub fn from_proto(proto: proto::TcpHost) -> Result<Self> {
 33        Ok(Self {
 34            port: proto.port.map(|p| p.try_into()).transpose()?,
 35            host: proto.host.map(|h| h.parse()).transpose()?,
 36            timeout: proto.timeout,
 37        })
 38    }
 39
 40    pub fn to_proto(&self) -> proto::TcpHost {
 41        proto::TcpHost {
 42            port: self.port.map(|p| p.into()),
 43            host: self.host.map(|h| h.to_string()),
 44            timeout: self.timeout,
 45        }
 46    }
 47}
 48
 49/// Represents the attach request information of the debug adapter
 50#[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
 51pub struct AttachRequest {
 52    /// The processId to attach to, if left empty we will show a process picker
 53    pub process_id: Option<u32>,
 54}
 55
 56/// Represents the launch request information of the debug adapter
 57#[derive(Deserialize, Serialize, Default, PartialEq, Eq, JsonSchema, Clone, Debug)]
 58pub struct LaunchRequest {
 59    /// The program that you trying to debug
 60    pub program: String,
 61    /// The current working directory of your project
 62    #[serde(default)]
 63    pub cwd: Option<PathBuf>,
 64    /// Arguments to pass to a debuggee
 65    #[serde(default)]
 66    pub args: Vec<String>,
 67    #[serde(default)]
 68    pub env: FxHashMap<String, String>,
 69}
 70
 71/// Represents the type that will determine which request to call on the debug adapter
 72#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
 73#[serde(rename_all = "lowercase", untagged)]
 74pub enum DebugRequest {
 75    /// Call the `launch` request on the debug adapter
 76    Launch(LaunchRequest),
 77    /// Call the `attach` request on the debug adapter
 78    Attach(AttachRequest),
 79}
 80
 81impl DebugRequest {
 82    pub fn to_proto(&self) -> proto::DebugRequest {
 83        match self {
 84            DebugRequest::Launch(launch_request) => proto::DebugRequest {
 85                request: Some(proto::debug_request::Request::DebugLaunchRequest(
 86                    proto::DebugLaunchRequest {
 87                        program: launch_request.program.clone(),
 88                        cwd: launch_request
 89                            .cwd
 90                            .as_ref()
 91                            .map(|cwd| cwd.to_string_lossy().into_owned()),
 92                        args: launch_request.args.clone(),
 93                        env: launch_request
 94                            .env
 95                            .iter()
 96                            .map(|(k, v)| (k.clone(), v.clone()))
 97                            .collect(),
 98                    },
 99                )),
100            },
101            DebugRequest::Attach(attach_request) => proto::DebugRequest {
102                request: Some(proto::debug_request::Request::DebugAttachRequest(
103                    proto::DebugAttachRequest {
104                        process_id: attach_request
105                            .process_id
106                            .expect("The process ID to be already filled out."),
107                    },
108                )),
109            },
110        }
111    }
112
113    pub fn from_proto(val: proto::DebugRequest) -> Result<DebugRequest> {
114        let request = val
115            .request
116            .ok_or_else(|| anyhow::anyhow!("Missing debug request"))?;
117        match request {
118            proto::debug_request::Request::DebugLaunchRequest(proto::DebugLaunchRequest {
119                program,
120                cwd,
121                args,
122                env,
123            }) => Ok(DebugRequest::Launch(LaunchRequest {
124                program,
125                cwd: cwd.map(From::from),
126                args,
127                env: env.into_iter().collect(),
128            })),
129
130            proto::debug_request::Request::DebugAttachRequest(proto::DebugAttachRequest {
131                process_id,
132            }) => Ok(DebugRequest::Attach(AttachRequest {
133                process_id: Some(process_id),
134            })),
135        }
136    }
137}
138
139impl From<LaunchRequest> for DebugRequest {
140    fn from(launch_config: LaunchRequest) -> Self {
141        DebugRequest::Launch(launch_config)
142    }
143}
144
145impl From<AttachRequest> for DebugRequest {
146    fn from(attach_config: AttachRequest) -> Self {
147        DebugRequest::Attach(attach_config)
148    }
149}
150
151/// This struct represent a user created debug task
152#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
153#[serde(rename_all = "snake_case")]
154pub struct DebugScenario {
155    pub adapter: SharedString,
156    /// Name of the debug task
157    pub label: SharedString,
158    /// A task to run prior to spawning the debuggee.
159    #[serde(default)]
160    pub build: Option<SharedString>,
161    #[serde(flatten)]
162    pub request: Option<DebugRequest>,
163    /// Additional initialization arguments to be sent on DAP initialization
164    #[serde(default)]
165    pub initialize_args: Option<serde_json::Value>,
166    /// Optional TCP connection information
167    ///
168    /// If provided, this will be used to connect to the debug adapter instead of
169    /// spawning a new process. This is useful for connecting to a debug adapter
170    /// that is already running or is started by another process.
171    #[serde(default)]
172    pub tcp_connection: Option<TcpArgumentsTemplate>,
173    /// Whether to tell the debug adapter to stop on entry
174    #[serde(default)]
175    pub stop_on_entry: Option<bool>,
176}
177
178impl DebugScenario {
179    pub fn cwd(&self) -> Option<&Path> {
180        if let Some(DebugRequest::Launch(config)) = &self.request {
181            config.cwd.as_ref().map(Path::new)
182        } else {
183            None
184        }
185    }
186}
187
188/// A group of Debug Tasks defined in a JSON file.
189#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
190#[serde(transparent)]
191pub struct DebugTaskFile(pub Vec<DebugScenario>);
192
193impl DebugTaskFile {
194    /// Generates JSON schema of Tasks JSON template format.
195    pub fn generate_json_schema() -> serde_json_lenient::Value {
196        let schema = SchemaSettings::draft07()
197            .with(|settings| settings.option_add_null_type = false)
198            .into_generator()
199            .into_root_schema_for::<Self>();
200
201        serde_json_lenient::to_value(schema).unwrap()
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use crate::{DebugRequest, LaunchRequest};
208
209    #[test]
210    fn test_can_deserialize_non_attach_task() {
211        let deserialized: DebugRequest =
212            serde_json::from_str(r#"{"program": "cafebabe"}"#).unwrap();
213        assert_eq!(
214            deserialized,
215            DebugRequest::Launch(LaunchRequest {
216                program: "cafebabe".to_owned(),
217                ..Default::default()
218            })
219        );
220    }
221}