@@ -1174,6 +1174,98 @@ local function move_focus(direction)
11741174 end
11751175end
11761176
1177+ --- Swap the focused pane with a sibling pane in the given direction
1178+ --- @param direction " left" | " right" | " up" | " down"
1179+ local function swap_pane (direction )
1180+ local root = get_active_root ()
1181+ if not state .focused_id or not root then
1182+ return
1183+ end
1184+
1185+ local path = find_node_path (root , state .focused_id )
1186+ if not path then
1187+ return
1188+ end
1189+
1190+ -- "left"/"right" implies moving along "row"
1191+ -- "up"/"down" implies moving along "col"
1192+ local target_split_type = (direction == " left" or direction == " right" ) and " row" or " col"
1193+ local forward = (direction == " right" or direction == " down" )
1194+
1195+ local sibling_node = nil
1196+
1197+ -- Traverse up the path to find a split of the correct type where we can swap
1198+ for i = # path - 1 , 1 , - 1 do
1199+ local node = path [i ]
1200+ local child = path [i + 1 ]
1201+
1202+ if node .type == " split" and node .direction == target_split_type then
1203+ -- Find index of child
1204+ local idx = 0
1205+ for k , c in ipairs (node .children ) do
1206+ if c == child then
1207+ idx = k
1208+ break
1209+ end
1210+ end
1211+
1212+ if forward then
1213+ if idx < # node .children then
1214+ sibling_node = node .children [idx + 1 ]
1215+ break
1216+ end
1217+ else
1218+ if idx > 1 then
1219+ sibling_node = node .children [idx - 1 ]
1220+ break
1221+ end
1222+ end
1223+ end
1224+ end
1225+
1226+ if sibling_node then
1227+ -- Found a sibling tree/pane. Find the closest leaf.
1228+ local target_leaf
1229+ if forward then
1230+ target_leaf = get_first_leaf (sibling_node )
1231+ else
1232+ target_leaf = get_last_leaf (sibling_node )
1233+ end
1234+
1235+ if target_leaf and target_leaf .id ~= state .focused_id then
1236+ -- Find both panes
1237+ local focused_path = find_node_path (root , state .focused_id )
1238+ local target_path = find_node_path (root , target_leaf .id )
1239+
1240+ if focused_path and target_path then
1241+ local focused_pane = focused_path [# focused_path ]
1242+ local target_pane = target_path [# target_path ]
1243+
1244+ -- Unfocus the currently focused pane before swapping
1245+ if state .app_focused then
1246+ focused_pane .pty :set_focus (false )
1247+ end
1248+
1249+ -- Swap both the PTY references and the IDs so everything stays consistent
1250+ focused_pane .pty , target_pane .pty = target_pane .pty , focused_pane .pty
1251+ focused_pane .id , target_pane .id = target_pane .id , focused_pane .id
1252+
1253+ -- Focus stays on the same ID (which now moved to the target position)
1254+ -- state.focused_id doesn't need to change since we swapped the IDs
1255+
1256+ -- Focus the pane that now has our original ID (at target position)
1257+ if state .app_focused then
1258+ target_pane .pty :set_focus (true )
1259+ end
1260+
1261+ update_cached_git_branch ()
1262+ prise .request_frame ()
1263+ prise .save ()
1264+ end
1265+ end
1266+ end
1267+ end
1268+
11771269local function open_rename_tab ()
11781270 if not state .rename_tab .input then
11791271 state .rename_tab .input = prise .create_text_input ()
@@ -1733,7 +1825,19 @@ function M.update(event)
17331825 return
17341826 end
17351827
1736- if k == " h" then
1828+ if event .data .ctrl and k == " h" then
1829+ swap_pane (" left" )
1830+ handled = true
1831+ elseif event .data .ctrl and k == " l" then
1832+ swap_pane (" right" )
1833+ handled = true
1834+ elseif event .data .ctrl and k == " j" then
1835+ swap_pane (" down" )
1836+ handled = true
1837+ elseif event .data .ctrl and k == " k" then
1838+ swap_pane (" up" )
1839+ handled = true
1840+ elseif k == " h" then
17371841 move_focus (" left" )
17381842 handled = true
17391843 elseif k == " l" then
0 commit comments