🫙 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:isbufvalid()
51 return self.buf and vim.api.nvim_buf_is_valid(self.buf)
52end
53
54function Terminal:ensurebuf()
55 if not self:isbufvalid() then
56 self:init()
57 end
58end
59
60function Terminal:init()
61 self.buf = vim.api.nvim_create_buf(true, true)
62 vim.api.nvim_set_option_value("buftype", "nofile", { buf = self.buf })
63
64 vim._with({ buf = self.buf }, function()
65 vim.fn.jobstart(self.cmd, {
66 term = true,
67 cwd = self.cwd,
68 on_exit = function()
69 self:delete()
70 end,
71 })
72 end)
73end
74
75function Terminal:iswinvalid()
76 return self.win and vim.api.nvim_win_is_valid(self.win)
77end
78
79function Terminal:toggle()
80 self:ensurebuf()
81
82 if not self:iswinvalid() then
83 self:open()
84 return
85 end
86
87 local wcfg = vim.api.nvim_win_get_config(self.win)
88 local hidden = wcfg.hide
89
90 if hidden then
91 self:open()
92 else
93 self:close()
94 end
95end
96
97function Terminal:open()
98 if not self:iswinvalid() then
99 self:ensurebuf()
100 self.win = vim.api.nvim_open_win(self.buf, true, vim.F.if_nil(self.winoptsfn, get_winopts)())
101 vim.api.nvim_set_option_value("signcolumn", "no", { win = self.win })
102 vim.api.nvim_set_option_value("foldcolumn", "0", { win = self.win })
103 self.on_open(self)
104 else
105 vim.api.nvim_win_set_config(self.win, {
106 hide = false,
107 })
108 vim.api.nvim_set_current_win(self.win)
109 end
110end
111
112function Terminal:close()
113 if not self:iswinvalid() then
114 return
115 end
116
117 vim.api.nvim_win_set_config(self.win, {
118 hide = true,
119 })
120
121 if vim.api.nvim_get_current_win() == self.win then
122 vim.cmd.wincmd("p")
123 end
124end
125
126function Terminal:quit()
127 if not self:iswinvalid() then
128 return
129 end
130
131 vim.api.nvim_win_close(self.win, true)
132 self.win = nil
133end
134
135function Terminal:delete()
136 self:quit()
137
138 if self:isbufvalid() then
139 vim.bo[self.buf].buflisted = false
140 vim.api.nvim_buf_delete(self.buf, { unload = true })
141 self.buf = nil
142 end
143end
144
145return Terminal