use crate::analysis::sampling::ReadWatch;
use crate::analysis::value_counts::{LineKind, Order, ValueCounts};
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct Computing {
pub column: String,
pub exact: bool,
pub watch: ReadWatch,
pub file_starts: Option<std::sync::Arc<Vec<usize>>>,
}
impl Computing {
pub fn files_reached(&self) -> Option<(usize, usize)> {
let starts = self.file_starts.as_ref()?;
let files = starts.len().checked_sub(1)?;
let seen = self.watch.rows_seen()?;
let reached = starts[..files].partition_point(|&start| start <= seen);
Some((reached.min(files), files))
}
}
#[derive(Debug, Default)]
pub struct ValueCountsModal {
pub columns: Vec<String>,
pub at: usize,
pub order: Order,
pub selected: usize,
pub offset: usize,
pub page: usize,
pub body_top: u16,
pub computing: Option<Computing>,
pub failed: Option<(String, String)>,
frame: Option<u64>,
held: HashMap<String, Arc<ValueCounts>>,
pub drill_return: bool,
pub view: Option<CountsView>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CountsView {
Listing,
Histogram,
}
impl ValueCountsModal {
pub fn open(&mut self, columns: Vec<String>, at: usize, frame: u64) {
if self.frame != Some(frame) || self.columns != columns {
self.held.clear();
}
self.frame = Some(frame);
self.columns = columns;
self.at = at.min(self.columns.len().saturating_sub(1));
self.computing = None;
self.failed = None;
self.drill_return = false;
self.view = None;
self.reset_cursor();
}
pub fn rebase(&mut self, frame: u64) {
self.frame = Some(frame);
}
pub fn column(&self) -> Option<&str> {
self.columns.get(self.at).map(String::as_str)
}
pub fn current(&self) -> Option<&Arc<ValueCounts>> {
self.held.get(self.column()?)
}
pub fn hold(&mut self, counts: ValueCounts) {
self.held.insert(counts.column.clone(), Arc::new(counts));
}
pub fn counting(&self) -> bool {
self.computing
.as_ref()
.is_some_and(|c| Some(c.column.as_str()) == self.column())
}
pub fn step(&mut self, by: isize) -> bool {
let to = self.at.saturating_add_signed(by);
if to == self.at || to >= self.columns.len() {
return false;
}
self.at = to;
self.failed = None;
self.view = None;
self.reset_cursor();
true
}
pub fn shows_histogram(&self) -> bool {
let has = self.current().is_some_and(|c| c.histogram.is_some());
has && self.view != Some(CountsView::Listing)
}
pub fn toggle_view(&mut self) {
if self.current().is_some_and(|c| c.histogram.is_some()) {
self.view = Some(if self.shows_histogram() {
CountsView::Listing
} else {
CountsView::Histogram
});
}
}
pub fn toggle_order(&mut self) {
self.order = self.order.toggled();
self.reset_cursor();
}
fn reset_cursor(&mut self) {
self.selected = 0;
self.offset = 0;
}
fn lines(&self) -> usize {
self.current()
.map(|c| c.lines(self.order).len())
.unwrap_or(0)
}
pub fn move_by(&mut self, by: isize) {
let last = self.lines().saturating_sub(1);
self.selected = self.selected.saturating_add_signed(by).min(last);
}
pub fn move_to_end(&mut self) {
self.selected = self.lines().saturating_sub(1);
}
pub fn move_to_start(&mut self) {
self.selected = 0;
}
pub fn selected_kind(&self) -> Option<LineKind> {
let counts = self.current()?;
counts.lines(self.order).get(self.selected).map(|l| l.kind)
}
pub fn scroll_into_view(&mut self, height: usize) {
let height = height.max(1);
self.page = height;
if self.selected < self.offset {
self.offset = self.selected;
} else if self.selected >= self.offset + height {
self.offset = self.selected + 1 - height;
}
let last = self.lines().saturating_sub(height);
self.offset = self.offset.min(last);
}
}
#[cfg(test)]
mod tests;