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}