tui-markdown 0.3.9

A simple library for converting markdown to a Ratatui Text value
Documentation

Tui-markdown

An experimental Proof of Concept library for converting markdown content to a Ratatui Text value. See Markdown-reader for an example application that uses this library.

Crate badge Docs.rs Badge Deps.rs Badge License Badge Codecov.io Badge Discord Badge

GitHub Repository · API Docs · [Examples] · Changelog · Contributing

Installation

cargo add tui-markdown

Usage

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

Syntax highlighting themes

With the default highlight-code feature enabled, fenced code blocks whose language is recognized use the built-in Base16OceanDark syntax-highlighting theme. Pass a BuiltinCodeTheme to select a different bundled theme:

use tui_markdown::{from_str_with_options, BuiltinCodeTheme, Options};

let options = Options::default().code_theme(BuiltinCodeTheme::InspiredGitHub);
let markdown = r#"```rust
fn main() {}
```"#;
let text = from_str_with_options(markdown, &options);

CodeTheme::from_file reads and parses a TextMate .tmTheme file immediately. The returned theme owns the parsed data, so rendering does not access the file again:

use tui_markdown::{CodeTheme, CodeThemeLoadError, Options};

fn options() -> Result<Options, CodeThemeLoadError> {
    let theme = CodeTheme::from_file("themes/solarized.tmTheme")?;
    Ok(Options::default().code_theme(theme))
}

Use CodeTheme::from_textmate with include_str! to compile a theme into the application instead of reading it at runtime. The returned theme owns the parsed data and does not borrow the source string:

use tui_markdown::{CodeTheme, CodeThemeLoadError, Options};

fn options() -> Result<Options, CodeThemeLoadError> {
    let source = include_str!("../themes/my-theme.tmTheme");
    let theme = CodeTheme::from_textmate(source)?;
    Ok(Options::default().code_theme(theme))
}

Markdown presentation symbols

The renderer normally retains heading markers and frames block code with triple backticks. A custom style sheet can replace either symbol with StyleSheet::heading_marker() and StyleSheet::code_block_fence(), or return an empty string to hide it:

use ratatui::style::Style;
use tui_markdown::{from_str_with_options, DefaultStyleSheet, Options, StyleSheet};

#[derive(Clone, Copy)]
struct MinimalStyleSheet;

impl StyleSheet for MinimalStyleSheet {
    fn heading(&self, level: u8) -> Style {
        DefaultStyleSheet.heading(level)
    }

    fn code(&self) -> Style {
        DefaultStyleSheet.code()
    }

    fn link(&self) -> Style {
        DefaultStyleSheet.link()
    }

    fn blockquote(&self) -> Style {
        DefaultStyleSheet.blockquote()
    }

    fn heading_meta(&self) -> Style {
        DefaultStyleSheet.heading_meta()
    }

    fn metadata_block(&self) -> Style {
        DefaultStyleSheet.metadata_block()
    }

    fn heading_marker(&self, _level: u8) -> &str {
        ""
    }

    fn code_block_fence(&self) -> &str {
        ""
    }
}

let options = Options::new(MinimalStyleSheet);
let markdown = "# Heading\n\n```text\ncode\n```";
let text = from_str_with_options(markdown, &options);

assert_eq!(text.to_string(), "Heading\n\ncode");

The code-block fence choice is independent of syntax highlighting and applies to fenced and indented code blocks alike. Other presentation symbols, such as list markers, blockquote prefixes, image indicators, and table borders, retain their standard output.

Status

This is working code, but not every markdown feature is supported. PRs welcome!

  • Headings
  • Heading attributes / classes / anchors
  • Normal paragraphs
  • Block quotes
  • Nested block quotes
  • GFM alerts
  • Bold (strong)
  • Italic (emphasis)
  • Strikethrough
  • Ordered lists
  • Unordered lists
  • Code blocks
  • HTML
  • Math
  • Footnotes
  • Definition lists
  • Linebreak handling
  • Rule
  • Tables
  • Tasklists
  • Links
  • Images
  • Metadata blocks
  • Superscript
  • Subscript

Linebreaks are rendered with Markdown defaults: soft breaks become spaces, hard breaks insert a new line.

Images render as text fallbacks rather than terminal graphics. The default output uses [img] followed by the image description, or the destination when the description is empty. For example, Before ![diagram](diagram.png) after renders as Before [img] diagram after.

Use ImageFallback to show the destination instead, or to include it after the description:

use tui_markdown::{from_str_with_options, ImageFallback, Options};

let options = Options::default().image_fallback(ImageFallback::AltTextAndUrl);
let text = from_str_with_options("![diagram](diagram.png)", &options);
assert_eq!(text.to_string(), "[img] diagram (diagram.png)");

GFM tables render with Unicode box-drawing borders and honor left, center, and right column alignment:

| Name | Status |
|:-----|-------:|
| API  | Ready  |
┌──────┬────────┐
│ Name │ Status │
├──────┼────────┤
│ API  │  Ready │
└──────┴────────┘

Column widths use terminal display width, so wide CJK and emoji characters remain aligned. Use StyleSheet::table_header() for header cells, StyleSheet::table_cell() for body cells, and StyleSheet::table_border() for the box-drawing borders. Cell styles cover content and padding while preserving inline formatting unless they set the same style property.

Links are rendered as label (URL). The link style applies to both the visible label and URL while preserving nested inline formatting such as bold text.

GFM alerts render a bold icon and canonical English label above their quoted body. Customize each kind's color with StyleSheet::alert(), its terminal-friendly icon with StyleSheet::alert_icon(), and its label with StyleSheet::alert_label(). Returning an empty icon or label displays only the other component.

Raw inline HTML tags and HTML blocks are displayed literally rather than interpreted as terminal markup. They are dimmed by default and can be customized with StyleSheet::html().

Inline and display math keep their $...$ and $$...$$ delimiters visible. Inline math is magenta and italic by default, while display math is magenta and preserves multiline formulas as separate terminal lines. Customize these styles with StyleSheet::math_inline() and StyleSheet::math_display().

Footnote references such as [^source] are displayed as [source], and definitions are displayed as [source]: .... References are dim and italic by default, while definitions are dim. Customize these styles with StyleSheet::footnote_ref() and StyleSheet::footnote_def().

Definition-list terms are bold by default, with each description rendered on its own line after a colon-and-space prefix. Customize them with StyleSheet::definition_term() and StyleSheet::definition_description().

Metadata blocks are rendered using the metadata block style so front matter is visible, including the delimiter lines (for example --- in YAML-style blocks).

use ratatui::text::Text;
use tui_markdown::from_str;

let markdown = r#"---
title: Demo
tags:
  - one
  - two
---

Body
"#;

let text = from_str(markdown);
assert_eq!(
    text,
    Text::from_iter([
        "---".into(),
        "title: Demo".into(),
        "tags:".into(),
        "  - one".into(),
        "  - two".into(),
        "---".into(),
        "".into(),
        "Body".into(),
    ])
);

License

Copyright (c) 2024 Josh McKinney

This project is licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

See CONTRIBUTING.md.