https://github.com/subev/sibling-jump.nvim
Navigate between sibling nodes in your code using Tree-sitter
https://github.com/subev/sibling-jump.nvim
Last synced: 13 days ago
JSON representation
Navigate between sibling nodes in your code using Tree-sitter
- Host: GitHub
- URL: https://github.com/subev/sibling-jump.nvim
- Owner: subev
- License: mit
- Created: 2025-12-28T14:06:01.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-01-01T11:51:55.000Z (about 1 month ago)
- Last Synced: 2026-01-03T22:50:51.249Z (about 1 month ago)
- Language: Lua
- Size: 220 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Roadmap: ROADMAP.md
- Agents: AGENTS.md
Awesome Lists containing this project
- awesome-neovim-sorted - subev/sibling-jump.nvim - sitter | (Motion)
- awesome-neovim - subev/sibling-jump.nvim - Context-aware navigation between sibling Tree-sitter nodes. (Motion / Tree-sitter Based)
README
# sibling-jump.nvim
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
## Features
- **Context-aware navigation**: Jumps between meaningful code units (statements, properties, array elements, etc.)
- **Block-loop** (separate keybinding): Cycle through a block's structural boundaries (start → branches → end → back to start)
- **Visual mode block selection**: Select entire blocks with block-loop in visual mode
- **Multi-language support**: Works with TypeScript, JavaScript, JSX, TSX, Lua, Java, C, C#, Python, and more
- **Smart boundary detection**: Prevents navigation from jumping out of context
- **Method chain navigation**: Seamlessly navigate through method chains like `obj.foo().bar().baz()`
- **If-else chain navigation**: Jump between if/else-if/else clauses
- **JSX/TSX support**: Navigate between JSX elements and attributes
- **Count support**: Use `3` to jump 3 siblings forward
## Sibling Navigation
Jump 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:**
- Statements (variable declarations, if/for/while, return, etc.)
- Object properties and type properties
- Array elements
- Function parameters and arguments
- Import specifiers
- JSX elements and attributes
- Method chains
- If-else-if chains
- Generic type parameters
- Union type members
- And more!
## Block-Loop (Optional Keybinding)
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 `}`
- Method chains → cycles between each method in the chain
## Supported Languages
**sibling-jump.nvim** works with any language that has Tree-sitter support. The following languages have been tested:
### Extensively Tested
- **TypeScript** (.ts)
- **TSX** (.tsx)
- **JavaScript** (.js)
- **JSX** (.jsx)
- **Lua** (.lua)
### Basic Support (Lightly Tested)
- **Java** (.java)
- **C** (.c)
- **C++** (.cpp)
- **C#** (.cs)
- **Python** (.py)
The plugin should work with most languages out of the box. If you encounter issues with a specific language, please [open an issue](https://github.com/yourusername/sibling-jump.nvim/issues) with a minimal example.
## Installation
### Using [lazy.nvim](https://github.com/folke/lazy.nvim)
```lua
{
"subev/sibling-jump.nvim",
config = function()
require("sibling_jump").setup({
next_key = "", -- Jump to next sibling (default)
prev_key = "", -- Jump to previous sibling (default)
block_loop_key = "", -- Cycle through block boundaries (optional)
center_on_jump = false, -- Center screen after jump (default: false)
})
end,
}
```
### Using [packer.nvim](https://github.com/wbthomason/packer.nvim)
```lua
use {
"subev/sibling-jump.nvim",
config = function()
require("sibling_jump").setup({
next_key = "",
prev_key = "",
block_loop_key = "", -- optional
})
end,
}
```
### Using [vim-plug](https://github.com/junegunn/vim-plug)
```vim
Plug 'subev/sibling-jump.nvim'
" In your init.vim or after/plugin/sibling-jump.lua:
lua << EOF
require("sibling_jump").setup({
next_key = "",
prev_key = "",
block_loop_key = "", -- optional
})
EOF
```
## Usage
Once installed, use your configured keybindings:
- `` - Jump to next sibling
- `` - Jump to previous sibling
- `3` - Jump 3 siblings forward (works with any count)
- `` - Cycle through block boundaries (if `block_loop_key` configured)
- `V` then `` - Select entire block in visual mode
### Examples
**Navigate object properties:**
```typescript
const obj = {
foo: 1, // →
bar: 2, // →
baz: 3, // cursor here
};
```
**Navigate array elements:**
```typescript
const arr = [
element1, // →
element2, // →
element3, // cursor here
];
```
**Navigate statements:**
```typescript
const x = 1; // →
const y = 2; // →
return x + y; // cursor here
```
**Navigate method chains:**
```typescript
obj
.foo() // →
.bar() // →
.baz(); // cursor here
```
**Navigate if-else chains:**
```typescript
if (condition1) {
// →
// ...
} else if (condition2) {
// →
// ...
} else {
// cursor here
// ...
}
```
**Navigate JSX elements:**
```tsx
<>
// →
// →
// cursor here
>
```
### Block-Loop Examples
**Cycle through a const declaration:**
```typescript
const config = {
// cursor on "const", →
foo: 1,
bar: 2,
}; // ← lands here, cycles back to "const"
```
**Cycle through if-else blocks:**
```typescript
if (condition1) {
// cursor on "if", →
// ...
} else if (cond2) {
// ← →
// ...
} else {
// ← →
// ...
} // ← lands here, cycles back to "if"
```
**Cycle through a switch statement:**
```typescript
switch (
value // cursor on "switch", →
) {
case 1: // ← →
break;
case 2: // ← →
break;
default: // ← →
break;
} // ← lands here, cycles back to "switch"
```
**Cycle through a for loop:**
```typescript
for (let i = 0; i < 10; i++) {
// cursor on "for", →
console.log(i);
} // ← lands here, cycles back
```
**Visual mode progressive selection:**
In visual mode, block-loop progressively extends the selection with each keypress:
```typescript
if (condition1) { // v to start visual, →
// ...
} else if (cond2) { // ← selection extends here, →
// ...
} else { // ← selection extends here, →
// ...
} // ← selection extends here, 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.
## Configuration
The `setup()` function accepts the following options:
```lua
require("sibling_jump").setup({
-- Key to jump to next sibling (default: "")
next_key = "",
-- Key to jump to previous sibling (default: "")
prev_key = "",
-- Key to cycle through block boundaries (default: nil = disabled)
-- When set, enables block-loop feature in both normal and visual modes
block_loop_key = "",
-- 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" },
})
```
### Recommended Configuration for TypeScript/JavaScript
To avoid keymap conflicts and improve performance, restrict the plugin to TS/JS files:
```lua
{
"subev/sibling-jump.nvim",
ft = { "typescript", "javascript", "typescriptreact", "javascriptreact" },
config = function()
require("sibling_jump").setup({
next_key = "",
prev_key = "",
center_on_jump = true,
filetypes = { "typescript", "javascript", "typescriptreact", "javascriptreact" },
})
end,
}
```
This configuration:
- Lazy loads the plugin only when opening TS/JS files (`ft` parameter)
- Creates buffer-local keymaps only for TS/JS files (`filetypes` option)
- Keymaps won't interfere with other filetypes
## Manual Buffer Control
You can manually enable/disable sibling-jump for any buffer using these commands:
```vim
: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:**
- Testing the plugin in non-TS/JS files (Python, Lua, etc.)
- Temporarily enabling for a specific file without changing config
- Quick experiments with the plugin in different languages
**Example:**
```vim
" Open a Python file
:e script.py
" Enable sibling-jump manually
:SiblingJumpBufferEnable
" Now and work in this buffer!
```
## Requirements
- Neovim >= 0.9.0 (requires Tree-sitter support)
- Tree-sitter parser for your language (automatically installed for most languages)
## Language Support
**Primary support:**
- TypeScript / JavaScript
- TSX / JSX
**Partial support:**
- Python
- Lua
- Other languages with Tree-sitter parsers (may work, but not extensively tested)
## How It Works
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** (``/``):
1. Finds the Tree-sitter node at your cursor
2. Identifies the appropriate "navigation context" (e.g., are you in an object, array, statement block?)
3. Finds the next/previous sibling node in that context
4. Jumps to it, staying within the same level of abstraction
**Block-Loop** (`` if configured):
1. Detects the block construct you're on (`const`, `if`, `for`, `switch`, etc.)
2. Collects all structural boundary positions (start, branches, end)
3. Cycles through them in order, wrapping from end back to start
## Testing
The plugin includes a comprehensive test suite with tests covering all supported navigation scenarios.
**Run tests:**
```bash
cd /path/to/sibling-jump.nvim
bash tests/test_runner.sh
```
All tests pass with Tree-sitter support for TypeScript/JavaScript/JSX/TSX.
## FAQ
### How is this different from treewalker.nvim?
**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.
## Alternatives
- **[syntax-tree-surfer](https://github.com/ziontee113/syntax-tree-surfer)** - Publicly archived. Had visual selection and swap features that inspired many Tree-sitter navigation plugins.
- **[nvim-treehopper](https://github.com/mfussenegger/nvim-treehopper)** - Leap-like approach with label-based jumps to annotated nodes, rather than direct next/prev movements.
- **[tree-climber.nvim](https://github.com/drybalka/tree-climber.nvim)** - Fine-grained AST node navigation. Gives more literal syntax tree access vs sibling-jump's context-aware approach.
- **[nvim-treesitter-textobjects](https://github.com/nvim-treesitter/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](https://github.com/rockerBOO/awesome-neovim/?tab=readme-ov-file#motion).
## Contributing
Contributions are welcome! Please feel free to submit issues or pull requests.
**See [ROADMAP.md](ROADMAP.md)** for planned features and future direction.
## License
MIT
## Credits
Developed by [@subev](https://github.com/subev)
## Development
You can develop this plugin directly in your lazy.nvim installation directory.
For AI-assisted development, see [`.ai/instructions.md`](.ai/instructions.md) for comprehensive project context, architecture details, and development guidelines.