use std::collections::HashSet;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use super::model::{filter_indices, sort_indices};
use super::{FilterScope, SelectMode, SortDir, Table, TableAction};
use crate::nav;
impl Table {
pub fn handle_key(&mut self, key: KeyEvent) -> TableAction {
if self.filtering {
self.handle_filter_key(key);
return TableAction::None;
}
let shift = key.modifiers.contains(KeyModifiers::SHIFT);
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
let page = self.viewport.get().max(1) as isize;
let last = self.view.len().saturating_sub(1);
match key.code {
KeyCode::Char('a') if ctrl => self.select_all(),
KeyCode::Up | KeyCode::Char('k') => self.move_rows(-1, shift),
KeyCode::Down | KeyCode::Char('j') => self.move_rows(1, shift),
KeyCode::PageUp => self.move_rows(-page, shift),
KeyCode::PageDown => self.move_rows(page, shift),
KeyCode::Home | KeyCode::Char('g') => self.move_to(0, shift),
KeyCode::End | KeyCode::Char('G') => self.move_to(last, shift),
KeyCode::Left | KeyCode::Char('h') => self.move_col(-1, shift),
KeyCode::Right | KeyCode::Char('l') => self.move_col(1, shift),
KeyCode::Char(' ') => self.toggle_current(),
KeyCode::Char('s') => self.sort_by_active(),
KeyCode::Char('f') => self.toggle_filter_scope(),
KeyCode::Char('m') => self.toggle_mode(),
KeyCode::Char('/') => self.filtering = true,
KeyCode::Esc => self.clear_selection(),
KeyCode::Enter => return TableAction::Activate,
_ => {}
}
TableAction::None
}
pub fn is_filtering(&self) -> bool {
self.filtering
}
fn handle_filter_key(&mut self, key: KeyEvent) {
match key.code {
KeyCode::Esc => {
self.filtering = false;
self.filter.clear();
self.rebuild_view();
}
KeyCode::Enter => self.filtering = false,
KeyCode::Backspace => {
self.filter.pop();
self.rebuild_view();
}
KeyCode::Char(ch) => {
self.filter.push(ch);
self.rebuild_view();
}
_ => {}
}
}
fn move_rows(&mut self, delta: isize, shift: bool) {
let target = nav::step_clamped(self.cursor, self.view.len(), delta);
self.move_to(target, shift);
}
fn move_to(&mut self, target: usize, shift: bool) {
if self.view.is_empty() {
return;
}
let prev = self.cursor;
self.cursor = target.min(self.view.len() - 1);
self.after_move(prev, shift);
}
fn move_col(&mut self, delta: isize, shift: bool) {
if self.columns.is_empty() {
return;
}
let prev_cell = self.cursor_cell();
self.active_col =
nav::step_clamped(self.active_col, self.columns.len(), delta);
if shift && self.mode == SelectMode::Cell {
if let Some(cell) = prev_cell {
self.selected_cells.insert(cell);
}
if let Some(cell) = self.cursor_cell() {
self.selected_cells.insert(cell);
}
}
}
fn after_move(&mut self, prev: usize, shift: bool) {
if !shift {
self.anchor = self.cursor;
return;
}
match self.mode {
SelectMode::Row => {
let (lo, hi) = minmax(self.anchor, self.cursor);
for view_idx in lo..=hi {
self.selected_rows.insert(self.view[view_idx]);
}
}
SelectMode::Cell => {
for view_idx in [prev, self.cursor] {
self.selected_cells
.insert((self.view[view_idx], self.active_col));
}
}
}
}
fn toggle_current(&mut self) {
match self.mode {
SelectMode::Row => {
if let Some(row) = self.cursor_row() {
toggle(&mut self.selected_rows, row);
}
}
SelectMode::Cell => {
if let Some(cell) = self.cursor_cell() {
toggle(&mut self.selected_cells, cell);
}
}
}
}
fn select_all(&mut self) {
match self.mode {
SelectMode::Row => {
self.selected_rows = self.view.iter().copied().collect();
}
SelectMode::Cell => {
self.selected_cells = self
.view
.iter()
.flat_map(|&row| {
(0..self.columns.len()).map(move |col| (row, col))
})
.collect();
}
}
}
fn clear_selection(&mut self) {
self.selected_rows.clear();
self.selected_cells.clear();
}
fn toggle_mode(&mut self) {
self.mode = match self.mode {
SelectMode::Row => SelectMode::Cell,
SelectMode::Cell => SelectMode::Row,
};
self.clear_selection();
}
fn toggle_filter_scope(&mut self) {
self.filter_scope = match self.filter_scope {
FilterScope::AllColumns => FilterScope::ActiveColumn,
FilterScope::ActiveColumn => FilterScope::AllColumns,
};
self.rebuild_view();
}
fn sort_by_active(&mut self) {
let dir = match self.sort {
Some((col, SortDir::Asc)) if col == self.active_col => {
SortDir::Desc
}
_ => SortDir::Asc,
};
self.sort = Some((self.active_col, dir));
self.rebuild_view();
}
fn rebuild_view(&mut self) {
let keep = self.cursor_row();
let mut view = filter_indices(
&self.rows,
&self.filter,
self.filter_scope,
self.active_col,
);
if let Some((col, dir)) = self.sort {
view = sort_indices(&self.rows, &self.columns, view, col, dir);
}
self.view = view;
self.cursor = keep
.and_then(|row| self.view.iter().position(|&r| r == row))
.unwrap_or(0)
.min(self.view.len().saturating_sub(1));
self.anchor = self.cursor;
}
}
fn toggle<T: Eq + std::hash::Hash>(set: &mut HashSet<T>, value: T) {
if !set.remove(&value) {
set.insert(value);
}
}
fn minmax(a: usize, b: usize) -> (usize, usize) {
if a <= b { (a, b) } else { (b, a) }
}