gitstack 5.3.0

Git history viewer with insights - Author stats, file heatmap, code ownership
Documentation
/// A struct providing common state and operations for list navigation
#[derive(Debug, Clone)]
pub struct ListNavigation {
    pub selected_index: usize,
    pub scroll_offset: usize,
}

impl ListNavigation {
    pub fn new() -> Self {
        Self {
            selected_index: 0,
            scroll_offset: 0,
        }
    }

    /// Move selection up (also adjusts scroll offset)
    pub fn move_up(&mut self) {
        if self.selected_index > 0 {
            self.selected_index -= 1;
            if self.selected_index < self.scroll_offset {
                self.scroll_offset = self.selected_index;
            }
        }
    }

    /// Move selection down
    pub fn move_down(&mut self, max_len: usize) {
        if max_len > 0 && self.selected_index < max_len - 1 {
            self.selected_index += 1;
        }
    }

    /// Adjust scroll offset based on visible line count
    pub fn adjust_scroll(&mut self, visible_lines: usize) {
        if visible_lines == 0 {
            return;
        }
        if self.selected_index >= self.scroll_offset + visible_lines {
            self.scroll_offset = self.selected_index - visible_lines + 1;
        }
    }

    /// Reset selection and scroll
    pub fn reset(&mut self) {
        self.selected_index = 0;
        self.scroll_offset = 0;
    }
}

impl Default for ListNavigation {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn test_new_initializes_to_zero() {
        let nav = ListNavigation::new();
        assert_eq!(nav.selected_index, 0);
        assert_eq!(nav.scroll_offset, 0);
    }

    #[test]
    fn test_move_up_decrements_index() {
        let mut nav = ListNavigation::new();
        nav.selected_index = 3;
        nav.move_up();
        assert_eq!(nav.selected_index, 2);
    }

    #[test]
    fn test_move_up_does_not_go_below_zero() {
        let mut nav = ListNavigation::new();
        nav.move_up();
        assert_eq!(nav.selected_index, 0);
    }

    #[test]
    fn test_move_up_adjusts_scroll_offset() {
        let mut nav = ListNavigation::new();
        nav.selected_index = 5;
        nav.scroll_offset = 5;
        nav.move_up();
        assert_eq!(nav.selected_index, 4);
        assert_eq!(nav.scroll_offset, 4);
    }

    #[test]
    fn test_move_down_increments_index() {
        let mut nav = ListNavigation::new();
        nav.move_down(5);
        assert_eq!(nav.selected_index, 1);
    }

    #[test]
    fn test_move_down_does_not_exceed_max() {
        let mut nav = ListNavigation::new();
        nav.selected_index = 4;
        nav.move_down(5);
        assert_eq!(nav.selected_index, 4);
    }

    #[test]
    fn test_move_down_empty_list() {
        let mut nav = ListNavigation::new();
        nav.move_down(0);
        assert_eq!(nav.selected_index, 0);
    }

    #[test]
    fn test_adjust_scroll_scrolls_down() {
        let mut nav = ListNavigation::new();
        nav.selected_index = 10;
        nav.adjust_scroll(5);
        assert_eq!(nav.scroll_offset, 6);
    }

    #[test]
    fn test_adjust_scroll_zero_visible() {
        let mut nav = ListNavigation::new();
        nav.selected_index = 10;
        nav.adjust_scroll(0);
        assert_eq!(nav.scroll_offset, 0);
    }

    #[test]
    fn test_reset() {
        let mut nav = ListNavigation::new();
        nav.selected_index = 5;
        nav.scroll_offset = 3;
        nav.reset();
        assert_eq!(nav.selected_index, 0);
        assert_eq!(nav.scroll_offset, 0);
    }
}