use super::{Attempt, Change, Churn, FileChurn, WorkFile};
use crate::files_view::{PREVIEW_CAP, Preview};
pub fn render(
ui: &mut egui::Ui,
attempts: &[Attempt],
patch: Option<&Preview>,
sel: &mut Option<WorkFile>,
) {
if attempts.is_empty() {
ui.weak(
"this workspace holds no ball, so it owes no work anywhere — nothing to compare. \
Claim one and its changes show up here.",
);
return;
}
egui::ScrollArea::vertical().show(ui, |ui| {
for attempt in attempts {
render_attempt(ui, attempt, sel);
ui.separator();
}
render_patch(ui, patch);
});
}
fn render_attempt(ui: &mut egui::Ui, attempt: &Attempt, sel: &mut Option<WorkFile>) {
ui.horizontal(|ui| {
ui.strong(&attempt.ball_id);
ui.weak(attempt.project.display().to_string())
.on_hover_text("The repository this ball's work is delivered into.");
});
match &attempt.change {
Change::Unreadable => {
ui.weak(
"this project's repository cannot be read here — it is gone, it is not a git \
repository, or its checkout is on no branch. Nothing is being compared.",
);
}
Change::Absent {
target,
source,
missing,
} => {
ui.weak(format!("comparing {target}..{source}"));
ui.weak(format!(
"but this repository has no {} — no work has been committed there yet, or the \
branch was removed.",
missing.join(" and no ")
));
}
Change::Diff {
target,
source,
target_oid,
source_oid,
files,
truncated,
} => {
ui.horizontal(|ui| {
ui.monospace(format!("{target}..{source}"))
.on_hover_text(format!(
"Everything on this ball's branch that is not yet on its delivery \
target. Run it yourself: git -C {} diff {target}..{source}",
attempt.project.display()
));
ui.weak(format!("{} … {}", short(target_oid), short(source_oid)));
});
render_files(ui, &attempt.ball_id, files, *truncated, sel);
}
}
}
fn render_files(
ui: &mut egui::Ui,
ball: &str,
files: &[FileChurn],
truncated: bool,
sel: &mut Option<WorkFile>,
) {
if files.is_empty() {
ui.weak("nothing changed yet — the branch is there, and it carries no edits.");
return;
}
for file in files {
let picked = sel
.as_ref()
.is_some_and(|s| s.ball == ball && s.path == file.path);
let label = format!("{} {}", churn_label(&file.churn), file.path);
if ui
.selectable_label(picked, label)
.on_hover_text(
"Read this file's changes below. No key of its own: Tab reaches it, \
Space presses it.",
)
.clicked()
{
*sel = Some(WorkFile {
ball: ball.to_owned(),
path: file.path.clone(),
});
}
}
if truncated {
ui.weak("… (more files changed than this listing shows)");
}
}
fn churn_label(churn: &Churn) -> String {
match churn {
Churn::Text { added, removed } => format!("+{added} -{removed}"),
Churn::Binary => "binary".to_owned(),
}
}
fn render_patch(ui: &mut egui::Ui, patch: Option<&Preview>) {
let Some(patch) = patch else {
ui.weak("pick a file to read its changes");
return;
};
match patch {
Preview::Text(text) if text.is_empty() => {
ui.weak("this file's patch came back empty");
}
Preview::Text(text) => {
ui.monospace(text);
}
Preview::Binary { size } => {
ui.weak(format!("binary change — {size} bytes of patch"));
}
Preview::Truncated { text, size } => {
ui.monospace(text);
ui.weak(format!(
"… (patch truncated at {} KiB of {size} bytes)",
PREVIEW_CAP / 1024
));
}
}
}
fn short(oid: &str) -> String {
oid.get(..crate::rail::SHORT_OID).unwrap_or(oid).to_owned()
}