*mini.notify* Show notifications

MIT License Copyright (c) 2024 Evgeni Chasnovski

------------------------------------------------------------------------------
                                                                    *MiniNotify*
Features:

- Show one or more highlighted notifications in a single floating window.

- Manage notifications (add, update, remove, clear).

- Custom |vim.notify()| implementation. To adjust, use |MiniNotify.make_notify()|
  or save-restore `vim.notify` manually after calling |MiniNotify.setup()|.

- Automated show of LSP progress report.

- Track history which can be accessed with |MiniNotify.get_all()|
  and shown with |MiniNotify.show_history()|.

# Setup ~

This module needs a setup with `require('mini.notify').setup({})` (replace
`{}` with your `config` table). It will create global Lua table `MiniNotify`
which you can use for scripting or manually (with `:lua MiniNotify.*`).

See |MiniNotify.config| for `config` structure and default values.

You can override runtime config settings locally to buffer inside
`vim.b.mininotify_config` which should have same structure as
`MiniNotify.config`. See |mini.nvim-buffer-local-config| for more details.

# Comparisons ~

- [j-hui/fidget.nvim](https://github.com/j-hui/fidget.nvim):
    - Basic goals of providing interface for notifications are similar.
    - Has more configuration options and visual effects, while this module
      does not (by design).

- [rcarriga/nvim-notify](https://github.com/rcarriga/nvim-notify):
    - Similar to 'j-hui/fidget.nvim'.

# Highlight groups ~

- `MiniNotifyBorder` - window border.
- `MiniNotifyLspProgress` - notifications from built-in LSP progress report.
- `MiniNotifyNormal` - basic foreground/background highlighting.
- `MiniNotifyTitle` - window title.

To change any highlight group, set it directly with |nvim_set_hl()|.

NOTE: |vim.notify()| override after |MiniNotify.make_notify()| uses own
highlight groups per notification level.

# Disabling ~

To disable showing notifications, set `vim.g.mininotify_disable` (globally) or
`vim.b.mininotify_disable` (for a buffer) to `true`. Considering high number
of different scenarios and customization intentions, writing exact rules
for disabling module's functionality is left to user. See
|mini.nvim-disabling-recipes| for common recipes.

------------------------------------------------------------------------------
                                                      *MiniNotify-specification*
# Notification specification ~

Notification is a table with the following keys:

- <msg> `(string)` - single string with notification message.
  Use `\n` to delimit several lines.
- <level> `(string)` - notification level as key of |vim.log.levels|.
  Like "ERROR", "WARN", "INFO", etc.
- <hl_group> `(string)` - highlight group with which notification is shown.
- <data> `(table)` - extra data to store in notification (like `source`, etc.).
- <ts_add> `(number)` - timestamp of when notification is added.
- <ts_update> `(number)` - timestamp of the latest notification update.
- <ts_remove> `(number|nil)` - timestamp of when notification is removed.
  It is `nil` if notification was never removed and thus considered "active".

Notes:
- Timestamps are compatible with |strftime()| and have fractional part.

------------------------------------------------------------------------------
                                                            *MiniNotify.setup()*
                          `MiniNotify.setup`({config})
Module setup

This will also:
- Set |vim.notify()| custom implementation (see |MiniNotify.make_notify()|).
- Clean the history. Use `MiniNotify.setup(MiniNotify.config)` to force
  clean history while preserving the config.

Parameters ~
{config} `(table|nil)` Module config table. See |MiniNotify.config|.

Usage ~
>lua
  require('mini.notify').setup() -- use default config
  -- OR
  require('mini.notify').setup({}) -- replace {} with your config table
<
------------------------------------------------------------------------------
                                                             *MiniNotify.config*
                              `MiniNotify.config`
Defaults ~
>lua
  MiniNotify.config = {
    -- Content management
    content = {
      -- Function which formats the notification message
      -- By default prepends message with notification time
      format = nil,

      -- Function which orders notification array from most to least important
      -- By default orders first by level and then by update timestamp
      sort = nil,
    },

    -- Notifications about LSP progress
    lsp_progress = {
      -- Whether to enable showing
      enable = true,

      -- Notification level
      level = 'INFO',

      -- Duration (in ms) of how long last message should be shown
      duration_last = 1000,
    },

    -- Window options
    window = {
      -- Floating window config
      config = {},

      -- Maximum window width as share (between 0 and 1) of available columns
      max_width_share = 0.382,

      -- Value of 'winblend' option
      winblend = 25,
    },
  }
<
# Content ~

`config.content` defines how notifications are shown.

`content.format` is a function which takes single notification object
(see |MiniNotify-specification|) and returns a string to be used directly
when showing notification.
Default: `nil` for |MiniNotify.default_format()|.

`content.sort` is a function which takes array of notification objects
(see |MiniNotify-specification|) and returns an array of such objects.
It can be used to define custom order and/or filter for notifications which
are shown simultaneously.
Note: Input contains notifications before applying `content.format`.
Default: `nil` for |MiniNotify.default_sort()|.

Example: >lua

  require('mini.notify').setup({
    content = {
      -- Use notification message as is for LSP progress
      format = function(notif)
        if notif.data.source == 'lsp_progress' then return notif.msg end
        return MiniNotify.default_format(notif)
      end,

      -- Show more recent notifications first
      sort = function(notif_arr)
        table.sort(
          notif_arr,
          function(a, b) return a.ts_update > b.ts_update end
        )
        return notif_arr
      end,
    },
  })
<
# LSP progress ~

`config.lsp_progress` defines automated notifications for LSP progress.
It is implemented as a single updating notification per progress with all
information about it.
Setting up is done inside |MiniNotify.setup()| via |vim.schedule()|'ed setting
of |lsp-handler| for "$/progress" method.

`lsp_progress.enable` is a boolean indicating whether LSP progress should
be shown in notifications. Can be disabled in current session.
Default: `true`. Note: Should be `true` during |MiniNotify.setup()| call to be able
to enable it in current session.

`lsp_progress.level` is a level to be used in |MiniNotify.add()|.
Default: `'INFO'`.

`lsp_progress.duration_last` is a number of milliseconds for the last progress
report to be shown on screen before removing it.
Default: 1000.

Notes:
- This respects previously set handler by saving and calling it.
- Overriding "$/progress" method of `vim.lsp.handlers` disables notifications.
- All LSP progress notifications set the following fields in `data`:
    - <source> is `"lsp_progress"`.
    - <client_name> is set to client's name (provided by client or inferred).
    - <context> is the latest LSP request context (`ctx` arg of |lsp-handler|).
    - <response> is the latest LSP response (`result` arg of |lsp-handler|).

# Window ~

`config.window` defines behavior of notification window.

`window.config` is a table defining floating window characteristics
or a callable returning such table (will be called with identifier of
window's buffer already showing notifications). It should have the same
structure as in |nvim_open_win()|. It has the following default values
which show notifications in the upper right corner with upper limit on width:
- `width` is chosen to fit buffer content but at most `window.max_width_share`
  share of 'columns'.
  To have higher maximum width, use function in `config.window` which computes
  dimensions inside of it (based on buffer content).
- `height` is chosen to fit buffer content with enabled 'wrap' (assuming
  default value of `width`).
- `anchor`, `col`, and `row` are "NE", 'columns', and 0 or 1 (depending on tabline).
- `border` is "single".
- `zindex` is 999 to be as much on top as reasonably possible.

`window.max_width_share` defines maximum window width as a share of 'columns'.
Should be a number between 0 (not included) and 1.
Default: 0.382.

Example for showing notifications in bottom right corner: >lua

  local win_config = function()
    local has_statusline = vim.o.laststatus > 0
    local pad = vim.o.cmdheight + (has_statusline and 1 or 0)
    return { anchor = 'SE', col = vim.o.columns, row = vim.o.lines - pad }
  end
  require('mini.notify').setup({ window = { config = win_config } })
<
`window.winblend` defines 'winblend' value for notification window.
Default: 25.

------------------------------------------------------------------------------
                                                      *MiniNotify.make_notify()*
                        `MiniNotify.make_notify`({opts})
Make vim.notify wrapper

Calling this function creates an implementation of |vim.notify()| powered by
this module. General idea is to show notification as soon as safely possible
(see |vim.schedule_wrap()|) and remove it after a configurable amount of time.

All notifications set `source = "vim.notify"` in their `data` field.

This is used with default options inside |MiniNotify.setup()|. To adjust,
call manually after `setup()`. For example, to show errors longer: >lua

  require('mini.notify').setup()
  vim.notify = MiniNotify.make_notify({ ERROR = { duration = 10000 } })
<
To preserve original `vim.notify`: >lua

  local notify_orig = vim.notify
  require('mini.notify').setup()
  vim.notify = notify_orig
<
Parameters ~
{opts} `(table|nil)` Options to configure behavior of notification `level`
  (as in |MiniNotify.add()|). Fields are the same as names of `vim.log.levels`
  with values being tables with possible fields:
  - <duration> `(number)` - duration (in ms) of how much a notification
    should be shown. If 0 or negative, notification is not shown at all.
  - <hl_group> `(string)` - highlight group of notification.
  Only data different to default can be supplied.

  Default: >lua

  {
    ERROR = { duration = 5000, hl_group = 'DiagnosticError'  },
    WARN  = { duration = 5000, hl_group = 'DiagnosticWarn'   },
    INFO  = { duration = 5000, hl_group = 'DiagnosticInfo'   },
    DEBUG = { duration = 0,    hl_group = 'DiagnosticHint'   },
    TRACE = { duration = 0,    hl_group = 'DiagnosticOk'     },
    OFF   = { duration = 0,    hl_group = 'MiniNotifyNormal' },
  }
<
------------------------------------------------------------------------------
                                                              *MiniNotify.add()*
              `MiniNotify.add`({msg}, {level}, {hl_group}, {data})
Add notification

Add notification to history. It is considered "active" and is shown.
To hide, call |MiniNotify.remove()| with identifier this function returns.

Example: >lua

  local id = MiniNotify.add('Hello', 'WARN', 'Comment')
  vim.defer_fn(function() MiniNotify.remove(id) end, 1000)
<
Parameters ~
{msg} `(string)` Notification message.
{level} `(string|nil)` Notification level as key of |vim.log.levels|.
  Default: `'INFO'`.
{hl_group} `(string|nil)` Notification highlight group.
  Default: `'MiniNotifyNormal'`.
{data} `(table|nil)` Extra data to store in the notification.
  Default: `{}`.

Return ~
`(number)` Notification identifier.

------------------------------------------------------------------------------
                                                           *MiniNotify.update()*
                        `MiniNotify.update`({id}, {new})
Update active notification

Modify contents of active notification.

Parameters ~
{id} `(number)` Identifier of currently active notification as returned
  by |MiniNotify.add()|.
{new} `(table)` Table with contents to update. Keys should be as non-timestamp
  fields of |MiniNotify-specification| and values - new content values.
  If present, field `data` is updated as is. Use |MiniNotify.get()| together
  with |vim.tbl_deep_extend()| to change only part of it.

------------------------------------------------------------------------------
                                                           *MiniNotify.remove()*
                           `MiniNotify.remove`({id})
Remove notification

If notification is active, make it not active (by setting `ts_remove` field).
If not active, do nothing.

Parameters ~
{id} `(number|nil)` Identifier of previously added notification.
  If it is not, nothing is done (silently).

------------------------------------------------------------------------------
                                                            *MiniNotify.clear()*
                              `MiniNotify.clear`()
Remove all active notifications

Hide all active notifications and stop showing window (if shown).

------------------------------------------------------------------------------
                                                          *MiniNotify.refresh()*
                             `MiniNotify.refresh`()
Refresh notification window

Make notification window show relevant information:
- Create an array of active notifications (see |MiniNotify-specification|).
- Apply `config.content.sort` to an array. If output has zero notifications,
  make notification window to not show.
- Apply `config.content.format` to each element of notification array and
  update its message.
- Construct content from notifications and show them in a window.

Note: effects are delayed if inside fast event (|vim.in_fast_event()|).

------------------------------------------------------------------------------
                                                              *MiniNotify.get()*
                             `MiniNotify.get`({id})
Get previously added notification by id

Parameters ~
{id} `(number)` Identifier of notification.

Return ~
`(table)` Notification object (see |MiniNotify-specification|).

------------------------------------------------------------------------------
                                                          *MiniNotify.get_all()*
                             `MiniNotify.get_all`()
Get all previously added notifications

Get map of used notifications with keys being notification identifiers.

Can be used to get only active notification objects. Example: >lua

  -- Get active notifications
  vim.tbl_filter(
    function(notif) return notif.ts_remove == nil end,
    MiniNotify.get_all()
  )
<
Return ~
`(table)` Map with notification object values (see |MiniNotify-specification|).
  Note: messages are taken from last valid update.

------------------------------------------------------------------------------
                                                     *MiniNotify.show_history()*
                          `MiniNotify.show_history`()
Show history

Open or reuse a scratch buffer with all previously shown notifications.

Notes:
- Content is ordered from oldest to newest based on latest update time.
- Message is formatted with `config.content.format`.

------------------------------------------------------------------------------
                                                   *MiniNotify.default_format()*
                      `MiniNotify.default_format`({notif})
Default content format

Used by default as `config.content.format`. Prepends notification message
with the human readable update time and a separator.

Parameters ~
{notif} `(table)` Notification object (see |MiniNotify-specification|).

Return ~
`(string)` Formatted notification message.

------------------------------------------------------------------------------
                                                     *MiniNotify.default_sort()*
                     `MiniNotify.default_sort`({notif_arr})
Default content sort

Used by default as `config.content.sort`. First sorts by notification's `level`
("ERROR" > "WARN" > "INFO" > "DEBUG" > "TRACE" > "OFF"; the bigger the more
important); if draw - by latest update time (the later the more important).

Parameters ~
{notif_arr} `(table)` Array of notifications (see |MiniNotify-specification|).

Return ~
`(table)` Sorted array of notifications.


 vim:tw=78:ts=8:noet:ft=help:norl: