Skip to content

Commit 4350921

Browse files
committed
fix(swap): Properly restore cursor and window view when swapping bufs
1 parent 97bab77 commit 4350921

2 files changed

Lines changed: 103 additions & 17 deletions

File tree

lua/smart-splits/swap.lua

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,27 +37,31 @@ function M.swap_bufs(direction, opts)
3737
local win_2 = vim.api.nvim_get_current_win()
3838
local view_2 = vim.fn.winsaveview()
3939

40-
if buf_1 == buf_2 then
41-
-- same buffer in both windows, so there is nothing to swap but the cursor and
42-
-- scroll position; folds have to come off first or restoring the view moves
43-
-- the cursor somewhere else
44-
local folds_1 = vim.api.nvim_get_option_value('foldenable', { win = win_1 })
45-
local folds_2 = vim.api.nvim_get_option_value('foldenable', { win = win_2 })
46-
vim.api.nvim_set_option_value('foldenable', false, { win = win_1 })
47-
vim.api.nvim_set_option_value('foldenable', false, { win = win_2 })
48-
49-
vim.api.nvim_set_current_win(win_1)
50-
vim.fn.winrestview(view_2)
51-
vim.api.nvim_set_current_win(win_2)
52-
vim.fn.winrestview(view_1)
53-
54-
vim.api.nvim_set_option_value('foldenable', folds_1, { win = win_1 })
55-
vim.api.nvim_set_option_value('foldenable', folds_2, { win = win_2 })
56-
else
40+
-- NB: if the buffers are the same,
41+
-- we don't need to swap them, but we
42+
-- do still want to swap the view/cursor/etc.
43+
-- below; do not early return inside this
44+
-- if statement
45+
if buf_1 ~= buf_2 then
5746
vim.api.nvim_win_set_buf(win_2, buf_1)
5847
vim.api.nvim_win_set_buf(win_1, buf_2)
5948
end
6049

50+
-- restore scroll positions; folds have to come off first or restoring
51+
-- the view moves the cursor somewhere else
52+
local folds_1 = vim.api.nvim_get_option_value('foldenable', { win = win_1 })
53+
local folds_2 = vim.api.nvim_get_option_value('foldenable', { win = win_2 })
54+
vim.api.nvim_set_option_value('foldenable', false, { win = win_1 })
55+
vim.api.nvim_set_option_value('foldenable', false, { win = win_2 })
56+
57+
vim.api.nvim_set_current_win(win_1)
58+
vim.fn.winrestview(view_2)
59+
vim.api.nvim_set_current_win(win_2)
60+
vim.fn.winrestview(view_1)
61+
62+
vim.api.nvim_set_option_value('foldenable', folds_1, { win = win_1 })
63+
vim.api.nvim_set_option_value('foldenable', folds_2, { win = win_2 })
64+
6165
local move_cursor = opts.move_cursor
6266
if move_cursor == nil then
6367
move_cursor = require('smart-splits.config').swap.move_cursor

tests/test_swap_spec.lua

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,88 @@ describe('swap_buf', function()
6060
ss.swap_buf_right({ move_cursor = false })
6161
assert.equals(wins[1], helpers.curwin())
6262
end)
63+
64+
it('preserves scroll position when move_cursor is true', function()
65+
ss.setup({ swap = { move_cursor = true } })
66+
local wins = helpers.create_vsplits(2)
67+
helpers.unique_buffers(wins)
68+
69+
-- Fill buffers with enough lines to scroll
70+
local lines = {}
71+
for i = 1, 100 do
72+
table.insert(lines, 'Line ' .. i)
73+
end
74+
vim.api.nvim_buf_set_lines(vim.api.nvim_win_get_buf(wins[1]), 0, -1, false, lines)
75+
vim.api.nvim_buf_set_lines(vim.api.nvim_win_get_buf(wins[2]), 0, -1, false, lines)
76+
77+
-- Scroll each window to different positions
78+
helpers.focus(wins[1])
79+
vim.api.nvim_win_set_cursor(wins[1], { 50, 0 })
80+
vim.cmd('normal! zz')
81+
local view_1_before = vim.fn.winsaveview()
82+
83+
helpers.focus(wins[2])
84+
vim.api.nvim_win_set_cursor(wins[2], { 75, 0 })
85+
vim.cmd('normal! zz')
86+
local view_2_before = vim.fn.winsaveview()
87+
88+
-- Swap with move_cursor
89+
helpers.focus(wins[1])
90+
ss.swap_buf_right()
91+
92+
-- Cursor should be in win_2 (following buf_1)
93+
assert.equals(wins[2], helpers.curwin())
94+
95+
-- Scroll position should be preserved
96+
local view_1_after = vim.fn.winsaveview()
97+
assert.equals(view_1_before.topline, view_1_after.topline)
98+
assert.equals(view_1_before.lnum, view_1_after.lnum)
99+
100+
-- Check win_1 as well
101+
helpers.focus(wins[1])
102+
local view_2_after = vim.fn.winsaveview()
103+
assert.equals(view_2_before.topline, view_2_after.topline)
104+
assert.equals(view_2_before.lnum, view_2_after.lnum)
105+
end)
106+
107+
it('preserves scroll position when move_cursor is false', function()
108+
-- default: move_cursor = false
109+
local wins = helpers.create_vsplits(2)
110+
helpers.unique_buffers(wins)
111+
112+
local lines = {}
113+
for i = 1, 100 do
114+
table.insert(lines, 'Line ' .. i)
115+
end
116+
vim.api.nvim_buf_set_lines(vim.api.nvim_win_get_buf(wins[1]), 0, -1, false, lines)
117+
vim.api.nvim_buf_set_lines(vim.api.nvim_win_get_buf(wins[2]), 0, -1, false, lines)
118+
119+
helpers.focus(wins[1])
120+
vim.api.nvim_win_set_cursor(wins[1], { 50, 0 })
121+
vim.cmd('normal! zz')
122+
local view_1_before = vim.fn.winsaveview()
123+
124+
helpers.focus(wins[2])
125+
vim.api.nvim_win_set_cursor(wins[2], { 75, 0 })
126+
vim.cmd('normal! zz')
127+
local view_2_before = vim.fn.winsaveview()
128+
129+
-- Swap without move_cursor (default)
130+
helpers.focus(wins[1])
131+
ss.swap_buf_right()
132+
133+
-- Cursor should stay in win_1
134+
assert.equals(wins[1], helpers.curwin())
135+
136+
-- win_1 now has buf_2, so it should have view_2's scroll position
137+
local view_1_after = vim.fn.winsaveview()
138+
assert.equals(view_2_before.topline, view_1_after.topline)
139+
140+
-- win_2 now has buf_1, so it should have view_1's scroll position
141+
helpers.focus(wins[2])
142+
local view_2_after = vim.fn.winsaveview()
143+
assert.equals(view_1_before.topline, view_2_after.topline)
144+
end)
63145
end)
64146

65147
describe('vertical swaps', function()

0 commit comments

Comments
 (0)