-- Highlight on yank vim.api.nvim_create_autocmd("TextYankPost", { group = vim.api.nvim_create_augroup("highlight_yank", { clear = true }), callback = function() vim.hl.on_yank() end, }) -- Buffer tab navigation (scoped to current window) local function cycle_win_buf(dir) local bufs = _G.get_win_bufs() if #bufs <= 1 then return end local current = vim.api.nvim_get_current_buf() for i, buf in ipairs(bufs) do if buf == current then local next = bufs[((i - 1 + dir) % #bufs) + 1] vim.api.nvim_set_current_buf(next) return end 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", "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 vim.api.nvim_set_current_buf(buf) switched = true break end end if not switched then vim.cmd("enew") vim.bo.bufhidden = "wipe" end vim.cmd("bdelete " .. current) 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" }) -- 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)" }) -- Floating popup terminal with tabs (toggle with ) local popup_term = { bufs = {}, current = 1, win = nil } local function ensure_term_buf(idx) if not popup_term.bufs[idx] or not vim.api.nvim_buf_is_valid(popup_term.bufs[idx]) then popup_term.bufs[idx] = vim.api.nvim_create_buf(false, true) end return popup_term.bufs[idx] end local function term_title() local parts = {} for i = 1, #popup_term.bufs do if not vim.api.nvim_buf_is_valid(popup_term.bufs[i]) then break end if i == popup_term.current then parts[#parts + 1] = "[" .. i .. "]" else parts[#parts + 1] = " " .. i .. " " end end return " Terminal " .. table.concat(parts) .. " " end local function open_popup_win(buf) local width = math.floor(vim.o.columns * 0.8) local height = math.floor(vim.o.lines * 0.75) popup_term.win = vim.api.nvim_open_win(buf, true, { relative = "editor", width = width, height = height, col = math.floor((vim.o.columns - width) / 2), row = math.floor((vim.o.lines - height) / 2) - 1, style = "minimal", border = "rounded", title = term_title(), title_pos = "center", }) end local function update_title() if popup_term.win and vim.api.nvim_win_is_valid(popup_term.win) then vim.api.nvim_win_set_config(popup_term.win, { title = term_title() }) end end local function switch_term(idx) if idx < 1 or idx > #popup_term.bufs then return end popup_term.current = idx local buf = ensure_term_buf(idx) if popup_term.win and vim.api.nvim_win_is_valid(popup_term.win) then vim.api.nvim_win_set_buf(popup_term.win, buf) update_title() if vim.bo[buf].buftype == "terminal" then vim.cmd.startinsert() end end end local function toggle_popup_term() if popup_term.win and vim.api.nvim_win_is_valid(popup_term.win) then vim.api.nvim_win_hide(popup_term.win) popup_term.win = nil return end local buf = ensure_term_buf(popup_term.current) open_popup_win(buf) if vim.bo[buf].buftype ~= "terminal" then vim.cmd.terminal() end vim.cmd.startinsert() end local function new_term_tab() local idx = #popup_term.bufs + 1 ensure_term_buf(idx) popup_term.current = idx if popup_term.win and vim.api.nvim_win_is_valid(popup_term.win) then vim.api.nvim_win_set_buf(popup_term.win, popup_term.bufs[idx]) update_title() vim.cmd.terminal() vim.cmd.startinsert() end end local function close_term_tab() if #popup_term.bufs <= 1 then return end local buf = popup_term.bufs[popup_term.current] table.remove(popup_term.bufs, popup_term.current) if popup_term.current > #popup_term.bufs then popup_term.current = #popup_term.bufs end switch_term(popup_term.current) if vim.api.nvim_buf_is_valid(buf) then vim.api.nvim_buf_delete(buf, { force = true }) 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" }) -- Terminal copy/paste vim.keymap.set("t", "", function() local reg = vim.fn.getreg("+") if reg ~= "" then vim.api.nvim_paste(reg, true, -1) end end, { desc = "Paste from clipboard" }) vim.keymap.set("t", "", [[V]], { desc = "Select current line (normal mode)" }) vim.keymap.set("t", "", [[]], { desc = "Exit to normal mode" }) -- Save/restore layout with :mksession vim.opt.sessionoptions = "buffers,curdir,folds,winpos,winsize,terminal" vim.keymap.set("n", "ss", ":mksession! ~/.config/nvim/session.vim", { desc = "Save session/layout" }) vim.keymap.set("n", "sl", ":source ~/.config/nvim/session.vim", { desc = "Load session/layout" }) -- Trim trailing whitespace on save vim.api.nvim_create_autocmd("BufWritePre", { group = vim.api.nvim_create_augroup("trim_whitespace", { clear = true }), pattern = "*", callback = function() local pos = vim.api.nvim_win_get_cursor(0) vim.cmd([[%s/\s\+$//e]]) vim.api.nvim_win_set_cursor(0, pos) end, })