irongit

A Neovim plugin enabling you to solve LeetCode problems.

181 lines4.4 KBLua
1local Group = require("leetcode-ui.group")
2local config = require("leetcode.config")
3local keys = config.user.keys
4
5local log = require("leetcode.logger")
6
7---@class lc.on_press
8---@field fn function
9---@field sc string
10
11---@class lc-ui.Layout._
12---@field line_idx integer
13---@field buttons lc.ui.Button[]
14---@field opts lc.ui.opts
15---@field keymaps table[]
16
17---@class lc.ui.Renderer : lc.ui.Group
18---@field bufnr integer
19---@field winid integer
20---@field _ lc-ui.Layout._ | lc.ui.Group.params
21local Renderer = Group:extend("LeetRenderer")
22
23function Renderer:draw(component)
24 self.bufnr = component.bufnr
25 self.winid = component.winid
26
27 if not (self.bufnr and vim.api.nvim_buf_is_valid(self.bufnr)) then
28 return
29 end
30
31 self:map("n", keys.confirm, function()
32 self:handle_press()
33 end)
34
35 self:clear_keymaps()
36 self._.buttons = {}
37 self._.line_idx = 1
38
39 local c_ok, c = pcall(vim.api.nvim_win_get_cursor, self.winid)
40 self:with_modifiable(function()
41 vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, {})
42 vim.api.nvim_buf_clear_namespace(self.bufnr, -1, 0, -1)
43
44 Renderer.super.draw(self, self, self._.opts)
45 end)
46 if c_ok then
47 pcall(vim.api.nvim_win_set_cursor, self.winid, c)
48 end
49end
50
51---@private
52---
53---@param fn function
54function Renderer:with_modifiable(fn)
55 local bufnr = self.bufnr
56 if not (bufnr and vim.api.nvim_buf_is_valid(bufnr)) then
57 return
58 end
59
60 local modi = vim.api.nvim_buf_get_option(bufnr, "modifiable")
61 if not modi then
62 vim.api.nvim_buf_set_option(bufnr, "modifiable", true)
63 end
64 fn()
65 if not modi then
66 vim.api.nvim_buf_set_option(bufnr, "modifiable", false)
67 end
68end
69
70function Renderer:map(mode, key, handler, opts) --
71 if not self.bufnr then
72 return
73 end
74 if type(key) == "number" then
75 key = tostring(key)
76 end
77
78 if type(key) == "table" then
79 for _, k in ipairs(key) do
80 self:map(mode, k, handler, opts)
81 end
82 elseif type(key) == "string" then
83 local options =
84 vim.tbl_deep_extend("force", { buffer = self.bufnr, clearable = false }, opts or {})
85
86 local clearable = options.clearable
87 options.clearable = nil
88
89 if clearable then
90 self._.keymaps[key] = mode
91 end
92
93 vim.keymap.set(mode, key, handler, options)
94 end
95end
96
97function Renderer:unmap(mode, key) --
98 pcall(vim.api.nvim_buf_del_keymap, self.bufnr, mode, key)
99end
100
101function Renderer:clear_keymaps()
102 for key, mode in pairs(self._.keymaps) do
103 self:unmap(mode, key)
104 end
105
106 self._.keymaps = {}
107end
108
109function Renderer:clear()
110 Renderer.super.clear(self)
111
112 self._.line_idx = 1
113 self._.buttons = {}
114 self:clear_keymaps()
115 self:with_modifiable(function()
116 vim.api.nvim_buf_set_lines(self.bufnr, 0, -1, false, {})
117 end)
118end
119
120---@param val? integer Optional value to increment line index by
121function Renderer:get_line_idx(val)
122 local line_idx = self._.line_idx
123 self._.line_idx = self._.line_idx + (val or 0)
124 return line_idx
125end
126
127---@param line_idx? integer The line that the press happened
128function Renderer:handle_press(line_idx)
129 local function feedenter()
130 vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<cr>", true, false, true), "n", true)
131 end
132
133 if not self.bufnr or not self.winid then
134 feedenter()
135 end
136 if not line_idx and not vim.api.nvim_win_is_valid(self.winid) then
137 return feedenter()
138 end
139
140 line_idx = line_idx or vim.api.nvim_win_get_cursor(self.winid)[1]
141
142 if not self._.buttons[line_idx] then
143 return feedenter()
144 end
145
146 local ok, err = pcall(function()
147 self._.buttons[line_idx]:press()
148 end)
149 if not ok then
150 log.error(err)
151 end
152end
153
154---@param button lc.ui.Button
155function Renderer:apply_button(button) --
156 local line_idx = self._.line_idx
157
158 for i = 1, #button:contents() do
159 self._.buttons[line_idx + i - 1] = button
160 end
161end
162
163---@param layout lc.ui.Renderer
164function Renderer:set(layout)
165 self._.items = layout._.items
166end
167
168---@param components lc.ui.Lines[]
169---@param opts? lc.ui.opts
170function Renderer:init(components, opts)
171 Renderer.super.init(self, components or {}, opts or {})
172
173 self._.line_idx = 1
174 self._.buttons = {}
175 self._.keymaps = {}
176end
177
178---@type fun(components?: table[], opts?: lc.ui.opts): lc.ui.Renderer
179local LeetRenderer = Renderer
180
181return LeetRenderer