Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 3 additions & 14 deletions lua/flutter-tools/devices.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ local commands = lazy.require("flutter-tools.commands") ---@module "flutter-tool
local executable = lazy.require("flutter-tools.executable") ---@module "flutter-tools.executable"
local config = lazy.require("flutter-tools.config") ---@module "flutter-tools.config"
local fmt = string.format
local api = vim.api

---@alias Device {name: string, id: string, platform: string, system: string, type: integer, cold_boot: boolean}

Expand Down Expand Up @@ -175,19 +174,9 @@ end
---@param title string
---@return fun(status: "success"|"failed")
local function start_progress(title)
if vim.fn.has("nvim-0.12") == 0 then
return function() end
end
local opts = { kind = "progress", source = "flutter-tools", title = title, status = "running" }
local id = api.nvim_echo({ { "Loading" } }, false, opts)
return function(status)
local message = status == "success" and "Done" or "Failed"
api.nvim_echo(
{ { message } },
false,
vim.tbl_extend("force", opts, { id = id, status = status })
)
end
local progress = ui.progress(title)
progress:report("Loading", "running")
return function(status) progress:report(status == "success" and "Done" or "Failed", status) end
end

-----------------------------------------------------------------------------//
Expand Down
31 changes: 31 additions & 0 deletions lua/flutter-tools/runners/debugger_runner.lua
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,21 @@ local function handle_inspect_event(isolate_id)
end)
end

---@type table<string, flutter.Progress>
local active_progress = {}

local function end_all_progress()
for progress_id, progress in pairs(active_progress) do
progress:report("Stopped", "failed")
active_progress[progress_id] = nil
end
end

