diff --git a/lua/core/autocmds.lua b/lua/core/autocmds.lua index ffad7ac..d6b16b8 100644 --- a/lua/core/autocmds.lua +++ b/lua/core/autocmds.lua @@ -6,8 +6,8 @@ vim.api.nvim_create_autocmd("TextYankPost", { end, }) --- Buffer tab navigation (scoped to current window) -local function cycle_win_buf(dir) +-- Buffer tab navigation (per-window, exclusive) +local function cycle_buf(dir) local bufs = _G.get_win_bufs() if #bufs <= 1 then return end local current = vim.api.nvim_get_current_buf() @@ -18,13 +18,13 @@ local function cycle_win_buf(dir) return end end + if #bufs > 0 then vim.api.nvim_set_current_buf(bufs[1]) end end -vim.keymap.set("n", "", function() cycle_win_buf(1) end, { desc = "Next buffer tab" }) -vim.keymap.set("n", "", function() cycle_win_buf(-1) end, { desc = "Previous buffer tab" }) +vim.keymap.set("n", "", function() cycle_buf(1) end, { desc = "Next buffer tab" }) +vim.keymap.set("n", "", function() cycle_buf(-1) end, { desc = "Previous buffer tab" }) vim.keymap.set("n", "x", function() local bufs = _G.get_win_bufs() local current = vim.api.nvim_get_current_buf() - -- Switch to another buffer in this window, or create a blank one local switched = false for _, buf in ipairs(bufs) do if buf ~= current then @@ -41,29 +41,40 @@ vim.keymap.set("n", "x", function() end, { desc = "Close buffer tab" }) --- Splits duplicate current file (default Vim behavior) -vim.keymap.set("n", "v", ":vsplit", { desc = "Vertical split" }) -vim.keymap.set("n", "s", ":split", { desc = "Horizontal split" }) +-- Prevent splitting from oil — redirect to last code window +vim.api.nvim_create_autocmd("WinNew", { + group = vim.api.nvim_create_augroup("no_split_oil", { clear = true }), + callback = function() + -- Skip floating windows (like popup terminal) + local new_win = vim.api.nvim_get_current_win() + if vim.api.nvim_win_get_config(new_win).relative ~= "" then return end --- Move between panes with Alt+h/l, wraps around (works in normal and terminal mode) -local function wrap_move(dir) - local cur = vim.api.nvim_get_current_win() - vim.cmd("wincmd " .. dir) - if vim.api.nvim_get_current_win() == cur then - -- Didn't move, wrap around - local opposite = ({ h = "l", l = "h", j = "k", k = "j" })[dir] - -- Go to the far opposite end - vim.cmd("99wincmd " .. opposite) - end -end -vim.keymap.set("n", "", function() wrap_move("h") end, { desc = "Move to left pane (wrap)" }) -vim.keymap.set("n", "", function() wrap_move("l") end, { desc = "Move to right pane (wrap)" }) -vim.keymap.set("n", "", function() wrap_move("j") end, { desc = "Move to pane below (wrap)" }) -vim.keymap.set("n", "", function() wrap_move("k") end, { desc = "Move to pane above (wrap)" }) -vim.keymap.set("t", "", function() vim.cmd("stopinsert") wrap_move("h") end, { desc = "Move to left pane (wrap)" }) -vim.keymap.set("t", "", function() vim.cmd("stopinsert") wrap_move("l") end, { desc = "Move to right pane (wrap)" }) -vim.keymap.set("t", "", function() vim.cmd("stopinsert") wrap_move("j") end, { desc = "Move to pane below (wrap)" }) -vim.keymap.set("t", "", function() vim.cmd("stopinsert") wrap_move("k") end, { desc = "Move to pane above (wrap)" }) + local prev_win = vim.fn.win_getid(vim.fn.winnr("#")) + if not vim.api.nvim_win_is_valid(prev_win) then return end + local prev_buf = vim.api.nvim_win_get_buf(prev_win) + if vim.bo[prev_buf].filetype ~= "oil" then return end + + -- A split was created from oil — close it and redo from code window + local new_buf = vim.api.nvim_get_current_buf() + vim.api.nvim_win_close(new_win, true) + + local target = _G.last_code_win + if not target or not vim.api.nvim_win_is_valid(target) then + -- Find any non-oil window + for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do + local buf = vim.api.nvim_win_get_buf(win) + if vim.bo[buf].filetype ~= "oil" and vim.api.nvim_win_get_config(win).relative == "" then + target = win + break + end + end + end + if target and vim.api.nvim_win_is_valid(target) then + vim.api.nvim_set_current_win(target) + vim.cmd("vsplit") + end + end, +}) -- Floating popup terminal with tabs (toggle with ) local popup_term = { bufs = {}, current = 1, win = nil } @@ -164,16 +175,50 @@ local function close_term_tab() end end --- Init first tab ensure_term_buf(1) vim.keymap.set("n", "", toggle_popup_term, { desc = "Toggle popup terminal" }) vim.keymap.set("t", "", toggle_popup_term, { desc = "Toggle popup terminal" }) -vim.keymap.set("t", "", function() switch_term(popup_term.current % #popup_term.bufs + 1) end, { desc = "Next terminal tab" }) -vim.keymap.set("t", "", function() switch_term((popup_term.current - 2) % #popup_term.bufs + 1) end, { desc = "Prev terminal tab" }) vim.keymap.set("t", "", new_term_tab, { desc = "New terminal tab" }) vim.keymap.set("t", "", close_term_tab, { desc = "Close terminal tab" }) +-- Move between panes with Alt+h/l, wraps around (works in normal and terminal mode) +local function wrap_move(dir) + local cur = vim.api.nvim_get_current_win() + vim.cmd("wincmd " .. dir) + if vim.api.nvim_get_current_win() == cur then + local opposite = ({ h = "l", l = "h", j = "k", k = "j" })[dir] + vim.cmd("99wincmd " .. opposite) + end +end +vim.keymap.set("n", "", function() wrap_move("h") end, { desc = "Move to left pane (wrap)" }) +vim.keymap.set("n", "", function() wrap_move("l") end, { desc = "Move to right pane (wrap)" }) +vim.keymap.set("n", "", function() wrap_move("j") end, { desc = "Move to pane below (wrap)" }) +vim.keymap.set("n", "", function() wrap_move("k") end, { desc = "Move to pane above (wrap)" }) +-- Alt+h/l in terminal: switch terminal tabs if popup is open, otherwise move panes +vim.keymap.set("t", "", function() + if popup_term.win and vim.api.nvim_win_is_valid(popup_term.win) then + switch_term((popup_term.current - 2) % #popup_term.bufs + 1) + else + vim.cmd("stopinsert") wrap_move("h") + end +end, { desc = "Prev term tab / left pane" }) +vim.keymap.set("t", "", function() + if popup_term.win and vim.api.nvim_win_is_valid(popup_term.win) then + switch_term(popup_term.current % #popup_term.bufs + 1) + else + vim.cmd("stopinsert") wrap_move("l") + end +end, { desc = "Next term tab / right pane" }) +vim.keymap.set("t", "", function() + if popup_term.win and vim.api.nvim_win_is_valid(popup_term.win) then return end + vim.cmd("stopinsert") wrap_move("j") +end, { desc = "Move to pane below (wrap)" }) +vim.keymap.set("t", "", function() + if popup_term.win and vim.api.nvim_win_is_valid(popup_term.win) then return end + vim.cmd("stopinsert") wrap_move("k") +end, { desc = "Move to pane above (wrap)" }) + -- Terminal copy/paste vim.keymap.set("t", "", function() local reg = vim.fn.getreg("+") diff --git a/lua/core/options.lua b/lua/core/options.lua index 6ded463..b47c5de 100644 --- a/lua/core/options.lua +++ b/lua/core/options.lua @@ -28,24 +28,42 @@ opt.splitright = true opt.equalalways = false opt.cursorline = true --- Per-window buffer tabs (winbar) --- Track which buffers were opened in each window +-- Per-window buffer tabs (exclusive: each file belongs to one split only) _G.win_bufs = {} +-- Remove a buffer from all windows' tracking +function _G.untrack_buf_everywhere(buf) + for win, bufs in pairs(_G.win_bufs) do + for i, b in ipairs(bufs) do + if b == buf then + table.remove(bufs, i) + break + end + end + end +end + function _G.track_buf() local win = vim.api.nvim_get_current_win() local buf = vim.api.nvim_get_current_buf() - if not vim.bo[buf].buflisted or vim.bo[buf].filetype == "oil" then return end - if not _G.win_bufs[win] then _G.win_bufs[win] = {} end - -- Remove if already tracked, then add to end - for i, b in ipairs(_G.win_bufs[win]) do - if b == buf then table.remove(_G.win_bufs[win], i) break end + if not vim.bo[buf].buflisted + or vim.bo[buf].filetype == "oil" + or vim.bo[buf].buftype == "terminal" + or vim.api.nvim_buf_get_name(buf) == "" then + return end + if not _G.win_bufs[win] then _G.win_bufs[win] = {} end + -- Already tracked in this window, skip + for _, b in ipairs(_G.win_bufs[win]) do + if b == buf then return end + end + -- Remove from any other window (exclusive ownership) + _G.untrack_buf_everywhere(buf) table.insert(_G.win_bufs[win], buf) end -function _G.get_win_bufs() - local win = vim.api.nvim_get_current_win() +function _G.get_win_bufs(win) + win = win or vim.api.nvim_get_current_win() local bufs = {} for _, buf in ipairs(_G.win_bufs[win] or {}) do if vim.api.nvim_buf_is_valid(buf) and vim.bo[buf].buflisted then @@ -57,13 +75,13 @@ function _G.get_win_bufs() end function _G.winbar_tabs() - local bufs = _G.get_win_bufs() + local win = vim.api.nvim_get_current_win() + local bufs = _G.get_win_bufs(win) if #bufs == 0 then return "" end local current = vim.api.nvim_get_current_buf() local parts = {} for _, buf in ipairs(bufs) do local name = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(buf), ":t") - if name == "" then name = "[No Name]" end local mod = vim.bo[buf].modified and " +" or "" if buf == current then table.insert(parts, "%#TabLineSel# " .. name .. mod .. " %#WinBar#") @@ -79,6 +97,15 @@ vim.api.nvim_create_autocmd("BufEnter", { callback = function() _G.track_buf() end, }) +-- Clean up tracking when a window closes +vim.api.nvim_create_autocmd("WinClosed", { + group = vim.api.nvim_create_augroup("clean_win_bufs", { clear = true }), + callback = function(ev) + local win = tonumber(ev.match) + if win then _G.win_bufs[win] = nil end + end, +}) + opt.winbar = "%{%v:lua.winbar_tabs()%}" -- Clipboard (system) diff --git a/lua/plugins/oil.lua b/lua/plugins/oil.lua index 9ef6f38..13d6634 100644 --- a/lua/plugins/oil.lua +++ b/lua/plugins/oil.lua @@ -66,6 +66,22 @@ vim.api.nvim_create_autocmd("FileType", { local dir = oil.get_current_dir() if not dir then return end local filepath = dir .. entry.name + local abs_path = vim.fn.fnamemodify(filepath, ":p") + + -- Check if file is already tracked in any window's tab list + for win, bufs in pairs(_G.win_bufs or {}) do + if vim.api.nvim_win_is_valid(win) then + for _, buf in ipairs(bufs) do + if vim.api.nvim_buf_is_valid(buf) + and vim.fn.fnamemodify(vim.api.nvim_buf_get_name(buf), ":p") == abs_path then + vim.api.nvim_set_current_win(win) + vim.api.nvim_set_current_buf(buf) + return + end + end + end + end + local target_win = nil -- Try last focused code window @@ -92,7 +108,6 @@ vim.api.nvim_create_autocmd("FileType", { if target_win then vim.api.nvim_set_current_win(target_win) - -- Remember blank buffer to clean up after opening local old_buf = vim.api.nvim_get_current_buf() local is_blank = vim.api.nvim_buf_get_name(old_buf) == "" and not vim.bo[old_buf].modified @@ -100,7 +115,16 @@ vim.api.nvim_create_autocmd("FileType", { and vim.api.nvim_buf_get_lines(old_buf, 0, 1, false)[1] == "" vim.cmd.edit(vim.fn.fnameescape(filepath)) if is_blank and old_buf ~= vim.api.nvim_get_current_buf() then - vim.api.nvim_buf_delete(old_buf, { force = true }) + local in_use = false + for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do + if vim.api.nvim_win_get_buf(win) == old_buf then + in_use = true + break + end + end + if not in_use then + vim.api.nvim_buf_delete(old_buf, { force = true }) + end end else -- No code window exists: create a split to the right