graft.nvim

March 23, 2026 · View on GitHub

A tiny outliner for Markdown bullet lists in Neovim.

Nested bullets are a nice way to organize thoughts. graft lets you move, select, and operate on them as subtrees.

Requirements

  • Neovim ≥ 0.10
  • Tree-sitter markdown parser (:TSInstall markdown)

Install

-- lazy.nvim
{ "satozawa/graft.nvim", ft = "markdown", opts = {} }

Or add ~/path/to/graft.nvim to your runtimepath and call require("graft").setup().

Keymaps

graft only activates in Markdown buffers. Non-bullet lines fall through to native behavior.

Subtree operations

KeyAction
Alt+k / Alt+jMove subtree up / down among siblings
Alt+h / Alt+lPromote (outdent) / demote (indent) subtree

Works in Normal and Insert mode.

KeyAction
]g / [gNext / previous sibling
]G / [GFirst child / parent

Text objects

KeySelects
agCurrent item + all descendants
igDescendants only (excludes current item)
iGDirect children and their subtrees

dag delete subtree · yag yank subtree · cag change subtree · >ag indent subtree · vag select subtree

Bullet continuation

KeyAction
EnterNew sibling after subtree (Normal) or split text (Insert)
oNew child if item has children, otherwise new sibling
ONew sibling above

Empty bullet + Enter removes the line.

Other

KeyAction
]pPaste with indent adjusted to match tree context
[x / ]xCheck / uncheck checkbox

Subtree folding is set via foldexpr. Use Neovim's fold commands as usual.

Configuration

All keymaps can be changed or disabled. Set any to false to turn it off.

require("graft").setup({
  keymaps = {
    move_up  = "<M-k>",  move_down = "<M-j>",
    promote  = "<M-h>",  demote    = "<M-l>",

    next_sibling = "]g",  prev_sibling = "[g",
    first_child  = "]G",  parent       = "[G",

    around_graft = "ag", inner_graft = "ig", inner_graft_shallow = "iG",

    enter_normal = "<CR>", enter_insert = "<CR>",
    o_normal = "o", o_upper_normal = "O",

    smart_paste = "]p", check = "[x", uncheck = "]x",
  },
})

Example — disable Enter/o/O overrides and remap operations to Ctrl:

require("graft").setup({
  keymaps = {
    enter_normal = false, enter_insert = false,
    o_normal = false, o_upper_normal = false,
    move_up = "<C-k>", move_down = "<C-j>",
    promote = "<C-h>", demote = "<C-l>",
  },
})

macOS terminal setup

If Alt+hjkl produces special characters instead of working as modifiers:

TerminalSetting
Ghosttymacos-option-as-alt = left
Kittymacos_option_as_alt yes
iTerm2Preferences → Profiles → Keys → Left Option → Esc+
WezTermsend_composed_key_when_left_alt_is_pressed = false

License

MIT