storageshower 0.27.1

Cyberpunk disk usage TUI
Documentation
use clap::Parser;
use crossterm::event::{
    self, DisableFocusChange, DisableMouseCapture, EnableFocusChange, EnableMouseCapture, Event,
};
use ratatui::DefaultTerminal;
use std::io;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use sysinfo::System;

use storageshower::app::App;
use storageshower::cli::{self, Cli};
use storageshower::system::{collect_disk_entries, collect_sys_stats, spawn_bg_collector};
use storageshower::types::{DiskEntry, SysStats};
use storageshower::ui::draw;

fn main() -> io::Result<()> {
    let cli = Cli::parse();

    if cli.help {
        cli::print_help();
        return Ok(());
    }
    if cli.version {
        cli::print_version();
        return Ok(());
    }
    if cli.list_colors {
        cli::print_colors();
        return Ok(());
    }
    if cli.export_theme {
        let mut prefs = storageshower::prefs::load_prefs_from(cli.config.as_deref());
        cli.apply_to(&mut prefs);
        cli::print_export_theme(&prefs);
        return Ok(());
    }

    let sys = System::new_all();
    let initial_stats = collect_sys_stats(&sys);
    let initial_disks = collect_disk_entries();
    drop(sys);

    let shared: Arc<Mutex<(SysStats, Vec<DiskEntry>)>> =
        Arc::new(Mutex::new((initial_stats, initial_disks)));

    spawn_bg_collector(Arc::clone(&shared));

    let mut terminal = ratatui::init();
    crossterm::execute!(std::io::stdout(), EnableMouseCapture, EnableFocusChange)?;
    let mut app = App::new(Arc::clone(&shared), &cli);
    let result = run_app(&mut terminal, &mut app);
    crossterm::execute!(std::io::stdout(), DisableMouseCapture, DisableFocusChange)?;
    ratatui::restore();
    result
}

fn run_app(terminal: &mut DefaultTerminal, app: &mut App) -> io::Result<()> {
    let mut last_data_refresh = Instant::now();

    loop {
        let refresh_dur = Duration::from_secs(app.prefs.refresh_rate);
        if last_data_refresh.elapsed() >= refresh_dur {
            app.refresh_data();
            last_data_refresh = Instant::now();
        }

        // Adjust scroll offsets to keep selection visible
        {
            let size = terminal.size()?;
            let border_rows: u16 = if app.prefs.show_border { 1 } else { 0 };
            let header_rows: u16 = if app.prefs.show_header { 2 } else { 0 };
            let footer_rows: u16 = 2 + border_rows;
            let chrome = border_rows + 2 + header_rows + footer_rows;
            let visible = size.height.saturating_sub(chrome) as usize;
            app.ensure_visible(visible.max(1));
            if app.drill.mode == storageshower::types::ViewMode::DrillDown {
                let drill_chrome: u16 = border_rows + 4 + footer_rows;
                let drill_visible = size.height.saturating_sub(drill_chrome) as usize;
                app.ensure_drill_visible(drill_visible.max(1));
            }
        }
        app.update_sorted();
        terminal.draw(|f| draw(f, app))?;

        if event::poll(Duration::from_millis(200))? {
            match event::read()? {
                Event::Key(key) => {
                    if key.kind == crossterm::event::KeyEventKind::Press {
                        app.handle_key(key);
                        if app.quit {
                            return Ok(());
                        }
                    }
                }
                Event::Mouse(mouse) => {
                    let size = terminal.size()?;
                    app.handle_mouse(mouse, size.width, size.height);
                }
                Event::FocusLost => {
                    app.hover = Default::default();
                }
                Event::Resize(_, _) => {}
                _ => {}
            }
        }
    }
}