quinjet 0.0.47

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

#[expect(
    clippy::too_many_lines,
    reason = "the draw pass reads better as one top-to-bottom pass"
)]
pub(super) fn draw_history_sidebar(
    frame: &mut Frame<'_>,
    area: Rect,
    app: &mut App,
    theme: &Theme,
    link_hits: &mut Vec<LinkHit>,
) -> Vec<SidebarHitArea> {
    let title = if app.filter.is_empty() {
        format!(
            " History · {}  {}{}  [b branch] ",
            app.history_branch_label(),
            app.history.len(),
            if app.history_complete { "" } else { "+" }
        )
    } else {
        format!(
            " History · {}  /{} ",
            app.history_branch_label(),
            app.filter
        )
    };
    let block = panel_block(
        title,
        app.focus == Focus::Sidebar && app.modal.is_none(),
        theme,
    );
    let inner = block.inner(area);
    frame.render_widget(block, area);
    if inner.height == 0 {
        return Vec::new();
    }

    let visible = app.visible_commit_indices();
    let height = usize::from(inner.height).div_euclid(2);
    app.sidebar_viewport(app.history_cursor, height, visible.len());
    let mut hits = Vec::new();
    let end = (app.sidebar_offset + height).min(visible.len());
    for (row_offset, index) in visible
        .iter()
        .take(end)
        .skip(app.sidebar_offset)
        .enumerate()
    {
        let cursor = app.sidebar_offset + row_offset;
        let Some(commit) = app.history.get(*index) else {
            continue;
        };
        let selected = cursor == app.history_cursor;
        let y = inner.y + cells(row_offset.saturating_mul(2));
        let row_style = Style::default().bg(if selected {
            theme.selected
        } else {
            theme.panel
        });
        let graph = history_glyph(commit, cursor);
        let badge = commit
            .decorations
            .first()
            .map(|decoration| format!("  {}", clean_decoration(decoration)))
            .unwrap_or_default();
        let reserved = 3 + graph.width() + badge.width();
        let subject = truncate_middle(
            &commit.subject,
            usize::from(inner.width).saturating_sub(reserved + 1),
        );
        let subject_line = Line::from(vec![
            Span::styled(
                if selected { "" } else { "   " },
                Style::default().fg(theme.accent),
            ),
            Span::styled(graph, Style::default().fg(graph_color(cursor, theme))),
            Span::styled(
                subject,
                Style::default().fg(theme.text).add_modifier(if selected {
                    Modifier::BOLD
                } else {
                    Modifier::empty()
                }),
            ),
            Span::styled(badge, Style::default().fg(theme.modified)),
        ]);
        let row_area = Rect::new(inner.x, y, inner.width.saturating_sub(1), 2);
        frame.render_widget(
            Paragraph::new(subject_line).style(row_style),
            Rect::new(row_area.x, row_area.y, row_area.width, 1),
        );
        let metadata_x = inner.x.saturating_add(3);
        let metadata_width = usize::from(inner.width.saturating_sub(4));
        let age = truncate_end(
            &format_relative_timestamp(&commit.committed_at),
            metadata_width.saturating_sub(commit.short_id.width() + 1),
        );
        let gap = metadata_width
            .saturating_sub(commit.short_id.width())
            .saturating_sub(age.width());
        let sha_area = Rect::new(
            metadata_x,
            y.saturating_add(1),
            cells(commit.short_id.width()),
            1,
        );
        frame.render_widget(
            Paragraph::new(Line::from(vec![
                link_span(
                    commit.short_id.clone(),
                    app.commit_open_target(&commit.id),
                    sha_area,
                    theme,
                    link_hits,
                ),
                Span::raw(" ".repeat(gap)),
                Span::styled(age, Style::default().fg(theme.muted)),
            ]))
            .style(row_style),
            Rect::new(metadata_x, y.saturating_add(1), cells(metadata_width), 1),
        );
        hits.push(SidebarHitArea {
            area: row_area,
            target: SidebarHit::Commit(*index),
        });
    }

    draw_scrollbar(frame, inner, app.sidebar_offset, visible.len(), theme);

    if visible.is_empty() {
        frame.render_widget(
            Paragraph::new(if app.history_loading {
                "\n  Loading commit history…"
            } else if app.history.is_empty() {
                "\n  No commits yet"
            } else {
                "\n  No commits match this filter"
            })
            .style(Style::default().fg(theme.muted)),
            inner,
        );
    } else if app.history_loading && inner.height > 1 {
        let area = Rect::new(inner.x, inner.bottom().saturating_sub(1), inner.width, 1);
        frame.render_widget(
            Paragraph::new("  Loading more commits…")
                .style(Style::default().fg(theme.accent).bg(theme.panel_alt)),
            area,
        );
    }
    hits
}