1use serde::{Deserialize, Serialize};
7use std::path::PathBuf;
8
9use crate::common::error::IpcError;
10
11#[derive(Debug, Serialize, Deserialize)]
13pub struct Request {
14 pub id: u64,
16 pub command: Command,
18}
19
20#[derive(Debug, Serialize, Deserialize)]
22pub struct Response {
23 pub id: u64,
25 pub success: bool,
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub result: Option<serde_json::Value>,
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub error: Option<IpcError>,
33}
34
35impl Response {
36 pub fn success(id: u64, result: serde_json::Value) -> Self {
38 Self {
39 id,
40 success: true,
41 result: Some(result),
42 error: None,
43 }
44 }
45
46 pub fn error(id: u64, error: IpcError) -> Self {
48 Self {
49 id,
50 success: false,
51 result: None,
52 error: Some(error),
53 }
54 }
55
56 pub fn ok(id: u64) -> Self {
58 Self {
59 id,
60 success: true,
61 result: Some(serde_json::json!({})),
62 error: None,
63 }
64 }
65}
66
67#[derive(Debug, Serialize, Deserialize)]
69#[serde(tag = "type", rename_all = "snake_case")]
70pub enum Command {
71 Start {
74 program: PathBuf,
75 args: Vec<String>,
76 adapter: Option<String>,
77 stop_on_entry: bool,
78 },
79
80 Attach {
82 pid: u32,
83 adapter: Option<String>,
84 },
85
86 Detach,
88
89 Stop,
91
92 Restart,
94
95 Status,
97
98 BreakpointAdd {
101 location: BreakpointLocation,
102 condition: Option<String>,
103 hit_count: Option<u32>,
104 },
105
106 BreakpointRemove {
108 id: Option<u32>,
109 all: bool,
110 },
111
112 BreakpointList,
114
115 BreakpointEnable { id: u32 },
117
118 BreakpointDisable { id: u32 },
120
121 Continue,
124
125 Next,
127
128 StepIn,
130
131 StepOut,
133
134 Pause,
136
137 StackTrace {
140 thread_id: Option<i64>,
141 limit: usize,
142 },
143
144 Locals { frame_id: Option<i64> },
146
147 Evaluate {
149 expression: String,
150 frame_id: Option<i64>,
151 context: EvaluateContext,
152 },
153
154 Scopes { frame_id: i64 },
156
157 Variables { reference: i64 },
159
160 Threads,
163
164 ThreadSelect { id: i64 },
166
167 FrameSelect { number: usize },
169
170 FrameUp,
172
173 FrameDown,
175
176 Context { lines: usize },
179
180 Await { timeout_secs: u64 },
183
184 GetOutput {
187 tail: Option<usize>,
188 clear: bool,
189 },
190
191 SubscribeOutput,
193
194 Shutdown,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201#[serde(tag = "type", rename_all = "snake_case")]
202pub enum BreakpointLocation {
203 Line { file: PathBuf, line: u32 },
205 Function { name: String },
207}
208
209impl BreakpointLocation {
210 pub fn parse(s: &str) -> Result<Self, crate::common::Error> {
212 if let Some(colon_idx) = s.rfind(':') {
215 let (file_part, line_part) = s.split_at(colon_idx);
216 let line_str = &line_part[1..]; if !line_str.is_empty() && line_str.chars().all(|c| c.is_ascii_digit()) {
220 let line: u32 = line_str.parse().map_err(|_| {
221 crate::common::Error::InvalidLocation(format!(
222 "invalid line number: {}",
223 line_str
224 ))
225 })?;
226 return Ok(Self::Line {
227 file: PathBuf::from(file_part),
228 line,
229 });
230 }
231 }
232
233 Ok(Self::Function {
235 name: s.to_string(),
236 })
237 }
238}
239
240impl std::fmt::Display for BreakpointLocation {
241 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242 match self {
243 Self::Line { file, line } => write!(f, "{}:{}", file.display(), line),
244 Self::Function { name } => write!(f, "{}", name),
245 }
246 }
247}
248
249#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
251#[serde(rename_all = "snake_case")]
252pub enum EvaluateContext {
253 #[default]
255 Watch,
256 Repl,
258 Hover,
260}
261
262#[derive(Debug, Serialize, Deserialize)]
266pub struct StatusResult {
267 pub daemon_running: bool,
268 pub session_active: bool,
269 pub state: Option<String>,
270 pub program: Option<String>,
271 pub adapter: Option<String>,
272 pub stopped_thread: Option<i64>,
273 pub stopped_reason: Option<String>,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct BreakpointInfo {
279 pub id: u32,
280 pub verified: bool,
281 pub source: Option<String>,
282 pub line: Option<u32>,
283 pub message: Option<String>,
284 pub enabled: bool,
285 pub condition: Option<String>,
286 pub hit_count: Option<u32>,
287}
288
289#[derive(Debug, Serialize, Deserialize)]
291pub struct StackFrameInfo {
292 pub id: i64,
293 pub name: String,
294 pub source: Option<String>,
295 pub line: Option<u32>,
296 pub column: Option<u32>,
297}
298
299#[derive(Debug, Serialize, Deserialize)]
301pub struct ThreadInfo {
302 pub id: i64,
303 pub name: String,
304 pub state: Option<String>,
305}
306
307#[derive(Debug, Serialize, Deserialize)]
309pub struct VariableInfo {
310 pub name: String,
311 pub value: String,
312 pub type_name: Option<String>,
313 pub variables_reference: i64,
314}
315
316#[derive(Debug, Serialize, Deserialize)]
318pub struct StopResult {
319 pub reason: String,
320 pub description: Option<String>,
321 pub thread_id: i64,
322 pub all_threads_stopped: bool,
323 pub hit_breakpoint_ids: Vec<u32>,
324 pub source: Option<String>,
326 pub line: Option<u32>,
327 pub column: Option<u32>,
328}
329
330#[derive(Debug, Serialize, Deserialize)]
332pub struct EvaluateResult {
333 pub result: String,
334 pub type_name: Option<String>,
335 pub variables_reference: i64,
336}
337
338#[derive(Debug, Serialize, Deserialize)]
340pub struct ContextResult {
341 pub thread_id: i64,
342 pub source: Option<String>,
343 pub line: u32,
344 pub column: Option<u32>,
345 pub function: Option<String>,
346 pub source_lines: Vec<SourceLine>,
348 pub locals: Vec<VariableInfo>,
350}
351
352#[derive(Debug, Serialize, Deserialize)]
354pub struct SourceLine {
355 pub number: u32,
356 pub content: String,
357 pub is_current: bool,
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 #[test]
365 fn test_parse_file_line() {
366 let loc = BreakpointLocation::parse("src/main.rs:42").unwrap();
367 match loc {
368 BreakpointLocation::Line { file, line } => {
369 assert_eq!(file, PathBuf::from("src/main.rs"));
370 assert_eq!(line, 42);
371 }
372 _ => panic!("Expected Line variant"),
373 }
374 }
375
376 #[test]
377 fn test_parse_function() {
378 let loc = BreakpointLocation::parse("main").unwrap();
379 match loc {
380 BreakpointLocation::Function { name } => {
381 assert_eq!(name, "main");
382 }
383 _ => panic!("Expected Function variant"),
384 }
385 }
386
387 #[test]
388 fn test_parse_namespaced_function() {
389 let loc = BreakpointLocation::parse("mymod::MyStruct::method").unwrap();
390 match loc {
391 BreakpointLocation::Function { name } => {
392 assert_eq!(name, "mymod::MyStruct::method");
393 }
394 _ => panic!("Expected Function variant"),
395 }
396 }
397
398 #[cfg(windows)]
399 #[test]
400 fn test_parse_windows_path() {
401 let loc = BreakpointLocation::parse(r"C:\Users\test\src\main.rs:42").unwrap();
402 match loc {
403 BreakpointLocation::Line { file, line } => {
404 assert_eq!(file, PathBuf::from(r"C:\Users\test\src\main.rs"));
405 assert_eq!(line, 42);
406 }
407 _ => panic!("Expected Line variant"),
408 }
409 }
410}