Skip to content

Add tables, images, math, alerts, footnotes, configurable themes, and parse() API#125

Open
lightsofapollo wants to merge 5 commits intojoshka:mainfrom
lightsofapollo:james/enhance
Open

Add tables, images, math, alerts, footnotes, configurable themes, and parse() API#125
lightsofapollo wants to merge 5 commits intojoshka:mainfrom
lightsofapollo:james/enhance

Conversation

@lightsofapollo
Copy link
Copy Markdown

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

  • Tables — Full GFM table support with Unicode box-drawing borders, column alignment (left/center/right), header styling. Uses unicode-width for correct column sizing with emoji/CJK characters.
  • Images — Alt-text fallback rendering with an image indicator prefix. Also adds a new parse() API that returns structured MarkdownContent with Image blocks, so consumers can plug in terminal image protocols (iTerm2/Kitty/Sixel) instead of just getting flattened alt-text.
  • Math — Inline ($...$) and display ($$...$$) rendering with dedicated 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 — Renders inline and block HTML as dim text instead of silently dropping it.
  • Configurable syntax themesOptions::code_theme() for built-in syntect themes, plus code_theme_file() and code_theme_custom() for loading .tmTheme files.
  • parse() / parse_with_options() — New API returning MarkdownContent with Text and Image block variants, enabling image-aware rendering by consumers.

The existing from_str() API is unchanged — everything here is additive. New StyleSheet methods are added for the new element types (table_header, table_border, alert, math_inline, etc.).

lightsofapollo and others added 5 commits February 7, 2026 18:57
…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>
@joshka
Copy link
Copy Markdown
Owner

joshka commented Feb 11, 2026

Woah cool! Yes this looks interesting! Ping me again if I forget to review this.

@ano333333
Copy link
Copy Markdown

ano333333 commented Feb 13, 2026

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:

Usage

Use with ratatui-image

let 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 texts

let input = "# Heading\n\n**bold**"; // this can come from whereever
let text = tui_markdown::from_str(input);
text.render(area, &mut buf);

@lightsofapollo
Copy link
Copy Markdown
Author

I'd be happy to !

@christo-auer
Copy link
Copy Markdown

Hell, yes! This would also address #122 .

@lightsofapollo: I haven't seen a MarkdownBlock::Url. Any plans for that? This would be useful to collect URLs to make them available for the user to open them.

@joshka Ping! :-)

@lightsofapollo
Copy link
Copy Markdown
Author

@joshka let me know whatcha think - I could throw a cylce into getting MarkdownBLock::Url if that's a usecase we want too

@codecov-commenter
Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.34556% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.85%. Comparing base (d2438c9) to head (819cbca).

Files with missing lines Patch % Lines
tui-markdown/src/options.rs 65.85% 14 Missing ⚠️
tui-markdown/src/lib.rs 97.32% 11 Missing ⚠️
markdown-reader/src/main.rs 0.00% 3 Missing ⚠️
tui-markdown/src/tables.rs 98.42% 3 Missing ⚠️
tui-markdown/src/content.rs 93.33% 1 Missing ⚠️
tui-markdown/src/style_sheet.rs 97.91% 1 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@joshka
Copy link
Copy Markdown
Owner

joshka commented Feb 24, 2026

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?

@christo-auer
Copy link
Copy Markdown

@joshka sure thing!

@ano333333
Copy link
Copy Markdown

ano333333 commented Feb 24, 2026

@joshka I'd love to ❤️ 👍

@christo-auer
Copy link
Copy Markdown

Sorry for being so pestering, @lightsofapollo . Any news on splitting the PR? Very keen on testing. :-)

@lightsofapollo
Copy link
Copy Markdown
Author

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

@christo-auer
Copy link
Copy Markdown

No problem! Just start with what you feel is right (and when you have time). Happy to test!

@christo-auer
Copy link
Copy Markdown

I posted a use case in #122 for making the output of links and images configurable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants