1pub mod adapters;
7pub mod detector;
8pub mod installer;
9pub mod registry;
10pub mod verifier;
11
12use crate::common::Result;
13use std::path::PathBuf;
14
15#[derive(Debug, Clone)]
17pub struct SetupOptions {
18 pub debugger: Option<String>,
20 pub version: Option<String>,
22 pub list: bool,
24 pub check: bool,
26 pub auto_detect: bool,
28 pub uninstall: bool,
30 pub path: bool,
32 pub force: bool,
34 pub dry_run: bool,
36 pub json: bool,
38}
39
40#[derive(Debug, Clone, serde::Serialize)]
42pub struct SetupResult {
43 pub status: SetupStatus,
44 pub debugger: String,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 pub version: Option<String>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub path: Option<PathBuf>,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub languages: Option<Vec<String>>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub message: Option<String>,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
57#[serde(rename_all = "snake_case")]
58pub enum SetupStatus {
59 Success,
60 AlreadyInstalled,
61 Uninstalled,
62 NotFound,
63 Failed,
64 DryRun,
65}
66
67pub async fn run(opts: SetupOptions) -> Result<()> {
69 if opts.list {
70 return list_debuggers(opts.json).await;
71 }
72
73 if opts.check {
74 return check_debuggers(opts.json).await;
75 }
76
77 if opts.auto_detect {
78 return auto_setup(opts).await;
79 }
80
81 let debugger = match &opts.debugger {
83 Some(d) => d.clone(),
84 None => {
85 if opts.json {
86 println!(
87 "{}",
88 serde_json::json!({
89 "status": "error",
90 "message": "No debugger specified. Use --list to see available debuggers."
91 })
92 );
93 } else {
94 println!("No debugger specified. Use --list to see available debuggers.");
95 println!();
96 println!("Available debuggers:");
97 for info in registry::all_debuggers() {
98 println!(
99 " {:12} - {} ({})",
100 info.id,
101 info.description,
102 info.languages.join(", ")
103 );
104 }
105 }
106 return Ok(());
107 }
108 };
109
110 if opts.path {
111 return show_path(&debugger, opts.json).await;
112 }
113
114 if opts.uninstall {
115 return uninstall_debugger(&debugger, opts.json).await;
116 }
117
118 install_debugger(&debugger, opts).await
120}
121
122async fn list_debuggers(json: bool) -> Result<()> {
124 let debuggers = registry::all_debuggers();
125 let mut results = Vec::new();
126
127 for info in debuggers {
128 let installer = registry::get_installer(info.id);
129 let status = if let Some(inst) = &installer {
130 inst.status().await.ok()
131 } else {
132 None
133 };
134
135 let status_str = match &status {
136 Some(installer::InstallStatus::Installed { version, .. }) => {
137 if let Some(v) = version {
138 format!("installed ({})", v)
139 } else {
140 "installed".to_string()
141 }
142 }
143 Some(installer::InstallStatus::Broken { reason, .. }) => {
144 format!("broken: {}", reason)
145 }
146 Some(installer::InstallStatus::NotInstalled) | None => "not installed".to_string(),
147 };
148
149 if json {
150 results.push(serde_json::json!({
151 "id": info.id,
152 "name": info.name,
153 "description": info.description,
154 "languages": info.languages,
155 "platforms": info.platforms.iter().map(|p| p.to_string()).collect::<Vec<_>>(),
156 "primary": info.primary,
157 "status": status_str,
158 "path": status.as_ref().and_then(|s| match s {
159 installer::InstallStatus::Installed { path, .. } => Some(path.display().to_string()),
160 installer::InstallStatus::Broken { path, .. } => Some(path.display().to_string()),
161 _ => None,
162 }),
163 }));
164 } else {
165 let status_indicator = match &status {
166 Some(installer::InstallStatus::Installed { .. }) => "✓",
167 Some(installer::InstallStatus::Broken { .. }) => "✗",
168 _ => " ",
169 };
170 println!(
171 " {} {:12} {:20} {}",
172 status_indicator,
173 info.id,
174 status_str,
175 info.languages.join(", ")
176 );
177 }
178 }
179
180 if json {
181 println!("{}", serde_json::to_string_pretty(&results)?);
182 }
183
184 Ok(())
185}
186
187async fn check_debuggers(json: bool) -> Result<()> {
189 let debuggers = registry::all_debuggers();
190 let mut results = Vec::new();
191 let mut found_any = false;
192
193 if !json {
194 println!("Checking installed debuggers...\n");
195 }
196
197 for info in debuggers {
198 let installer = match registry::get_installer(info.id) {
199 Some(i) => i,
200 None => continue,
201 };
202
203 let status = installer.status().await.ok();
204
205 if let Some(installer::InstallStatus::Installed { path, version }) = &status {
206 found_any = true;
207
208 let verify_result = installer.verify().await;
210 let working = verify_result.as_ref().map(|v| v.success).unwrap_or(false);
211
212 if json {
213 results.push(serde_json::json!({
214 "id": info.id,
215 "path": path.display().to_string(),
216 "version": version,
217 "working": working,
218 "error": verify_result.as_ref().ok().and_then(|v| v.error.clone()),
219 }));
220 } else {
221 let status_icon = if working { "✓" } else { "✗" };
222 println!("{} {}", status_icon, info.id);
223 println!(" Path: {}", path.display());
224 if let Some(v) = version {
225 println!(" Version: {}", v);
226 }
227 if !working {
228 if let Ok(v) = &verify_result {
229 if let Some(err) = &v.error {
230 println!(" Error: {}", err);
231 }
232 }
233 }
234 println!();
235 }
236 }
237 }
238
239 if json {
240 println!("{}", serde_json::to_string_pretty(&results)?);
241 } else if !found_any {
242 println!("No debuggers installed.");
243 println!("Use 'debugger setup --list' to see available debuggers.");
244 }
245
246 Ok(())
247}
248
249async fn auto_setup(opts: SetupOptions) -> Result<()> {
251 let project_types = detector::detect_project_types(std::env::current_dir()?.as_path());
252
253 if project_types.is_empty() {
254 if opts.json {
255 println!(
256 "{}",
257 serde_json::json!({
258 "status": "no_projects",
259 "message": "No recognized project types found in current directory."
260 })
261 );
262 } else {
263 println!("No recognized project types found in current directory.");
264 }
265 return Ok(());
266 }
267
268 let debuggers: Vec<&str> = project_types
269 .iter()
270 .flat_map(|pt| detector::debuggers_for_project(pt))
271 .collect::<std::collections::HashSet<_>>()
272 .into_iter()
273 .collect();
274
275 if !opts.json {
276 println!(
277 "Detected project types: {}",
278 project_types
279 .iter()
280 .map(|p| format!("{:?}", p))
281 .collect::<Vec<_>>()
282 .join(", ")
283 );
284 println!(
285 "Will install debuggers: {}",
286 debuggers.join(", ")
287 );
288 println!();
289 }
290
291 let mut results = Vec::new();
292
293 for debugger in debuggers {
294 let result = install_debugger_inner(
295 debugger,
296 &SetupOptions {
297 debugger: Some(debugger.to_string()),
298 ..opts.clone()
299 },
300 )
301 .await;
302
303 if opts.json {
304 results.push(result);
305 }
306 }
307
308 if opts.json {
309 println!("{}", serde_json::to_string_pretty(&results)?);
310 }
311
312 Ok(())
313}
314
315async fn show_path(debugger: &str, json: bool) -> Result<()> {
317 let installer = match registry::get_installer(debugger) {
318 Some(i) => i,
319 None => {
320 if json {
321 println!(
322 "{}",
323 serde_json::json!({
324 "status": "not_found",
325 "debugger": debugger,
326 "message": format!("Unknown debugger: {}", debugger)
327 })
328 );
329 } else {
330 println!("Unknown debugger: {}", debugger);
331 }
332 return Ok(());
333 }
334 };
335
336 let status = installer.status().await?;
337
338 match status {
339 installer::InstallStatus::Installed { path, version } => {
340 if json {
341 println!(
342 "{}",
343 serde_json::json!({
344 "status": "installed",
345 "debugger": debugger,
346 "path": path.display().to_string(),
347 "version": version,
348 })
349 );
350 } else {
351 println!("{}", path.display());
352 }
353 }
354 installer::InstallStatus::Broken { path, reason } => {
355 if json {
356 println!(
357 "{}",
358 serde_json::json!({
359 "status": "broken",
360 "debugger": debugger,
361 "path": path.display().to_string(),
362 "reason": reason,
363 })
364 );
365 } else {
366 println!("{} (broken: {})", path.display(), reason);
367 }
368 }
369 installer::InstallStatus::NotInstalled => {
370 if json {
371 println!(
372 "{}",
373 serde_json::json!({
374 "status": "not_installed",
375 "debugger": debugger,
376 })
377 );
378 } else {
379 println!("{} is not installed", debugger);
380 }
381 }
382 }
383
384 Ok(())
385}
386
387async fn uninstall_debugger(debugger: &str, json: bool) -> Result<()> {
389 let installer = match registry::get_installer(debugger) {
390 Some(i) => i,
391 None => {
392 if json {
393 println!(
394 "{}",
395 serde_json::json!({
396 "status": "not_found",
397 "debugger": debugger,
398 "message": format!("Unknown debugger: {}", debugger)
399 })
400 );
401 } else {
402 println!("Unknown debugger: {}", debugger);
403 }
404 return Ok(());
405 }
406 };
407
408 match installer.uninstall().await {
409 Ok(()) => {
410 if json {
411 println!(
412 "{}",
413 serde_json::json!({
414 "status": "uninstalled",
415 "debugger": debugger,
416 })
417 );
418 } else {
419 println!("{} uninstalled", debugger);
420 }
421 }
422 Err(e) => {
423 if json {
424 println!(
425 "{}",
426 serde_json::json!({
427 "status": "error",
428 "debugger": debugger,
429 "message": e.to_string(),
430 })
431 );
432 } else {
433 println!("Failed to uninstall {}: {}", debugger, e);
434 }
435 }
436 }
437
438 Ok(())
439}
440
441async fn install_debugger(debugger: &str, opts: SetupOptions) -> Result<()> {
443 let result = install_debugger_inner(debugger, &opts).await;
444
445 if opts.json {
446 println!("{}", serde_json::to_string_pretty(&result)?);
447 }
448
449 Ok(())
450}
451
452async fn install_debugger_inner(debugger: &str, opts: &SetupOptions) -> SetupResult {
454 let installer = match registry::get_installer(debugger) {
455 Some(i) => i,
456 None => {
457 return SetupResult {
458 status: SetupStatus::NotFound,
459 debugger: debugger.to_string(),
460 version: None,
461 path: None,
462 languages: None,
463 message: Some(format!("Unknown debugger: {}", debugger)),
464 };
465 }
466 };
467
468 let status = match installer.status().await {
470 Ok(s) => s,
471 Err(e) => {
472 return SetupResult {
473 status: SetupStatus::Failed,
474 debugger: debugger.to_string(),
475 version: None,
476 path: None,
477 languages: None,
478 message: Some(format!("Failed to check status: {}", e)),
479 };
480 }
481 };
482
483 if let installer::InstallStatus::Installed { path, version } = &status {
485 if !opts.force {
486 if !opts.json {
487 println!(
488 "{} is already installed at {}",
489 debugger,
490 path.display()
491 );
492 if let Some(v) = version {
493 println!("Version: {}", v);
494 }
495 println!("Use --force to reinstall.");
496 }
497 return SetupResult {
498 status: SetupStatus::AlreadyInstalled,
499 debugger: debugger.to_string(),
500 version: version.clone(),
501 path: Some(path.clone()),
502 languages: Some(
503 installer
504 .info()
505 .languages
506 .iter()
507 .map(|s| s.to_string())
508 .collect(),
509 ),
510 message: None,
511 };
512 }
513 }
514
515 if opts.dry_run {
517 let method = installer.best_method().await;
518 if !opts.json {
519 println!("Would install {} using:", debugger);
520 match &method {
521 Ok(m) => println!(" Method: {:?}", m),
522 Err(e) => println!(" Error determining method: {}", e),
523 }
524 }
525 return SetupResult {
526 status: SetupStatus::DryRun,
527 debugger: debugger.to_string(),
528 version: opts.version.clone(),
529 path: None,
530 languages: Some(
531 installer
532 .info()
533 .languages
534 .iter()
535 .map(|s| s.to_string())
536 .collect(),
537 ),
538 message: Some(format!("Method: {:?}", method)),
539 };
540 }
541
542 if !opts.json {
544 println!("Installing {}...", debugger);
545 }
546
547 let install_opts = installer::InstallOptions {
548 version: opts.version.clone(),
549 force: opts.force,
550 };
551
552 match installer.install(install_opts).await {
553 Ok(result) => {
554 if let Err(e) = update_config(debugger, &result.path, &result.args).await {
556 if !opts.json {
557 println!("Warning: Failed to update configuration: {}", e);
558 }
559 }
560
561 if !opts.json {
562 println!();
563 println!(
564 "✓ {} {} installed to {}",
565 installer.info().name,
566 result.version.as_deref().unwrap_or(""),
567 result.path.display()
568 );
569 println!();
570 println!(
571 "Configuration updated. Use 'debugger start --adapter {} ./program' to debug.",
572 debugger
573 );
574 }
575
576 SetupResult {
577 status: SetupStatus::Success,
578 debugger: debugger.to_string(),
579 version: result.version,
580 path: Some(result.path),
581 languages: Some(
582 installer
583 .info()
584 .languages
585 .iter()
586 .map(|s| s.to_string())
587 .collect(),
588 ),
589 message: None,
590 }
591 }
592 Err(e) => {
593 if !opts.json {
594 println!("✗ Failed to install {}: {}", debugger, e);
595 }
596 SetupResult {
597 status: SetupStatus::Failed,
598 debugger: debugger.to_string(),
599 version: None,
600 path: None,
601 languages: None,
602 message: Some(e.to_string()),
603 }
604 }
605 }
606}
607
608async fn update_config(debugger: &str, path: &std::path::Path, args: &[String]) -> Result<()> {
610 use crate::common::paths::{config_path, ensure_config_dir};
611 use std::io::Write;
612
613 ensure_config_dir()?;
614
615 let config_file = match config_path() {
616 Some(p) => p,
617 None => return Ok(()),
618 };
619
620 let mut content = if config_file.exists() {
622 std::fs::read_to_string(&config_file)?
623 } else {
624 String::new()
625 };
626
627 let mut config: toml::Table = if content.is_empty() {
629 toml::Table::new()
630 } else {
631 content.parse().map_err(|e| {
632 crate::common::Error::ConfigParse(format!(
633 "Failed to parse {}: {}",
634 config_file.display(),
635 e
636 ))
637 })?
638 };
639
640 if !config.contains_key("adapters") {
642 config.insert("adapters".to_string(), toml::Value::Table(toml::Table::new()));
643 }
644
645 let adapters = config
646 .get_mut("adapters")
647 .and_then(|v| v.as_table_mut())
648 .expect("Expected 'adapters' to be a TOML table");
649
650 let mut adapter_table = toml::Table::new();
652 adapter_table.insert(
653 "path".to_string(),
654 toml::Value::String(path.display().to_string()),
655 );
656 if !args.is_empty() {
657 adapter_table.insert(
658 "args".to_string(),
659 toml::Value::Array(args.iter().map(|s| toml::Value::String(s.clone())).collect()),
660 );
661 }
662
663 adapters.insert(debugger.to_string(), toml::Value::Table(adapter_table));
664
665 content = toml::to_string_pretty(&config).unwrap_or_default();
667 let mut file = std::fs::File::create(&config_file)?;
668 file.write_all(content.as_bytes())?;
669
670 Ok(())
671}