diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 0000000..b6f29ab --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,39 @@ +name: Generate docs + +on: + push: + branches: + - main + - gen-docs + paths: + - '.github/**' + - 'doc/**' + - 'lua/**' + +jobs: + docs: + runs-on: ubuntu-latest + name: Generate Fidget documentation + steps: + - uses: actions/checkout@v2 + + - name: API docs from code + run: | + curl -Lq https://github.com/numToStr/lemmy-help/releases/latest/download/lemmy-help-x86_64-unknown-linux-gnu.tar.gz | tar xz + ./lemmy-help -f -a -c -t lua/{fidget.lua,fidget/notification.lua,fidget/progress.lua,fidget/progress/lsp.lua,fidget/spinner.lua} > doc/fidget-api.txt + + - name: Usage docs from README (panvimdoc) + uses: kdheepak/panvimdoc@main + with: + pandoc: README.md + vimdoc: fidget + description: Extensible UI for Neovim notifications and LSP progress messages. + version: "NVIM v0.8.0" + demojify: true + treesitter: true + shiftheadinglevelby: -1 + + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: "Auto generate docs" + branch: ${{ github.head_ref }} diff --git a/.github/workflows/panvimdoc.yml b/.github/workflows/panvimdoc.yml deleted file mode 100644 index d0bef13..0000000 --- a/.github/workflows/panvimdoc.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: panvimdoc - -on: - push: - branches: - - main - paths: - - '.github/**' - - 'doc/**' - -jobs: - docs: - runs-on: ubuntu-latest - name: pandoc to vimdoc - steps: - - uses: actions/checkout@v2 - - name: panvimdoc - uses: kdheepak/panvimdoc@main - with: - vimdoc: fidget - description: Standalone UI for nvim-lsp progress - pandoc: doc/fidget.md - - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: "Auto generate docs" - branch: ${{ github.head_ref }} diff --git a/.gitignore b/.gitignore index 0a56e3f..61271ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +/.luarc.json /doc/tags diff --git a/README.md b/README.md index b76cbd7..738d9c0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# fidget.nvim + + +# 😵 Fidget Extensible UI for Neovim notifications and LSP progress messages. @@ -65,7 +67,9 @@ who doesn't love a little bit of terminal eye candy, as a treat? [lsp-progress]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#progress [vim-notify]: https://neovim.io/doc/user/lua.html#vim.notify() -## Quickstart + + +## Getting Started ### Requirements @@ -76,7 +80,6 @@ with an LSP server that uses the [`$/progress`][lsp-progress] handler. For an up-to-date list of LSP servers this plugin is known to work with, see [this Wiki page](https://github.com/j-hui/fidget.nvim/wiki/Known-compatible-LSP-servers). - ### Installation Install this plugin using your favorite plugin manager. @@ -98,19 +101,133 @@ See the [documentation](doc/fidget.md) for `setup()` options. ```vim Plug 'j-hui/fidget.nvim' -``` -Make sure the plugin is installed run `:PlugInstall`. +" Make sure the plugin is installed using :PlugInstall. Then, somewhere after plug#end(): +lua < + +For more details, see [fidget-option.txt](doc/fidget-option.txt). + + + + + +## Lua API + + + +Fidget has a Lua API, with [documentation](doc/fidget-api.txt) generated from +source code. You are encouraged to hack around with that. + + + + + +## Highlights + +Rather than defining its own highlights, Fidget uses built-in highlight groups +that are typically overridden by custom Vim color schemes. With any luck, these +will look reasonable when rendered, but the visual outcome will really depend +on what your color scheme decided to do with those highlight groups. + ## Related Work [rcarriga/nvim-notify](https://github.com/rcarriga/nvim-notify) is first and diff --git a/doc/fidget-option.txt b/doc/fidget-option.txt new file mode 100644 index 0000000..6c8cf8c --- /dev/null +++ b/doc/fidget-option.txt @@ -0,0 +1,730 @@ +*fidget-option.txt* Fidget setup() options + +============================================================================== + +This file contains detailed documentation about all the setup() options that +Fidget supports. + +For general documentation, see |fidget.txt|. + +For Fidget's Lua API documentation, see |fidget-api.txt|. + +============================================================================== + +progress.poll_rate *fidget.option.progress.poll_rate* + + How and when to poll for progress messages + + Set to `0` to immediately poll on each |LspProgress| event. + + Set to a positive number to poll for progress messages at the specified + frequency (Hz, i.e., polls per second). Combining a slow `poll_rate` + (e.g., `0.5`) with the `ignore_done_already` setting can be used to filter + out short-lived progress tasks, de-cluttering notifications. + + Note that if too many LSP progress messages are sent between polls, + Neovim's progress ring buffer will overflow and messages will be + overwritten (dropped), possibly causing stale progress notifications. + Workarounds include using the |fidget.option.progress.lsp.progress_ringbuf_size| + option, or manually calling |fidget.notification.reset| (see #167). + + Set to `false` to disable polling altogether; you can still manually poll + progress messages by calling |fidget.progress.poll|. + + Type: ~ + `number|false` + + Default: ~ + `0` + + +progress.suppress_on_insert *fidget.option.progress.suppress_on_insert* + + Suppress new messages while in insert mode + + Note that progress messages for new tasks will be dropped, but existing + tasks will be processed to completion. + + Type: ~ + `boolean` + + Default: ~ + `false` + + +progress.ignore_done_already *fidget.option.progress.ignore_done_already* + + Ignore new tasks that are already complete + + This is useful if you want to avoid excessively bouncy behavior, and only + seeing notifications for long-running tasks. Works best when combined with + a low `poll_rate`. + + Type: ~ + `boolean` + + Default: ~ + `false` + + +progress.ignore_empty_message *fidget.option.progress.ignore_empty_message* + + Ignore new tasks that don’t contain a message + + Some servers may send empty messages for tasks that don’t actually exist. + And if those tasks are never completed, they will become stale in Fidget. + This option tells Fidget to ignore such messages unless the LSP server + has anything meaningful to say. (See #171) + + Note that progress messages for new empty tasks will be dropped, but + existing tasks will be processed to completion. + + Type: ~ + `boolean` + + Default: ~ + `false` + + +progress.notification_group *fidget.option.progress.notification_group* + + How to get a progress message’s notification group key + + Set this to return a constant to group all LSP progress messages together, + e.g., + +>lua + notification_group = function(msg) + -- N.B. you may also want to configure this group key ("lsp_progress") + -- using progress.display.overrides or notification.configs + return "lsp_progress" + end +< + + Type: ~ + `fun(msg: ProgressMessage): Key` + + Default: ~ +>lua + function(msg) + return msg.lsp_client.name + end +< + + +progress.clear_on_detach *fidget.option.progress.clear_on_detach* + + Clear notification group when LSP server detaches + + This option should be set to a function that, given a client ID number, + returns the notification group to clear. No group will be cleared if + the function returns `nil`. + + The default setting looks up and returns the LSP client name, which is + also used by |fidget.option.progress.notification_group|. + + Set this option to `false` to disable this feature entirely (no + |LspDetach| callback will be installed). + + Default value: + + Type: ~ + `false|fun(client_id: number): Key` + + Default: ~ +>lua + clear_on_detach = function(client_id) + local client = vim.lsp.get_client_by_id(client_id) + return client and client.name or nil + end +< + + +progress.ignore *fidget.option.progress.ignore* + + List of LSP servers to ignore + + Example: +>lua + ignore = { "rust_analyzer" } +< + + Type: ~ + `Key[]` + + Default: ~ + `{}` + + +progress.display.render_limit *fidget.option.progress.display.render_limit* + + How many LSP messages to show at once + + If `false`, no limit. + + This is used to configure each LSP notification group, so by default, + this is a per-server limit. + + Type: ~ + `number|false` + + Default: ~ + `16` + + +progress.display.done_ttl *fidget.option.progress.display.done_ttl* + + How long a message should persist after completion + + Set to `0` to use notification group config default, and `math.huge` to + show notification indefinitely (until overwritten). + + Measured in seconds. + + Type: ~ + `number` + + Default: ~ + `3` + +progress.display.done_icon *fidget.option.progress.display.done_icon* + + Icon shown when all LSP progress tasks are complete + + When a string literal is given (e.g., `"✔"`), it is used as a static icon; + when a table (e.g., `{"dots"}` or `{ pattern = "clock", period = 2 }`) is + given, it is used to generate an animation function; when a function is + specified (e.g., `function(now) return now % 2 < 1 and "+" or "-" end`), + it is used as the animation function. + + See also: |fidget.spinner.Manga| and |fidget.spinner.Anime|. + + Type: ~ + `string|Manga|Anime` + + Default: ~ + `"✔"` + + +progress.display.done_style *fidget.option.progress.display.done_style* + + Highlight group for completed LSP tasks + + Type: ~ + `string` + + Default: ~ + `"Constant"` + + +progress.display.progress_ttl *fidget.option.progress.display.progress_ttl* + + How long a message should persist when in progress + + Set to `0` to use notification group config default, and `math.huge` to + show notification indefinitely (until overwritten). + + Measured in seconds. + + Type: ~ + `number` + + Default: ~ + `math.huge` + + +progress.display.progress_icon *fidget.option.progress.display.progress_icon* + + Icon shown when LSP progress tasks are in progress + + When a string literal is given (e.g., `"✔"`), it is used as a static icon; + when a table (e.g., `{"dots"}` or `{ pattern = "clock", period = 2 }`) is + given, it is used to generate an animation function; when a function is + specified (e.g., `function(now) return now % 2 < 1 and "+" or "-" end`), + it is used as the animation function. + + Type: ~ + `string|Manga` + + Default: ~ + `{ "dots" }` + + +progress.display.progress_style *fidget.option.progress.display.progress_style* + + Highlight group for in-progress LSP tasks + + Type: ~ + `string` + + Default: ~ + `"WarningMsg"` + + +progress.display.group_style *fidget.option.progress.display.group_style* + + Highlight group for group name (LSP server name) + + Type: ~ + `string` + + Default: ~ + `"Title"` + + +progress.display.icon_style *fidget.option.progress.display.icon_style* + + Highlight group for group icons + + Type: ~ + `string` + + Default: ~ + `"Question"` + + + +progress.display.priority *fidget.option.progress.display.priority* + + Ordering priority for LSP notification group + + Type: ~ + `number|false` + + Default: ~ + `30` + + +progress.display.format_message *fidget.option.progress.display.format_message* + + + How to format a progress message + + + Example: + +>lua + format_message = function(msg) + if string.find(msg.title, "Indexing") then + return nil -- Ignore "Indexing..." progress messages + end + if msg.message then + return msg.message + else + return msg.done and "Completed" or "In progress..." + end + end +< + + Type: ~ + `fun(msg: ProgressMessage): string` + + Default: ~ + `fidget.display.default_format_message` + + where +>lua + function fidget.display.default_format_message(msg) + local message = msg.message + if not message then + message = msg.done and "Completed" or "In progress..." + end + if msg.percentage ~= nil then + message = string.format("%s (%.0f%%)", message, msg.percentage) + end + return message + end +< + +progress.display.format_annote *fidget.option.progress.display.format_annote* + + How to format a progress annotation + + Type: ~ + `fun(msg: ProgressMessage): string` + + Default: ~ + `msg.title` + + + +progress.display.format_group_name *fidget.option.progress.display.format_group_name* + + How to format a progress notification group’s name + + Example: +>lua + format_group_name = function(group) + return "lsp:" .. tostring(group) + end +< + + Type: ~ + `fun(group: NotificationKey): NotificationDisplay` + + Default: ~ + `tostring` + +progress.display.overrides *fidget.option.progress.display.overrides* + + Override options from the default notification config + + Keys of the table are each notification group’s `key`. + + Example: +>lua + overrides = { + hls = { + name = "Haskell Language Server", + priority = 60, + icon = fidget.progress.display.for_icon(fidget.spinner.animate("triangle", 3), "💯"), + }, + rust_analyzer = { + name = "Rust Analyzer", + icon = fidget.progress.display.for_icon(fidget.spinner.animate("arrow", 2.5), "🦀"), + }, + } +< + + Type: ~ + `{ [NotificationKey]: NotificationConfig }` + + Default: ~ + `{ rust_analyzer = { name = "rust-analyzer" } }` + + +progress.lsp.progress_ringbuf_size *fidget.option.progress.lsp.progress_ringbuf_size* + + Configure the nvim’s LSP progress ring buffer size + + Useful for avoiding progress message overflow when the LSP server blasts + more messages than the ring buffer can handle (see #167). + + Leaves the progress ringbuf size at its default if this setting is 0 or + less. Doesn’t do anything for Neovim pre-v0.10.0. + + Type: ~ + `number` + + Default: ~ + `0` + +notification.poll_rate *fidget.option.notification.poll_rate* + + How frequently to poll and render notifications + + Measured in Hertz (frames per second). + + Type: ~ + `number` + + Default: ~ + `10` + + +notification.filter *fidget.option.notification.filter* + + Minimum notifications level + + + Note that this filter only applies to notifications with an explicit + numeric level (i.e., `vim.log.levels`). + + Set to `vim.log.levels.OFF` to filter out all notifications with an + numeric level, or `vim.log.levels.TRACE` to turn off filtering. + + Type: ~ + `vim.log.levels` + + Default: ~ + `vim.log.levels.INFO` + + +notification.override_vim_notify *fidget.option.notification.override_vim_notify* + + Automatically override vim.notify() with Fidget + + Equivalent to the following: +>lua + fidget.setup({ --[[ options ]] }) + vim.notify = fidget.notify +< + + Type: ~ + `boolean` + + Default: ~ + `false` + + +notification.configs *fidget.option.notification.configs* + + How to configure notification groups when instantiated + + A configuration with the key `"default"` should always be specified, and is + used as the fallback for notifications lacking a group key. + + Type: ~ + `{ [NotificationKey]: NotificationConfig }` + + Default: ~ + `{ default = fidget.notification.default_config }`) + + where +>lua + fidget.notification.default_config = { + name = "Notifications", + icon = "❰❰", + ttl = 5, + group_style = "Title", + icon_style = "Special", + annote_style = "Question", + debug_style = "Comment", + warn_style = "WarningMsg", + error_style = "ErrorMsg", + debug_annote = "DEBUG", + info_annote = "INFO", + warn_annote = "WARN", + error_annote = "ERROR", + } + + +notification.view.stack_upwards *fidget.option.notification.view.stack_upwards* + + Display notification items from bottom to top + + Setting this to true tends to lead to more stable animations when the + window is bottom-aligned. + + Type: ~ + `boolean` + + Default: ~ + `true` + + +notification.view.icon_separator *fidget.option.notification.view.icon_separator* + + Separator between group name and icon + + Must not contain any newlines. Set to `""` to remove the gap between names and + icons in all notification groups. + + + Type: ~ + `string` + + Default: ~ + `" "` + + +notification.view.group_separator *fidget.option.notification.view.group_separator* + + Separator between notification groups + + Must not contain any newlines. Set to `nil` to omit separator entirely. + + Type: ~ + `string|nil` + + Default: ~ + `"---"` + + +notification.view.group_separator_hl *fidget.option.notification.view.group_separator_hl* + + Highlight group used for group separator + + Type: ~ + `string|nil` + + Default: ~ + `"Comment"` + + +notification.window.normal_hl *fidget.option.notification.window.normal_hl* + + Base highlight group in the notification window + + Used by any Fidget notification text that is not otherwise highlighted, + i.e., message text. + + Note that we use this blanket highlight for all messages to avoid adding + separate highlights to each line (whose lengths may vary). + + With `winblend` set to anything less than `100`, this will also affect the + background color in the notification box area (see `winblend` docs). + + Type: ~ + `string` + + Default: ~ + `"Comment"` + + +notification.window.winblend *fidget.option.notification.window.winblend* + + Background color opacity in the notification window + + Note that the notification window is rectangular, so any cells covered by + that rectangular area is affected by the background color of `normal_hl`. + With `winblend` set to anything less than `100`, the background of + `normal_hl` will be blended with that of whatever is underneath, + including, e.g., a shaded `colorcolumn`, which is usually not desirable. + + However, if you would like to display the notification window as its own + "boxed" area (especially if you are using a non-"none" `border`), you may + consider setting `winblend` to something less than `100`. + + See also: options for |nvim_open_win()|. + + Type: ~ + `number` + + Default: ~ + `100` + + +notification.window.border *fidget.option.notification.window.border* + + Border around the notification window + + See also: options for |nvim_open_win()|. + + Type: ~ + `"none" | "single" | "double" | "rounded" | "solid" | "shadow" | string[]` + + Default: ~ + `"none"` + + +notification.window.zindex *fidget.option.notification.window.zindex* + + Stacking priority of the notification window + + Note that the default priority for Vim windows is 50. + + See also: options for |nvim_open_win()|. + + Type: ~ + `number` + + Default: ~ + `45` + + +notification.window.width *fidget.option.notification.window.width* + + Maximum width of the notification window + + `0` means no maximum width. + + Type: ~ + `integer` + + Default: ~ + `0` + + + +notification.window.height *fidget.option.notification.window.height* + + Maximum height of the notification window + + `0` means no maximum height. + + Type: ~ + `integer` + + Default: ~ + `0` + + +notification.window.x_padding *fidget.option.notification.window.x_padding* + + Padding from right edge of window boundary + + Type: ~ + `integer` + + Default: ~ + `1` + + +notification.window.y_padding *fidget.option.notification.window.y_padding* + + Padding from bottom edge of window boundary + + Type: ~ + `integer` + + Default: ~ + `0` + + +notification.window.align *fidget.option.notification.window.align* + + How to align the notification window + + Type: ~ + `"top"|"bottom"|"avoid_cursor"` + + Default: ~ + `"bottom"` + + +notification.window.relative *fidget.option.notification.window.relative* + + What the notification window position is relative to + + See also: options for |nvim_open_win()|. + + Type: ~ + `"editor"|"win"` + + Default: ~ + `"editor"` + + +logger.level *fidget-logger.level* + + Minimum logging level + + Set to `vim.log.levels.OFF` to disable logging, or `vim.log.levels.TRACE` + to enable all logging. + + Note that this option only filters logging (useful for debugging), and is + different from `notification.filter`, which filters `notify()` messages. + + Type: ~ + `vim.log.levels` + + Default: ~ + `vim.log.levels.WARN` + + +logger.float_precision *fidget-logger.float_precision* + + Limit the number of decimals displayed for floats + + Type: ~ + `number` + + Default: ~ + `0.01` + + +logger.path *fidget-logger.path* + + Where Fidget writes its logs to + + Using `vim.fn.stdpath("cache")`, the default path usually ends up at + `~/.cache/nvim/fidget.nvim.log`. + + Type: ~ + `string` + + Default: ~ + `string.format("%s/fidget.nvim.log", vim.fn.stdpath("cache"))` + +vim:tw=78:ts=4:ft=help:norl: diff --git a/doc/fidget.md b/doc/fidget.md deleted file mode 100644 index a2cdf6a..0000000 --- a/doc/fidget.md +++ /dev/null @@ -1,824 +0,0 @@ ---- -project: fidget -vimversion: Neovim v0.8.0 -toc: true -description: Extensible UI for Neovim notifications and LSP progress messages ---- - -# Installation - -Install this plugin using your favorite plugin manager. -Once installed, make sure to call its `setup()` function (in Lua), e.g.: - -```lua -require("fidget").setup { - -- options -} -``` - -`setup` takes a table of options as its parameter, used to configure the plugin. - -# Options - -The following table shows the default options for this plugin: - -```lua -{ - -- Options related to LSP progress subsystem - progress = { - poll_rate = 0, -- How and when to poll for progress messages - suppress_on_insert = false, -- Suppress new messages while in insert mode - ignore_done_already = false, -- Ignore new tasks that are already complete - ignore_empty_message = false, -- Ignore new tasks that don't contain a message - clear_on_detach = -- Clear notification group when LSP server detaches - function(client_id) - local client = vim.lsp.get_client_by_id(client_id) - return client and client.name or nil - end, - notification_group = -- How to get a progress message's notification group key - function(msg) return msg.lsp_client.name end, - ignore = {}, -- List of LSP servers to ignore - - -- Options related to how LSP progress messages are displayed as notifications - display = { - render_limit = 16, -- How many LSP messages to show at once - done_ttl = 3, -- How long a message should persist after completion - done_icon = "✔", -- Icon shown when all LSP progress tasks are complete - done_style = "Constant", -- Highlight group for completed LSP tasks - progress_ttl = math.huge, -- How long a message should persist when in progress - progress_icon = -- Icon shown when LSP progress tasks are in progress - { pattern = "dots", period = 1 }, - progress_style = -- Highlight group for in-progress LSP tasks - "WarningMsg", - group_style = "Title", -- Highlight group for group name (LSP server name) - icon_style = "Question", -- Highlight group for group icons - priority = 30, -- Ordering priority for LSP notification group - format_message = -- How to format a progress message - require("fidget.progress.display").default_format_message, - format_annote = -- How to format a progress annotation - function(msg) return msg.title end, - format_group_name = -- How to format a progress notification group's name - function(group) return tostring(group) end, - overrides = { -- Override options from the default notification config - rust_analyzer = { name = "rust-analyzer" }, - }, - }, - - -- Options related to Neovim's built-in LSP client - lsp = { - progress_ringbuf_size = 0, -- Configure the nvim's LSP progress ring buffer size - }, - }, - - -- Options related to notification subsystem - notification = { - poll_rate = 10, -- How frequently to update and render notifications - filter = vim.log.levels.INFO, -- Minimum notifications level - override_vim_notify = false, -- Automatically override vim.notify() with Fidget - configs = -- How to configure notification groups when instantiated - { default = require("fidget.notification").default_config }, - - -- Options related to how notifications are rendered as text - view = { - stack_upwards = true, -- Display notification items from bottom to top - icon_separator = " ", -- Separator between group name and icon - group_separator = "---", -- Separator between notification groups - group_separator_hl = -- Highlight group used for group separator - "Comment", - }, - - -- Options related to the notification window and buffer - window = { - normal_hl = "Comment", -- Base highlight group in the notification window - winblend = 100, -- Background color opacity in the notification window - border = "none", -- Border around the notification window - zindex = 45, -- Stacking priority of the notification window - max_width = 0, -- Maximum width of the notification window - max_height = 0, -- Maximum height of the notification window - x_padding = 1, -- Padding from right edge of window boundary - y_padding = 0, -- Padding from bottom edge of window boundary - align = "bottom", -- How to align the notification window - relative = "editor", -- What the notification window position is relative to - }, - }, - - -- Options related to logging - logger = { - level = vim.log.levels.WARN, -- Minimum logging level - float_precision = 0.01, -- Limit the number of decimals displayed for floats - path = -- Where Fidget writes its logs to - string.format("%s/fidget.nvim.log", vim.fn.stdpath("cache")), - }, -} -``` - -progress.poll_rate -: How and when to poll for progress messages - -Set to `0` to immediately poll on each `LspProgress` event. - -Set to a positive number to poll for progress messages at the specified -frequency (Hz, i.e., polls per second). Combining a slow `poll_rate` -(e.g., `0.5`) with the `ignore_done_already` setting can be used to filter -out short-lived progress tasks, de-cluttering notifications. - -Note that if too many LSP progress messages are sent between polls, -Neovim's progress ring buffer will overflow and messages will be -overwritten (dropped), possibly causing stale progress notifications. -Workarounds include using the `progress.lsp.progress_ringbuf_size` option, -or manually calling `fidget.notification.reset()` (see #167). - -Set to `false` to disable polling altogether; you can still manually poll -progress messages by calling `fidget.progress.poll()`. - -Type: `number | false` (default: `0`) - -progress.suppress_on_insert -: Suppress new messages while in insert mode - -Note that progress messages for new tasks will be dropped, but existing -tasks will be processed to completion. - -Type: `boolean` (default: `false`) - -progress.ignore_done_already -: Ignore new tasks that are already complete - -This is useful if you want to avoid excessively bouncy behavior, and only -seeing notifications for long-running tasks. Works best when combined with -a low `poll_rate`. - -Type: `boolean` (default: `false`) - -progress.ignore_empty_message -: Ignore new tasks that don't contain a message - -Some servers may send empty messages for tasks that don't actually exist. -And if those tasks are never completed, they will become stale in Fidget. -This option tells Fidget to ignore such messages unless the LSP server has -anything meaningful to say. (See #171) - -Note that progress messages for new empty tasks will be dropped, but -existing tasks will be processed to completion. - -Type: `boolean` (default: `false`) - -progress.notification_group -: How to get a progress message's notification group key - -Set this to return a constant to group all LSP progress messages together, -e.g., - -```lua -notification_group = function(msg) - -- N.B. you may also want to configure this group key ("lsp_progress") - -- using progress.display.overrides or notification.configs - return "lsp_progress" -end -``` - -Type: `fun(msg: ProgressMessage): NotificationKey` (default: `msg.lsp_client.name`) - -progress.clear_on_detach -: Clear notification group when LSP server detaches - -This option should be set to a function that, given a client ID number, -returns the notification group to clear. No group will be cleared if the -the function returns `nil`. - -The default setting looks up and returns the LSP client name, which is also used -by `progress.notification_group`. - -Set this option to `nil` to disable this feature entirely (no `LspDetach` -callback will be registered). - -Default value: - -```lua -clear_on_detach = function(client_id) - local client = vim.lsp.get_client_by_id(client_id) - return client and client.name or nil -end -``` - -Type: `(fun(client_id: number): NotificationKey)?` (default: `client.name`) - -progress.ignore -: List of LSP servers to ignore - -Example: - -```lua -ignore = { "rust_analyzer" } -``` - -Type: `NotificationKey[]` (default: `{}`) - -progress.display.render_limit -: How many LSP messages to show at once - -If `false`, no limit. - -This is used to configure each LSP notification group, so by default, this -is a per-server limit. - -Type: `number | false` (default: `16`) - -progress.display.done_ttl -: How long a message should persist after completion - -Set to `0` to use notification group config default, and `math.huge` to show -notification indefinitely (until overwritten). - -Measured in seconds. - -Type: `number` (default: `3`) - -progress.display.done_icon -: Icon shown when all LSP progress tasks are complete - -When a string literal is given (e.g., `"✔"`), it is used as a static icon; -when a table (e.g., `{"dots"}` or `{ pattern = "clock", period = 2 }`) is -given, it is used to generate an animation function; when a function is -specified (e.g., `function(now) return now % 2 < 1 and "+" or "-" end`), -it is used as the animation function. - -Type: `string | Manga` (default: `"✔"`) - -progress.display.done_style -: Highlight group for completed LSP tasks - -Type: `string` (default: `"Constant"`) - -progress.display.progress_ttl -: How long a message should persist when in progress - -Set to `0` to use notification group config default, and `math.huge` to show -notification indefinitely (until overwritten). - -Measured in seconds. - -Type: `number` (default: `math.huge`) - -progress.display.progress_icon -: Icon shown when LSP progress tasks are in progress - -When a string literal is given (e.g., `"✔"`), it is used as a static icon; -when a table (e.g., `{"dots"}` or `{ pattern = "clock", period = 2 }`) is -given, it is used to generate an animation function; when a function is -specified (e.g., `function(now) return now % 2 < 1 and "+" or "-" end`), -it is used as the animation function. - -Type: `string | Manga` (default: `{ "dots" }`) - -progress.display.progress_style -: Highlight group for in-progress LSP tasks - -Type: `string` (default: `"WarningMsg"`) - -progress.display.group_style -: Highlight group for group name (LSP server name) - -Type: `string` (default: `"Title"`) - -progress.display.icon_style -: Highlight group for group icons - -Type: `string` (default: `"Question"`) - -progress.display.priority -: Ordering priority for LSP notification group - -Type: `number?` (default: `30`) - -progress.display.format_message -: How to format a progress message - -Example: - -```lua -format_message = function(msg) - if string.find(msg.title, "Indexing") then - return nil -- Ignore "Indexing..." progress messages - end - if msg.message then - return msg.message - else - return msg.done and "Completed" or "In progress..." - end -end -``` - -Type: `fun(msg: ProgressMessage): string` (default: `fidget.display.default_format_message`) - -where - -```lua -function fidget.display.default_format_message(msg) - local message = msg.message - if not message then - message = msg.done and "Completed" or "In progress..." - end - if msg.percentage ~= nil then - message = string.format("%s (%.0f%%)", message, msg.percentage) - end - return message -end -``` - -progress.display.format_annote -: How to format a progress annotation - -Type: `fun(msg: ProgressMessage): string` (default: `msg.title`) - -progress.display.format_group_name -: How to format a progress notification group's name - -Example: - -```lua -format_group_name = function(group) - return "lsp:" .. tostring(group) -end -``` - -Type: `fun(group: NotificationKey): NotificationDisplay` (default: `tostring`) - -progress.display.overrides -: Override options from the default notification config - -Keys of the table are each notification group's `key`. - -Example: - -```lua -overrides = { - hls = { - name = "Haskell Language Server", - priority = 60, - icon = fidget.progress.display.for_icon(fidget.spinner.animate("triangle", 3), "💯"), - }, - rust_analyzer = { - name = "Rust Analyzer", - icon = fidget.progress.display.for_icon(fidget.spinner.animate("arrow", 2.5), "🦀"), - }, -} -``` - -Type: `{ [NotificationKey]: NotificationConfig }` (default: `{ rust_analyzer = { name = "rust-analyzer" } }`) - -progress.lsp.progress_ringbuf_size -: Configure the nvim's LSP progress ring buffer size - -Useful for avoiding progress message overflow when the LSP server blasts more -messages than the ring buffer can handle (see #167). - -Leaves the progress ringbuf size at its default if this setting is 0 or less. -Doesn't do anything for Neovim pre-v0.10.0. - -Type: `number` (default: `0`) - -notification.poll_rate -: How frequently to poll and render notifications - -Measured in Hertz (frames per second). - -Type: `number` (default: `10`) - -notification.filter -: Minimum notifications level - -Note that this filter only applies to notifications with an explicit -numeric level (i.e., `vim.log.levels`). - -Set to `vim.log.levels.OFF` to filter out all notifications with an -numeric level, or `vim.log.levels.TRACE` to turn off filtering. - -Type: `vim.log.levels` (default: `vim.log.levels.INFO`) - -notification.override_vim_notify -: Automatically override vim.notify() with Fidget - -Equivalent to the following: - -```lua -fidget.setup({ --[[ options ]] }) -vim.notify = fidget.notify -``` - -Type: `boolean` (default: `false`) - -notification.configs -: How to configure notification groups when instantiated - -A configuration with the key `"default"` should always be specified, and -is used as the fallback for notifications lacking a group key. - -Type: `{ [NotificationKey]: NotificationConfig }` (default: `{ default = fidget.notification.default_config }`) - -where - -```lua -fidget.notification.default_config = { - name = "Notifications", - icon = "❰❰", - ttl = 5, - group_style = "Title", - icon_style = "Special", - annote_style = "Question", - debug_style = "Comment", - warn_style = "WarningMsg", - error_style = "ErrorMsg", - debug_annote = "DEBUG", - info_annote = "INFO", - warn_annote = "WARN", - error_annote = "ERROR", -} -``` - -notification.view.stack_upwards -: Display notification items from bottom to top - -Setting this to true tends to lead to more stable animations when the -window is bottom-aligned. - -Type: `boolean` (default: `true`) - -notification.view.icon_separator -: Separator between group name and icon - -Must not contain any newlines. Set to `""` to remove the gap between names -and icons in _all_ notification groups. - -Type: `string` (default: `" "`) - -notification.view.group_separator -: Separator between notification groups - -Must not contain any newlines. Set to `nil` to omit separator entirely. - -Type: `string?` (default: `"---"`) - -notification.view.group_separator_hl -: Highlight group used for group separator - -Type: `string?` (default: `"Comment"`) - -notification.window.normal_hl -: Base highlight group in the notification window - -Used by any Fidget notification text that is not otherwise highlighted, -i.e., message text. - -Note that we use this blanket highlight for all messages to avoid adding -separate highlights to each line (whose lengths may vary). - -Set to empty string to keep your theme defaults. - -With `winblend` set to anything less than `100`, this will also affect the -background color in the notification box area (see `winblend` docs). - -Type: `string` (default: `"Comment"`) - -notification.window.winblend -: Background color opacity in the notification window - -Note that the notification window is rectangular, so any cells covered by -that rectangular area is affected by the background color of `normal_hl`. -With `winblend` set to anything less than `100`, the background of -`normal_hl` will be blended with that of whatever is underneath, -including, e.g., a shaded `colorcolumn`, which is usually not desirable. - -However, if you would like to display the notification window as its own -"boxed" area (especially if you are using a non-"none" `border`), you may -consider setting `winblend` to something less than `100`. - -See also: options for [nvim_open_win()](). - -Type: `number` (default: `100`) - -notification.window.border -: Border around the notification window - -See also: options for [nvim_open_win()](). - -Type: `"none" | "single" | "double" | "rounded" | "solid" | "shadow" | string[]` (default: `"none"`) - -notification.window.border_hl -: Highlight group for notification window border - -Set to empty string to keep your theme's default `FloatBorder` highlight. - -Type: `string` (default: `""`) - -notification.window.zindex -: Stacking priority of the notification window - -Note that the default priority for Vim windows is 50. - -See also: options for [nvim_open_win()](). - -Type: `number` (default: `45`) - -notification.window.width -: Maximum width of the notification window - -`0` means no maximum width. - -Type: `integer` (default: `0`) - -notification.window.height -: Maximum height of the notification window - -`0` means no maximum height. - -Type: `integer` (default: `0`) - -notification.window.x_padding -: Padding from right edge of window boundary - -Type: `integer` (default: `1`) - -notification.window.y_padding -: Padding from bottom edge of window boundary - -Type: `integer` (default: `0`) - -notification.window.align -: How to align the notification window - -Type: `"top"|"bottom"|"avoid_cursor"` (default: `"bottom"`) - -notification.window.relative -: What the notification window position is relative to - -See also: options for [nvim_open_win()](https://neovim.io/doc/user/api.html#nvim_open_win()). - -Type: `"editor"|"win"` (default: `"editor"`) - -logger.level -: Minimum logging level - -Set to `vim.log.levels.OFF` to disable logging, or `vim.log.levels.TRACE` -to enable all logging. - -Note that this option only filters logging (useful for debugging), and is -different from `notification.filter`, which filters `notify()` messages. - -Type: `vim.log.levels` (default: `vim.log.levels.WARN`) - -logger.float_precision -: Limit the number of decimals displayed for floats - -Type: `number` (default: `0.01`) - -logger.path -: Where Fidget writes its logs to - -Using `vim.fn.stdpath("cache")`, the default path usually ends up at -`~/.cache/nvim/fidget.nvim.log`. - -Type: `string` (default: `string.format("%s/fidget.nvim.log", vim.fn.stdpath("cache"))`) - - - - - -# Highlights - -Rather than defining its own highlights, Fidget uses built-in highlight groups -that are typically overridden by custom Vim color schemes. With any luck, these -will look reasonable when rendered, but the visual outcome will really depend on -what your color scheme decided to do with those highlight groups. - - - -# Fidget Lua API - - - -Note that this Lua API documentation is not written for GitHub Markdown. -You might have a better experience reading it in Vim using `:h fidget.txt`. - - - -## Types - -NotificationKey -: Determines the identity of notification items and groups. - -Alias for `any` (non-`nil`) value. - -NotificationLevel -: Second (`level`) parameter of `:h fidget-fidget.notification.notify()`. - -Alias for `number | string`. - -`string` indicates highlight group name; otherwise, `number` indicates -the `:h vim.log.levels` value (that will resolve to a highlight group as -determined by the `:h fidget-NotificationConfig`). - -NotificationOptions -: Third (`opts`) parameter of `:h fidget-fidget.notification.notify()`. - -Fields: - -- `key`: (`NotificationKey?`) Replace existing notification item of the same key -- `group`: (`any?`) Group that this notification item belongs to -- `annote`: (`string?`) Optional single-line title that accompanies the message -- `hidden`: (`boolean?`) Whether this item should be shown -- `ttl`: (`number?`) How long after a notification item should exist; pass 0 to use default value -- `update_only`: (`boolean?`) If true, don't create new notification items -- `data`: (`any?`) Arbitrary data attached to notification item, can be used by `:h fidget-NotificationDisplay` function - -NotificationDisplay -: Displayed element in a `:h fidget-NotificationGroup`. - -Alias for `string | fun(now: number, items: NotificationItem[]): string`. - -If a callable `function`, it is invoked every render cycle with the items -list; useful for rendering animations and other dynamic content. - -NotificationConfig -: Used to configure the behavior of notification groups. - -See also: `:h fidget-notification.configs`. - -Fields: - -- `name`: (`NotificationDisplay?`) Name of the group; if nil, tostring(key) is used as name -- `icon`: (`NotificationDisplay?`) Icon of the group; if nil, no icon is used -- `icon_on_left`: (`boolean?`) If true, icon is rendered on the left instead of right -- `annote_separator`: (`string?`) Separator between message from annote; defaults to " " -- `ttl`: (`number?`) How long a notification item should exist; defaults to 3 -- `render_limit`: (`number?`) how many notification items to show at once -- `group_style`: (`string?`) Style used to highlight group name; defaults to "Title" -- `icon_style`: (`string?`) Style used to highlight icon; if nil, use group_style -- `annote_style`: (`string?`) Default style used to highlight item annotes; defaults to "Question" -- `debug_style`: (`string?`) Style used to highlight debug item annotes -- `info_style`: (`string?`) Style used to highlight info item annotes -- `warn_style`: (`string?`) style used to highlight warn item annotes -- `error_style`: (`string?`) style used to highlight error item annotes -- `debug_annote`: (`string?`) default annotation for debug items -- `info_annote`: (`string?`) default annotation for info items -- `warn_annote`: (`string?`) default annotation for warn items -- `error_annote`: (`string?`) default annotation for error items -- `priority`: (`number?`) order in which group should be displayed; defaults to 50 - -Anime -: A function that takes a timestamp and renders a frame (string). - -Parameters: - -- `now`: (`number`) The current timestamp (in seconds) - -Returns: - -- `string`: The contents of the frame right `now` - -Manga -: A Manga is a table specifying an `:h fidget-Anime` to generate. - -Fields: - -- `pattern`: (`string[] | string`) The name of pattern (see `:h fidget-Spinners`) -- `period`: (`number`) How long one cycle of the animation should take, in seconds - -ProgressMessage -: Used to configure the behavior of notification groups. - -Fields: - -- `token`: (`any`) Unique identifier used to accumulate updates -- `title`: (`string?`) Name of the task in progress -- `message`: (`string?`) Message describing the progress -- `percentage: (`number?`) How much of the progress is complete (out of 100). -- `done`: (`boolean`) Whether this progress completed. Ignore percentage if done is true. -- `cancellable`: (`boolean`) Whether this task can be canceled (though doing so is currently unsupported using Fidget) -- `lsp_client`: (`table`) LSP client table this message came from - -## Functions - -fidget.notify({msg}, {level}, {opts}) -: Alias for `:h fidget.notifications.notify()`. - -fidget.progress.suppress({suppress}) -: Suppress consumption of progress messages. - -Pass `true` as argument to turn on suppression, or `false` to turn it off. - -If no argument is given, suppression state is toggled. - -Parameters: - -- `{suppress}`: (`boolean?`) whether to suppress or toggle suppression - -fidget.notification.notify({msg}, {level}, {opts}) -: Send a notification. - -Can be used to override `vim.notify()`, e.g., - -```lua -vim.notify = require("fidget.notifications").notify -``` - -Parameters: - -- `{msg}`: (`string?`) Content of the notification to show to the user. -- `{level}`: (`NotificationLevel?`) One of the values from `:h vim.log.levels`, or the name of a highlight group. -- `{opts}`: (`NotificationOptions?`) Notification options (see `:h fidget-NotificationOptions`). - -fidget.notification.suppress({suppress}) -: Suppress notification window. - -Pass `true` as argument to turn on suppression, or `false` to turn it off. - -If no argument is given, suppression state is toggled. - -Parameters: - -- `{suppress}`: (`boolean?`) Whether to suppress or toggle suppression - -fidget.notification.clear({group_key}) -: Clear notifications. - -If the given `group_key` is `nil`, then all groups are cleared. - -Parameters: - -- `{group_key}`: (`NotificationKey?`) Which group to clear - -fidget.notification.reset() -: Reset notification subsystem state. - -fidget.spinner.animate({pattern}, {period}) -: Generate an `:h fidget-Anime` function. - -Parameters: - -- `{pattern}`: `(string[] | string)` Either an array of frames, or the name of a known pattern (see `:h fidget-Spinners`) -- `{period}`: `(number)` How long one cycle of the animation should take, in seconds - -Returns: - -- `(Anime)` Call this function to compute the frame at some given timestamp - - - -## Spinners - -The following spinner patterns are defined in `fidget.spinner.patterns`: - -``` -check -dots -dots_negative -dots_snake -dots_footsteps -dots_hop -line -pipe -dots_ellipsis -dots_scrolling -star -flip -hamburger -grow_vertical -grow_horizontal -noise -dots_bounce -triangle -arc -circle -square_corners -circle_quarters -circle_halves -dots_toggle -box_toggle -arrow -zip -bouncing_bar -bouncing_ball -clock -earth -moon -dots_pulse -meter -``` - -See of the plugin source code to see each -animation frame of each pattern. - - - - -# Troubleshooting - -If in doubt, file an issue on . - -Logs are written to `~/.cache/nvim/fidget.nvim.log`. - -# Acknowledgements - -[fidget-spinner](#spinner) designs adapted from the npm package -[sindresorhus/cli-spinners](https://github.com/sindresorhus/cli-spinners). diff --git a/lua/fidget.lua b/lua/fidget.lua index 7cc3811..9ddcca9 100644 --- a/lua/fidget.lua +++ b/lua/fidget.lua @@ -1,33 +1,61 @@ ---- Fidget's top-level module. -local M = {} -M.progress = require("fidget.progress") -M.notification = require("fidget.notification") -M.spinner = require("fidget.spinner") -M.logger = require("fidget.logger") +---@diagnostic disable: unused-local -require("fidget.options").declare(M, "", { - progress = M.progress, - notification = M.notification, - logger = M.logger, +---@brief [[ +---*fidget-api.txt* For Neovim version 0.8+ Last change: see Git log +---@brief ]] +--- +---@toc fidget.api.toc +--- +---@brief [[ +--- *fidget.api* +--- This file contains generated documentation for Fidget's Lua API, though of +--- course you will also find plenty more detail documented in the source code. +--- +--- For help setting up this plugin, see |fidget.txt| and |fidget-option.txt|. +---@brief ]] + +---Fidget's top-level module. +local fidget = {} +fidget.progress = require("fidget.progress") +fidget.notification = require("fidget.notification") +fidget.spinner = require("fidget.spinner") +fidget.logger = require("fidget.logger") + +--- Set up Fidget plugin. +--- +---@param opts table Plugin options. See |fidget-options| or |fidget-option.txt|. +function fidget.setup(opts) end + +require("fidget.options").declare(fidget, "", { + progress = fidget.progress, + notification = fidget.notification, + logger = fidget.logger, }, function(warn_log) - if M.options.notification.override_vim_notify then - M.logger.info("overriding vim.notify() with fidget.notify()") - vim.notify = M.notify + if fidget.options.notification.override_vim_notify then + fidget.logger.info("overriding vim.notify() with fidget.notify()") + vim.notify = fidget.notify end - M.logger.info("fidget.nvim setup() complete.") + fidget.logger.info("fidget.nvim setup() complete.") if #warn_log > 0 then - M.logger.warn("Encountered unknown options during setup():") + fidget.logger.warn("Encountered unknown options during setup():") for _, w in ipairs(warn_log) do - M.logger.warn("-", w) + fidget.logger.warn("-", w) end local warn_msg = string.format( "Encountered %d unknown options during setup().\nSee log (%s) for details.", - #warn_log, M.options.logger.path) - M.notification.notify(warn_msg, vim.log.levels.WARN, { annote = "fidget.nvim" }) + #warn_log, fidget.options.logger.path) + fidget.notification.notify(warn_msg, vim.log.levels.WARN, { annote = "fidget.nvim" }) end end) -M.notify = M.notification.notify +--- Alias for |fidget.notification.notify|. +---@param msg string|nil Content of the notification to show to the user. +---@param level Level|nil How to format the notification. +---@param opts Options|nil Optional parameters (see |fidget.notification.Options|). +function fidget.notify(msg, level, opts) + fidget.notification.notify(msg, level, opts) +end +fidget.notify = fidget.notification.notify -return M +return fidget diff --git a/lua/fidget/integration/nvim-tree.lua b/lua/fidget/integration/nvim-tree.lua new file mode 100644 index 0000000..70f38b4 --- /dev/null +++ b/lua/fidget/integration/nvim-tree.lua @@ -0,0 +1,26 @@ +local M = {} + +require("fidget.options").declare(M, "integration.nvim-tree", { + enable = false, +}, function() + if M.option.enable == true then + local ntree = require("nvim-tree") + local win = require("fidget.notification.window") + + ntree.api.events.subscribe(ntree.api.events.TreeOpen, function() + if win.relative == "editor" then + + end + end) + + ntree.api.events.subscribe(ntree.api.events.TreeClose, function() + win.set_x_offset(0) + end) + + ntree.api.events.subscripe(ntree.api.events.Resize, function(size) + + end) + end +end) + +return M diff --git a/lua/fidget/notification.lua b/lua/fidget/notification.lua index 8f6f568..47b3cbf 100644 --- a/lua/fidget/notification.lua +++ b/lua/fidget/notification.lua @@ -1,68 +1,78 @@ ---- Fidget's notification subsystem. -local M = {} -M.model = require("fidget.notification.model") -M.window = require("fidget.notification.window") -M.view = require("fidget.notification.view") -local poll = require("fidget.poll") -local logger = require("fidget.logger") +---@mod fidget.notification Notification subsystem +local notification = {} +notification.model = require("fidget.notification.model") +notification.window = require("fidget.notification.window") +notification.view = require("fidget.notification.view") +local poll = require("fidget.poll") +local logger = require("fidget.logger") --- Used to determine the identity of notification items and groups. ----@alias NotificationKey any +---@alias Key any ---- Second (level) paramter passed to fidget.notification.notify(). +--- Second (level) paramter passed to |fidget.notification.notify|. --- --- `string` indicates highlight group name; otherwise, `number` indicates ---- the `:h vim.log.levels` value (that will resolve to a highlight group as ---- determined by the `:h NotificationConfig`). ----@alias NotificationLevel number | string +--- the |vim.log.levels| value (that will resolve to a highlight group as +--- determined by the |fidget.notification.Config|). +---@alias Level number|string ---- Third (opts) parameter passed to fidget.notification.notify(). ----@class NotificationOptions ----@field key NotificationKey? Replace existing notification item of the same key ----@field group any? Group that this notification item belongs to ----@field annote string? Optional single-line title that accompanies the message ----@field hidden boolean? Whether this item should be shown ----@field ttl number? How long after a notification item should exist; pass 0 to use default value ----@field update_only boolean? If true, don't create new notification items ----@field data any? Arbitrary data attached to notification item +--- Third (opts) parameter passed to |fidget.notification.notify|. +---@class Options +---@field key Key|nil Replace existing notification item of the same key +---@field group any|nil Group that this notification item belongs to +---@field annote string|nil Optional single-line title that accompanies the message +---@field hidden boolean|nil Whether this item should be shown +---@field ttl number|nil How long after a notification item should exist; pass 0 to use default value +---@field update_only boolean|nil If true, don't create new notification items +---@field data any|nil Arbitrary data attached to notification item ---- Something that can be displayed in a NotificationGroup. +--- Something that can be displayed in a |fidget.notification.Group|. --- --- If a callable `function`, it is invoked every render cycle with the items --- list; useful for rendering animations and other dynamic content. ----@alias NotificationDisplay string | fun(now: number, items: NotificationItem[]): string +---@alias Display string|fun(now: number, items: Item[]): string --- Used to configure the behavior of notification groups. --- --- If both name and icon are nil, then no group header is rendered. --- ----@class NotificationConfig ----@field name NotificationDisplay? name of the group ----@field icon NotificationDisplay? icon of the group ----@field icon_on_left boolean? if true, icon is rendered on the left instead of right ----@field annote_separator string? separator between message from annote; defaults to " " ----@field ttl number? how long a notification item should exist; defaults to 3 ----@field render_limit number? how many notification items to show at once ----@field group_style string? style used to highlight group name; defaults to "Title" ----@field icon_style string? style used to highlight icon; if nil, use group_style ----@field annote_style string? default style used to highlight item annotes; defaults to "Question" ----@field debug_style string? style used to highlight debug item annotes ----@field info_style string? style used to highlight info item annotes ----@field warn_style string? style used to highlight warn item annotes ----@field error_style string? style used to highlight error item annotes ----@field debug_annote string? default annotation for debug items ----@field info_annote string? default annotation for info items ----@field warn_annote string? default annotation for warn items ----@field error_annote string? default annotation for error items ----@field priority number? order in which group should be displayed; defaults to 50 +--- Note that the actual `|fidget.notification.default_config|` defines a few +--- more defaults than what is documented here, which pertain to the fallback +--- used if the corresponding field in the `default` config table is `nil`. +--- +---@class Config +---@field name Display|nil Name of the group +---@field icon Display|nil Icon of the group +---@field icon_on_left boolean|nil If `true`, icon is rendered on the left instead of right +---@field annote_separator string|nil Separator between message from annote; defaults to `" "` +---@field ttl number|nil How long a notification item should exist; defaults to `5` +---@field render_limit number|nil How many notification items to show at once +---@field group_style string|nil Style used to highlight group name; defaults to `"Title"` +---@field icon_style string|nil Style used to highlight icon; if nil, use `group_style` +---@field annote_style string|nil Default style used to highlight item annotes; defaults to `"Question"` +---@field debug_style string|nil Style used to highlight debug item annotes +---@field info_style string|nil Style used to highlight info item annotes +---@field warn_style string|nil Style used to highlight warn item annotes +---@field error_style string|nil Style used to highlight error item annotes +---@field debug_annote string|nil Default annotation for debug items +---@field info_annote string|nil Default annotation for info items +---@field warn_annote string|nil Default annotation for warn items +---@field error_annote string|nil Default annotation for error items +---@field priority number|nil Order in which group should be displayed; defaults to `50` --- Default notification configuration. --- --- Exposed publicly because it might be useful for users to integrate for when --- they are adding their own configs. --- ----@type NotificationConfig -M.default_config = { +--- To see the default values, run: +--- +--- >vim +--- :lua print(vim.inspect(require("fidget.notification").default_config)) +--- < +--- +---@type Config +notification.default_config = { name = "Notifications", icon = "❰❰", ttl = 5, @@ -80,7 +90,7 @@ M.default_config = { } --- Options related to notification subsystem -require("fidget.options").declare(M, "notification", { +require("fidget.options").declare(notification, "notification", { --- How frequently to update and render notifications --- --- Measured in Hertz (frames per second). @@ -103,10 +113,10 @@ require("fidget.options").declare(M, "notification", { --- --- Equivalent to the following: --- - --- ```lua + --->lua --- fidget.setup({ --[[ options ]] }) --- vim.notify = fidget.notify - --- ``` + ---< --- ---@type boolean override_vim_notify = false, @@ -116,21 +126,21 @@ require("fidget.options").declare(M, "notification", { --- A configuration with the key `"default"` should always be specified, and --- is used as the fallback for notifications lacking a group key. --- - ---@type { [NotificationKey]: NotificationConfig } - configs = { default = M.default_config }, + ---@type { [Key]: Config } + configs = { default = notification.default_config }, - view = M.view, - window = M.window, + view = notification.view, + window = notification.window, }, function() -- Need to ensure that there is some sane default config. - if not M.options.configs.default then + if not notification.options.configs.default then logger.warn("no default notification config specified; using default") - M.options.configs.default = M.default_config + notification.options.configs.default = notification.default_config end end) --- The "model" of notifications: a list of notification groups. ----@type NotificationGroup[] +---@type Group[] local groups = {} --- Whether the notification window is suppressed. @@ -140,14 +150,14 @@ local view_suppressed = false --- --- Can be used to override `vim.notify()`, e.g., --- ---- ```lua +--->lua --- vim.notify = require("fidget.notifications").notify ---- ``` +---< --- ----@param msg string? ----@param level NotificationLevel? ----@param opts NotificationOptions? -function M.notify(msg, level, opts) +---@param msg string|nil Content of the notification to show to the user. +---@param level Level|nil How to format the notification. +---@param opts Options|nil Optional parameters (see |fidget.notification.Options|). +function notification.notify(msg, level, opts) if msg ~= nil and type(msg) ~= "string" then error("message: expected string, got " .. type(msg)) end @@ -160,26 +170,31 @@ function M.notify(msg, level, opts) error("opts: expected table, got " .. type(opts)) end - if type(level) == "number" and level < M.options.filter then + if type(level) == "number" and level < notification.options.filter then logger.info(string.format("Filtered out notification (%s): %s", logger.fmt_level(level), msg)) return end local now = poll.get_time() local n_groups = #groups - M.model.update(now, M.options.configs, groups, msg, level, opts) + notification.model.update(now, notification.options.configs, groups, msg, level, opts) if n_groups ~= #groups then groups = vim.fn.sort(groups, function(a, b) return (a.config.priority or 50) - (b.config.priority or 50) end) end - M.poller:start_polling(M.options.poll_rate) + notification.poller:start_polling(notification.options.poll_rate) end --- Close the notification window. --- ----@return boolean closed_successfully -function M.close() - return M.window.guard(function() - M.window.close() +--- Note that it the window will pop open again as soon as there is any reason +--- to (e.g., if another notification or LSP progress message is received). +--- +--- To temporarily stop the window from opening, see |fidget.notification.suppress|. +--- +---@return boolean closed_successfully Whether the window closed successfully. +function notification.close() + return notification.window.guard(function() + notification.window.close() end) end @@ -187,8 +202,8 @@ end --- --- If the given `group_key` is `nil`, then all groups are cleared. --- ----@param group_key NotificationKey? Which group to clear -function M.clear(group_key) +---@param group_key Key|nil Which group to clear +function notification.clear(group_key) if group_key == nil then groups = {} else @@ -200,33 +215,34 @@ function M.clear(group_key) end end if #groups == 0 then - M.window.guard(M.window.close) + notification.window.guard(notification.window.close) end end --- Reset notification subsystem state. -function M.reset() - M.clear() - M.poller:reset_error() -- Clear error if previously encountered one +function notification.reset() + notification.clear() + notification.poller:reset_error() -- Clear error if previously encountered one end --- The poller for the notification subsystem. -M.poller = poll.Poller { +---@protected +notification.poller = poll.Poller { name = "notification", poll = function(self) - groups = M.model.tick(self:now(), groups) + groups = notification.model.tick(self:now(), groups) -- TODO: if not modified, don't re-render - local v = M.view.render(self:now(), groups) + local v = notification.view.render(self:now(), groups) if #v.lines > 0 then if view_suppressed then return true end - M.window.guard(function() - M.window.set_lines(v.lines, v.highlights, v.width) - M.window.show(v.width, #v.lines) + notification.window.guard(function() + notification.window.set_lines(v.lines, v.highlights, v.width) + notification.window.show(v.width, #v.lines) end) return true else @@ -235,7 +251,7 @@ M.poller = poll.Poller { end -- If we could not close the window, keep polling, i.e., keep trying to close the window. - return not M.close() + return not notification.close() end end } @@ -244,12 +260,14 @@ M.poller = poll.Poller { --- --- Inherits missing keys from the default config. --- ----@param key NotificationKey ----@param config NotificationConfig? ----@param overwrite boolean -function M.set_config(key, config, overwrite) - if overwrite or not M.options.configs[key] then - M.options.configs[key] = vim.tbl_extend("keep", config, M.options.configs.default) +---@param key Key Which config to set. +---@param config Config|nil What to set as config. +---@param overwrite boolean Whether to overwrite existing config, if any. +--- +---@see fidget.notification.Config +function notification.set_config(key, config, overwrite) + if overwrite or not notification.options.configs[key] then + notification.options.configs[key] = vim.tbl_extend("keep", config, notification.options.configs.default) end end @@ -258,8 +276,9 @@ end --- Pass `true` as argument to turn on suppression, or `false` to turn it off. --- --- If no argument is given, suppression state is toggled. ----@param suppress boolean? Whether to suppress or toggle suppression -function M.suppress(suppress) +--- +---@param suppress boolean|nil Whether to suppress or toggle suppression +function notification.suppress(suppress) if suppress == nil then view_suppressed = not view_suppressed else @@ -267,8 +286,8 @@ function M.suppress(suppress) end if view_suppressed then - M.close() + notification.close() end end -return M +return notification diff --git a/lua/fidget/notification/model.lua b/lua/fidget/notification/model.lua index 73f5d63..b3cb91c 100644 --- a/lua/fidget/notification/model.lua +++ b/lua/fidget/notification/model.lua @@ -1,3 +1,5 @@ +---Fidget notification abstract state (internal) +--- --- Type definitions and helper methods for the notifications model --- (i.e., its abstract state). --- @@ -9,27 +11,27 @@ local M = {} --- A collection of NotificationItems. ----@class NotificationGroup ----@field key NotificationKey used to distinguish this group from others ----@field config NotificationConfig configuration for this group ----@field items NotificationItem[] items displayed in the group +---@class Group +---@field key Key used to distinguish this group from others +---@field config Config configuration for this group +---@field items Item[] items displayed in the group --- Notification element containing a message and optional annotation. ----@class NotificationItem ----@field key NotificationKey used to distinguish this item from others ----@field message string displayed message for the item ----@field annote string? optional title that accompanies the message ----@field style string style used to render the annote/title, if any ----@field hidden boolean whether this item should be shown ----@field expires_at number what time this item should be removed; math.huge means never ----@field data any? arbitrary data attached to notification item +---@class Item +---@field key Key used to distinguish this item from others +---@field message string displayed message for the item +---@field annote string|nil optional title that accompanies the message +---@field style string style used to render the annote/title, if any +---@field hidden boolean whether this item should be shown +---@field expires_at number what time this item should be removed; math.huge means never +---@field data any|nil arbitrary data attached to notification item --- Get the notification group indexed by group_key; create one if none exists. --- ----@param configs { [NotificationKey]: NotificationConfig } ----@param groups NotificationGroup[] ----@param group_key NotificationKey ----@return NotificationGroup group +---@param configs { [Key]: Config } +---@param groups Group[] +---@param group_key Key +---@return Group group local function get_group(configs, groups, group_key) for _, group in ipairs(groups) do if group.key == group_key then @@ -39,7 +41,7 @@ local function get_group(configs, groups, group_key) -- Group not found; create it and insert it into list of active groups. - ---@type NotificationGroup + ---@type Group local group = { key = group_key, items = {}, @@ -51,9 +53,9 @@ end --- Search for an item with the given key among a notification group. --- ----@param group NotificationGroup ----@param key NotificationKey ----@return NotificationItem? +---@param group Group +---@param key Key +---@return Item|nil local function find_item(group, key) if key == nil then return nil @@ -72,9 +74,9 @@ end --- Obtain the style specified by the level parameter of a .update(), --- reading from config if necessary. --- ----@param config NotificationConfig ----@param level number | string | nil ----@return string? +---@param config Config +---@param level number|string|nil +---@return string|nil local function style_from_level(config, level) if type(level) == "number" then if level == vim.log.levels.INFO and config.info_style then @@ -93,9 +95,9 @@ end --- Obtain the annotation from the specified level of an .update() call. --- ----@param config NotificationConfig ----@param level number | string | nil ----@return string? +---@param config Config +---@param level number|string|nil +---@return string|nil local function annote_from_level(config, level) if type(level) == "number" then if level == vim.log.levels.INFO then @@ -113,8 +115,8 @@ local function annote_from_level(config, level) end --- Compute the expiry time based on the given TTL (from notify() options) and the default TTL (from config). ----@param ttl number? ----@param default_ttl number? +---@param ttl number|nil +---@param default_ttl number|nil ---@return number expiry_time local function compute_expiry(now, ttl, default_ttl) if not ttl or ttl == 0 then @@ -128,12 +130,13 @@ end --- --- The API of this function is based on that of vim.notify(). --- +---@protected ---@param now number ----@param configs table ----@param groups NotificationGroup[] ----@param msg string? ----@param level NotificationLevel? ----@param opts NotificationOptions? +---@param configs table +---@param groups Group[] +---@param msg string|nil +---@param level Level|nil +---@param opts Options|nil function M.update(now, configs, groups, msg, level, opts) opts = opts or {} local group_key = opts.group ~= nil and opts.group or "default" @@ -145,7 +148,7 @@ function M.update(now, configs, groups, msg, level, opts) if msg == nil or opts.update_only then return end - ---@type NotificationItem + ---@type Item local new_item = { key = opts.key, message = msg, @@ -172,9 +175,10 @@ end --- Updates each group in-place (i.e., removes items from them), but returns --- a list of groups that still have items left. --- +---@protected ---@param now number timestamp of current frame. ----@param groups NotificationGroup[] ----@return NotificationGroup[] +---@param groups Group[] +---@return Group[] function M.tick(now, groups) local new_groups = {} for _, group in ipairs(groups) do diff --git a/lua/fidget/notification/view.lua b/lua/fidget/notification/view.lua index 1e97502..2606049 100644 --- a/lua/fidget/notification/view.lua +++ b/lua/fidget/notification/view.lua @@ -31,38 +31,38 @@ require("fidget.options").declare(M, "notification.view", { --- Separator between group name and icon --- --- Must not contain any newlines. Set to `""` to remove the gap between names - --- and icons in _all_ notification groups. + --- and icons in all notification groups. --- ---@type string icon_separator = " ", --- Separator between notification groups --- - --- Must not contain any newlines. Set to `nil` to omit separator entirely. + --- Must not contain any newlines. Set to `false` to omit separator entirely. --- - ---@type string? + ---@type string|false group_separator = "---", --- Highlight group used for group separator --- - ---@type string? + ---@type string|false group_separator_hl = "Comment", }) --- Render group separator item. --- ----@return NotificationRenderItem? group_separator +---@return NotificationRenderItem|nil group_separator function M.render_group_separator() if not M.options.group_separator then return nil end return { - lines = { M.options.group_separator }, + lines = { M.options.group_separator or nil }, highlights = { M.options.group_separator_hl and { - hl_group = M.options.group_separator_hl, + hl_group = M.options.group_separator_hl or nil, line = 0, col_start = 0, col_end = -1, @@ -73,9 +73,9 @@ end --- Render the header of a group, containing group name and icon. --- ----@param now number timestamp of current render frame ----@param group NotificationGroup ----@return NotificationRenderItem? group_header +---@param now number timestamp of current render frame +---@param group Group +---@return NotificationRenderItem|nil group_header function M.render_group_header(now, group) local group_name = group.config.name if type(group_name) == "function" then @@ -154,9 +154,9 @@ end --- Render a notification item, containing message and annote. --- ----@param item NotificationItem ----@param config NotificationConfig ----@return NotificationRenderItem? render_item +---@param item Item +---@param config Config +---@return NotificationRenderItem|nil render_item function M.render_item(item, config) local lines, highlights = {}, {} @@ -190,7 +190,7 @@ end --- Render notifications into lines and highlights. --- ---@param now number timestamp of current render frame ----@param groups NotificationGroup[] +---@param groups Group[] ---@return NotificationView view function M.render(now, groups) ---@type NotificationRenderItem[] diff --git a/lua/fidget/notification/window.lua b/lua/fidget/notification/window.lua index a361430..545aac7 100644 --- a/lua/fidget/notification/window.lua +++ b/lua/fidget/notification/window.lua @@ -112,17 +112,17 @@ require("fidget.options").declare(M, "notification.window", { local state = { --- ID of the buffer that notifications are rendered to. --- - ---@type number? + ---@type number|nil buffer_id = nil, --- ID of the window that the notification buffer is shown in. --- - ---@type number? + ---@type number|nil window_id = nil, --- ID of the namespace on which highlights are created. --- - ---@type number? + ---@type number|nil namespace_id = nil, --- Additional, temporary offset. @@ -130,7 +130,7 @@ local state = { --- Useful for temporarily adding additional padding to account for space --- taken up by other plugins' windows. --- - ---@type number? + ---@type number|nil x_offset = 0, } @@ -409,7 +409,7 @@ end --- ---@param lines string[] lines to place into buffer ---@param highlights NotificationHighlight[] list of highlights to apply ----@param right_col number? optional display width, to right-justify +---@param right_col number|nil optional display width, to right-justify function M.set_lines(lines, highlights, right_col) local buffer_id = M.get_buffer() local namespace_id = M.get_namespace() diff --git a/lua/fidget/options.lua b/lua/fidget/options.lua index cf6e550..736533c 100644 --- a/lua/fidget/options.lua +++ b/lua/fidget/options.lua @@ -1,3 +1,4 @@ +--- Declarative options for this plugin (not specific to Fidget). local M = {} --- Declare a table as a fidget module, so we can tell using is_fidget_module(). diff --git a/lua/fidget/progress.lua b/lua/fidget/progress.lua index bfdb96f..8ce6a5c 100644 --- a/lua/fidget/progress.lua +++ b/lua/fidget/progress.lua @@ -1,7 +1,7 @@ ---- Fidget's LSP progress subsystem. -local M = {} -M.display = require("fidget.progress.display") -M.lsp = require("fidget.progress.lsp") +---@mod fidget.progress LSP progress subsystem +local progress = {} +progress.display = require("fidget.progress.display") +progress.lsp = require("fidget.progress.lsp") local poll = require("fidget.poll") local notification = require("fidget.notification") local logger = require("fidget.logger") @@ -10,10 +10,10 @@ local logger = require("fidget.logger") local autocmds = {} --- Options related to LSP progress notification subsystem -require("fidget.options").declare(M, "progress", { +require("fidget.options").declare(progress, "progress", { --- How and when to poll for progress messages --- - --- Set to `0` to immediately poll on each `LspProgress` event. + --- Set to `0` to immediately poll on each |LspProgress| event. --- --- Set to a positive number to poll for progress messages at the specified --- frequency (Hz, i.e., polls per second). Combining a slow `poll_rate` @@ -23,11 +23,11 @@ require("fidget.options").declare(M, "progress", { --- Note that if too many LSP progress messages are sent between polls, --- Neovim's progress ring buffer will overflow and messages will be --- overwritten (dropped), possibly causing stale progress notifications. - --- Workarounds include using the `progress.lsp.progress_ringbuf_size` option, - --- or manually calling `fidget.notification.reset()` (see #167). + --- Workarounds include using the |fidget.option.progress.lsp.progress_ringbuf_size| + --- option, or manually calling |fidget.notification.reset| (see #167). --- --- Set to `false` to disable polling altogether; you can still manually poll - --- progress messages by calling `fidget.progress.poll()`. + --- progress messages by calling |fidget.progress.poll|. --- ---@type number|false poll_rate = 0, @@ -67,15 +67,15 @@ require("fidget.options").declare(M, "progress", { --- Set this to return a constant to group all LSP progress messages together, --- e.g., --- - --- ```lua + --->lua --- notification_group = function(msg) --- -- N.B. you may also want to configure this group key ("lsp_progress") --- -- using progress.display.overrides or notification.configs --- return "lsp_progress" --- end - --- ``` + ---< --- - ---@type fun(msg: ProgressMessage): NotificationKey + ---@type fun(msg: ProgressMessage): Key notification_group = function(msg) return msg.lsp_client.name end, @@ -83,17 +83,17 @@ require("fidget.options").declare(M, "progress", { --- Clear notification group when LSP server detaches --- --- This option should be set to a function that, given a client ID number, - --- returns the notification group to clear. No group will be cleared if the + --- returns the notification group to clear. No group will be cleared if --- the function returns `nil`. --- --- The default setting looks up and returns the LSP client name, which is - --- also used by `progress.notification_group`. + --- also used by |fidget.option.progress.notification_group|. --- - --- Set this option to `nil` to disable this feature entirely (no `LspDetach` - --- callback will be registered). + --- Set this option to `false` to disable this feature entirely (no + --- |LspDetach| callback will be installed). --- --- - ---@type (fun(client_id: number): NotificationKey)? + ---@type false|fun(client_id: number): Key clear_on_detach = function(client_id) local client = vim.lsp.get_client_by_id(client_id) return client and client.name or nil @@ -103,15 +103,15 @@ require("fidget.options").declare(M, "progress", { --- --- Example: --- - --- ```lua + --->lua --- ignore = { "rust_analyzer" } - --- ``` + ---< --- - ---@type NotificationKey[] + ---@type Key[] ignore = {}, - display = M.display, - lsp = M.lsp, + display = progress.display, + lsp = progress.lsp, }, function() -- Ensure setup() reentrancy for _, autocmd in pairs(autocmds) do @@ -119,21 +119,21 @@ require("fidget.options").declare(M, "progress", { end autocmds = {} - if M.options.poll_rate ~= false then - autocmds["LspProgress"] = M.lsp.on_progress_message(function() - if M.options.poll_rate > 0 then - M.poller:start_polling(M.options.poll_rate) + if progress.options.poll_rate ~= false then + autocmds["LspProgress"] = progress.lsp.on_progress_message(function() + if progress.options.poll_rate > 0 then + progress.poller:start_polling(progress.options.poll_rate) else - M.poller:poll_once() + progress.poller:poll_once() end end) end - if M.options.clear_on_detach then + if progress.options.clear_on_detach then autocmds["LspDetach"] = vim.api.nvim_create_autocmd("LspDetach", { desc = "Fidget LSP detach handler", callback = function(args) - M.on_detach(args.data.client_id) + progress.on_detach(args.data.client_id) end, }) end @@ -144,38 +144,42 @@ local progress_suppressed = false --- Cache of generated LSP notification group configs. --- ----@type { [NotificationKey]: NotificationConfig } +---@type { [Key]: Config } local loaded_configs = {} --- Lazily load the notification configuration for some progress message. --- +---@protected ---@param msg ProgressMessage -function M.load_config(msg) - local group = M.options.notification_group(msg) +function progress.load_config(msg) + local group = progress.options.notification_group(msg) if loaded_configs[group] then return end - local config = M.display.make_config(group) + local config = progress.display.make_config(group) notification.set_config(group, config, false) end +--- Format a progress message for vim.notify(). +--- +---@protected ---@param msg ProgressMessage ----@return string? ----@return number ----@return NotificationOptions -function M.format_progress(msg) - local group = M.options.notification_group(msg) - local message = M.options.display.format_message(msg) - local annote = M.options.display.format_annote(msg) +---@return string|nil message +---@return number level +---@return Options opts +function progress.format_progress(msg) + local group = progress.options.notification_group(msg) + local message = progress.options.display.format_message(msg) + local annote = progress.options.display.format_annote(msg) local update_only = false - if M.options.ignore_done_already and msg.done then + if progress.options.ignore_done_already and msg.done then update_only = true - elseif M.options.ignore_empty_message and msg.message == nil then + elseif progress.options.ignore_empty_message and msg.message == nil then update_only = true - elseif M.options.suppress_on_insert and string.find(vim.fn.mode(), "i") then + elseif progress.options.suppress_on_insert and string.find(vim.fn.mode(), "i") then update_only = true end @@ -184,20 +188,21 @@ function M.format_progress(msg) group = group, annote = annote, update_only = update_only, - ttl = msg.done and 0 or M.display.options.progress_ttl, -- Use config default when done - data = msg.done, -- use data to convey whether this task is done + ttl = msg.done and 0 or progress.display.options.progress_ttl, -- Use config default when done + data = msg.done, -- use data to convey whether this task is done } end --- Poll for progress messages to feed to the fidget notifications subsystem. -M.poller = poll.Poller { +---@private +progress.poller = poll.Poller { name = "progress", poll = function() if progress_suppressed then return false end - local messages = M.lsp.poll_for_messages() + local messages = progress.lsp.poll_for_messages() if #messages == 0 then logger.info("No LSP messages (that can be displayed)") return false @@ -206,7 +211,7 @@ M.poller = poll.Poller { for _, msg in ipairs(messages) do -- Determine if we should ignore this message local ignore = false - for _, lsp_name in ipairs(M.options.ignore) do + for _, lsp_name in ipairs(progress.options.ignore) do -- NOTE: hopefully this loop isn't too expensive. -- But if it is, consider indexing by hash. if msg.lsp_client.name == lsp_name then @@ -217,21 +222,28 @@ M.poller = poll.Poller { end if not ignore then logger.info("Notifying LSP progress message:", msg) - M.load_config(msg) - notification.notify(M.format_progress(msg)) + progress.load_config(msg) + notification.notify(progress.format_progress(msg)) end end return true end } +--- Poll for progress messages once. +--- +--- Potentially useful if you're planning on "driving" Fidget yourself. +function progress.poll() + progress.poller:poll_once() +end + --- Suppress consumption of progress messages. --- --- Pass `false` as argument to turn off suppression. --- --- If no argument is given, suppression state is toggled. ----@param suppress boolean? Whether to suppress or toggle suppression -function M.suppress(suppress) +---@param suppress boolean|nil Whether to suppress or toggle suppression +function progress.suppress(suppress) if suppress == nil then progress_suppressed = not progress_suppressed else @@ -243,13 +255,14 @@ end --- --- Clears notification group given by `options.clear_on_detach`. --- +---@protected ---@param client_id number -function M.on_detach(client_id) - local group_key = M.options.clear_on_detach(client_id) +function progress.on_detach(client_id) + local group_key = progress.options.clear_on_detach(client_id) if group_key == nil then return end notification.clear(group_key) end -return M +return progress diff --git a/lua/fidget/progress/display.lua b/lua/fidget/progress/display.lua index 0f72373..ccc2acc 100644 --- a/lua/fidget/progress/display.lua +++ b/lua/fidget/progress/display.lua @@ -1,3 +1,4 @@ +--- Options and helpers for transforming local M = {} local spinner = require("fidget.spinner") @@ -22,10 +23,10 @@ require("fidget.options").declare(M, "progress.display", { --- --- If `false`, no limit. --- - --- This is used to configure each LSP notification group, so by default, this - --- is a per-server limit. + --- This is used to configure each LSP notification group, so by default, + --- this is a per-server limit. --- - ---@type number | false + ---@type number|false render_limit = 16, --- How long a message should persist after completion @@ -46,7 +47,9 @@ require("fidget.options").declare(M, "progress.display", { --- specified (e.g., `function(now) return now % 2 < 1 and "+" or "-" end`), --- it is used as the animation function. --- - ---@type string | Manga | Anime + --- See also: |fidget.spinner.Manga| and |fidget.spinner.Anime|. + --- + ---@type string|Manga|Anime done_icon = "✔", --- Highlight group for completed LSP tasks @@ -72,7 +75,7 @@ require("fidget.options").declare(M, "progress.display", { --- specified (e.g., `function(now) return now % 2 < 1 and "+" or "-" end`), --- it is used as the animation function. --- - ---@type string | Manga | Anime + ---@type string|Manga|Anime progress_icon = { "dots" }, --- Highlight group for in-progress LSP tasks @@ -92,14 +95,14 @@ require("fidget.options").declare(M, "progress.display", { --- Ordering priority for LSP notification group --- - ---@type number? + ---@type number|false priority = 30, --- How to format a progress message --- --- Example: --- - --- ```lua + --->lua --- format_message = function(msg) --- if string.find(msg.title, "Indexing") then --- return nil -- Ignore "Indexing..." progress messages @@ -110,7 +113,7 @@ require("fidget.options").declare(M, "progress.display", { --- return msg.done and "Completed" or "In progress..." --- end --- end - --- ``` + ---< --- ---@type fun(msg: ProgressMessage): string format_message = M.default_format_message, @@ -126,13 +129,13 @@ require("fidget.options").declare(M, "progress.display", { --- --- Example: --- - --- ```lua + --->lua --- format_group_name = function(group) --- return "lsp:" .. tostring(group) --- end - --- ``` + ---< --- - ---@type fun(group: NotificationKey): NotificationDisplay + ---@type fun(group: Key): Display format_group_name = tostring, --- Override options from the default notification config @@ -141,7 +144,7 @@ require("fidget.options").declare(M, "progress.display", { --- --- Example: --- - --- ```lua + --->lua --- overrides = { --- hls = { --- name = "Haskell Language Server", @@ -153,9 +156,9 @@ require("fidget.options").declare(M, "progress.display", { --- icon = fidget.progress.display.for_icon(fidget.spinner.animate("arrow", 2.5), "🦀"), --- }, --- } - --- ``` + ---< --- - ---@type { [NotificationKey]: NotificationConfig } + ---@type { [Key]: Config } overrides = { rust_analyzer = { name = "rust-analyzer" }, } @@ -165,7 +168,7 @@ require("fidget.options").declare(M, "progress.display", { --- ---@param progress string|Anime progress icon/animation function ---@param done string|Anime completion icon/animation function ----@return NotificationDisplay icon_display +---@return Display icon_display function M.for_icon(progress, done) return function(now, items) for _, item in ipairs(items) do @@ -179,8 +182,8 @@ function M.for_icon(progress, done) end --- Create the config for a language server indexed by the given group key. ----@param group NotificationKey ----@return NotificationConfig +---@param group Key +---@return Config function M.make_config(group) local progress = M.options.progress_icon if type(progress) == "table" then @@ -202,7 +205,7 @@ function M.make_config(group) annote_style = M.options.progress_style, warn_style = M.options.progress_style, info_style = M.options.done_style, - priority = M.options.priority, + priority = M.options.priority or nil, } if M.options.overrides[group] then diff --git a/lua/fidget/progress/lsp.lua b/lua/fidget/progress/lsp.lua index 8642b5f..21fc1f1 100644 --- a/lua/fidget/progress/lsp.lua +++ b/lua/fidget/progress/lsp.lua @@ -1,15 +1,15 @@ ---- Fidget shim layer to Neovim's lsp API. +---@mod fidget.progress.lsp Neovim LSP shim layer local M = {} local logger = require("fidget.logger") ---@class ProgressMessage ----@field token any Unique identifier used to accumulate updates ----@field title string? Name of the task in progress ----@field message string? Message describing the progress ----@field percentage number? How much of the progress is complete (out of 100). ----@field done boolean Whether this progress completed. Ignore percentage if done is true. ----@field cancellable boolean Whether this task can be canceled (though doing so is unsupported with Fidget) ----@field lsp_client table LSP client table this message came from +---@field token Key Unique identifier used to accumulate updates +---@field title string|nil Name of the task in progress +---@field message string|nil Message describing the progress +---@field percentage number|nil How much of the progress is complete (out of 100) +---@field done boolean Whether this progress completed; ignore `percentage` if `done` is `true` +---@field cancellable boolean Whether this task can be canceled (though doing so is unsupported with Fidget) +---@field lsp_client table LSP client table this message came from --- Autocmd ID for the LSPAttach event. ---@type number? @@ -49,6 +49,7 @@ end) --- reports into strings. --- ---@return ProgressMessage[] progress_messages +---@see fidget.progress.lsp.ProgressMessage function M.poll_for_messages() local messages = {} for _, client in ipairs(vim.lsp.get_clients()) do @@ -83,6 +84,7 @@ end --- Register handler for LSP progress updates. --- +---@protected ---@param fn function ---@return number function M.on_progress_message(fn) @@ -123,6 +125,7 @@ if not vim.lsp.status then --- [get_progress_messages]: https://github.com/neovim/neovim/blob/v0.9.4/runtime/lua/vim/lsp/util.lua#L354-L385 --- [lsp-status-pr]: https://github.com/neovim/neovim/pull/23958 --- + ---@protected ---@return ProgressMessage[] progress_messages function M.poll_for_messages() local messages = {} @@ -165,6 +168,7 @@ if not vim.lsp.status then --- Register autocmd callback for LspProgressUpdate event (v0.8.0--v0.9.4). --- + ---@protected ---@param fn function ---@return number function M.on_progress_message(fn) diff --git a/lua/fidget/spinner.lua b/lua/fidget/spinner.lua index 72b9fc4..282dd3f 100644 --- a/lua/fidget/spinner.lua +++ b/lua/fidget/spinner.lua @@ -1,11 +1,12 @@ -local M = {} -M.patterns = require("fidget.spinner.patterns") +---@mod fidget.spinner Spinner animations +local spinner = {} +spinner.patterns = require("fidget.spinner.patterns") --- A Manga is a table specifying an Anime to generate. --- --- The period is specified in seconds; if omitted, it defaults to 1. --- ----@alias Manga { pattern: string[]|string, period: number? } | { [1]: string[]|string } +---@alias Manga { pattern: string[]|string, period: number|nil } | { [1]: string[]|string } --- An Anime is a function that takes a timestamp and renders a frame (string). --- @@ -17,13 +18,13 @@ M.patterns = require("fidget.spinner.patterns") --- The period is specified in seconds; if omitted, it defaults to 1. --- ---@param pattern string[]|string Either an array of frames, or the name of a known pattern ----@param period number? How long one cycle of the animation should take, in seconds ----@return Anime anime Call this function to compute the frame at some timestamp -function M.animate(pattern, period) +---@param period number|nil How long one cycle of the animation should take, in seconds +---@return Anime anime Call this function to compute the frame at some timestamp +function spinner.animate(pattern, period) period = period or 1 if type(pattern) == "string" then local pattern_name = pattern - pattern = M.patterns[pattern_name] + pattern = spinner.patterns[pattern_name] assert(pattern ~= nil, "Unknown pattern: " .. pattern_name) end @@ -43,4 +44,4 @@ function M.animate(pattern, period) end end -return M +return spinner