From 27b70dbb74b74c3e9df20d106474305802c6af66 Mon Sep 17 00:00:00 2001 From: Vishvadharman Date: Fri, 5 Jun 2026 01:03:17 +0530 Subject: [PATCH] feat(settings): add system diagnostics tool --- src/diagnostics.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + src/ui/app.rs | 40 ++++++++++++++++++++++-------- src/ui/draw.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 src/diagnostics.rs diff --git a/src/diagnostics.rs b/src/diagnostics.rs new file mode 100644 index 0000000..ea14b58 --- /dev/null +++ b/src/diagnostics.rs @@ -0,0 +1,61 @@ +use std::path::Path; +use std::process::Command; + +#[derive(Debug, Clone)] +pub struct DiagnosticEntry { + pub tool: String, + pub found: bool, + pub version: String, +} + +pub fn run_diagnostics() -> Vec { + let tools = ["apt", "brew", "pacman", "yay", "curl", "git"]; + let mut results = Vec::new(); + + for tool in tools { + let found = command_exists(tool); + + let version = if found { get_tool_version(tool) } else { "Not Found".to_string() }; + + results.push(DiagnosticEntry { tool: tool.to_string(), found, version }); + } + + results.push(DiagnosticEntry { + tool: "TRX".to_string(), + found: true, + version: env!("CARGO_PKG_VERSION").to_string(), + }); + + if let Some(proj_dirs) = directories::ProjectDirs::from("", "", "trx") { + let config_path = proj_dirs.config_dir().join("config.toml"); + + results.push(DiagnosticEntry { + tool: "Config".to_string(), + found: Path::new(&config_path).exists(), + version: config_path.display().to_string(), + }); + } + + results +} + +fn command_exists(tool: &str) -> bool { + let check_command = if cfg!(windows) { "where" } else { "which" }; + + Command::new(check_command) + .arg(tool) + .output() + .map(|output| output.status.success()) + .unwrap_or(false) +} + +fn get_tool_version(tool: &str) -> String { + Command::new(tool) + .arg("--version") + .output() + .ok() + .and_then(|output| String::from_utf8(output.stdout).ok()) + .and_then(|stdout| stdout.lines().next().map(|line| line.trim().to_string())) + .filter(|line| !line.is_empty()) + .unwrap_or_else(|| "Unknown".to_string()) +} diff --git a/src/main.rs b/src/main.rs index 387b267..fa53abc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod config; +mod diagnostics; mod fuzzy; mod managers; mod ui; diff --git a/src/ui/app.rs b/src/ui/app.rs index d89b46f..b91b9ef 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -33,6 +33,7 @@ pub enum DetailsState { } pub struct App { + pub diagnostics_results: Option>, pub input: String, pub character_index: usize, pub input_mode: InputMode, @@ -104,6 +105,7 @@ impl App { }; let mut app = Self { + diagnostics_results: None, input: String::new(), input_mode: InputMode::Normal, current_tab, @@ -479,6 +481,17 @@ impl App { self.set_popup(format!("Border Style: {}", self.config.settings.border_style), Color::Cyan); } + fn diagnostics_settings_index(&self) -> usize { + let mgr_count = self.available_managers.len(); + let mut index = 6 + mgr_count + 3; + + if self.config.theme_name == "Custom" { + index += 6; + } + + index + } + fn next_spinner_type(&mut self) { let types = ["Dots", "Bars", "Pulse", "Classic"]; let current_pos = @@ -615,6 +628,16 @@ impl App { continue; } + if self.diagnostics_results.is_some() + && matches!( + key.code, + KeyCode::Esc | KeyCode::Enter | KeyCode::Char('q') + ) + { + self.diagnostics_results = None; + continue; + } + match self.input_mode { InputMode::Normal if key.kind == KeyEventKind::Press => { let keys = &self.config.keys; @@ -762,11 +785,7 @@ impl App { } KeyCode::Down | KeyCode::Char('j') => { if self.current_tab == Tab::Settings { - let max = if self.config.theme_name == "Custom" { - 14 - } else { - 8 - }; + let max = self.diagnostics_settings_index(); if self.settings_index < max { self.settings_index += 1; } @@ -836,12 +855,7 @@ impl App { match mouse_event.kind { event::MouseEventKind::ScrollDown => { if self.current_tab == Tab::Settings { - let mgr_count = self.available_managers.len(); - let max = if self.config.theme_name == "Custom" { - 5 + mgr_count + 6 - } else { - 5 + mgr_count - }; + let max = self.diagnostics_settings_index(); if self.settings_index < max { self.settings_index += 1; } @@ -985,6 +999,10 @@ impl App { fn handle_settings_toggle(&mut self) { let mgr_count = self.available_managers.len(); + if self.settings_index == self.diagnostics_settings_index() { + self.diagnostics_results = Some(crate::diagnostics::run_diagnostics()); + return; + } match self.settings_index { 1 => { // Auto Update Check diff --git a/src/ui/draw.rs b/src/ui/draw.rs index c55c00d..bcf3bb8 100644 --- a/src/ui/draw.rs +++ b/src/ui/draw.rs @@ -107,6 +107,10 @@ pub fn draw_ui(frame: &mut Frame, app: &mut App) { draw_update_prompt(frame, app, &theme_colors); } + if app.diagnostics_results.is_some() { + draw_diagnostics_popup(frame, app, &theme_colors); + } + if let Some((msg, color)) = &app.popup_message { draw_popup(frame, msg, *color, &theme_colors, &app.config.settings.border_style); } @@ -241,6 +245,53 @@ fn draw_popup( frame.render_widget(paragraph, area); } +fn draw_diagnostics_popup(frame: &mut Frame, app: &App, theme: &crate::config::Theme) { + let area = centered_rect(85, 80, frame.area()); + frame.render_widget(Clear, area); + + let border_type = get_border_type(&app.config.settings.border_style); + let border_color = app.config.get_color(&theme.border_color); + let highlight_color = app.config.get_color(&theme.highlight_color); + let primary_color = app.config.get_color(&theme.text_primary); + let secondary_color = app.config.get_color(&theme.text_secondary); + + let mut lines = vec![ + Line::from(vec![Span::styled( + "Tool Found Version / Path", + Style::default().fg(highlight_color).add_modifier(Modifier::BOLD), + )]), + Line::from(""), + ]; + + if let Some(results) = &app.diagnostics_results { + for entry in results { + let found = if entry.found { "Yes" } else { "No" }; + lines.push(Line::from(vec![ + Span::styled(format!("{:<20}", entry.tool), Style::default().fg(primary_color)), + Span::styled(format!("{:<10}", found), Style::default().fg(secondary_color)), + Span::styled(entry.version.clone(), Style::default().fg(primary_color)), + ])); + } + } + + lines.push(Line::from("")); + lines.push(Line::from(Span::styled( + "Press Esc, Enter, or q to close", + Style::default().fg(secondary_color), + ))); + + let paragraph = Paragraph::new(lines) + .block( + Block::bordered() + .title("System Diagnostics") + .border_type(border_type) + .border_style(Style::default().fg(border_color)), + ) + .wrap(Wrap { trim: false }); + + frame.render_widget(paragraph, area); +} + fn draw_settings_tab(frame: &mut Frame, app: &App, area: Rect, theme: &crate::config::Theme) { let highlight_color = app.config.get_color(&theme.highlight_color); let border_color = app.config.get_color(&theme.border_color); @@ -356,6 +407,7 @@ fn draw_settings_tab(frame: &mut Frame, app: &App, area: Rect, theme: &crate::co draw_setting!(current_idx, "Theme Preset", &app.config.theme_name, false); draw_setting!(current_idx + 1, "Border Style", &app.config.settings.border_style, false); draw_setting!(current_idx + 2, "Spinner Type", &app.config.settings.spinner_type, false); + current_idx += 3; if app.config.theme_name == "Custom" { @@ -364,6 +416,7 @@ fn draw_settings_tab(frame: &mut Frame, app: &App, area: Rect, theme: &crate::co "--- Custom Colors ---", Style::default().fg(highlight_color).add_modifier(Modifier::BOLD), ))); + if let Some(ref ct) = app.config.custom_theme { draw_setting!(current_idx, "Border Color", &ct.border_color, false); draw_setting!(current_idx + 1, "Highlight Color", &ct.highlight_color, false); @@ -372,8 +425,17 @@ fn draw_settings_tab(frame: &mut Frame, app: &App, area: Rect, theme: &crate::co draw_setting!(current_idx + 4, "Text Primary", &ct.text_primary, false); draw_setting!(current_idx + 5, "Text Secondary", &ct.text_secondary, false); } + + current_idx += 6; } + settings_lines.push(Line::from("")); + settings_lines.push(Line::from(Span::styled( + "--- Diagnostics ---", + Style::default().fg(highlight_color).add_modifier(Modifier::BOLD), + ))); + draw_setting!(current_idx, "Run Diagnostics", "Press Enter", true); + let paragraph = Paragraph::new(settings_lines) .block( Block::bordered()