You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

118 lines
3.8 KiB

-- Terminal helpers layered on top of the native LazyVim/Snacks terminal.
--
-- Native behaviour is kept intact and untouched:
-- <C-/> / <C-_> toggle a bottom terminal at the project root (supports a
-- count, e.g. 2<C-/> opens a *separate* terminal #2)
-- <leader>fT terminal in the current working directory
--
-- This module adds:
-- <Tab> a *dedicated* floating terminal (stable toggle, one-key close)
-- <leader>ft a picker that lists every live terminal so any contextual
-- terminal can be focused or restored even after its window was
-- closed/hidden.
local M = {}
-- The <Tab> floating terminal. Tracked as an upvalue so <Tab> is a real
-- open/close toggle regardless of which directory the editor is in.
---@type snacks.win?
local float
--- Open or focus the <Tab> floating terminal, or hide it if already focused.
function M.toggle_float()
-- Reuse the existing instance: hide if visible, re-show if hidden.
if float and float:buf_valid() then
if float:win_valid() then
float:hide()
else
float:show()
vim.cmd("startinsert")
end
return
end
-- First open: a high fixed count guarantees this terminal never collides with
-- the count-keyed bottom terminals (1, 2, 3 ...), so it is always the *same*
-- float for <Tab> no matter the context.
float = Snacks.terminal(nil, {
count = 99,
win = { position = "float", border = "rounded" },
})
-- Close it with a single keystroke from inside the shell: <Tab> hides the
-- window (buffer-local, so it only affects this floating terminal and never
-- the bottom split, where shell tab-completion must keep working).
if float and float.buf then
vim.keymap.set("t", "<Tab>", function()
if float and float:win_valid() then
float:hide()
end
end, { buffer = float.buf, silent = true, desc = "Hide floating terminal" })
end
end
--- List every live Snacks terminal in a picker. Restore/focus one with <CR>,
--- kill one with <d>.
function M.picker()
local terms = Snacks.terminal.list()
if #terms == 0 then
Snacks.notify.info("No open terminals")
return
end
local items = {} ---@type snacks.picker.finder.Item[]
for _, term in ipairs(terms) do
local meta = vim.b[term.buf] and vim.b[term.buf].snacks_terminal or {}
items[#items + 1] = {
term = term,
buf = term.buf,
id = meta.id,
cwd = meta.cwd or "?",
cmd = meta.cmd or vim.o.shell,
visible = term:win_valid(),
text = ("#%s %s %s"):format(meta.id or "?", meta.cwd or "?", meta.cmd or vim.o.shell),
}
end
Snacks.picker({
title = "Terminals",
items = items,
format = function(item)
local ret = {} ---@type snacks.picker.Highlight[]
ret[#ret + 1] = {
item.visible and "" or "",
item.visible and "SnacksPickerSpecial" or "SnacksPickerComment",
}
ret[#ret + 1] = { ("#%-2s"):format(item.id or "?"), "Number" }
ret[#ret + 1] = { " " .. vim.fn.fnamemodify(item.cwd, ":~"), "SnacksPickerPath" }
ret[#ret + 1] = { " " .. vim.fn.fnamemodify(item.cmd, ":t"), "SnacksPickerComment" }
return ret
end,
confirm = function(picker, item)
picker:close()
local term = item.term
if not term:buf_valid() then
Snacks.notify.warn("Terminal no longer exists")
return
end
if term:win_valid() then
vim.api.nvim_set_current_win(term.win)
else
term:show()
vim.cmd("startinsert")
end
end,
win = { input = { keys = { ["d"] = "term_delete" } } },
actions = {
term_delete = function(picker, item)
local term = item.term
picker:close()
if term and term:buf_valid() then
term:close()
pcall(vim.api.nvim_buf_delete, term.buf, { force = true })
end
end,
},
})
end
return M