Skip to content
This repository was archived by the owner on Jan 19, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 58 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,82 @@
# Binary-Swap: swap operands in binary expressions

Small plugin for neovim 0.8+ powered on [treesitter](https://github.com/nvim-treesitter/nvim-treesitter) for swapping operands and operators in binary expressions: `123 > 0` to `0 < 123` and `1 + 2` to `2 + 1`
Small plugin for neovim 0.11+ powered by [treesitter](https://github.com/nvim-treesitter/nvim-treesitter)
for swapping operands and operators in binary expressions:
`123 > 0` to `0 < 123` and `1 + 2` to `2 + 1`

- Comparison operations;
- Mathematical operations;

https://user-images.githubusercontent.com/46977173/201508787-1b9604a1-1d0a-4feb-86d2-8b5417f4f679.mov
> [!NOTE]
> This is a maintained fork of
> [binary-swap](https://github.com/Wansmer/binary-swap.nvim) following its
> archivation. So far almost all code logic is the same with only minor refactoring.

<https://user-images.githubusercontent.com/46977173/201508787-1b9604a1-1d0a-4feb-86d2-8b5417f4f679.mov>

## Installation

With [lazy.nvim](https://github.com/folke/lazy.nvim):

```lua
{
"Maneren/binary-swap.nvim",
dependencies = {
{ "nvim-treesitter/nvim-treesitter" },
},
opts = {},
keys = {
{
"KEY",
function ()
require("binary-swap").swap_operands()
end
},
{
"KEY",
function ()
require("binary-swap").swap_operands_with_operator()
end
},
}
}
```

# Installation
With [packer.nvim](https://github.com/wbthomason/packer.nvim):

With [packer.nvim]():
> [!WARNING]
> Not officially supported anymore

```lua
use({
'Wansmer/binary-swap.nvim',
"Maneren/binary-swap.nvim",
setup = function ()
vim.keymap.set('n', 'YOUR PREFER KEYS', function ()
require('binary-swap').swap_operands()
vim.keymap.set("n", "KEY", function ()
require("binary-swap").swap_operands()
end)
vim.keymap.set('n', 'YOUR PREFER KEYS', function ()
require('binary-swap').swap_operands_with_operator()
vim.keymap.set("n", "KEY", function ()
require("binary-swap").swap_operands_with_operator()
end)
end
})
```

Binary-wap doesn't set up keymaps by default and no required additional settings.
## Usage

There are two methods available outside:

1. `require('binary-swap').swap_operands()` – swap only operands
(e.g., `MAX_VALUE >= getCurrentValue()` will transform to `getCurrentValue() >= MAX_VALUE`, here **operator** `>=` is not changed);
(e.g., `MAX_VALUE >= getCurrentValue()` will transform to
`getCurrentValue() >= MAX_VALUE`; **operator** `>=` is not changed);

2. `require('binary-swap').swap_operands_with_operator()` – swap operands and operator to opposite if possible. (e.g., `MAX_VALUE >= getCurrentValue()` transforms to `getCurrentValue() <= MAX_VALUE`)
2. `require('binary-swap').swap_operands_with_operator()` – swap operands and
operator to opposite if possible. (e.g., `MAX_VALUE >= getCurrentValue()`
transforms to `getCurrentValue() <= MAX_VALUE`)

## Note
## Languages

I considered a few different languages and at each of them node with binary expression node has type `binary_expression`. Plugin searches this type by default because have no reason to add additional options to set up. If in your favorite language, this type is different, feel free to open an issue.
Most languages have binary expression node with the type `binary_expression`, so
they should work out of the box. However, some languages may use different
node types, for example Python uses few `*_operator` node types. Those have to
be listed manually in the plugins, so they are being added when encountered.
Feel free to open an issue if your favorite language doesn't work.
109 changes: 58 additions & 51 deletions lua/binary-swap/swap.lua
Original file line number Diff line number Diff line change
@@ -1,101 +1,108 @@
local query = require('vim.treesitter.query')
local ts = require('vim.treesitter')
-- `ts.get_node_text` for NVIM v0.9.0-dev-1275+gcbbf8bd66-dirty and newer
-- see: https://github.com/neovim/neovim/pull/22761
local get_node_text = ts.get_node_text or query.get_node_text
local ts_ok, ts_utils = pcall(require, 'nvim-treesitter.ts_utils')

if not ts_ok then
return
end

local M = {}

local OPPOSITES = vim.tbl_add_reverse_lookup({
local OPPOSITES = {
['>='] = '<=',
['<='] = '>=',
['>'] = '<',
['<'] = '>',
['<<'] = '>>',
})
['>>'] = '<<',
}

local BINARY = 'binary_expression'
local OPERATOR_INDEX = 2
local BINARY = {
'binary_expression',
'binary_operator',
'boolean_operator',
'comparison_operator',
}

---Return TSNode with type 'binary_expression' or nil
---@param node userdata
---@return userdata|nil
---@param node TSNode
---@return TSNode|nil
local function get_binary_node(node)
if not node then
return
if vim.tbl_contains(BINARY, node:type()) then
return node
end

if node:type() ~= BINARY then
node = node:parent()
return get_binary_node(node)
local parent = node:parent()

if not parent then
return
end

return node
return get_binary_node(parent)
end

---Returned list-like table with children of node
---This function is pretty much copied from 'nvim-treesitter'
---(TSRange:collect_children)
---@param node userdata TSNode instance
---@param filter? function Function for filtering output list
---@return table
local function collect_children(node, filter)
---@param node TSNode TSNode instance
---@return TSNode[]
local function collect_children(node)
local children = {}

for child in node:iter_children() do
if not filter or filter(child) then
table.insert(children, child)
end
table.insert(children, child)
end

return children
end

---Returned swapped operands and opposite operator if it needs
---@param operands userdata[]
---@param operands TSNode[]
---@param swap_operator? boolean Swap operator to opposite or not
---@return table[]
local function swap_operands(operands, swap_operator)
local replacement = {}
for idx = #operands, 1, -1 do
local text = get_node_text(operands[idx], 0)

if type(text) == 'string' then
text = vim.split(text, '\n')
end
for idx = #operands, 1, -1 do
local text = vim.treesitter.get_node_text(operands[idx], 0)

local reversed_idx = #operands - idx + 1

if swap_operator and idx == OPERATOR_INDEX then
local operator = OPPOSITES[text[1]]
text = operator and { operator } or text
if swap_operator and idx == reversed_idx then
local operator = OPPOSITES[text]

if operator then
text = operator
end
end

local range = { operands[reversed_idx]:range() }
table.insert(replacement, { text = text, range = range })
table.insert(replacement, { text = vim.split(text, '\n'), range = range })
end

return replacement
end

---Format and replace binary expression under cursor
---@param swap_operator? boolean Swap operator to opposite or not
function M.format_and_replace(swap_operator)
local parser = vim.treesitter.get_parser(0)
local parser = vim.treesitter.get_parser()

if not parser then
return
end

parser:parse()

local node = ts_utils.get_node_at_cursor(0)
local node = vim.treesitter.get_node()

if not node then
return
end

local binary_expression = get_binary_node(node)
if binary_expression then
local operands = collect_children(binary_expression)
local replacement = swap_operands(operands, swap_operator)

for i = #replacement, 1, -1 do
local sr, sc, er, ec = unpack(replacement[i].range)
vim.api.nvim_buf_set_text(0, sr, sc, er, ec, replacement[i].text)
end
if not binary_expression then
return
end

local operands = collect_children(binary_expression)

local replacement = swap_operands(operands, swap_operator)

for i = #replacement, 1, -1 do
local sr, sc, er, ec = unpack(replacement[i].range)
vim.api.nvim_buf_set_text(0, sr, sc, er, ec, replacement[i].text)
end
end

Expand Down