fmtview 0.4.0

Fast CLI viewer for highlighting, search, and diffs across JSON, JSONL, markup, Markdown, TOML, text, and Jinja
Documentation
use std::sync::Arc;

use super::{DiffChange, UnifiedDiffRow};

pub(super) fn parse_unified_rows<I, S, E>(lines: I) -> Result<Vec<UnifiedDiffRow>, E>
where
    I: IntoIterator<Item = Result<S, E>>,
    S: AsRef<str>,
{
    let mut rows = Vec::new();
    let mut left_line = 0_usize;
    let mut right_line = 0_usize;
    let mut in_hunk = false;

    for line in lines {
        let line = line?;
        let line = line.as_ref();
        if let Some((left_start, right_start)) = parse_hunk_start(line) {
            left_line = left_start;
            right_line = right_start;
            in_hunk = true;
            continue;
        }

        if !in_hunk {
            continue;
        }

        let Some(marker) = line.as_bytes().first().copied() else {
            continue;
        };
        let content = Arc::<str>::from(line.get(1..).unwrap_or_default());
        match marker {
            b' ' => {
                rows.push(UnifiedDiffRow::Context {
                    left: left_line,
                    right: right_line,
                    content,
                });
                left_line = left_line.saturating_add(1);
                right_line = right_line.saturating_add(1);
            }
            b'-' => {
                rows.push(UnifiedDiffRow::Delete {
                    left: left_line,
                    content,
                    change: DiffChange::default(),
                });
                left_line = left_line.saturating_add(1);
            }
            b'+' => {
                rows.push(UnifiedDiffRow::Insert {
                    right: right_line,
                    content,
                    change: DiffChange::default(),
                });
                right_line = right_line.saturating_add(1);
            }
            b'\\' => {}
            _ => {}
        }
    }

    Ok(rows)
}

fn parse_hunk_start(line: &str) -> Option<(usize, usize)> {
    if !line.starts_with("@@ ") {
        return None;
    }

    let mut parts = line.split_whitespace();
    parts.next()?;
    let left = parse_range_start(parts.next()?)?;
    let right = parse_range_start(parts.next()?)?;
    Some((left, right))
}

fn parse_range_start(token: &str) -> Option<usize> {
    token
        .trim_start_matches(['-', '+'])
        .split(',')
        .next()?
        .parse()
        .ok()
}