Skip to content

Commit b646e89

Browse files
authored
feat: add swap windows feature (#130)
* feat: add swap windows feature * refactor: remove utils
1 parent 4368d28 commit b646e89

File tree

13 files changed

+339
-198
lines changed

13 files changed

+339
-198
lines changed

README.md

+38
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ The following defaults are given:
109109

110110
-- sets resize steps for y axis
111111
resize_step_y = 1,
112+
},
113+
swap = {
114+
-- cycles to opposite pane while navigating into the border
115+
cycle_navigation = false,
116+
117+
-- enables default keybindings (C-A-hjkl) for normal mode
118+
enable_default_keybindings = true,
112119
}
113120
}
114121
```
@@ -207,6 +214,37 @@ To run custom bindings in nvim, make sure to not set `enable_default_keybindings
207214
}
208215
```
209216
217+
### swap
218+
219+
Add the following bindings to your `~/.tmux.conf`:
220+
221+
> It is important to note, that your bindings in nvim must match the defined bindings in tmux! Otherwise the pass through will not have the seamless effect!
222+
223+
```tmux
224+
is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'"
225+
226+
bind -n 'C-M-h' if-shell "$is_vim" 'send-keys C-M-h' 'swap-pane -s "{left-of}"'
227+
bind -n 'C-M-j' if-shell "$is_vim" 'send-keys C-M-j' 'swap-pane -s "{down-of}"'
228+
bind -n 'C-M-k' if-shell "$is_vim" 'send-keys C-M-k' 'swap-pane -s "{up-of}"'
229+
bind -n 'C-M-l' if-shell "$is_vim" 'send-keys C-M-l' 'swap-pane -s "{right-of}"'
230+
231+
bind-key -T copy-mode-vi C-M-h swap-pane -s "{left-of}"
232+
bind-key -T copy-mode-vi C-M-j swap-pane -s "{down-of}"
233+
bind-key -T copy-mode-vi C-M-k swap-pane -s "{up-of}"
234+
bind-key -T copy-mode-vi C-M-l swap-pane -s "{right-of}"
235+
```
236+
237+
To run custom bindings in nvim, make sure to not set `enable_default_keybindings` to `true`. The following functions are used to resize windows:
238+
239+
```lua
240+
{
241+
[[<cmd>lua require("tmux").swap_left()<cr>]],
242+
[[<cmd>lua require("tmux").swap_bottom()<cr>]],
243+
[[<cmd>lua require("tmux").swap_top()<cr>]],
244+
[[<cmd>lua require("tmux").swap_right()<cr>]],
245+
}
246+
```
247+
210248
## tpm
211249
212250
If you are using [tmux plugin manager](https://github.com/tmux-plugins/tpm), you can add the following plugin.

lua/tmux/configuration/options.lua

+7
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ local M = {
7777
-- sets resize steps for y axis
7878
resize_step_y = 1,
7979
},
80+
swap = {
81+
-- cycles to opposite pane while navigating into the border
82+
cycle_navigation = false,
83+
84+
-- enables default keybindings (C-M-hjkl) for normal mode
85+
enable_default_keybindings = false,
86+
},
8087
}
8188

8289
function M.set(options)

lua/tmux/init.lua

+12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ local copy = require("tmux.copy")
33
local log = require("tmux.log")
44
local navigation = require("tmux.navigation")
55
local resize = require("tmux.resize")
6+
local swap = require("tmux.swap")
67
local tmux = require("tmux.wrapper.tmux")
78

89
local options = {
@@ -15,6 +16,9 @@ local options = {
1516
resize = {
1617
enable_default_keybindings = true,
1718
},
19+
swap = {
20+
enable_default_keybindings = true,
21+
},
1822
}
1923

2024
local M = {
@@ -30,6 +34,11 @@ local M = {
3034
resize_bottom = resize.to_bottom,
3135
resize_top = resize.to_top,
3236
resize_right = resize.to_right,
37+
38+
swap_left = swap.to_left,
39+
swap_bottom = swap.to_bottom,
40+
swap_top = swap.to_top,
41+
swap_right = swap.to_right,
3342
}
3443

3544
function M.setup(options_, logging)
@@ -55,6 +64,9 @@ function M.setup(options_, logging)
5564

5665
log.debug("setup resize")
5766
resize.setup()
67+
68+
log.debug("setup swap")
69+
swap.setup()
5870
end
5971

6072
return M

lua/tmux/layout/init.lua

+33
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
local log = require("tmux.log")
22
local parse = require("tmux.layout.parse")
3+
local nvim = require("tmux.wrapper.nvim")
34
local tmux = require("tmux.wrapper.tmux")
45

56
local direction_checks = {
@@ -53,6 +54,17 @@ local function check_is_border(display, id, direction)
5354
return nil
5455
end
5556

57+
--return true if there is no other nvim window in the direction of @border
58+
local function is_only_window(border)
59+
if border == "h" or border == "l" then
60+
--check for other horizontal window
61+
return (nvim.winnr("1h") == nvim.winnr("1l"))
62+
else
63+
--check for other vertical window
64+
return (nvim.winnr("1j") == nvim.winnr("1k"))
65+
end
66+
end
67+
5668
local M = {}
5769

5870
function M.is_border(direction)
@@ -67,4 +79,25 @@ function M.is_border(direction)
6779
return result
6880
end
6981

82+
function M.is_tmux_target(border)
83+
if not tmux.is_tmux then
84+
return false
85+
end
86+
87+
return not M.is_border(border) or is_only_window(border)
88+
end
89+
90+
function M.has_tmux_target(direction, persist_zoom, cycle_navigation)
91+
if not tmux.is_tmux then
92+
return false
93+
end
94+
if tmux.is_zoomed() and persist_zoom then
95+
return false
96+
end
97+
if not M.is_border(direction) then
98+
return true
99+
end
100+
return cycle_navigation and not M.is_border(nvim.opposite_direction(direction))
101+
end
102+
70103
return M

lua/tmux/navigation/init.lua

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ local navigate = require("tmux.navigation.navigate")
33
local options = require("tmux.configuration.options")
44

55
local M = {}
6+
67
function M.setup()
78
if options.navigation.enable_default_keybindings then
89
keymaps.register("n", {

lua/tmux/navigation/navigate.lua

+4-21
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,18 @@ local nvim = require("tmux.wrapper.nvim")
44
local options = require("tmux.configuration.options")
55
local tmux = require("tmux.wrapper.tmux")
66

7-
local opposite_directions = {
8-
h = "l",
9-
j = "k",
10-
k = "j",
11-
l = "h",
12-
}
13-
147
local M = {}
15-
function M.has_tmux_target(direction)
16-
if not tmux.is_tmux then
17-
return false
18-
end
19-
if tmux.is_zoomed() and options.navigation.persist_zoom then
20-
return false
21-
end
22-
if not layout.is_border(direction) then
23-
return true
24-
end
25-
return options.navigation.cycle_navigation and not layout.is_border(opposite_directions[direction])
26-
end
278

289
function M.to(direction)
2910
log.debug("navigate_to: " .. direction)
3011

3112
local is_nvim_border = nvim.is_nvim_border(direction)
32-
if (nvim.is_nvim_float() or is_nvim_border) and M.has_tmux_target(direction) then
13+
local has_tmux_target =
14+
layout.has_tmux_target(direction, options.navigation.persist_zoom, options.navigation.cycle_navigation)
15+
if (nvim.is_nvim_float() or is_nvim_border) and has_tmux_target then
3316
tmux.change_pane(direction)
3417
elseif is_nvim_border and options.navigation.cycle_navigation then
35-
nvim.wincmd(opposite_directions[direction], 999)
18+
nvim.wincmd(nvim.opposite_direction(direction), 999)
3619
elseif not is_nvim_border then
3720
nvim.wincmd(direction, vim.v.count)
3821
end

lua/tmux/resize.lua

+5-23
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,8 @@ local nvim = require("tmux.wrapper.nvim")
44
local options = require("tmux.configuration.options")
55
local tmux = require("tmux.wrapper.tmux")
66

7-
--return true if there is no other nvim window in the direction of @border
8-
local function is_only_window(border)
9-
if border == "h" or border == "l" then
10-
--check for other horizontal window
11-
return (nvim.winnr("1h") == nvim.winnr("1l"))
12-
else
13-
--check for other vertical window
14-
return (nvim.winnr("1j") == nvim.winnr("1k"))
15-
end
16-
end
17-
18-
local function is_tmux_target(border)
19-
if not tmux.is_tmux then
20-
return false
21-
end
22-
23-
return not layout.is_border(border) or is_only_window(border)
24-
end
25-
267
local M = {}
8+
279
function M.setup()
2810
if options.resize.enable_default_keybindings then
2911
keymaps.register("n", {
@@ -38,7 +20,7 @@ end
3820
function M.to_left(step)
3921
step = step or options.resize.resize_step_x
4022
local is_border = nvim.is_nvim_border("l")
41-
if (nvim.is_nvim_float() or is_border) and is_tmux_target("l") then
23+
if (nvim.is_nvim_float() or is_border) and layout.is_tmux_target("l") then
4224
tmux.resize("h", step)
4325
elseif is_border then
4426
nvim.resize("x", "+", step)
@@ -50,7 +32,7 @@ end
5032
function M.to_bottom(step)
5133
step = step or options.resize.resize_step_y
5234
local is_border = nvim.is_nvim_border("j")
53-
if (nvim.is_nvim_float() or is_border) and is_tmux_target("j") then
35+
if (nvim.is_nvim_float() or is_border) and layout.is_tmux_target("j") then
5436
tmux.resize("j", step)
5537
elseif is_border then
5638
nvim.resize("y", "-", step)
@@ -62,7 +44,7 @@ end
6244
function M.to_top(step)
6345
step = step or options.resize.resize_step_y
6446
local is_border = nvim.is_nvim_border("j")
65-
if (nvim.is_nvim_float() or is_border) and is_tmux_target("j") then
47+
if (nvim.is_nvim_float() or is_border) and layout.is_tmux_target("j") then
6648
tmux.resize("k", step)
6749
elseif is_border then
6850
nvim.resize("y", "+", step)
@@ -74,7 +56,7 @@ end
7456
function M.to_right(step)
7557
step = step or options.resize.resize_step_x
7658
local is_border = nvim.is_nvim_border("l")
77-
if (nvim.is_nvim_float() or is_border) and is_tmux_target("l") then
59+
if (nvim.is_nvim_float() or is_border) and layout.is_tmux_target("l") then
7860
tmux.resize("l", step)
7961
elseif is_border then
8062
nvim.resize("x", "-", step)

lua/tmux/swap.lua

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
local keymaps = require("tmux.keymaps")
2+
local options = require("tmux.configuration.options")
3+
local layout = require("tmux.layout")
4+
local nvim = require("tmux.wrapper.nvim")
5+
local tmux = require("tmux.wrapper.tmux")
6+
7+
local M = {}
8+
9+
function M.setup()
10+
if options.swap.enable_default_keybindings then
11+
keymaps.register("n", {
12+
["<C-A-h>"] = [[<cmd>lua require'tmux'.swap_left()<cr>]],
13+
["<C-A-j>"] = [[<cmd>lua require'tmux'.swap_bottom()<cr>]],
14+
["<C-A-k>"] = [[<cmd>lua require'tmux'.swap_top()<cr>]],
15+
["<C-A-l>"] = [[<cmd>lua require'tmux'.swap_right()<cr>]],
16+
})
17+
end
18+
end
19+
20+
function M.to(direction)
21+
local is_nvim_border = nvim.is_nvim_border(direction)
22+
local persist_zoom = true -- tmux swap-pane when zoomed causes error
23+
local has_tmux_target = layout.has_tmux_target(direction, persist_zoom, options.swap.cycle_navigation)
24+
if (nvim.is_nvim_float() or is_nvim_border) and has_tmux_target then
25+
tmux.swap(direction)
26+
elseif is_nvim_border and options.swap.cycle_navigation then
27+
nvim.swap(nvim.opposite_direction(direction), 999)
28+
elseif not is_nvim_border then
29+
nvim.swap(direction, vim.v.count)
30+
end
31+
end
32+
33+
function M.to_left()
34+
M.to("h")
35+
end
36+
37+
function M.to_bottom()
38+
M.to("j")
39+
end
40+
41+
function M.to_top()
42+
M.to("k")
43+
end
44+
45+
function M.to_right()
46+
M.to("l")
47+
end
48+
49+
return M

lua/tmux/wrapper/nvim.lua

+18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
local opposite_directions = { h = "l", j = "k", k = "j", l = "h" }
2+
13
local M = {}
4+
25
function M.is_nvim_border(border)
36
return M.winnr() == M.winnr("1" .. border)
47
end
@@ -16,6 +19,17 @@ function M.resize(axis, direction, step_size)
1619
return vim.api.nvim_command(command .. direction .. step_size)
1720
end
1821

22+
function M.swap(direction, count)
23+
local current_win = vim.api.nvim_get_current_win()
24+
local current_buf = vim.api.nvim_win_get_buf(current_win)
25+
local target_winnr = M.winnr(count .. direction)
26+
local target_win = vim.api.nvim_call_function("win_getid", { target_winnr })
27+
local target_buf = vim.api.nvim_win_get_buf(target_win)
28+
vim.api.nvim_win_set_buf(current_win, target_buf)
29+
vim.api.nvim_win_set_buf(target_win, current_buf)
30+
M.wincmd(direction, count)
31+
end
32+
1933
function M.wincmd(direction, count)
2034
return vim.api.nvim_command((count or 1) .. "wincmd " .. direction)
2135
end
@@ -24,4 +38,8 @@ function M.winnr(direction)
2438
return vim.api.nvim_call_function("winnr", { direction })
2539
end
2640

41+
function M.opposite_direction(direction)
42+
return opposite_directions[direction]
43+
end
44+
2745
return M

lua/tmux/wrapper/tmux.lua

+6-6
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,8 @@ local vim = vim
22

33
local log = require("tmux.log")
44

5-
local tmux_directions = {
6-
h = "L",
7-
j = "D",
8-
k = "U",
9-
l = "R",
10-
}
5+
local tmux_directions = { h = "L", j = "D", k = "U", l = "R" }
6+
local tmux_swap_directions = { h = "left", j = "down", k = "up", l = "right" }
117

128
local function get_tmux()
139
return os.getenv("TMUX")
@@ -94,6 +90,10 @@ function M.resize(direction, step)
9490
execute(string.format("resize-pane -t '%s' -%s %d", get_tmux_pane(), tmux_directions[direction], step))
9591
end
9692

93+
function M.swap(direction)
94+
execute(string.format("swap-pane -t '%s' -s '{%s-of}'", get_tmux_pane(), tmux_swap_directions[direction]))
95+
end
96+
9797
function M.set_buffer(content, sync_clipboard)
9898
content = content:gsub("\\", "\\\\")
9999
content = content:gsub('"', '\\"')

0 commit comments

Comments
 (0)