🫙 tiny scratch terminal for Neovim
1---@return vim.api.keyset.win_config
2local function get_winopts()
3 local horiz = vim.opt.fillchars:get().horiz or "─"
4
5 local height = math.ceil(vim.o.lines * 0.3)
6 local offset = vim.o.cmdheight + 1
7
8 return {
9 relative = "editor",
10 col = 0,
11 row = vim.o.lines - height - offset - 1,
12 height = height,
13 width = vim.o.columns,
14 style = "minimal",
15 border = { horiz, horiz, horiz, "", "", "", "", "" },
16 }
17end
18
19---@class jamjar.Terminal.proto
20---@field cmd? string
21---@field cwd? string
22---@field winoptsfn? fun(): vim.api.keyset.win_config
23---@field on_open? fun(self: jamjar.Terminal)
24
25---@class jamjar.Terminal : jamjar.Terminal.proto
26---@field cmd string
27---@field buf integer
28---@field win? integer
29local Terminal = {}
30Terminal.__index = Terminal
31
32local function get_defaultopts()
33 return {
34 cmd = vim.o.shell,
35 on_open = function(self)
36 vim.api.nvim_set_option_value(
37 "winhighlight",
38 "Normal:Normal,NormalNC:NormalNC,FloatBorder:WinSeparator",
39 { win = self.win }
40 )
41 end,
42 }
43end
44
45---@param opts? jamjar.Terminal.proto
46function Terminal:new(opts)
47 return setmetatable(vim.tbl_extend("force", get_defaultopts(), opts or {}), self)
48end
49
50function Terminal:init()
51 self.buf = vim.api.nvim_create_buf(true, true)
52 vim.api.nvim_set_option_value("buftype", "nofile", { buf = self.buf })
53
54 vim._with({ buf = self.buf }, function()
55 vim.fn.jobstart(self.cmd, {
56 term = true,
57 cwd = self.cwd,
58 on_exit = function()
59 self:delete()
60 end,
61 })
62 end)
63end
64
65function Terminal:toggle()
66 if not self.win then
67 self:open()
68 return
69 end
70
71 local wcfg = vim.api.nvim_win_get_config(self.win)
72 local hidden = wcfg.hide
73
74 if hidden then
75 self:open()
76 else
77 self:close()
78 end
79end
80
81function Terminal:open()
82 if not self.win then
83 if not self.buf then
84 self:init()
85 end
86 self.win = vim.api.nvim_open_win(self.buf, true, self.winoptsfn and self.winoptsfn() or get_winopts())
87 vim.api.nvim_set_option_value("signcolumn", "no", { win = self.win })
88 vim.api.nvim_set_option_value("foldcolumn", "0", { win = self.win })
89 self.on_open(self)
90 else
91 vim.api.nvim_win_set_config(self.win, {
92 hide = false,
93 })
94 vim.api.nvim_set_current_win(self.win)
95 end
96end
97
98function Terminal:close()
99 if self.win then
100 vim.api.nvim_win_set_config(self.win, {
101 hide = true,
102 })
103 if vim.api.nvim_get_current_win() == self.win then
104 vim.cmd.wincmd("p")
105 end
106 end
107end
108
109function Terminal:quit()
110 if self.win then
111 vim.api.nvim_win_close(self.win, true)
112 self.win = nil
113 end
114end
115
116function Terminal:delete()
117 self:quit()
118
119 if self.buf and vim.api.nvim_buf_is_valid(self.buf) then
120 vim.bo[self.buf].buflisted = false
121 vim.api.nvim_buf_delete(self.buf, { unload = true })
122 self.buf = nil
123 end
124end
125
126return Terminal