chore: update Keybinds.md more navigation fixes (primarly disabling mouse when terminal is open) rm session
258 lines
9.2 KiB
Lua
258 lines
9.2 KiB
Lua
-- 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 (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()
|
|
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
|
|
if #bufs > 0 then vim.api.nvim_set_current_buf(bufs[1]) end
|
|
end
|
|
vim.keymap.set("n", "<Tab>", function() cycle_buf(1) end, { desc = "Next buffer tab" })
|
|
vim.keymap.set("n", "<S-Tab>", function() cycle_buf(-1) end, { desc = "Previous buffer tab" })
|
|
vim.keymap.set("n", "<leader>x", function()
|
|
local bufs = _G.get_win_bufs()
|
|
local current = vim.api.nvim_get_current_buf()
|
|
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" })
|
|
|
|
|
|
-- 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
|
|
|
|
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 <C-t>)
|
|
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
|
|
|
|
ensure_term_buf(1)
|
|
|
|
vim.keymap.set("n", "<C-t>", toggle_popup_term, { desc = "Toggle popup terminal" })
|
|
vim.keymap.set("t", "<C-t>", toggle_popup_term, { desc = "Toggle popup terminal" })
|
|
vim.keymap.set("t", "<C-n>", new_term_tab, { desc = "New terminal tab" })
|
|
vim.keymap.set("t", "<C-w>", 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", "<A-h>", function() wrap_move("h") end, { desc = "Move to left pane (wrap)" })
|
|
vim.keymap.set("n", "<A-l>", function() wrap_move("l") end, { desc = "Move to right pane (wrap)" })
|
|
vim.keymap.set("n", "<A-j>", function() wrap_move("j") end, { desc = "Move to pane below (wrap)" })
|
|
vim.keymap.set("n", "<A-k>", function() wrap_move("k") end, { desc = "Move to pane above (wrap)" })
|
|
-- Alt+h/l in terminal: only switch terminal tabs in popup, no pane navigation
|
|
vim.keymap.set("t", "<A-h>", 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)
|
|
end
|
|
end, { desc = "Prev term tab" })
|
|
vim.keymap.set("t", "<A-l>", 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)
|
|
end
|
|
end, { desc = "Next term tab" })
|
|
-- Block pane navigation in terminal mode
|
|
vim.keymap.set("t", "<A-j>", "<Nop>")
|
|
vim.keymap.set("t", "<A-k>", "<Nop>")
|
|
|
|
-- Disable mouse when terminal is focused, restore on leave
|
|
vim.api.nvim_create_autocmd("TermEnter", {
|
|
group = vim.api.nvim_create_augroup("term_no_mouse", { clear = true }),
|
|
callback = function() vim.o.mouse = "" end,
|
|
})
|
|
vim.api.nvim_create_autocmd("TermLeave", {
|
|
group = vim.api.nvim_create_augroup("term_restore_mouse", { clear = true }),
|
|
callback = function() vim.o.mouse = "a" end,
|
|
})
|
|
|
|
-- Terminal copy/paste
|
|
vim.keymap.set("t", "<C-v>", 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", "<C-y>", [[<C-\><C-n>V]], { desc = "Select current line (normal mode)" })
|
|
|
|
-- Auto-enter insert mode when switching to a terminal window
|
|
-- Uses WinEnter only (not BufEnter) so :wq from nested editors (e.g. git commit) works
|
|
vim.api.nvim_create_autocmd("WinEnter", {
|
|
group = vim.api.nvim_create_augroup("term_auto_insert", { clear = true }),
|
|
callback = function()
|
|
if vim.bo.buftype == "terminal" and vim.fn.mode() ~= "t" then
|
|
vim.cmd.startinsert()
|
|
end
|
|
end,
|
|
})
|
|
|
|
-- Save/restore layout with :mksession
|
|
vim.opt.sessionoptions = "buffers,curdir,folds,winpos,winsize,terminal"
|
|
vim.keymap.set("n", "<leader>ss", ":mksession! ~/.config/nvim/session.vim<CR>", { desc = "Save session/layout" })
|
|
vim.keymap.set("n", "<leader>sl", ":source ~/.config/nvim/session.vim<CR>", { 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,
|
|
})
|