Releases: delphinus/md-render.nvim
v3.1.1
v3.1.1 makes the experimental :MdRender auto command a true toggle: calling it twice now returns the buffer to its original (auto-off, source displayed) state instead of leaving the render buffer on screen.
Bug fix
:MdRender auto is now symmetric (#18)
MdPreview.auto_off previously cleared b:md_render_auto, the InsertLeave autocmd, and the Insert-entry keymaps, but explicitly left the displayed mode untouched. Because :MdRender auto (no argument) is auto_toggle in disguise, this made the command asymmetric — the first invocation swapped source → render, but the second one removed the autocmds while leaving render on screen.
auto_off now also swaps the current window back to source when it's showing this buffer's render view, so:
:MdRender autothen:MdRender autoreturns to the pre-toggle state.:MdRender auto onthen:MdRender auto offends up exactly where you started.
auto_toggle itself is unchanged; it inherits the fix from auto_off. The behavior change is documented in :help :MdRender-auto and :help MdPreview.auto_off().
If you were relying on the previous "leaves displayed mode untouched" behavior of auto_off, follow it with :MdRender toggle to restore render display.
Full Changelog: v3.1.0...v3.1.1
v3.1.0
v3.1.0 adds CommonMark autolink rendering (including inline GitHub user-attachments videos) and a dedicated MdRenderInlineCode highlight group, plus a fix for heading background colors under tokyonight and similar colorschemes.
New highlight group
MdRenderInlineCode for backtick code spans (#14, #15)
Inline code (`code` and <code>) was previously hardcoded to the String highlight group, which in many colorschemes shares its green foreground with H3+ heading groups — making code text hard to distinguish from headings. The new MdRenderInlineCode group keeps the String foreground but adds a subtle neutral background (Comment fg blended with Normal/NormalFloat bg at 0.3), so code stays visually distinct from prose and headings even when both end up green.
You can override it in your config:
vim.api.nvim_set_hl(0, "MdRenderInlineCode", { fg = ..., bg = ... })Improvements
CommonMark autolinks and inline GitHub user-attachments media (#13)
CommonMark autolinks (<https://example.com>) now render as clickable links with the angle brackets stripped from the output (previously the brackets leaked through as literals).
Standalone autolink lines pointing at GitHub's user-attachments CDN — for example:
<https://github.com/user-attachments/assets/4c042f5c-fb7f-4a1e-a3d9-e2ab43ae215a>
— now render as inline video/image, mirroring github.com's behavior. The existing download + magic-byte detection + ffprobe pipeline handles the actual media rendering, so READMEs that embed GitHub-hosted demo videos (e.g. undo-glow.nvim, many Neovim plugins) display correctly in the preview buffer.
The media routing is restricted to github.com/user-attachments/ URLs to avoid leaving "Loading..." placeholders for non-media URLs that happen to have no extension.
Heading background colors propagate from treesitter (#16)
setup_heading_highlights now carries over the full attribute table from @markup.heading.<level>.markdown instead of only the foreground. Colorschemes that paint a heading background (notably tokyonight, which defines a distinct bg per heading level) now show the same background in the preview buffer as in the source buffer.
Full Changelog: v3.0.0...v3.1.0
v3.0.0
v3.0.0 is a major release with two breaking changes: the minimum Neovim version is now 0.12, and the 7 top-level commands are consolidated under :MdRender <subcommand> (the old commands still work as deprecated aliases — they will be removed in v4.0.0).
Breaking changes
Minimum Neovim is now 0.12
Image / video rendering now writes to the terminal through vim.api.nvim_ui_send instead of opening /dev/tty directly, which requires Neovim 0.12+. This survives :restart cleanly, removes the ioctl-on-fd-1 workaround, and lets Neovim own the TTY plumbing. Users on Neovim 0.10 / 0.11 should pin to v2.55.x.
Forward-looking deprecation: legacy commands
All 7 legacy top-level commands continue to work in this release. Each one now prints a one-shot deprecation warning per Neovim session and will be removed in v4.0.0. Update your config to the new :MdRender <subcommand> form at your convenience.
New: unified :MdRender <subcommand> (#11, #12)
The 7 top-level commands are consolidated into a single :MdRender command with subcommands, following the convention used by Trouble v3, Telescope, Lazy, and Octo.
| Old | New |
|---|---|
:MdRender |
:MdRender (unchanged) or :MdRender float |
:MdRenderTab |
:MdRender tab |
:MdRenderToggle |
:MdRender toggle |
:MdRenderSplit |
:MdRender split |
:vert MdRenderSplit |
:vert MdRender split |
:MdRenderAuto on|off |
:MdRender auto on|off|toggle |
:MdRenderPager |
:MdRender pager |
:MdRenderDemo |
:MdRender demo |
Each new feature has tended to ship as another top-level command — a recent request for :MdRenderVsplit would have pushed the count to 8, and a symmetric :MdRenderHsplit to 9. Consolidating now bounds that growth and follows a convention users already know from other plugins.
Tab completion
Two-level completion is included:
:MdRender <TAB>→float,tab,pager,toggle,split,auto,demo:MdRender auto <TAB>→on,off,toggle:vert MdRender <TAB>works too — Vim modifiers are tolerated
Vim modifiers compose with split
Direction control on :MdRender split uses the standard Vim split modifiers (:vert, :tab, :topleft, :botright) rather than subcommand variants, so e.g. :tab MdRender split opens the source/render pair inside a new tab.
Improvements
- Kitty graphics detection on Neovim 0.13+ uses
vim.tty.query_apcto send a real APC probe to the terminal. Silent terminals fall back to the existingTERM_PROGRAM/ env var heuristics, so behaviour is unchanged on 0.12.
Backwards compatibility
- All 6 legacy commands (
:MdRenderTab,:MdRenderToggle,:MdRenderSplit,:MdRenderAuto,:MdRenderPager,:MdRenderDemo) remain registered and forward to the new dispatcher - First invocation per session prints a
vim.notify_oncedeprecation warning suggesting the new form <Plug>(md-render-toggle),<Plug>(md-render-split),<Plug>(md-render-auto),<Plug>(md-render-demo),<Plug>(md-render-preview),<Plug>(md-render-preview-tab)are unchanged — user keymaps are not affected- Lua API (
require("md-render").preview.*) is unchanged
Migration
Search-and-replace your config:
" before
nnoremap <leader>mt <Cmd>MdRenderToggle<CR>
nnoremap <leader>ms <Cmd>vert MdRenderSplit<CR>
autocmd FileType markdown silent! MdRenderAuto on
" after
nnoremap <leader>mt <Cmd>MdRender toggle<CR>
nnoremap <leader>ms <Cmd>vert MdRender split<CR>
autocmd FileType markdown silent! MdRender auto onIf you use <Plug> mappings, no changes are needed.
Docs
doc/md-render.txtanddoc/md-render.jaxrestructured: single*:MdRender*block with new sub-tags*:MdRender-{float,tab,toggle,split,auto,pager,demo}*. Old tags*:MdRenderToggle*, etc. retained as deprecated stubs so:help :MdRenderTogglestill resolves- README (English and Japanese) updated with the new command table and a backwards-compatibility note
Notes
- 35 new test cases in
tests/command_test.lua; full suite at 500 passing across all platforms - The PTY integration tests (
pty_image_test.lua,pty_capture.py,pty_image_scenario.lua) were removed — they captured/dev/ttywrites from a headless child Neovim, which is incompatible withnvim_ui_send(no UI is attached in--headlessmode). Byte correctness is still verified byimage_test.luavianvim_ui_sendmonkey-patching, mirroring upstreamimg_spec.lua
Full Changelog: v2.55.2...v3.0.0
v2.55.2
Bug fixes
:MdRenderToggle: rebuild the render buffer onBufEnterwhen the source changed while the render buffer was hidden. Previously, jumping back to a hidden render buffer viaCtrl-O/Ctrl-I,:buffer,:b#, or any other path that did not go through:MdRenderToggleleft stale content on screen until the buffer was wiped manually. Reported by @mjrogozinski in #5.
Documentation
- README: use
<img>for the:MdRenderSplitdemo video so it autoplays on GitHub.
Full Changelog: v2.55.1...v2.55.2
v2.55.1
Patch release polishing the :MdRenderSplit experience that shipped in v2.55.0.
Fixes
- Inline images no longer flicker out when the source-side cursor moves in
:MdRenderSplit. Settingline_hl_groupon a render line that carries a Kitty Graphics placement caused the terminal to repaint those cells and wipe the image overlay; the image redraw autocmd only fires forCursorMoved/WinScrolledinside the render window, so source-side moves left the image gone. The shadow-line set now skips image-occupied rows. (611a85e)
Documentation
MdRenderShadowCursoris now documented in:help md-render-highlights(bothmd-render.txtandmd-render.jax), so users can override the highlight that marks the matching line on the inactive side of:MdRenderSplit. (1b3066f)- README and README.ja have a short demo video at the top of the Source/render split section showing live edit propagation and inline-image rendering across the two panes. (2f69d03)
Full Changelog: v2.55.0...v2.55.1
v2.55.0
New Features
:MdRenderToggle— toggle the current window between source Markdown and a rendered view, in place. No new tab or floating window. Ideal for split layouts (e.g. code in one split, rendered README in the other).:MdRenderSplit— open source and rendered views in adjacent windows. Direction follows the standard Vim split modifiers (:vert,:topleft,:botright, …). Cursor and scroll position are synchronised in both directions, source edits propagate live to the render side with a 150 ms debounce, and a shadow cursor highlights the matching line on the inactive side.:MdRenderAuto [on|off](experimental) — auto-flip per buffer: render while in Normal mode, swap to source onInsertEnter. The keysi/I/a/A/o/Oon the render buffer transparently swap to source first and then enter Insert at the corresponding position. Other editing entry points (Ex commands, macros, operator-pending sequences likec{motion}, plugin mappings, etc.) silently fail on the read-only render buffer; follow-up improvements are tracked in #10.
Improvements
- Render windows hide their gutter (
number,relativenumber,list,signcolumn,foldcolumn,statuscolumn) and restore the originals on swap-back. - Image / video centering is now computed against the visible text area (excluding any gutter) and Kitty graphics are placed with the same offset, so images line up with the centered placeholder text even when a custom
statuscolumnis in use. - Fixed a bug where images could overflow into the statusline by 1 row when the render window has a winbar (e.g. the "Markdown Preview" label on
:MdRenderSplit).nvim_win_get_height()includes the winbar; the bottom-crop now usesgetwininfo().heightwhich excludes it. :won the render buffer is forwarded to the source viaBufWriteCmd, so you can save without leaving render mode.
Notes
- Resolves #5 (the original in-place toggle proposal).
- 132 new test cases in
tests/toggle_test.lua; full suite at 474 passing across all platforms. - Single-window image binding remains a known limitation: opening multiple render windows for the same source only displays inline images in the most recently bound one. Documented in README and
:help.
Full Changelog: v2.54.2...v2.55.0
v2.54.2
Bug Fixes
- Fix
E976: Using a Blob as a Stringwhen rendering mermaid diagrams on Neovim ≤ 0.11 —mermaid_cache_pathjoined the source / theme / background-color hex with a NUL byte (\0) before passing tovim.fn.sha256(). On Neovim ≤ 0.11, the Lua→vimscript bridge converts strings containing NUL bytes into Blob values, andvim.fn.sha256()rejects Blobs with E976 — so any:MdRenderinvocation against a buffer containing a mermaid block failed. The separator is now|, which avoids the Blob conversion entirely. Fixed upstream in Neovim v0.12 (vim-patch 9.1.1774). (#7, #8)
v2.54.1
Bug Fixes
- Stop long URLs in inline code spans from forcing the float window to widen — bare URLs (e.g.
https://very/long/url) are truncated to 50 display columns byprocess_bare_urls, but URLs wrapped in inline code (`https://very/long/url`) bypass that truncation entirely.wrap_wordsonly flushed ASCII words at-after a word character, so a long URL with no hyphens for many characters became a single unbreakable segment — wider thanmax_widthand auto-sizing the floating window to that width.wrap_wordsnow also flushes at/,?, and&, while the[%w]guard keeps the://ofhttps://bonded as the protocol prefix and short paths like~/.config/nvim/init.luacontinue to render on one line (segments are reassembled when they fit).
v2.54.0
Performance
This release rewrites the image transmission pipeline so previewing Markdown files with many large images is no longer disruptive. On a file containing nine 5712×4284 iPhone photos, opening it in the Telescope previewer used to lock the entire terminal for 1–2 seconds; that freeze is now gone, and revisiting the file is essentially instant.
- Cache converted PNGs on disk — JPEG/WebP sources are converted via
sips/ffmpeg/magickonce and stored understdpath("cache")/md-render/converted/, keyed bysha256(path) + mtime + max-dim. Cache hits return in <1 ms instead of paying the per-call ~250 ms conversion cost. The cache invalidates automatically when the source's mtime changes. - Transmit images progressively, not in bulk —
setup_imagesno longer floods the terminal with N transmit commands at once. Placements are processed one at a time withvim.scheduleyields between each, so the terminal can interleave PNG decoding with input handling instead of stalling its UI thread. - Lazy load by viewport — only placements whose buffer rows are within ±10 of the visible viewport are transmitted on the initial pass. Off-screen images are picked up by the existing
WinScrolled-driven retry as they scroll into view, so the first paint cost scales with what is actually visible (typically 1–2 images) rather than the file's total image count. - Debounce the Telescope previewer render —
define_previewreturns in <1 ms by deferring all heavy work (file read, markdown build, image setup) behind an 80 ms debounce timer. Rapidj/knavigation cancels the pending render entirely, so files the user is just scrolling past pay no render cost. When the timer does fire, the render is split acrossvim.scheduleboundaries (build_content→apply_content_to_buffer→setup_images), and a generation counter aborts in-flight renders the moment a newer file change arrives. - Skip duplicate in-flight conversions — the redraw retry loop in
setup_imagestracks an_convertingflag per placement so scrolling during conversion no longer respawns the samesips/ffmpegprocess.
Bug Fixes
- Resolve image paths in the Telescope previewer — the previewer was calling
build_contentwithoutbuf_dir, which left Obsidian wiki-link images like![[IMG.jpeg]]unable to locate the vault root and rendered them as plain placeholder text. Now passes the source file's directory so vault detection and relative-path resolution work the same as:MdRender. - Restore correct rendering for cropped images on WezTerm —
put_imagepreviously emitted only the source rect dimensions it needed (e.g.,h=Nalone for bottom crop), and some terminals interpret a partial source rectangle as "no crop" and silently scale the entire image into the display cells, which made images near a window's bottom edge appear vertically squashed. The crop params now always emit a completex,y,w,hsource rect when any dimension is set. - Use converted dimensions when reporting transmitted image size —
transmit_image_asyncreturnedtx_w/tx_honly whenis_tempwas true. With the new on-disk cache the result is no longer "temp" but its dimensions still differ from the source, so callers were left computing source rectangles against the original (too-large) image dimensions — surfacing as squashed images in the regular preview and as missing images (only the placeholder header showed) in the Telescope previewer. Decision is now based on whether conversion actually happened.
Internals
The image module now has a MAX_CONVERT_DIM constant (currently 2000 px) used consistently across all three converter backends and the cache key, making it trivial to tune the resize bound in one place if needed.
v2.53.1
Bug Fixes
- Keep opening brackets bonded to following content when wrapping — opening brackets like
(,「,『,{,[no longer end up stranded at line end (e.g. on a line by themselves) when a table cell or wrapped line breaks awkwardly around them. Two guards inwrap_wordsprevent the 追い出し and regular-push paths from emitting a line ending with an opener, and the table emergency splitter now mirrors its existing NO_BREAK_START handling for NO_BREAK_END. - Apply kinsoku to ASCII quotes (
",') based on context — straight ASCII quotes are not in the standard JIS X 4051 kinsoku tables (the same glyph serves both opening and closing roles), so opening quotes used to land at line end (e.g.(例: "immediately before a linebreak). They are now classified per-occurrence based on surrounding characters: opening quotes (preceded by space / bracket / punctuation) are treated as 行末禁則, closing quotes (followed by the same) as 行頭禁則. Mid-word'such asit'sis left neutral.