9 changed files with 233 additions and 177 deletions
@ -0,0 +1,118 @@
|
||||
-- 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 |
||||
@ -0,0 +1,8 @@
|
||||
return { |
||||
{ |
||||
"MeanderingProgrammer/render-markdown.nvim", |
||||
opts = { |
||||
enabled = false, |
||||
}, |
||||
}, |
||||
} |
||||
@ -0,0 +1,23 @@
|
||||
-- Terminal UX layered on Snacks (replaces toggleterm). |
||||
-- Helpers live in lua/config/terminal.lua; keys are declared here so lazy.nvim |
||||
-- registers the which-key labels with the rest of the snacks spec. |
||||
return { |
||||
"folke/snacks.nvim", |
||||
keys = { |
||||
{ |
||||
"<Tab>", |
||||
function() |
||||
require("config.terminal").toggle_float() |
||||
end, |
||||
desc = "Terminal (float)", |
||||
mode = "n", |
||||
}, |
||||
{ |
||||
"<leader>ft", |
||||
function() |
||||
require("config.terminal").picker() |
||||
end, |
||||
desc = "Terminals (list/restore)", |
||||
}, |
||||
}, |
||||
} |
||||
Loading…
Reference in new issue