leaf-markdown-viewer 1.26.2

Terminal Markdown previewer with a GUI-like experience
use super::{App, CodeBlockFlash};
use crate::{clipboard::copy_to_clipboard, markdown::CodeBlockInfo};

impl App {
    pub(crate) fn set_code_blocks(&mut self, code_blocks: Vec<CodeBlockInfo>) {
        self.code_blocks = code_blocks;
    }

    pub(crate) fn is_code_select_mode(&self) -> bool {
        self.code_select.is_some()
    }

    pub(crate) fn exit_code_select_mode(&mut self) -> bool {
        let was_active = self.code_select.is_some();
        self.code_select = None;
        was_active
    }

    fn visible_code_block_indices(&self) -> Vec<usize> {
        let top = self.scroll;
        let bottom = self.visible_end();
        self.code_blocks
            .iter()
            .enumerate()
            .filter(|(_, b)| b.rendered_end >= top && b.rendered_start < bottom)
            .map(|(i, _)| i)
            .collect()
    }

    pub(crate) fn copy_first_visible_code_block(&mut self) {
        let indices = self.visible_code_block_indices();
        match indices.first() {
            Some(&idx) => self.copy_code_block_at(idx),
            None => self.set_code_block_flash(CodeBlockFlash::NoneVisible),
        }
    }

    pub(crate) fn enter_code_select_mode(&mut self) {
        let indices = self.visible_code_block_indices();
        match indices.first() {
            Some(&idx) => self.code_select = Some(idx),
            None => self.set_code_block_flash(CodeBlockFlash::NoneVisible),
        }
    }

    pub(crate) fn code_select_next(&mut self) {
        let indices = self.visible_code_block_indices();
        if indices.is_empty() {
            self.code_select = None;
            return;
        }
        let current = self.code_select.unwrap_or(usize::MAX);
        let next = indices
            .iter()
            .copied()
            .find(|&i| i > current)
            .unwrap_or(indices[0]);
        self.code_select = Some(next);
    }

    pub(crate) fn code_select_prev(&mut self) {
        let indices = self.visible_code_block_indices();
        if indices.is_empty() {
            self.code_select = None;
            return;
        }
        let current = self.code_select.unwrap_or(0);
        let prev = indices
            .iter()
            .rev()
            .copied()
            .find(|&i| i < current)
            .unwrap_or_else(|| *indices.last().unwrap());
        self.code_select = Some(prev);
    }

    pub(crate) fn copy_selected_code_block(&mut self) {
        if let Some(idx) = self.code_select {
            self.copy_code_block_at(idx);
        }
        self.code_select = None;
    }

    pub(crate) fn copy_code_block_at(&mut self, idx: usize) {
        let raw = match self.code_blocks.get(idx) {
            Some(b) => b.raw_content.clone(),
            None => {
                self.set_code_block_flash(CodeBlockFlash::CopyFailed);
                return;
            }
        };
        if copy_to_clipboard(&raw) {
            self.set_code_block_flash(CodeBlockFlash::Copied);
        } else {
            self.set_code_block_flash(CodeBlockFlash::CopyFailed);
        }
    }

    pub(crate) fn code_block_at(&self, line_idx: usize, inner_col: u16) -> Option<usize> {
        let col = inner_col as usize;
        self.code_blocks.iter().position(|b| {
            line_idx >= b.rendered_start
                && line_idx <= b.rendered_end
                && col >= b.rendered_offset
                && col < b.rendered_offset + b.rendered_width
        })
    }

    pub(crate) fn highlighted_code_block(&self) -> Option<usize> {
        self.code_select
    }
}