March 18, 2026
7 pull requests merged across 2 repos
bahdotsh/mdterm
- Inline links no longer show the raw URL after the link text — just the text, like GitHub renders it (fixes #22)
- Link picker (
fkey) now showstext → URLfor each entry instead of just the URL, and same-URL links with different text get separate entries (fixes #23)
Changes
markdown.rs: Remove URL span fromEnd(TagEnd::Link)handlerviewer.rs: Replace URL-based dedup with adjacency-based merging for link extraction; update overlay to rendertext → URLwith distinct colors
Test plan
- Open
test.md, verify links show only text inline (no URL appended) - Press
f, verify each link shows "text → URL" format - Confirm same-URL links (
readme 1,readme 2) appear as separate entries - Image links still show URL-only in the picker
Closes #22, closes #23
- Move all expensive image work (Lanczos3 resize, PNG/Sixel/base64 encode) to background threads — the main thread never touches image pixels, eliminating UI hangs during image loading
- Replace
rebuild()with lightweightfinalize_layout()when image fetches complete — adjusts placeholder rows and rebuilds indices without re-parsing the entire markdown document - Store raw images as
Arc<DynamicImage>to avoid cloning large pixel buffers when handing off to pre-render threads
Details
The old code fetched images in the background but ran pre_render() synchronously on the main thread. For large images this meant hundreds of milliseconds of Lanczos3 resizing and protocol-specific encoding blocking the event loop. It also called rebuild() (full markdown re-parse) on every single image completion.
Now there are two background channels:
1. Fetch channel (existing) — downloads/decodes raw images
2. Render channel (new) — resizes and encodes for the target protocol (Kitty/iTerm2/Sixel/HalfBlock)
Results from stale terminal widths or cancelled file switches are automatically discarded. The viewer shows a "Loading" placeholder until both fetch and pre-render complete.
Test plan
-
cargo build— compiles cleanly -
cargo test— all 120 tests pass -
cargo clippy— no warnings - Manual: open a markdown file with multiple remote images, verify smooth scrolling during load
- Manual: resize terminal while images are loading, verify images re-render at correct size
- Manual: switch files while images are loading, verify no stale images appear
- Replace transient status bar messages with a centered toast overlay (auto-expires after 1s)
- Click-to-copy: click any heading section, list, or code block to copy its content to clipboard
- Add
LineMeta::ListItem { list_id }to track list membership for whole-list copy - Pre-compute TOC section ranges and content for instant heading-section copy
- Pointer cursor shown over all copyable elements (headings, lists, code blocks)
- Fix UTF-8 panic in heading label truncation (byte-index slice → char-safe truncation)
- Fix toast overlay positioning to account for title bar and clamp on small terminals
- Update README to reflect new click-to-copy behavior
Test plan
- Click a code block → toast says "Code block copied", clipboard has the code
- Click a heading line → toast says "Copied: <heading>", clipboard has the full section text
- Click a list item → toast says "List copied", clipboard has the entire list
- Click a link → still opens/navigates as before (no copy)
- Hover over code/heading/list → pointer cursor appears
- Hover over normal text → default cursor
- Toast disappears after ~1 second
-
Ystill copies full document,cstill copies nearest code block - Test with a heading containing emoji/CJK characters — no panic on click
- Resize to a very small terminal — toast stays within bounds
- Resolve relative markdown links (e.g.
docker.md,./foo.md,file.md#section) against the current file's directory and open them in the viewer instead of showing "unsupported URL scheme" - Add a navigation history stack with
Backspaceto go back to the previous file and scroll position - Add test files (
test.mdsection +test_linked.md) to exercise local link navigation
Closes #11
Test plan
-
cargo run -- test.md, pressf, select a local file link — it opens in the viewer - Press
Backspace— returns totest.mdat the original scroll position - Try the anchor link (
test_linked.md#heading-for-anchor-test) — opens file and scrolls to heading - Try the nonexistent link — shows blocked message
- Click a local file link directly with mouse — same behavior
- Press
F1— help screen showsBackspacekeybinding -
cargo testpasses,cargo clippyclean
Adds Sixel image protocol support as a middle tier between iTerm2 and the Unicode half-block fallback. Terminals like foot, mlterm, mintty, and contour can now render actual pixel-level images instead of the half-block approximation.
- Zero new dependencies — Sixel encoding implemented in-house (median-cut quantization to 256 colors, 6-row band encoding with RLE compression)
- Auto-detection for known Sixel terminals via
TERM_PROGRAM/TERMenv vars - Manual override via
MDTERM_IMAGE_PROTOCOL=sixelfor terminals not auto-detected - Block rendering with crop caching for smooth scrolling (same pattern as iTerm2 path)
- 8 new unit tests covering encoding, RLE, and quantization
Protocol priority
Kitty > iTerm2 > Sixel > HalfBlock
Terminals that support multiple protocols (e.g. WezTerm supports both Kitty and Sixel) will use the most efficient one.
Auto-detected Sixel terminals
| Terminal | Detection method |
|---|---|
| foot | TERM_PROGRAM=foot or TERM=foot/foot-extra |
| mlterm | TERM_PROGRAM=mlterm or MLTERM env var |
| mintty | TERM_PROGRAM=mintty |
| contour | TERM_PROGRAM=contour |
Test plan
-
cargo check— compiles clean -
cargo clippy— zero warnings -
cargo test— all 117 tests pass (109 existing + 8 new) -
cargo build --release— release build succeeds - Manual test on a Sixel-capable terminal (e.g.
MDTERM_IMAGE_PROTOCOL=sixel mdterm README.md)
— mouse capture was grabbing all events, preventing text selection and link clicks.
- Click to open links: Left-clicking a link in the rendered markdown opens it in the default browser (uses the same
open::that()as the link picker) - Hover cursor: Mouse pointer changes to a hand cursor (OSC 22) when hovering over links, resets when moving off. Terminals that don't support OSC 22 silently ignore it
- Mouse capture toggle (
m): Pressmto disable mouse capture entirely so the terminal can handle text selection natively. Press again to re-enable scroll wheel support. Status message confirms the state
Test plan
-
cargo clippy— no warnings -
cargo test— 102 passed, 0 failed - Open a markdown file with links, verify left-click opens them in browser
- Hover over a link and verify cursor changes to pointer (in supported terminals: kitty, foot, xterm)
- Press
mto toggle mouse capture off, verify text selection works - Press
magain, verify scroll wheel works - Verify
F1help screen shows the newmkeybinding
ratatui/awesome-ratatui
Four new ratatui-based projects:
- giff: TUI for git diffs with interactive rebase
- wrkflw: TUI for validating GitHub Actions locally
- blogr: static site generator with a terminal editor
- snipt: text snippet expansion with a TUI manager
<!-- Thanks for making Ratatui awesome! 🐭 -->
- Link to your project (GitHub/crates.io):
- https://github.com/bahdotsh/giff
- https://github.com/bahdotsh/wrkflw
- https://github.com/bahdotsh/blogr
- https://github.com/snipt/snipt
- Description (optional):
Four ratatui-based TUI tools:
- giff: git diffs with interactive rebase
- wrkflw: validate/execute GitHub Actions locally
- blogr: terminal-based static site generator with TUI editor
- snipt: text snippet expansion with TUI manager
giff
wrkflw
blogr
snipt
-->