Add tables, images, math, alerts, footnotes, configurable themes, and parse() API#125
Add tables, images, math, alerts, footnotes, configurable themes, and parse() API#125lightsofapollo wants to merge 5 commits intojoshka:mainfrom
Conversation
…nition lists Implement rendering for all previously unsupported markdown elements: - **Tables**: Full GFM table support with Unicode box-drawing borders, column alignment (left/center/right), header styling, and inline formatting within cells. New `tables` module with `TableBuilder`. - **Images**: Alt-text fallback rendering with image indicator prefix. New `images` module with `terminal-images` feature flag for future inline image protocol support. - **Math**: Inline (`$...$`) and display (`$$...$$`) math rendering with magenta styling. - **GFM Alerts**: Note, Tip, Important, Warning, and Caution callout blockquotes with colored icons and labels. - **Footnotes**: Reference and definition rendering. - **Definition Lists**: Term/description pairs with indentation. - **HTML**: Inline and block HTML rendered as dim text instead of being silently dropped. - **Links**: Link text now styled with the link style (blue underline), not just the URL portion. Extends `StyleSheet` trait with `image_alt`, `table_header`, `table_border`, `alert`, `html`, `math_inline`, `math_display`, `footnote_ref`, `footnote_def`, `definition_title`, and `definition_desc` methods. Adds `--test` flag to markdown-reader for loading the comprehensive test fixture, plus comprehensive integration tests and snapshots. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…blocks
Introduce `MarkdownContent` and `MarkdownBlock` types that allow
consumers to handle images separately from text instead of flattening
everything to alt-text. The new `parse()` and `parse_with_options()`
functions return `MarkdownContent` with a `blocks: Vec<MarkdownBlock>`
where each block is either `Text(Text)` or `Image { url, alt, title }`.
This enables terminal image protocol rendering (iTerm2/Kitty/Sixel)
by consumers while preserving the existing `from_str()` API unchanged.
Also adds `image` and `ratatui-image` as optional deps behind the
`terminal-images` feature flag, and a `link()` method to `StyleSheet`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace byte-length width calculation with `unicode_width::UnicodeWidthStr` so that emoji and CJK characters are measured by their display column width instead of their UTF-8 byte length. This fixes misaligned table columns when cells contain emoji like ✅ or 🟧. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add `code_theme` field to `Options` with a builder method so consumers
can select any built-in syntect theme instead of the hardcoded
"base16-ocean.dark".
let options = Options::default().code_theme("Solarized (dark)");
let text = from_str_with_options(markdown, &options);
Add `available_themes()` function (behind `highlight-code` feature) to
list valid theme names at runtime. Invalid theme names fall back to
the default with a warning rather than panicking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add two new builder methods to Options (behind highlight-code feature):
- `code_theme_file(path)` — loads a .tmTheme file from disk
- `code_theme_custom(theme)` — accepts a syntect Theme directly
Custom themes take precedence over the built-in theme name. Re-export
`SyntectTheme` and `LoadingError` so consumers don't need a direct
syntect dependency.
Usage:
let opts = Options::default().code_theme_file("Dracula.tmTheme")?;
let text = from_str_with_options(markdown, &opts);
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Woah cool! Yes this looks interesting! Ping me again if I forget to review this. |
|
This is exactly what I was trying to achieve in my fork, and I’d love to see this merged!I also appreciate that it doesn't introduce any breaking changes. Would you consider updating tui-markdown/README.md as well? For example: UsageUse with ratatui-imagelet input = "# Heading\n\n**bold**"; // this can come from whereever
let blocks = tui_markdown::parse(input);
for block in &content.blocks {
match block {
tui_markdown::MarkdownBlock::Text(text) => {
// Render text normally with ratatui
}
tui_markdown::MarkdownBlock::Image { url, alt, .. } => {
// Render image using terminal image protocol or fallback
}
}
}Use without ratatui-image and show images as textslet input = "# Heading\n\n**bold**"; // this can come from whereever
let text = tui_markdown::from_str(input);
text.render(area, &mut buf); |
|
I'd be happy to ! |
|
Hell, yes! This would also address #122 . @lightsofapollo: I haven't seen a @joshka Ping! :-) |
|
@joshka let me know whatcha think - I could throw a cylce into getting MarkdownBLock::Url if that's a usecase we want too |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #125 +/- ##
===========================================
+ Coverage 57.79% 78.85% +21.06%
===========================================
Files 7 9 +2
Lines 661 1329 +668
===========================================
+ Hits 382 1048 +666
- Misses 279 281 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Hi there (thanks for the pin). Sorry for the delay in this one, and apologies that it will still be a little longer. Lots of higher priority stuff happening in my life right now which should hopefully calm down soon. One thing that would help get this over the line easier would be to chop it into smaller easy to consume bits (I know Codex is pretty good at doing this split - I suspect CC is probably good at it too). Ideally small easy to review pieces that are obviously correct would be good to see here. @ano333333 @christo-auer would you be able to help get this over the line by doing a first review of the split up PRs if @lightsofapollo split the PRs? |
|
@joshka sure thing! |
|
@joshka I'd love to ❤️ 👍 |
|
Sorry for being so pestering, @lightsofapollo . Any news on splitting the PR? Very keen on testing. :-) |
|
Sorry! Been busy with a few other things. I should have asked earlier how do we want to split this? I think tables are probably the most import addition IIRC |
|
No problem! Just start with what you feel is right (and when you have time). Happy to test! |
|
I posted a use case in #122 for making the output of links and images configurable. |
I've been using tui-markdown in a project and kept running into missing element types — tables, images, math blocks, GFM alerts, footnotes, etc. I ended up implementing them and figured I'd open this up to see if you'd be interested in upstreaming any of it. Totally understand if the scope is too broad or if you'd prefer smaller PRs — happy to split things up.
What's in here
unicode-widthfor correct column sizing with emoji/CJK characters.parse()API that returns structuredMarkdownContentwithImageblocks, so consumers can plug in terminal image protocols (iTerm2/Kitty/Sixel) instead of just getting flattened alt-text.$...$) and display ($$...$$) rendering with dedicated styling.Options::code_theme()for built-in syntect themes, pluscode_theme_file()andcode_theme_custom()for loading.tmThemefiles.parse()/parse_with_options()— New API returningMarkdownContentwithTextandImageblock variants, enabling image-aware rendering by consumers.The existing
from_str()API is unchanged — everything here is additive. NewStyleSheetmethods are added for the new element types (table_header,table_border,alert,math_inline, etc.).