snip-it 1.3.3

Fast terminal snippet manager with fuzzy search, TUI, variable expansion, and end-to-end encrypted cross-device sync
Documentation
use ratatui::widgets::ScrollbarState;

#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(super) enum SortMode {
    #[default]
    None,
    Newest,
    Oldest,
    AlphaAsc,
    AlphaDesc,
}

#[derive(Clone, Default)]
pub(super) struct SelectState {
    pub selected: usize,
    pub list_state: ratatui::widgets::ListState,
    pub scroll_state: ScrollbarState,
}

impl SelectState {
    pub fn new() -> Self {
        let mut list_state = ratatui::widgets::ListState::default();
        list_state.select(Some(0));
        SelectState {
            selected: 0,
            list_state,
            scroll_state: ScrollbarState::default(),
        }
    }

    pub fn update(&mut self, filtered_len: usize) {
        if filtered_len == 0 {
            self.selected = 0;
        } else if self.selected >= filtered_len {
            self.selected = filtered_len.saturating_sub(1);
        }
        self.list_state.select(Some(self.selected));
        self.scroll_state = self
            .scroll_state
            .content_length(filtered_len)
            .position(self.selected);
    }

    pub fn move_up(&mut self) {
        self.selected = self.selected.saturating_sub(1);
    }

    pub fn move_down(&mut self, max: usize) {
        if self.selected + 1 < max {
            self.selected += 1;
        }
    }

    pub fn move_to_top(&mut self) {
        self.selected = 0;
    }

    pub fn move_to_bottom(&mut self, max: usize) {
        self.selected = max.saturating_sub(1);
    }
}

#[derive(Clone, Default)]
pub(super) struct FilterState {
    pub sort_mode: SortMode,
    pub tag_filter_text: String,
}

impl FilterState {
    pub fn toggle_sort_new(&mut self) {
        self.sort_mode = if self.sort_mode == SortMode::Newest {
            SortMode::None
        } else {
            SortMode::Newest
        };
    }
    pub fn toggle_sort_old(&mut self) {
        self.sort_mode = if self.sort_mode == SortMode::Oldest {
            SortMode::None
        } else {
            SortMode::Oldest
        };
    }
    pub fn toggle_sort_alpha(&mut self) {
        self.sort_mode = if self.sort_mode == SortMode::AlphaAsc {
            SortMode::None
        } else {
            SortMode::AlphaAsc
        };
    }
    pub fn toggle_sort_alpha_rev(&mut self) {
        self.sort_mode = if self.sort_mode == SortMode::AlphaDesc {
            SortMode::None
        } else {
            SortMode::AlphaDesc
        };
    }
}

pub(super) fn is_ctrl_key(key: &crossterm::event::KeyEvent, c: char) -> bool {
    key.code == crossterm::event::KeyCode::Char(c)
        && key
            .modifiers
            .contains(crossterm::event::KeyModifiers::CONTROL)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_filter_state_toggle_sort_new() {
        let mut state = FilterState::default();
        assert_eq!(state.sort_mode, SortMode::None);

        state.toggle_sort_new();
        assert_eq!(state.sort_mode, SortMode::Newest);

        state.toggle_sort_new();
        assert_eq!(state.sort_mode, SortMode::None);
    }

    #[test]
    fn test_filter_state_toggle_sort_alpha() {
        let mut state = FilterState::default();
        assert_eq!(state.sort_mode, SortMode::None);

        state.toggle_sort_alpha();
        assert_eq!(state.sort_mode, SortMode::AlphaAsc);

        state.toggle_sort_alpha_rev();
        assert_eq!(state.sort_mode, SortMode::AlphaDesc);
    }

    #[test]
    fn test_is_ctrl_key_true() {
        let key = crossterm::event::KeyEvent {
            code: crossterm::event::KeyCode::Char('c'),
            modifiers: crossterm::event::KeyModifiers::CONTROL,
            kind: crossterm::event::KeyEventKind::Press,
            state: crossterm::event::KeyEventState::NONE,
        };
        assert!(is_ctrl_key(&key, 'c'));
    }

    #[test]
    fn test_is_ctrl_key_false_no_modifier() {
        let key = crossterm::event::KeyEvent {
            code: crossterm::event::KeyCode::Char('c'),
            modifiers: crossterm::event::KeyModifiers::empty(),
            kind: crossterm::event::KeyEventKind::Press,
            state: crossterm::event::KeyEventState::NONE,
        };
        assert!(!is_ctrl_key(&key, 'c'));
    }

    #[test]
    fn test_is_ctrl_key_false_different_char() {
        let key = crossterm::event::KeyEvent {
            code: crossterm::event::KeyCode::Char('a'),
            modifiers: crossterm::event::KeyModifiers::CONTROL,
            kind: crossterm::event::KeyEventKind::Press,
            state: crossterm::event::KeyEventState::NONE,
        };
        assert!(!is_ctrl_key(&key, 'c'));
    }
}