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