fmtview 0.4.0

Fast CLI viewer for highlighting, search, and diffs across JSON, JSONL, markup, Markdown, TOML, text, and Jinja
Documentation
use crate::load::ViewFile;
use crate::tui::palette::{error_style, gutter_style};

use super::super::input::ViewState;
use super::{format_count, line_number_digits};
use ratatui::style::Style;

pub(in crate::viewer) fn file_title_text(
    file: &dyn ViewFile,
    state: &ViewState,
    current: usize,
    bottom: usize,
    progress: usize,
) -> String {
    format!(
        " {} | {} lines | {}-{} | {:>3}% | {} ",
        file.label(),
        line_count_text(file),
        current,
        bottom,
        progress,
        display_mode_text(state)
    )
}

pub(in crate::viewer) fn file_footer_text(file: &dyn ViewFile, state: &ViewState) -> String {
    if state.search_active {
        format!(
            " search: {} | Enter find | Backspace edit | Esc cancel ",
            state.search_buffer
        )
    } else if !state.jump_buffer.is_empty() {
        format!(
            " go to line: {} / {} | Enter jump | Backspace edit | Esc cancel ",
            state.jump_buffer,
            line_count_text(file)
        )
    } else if let Some(message) = state
        .search_message
        .as_ref()
        .or(state.notice_message.as_ref())
    {
        format!(
            " {message}{} | / search | n/N | Esc clear ",
            search_count_suffix(state)
        )
    } else {
        idle_footer_text(state)
    }
}

pub(in crate::viewer) fn file_footer_style(state: &ViewState) -> Style {
    if state.is_notice_visible() {
        error_style()
    } else {
        gutter_style()
    }
}

pub(in crate::viewer) fn idle_footer_text(state: &ViewState) -> String {
    let wrap_hint = if state.wrap { "w unwrap" } else { "w wrap" };
    let mouse_hint = if state.mouse_capture {
        "m select"
    } else {
        "m mouse"
    };
    let position = wrap_position_text(state)
        .map(|position| format!("{position} | "))
        .unwrap_or_default();
    let search = search_count_text(state)
        .map(|count| format!("{count} | "))
        .unwrap_or_default();
    format!(
        " {position}{search}{wrap_hint} | {mouse_hint} | / search n/N | ]/[ structure | 123 Enter jump to line | Space/f,b "
    )
}

pub(in crate::viewer) fn search_count_suffix(state: &ViewState) -> String {
    search_count_text(state)
        .map(|count| format!(" | {count}"))
        .unwrap_or_default()
}

pub(in crate::viewer) fn search_count_text(state: &ViewState) -> Option<String> {
    let index = state.search_index.as_ref()?;
    if index.query != state.search_query {
        return None;
    }

    let suffix = if index.exact { "" } else { "+" };
    let matches = state
        .search_match_ordinal
        .map(|ordinal| index.matches.max(ordinal))
        .unwrap_or(index.matches);
    let noun = if matches == 1 { "match" } else { "matches" };
    if let Some(ordinal) = state.search_match_ordinal {
        return Some(format!(
            "{}/{}{suffix} {noun}",
            format_count(ordinal),
            format_count(matches)
        ));
    }

    Some(format!("{}{suffix} {noun}", format_count(matches)))
}

pub(in crate::viewer) fn display_mode_text(state: &ViewState) -> String {
    if state.wrap {
        return wrap_position_text(state)
            .map(|position| format!("wrap {position}"))
            .unwrap_or_else(|| "wrap".to_owned());
    }

    format!("nowrap x:{}", state.x)
}

pub(in crate::viewer) fn line_count_text(file: &dyn ViewFile) -> String {
    let count = file.line_count();
    if file.line_count_exact() {
        count.to_string()
    } else {
        format!("{count}+")
    }
}

pub(in crate::viewer) fn gutter_digits(file: &dyn ViewFile, selection_mode: bool) -> usize {
    if selection_mode {
        0
    } else if file.line_count_exact() {
        line_number_digits(file.line_count())
    } else {
        line_number_digits(file.line_count()).max(4)
    }
}

fn wrap_position_text(state: &ViewState) -> Option<String> {
    if !state.wrap || state.top_row_offset == 0 {
        return None;
    }

    Some(format!("+{} rows", format_count(state.top_row_offset)))
}