vimtricks.wiki Concise Vim tricks, one at a time.

How do I incrementally expand or shrink a selection based on syntax tree nodes using nvim-treesitter?

Answer

gnn / grn / grm

Explanation

nvim-treesitter's incremental selection module lets you grow and shrink your visual selection one syntax node at a time. Instead of manually counting brackets or using motion combinations, you initiate a selection at the current node and then tap a single key to expand to the enclosing node — function argument → argument list → function call → expression — until you have exactly what you need. This is far more precise than v + motion combinations for structured code.

How it works

Enable the module in your treesitter config:

require('nvim-treesitter.configs').setup({
  incremental_selection = {
    enable = true,
    keymaps = {
      init_selection    = 'gnn',  -- start selection at current node
      node_incremental  = 'grn',  -- expand to parent node
      scope_incremental = 'grc',  -- expand to enclosing scope
      node_decremental  = 'grm',  -- shrink back to child node
    },
  },
})

Keys:

  • gnn — initiate selection at the syntax node under the cursor (enters Visual mode)
  • grn — expand the selection to the next larger syntax node
  • grc — expand to the nearest enclosing scope (function body, block, etc.)
  • grm — shrink the selection back to the previous, smaller node

Example

With the cursor on name in print(greet(name)), pressing gnn selects the identifier. Pressing grn expands to greet(name) (the function call). Pressing grn again selects print(greet(name)). Now you can yank, delete, or change the entire expression.

Tips

  • After initiating with gnn, you are in Visual mode — all normal visual operators (d, y, c, >) work on the current selection
  • grm undoes expansions one step at a time, so you can overshoot and come back
  • This works on any language with a treesitter grammar installed (:TSInstall python, :TSInstall go, etc.)
  • Combine with nvim-treesitter-textobjects for even more precise node selections like @function.outer and @class.inner

Next

How do I scroll the current line to the top of the screen and move the cursor to the first non-blank character?