iris-reader 1.0.0

A terminal-native viewer for documents, structured data, images, PDFs, databases, and stdin.
Documentation
pub mod markdown;
pub mod text;

use std::path::Path;

use ratatui::text::Line;

use crate::{
    image::ImageManager,
    input::{Input, InputKind},
    search::{SearchMatch, SearchMatcher},
    theme::Theme,
};

pub type NodeId = usize;
pub type HeadingId = NodeId;

#[derive(Debug, Clone)]
pub struct RenderedLine {
    pub line: Line<'static>,
    pub plain: String,
    pub source_id: Option<NodeId>,
}

impl RenderedLine {
    pub fn empty() -> Self {
        Self {
            line: Line::default(),
            plain: String::new(),
            source_id: None,
        }
    }
}

#[derive(Debug, Clone)]
pub struct RenderedHeading {
    pub id: HeadingId,
    pub level: u8,
    pub title: String,
    pub slug: String,
    pub line: usize,
}

#[derive(Debug, Clone)]
pub struct RenderedLink {
    pub source_id: NodeId,
    pub destination: String,
    pub line: usize,
    pub start_column: usize,
    pub end_column: usize,
}

#[derive(Debug, Clone)]
pub struct RenderedImage {
    pub source_id: NodeId,
    pub source: String,
    pub alt: String,
    pub destination: Option<String>,
    pub line: usize,
    pub column: usize,
    pub height: u16,
}

#[derive(Debug, Clone, Default)]
pub struct RenderedDocument {
    pub lines: Vec<RenderedLine>,
    pub headings: Vec<RenderedHeading>,
    pub links: Vec<RenderedLink>,
    pub images: Vec<RenderedImage>,
    pub max_width: usize,
}

impl RenderedDocument {
    pub fn finish(mut self) -> Self {
        self.max_width = self
            .lines
            .iter()
            .map(|line| unicode_width::UnicodeWidthStr::width(line.plain.as_str()))
            .max()
            .unwrap_or(0);
        self
    }
}

pub enum ViewDocument {
    Text(text::TextDocument),
    Markdown(markdown::Document),
}

impl ViewDocument {
    pub fn from_input(input: &Input) -> Self {
        match input.kind {
            InputKind::Text => Self::Text(text::TextDocument::new(&input.content)),
            InputKind::Markdown => Self::Markdown(markdown::parse(&input.content)),
        }
    }

    pub fn render(
        &self,
        width: u16,
        theme: &Theme,
        wrap: bool,
        tab_width: usize,
        images: &mut ImageManager,
        base_dir: Option<&Path>,
    ) -> RenderedDocument {
        match self {
            Self::Text(document) => text::render(document, width, theme, wrap, tab_width),
            Self::Markdown(document) => {
                markdown::render(document, width, theme, wrap, tab_width, images, base_dir)
            }
        }
    }

    pub fn search(&self, matcher: &SearchMatcher) -> Vec<SearchMatch> {
        match self {
            Self::Text(document) => document.search(matcher),
            Self::Markdown(document) => document.search(matcher),
        }
    }

    pub fn is_markdown(&self) -> bool {
        matches!(self, Self::Markdown(_))
    }
}