irongit

A Neovim plugin enabling you to solve LeetCode problems.

fix: spinner hang-ups

Refactor spinner logic to eliminate spinner hanging up.
kawreauthored
parent 133e5e4commit 4f095e74e8a5df03a871b24f37d90f38dd6e94e7Browse files

10 files changed, +158 -96

+5-0README.md
@@ -97,6 +97,11 @@ To see full configuration types see [template.lua](./lua/leetcode/config/templat
9797 update_interval = 60 * 60 * 24 * 7, ---@type integer 7 days
9898 },
9999
100+ editor = {
101+ reset_previous_code = true, ---@type boolean
102+ fold_imports = true, ---@type boolean
103+ },
104+
100105 console = {
101106 open_on_runcode = true, ---@type boolean
102107
+3-3lua/leetcode-ui/lines/calendar.lua
@@ -188,12 +188,12 @@ function Calendar:fetch()
188188 end
189189
190190 function Calendar:update()
191- local spinner = Spinner:init("updating calendar")
191+ local spinner = Spinner:start("updating calendar")
192192 statistics.calendar(function(res, err)
193193 if err then
194- spinner:stop(err.msg, false)
194+ spinner:error(err.msg)
195195 else
196- spinner:stop("calendar updated", true, { timeout = 200 })
196+ spinner:success("calendar updated")
197197 self:handle_res(res)
198198 end
199199 end)
+3-3lua/leetcode-ui/lines/solved.lua
@@ -79,12 +79,12 @@ function Solved:handle_res(res)
7979 end
8080
8181 function Solved:update()
82- local spinner = Spinner:init("updating solved problems...")
82+ local spinner = Spinner:start("updating solved problems")
8383 statistics.solved(function(res, err)
8484 if err then
85- spinner:stop(err.msg, false)
85+ spinner:error(err.msg)
8686 else
87- spinner:stop("solved problems updated", true, { timeout = 200 })
87+ spinner:success("solved problems updated")
8888 self:handle_res(res)
8989 end
9090 end)
+0-2lua/leetcode-ui/popup/info.lua
@@ -8,8 +8,6 @@ local Parser = require("leetcode.parser")
88
99 local config = require("leetcode.config")
1010 local keys = config.user.keys
11-local utils = require("leetcode.utils")
12-local log = require("leetcode.logger")
1311
1412 ---@class lc.ui.InfoPopup : lc.ui.Popup
1513 ---@field popup NuiPopup
+3-3lua/leetcode-ui/popup/languages.lua
@@ -63,13 +63,13 @@ end
6363 function Languages:mount()
6464 Languages.super.mount(self)
6565
66- local spinner = Spinner:init("fetching user languages", "dot")
66+ local spinner = Spinner:start("fetching user languages", "dot")
6767 stats_api.languages(function(res, err)
6868 if err then
69- spinner:stop(err.msg, false)
69+ spinner:error(err.msg)
7070 else
7171 self:populate(res)
72- spinner:stop(nil, true, { timeout = 500 })
72+ spinner:success("languages fetched")
7373 Languages.super.draw(self)
7474 end
7575 end)
+3-3lua/leetcode-ui/popup/skills.lua
@@ -47,13 +47,13 @@ end
4747 function Skills:mount()
4848 Skills.super.mount(self)
4949
50- local spinner = Spinner:init("fetching user skills", "dot")
50+ local spinner = Spinner:start("fetching user skills", "dot")
5151 stats_api.skills(function(res, err)
5252 if err then
53- spinner:stop(err.msg, false)
53+ spinner:error(err.msg)
5454 else
5555 self:populate(res)
56- spinner:stop(nil, true, { timeout = 200 })
56+ spinner:success("skills fetched")
5757 Skills.super.draw(self)
5858 end
5959 end)
+9-11lua/leetcode/api/problems.lua
@@ -4,8 +4,6 @@ local config = require("leetcode.config")
44 local urls = require("leetcode.api.urls")
55 local Spinner = require("leetcode.logger.spinner")
66
7-local log = require("leetcode.logger")
8-
97 ---@class lc.ProblemsApi
108 local Problems = {}
119
@@ -18,7 +16,7 @@ function Problems.all(cb, noti)
1816
1917 local spinner
2018 if noti then
21- spinner = Spinner:init("updating problemlist cache...", "points")
19+ spinner = Spinner:start("updating problemlist cache", "points")
2220 end
2321
2422 if cb then
@@ -26,7 +24,7 @@ function Problems.all(cb, noti)
2624 callback = function(res, err)
2725 if err then
2826 if spinner then
29- spinner:stop(err.msg, false)
27+ spinner:error(err.msg)
3028 end
3129 return cb(nil, err)
3230 end
@@ -40,21 +38,21 @@ function Problems.all(cb, noti)
4038 Problems.translated_titles(function(titles, terr)
4139 if terr then
4240 if spinner then
43- spinner:stop(terr.msg, false)
41+ spinner:error(terr.msg)
4442 end
4543 return cb(nil, terr)
4644 end
4745
4846 problems = utils.translate_titles(problems, titles)
4947 if spinner then
50- spinner:stop("cache updated")
48+ spinner:success("cache updated")
5149 end
5250
5351 cb(problems)
5452 end)
5553 else
5654 if spinner then
57- spinner:stop("cache updated")
55+ spinner:success("cache updated")
5856 end
5957
6058 cb(problems)
@@ -65,7 +63,7 @@ function Problems.all(cb, noti)
6563 local res, err = utils.get(endpoint)
6664 if err then
6765 if spinner then
68- spinner:stop(err.msg, false)
66+ spinner:error(err.msg)
6967 end
7068 return nil, err
7169 end
@@ -76,18 +74,18 @@ function Problems.all(cb, noti)
7674 local titles, terr = Problems.translated_titles()
7775 if terr then
7876 if spinner then
79- spinner:stop(terr.msg, false)
77+ spinner:error(terr.msg)
8078 end
8179 return nil, terr
8280 end
8381
8482 if spinner then
85- spinner:stop("problems cache updated")
83+ spinner:success("problems cache updated")
8684 end
8785 return utils.translate_titles(problems, titles)
8886 else
8987 if spinner then
90- spinner:stop("problems cache updated")
88+ spinner:success("problems cache updated")
9189 end
9290 return problems
9391 end
+118-63lua/leetcode/logger/spinner/init.lua
@@ -1,13 +1,16 @@
11 local config = require("leetcode.config")
22 local t = require("leetcode.translator")
3+local lvls = vim.log.levels
34
45 ---@class lc.Spinner
5----@field spinner lc.spinner | nil
6+---@field spinner lc.spinner
7+---@field stype lc.spinner_types
68 ---@field index integer
7----@field noti any
9+---@field notif any
10+---@field timer uv.uv_timer_t
811 ---@field msg string
9-local spinner = {}
10-spinner.__index = spinner
12+local Spinner = {}
13+Spinner.__index = Spinner
1114
1215 ---@class lc.spinner
1316 ---@field frames string[]
@@ -39,96 +42,148 @@ local spinners = {
3942 },
4043 }
4144
45+---@param msg? string
46+---@param opts? table
47+function Spinner:error(msg, opts)
48+ self:set_icon("󰅘")
49+ self:stop(msg, opts)
50+end
51+
52+---@param msg? string
53+---@param opts? table
54+function Spinner:success(msg, opts)
55+ self:set_icon("")
56+ self:stop(msg, opts)
57+end
58+
4259 ---@private
43-function spinner:spin()
44- local stype = self.spinner
45- if not stype then
46- return
47- end
60+---@param msg? string
61+---@param opts? table
62+function Spinner:stop(msg, opts)
63+ self:set_message(msg)
64+ self.opts.timeout = 2500
65+ self:extend(opts)
4866
49- self:set(nil, nil, {
50- icon = stype.frames[self.index + 1],
51- })
67+ if self.timer:is_active() then
68+ self.timer:stop()
69+ end
5270
53- self.index = (self.index + 1) % #stype.frames
71+ if not self._closed then
72+ self._closed = true
73+ self.timer:close(function()
74+ self:replace()
75+ end)
76+ end
77+end
5478
55- local fps = math.floor(1000 / stype.fps)
56- vim.defer_fn(function()
57- self:spin()
58- end, fps)
79+---@private
80+function Spinner:set_icon(icon)
81+ self:extend({ icon = icon })
5982 end
6083
6184 ---@private
62----
63----@param msg? string
64----@param lvl? integer
65----@param opts? table
66-function spinner:set(msg, lvl, opts)
67- if not self.spinner then
68- return
85+function Spinner:set_lvl(lvl)
86+ if lvl then
87+ self.lvl = lvl
6988 end
89+end
7090
91+---@private
92+function Spinner:set_message(msg)
7193 if msg then
72- self:update(msg)
94+ self.msg = msg
7395 end
74- lvl = lvl or vim.log.levels.INFO
75-
76- local id = type(self.noti) == "table" and self.noti.id or self.noti
96+end
7797
78- opts = vim.tbl_deep_extend("force", {
79- hide_from_history = true,
80- history = false,
81- title = config.name,
82- timeout = false,
83- replace = id,
84- id = id,
85- }, opts or {})
98+function Spinner:soft_update(msg, lvl, opts)
99+ self:set_message(msg)
100+ self:set_lvl(lvl)
101+ self:extend(opts)
102+end
86103
87- self.noti = vim.notify(self.msg, lvl, opts)
104+function Spinner:update(msg, lvl, opts)
105+ self:soft_update(msg, lvl, opts)
106+ self:reset_loop()
88107 end
89108
90----@param spinner_type lc.spinner_types
91-function spinner:change(spinner_type)
92- self.spinner = spinners[spinner_type]
109+---@private
110+---@param opts? table
111+function Spinner:extend(opts)
112+ if opts then
113+ self.opts = vim.tbl_deep_extend("force", self.opts, opts)
114+ end
93115 end
94116
95----@param msg any
96-function spinner:update(msg)
97- self.msg = t(tostring(msg))
117+---@private
118+function Spinner:replace()
119+ if self.notif then
120+ local replace_id = type(self.notif) == "table" and self.notif.id or self.notif
121+ assert(replace_id, "Unknown notification format, please open an issue.")
122+ self:extend({ replace = replace_id, id = replace_id })
123+ end
124+
125+ local msg = self.msg
126+ if self.timer:is_active() then
127+ msg = msg .. "…"
128+ end
129+
130+ self.notif = vim.notify(msg, self.lvl, self.opts)
98131 end
99132
100-function spinner:start()
101- self:spin()
102- return self
133+---@private
134+function Spinner:reset_loop()
135+ if self.timer:is_active() then
136+ self.timer:stop()
137+ end
138+ self:loop()
103139 end
104140
105----@param msg? string
106----@param success? boolean
107----@param opts? table
108-function spinner:stop(msg, success, opts)
109- success = success == nil and true or success
141+---@param spinner_type lc.spinner_types
142+function Spinner:use(spinner_type)
143+ if self.stype == spinner_type then
144+ return
145+ end
146+ assert(spinners[spinner_type], "Unknown spinner type: " .. spinner_type)
110147
111- opts = vim.tbl_deep_extend("force", {
112- icon = success and "" or "󰅘",
113- timeout = 1500,
114- }, opts or {})
148+ self.index = 0
149+ self.stype = spinner_type
150+ self:reset_loop()
151+end
115152
116- local lvl = vim.log.levels[success and "INFO" or "ERROR"]
153+---@private
154+function Spinner:loop()
155+ local fps = math.floor(1000 / spinners[self.stype].fps)
156+
157+ local function update_spinner()
158+ self.index = (self.index + 1) % #spinners[self.stype].frames
159+ self:set_icon(spinners[self.stype].frames[self.index + 1])
160+ self:replace()
161+ end
117162
118- self:set(msg, lvl, opts)
119- self.spinner = nil
163+ self.timer:start(0, fps, vim.schedule_wrap(update_spinner))
120164 end
121165
122166 ---@param msg? string
123167 ---@param spinner_type? lc.spinner_types
124-function spinner:init(msg, spinner_type)
168+function Spinner:start(msg, spinner_type)
169+ local opts = {
170+ hide_from_history = true,
171+ history = false,
172+ title = config.name,
173+ timeout = false,
174+ }
175+
125176 self = setmetatable({
126177 index = 0,
127- spinner = spinners[spinner_type or "dot"],
178+ timer = vim.loop.new_timer(),
179+ msg = msg,
180+ stype = spinner_type or "dot",
181+ lvl = lvls.INFO,
182+ opts = opts,
128183 }, self)
129184
130- self:update(msg or "")
131- return self:start()
185+ self:loop()
186+ return self
132187 end
133188
134-return spinner
189+return Spinner
+6-6lua/leetcode/logger/spinner/judge.lua
@@ -6,24 +6,24 @@ Judge.__index = Judge
66 setmetatable(Judge, Spinner)
77
88 local check_state = {
9- ["PENDING"] = "Pending…",
10- ["STARTED"] = "Judging…",
9+ ["PENDING"] = "Pending",
10+ ["STARTED"] = "Judging",
1111 ["SUCCESS"] = "Finished",
1212 ["FAILURE"] = "Failed", -- CODE: 16
1313 }
1414
1515 function Judge:from_state(state)
16- self:update(check_state[state])
16+ self:soft_update(check_state[state])
1717
1818 if state == "PENDING" then
19- self:change("points")
19+ self:use("points")
2020 else
21- self:change("dot")
21+ self:use("dot")
2222 end
2323 end
2424
2525 function Judge:init()
26- local spinner = Spinner:init(check_state["PENDING"], "points")
26+ local spinner = Spinner:start(check_state["PENDING"], "points")
2727 return setmetatable(spinner, Judge)
2828 end
2929
+8-2lua/leetcode/runner/init.lua
@@ -1,3 +1,5 @@
1+---@module 'plenary'
2+
13 local log = require("leetcode.logger")
24 local interpreter = require("leetcode.api.interpreter")
35 local config = require("leetcode.config")
@@ -50,11 +52,15 @@ function Runner:handle(submit)
5052 end
5153
5254 if item then
53- judge:stop(item.status_msg, item._.success)
55+ if item._.success then
56+ judge:success(item.status_msg)
57+ else
58+ judge:error(item.status_msg)
59+ end
5460 elseif state then
5561 judge:from_state(state)
5662 elseif err then
57- judge:stop(err.msg or "Something went wrong", false)
63+ judge:error(err.msg or "Something went wrong")
5864 end
5965
6066 if item then