yankdown.nvim
July 14, 2026 · View on GitHub
- Overview
- Demo
- Features
- Requirements
- Installation
- Setup
- Usage
- Fallback behavior
- How it works
- Architecture
- Development
Overview
We're past peak prose. Every LLM outputs Markdown. Every answer, every code review, every draft — it's all ## headings, - lists, and ``` backticks. You copy from a browser, a doc, an AI chat — and Neovim gets raw HTML or rich text. Which you then clean by hand. In 2026. While your AI writes in GFM natively.
yankdown.nvim fixes that. It pastes rich content from browsers, Google Docs, or Word into Neovim as clean GitHub Flavored Markdown — no intermediate file, no manual conversion.
It reads HTML from the system clipboard, pipes it through pandoc, and inserts the result at the cursor. When HTML is unavailable or a required tool is missing, it falls back to native paste transparently.
Demo
Copy the content from https://pandoc.org/ and simply paste using yankdown.nvim.

Recording generated with VHS — see assets/demo.tape.

Features
- Clipboard HTML → GFM — paste rich content as clean Markdown, not raw HTML.
- Auto-fallback — native paste when HTML is absent, pandoc is missing, or the platform is unsupported.
- One-time warnings —
vim.notifyfires once per missing tool, then stays quiet. - Multiple paste targets — normal (after/before cursor), visual (replace selection), insert (at cursor).
- Optional paste interception —
p/Pauto-override in Markdown buffers only (buffer-local, filetype-scoped). - Plug mappings —
<Plug>(yankdown-paste-after)and<Plug>(yankdown-paste-before)for custom keybindings. - Dependency check — cached diagnostics plus
:YankdownCheckfor missing tools (pandoc,xclip,wl-paste, …) without noisy startup warnings.
Requirements
| Dependency | Version | Purpose |
|---|---|---|
| Neovim | 0.10+ | Uses vim.system for async shell calls |
| pandoc | any | HTML to GFM conversion engine |
Clipboard providers
| Platform | Tool | Notes |
|---|---|---|
| macOS | osascript | Built-in |
| Wayland | wl-paste | From wl-clipboard package |
| X11 | xclip | |
| Windows | powershell.exe | Built-in PowerShell clipboard access (CF_HTML) |
Installation
With lazy.nvim:
{
"ntk148v/yankdown.nvim",
opts = {}, -- calls setup() automatically
}
With vim-plug:
Plug 'ntk148v/yankdown.nvim'
With packer.nvim:
use 'ntk148v/yankdown.nvim'
Setup
require("yankdown").setup({
auto_intercept = false, -- override p/P in Markdown buffers
notify = true, -- warn once on paste fallback/conversion failure
check = "lazy", -- cache dependency check on first Markdown paste
})
Options
| Key | Default | Description |
|---|---|---|
auto_intercept | false | Buffer-local p/P override for Markdown filetype only. |
notify | true | One-time vim.notify on paste fallback/conversion failures. |
check | "lazy" | Cache dependency status on first Markdown paste. Use "startup" to cache during setup(), or false to disable proactive checks. |
Usage
Commands
:YankdownPaste " paste after cursor
:YankdownPaste! " paste before cursor
:YankdownCheck " display dependency status
Lua
require("yankdown").paste({ direction = "after" })
require("yankdown").paste({ direction = "before" })
Keymaps
vim.keymap.set({ "n", "x", "i" }, "<leader>p", function()
require("yankdown").paste({ direction = "after" })
end)
Plug mappings
:map <leader>p <Plug>(yankdown-paste-after)
:map <leader>P <Plug>(yankdown-paste-before)
Optional paste interception
Enable automatic interception of p and P in Markdown buffers:
require("yankdown").setup({
auto_intercept = true,
})
This creates buffer-local mappings only for filetype=markdown. No global keys are touched.
Fallback behavior
Native paste (as if yankdown.nvim were not installed) is used when:
| Condition | Notification |
|---|---|
| Buffer is not Markdown | Silent |
| Clipboard has no HTML | Silent |
| Platform/provider unsupported | Warn once if notify = true |
| Clipboard tool missing | Warn once if notify = true |
pandoc missing or conversion fails | Warn once if notify = true |
How it works
Paste invoked
├─ Markdown buffer? ─── no ──→ Native paste
└─ yes
├─ Clipboard has HTML? ─── no ──→ Native paste
└─ yes
├─ Platform supported? ─── no ──→ Warn once → Native paste
└─ yes
├─ Read HTML from clipboard
├─ Pipe through pandoc
├─ pandoc succeeds? ─── no ──→ Warn once → Native paste
└─ yes
└─ Insert GFM at cursor
Architecture
┌─ User ─────────────────────────────────┐
│ Keymap / Command │
└──────────┬──────────────────────────────┘
│
┌──────────▼──────────────────────────────┐
│ yankdown.nvim │
│ ┌────────────────────────────────────┐ │
│ │ init.lua setup, command, plugs │ │
│ │ paste.lua orchestrator │ │
│ │ clipboard.lua provider detection │ │
│ │ HTML read │ │
│ │ convert.lua pandoc wrapper │ │
│ │ native.lua fallback passthrough │ │
│ └────────────────────────────────────┘ │
└──────────┬──────────────────────────────┘
│
┌──────────▼──────────────────────────────┐
│ System │
│ osascript / wl-paste / xclip / pwsh │
│ pandoc │
└─────────────────────────────────────────┘
Development
# Run tests
nvim --headless -c "luafile tests/run.lua" -c "q"
Project layout:
lua/yankdown/init.lua— entry point, setup, command registration.lua/yankdown/paste.lua— orchestration, mode detection, insert logic.lua/yankdown/clipboard.lua— platform detection, HTML clipboard read.lua/yankdown/convert.lua—pandocinvocation.lua/yankdown/native.lua— fallback native paste.lua/yankdown/check.lua— pre-flight dependency check.tests/— minitest-based test suite.