Skip to content

Commit f83d36e

Browse files
committed
Implement scope confinement for swap commands
1 parent cd3db8d commit f83d36e

17 files changed

+309
-0
lines changed

lua/treewalker/swap.lua

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,24 @@ local function is_supported_ft()
3838
return not unsupported_filetypes[ft]
3939
end
4040

41+
---@param current_node TSNode
42+
---@param candidate TSNode
43+
---@return boolean
44+
local function should_confine_swap(current_node, candidate)
45+
local opts = require('treewalker').opts
46+
if opts.scope_confined ~= true then
47+
return false
48+
end
49+
50+
local current_parent = nodes.scope_parent(current_node)
51+
if not current_parent then
52+
return false
53+
end
54+
55+
local candidate_anchor = nodes.get_highest_row_coincident(candidate)
56+
return not nodes.is_descendant_of(current_parent, candidate_anchor)
57+
end
58+
4159
function M.swap_down()
4260
vim.cmd("normal! ^")
4361
if not is_supported_ft() then return end
@@ -50,6 +68,10 @@ function M.swap_down()
5068
local target = targets.down(current, row)
5169
if not target then return end
5270

71+
if should_confine_swap(current, target) then
72+
return
73+
end
74+
5375
local current_augments = augment.get_node_augments(current)
5476
local current_all = { current, unpack(current_augments) }
5577
local current_srow = nodes.get_srow(current)
@@ -82,6 +104,10 @@ function M.swap_up()
82104
local target = targets.up(current, row)
83105
if not target then return end
84106

107+
if should_confine_swap(current, target) then
108+
return
109+
end
110+
85111
local current_augments = augment.get_node_augments(current)
86112
local current_all = { current, unpack(current_augments) }
87113
local current_srow = nodes.get_srow(current)
@@ -121,6 +147,10 @@ function M.swap_right()
121147

122148
if not current or not target then return end
123149

150+
if should_confine_swap(current, target) then
151+
return
152+
end
153+
124154
-- set a mark to track where the target started, so we may later go there after the swap
125155
local ns_id = vim.api.nvim_create_namespace("treewalker#swap_right")
126156
local ext_id = vim.api.nvim_buf_set_extmark(
@@ -158,6 +188,10 @@ function M.swap_left()
158188

159189
if not current or not target then return end
160190

191+
if should_confine_swap(current, target) then
192+
return
193+
end
194+
161195
operations.swap_nodes(target, current)
162196

163197
-- Place cursor

tests/treewalker/c_sharp_spec.lua

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,5 +115,21 @@ describe("In a C Sharp file", function()
115115
it("confines move_up", function()
116116
h.assert_confined_by_parent(20, 1, 'up')
117117
end)
118+
119+
it("confines swap_down", function()
120+
h.assert_swap_confined_by_parent(22, 1, 'down')
121+
end)
122+
123+
it("confines swap_up", function()
124+
h.assert_swap_confined_by_parent(20, 1, 'up')
125+
end)
126+
127+
it("confines swap_right", function()
128+
h.assert_swap_confined_by_parent(22, 1, 'right')
129+
end)
130+
131+
it("confines swap_left", function()
132+
h.assert_swap_confined_by_parent(20, 1, 'left')
133+
end)
118134
end)
119135
end)

tests/treewalker/c_spec.lua

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,21 @@ describe("In a c file:", function()
8282
it("confines move_up", function()
8383
h.assert_confined_by_parent(10, 1, 'up')
8484
end)
85+
86+
it("confines swap_down", function()
87+
h.assert_swap_confined_by_parent(23, 1, 'down')
88+
end)
89+
90+
it("confines swap_up", function()
91+
h.assert_swap_confined_by_parent(10, 1, 'up')
92+
end)
93+
94+
it("confines swap_right", function()
95+
h.assert_swap_confined_by_parent(23, 1, 'right')
96+
end)
97+
98+
it("confines swap_left", function()
99+
h.assert_swap_confined_by_parent(10, 1, 'left')
100+
end)
85101
end)
86102
end)

tests/treewalker/go_spec.lua

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,21 @@ describe("Movement in Go file with tab indentation:", function()
6060
it("confines move_up", function()
6161
h.assert_confined_by_parent(16, 1, 'up')
6262
end)
63+
64+
it("confines swap_down", function()
65+
h.assert_swap_confined_by_parent(17, 1, 'down')
66+
end)
67+
68+
it("confines swap_up", function()
69+
h.assert_swap_confined_by_parent(16, 1, 'up')
70+
end)
71+
72+
it("confines swap_right", function()
73+
h.assert_swap_confined_by_parent(17, 1, 'right')
74+
end)
75+
76+
it("confines swap_left", function()
77+
h.assert_swap_confined_by_parent(16, 1, 'left')
78+
end)
6379
end)
6480
end)

