Something went wrong. Try again.
Plugin for managing terminal in neovim
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377local M = {}
local Popup = require("nui.popup")local Layout = require("nui.layout")local Input = require("nui.input")local event = require("nui.utils.autocmd").eventlocal terminal_manager = require("term.utils.terminal")
local _defaults = { margin = 2, width = 120, height = 30, border = "rounded", default_shell = vim.env.SHELL or "/bin/bash", auto_focus_terminal = true, session_list_width = "20%", terminal_width = "80%",}
local _state = { active_session_id = nil, session_list = {},}
---Render the session list in the left panel---@param left_panel any: The left panel popuplocal function _render_sessions(left_panel) if not left_panel or not left_panel.bufnr or not vim.api.nvim_buf_is_valid(left_panel.bufnr) then return end
local sessions = terminal_manager.list() _state.session_list = sessions
local session_lines = { "=== Sessions ===", "", }
for i, session in ipairs(sessions) do local marker = (session.id == _state.active_session_id) and "* " or " " table.insert( session_lines, string.format("%s[%d] %s", marker, i, session.cmd) ) end
if #sessions == 0 then table.insert(session_lines, "No active sessions") end
table.insert(session_lines, "") table.insert(session_lines, "=== Keys ===") table.insert(session_lines, "S-j - Next Session") table.insert(session_lines, "S-k - Prev Session") table.insert(session_lines, "S-r - Rename Session") table.insert(session_lines, "S-x - Delete Session")
vim.api.nvim_buf_set_lines(left_panel.bufnr, 0, -1, false, session_lines)end
---@param opts DashboardOptions|nillocal function toggle(opts) opts = vim.tbl_deep_extend("force", _defaults, opts or {})
-- Left panel: session list local left_panel = Popup({ enter = true, focusable = true, border = { style = opts.border, padding = { 0, 1 }, }, win_options = { winhighlight = "Normal:NormalFloat", }, buf_options = { modifiable = true, readonly = false, }, })
-- Right panel: terminal local right_panel = Popup({ enter = false, focusable = true, border = { style = opts.border, padding = { 0, 1 }, }, win_options = { winhighlight = "Normal:NormalFloat", }, buf_options = { modifiable = true, readonly = false, }, })
-- Create layout with configurable split local layout = Layout( { relative = "editor", position = "50%", size = { width = opts.width, height = opts.height, }, }, Layout.Box({ Layout.Box(left_panel, { size = opts.session_list_width }), Layout.Box(right_panel, { size = opts.terminal_width }), }, { dir = "row" }) )
layout:mount()
-- Forward declarations for recursive functions local next_session, prev_session, rename_session, delete_session, switch_to_session
-- Function to switch to a session and display it function switch_to_session(session_id) if not terminal_manager.exists(session_id) then return end
_state.active_session_id = session_id terminal_manager.switch(session_id)
local session = terminal_manager.get_by_id(session_id) local winid = right_panel.winid
if winid and vim.api.nvim_win_is_valid(winid) then vim.api.nvim_set_current_win(winid)
-- Check if session has a buffer already if not session.bufnr or not vim.api.nvim_buf_is_valid(session.bufnr) then -- Create new terminal buffer local shell = opts.default_shell local cmd = session.cmd local terminal_cmd = shell .. " -c " .. cmd session.bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_win_set_buf(winid, session.bufnr) vim.fn.termopen(terminal_cmd, { on_exit = function() -- Close session when terminal exits terminal_manager.close(session_id) end, })
-- Set keymaps for new terminal buffer vim.api.nvim_win_call(winid, function() vim.keymap.set( "t", "<S-j>", next_session, { noremap = true, buffer = true } ) vim.keymap.set( "t", "<S-k>", prev_session, { noremap = true, buffer = true } ) vim.keymap.set( "t", "<S-r>", rename_session, { noremap = true, buffer = true } ) vim.keymap.set( "t", "<S-x>", delete_session, { noremap = true, buffer = true } ) if opts.auto_focus_terminal then vim.cmd("startinsert") end end) else -- Reuse existing buffer vim.api.nvim_win_set_buf(winid, session.bufnr)
-- Set keymaps for reused buffer vim.api.nvim_win_call(winid, function() vim.keymap.set( "t", "<S-j>", next_session, { noremap = true, buffer = true } ) vim.keymap.set( "t", "<S-k>", prev_session, { noremap = true, buffer = true } ) vim.keymap.set( "t", "<S-r>", rename_session, { noremap = true, buffer = true } ) vim.keymap.set( "t", "<S-x>", delete_session, { noremap = true, buffer = true } )
if opts.auto_focus_terminal then vim.cmd("startinsert") end end) end end
_render_sessions(left_panel) end
-- Navigation functions function next_session() if #_state.session_list == 0 then return end local current_idx = nil for i, session in ipairs(_state.session_list) do if session.id == _state.active_session_id then current_idx = i break end end if not current_idx then return end local next_idx = current_idx < #_state.session_list and current_idx + 1 or 1 switch_to_session(_state.session_list[next_idx].id) end
function prev_session() if #_state.session_list == 0 then return end local current_idx = nil for i, session in ipairs(_state.session_list) do if session.id == _state.active_session_id then current_idx = i break end end if not current_idx then return end local prev_idx = current_idx > 1 and current_idx - 1 or #_state.session_list switch_to_session(_state.session_list[prev_idx].id) end
function rename_session() local active_session = terminal_manager.get_by_id(_state.active_session_id) if not active_session then vim.notify("No active session", vim.log.levels.WARN) return end
local input = Input({ position = "50%", size = { width = 50, height = 3, }, border = { style = "rounded", padding = { 0, 1 }, }, win_options = { winhighlight = "Normal:NormalFloat", }, }, { prompt = "Rename to: ", default_value = active_session.cmd, on_submit = function(value) if value and value ~= "" then active_session.cmd = value _render_sessions(left_panel) end end, }) input:mount() end
function delete_session() if not _state.active_session_id then vim.notify("No active session", vim.log.levels.WARN) return end
terminal_manager.close(_state.active_session_id) vim.notify("Session deleted", vim.log.levels.INFO) end
-- Listen for session close events terminal_manager.on("on_session_closed", function(data) _render_sessions(left_panel)
-- If closed session was active, switch to another if data.id == _state.active_session_id then local sessions = terminal_manager.list() if #sessions > 0 then switch_to_session(sessions[1].id) else _state.active_session_id = nil end end end)
-- Determine initial session local initial_session_id if opts.cmd then -- Create new session if command is provided local session = terminal_manager.create(opts.cmd) initial_session_id = session and session.id or nil else -- Use existing active session or create default local active = terminal_manager.get_active() if active then initial_session_id = active.id else -- No existing session, create default shell local session = terminal_manager.create(opts.default_shell) initial_session_id = session and session.id or nil end end
-- Render initial session list _render_sessions(left_panel)
-- Display initial session if initial_session_id then switch_to_session(initial_session_id) end
-- Map keys for sidebar if vim.api.nvim_win_is_valid(left_panel.winid) then left_panel:map("n", "<S-j>", next_session, { noremap = true }) left_panel:map("n", "<S-k>", prev_session, { noremap = true }) left_panel:map("n", "<S-r>", rename_session, { noremap = true }) left_panel:map("n", "<S-x>", delete_session, { noremap = true }) end
-- Auto-enter terminal mode when entering terminal buffer vim.api.nvim_create_autocmd(event.BufEnter, { callback = function() local current_win = vim.api.nvim_get_current_win() if current_win ~= right_panel.winid and current_win ~= left_panel.winid then layout:unmount() return end
if vim.bo.buftype == "terminal" then vim.cmd("startinsert") end end, })end
M.toggle = toggle
return M