fmtview 0.4.1

Fast CLI viewer for highlighting, search, and diffs across JSON, JSONL, markup, Markdown, TOML, text, and Jinja
Documentation
use std::time::{Duration, Instant};

use super::search::{SearchMatchIndex, SearchTarget, SearchTask};
use crate::viewer::file::structure::{StructureTask, StructureViewport};

pub(in crate::viewer) struct ViewState {
    pub(in crate::viewer) top: usize,
    pub(in crate::viewer) top_row_offset: usize,
    pub(in crate::viewer) top_max_row_offset: usize,
    pub(in crate::viewer) wrap_bounds_stale: bool,
    pub(in crate::viewer) x: usize,
    pub(in crate::viewer) wrap: bool,
    pub(in crate::viewer) jump_buffer: String,
    pub(in crate::viewer) search_active: bool,
    pub(in crate::viewer) search_buffer: String,
    pub(in crate::viewer) search_query: String,
    pub(in crate::viewer) notice_message: Option<String>,
    pub(in crate::viewer) notice_expires_at: Option<Instant>,
    pub(in crate::viewer) search_message: Option<String>,
    pub(in crate::viewer) search_task: Option<SearchTask>,
    pub(in crate::viewer) search_index: Option<SearchMatchIndex>,
    pub(in crate::viewer) search_match_ordinal: Option<usize>,
    pub(in crate::viewer) search_match_target: Option<SearchTarget>,
    pub(in crate::viewer) search_target: Option<SearchTarget>,
    pub(in crate::viewer) search_cursor: Option<usize>,
    pub(in crate::viewer) structure_task: Option<StructureTask>,
    pub(in crate::viewer) structure_target: Option<SearchTarget>,
    pub(in crate::viewer) structure_cursor: Option<usize>,
    pub(in crate::viewer) structure_viewport: Option<StructureViewport>,
    pub(in crate::viewer) viewport_at_tail: bool,
    pub(in crate::viewer) preserve_tail_on_next_draw: bool,
    pub(in crate::viewer) mouse_capture: bool,
}

impl Default for ViewState {
    fn default() -> Self {
        Self {
            top: 0,
            top_row_offset: 0,
            top_max_row_offset: 0,
            wrap_bounds_stale: false,
            x: 0,
            wrap: true,
            jump_buffer: String::new(),
            search_active: false,
            search_buffer: String::new(),
            search_query: String::new(),
            notice_message: None,
            notice_expires_at: None,
            search_message: None,
            search_task: None,
            search_index: None,
            search_match_ordinal: None,
            search_match_target: None,
            search_target: None,
            search_cursor: None,
            structure_task: None,
            structure_target: None,
            structure_cursor: None,
            structure_viewport: None,
            viewport_at_tail: false,
            preserve_tail_on_next_draw: false,
            mouse_capture: true,
        }
    }
}

#[derive(Debug, Default)]
pub(in crate::viewer) struct EventAction {
    pub(in crate::viewer) dirty: bool,
    pub(in crate::viewer) quit: bool,
    pub(in crate::viewer) mouse_capture: Option<bool>,
}

impl EventAction {
    pub(in crate::viewer) fn merge(&mut self, next: Self) {
        self.dirty |= next.dirty;
        self.quit |= next.quit;
        self.mouse_capture = next.mouse_capture.or(self.mouse_capture);
    }
}

impl ViewState {
    pub(in crate::viewer) fn has_active_prompt(&self) -> bool {
        self.search_active || !self.jump_buffer.is_empty()
    }

    pub(in crate::viewer) fn set_notice(
        &mut self,
        message: String,
        now: Instant,
        duration: Duration,
    ) {
        self.notice_message = Some(message);
        self.notice_expires_at = Some(now + duration);
    }

    pub(in crate::viewer) fn clear_notice(&mut self) -> bool {
        let was_active = self.notice_message.is_some();
        self.notice_message = None;
        self.notice_expires_at = None;
        was_active
    }

    pub(in crate::viewer) fn expire_notice(&mut self, now: Instant) -> bool {
        if self
            .notice_expires_at
            .is_some_and(|expires_at| now >= expires_at)
        {
            return self.clear_notice();
        }
        false
    }

    pub(in crate::viewer) fn is_notice_visible(&self) -> bool {
        !self.search_active
            && self.jump_buffer.is_empty()
            && self.search_message.is_none()
            && self.notice_message.is_some()
    }
}