quinjet 0.0.71

A fast, live, keyboard-first Git source-control interface for the terminal
#[cfg_attr(not(test), expect(clippy::wildcard_imports, reason = "shared"))]
use super::*;
use crate::git::github::{PullRequestStack, PullRequestStackMember};

pub(super) fn draw_compact_stack_strip(
    frame: &mut Frame<'_>,
    area: Rect,
    app: &mut App,
    stack: &PullRequestStack,
    theme: &Theme,
) -> Vec<SidebarHitArea> {
    frame.render_widget(
        section_separator_block("REVIEW PATH · p/n select · Shift+↑/↓ range", theme),
        Rect::new(area.x, area.y, area.width, area.height.min(1)),
    );
    let cursor = stack
        .members
        .iter()
        .position(|member| Some(member.position) == app.pull_request_stack_cursor)
        .unwrap_or_default();
    if app.sidebar_last_cursor != Some(cursor) {
        app.sidebar_free_scroll = false;
    }
    let start = if app.sidebar_free_scroll {
        let last = stack.members.len().saturating_sub(1);
        app.sidebar_offset
            .min(compact_stack_start(stack, last, area.width))
    } else {
        compact_stack_start(stack, cursor, area.width)
    };
    app.sidebar_offset = start;
    app.sidebar_last_cursor = Some(cursor);
    let mut x = area.x;
    let y = area.y.saturating_add(area.height.saturating_sub(1));
    let mut hits = Vec::new();
    for member in stack.members.iter().skip(start) {
        let label = compact_stack_label(member, stack);
        let width = cells(label.width()).min(area.right().saturating_sub(x));
        let row = Rect::new(x, y, width, 1);
        frame.render_widget(
            Paragraph::new(label).style(
                Style::default()
                    .fg(if Some(member.position) == app.pull_request_stack_cursor {
                        theme.text
                    } else {
                        theme.muted
                    })
                    .bg(if Some(member.position) == app.pull_request_stack_cursor {
                        theme.selected
                    } else {
                        theme.panel
                    }),
            ),
            row,
        );
        hits.push(SidebarHitArea {
            area: row,
            target: SidebarHit::PullRequestStackMember(member.position),
        });
        x = x.saturating_add(width);
        if x >= area.right() {
            break;
        }
    }
    hits
}

fn compact_stack_start(stack: &PullRequestStack, cursor: usize, width: u16) -> usize {
    let mut start = cursor.min(stack.members.len().saturating_sub(1));
    let mut used = stack
        .members
        .get(start)
        .map_or(0, |member| compact_stack_label(member, stack).width());
    while start > 0 {
        let previous = start.saturating_sub(1);
        let previous_width = stack
            .members
            .get(previous)
            .map_or(0, |member| compact_stack_label(member, stack).width());
        if used.saturating_add(previous_width) > usize::from(width) {
            break;
        }
        start = previous;
        used = used.saturating_add(previous_width);
    }
    start
}

fn compact_stack_label(member: &PullRequestStackMember, stack: &PullRequestStack) -> String {
    let tip = if stack.tip() == Some(member) {
        " TIP"
    } else {
        ""
    };
    format!(" {} #{}{tip} ", member.position, member.number)
}