Skip to content

Commit e169948

Browse files
authored
feat: improve runner and executer notification handling (#254)
* feat: improve runner and executer notification handling With the new approach we get rid of the global states managed in the notifications table. Everything is capsulated within the created notification instance. That also allows to fix an issue where a notification is dismissed while a build process is still ongoing and the spinner should be updated. Updating the spinner fails, because the notification timed out and got dismissed, resulting in a repeating "No Matching notification to replace found" error. The new instance based approach tracks the shown notification and replaces any existing notification when the message is updated. If the notification times out and is dismissed, the instance will create a new notification automatically. * feat: automatically resize notification window When the content gets updated, the notification window is resized to match the new message --------- Co-authored-by: Denzel <>
1 parent aedf4b6 commit e169948

3 files changed

Lines changed: 125 additions & 94 deletions

File tree

lua/cmake-tools/init.lua

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ function cmake.setup(values)
3434
const.cmake_runner.opts or {}
3535
)
3636

37+
require("cmake-tools.notification").setup(const.cmake_notifications)
38+
3739
config = Config:new(const)
3840

3941
-- auto reload previous session
@@ -119,7 +121,7 @@ function cmake.generate(opt, callback)
119121
end
120122
cmake.configure_compile_commands()
121123
cmake.create_regenerate_on_save_autocmd()
122-
end, const.cmake_notifications)
124+
end)
123125
end
124126

125127
-- if exists cmake-kits.json, kit is used to set
@@ -181,7 +183,7 @@ function cmake.generate(opt, callback)
181183
end
182184
cmake.configure_compile_commands()
183185
cmake.create_regenerate_on_save_autocmd()
184-
end, const.cmake_notifications)
186+
end)
185187
end
186188

187189
--- Clean targets
@@ -208,7 +210,7 @@ function cmake.clean(callback)
208210
if type(callback) == "function" then
209211
callback()
210212
end
211-
end, const.cmake_notifications)
213+
end)
212214
end
213215

214216
--- Build this project using the make toolchain of target platform
@@ -276,7 +278,7 @@ function cmake.build(opt, callback)
276278
if type(callback) == "function" then
277279
callback()
278280
end
279-
end, const.cmake_notifications)
281+
end)
280282
end
281283

282284
function cmake.quick_build(opt, callback)
@@ -341,8 +343,7 @@ function cmake.install(opt)
341343
args,
342344
config.cwd,
343345
config.executor,
344-
nil,
345-
const.cmake_notifications
346+
nil
346347
)
347348
end
348349

@@ -421,16 +422,7 @@ function cmake.run(opt)
421422
local env = environment.get_run_environment(config, opt.target)
422423
local _args = opt.args and opt.args or config.target_settings[opt.target].args
423424
local cmd = target_path
424-
utils.run(
425-
cmd,
426-
config.env_script,
427-
env,
428-
_args,
429-
launch_path,
430-
config.runner,
431-
nil,
432-
const.cmake_notifications
433-
)
425+
utils.run(cmd, config.env_script, env, _args, launch_path, config.runner, nil)
434426
end)
435427
else
436428
local result = config:get_launch_target()
@@ -468,8 +460,7 @@ function cmake.run(opt)
468460
cmake:get_launch_args(),
469461
launch_path,
470462
config.runner,
471-
nil,
472-
const.cmake_notifications
463+
nil
473464
)
474465
end
475466
)

lua/cmake-tools/notification.lua

Lines changed: 92 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,100 @@
11
local has_notify, notify = pcall(require, "notify")
22

