laziergit 1.1.0

an even lazier git implementation than lazygit
use crate::app::{App, Focus};
use crate::layout;
use ratatui::layout::Rect;

const SCROLL_STEP: i32 = 3;

impl App {
    pub fn set_area(&mut self, area: Rect) {
        self.area = area;
        let max = self.max_diff_scroll();
        if self.diff_scroll > max {
            self.diff_scroll = max;
        }
        self.ensure_visible();
    }

    pub fn push_digit(&mut self, digit: u32) {
        if digit == 0 && self.count.is_none() {
            return;
        }
        let next = self
            .count
            .unwrap_or(0)
            .saturating_mul(10)
            .saturating_add(digit);
        self.count = Some(next.min(99_999));
    }

    pub fn take_count(&mut self) -> i32 {
        self.count.take().unwrap_or(1).max(1) as i32
    }

    pub fn clear_pending(&mut self) {
        self.count = None;
        self.awaiting_g = false;
    }

    pub fn pending_label(&self) -> Option<String> {
        if let Some(count) = self.count {
            Some(count.to_string())
        } else if self.awaiting_g {
            Some("g".to_string())
        } else {
            None
        }
    }

    pub fn press_g(&mut self) {
        if self.awaiting_g {
            self.awaiting_g = false;
            let row = self.count.take();
            self.go_to_line(row, false);
        } else {
            self.awaiting_g = true;
        }
    }

    pub fn go_bottom(&mut self) {
        let row = self.count.take();
        self.go_to_line(row, true);
    }

    pub fn move_selection(&mut self, delta: i32) {
        if self.rows.is_empty() {
            return;
        }
        let last = self.rows.len() as i32 - 1;
        let target = (self.selected as i32 + delta).clamp(0, last) as usize;
        self.set_selected(target);
    }

    pub fn set_selected(&mut self, index: usize) {
        if self.rows.is_empty() {
            return;
        }
        let clamped = index.min(self.rows.len() - 1);
        if clamped != self.selected {
            self.selected = clamped;
            self.reload_diff();
        }
        self.ensure_visible();
    }

    pub fn scroll_diff(&mut self, lines: i32) {
        let max = self.max_diff_scroll() as i32;
        self.diff_scroll = (self.diff_scroll as i32 + lines).clamp(0, max) as u16;
    }

    pub fn wheel_step(&self) -> i32 {
        SCROLL_STEP
    }

    pub fn ensure_visible(&mut self) {
        let height = layout::regions(self.area).sidebar_inner.height as usize;
        if height == 0 || self.rows.is_empty() {
            return;
        }
        if self.selected < self.sidebar_offset {
            self.sidebar_offset = self.selected;
        } else if self.selected >= self.sidebar_offset + height {
            self.sidebar_offset = self.selected + 1 - height;
        }
        let max_offset = self.rows.len().saturating_sub(height);
        self.sidebar_offset = self.sidebar_offset.min(max_offset);
    }

    pub fn max_diff_scroll(&self) -> u16 {
        let height = layout::regions(self.area).diff_inner.height as usize;
        self.diff_rows.len().saturating_sub(height) as u16
    }

    fn go_to_line(&mut self, row: Option<u32>, default_bottom: bool) {
        match self.focus {
            Focus::Sidebar => {
                let last = self.rows.len().saturating_sub(1);
                let target = match row {
                    Some(r) => (r as usize).saturating_sub(1).min(last),
                    None if default_bottom => last,
                    None => 0,
                };
                self.set_selected(target);
            }
            Focus::Diff => {
                let max = self.max_diff_scroll();
                self.diff_scroll = match row {
                    Some(r) => (r.saturating_sub(1) as u16).min(max),
                    None if default_bottom => max,
                    None => 0,
                };
            }
        }
    }
}