Skip to main content

data_beans/interactive/
mod.rs

1//! Full-screen terminal views (feature `tui`), and line prompts for when
2//! there is no terminal.
3//!
4//! - [`ui`]: the shared pieces for building a view. The palette (terminal
5//!   foreground plus one accent), [`ui::panel`], [`ui::header`],
6//!   [`ui::help_line`], [`ui::input_line`], the [`ui::Screen`] trait driven by
7//!   [`ui::run_screen`], and histograms: [`ui::Scale`], [`ui::Binning`],
8//!   [`ui::Binned`], [`ui::HistPlot`], and [`ui::MirrorPlot`] for two series
9//!   around one zero line.
10//! - [`cutoff_tui`]: row and column nnz cutoff picker over plain nnz vectors.
11//! - [`stat_tui`]: table-and-histogram explorer over plain name and value
12//!   columns, optionally marking entries and handing them back.
13//!
14//! Open a view only when [`tui_available`]; otherwise fall back to text.
15
16pub mod cutoff_tui;
17pub mod stat_tui;
18pub mod ui;
19
20use std::io::{self, IsTerminal, Write};
21
22/// Whether a full-screen session can run: both stdin and stdout are a terminal.
23/// Otherwise callers fall back to the line prompts below.
24pub fn tui_available() -> bool {
25    io::stdin().is_terminal() && io::stdout().is_terminal()
26}
27
28/// User action after viewing histogram or other interactive prompts
29#[derive(Debug, Clone)]
30pub enum UserAction {
31    Proceed,
32    AdjustCutoffs(usize, usize),
33    Cancel,
34}
35
36/// Prompt user for action in interactive mode after showing histogram
37pub fn prompt_user_action(
38    current_row_cutoff: usize,
39    current_col_cutoff: usize,
40) -> anyhow::Result<UserAction> {
41    println!("\nOptions:");
42    println!("  [p] Proceed with current cutoffs");
43    println!("  [a] Adjust cutoffs");
44    println!("  [c] Cancel");
45    print!("\nChoose an option (p/a/c): ");
46    io::stdout().flush()?;
47
48    let mut input = String::new();
49    io::stdin().read_line(&mut input)?;
50    let choice = input.trim().to_lowercase();
51
52    match choice.as_str() {
53        "p" | "proceed" | "y" | "yes" => Ok(UserAction::Proceed),
54        "c" | "cancel" | "n" | "no" => Ok(UserAction::Cancel),
55        "a" | "adjust" => {
56            let new_row_cutoff = prompt_cutoff_value("row", current_row_cutoff)?;
57            let new_col_cutoff = prompt_cutoff_value("column", current_col_cutoff)?;
58            Ok(UserAction::AdjustCutoffs(new_row_cutoff, new_col_cutoff))
59        }
60        _ => {
61            println!("Invalid choice. Cancelling operation.");
62            Ok(UserAction::Cancel)
63        }
64    }
65}
66
67/// Prompt user for a single cutoff value
68fn prompt_cutoff_value(label: &str, current: usize) -> anyhow::Result<usize> {
69    print!("\nEnter new {} nnz cutoff (current: {}): ", label, current);
70    io::stdout().flush()?;
71
72    let mut input = String::new();
73    io::stdin().read_line(&mut input)?;
74
75    let value = input.trim().parse::<usize>().unwrap_or(current);
76    Ok(value)
77}
78
79/// Simple yes/no confirmation prompt
80pub fn confirm(message: &str) -> anyhow::Result<bool> {
81    print!("{} (y/n): ", message);
82    io::stdout().flush()?;
83
84    let mut input = String::new();
85    io::stdin().read_line(&mut input)?;
86    let choice = input.trim().to_lowercase();
87
88    Ok(matches!(choice.as_str(), "y" | "yes"))
89}