API Reference
February 17, 2026 · View on GitHub
All public types live in the markdown namespace. Headers are in markdown/include/markdown/.
ast.hpp -- AST Node Types
Defines the Markdown abstract syntax tree representation.
NodeType (enum class)
enum class NodeType {
Document, // Root container node
Heading, // Heading block (level 1-6)
Paragraph, // Paragraph block
Text, // Leaf text content
Emphasis, // Italic (*text* or _text_)
Strong, // Bold (**text** or __text__)
Link, // Hyperlink [text](url)
ListItem, // Single list item
BulletList, // Unordered list container
OrderedList, // Ordered list container
CodeInline, // Inline code (`code`)
CodeBlock, // Fenced code block (```)
BlockQuote, // Block quote (> text)
SoftBreak, // Soft line break (single newline)
HardBreak, // Hard line break (two spaces + newline)
ThematicBreak, // Horizontal rule (---)
Image, // Image ()
};
ASTNode (struct)
struct ASTNode {
NodeType type = NodeType::Document;
std::string text; // Leaf text content (Text, CodeInline, CodeBlock)
std::string url; // URL for Link and Image nodes
int level = 0; // Heading level (1-6), 0 for non-headings
int list_start = 1; // Starting number for OrderedList
std::vector<ASTNode> children;
};
MarkdownAST (type alias)
using MarkdownAST = ASTNode;
The root node is always NodeType::Document with children representing top-level blocks.
Example: Walking an AST
#include "markdown/ast.hpp"
#include "markdown/parser.hpp"
void print_tree(markdown::ASTNode const& node, int depth = 0) {
for (int i = 0; i < depth; ++i) std::cout << " ";
std::cout << static_cast<int>(node.type);
if (!node.text.empty()) std::cout << " \"" << node.text << "\"";
if (!node.url.empty()) std::cout << " url=" << node.url;
if (node.level > 0) std::cout << " level=" << node.level;
std::cout << "\n";
for (auto const& child : node.children)
print_tree(child, depth + 1);
}
auto parser = markdown::make_cmark_parser();
auto ast = parser->parse("# Hello **world**");
print_tree(ast);
// Output:
// 0 (Document)
// 1 level=1 (Heading)
// 3 "Hello " (Text)
// 5 (Strong)
// 3 "world" (Text)
parser.hpp -- Markdown Parser
MarkdownParser (abstract class)
class MarkdownParser {
public:
virtual ~MarkdownParser() = default;
// Parse Markdown text into an AST.
// Returns false if parsing failed; out will contain a raw-text fallback.
virtual bool parse(std::string_view input, MarkdownAST& out) = 0;
// Convenience overload: returns AST directly, ignoring success/failure.
MarkdownAST parse(std::string_view input);
};
make_cmark_parser()
std::unique_ptr<MarkdownParser> make_cmark_parser();
Factory function that creates a parser backed by cmark-gfm. The cmark-gfm headers and types are completely hidden inside the implementation. This is the only way to obtain a parser instance.
Example: Parsing Markdown
#include "markdown/parser.hpp"
auto parser = markdown::make_cmark_parser();
// Two-overload usage:
// 1. Convenience (returns AST)
auto ast = parser->parse("**bold** and *italic*");
// 2. Output parameter (returns success flag)
markdown::MarkdownAST ast2;
bool ok = parser->parse("Some text", ast2);
if (!ok) {
// Parsing failed -- ast2 contains a raw-text fallback
}
viewer.hpp -- Markdown Viewer Component
LinkEvent (enum class)
enum class LinkEvent {
Focus, // Item received Tab focus
Press, // Item was activated with Enter
};
ViewerKeys (struct)
struct ViewerKeys {
ftxui::Event activate = ftxui::Event::Return; // Enter edit/link-press mode
ftxui::Event deactivate = ftxui::Event::Escape; // Exit active mode
ftxui::Event next = ftxui::Event::Tab; // Cycle forward through links
ftxui::Event prev = ftxui::Event::TabReverse; // Cycle backward through links
};
Configurable key bindings for viewer interaction. Override any field to change the corresponding key. Default values match the standard terminal conventions.
Viewer (class)
class Viewer {
public:
explicit Viewer(std::unique_ptr<MarkdownParser> parser);
Content Management
// Set the Markdown text to display.
// Triggers re-parse on next render.
void set_content(std::string_view markdown_text);
Scroll Control
// Set scroll position as a ratio: 0.0 = top, 1.0 = bottom.
void set_scroll(float ratio);
// Show or hide the vertical scroll indicator. Default: true.
void show_scrollbar(bool show);
// Query current scroll ratio.
float scroll() const;
// Query viewport/content height info (populated after render).
ScrollInfo const& scroll_info() const;
// Query scrollbar visibility.
bool scrollbar_visible() const;
The built-in component handles scrolling via keyboard and mouse:
| Input | Action |
|---|---|
| Arrow Up/Down | Scroll by 5% |
| Page Up/Down | Scroll by one viewport height (proportional) |
| Home / End | Jump to top / bottom |
| Mouse Wheel | Scroll by 5% per tick |
Link Interaction
// Register a callback for link focus and press events.
// The callback receives the link URL and the event type (Focus or Press).
void on_link_click(
std::function<void(std::string const&, LinkEvent)> callback);
Tab Focus Integration
// When set, Tab past last link calls cb(+1), Shift+Tab before first calls cb(-1).
// The viewer clears its focus and deactivates. If not set, Tab wraps (default).
void on_tab_exit(std::function<void(int direction)> callback);
// Parent calls this to give the viewer link focus from a direction.
// direction > 0: focus first link; direction < 0: focus last link.
// Returns true if focus was accepted (there are links), false otherwise.
// Sets active(true) and fires on_link_click with LinkEvent::Focus.
bool enter_focus(int direction);
These two methods enable Tab cycling between parent UI elements and viewer links. The parent intercepts Tab when the viewer is not active, calls enter_focus() to hand off, and receives control back via the on_tab_exit callback. If no callback is set, Tab wraps through links as before.
Key Bindings
// Override the default key bindings (Enter, Esc, Tab, Shift+Tab).
void set_keys(ViewerKeys const& keys);
// Query the current key bindings (useful for parent event handlers).
ViewerKeys const& keys() const;
The keys() accessor lets parent components reference the same keys the viewer uses internally, so event checks stay consistent even when keys are customized.
Theming
// Apply a theme. Only triggers re-build if the theme name changed.
void set_theme(Theme const& theme);
// Query current theme.
Theme const& theme() const;
Component
// Get the FTXUI Component for embedding in layouts.
// The component is created on first call and cached.
ftxui::Component component();
Active State
// Whether the viewer is in "active" mode (accepting link navigation).
bool active() const;
void set_active(bool a);
Focus Tracking
// Focus index into link_targets: -1 = nothing focused, 0..N-1 = link index.
int focused_index() const;
// URL of the currently focused link. Empty string if nothing is focused.
std::string focused_value() const;
// True if any link is focused.
bool is_link_focused() const;
Embed Mode
// In embed mode, the viewer skips its internal scroll frame and flex.
// The caller is responsible for wrapping the element in a scroll container.
// Useful for combining viewer content with other elements (like email headers)
// in a single scrollable frame.
void set_embed(bool embed);
bool is_embed() const;
};
Example: Basic Viewer
#include "markdown/parser.hpp"
#include "markdown/viewer.hpp"
#include <ftxui/component/screen_interactive.hpp>
int main() {
auto viewer = std::make_shared<markdown::Viewer>(
markdown::make_cmark_parser());
viewer->set_content("# Hello\n\nThis is **bold** and [a link](https://example.com).");
viewer->show_scrollbar(true);
viewer->on_link_click([](std::string const& url, markdown::LinkEvent ev) {
if (ev == markdown::LinkEvent::Press) {
// Handle link activation
}
});
auto screen = ftxui::ScreenInteractive::Fullscreen();
screen.Loop(viewer->component());
}
Example: Tab Integration with Parent
auto viewer = std::make_shared<markdown::Viewer>(
markdown::make_cmark_parser());
viewer->set_content("Check the [docs](https://docs.example.com).");
// Viewer calls back when Tab exits past bounds
viewer->on_tab_exit([&](int direction) {
// direction +1: Tab past last link → move focus to next parent element
// direction -1: Shift+Tab past first link → move to previous parent element
});
// Parent hands off Tab focus to the viewer
viewer->enter_focus(+1); // focus first link
viewer->enter_focus(-1); // focus last link
// In the parent's event handler, use viewer->keys() for consistency:
if (event == viewer->keys().next && !viewer->active()) {
viewer->enter_focus(+1); // give viewer Tab focus
}
// Optionally override keys before creating the component:
markdown::ViewerKeys keys;
keys.next = ftxui::Event::Character('n');
keys.prev = ftxui::Event::Character('p');
viewer->set_keys(keys);
editor.hpp -- Markdown Editor Component
Editor (class)
class Editor {
public:
explicit Editor();
Component
// Get the FTXUI Component for embedding in layouts.
ftxui::Component component();
Content
// Read-only reference to the current document text.
std::string const& content() const;
// Replace the entire document content.
void set_content(std::string text);
Cursor Information
// Current cursor line (1-based).
int cursor_line() const;
// Current cursor column (1-based).
int cursor_col() const;
// Cursor byte offset in the text.
int cursor_position() const;
// Total number of lines in the document.
int total_lines() const;
State
// Whether the editor is in "active" mode (accepting text input).
// Enter activates, Esc deactivates.
bool active() const;
Cursor Manipulation
// Set cursor position by byte offset.
void set_cursor_position(int byte_offset);
// Set cursor position by line and column (1-based).
void set_cursor(int line, int col);
// Move cursor up (negative) or down (positive) by delta lines,
// preserving the current column. Clamped to document bounds.
void move_cursor_lines(int delta);
The built-in component handles scrolling via keyboard and mouse:
| Input | Action |
|---|---|
| Page Up/Down | Move cursor by 20 lines |
| Mouse Wheel | Move cursor by 3 lines per tick |
Theming
// Apply a theme for syntax highlighting colors.
void set_theme(Theme const& theme);
};
Example: Editor with Live Preview
#include "markdown/editor.hpp"
#include "markdown/viewer.hpp"
#include "markdown/parser.hpp"
auto editor = std::make_shared<markdown::Editor>();
editor->set_content("# My Document\n\nStart typing...");
auto viewer = std::make_shared<markdown::Viewer>(
markdown::make_cmark_parser());
// In a Renderer lambda:
viewer->set_content(editor->content()); // Live update
// Sync scroll to cursor position:
float ratio = 0.0f;
if (editor->total_lines() > 1) {
ratio = static_cast<float>(editor->cursor_line() - 1) /
static_cast<float>(editor->total_lines() - 1);
}
viewer->set_scroll(ratio);
dom_builder.hpp -- AST to FTXUI DOM
LinkTarget (struct)
struct LinkTarget {
std::vector<ftxui::Box> boxes; // Bounding boxes on screen
std::string url; // The link URL
};
Each link in the rendered output produces a LinkTarget with one or more bounding boxes (a link that wraps across lines will have multiple boxes).
DomBuilder (class)
class DomBuilder {
public:
// Convert an AST into an FTXUI Element tree.
// focused_link: index of link to highlight (-1 for none).
// theme: styling configuration.
ftxui::Element build(MarkdownAST const& ast,
int focused_link = -1,
Theme const& theme = theme_default());
// Query link targets after build().
// Returns the list of links found during the last build.
std::vector<LinkTarget> const& link_targets() const;
};
Example: Direct DOM Building
#include "markdown/parser.hpp"
#include "markdown/dom_builder.hpp"
#include <ftxui/screen/screen.hpp>
auto parser = markdown::make_cmark_parser();
auto ast = parser->parse("Hello **world** with a [link](https://example.com)");
markdown::DomBuilder builder;
auto element = builder.build(ast, /*focused_link=*/0, markdown::theme_colorful());
// Render to a screen
auto screen = ftxui::Screen::Create(ftxui::Dimension::Fixed(60),
ftxui::Dimension::Fixed(5));
ftxui::Render(screen, element);
std::cout << screen.ToString() << std::endl;
// Query link positions
for (auto const& link : builder.link_targets()) {
std::cout << "Link: " << link.url
<< " at " << link.boxes.size() << " box(es)\n";
}
Rendering Rules
| AST Node | FTXUI Rendering |
|---|---|
| Heading (level 1) | theme.heading1 decorator (default: bold + underlined) |
| Heading (level 2) | theme.heading2 decorator (default: bold) |
| Heading (level 3+) | theme.heading3 decorator (default: bold + dim) |
| Strong | ftxui::bold |
| Emphasis | ftxui::italic (where supported) |
| Link | ftxui::underlined + theme.link + ftxui::reflect() for boxes |
| Link (focused) | adds ftxui::inverted + ftxui::focus |
| BulletList | " * " prefix per item, indentation for nesting |
| OrderedList | " N. " prefix per item, numbering from list_start |
| CodeInline | theme.code_inline decorator (default: inverted) |
| CodeBlock | bordered box + theme.code_block decorator |
| BlockQuote | " | " prefix + theme.blockquote decorator |
| ThematicBreak | ftxui::separator() |
| Image | "[img: alt_text]" placeholder |
| SoftBreak | single space |
| HardBreak | line break |
highlight.hpp -- Lexical Syntax Highlighting
highlight_markdown_syntax()
ftxui::Element highlight_markdown_syntax(
std::string_view text,
Theme const& theme = theme_default());
Highlights Markdown syntax markers in raw text. Returns an FTXUI Element where syntax characters (#, *, _, `, [, ], (, ), >, -) are styled with theme.syntax. Regular text is unstyled.
This is lexical highlighting -- it colors characters based on pattern matching, not AST structure. It does not understand nesting or context.
highlight_markdown_with_cursor()
ftxui::Element highlight_markdown_with_cursor(
std::string_view text,
int cursor_position, // Byte offset of cursor in text
bool focused, // Whether cursor should be visible
bool hovered, // Whether hover styling applies
bool show_line_numbers = false,
Theme const& theme = theme_default());
Same as highlight_markdown_syntax() but with an embedded cursor at the given byte position. When focused is true, the character at the cursor position is rendered with inverted colors. When show_line_numbers is true, a line number gutter is prepended.
Used internally by the Editor's InputOption::transform callback.
Example: Rendering Highlighted Text
#include "markdown/highlight.hpp"
#include <ftxui/screen/screen.hpp>
std::string text = "# Hello **world**";
auto element = markdown::highlight_markdown_syntax(text);
auto screen = ftxui::Screen::Create(ftxui::Dimension::Fixed(40),
ftxui::Dimension::Fixed(1));
ftxui::Render(screen, element);
// Output: "# Hello **world**" with # and ** in yellow+dim
theme.hpp -- Theme Configuration
Theme (struct)
struct Theme {
std::string name; // Theme identifier (used for change detection)
// Editor syntax highlighting
ftxui::Decorator syntax; // Color for syntax markers (#, *, `, etc.)
ftxui::Decorator gutter; // Color for line number gutter
// Viewer elements
ftxui::Decorator heading1; // H1 style
ftxui::Decorator heading2; // H2 style
ftxui::Decorator heading3; // H3-H6 style
ftxui::Decorator link; // Link style (added on top of underlined)
ftxui::Decorator code_inline; // Inline code style
ftxui::Decorator code_block; // Code block content style
ftxui::Decorator blockquote; // Block quote text style
};
Built-in Themes
Theme const& theme_default(); // "Default" -- minimal, works everywhere
Theme const& theme_high_contrast(); // "Contrast" -- bold + white, high visibility
Theme const& theme_colorful(); // "Colorful" -- color-coded elements
| Field | Default | Contrast | Colorful |
|---|---|---|---|
| syntax | yellow + dim | white + bold | green |
| gutter | dim | white | blue + dim |
| heading1 | bold + underlined | bold + underlined | bold + underlined + cyan |
| heading2 | bold | bold | bold + green |
| heading3 | bold + dim | bold | bold + yellow |
| link | nothing | bold | magenta |
| code_inline | inverted | inverted | inverted + yellow |
| code_block | nothing | nothing | green |
| blockquote | dim | nothing | blue |
Example: Custom Theme
#include "markdown/theme.hpp"
markdown::Theme my_theme{
"MyTheme",
ftxui::color(ftxui::Color::Red), // syntax
ftxui::dim, // gutter
ftxui::Decorator(ftxui::bold) | ftxui::underlined, // heading1
ftxui::bold, // heading2
ftxui::Decorator(ftxui::bold) | ftxui::dim, // heading3
ftxui::color(ftxui::Color::Blue), // link
ftxui::inverted, // code_inline
ftxui::nothing, // code_block
ftxui::dim, // blockquote
};
viewer->set_theme(my_theme);
editor->set_theme(my_theme);
MSVC note: When composing decorators,
ftxui::bold | ftxui::underlinedfails on MSVC because both are raw function pointers. Wrap the first operand:ftxui::Decorator(ftxui::bold) | ftxui::underlined. Decorators returned byftxui::color()are alreadystd::functionand compose normally.
scroll_frame.hpp -- Custom Scroll Container
ScrollInfo (struct)
struct ScrollInfo {
int viewport_height = 0;
int content_height = 0;
};
Populated by DirectScrollFrame during layout. Provides the viewport and content dimensions needed for proportional scroll calculations (e.g. viewport-sized page steps).
DirectScrollFrame (class)
class DirectScrollFrame : public ftxui::Node {
public:
DirectScrollFrame(ftxui::Element child, float ratio,
ScrollInfo* info = nullptr);
};
A custom FTXUI Node that scrolls its child by a direct ratio. The scroll offset is computed as ratio * scrollable_height, where scrollable_height = content_height - viewport_height. The ratio is clamped to [0.0, 1.0].
Unlike FTXUI's yframe, which centers the element that has ftxui::focus, DirectScrollFrame ignores focus and positions purely by ratio.
If a ScrollInfo* is provided, the viewport and content heights are written to it during layout.
direct_scroll()
ftxui::Element direct_scroll(ftxui::Element child, float ratio,
ScrollInfo* info = nullptr);
Convenience function that creates a DirectScrollFrame. Pass a ScrollInfo* to capture layout dimensions.
Example: Scrollable Content
#include "markdown/scroll_frame.hpp"
ftxui::Elements lines;
for (int i = 0; i < 100; ++i) {
lines.push_back(ftxui::text("Line " + std::to_string(i)));
}
float scroll_ratio = 0.5f; // Middle of content
markdown::ScrollInfo info;
auto scrollable = markdown::direct_scroll(
ftxui::vbox(std::move(lines)) | ftxui::vscroll_indicator,
scroll_ratio, &info);
// Render in a fixed viewport
auto screen = ftxui::Screen::Create(ftxui::Dimension::Fixed(40),
ftxui::Dimension::Fixed(10));
ftxui::Render(screen, scrollable);
// After render: info.viewport_height and info.content_height are set.
text_utils.hpp -- UTF-8 Utilities
All functions are inline and header-only.
Character Length and Counting
// Byte length of the UTF-8 character starting at leading_byte.
size_t utf8_byte_length(char leading_byte);
// Count UTF-8 characters (not bytes) in text.
int utf8_char_count(std::string_view text);
// Convert character index (0-based) to byte offset.
size_t utf8_char_to_byte(std::string_view text, int char_index);
Unicode Codepoint
// Decode UTF-8 codepoint starting at data[0..len).
uint32_t utf8_codepoint(const char* data, size_t len);
// Terminal display width of a Unicode codepoint.
// Returns 2 for CJK Unified Ideographs, Fullwidth forms, Hangul,
// Hiragana, Katakana, and other East Asian Wide characters.
// Returns 1 for everything else.
int codepoint_width(uint32_t cp);
Display Width
// Total terminal display width of a UTF-8 string.
// Accounts for wide characters (CJK etc.) that occupy 2 columns.
int utf8_display_width(std::string_view text);
// Map terminal visual column (0-based) to byte offset in text.
// Accounts for wide characters.
size_t visual_col_to_byte(std::string_view text, int col);
Line Utilities
// Split text into lines (without newline characters).
// A trailing newline produces an empty final element.
std::vector<std::string_view> split_lines(std::string_view text);
Gutter Utilities
// Number of digits needed to display total_lines (e.g. 100 -> 3).
int gutter_width(int total_lines);
// Total gutter width: digits + 3 (for " | " separator).
int gutter_chars(int total_lines);
Example: UTF-8 Width Calculation
#include "markdown/text_utils.hpp"
// ASCII
assert(markdown::utf8_display_width("Hello") == 5);
// CJK characters (2 columns each)
assert(markdown::utf8_display_width("\xe4\xb8\x96\xe7\x95\x8c") == 4); // "世界"
// Mixed
assert(markdown::utf8_display_width("Hi\xe4\xb8\x96") == 4); // "Hi世" = 2 + 2