quinjet 0.0.50

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_arguments,
    reason = "the branch picker needs the live worktree list from the session"
)]
pub(super) fn draw_branches(
    frame: &mut Frame<'_>,
    items: &[Branch],
    selected: usize,
    query: &crate::app::TextBuffer,
    loading: bool,
    app: &App,
    list: &mut ModalList<'_>,
    theme: &Theme,
) {
    let height = frame.area().height.saturating_sub(8).min(25);
    let area = centered_rect(
        frame.area().width.saturating_sub(12).min(76),
        height,
        frame.area(),
    );
    frame.render_widget(Clear, area);
    let block = modal_block(" Branches ", theme);
    let inner = block.inner(area);
    frame.render_widget(block, area);
    let query_area = Rect::new(inner.x, inner.y, inner.width, 1);
    frame.render_widget(
        Paragraph::new(Line::from(vec![
            Span::styled(" / ", Style::default().fg(theme.accent)),
            Span::styled(query.value.as_str(), Style::default().fg(theme.text)),
        ]))
        .style(Style::default().bg(theme.panel_alt)),
        query_area,
    );
    set_text_cursor(
        frame,
        Rect::new(
            query_area.x + 3,
            query_area.y,
            query_area.width.saturating_sub(3),
            1,
        ),
        query,
        false,
    );

    let visible = App::filtered_branches(items, &query.value);
    let list_area = Rect::new(
        inner.x,
        inner.y + 2,
        inner.width,
        inner.height.saturating_sub(4),
    );
    if loading {
        frame.render_widget(
            Paragraph::new("Loading branches…").style(Style::default().fg(theme.muted)),
            list_area,
        );
    } else {
        let offset = list.offset(selected, list_area.height as usize, visible.len());
        let lines = visible
            .iter()
            .skip(offset)
            .take(list_area.height as usize)
            .enumerate()
            .filter_map(|(visible_offset, index)| {
                let branch = items.get(*index)?;
                let cursor = offset + visible_offset;
                let style = if cursor == selected {
                    Style::default()
                        .bg(theme.selected)
                        .fg(theme.text)
                        .add_modifier(Modifier::BOLD)
                } else {
                    Style::default().fg(theme.text)
                };
                Some(Line::from(vec![
                    Span::styled(
                        if branch.current { "" } else { "   " },
                        Style::default()
                            .fg(if branch.current {
                                theme.success
                            } else {
                                theme.muted
                            })
                            .bg(style.bg.unwrap_or(theme.panel)),
                    ),
                    Span::styled(
                        truncate_middle(&branch.name, list_area.width.saturating_sub(30) as usize),
                        style,
                    ),
                    Span::styled(
                        {
                            let mut meta = format!(
                                "  {}  {}",
                                branch.short_id,
                                format_relative_timestamp(&branch.relative_date)
                            );
                            if let Some(path) = app.worktree_path_for_branch(&branch.name) {
                                meta.push_str("  ");
                                meta.push_str(&truncate_middle(&path.display().to_string(), 24));
                            }
                            meta
                        },
                        Style::default()
                            .fg(theme.muted)
                            .bg(style.bg.unwrap_or(theme.panel)),
                    ),
                ]))
            })
            .collect::<Vec<_>>();
        for index in 0..lines.len() {
            list.hit(
                Rect::new(
                    list_area.x,
                    list_area.y.saturating_add(cells(index)),
                    list_area.width,
                    1,
                ),
                offset.saturating_add(index),
            );
        }
        frame.render_widget(Paragraph::new(lines), list_area);
    }
    draw_modal_hint(
        frame,
        area,
        "Enter switch   Ctrl+n new   F2/Ctrl+r rename   Delete delete   Esc close",
        theme,
    );
}

