debug_format.rs

  1use dap_types::StartDebuggingRequestArguments;
  2use schemars::{JsonSchema, r#gen::SchemaSettings};
  3use serde::{Deserialize, Serialize};
  4use std::net::Ipv4Addr;
  5use std::path::PathBuf;
  6use util::ResultExt;
  7
  8use crate::{TaskTemplate, TaskTemplates, TaskType, task_template::DebugArgs};
  9
 10impl Default for DebugConnectionType {
 11    fn default() -> Self {
 12        DebugConnectionType::TCP(TCPHost::default())
 13    }
 14}
 15
 16/// Represents the host information of the debug adapter
 17#[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
 18pub struct TCPHost {
 19    /// The port that the debug adapter is listening on
 20    ///
 21    /// Default: We will try to find an open port
 22    pub port: Option<u16>,
 23    /// The host that the debug adapter is listening too
 24    ///
 25    /// Default: 127.0.0.1
 26    pub host: Option<Ipv4Addr>,
 27    /// The max amount of time in milliseconds to connect to a tcp DAP before returning an error
 28    ///
 29    /// Default: 2000ms
 30    pub timeout: Option<u64>,
 31}
 32
 33impl TCPHost {
 34    /// Get the host or fallback to the default host
 35    pub fn host(&self) -> Ipv4Addr {
 36        self.host.unwrap_or_else(|| Ipv4Addr::new(127, 0, 0, 1))
 37    }
 38}
 39
 40/// Represents the attach request information of the debug adapter
 41#[derive(Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
 42pub struct AttachConfig {
 43    /// The processId to attach to, if left empty we will show a process picker
 44    pub process_id: Option<u32>,
 45}
 46
 47/// Represents the launch request information of the debug adapter
 48#[derive(Deserialize, Serialize, Default, PartialEq, Eq, JsonSchema, Clone, Debug)]
 49pub struct LaunchConfig {
 50    /// The program that you trying to debug
 51    pub program: String,
 52    /// The current working directory of your project
 53    pub cwd: Option<PathBuf>,
 54    /// Arguments to pass to a debuggee
 55    #[serde(default)]
 56    pub args: Vec<String>,
 57}
 58
 59/// Represents the type that will determine which request to call on the debug adapter
 60#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
 61#[serde(rename_all = "lowercase", untagged)]
 62pub enum DebugRequestType {
 63    /// Call the `launch` request on the debug adapter
 64    Launch(LaunchConfig),
 65    /// Call the `attach` request on the debug adapter
 66    Attach(AttachConfig),
 67}
 68
 69impl From<LaunchConfig> for DebugRequestType {
 70    fn from(launch_config: LaunchConfig) -> Self {
 71        DebugRequestType::Launch(launch_config)
 72    }
 73}
 74
 75impl From<AttachConfig> for DebugRequestType {
 76    fn from(attach_config: AttachConfig) -> Self {
 77        DebugRequestType::Attach(attach_config)
 78    }
 79}
 80/// Represents a request for starting the debugger.
 81/// Contrary to `DebugRequestType`, `DebugRequestDisposition` is not Serializable.
 82#[derive(PartialEq, Eq, Clone, Debug)]
 83pub enum DebugRequestDisposition {
 84    /// Debug session configured by the user.
 85    UserConfigured(DebugRequestType),
 86    /// Debug session configured by the debug adapter
 87    ReverseRequest(StartDebuggingRequestArguments),
 88}
 89
 90impl DebugRequestDisposition {
 91    /// Get the current working directory from request if it's a launch request and exits
 92    pub fn cwd(&self) -> Option<PathBuf> {
 93        match self {
 94            Self::UserConfigured(DebugRequestType::Launch(launch_config)) => {
 95                launch_config.cwd.clone()
 96            }
 97            _ => None,
 98        }
 99    }
100}
101
102impl TryFrom<TaskTemplate> for DebugTaskDefinition {
103    type Error = ();
104
105    fn try_from(value: TaskTemplate) -> Result<Self, Self::Error> {
106        let TaskType::Debug(debug_args) = value.task_type else {
107            return Err(());
108        };
109
110        let request = match debug_args.request {
111            crate::DebugArgsRequest::Launch => DebugRequestType::Launch(LaunchConfig {
112                program: value.command,
113                cwd: value.cwd.map(PathBuf::from),
114                args: value.args,
115            }),
116            crate::DebugArgsRequest::Attach(attach_config) => {
117                DebugRequestType::Attach(attach_config)
118            }
119        };
120
121        Ok(DebugTaskDefinition {
122            adapter: debug_args.adapter,
123            request,
124            label: value.label,
125            initialize_args: debug_args.initialize_args,
126            tcp_connection: debug_args.tcp_connection,
127            locator: debug_args.locator,
128            stop_on_entry: debug_args.stop_on_entry,
129        })
130    }
131}
132
133impl DebugTaskDefinition {
134    /// Translate from debug definition to a task template
135    pub fn to_zed_format(self) -> anyhow::Result<TaskTemplate> {
136        let (command, cwd, request) = match self.request {
137            DebugRequestType::Launch(launch_config) => (
138                launch_config.program,
139                launch_config
140                    .cwd
141                    .map(|cwd| cwd.to_string_lossy().to_string()),
142                crate::task_template::DebugArgsRequest::Launch,
143            ),
144            DebugRequestType::Attach(attach_config) => (
145                "".to_owned(),
146                None,
147                crate::task_template::DebugArgsRequest::Attach(attach_config),
148            ),
149        };
150
151        let task_type = TaskType::Debug(DebugArgs {
152            adapter: self.adapter,
153            request,
154            initialize_args: self.initialize_args,
155            locator: self.locator,
156            tcp_connection: self.tcp_connection,
157            stop_on_entry: self.stop_on_entry,
158        });
159
160        let label = self.label.clone();
161
162        Ok(TaskTemplate {
163            label,
164            command,
165            args: vec![],
166            task_type,
167            cwd,
168            ..Default::default()
169        })
170    }
171}
172/// Represents the type of the debugger adapter connection
173#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
174#[serde(rename_all = "lowercase", tag = "connection")]
175pub enum DebugConnectionType {
176    /// Connect to the debug adapter via TCP
177    TCP(TCPHost),
178    /// Connect to the debug adapter via STDIO
179    STDIO,
180}
181
182/// This struct represent a user created debug task
183#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
184#[serde(rename_all = "snake_case")]
185pub struct DebugTaskDefinition {
186    /// The adapter to run
187    pub adapter: String,
188    /// The type of request that should be called on the debug adapter
189    #[serde(flatten)]
190    pub request: DebugRequestType,
191    /// Name of the debug task
192    pub label: String,
193    /// Additional initialization arguments to be sent on DAP initialization
194    pub initialize_args: Option<serde_json::Value>,
195    /// Optional TCP connection information
196    ///
197    /// If provided, this will be used to connect to the debug adapter instead of
198    /// spawning a new process. This is useful for connecting to a debug adapter
199    /// that is already running or is started by another process.
200    pub tcp_connection: Option<TCPHost>,
201    /// Locator to use
202    /// -- cargo
203    pub locator: Option<String>,
204    /// Whether to tell the debug adapter to stop on entry
205    pub stop_on_entry: Option<bool>,
206}
207
208/// A group of Debug Tasks defined in a JSON file.
209#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
210#[serde(transparent)]
211pub struct DebugTaskFile(pub Vec<DebugTaskDefinition>);
212
213impl DebugTaskFile {
214    /// Generates JSON schema of Tasks JSON template format.
215    pub fn generate_json_schema() -> serde_json_lenient::Value {
216        let schema = SchemaSettings::draft07()
217            .with(|settings| settings.option_add_null_type = false)
218            .into_generator()
219            .into_root_schema_for::<Self>();
220
221        serde_json_lenient::to_value(schema).unwrap()
222    }
223}
224
225impl TryFrom<DebugTaskFile> for TaskTemplates {
226    type Error = anyhow::Error;
227
228    fn try_from(value: DebugTaskFile) -> Result<Self, Self::Error> {
229        let templates = value
230            .0
231            .into_iter()
232            .filter_map(|debug_definition| debug_definition.to_zed_format().log_err())
233            .collect();
234
235        Ok(Self(templates))
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use crate::{DebugRequestType, LaunchConfig};
242
243    #[test]
244    fn test_can_deserialize_non_attach_task() {
245        let deserialized: DebugRequestType =
246            serde_json::from_str(r#"{"program": "cafebabe"}"#).unwrap();
247        assert_eq!(
248            deserialized,
249            DebugRequestType::Launch(LaunchConfig {
250                program: "cafebabe".to_owned(),
251                ..Default::default()
252            })
253        );
254    }
255}