//! Markdown-to-styled-lines renderer for bbs-md. //! //! Converts a markdown string into `Vec>` using pulldown-cmark //! event processing, CGA-themed styling, syntax-highlighted code blocks, and //! box-drawing table grids. //! //! # Public API //! //! - `render_markdown(input: &str, width: u16, vault_path: Option<&Path>) -> (Vec>, Vec, Vec)` use std::path::Path; use pulldown_cmark::{ Alignment, CodeBlockKind, Event, HeadingLevel, LinkType, Options, Parser, Tag, TagEnd, TextMergeStream, }; use ratatui::style::{Color, Modifier, Style}; use ratatui::text::{Line, Span}; // ── LinkRecord ───────────────────────────────────────────────────────────────── /// Metadata for a single link discovered during rendering. /// /// Produced by `render_markdown` as a parallel structure alongside `Vec`. /// Plan 02 consumes this to wire Tab-cycling navigation and link following. pub struct LinkRecord { /// Index into `Vec` where this link appears. pub line_index: usize, /// Character column offset within that line where the link bracket `[` starts. /// Uses `chars().count()` for multi-byte Unicode correctness (Pitfall 4). pub col_offset: usize, /// Display length of the full `[Link Text]` span (including brackets). pub span_len: usize, /// Raw destination — vault-relative path for inline links, wiki target for wiki-links. pub dest: String, /// True if this is a `[[wiki-link]]` (needs `vault::resolve_wiki_link` at nav time). /// False for inline `[text](path.md)` links. pub is_wiki: bool, } // ── CopyableBlock ──────────────────────────────────────────────────────────── /// The kind of copyable block discovered during rendering. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BlockKind { Code, Table, Blockquote, Text, } /// Metadata for a single copyable block discovered during rendering. /// /// Produced by `render_markdown` alongside `Vec` and `Vec`. /// Used to support OSC 52 copy-to-clipboard in copy mode or on right-click. pub struct CopyableBlock { /// Index into `Vec` where this block begins. pub start_line: usize, /// Index into `Vec` where this block ends (inclusive). pub end_line: usize, /// Raw content suitable for clipboard (plain text). pub raw_content: String, /// What kind of block this is. pub kind: BlockKind, } // ── Internal pending link helpers ───────────────────────────────────────────── /// Captured at `Tag::Link` Start — records link metadata before the text spans are pushed. struct PendingLink { dest: String, is_wiki: bool, /// Character column offset computed from current_spans at the moment Start fires. col_offset: usize, /// Style chosen for this link's brackets and text. link_style: Style, } /// Created at `TagEnd::Link` — fully formed except for `line_index` (known only at flush). struct PendingLinkRecord { dest: String, is_wiki: bool, col_offset: usize, /// Display length including brackets: computed from spans pushed between Start and End. span_len: usize, } // ── RenderState ─────────────────────────────────────────────────────────────── /// Internal mutable state threaded through the event handler. struct RenderState { /// Accumulated output lines. lines: Vec>, /// Spans for the current (in-progress) line. current_spans: Vec>, /// Style stack — push on block/inline Start, pop on End. style_stack: Vec