fmtview 0.4.2

Fast CLI viewer for highlighting, search, and diffs across JSON, JSONL, markup, Markdown, TOML, text, and Jinja
Documentation
mod diff;
mod file;

use std::io::{self, Write};

use anyhow::{Context, Result};
use crossterm::{
    event::{DisableMouseCapture, EnableMouseCapture},
    execute,
    terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::backend::CrosstermBackend;

use crate::{load::ViewFile, transform::FormatKind, tui::screen::ViewerTerminal};

pub fn run(file: Box<dyn ViewFile>, mode: FormatKind, notice: Option<String>) -> Result<()> {
    run_terminal(|terminal| file::run_loop(terminal, file.as_ref(), mode, notice))
}

pub(crate) fn run_diff(view: crate::diff::DiffView) -> Result<()> {
    run_terminal(|terminal| diff::run_loop(terminal, view))
}

fn run_terminal<F>(run_loop: F) -> Result<()>
where
    F: FnOnce(&mut ViewerTerminal<CrosstermBackend<io::Stdout>>) -> Result<()>,
{
    let mut stdout = io::stdout();
    enable_raw_mode().context("failed to enable raw mode")?;
    let mut cleanup = TerminalCleanup::active();
    execute!(stdout, EnterAlternateScreen, EnableMouseCapture)
        .context("failed to enter alternate screen")?;

    let backend = CrosstermBackend::new(stdout);
    let mut terminal = ViewerTerminal::new(backend);
    let result = run_loop(&mut terminal);

    disable_raw_mode().ok();
    execute!(
        terminal.backend_mut(),
        DisableMouseCapture,
        LeaveAlternateScreen
    )
    .ok();
    cleanup.disarm();
    terminal.show_cursor().ok();

    result
}

struct TerminalCleanup {
    active: bool,
}

impl TerminalCleanup {
    fn active() -> Self {
        Self { active: true }
    }

    fn disarm(&mut self) {
        self.active = false;
    }
}

impl Drop for TerminalCleanup {
    fn drop(&mut self) {
        if !self.active {
            return;
        }

        disable_raw_mode().ok();
        let mut stdout = io::stdout();
        execute!(stdout, DisableMouseCapture, LeaveAlternateScreen).ok();
        stdout.flush().ok();
    }
}

#[cfg(test)]
use crate::load::IndexedTempFile;
#[cfg(test)]
use crate::tui::{
    palette::*,
    screen::{TerminalFrame, draw_cells},
};
#[cfg(test)]
use file::TestViewerCaches as ViewerCaches;
#[cfg(test)]
use file::breadcrumb::JsonBreadcrumbCache;
#[cfg(test)]
use file::markdown_modes::MarkdownModeCache;
#[cfg(test)]
use file::position::{adjust_state_for_visible_height, resolve_targets_from_view};
#[cfg(test)]
use file::position::{
    resolve_search_target_position, resolve_structure_target_position, search_context_rows,
    visual_row_for_byte,
};
#[cfg(test)]
use file::structure::*;
#[cfg(test)]
use file::{
    MOUSE_HORIZONTAL_COLUMNS, MOUSE_SCROLL_LINES, NOTICE_DURATION, TAIL_ROW_OFFSET,
    WRAP_RENDER_CHUNK_ROWS,
};

#[cfg(test)]
mod tests;