local listened_events = {
{ "after", "event_output" },
{ "before", "event_progressStart" },
{ "before", "event_progressUpdate" },
{ "before", "event_progressEnd" },
{ "before", "event_exited" },
{ "before", "event_terminated" },
{ "before", "event_app.started" },
Expand Down Expand Up @@ -197,7 +210,24 @@ local function register_dap_listeners(on_run_data, on_run_exit)
end
end

dap.listeners.before["event_progressStart"][plugin_identifier] = function(_, body)
local progress = active_progress[body.progressId] or ui.progress(body.title)
active_progress[body.progressId] = progress
progress:report(body.message, "running", { percent = body.percentage })
end
dap.listeners.before["event_progressUpdate"][plugin_identifier] = function(_, body)
local progress = active_progress[body.progressId]
if progress then progress:report(body.message, "running", { percent = body.percentage }) end
end
dap.listeners.before["event_progressEnd"][plugin_identifier] = function(_, body)
local progress = active_progress[body.progressId]
if not progress then return end
progress:report(body.message or "Done", "success")
active_progress[body.progressId] = nil
end

local handle_termination = function()
end_all_progress()
if next(before_start_logs) ~= nil then on_run_exit(before_start_logs) end
if vm_service.is_connected() then vm_service.disconnect() end
end
Expand Down Expand Up @@ -271,6 +301,7 @@ dap.listeners.on_session[plugin_identifier] = function(_, session)
return
end
tracked_session_id = session.id
session.on_close[plugin_identifier] = end_all_progress
local listeners = pending_listeners or get_untracked_session_listeners()
pending_listeners = nil
if listeners then
Expand Down
62 changes: 62 additions & 0 deletions lua/flutter-tools/ui.lua
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,66 @@ function M.open_win(opts, on_open)
end
end

M.progress_timing = {
-- Ghostty drops an OSC 9;4 progress bar after 15s without an update.
keepalive_ms = 10000,
-- Give up on a progress with no real update in this long, so a hung or crashed process cannot
-- keep the bar alive forever.
stall_ms = 2 * 60 * 1000,
}

---@alias flutter.ProgressStatus "running"|"success"|"failed"

---@class flutter.Progress
---@field report fun(self: flutter.Progress, message: string?, status: flutter.ProgressStatus, opts: {percent?: number, history?: boolean}?)

---A native progress message (Neovim 0.12+) that stays visible in terminals which expire idle
---progress bars. Reporting keeps one message id, so later reports update it in place.
---@param title string
---@return flutter.Progress
function M.progress(title)
if vim.fn.has("nvim-0.12") == 0 then return { report = function() end } end
local id, message, percent, timer
local last_update = 0

local function stop_keepalive()
if not timer then return end
timer:stop()
timer:close()
timer = nil
end

local function echo(status, history)
id = api.nvim_echo({ { message or title } }, history or false, {
id = id,
kind = "progress",
source = "flutter-tools",
title = title,
status = status,
percent = percent,
})
end

local function keepalive()
if not timer then return end
if vim.uv.now() - last_update > M.progress_timing.stall_ms then return stop_keepalive() end
echo("running")
end

return {
report = function(_, new_message, status, opts)
opts = opts or {}
message = new_message or message
percent = opts.percent and math.floor(opts.percent)
last_update = vim.uv.now()
echo(status, opts.history)
if status ~= "running" then return stop_keepalive() end
if timer then return end
timer = assert(vim.uv.new_timer())
local interval = M.progress_timing.keepalive_ms
timer:start(interval, interval, vim.schedule_wrap(keepalive))
end,
}
end

return M
18 changes: 9 additions & 9 deletions lua/flutter-tools/widget_preview.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ local OUTPUT_LINES_ON_FAILURE = 15
---@field cancelled boolean
---@field output string[] recent output, shown if the previewer fails
---@field dir string
---@field progress_id (integer|string)?
---@field progress flutter.Progress

---@type flutter.WidgetPreviewState?
local state = nil
Expand Down Expand Up @@ -58,13 +58,7 @@ end
---@param message string
---@param status "running"|"success"|"failed"
local function report(current, message, status)
current.progress_id = api.nvim_echo({ { message } }, status ~= "running", {
id = current.progress_id,
kind = "progress",
source = "flutter-tools",
title = "Widget preview",
status = status,
})
current.progress:report(message, status, { history = status ~= "running" })
end

---@param current flutter.WidgetPreviewState
Expand Down Expand Up @@ -208,7 +202,13 @@ function M.show(dir)
if not project_dir then return ui.notify("Unable to find a Flutter project", ui.ERROR) end

---@type flutter.WidgetPreviewState
local current = { started = false, cancelled = false, output = {}, dir = project_dir }
local current = {
started = false,
cancelled = false,
output = {},
dir = project_dir,
progress = ui.progress("Widget preview"),
}
state = current
report(current, "Starting", "running")
dtd.start(function(err)
Expand Down
63 changes: 62 additions & 1 deletion tests/debugger_runner_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ describe("debugger runner", function()
dap.adapters.dart = nil
dap.configurations.dart = nil
dap.listeners.after["event_output"]["flutter-tools"] = nil
for _, event in ipairs({ "event_progressStart", "event_progressUpdate", "event_progressEnd" }) do
dap.listeners.before[event]["flutter-tools"] = nil
end
runner.on_untracked_session(function() end)
package.loaded["flutter-tools.runners.debugger_runner"] = nil
end)
Expand Down Expand Up @@ -57,7 +60,10 @@ describe("debugger runner", function()
}
end)

dap.listeners.on_session["flutter-tools"](nil, { id = 1, config = { type = "dart" } })
dap.listeners.on_session["flutter-tools"](
nil,
{ id = 1, config = { type = "dart" }, on_close = {} }
)
dap.listeners.after["event_output"]["flutter-tools"](nil, {
category = "stderr",
output = "first\nsecond",
Expand All @@ -74,4 +80,59 @@ describe("debugger runner", function()

assert.is_false(called)
end)

it("reports adapter progress as native progress messages", function()
local reported = {}
local autocmd = vim.api.nvim_create_autocmd("Progress", {
callback = function(ev)
table.insert(reported, { ev.data.title, ev.data.status, ev.data.percent })
end,
})
runner.on_untracked_session(function()
return { on_run_data = function() end, on_run_exit = function() end }
end)
dap.listeners.on_session["flutter-tools"](
nil,
{ id = 3, config = { type = "dart" }, on_close = {} }
)

local before = dap.listeners.before
before["event_progressStart"]["flutter-tools"](nil, {
progressId = "launch",
title = "Flutter",
message = "Launching…",
})
before["event_progressUpdate"]["flutter-tools"](nil, { progressId = "launch", percentage = 50 })
before["event_progressUpdate"]["flutter-tools"](nil, { progressId = "unknown", message = "x" })
before["event_progressEnd"]["flutter-tools"](nil, { progressId = "launch" })
vim.api.nvim_del_autocmd(autocmd)

assert.are.same({
{ "Flutter", "running" },
{ "Flutter", "running", 50 },
{ "Flutter", "success" },
}, reported)
end)

it("fails running progress when the session closes without ending it", function()
local statuses = {}
local autocmd = vim.api.nvim_create_autocmd("Progress", {
callback = function(ev) table.insert(statuses, ev.data.status) end,
})
runner.on_untracked_session(function()
return { on_run_data = function() end, on_run_exit = function() end }
end)
local session = { id = 4, config = { type = "dart" }, on_close = {} }
dap.listeners.on_session["flutter-tools"](nil, session)

dap.listeners.before["event_progressStart"]["flutter-tools"](nil, {
progressId = "launch",
title = "Flutter",
message = "Launching…",
})
session.on_close["flutter-tools"](session)
vim.api.nvim_del_autocmd(autocmd)

assert.are.same({ "running", "failed" }, statuses)
end)
end)
3 changes: 3 additions & 0 deletions tests/devices_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ INFO | Storing crashdata in: /tmp/android-ts/emu-crash-34.2.14.db, detection
notify = function(msg, level)
if msg ~= "" then table.insert(notifications, { msg = msg, level = level }) end
end,
progress = function()
return { report = function() end }
end,
}
package.loaded["plenary.job"] = {
new = function(_, opts)
Expand Down
47 changes: 47 additions & 0 deletions tests/ui_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
describe("ui.progress", function()
local ui
local original_timing
local events
local autocmd

before_each(function()
ui = require("flutter-tools.ui")
original_timing = ui.progress_timing
events = {}
autocmd = vim.api.nvim_create_autocmd("Progress", {
callback = function(ev) table.insert(events, { ev.data.status, ev.data.percent }) end,
})
end)

after_each(function()
ui.progress_timing = original_timing
vim.api.nvim_del_autocmd(autocmd)
end)

it("resends a running progress so terminals keep the bar", function()
ui.progress_timing = { keepalive_ms = 10, stall_ms = 60000 }
local progress = ui.progress("Test")

progress:report("Working", "running", { percent = 40 })
vim.wait(100, function() return #events >= 3 end)
progress:report("Done", "success")
local count = #events
vim.wait(50)

assert.is_true(count >= 4)
assert.are.same({ "running", 40 }, events[2])
assert.are.same({ "success" }, events[count])
assert.are.equal(count, #events)
end)

it("stops resending a progress that has not been updated for too long", function()
ui.progress_timing = { keepalive_ms = 10, stall_ms = 0 }
local progress = ui.progress("Test")

progress:report("Working", "running")
vim.wait(50)

assert.are.same({ { "running" } }, events)
progress:report("Done", "success")
end)
end)
Loading