3-
local notification = {
4-
notification = {},
5-
}
6-
7-
function notification.update_spinner() -- update spinner helper function to defer
8-
if notification.notification.spinner_idx then
9-
local new_spinner = (notification.notification.spinner_idx + 1)
10-
% #notification.notification.spinner
11-
notification.notification.spinner_idx = new_spinner
12-
13-
notification.notification.id = notification.notify(nil, notification.notification.level, {
14-
title = "CMakeTools",
15-
hide_from_history = true,
16-
icon = notification.notification.spinner[new_spinner],
17-
replace = notification.notification.id,
18-
})
19-
20-
vim.defer_fn(function()
21-
notification.update_spinner()
22-
end, notification.notification.refresh_rate_ms)
3+
local function render(self)
4+
if self.closed then
5+
self.opts.replace = nil
6+
else
7+
self.opts.replace = self.id
238
end
9+
10+
self.id = notify(self.msg, self.level, self.opts)
11+
self.opts.replace = nil
12+
self.closed = false
13+
end
14+
15+
local config = {}
16+
local Notification = {}
17+
18+
function Notification.setup(cfg)
19+
config = cfg
20+
end
21+
22+
function Notification:new(type)
23+
local instance = setmetatable({}, self)
24+
self.__index = self
25+
26+
instance.spinner_idx = 1
27+
instance.closed = true
28+
instance.enabled = has_notify and config[type].enabled
29+
30+
instance.spinnerTimer = vim.loop.new_timer()
31+
32+
return instance
33+
end
34+
35+
function Notification:startSpinner()
36+
if not self.enabled or self.spinnerRunning then
37+
return
38+
end
39+
self.spinnerRunning = true
40+
self.spinnerTimer:start(
41+
config.refresh_rate_ms,
42+
config.refresh_rate_ms,
43+
vim.schedule_wrap(function()
44+
self.spinner_idx = (self.spinner_idx + 1) % #config.spinner
45+
46+
self.opts.replace = self.id
47+
self.opts.icon = config.spinner[self.spinner_idx]
48+
render(self)
49+
end)
50+
)
51+
end
52+
53+
function Notification:stopSpinner()
54+
self.spinnerRunning = false
55+
self.spinnerTimer:stop()
2456
end
2557

