mdview
A minimal terminal markdown reader written in Rust. Published on crates.io as mdview-tui; the installed binary is mdview.
mdview notes.md
mdview --help # usage
mdview --version # version
mdview --licenses # what this binary contains
What it does
Renders a single markdown file in a centered, fixed-width column inside the alternate screen. Toggle between the rendered view and the raw source with Tab. Scroll with the trackpad/mouse wheel, or with j/k and arrow keys — a thumb on the right border tracks your position. Press ? for the full key list.
YAML front matter — the ----delimited block some files open with — renders as an aligned key/value header with a rule under it, not as markdown. Body paragraphs are justified to the column width. Standalone images render inline at up to 80% of the column width, or at their own size if they are smaller than that — as the real picture in terminals that support iTerm2's inline image protocol (iTerm2, WezTerm, Rio, including over ssh), and as half-block (▀) pixel previews everywhere else. Press o or click one (the bottom bar shows click: <file> while hovering) to open the original in your system viewer, which is also how SVG and PDF are handled since neither can be drawn inline. LaTeX math renders as Unicode: $\gamma^d$ → γᵈ, $h_{\min}$ → hₘᵢₙ, \mathbb{E}[S] → 𝔼[S], with $$…$$ display equations centered on their own line. Complex TeX degrades to readable linear form (\frac{a}{b} → (a)/(b)) rather than raw source.
Install
The crate is published as mdview-tui (the mdview name on crates.io was already taken), but the binary it installs is mdview — so you still run mdview notes.md after installing.
Or build from source:
Keys
| Key | Action |
|---|---|
Tab |
Toggle rendered ↔ raw view |
j / ↓ |
Scroll down one line |
k / ↑ |
Scroll up one line |
Space / PgDn |
Scroll down a page |
PgUp |
Scroll up a page |
g / Home |
Jump to top |
G / End |
Jump to bottom |
| Mouse wheel | Scroll |
- / + |
Narrow / widen the content column |
o |
Open first visible image in system viewer |
| Left-click on image | Open that image in system viewer |
r |
Reload the file from disk |
y |
Copy the file path to the clipboard |
? |
Toggle the keybinding overlay |
q / Esc |
Quit |
Tweaks
A few constants near the top of src/main.rs control the look:
const MAX_CONTENT_WIDTH: u16 = 130; // cap on column width
const MIN_CONTENT_WIDTH: u16 = 80; // floor for -/+ adjustments
const DEFAULT_CONTENT_WIDTH: u16 = 90; // startup width target
const SIDE_MARGIN: u16 = 4; // breathing room left+right
const WIDTH_STEP: u16 = 4; // cells per -/+ press
const FRAME_COLOR: Color = DarkGray;
const TITLE_COLOR: Color = Green;
Startup column = min(DEFAULT_CONTENT_WIDTH, terminal_width - SIDE_MARGIN). Adjust live with - and + (clamped to [MIN_CONTENT_WIDTH, min(MAX_CONTENT_WIDTH, terminal_width - SIDE_MARGIN)]); each press re-renders the cached markdown.
Images have their own constants near the top of src/render.rs:
const IMAGE_WIDTH_PCT: u32 = 80; // share of the column an image may fill
const IMAGE_CELL_PX: u32 = 8; // assumed cell width; decides when an image is "small"
const IMAGE_PX_PER_CELL: u32 = 8; // inline-protocol resolution: sharpness vs bytes per frame
An image is drawn min(IMAGE_WIDTH_PCT of the column, source_width / IMAGE_CELL_PX) cells wide, with the height following the source aspect ratio — so large figures fit the column and small ones keep their own size rather than being blown up. Height is not capped: a tall image is simply taller than the viewport and you scroll through it.
IMAGE_PX_PER_CELL affects only the inline protocol. Raising it is sharper on a retina display, but there are no image ids in the protocol, so every scroll step resends the payload — push it too far and the half-block underlay starts showing through while scrolling. Set MDVIEW_NO_INLINE_IMAGES=1 to turn inline rendering off entirely and use half-blocks everywhere.
Stack
ratatui+crossterm— TUI rendering and inputpulldown-cmark— CommonMark parsersyntect— code-block syntax highlightingimage— decoding and PNG re-encoding for inline imagesunicode-width— cell widths for table layoutarboard— clipboardanyhow— error handling
The markdown renderer is a hand-rolled walk over the pulldown-cmark event stream — see src/render.rs. It handles headings (with ═/─ underbars on h1/h2), bold/italic/strikethrough/links, inline code, fenced code blocks (syntect-highlighted with a dark background and a language tag), ordered and unordered lists with nesting, blockquotes with a left bar, horizontal rules, tables with box-drawing borders that shrink columns to fit the content width, justified paragraphs, half-block image previews, and TeX math via a small hand-rolled converter (src/math.rs). On terminals that speak iTerm2's inline image protocol, src/graphics.rs paints the real image over those half-block rows, cropped to whatever part of it is on screen; set MDVIEW_NO_INLINE_IMAGES=1 to force the half-block fallback.
License
MIT — see LICENSE.