fmtview 0.4.3

Fast CLI viewer for highlighting, search, and diffs across JSON, JSONL, markup, Markdown, TOML, text, and Jinja
Documentation
use crate::formats::shared::{following_lines, max_observed_offset};

pub(crate) fn block_end(
    lines: &[String],
    read_start: usize,
    start_offset: usize,
    viewport_bottom: usize,
) -> Option<usize> {
    let max_offset = max_observed_offset(lines, read_start, viewport_bottom)?;
    let start_keyword = keyword(lines.get(start_offset)?)?;
    let Some(close_keyword) = close_keyword(start_keyword) else {
        return Some(read_start + start_offset);
    };
    let open_keyword = open_keyword(start_keyword);
    let mut depth = 1_usize;
    for (offset, line) in following_lines(lines, start_offset, max_offset) {
        let Some(keyword) = keyword(line) else {
            continue;
        };
        if keyword == open_keyword {
            depth = depth.saturating_add(1);
        } else if keyword == close_keyword {
            depth = depth.saturating_sub(1);
            if depth == 0 {
                return Some(read_start + offset);
            }
        }
    }
    None
}

pub(crate) fn is_block(line: &str) -> bool {
    keyword(line).is_some_and(|keyword| {
        matches!(
            keyword,
            "block"
                | "endblock"
                | "if"
                | "elif"
                | "else"
                | "endif"
                | "for"
                | "endfor"
                | "macro"
                | "endmacro"
                | "filter"
                | "endfilter"
                | "call"
                | "endcall"
                | "include"
                | "extends"
                | "set"
                | "endset"
                | "with"
                | "endwith"
        )
    })
}

pub(crate) fn keyword(line: &str) -> Option<&str> {
    let trimmed = line.trim_start();
    let rest = trimmed.strip_prefix("{%")?;
    rest.split_whitespace().next()
}

fn open_keyword(keyword: &str) -> &str {
    match keyword {
        "elif" | "else" => "if",
        _ => keyword,
    }
}

fn close_keyword(keyword: &str) -> Option<&'static str> {
    match keyword {
        "block" => Some("endblock"),
        "if" | "elif" | "else" => Some("endif"),
        "for" => Some("endfor"),
        "macro" => Some("endmacro"),
        "filter" => Some("endfilter"),
        "call" => Some("endcall"),
        "set" => Some("endset"),
        "with" => Some("endwith"),
        _ => None,
    }
}