fmtview 0.2.1

Fast terminal formatter and viewer for JSON, JSONL, XML-compatible markup, and formatted diffs
Documentation
use anyhow::Result;

use crate::line_index::ViewFile;

use super::super::input::ViewState;
use super::{
    line::rendered_row_count,
    types::{RenderContext, ViewPosition},
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::viewer) struct TailPositionKey {
    pub(in crate::viewer) line_count: usize,
    pub(in crate::viewer) visible_height: usize,
    pub(in crate::viewer) width: usize,
}

#[derive(Debug, Default)]
pub(in crate::viewer) struct TailPositionCache {
    pub(in crate::viewer) key: Option<TailPositionKey>,
    pub(in crate::viewer) position: Option<ViewPosition>,
}

impl TailPositionCache {
    pub(in crate::viewer) fn position(
        &mut self,
        file: &dyn ViewFile,
        visible_height: usize,
        context: RenderContext,
    ) -> Result<ViewPosition> {
        if !context.wrap {
            return Ok(ViewPosition {
                top: last_full_logical_page_top(file.line_count(), visible_height),
                row_offset: 0,
            });
        }

        let key = TailPositionKey {
            line_count: file.line_count(),
            visible_height,
            width: context.width,
        };
        if self.key == Some(key) {
            if let Some(position) = self.position {
                return Ok(position);
            }
        }

        let position = compute_tail_position(file, visible_height, context)?;
        self.key = Some(key);
        self.position = Some(position);
        Ok(position)
    }
}

pub(in crate::viewer) fn compute_tail_position(
    file: &dyn ViewFile,
    visible_height: usize,
    context: RenderContext,
) -> Result<ViewPosition> {
    let line_count = file.line_count();
    if line_count == 0 || visible_height == 0 {
        return Ok(ViewPosition {
            top: 0,
            row_offset: 0,
        });
    }

    if !context.wrap {
        return Ok(ViewPosition {
            top: last_full_logical_page_top(line_count, visible_height),
            row_offset: 0,
        });
    }

    let mut needed_rows = visible_height;
    let mut end = line_count;
    while end > 0 {
        let start = end.saturating_sub(visible_height.max(32));
        let lines = file.read_window(start, end - start)?;
        for (index, line) in lines.iter().enumerate().rev() {
            let line_index = start + index;
            let rows = rendered_row_count(line, context);
            if rows >= needed_rows {
                return Ok(ViewPosition {
                    top: line_index,
                    row_offset: rows - needed_rows,
                });
            }
            needed_rows -= rows;
        }
        end = start;
    }

    Ok(ViewPosition {
        top: 0,
        row_offset: 0,
    })
}

pub(in crate::viewer) fn last_full_logical_page_top(
    line_count: usize,
    visible_height: usize,
) -> usize {
    line_count.saturating_sub(visible_height.max(1))
}

pub(in crate::viewer) fn is_after_tail(state: &ViewState, tail: ViewPosition) -> bool {
    state.top > tail.top || (state.top == tail.top && state.top_row_offset > tail.row_offset)
}