26-
function notification.notify(msg, lvl, opts)
27-
if notification.notification.enabled and has_notify then
28-
opts.hide_from_history = true
29-
return notify(msg, lvl, opts)
58+
function Notification:notify(msg, level, opts)
59+
if not self.enabled then
60+
return
61+
end
62+
63+
self.msg = msg or ""
64+
self.level = level
65+
self.opts = opts or {}
66+
67+
local on_close = self.opts.on_close
68+
local on_open = self.opts.on_open
69+
70+
self.opts.hide_from_history = true
71+
self.opts.title = "CMakeTools"
72+
self.opts.on_close = function(win)
73+
self.closed = true
74+
if on_close then
75+
on_close(win)
76+
end
77+
end
78+
self.opts.on_open = function(win)
79+
self.win = win
80+
self.width = vim.api.nvim_win_get_width(win)
81+
if on_open then
82+
on_open(win)
83+
end
84+
end
85+
86+
render(self)
87+
88+
-- update the notification width when the message was updated
89+
local timeDigits = 8
90+
local headlineLength = (self.opts.icon and (#self.opts.icon + 1) or 0)
91+
+ #self.opts.title
92+
+ 3 -- padding between title and time
93+
+ timeDigits
94+
95+
if self.width then
96+
vim.api.nvim_win_set_width(self.win, math.max(#self.msg + 1, headlineLength))
3097
end
3198
end
3299

33-
return notification
100+
return Notification

lua/cmake-tools/utils.lua

Lines changed: 24 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -160,18 +160,17 @@ function utils.has_active_job(runner_data, executor_data)
160160
or utils.get_runner(runner_data.name).has_active_job(runner_data.opts)
161161
end
162162

163-
local notify_update_line = function(out, err)
164-
if not notification.notification.enabled then
165-
return
166-
end
167-
local line = err and err or out
168-
if line ~= nil then
169-
if line and vim.fn.match(line, "^%[%s*(%d+)%s*%%%]") then -- only show lines containing build progress e.g [ 12%]
170-
notification.notification.id = notification.notify( -- notify with percentage and message
171-
line,
172-
err and "warn" or notification.notification.level,
173-
{ replace = notification.notification.id, title = "CMakeTools" }
174-
)
163+
local notify_update_line = function(ntfy)
164+
return function(out, err)
165+
if not ntfy.enabled then
166+
return
167+
end
168+
local line = err and err or out
169+
if line ~= nil then
170+
if line and vim.fn.match(line, "^%[%s*(%d+)%s*%%%]") then -- only show lines containing build progress e.g [ 12%]
171+
ntfy:notify(line, err and "warn" or "info")
172+
ntfy:startSpinner()
173+
end
175174
end
176175
end
177176
end
@@ -185,44 +184,31 @@ end
185184
---@param runner runner_conf the executor or runner
186185
---@param on_success nil|function extra arguments, f.e on_success is a callback to be called when the process finishes
187186
---@return nil
188-
function utils.run(cmd, env_script, env, args, cwd, runner, on_success, cmake_notifications)
187+
function utils.run(cmd, env_script, env, args, cwd, runner, on_success)
189188
-- save all
190189
vim.cmd("silent exec " .. '"wall"')
191190

192-
notification.notification = cmake_notifications
193-
notification.notification.enabled = cmake_notifications.runner.enabled
191+
local ntfy = notification:new("runner")
194192

195-
if notification.notification.enabled then
196-
notification.notification.spinner_idx = 1
197-
notification.notification.level = "info"
198-
199-
notification.notification.id =
200-
notification.notify(cmd, notification.notification.level, { title = "CMakeTools" })
201-
notification.update_spinner()
202-
end
193+
ntfy:notify(cmd, "info")
203194

204195
local _mes =
205196
{ "[RUN]:", cmd, table.concat(args, " "), "<ENV>", table.concat(env, " "), "{CWD}", cwd }
206197
scratch.append(table.concat(_mes, " "))
207198

208199
utils.get_runner(runner.name).run(cmd, env_script, env, args, cwd, runner.opts, function(code)
209200
local msg = "Exited with code " .. code
210-
local level = cmake_notifications.level
211201
local icon = ""
202+
local level = nil -- use the previously defined level
212203
if code ~= 0 then
213204
level = "error"
214205
icon = ""
215206
end
216-
notification.notify(
217-
msg,
218-
level,
219-
{ icon = icon, replace = notification.notification.id, timeout = 3000 }
220-
)
221-
notification.notification = {} -- reset and stop update_spinner
207+
ntfy:notify(msg, level, { icon = icon, timeout = 3000 })
222208
if code == 0 and on_success then
223209
on_success()
224210
end
225-
end, notify_update_line)
211+
end, notify_update_line(ntfy))
226212
end
227213

228214
---Run a command using specified executor, this is used by generate, build, clean, install, etc.
@@ -234,21 +220,12 @@ end
234220
---@param executor executor_conf the executor or runner
235221
---@param on_success nil|function extra arguments, f.e on_success is a callback to be called when the process exits with a 0 exit code
236222
---@return nil
237-
function utils.execute(cmd, env_script, env, args, cwd, executor, on_success, cmake_notifications)
223+
function utils.execute(cmd, env_script, env, args, cwd, executor, on_success)
238224
-- save all
239225
vim.cmd("silent exec " .. '"wall"')
240226

241-
notification.notification = cmake_notifications
242-
notification.notification.enabled = cmake_notifications.executor.enabled
243-
244-
if notification.notification.enabled then
245-
notification.notification.spinner_idx = 1
246-
notification.notification.level = "info"
247-
248-
notification.notification.id =
249-
notification.notify(cmd, notification.notification.level, { title = "CMakeTools" })
250-
notification.update_spinner()
251-
end
227+
local ntfy = notification:new("executor")
228+
ntfy:notify(cmd, "info")
252229

253230
local _mes =
254231
{ "[EXECUTE]:", cmd, table.concat(args, " "), "<ENV>", table.concat(env, " "), "{CWD}", cwd }
@@ -257,23 +234,19 @@ function utils.execute(cmd, env_script, env, args, cwd, executor, on_success, cm
257234
utils
258235
.get_executor(executor.name)
259236
.run(cmd, env_script, env, args, cwd, executor.opts, function(code)
237+
ntfy:stopSpinner()
260238
local msg = "Exited with code " .. code
261-
local level = cmake_notifications.level
239+
local level = nil -- use the previously defined level
262240
local icon = ""
263241
if code ~= 0 then
264242
level = "error"
265243
icon = ""
266244
end
267-
notification.notify(
268-
msg,
269-
level,
270-
{ icon = icon, replace = notification.notification.id, timeout = 3000 }
271-
)
272-
notification.notification = {} -- reset and stop update_spinner
245+
ntfy:notify(msg, level, { icon = icon, timeout = 3000 })
273246
if code == 0 and type(on_success) == "function" then
274247
on_success()
275248
end
276-
end, notify_update_line)
249+
end, notify_update_line(ntfy))
277250
end
278251

279252
return utils

0 commit comments

Comments
 (0)