Skip to main content

mermaid_cli/render/
diff.rs

1//! Diff-line marker conventions shared between the edit_file tool
2//! (which generates diff output) and the chat renderer (which colors
3//! added/removed lines red/green).
4//!
5//! Kept in the render layer rather than in the tool impl because
6//! every consumer is the renderer. The producer today is
7//! `generate_diff` — inside the edit tool's helper code — and uses
8//! the same constants to format its lines.
9
10/// Marker for a REMOVED line. Formatted as `{num:>4}{marker}{content}`
11/// so the three-byte width stays aligned across line numbers up to
12/// 9,999. Lines without a matching prefix fall through to `Context`.
13pub const DIFF_REMOVED_MARKER: &str = " - ";
14
15/// Marker for an ADDED line.
16pub const DIFF_ADDED_MARKER: &str = " + ";
17
18/// Classification of a diff line. The chat renderer matches on this
19/// to choose a background color.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum DiffLineKind {
22    Context,
23    Removed,
24    Added,
25}
26
27/// Parse one line of diff output. Lines that don't follow the
28/// `{number}{marker}{content}` shape — including malformed or
29/// truncated input — fall through to `Context` so the renderer's
30/// match stays exhaustive without panicking.
31pub fn parse_diff_line(line: &str) -> DiffLineKind {
32    let trimmed = line.trim_start();
33    let after_num = trimmed.trim_start_matches(|c: char| c.is_ascii_digit());
34    if after_num.starts_with(DIFF_REMOVED_MARKER) {
35        DiffLineKind::Removed
36    } else if after_num.starts_with(DIFF_ADDED_MARKER) {
37        DiffLineKind::Added
38    } else {
39        DiffLineKind::Context
40    }
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn parses_added_line() {
49        let line = format!("   5{}fn main() {{", DIFF_ADDED_MARKER);
50        assert_eq!(parse_diff_line(&line), DiffLineKind::Added);
51    }
52
53    #[test]
54    fn parses_removed_line() {
55        let line = format!("  12{}old = true;", DIFF_REMOVED_MARKER);
56        assert_eq!(parse_diff_line(&line), DiffLineKind::Removed);
57    }
58
59    #[test]
60    fn parses_context_line() {
61        let line = "   7   existing line";
62        assert_eq!(parse_diff_line(line), DiffLineKind::Context);
63    }
64
65    #[test]
66    fn malformed_falls_through_to_context() {
67        assert_eq!(parse_diff_line("no number at start"), DiffLineKind::Context);
68        assert_eq!(parse_diff_line(""), DiffLineKind::Context);
69    }
70}