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