Skip to content

aaronik/treewalker.nvim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Treewalker.nvim

🌳🌲🌴🌲🌴🌳

Neovim 100% Lua   build status GitHub issues by-label GitHub Pull Requests

A fast paced demo of Treewalker.nvim

Move around your code in a syntax tree aware manner.

Treewalker uses neovim's native Treesitter under the hood to enable movement around and swapping of code objects like functions, blocks, and statements.
Design goals include stability, ergonomics, and simplicity.
Made in 100% Lua, with no dependencies.


Movement

The movement commands move you through code in an intuitive way, skipping nodes that aren't conducive to nimble movement through code:

  • :Treewalker Up - Moves up to the previous neighbor node
  • :Treewalker Down - Moves down to the next neighbor node
  • :Treewalker Left - Moves to the first ancestor node that's on a different line from the current node
  • :Treewalker Right - Moves to the next node down that's indented further than the current node

For markdown files, Treewalker navigates around headings (#, ##, etc.)

All movement commands by default add to the jumplist, so if you use a movement command and then feel lost, you have Ctrl-o available to bring you back to where you last were.


Swapping

The swapping commands swap whatever node your cursor is on with one of its neighbors.

Swap{Up,Down} operate on a linewise basis, bringing along nodes' comments, decorators, and annotations. These are meant for swapping declarations and definitions - things that take up whole lines.

Swap{Left,Right} operate on a literal nodewise basis, and are meant for swapping function arguments, enum members, list elements, etc -- things that are many per line. If used on a top level node on a line, it'll swap the same nodes as Up/Down, but won't take the comments, decorators or annotations with them.

  • :Treewalker SwapUp - Swaps the highest node on the line upwards in the document
  • :Treewalker SwapDown - Swaps the highest node on the line downward in the document
  • :Treewalker SwapLeft - Swap the node under the cursor with its previous neighbor
  • :Treewalker SwapRight - Swap the node under the cursor with its next neighbor

More Examples

Typing out the Move commands manually A demo of moving around some code slowly typing out each Treewalker move command
Typing out the SwapUp/SwapDown commands manually A demo of swapping code slowly using Treewalker swap commands

Installation

{
  'aaronik/treewalker.nvim',

  -- optional (see options below)
  opts = { ... }
}
use {
  'aaronik/treewalker.nvim',

  -- optional (see options below)
  setup = function()
      require('treewalker').setup({ ... })
  end
}
Plug 'aaronik/treewalker.nvim'

" Optionally (see options below)
:lua require('treewalker').setup({ ... })

Options

Treewalker aims for sane behavior, but you can modify some via the options below.

-- The defaults:
{
  -- Whether to briefly highlight the node after jumping to it
  highlight = true,

  -- How long should above highlight last (in ms)
  highlight_duration = 250,

  -- The color of the above highlight. Must be a valid vim highlight group.
  -- (see :h highlight-group for options)
  highlight_group = 'CursorLine',

  -- Whether to create a visual selection after a movement to a node.
  -- If true, highlight is disabled and a visual selection is made in
  -- its place.
  select = false,

  -- Whether to use vim.notify to warn when there are missing parsers or incorrect options
  notifications = true,

  -- Whether the plugin adds movements to the jumplist -- true | false | 'left'
  --  true: All movements more than 1 line are added to the jumplist. This is the default,
  --        and is meant to cover most use cases. It's modeled on how { and } natively add
  --        to the jumplist.
  --  false: Treewalker does not add to the jumplist at all
  --  "left": Treewalker only adds :Treewalker Left to the jumplist. This seems the most
  --          likely jump to cause location confusion, so use this to minimize writes
  --          to the jumplist, while maintaining some ability to go back.
  jumplist = true,
}

Mapping

I found Ctrl - h / j / k / l to be a natural flow for this plugin, and adding Shift to that for swapping felt like a clean follow on. So here are the mappings I use:

In init.lua:

-- movement
vim.keymap.set({ 'n', 'v' }, '<C-k>', '<cmd>Treewalker Up<cr>', { silent = true })
vim.keymap.set({ 'n', 'v' }, '<C-j>', '<cmd>Treewalker Down<cr>', { silent = true })
vim.keymap.set({ 'n', 'v' }, '<C-h>', '<cmd>Treewalker Left<cr>', { silent = true })
vim.keymap.set({ 'n', 'v' }, '<C-l>', '<cmd>Treewalker Right<cr>', { silent = true })

-- swapping
vim.keymap.set('n', '<C-S-k>', '<cmd>Treewalker SwapUp<cr>', { silent = true })
vim.keymap.set('n', '<C-S-j>', '<cmd>Treewalker SwapDown<cr>', { silent = true })
vim.keymap.set('n', '<C-S-h>', '<cmd>Treewalker SwapLeft<cr>', { silent = true })
vim.keymap.set('n', '<C-S-l>', '<cmd>Treewalker SwapRight<cr>', { silent = true })

Alternatives

  • syntax-tree-surfer is publicly archived and I could not get it to work :/ Treewalker has a robust test suite, is well typed, and has CI (automated testing), to help the plugin be stable. I believe Treewalker usage is a little bit simpler and more intuitive. Treewalker is missing the visual selection swap feature that syntax-tree-surfer has (See #32).

  • nvim-treehopper is similar to Treewalker in that it uses the AST to navigate, but it takes more of a leap like approach, only annotating interesting nodes. Treewalker provides movements that can be called from anywhere to interact with neighboring nodes.

  • nvim-treesitter-textobjects can swap a subset of node types, but misses some types (ex. rust enums). Treewalker is not aware of node type names, only the structure of the AST, so left/right swaps should work where you want them to. nvim-treesitter-textobjects can also move to nodes, but treats node types individually, whereas Treewalker is agnostic about node types, treating them all the same, and interacting with the neighboring relevant node.

  • nvim-treesitter.ts_utils offers a programmatic interface for swapping nodes. It works mostly the same as Treewalker under the hood. Some of Treewalker's left/right swapping code is inspired by ts_utils. Treewalker operates a little differently though, picking the highest node with a coinciding start position, vs. ts_utils's picking the lowest node with a coinciding start position. Practically, what Treewalker offers beyond ts_utils is doing the work of finding the next relevant node and packaging the functionality into a hopefully nice user experience.

  • tree-climber.nvim I discovered long after having made Treewalker. It seems to be the most similar of all of these alternatives. It works mostly the same as Treewalker, but sometimes gets stuck on certain nodes, and navigates to nodes that don't necessarily seem helpful to go to. In my usage, it seems like tree-climber gives you more fine grained access to each individual node, and works better than Treewalker for navigating the literal syntax tree. Treewalker selects nodes on a more linewise approach, which enables larger movements to nodes that seem more relevant to moving around code.