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 #[serde(default)]
80 initial_breakpoints: Vec<String>,
81 },
82
83 Attach {
85 pid: u32,
86 adapter: Option<String>,
87 },
88
89 Detach,
91
92 Stop,
94
95 Restart,
97
98 Status,
100
101 BreakpointAdd {
104 location: BreakpointLocation,
105 condition: Option<String>,
106 hit_count: Option<u32>,
107 },
108
109 BreakpointRemove {
111 id: Option<u32>,
112 all: bool,
113 },
114
115 BreakpointList,
117
118 BreakpointEnable { id: u32 },
120
121 BreakpointDisable { id: u32 },
123
124 Continue,
127
128 Next,
130
131 StepIn,
133
134 StepOut,
136
137 Pause,
139
140 StackTrace {
143 thread_id: Option<i64>,
144 limit: usize,
145 },
146
147 Locals { frame_id: Option<i64> },
149
150 Evaluate {
152 expression: String,
153 frame_id: Option<i64>,
154 context: EvaluateContext,
155 },
156
157 Scopes { frame_id: i64 },
159
160 Variables { reference: i64 },
162
163 Threads,
166
167 ThreadSelect { id: i64 },
169
170 FrameSelect { number: usize },
172
173 FrameUp,
175
176 FrameDown,
178
179 Context { lines: usize },
182
183 Await { timeout_secs: u64 },
186
187 GetOutput {
190 tail: Option<usize>,
191 clear: bool,
192 },
193
194 SubscribeOutput,
196
197 Shutdown,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
204#[serde(tag = "type", rename_all = "snake_case")]
205pub enum BreakpointLocation {
206 Line { file: PathBuf, line: u32 },
208 Function { name: String },
210}
211
212impl BreakpointLocation {
213 pub fn parse(s: &str) -> Result<Self, crate::common::Error> {
215 if let Some(colon_idx) = s.rfind(':') {
218 let (file_part, line_part) = s.split_at(colon_idx);
219 let line_str = &line_part[1..]; if !line_str.is_empty() && line_str.chars().all(|c| c.is_ascii_digit()) {
223 let line: u32 = line_str.parse().map_err(|_| {
224 crate::common::Error::InvalidLocation(format!(
225 "invalid line number: {}",
226 line_str
227 ))
228 })?;
229 return Ok(Self::Line {
230 file: PathBuf::from(file_part),
231 line,
232 });
233 }
234 }
235
236 Ok(Self::Function {
238 name: s.to_string(),
239 })
240 }
241}
242
243impl std::fmt::Display for BreakpointLocation {
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 match self {
246 Self::Line { file, line } => write!(f, "{}:{}", file.display(), line),
247 Self::Function { name } => write!(f, "{}", name),
248 }
249 }
250}
251
252#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
254#[serde(rename_all = "snake_case")]
255pub enum EvaluateContext {
256 #[default]
258 Watch,
259 Repl,
261 Hover,
263}
264
265#[derive(Debug, Serialize, Deserialize)]
269pub struct StatusResult {
270 pub daemon_running: bool,
271 pub session_active: bool,
272 pub state: Option<String>,
273 pub program: Option<String>,
274 pub adapter: Option<String>,
275 pub stopped_thread: Option<i64>,
276 pub stopped_reason: Option<String>,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
281pub struct BreakpointInfo {
282 pub id: u32,
283 pub verified: bool,
284 pub source: Option<String>,
285 pub line: Option<u32>,
286 pub message: Option<String>,
287 pub enabled: bool,
288 pub condition: Option<String>,
289 pub hit_count: Option<u32>,
290}
291
292#[derive(Debug, Serialize, Deserialize)]
294pub struct StackFrameInfo {
295 pub id: i64,
296 pub name: String,
297 pub source: Option<String>,
298 pub line: Option<u32>,
299 pub column: Option<u32>,
300}
301
302#[derive(Debug, Serialize, Deserialize)]
304pub struct ThreadInfo {
305 pub id: i64,
306 pub name: String,
307 pub state: Option<String>,
308}
309
310#[derive(Debug, Serialize, Deserialize)]
312pub struct VariableInfo {
313 pub name: String,
314 pub value: String,
315 pub type_name: Option<String>,
316 pub variables_reference: i64,
317}
318
319#[derive(Debug, Serialize, Deserialize)]
321pub struct StopResult {
322 pub reason: String,
323 pub description: Option<String>,
324 #[serde(default)]
325 pub thread_id: Option<i64>,
326 #[serde(default)]
327 pub all_threads_stopped: bool,
328 #[serde(default)]
329 pub hit_breakpoint_ids: Vec<u32>,
330 pub source: Option<String>,
332 pub line: Option<u32>,
333 pub column: Option<u32>,
334}
335
336#[derive(Debug, Serialize, Deserialize)]
338pub struct EvaluateResult {
339 pub result: String,
340 pub type_name: Option<String>,
341 pub variables_reference: i64,
342}
343
344#[derive(Debug, Serialize, Deserialize)]
346pub struct ContextResult {
347 pub thread_id: i64,
348 pub source: Option<String>,
349 pub line: u32,
350 pub column: Option<u32>,
351 pub function: Option<String>,
352 pub source_lines: Vec<SourceLine>,
354 pub locals: Vec<VariableInfo>,
356}
357
358#[derive(Debug, Serialize, Deserialize)]
360pub struct SourceLine {
361 pub number: u32,
362 pub content: String,
363 pub is_current: bool,
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 #[test]
371 fn test_parse_file_line() {
372 let loc = BreakpointLocation::parse("src/main.rs:42").unwrap();
373 match loc {
374 BreakpointLocation::Line { file, line } => {
375 assert_eq!(file, PathBuf::from("src/main.rs"));
376 assert_eq!(line, 42);
377 }
378 _ => panic!("Expected Line variant"),
379 }
380 }
381
382 #[test]
383 fn test_parse_function() {
384 let loc = BreakpointLocation::parse("main").unwrap();
385 match loc {
386 BreakpointLocation::Function { name } => {
387 assert_eq!(name, "main");
388 }
389 _ => panic!("Expected Function variant"),
390 }
391 }
392
393 #[test]
394 fn test_parse_namespaced_function() {
395 let loc = BreakpointLocation::parse("mymod::MyStruct::method").unwrap();
396 match loc {
397 BreakpointLocation::Function { name } => {
398 assert_eq!(name, "mymod::MyStruct::method");
399 }
400 _ => panic!("Expected Function variant"),
401 }
402 }
403
404 #[cfg(windows)]
405 #[test]
406 fn test_parse_windows_path() {
407 let loc = BreakpointLocation::parse(r"C:\Users\test\src\main.rs:42").unwrap();
408 match loc {
409 BreakpointLocation::Line { file, line } => {
410 assert_eq!(file, PathBuf::from(r"C:\Users\test\src\main.rs"));
411 assert_eq!(line, 42);
412 }
413 _ => panic!("Expected Line variant"),
414 }
415 }
416}