From 7671d28d6e99cf4adbf50716209a220df3fa387e Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Wed, 22 Jul 2026 12:14:45 -0600 Subject: [PATCH] feat(render): detect markdown table rows and separators --- src/render/markdown.rs | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/render/markdown.rs b/src/render/markdown.rs index b53d640..38949eb 100644 --- a/src/render/markdown.rs +++ b/src/render/markdown.rs @@ -32,6 +32,10 @@ static NUMBERED_ITEM_RE: LazyLock = LazyLock::new(|| Regex::new(r"^\s*\d+\. +.+").unwrap()); static HRULE_RE: LazyLock = LazyLock::new(|| Regex::new(r"^\s*(-{3,}|_{3,}|\*{3,})\s*$").unwrap()); +static TABLE_SEPARATOR_RE: LazyLock = + LazyLock::new(|| Regex::new(r"^\s*\|(\s*:?-+:?\s*\|)+\s*$").unwrap()); +static TABLE_ROW_RE: LazyLock = + LazyLock::new(|| Regex::new(r"^\s*\|.*\|\s*$").unwrap()); static INLINE_CODE_RE: LazyLock = LazyLock::new(|| Regex::new(r"`([^`\n]+)`").unwrap()); @@ -62,6 +66,8 @@ pub enum LineKind { BulletItem, NumberedItem, HorizontalRule, + TableRow, + TableSeparator, Paragraph, } @@ -90,6 +96,12 @@ fn detect_line_kind(line: &str) -> LineKind { if NUMBERED_ITEM_RE.is_match(line).unwrap_or(false) { return LineKind::NumberedItem; } + if TABLE_SEPARATOR_RE.is_match(line).unwrap_or(false) { + return LineKind::TableSeparator; + } + if TABLE_ROW_RE.is_match(line).unwrap_or(false) { + return LineKind::TableRow; + } LineKind::Paragraph } @@ -130,6 +142,7 @@ fn render_markdown_line(line: &str, kind: LineKind, styles: &MarkdownStyles) -> LineKind::NumberedItem => render_numbered(line, styles), LineKind::TaskItem(checked) => render_task(line, checked, styles), LineKind::HorizontalRule => render_hrule(styles), + LineKind::TableRow | LineKind::TableSeparator => apply_inline(line, styles), LineKind::Paragraph => apply_inline(line, styles), } } @@ -1031,6 +1044,42 @@ std::error::Error>> { assert_eq!(detect_line_kind(" "), LineKind::Paragraph); } + #[test] + fn detect_line_kind_table_row() { + assert_eq!(detect_line_kind("| a | b |"), LineKind::TableRow); + assert_eq!(detect_line_kind("|a|b|c|"), LineKind::TableRow); + assert_eq!(detect_line_kind(" | a | b | "), LineKind::TableRow); + assert_eq!(detect_line_kind("| | |"), LineKind::TableRow); + } + + #[test] + fn detect_line_kind_table_separator() { + assert_eq!(detect_line_kind("|---|---|"), LineKind::TableSeparator); + assert_eq!(detect_line_kind("| --- | --- |"), LineKind::TableSeparator); + assert_eq!( + detect_line_kind("|:---|---:|:---:|---|"), + LineKind::TableSeparator, + ); + assert_eq!(detect_line_kind("|:--|--:|:-:|"), LineKind::TableSeparator); + assert_eq!(detect_line_kind(" |---|---| "), LineKind::TableSeparator); + } + + #[test] + fn detect_line_kind_non_table_pipe_line_stays_paragraph() { + assert_eq!( + detect_line_kind("use `a | b` for or"), + LineKind::Paragraph, + ); + assert_eq!(detect_line_kind("| trailing"), LineKind::Paragraph); + assert_eq!(detect_line_kind("no closer |"), LineKind::Paragraph); + } + + #[test] + fn detect_line_kind_prefers_separator_over_row() { + assert_eq!(detect_line_kind("|---|---|"), LineKind::TableSeparator); + assert_ne!(detect_line_kind("|---|---|"), LineKind::TableRow); + } + fn test_styles() -> MarkdownStyles { MarkdownStyles { heading: (Color::Yellow, true),