datui_lib/analysis/
value_counts_modal.rs1use crate::analysis::sampling::ReadWatch;
5use crate::analysis::value_counts::{LineKind, Order, ValueCounts};
6use std::collections::HashMap;
7use std::sync::Arc;
8
9#[derive(Debug, Clone)]
11pub struct Computing {
12 pub column: String,
13 pub exact: bool,
15 pub watch: ReadWatch,
17 pub file_starts: Option<std::sync::Arc<Vec<usize>>>,
20}
21
22impl Computing {
23 pub fn files_reached(&self) -> Option<(usize, usize)> {
25 let starts = self.file_starts.as_ref()?;
26 let files = starts.len().checked_sub(1)?;
27 let seen = self.watch.rows_seen()?;
28 let reached = starts[..files].partition_point(|&start| start <= seen);
29 Some((reached.min(files), files))
30 }
31}
32
33#[derive(Debug, Default)]
34pub struct ValueCountsModal {
35 pub columns: Vec<String>,
37 pub at: usize,
39 pub order: Order,
40 pub selected: usize,
42 pub offset: usize,
43 pub page: usize,
45 pub body_top: u16,
47 pub computing: Option<Computing>,
48 pub failed: Option<(String, String)>,
50 frame: Option<u64>,
52 held: HashMap<String, Arc<ValueCounts>>,
54 pub drill_return: bool,
56 pub view: Option<CountsView>,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub enum CountsView {
64 Listing,
65 Histogram,
66}
67
68impl ValueCountsModal {
69 pub fn open(&mut self, columns: Vec<String>, at: usize, frame: u64) {
72 if self.frame != Some(frame) || self.columns != columns {
73 self.held.clear();
74 }
75 self.frame = Some(frame);
76 self.columns = columns;
77 self.at = at.min(self.columns.len().saturating_sub(1));
78 self.computing = None;
79 self.failed = None;
80 self.drill_return = false;
81 self.view = None;
82 self.reset_cursor();
83 }
84
85 pub fn rebase(&mut self, frame: u64) {
87 self.frame = Some(frame);
88 }
89
90 pub fn column(&self) -> Option<&str> {
91 self.columns.get(self.at).map(String::as_str)
92 }
93
94 pub fn current(&self) -> Option<&Arc<ValueCounts>> {
96 self.held.get(self.column()?)
97 }
98
99 pub fn hold(&mut self, counts: ValueCounts) {
100 self.held.insert(counts.column.clone(), Arc::new(counts));
101 }
102
103 pub fn counting(&self) -> bool {
105 self.computing
106 .as_ref()
107 .is_some_and(|c| Some(c.column.as_str()) == self.column())
108 }
109
110 pub fn step(&mut self, by: isize) -> bool {
112 let to = self.at.saturating_add_signed(by);
113 if to == self.at || to >= self.columns.len() {
114 return false;
115 }
116 self.at = to;
117 self.failed = None;
118 self.view = None;
119 self.reset_cursor();
120 true
121 }
122
123 pub fn shows_histogram(&self) -> bool {
126 let has = self.current().is_some_and(|c| c.histogram.is_some());
127 has && self.view != Some(CountsView::Listing)
128 }
129
130 pub fn toggle_view(&mut self) {
132 if self.current().is_some_and(|c| c.histogram.is_some()) {
133 self.view = Some(if self.shows_histogram() {
134 CountsView::Listing
135 } else {
136 CountsView::Histogram
137 });
138 }
139 }
140
141 pub fn toggle_order(&mut self) {
142 self.order = self.order.toggled();
143 self.reset_cursor();
144 }
145
146 fn reset_cursor(&mut self) {
147 self.selected = 0;
148 self.offset = 0;
149 }
150
151 fn lines(&self) -> usize {
152 self.current()
153 .map(|c| c.lines(self.order).len())
154 .unwrap_or(0)
155 }
156
157 pub fn move_by(&mut self, by: isize) {
159 let last = self.lines().saturating_sub(1);
160 self.selected = self.selected.saturating_add_signed(by).min(last);
161 }
162
163 pub fn move_to_end(&mut self) {
164 self.selected = self.lines().saturating_sub(1);
165 }
166
167 pub fn move_to_start(&mut self) {
168 self.selected = 0;
169 }
170
171 pub fn selected_kind(&self) -> Option<LineKind> {
173 let counts = self.current()?;
174 counts.lines(self.order).get(self.selected).map(|l| l.kind)
175 }
176
177 pub fn scroll_into_view(&mut self, height: usize) {
179 let height = height.max(1);
180 self.page = height;
181 if self.selected < self.offset {
182 self.offset = self.selected;
183 } else if self.selected >= self.offset + height {
184 self.offset = self.selected + 1 - height;
185 }
186 let last = self.lines().saturating_sub(height);
187 self.offset = self.offset.min(last);
188 }
189}
190
191#[cfg(test)]
192mod tests;