···11+local jamjar = {}
22+33+---@type table<string, jamjar.Terminal>
44+jamjar._terminals = {}
55+66+jamjar.make = function(name, opts)
77+ return function()
88+ local term = jamjar._terminals[name]
99+ if not term then
1010+ term = require("jamjar.terminal"):new(opts)
1111+ vim.schedule(function()
1212+ jamjar._terminals[name] = term
1313+ end)
1414+1515+ term:init()
1616+ end
1717+ term:toggle()
1818+ end
1919+end
2020+2121+return jamjar
+126
lua/jamjar/terminal.lua
···11+---@return vim.api.keyset.win_config
22+local function get_winopts()
33+ local horiz = vim.opt.fillchars:get().horiz or "─"
44+55+ local height = math.ceil(vim.o.lines * 0.3)
66+ local offset = vim.o.cmdheight + 1
77+88+ return {
99+ relative = "editor",
1010+ col = 0,
1111+ row = vim.o.lines - height - offset - 1,
1212+ height = height,
1313+ width = vim.o.columns,
1414+ style = "minimal",
1515+ border = { horiz, horiz, horiz, "", "", "", "", "" },
1616+ }
1717+end
1818+1919+---@class jamjar.Terminal.proto
2020+---@field cmd? string
2121+---@field cwd? string
2222+---@field winoptsfn? fun(): vim.api.keyset.win_config
2323+---@field on_open? fun(self: jamjar.Terminal)
2424+2525+---@class jamjar.Terminal : jamjar.Terminal.proto
2626+---@field cmd string
2727+---@field buf integer
2828+---@field win? integer
2929+local Terminal = {}
3030+Terminal.__index = Terminal
3131+3232+local function get_defaultopts()
3333+ return {
3434+ cmd = vim.o.shell,
3535+ on_open = function(self)
3636+ vim.api.nvim_set_option_value(
3737+ "winhighlight",
3838+ "Normal:Normal,NormalNC:NormalNC,FloatBorder:WinSeparator",
3939+ { win = self.win }
4040+ )
4141+ end,
4242+ }
4343+end
4444+4545+---@param opts? jamjar.Terminal.proto
4646+function Terminal:new(opts)
4747+ return setmetatable(vim.tbl_extend("force", get_defaultopts(), opts or {}), self)
4848+end
4949+5050+function Terminal:init()
5151+ self.buf = vim.api.nvim_create_buf(true, true)
5252+ vim.api.nvim_set_option_value("buftype", "nofile", { buf = self.buf })
5353+5454+ vim._with({ buf = self.buf }, function()
5555+ vim.fn.jobstart(self.cmd, {
5656+ term = true,
5757+ cwd = self.cwd,
5858+ on_exit = function()
5959+ self:delete()
6060+ end,
6161+ })
6262+ end)
6363+end
6464+6565+function Terminal:toggle()
6666+ if not self.win then
6767+ self:open()
6868+ return
6969+ end
7070+7171+ local wcfg = vim.api.nvim_win_get_config(self.win)
7272+ local hidden = wcfg.hide
7373+7474+ if hidden then
7575+ self:open()
7676+ else
7777+ self:close()
7878+ end
7979+end
8080+8181+function Terminal:open()
8282+ if not self.win then
8383+ if not self.buf then
8484+ self:init()
8585+ end
8686+ self.win = vim.api.nvim_open_win(self.buf, true, self.winoptsfn and self.winoptsfn() or get_winopts())
8787+ vim.api.nvim_set_option_value("signcolumn", "no", { win = self.win })
8888+ vim.api.nvim_set_option_value("foldcolumn", "0", { win = self.win })
8989+ self.on_open(self)
9090+ else
9191+ vim.api.nvim_win_set_config(self.win, {
9292+ hide = false,
9393+ })
9494+ vim.api.nvim_set_current_win(self.win)
9595+ end
9696+end
9797+9898+function Terminal:close()
9999+ if self.win then
100100+ vim.api.nvim_win_set_config(self.win, {
101101+ hide = true,
102102+ })
103103+ if vim.api.nvim_get_current_win() == self.win then
104104+ vim.cmd.wincmd("p")
105105+ end
106106+ end
107107+end
108108+109109+function Terminal:quit()
110110+ if self.win then
111111+ vim.api.nvim_win_close(self.win, true)
112112+ self.win = nil
113113+ end
114114+end
115115+116116+function Terminal:delete()
117117+ self:quit()
118118+119119+ if self.buf and vim.api.nvim_buf_is_valid(self.buf) then
120120+ vim.bo[self.buf].buflisted = false
121121+ vim.api.nvim_buf_delete(self.buf, { unload = true })
122122+ self.buf = nil
123123+ end
124124+end
125125+126126+return Terminal