diff --git a/lua/flutter-tools/commands.lua b/lua/flutter-tools/commands.lua index 1ab2d78..54a2b4f 100644 --- a/lua/flutter-tools/commands.lua +++ b/lua/flutter-tools/commands.lua @@ -87,10 +87,31 @@ local function has_recoverable_error(lines) return false, nil end +local ERROR_NOTIFY_DELAY_MS = 200 +---@type string[] +local pending_errors = {} +local error_timer = nil + +local function flush_errors() + if #pending_errors == 0 then return end + local lines = pending_errors + pending_errors = {} + ui.notify(lines, ui.ERROR, { timeout = 5000 }) +end + +---stderr arrives one line at a time, so collect a burst of lines into a single notification +---@param line string +local function queue_error_notification(line) + table.insert(pending_errors, line) + if not error_timer then error_timer = assert(vim.uv.new_timer()) end + error_timer:stop() + error_timer:start(ERROR_NOTIFY_DELAY_MS, 0, vim.schedule_wrap(flush_errors)) +end + ---Handle output from flutter run command ---@param is_err boolean if this is stdout or stderr local function on_run_data(is_err, data) - if is_err and config.dev_log.notify_errors then ui.notify(data, ui.ERROR, { timeout = 5000 }) end + if is_err and config.dev_log.notify_errors then queue_error_notification(data) end update_device_from_output(data) dev_log.log(data) end @@ -660,6 +681,7 @@ if __TEST then M.__get_run_args = get_run_args M.__update_device_from_output = update_device_from_output M.__set_current_device = set_current_device + M.__on_run_data = on_run_data end return M diff --git a/lua/flutter-tools/runners/debugger_runner.lua b/lua/flutter-tools/runners/debugger_runner.lua index 855447a..8524b2a 100644 --- a/lua/flutter-tools/runners/debugger_runner.lua +++ b/lua/flutter-tools/runners/debugger_runner.lua @@ -239,7 +239,7 @@ function DebuggerRunner:run( if body and body.output then for line in body.output:gmatch("[^\r\n]+") do if not started then table.insert(before_start_logs, line) end - on_run_data(body.category == "sterr", line) + on_run_data(body.category == "stderr", line) end end end, @@ -305,7 +305,7 @@ function DebuggerRunner:attach(paths, args, cwd, on_run_data, on_run_exit) if body and body.output then for line in body.output:gmatch("[^\r\n]+") do if not started then table.insert(before_start_logs, line) end - on_run_data(body.category == "sterr", line) + on_run_data(body.category == "stderr", line) end end end, function(before_start_logs) on_run_exit(before_start_logs, args) end) diff --git a/tests/commands_spec.lua b/tests/commands_spec.lua index aca979d..139577b 100644 --- a/tests/commands_spec.lua +++ b/tests/commands_spec.lua @@ -109,4 +109,23 @@ describe("commands", function() assert.is_nil(commands.current_device()) end) + + it("should batch consecutive error lines into one notification", function() + local config = require("flutter-tools.config") + local ui = require("flutter-tools.ui") + local original_notify = ui.notify + local notifications = {} + ui.notify = function(msg) table.insert(notifications, msg) end + config.dev_log.notify_errors = true + + commands.__on_run_data(true, "Error: first line") + commands.__on_run_data(true, "second line") + commands.__on_run_data(false, "regular output") + commands.__on_run_data(true, "third line") + vim.wait(1000, function() return #notifications > 0 end) + + config.dev_log.notify_errors = false + ui.notify = original_notify + assert.are.same({ { "Error: first line", "second line", "third line" } }, notifications) + end) end)