irongit

A Neovim plugin enabling you to solve LeetCode problems.

feat: better mount and reset lines on `random` and `daily`

kawreauthored
parent 55d4313commit b13daa7f20df55709881b16a8b084e7fffe2ea42Browse files

5 files changed, +138 -96

+1-1lua/leetcode-ui/lines/button/menu/exit.lua
@@ -8,7 +8,7 @@ function MenuExitButton:init()
88 MenuExitButton.super.init(self, "Exit", {
99 icon = "󰩈",
1010 sc = "q",
11- on_press = vim.cmd.quitall,
11+ on_press = function() vim.cmd("qa!") end,
1212 })
1313 end
1414
+121-75lua/leetcode-ui/question.lua
@@ -16,10 +16,11 @@ local log = require("leetcode.logger")
1616 ---@field console lc.ui.Console
1717 ---@field lang string
1818 ---@field cache lc.cache.Question
19+---@field reset boolean
1920 local Question = Object("LeetQuestion")
2021
2122 ---@param raw? boolean
22-function Question:get_snippet(raw)
23+function Question:snippet(raw)
2324 local snippets = self.q.code_snippets ~= vim.NIL and self.q.code_snippets or {}
2425 local snip = vim.tbl_filter(function(snip) return snip.lang_slug == self.lang end, snippets)[1]
2526 if not snip then return end
@@ -28,7 +29,30 @@ function Question:get_snippet(raw)
2829 return raw and code or self:injector(code)
2930 end
3031
31-function Question:create_file()
32+---@param code? string
33+function Question:set_lines(code)
34+ if not vim.api.nvim_buf_is_valid(self.bufnr) then return end
35+
36+ pcall(vim.cmd.undojoin)
37+ local s_i, e_i = self:range()
38+ code = code and code or (self:snippet(true) or "")
39+ vim.api.nvim_buf_set_lines(self.bufnr, s_i - 1, e_i, false, vim.split(code, "\n"))
40+end
41+
42+function Question:reset_lines()
43+ if not self.reset then return end
44+
45+ local new_lines = self:snippet(true) or ""
46+
47+ vim.schedule(function() --
48+ log.info("Previous code found, resetting…\nTo undo, simply press `u`")
49+ end)
50+
51+ self:set_lines(new_lines)
52+end
53+
54+---@return string path, boolean? existed
55+function Question:path()
3256 local lang = utils.get_lang(self.lang)
3357 local alt = lang.alt and ("." .. lang.alt) or ""
3458
@@ -38,47 +62,39 @@ function Question:create_file()
3862 self.file = config.storage.home:joinpath(fn_legacy)
3963
4064 if self.file:exists() then --
41- return self.file:absolute()
65+ return self.file:absolute(), true
4266 end
4367
4468 local fn = ("%s.%s%s.%s"):format(self.q.frontend_id, self.q.title_slug, alt, lang.ft)
4569 self.file = config.storage.home:joinpath(fn)
70+ local existed = true
4671
4772 if not self.file:exists() then --
48- self.file:write(self:get_snippet(), "w")
73+ self.file:write(self:snippet(), "w")
74+ existed = false
4975 end
5076
51- return self.file:absolute()
77+ return self.file:absolute(), existed
5278 end
5379
54----@param new_tabp? boolean
55----@return boolean was_loaded
56-function Question:create_buffer(new_tabp)
57- local file_name = self:create_file()
80+function Question:create_buffer()
81+ local path, existed = self:path()
5882
59- local buf = vim.fn.bufadd(file_name)
60- assert(buf ~= 0, "Failed to create buffer")
83+ vim.cmd("$tabe " .. path)
6184
62- self.bufnr = buf
63- if vim.fn.bufloaded(self.bufnr) == 1 then
64- return true
65- else
66- vim.fn.bufload(self.bufnr)
67- end
85+ self.bufnr = vim.api.nvim_get_current_buf()
86+ self.winid = vim.api.nvim_get_current_win()
6887
69- local cmd
70- if new_tabp then
71- cmd = ("$tabe %s"):format(file_name)
72- else
73- cmd = ("edit %s"):format(file_name)
74- end
88+ vim.api.nvim_set_option_value("buflisted", true, { buf = self.bufnr })
7589
7690 local i = self:fold_range()
77- if i then cmd = cmd .. (" | %d,%dfold"):format(1, i) end
78-
79- vim.api.nvim_exec2(cmd, {})
91+ if i then --
92+ pcall(vim.cmd, ("%d,%dfold"):format(1, i))
93+ end
8094
81- return false
95+ if existed then --
96+ self:reset_lines()
97+ end
8298 end
8399
84100 ---@param before boolean
@@ -99,7 +115,7 @@ function Question:inject(before)
99115 end
100116
101117 if res and res ~= "" then
102- return before and (res .. "\n\n") or ("\n\n" .. res)
118+ return before and (res .. "\n") or ("\n" .. res)
103119 else
104120 return nil
105121 end
@@ -109,50 +125,62 @@ end
109125 function Question:injector(code)
110126 local lang = utils.get_lang(self.lang)
111127
112- local inj_before = self:inject(true) or ""
113- local inj_after = self:inject(false) or ""
128+ local parts = {
129+ ("%s @leet start"):format(lang.comment),
130+ code,
131+ ("%s @leet end"):format(lang.comment),
132+ }
114133
115- return inj_before --
116- .. ("%s @leet start\n"):format(lang.comment)
117- .. code
118- .. ("\n%s @leet end"):format(lang.comment)
119- .. inj_after
120-end
134+ local before = self:inject(true)
135+ if before then table.insert(parts, 1, before) end
121136
122----@param pre? boolean
123-function Question:_unmount(pre)
124- self.info:unmount()
125- self.console:unmount()
126- self.description:unmount()
137+ local after = self:inject(false)
138+ if after then table.insert(parts, after) end
127139
128- if not pre and vim.api.nvim_buf_is_valid(self.bufnr) then
129- vim.api.nvim_buf_delete(self.bufnr, { force = true })
130- end
140+ return table.concat(parts, "\n")
141+end
131142
132- if vim.api.nvim_win_is_valid(self.winid) then --
133- vim.api.nvim_win_close(self.winid, true)
143+function Question:unmount()
144+ if vim.v.dying ~= 0 then --
145+ return
134146 end
135147
136- _Lc_questions = vim.tbl_filter(function(q) return q.bufnr ~= self.bufnr end, _Lc_questions)
148+ vim.schedule(function()
149+ self.info:unmount()
150+ self.console:unmount()
151+ self.description:unmount()
152+
153+ if vim.api.nvim_buf_is_valid(self.bufnr) then
154+ vim.api.nvim_buf_delete(self.bufnr, { force = true, unload = false })
155+ end
156+
157+ _Lc_questions = vim.tbl_filter(function(q) --
158+ return q.bufnr ~= self.bufnr
159+ end, _Lc_questions)
137160
138- self = nil
161+ self = nil
162+ end)
139163 end
140164
141----@param self lc.ui.Question
142----@param pre? boolean
143-Question.unmount = vim.schedule_wrap(function(self, pre) self:_unmount(pre) end)
165+function Question:_unmount()
166+ if vim.api.nvim_win_is_valid(self.winid) then vim.api.nvim_win_close(self.winid, true) end
167+end
144168
145-function Question:handle_mount()
146- self:create_buffer(true)
169+local group = vim.api.nvim_create_augroup("leetcode_questions", { clear = true })
170+function Question:autocmds()
171+ vim.api.nvim_create_autocmd("WinClosed", {
172+ group = group,
173+ buffer = self.bufnr,
174+ callback = function() self:unmount() end,
175+ })
176+end
147177
148- self.winid = vim.api.nvim_get_current_win()
178+function Question:handle_mount()
179+ self:create_buffer()
149180
150181 table.insert(_Lc_questions, self)
151182
152- vim.api.nvim_create_autocmd("QuitPre", {
153- buffer = self.bufnr,
154- callback = function() self:unmount(true) end,
155- })
183+ self:autocmds()
156184
157185 self.description = Description(self):mount()
158186 self.console = Console(self)
@@ -173,7 +201,7 @@ function Question:mount()
173201 end
174202 self.q = q
175203
176- if self:get_snippet() then
204+ if self:snippet() then
177205 self:handle_mount()
178206 else
179207 local msg = ("Snippet for `%s` not found. Select a different language"):format(self.lang)
@@ -232,37 +260,55 @@ end
232260 ---@param self lc.ui.Question
233261 ---@param lang lc.lang
234262 Question.change_lang = vim.schedule_wrap(function(self, lang)
235- if vim.api.nvim_get_current_win() ~= self.winid then
236- vim.api.nvim_set_current_win(self.winid)
237- end
238-
239263 local old_lang, old_bufnr = self.lang, self.bufnr
240- self.lang = lang
241264
242- local ok, was_loaded = pcall(Question.create_buffer, self)
243- if ok then
244- vim.api.nvim_buf_set_option(old_bufnr, "buflisted", false)
245- vim.api.nvim_buf_set_option(self.bufnr, "buflisted", true)
265+ local ok, err = pcall(function()
266+ self.lang = lang
267+ local path, existed = self:path()
268+
269+ self.bufnr = vim.fn.bufadd(path)
270+ assert(self.bufnr ~= 0, "Failed to create buffer " .. path)
246271
247- if was_loaded then
248- vim.api.nvim_win_set_buf(self.winid, self.bufnr)
249- else
272+ local loaded = vim.api.nvim_buf_is_loaded(self.bufnr)
273+ vim.fn.bufload(self.bufnr)
274+
275+ vim.api.nvim_win_set_buf(self.winid, self.bufnr)
276+
277+ vim.api.nvim_set_option_value("buflisted", false, { buf = old_bufnr })
278+ vim.api.nvim_set_option_value("buflisted", true, { buf = self.bufnr })
279+
280+ local i = self:fold_range()
281+ if i then --
282+ pcall(vim.cmd, ("%d,%dfold"):format(1, i))
283+ end
284+
285+ if existed then --
286+ self:reset_lines()
287+ end
288+
289+ if not loaded then --
250290 utils.exec_hook("question_enter", self)
251291 end
252- else
253- log.error("Changing language failed")
292+
293+ self:autocmds()
294+ end)
295+
296+ if not ok then
297+ log.error("Failed to change language\n" .. err)
254298 self.lang = old_lang
255299 self.bufnr = old_bufnr
256300 end
257301 end)
258302
259303 ---@param problem lc.cache.Question
260-function Question:init(problem)
304+---@param reset boolean
305+function Question:init(problem, reset)
261306 self.cache = problem
262307 self.lang = config.lang
308+ self.reset = reset and true or false
263309 end
264310
265----@type fun(question: lc.cache.Question): lc.ui.Question
311+---@type fun(question: lc.cache.Question, reset?: boolean): lc.ui.Question
266312 local LeetQuestion = Question
267313
268314 return LeetQuestion
+9-10lua/leetcode/command/init.lua
@@ -90,14 +90,14 @@ function cmd.delete_cookie()
9090 cookie.delete()
9191 end
9292
93-cmd.q_close_all = vim.schedule_wrap(function()
93+cmd.q_close_all = function()
9494 local utils = require("leetcode.utils")
9595 local qs = utils.question_tabs()
9696
9797 for _, tabp in ipairs(qs) do
98- tabp.question:unmount()
98+ tabp.question:_unmount()
9999 end
100-end)
100+end
101101
102102 cmd.expire = vim.schedule_wrap(function()
103103 local tabp = api.nvim_get_current_tabpage()
@@ -127,7 +127,7 @@ function cmd.qot()
127127 problems.question_of_today(function(qot, err)
128128 if err then return log.err(err) end
129129 local problemlist = require("leetcode.cache.problemlist")
130- Question(problemlist.get_by_title_slug(qot.title_slug)):mount()
130+ Question(problemlist.get_by_title_slug(qot.title_slug), true):mount()
131131 end)
132132 end
133133
@@ -151,7 +151,7 @@ function cmd.random_question(opts)
151151
152152 local item = problems.get_by_title_slug(q.title_slug) or {}
153153 local Question = require("leetcode-ui.question")
154- Question(item):mount()
154+ Question(item, true):mount()
155155 end
156156
157157 function cmd.start_with_cmd()
@@ -281,8 +281,7 @@ function cmd.reset()
281281 local q = utils.curr_question()
282282 if not q then return end
283283
284- local snip = q:get_snippet(true)
285- utils.set_question_lines(q, snip)
284+ q:set_lines()
286285 end
287286
288287 function cmd.last_submit()
@@ -303,8 +302,8 @@ function cmd.last_submit()
303302 return
304303 end
305304
306- if type(res) == "table" and res.code and api.nvim_buf_is_valid(q.bufnr) then
307- utils.set_question_lines(q, res.code)
305+ if type(res) == "table" and res.code then
306+ q:set_lines(res.code)
308307 else
309308 log.error("Something went wrong")
310309 end
@@ -359,7 +358,7 @@ function cmd.inject()
359358 end
360359
361360 if end_i == nil then
362- log.error("`@leet start` not found")
361+ log.error("`@leet end` not found")
363362 else
364363 local after = q:inject(false)
365364 if after then
+7-3lua/leetcode/pickers/question.lua
@@ -124,7 +124,7 @@ return {
124124 }),
125125 sorter = conf.generic_sorter(theme),
126126 attach_mappings = function(prompt_bufnr, map)
127- actions.select_default:replace(function()
127+ local function mount_question(reset)
128128 local selection = action_state.get_selected_entry()
129129 if not selection then return end
130130
@@ -134,8 +134,12 @@ return {
134134 end
135135
136136 actions.close(prompt_bufnr)
137- Question(q):mount()
138- end)
137+ Question(q, reset):mount()
138+ end
139+
140+ actions.select_default:replace(function() mount_question() end)
141+ map({ "n", "i" }, "<C-Enter>", function() mount_question(true) end)
142+
139143 return true
140144 end,
141145 })
+0-7lua/leetcode/utils.lua
@@ -115,11 +115,4 @@ function utils.norm_ins(str)
115115 return ins:sub(2, #ins - 1)
116116 end
117117
118-function utils.set_question_lines(q, code)
119- if not vim.api.nvim_buf_is_valid(q.bufnr) then return end
120-
121- local s_i, e_i = q:range()
122- vim.api.nvim_buf_set_lines(q.bufnr, s_i - 1, e_i, false, vim.split(code, "\n"))
123-end
124-
125118 return utils