leaf-markdown-viewer 1.26.2

Terminal Markdown previewer with a GUI-like experience
use super::App;
use crate::markdown::{display_width, LinkSpan};
use std::collections::HashMap;

pub(super) fn link_spans_to_map(link_spans: Vec<LinkSpan>) -> HashMap<usize, Vec<LinkSpan>> {
    let mut map: HashMap<usize, Vec<LinkSpan>> = HashMap::new();
    for span in link_spans {
        map.entry(span.line_idx).or_default().push(span);
    }
    map
}

impl App {
    pub(crate) fn set_link_spans(&mut self, link_spans: Vec<LinkSpan>) {
        self.link_spans_by_line = link_spans_to_map(link_spans);
    }

    pub(crate) fn link_at_position(
        &self,
        col: u16,
        row: u16,
        padding: u16,
        sb_width: u16,
        gutter_width: u16,
    ) -> Option<&LinkSpan> {
        self.find_hovered_link(col, row, padding, sb_width, gutter_width)
            .and_then(|(line_idx, span_idx)| {
                self.link_spans_by_line
                    .get(&line_idx)
                    .and_then(|spans| spans.get(span_idx))
            })
    }

    pub(crate) fn find_hovered_link(
        &self,
        col: u16,
        row: u16,
        padding: u16,
        sb_width: u16,
        gutter_width: u16,
    ) -> Option<(usize, usize)> {
        let area = self.content_area;
        let inner_x = area.x + padding + gutter_width;
        let inner_w = area
            .width
            .saturating_sub(padding * 2)
            .saturating_sub(sb_width)
            .saturating_sub(gutter_width);

        if col < inner_x || col >= inner_x + inner_w || row < area.y || row >= area.y + area.height
        {
            return None;
        }

        let rel_col = (col - inner_x) as usize;
        let rel_row = (row - area.y) as usize;
        let content_width = inner_w.max(1) as usize;

        let mut visual_row = 0usize;
        for line_idx in self.scroll..self.total() {
            let line = &self.lines[line_idx];
            let line_width: usize = line
                .spans
                .iter()
                .map(|s| display_width(s.content.as_ref()))
                .sum();
            let wrapped_lines = if line_width == 0 {
                1
            } else {
                line_width.div_ceil(content_width)
            };

            if rel_row < visual_row + wrapped_lines {
                let row_in_wrap = rel_row - visual_row;
                let char_col = row_in_wrap * content_width + rel_col;
                if let Some(spans) = self.link_spans_by_line.get(&line_idx) {
                    if let Some(idx) = spans
                        .iter()
                        .position(|ls| char_col >= ls.start_col && char_col < ls.end_col)
                    {
                        return Some((line_idx, idx));
                    }
                }
                return None;
            }
            visual_row += wrapped_lines;
            if visual_row > area.height as usize {
                break;
            }
        }
        None
    }
}