Navigate between sibling nodes in your code using Tree-sitter. Context-aware navigation that keeps you at the right level of abstraction.
https://github.com/user-attachments/assets/62c59d9a-8593-49b2-b124-1e547c2853cd
obj.foo().bar().baz()3<C-j> to jump 3 siblings forwardJump between nodes at the same nesting level. When your cursor is on a statement, property, or element, pressing the navigation key moves you to the next/previous sibling.
Supported contexts:
A complementary feature with its own keybinding. When triggered, it cycles through a block's structural boundaries instead of jumping to siblings.
https://github.com/user-attachments/assets/de9d8239-0e4e-4a39-8f33-0b77bca87876
Supported constructs:
const/let/var declarations → cycles between keyword and closing }/)if/else if/else blocks → cycles through all branches and closing }for/while loops → cycles between keyword and closing }switch statements → cycles through switch, each case/default, and closing }function declarations → cycles between keyword and closing }type/interface declarations → cycles between keyword and closing }sibling-jump.nvim works with any language that has Tree-sitter support. The following languages have been tested:
The plugin should work with most languages out of the box. If you encounter issues with a specific language, please open an issue with a minimal example.
{
"subev/sibling-jump.nvim",
config = function()
require("sibling_jump").setup({
next_key = "<C-j>", -- Jump to next sibling (default)
prev_key = "<C-k>", -- Jump to previous sibling (default)
block_loop_key = "<C-l>", -- Cycle through block boundaries (optional)
center_on_jump = false, -- Center screen after jump (default: false)
})
end,
}
use {
"subev/sibling-jump.nvim",
config = function()
require("sibling_jump").setup({
next_key = "<C-j>",
prev_key = "<C-k>",
block_loop_key = "<C-l>", -- optional
})
end,
}
Plug 'subev/sibling-jump.nvim'
" In your init.vim or after/plugin/sibling-jump.lua:
lua << EOF
require("sibling_jump").setup({
next_key = "<C-j>",
prev_key = "<C-k>",
block_loop_key = "<C-l>", -- optional
})
EOF
Once installed, use your configured keybindings:
<C-j> - Jump to next sibling<C-k> - Jump to previous sibling3<C-j> - Jump 3 siblings forward (works with any count)<C-l> - Cycle through block boundaries (if block_loop_key configured)V then <C-l> - Select entire block in visual modeNavigate object properties:
const obj = {
foo: 1, // <C-j> →
bar: 2, // <C-j> →
baz: 3, // cursor here
};
Navigate array elements:
const arr = [
element1, // <C-j> →
element2, // <C-j> →
element3, // cursor here
];
Navigate statements:
const x = 1; // <C-j> →
const y = 2; // <C-j> →
return x + y; // cursor here
Navigate method chains:
obj
.foo() // <C-j> →
.bar() // <C-j> →
.baz(); // cursor here
Navigate if-else chains:
if (condition1) {
// <C-j> →
// ...
} else if (condition2) {
// <C-j> →
// ...
} else {
// cursor here
// ...
}
Navigate JSX elements:
<>
<Header /> // <C-j> →
<Content /> // <C-j> →
<Footer /> // cursor here
</>
Cycle through a const declaration:
const config = {
// cursor on "const", <C-j> →
foo: 1,
bar: 2,
}; // ← lands here, <C-j> cycles back to "const"
Cycle through if-else blocks:
if (condition1) {
// cursor on "if", <C-j> →
// ...
} else if (cond2) {
// ← <C-j> →
// ...
} else {
// ← <C-j> →
// ...
} // ← lands here, <C-j> cycles back to "if"
Cycle through a switch statement:
switch (
value // cursor on "switch", <C-j> →
) {
case 1: // ← <C-j> →
break;
case 2: // ← <C-j> →
break;
default: // ← <C-j> →
break;
} // ← lands here, <C-j> cycles back to "switch"
Cycle through a for loop:
for (let i = 0; i < 10; i++) {
// cursor on "for", <C-j> →
console.log(i);
} // ← lands here, <C-j> cycles back
Visual mode progressive selection:
In visual mode, block-loop progressively extends the selection with each keypress:
if (condition1) { // v to start visual, <C-l> →
// ...
} else if (cond2) { // ← selection extends here, <C-l> →
// ...
} else { // ← selection extends here, <C-l> →
// ...
} // ← selection extends here, <C-l> wraps back
This lets you precisely control how much of the block to select - useful for selecting just the if-else-if portion without the final else, for example.
The setup() function accepts the following options:
require("sibling_jump").setup({
-- Key to jump to next sibling (default: "<C-j>")
next_key = "<C-j>",
-- Key to jump to previous sibling (default: "<C-k>")
prev_key = "<C-k>",
-- Key to cycle through block boundaries (default: nil = disabled)
-- When set, enables block-loop feature in both normal and visual modes
block_loop_key = "<C-l>",
-- Whether to center screen after each jump (default: false)
center_on_jump = false,
-- Separate center setting for block-loop (default: uses center_on_jump value)
block_loop_center_on_jump = false,
-- Optional: Restrict keymaps to specific filetypes (default: nil = global keymaps)
-- When set, creates buffer-local keymaps only for these filetypes
filetypes = { "typescript", "javascript", "typescriptreact", "javascriptreact" },
})
To avoid keymap conflicts and improve performance, restrict the plugin to TS/JS files:
{
"subev/sibling-jump.nvim",
ft = { "typescript", "javascript", "typescriptreact", "javascriptreact" },
config = function()
require("sibling_jump").setup({
next_key = "<C-j>",
prev_key = "<C-k>",
center_on_jump = true,
filetypes = { "typescript", "javascript", "typescriptreact", "javascriptreact" },
})
end,
}
This configuration:
ft parameter)filetypes option)You can manually enable/disable sibling-jump for any buffer using these commands:
:SiblingJumpBufferEnable " Enable for current buffer
:SiblingJumpBufferDisable " Disable for current buffer
:SiblingJumpBufferToggle " Toggle on/off for current buffer
:SiblingJumpBufferStatus " Check if enabled for current buffer
Use cases:
Example:
" Open a Python file
:e script.py
" Enable sibling-jump manually
:SiblingJumpBufferEnable
" Now <C-j> and <C-k> work in this buffer!
Primary support:
Partial support:
sibling-jump uses Neovim's Tree-sitter integration to understand your code's structure. Instead of jumping by lines or words, it jumps between meaningful syntactic units.
Sibling Navigation (<C-j>/<C-k>):
Block-Loop (<C-l> if configured):
const, if, for, switch, etc.)The plugin includes a comprehensive test suite with tests covering all supported navigation scenarios.
Run tests:
cd /path/to/sibling-jump.nvim
bash tests/test_runner.sh
All tests pass with Tree-sitter support for TypeScript/JavaScript/JSX/TSX.
sibling-jump stays within your current context. When you are in a function you are jumping only inside of its top level statements/expressions. when you're in an object, it jumps between properties. When you're in an array, it jumps between elements. When you're in an if-else chain, it treats the entire chain as one navigable unit.
treewalker.nvim is great for full AST traversal (4 directions, moving between nesting levels), but sibling-jump focuses on "just working" horizontally - staying at the same level of abstraction without accidentally jumping out of your current block. The following shouldn't be possible with sibling-jump.nvim
sibling-jump also has a block-loop feature that cycles through a construct's boundaries (if → else if → else → closing brace). In visual mode, it selects the entire block - useful for quickly selecting an if-else chain, function, or declaration for deletion/yanking.
syntax-tree-surfer - Publicly archived. Had visual selection and swap features that inspired many Tree-sitter navigation plugins.
nvim-treehopper - Leap-like approach with label-based jumps to annotated nodes, rather than direct next/prev movements.
tree-climber.nvim - Fine-grained AST node navigation. Gives more literal syntax tree access vs sibling-jump's context-aware approach.
nvim-treesitter-textobjects - Node-type-specific movements and swaps. sibling-jump is node-type agnostic.
For more Tree-sitter motion plugins, see awesome-neovim#motion.
Contributions are welcome! Please feel free to submit issues or pull requests.
See ROADMAP.md for planned features and future direction.
MIT
Developed by @subev
You can develop this plugin directly in your lazy.nvim installation directory.
For AI-assisted development, see .ai/instructions.md for comprehensive project context, architecture details, and development guidelines.