mdv 2.2.0

Terminal Markdown Viewer
Documentation
use super::{CowStr, EventRenderer, PRETTY_ACCENT_COLOR, Result, ThemeElement, create_style};
use crate::terminal::AnsiStyle;
use crate::utils::{display_width, strip_ansi};

impl<'a> EventRenderer<'a> {
    pub(super) fn handle_html(&mut self, html: CowStr) -> Result<()> {
        if html.as_ref().trim() == crate::markdown::BLANK_LINE_MARKER {
            self.handle_explicit_blank_line();
            return Ok(());
        }
        self.render_html_fragment(&html)
    }

    pub(super) fn handle_inline_html(&mut self, html: CowStr) -> Result<()> {
        if html.as_ref().trim() == crate::markdown::BLANK_LINE_MARKER {
            self.handle_explicit_blank_line();
            return Ok(());
        }
        self.render_html_fragment(&html)
    }

    fn render_html_fragment(&mut self, html: &CowStr) -> Result<()> {
        let html_str = html.as_ref();
        let trimmed = html_str.trim();

        let is_comment = trimmed.starts_with("<!--") && trimmed.ends_with("-->");
        if !is_comment || self.config.hide_comments {
            return Ok(());
        }

        self.note_paragraph_content();

        let style = create_style(self.theme, ThemeElement::Text);
        let mut followup_prefix: Option<String> = None;
        let line_start = self
            .output
            .rfind('\n')
            .map(|idx| idx.saturating_add(1))
            .unwrap_or(0);
        let current_line = &self.output[line_start..];

        if current_line.is_empty() {
            let prefix = self.current_line_prefix();
            if !prefix.is_empty() {
                self.push_indent_for_line_start();
                followup_prefix = Some(prefix);
            }
        } else {
            let prefix = self.current_line_prefix();
            if !prefix.is_empty() && current_line == prefix {
                followup_prefix = Some(prefix);
            }
        }

        let mut segments = html_str.split('\n').peekable();
        let mut first_segment = true;

        while let Some(segment) = segments.next() {
            if !first_segment {
                self.output.push('\n');
                if let Some(prefix) = followup_prefix.as_ref() {
                    if !prefix.is_empty() && (!segment.is_empty() || segments.peek().is_some()) {
                        self.output.push_str(prefix);
                    }
                }
            } else {
                first_segment = false;
            }

            if segment.is_empty() {
                continue;
            }

            let rendered = style.apply(segment, self.config.no_colors);
            self.output.push_str(&rendered);
        }

        self.commit_pending_heading_placeholder_if_content();

        Ok(())
    }

    pub(super) fn handle_horizontal_rule(&mut self) -> Result<()> {
        self.reset_explicit_blank_line_streak();
        let prefix = self.current_rule_prefix();
        let prefix_width = display_width(&strip_ansi(&prefix));
        let width = self
            .effective_text_width()
            .saturating_sub(prefix_width)
            .max(1);
        let rule = if width >= 2 {
            format!("{}", "".repeat(width.saturating_sub(2)))
        } else {
            "".repeat(width)
        };
        let styled_rule = AnsiStyle::new()
            .fg(PRETTY_ACCENT_COLOR)
            .apply(&rule, self.config.no_colors);

        if !self.output.is_empty() {
            self.ensure_contextual_blank_line_with_prefix(&prefix);
        }

        if !prefix.is_empty() {
            self.output.push_str(&prefix);
        }
        self.output.push_str(&styled_rule);
        self.output.push('\n');
        self.commit_pending_heading_placeholder_if_content();
        Ok(())
    }

    pub(super) fn handle_footnote_reference(&mut self, name: CowStr) -> Result<()> {
        self.note_paragraph_content();
        self.register_footnote_reference(name.as_ref());

        let marker = format!("[^{}]", name);
        if self.should_highlight_footnote_reference(name.as_ref()) {
            let style = create_style(self.theme, ThemeElement::Link);
            let footnote = style.apply(&marker, self.config.no_colors);
            self.output.push_str(&footnote);
        } else {
            self.output.push_str(&marker);
        }
        self.commit_pending_heading_placeholder_if_content();
        Ok(())
    }

    pub(super) fn handle_task_list_marker(&mut self, checked: bool) -> Result<()> {
        self.note_paragraph_content();
        self.pending_task_marker = false;
        self.pending_task_marker_buffer.clear();
        let marker = if checked { "[✓] " } else { "[ ] " };
        let style = create_style(self.theme, ThemeElement::ListMarker);
        let styled_marker = style.apply(marker, self.config.no_colors);
        self.output.push_str(&styled_marker);
        self.commit_pending_heading_placeholder_if_content();
        Ok(())
    }
}