Skip to content

Releases: delphinus/md-render.nvim

v3.1.1

16 May 02:18
cd751c9

Choose a tag to compare

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 auto then :MdRender auto returns to the pre-toggle state.
  • :MdRender auto on then :MdRender auto off ends 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

15 May 06:58
b8a4a36

Choose a tag to compare

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

12 May 13:58
b12177a

Choose a tag to compare

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_apc to send a real APC probe to the terminal. Silent terminals fall back to the existing TERM_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_once deprecation 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 on

If you use <Plug> mappings, no changes are needed.

Docs

  • doc/md-render.txt and doc/md-render.jax restructured: 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 :MdRenderToggle still 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/tty writes from a headless child Neovim, which is incompatible with nvim_ui_send (no UI is attached in --headless mode). Byte correctness is still verified by image_test.lua via nvim_ui_send monkey-patching, mirroring upstream img_spec.lua

Full Changelog: v2.55.2...v3.0.0

v2.55.2

12 May 03:32
9f08997

Choose a tag to compare

Bug fixes

  • :MdRenderToggle: rebuild the render buffer on BufEnter when the source changed while the render buffer was hidden. Previously, jumping back to a hidden render buffer via Ctrl-O / Ctrl-I, :buffer, :b#, or any other path that did not go through :MdRenderToggle left stale content on screen until the buffer was wiped manually. Reported by @mjrogozinski in #5.

Documentation

  • README: use <img> for the :MdRenderSplit demo video so it autoplays on GitHub.

Full Changelog: v2.55.1...v2.55.2

v2.55.1

11 May 05:28
2f69d03

Choose a tag to compare

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. Setting line_hl_group on 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 for CursorMoved / WinScrolled inside the render window, so source-side moves left the image gone. The shadow-line set now skips image-occupied rows. (611a85e)

Documentation

  • MdRenderShadowCursor is now documented in :help md-render-highlights (both md-render.txt and md-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

11 May 04:31
cf4fef1

Choose a tag to compare

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 on InsertEnter. The keys i / I / a / A / o / O on 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 like c{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 statuscolumn is 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 uses getwininfo().height which excludes it.
  • :w on the render buffer is forwarded to the source via BufWriteCmd, 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

29 Apr 02:22
63d7cc5

Choose a tag to compare

Bug Fixes

  • Fix E976: Using a Blob as a String when rendering mermaid diagrams on Neovim ≤ 0.11mermaid_cache_path joined the source / theme / background-color hex with a NUL byte (\0) before passing to vim.fn.sha256(). On Neovim ≤ 0.11, the Lua→vimscript bridge converts strings containing NUL bytes into Blob values, and vim.fn.sha256() rejects Blobs with E976 — so any :MdRender invocation 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

27 Apr 05:08
9874f13

Choose a tag to compare

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 by process_bare_urls, but URLs wrapped in inline code (`https://very/long/url`) bypass that truncation entirely. wrap_words only flushed ASCII words at - after a word character, so a long URL with no hyphens for many characters became a single unbreakable segment — wider than max_width and auto-sizing the floating window to that width. wrap_words now also flushes at /, ?, and &, while the [%w] guard keeps the :// of https:// bonded as the protocol prefix and short paths like ~/.config/nvim/init.lua continue to render on one line (segments are reassembled when they fit).

v2.54.0

26 Apr 04:03
f24642d

Choose a tag to compare

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/magick once and stored under stdpath("cache")/md-render/converted/, keyed by sha256(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 bulksetup_images no longer floods the terminal with N transmit commands at once. Placements are processed one at a time with vim.schedule yields 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 renderdefine_preview returns in <1 ms by deferring all heavy work (file read, markdown build, image setup) behind an 80 ms debounce timer. Rapid j/k navigation 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 across vim.schedule boundaries (build_contentapply_content_to_buffersetup_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_images tracks an _converting flag per placement so scrolling during conversion no longer respawns the same sips/ffmpeg process.

Bug Fixes

  • Resolve image paths in the Telescope previewer — the previewer was calling build_content without buf_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 WezTermput_image previously emitted only the source rect dimensions it needed (e.g. ,h=N alone 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 complete x,y,w,h source rect when any dimension is set.
  • Use converted dimensions when reporting transmitted image sizetransmit_image_async returned tx_w/tx_h only when is_temp was 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

21 Apr 06:36
75d976d

Choose a tag to compare

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 in wrap_words prevent 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 as it's is left neutral.