Neovim plugin for treesitter based navigation and selection. Takes inspiration from ParEdit.
Requires neovim >= 0.10.
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.
tc-diff-this.webm
Populate the quick fix with all branches required to reach the current node.
tc-show-control-flow.mp4
Use your preferred package manager, or the built-in package system (:help packages).
mkdir -p ~/.config/nvim/pack/dkendal/opt
cd ~/.config/nvim/pack/dkendal/opt
git clone https://github.com/dkendal/nvim-treeclimber.git-- ~/.config/nvim/init.lua
vim.cmd.packadd('nvim-treeclimber')
require('nvim-treeclimber').setup()The plugin can be configured in three ways:
- Using
setup()function:
require('nvim-treeclimber').setup({
highlight = true -- or other highlight options (see below)
})- Using lazy.nvim opts:
{
"dkendal/nvim-treeclimber",
opts = {
highlight = true
}
}- Setting vim.g.treeclimber directly:
vim.g.treeclimber = {
highlight = true
}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:
-- 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
If you're using lazy.nvim, you can configure nvim-treeclimber with lazy-loaded key mappings:
return {
"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" },
}To customize key bindings, use the <Plug> mappings from the table above. For example, to use H/L for navigation:
local 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.








