use anyhow::Result;
use ratada::nav;
use crate::domain::quantity::Quantity;
use crate::keymap::Action;
use crate::tui::app::App;
use crate::tui::interaction::Interaction;
use crate::tui::views::ViewId;
use crate::tui::views::calc::Mode;
impl App {
pub(super) fn apply_variables_action(
&mut self,
action: Action,
io: &mut dyn Interaction,
) -> Result<bool> {
let total = self.service.variables().len();
match action {
Action::Up => {
self.var_selected = nav::cycle(self.var_selected, total, -1);
}
Action::Down => {
self.var_selected = nav::cycle(self.var_selected, total, 1);
}
Action::PageUp => {
self.var_selected =
self.page_step(self.var_selected, total, -1);
}
Action::PageDown => {
self.var_selected = self.page_step(self.var_selected, total, 1);
}
Action::Top => self.var_selected = 0,
Action::Bottom => self.var_selected = total.saturating_sub(1),
Action::InsertVariable => self.insert_variable(),
Action::CopyPlain => self.copy_variable(false),
Action::CopyDisplay => self.copy_variable(true),
Action::DeleteVariable => self.delete_variable(),
Action::ResetVariables => self.reset_variables(io)?,
Action::Back => self.active = ViewId::Calc,
_ => return Ok(false),
}
Ok(true)
}
fn selected_variable(&self) -> Option<(String, Quantity)> {
self.service
.variables()
.iter()
.nth(self.var_selected)
.map(|(name, value)| (name.clone(), value.clone()))
}
fn insert_variable(&mut self) {
let Some((name, _)) = self.selected_variable() else {
return;
};
self.replace_selection(&name);
self.active = ViewId::Calc;
self.mode = Mode::Input;
self.report(format!("inserted {name}"));
}
fn copy_variable(&mut self, as_displayed: bool) {
let Some((_, value)) = self.selected_variable() else {
return;
};
if as_displayed {
self.copy_display(&value);
} else {
self.copy_plain(&value);
}
}
fn delete_variable(&mut self) {
let Some((name, _)) = self.selected_variable() else {
return;
};
self.service.remove_variable(&name);
let total = self.service.variables().len();
self.var_selected = self.var_selected.min(total.saturating_sub(1));
self.report(format!("removed {name}"));
}
fn reset_variables(&mut self, io: &mut dyn Interaction) -> Result<()> {
if self.service.variables().is_empty() {
return Ok(());
}
if !self.confirm(io, " Reset all variables? ")? {
return Ok(());
}
self.service.reset_variables();
self.var_selected = 0;
self.report("variables reset".to_string());
Ok(())
}
}
#[cfg(test)]
mod tests {
use crossterm::event::KeyCode;
use super::*;
use crate::tui::app::testing::{
key, press, render_to_string, submit, test_app,
};
#[test]
fn variables_view_lists_and_resets_after_confirmation() {
let mut app = test_app();
submit(&mut app, "7");
submit(&mut app, "=x");
assert_eq!(
app.service()
.variables()
.get("x")
.map(Quantity::display_value),
Some(7.0),
);
press(&mut app, key(KeyCode::F(4)));
assert_eq!(app.active, ViewId::Variables);
assert!(render_to_string(&app, 80, 24).contains("x = 7"));
press(&mut app, key(KeyCode::Char('R')));
assert_eq!(app.service().variables().len(), 0);
}
#[test]
fn inserting_a_variable_returns_to_the_calc_view() {
let mut app = test_app();
submit(&mut app, "7");
submit(&mut app, "=x");
press(&mut app, key(KeyCode::F(4)));
press(&mut app, key(KeyCode::Enter));
assert_eq!(app.active, ViewId::Calc);
assert_eq!(app.mode, Mode::Input);
assert_eq!(app.input, "x");
}
#[test]
fn page_keys_move_the_selection_in_the_variables_list() {
let mut app = test_app();
for index in 0..30 {
submit(&mut app, &format!("{index}"));
submit(&mut app, &format!("=v{index:02}"));
}
press(&mut app, key(KeyCode::F(4)));
render_to_string(&app, 80, 12);
assert_eq!(app.var_selected, 0);
press(&mut app, key(KeyCode::PageDown));
let after_page_down = app.var_selected;
assert!(after_page_down > 0, "PageDown must move the selection");
press(&mut app, key(KeyCode::PageUp));
assert!(
app.var_selected < after_page_down,
"PageUp must move it back",
);
}
#[test]
fn a_page_jump_stops_at_the_ends_instead_of_wrapping() {
let mut app = test_app();
submit(&mut app, "1");
submit(&mut app, "=a");
submit(&mut app, "2");
submit(&mut app, "=b");
press(&mut app, key(KeyCode::F(4)));
render_to_string(&app, 80, 12);
press(&mut app, key(KeyCode::PageUp));
assert_eq!(app.var_selected, 0, "clamped at the top");
press(&mut app, key(KeyCode::PageDown));
press(&mut app, key(KeyCode::PageDown));
assert_eq!(app.var_selected, 1, "clamped at the bottom");
}
#[test]
fn an_overlong_variable_row_is_truncated_with_an_ellipsis() {
let mut app = test_app();
submit(&mut app, "123456789012345678901234567890");
submit(&mut app, "=verylongvariablename");
press(&mut app, key(KeyCode::F(4)));
let screen = render_to_string(&app, 24, 12);
assert!(screen.contains('\u{2026}'), "got: {screen}");
}
#[test]
fn esc_leaves_a_list_view_for_the_calc_view() {
let mut app = test_app();
press(&mut app, key(KeyCode::F(4)));
press(&mut app, key(KeyCode::Esc));
assert_eq!(app.active, ViewId::Calc);
}
}