Skip to content

Dkendal/nvim-treeclimber

Repository files navigation

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 select-prev
<Plug>(treeclimber-select-shrink) n,x,o Shrink selection (select child node) select-shrink
<Plug>(treeclimber-select-parent) n,x,o Expand selection (select parent node) select-expand
<Plug>(treeclimber-select-next) n,x,o Select the next sibling node select-next
<Plug>(treeclimber-select-grow-forward) n,x,o Grow selection forward (add next sibling) grow-selection-next
<Plug>(treeclimber-select-grow-backward) n,x,o Grow selection backward (add previous sibling) grow-selection-prev
<Plug>(treeclimber-select-siblings-backward) n,x,o Select first sibling select-first-sibling
<Plug>(treeclimber-select-siblings-forward) n,x,o Select last sibling select-last-sibling
<Plug>(treeclimber-select-top-level) n,x,o Select top-level node select-top-level
<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.

tc-diff-this.webm

:TCShowControlFlow

Populate the quick fix with all branches required to reach the current node.

tc-show-control-flow.mp4

Installation

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()

Configuration

The plugin can be configured in three ways:

  1. Using setup() function:
require('nvim-treeclimber').setup({
  highlight = true  -- or other highlight options (see below)
})
  1. Using lazy.nvim opts:
{
  "dkendal/nvim-treeclimber",
  opts = {
    highlight = true
  }
}
  1. Setting vim.g.treeclimber directly:
vim.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 highlighting
  • number - 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 boundaries
  • TreeClimberSibling - Highlights sibling nodes
  • TreeClimberParent - Highlights parent nodes
  • TreeClimberParentStart - Highlights parent node start

Lazy.nvim Configuration

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" },
}

Custom Key Bindings

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.

About

Neovim structured editing plugin

Topics

Resources

Stars

Watchers

Forks

Contributors 3

  •  
  •  
  •