Skip to main content

declutter/
render.rs

1use std::fmt::Write;
2
3use crate::diff::RowKind;
4use crate::moves::detect;
5use crate::project::LayerMode;
6use crate::review::{Detection, DiffModes, FileReview, Layers, Summary};
7
8/// The decluttered diff as plain text. Line numbers in hunk headers refer to the
9/// original files, so the output is for reading, not for `git apply`.
10pub fn plain(files: &[FileReview], layers: Layers) -> String {
11    let mut out = String::new();
12    let listed: Vec<&FileReview> = files
13        .iter()
14        .filter(|file| file.is_visible(layers.tests))
15        .collect();
16    let moves = detect(&listed, layers.into());
17    for (file_index, file) in listed.iter().enumerate() {
18        let view = file.view(layers);
19        let _ = writeln!(out, "{} [{}]", file_title(file), file.tag());
20        if file.detection == Detection::Binary {
21            let _ = writeln!(out, "  (binary file not shown)");
22        } else if view.hunks.is_empty() {
23            let _ = writeln!(out, "  ({})", empty_message(file, layers.into()));
24        }
25        for (hunk_index, hunk) in view.hunks.iter().enumerate() {
26            let _ = writeln!(out, "{}", hunk.header());
27            for (row_index, row) in hunk.rows.iter().enumerate() {
28                if let Some(moved) = moves.get(&(file_index, hunk_index, row_index))
29                    && moved.starts_block
30                {
31                    let _ = writeln!(out, "{}", moved.describe(&file.path));
32                }
33                let sign = match row.kind {
34                    RowKind::Context => ' ',
35                    RowKind::Removed => '-',
36                    RowKind::Added => '+',
37                };
38                let _ = writeln!(out, "{sign}{}", row.text);
39            }
40        }
41        let _ = writeln!(out);
42    }
43    let _ = writeln!(out, "{}", Summary::new(files, layers).status_line(layers));
44    out
45}
46
47pub fn file_title(file: &FileReview) -> String {
48    match &file.old_path {
49        Some(old) => format!("{} {old} → {}", file.status.letter(), file.path),
50        None => format!("{} {}", file.status.letter(), file.path),
51    }
52}
53
54/// Why a file with changes shows nothing under these layers.
55pub fn empty_message(file: &FileReview, modes: DiffModes) -> String {
56    let hidden = file.view(modes).hidden_hunks;
57    let adjective = modes.adjective();
58    match modes.outcome().0 {
59        _ if file.total_hunks() == 0 => "no textual changes".to_string(),
60        LayerMode::Hidden => format!(
61            "{adjective}-only changes: {hidden} hunk{} hidden",
62            if hidden == 1 { "" } else { "s" }
63        ),
64        LayerMode::Only => format!("no {adjective} changes"),
65        LayerMode::Shown => "no changes".to_string(),
66    }
67}