quinjet 0.0.52

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

fn ahead_label(count: usize) -> String {
    format!(" ahead {count}")
}

fn behind_label(count: usize) -> String {
    format!(" behind {count}")
}

pub(crate) fn status(status: &RepoStatus) -> String {
    let mut out = Report::default();
    let branch = &status.branch;
    let head = if branch.detached {
        format!("HEAD detached at {}", branch.head)
    } else {
        format!("On branch {}", branch.head)
    };
    out.line(&head);
    if let Some(upstream) = &branch.upstream {
        let mut divergence = String::new();
        if branch.ahead > 0 {
            divergence.push_str(&ahead_label(branch.ahead));
        }
        if branch.behind > 0 {
            divergence.push_str(&behind_label(branch.behind));
        }
        out.line(&format!("Tracking {upstream}{divergence}"));
    }
    if status.changes.is_empty() {
        out.line("\nWorking tree clean");
        return out.finish();
    }
    for area in [
        ChangeArea::Conflict,
        ChangeArea::Staged,
        ChangeArea::Unstaged,
    ] {
        let changes: Vec<_> = status
            .changes
            .iter()
            .filter(|change| change.area == area)
            .collect();
        if changes.is_empty() {
            continue;
        }
        out.line(&format!("\n{} ({})", area.label(), changes.len()));
        for change in changes {
            let rename = change
                .original_path
                .as_ref()
                .map(|from| format!(" (from {})", from.display()))
                .unwrap_or_default();
            out.line(&format!(
                "  {:<3} {}{rename}",
                change.status.code(),
                change.display_path()
            ));
        }
    }
    out.finish()
}

pub(crate) fn diff(document: &DiffDocument) -> String {
    let mut out = Report::default();
    for line in &document.lines {
        let text = line.text();
        match line.kind {
            DiffLineKind::FileHeader => {
                if !out.empty() {
                    out.blank();
                }
                out.line(&text);
            }
            DiffLineKind::FileFooter => {}
            DiffLineKind::HunkHeader | DiffLineKind::Meta | DiffLineKind::Review => {
                out.line(&text);
            }
            DiffLineKind::Added => {
                out.line(&format!("+{text}"));
            }
            DiffLineKind::Removed => {
                out.line(&format!("-{text}"));
            }
            DiffLineKind::Context => {
                out.line(&format!(" {text}"));
            }
        }
    }
    if document.truncated {
        out.line("\n[output reached Quinjet's size cap and was truncated]");
    }
    out.finish()
}

pub(crate) fn history(commits: &[Commit]) -> String {
    let mut out = Report::default();
    for commit in commits {
        let decorations = if commit.decorations.is_empty() {
            String::new()
        } else {
            format!("  ({})", commit.decorations.join(", "))
        };
        out.line(&format!(
            "{}  {}  {:<16}  {}{decorations}",
            commit.short_id,
            format_local_timestamp(&commit.authored_at),
            truncate(&commit.author, 16),
            commit.subject
        ));
    }
    out.finish()
}

pub(crate) fn commit(commit: &Commit) -> String {
    let mut out = Report::default();
    out.line(&format!("commit {}", commit.id));
    if commit.parent_ids.len() > 1 {
        out.line(&format!("Merge:  {}", commit.parent_ids.join(" ")));
    }
    out.line(&format!(
        "Author: {} <{}>",
        commit.author, commit.author_email
    ));
    out.line(&format!(
        "Date:   {}",
        format_local_timestamp(&commit.authored_at)
    ));
    out.line(&format!("\n    {}\n", commit.subject));
    out.finish()
}

pub(crate) fn branches(branches: &[Branch]) -> String {
    let mut out = Report::default();
    for branch in branches {
        let upstream = branch
            .upstream
            .as_ref()
            .map(|upstream| format!("  -> {upstream}"))
            .unwrap_or_default();
        out.line(&format!(
            "{} {:<28} {:<10} {}{upstream}",
            if branch.current { "*" } else { " " },
            branch.name,
            branch.short_id,
            format_local_timestamp(&branch.relative_date)
        ));
    }
    out.finish()
}

pub(crate) fn history_branches(branches: &[HistoryBranch]) -> String {
    let mut out = Report::default();
    for branch in branches {
        out.line(&format!(
            "{} {:<40} {:<8} {:<10} {}",
            if branch.current { "*" } else { " " },
            branch.name,
            if branch.remote { "remote" } else { "local" },
            branch.short_id,
            format_local_timestamp(&branch.relative_date)
        ));
    }
    out.finish()
}

pub(crate) fn stashes(stashes: &[Stash]) -> String {
    let mut out = Report::default();
    for stash in stashes {
        out.line(&format!(
            "{:<12} {:<10} {:<14} on {}: {}",
            stash.reference,
            stash.short_id,
            format_local_timestamp(&stash.relative_date),
            stash.branch,
            stash.message
        ));
    }
    out.finish()
}

pub(crate) fn worktrees(worktrees: &[Worktree]) -> String {
    let mut out = Report::default();
    for worktree in worktrees {
        let branch = worktree.branch_label();
        let head = if worktree.head.is_empty() {
            "-"
        } else {
            worktree.short_head()
        };
        let mut flags = String::new();
        if worktree.locked.is_some() {
            flags.push_str("  locked");
        }
        if worktree.prunable.is_some() {
            flags.push_str("  prunable");
        }
        if let Some(updated_at) = worktree.updated_at.as_deref() {
            flags.push_str("  ");
            flags.push_str(&format_relative_timestamp(updated_at));
        }
        out.line(&format!(
            "{} {}  {:<16}  {head}{flags}",
            if worktree.current { "*" } else { " " },
            worktree.path.display(),
            branch
        ));
    }
    out.finish()
}

pub(crate) fn projects(projects: &[ProjectGroup]) -> String {
    let mut out = Report::default();
    for project in projects {
        out.line(&format!(
            "{}  {}",
            project.name,
            project.common_dir.display()
        ));
        for worktree in &project.worktrees {
            out.line(&format!(
                "  {}  {:<16}  {}",
                worktree.path.display(),
                worktree.branch_label(),
                worktree.short_head()
            ));
        }
    }
    out.finish()
}