app.progress - Progress Dialog API
Show and drive progress dialogs.
Permission: L0 Safe — no declaration needed, always available
The module has a single entry point: app.progress.create(config) shows a dialog and
returns a handle; everything after that is a method on the handle.
local p = app.progress.create({ title = "Working" })
p:update(50, "Halfway there")
p:close()
Handle methods are called with a colon (p:update(...)), not a dot.
app.progress.create(config)
Creates and shows a progress dialog.
Parameters:
config(table):title(string, required) - Dialog titlemessage(string) - Detail message, empty by defaultindeterminate(boolean) - Indeterminate progress (defaulttrue, a sliding bar)cancellable(boolean) - Show a Cancel button (defaultfalse)onCancel(function) - Called when the user clicks Cancel
Returns: progressHandle; returns nil, error on failure
local p, err = app.progress.create({
title = "Downloading",
message = "Preparing...",
indeterminate = false,
cancellable = true,
onCancel = function()
app.log.info("Download cancelled")
end
})
if not p then
app.log.error("Could not create progress dialog: " .. tostring(err))
return
end
Omitting title fails outright — it is the only required field.
Handle methods
handle:update(value, message?)
Updates the progress value and optionally the message.
Parameters:
value(number) - Progress, 0–100 (not 0–1)message(string, optional) - Also update the message text
for i = 1, #files do
p:update(i * 100 / #files, "Processing: " .. app.path.basename(files[i]))
end
Calling
update()switches an indeterminate dialog to determinate. Even when created withindeterminate = true, the firstupdate()turns the sliding bar into a filled bar driven byvalue. To stay indeterminate, use onlysetMessage().
handle:setMessage(message)
Updates only the message text. It does not touch the progress value and does not switch an indeterminate dialog to determinate.
Parameters:
message(string) - The new message
p:setMessage("Running step 2...")
handle:isCancelled()
Whether the user has clicked Cancel. Requires cancellable = true at creation.
Returns: boolean
if p:isCancelled() then
p:close()
return
end
handle:close()
Closes the dialog.
p:close()
Two ways to handle cancellation
With cancellable = true there are two approaches; pick one based on the shape of your
work:
Poll isCancelled() — good for batch loops, where the exit point is visible:
for i = 1, 100 do
if p:isCancelled() then break end
p:update(i, "Step " .. i)
end
The onCancel callback — good when there is no loop to check inside (waiting on an
async result, for example):
local cancelled = false
local p = app.progress.create({
title = "Long operation",
cancellable = true,
onCancel = function() cancelled = true end
})
They can be combined, but there is rarely a reason to.
Examples
Batch file processing
function MyPlugin:handleBatchProcess(context)
local files = context.selectedFiles
local total = #files
local p = app.progress.create({
title = "Batch processing",
message = "Preparing...",
indeterminate = false,
cancellable = true
})
if not p then return end
local processed = 0
for i, file in ipairs(files) do
if p:isCancelled() then break end
p:update(i * 100 / total, "Processing: " .. app.path.basename(file))
self:processFile(file)
processed = processed + 1
end
p:close()
app.notification.show("Done", "Processed " .. processed .. " files")
end
Indeterminate progress (unknown duration)
function MyPlugin:handleSearch(context)
local p = app.progress.create({
title = "Searching",
message = "Looking through files...",
indeterminate = true
})
if not p then return end
-- Only setMessage here, never update — update would turn the bar solid
p:setMessage("Scanning subdirectories...")
local results = self:searchFiles()
p:close()
app.notification.show("Done", "Found " .. #results .. " files")
end
Several at once
Every create() is an independent dialog; they do not interfere:
function MyPlugin:handleParallelDownload(context)
local urls = {"url1", "url2", "url3"}
local bars = {}
for i, url in ipairs(urls) do
bars[i] = app.progress.create({
title = "Download " .. i,
message = "Preparing...",
indeterminate = false
})
end
for pct = 1, 100 do
for _, p in ipairs(bars) do
if p then p:update(pct, "Downloading... " .. pct .. "%") end
end
end
for _, p in ipairs(bars) do
if p then p:close() end
end
end