Smart paste for Markdown in Neovim
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.
Copy the content from https://pandoc.org/ and simply paste using yankdown.nvim.

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

vim.notify fires once per missing tool, then stays quiet.p/P auto-override in Markdown buffers only (buffer-local, filetype-scoped).<Plug>(yankdown-paste-after) and <Plug>(yankdown-paste-before) for custom keybindings.:YankdownCheck for missing tools (pandoc, xclip, wl-paste, …) without noisy startup warnings.| 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) |
With lazy.nvim:
{
"ntk148v/yankdown.nvim",
opts = {}, -- calls setup() automatically
}
With vim-plug:
Plug 'ntk148v/yankdown.nvim'
With packer.nvim:
use 'ntk148v/yankdown.nvim'
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. |
:YankdownPaste " paste after cursor
:YankdownPaste! " paste before cursor
:YankdownCheck " display dependency status
require("yankdown").paste({ direction = "after" })
require("yankdown").paste({ direction = "before" })
vim.keymap.set({ "n", "x", "i" }, "<leader>p", function()
require("yankdown").paste({ direction = "after" })
end)
:map <leader>p <Plug>(yankdown-paste-after)
:map <leader>P <Plug>(yankdown-paste-before)
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.
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 |
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
┌─ 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 │
└─────────────────────────────────────────┘
# 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 — pandoc invocation.lua/yankdown/native.lua — fallback native paste.lua/yankdown/check.lua — pre-flight dependency check.tests/ — minitest-based test suite.