#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MainViewContent {
Datatable,
Analysis,
Chart,
Home,
}
impl MainViewContent {
pub fn from_app_state(analysis_active: bool, input_mode_chart: bool) -> Self {
if analysis_active {
MainViewContent::Analysis
} else if input_mode_chart {
MainViewContent::Chart
} else {
MainViewContent::Datatable
}
}
}
#[derive(Debug, Clone)]
pub enum ControlBarSpec {
Datatable { dimmed: bool, query_active: bool },
Custom(Vec<(&'static str, &'static str)>),
}
pub fn control_bar_spec(app: &crate::App, content: MainViewContent) -> ControlBarSpec {
match content {
MainViewContent::Datatable => {
let query_active = app
.data_table_state
.as_ref()
.map(|s| !s.active_query.trim().is_empty())
.unwrap_or(false);
let dimmed = app.show_help
|| app.input_mode == crate::InputMode::Editing
|| app.input_mode == crate::InputMode::SortFilter
|| app.input_mode == crate::InputMode::PivotMelt
|| app.input_mode == crate::InputMode::Info
|| app.sort_filter_modal.active;
ControlBarSpec::Datatable {
dimmed,
query_active,
}
}
MainViewContent::Analysis => {
let mut pairs = vec![
("Esc", "Back"),
(crate::glyphs::get().updown, "Navigate"),
(crate::glyphs::get().updown_lr, "Scroll Columns"),
("Tab", "Sidebar"),
("Enter", "Select"),
];
if app.sampling_threshold.is_some() {
if let Some(results) = app.analysis_modal.current_results() {
if results.sample_size.is_some() {
pairs.push(("r", "Resample"));
}
}
}
ControlBarSpec::Custom(pairs)
}
MainViewContent::Chart => ControlBarSpec::Custom(vec![("Esc", "Back"), ("e", "Export")]),
MainViewContent::Home => ControlBarSpec::Custom(home_control_keys(
app.home.path_input_active,
app.home.browsing.is_some(),
!app.home.filter.is_empty(),
app.data_table_state.is_some(),
)),
}
}
pub fn home_control_keys(
path_input_active: bool,
browsing: bool,
has_filter: bool,
has_data: bool,
) -> Vec<(&'static str, &'static str)> {
let g = crate::glyphs::get();
let mut keys = vec![("Enter", "Open"), (g.updown, "Move")];
if path_input_active {
keys.push(("Esc", "Cancel"));
keys.push(("Tab", "Complete"));
} else {
keys.push((
"Esc",
if has_filter {
"Clear"
} else if browsing {
"Up"
} else if has_data {
"Back to data"
} else {
"Quit"
},
));
keys.push(("type", "Filter"));
keys.push(("~", "Path"));
if browsing {
keys.push(("Bksp", "Up"));
}
keys.push((g.updown_lr, "Fold"));
keys.push(("Tab", "Sort"));
}
if !keys.iter().any(|(_, label)| *label == "Quit") {
keys.push(("^C", "Quit"));
}
keys
}
#[cfg(test)]
mod tests {
use super::home_control_keys;
fn all_states() -> Vec<(bool, bool, bool, bool)> {
let mut out = Vec::new();
for path_input in [false, true] {
for browsing in [false, true] {
for filter in [false, true] {
for data in [false, true] {
out.push((path_input, browsing, filter, data));
}
}
}
}
out
}
#[test]
fn home_bar_never_advertises_bare_q_as_quit() {
for (p, b, f, d) in all_states() {
for (key, _) in home_control_keys(p, b, f, d) {
assert_ne!(
key, "q",
"bare `q` advertised in state (path={p}, browsing={b}, filter={f}, data={d})"
);
}
}
}
#[test]
fn home_bar_always_offers_a_way_out() {
for (p, b, f, d) in all_states() {
let keys = home_control_keys(p, b, f, d);
assert!(
keys.iter().any(|(_, label)| *label == "Quit"),
"no quit offered in state (path={p}, browsing={b}, filter={f}, data={d})"
);
}
}
#[test]
fn home_bar_leads_with_the_way_out() {
for (p, b, f, d) in all_states() {
let keys = home_control_keys(p, b, f, d);
let escape_at = keys
.iter()
.position(|(key, _)| *key == "Esc")
.expect("Esc is always offered");
assert!(
escape_at < 3,
"Esc is {escape_at} deep in state (path={p}, browsing={b}, filter={f}, data={d}); \
a narrow bar would cut it"
);
}
}
#[test]
fn home_bar_labels_esc_with_what_it_will_do() {
let esc = |p, b, f, d| {
home_control_keys(p, b, f, d)
.into_iter()
.find(|(key, _)| *key == "Esc")
.map(|(_, label)| label)
};
assert_eq!(esc(false, false, true, false), Some("Clear"));
assert_eq!(esc(false, true, false, false), Some("Up"));
assert_eq!(esc(false, false, false, true), Some("Back to data"));
assert_eq!(esc(false, false, false, false), Some("Quit"));
assert_eq!(esc(true, false, false, false), Some("Cancel"));
}
#[test]
fn home_bar_offers_up_only_while_browsing() {
let has_up = |b| {
home_control_keys(false, b, false, false)
.iter()
.any(|(_, label)| *label == "Up")
};
assert!(has_up(true));
assert!(!has_up(false));
}
}