Skip to content

Commit 1c53e77

Browse files
committed
I thiiiink everything works except for cursor placement
1 parent 1f1ce7a commit 1c53e77

File tree

15 files changed

+330
-132
lines changed

15 files changed

+330
-132
lines changed

README.md

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,27 @@
1010

1111
Treewalker is a plugin that gives you the ability to **move around your code in a syntax tree aware manner**.
1212
It uses [Treesitter](https://github.com/tree-sitter/tree-sitter) under the hood for syntax tree awareness.
13-
It offers four subcommands: Up, Down, Right, and Left. Each command moves through the syntax tree
14-
in an intuitive way.
13+
It offers six subcommands: Up, Down, Right, and Left for movement, and SwapUp and SwapDown for intelligent node swapping.
14+
15+
Each movement command moves you through the syntax tree in an intuitive way.
1516

1617
* **Up/Down** - Moves up or down to the next neighbor node
1718
* **Right** - Finds the next good child node
1819
* **Left** - Finds the next good parent node
1920

21+
The swap commands intelligently swap nodes, including comments and attributes/decorators.
22+
2023
---
2124

22-
Moving slowly, showing each command
23-
![A demo of moving around some code slowly typing out each command](static/slow_demo.gif)
25+
<details>
26+
<summary>Typing out the Move commands manually</summary>
27+
<img src="static/slow_move_demo.gif" alt="A demo of moving around some code slowly typing out each Treewalker move command">
28+
</details>
29+
30+
<details>
31+
<summary>Typing out the Swap commands manually</summary>
32+
<img src="static/slow_swap_demo.gif" alt="A demo of swapping code slowly using Treewalker swap commands">
33+
</details>
2434

2535
---
2636

@@ -42,8 +52,26 @@ Moving slowly, showing each command
4252
This is how I have mine mapped; in `init.lua`:
4353

4454
```lua
45-
vim.api.nvim_set_keymap('n', '<C-j>', ':Treewalker Down<CR>', { noremap = true })
46-
vim.api.nvim_set_keymap('n', '<C-k>', ':Treewalker Up<CR>', { noremap = true })
47-
vim.api.nvim_set_keymap('n', '<C-h>', ':Treewalker Left<CR>', { noremap = true })
48-
vim.api.nvim_set_keymap('n', '<C-l>', ':Treewalker Right<CR>', { noremap = true })
55+
vim.keymap.set({ 'n', 'v' }, '<C-k>', '<cmd>Treewalker Up<cr>', { noremap = true, silent = true })
56+
vim.keymap.set({ 'n', 'v' }, '<C-j>', '<cmd>Treewalker Down<cr>', { noremap = true, silent = true })
57+
vim.keymap.set({ 'n', 'v' }, '<C-l>', '<cmd>Treewalker Right<cr>', { noremap = true, silent = true })
58+
vim.keymap.set({ 'n', 'v' }, '<C-h>', '<cmd>Treewalker Left<cr>', { noremap = true, silent = true })
59+
vim.keymap.set('n', '<C-S-j>', '<cmd>Treewalker SwapDown<cr>', { noremap = true, silent = true })
60+
vim.keymap.set('n', '<C-S-k>', '<cmd>Treewalker SwapUp<cr>', { noremap = true, silent = true })
61+
```
62+
63+
I also utilize some
64+
[nvim-treesitter-textobjects](https://github.com/nvim-treesitter/nvim-treesitter-textobjects?tab=readme-ov-file#text-objects-swap)
65+
commands to round out the swap commands - `<C-S-{j,k,l,h}>` just feel really good to me, so might as well get the
66+
lateral swapping as well. (This is not something that `Treewalker` needs to do as it already exists from other libraries)
67+
68+
```lua
69+
vim.keymap.set('n', "<C-S-l>", ":TSTextobjectSwapNext @parameter.inner<CR>", { noremap = true, silent = true })
70+
vim.keymap.set('n', "<C-S-h>", ":TSTextobjectSwapPrevious @parameter.inner<CR>", { noremap = true, silent = true })
4971
```
72+
73+
The above can also be accomplished with
74+
[nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) using
75+
[ts_utils](https://github.com/nvim-treesitter/nvim-treesitter?tab=readme-ov-file#utilities).
76+
See [this PR](https://github.com/aaronik/treewalker.nvim/pull/10/files) for
77+
an example of that!

TODO.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
# TODO
22

33
* Check for errant util.log or util.R usages in CI/local
4-
* Swapping
5-
* Jumplist
64
* Get more languages into movement/highlight/swap specs
75
* :help treesitter-parsers
8-
* Python decorators (tough b/c of the identifier node underneath)

lua/treewalker/init.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ function Treewalker.setup(opts)
2020
end
2121
end
2222

23+
-- TODO This is clever kinda, but it breaks autocomplete of `require('treewalker')`
24+
2325
-- Assign move_{in,out,up,down}
2426
for fn_name, fn in pairs(movement) do
2527
Treewalker[fn_name] = fn

lua/treewalker/nodes.lua

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
local lines = require "treewalker.lines"
2-
local util = require "treewalker.util"
1+
local lines = require "treewalker.lines"
2+
local util = require "treewalker.util"
33

44
-- These are regexes but just happen to be real simple so far
5-
local TARGET_BLACKLIST_TYPE_MATCHERS = {
5+
local TARGET_BLACKLIST_TYPE_MATCHERS = {
66
"comment",
77
"attribute_item", -- decorators (rust)
88
"decorat", -- decorators (py)
@@ -15,13 +15,13 @@ local HIGHLIGHT_BLACKLIST_TYPE_MATCHERS = {
1515
"block",
1616
}
1717

18-
local AUGMENT_TARGET_TYPE_MATCHERS = {
18+
local AUGMENT_TARGET_TYPE_MATCHERS = {
1919
"comment",
2020
"attribute_item", -- decorators (rust)
2121
"decorat", -- decorators (py)
2222
}
2323

24-
local M = {}
24+
local M = {}
2525

2626
---@param node TSNode
2727
---@param matchers string[]
@@ -232,7 +232,7 @@ function M.log(node)
232232
local row = M.range(node)[1] + 1
233233
local line = lines.get_line(row)
234234
local col = lines.get_start_col(line)
235-
local log_string = "dest:"
235+
local log_string = ""
236236
log_string = log_string .. string.format(" [%s/%s]", row, col)
237237
log_string = log_string .. string.format(" (%s)", node:type())
238238
log_string = log_string .. string.format(" |%s|", line)

lua/treewalker/ops.lua

Lines changed: 4 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
1-
local util = require('treewalker.util')
21
local nodes = require('treewalker.nodes')
32
local lines = require('treewalker.lines')
4-
local augment = require('treewalker.augment')
53

64
local M = {}
75

6+
-- For a potentially more nvim-y way to do it, see how treesitter-utils does it:
7+
-- https://github.com/nvim-treesitter/nvim-treesitter/blob/981ca7e353da6ea69eaafe4348fda5e800f9e1d8/lua/nvim-treesitter/ts_utils.lua#L388
8+
-- (ts_utils.swap_nodes)
9+
810
---Flash a highlight over the given range
911
---@param range Range4
1012
---@param duration integer
1113
function M.highlight(range, duration)
1214
local start_row, start_col, end_row, end_col = range[1], range[2], range[3], range[4]
1315
local ns_id = vim.api.nvim_create_namespace("")
14-
-- local hl_group = "DiffAdd"
15-
-- local hl_group = "MatchParen"
16-
-- local hl_group = "Search"
1716
local hl_group = "ColorColumn"
1817

1918
for row = start_row, end_row do
@@ -41,22 +40,13 @@ end
4140
---@param row integer
4241
---@param node TSNode
4342
function M.jump(row, node)
44-
nodes.log(node)
4543
vim.cmd("normal! m'") -- Add originating node to jump list
4644
vim.api.nvim_win_set_cursor(0, { row, 0 })
4745
vim.cmd("normal! ^") -- Jump to start of line
4846
if require("treewalker").opts.highlight then
4947
local range = nodes.range(node)
5048
local duration = require("treewalker").opts.highlight_duration
5149
M.highlight(range, duration)
52-
53-
-- -- TODO Make this not look like butt, if it's even wanted
54-
-- local augments = augment.get_node_augments(node)
55-
-- for _, aug in ipairs(augments) do
56-
-- range = nodes.range(aug)
57-
-- M.highlight(range, duration)
58-
-- end
59-
6050
end
6151
end
6252

@@ -83,45 +73,3 @@ end
8373

8474
return M
8575

86-
---- Leaving this here for now because my gut says this is a better way to do it,
87-
---- and at some point it may want to get done.
88-
---- https://github.com/nvim-treesitter/nvim-treesitter/blob/981ca7e353da6ea69eaafe4348fda5e800f9e1d8/lua/nvim-treesitter/ts_utils.lua#L388
89-
---- (ts_utils.swap_nodes)
90-
-----@param rows1 [integer, integer] -- [start row, end row]
91-
-----@param rows2 [integer, integer] -- [start row, end row]
92-
--function M.swap(rows1, rows2)
93-
-- local s1, e1, s2, e2 = rows1[1], rows1[2], rows2[1], rows2[2]
94-
-- local text1 = lines.get_lines(s1 + 1, e1 + 1)
95-
-- local text2 = lines.get_lines(s2 + 1, e2 + 1)
96-
97-
-- util.log("text1: " .. s1 .. "/" .. e1)
98-
-- util.log("text2: " .. s2 .. "/" .. e2)
99-
100-
-- ---@type lsp.Range
101-
-- local range1 = {
102-
-- start = { line = s1, character = 0 },
103-
-- ["end"] = { line = e1, character = 0 } -- end is reserved
104-
-- }
105-
106-
-- ---@type lsp.Range
107-
-- local range2 = {
108-
-- start = { line = s2, character = 0 },
109-
-- ["end"] = { line = e2 + 1, character = 0 }
110-
-- }
111-
112-
-- -- util.log(range1, range2)
113-
114-
-- -- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textEdit
115-
-- lines.set_lines(s1 + 1, text2)
116-
-- ---@type lsp.TextEdit
117-
-- local edit1 = { range = range1, newText = table.concat(text2, "\n") }
118-
119-
-- lines.set_lines(s2 + 1, text1)
120-
-- ---@type lsp.TextEdit
121-
-- local edit2 = { range = range2, newText = table.concat(text1, "\n") }
122-
123-
-- local bufnr = vim.api.nvim_get_current_buf()
124-
-- -- vim.lsp.util.apply_text_edits({ edit1, edit2 }, bufnr, "utf-8")
125-
--end
126-
127-

lua/treewalker/swap.lua

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,66 @@ local nodes = require "treewalker.nodes"
22
local ops = require "treewalker.ops"
33
local targets = require "treewalker.targets"
44
local augment = require "treewalker.augment"
5-
local util = require "treewalker.util"
65

76
local M = {}
87

9-
---@return nil
8+
---@return boolean
9+
local function is_on_target_node()
10+
local node = vim.treesitter.get_node()
11+
if not node then return false end
12+
if not nodes.is_jump_target(node) then return false end
13+
if vim.fn.line('.') - 1 ~= node:range() then return false end
14+
return true
15+
end
16+
17+
---@return boolean
18+
local function is_supported_ft()
19+
local unsupported_filetypes = {
20+
["text"] = true,
21+
["markdown"] = true,
22+
}
23+
24+
local bufnr = vim.api.nvim_get_current_buf()
25+
local ft = vim.bo[bufnr].filetype
26+
27+
return not unsupported_filetypes[ft]
28+
end
29+
1030
function M.swap_down()
31+
if not is_on_target_node() then return end
32+
if not is_supported_ft() then return end
33+
1134
local target, row, line = targets.down()
1235

1336
if not target or not row or not line then
1437
--util.log("no down candidate")
1538
return
1639
end
1740

18-
-- TODO remove this comment it's just for testing fr fr
1941
local current = nodes.get_current()
2042
local current_range = nodes.range(current)
21-
local all = augment.get_node_augments(current)
22-
table.insert(all, current)
23-
local current_rows = nodes.row_range(all)
43+
local current_augments = augment.get_node_augments(current)
44+
local current_all = { current, unpack(current_augments) }
45+
local current_all_rows = nodes.row_range(current_all)
2446

2547
local target_range = nodes.range(target)
26-
local target_rows = { target_range[1], target_range[3] }
48+
local target_augments = augment.get_node_augments(target)
49+
local target_all = { target, unpack(target_augments) }
50+
local target_all_rows = nodes.row_range(target_all)
2751

28-
ops.swap(current_rows, target_rows)
52+
ops.swap(current_all_rows, target_all_rows)
2953

3054
-- Place cursor
3155
local node_length_diff = ((current_range[3] - current_range[1]) + 1) - ((target_range[3] - target_range[1]) + 1)
32-
vim.fn.cursor(target_range[1] - node_length_diff + 1, target_range[2] + 1)
56+
local x = target_range[1] - node_length_diff + 1
57+
local y = target_range[2] + 1
58+
vim.fn.cursor(x, y)
3359
end
3460

35-
---@return nil
3661
function M.swap_up()
62+
if not is_on_target_node() then return end
63+
if not is_supported_ft() then return end
64+
3765
local target, row, line = targets.up()
3866

3967
if not target or not row or not line then
@@ -42,16 +70,25 @@ function M.swap_up()
4270
end
4371

4472
local current = nodes.get_current()
45-
local current_range = nodes.range(current)
46-
local current_rows = { current_range[1], current_range[3] }
73+
local current_augments = augment.get_node_augments(current)
74+
local current_all = { current, unpack(current_augments) }
75+
local current_all_rows = nodes.row_range(current_all)
4776

4877
local target_range = nodes.range(target)
49-
local target_rows = { target_range[1], target_range[3] }
78+
local target_augments = augment.get_node_augments(target)
79+
local target_all = { target, unpack(target_augments) }
80+
local target_all_rows = nodes.row_range(target_all)
5081

51-
ops.swap(target_rows, current_rows)
82+
ops.swap(target_all_rows, current_all_rows)
5283

5384
-- Place cursor
54-
vim.fn.cursor(target_range[1] + 1, target_range[2] + 1)
85+
local target_augment_rows = nodes.row_range(target_augments)
86+
local target_augment_length = #target_augments > 0 and (target_augment_rows[2] + 1 - target_augment_rows[1]) or 0
87+
local current_augment_rows = nodes.row_range(current_augments)
88+
local current_augment_length = #current_augments > 0 and (current_augment_rows[2] + 1 - current_augment_rows[1]) or 0
89+
local x = target_range[1] + 1 + current_augment_length - target_augment_length
90+
local y = target_range[2] + 1
91+
vim.fn.cursor(x, y)
5592
end
5693

5794
return M

plugin/init.lua

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,6 @@ local subcommands = {
2727

2828
SwapDown = function()
2929
tw().swap_down()
30-
end,
31-
32-
SwapRight = function()
33-
tw().swap_in()
34-
end,
35-
36-
SwapLeft = function()
37-
tw().swap_out()
3830
end
3931
}
4032

static/fast_demo.gif

-1.14 MB
Loading
File renamed without changes.

static/slow_swap_demo.gif

298 KB
Loading

0 commit comments

Comments
 (0)