Neovim plugin to swap places of siblings, e.g., arguments, parameters, attributes, pairs in objects, array's items etc., which located near and separated by allowed_separators or space.
- Zero-config (almost): No need to setup specific language – should works from scratch with all languages supported by Tree-Sitter;
- Simple: Just grab this node and move;
- Sticky-cursor: The cursor follows the text on which it was called;
- Smart: Able to replace operand in binary expressions and Mathematical operations to opposite1.
- Dot-repeat: Dot-repeat and
v:countfor all keymaps
demo_sibling-swap.mov
- Neovim 0.10+
- Installed tree-sitter's parsers (e.g. with nvim-treesitter
- Read the WARNING
With packer.nvim:
use({
'Wansmer/sibling-swap.nvim',
requires = { 'nvim-treesitter' },
config = function()
require('sibling-swap').setup({--[[ your config ]]})
end,
})local DEFAULT_SETTINGS = {
allowed_separators = {
',',
';',
'and',
'or',
'&&',
'&',
'||',
'|',
'==',
'===',
'!=',
'!==',
'-',
'+',
['<'] = '>',
['<='] = '>=',
['>'] = '<',
['>='] = '<=',
},
use_default_keymaps = true,
-- Highlight recently swapped node. Can be boolean or table
-- If table: { ms = 500, hl_opts = { link = 'IncSearch' } }
-- `hl_opts` is a `val` from `nvim_set_hl()`
highlight_node_at_cursor = false,
-- keybinding for movements to right or left (and up or down, if `allow_interline_swaps` is true)
-- (`<C-,>` and `<C-.>` may not map to control chars at system level, so are sent by certain terminals as just `,` and `.`. In this case, just add the mappings you want.)
keymaps = {
['<C-.>'] = 'swap_with_right',
['<C-,>'] = 'swap_with_left',
['<space>.'] = 'swap_with_right_with_opp',
['<space>,'] = 'swap_with_left_with_opp',
},
ignore_injected_langs = false,
-- allow swaps across lines
allow_interline_swaps = true,
-- swaps interline siblings without separators (no recommended, helpful for swaps html-like attributes)
interline_swaps_without_separator = false,
-- Fallbacs for tiny settings for langs and nodes. See #fallback
fallback = {},
}allowed_separators: list of separators for detecting suitable siblings. 'Separators' meaning unnamed treesitter node.
If you need to change separator to the opposite value (e.g., in binary expressions), set it like key = value.
If you want to disable something separator - set it to false.
Example:
require('sibling-swap').setup({
allowed_separators = {
-- standart
'=',
-- with opposite value
['>>'] = '<<',
['<<'] = '>>',
-- disable
['&'] = false,
}
})use_default_keymaps - use default keymaps or not.
keymaps - keymaps by default.
If you want to change it, here are two ways to do it:
- Change it in options (like above). Be sure that
use_default_keymapsis 'true'; - Add
vim.keymap.set('n', 'YOUR_PREFER_KEYS', require('sibling-swap').swap_with_left)anywhere in your config. Be sure thatuse_default_keymapsis 'false';
ignore_injected_langs: 'true' is not recommended. If set to 'true', plugin will not to recognize injected languages, e.g. blocks of code in markdown, js in html or js in vue.
Here is two reason to set it 'true':
If you no work with filetypes allowing injected languages; If you want to be able to swap node with injected language when cursor is placed on injected, e.g.:
<template>
<app-item @click="clic | kHandler" class="class" />
|
<!-- The 'clickHandler' is a javascript and it have not any -->
<!-- siblings. If 'ignore_injected_langs' is 'false', the plugin will do nothing. -->
<!-- If 'ignore_injected_langs' is 'true', attribute '@click="clickHandler"' will -->
<!-- swap. But in section 'script' or 'stile' the plugin will not working. -->
</template>
<script setup>
const one = { tw|o: 'two', one: 'one' }
|
// If 'ignore_injected_langs' is 'true', Tree-Sitter recognize
// all <script> section as injected language and it will be
// ignored.
</script>You can pass control to a third-party plugin or custom function if the node search process finds matches with the nodes listed here.
---@class FallbackItem
---@field enable boolean|function(node: TSNode): boolean
---@field action function(node: TSNode, side: string): void
require('sibling-swap').setup({
---@field fallback table<string, table<string, FallbackItem>>
fallback = {
['javascript'] = {
string = {
enable = function(node)
-- some condition
return true
end,
action = function(node, side)
-- Do something instead of swapping
end,
},
},
},
})The plugin works with SIBLINGS. It means that any sibling which is located near, has ‘allowed’ separator or space between each other and placed in same level in ‘treesitter’ tree - is suitable for swaps. You don't need to configure each language separately. It is assumed that you understand it before using.
Examples:
function test (a) { return a; }
// |
// cursor here and you trigger 'swap_with_right', code will transform to
function (a) test { return a; }
// because 'test' and '(a)' on same line, on one level in tree and has space between each other<p class="swap" is="left">Swap me</p>
<!-- | -->
<!-- cursor here and you trigger 'swap_with_left', code will transform to -->
<class="swap" p is="left">Swap me</p>
<!-- because 'class="swap"' and 'p' on same line, on one level in tree and has space between each other -->Footnotes
-
If you want to swap operand and operators with by one key from anywhere in binary expressions, look at binary-swap.nvim ↩