#[expect(
    clippy::too_many_arguments,
    reason = "the picker receives its modal state, list geometry, and palette"
)]
pub(super) fn draw_history_branches(
    frame: &mut Frame<'_>,
    items: &[HistoryBranch],
    selected: usize,
    query: &crate::app::TextBuffer,
    loading: bool,
    list: &mut ModalList<'_>,
    theme: &Theme,
) {
    let height = frame.area().height.saturating_sub(8).min(25);
    let area = centered_rect(
        frame.area().width.saturating_sub(12).min(82),
        height,
        frame.area(),
    );
    frame.render_widget(Clear, area);
    let block = modal_block(" View Branch History — no checkout ", theme);
    let inner = block.inner(area);
    frame.render_widget(block, area);
    let query_area = Rect::new(inner.x, inner.y, inner.width, 1);
    frame.render_widget(
        Paragraph::new(Line::from(vec![
            Span::styled(" / ", Style::default().fg(theme.accent)),
            Span::styled(query.value.as_str(), Style::default().fg(theme.text)),
        ]))
        .style(Style::default().bg(theme.panel_alt)),
        query_area,
    );
    set_text_cursor(
        frame,
        Rect::new(
            query_area.x + 3,
            query_area.y,
            query_area.width.saturating_sub(3),
            1,
        ),
        query,
        false,
    );
    let list_area = Rect::new(
        inner.x,
        inner.y + 2,
        inner.width,
        inner.height.saturating_sub(4),
    );
    if loading {
        frame.render_widget(
            Paragraph::new("Loading local and remote-tracking branches…")
                .style(Style::default().fg(theme.muted)),
            list_area,
        );
    } else {
        let visible = App::filtered_history_branches(items, &query.value);
        let offset = list.offset(selected, list_area.height as usize, visible.len());
        let lines = visible
            .iter()
            .skip(offset)
            .take(list_area.height as usize)
            .enumerate()
            .filter_map(|(visible_offset, index)| {
                let branch = items.get(*index)?;
                let active = offset + visible_offset == selected;
                let background = if active { theme.selected } else { theme.panel };
                Some(Line::from(vec![
                    Span::styled(
                        if branch.current { "" } else { "   " },
                        Style::default()
                            .fg(if branch.current {
                                theme.success
                            } else {
                                theme.muted
                            })
                            .bg(background),
                    ),
                    Span::styled(
                        truncate_middle(&branch.name, list_area.width.saturating_sub(34) as usize),
                        Style::default()
                            .fg(theme.text)
                            .bg(background)
                            .add_modifier(if active {
                                Modifier::BOLD
                            } else {
                                Modifier::empty()
                            }),
                    ),
                    Span::styled(
                        format!(
                            "  {}  {}  {}",
                            if branch.remote { "remote" } else { "local" },
                            branch.short_id,
                            format_relative_timestamp(&branch.relative_date)
                        ),
                        Style::default().fg(theme.muted).bg(background),
                    ),
                ]))
            })
            .collect::<Vec<_>>();
        for index in 0..lines.len() {
            list.hit(
                Rect::new(
                    list_area.x,
                    list_area.y.saturating_add(cells(index)),
                    list_area.width,
                    1,
                ),
                offset.saturating_add(index),
            );
        }
        frame.render_widget(Paragraph::new(lines), list_area);
    }
    draw_modal_hint(
        frame,
        area,
        "Enter view history (HEAD and worktree stay unchanged)   Esc close",
        theme,
    );
}

#[expect(
    clippy::too_many_arguments,
    reason = "the picker receives its modal state, list geometry, and palette"
)]
pub(super) fn draw_compare_branches(
    frame: &mut Frame<'_>,
    items: &[HistoryBranch],
    selected: usize,
    query: &crate::app::TextBuffer,
    loading: bool,
    list: &mut ModalList<'_>,
    theme: &Theme,
) {
    let height = frame.area().height.saturating_sub(8).min(25);
    let area = centered_rect(
        frame.area().width.saturating_sub(12).min(82),
        height,
        frame.area(),
    );
    frame.render_widget(Clear, area);
    let block = modal_block(" Compare Current Branch With… ", theme);
    let inner = block.inner(area);
    frame.render_widget(block, area);
    let query_area = Rect::new(inner.x, inner.y, inner.width, 1);
    frame.render_widget(
        Paragraph::new(Line::from(vec![
            Span::styled(" / ", Style::default().fg(theme.accent)),
            Span::styled(query.value.as_str(), Style::default().fg(theme.text)),
        ]))
        .style(Style::default().bg(theme.panel_alt)),
        query_area,
    );
    set_text_cursor(
        frame,
        Rect::new(
            query_area.x + 3,
            query_area.y,
            query_area.width.saturating_sub(3),
            1,
        ),
        query,
        false,
    );
    let list_area = Rect::new(
        inner.x,
        inner.y + 2,
        inner.width,
        inner.height.saturating_sub(4),
    );
    if loading {
        frame.render_widget(
            Paragraph::new("Loading local and remote-tracking branches…")
                .style(Style::default().fg(theme.muted)),
            list_area,
        );
    } else {
        let visible = App::filtered_history_branches(items, &query.value)
            .into_iter()
            .filter(|index| items.get(*index).is_some_and(|item| !item.current))
            .collect::<Vec<_>>();
        let offset = list.offset(selected, list_area.height as usize, visible.len());
        let lines = visible
            .iter()
            .skip(offset)
            .take(list_area.height as usize)
            .enumerate()
            .filter_map(|(visible_offset, index)| {
                let branch = items.get(*index)?;
                let active = offset + visible_offset == selected;
                let background = if active { theme.selected } else { theme.panel };
                Some(Line::from(vec![
                    Span::styled(
                        if active { "" } else { "   " },
                        Style::default().fg(theme.accent).bg(background),
                    ),
                    Span::styled(
                        truncate_middle(&branch.name, list_area.width.saturating_sub(34) as usize),
                        Style::default()
                            .fg(theme.text)
                            .bg(background)
                            .add_modifier(if active {
                                Modifier::BOLD
                            } else {
                                Modifier::empty()
                            }),
                    ),
                    Span::styled(
                        format!(
                            "  {}  {}  {}",
                            if branch.remote { "remote" } else { "local" },
                            branch.short_id,
                            format_relative_timestamp(&branch.relative_date)
                        ),
                        Style::default().fg(theme.muted).bg(background),
                    ),
                ]))
            })
            .collect::<Vec<_>>();
        for index in 0..lines.len() {
            list.hit(
                Rect::new(
                    list_area.x,
                    list_area.y.saturating_add(cells(index)),
                    list_area.width,
                    1,
                ),
                offset.saturating_add(index),
            );
        }
        frame.render_widget(Paragraph::new(lines), list_area);
        if visible.is_empty() {
            frame.render_widget(
                Paragraph::new("No other branches match this filter")
                    .style(Style::default().fg(theme.muted)),
                list_area,
            );
        }
    }
    draw_modal_hint(
        frame,
        area,
        "Enter calculate diff against HEAD   Esc close",
        theme,
    );
}