1#![allow(dead_code)]
25#![allow(unused_imports)]
26
27use ipc_channel::ipc;
28use nix::unistd::{ForkResult, fork};
29use serde::{Deserialize, Serialize};
30use std::collections::{BTreeMap, VecDeque};
31use std::process;
32use std::sync::{Arc, Mutex, OnceLock, Once};
33
34
35
36#[cfg(test)]
37mod tests;
38#[cfg(not(unix))]
39compile_error!("This crate is only supported on Unix-like systems (Linux, macOS, etc.)");
40
41
42#[derive(Serialize, Deserialize, Debug, Clone)]
43struct DebugMessage {
44 timestamp: u64,
45 file: String,
46 line: u32,
47 column: u32,
48 variables: BTreeMap<String, VariableDebugFrame>,
49 backtrace: String,
50}
51
52#[derive(Serialize, Deserialize, Debug, Clone)]
53struct DebugResponse {
54 continue_execution: bool,
55}
56
57#[derive(Debug, Clone)]
63struct DebugFrameHistory {
64 frames: VecDeque<DebugMessage>,
65 current_index: usize,
66}
67
68impl DebugFrameHistory {
69 fn new() -> Self {
70 Self {
71 frames: VecDeque::new(),
72 current_index: 0,
73 }
74 }
75
76 fn add_frame(&mut self, frame: DebugMessage) {
77 self.frames.push_back(frame);
78 self.current_index = self.frames.len().saturating_sub(1);
79 }
80
81 fn go_backward(&mut self) -> bool {
82 if self.current_index > 0 {
83 self.current_index -= 1;
84 true
85 } else {
86 false
87 }
88 }
89
90 fn go_forward(&mut self) -> bool {
91 if self.current_index + 1 < self.frames.len() {
92 self.current_index += 1;
93 true
94 } else {
95 false
96 }
97 }
98
99 fn get_current_frame(&self) -> Option<&DebugMessage> {
100 self.frames.get(self.current_index)
101 }
102
103 fn can_go_backward(&self) -> bool {
104 self.current_index > 0
105 }
106
107 fn can_go_forward(&self) -> bool {
108 self.current_index + 1 < self.frames.len()
109 }
110
111 fn get_position_info(&self) -> (usize, usize) {
112 (self.current_index + 1, self.frames.len())
113 }
114}
115
116static DEBUG_SENDER: OnceLock<Arc<Mutex<ipc::IpcSender<DebugMessage>>>> = OnceLock::new();
118static RESPONSE_RECEIVER: OnceLock<Arc<Mutex<ipc::IpcReceiver<DebugResponse>>>> = OnceLock::new();
119
120#[cfg(debug_assertions)]
122fn init() {
123 let (debug_tx, debug_rx) = match ipc::channel() {
124 Ok(channel) => channel,
125 Err(e) => {
126 panic!("Failed to create debug IPC channel: {}", e);
127 }
128 };
129
130 let (response_tx, response_rx) = match ipc::channel() {
131 Ok(channel) => channel,
132 Err(e) => {
133 panic!("Failed to create response IPC channel: {}", e);
134 }
135 };
136
137 match unsafe { fork() } {
138 Ok(ForkResult::Parent { child: _ }) => {
139 if DEBUG_SENDER.set(Arc::new(Mutex::new(debug_tx))).is_err() {
140 panic!("Failed to set debug sender");
141 }
142
143 if RESPONSE_RECEIVER
144 .set(Arc::new(Mutex::new(response_rx)))
145 .is_err()
146 {
147 panic!("Failed to set response receiver");
148 }
149 }
150 Ok(ForkResult::Child) => {
151 debug_server_process(debug_rx, response_tx);
152 unreachable!()
153 }
154 Err(e) => {
155 panic!("Fork failed: {}", e);
156 }
157 }
158}
159
160pub fn send_to_debug_server_and_wait(
161 variables: BTreeMap<String, VariableDebugFrame>,
162 file: &str,
163 line: u32,
164 column: u32,
165 backtrace: String,
166) {
167 if let (Some(sender), Some(receiver)) = (DEBUG_SENDER.get(), RESPONSE_RECEIVER.get()) {
168 if let (Ok(sender), Ok(receiver)) = (sender.lock(), receiver.lock()) {
169 let message = DebugMessage {
170 timestamp: std::time::SystemTime::now()
171 .duration_since(std::time::UNIX_EPOCH)
172 .unwrap_or_default()
173 .as_millis() as u64,
174 file: file.to_string(),
175 line,
176 column,
177 variables,
178 backtrace,
179 };
180
181 if let Err(e) = sender.send(message) {
182 eprintln!("Failed to send debug message: {}", e);
183 return;
184 }
185
186 match receiver.recv() {
188 Ok(_response) => {
189 }
191 Err(e) => {
192 eprintln!("Failed to receive response from debug server: {}", e);
193 }
194 }
195 }
196 }
197}
198
199fn debug_server_process(
201 rx: ipc::IpcReceiver<DebugMessage>,
202 response_tx: ipc::IpcSender<DebugResponse>,
203) {
204 let mut frame_history = DebugFrameHistory::new();
205
206 loop {
207 match rx.recv() {
208 Ok(message) => {
209 frame_history.add_frame(message);
210 show_debug_gui_with_history(&mut frame_history);
211
212 let response = DebugResponse {
214 continue_execution: true,
215 };
216
217 if let Err(e) = response_tx.send(response) {
218 eprintln!("Failed to send response: {}", e);
219 break;
220 }
221 }
222 Err(e) => {
223 eprintln!("Debug server: Channel error: {}", e);
224 break;
225 }
226 }
227 }
228 process::exit(0);
229}
230
231fn create_json_from_variables(variables: &BTreeMap<String, VariableDebugFrame>) -> String {
232 let mut json_parts = Vec::new();
233
234 for (name, var_frame) in variables {
235 let escaped_value = var_frame.value
236 .replace('\\', "\\\\")
237 .replace('"', "\\\"")
238 .replace('\n', "\\n")
239 .replace('\r', "\\r")
240 .replace('\t', "\\t");
241
242 let escaped_type = var_frame.type_name
244 .replace('\\', "\\\\")
245 .replace('"', "\\\"");
246
247 json_parts.push(format!(" \"{}\": \"{}\"", name, escaped_value));
248 json_parts.push(format!(" \"{}_type\": \"{}\"", name, escaped_type));
249
250 let size_value = if let Some(size) = var_frame.size_hint {
251 size.to_string()
252 } else {
253 "unknown".to_string()
254 };
255
256 json_parts.push(format!(" \"{}_size\": \"{}\"", name, size_value));
258 }
259
260 format!("{{\n{}\n}}", json_parts.join(",\n"))
261}
262
263fn copy_to_clipboard(text: &str) -> Result<(), Box<dyn std::error::Error>> {
265 use arboard::Clipboard;
266 let mut clipboard = Clipboard::new()?;
267 clipboard.set_text(text)?;
268 Ok(())
269}
270
271fn show_debug_gui_with_history(frame_history: &mut DebugFrameHistory) {
273 use eframe::egui;
274 use std::sync::{Arc, Mutex};
275
276 if let Some(current_message) = frame_history.get_current_frame() {
277 let filename = std::path::Path::new(¤t_message.file)
278 .file_name()
279 .and_then(|name| name.to_str())
280 .unwrap_or(¤t_message.file);
281
282 let window_title = format!("[Mazer Debug] - {}:{}", filename, current_message.line);
283
284 let options = eframe::NativeOptions {
285 viewport: egui::ViewportBuilder::default()
286 .with_inner_size([900.0, 700.0])
287 .with_title(&window_title)
288 .with_resizable(true),
289 ..Default::default()
290 };
291
292 let frame_history_arc = Arc::new(Mutex::new(frame_history.clone()));
293 let frame_history_gui = frame_history_arc.clone();
294 let show_backtrace = Arc::new(Mutex::new(false));
295 let show_backtrace_gui = show_backtrace.clone();
296
297 let _ = eframe::run_simple_native(&window_title, options, move |ctx, _frame| {
298 let mut style = (*ctx.style()).clone();
299 style.text_styles = [
300 (egui::TextStyle::Heading, egui::FontId::proportional(20.0)),
301 (egui::TextStyle::Body, egui::FontId::proportional(24.0)),
302 (egui::TextStyle::Monospace, egui::FontId::monospace(22.0)),
303 (egui::TextStyle::Button, egui::FontId::proportional(24.0)),
304 (egui::TextStyle::Small, egui::FontId::proportional(18.0)),
305 ]
306 .into();
307 style.wrap_mode = Some(egui::TextWrapMode::Wrap);
308 if style.visuals.dark_mode {
309 style.visuals.override_text_color = Some(egui::Color32::WHITE);
310 }
311 ctx.set_style(style.clone());
312
313 egui::CentralPanel::default().show(ctx, |ui| {
314 let mut frame_history_guard = frame_history_gui.lock().unwrap();
315
316 egui::TopBottomPanel::top("top_panel").show_inside(ui, |ui| {
318 ui.horizontal(|ui| {
319 ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| {
321 let can_go_back = frame_history_guard.can_go_backward();
322 let can_go_forward = frame_history_guard.can_go_forward();
323
324 ui.add_enabled_ui(can_go_back, |ui| {
325 if ui.button("⬅ Previous").clicked() {
326 frame_history_guard.go_backward();
327 }
328 });
329
330 ui.add_enabled_ui(can_go_forward, |ui| {
331 if ui.button("Next ➡").clicked() {
332 frame_history_guard.go_forward();
333 }
334 });
335
336 let (current_pos, total_frames) = frame_history_guard.get_position_info();
337 ui.label(format!("Frame {}/{}", current_pos, total_frames));
338 });
339
340 ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
342 if let Some(current_frame) = frame_history_guard.get_current_frame() {
343 if ui.button("📋").clicked() {
344 let json_string = create_json_from_variables(¤t_frame.variables);
345 if let Err(e) = copy_to_clipboard(&json_string) {
346 eprintln!("Failed to copy to clipboard: {}", e);
347 }
348 }
349
350 if ui.button("📋 Backtrace").clicked() {
351 *show_backtrace_gui.lock().unwrap() = true;
352 }
353 }
354 });
355 });
356 });
357
358 egui::CentralPanel::default().show_inside(ui, |ui| {
360 if let Some(current_frame) = frame_history_guard.get_current_frame() {
361 ui.horizontal(|ui| {
363 ui.strong("File:");
364 ui.label(¤t_frame.file);
365 ui.strong("Line:");
366 ui.label(current_frame.line.to_string());
367 ui.strong("Column:");
368 ui.label(current_frame.column.to_string());
369 });
370 ui.separator();
371
372 egui::ScrollArea::vertical()
373 .max_width(f32::INFINITY)
374 .auto_shrink([false; 2])
375 .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysVisible)
376 .show(ui, |ui| {
377 ui.allocate_ui_with_layout(
378 ui.available_size(),
379 egui::Layout::left_to_right(egui::Align::Min),
380 |ui| {
381 egui::Grid::new("debug_table")
382 .num_columns(2)
383 .spacing([40.0, 4.0])
384 .striped(true)
385 .min_col_width(ui.available_width() / 2.0)
386 .show(ui, |ui| {
387 ui.strong("Name (Type, Size)");
388 ui.strong("Value");
389 ui.end_row();
390
391 use egui_extras::syntax_highlighting::{
393 CodeTheme, code_view_ui,
394 };
395 let theme = CodeTheme::from_style(&style);
396 for (name, var_frame) in ¤t_frame.variables {
397 let size_info = if let Some(size) = var_frame.size_hint {
399 format!("{} bytes", size)
400 } else {
401 "unknown".to_string()
402 };
403
404 let name_with_info = format!("{}\n\n\t{}\n\t{}\n",
405 name,
406 size_info,
407 var_frame.type_name,
408 );
409
410 ui.label(name_with_info);
411 code_view_ui(ui, &theme, &var_frame.value, "rs");
412 ui.end_row();
413 }
414 });
415 },
416 );
417 });
418 }
419 });
420 });
421
422 let mut show_backtrace_state = show_backtrace_gui.lock().unwrap();
424 if *show_backtrace_state {
425 let mut open = true;
426 egui::Window::new("🔍 Call Stack / Backtrace")
427 .open(&mut open)
428 .default_width(800.0)
429 .default_height(600.0)
430 .resizable(true)
431 .collapsible(false)
432 .show(ctx, |ui| {
433 let frame_history_guard = frame_history_gui.lock().unwrap();
434 if let Some(current_frame) = frame_history_guard.get_current_frame() {
435 ui.horizontal(|ui| {
436 ui.strong("Backtrace for:");
437 ui.label(format!("{}:{}", current_frame.file, current_frame.line));
438 ui.separator();
439 if ui.button("📋 Copy").clicked() {
440 if let Err(e) = copy_to_clipboard(¤t_frame.backtrace) {
441 eprintln!("Failed to copy backtrace to clipboard: {}", e);
442 }
443 }
444 });
445 ui.separator();
446
447 egui::ScrollArea::vertical()
448 .max_width(f32::INFINITY)
449 .auto_shrink([false; 2])
450 .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysVisible)
451 .show(ui, |ui| {
452 let backtrace_lines: Vec<&str> = current_frame.backtrace.lines().collect();
454
455 for line in backtrace_lines.iter() {
456 let line = line.trim();
457 if line.is_empty() {
458 continue;
459 }
460
461 ui.horizontal(|ui| {
462 if line.contains("::") && (line.contains(".rs:") || line.contains("src/")) {
464 ui.colored_label(egui::Color32::LIGHT_BLUE, line);
466 } else if line.starts_with("at ") {
467 ui.colored_label(egui::Color32::LIGHT_GREEN, line);
469 } else if line.contains("libstd") || line.contains("libcore") || line.contains("liballoc") {
470 ui.colored_label(egui::Color32::GRAY, line);
472 } else {
473 ui.label(line);
475 }
476 });
477 }
478
479 if backtrace_lines.is_empty() || backtrace_lines.len() < 2 {
481 ui.colored_label(egui::Color32::YELLOW, "⚠ Backtrace information not available or limited");
482 ui.label("Try enabling debug symbols or running in debug mode for more detailed backtraces.");
483 }
484 });
485 }
486 });
487
488 if !open {
489 *show_backtrace_state = false;
490 }
491 }
492 });
493
494 if let Ok(updated_history) = frame_history_arc.lock() {
496 *frame_history = updated_history.clone();
497 }
498 }
499}
500
501#[cfg(debug_assertions)]
502pub fn ensure_init() {
503 START.call_once(|| {
504 init();
505 });
506}
507
508
509static START: Once = Once::new();
510
511#[derive(Serialize, Deserialize, Debug, Clone)]
512pub struct VariableDebugFrame {
513 pub name: String,
514 pub value: String,
515 pub type_name: String,
516 pub size_hint: Option<usize>,
517}
518
519#[macro_export]
520macro_rules! inspect {
521 ( $( $var:expr ),+ $(,)? ) => {{
522 #[cfg(debug_assertions)]
523 {
524 $crate::ensure_init();
525 use std::collections::BTreeMap;
526 let mut map = BTreeMap::new();
527 $(
528 let var_name = stringify!($var).to_string();
529
530 let type_name = std::any::type_name_of_val(&$var).to_string();
531 let size_hint = std::mem::size_of_val(&$var);
532
533 let vframe = $crate::VariableDebugFrame {
534 name: var_name.clone(),
535 value: format!("{:#?}", $var),
536 type_name: type_name.clone(),
537 size_hint: Some(size_hint),
538 };
539
540 map.insert(var_name, vframe);
541 )+
542
543 let bt = std::backtrace::Backtrace::force_capture();
544
545 $crate::send_to_debug_server_and_wait(
547 map.clone(),
548 file!(),
549 line!(),
550 column!(),
551 format!("{}", bt)
552 );
553
554 map
555 }
556 }};
557}
558
559#[macro_export]
560macro_rules! inspect_when {
564 ($condition:expr, $( $var:expr ),+ $(,)? ) => {{
565 #[cfg(debug_assertions)]
566 {
567 if $condition {
568 $crate::inspect!($($var),+);
569 }
570 }
571 }};
572}