Nvim treeclimber
Neovim structured editing plugin
Neovim plugin for treesitter based navigation and selection. Takes inspiration from [ParEdit](https://calva.io/paredit/). The project is written primarily in Lua, distributed under the Apache License 2.0 license, first published in 2021. Key topics include: lua, neovim, neovim-lua-plugin, neovim-plugin, nvim-treesitter.
Nvim-Treeclimber
Neovim plugin for treesitter based navigation and selection.
Takes inspiration from ParEdit.
Requires neovim >= 0.10.
Usage
Navigation
| Plug Mapping | Mode | Action | Demo |
|---|---|---|---|
<Plug>(treeclimber-select-previous) | n,x,o | Select the previous sibling node | ![]() |
<Plug>(treeclimber-select-shrink) | n,x,o | Shrink selection (select child node) | ![]() |
<Plug>(treeclimber-select-parent) | n,x,o | Expand selection (select parent node) | ![]() |
<Plug>(treeclimber-select-next) | n,x,o | Select the next sibling node | ![]() |
<Plug>(treeclimber-select-grow-forward) | n,x,o | Grow selection forward (add next sibling) | ![]() |
<Plug>(treeclimber-select-grow-backward) | n,x,o | Grow selection backward (add previous sibling) | ![]() |
<Plug>(treeclimber-select-siblings-backward) | n,x,o | Select first sibling | ![]() |
<Plug>(treeclimber-select-siblings-forward) | n,x,o | Select last sibling | ![]() |
<Plug>(treeclimber-select-top-level) | n,x,o | Select top-level node | ![]() |
<Plug>(treeclimber-select-backward) | n,x,o | Select and move to node start | |
<Plug>(treeclimber-select-forward-end) | n,x,o | Select and move to node end | |
<Plug>(treeclimber-show-control-flow) | n | Show control flow | |
<Plug>(treeclimber-select-current-node) | x,o | Select current node (inner) | |
<Plug>(treeclimber-select-expand) | x,o | Select parent node (around) |
Commands
:TCDiffThis
Diff two visual selections based on their AST difference.
Requires that difft is available in your path.
To use, make your first selection and call :TCDiffThis, then make your second selection and call :TCDiffThis again.
:TCShowControlFlow
Populate the quick fix with all branches required to reach the current node.
https://user-images.githubusercontent.com/3162299/203097777-a9a84c2d-8dec-4db8-a4c7-4c9a66ca26fe.mp4
Installation
Use your preferred package manager, or the built-in package system (:help packages).
shmkdir -p ~/.config/nvim/pack/dkendal/opt cd ~/.config/nvim/pack/dkendal/opt git clone https://github.com/dkendal/nvim-treeclimber.git
lua-- ~/.config/nvim/init.lua vim.cmd.packadd('nvim-treeclimber') require('nvim-treeclimber').setup()
Configuration
The plugin can be configured in three ways:
- Using
setup()function:
luarequire('nvim-treeclimber').setup({ highlight = true -- or other highlight options (see below) })
- Using lazy.nvim opts:
lua{ "dkendal/nvim-treeclimber", opts = { highlight = true } }
- Setting vim.g.treeclimber directly:
luavim.g.treeclimber = { highlight = true }
Configuration Options
highlight
Controls the visual highlighting of treesitter nodes during navigation. Can be:
true(default) - Enable automatic highlighting with default blend value (50)false- Disable all highlightingnumber- Enable highlighting with custom blend value between the Visual and Normal highlights (0-100, where 0 is the same as Visual and 100 is the same as Normal)function- Custom function for dynamic highlight setup (e.g., colorscheme changes)
Examples:
lua-- Disable highlighting require('nvim-treeclimber').setup({ highlight = false }) -- Custom blend value (lighter highlighting) require('nvim-treeclimber').setup({ highlight = 25 }) -- Dynamic highlight function (adapts to colorscheme changes) require('nvim-treeclimber').setup({ highlight = function() local hi = require("nvim-treeclimber.hi") local Normal = hi.get_hl("Normal", { follow = true }) local normal = hi.HSLUVHighlight:new(Normal) local Visual = hi.get_hl("Visual", { follow = true }) local visual = hi.HSLUVHighlight:new(Visual) local blend_value = 50 local set_hl = vim.api.nvim_set_hl set_hl(0, "TreeClimberSiblingBoundary", { background = visual.bg.mix(normal.bg, blend_value).hex }) set_hl(0, "TreeClimberSibling", { background = visual.bg.mix(normal.bg, blend_value).hex }) set_hl(0, "TreeClimberParent", { background = visual.bg.mix(normal.bg, blend_value).hex }) set_hl(0, "TreeClimberParentStart", { background = visual.bg.mix(normal.bg, blend_value).hex }) end }) -- For hardcoded highlights, disable the plugin's highlighting and set your own: require('nvim-treeclimber').setup({ highlight = false }) vim.api.nvim_set_hl(0, "TreeClimberSiblingBoundary", { bg = "#3c3836" }) vim.api.nvim_set_hl(0, "TreeClimberSibling", { bg = "#504945" }) vim.api.nvim_set_hl(0, "TreeClimberParent", { bg = "#665c54" }) vim.api.nvim_set_hl(0, "TreeClimberParentStart", { bg = "#7c6f64" })
The plugin automatically creates these highlight groups that blend the Visual highlight with the Normal background color:
TreeClimberSiblingBoundary- Highlights sibling boundariesTreeClimberSibling- Highlights sibling nodesTreeClimberParent- Highlights parent nodesTreeClimberParentStart- Highlights parent node start
Lazy.nvim Configuration
If you're using lazy.nvim, you can configure nvim-treeclimber with lazy-loaded key mappings:
luareturn { "dkendal/nvim-treeclimber", config = function() require('nvim-treeclimber').setup() end, keys = { -- Core navigation { "<M-h>", "<Plug>(treeclimber-select-previous)", mode = { "n", "x", "o" }, desc = "Select previous node" }, { "<M-l>", "<Plug>(treeclimber-select-next)", mode = { "n", "x", "o" }, desc = "Select next node" }, { "<M-k>", "<Plug>(treeclimber-select-parent)", mode = { "n", "x", "o" }, desc = "Select parent node" }, { "<M-j>", "<Plug>(treeclimber-select-shrink)", mode = { "n", "x", "o" }, desc = "Select child node" }, -- Growth selection { "<M-H>", "<Plug>(treeclimber-select-grow-backward)", mode = { "n", "x", "o" }, desc = "Grow selection backward" }, { "<M-L>", "<Plug>(treeclimber-select-grow-forward)", mode = { "n", "x", "o" }, desc = "Grow selection forward" }, -- Sibling navigation { "<M-[>", "<Plug>(treeclimber-select-siblings-backward)", mode = { "n", "x", "o" }, desc = "Select first sibling" }, { "<M-]>", "<Plug>(treeclimber-select-siblings-forward)", mode = { "n", "x", "o" }, desc = "Select last sibling" }, -- Top level { "<M-g>", "<Plug>(treeclimber-select-top-level)", mode = { "n", "x", "o" }, desc = "Select top-level node" }, -- Movement selection { "<M-b>", "<Plug>(treeclimber-select-backward)", mode = { "n", "x", "o" }, desc = "Select and move to node start" }, { "<M-e>", "<Plug>(treeclimber-select-forward-end)", mode = { "n", "x", "o" }, desc = "Select and move to node end" }, -- Visual/operator mode specific { "i.", "<Plug>(treeclimber-select-current-node)", mode = { "x", "o" }, desc = "Select current node (inner)" }, { "a.", "<Plug>(treeclimber-select-expand)", mode = { "x", "o" }, desc = "Select parent node (around)" }, -- Commands { "<leader>k", "<Plug>(treeclimber-show-control-flow)", mode = "n", desc = "Show control flow" }, }, cmd = { "TCDiffThis", "TCShowControlFlow", "TCHighlightExternalDefinitions" }, }
Custom Key Bindings
To customize key bindings, use the <Plug> mappings from the table above. For example, to use H/L for navigation:
lualocal tc = require('nvim-treeclimber') -- Use custom keys vim.keymap.set({ "n", "x", "o" }, "H", "<Plug>(treeclimber-select-previous)") vim.keymap.set({ "n", "x", "o" }, "L", "<Plug>(treeclimber-select-next)")
Or use the keys attribute if you're using lazy.nvim.
Copyright Dylan Kendal 2025.
Contributors
Showing top 3 contributors by commit count.









