1pub mod spawn;
6
7use crate::commands::{BreakpointCommands, Commands};
8use crate::common::{Error, Result};
9use crate::ipc::protocol::{
10 BreakpointInfo, BreakpointLocation, Command, ContextResult, EvaluateContext, EvaluateResult,
11 StackFrameInfo, StatusResult, StopResult, ThreadInfo, VariableInfo,
12};
13use crate::ipc::DaemonClient;
14use crate::setup;
15use crate::testing;
16
17pub async fn dispatch(command: Commands) -> Result<()> {
19 match command {
20 Commands::Daemon => {
21 unreachable!("Daemon command should be handled in main")
23 }
24
25 Commands::Start {
26 program,
27 args,
28 adapter,
29 stop_on_entry,
30 } => {
31 spawn::ensure_daemon_running().await?;
32 let mut client = DaemonClient::connect().await?;
33
34 let program = program.canonicalize().unwrap_or(program);
35
36 let result = client
37 .send_command(Command::Start {
38 program: program.clone(),
39 args,
40 adapter,
41 stop_on_entry,
42 })
43 .await?;
44
45 println!("Started debugging: {}", program.display());
46
47 if stop_on_entry {
48 println!("Stopped at entry point. Use 'debugger continue' to run.");
49 } else {
50 println!("Program is running. Use 'debugger await' to wait for a stop.");
51 }
52
53 Ok(())
54 }
55
56 Commands::Attach { pid, adapter } => {
57 spawn::ensure_daemon_running().await?;
58 let mut client = DaemonClient::connect().await?;
59
60 client.send_command(Command::Attach { pid, adapter }).await?;
61
62 println!("Attached to process {}", pid);
63 println!("Program is stopped. Use 'debugger continue' to run.");
64
65 Ok(())
66 }
67
68 Commands::Breakpoint(bp_cmd) => match bp_cmd {
69 BreakpointCommands::Add {
70 location,
71 condition,
72 hit_count,
73 } => {
74 let mut client = DaemonClient::connect().await?;
75 let loc = BreakpointLocation::parse(&location)?;
76
77 let result = client
78 .send_command(Command::BreakpointAdd {
79 location: loc,
80 condition,
81 hit_count,
82 })
83 .await?;
84
85 let info: BreakpointInfo = serde_json::from_value(result)?;
86 print_breakpoint_added(&info);
87
88 Ok(())
89 }
90
91 BreakpointCommands::Remove { id, all } => {
92 let mut client = DaemonClient::connect().await?;
93
94 client
95 .send_command(Command::BreakpointRemove { id, all })
96 .await?;
97
98 if all {
99 println!("All breakpoints removed");
100 } else if let Some(id) = id {
101 println!("Breakpoint {} removed", id);
102 }
103
104 Ok(())
105 }
106
107 BreakpointCommands::List => {
108 let mut client = DaemonClient::connect().await?;
109
110 let result = client.send_command(Command::BreakpointList).await?;
111 let breakpoints: Vec<BreakpointInfo> =
112 serde_json::from_value(result["breakpoints"].clone())?;
113
114 if breakpoints.is_empty() {
115 println!("No breakpoints set");
116 } else {
117 println!("Breakpoints:");
118 for bp in &breakpoints {
119 print_breakpoint(bp);
120 }
121 }
122
123 Ok(())
124 }
125
126 BreakpointCommands::Enable { id } => {
127 let mut client = DaemonClient::connect().await?;
128 client
129 .send_command(Command::BreakpointEnable { id })
130 .await?;
131 println!("Breakpoint {} enabled", id);
132 Ok(())
133 }
134
135 BreakpointCommands::Disable { id } => {
136 let mut client = DaemonClient::connect().await?;
137 client
138 .send_command(Command::BreakpointDisable { id })
139 .await?;
140 println!("Breakpoint {} disabled", id);
141 Ok(())
142 }
143 },
144
145 Commands::Break { location, condition } => {
146 let mut client = DaemonClient::connect().await?;
148 let loc = BreakpointLocation::parse(&location)?;
149
150 let result = client
151 .send_command(Command::BreakpointAdd {
152 location: loc,
153 condition,
154 hit_count: None,
155 })
156 .await?;
157
158 let info: BreakpointInfo = serde_json::from_value(result)?;
159 print_breakpoint_added(&info);
160
161 Ok(())
162 }
163
164 Commands::Continue => {
165 let mut client = DaemonClient::connect().await?;
166 client.send_command(Command::Continue).await?;
167 println!("Continuing execution...");
168 Ok(())
169 }
170
171 Commands::Next => {
172 let mut client = DaemonClient::connect().await?;
173 client.send_command(Command::Next).await?;
174 println!("Stepping over...");
175 Ok(())
176 }
177
178 Commands::Step => {
179 let mut client = DaemonClient::connect().await?;
180 client.send_command(Command::StepIn).await?;
181 println!("Stepping into...");
182 Ok(())
183 }
184
185 Commands::Finish => {
186 let mut client = DaemonClient::connect().await?;
187 client.send_command(Command::StepOut).await?;
188 println!("Stepping out...");
189 Ok(())
190 }
191
192 Commands::Pause => {
193 let mut client = DaemonClient::connect().await?;
194 client.send_command(Command::Pause).await?;
195 println!("Pausing execution...");
196 Ok(())
197 }
198
199 Commands::Backtrace { limit, locals } => {
200 let mut client = DaemonClient::connect().await?;
201
202 let result = client
203 .send_command(Command::StackTrace {
204 thread_id: None,
205 limit,
206 })
207 .await?;
208
209 let frames: Vec<StackFrameInfo> = serde_json::from_value(result["frames"].clone())?;
210
211 if frames.is_empty() {
212 println!("No stack frames");
213 } else {
214 for (i, frame) in frames.iter().enumerate() {
215 let source = frame.source.as_deref().unwrap_or("?");
216 let line = frame.line.map(|l| l.to_string()).unwrap_or_else(|| "?".to_string());
217 println!("#{} {} at {}:{}", i, frame.name, source, line);
218
219 if locals {
220 let locals_result = client
222 .send_command(Command::Locals {
223 frame_id: Some(frame.id),
224 })
225 .await;
226
227 if let Ok(result) = locals_result {
228 if let Ok(vars) =
229 serde_json::from_value::<Vec<VariableInfo>>(result["variables"].clone())
230 {
231 for var in vars {
232 println!(
233 " {} = {}{}",
234 var.name,
235 var.value,
236 var.type_name
237 .map(|t| format!(" ({})", t))
238 .unwrap_or_default()
239 );
240 }
241 }
242 }
243 }
244 }
245 }
246
247 Ok(())
248 }
249
250 Commands::Locals => {
251 let mut client = DaemonClient::connect().await?;
252
253 let result = client
254 .send_command(Command::Locals { frame_id: None })
255 .await?;
256
257 let vars: Vec<VariableInfo> = serde_json::from_value(result["variables"].clone())?;
258
259 if vars.is_empty() {
260 println!("No local variables");
261 } else {
262 println!("Local variables:");
263 for var in &vars {
264 println!(
265 " {} = {}{}",
266 var.name,
267 var.value,
268 var.type_name
269 .as_ref()
270 .map(|t| format!(" ({})", t))
271 .unwrap_or_default()
272 );
273 }
274 }
275
276 Ok(())
277 }
278
279 Commands::Print { expression } => {
280 let mut client = DaemonClient::connect().await?;
281
282 let result = client
283 .send_command(Command::Evaluate {
284 expression: expression.clone(),
285 frame_id: None,
286 context: EvaluateContext::Watch,
287 })
288 .await?;
289
290 let eval: EvaluateResult = serde_json::from_value(result)?;
291 println!(
292 "{} = {}{}",
293 expression,
294 eval.result,
295 eval.type_name.map(|t| format!(" ({})", t)).unwrap_or_default()
296 );
297
298 Ok(())
299 }
300
301 Commands::Eval { expression } => {
302 let mut client = DaemonClient::connect().await?;
303
304 let result = client
305 .send_command(Command::Evaluate {
306 expression: expression.clone(),
307 frame_id: None,
308 context: EvaluateContext::Repl,
309 })
310 .await?;
311
312 let eval: EvaluateResult = serde_json::from_value(result)?;
313 println!("{}", eval.result);
314
315 Ok(())
316 }
317
318 Commands::Context { lines } => {
319 let mut client = DaemonClient::connect().await?;
320
321 let result = client.send_command(Command::Context { lines }).await?;
322
323 let ctx: ContextResult = serde_json::from_value(result)?;
324
325 if let Some(source) = &ctx.source {
327 println!(
328 "Thread {} stopped at {}:{}",
329 ctx.thread_id, source, ctx.line
330 );
331 }
332 if let Some(func) = &ctx.function {
333 println!("In function: {}", func);
334 }
335 println!();
336
337 for line in &ctx.source_lines {
339 let marker = if line.is_current { "->" } else { " " };
340 println!("{} {:>4} | {}", marker, line.number, line.content);
341 }
342
343 if !ctx.locals.is_empty() {
345 println!();
346 println!("Locals:");
347 for var in &ctx.locals {
348 println!(
349 " {} = {}{}",
350 var.name,
351 var.value,
352 var.type_name
353 .as_ref()
354 .map(|t| format!(" ({})", t))
355 .unwrap_or_default()
356 );
357 }
358 }
359
360 Ok(())
361 }
362
363 Commands::Threads => {
364 let mut client = DaemonClient::connect().await?;
365
366 let result = client.send_command(Command::Threads).await?;
367 let threads: Vec<ThreadInfo> = serde_json::from_value(result["threads"].clone())?;
368
369 if threads.is_empty() {
370 println!("No threads");
371 } else {
372 println!("Threads:");
373 for thread in &threads {
374 println!(" {} - {}", thread.id, thread.name);
375 }
376 }
377
378 Ok(())
379 }
380
381 Commands::Thread { id } => {
382 let mut client = DaemonClient::connect().await?;
383
384 if let Some(id) = id {
385 client
386 .send_command(Command::ThreadSelect { id })
387 .await?;
388 println!("Switched to thread {}", id);
389 } else {
390 let result = client.send_command(Command::Status).await?;
392 let status: StatusResult = serde_json::from_value(result)?;
393 if let Some(thread_id) = status.stopped_thread {
394 println!("Current thread: {}", thread_id);
395 } else {
396 println!("No thread selected");
397 }
398 }
399
400 Ok(())
401 }
402
403 Commands::Frame { number } => {
404 let mut client = DaemonClient::connect().await?;
405
406 if let Some(n) = number {
407 client
408 .send_command(Command::FrameSelect { number: n })
409 .await?;
410 println!("Switched to frame {}", n);
411 } else {
412 println!("Current frame: 0 (use 'debugger backtrace' to see all frames)");
413 }
414
415 Ok(())
416 }
417
418 Commands::Up => {
419 let mut client = DaemonClient::connect().await?;
420 let result = client.send_command(Command::FrameUp).await?;
421 print_frame_nav_result(&result);
422 Ok(())
423 }
424
425 Commands::Down => {
426 let mut client = DaemonClient::connect().await?;
427 let result = client.send_command(Command::FrameDown).await?;
428 print_frame_nav_result(&result);
429 Ok(())
430 }
431
432 Commands::Await { timeout } => {
433 let mut client = DaemonClient::connect().await?;
434
435 println!("Waiting for program to stop (timeout: {}s)...", timeout);
436
437 let result = client
438 .send_command(Command::Await {
439 timeout_secs: timeout,
440 })
441 .await?;
442
443 if result.get("already_stopped").and_then(|v| v.as_bool()).unwrap_or(false) {
445 let reason = result["reason"].as_str().unwrap_or("unknown");
446 println!("Program was already stopped: {}", reason);
447 } else if let Some(reason) = result.get("reason").and_then(|v| v.as_str()) {
448 match reason {
449 "exited" => {
450 let code = result["exit_code"].as_i64().unwrap_or(0);
451 println!("Program exited with code {}", code);
452 }
453 "terminated" => {
454 println!("Program terminated");
455 }
456 _ => {
457 let stop: StopResult = serde_json::from_value(result)?;
458 print_stop_result(&stop);
459 }
460 }
461 }
462
463 Ok(())
464 }
465
466 Commands::Output { follow, tail, clear } => {
467 let mut client = DaemonClient::connect().await?;
468
469 if follow {
470 println!("Output streaming not yet implemented");
471 return Ok(());
472 }
473
474 let result = client
475 .send_command(Command::GetOutput { tail, clear })
476 .await?;
477
478 let output = result["output"].as_str().unwrap_or("");
479 if output.is_empty() {
480 println!("(no output)");
481 } else {
482 print!("{}", output);
483 }
484
485 Ok(())
486 }
487
488 Commands::Status => {
489 match DaemonClient::connect().await {
490 Ok(mut client) => {
491 let result = client.send_command(Command::Status).await?;
492 let status: StatusResult = serde_json::from_value(result)?;
493
494 println!("Daemon: running");
495 if status.session_active {
496 println!("Session: active");
497 if let Some(program) = status.program {
498 println!("Program: {}", program);
499 }
500 if let Some(adapter) = status.adapter {
501 println!("Adapter: {}", adapter);
502 }
503 if let Some(state) = status.state {
504 println!("State: {}", state);
505 }
506 if let Some(reason) = status.stopped_reason {
507 println!("Stopped reason: {}", reason);
508 }
509 if let Some(thread) = status.stopped_thread {
510 println!("Stopped thread: {}", thread);
511 }
512 } else {
513 println!("Session: none");
514 }
515 }
516 Err(Error::DaemonNotRunning) => {
517 println!("Daemon: not running");
518 println!("Session: none");
519 }
520 Err(e) => return Err(e),
521 }
522
523 Ok(())
524 }
525
526 Commands::Stop => {
527 let mut client = DaemonClient::connect().await?;
528 client.send_command(Command::Stop).await?;
529 println!("Debug session stopped");
530 Ok(())
531 }
532
533 Commands::Detach => {
534 let mut client = DaemonClient::connect().await?;
535 client.send_command(Command::Detach).await?;
536 println!("Detached from process (process continues running)");
537 Ok(())
538 }
539
540 Commands::Restart => {
541 let mut client = DaemonClient::connect().await?;
542 client.send_command(Command::Restart).await?;
543 println!("Program restarted");
544 Ok(())
545 }
546
547 Commands::Logs { lines, follow, clear } => {
548 use crate::common::logging;
549
550 let log_path = logging::daemon_log_path();
551
552 if let Some(path) = log_path {
553 if clear {
554 logging::truncate_daemon_log()?;
555 println!("Daemon log cleared: {}", path.display());
556 return Ok(());
557 }
558
559 if !path.exists() {
560 println!("No daemon log file found at: {}", path.display());
561 println!("The daemon may not have been started yet.");
562 return Ok(());
563 }
564
565 if follow {
566 println!("Following daemon log: {} (Ctrl+C to stop)", path.display());
567 println!("---");
568 let status = std::process::Command::new("tail")
570 .args(["-f", "-n", &lines.to_string()])
571 .arg(&path)
572 .status();
573
574 match status {
575 Ok(_) => {}
576 Err(e) => {
577 eprintln!("Failed to follow log: {}", e);
578 }
579 }
580 } else {
581 let content = std::fs::read_to_string(&path)?;
583 let all_lines: Vec<&str> = content.lines().collect();
584 let start = all_lines.len().saturating_sub(lines);
585
586 println!("Daemon log: {} (last {} lines)", path.display(), lines);
587 println!("---");
588 for line in &all_lines[start..] {
589 println!("{}", line);
590 }
591
592 if all_lines.is_empty() {
593 println!("(log is empty)");
594 }
595 }
596 } else {
597 println!("Could not determine log file path");
598 }
599
600 Ok(())
601 }
602
603 Commands::Setup {
604 debugger,
605 version,
606 list,
607 check,
608 auto_detect,
609 uninstall,
610 path,
611 force,
612 dry_run,
613 json,
614 } => {
615 let opts = setup::SetupOptions {
616 debugger,
617 version,
618 list,
619 check,
620 auto_detect,
621 uninstall,
622 path,
623 force,
624 dry_run,
625 json,
626 };
627 setup::run(opts).await
628 }
629
630 Commands::Test { path, verbose } => {
631 let result = testing::run_scenario(&path, verbose).await?;
632
633 if result.passed {
634 std::process::exit(0);
635 } else {
636 std::process::exit(1);
637 }
638 }
639 }
640}
641
642fn print_frame_nav_result(result: &serde_json::Value) {
644 let frame_index = result["selected"].as_u64().unwrap_or(0);
645
646 if let Ok(frame_info) = serde_json::from_value::<StackFrameInfo>(result["frame"].clone()) {
647 let source = frame_info.source.as_deref().unwrap_or("?");
648 let line = frame_info
649 .line
650 .map(|l| l.to_string())
651 .unwrap_or_else(|| "?".to_string());
652 println!("#{} {} at {}:{}", frame_index, frame_info.name, source, line);
653 } else {
654 println!("Switched to frame {}", frame_index);
655 }
656}
657
658fn print_breakpoint_added(info: &BreakpointInfo) {
659 if info.verified {
660 println!(
661 "Breakpoint {} set at {}:{}",
662 info.id,
663 info.source.as_deref().unwrap_or("?"),
664 info.line.map(|l| l.to_string()).unwrap_or_else(|| "?".to_string())
665 );
666 } else {
667 println!(
668 "Breakpoint {} pending{}",
669 info.id,
670 info.message.as_ref().map(|m| format!(": {}", m)).unwrap_or_default()
671 );
672 }
673}
674
675fn print_breakpoint(info: &BreakpointInfo) {
676 let status = if info.enabled {
677 if info.verified { "✓" } else { "?" }
678 } else {
679 "○"
680 };
681
682 let location = match (&info.source, info.line) {
683 (Some(source), Some(line)) => format!("{}:{}", source, line),
684 (Some(source), None) => source.clone(),
685 (None, Some(line)) => format!(":{}", line),
686 (None, None) => "unknown".to_string(),
687 };
688
689 let extras = [
690 info.condition.as_ref().map(|c| format!("if {}", c)),
691 info.hit_count.map(|n| format!("hits: {}", n)),
692 info.message.clone(),
693 ]
694 .into_iter()
695 .flatten()
696 .collect::<Vec<_>>()
697 .join(", ");
698
699 if extras.is_empty() {
700 println!(" {} {} {}", status, info.id, location);
701 } else {
702 println!(" {} {} {} ({})", status, info.id, location, extras);
703 }
704}
705
706fn print_stop_result(stop: &StopResult) {
707 match stop.reason.as_str() {
708 "breakpoint" => {
709 println!("Stopped at breakpoint");
710 if !stop.hit_breakpoint_ids.is_empty() {
711 println!(" Breakpoint IDs: {:?}", stop.hit_breakpoint_ids);
712 }
713 }
714 "step" => {
715 println!("Step completed");
716 }
717 "exception" | "signal" => {
718 println!(
719 "Stopped: {}",
720 stop.description.as_deref().unwrap_or(&stop.reason)
721 );
722 }
723 "pause" => {
724 println!("Paused");
725 }
726 "entry" => {
727 println!("Stopped at entry point");
728 }
729 _ => {
730 println!("Stopped: {}", stop.reason);
731 }
732 }
733
734 if let (Some(source), Some(line)) = (&stop.source, stop.line) {
735 println!(" Location: {}:{}", source, line);
736 }
737}