Skip to content

Commit

Permalink
feat: public progress API for non-lsp use cases (#178)
Browse files Browse the repository at this point in the history
Co-authored-by: John Hui <11800204+j-hui@users.noreply.github.com>
  • Loading branch information
willothy and j-hui committed Dec 7, 2023
1 parent b045c3e commit d81cc08
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 0 deletions.
58 changes: 58 additions & 0 deletions doc/fidget-api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Table of Contents *fidget.api.toc*
Notification subsystem ··································· |fidget.notification|
LSP progress subsystem ······································· |fidget.progress|
Neovim LSP shim layer ···································· |fidget.progress.lsp|
Non-LSP progress messages ····························· |fidget.progress.handle|
Spinner animations ············································ |fidget.spinner|

*fidget.api*
Expand Down Expand Up @@ -245,6 +246,63 @@ M.poll_for_messages() *fidget.progress.lsp.poll_for_messages*
|fidget.progress.lsp.ProgressMessage|


==============================================================================
Non-LSP progress messages *fidget.progress.handle*

ProgressHandle : ProgressMessage *fidget.progress.handle.ProgressHandle*
A handle for a progress message, reactive to changes

Fields: ~
{cancel} (fun(self:ProgressHandle)) Cancel the task
{finish} (fun(self:ProgressHandle)) Mark the task as complete
{report} (fun(self:ProgressHandle,msg:ProgressMessage|table<string,any>)) Update one or more properties of the progress message


handle.create({message}) *fidget.progress.handle.create*
Create a new progress message, and return a handle to it for updating.
The handle is a reactive object, so you can update its properties and the
message will be updated accordingly. You can also use the `report` method to
update multiple properties at once.

Example:

>lua
local progress = require("fidget.progress")

local handle = progress.handle.create({
title = "My Task",
message = "Doing something...",
lsp_client = { name = "my_fake_lsp" },
percentage = 0,
})

-- You can update properties directly and the
-- progress message will be updated accordingly
handle.message = "Doing something else..."

-- Or you can use the `report` method to bulk-update
-- properties.
handle:report({
title = "The task status changed"
message = "Doing another thing...",
percentage = 50,
})

-- You can also cancel the task (errors if not cancellable)
handle:cancel()

-- Or mark it as complete (updates percentage to 100 automatically)
handle:finish()
<


Parameters: ~
{message} (ProgressMessage|table<string,any>) The initial progress message

Returns: ~
(ProgressHandle) @nodiscard


==============================================================================
Spinner animations *fidget.spinner*

Expand Down
1 change: 1 addition & 0 deletions lua/fidget/progress.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
local progress = {}
progress.display = require("fidget.progress.display")
progress.lsp = require("fidget.progress.lsp")
progress.handle = require("fidget.progress.handle")
local poll = require("fidget.poll")
local notification = require("fidget.notification")
local logger = require("fidget.logger")
Expand Down
158 changes: 158 additions & 0 deletions lua/fidget/progress/handle.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---@mod fidget.progress.handle Non-LSP progress messages
local handle = {}

local notification = require("fidget.notification")

local next_id = 0
local prefix = "fidget-progress-handle-"

local function next_token()
next_id = next_id + 1
return prefix .. tostring(next_id)
end

--- A handle for a progress message, reactive to changes
---@class ProgressHandle: ProgressMessage
---@field cancel fun(self: ProgressHandle) Cancel the task
---@field finish fun(self: ProgressHandle) Mark the task as complete
---@field report fun(self: ProgressHandle, msg: ProgressMessage|table<string,any>) Update one or more properties of the progress message
---@field private _raw ProgressMessage The internal progress message data
---@field private _proxy userdata A proxy object used to handle cleanup
local ProgressHandle = {}

function ProgressHandle.new(message)
local progress = require("fidget.progress")

local self = {}

-- Use a proxy with __gc to handle cleanup, ensuring that we don't
-- leak notifications if the user doesn't call finish() or cancel().
self._proxy = newproxy(true)
self._raw = message

getmetatable(self._proxy).__gc = function()
if not self._raw.done then
self._raw.done = true
notification.notify(progress.format_progress(self._raw))
end
end

setmetatable(self, ProgressHandle)

-- Load the notification config
progress.load_config(self._raw)

-- Initial update (for begin)
notification.notify(progress.format_progress(self._raw))

return self
end

function ProgressHandle:__newindex(k, v)
if k == "token" then
error("notification tokens cannot be modified")
end
self._raw[k] = v
notification.notify(require("fidget.progress").format_progress(self._raw))
end

function ProgressHandle:__index(k)
return ProgressHandle[k] or self._raw[k]
end

function ProgressHandle:report(props)
if self._raw.done then
return
end
props.token = nil
for k, v in pairs(props) do
self._raw[k] = v
end
notification.notify(require("fidget.progress").format_progress(self._raw))
end

function ProgressHandle:cancel()
if self._raw.done then
return
end
if self._raw.cancellable then
self._raw.done = true
notification.notify(require("fidget.progress").format_progress(self._raw))
else
error("attempted to cancel non-cancellable progress")
end
end

function ProgressHandle:finish()
if self._raw.done then
return
end
self._raw.done = true
if self._raw.percentage ~= nil then
self._raw.percentage = 100
end
notification.notify(require("fidget.progress").format_progress(self._raw))
end

--- Create a new progress message, and return a handle to it for updating.
--- The handle is a reactive object, so you can update its properties and the
--- message will be updated accordingly. You can also use the `report` method to
--- update multiple properties at once.
---
--- Example:
---
--->lua
--- local progress = require("fidget.progress")
---
--- local handle = progress.handle.create({
--- title = "My Task",
--- message = "Doing something...",
--- lsp_client = { name = "my_fake_lsp" },
--- percentage = 0,
--- })
---
--- -- You can update properties directly and the
--- -- progress message will be updated accordingly
--- handle.message = "Doing something else..."
---
--- -- Or you can use the `report` method to bulk-update
--- -- properties.
--- handle:report({
--- title = "The task status changed"
--- message = "Doing another thing...",
--- percentage = 50,
--- })
---
--- -- You can also cancel the task (errors if not cancellable)
--- handle:cancel()
---
--- -- Or mark it as complete (updates percentage to 100 automatically)
--- handle:finish()
---<
---
---@param message ProgressMessage|table<string, any> The initial progress message
---@return ProgressHandle
---@nodiscard
function handle.create(message)
message = message and vim.deepcopy(message) or {}

-- Generate a unique token for this message
message.token = next_token()

-- Set required fields
message.lsp_client = message.lsp_client or {
name = "fidget",
}
if message.done == nil then
message.done = false
end

-- Cancellable by default
if message.cancellable == nil then
message.cancellable = true
end

return ProgressHandle.new(message)
end

return handle
1 change: 1 addition & 0 deletions scripts/build-api-docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ esac
lua/fidget/notification.lua \
lua/fidget/progress.lua \
lua/fidget/progress/lsp.lua \
lua/fidget/progress/handle.lua \
lua/fidget/spinner.lua \
> doc/fidget-api.txt

Expand Down

0 comments on commit d81cc08

Please sign in to comment.