ratatui-markdown 0.3.3

Markdown rendering, syntax highlighting, collapsible trees, and rich scroll widgets for ratatui
Documentation
use ratatui::{
    layout::Rect,
    style::{Modifier, Style},
    text::{Line, Span},
    widgets::Paragraph,
    Frame,
};

use super::HybridScrollView;
use crate::{scroll::render_arrow_scrollbar, theme::RichTextTheme};

pub(super) fn render(
    view: &mut HybridScrollView,
    f: &mut Frame,
    inner_area: Rect,
    outer_area: Rect,
    theme: &impl RichTextTheme,
) {
    let visible_height = inner_area.height as usize;
    view.viewport_height = visible_height.max(1);

    if view.scroll_offset > view.max_offset() {
        view.scroll_offset = view.max_offset();
    }

    let total = view.lines.len();
    if total == 0 {
        return;
    }

    let start = view.scroll_offset;
    let end = (start + visible_height).min(total);

    let highlight_bg = theme.get_popup_selected_background();

    let visible_lines: Vec<Line> = (start..end)
        .map(|line_idx| {
            let mut line = view.lines[line_idx].clone();

            if let Some(region_idx) = view.engaged_region {
                let item = &view.regions[region_idx].items[view.cursor_item];
                if line_idx >= item.start_line && line_idx < item.end_line {
                    for span in &mut line.spans {
                        span.style = span.style.bg(highlight_bg).add_modifier(Modifier::BOLD);
                    }
                }
            }

            if view.show_cursor_indicator {
                let is_engaged_line = view.engaged_region.is_some_and(|region_idx| {
                    let item = &view.regions[region_idx].items[view.cursor_item];
                    line_idx >= item.start_line && line_idx < item.end_line
                });
                let prefix_str = if is_engaged_line { "> " } else { "  " };
                line.spans.insert(
                    0,
                    Span::styled(prefix_str, Style::default().fg(theme.get_primary_color())),
                );
            } else if view.left_padding {
                line.spans.insert(0, Span::raw(" "));
            }

            line
        })
        .collect();

    let paragraph = Paragraph::new(visible_lines);
    f.render_widget(paragraph, inner_area);

    if total > visible_height {
        render_arrow_scrollbar(
            f,
            outer_area,
            total,
            visible_height,
            view.scroll_offset,
            theme,
        );
    }
}