Skip to content

Commit 4f095e7

Browse files
committed
fix: spinner hang-ups
Refactor spinner logic to eliminate spinner hanging up.
1 parent 133e5e4 commit 4f095e7

10 files changed

Lines changed: 158 additions & 96 deletions

File tree

‎README.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

‎lua/leetcode-ui/lines/calendar.lua‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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)

‎lua/leetcode-ui/lines/solved.lua‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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)

‎lua/leetcode-ui/popup/info.lua‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

‎lua/leetcode-ui/popup/languages.lua‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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)

‎lua/leetcode-ui/popup/skills.lua‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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)

‎lua/leetcode/api/problems.lua‎

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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,15 +16,15 @@ 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
2523
utils.get(endpoint, {
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
Lines changed: 118 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -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

0 commit comments

Comments
 (0)