tests/treewalker/haskell_spec.lua

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,21 @@ describe("In a haskell file: ", function()
4040
it("confines move_up", function()
4141
h.assert_confined_by_parent(11, 1, 'up')
4242
end)
43+
44+
it("confines swap_down", function()
45+
h.assert_swap_confined_by_parent(10, 1, 'down')
46+
end)
47+
48+
it("confines swap_up", function()
49+
h.assert_swap_confined_by_parent(11, 1, 'up')
50+
end)
51+
52+
it("confines swap_right", function()
53+
h.assert_swap_confined_by_parent(10, 1, 'right')
54+
end)
55+
56+
it("confines swap_left", function()
57+
h.assert_swap_confined_by_parent(11, 1, 'left')
58+
end)
4359
end)
4460
end)

tests/treewalker/helpers.lua

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,39 @@ function M.assert_confined_by_parent(row, col, direction)
157157
assert(vim.treesitter.is_ancestor(scope_parent, after_anchor))
158158
end
159159

160+
---
161+
--- Swap should also respect scope_confined: when swapping at the edge of the current
162+
--- scope, it should not swap with a node outside that scope.
163+
---
164+
---@param row integer
165+
---@param col integer
166+
---@param direction 'up'|'down'|'left'|'right'
167+
function M.assert_swap_confined_by_parent(row, col, direction)
168+
local nodes = require("treewalker.nodes")
169+
170+
vim.fn.cursor(row, col)
171+
172+
local before = nodes.get_highest_row_coincident(nodes.get_highest_node_at_current_row())
173+
local scope_parent = before:parent()
174+
175+
assert(scope_parent ~= nil, "test must start within a non-top-level scope")
176+
177+
local original = lines.get_lines(0, -1)
178+
179+
if direction == "up" then
180+
tw.swap_up()
181+
elseif direction == "down" then
182+
tw.swap_down()
183+
elseif direction == "left" then
184+
tw.swap_left()
185+
elseif direction == "right" then
186+
tw.swap_right()
187+
else
188+
error("invalid direction: " .. tostring(direction))
189+
end
190+
191+
-- Should not have swapped across the scope boundary.
192+
assert.same(original, lines.get_lines(0, -1))
193+
end
194+
160195
return M

tests/treewalker/html_spec.lua

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,21 @@ describe("In an html file", function()
6565
it("confines move_up", function()
6666
h.assert_confined_by_parent(15, 1, 'up')
6767
end)
68+
69+
it("confines swap_down", function()
70+
h.assert_swap_confined_by_parent(15, 1, 'down')
71+
end)
72+
73+
it("confines swap_up", function()
74+
h.assert_swap_confined_by_parent(15, 1, 'up')
75+
end)
76+
77+
it("confines swap_right", function()
78+
h.assert_swap_confined_by_parent(15, 1, 'right')
79+
end)
80+
81+
it("confines swap_left", function()
82+
h.assert_swap_confined_by_parent(15, 1, 'left')
83+
end)
6884
end)
6985
end)

tests/treewalker/java_spec.lua

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,5 +154,21 @@ describe("In a java file:", function()
154154
it("confines move_up", function()
155155
h.assert_confined_by_parent(29, 1, 'up')
156156
end)
157+
158+
it("confines swap_down", function()
159+
h.assert_swap_confined_by_parent(24, 1, 'down')
160+
end)
161+
162+
it("confines swap_up", function()
163+
h.assert_swap_confined_by_parent(29, 1, 'up')
164+
end)
165+
166+
it("confines swap_right", function()
167+
h.assert_swap_confined_by_parent(24, 1, 'right')
168+
end)
169+
170+
it("confines swap_left", function()
171+
h.assert_swap_confined_by_parent(29, 1, 'left')
172+
end)
157173
end)
158174
end)

tests/treewalker/javascript_spec.lua

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,21 @@ describe("Swapping in a typescript file:", function()
2828
it("confines move_up", function()
2929
h.assert_confined_by_parent(7, 1, 'up')
3030
end)
31+
32+
it("confines swap_down", function()
33+
h.assert_swap_confined_by_parent(5, 3, 'down')
34+
end)
35+
36+
it("confines swap_up", function()
37+
h.assert_swap_confined_by_parent(7, 1, 'up')
38+
end)
39+
40+
it("confines swap_right", function()
41+
h.assert_swap_confined_by_parent(5, 3, 'right')
42+
end)
43+
44+
it("confines swap_left", function()
45+
h.assert_swap_confined_by_parent(7, 1, 'left')
46+
end)
3147
end)
3248
end)

tests/treewalker/markdown_spec.lua

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,5 +382,21 @@ describe("Swapping in a markdown file with h2s at the top:", function()
382382
it("confines move_up", function()
383383
h.assert_confined_by_parent(19, 1, 'up')
384384
end)
385+
386+
it("confines swap_down", function()
387+
h.assert_swap_confined_by_parent(33, 1, 'down')
388+
end)
389+
390+
it("confines swap_up", function()
391+
h.assert_swap_confined_by_parent(19, 1, 'up')
392+
end)
393+
394+
it("confines swap_right", function()
395+
h.assert_swap_confined_by_parent(33, 1, 'right')
396+
end)
397+
398+
it("confines swap_left", function()
399+
h.assert_swap_confined_by_parent(19, 1, 'left')
400+
end)
385401
end)
386402
end)

0 commit comments

Comments
 (0)