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 #[serde(default)]
45 pub process_id: Option<u32>,
46}
47
48/// Represents the launch request information of the debug adapter
49#[derive(Deserialize, Serialize, Default, PartialEq, Eq, JsonSchema, Clone, Debug)]
50pub struct LaunchConfig {
51 /// The program that you trying to debug
52 pub program: String,
53 /// The current working directory of your project
54 pub cwd: Option<PathBuf>,
55 /// Args to pass to a debuggee
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
69/// Represents a request for starting the debugger.
70/// Contrary to `DebugRequestType`, `DebugRequestDisposition` is not Serializable.
71#[derive(PartialEq, Eq, Clone, Debug)]
72pub enum DebugRequestDisposition {
73 /// Debug session configured by the user.
74 UserConfigured(DebugRequestType),
75 /// Debug session configured by the debug adapter
76 ReverseRequest(StartDebuggingRequestArguments),
77}
78
79impl DebugRequestDisposition {
80 /// Get the current working directory from request if it's a launch request and exits
81 pub fn cwd(&self) -> Option<PathBuf> {
82 match self {
83 Self::UserConfigured(DebugRequestType::Launch(launch_config)) => {
84 launch_config.cwd.clone()
85 }
86 _ => None,
87 }
88 }
89}
90/// Represents the configuration for the debug adapter
91#[derive(PartialEq, Eq, Clone, Debug)]
92pub struct DebugAdapterConfig {
93 /// Name of the debug task
94 pub label: String,
95 /// The type of adapter you want to use
96 pub adapter: String,
97 /// The type of request that should be called on the debug adapter
98 pub request: DebugRequestDisposition,
99 /// Additional initialization arguments to be sent on DAP initialization
100 pub initialize_args: Option<serde_json::Value>,
101 /// Optional TCP connection information
102 ///
103 /// If provided, this will be used to connect to the debug adapter instead of
104 /// spawning a new process. This is useful for connecting to a debug adapter
105 /// that is already running or is started by another process.
106 pub tcp_connection: Option<TCPHost>,
107 /// What Locator to use to configure the debug task
108 pub locator: Option<String>,
109 /// Whether to tell the debug adapter to stop on entry
110 pub stop_on_entry: Option<bool>,
111}
112
113impl From<DebugTaskDefinition> for DebugAdapterConfig {
114 fn from(def: DebugTaskDefinition) -> Self {
115 Self {
116 label: def.label,
117 adapter: def.adapter,
118 request: DebugRequestDisposition::UserConfigured(def.request),
119 initialize_args: def.initialize_args,
120 tcp_connection: def.tcp_connection,
121 locator: def.locator,
122 stop_on_entry: def.stop_on_entry,
123 }
124 }
125}
126
127impl TryFrom<DebugAdapterConfig> for DebugTaskDefinition {
128 type Error = ();
129 fn try_from(def: DebugAdapterConfig) -> Result<Self, Self::Error> {
130 let request = match def.request {
131 DebugRequestDisposition::UserConfigured(debug_request_type) => debug_request_type,
132 DebugRequestDisposition::ReverseRequest(_) => return Err(()),
133 };
134
135 Ok(Self {
136 label: def.label,
137 adapter: def.adapter,
138 request,
139 initialize_args: def.initialize_args,
140 tcp_connection: def.tcp_connection,
141 locator: def.locator,
142 stop_on_entry: def.stop_on_entry,
143 })
144 }
145}
146
147impl DebugTaskDefinition {
148 /// Translate from debug definition to a task template
149 pub fn to_zed_format(self) -> anyhow::Result<TaskTemplate> {
150 let (command, cwd, request) = match self.request {
151 DebugRequestType::Launch(launch_config) => (
152 launch_config.program,
153 launch_config
154 .cwd
155 .map(|cwd| cwd.to_string_lossy().to_string()),
156 crate::task_template::DebugArgsRequest::Launch,
157 ),
158 DebugRequestType::Attach(attach_config) => (
159 "".to_owned(),
160 None,
161 crate::task_template::DebugArgsRequest::Attach(attach_config),
162 ),
163 };
164
165 let task_type = TaskType::Debug(DebugArgs {
166 adapter: self.adapter,
167 request,
168 initialize_args: self.initialize_args,
169 locator: self.locator,
170 tcp_connection: self.tcp_connection,
171 stop_on_entry: self.stop_on_entry,
172 });
173
174 let label = self.label.clone();
175
176 Ok(TaskTemplate {
177 label,
178 command,
179 args: vec![],
180 task_type,
181 cwd,
182 ..Default::default()
183 })
184 }
185}
186/// Represents the type of the debugger adapter connection
187#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
188#[serde(rename_all = "lowercase", tag = "connection")]
189pub enum DebugConnectionType {
190 /// Connect to the debug adapter via TCP
191 TCP(TCPHost),
192 /// Connect to the debug adapter via STDIO
193 STDIO,
194}
195
196/// This struct represent a user created debug task
197#[derive(Deserialize, Serialize, PartialEq, Eq, JsonSchema, Clone, Debug)]
198#[serde(rename_all = "snake_case")]
199pub struct DebugTaskDefinition {
200 /// The adapter to run
201 pub adapter: String,
202 /// The type of request that should be called on the debug adapter
203 #[serde(flatten)]
204 pub request: DebugRequestType,
205 /// Name of the debug task
206 pub label: String,
207 /// Additional initialization arguments to be sent on DAP initialization
208 pub initialize_args: Option<serde_json::Value>,
209 /// Optional TCP connection information
210 ///
211 /// If provided, this will be used to connect to the debug adapter instead of
212 /// spawning a new process. This is useful for connecting to a debug adapter
213 /// that is already running or is started by another process.
214 pub tcp_connection: Option<TCPHost>,
215 /// Locator to use
216 /// -- cargo
217 pub locator: Option<String>,
218 /// Whether to tell the debug adapter to stop on entry
219 pub stop_on_entry: Option<bool>,
220}
221
222/// A group of Debug Tasks defined in a JSON file.
223#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
224#[serde(transparent)]
225pub struct DebugTaskFile(pub Vec<DebugTaskDefinition>);
226
227impl DebugTaskFile {
228 /// Generates JSON schema of Tasks JSON template format.
229 pub fn generate_json_schema() -> serde_json_lenient::Value {
230 let schema = SchemaSettings::draft07()
231 .with(|settings| settings.option_add_null_type = false)
232 .into_generator()
233 .into_root_schema_for::<Self>();
234
235 serde_json_lenient::to_value(schema).unwrap()
236 }
237}
238
239impl TryFrom<DebugTaskFile> for TaskTemplates {
240 type Error = anyhow::Error;
241
242 fn try_from(value: DebugTaskFile) -> Result<Self, Self::Error> {
243 let templates = value
244 .0
245 .into_iter()
246 .filter_map(|debug_definition| debug_definition.to_zed_format().log_err())
247 .collect();
248
249 Ok(Self(templates))
250 }
251}