Regenerate nvim config
This commit is contained in:
1
config/neovim/store/lazy-plugins/nvim-notify/.github/FUNDING.yml
vendored
Normal file
1
config/neovim/store/lazy-plugins/nvim-notify/.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
github: rcarriga
|
||||
62
config/neovim/store/lazy-plugins/nvim-notify/.github/workflows/docgen.yaml
vendored
Normal file
62
config/neovim/store/lazy-plugins/nvim-notify/.github/workflows/docgen.yaml
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
# Taken from telescope
|
||||
name: Generate docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build-sources:
|
||||
name: Generate docs
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
url: https://github.com/neovim/neovim/releases/download/v0.5.1/nvim-linux64.tar.gz
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: date +%F > todays-date
|
||||
- name: Restore cache for today's nightly.
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: _neovim
|
||||
key: ${{ runner.os }}-${{ matrix.url }}-${{ hashFiles('todays-date') }}
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
test -d _neovim || {
|
||||
mkdir -p _neovim
|
||||
curl -sL ${{ matrix.url }} | tar xzf - --strip-components=1 -C "${PWD}/_neovim"
|
||||
}
|
||||
mkdir -p ~/.local/share/nvim/site/pack/vendor/start
|
||||
git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ~/.local/share/nvim/site/pack/vendor/start/plenary.nvim
|
||||
git clone --depth 1 https://github.com/tjdevries/tree-sitter-lua ~/.local/share/nvim/site/pack/vendor/start/tree-sitter-lua
|
||||
ln -s $(pwd) ~/.local/share/nvim/site/pack/vendor/start
|
||||
- name: Build parser
|
||||
run: |
|
||||
# We have to build the parser every single time to keep up with parser changes
|
||||
cd ~/.local/share/nvim/site/pack/vendor/start/tree-sitter-lua
|
||||
make dist
|
||||
cd -
|
||||
- name: Generating docs
|
||||
run: |
|
||||
export PATH="${PWD}/_neovim/bin:${PATH}"
|
||||
export VIM="${PWD}/_neovim/share/nvim/runtime"
|
||||
nvim --version
|
||||
./scripts/docgen
|
||||
- name: Update documentation
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COMMIT_MSG: |
|
||||
docs: update doc/nvim-notify.txt
|
||||
skip-checks: true
|
||||
run: |
|
||||
git config user.email "actions@github"
|
||||
git config user.name "Github Actions"
|
||||
git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
|
||||
git add doc/
|
||||
# Only commit and push if we have changes
|
||||
git diff --quiet && git diff --staged --quiet || (git commit -m "${COMMIT_MSG}"; git push origin HEAD:${GITHUB_REF})
|
||||
70
config/neovim/store/lazy-plugins/nvim-notify/.github/workflows/workflow.yaml
vendored
Normal file
70
config/neovim/store/lazy-plugins/nvim-notify/.github/workflows/workflow.yaml
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
name: nvim-notify Workflow
|
||||
on:
|
||||
- push
|
||||
jobs:
|
||||
style:
|
||||
name: style
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: JohnnyMorganz/stylua-action@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: latest
|
||||
args: --check lua/ tests/
|
||||
|
||||
tests:
|
||||
name: tests
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
url: https://github.com/neovim/neovim/releases/download/v0.5.1/nvim-linux64.tar.gz
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: date +%F > todays-date
|
||||
- name: Restore cache for today's nightly.
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: _neovim
|
||||
key: ${{ runner.os }}-x64-${{ hashFiles('todays-date') }}
|
||||
|
||||
- name: Prepare dependencies
|
||||
run: |
|
||||
test -d _neovim || {
|
||||
mkdir -p _neovim
|
||||
curl -sL ${{ matrix.url }} | tar xzf - --strip-components=1 -C "${PWD}/_neovim"
|
||||
}
|
||||
mkdir -p ~/.local/share/nvim/site/pack/vendor/start
|
||||
git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ~/.local/share/nvim/site/pack/vendor/start/plenary.nvim
|
||||
ln -s $(pwd) ~/.local/share/nvim/site/pack/vendor/start
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
export PATH="${PWD}/_neovim/bin:${PATH}"
|
||||
export VIM="${PWD}/_neovim/share/nvim/runtime"
|
||||
nvim --version
|
||||
./scripts/test
|
||||
|
||||
release:
|
||||
name: release
|
||||
if: ${{ github.ref == 'refs/heads/master' }}
|
||||
needs:
|
||||
- style
|
||||
- tests
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: npx semantic-release
|
||||
3
config/neovim/store/lazy-plugins/nvim-notify/.gitignore
vendored
Normal file
3
config/neovim/store/lazy-plugins/nvim-notify/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
neovim/
|
||||
plenary.nvim/
|
||||
doc/tags
|
||||
12
config/neovim/store/lazy-plugins/nvim-notify/.releaserc.json
Normal file
12
config/neovim/store/lazy-plugins/nvim-notify/.releaserc.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
[
|
||||
"@semantic-release/github",
|
||||
{
|
||||
"successComment": false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
21
config/neovim/store/lazy-plugins/nvim-notify/Dockerfile
Normal file
21
config/neovim/store/lazy-plugins/nvim-notify/Dockerfile
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
ARG NEOVIM_RELEASE=${NEOVIM_RELEASE:-https://github.com/neovim/neovim/releases/download/nightly/nvim-linux64.tar.gz}
|
||||
FROM ubuntu
|
||||
ARG NEOVIM_RELEASE
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get -y install git curl tar gcc g++
|
||||
RUN mkdir /neovim
|
||||
RUN curl -sL ${NEOVIM_RELEASE} | tar xzf - --strip-components=1 -C "/neovim"
|
||||
RUN git clone --depth 1 https://github.com/nvim-lua/plenary.nvim
|
||||
RUN git clone --depth 1 https://github.com/tjdevries/tree-sitter-lua
|
||||
|
||||
WORKDIR tree-sitter-lua
|
||||
RUN mkdir -p build parser; \
|
||||
cc -o ./build/parser.so -I ./src src/parser.c src/scanner.c -shared -Os -lstdc++ -fPIC; \
|
||||
ln -s ../build/parser.so parser/lua.so;
|
||||
|
||||
RUN mkdir /notify
|
||||
WORKDIR /notify
|
||||
|
||||
ENTRYPOINT ["bash", "-c", "PATH=/neovim/bin:${PATH} VIM=/neovim/share/nvim/runtime nvim --headless -c 'set rtp+=. | set rtp+=../plenary.nvim/ | set rtp+=../tree-sitter-lua/ | runtime! plugin/plenary.vim | luafile ./scripts/gendocs.lua' -c 'qa'"]
|
||||
21
config/neovim/store/lazy-plugins/nvim-notify/LICENSE
Normal file
21
config/neovim/store/lazy-plugins/nvim-notify/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Rónán Carrigan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
323
config/neovim/store/lazy-plugins/nvim-notify/README.md
Normal file
323
config/neovim/store/lazy-plugins/nvim-notify/README.md
Normal file
@ -0,0 +1,323 @@
|
||||
# nvim-notify
|
||||
|
||||
A fancy, configurable, notification manager for NeoVim
|
||||
|
||||

|
||||
|
||||
Credit to [sunjon](https://github.com/sunjon) for [the design](https://neovim.discourse.group/t/wip-animated-notifications-plugin/448) that inspired the appearance of this plugin.
|
||||
|
||||
* [Installation](#Installation)
|
||||
* [Usage](#usage)
|
||||
- [Viewing History](#viewing-history)
|
||||
* [Configuration](#configuration)
|
||||
- [Setup](#setup)
|
||||
- [Highlights](#highlights)
|
||||
- [Render Style](#render-style)
|
||||
- [Animation Style](#animation-style)
|
||||
+ [Opening the window](#opening-the-window)
|
||||
+ [Changing the window](#changing-the-window)
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Make sure to use a font which supported glyphs (icons), font can be found [here](https://github.com/ryanoasis/nerd-fonts).
|
||||
|
||||
24-bit colour is required, which can be enabled by adding this to your init.lua
|
||||
```lua
|
||||
vim.opt.termguicolors = true
|
||||
```
|
||||
|
||||
Then you can install nvim-notify with the package manager of your choice.
|
||||
|
||||
[**dein**](https://github.com/Shougo/dein.vim):
|
||||
|
||||
```vim
|
||||
call dein#add("rcarriga/nvim-notify")
|
||||
```
|
||||
|
||||
[**vim-plug**](https://github.com/junegunn/vim-plug):
|
||||
|
||||
```vim
|
||||
Plug 'rcarriga/nvim-notify'
|
||||
```
|
||||
|
||||
[**packer**](https://github.com/wbthomason/packer.nvim):
|
||||
|
||||
```lua
|
||||
use 'rcarriga/nvim-notify'
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Simply call the module with a message!
|
||||
|
||||
```lua
|
||||
require("notify")("My super important message")
|
||||
```
|
||||
|
||||
Other plugins can use the notification windows by setting it as your default notify function
|
||||
|
||||
```lua
|
||||
vim.notify = require("notify")
|
||||
```
|
||||
|
||||
You can supply a level to change the border highlighting
|
||||
|
||||
```lua
|
||||
vim.notify("This is an error message", "error")
|
||||
```
|
||||
|
||||
Updating an existing notification is also possible!
|
||||
|
||||

|
||||
|
||||
|
||||
Use treesitter highlighting inside notifications with opacity changing
|
||||
|
||||

|
||||
|
||||
There are a number of custom options that can be supplied in a table as the third argument.
|
||||
See `:h NotifyOptions` for details.
|
||||
|
||||
Sample code for the first GIF above:
|
||||
|
||||
```lua
|
||||
local plugin = "My Awesome Plugin"
|
||||
|
||||
vim.notify("This is an error message.\nSomething went wrong!", "error", {
|
||||
title = plugin,
|
||||
on_open = function()
|
||||
vim.notify("Attempting recovery.", vim.log.levels.WARN, {
|
||||
title = plugin,
|
||||
})
|
||||
local timer = vim.loop.new_timer()
|
||||
timer:start(2000, 0, function()
|
||||
vim.notify({ "Fixing problem.", "Please wait..." }, "info", {
|
||||
title = plugin,
|
||||
timeout = 3000,
|
||||
on_close = function()
|
||||
vim.notify("Problem solved", nil, { title = plugin })
|
||||
vim.notify("Error code 0x0395AF", 1, { title = plugin })
|
||||
end,
|
||||
})
|
||||
end)
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
You can also use plenary's async library to avoid using callbacks:
|
||||
```lua
|
||||
local async = require("plenary.async")
|
||||
local notify = require("notify").async
|
||||
|
||||
async.run(function()
|
||||
notify("Let's wait for this to close").events.close()
|
||||
notify("It closed!")
|
||||
end)
|
||||
```
|
||||
|
||||
Set a custom filetype to take advantage of treesitter highlighting:
|
||||
|
||||
```lua
|
||||
vim.notify(text, "info", {
|
||||
title = "My Awesome Plugin",
|
||||
on_open = function(win)
|
||||
local buf = vim.api.nvim_win_get_buf(win)
|
||||
vim.api.nvim_buf_set_option(buf, "filetype", "markdown")
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
Check out the wiki for more examples!
|
||||
|
||||
### Viewing History
|
||||
|
||||
If you have [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim) installed then you can use the `notify` extension to search the history:
|
||||
|
||||
```vim
|
||||
:Telescope notify
|
||||
```
|
||||
or in lua
|
||||
```lua
|
||||
require('telescope').extensions.notify.notify(<opts>)
|
||||
```
|
||||
|
||||
**Note:** If you lazy load `telescope` you should manually call `require("telescope").load_extension("notify")` before using the above commands. If you don't lazy load `telescope` then `notify` does this for you.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://user-images.githubusercontent.com/24252670/136264308-2fcdfe57-a8f6-4b34-8ea1-e3a8349bc581.png" />
|
||||
</p>
|
||||
|
||||
There is a command to display a log of the history.
|
||||
|
||||
```vim
|
||||
:Notifications
|
||||
```
|
||||
|
||||
<p align="center">
|
||||
<img src="https://user-images.githubusercontent.com/24252670/136264653-83038119-634b-48e7-8e8a-0edf4546efe2.png" />
|
||||
</p>
|
||||
|
||||
You can get a list of past notifications with the history function
|
||||
```lua
|
||||
require("notify").history()
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Setup
|
||||
|
||||
You can optionally call the `setup` function to provide configuration options
|
||||
|
||||
See `:h notify.Config` for options and `:h notify.setup()` for default values.
|
||||
|
||||
### Highlights
|
||||
|
||||
You can define custom highlights by supplying highlight groups for each of the levels.
|
||||
The naming scheme follows a simple structure: `Notify<upper case level name><section>`.
|
||||
If you want to use custom levels, you can define the highlights for them or
|
||||
they will follow the `INFO` highlights by default.
|
||||
|
||||
Here are the defaults:
|
||||
|
||||
```vim
|
||||
highlight NotifyERRORBorder guifg=#8A1F1F
|
||||
highlight NotifyWARNBorder guifg=#79491D
|
||||
highlight NotifyINFOBorder guifg=#4F6752
|
||||
highlight NotifyDEBUGBorder guifg=#8B8B8B
|
||||
highlight NotifyTRACEBorder guifg=#4F3552
|
||||
highlight NotifyERRORIcon guifg=#F70067
|
||||
highlight NotifyWARNIcon guifg=#F79000
|
||||
highlight NotifyINFOIcon guifg=#A9FF68
|
||||
highlight NotifyDEBUGIcon guifg=#8B8B8B
|
||||
highlight NotifyTRACEIcon guifg=#D484FF
|
||||
highlight NotifyERRORTitle guifg=#F70067
|
||||
highlight NotifyWARNTitle guifg=#F79000
|
||||
highlight NotifyINFOTitle guifg=#A9FF68
|
||||
highlight NotifyDEBUGTitle guifg=#8B8B8B
|
||||
highlight NotifyTRACETitle guifg=#D484FF
|
||||
highlight link NotifyERRORBody Normal
|
||||
highlight link NotifyWARNBody Normal
|
||||
highlight link NotifyINFOBody Normal
|
||||
highlight link NotifyDEBUGBody Normal
|
||||
highlight link NotifyTRACEBody Normal
|
||||
```
|
||||
|
||||
### Render Style
|
||||
|
||||
The appearance of the notifications can be configured, using either built-in methods or custom functions.
|
||||
See `:help notify-render()` for details
|
||||
|
||||
1. "default"
|
||||
|
||||

|
||||
|
||||
2. "minimal"
|
||||
|
||||

|
||||
|
||||
3. "simple"
|
||||
|
||||

|
||||
|
||||
4. "compact"
|
||||
|
||||

|
||||
|
||||
5. "wrapped-compact"
|
||||
|
||||
Mostly same as `compact`, but lines are wrapped based on `max_width`, some padding is added.
|
||||
|
||||

|
||||
|
||||
Feel free to submit custom rendering functions to share with others!
|
||||
|
||||
### Animation Style
|
||||
|
||||
The animation is designed to work in stages. The first stage is the opening of
|
||||
the window, and all subsequent stages can changes the position or opacity of
|
||||
the window. You can use one of the built-in styles or provide your own in the setup.
|
||||
|
||||
1. "fade_in_slide_out"
|
||||
|
||||

|
||||
|
||||
2. "fade"
|
||||
|
||||

|
||||
|
||||
3. "slide"
|
||||
|
||||

|
||||
|
||||
4. "static"
|
||||
|
||||

|
||||
|
||||
Custom styles can be provided by setting the config `stages` value to a list of
|
||||
functions.
|
||||
|
||||
If you create a custom style, feel free to open a PR to submit it as a built-in style!
|
||||
|
||||
**NB.** This is a prototype API that is open to change. I am looking for
|
||||
feedback on both issues or extra data that could be useful in creating
|
||||
animation styles.
|
||||
|
||||
Check the [built-in styles](./lua/notify/stages/) to see examples
|
||||
|
||||
#### Opening the window
|
||||
|
||||
The first function in the list should return a table to be provided to
|
||||
`nvim_open_win`, optionally including an extra `opacity` key which can be
|
||||
between 0-100.
|
||||
|
||||
The function is given a state table that contains the following keys:
|
||||
|
||||
- `message: table` State of the message to be shown
|
||||
- `width` Width of the message buffer
|
||||
- `height` Height of the message buffer
|
||||
- `open_windows: integer[]` List of all window IDs currently showing messages
|
||||
- `buffer: integer` The buffer containing the rendered notification message.
|
||||
|
||||
If a notification can't be shown at the moment the function should return `nil`.
|
||||
|
||||
#### Changing the window
|
||||
|
||||
All following functions should return the goal values for the window to reach from it's current point.
|
||||
They will receive the same state object as the initial function and a second argument of the window ID.
|
||||
|
||||
The following fields can be returned in a table:
|
||||
- `col`
|
||||
- `row`
|
||||
- `height`
|
||||
- `width`
|
||||
- `opacity`
|
||||
|
||||
These can be provided as either numbers or as a table. If they are
|
||||
provided as numbers then they will change instantly the value given.
|
||||
|
||||
If they are provided as a table, they will be treated as a value to animate towards.
|
||||
This uses a dampened spring algorithm to provide a natural feel to the movement.
|
||||
|
||||
The table must contain the goal value as the 1st index (e.g. `{10}`)
|
||||
|
||||
All other values are provided with keys:
|
||||
|
||||
- `damping: number` How motion decays over time. Values less than 1 mean the spring can overshoot.
|
||||
- Bounds: >= 0
|
||||
- Default: 1
|
||||
- `frequency: number` How fast the spring oscillates
|
||||
- Bounds: >= 0
|
||||
- Default: 1
|
||||
- `complete: fun(value: number): bool` Function to determine if value has reached its goal. If not
|
||||
provided it will complete when the value rounded to 2 decimal places is equal
|
||||
to the goal.
|
||||
|
||||
Once the last function has reached its goals, the window is removed.
|
||||
|
||||
One of the stages should also return the key `time` set to true. This is
|
||||
treated as the stage which the notification is on a timer. The goals of this
|
||||
stage are not used to check if it is complete. The next stage will start
|
||||
once the notification reaches its timeout.
|
||||
235
config/neovim/store/lazy-plugins/nvim-notify/doc/nvim-notify.txt
Normal file
235
config/neovim/store/lazy-plugins/nvim-notify/doc/nvim-notify.txt
Normal file
@ -0,0 +1,235 @@
|
||||
*nvim-notify.txt* A fancy, configurable notification manager for NeoVim
|
||||
|
||||
==============================================================================
|
||||
|
||||
A fancy, configurable notification manager for NeoVim
|
||||
|
||||
notify *notify*
|
||||
|
||||
|
||||
*notify.setup()*
|
||||
`setup`({user_config})
|
||||
|
||||
Configure nvim-notify
|
||||
See: ~
|
||||
|notify.Config|
|
||||
|notify-render|
|
||||
|
||||
Parameters~
|
||||
{user_config} `(notify.Config|nil)`
|
||||
Default values:
|
||||
>lua
|
||||
{
|
||||
background_colour = "NotifyBackground",
|
||||
fps = 30,
|
||||
icons = {
|
||||
DEBUG = "",
|
||||
ERROR = "",
|
||||
INFO = "",
|
||||
TRACE = "✎",
|
||||
WARN = ""
|
||||
},
|
||||
level = 2,
|
||||
minimum_width = 50,
|
||||
render = "default",
|
||||
stages = "fade_in_slide_out",
|
||||
time_formats = {
|
||||
notification = "%T",
|
||||
notification_history = "%FT%T"
|
||||
},
|
||||
timeout = 5000,
|
||||
top_down = true
|
||||
}
|
||||
<
|
||||
|
||||
*notify.Options*
|
||||
Options for an individual notification
|
||||
Fields~
|
||||
{title} `(string)`
|
||||
{icon} `(string)`
|
||||
{timeout} `(number|boolean)` Time to show notification in milliseconds, set to false to disable timeout.
|
||||
{on_open} `(function)` Callback for when window opens, receives window as argument.
|
||||
{on_close} `(function)` Callback for when window closes, receives window as argument.
|
||||
{keep} `(function)` Function to keep the notification window open after timeout, should return boolean.
|
||||
{render} `(function|string)` Function to render a notification buffer.
|
||||
{replace} `(integer|notify.Record)` Notification record or the record `id` field. Replace an existing notification if still open. All arguments not given are inherited from the replaced notification including message and level.
|
||||
{hide_from_history} `(boolean)` Hide this notification from the history
|
||||
{animate} `(boolean)` If false, the window will jump to the timed stage. Intended for use in blocking events (e.g. vim.fn.input)
|
||||
|
||||
*notify.Events*
|
||||
Async events for a notification
|
||||
Fields~
|
||||
{open} `(function)` Resolves when notification is opened
|
||||
{close} `(function)` Resolved when notification is closed
|
||||
|
||||
*notify.Record*
|
||||
Record of a previously sent notification
|
||||
Fields~
|
||||
{id} `(integer)`
|
||||
{message} `(string[])` Lines of the message
|
||||
{level} `(string|integer)` Log level. See vim.log.levels
|
||||
{title} `(string[])` Left and right sections of the title
|
||||
{icon} `(string)` Icon used for notification
|
||||
{time} `(number)` Time of message, as returned by `vim.fn.localtime()`
|
||||
{render} `(function)` Function to render notification buffer
|
||||
|
||||
*notify.AsyncRecord*
|
||||
Inherits: `notify.Record`
|
||||
|
||||
Fields~
|
||||
{events} `(notify.Events)`
|
||||
|
||||
*notify.notify()*
|
||||
`notify`({message}, {level}, {opts})
|
||||
|
||||
Display a notification.
|
||||
|
||||
You can call the module directly rather than using this:
|
||||
>lua
|
||||
require("notify")(message, level, opts)
|
||||
<
|
||||
Parameters~
|
||||
{message} `(string|string[])` Notification message
|
||||
{level} `(string|number)` Log level. See vim.log.levels
|
||||
{opts} `(notify.Options)` Notification options
|
||||
Return~
|
||||
`(notify.Record)`
|
||||
|
||||
*notify.async()*
|
||||
`async`({message}, {level}, {opts})
|
||||
|
||||
Display a notification asynchronously
|
||||
|
||||
This uses plenary's async library, allowing a cleaner interface for
|
||||
open/close events. You must call this function within an async context.
|
||||
|
||||
The `on_close` and `on_open` options are not used.
|
||||
|
||||
Parameters~
|
||||
{message} `(string|string[])` Notification message
|
||||
{level} `(string|number)` Log level. See vim.log.levels
|
||||
{opts} `(notify.Options)` Notification options
|
||||
Return~
|
||||
`(notify.AsyncRecord)`
|
||||
|
||||
*notify.history()*
|
||||
`history`({opts})
|
||||
|
||||
Get records of all previous notifications
|
||||
|
||||
You can use the `:Notifications` command to display a log of previous notifications
|
||||
Parameters~
|
||||
{opts?} `(notify.HistoryOpts)`
|
||||
Return~
|
||||
`(notify.Record[])`
|
||||
|
||||
*notify.HistoryOpts*
|
||||
Fields~
|
||||
{include_hidden} `(boolean)` Include notifications hidden from history
|
||||
|
||||
*notify.dismiss()*
|
||||
`dismiss`({opts})
|
||||
|
||||
Dismiss all notification windows currently displayed
|
||||
Parameters~
|
||||
{opts} `(notify.DismissOpts)`
|
||||
|
||||
*notify.DismissOpts*
|
||||
Fields~
|
||||
{pending} `(boolean)` Clear pending notifications
|
||||
{silent} `(boolean)` Suppress notification that pending notifications were dismissed.
|
||||
|
||||
*notify.open()*
|
||||
`open`({notif_id}, {opts})
|
||||
|
||||
Open a notification in a new buffer
|
||||
Parameters~
|
||||
{notif_id} `(integer|notify.Record)`
|
||||
{opts} `(notify.OpenOpts)`
|
||||
Return~
|
||||
`(notify.OpenedBuffer)`
|
||||
|
||||
*notify.OpenOpts*
|
||||
Fields~
|
||||
{buffer} `(integer)` Use this buffer, instead of creating a new one
|
||||
{max_width} `(integer)` Render message to this width (used to limit window decoration sizes)
|
||||
|
||||
*notify.OpenedBuffer*
|
||||
Fields~
|
||||
{buffer} `(integer)` Created buffer number
|
||||
{height} `(integer)` Height of the buffer content including extmarks
|
||||
{width} `(integer)` width of the buffer content including extmarks
|
||||
{highlights} `(table<string, string>)` Highlights used for the buffer contents
|
||||
|
||||
*notify.pending()*
|
||||
`pending`()
|
||||
|
||||
Number of notifications currently waiting to be displayed
|
||||
Return~
|
||||
`(integer[])`
|
||||
|
||||
*notify.instance()*
|
||||
`instance`({user_config}, {inherit})
|
||||
|
||||
Configure an instance of nvim-notify.
|
||||
You can use this to manage a separate instance of nvim-notify with completely different configuration.
|
||||
The returned instance will have the same functions as the notify module.
|
||||
Parameters~
|
||||
{user_config} `(notify.Config)`
|
||||
{inherit?} `(boolean)` Inherit the global configuration, default true
|
||||
|
||||
|
||||
==============================================================================
|
||||
notify.config *notify.config*
|
||||
|
||||
*notify.Config*
|
||||
Fields~
|
||||
{level} `(string|integer)` Minimum log level to display. See vim.log.levels.
|
||||
{timeout} `(number)` Default timeout for notification
|
||||
{max_width} `(number|function)` Max number of columns for messages
|
||||
{max_height} `(number|function)` Max number of lines for a message
|
||||
{stages} `(string|function[])` Animation stages
|
||||
{background_colour} `(string)` For stages that change opacity this is treated as the highlight behind the window. Set this to either a highlight group, an RGB hex value e.g. "#000000" or a function returning an RGB code for dynamic values
|
||||
{icons} `(table)` Icons for each level (upper case names)
|
||||
{time_formats} `(table)` Time formats for different kind of notifications
|
||||
{on_open} `(function)` Function called when a new window is opened, use for changing win settings/config
|
||||
{on_close} `(function)` Function called when a window is closed
|
||||
{render} `(function|string)` Function to render a notification buffer or a built-in renderer name
|
||||
{minimum_width} `(integer)` Minimum width for notification windows
|
||||
{fps} `(integer)` Frames per second for animation stages, higher value means smoother animations but more CPU usage
|
||||
{top_down} `(boolean)` whether or not to position the notifications at the top or not
|
||||
|
||||
|
||||
==============================================================================
|
||||
notify-render *notify-render*
|
||||
|
||||
Notification buffer rendering
|
||||
|
||||
Custom rendering can be provided by both the user config in the setup or on
|
||||
an individual notification using the `render` key.
|
||||
The key can either be the name of a built-in renderer or a custom function.
|
||||
|
||||
Built-in renderers:
|
||||
- `"default"`
|
||||
- `"minimal"`
|
||||
- `"simple"`
|
||||
- `"compact"`
|
||||
- `"wrapped-compact"`
|
||||
|
||||
Custom functions should accept a buffer, a notification record and a highlights table
|
||||
|
||||
>
|
||||
render: fun(buf: integer, notification: notify.Record, highlights: notify.Highlights, config)
|
||||
<
|
||||
You should use the provided highlight groups to take advantage of opacity
|
||||
changes as they will be updated as the notification is animated
|
||||
|
||||
*notify.Highlights*
|
||||
Fields~
|
||||
{title} `(string)`
|
||||
{icon} `(string)`
|
||||
{border} `(string)`
|
||||
{body} `(string)`
|
||||
|
||||
|
||||
vim:tw=78:ts=8:noet:ft=help:norl:
|
||||
@ -0,0 +1,5 @@
|
||||
local M = {}
|
||||
|
||||
M.spring = require("notify.animate.spring")
|
||||
|
||||
return M
|
||||
@ -0,0 +1,56 @@
|
||||
-- Adapted from https://gist.github.com/Fraktality/1033625223e13c01aa7144abe4aaf54d
|
||||
-- Explanation found here https://www.ryanjuckett.com/damped-springs/
|
||||
local pi = math.pi
|
||||
local exp = math.exp
|
||||
local sin = math.sin
|
||||
local cos = math.cos
|
||||
local sqrt = math.sqrt
|
||||
|
||||
---@class SpringState
|
||||
---@field position number
|
||||
---@field velocity number | nil
|
||||
|
||||
---@param dt number @Step in time
|
||||
---@param state SpringState
|
||||
return function(dt, goal, state, frequency, damping)
|
||||
local angular_freq = frequency * 2 * pi
|
||||
|
||||
local cur_vel = state.velocity or 0
|
||||
|
||||
local offset = state.position - goal
|
||||
local decay = exp(-dt * damping * angular_freq)
|
||||
|
||||
local new_pos
|
||||
local new_vel
|
||||
|
||||
if damping == 1 then -- critically damped
|
||||
new_pos = (cur_vel * dt + offset * (angular_freq * dt + 1)) * decay + goal
|
||||
new_vel = (cur_vel - angular_freq * dt * (offset * angular_freq + cur_vel)) * decay
|
||||
elseif damping < 1 then -- underdamped
|
||||
local c = sqrt(1 - damping * damping)
|
||||
|
||||
local i = cos(angular_freq * c * dt)
|
||||
local j = sin(angular_freq * c * dt)
|
||||
|
||||
new_pos = (i * offset + j * (cur_vel + damping * angular_freq * offset) / (angular_freq * c))
|
||||
* decay
|
||||
+ goal
|
||||
new_vel = (i * c * cur_vel - j * (cur_vel * damping + angular_freq * offset)) * decay / c
|
||||
else -- overdamped
|
||||
local c = sqrt(damping * damping - 1)
|
||||
|
||||
local r1 = -angular_freq * (damping - c)
|
||||
local r2 = -angular_freq * (damping + c)
|
||||
|
||||
local co2 = (cur_vel - r1 * offset) / (2 * angular_freq * c)
|
||||
local co1 = offset - co2
|
||||
|
||||
local e1 = co1 * exp(r1 * dt)
|
||||
local e2 = co2 * exp(r2 * dt)
|
||||
|
||||
new_pos = e1 + e2 + goal
|
||||
new_pos = r1 * e1 + r2 * e2
|
||||
end
|
||||
state.position = new_pos
|
||||
state.velocity = new_vel
|
||||
end
|
||||
@ -0,0 +1,41 @@
|
||||
local M = {}
|
||||
|
||||
function M.setup()
|
||||
vim.cmd([[
|
||||
hi default link NotifyBackground Normal
|
||||
hi default NotifyERRORBorder guifg=#8A1F1F
|
||||
hi default NotifyWARNBorder guifg=#79491D
|
||||
hi default NotifyINFOBorder guifg=#4F6752
|
||||
hi default NotifyDEBUGBorder guifg=#8B8B8B
|
||||
hi default NotifyTRACEBorder guifg=#4F3552
|
||||
hi default NotifyERRORIcon guifg=#F70067
|
||||
hi default NotifyWARNIcon guifg=#F79000
|
||||
hi default NotifyINFOIcon guifg=#A9FF68
|
||||
hi default NotifyDEBUGIcon guifg=#8B8B8B
|
||||
hi default NotifyTRACEIcon guifg=#D484FF
|
||||
hi default NotifyERRORTitle guifg=#F70067
|
||||
hi default NotifyWARNTitle guifg=#F79000
|
||||
hi default NotifyINFOTitle guifg=#A9FF68
|
||||
hi default NotifyDEBUGTitle guifg=#8B8B8B
|
||||
hi default NotifyTRACETitle guifg=#D484FF
|
||||
hi default link NotifyERRORBody Normal
|
||||
hi default link NotifyWARNBody Normal
|
||||
hi default link NotifyINFOBody Normal
|
||||
hi default link NotifyDEBUGBody Normal
|
||||
hi default link NotifyTRACEBody Normal
|
||||
|
||||
hi default link NotifyLogTime Comment
|
||||
hi default link NotifyLogTitle Special
|
||||
]])
|
||||
end
|
||||
|
||||
M.setup()
|
||||
|
||||
vim.cmd([[
|
||||
augroup NvimNotifyRefreshHighlights
|
||||
autocmd!
|
||||
autocmd ColorScheme * lua require('notify.config.highlights').setup()
|
||||
augroup END
|
||||
]])
|
||||
|
||||
return M
|
||||
@ -0,0 +1,202 @@
|
||||
---@tag notify.config
|
||||
|
||||
local Config = {}
|
||||
local util = require("notify.util")
|
||||
|
||||
require("notify.config.highlights")
|
||||
|
||||
local BUILTIN_RENDERERS = {
|
||||
DEFAULT = "default",
|
||||
MINIMAL = "minimal",
|
||||
}
|
||||
|
||||
local BUILTIN_STAGES = {
|
||||
FADE = "fade",
|
||||
SLIDE = "slide",
|
||||
FADE_IN_SLIDE_OUT = "fade_in_slide_out",
|
||||
STATIC = "static",
|
||||
}
|
||||
|
||||
local default_config = {
|
||||
level = vim.log.levels.INFO,
|
||||
timeout = 5000,
|
||||
max_width = nil,
|
||||
max_height = nil,
|
||||
stages = BUILTIN_STAGES.FADE_IN_SLIDE_OUT,
|
||||
render = BUILTIN_RENDERERS.DEFAULT,
|
||||
background_colour = "NotifyBackground",
|
||||
on_open = nil,
|
||||
on_close = nil,
|
||||
minimum_width = 50,
|
||||
fps = 30,
|
||||
top_down = true,
|
||||
time_formats = {
|
||||
notification_history = "%FT%T",
|
||||
notification = "%T",
|
||||
},
|
||||
icons = {
|
||||
ERROR = "",
|
||||
WARN = "",
|
||||
INFO = "",
|
||||
DEBUG = "",
|
||||
TRACE = "✎",
|
||||
},
|
||||
}
|
||||
|
||||
---@class notify.Config
|
||||
---@field level string|integer Minimum log level to display. See vim.log.levels.
|
||||
---@field timeout number Default timeout for notification
|
||||
---@field max_width number|function Max number of columns for messages
|
||||
---@field max_height number|function Max number of lines for a message
|
||||
---@field stages string|function[] Animation stages
|
||||
---@field background_colour string For stages that change opacity this is treated as the highlight behind the window. Set this to either a highlight group, an RGB hex value e.g. "#000000" or a function returning an RGB code for dynamic values
|
||||
---@field icons table Icons for each level (upper case names)
|
||||
---@field time_formats table Time formats for different kind of notifications
|
||||
---@field on_open function Function called when a new window is opened, use for changing win settings/config
|
||||
---@field on_close function Function called when a window is closed
|
||||
---@field render function|string Function to render a notification buffer or a built-in renderer name
|
||||
---@field minimum_width integer Minimum width for notification windows
|
||||
---@field fps integer Frames per second for animation stages, higher value means smoother animations but more CPU usage
|
||||
---@field top_down boolean whether or not to position the notifications at the top or not
|
||||
|
||||
local opacity_warned = false
|
||||
|
||||
local function validate_highlight(colour_or_group, needs_opacity)
|
||||
if type(colour_or_group) == "function" then
|
||||
return colour_or_group
|
||||
end
|
||||
if colour_or_group:sub(1, 1) == "#" then
|
||||
return function()
|
||||
return colour_or_group
|
||||
end
|
||||
end
|
||||
return function()
|
||||
local group = vim.api.nvim_get_hl_by_name(colour_or_group, true)
|
||||
if not group or not group.background then
|
||||
if needs_opacity and not opacity_warned then
|
||||
opacity_warned = true
|
||||
vim.schedule(function()
|
||||
vim.notify("Highlight group '" .. colour_or_group .. [[' has no background highlight
|
||||
Please provide an RGB hex value or highlight group with a background value for 'background_colour' option.
|
||||
This is the colour that will be used for 100% transparency.
|
||||
```lua
|
||||
require("notify").setup({
|
||||
background_colour = "#000000",
|
||||
})
|
||||
```
|
||||
Defaulting to #000000]], "warn", {
|
||||
title = "nvim-notify",
|
||||
on_open = function(win)
|
||||
local buf = vim.api.nvim_win_get_buf(win)
|
||||
vim.api.nvim_buf_set_option(buf, "filetype", "markdown")
|
||||
end,
|
||||
})
|
||||
end)
|
||||
end
|
||||
return "#000000"
|
||||
end
|
||||
return string.format("#%x", group.background)
|
||||
end
|
||||
end
|
||||
|
||||
function Config._format_default()
|
||||
local lines = { "Default values:", ">lua" }
|
||||
for line in vim.gsplit(vim.inspect(default_config), "\n", true) do
|
||||
table.insert(lines, " " .. line)
|
||||
end
|
||||
table.insert(lines, "<")
|
||||
return lines
|
||||
end
|
||||
|
||||
function Config.setup(custom_config)
|
||||
local user_config = vim.tbl_deep_extend("keep", custom_config or {}, default_config)
|
||||
local config = {}
|
||||
|
||||
function config.merged()
|
||||
return user_config
|
||||
end
|
||||
|
||||
function config.level()
|
||||
local level = user_config.level
|
||||
if type(level) == "number" then
|
||||
return level
|
||||
end
|
||||
return vim.log.levels[vim.fn.toupper(level)] or vim.log.levels.INFO
|
||||
end
|
||||
|
||||
function config.fps()
|
||||
return user_config.fps
|
||||
end
|
||||
|
||||
function config.background_colour()
|
||||
return tonumber(user_config.background_colour():gsub("#", "0x"), 16)
|
||||
end
|
||||
|
||||
function config.time_formats()
|
||||
return user_config.time_formats
|
||||
end
|
||||
|
||||
function config.icons()
|
||||
return user_config.icons
|
||||
end
|
||||
|
||||
function config.stages()
|
||||
return user_config.stages
|
||||
end
|
||||
|
||||
function config.default_timeout()
|
||||
return user_config.timeout
|
||||
end
|
||||
|
||||
function config.on_open()
|
||||
return user_config.on_open
|
||||
end
|
||||
|
||||
function config.top_down()
|
||||
return user_config.top_down
|
||||
end
|
||||
|
||||
function config.on_close()
|
||||
return user_config.on_close
|
||||
end
|
||||
|
||||
function config.render()
|
||||
return user_config.render
|
||||
end
|
||||
|
||||
function config.minimum_width()
|
||||
return user_config.minimum_width
|
||||
end
|
||||
|
||||
function config.max_width()
|
||||
return util.is_callable(user_config.max_width) and user_config.max_width()
|
||||
or user_config.max_width
|
||||
end
|
||||
|
||||
function config.max_height()
|
||||
return util.is_callable(user_config.max_height) and user_config.max_height()
|
||||
or user_config.max_height
|
||||
end
|
||||
|
||||
local stages = config.stages()
|
||||
|
||||
local needs_opacity =
|
||||
vim.tbl_contains({ BUILTIN_STAGES.FADE_IN_SLIDE_OUT, BUILTIN_STAGES.FADE }, stages)
|
||||
|
||||
if needs_opacity and not vim.opt.termguicolors:get() and vim.fn.has("nvim-0.10") == 0 then
|
||||
user_config.stages = BUILTIN_STAGES.STATIC
|
||||
vim.schedule(function()
|
||||
vim.notify(
|
||||
"Opacity changes require termguicolors to be set.\nChange to different animation stages or set termguicolors to disable this warning",
|
||||
"warn",
|
||||
{ title = "nvim-notify" }
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
user_config.background_colour = validate_highlight(user_config.background_colour, needs_opacity)
|
||||
|
||||
return config
|
||||
end
|
||||
|
||||
return Config
|
||||
199
config/neovim/store/lazy-plugins/nvim-notify/lua/notify/init.lua
Normal file
199
config/neovim/store/lazy-plugins/nvim-notify/lua/notify/init.lua
Normal file
@ -0,0 +1,199 @@
|
||||
---@text
|
||||
--- A fancy, configurable notification manager for NeoVim
|
||||
|
||||
local config = require("notify.config")
|
||||
local instance = require("notify.instance")
|
||||
|
||||
---@class notify
|
||||
local notify = {}
|
||||
|
||||
local global_instance, global_config
|
||||
|
||||
--- Configure nvim-notify
|
||||
--- See: ~
|
||||
--- |notify.Config|
|
||||
--- |notify-render|
|
||||
---
|
||||
---@param user_config notify.Config|nil
|
||||
---@eval return require('notify.config')._format_default()
|
||||
function notify.setup(user_config)
|
||||
global_instance, global_config = notify.instance(user_config)
|
||||
local has_telescope = (vim.fn.exists("g:loaded_telescope") == 1)
|
||||
if has_telescope then
|
||||
require("telescope").load_extension("notify")
|
||||
end
|
||||
vim.cmd([[command! Notifications :lua require("notify")._print_history()<CR>]])
|
||||
end
|
||||
|
||||
function notify._config()
|
||||
return config.setup(global_config)
|
||||
end
|
||||
|
||||
---@class notify.Options
|
||||
--- Options for an individual notification
|
||||
---@field title string
|
||||
---@field icon string
|
||||
---@field timeout number|boolean Time to show notification in milliseconds, set to false to disable timeout.
|
||||
---@field on_open function Callback for when window opens, receives window as argument.
|
||||
---@field on_close function Callback for when window closes, receives window as argument.
|
||||
---@field keep function Function to keep the notification window open after timeout, should return boolean.
|
||||
---@field render function|string Function to render a notification buffer.
|
||||
---@field replace integer|notify.Record Notification record or the record `id` field. Replace an existing notification if still open. All arguments not given are inherited from the replaced notification including message and level.
|
||||
---@field hide_from_history boolean Hide this notification from the history
|
||||
---@field animate boolean If false, the window will jump to the timed stage. Intended for use in blocking events (e.g. vim.fn.input)
|
||||
|
||||
---@class notify.Events
|
||||
--- Async events for a notification
|
||||
---@field open function Resolves when notification is opened
|
||||
---@field close function Resolved when notification is closed
|
||||
|
||||
---@class notify.Record
|
||||
--- Record of a previously sent notification
|
||||
---@field id integer
|
||||
---@field message string[] Lines of the message
|
||||
---@field level string|integer Log level. See vim.log.levels
|
||||
---@field title string[] Left and right sections of the title
|
||||
---@field icon string Icon used for notification
|
||||
---@field time number Time of message, as returned by `vim.fn.localtime()`
|
||||
---@field render function Function to render notification buffer
|
||||
|
||||
---@class notify.AsyncRecord : notify.Record
|
||||
---@field events notify.Events
|
||||
|
||||
--- Display a notification.
|
||||
---
|
||||
--- You can call the module directly rather than using this:
|
||||
--- >lua
|
||||
--- require("notify")(message, level, opts)
|
||||
--- <
|
||||
---@param message string|string[] Notification message
|
||||
---@param level string|number Log level. See vim.log.levels
|
||||
---@param opts notify.Options Notification options
|
||||
---@return notify.Record
|
||||
function notify.notify(message, level, opts)
|
||||
if not global_instance then
|
||||
notify.setup()
|
||||
end
|
||||
return global_instance.notify(message, level, opts)
|
||||
end
|
||||
|
||||
--- Display a notification asynchronously
|
||||
---
|
||||
--- This uses plenary's async library, allowing a cleaner interface for
|
||||
--- open/close events. You must call this function within an async context.
|
||||
---
|
||||
--- The `on_close` and `on_open` options are not used.
|
||||
---
|
||||
---@param message string|string[] Notification message
|
||||
---@param level string|number Log level. See vim.log.levels
|
||||
---@param opts notify.Options Notification options
|
||||
---@return notify.AsyncRecord
|
||||
function notify.async(message, level, opts)
|
||||
if not global_instance then
|
||||
notify.setup()
|
||||
end
|
||||
return global_instance.async(message, level, opts)
|
||||
end
|
||||
|
||||
--- Get records of all previous notifications
|
||||
---
|
||||
--- You can use the `:Notifications` command to display a log of previous notifications
|
||||
---@param opts? notify.HistoryOpts
|
||||
---@return notify.Record[]
|
||||
function notify.history(opts)
|
||||
if not global_instance then
|
||||
notify.setup()
|
||||
end
|
||||
return global_instance.history(opts)
|
||||
end
|
||||
|
||||
---@class notify.HistoryOpts
|
||||
---@field include_hidden boolean Include notifications hidden from history
|
||||
|
||||
--- Dismiss all notification windows currently displayed
|
||||
---@param opts notify.DismissOpts
|
||||
function notify.dismiss(opts)
|
||||
if not global_instance then
|
||||
notify.setup()
|
||||
end
|
||||
return global_instance.dismiss(opts)
|
||||
end
|
||||
|
||||
---@class notify.DismissOpts
|
||||
---@field pending boolean Clear pending notifications
|
||||
---@field silent boolean Suppress notification that pending notifications were dismissed.
|
||||
|
||||
--- Open a notification in a new buffer
|
||||
---@param notif_id integer|notify.Record
|
||||
---@param opts notify.OpenOpts
|
||||
---@return notify.OpenedBuffer
|
||||
function notify.open(notif_id, opts)
|
||||
if not global_instance then
|
||||
notify.setup()
|
||||
end
|
||||
return global_instance.open(notif_id, opts)
|
||||
end
|
||||
|
||||
---@class notify.OpenOpts
|
||||
---@field buffer integer Use this buffer, instead of creating a new one
|
||||
---@field max_width integer Render message to this width (used to limit window decoration sizes)
|
||||
|
||||
---@class notify.OpenedBuffer
|
||||
---@field buffer integer Created buffer number
|
||||
---@field height integer Height of the buffer content including extmarks
|
||||
---@field width integer width of the buffer content including extmarks
|
||||
---@field highlights table<string, string> Highlights used for the buffer contents
|
||||
|
||||
--- Number of notifications currently waiting to be displayed
|
||||
---@return integer[]
|
||||
function notify.pending()
|
||||
if not global_instance then
|
||||
notify.setup()
|
||||
end
|
||||
return global_instance.pending()
|
||||
end
|
||||
|
||||
function notify._print_history()
|
||||
if not global_instance then
|
||||
notify.setup()
|
||||
end
|
||||
for _, notif in ipairs(global_instance.history()) do
|
||||
vim.api.nvim_echo({
|
||||
{
|
||||
vim.fn.strftime(notify._config().time_formats().notification_history, notif.time),
|
||||
"NotifyLogTime",
|
||||
},
|
||||
{ " ", "MsgArea" },
|
||||
{ notif.title[1], "NotifyLogTitle" },
|
||||
{ #notif.title[1] > 0 and " " or "", "MsgArea" },
|
||||
{ notif.icon, "Notify" .. notif.level .. "Title" },
|
||||
{ " ", "MsgArea" },
|
||||
{ notif.level, "Notify" .. notif.level .. "Title" },
|
||||
{ " ", "MsgArea" },
|
||||
{ table.concat(notif.message, "\n"), "MsgArea" },
|
||||
}, false, {})
|
||||
end
|
||||
end
|
||||
|
||||
--- Configure an instance of nvim-notify.
|
||||
--- You can use this to manage a separate instance of nvim-notify with completely different configuration.
|
||||
--- The returned instance will have the same functions as the notify module.
|
||||
---@param user_config notify.Config
|
||||
---@param inherit? boolean Inherit the global configuration, default true
|
||||
function notify.instance(user_config, inherit)
|
||||
return instance(user_config, inherit, global_config)
|
||||
end
|
||||
|
||||
setmetatable(notify, {
|
||||
__call = function(_, m, l, o)
|
||||
if vim.in_fast_event() or vim.fn.has("vim_starting") == 1 then
|
||||
vim.schedule(function()
|
||||
notify.notify(m, l, o)
|
||||
end)
|
||||
else
|
||||
return notify.notify(m, l, o)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
return notify
|
||||
@ -0,0 +1,169 @@
|
||||
local stages = require("notify.stages")
|
||||
local config = require("notify.config")
|
||||
local Notification = require("notify.service.notification")
|
||||
local WindowAnimator = require("notify.windows")
|
||||
local NotificationService = require("notify.service")
|
||||
local NotificationBuf = require("notify.service.buffer")
|
||||
local stage_util = require("notify.stages.util")
|
||||
|
||||
---@param user_config notify.Config
|
||||
---@param inherit? boolean Inherit the global configuration, default true
|
||||
---@param global_config notify.Config
|
||||
return function(user_config, inherit, global_config)
|
||||
---@type notify.Notification[]
|
||||
local notifications = {}
|
||||
|
||||
user_config = user_config or {}
|
||||
if inherit ~= false and global_config then
|
||||
user_config = vim.tbl_deep_extend("force", global_config, user_config)
|
||||
end
|
||||
|
||||
local instance_config = config.setup(user_config)
|
||||
|
||||
local animator_stages = instance_config.stages()
|
||||
local direction = instance_config.top_down() and stage_util.DIRECTION.TOP_DOWN
|
||||
or stage_util.DIRECTION.BOTTOM_UP
|
||||
|
||||
animator_stages = type(animator_stages) == "string" and stages[animator_stages](direction)
|
||||
or animator_stages
|
||||
local animator = WindowAnimator(animator_stages, instance_config)
|
||||
local service = NotificationService(instance_config, animator)
|
||||
|
||||
local instance = {}
|
||||
|
||||
local function get_render(render)
|
||||
if type(render) == "function" then
|
||||
return render
|
||||
end
|
||||
return require("notify.render")[render]
|
||||
end
|
||||
|
||||
function instance.notify(message, level, opts)
|
||||
opts = opts or {}
|
||||
if opts.replace then
|
||||
if type(opts.replace) == "table" then
|
||||
opts.replace = opts.replace.id
|
||||
end
|
||||
local existing = notifications[opts.replace]
|
||||
if not existing then
|
||||
vim.notify("Invalid notification to replace", "error", { title = "nvim-notify" })
|
||||
return
|
||||
end
|
||||
local notif_keys = {
|
||||
"title",
|
||||
"icon",
|
||||
"timeout",
|
||||
"keep",
|
||||
"on_open",
|
||||
"on_close",
|
||||
"render",
|
||||
"hide_from_history",
|
||||
"animate",
|
||||
}
|
||||
message = message or existing.message
|
||||
level = level or existing.level
|
||||
for _, key in ipairs(notif_keys) do
|
||||
opts[key] = opts[key] or existing[key]
|
||||
end
|
||||
end
|
||||
opts.render = get_render(opts.render or instance_config.render())
|
||||
local id = #notifications + 1
|
||||
local notification = Notification(id, message, level, opts, instance_config)
|
||||
table.insert(notifications, notification)
|
||||
local level_num = vim.log.levels[notification.level]
|
||||
if opts.replace then
|
||||
service:replace(opts.replace, notification)
|
||||
elseif not level_num or level_num >= instance_config.level() then
|
||||
service:push(notification)
|
||||
end
|
||||
return {
|
||||
id = id,
|
||||
}
|
||||
end
|
||||
|
||||
---@param notif_id integer|notify.Record
|
||||
---@param opts table
|
||||
function instance.open(notif_id, opts)
|
||||
opts = opts or {}
|
||||
if type(notif_id) == "table" then
|
||||
notif_id = notif_id.id
|
||||
end
|
||||
local notif = notifications[notif_id]
|
||||
if not notif then
|
||||
vim.notify(
|
||||
"Invalid notification id: " .. notif_id,
|
||||
vim.log.levels.WARN,
|
||||
{ title = "nvim-notify" }
|
||||
)
|
||||
return
|
||||
end
|
||||
local buf = opts.buffer or vim.api.nvim_create_buf(false, true)
|
||||
local notif_buf =
|
||||
NotificationBuf(buf, notif, vim.tbl_extend("keep", opts, { config = instance_config }))
|
||||
notif_buf:render()
|
||||
return {
|
||||
buffer = buf,
|
||||
height = notif_buf:height(),
|
||||
width = notif_buf:width(),
|
||||
highlights = {
|
||||
body = notif_buf.highlights.body,
|
||||
border = notif_buf.highlights.border,
|
||||
title = notif_buf.highlights.title,
|
||||
icon = notif_buf.highlights.icon,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
function instance.async(message, level, opts)
|
||||
opts = opts or {}
|
||||
local async = require("plenary.async")
|
||||
local send_close, wait_close = async.control.channel.oneshot()
|
||||
opts.on_close = send_close
|
||||
|
||||
local send_open, wait_open = async.control.channel.oneshot()
|
||||
opts.on_open = send_open
|
||||
|
||||
async.util.scheduler()
|
||||
local record = instance.notify(message, level, opts)
|
||||
return vim.tbl_extend("error", record, {
|
||||
events = {
|
||||
open = wait_open,
|
||||
close = wait_close,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
function instance.history(args)
|
||||
args = args or {}
|
||||
local records = {}
|
||||
for _, notif in ipairs(notifications) do
|
||||
if not notif.hide_from_history or args.include_hidden then
|
||||
records[#records + 1] = notif:record()
|
||||
end
|
||||
end
|
||||
return records
|
||||
end
|
||||
|
||||
function instance.dismiss(opts)
|
||||
if service then
|
||||
service:dismiss(opts or {})
|
||||
end
|
||||
end
|
||||
|
||||
function instance.pending()
|
||||
return service and service:pending() or {}
|
||||
end
|
||||
|
||||
setmetatable(instance, {
|
||||
__call = function(_, m, l, o)
|
||||
if vim.in_fast_event() then
|
||||
vim.schedule(function()
|
||||
instance.notify(m, l, o)
|
||||
end)
|
||||
else
|
||||
return instance.notify(m, l, o)
|
||||
end
|
||||
end,
|
||||
})
|
||||
return instance, instance_config.merged()
|
||||
end
|
||||
@ -0,0 +1,9 @@
|
||||
local M = {}
|
||||
|
||||
local namespace = vim.api.nvim_create_namespace("nvim-notify")
|
||||
|
||||
function M.namespace()
|
||||
return namespace
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,36 @@
|
||||
local base = require("notify.render.base")
|
||||
|
||||
return function(bufnr, notif, highlights)
|
||||
local namespace = base.namespace()
|
||||
local icon = notif.icon
|
||||
local title = notif.title[1]
|
||||
|
||||
local prefix
|
||||
if type(title) == "string" and #title > 0 then
|
||||
prefix = string.format("%s | %s:", icon, title)
|
||||
else
|
||||
prefix = string.format("%s |", icon)
|
||||
end
|
||||
notif.message[1] = string.format("%s %s", prefix, notif.message[1])
|
||||
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, notif.message)
|
||||
|
||||
local icon_length = vim.str_utfindex(icon)
|
||||
local prefix_length = vim.str_utfindex(prefix)
|
||||
|
||||
vim.api.nvim_buf_set_extmark(bufnr, namespace, 0, 0, {
|
||||
hl_group = highlights.icon,
|
||||
end_col = icon_length + 1,
|
||||
priority = 50,
|
||||
})
|
||||
vim.api.nvim_buf_set_extmark(bufnr, namespace, 0, icon_length + 1, {
|
||||
hl_group = highlights.title,
|
||||
end_col = prefix_length + 1,
|
||||
priority = 50,
|
||||
})
|
||||
vim.api.nvim_buf_set_extmark(bufnr, namespace, 0, prefix_length + 1, {
|
||||
hl_group = highlights.body,
|
||||
end_line = #notif.message,
|
||||
priority = 50,
|
||||
})
|
||||
end
|
||||
@ -0,0 +1,54 @@
|
||||
local api = vim.api
|
||||
local base = require("notify.render.base")
|
||||
|
||||
return function(bufnr, notif, highlights, config)
|
||||
local left_icon = notif.icon .. " "
|
||||
local max_message_width = math.max(math.max(unpack(vim.tbl_map(function(line)
|
||||
return vim.fn.strchars(line)
|
||||
end, notif.message))))
|
||||
local right_title = notif.title[2]
|
||||
local left_title = notif.title[1]
|
||||
local title_accum = vim.str_utfindex(left_icon)
|
||||
+ vim.str_utfindex(right_title)
|
||||
+ vim.str_utfindex(left_title)
|
||||
|
||||
local left_buffer = string.rep(" ", math.max(0, max_message_width - title_accum))
|
||||
|
||||
local namespace = base.namespace()
|
||||
api.nvim_buf_set_lines(bufnr, 0, 1, false, { "", "" })
|
||||
api.nvim_buf_set_extmark(bufnr, namespace, 0, 0, {
|
||||
virt_text = {
|
||||
{ " " },
|
||||
{ left_icon, highlights.icon },
|
||||
{ left_title .. left_buffer, highlights.title },
|
||||
},
|
||||
virt_text_win_col = 0,
|
||||
priority = 10,
|
||||
})
|
||||
api.nvim_buf_set_extmark(bufnr, namespace, 0, 0, {
|
||||
virt_text = { { " " }, { right_title, highlights.title }, { " " } },
|
||||
virt_text_pos = "right_align",
|
||||
priority = 10,
|
||||
})
|
||||
api.nvim_buf_set_extmark(bufnr, namespace, 1, 0, {
|
||||
virt_text = {
|
||||
{
|
||||
string.rep(
|
||||
"━",
|
||||
math.max(vim.str_utfindex(left_buffer) + title_accum + 2, config.minimum_width())
|
||||
),
|
||||
highlights.border,
|
||||
},
|
||||
},
|
||||
virt_text_win_col = 0,
|
||||
priority = 10,
|
||||
})
|
||||
api.nvim_buf_set_lines(bufnr, 2, -1, false, notif.message)
|
||||
|
||||
api.nvim_buf_set_extmark(bufnr, namespace, 2, 0, {
|
||||
hl_group = highlights.body,
|
||||
end_line = 1 + #notif.message,
|
||||
end_col = #notif.message[#notif.message],
|
||||
priority = 50, -- Allow treesitter to override
|
||||
})
|
||||
end
|
||||
@ -0,0 +1,38 @@
|
||||
---@tag notify-render
|
||||
---@text
|
||||
--- Notification buffer rendering
|
||||
---
|
||||
--- Custom rendering can be provided by both the user config in the setup or on
|
||||
--- an individual notification using the `render` key.
|
||||
--- The key can either be the name of a built-in renderer or a custom function.
|
||||
---
|
||||
--- Built-in renderers:
|
||||
--- - `"default"`
|
||||
--- - `"minimal"`
|
||||
--- - `"simple"`
|
||||
--- - `"compact"`
|
||||
--- - `"wrapped-compact"`
|
||||
---
|
||||
--- Custom functions should accept a buffer, a notification record and a highlights table
|
||||
---
|
||||
--- >
|
||||
--- render: fun(buf: integer, notification: notify.Record, highlights: notify.Highlights, config)
|
||||
--- <
|
||||
--- You should use the provided highlight groups to take advantage of opacity
|
||||
--- changes as they will be updated as the notification is animated
|
||||
|
||||
---@class notify.Highlights
|
||||
---@field title string
|
||||
---@field icon string
|
||||
---@field border string
|
||||
---@field body string
|
||||
|
||||
local M = {}
|
||||
|
||||
setmetatable(M, {
|
||||
__index = function(_, key)
|
||||
return require("notify.render." .. key)
|
||||
end,
|
||||
})
|
||||
|
||||
return M
|
||||
@ -0,0 +1,14 @@
|
||||
local api = vim.api
|
||||
local base = require("notify.render.base")
|
||||
|
||||
return function(bufnr, notif, highlights)
|
||||
local namespace = base.namespace()
|
||||
api.nvim_buf_set_lines(bufnr, 0, -1, false, notif.message)
|
||||
|
||||
api.nvim_buf_set_extmark(bufnr, namespace, 0, 0, {
|
||||
hl_group = highlights.icon,
|
||||
end_line = #notif.message - 1,
|
||||
end_col = #notif.message[#notif.message],
|
||||
priority = 50,
|
||||
})
|
||||
end
|
||||
@ -0,0 +1,44 @@
|
||||
local api = vim.api
|
||||
local base = require("notify.render.base")
|
||||
|
||||
return function(bufnr, notif, highlights, config)
|
||||
local max_message_width = math.max(math.max(unpack(vim.tbl_map(function(line)
|
||||
return vim.fn.strchars(line)
|
||||
end, notif.message))))
|
||||
local title = notif.title[1]
|
||||
local title_accum = vim.str_utfindex(title)
|
||||
|
||||
local title_buffer = string.rep(
|
||||
" ",
|
||||
(math.max(max_message_width, title_accum, config.minimum_width()) - title_accum) / 2
|
||||
)
|
||||
|
||||
local namespace = base.namespace()
|
||||
|
||||
api.nvim_buf_set_lines(bufnr, 0, 1, false, { "", "" })
|
||||
api.nvim_buf_set_extmark(bufnr, namespace, 0, 0, {
|
||||
virt_text = {
|
||||
{ title_buffer .. title .. title_buffer, highlights.title },
|
||||
},
|
||||
virt_text_win_col = 0,
|
||||
priority = 10,
|
||||
})
|
||||
api.nvim_buf_set_extmark(bufnr, namespace, 1, 0, {
|
||||
virt_text = {
|
||||
{
|
||||
string.rep("━", math.max(max_message_width, title_accum, config.minimum_width())),
|
||||
highlights.border,
|
||||
},
|
||||
},
|
||||
virt_text_win_col = 0,
|
||||
priority = 10,
|
||||
})
|
||||
api.nvim_buf_set_lines(bufnr, 2, -1, false, notif.message)
|
||||
|
||||
api.nvim_buf_set_extmark(bufnr, namespace, 2, 0, {
|
||||
hl_group = highlights.body,
|
||||
end_line = 1 + #notif.message,
|
||||
end_col = #notif.message[#notif.message],
|
||||
priority = 50,
|
||||
})
|
||||
end
|
||||
@ -0,0 +1,89 @@
|
||||
-- alternative compact renderer for nvim-notify.
|
||||
-- Wraps text and adds some padding (only really to the left, since padding to
|
||||
-- the right is somehow not display correctly).
|
||||
-- Modified version of https://github.com/rcarriga/nvim-notify/blob/master/lua/notify/render/compact.lua
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
---@param line string
|
||||
---@param width number
|
||||
---@return string[]
|
||||
local function split_length(line, width)
|
||||
local text = {}
|
||||
local next_line
|
||||
while true do
|
||||
if #line == 0 then
|
||||
return text
|
||||
end
|
||||
next_line, line = line:sub(1, width), line:sub(width)
|
||||
text[#text + 1] = next_line
|
||||
end
|
||||
end
|
||||
|
||||
---@param lines string[]
|
||||
---@param max_width number
|
||||
---@return string[]
|
||||
local function custom_wrap(lines, max_width)
|
||||
local wrapped_lines = {}
|
||||
for _, line in pairs(lines) do
|
||||
local new_lines = split_length(line, max_width)
|
||||
for _, nl in ipairs(new_lines) do
|
||||
nl = nl:gsub("^%s*", " "):gsub("%s*$", " ") -- ensure padding
|
||||
table.insert(wrapped_lines, nl)
|
||||
end
|
||||
end
|
||||
return wrapped_lines
|
||||
end
|
||||
|
||||
---@param bufnr number
|
||||
---@param notif object
|
||||
---@param highlights object
|
||||
---@param config object plugin config_obj
|
||||
return function(bufnr, notif, highlights, config)
|
||||
local namespace = require("notify.render.base").namespace()
|
||||
local icon = notif.icon
|
||||
local title = notif.title[1]
|
||||
local prefix
|
||||
|
||||
-- wrap the text & add spacing
|
||||
local max_width = config.max_width()
|
||||
if max_width == nil then
|
||||
max_width = 80
|
||||
end
|
||||
local message = custom_wrap(notif.message, max_width)
|
||||
|
||||
local default_titles = { "Error", "Warning", "Notify" }
|
||||
local has_valid_manual_title = type(title) == "string"
|
||||
and #title > 0
|
||||
and not vim.tbl_contains(default_titles, title)
|
||||
|
||||
if has_valid_manual_title then
|
||||
-- has title = icon + title as header row
|
||||
prefix = string.format(" %s %s", icon, title)
|
||||
table.insert(message, 1, prefix)
|
||||
else
|
||||
-- no title = prefix the icon
|
||||
prefix = string.format(" %s", icon)
|
||||
message[1] = string.format("%s %s", prefix, message[1])
|
||||
end
|
||||
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, message)
|
||||
|
||||
local icon_length = vim.str_utfindex(icon)
|
||||
local prefix_length = vim.str_utfindex(prefix) + 1
|
||||
|
||||
vim.api.nvim_buf_set_extmark(bufnr, namespace, 0, 0, {
|
||||
hl_group = highlights.icon,
|
||||
end_col = icon_length + 1,
|
||||
priority = 50,
|
||||
})
|
||||
vim.api.nvim_buf_set_extmark(bufnr, namespace, 0, icon_length + 1, {
|
||||
hl_group = highlights.title,
|
||||
end_col = prefix_length + 1,
|
||||
priority = 50,
|
||||
})
|
||||
vim.api.nvim_buf_set_extmark(bufnr, namespace, 0, prefix_length + 1, {
|
||||
hl_group = highlights.body,
|
||||
end_line = #message,
|
||||
priority = 50,
|
||||
})
|
||||
end
|
||||
@ -0,0 +1,187 @@
|
||||
local util = require("notify.util")
|
||||
|
||||
---@class NotifyBufHighlights
|
||||
---@field groups table
|
||||
---@field opacity number
|
||||
---@field title string
|
||||
---@field border string
|
||||
---@field icon string
|
||||
---@field body string
|
||||
---@field buffer number
|
||||
---@field _config table
|
||||
local NotifyBufHighlights = {}
|
||||
|
||||
local function manual_get_hl(name)
|
||||
local synID = vim.fn.synIDtrans(vim.fn.hlID(name))
|
||||
local result = {
|
||||
foreground = tonumber(vim.fn.synIDattr(synID, "fg"):gsub("#", ""), 16),
|
||||
background = tonumber(vim.fn.synIDattr(synID, "bg"):gsub("#", ""), 16),
|
||||
}
|
||||
return result
|
||||
end
|
||||
|
||||
local function get_hl(name)
|
||||
local definition = vim.api.nvim_get_hl_by_name(name, true)
|
||||
if definition[true] then
|
||||
-- https://github.com/neovim/neovim/issues/18024
|
||||
return manual_get_hl(name)
|
||||
end
|
||||
return definition
|
||||
end
|
||||
|
||||
function NotifyBufHighlights:new(level, buffer, config)
|
||||
local function linked_group(section)
|
||||
local orig = "Notify" .. level .. section
|
||||
if vim.fn.hlID(orig) == 0 then
|
||||
orig = "NotifyINFO" .. section
|
||||
end
|
||||
local new = orig .. buffer
|
||||
|
||||
vim.api.nvim_set_hl(0, new, { link = orig })
|
||||
|
||||
return new, get_hl(new)
|
||||
end
|
||||
|
||||
local title, title_def = linked_group("Title")
|
||||
local border, border_def = linked_group("Border")
|
||||
local body, body_def = linked_group("Body")
|
||||
local icon, icon_def = linked_group("Icon")
|
||||
|
||||
local groups = {
|
||||
[title] = title_def,
|
||||
[border] = border_def,
|
||||
[body] = body_def,
|
||||
[icon] = icon_def,
|
||||
}
|
||||
local buf_highlights = {
|
||||
groups = groups,
|
||||
opacity = 100,
|
||||
border = border,
|
||||
body = body,
|
||||
title = title,
|
||||
icon = icon,
|
||||
buffer = buffer,
|
||||
_config = config,
|
||||
}
|
||||
self.__index = self
|
||||
setmetatable(buf_highlights, self)
|
||||
return buf_highlights
|
||||
end
|
||||
|
||||
function NotifyBufHighlights:_redefine_treesitter()
|
||||
local buf_highlighter = require("vim.treesitter.highlighter").active[self.buffer]
|
||||
|
||||
if not buf_highlighter then
|
||||
return
|
||||
end
|
||||
local render_namespace = vim.api.nvim_create_namespace("notify-treesitter-override")
|
||||
vim.api.nvim_buf_clear_namespace(self.buffer, render_namespace, 0, -1)
|
||||
|
||||
local function link(orig)
|
||||
local new = orig .. self.buffer
|
||||
if self.groups[new] then
|
||||
return new
|
||||
end
|
||||
vim.api.nvim_set_hl(0, new, { link = orig })
|
||||
self.groups[new] = get_hl(new)
|
||||
return new
|
||||
end
|
||||
|
||||
local matches = {}
|
||||
|
||||
local i = 0
|
||||
buf_highlighter.tree:for_each_tree(function(tstree, tree)
|
||||
if not tstree then
|
||||
return
|
||||
end
|
||||
|
||||
local root = tstree:root()
|
||||
|
||||
local query = buf_highlighter:get_query(tree:lang())
|
||||
|
||||
-- Some injected languages may not have highlight queries.
|
||||
if not query:query() then
|
||||
return
|
||||
end
|
||||
|
||||
local iter = query:query():iter_captures(root, buf_highlighter.bufnr)
|
||||
|
||||
for capture, node, metadata in iter do
|
||||
-- Wait until we get at least a single capture as we don't know when parsing is complete.
|
||||
self._treesitter_redefined = true
|
||||
local hl = query.hl_cache[capture]
|
||||
|
||||
if hl then
|
||||
i = i + 1
|
||||
local c = query._query.captures[capture] -- name of the capture in the query
|
||||
if c ~= nil then
|
||||
local capture_hl
|
||||
-- Removed in nightly with change of highlight names to @...
|
||||
-- https://github.com/neovim/neovim/pull/19931
|
||||
if query._get_hl_from_capture then
|
||||
local general_hl, is_vim_hl = query:_get_hl_from_capture(capture)
|
||||
capture_hl = is_vim_hl and general_hl or (tree:lang() .. general_hl)
|
||||
else
|
||||
capture_hl = query._query.captures[capture]
|
||||
if not vim.startswith(capture_hl, "_") then
|
||||
capture_hl = "@" .. capture_hl .. "." .. tree:lang()
|
||||
end
|
||||
end
|
||||
|
||||
local start_row, start_col, end_row, end_col = node:range()
|
||||
local custom_hl = link(capture_hl)
|
||||
|
||||
vim.api.nvim_buf_set_extmark(self.buffer, render_namespace, start_row, start_col, {
|
||||
end_row = end_row,
|
||||
end_col = end_col,
|
||||
hl_group = custom_hl,
|
||||
-- TODO: Not sure how neovim's highlighter doesn't have issues with overriding highlights
|
||||
-- Three marks on same region always show the second for some reason AFAICT
|
||||
priority = metadata.priority or i + 200,
|
||||
conceal = metadata.conceal,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end, true)
|
||||
return matches
|
||||
end
|
||||
|
||||
function NotifyBufHighlights:set_opacity(alpha)
|
||||
if
|
||||
not self._treesitter_redefined
|
||||
and vim.api.nvim_buf_get_option(self.buffer, "filetype") ~= "notify"
|
||||
then
|
||||
self:_redefine_treesitter()
|
||||
end
|
||||
self.opacity = alpha
|
||||
local background = self._config.background_colour()
|
||||
for group, fields in pairs(self.groups) do
|
||||
local updated_fields = {}
|
||||
vim.api.nvim_set_hl(0, group, updated_fields)
|
||||
local hl_string = ""
|
||||
if fields.foreground then
|
||||
hl_string = "guifg=#"
|
||||
.. string.format("%06x", util.blend(fields.foreground, background, alpha / 100))
|
||||
end
|
||||
if fields.background then
|
||||
hl_string = hl_string
|
||||
.. " guibg=#"
|
||||
.. string.format("%06x", util.blend(fields.background, background, alpha / 100))
|
||||
end
|
||||
|
||||
if hl_string ~= "" then
|
||||
-- Can't use nvim_set_hl https://github.com/neovim/neovim/issues/18160
|
||||
vim.cmd("hi " .. group .. " " .. hl_string)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function NotifyBufHighlights:get_opacity()
|
||||
return self.opacity
|
||||
end
|
||||
|
||||
---@return NotifyBufHighlights
|
||||
return function(level, buffer, config)
|
||||
return NotifyBufHighlights:new(level, buffer, config)
|
||||
end
|
||||
@ -0,0 +1,159 @@
|
||||
local api = vim.api
|
||||
|
||||
local NotifyBufHighlights = require("notify.service.buffer.highlights")
|
||||
|
||||
---@class NotificationBuf
|
||||
---@field highlights NotifyBufHighlights
|
||||
---@field _config table
|
||||
---@field _notif notify.Notification
|
||||
---@field _state "open" | "closed"
|
||||
---@field _buffer number
|
||||
---@field _height number
|
||||
---@field _width number
|
||||
---@field _max_width number | nil
|
||||
local NotificationBuf = {}
|
||||
|
||||
local BufState = {
|
||||
OPEN = "open",
|
||||
CLOSED = "close",
|
||||
}
|
||||
|
||||
function NotificationBuf:new(kwargs)
|
||||
local notif_buf = {
|
||||
_config = kwargs.config,
|
||||
_max_width = kwargs.max_width,
|
||||
_buffer = kwargs.buffer,
|
||||
_state = BufState.CLOSED,
|
||||
_width = 0,
|
||||
_height = 0,
|
||||
}
|
||||
setmetatable(notif_buf, self)
|
||||
self.__index = self
|
||||
notif_buf:set_notification(kwargs.notif)
|
||||
return notif_buf
|
||||
end
|
||||
|
||||
function NotificationBuf:set_notification(notif)
|
||||
self._notif = notif
|
||||
self:_create_highlights()
|
||||
end
|
||||
|
||||
function NotificationBuf:_create_highlights()
|
||||
local existing_opacity = self.highlights and self.highlights.opacity or 100
|
||||
self.highlights = NotifyBufHighlights(self._notif.level, self._buffer, self._config)
|
||||
if existing_opacity < 100 then
|
||||
self.highlights:set_opacity(existing_opacity)
|
||||
end
|
||||
end
|
||||
|
||||
function NotificationBuf:open(win)
|
||||
if self._state ~= BufState.CLOSED then
|
||||
return
|
||||
end
|
||||
self._state = BufState.OPEN
|
||||
local record = self._notif:record()
|
||||
if self._notif.on_open then
|
||||
self._notif.on_open(win, record)
|
||||
end
|
||||
if self._config.on_open() then
|
||||
self._config.on_open()(win, record)
|
||||
end
|
||||
end
|
||||
|
||||
function NotificationBuf:should_animate()
|
||||
return self._notif.animate
|
||||
end
|
||||
|
||||
function NotificationBuf:close(win)
|
||||
if self._state ~= BufState.OPEN then
|
||||
return
|
||||
end
|
||||
self._state = BufState.CLOSED
|
||||
vim.schedule(function()
|
||||
if self._notif.on_close then
|
||||
self._notif.on_close(win)
|
||||
end
|
||||
if self._config.on_close() then
|
||||
self._config.on_close()(win)
|
||||
end
|
||||
pcall(api.nvim_buf_delete, self._buffer, { force = true })
|
||||
end)
|
||||
end
|
||||
|
||||
function NotificationBuf:height()
|
||||
return self._height
|
||||
end
|
||||
|
||||
function NotificationBuf:width()
|
||||
return self._width
|
||||
end
|
||||
|
||||
function NotificationBuf:should_stay()
|
||||
if self._notif.keep then
|
||||
return self._notif.keep()
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function NotificationBuf:render()
|
||||
local notif = self._notif
|
||||
local buf = self._buffer
|
||||
|
||||
local render_namespace = require("notify.render.base").namespace()
|
||||
api.nvim_buf_set_option(buf, "filetype", "notify")
|
||||
api.nvim_buf_set_option(buf, "modifiable", true)
|
||||
api.nvim_buf_clear_namespace(buf, render_namespace, 0, -1)
|
||||
|
||||
notif.render(buf, notif, self.highlights, self._config)
|
||||
|
||||
api.nvim_buf_set_option(buf, "modifiable", false)
|
||||
|
||||
local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
||||
local width = self._config.minimum_width()
|
||||
for _, line in pairs(lines) do
|
||||
width = math.max(width, vim.str_utfindex(line))
|
||||
end
|
||||
local success, extmarks =
|
||||
pcall(api.nvim_buf_get_extmarks, buf, render_namespace, 0, #lines, { details = true })
|
||||
if not success then
|
||||
extmarks = {}
|
||||
end
|
||||
local virt_texts = {}
|
||||
for _, mark in ipairs(extmarks) do
|
||||
local details = mark[4]
|
||||
for _, virt_text in ipairs(details.virt_text or {}) do
|
||||
virt_texts[mark[2]] = (virt_texts[mark[2]] or "") .. virt_text[1]
|
||||
end
|
||||
end
|
||||
for _, text in pairs(virt_texts) do
|
||||
width = math.max(width, vim.str_utfindex(text))
|
||||
end
|
||||
|
||||
self._width = width
|
||||
self._height = #lines
|
||||
end
|
||||
|
||||
function NotificationBuf:timeout()
|
||||
return self._notif.timeout
|
||||
end
|
||||
|
||||
function NotificationBuf:buffer()
|
||||
return self._buffer
|
||||
end
|
||||
|
||||
function NotificationBuf:is_valid()
|
||||
return self._buffer and vim.api.nvim_buf_is_valid(self._buffer)
|
||||
end
|
||||
|
||||
function NotificationBuf:level()
|
||||
return self._notif.level
|
||||
end
|
||||
|
||||
---@param buf number
|
||||
---@param notification notify.Notification;q
|
||||
---@return NotificationBuf
|
||||
return function(buf, notification, opts)
|
||||
return NotificationBuf:new(
|
||||
vim.tbl_extend("keep", { buffer = buf, notif = notification }, opts or {})
|
||||
)
|
||||
end
|
||||
@ -0,0 +1,116 @@
|
||||
local util = require("notify.util")
|
||||
local NotificationBuf = require("notify.service.buffer")
|
||||
|
||||
---@class NotificationService
|
||||
---@field private _running boolean
|
||||
---@field private _pending FIFOQueue
|
||||
---@field private _animator WindowAnimator
|
||||
---@field private _buffers table<integer, NotificationBuf>
|
||||
---@field private _fps integer
|
||||
local NotificationService = {}
|
||||
|
||||
---@class notify.ServiceConfig
|
||||
---@field fps integer
|
||||
|
||||
---@param config notify.ServiceConfig
|
||||
function NotificationService:new(config, animator)
|
||||
local service = {
|
||||
_config = config,
|
||||
_fps = config.fps(),
|
||||
_animator = animator,
|
||||
_pending = util.FIFOQueue(),
|
||||
_running = false,
|
||||
_buffers = {},
|
||||
}
|
||||
self.__index = self
|
||||
setmetatable(service, self)
|
||||
return service
|
||||
end
|
||||
|
||||
function NotificationService:_run()
|
||||
self._running = true
|
||||
local succees, updated =
|
||||
pcall(self._animator.render, self._animator, self._pending, 1 / self._fps)
|
||||
if not succees then
|
||||
print("Error running notification service: " .. updated)
|
||||
self._running = false
|
||||
return
|
||||
end
|
||||
if not updated then
|
||||
self._running = false
|
||||
return
|
||||
end
|
||||
vim.defer_fn(function()
|
||||
self:_run()
|
||||
end, 1000 / self._fps)
|
||||
end
|
||||
|
||||
---@param notif notify.Notification;q
|
||||
---@return integer
|
||||
function NotificationService:push(notif)
|
||||
local buf = vim.api.nvim_create_buf(false, true)
|
||||
local notif_buf = NotificationBuf(buf, notif, { config = self._config })
|
||||
notif_buf:render()
|
||||
self._buffers[notif.id] = notif_buf
|
||||
self._pending:push(notif_buf)
|
||||
if not self._running then
|
||||
self:_run()
|
||||
else
|
||||
-- Forces a render during blocking events
|
||||
-- https://github.com/rcarriga/nvim-notify/issues/5
|
||||
pcall(self._animator.render, self._animator, self._pending, 1 / self._fps)
|
||||
end
|
||||
vim.cmd("redraw")
|
||||
return buf
|
||||
end
|
||||
|
||||
function NotificationService:replace(id, notif)
|
||||
local existing = self._buffers[id]
|
||||
if not (existing and existing:is_valid()) then
|
||||
vim.notify("No matching notification found to replace")
|
||||
return
|
||||
end
|
||||
existing:set_notification(notif)
|
||||
self._buffers[id] = nil
|
||||
self._buffers[notif.id] = existing
|
||||
pcall(existing.render, existing)
|
||||
local win = vim.fn.bufwinid(existing:buffer())
|
||||
if win ~= -1 then
|
||||
-- Highlights can change name if level changed so we have to re-link
|
||||
-- vim.wo does not behave like setlocal, thus we use setwinvar to set a
|
||||
-- local option. Otherwise our changes would affect subsequently opened
|
||||
-- windows.
|
||||
-- see e.g. neovim#14595
|
||||
vim.fn.setwinvar(
|
||||
win,
|
||||
"&winhl",
|
||||
"Normal:" .. existing.highlights.body .. ",FloatBorder:" .. existing.highlights.border
|
||||
)
|
||||
self._animator:on_refresh(win)
|
||||
end
|
||||
end
|
||||
|
||||
function NotificationService:dismiss(opts)
|
||||
local notif_wins = vim.tbl_keys(self._animator.win_stages)
|
||||
for _, win in pairs(notif_wins) do
|
||||
pcall(vim.api.nvim_win_close, win, true)
|
||||
end
|
||||
if opts.pending then
|
||||
local cleared = 0
|
||||
while self._pending:pop() do
|
||||
cleared = cleared + 1
|
||||
end
|
||||
if not opts.silent then
|
||||
vim.notify("Cleared " .. cleared .. " pending notifications")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function NotificationService:pending()
|
||||
return self._pending:length()
|
||||
end
|
||||
|
||||
---@return NotificationService
|
||||
return function(config, animator)
|
||||
return NotificationService:new(config, animator)
|
||||
end
|
||||
@ -0,0 +1,78 @@
|
||||
---@class notify.Notification
|
||||
---@field id integer
|
||||
---@field level string
|
||||
---@field message string[]
|
||||
---@field timeout number | nil
|
||||
---@field title string[]
|
||||
---@field icon string
|
||||
---@field time number
|
||||
---@field width number
|
||||
---@field animate boolean
|
||||
---@field hide_from_history boolean
|
||||
---@field keep fun(): boolean
|
||||
---@field on_open fun(win: number, record: notify.Record) | nil
|
||||
---@field on_close fun(win: number, record: notify.Record) | nil
|
||||
---@field render fun(buf: integer, notification: notify.Notification, highlights: table<string, string>)
|
||||
local Notification = {}
|
||||
|
||||
local level_maps = vim.tbl_extend("keep", {}, vim.log.levels)
|
||||
for k, v in pairs(vim.log.levels) do
|
||||
level_maps[v] = k
|
||||
end
|
||||
|
||||
function Notification:new(id, message, level, opts, config)
|
||||
if type(level) == "number" then
|
||||
level = level_maps[level]
|
||||
end
|
||||
if type(message) == "string" then
|
||||
message = vim.split(message, "\n")
|
||||
end
|
||||
level = vim.fn.toupper(level or "info")
|
||||
local time = vim.fn.localtime()
|
||||
local title = opts.title or ""
|
||||
if type(title) == "string" then
|
||||
title = { title, vim.fn.strftime(config.time_formats().notification, time) }
|
||||
end
|
||||
vim.validate({
|
||||
message = { message, "table" },
|
||||
level = { level, "string" },
|
||||
title = { title, "table" },
|
||||
})
|
||||
local notif = {
|
||||
id = id,
|
||||
message = message,
|
||||
title = title,
|
||||
icon = opts.icon or config.icons()[level] or config.icons().INFO,
|
||||
time = time,
|
||||
timeout = opts.timeout,
|
||||
level = level,
|
||||
keep = opts.keep,
|
||||
on_open = opts.on_open,
|
||||
on_close = opts.on_close,
|
||||
animate = opts.animate ~= false,
|
||||
render = opts.render,
|
||||
hide_from_history = opts.hide_from_history,
|
||||
}
|
||||
self.__index = self
|
||||
setmetatable(notif, self)
|
||||
return notif
|
||||
end
|
||||
|
||||
function Notification:record()
|
||||
return {
|
||||
id = self.id,
|
||||
message = self.message,
|
||||
level = self.level,
|
||||
time = self.time,
|
||||
title = self.title,
|
||||
icon = self.icon,
|
||||
render = self.render,
|
||||
}
|
||||
end
|
||||
|
||||
---@param message string | string[]
|
||||
---@param level string | number
|
||||
---@param opts notify.Options
|
||||
return function(id, message, level, opts, config)
|
||||
return Notification:new(id, message, level, opts, config)
|
||||
end
|
||||
@ -0,0 +1,48 @@
|
||||
local stages_util = require("notify.stages.util")
|
||||
|
||||
return function(direction)
|
||||
return {
|
||||
function(state)
|
||||
local next_height = state.message.height + 2
|
||||
local next_row = stages_util.available_slot(state.open_windows, next_height, direction)
|
||||
if not next_row then
|
||||
return nil
|
||||
end
|
||||
return {
|
||||
relative = "editor",
|
||||
anchor = "NE",
|
||||
width = state.message.width,
|
||||
height = state.message.height,
|
||||
col = vim.opt.columns:get(),
|
||||
row = next_row,
|
||||
border = "rounded",
|
||||
style = "minimal",
|
||||
opacity = 0,
|
||||
}
|
||||
end,
|
||||
function()
|
||||
return {
|
||||
opacity = { 100 },
|
||||
col = { vim.opt.columns:get() },
|
||||
}
|
||||
end,
|
||||
function()
|
||||
return {
|
||||
col = { vim.opt.columns:get() },
|
||||
time = true,
|
||||
}
|
||||
end,
|
||||
function()
|
||||
return {
|
||||
opacity = {
|
||||
0,
|
||||
frequency = 2,
|
||||
complete = function(cur_opacity)
|
||||
return cur_opacity <= 4
|
||||
end,
|
||||
},
|
||||
col = { vim.opt.columns:get() },
|
||||
}
|
||||
end,
|
||||
}
|
||||
end
|
||||
@ -0,0 +1,77 @@
|
||||
local stages_util = require("notify.stages.util")
|
||||
|
||||
return function(direction)
|
||||
return {
|
||||
function(state)
|
||||
local next_height = state.message.height + 2
|
||||
local next_row = stages_util.available_slot(state.open_windows, next_height, direction)
|
||||
if not next_row then
|
||||
return nil
|
||||
end
|
||||
return {
|
||||
relative = "editor",
|
||||
anchor = "NE",
|
||||
width = state.message.width,
|
||||
height = state.message.height,
|
||||
col = vim.opt.columns:get(),
|
||||
row = next_row,
|
||||
border = "rounded",
|
||||
style = "minimal",
|
||||
opacity = 0,
|
||||
}
|
||||
end,
|
||||
function(state, win)
|
||||
return {
|
||||
opacity = { 100 },
|
||||
col = { vim.opt.columns:get() },
|
||||
row = {
|
||||
stages_util.slot_after_previous(win, state.open_windows, direction),
|
||||
frequency = 3,
|
||||
complete = function()
|
||||
return true
|
||||
end,
|
||||
},
|
||||
}
|
||||
end,
|
||||
function(state, win)
|
||||
return {
|
||||
col = { vim.opt.columns:get() },
|
||||
time = true,
|
||||
row = {
|
||||
stages_util.slot_after_previous(win, state.open_windows, direction),
|
||||
frequency = 3,
|
||||
complete = function()
|
||||
return true
|
||||
end,
|
||||
},
|
||||
}
|
||||
end,
|
||||
function(state, win)
|
||||
return {
|
||||
width = {
|
||||
1,
|
||||
frequency = 2.5,
|
||||
damping = 0.9,
|
||||
complete = function(cur_width)
|
||||
return cur_width < 3
|
||||
end,
|
||||
},
|
||||
opacity = {
|
||||
0,
|
||||
frequency = 2,
|
||||
complete = function(cur_opacity)
|
||||
return cur_opacity <= 4
|
||||
end,
|
||||
},
|
||||
col = { vim.opt.columns:get() },
|
||||
row = {
|
||||
stages_util.slot_after_previous(win, state.open_windows, direction),
|
||||
frequency = 3,
|
||||
complete = function()
|
||||
return true
|
||||
end,
|
||||
},
|
||||
}
|
||||
end,
|
||||
}
|
||||
end
|
||||
@ -0,0 +1,20 @@
|
||||
local M = {}
|
||||
|
||||
---@class MessageState
|
||||
---@field width number
|
||||
---@field height number
|
||||
|
||||
---@alias InitStage fun(open_windows: number[], message_state: MessageState): table | nil
|
||||
---@alias AnimationStage fun(win: number, message_state: MessageState): table
|
||||
|
||||
---@alias Stage InitStage | AnimationStage
|
||||
---@alias Stages Stage[]
|
||||
|
||||
setmetatable(M, {
|
||||
---@return Stages
|
||||
__index = function(_, key)
|
||||
return require("notify.stages." .. key)
|
||||
end,
|
||||
})
|
||||
|
||||
return M
|
||||
@ -0,0 +1,30 @@
|
||||
local stages_util = require("notify.stages.util")
|
||||
|
||||
return function(direction)
|
||||
return {
|
||||
function(state)
|
||||
local next_height = state.message.height + 2
|
||||
local next_row = stages_util.available_slot(state.open_windows, next_height, direction)
|
||||
if not next_row then
|
||||
return nil
|
||||
end
|
||||
return {
|
||||
relative = "editor",
|
||||
anchor = "NE",
|
||||
width = state.message.width,
|
||||
height = state.message.height,
|
||||
col = vim.opt.columns:get(),
|
||||
row = next_row,
|
||||
border = "rounded",
|
||||
style = "minimal",
|
||||
}
|
||||
end,
|
||||
function(state, win)
|
||||
return {
|
||||
col = vim.opt.columns:get(),
|
||||
time = true,
|
||||
row = stages_util.slot_after_previous(win, state.open_windows, direction),
|
||||
}
|
||||
end,
|
||||
}
|
||||
end
|
||||
@ -0,0 +1,48 @@
|
||||
local stages_util = require("notify.stages.util")
|
||||
|
||||
return function(direction)
|
||||
return {
|
||||
function(state)
|
||||
local next_height = state.message.height + 2
|
||||
local next_row = stages_util.available_slot(state.open_windows, next_height, direction)
|
||||
if not next_row then
|
||||
return nil
|
||||
end
|
||||
return {
|
||||
relative = "editor",
|
||||
anchor = "NE",
|
||||
width = 1,
|
||||
height = state.message.height,
|
||||
col = vim.opt.columns:get(),
|
||||
row = next_row,
|
||||
border = "rounded",
|
||||
style = "minimal",
|
||||
}
|
||||
end,
|
||||
function(state)
|
||||
return {
|
||||
width = { state.message.width, frequency = 2 },
|
||||
col = { vim.opt.columns:get() },
|
||||
}
|
||||
end,
|
||||
function()
|
||||
return {
|
||||
col = { vim.opt.columns:get() },
|
||||
time = true,
|
||||
}
|
||||
end,
|
||||
function()
|
||||
return {
|
||||
width = {
|
||||
1,
|
||||
frequency = 2.5,
|
||||
damping = 0.9,
|
||||
complete = function(cur_width)
|
||||
return cur_width < 2
|
||||
end,
|
||||
},
|
||||
col = { vim.opt.columns:get() },
|
||||
}
|
||||
end,
|
||||
}
|
||||
end
|
||||
@ -0,0 +1,29 @@
|
||||
local stages_util = require("notify.stages.util")
|
||||
|
||||
return function(direction)
|
||||
return {
|
||||
function(state)
|
||||
local next_height = state.message.height + 2
|
||||
local next_row = stages_util.available_slot(state.open_windows, next_height, direction)
|
||||
if not next_row then
|
||||
return nil
|
||||
end
|
||||
return {
|
||||
relative = "editor",
|
||||
anchor = "NE",
|
||||
width = state.message.width,
|
||||
height = state.message.height,
|
||||
col = vim.opt.columns:get(),
|
||||
row = next_row,
|
||||
border = "rounded",
|
||||
style = "minimal",
|
||||
}
|
||||
end,
|
||||
function()
|
||||
return {
|
||||
col = vim.opt.columns:get(),
|
||||
time = true,
|
||||
}
|
||||
end,
|
||||
}
|
||||
end
|
||||
@ -0,0 +1,197 @@
|
||||
local max, min = math.max, math.min
|
||||
local util = require("notify.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
M.DIRECTION = {
|
||||
TOP_DOWN = "top_down",
|
||||
BOTTOM_UP = "bottom_up",
|
||||
LEFT_RIGHT = "left_right",
|
||||
RIGHT_LEFT = "right_left",
|
||||
}
|
||||
|
||||
local function is_increasing(direction)
|
||||
return (direction == M.DIRECTION.TOP_DOWN or direction == M.DIRECTION.LEFT_RIGHT)
|
||||
end
|
||||
|
||||
local function moves_vertically(direction)
|
||||
return (direction == M.DIRECTION.TOP_DOWN or direction == M.DIRECTION.BOTTOM_UP)
|
||||
end
|
||||
|
||||
function M.slot_name(direction)
|
||||
if moves_vertically(direction) then
|
||||
return "height"
|
||||
end
|
||||
return "width"
|
||||
end
|
||||
|
||||
local function less(a, b)
|
||||
return a < b
|
||||
end
|
||||
|
||||
local function greater(a, b)
|
||||
return a > b
|
||||
end
|
||||
|
||||
local function overlaps(a, b)
|
||||
return a.min <= b.max and b.min <= a.max
|
||||
end
|
||||
|
||||
local move_slot = function(direction, slot, delta)
|
||||
if is_increasing(direction) then
|
||||
return slot + delta
|
||||
end
|
||||
return slot - delta
|
||||
end
|
||||
|
||||
local function slot_key(direction)
|
||||
return moves_vertically(direction) and "row" or "col"
|
||||
end
|
||||
|
||||
local function space_key(direction)
|
||||
return moves_vertically(direction) and "height" or "width"
|
||||
end
|
||||
|
||||
-- TODO: Use direction to check border lists
|
||||
local function border_padding(direction, win_conf)
|
||||
if not win_conf.border or win_conf.border == "none" then
|
||||
return 0
|
||||
end
|
||||
return 2
|
||||
end
|
||||
|
||||
---@param windows number[]
|
||||
---@param direction string
|
||||
---@return { max: integer, min: integer}[]
|
||||
local function window_intervals(windows, direction, cmp)
|
||||
local win_intervals = {}
|
||||
for _, w in ipairs(windows) do
|
||||
local exists, existing_conf = util.get_win_config(w)
|
||||
if exists then
|
||||
local border_space = border_padding(direction, existing_conf)
|
||||
win_intervals[#win_intervals + 1] = {
|
||||
min = existing_conf[slot_key(direction)],
|
||||
max = existing_conf[slot_key(direction)]
|
||||
+ existing_conf[space_key(direction)]
|
||||
+ border_space
|
||||
- 1,
|
||||
}
|
||||
end
|
||||
end
|
||||
table.sort(win_intervals, function(a, b)
|
||||
return cmp(a.min, b.min)
|
||||
end)
|
||||
return win_intervals
|
||||
end
|
||||
|
||||
function M.get_slot_range(direction)
|
||||
local top = vim.opt.tabline:get() == "" and 0 or 1
|
||||
local bottom = vim.opt.lines:get()
|
||||
- (vim.opt.cmdheight:get() + (vim.opt.laststatus:get() > 0 and 1 or 0))
|
||||
local left = 1
|
||||
local right = vim.opt.columns:get()
|
||||
if M.DIRECTION.TOP_DOWN == direction then
|
||||
return top, bottom
|
||||
elseif M.DIRECTION.BOTTOM_UP == direction then
|
||||
return bottom, top
|
||||
elseif M.DIRECTION.LEFT_RIGHT == direction then
|
||||
return left, right
|
||||
elseif M.DIRECTION.RIGHT_LEFT == direction then
|
||||
return right, left
|
||||
end
|
||||
error(string.format("Invalid direction: %s", direction))
|
||||
end
|
||||
|
||||
---@param existing_wins number[] Windows to avoid overlapping
|
||||
---@param required_space number Window height or width including borders
|
||||
---@param direction string Direction to stack windows, one of M.DIRECTION
|
||||
---@return number | nil Slot to place window at or nil if no slot available
|
||||
function M.available_slot(existing_wins, required_space, direction)
|
||||
local increasing = is_increasing(direction)
|
||||
local cmp = increasing and less or greater
|
||||
local first_slot, last_slot = M.get_slot_range(direction)
|
||||
|
||||
local function create_interval(start_slot)
|
||||
local end_slot = move_slot(direction, start_slot, required_space - 1)
|
||||
return { min = min(start_slot, end_slot), max = max(start_slot, end_slot) }
|
||||
end
|
||||
|
||||
local interval = create_interval(first_slot)
|
||||
|
||||
local intervals = window_intervals(existing_wins, direction, cmp)
|
||||
|
||||
for _, next_interval in ipairs(intervals) do
|
||||
if overlaps(next_interval, interval) then
|
||||
interval = create_interval(
|
||||
move_slot(direction, increasing and next_interval.max or next_interval.min, 1)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if #intervals > 0 and not cmp(is_increasing and interval.max or interval.min, last_slot) then
|
||||
return nil
|
||||
end
|
||||
|
||||
return interval.min
|
||||
end
|
||||
|
||||
---Gets the next slot available for the given window while maintaining its position using the given list.
|
||||
---@param win number
|
||||
---@param open_windows number[]
|
||||
---@param direction string
|
||||
function M.slot_after_previous(win, open_windows, direction)
|
||||
local key = slot_key(direction)
|
||||
local cmp = is_increasing(direction) and less or greater
|
||||
local exists, cur_win_conf = util.get_win_config(win)
|
||||
if not exists then
|
||||
return 0
|
||||
end
|
||||
|
||||
local cur_slot = cur_win_conf[key]
|
||||
local win_confs = {}
|
||||
for _, w in ipairs(open_windows) do
|
||||
local success, conf = util.get_win_config(w)
|
||||
if success then
|
||||
win_confs[w] = conf
|
||||
end
|
||||
end
|
||||
|
||||
local preceding_wins = vim.tbl_filter(function(open_win)
|
||||
return win_confs[open_win] and cmp(win_confs[open_win][key], cur_slot)
|
||||
end, open_windows)
|
||||
|
||||
if #preceding_wins == 0 then
|
||||
local start = M.get_slot_range(direction)
|
||||
if is_increasing(direction) then
|
||||
return start
|
||||
end
|
||||
return move_slot(
|
||||
direction,
|
||||
start,
|
||||
cur_win_conf[space_key(direction)] + border_padding(direction, cur_win_conf)
|
||||
)
|
||||
end
|
||||
|
||||
table.sort(preceding_wins, function(a, b)
|
||||
return cmp(win_confs[a][key], win_confs[b][key])
|
||||
end)
|
||||
|
||||
local last_win = preceding_wins[#preceding_wins]
|
||||
local last_win_conf = win_confs[last_win]
|
||||
|
||||
if is_increasing(direction) then
|
||||
return move_slot(
|
||||
direction,
|
||||
last_win_conf[key],
|
||||
last_win_conf[space_key(direction)] + border_padding(direction, last_win_conf)
|
||||
)
|
||||
else
|
||||
return move_slot(
|
||||
direction,
|
||||
last_win_conf[key],
|
||||
cur_win_conf[space_key(direction)] + border_padding(direction, cur_win_conf)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,120 @@
|
||||
local M = {}
|
||||
|
||||
local min, max, floor = math.min, math.max, math.floor
|
||||
local rshift, lshift, band, bor = bit.rshift, bit.lshift, bit.band, bit.bor
|
||||
function M.is_callable(obj)
|
||||
return type(obj) == "function" or (type(obj) == "table" and obj.__call)
|
||||
end
|
||||
|
||||
function M.lazy_require(require_path)
|
||||
return setmetatable({}, {
|
||||
__call = function(_, ...)
|
||||
return require(require_path)(...)
|
||||
end,
|
||||
__index = function(_, key)
|
||||
return require(require_path)[key]
|
||||
end,
|
||||
__newindex = function(_, key, value)
|
||||
require(require_path)[key] = value
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
function M.pop(tbl, key, default)
|
||||
local val = default
|
||||
if tbl[key] then
|
||||
val = tbl[key]
|
||||
tbl[key] = nil
|
||||
end
|
||||
return val
|
||||
end
|
||||
|
||||
function M.blend(fg_hex, bg_hex, alpha)
|
||||
local segment = 0xFF0000
|
||||
local result = 0
|
||||
for i = 2, 0, -1 do
|
||||
local blended = alpha * rshift(band(fg_hex, segment), i * 8)
|
||||
+ (1 - alpha) * rshift(band(bg_hex, segment), i * 8)
|
||||
|
||||
result = bor(lshift(result, 8), floor((min(max(blended, 0), 255)) + 0.5))
|
||||
segment = rshift(segment, 8)
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
function M.round(num, decimals)
|
||||
if decimals then
|
||||
return tonumber(string.format("%." .. decimals .. "f", num))
|
||||
end
|
||||
return math.floor(num + 0.5)
|
||||
end
|
||||
|
||||
function M.partial(func, ...)
|
||||
local args = { ... }
|
||||
return function(...)
|
||||
local final = {}
|
||||
vim.list_extend(final, args)
|
||||
vim.list_extend(final, { ... })
|
||||
return func(unpack(final))
|
||||
end
|
||||
end
|
||||
|
||||
function M.get_win_config(win)
|
||||
local success, conf = pcall(vim.api.nvim_win_get_config, win)
|
||||
if not success or not conf.row then
|
||||
return false, conf
|
||||
end
|
||||
if type(conf.row) == "table" then
|
||||
conf.row = conf.row[false]
|
||||
end
|
||||
if type(conf.col) == "table" then
|
||||
conf.col = conf.col[false]
|
||||
end
|
||||
return success, conf
|
||||
end
|
||||
|
||||
function M.open_win(notif_buf, enter, opts)
|
||||
local win = vim.api.nvim_open_win(notif_buf:buffer(), enter, opts)
|
||||
-- vim.wo does not behave like setlocal, thus we use setwinvar to set local
|
||||
-- only options. Otherwise our changes would affect subsequently opened
|
||||
-- windows.
|
||||
-- see e.g. neovim#14595
|
||||
vim.fn.setwinvar(
|
||||
win,
|
||||
"&winhl",
|
||||
"Normal:" .. notif_buf.highlights.body .. ",FloatBorder:" .. notif_buf.highlights.border
|
||||
)
|
||||
vim.fn.setwinvar(win, "&wrap", 0)
|
||||
return win
|
||||
end
|
||||
|
||||
M.FIFOQueue = require("notify.util.queue")
|
||||
|
||||
function M.rgb_to_numbers(s)
|
||||
local colours = {}
|
||||
for a in string.gmatch(s, "[A-Fa-f0-9][A-Fa-f0-9]") do
|
||||
colours[#colours + 1] = tonumber(a, 16)
|
||||
end
|
||||
return colours
|
||||
end
|
||||
|
||||
function M.numbers_to_rgb(colours)
|
||||
local colour = "#"
|
||||
for _, num in pairs(colours) do
|
||||
colour = colour .. string.format("%X", num)
|
||||
end
|
||||
return colour
|
||||
end
|
||||
|
||||
function M.highlight(name, fields)
|
||||
local fields_string = ""
|
||||
for field, value in pairs(fields) do
|
||||
fields_string = fields_string .. " " .. field .. "=" .. value
|
||||
end
|
||||
if fields_string ~= "" then
|
||||
vim.cmd("hi " .. name .. fields_string)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,54 @@
|
||||
---@class FIFOQueue
|
||||
local FIFOQueue = {}
|
||||
|
||||
function FIFOQueue:pop()
|
||||
if self:is_empty() then
|
||||
return nil
|
||||
end
|
||||
local r = self[self.pop_from]
|
||||
self[self.pop_from] = nil
|
||||
self.pop_from = self.pop_from - 1
|
||||
return r
|
||||
end
|
||||
|
||||
function FIFOQueue:peek()
|
||||
return self[self.pop_from]
|
||||
end
|
||||
|
||||
function FIFOQueue:push(val)
|
||||
self[self.push_to] = val
|
||||
self.push_to = self.push_to - 1
|
||||
end
|
||||
|
||||
function FIFOQueue:is_empty()
|
||||
return self:length() == 0
|
||||
end
|
||||
|
||||
function FIFOQueue:length()
|
||||
return self.pop_from - self.push_to
|
||||
end
|
||||
|
||||
function FIFOQueue:iter()
|
||||
local i = self.pop_from + 1
|
||||
return function()
|
||||
if i > self.push_to + 1 then
|
||||
i = i - 1
|
||||
return self[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function FIFOQueue:new()
|
||||
local queue = {
|
||||
pop_from = 1,
|
||||
push_to = 1,
|
||||
}
|
||||
self.__index = self
|
||||
setmetatable(queue, self)
|
||||
return queue
|
||||
end
|
||||
|
||||
---@return FIFOQueue
|
||||
return function()
|
||||
return FIFOQueue:new()
|
||||
end
|
||||
@ -0,0 +1,326 @@
|
||||
local api = vim.api
|
||||
local animate = require("notify.animate")
|
||||
local util = require("notify.util")
|
||||
local round = util.round
|
||||
local max = math.max
|
||||
|
||||
---@class WindowAnimator
|
||||
---@field config table
|
||||
---@field win_states table<number, table<string, SpringState>>
|
||||
---@field win_stages table<number, integer>
|
||||
---@field notif_bufs table<number, NotificationBuf>
|
||||
---@field timers table
|
||||
---@field stages table
|
||||
local WindowAnimator = {}
|
||||
|
||||
function WindowAnimator:new(stages, config)
|
||||
local animator = {
|
||||
config = config,
|
||||
win_stages = {},
|
||||
win_states = {},
|
||||
notif_bufs = {},
|
||||
timers = {},
|
||||
stages = stages,
|
||||
}
|
||||
self.__index = self
|
||||
setmetatable(animator, self)
|
||||
return animator
|
||||
end
|
||||
|
||||
function WindowAnimator:render(queue, time)
|
||||
self:push_pending(queue)
|
||||
if vim.tbl_isempty(self.win_stages) then
|
||||
return false
|
||||
end
|
||||
local open_windows = vim.tbl_keys(self.win_stages)
|
||||
for win, _ in pairs(self.win_stages) do
|
||||
self:_update_window(time, win, open_windows)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function WindowAnimator:push_pending(queue)
|
||||
if queue:is_empty() then
|
||||
return
|
||||
end
|
||||
while not queue:is_empty() do
|
||||
---@type NotificationBuf
|
||||
local notif_buf = queue:peek()
|
||||
if not notif_buf:is_valid() then
|
||||
queue:pop()
|
||||
else
|
||||
local windows = vim.tbl_keys(self.win_stages)
|
||||
local win_opts = self.stages[1]({
|
||||
message = self:_get_dimensions(notif_buf),
|
||||
open_windows = windows,
|
||||
})
|
||||
if not win_opts then
|
||||
return
|
||||
end
|
||||
local opacity = util.pop(win_opts, "opacity")
|
||||
if opacity then
|
||||
notif_buf.highlights:set_opacity(opacity)
|
||||
end
|
||||
win_opts.noautocmd = true
|
||||
local win = util.open_win(notif_buf, false, win_opts)
|
||||
vim.fn.setwinvar(
|
||||
win,
|
||||
"&winhl",
|
||||
"Normal:" .. notif_buf.highlights.body .. ",FloatBorder:" .. notif_buf.highlights.border
|
||||
)
|
||||
self.win_stages[win] = 2
|
||||
self.win_states[win] = {}
|
||||
self.notif_bufs[win] = notif_buf
|
||||
queue:pop()
|
||||
notif_buf:open(win)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function WindowAnimator:_advance_win_stage(win)
|
||||
local cur_stage = self.win_stages[win]
|
||||
if not cur_stage then
|
||||
return
|
||||
end
|
||||
if cur_stage < #self.stages then
|
||||
if api.nvim_get_current_win() == win then
|
||||
return
|
||||
end
|
||||
self.win_stages[win] = cur_stage + 1
|
||||
return
|
||||
end
|
||||
|
||||
self.win_stages[win] = nil
|
||||
|
||||
local function close()
|
||||
if api.nvim_get_current_win() == win then
|
||||
return vim.defer_fn(close, 1000)
|
||||
end
|
||||
self:_remove_win(win)
|
||||
end
|
||||
|
||||
close()
|
||||
end
|
||||
|
||||
function WindowAnimator:_remove_win(win)
|
||||
pcall(api.nvim_win_close, win, true)
|
||||
self.win_stages[win] = nil
|
||||
self.win_states[win] = nil
|
||||
local notif_buf = self.notif_bufs[win]
|
||||
self.notif_bufs[win] = nil
|
||||
notif_buf:close(win)
|
||||
end
|
||||
|
||||
function WindowAnimator:on_refresh(win)
|
||||
local notif_buf = self.notif_bufs[win]
|
||||
if not notif_buf then
|
||||
return
|
||||
end
|
||||
if self.timers[win] then
|
||||
self.timers[win]:set_repeat(notif_buf:timeout() or self.config.default_timeout())
|
||||
self.timers[win]:again()
|
||||
end
|
||||
end
|
||||
|
||||
function WindowAnimator:_start_timer(win)
|
||||
local buf_time = self.notif_bufs[win]:timeout() == nil and self.config.default_timeout()
|
||||
or self.notif_bufs[win]:timeout()
|
||||
if buf_time ~= false then
|
||||
if buf_time == true then
|
||||
buf_time = nil
|
||||
end
|
||||
local timer = vim.loop.new_timer()
|
||||
self.timers[win] = timer
|
||||
timer:start(
|
||||
buf_time,
|
||||
buf_time,
|
||||
vim.schedule_wrap(function()
|
||||
timer:stop()
|
||||
self.timers[win] = nil
|
||||
local notif_buf = self.notif_bufs[win]
|
||||
if notif_buf and notif_buf:should_stay() then
|
||||
return
|
||||
end
|
||||
self:_advance_win_stage(win)
|
||||
end)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function WindowAnimator:_update_window(time, win, open_windows)
|
||||
local stage = self.win_stages[win]
|
||||
local notif_buf = self.notif_bufs[win]
|
||||
local win_goals = self:_get_win_goals(win, stage, open_windows)
|
||||
|
||||
if not win_goals then
|
||||
self:_remove_win(win)
|
||||
end
|
||||
|
||||
-- If we don't animate, then we move to all goals instantly.
|
||||
-- Can't just jump to the end, because we need to the intermediate changes
|
||||
while
|
||||
not notif_buf:should_animate()
|
||||
and win_goals.time == nil
|
||||
and self.win_stages[win] < #self.stages
|
||||
do
|
||||
for field, goal in pairs(win_goals) do
|
||||
if type(goal) == "table" then
|
||||
win_goals[field] = goal[1]
|
||||
end
|
||||
end
|
||||
self:_advance_win_state(win, win_goals, time)
|
||||
self:_advance_win_stage(win)
|
||||
stage = self.win_stages[win]
|
||||
win_goals = self:_get_win_goals(win, stage, open_windows)
|
||||
end
|
||||
|
||||
if win_goals.time and not self.timers[win] then
|
||||
self:_start_timer(win)
|
||||
end
|
||||
|
||||
self:_advance_win_state(win, win_goals, time)
|
||||
|
||||
if self:_is_complete(win, win_goals) and not win_goals.time then
|
||||
self:_advance_win_stage(win)
|
||||
end
|
||||
end
|
||||
|
||||
function WindowAnimator:_is_complete(win, goals)
|
||||
local complete = true
|
||||
local win_state = self.win_states[win]
|
||||
if not win_state then
|
||||
return true
|
||||
end
|
||||
for field, goal in pairs(goals) do
|
||||
if field ~= "time" then
|
||||
if type(goal) == "table" then
|
||||
if goal.complete then
|
||||
complete = goal.complete(win_state[field].position)
|
||||
else
|
||||
complete = goal[1] == round(win_state[field].position, 2)
|
||||
end
|
||||
end
|
||||
if not complete then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return complete
|
||||
end
|
||||
|
||||
function WindowAnimator:_advance_win_state(win, goals, time)
|
||||
local win_state = self.win_states[win]
|
||||
|
||||
local win_configs = {}
|
||||
|
||||
local function win_conf(win_)
|
||||
if win_configs[win_] then
|
||||
return win_configs[win_]
|
||||
end
|
||||
local exists, conf = util.get_win_config(win_)
|
||||
if not exists then
|
||||
self:_remove_win(win_)
|
||||
return
|
||||
end
|
||||
win_configs[win_] = conf
|
||||
return conf
|
||||
end
|
||||
|
||||
for field, goal in pairs(goals) do
|
||||
if field ~= "time" then
|
||||
local goal_type = type(goal)
|
||||
-- Handle spring goal
|
||||
if goal_type == "table" and goal[1] then
|
||||
if not win_state[field] then
|
||||
if field == "opacity" then
|
||||
win_state[field] = { position = self.notif_bufs[win].highlights:get_opacity() }
|
||||
else
|
||||
local conf = win_conf(win)
|
||||
if not conf then
|
||||
return true
|
||||
end
|
||||
win_state[field] = { position = conf[field] }
|
||||
end
|
||||
end
|
||||
animate.spring(time, goal[1], win_state[field], goal.frequency or 1, goal.damping or 1)
|
||||
--- Directly move goal
|
||||
elseif goal_type ~= "table" then
|
||||
win_state[field] = { position = goal }
|
||||
else
|
||||
error("nvim-notify: Invalid stage goal: " .. vim.inspect(goal))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self:_apply_win_state(win, win_state)
|
||||
end
|
||||
|
||||
function WindowAnimator:_get_win_goals(win, win_stage, open_windows)
|
||||
local notif_buf = self.notif_bufs[win]
|
||||
local win_goals = self.stages[win_stage]({
|
||||
buffer = notif_buf:buffer(),
|
||||
message = self:_get_dimensions(notif_buf),
|
||||
open_windows = open_windows,
|
||||
}, win)
|
||||
return win_goals
|
||||
end
|
||||
|
||||
function WindowAnimator:_get_dimensions(notif_buf)
|
||||
return {
|
||||
height = math.min(self.config.max_height() or 1000, notif_buf:height()),
|
||||
width = math.min(self.config.max_width() or 1000, notif_buf:width()),
|
||||
}
|
||||
end
|
||||
|
||||
function WindowAnimator:_apply_win_state(win, win_state)
|
||||
local win_updated = false
|
||||
if win_state.opacity then
|
||||
win_updated = true
|
||||
local notif_buf = self.notif_bufs[win]
|
||||
if notif_buf:is_valid() then
|
||||
notif_buf.highlights:set_opacity(win_state.opacity.position)
|
||||
vim.fn.setwinvar(
|
||||
win,
|
||||
"&winhl",
|
||||
"Normal:" .. notif_buf.highlights.body .. ",FloatBorder:" .. notif_buf.highlights.border
|
||||
)
|
||||
end
|
||||
end
|
||||
local exists, conf = util.get_win_config(win)
|
||||
local new_conf = {}
|
||||
if not exists then
|
||||
self:_remove_win(win)
|
||||
else
|
||||
local function set_field(field, min, round_to)
|
||||
if not win_state[field] then
|
||||
return
|
||||
end
|
||||
local new_value = max(round(win_state[field].position, round_to), min)
|
||||
if new_value == conf[field] then
|
||||
return
|
||||
end
|
||||
win_updated = true
|
||||
new_conf[field] = new_value
|
||||
end
|
||||
|
||||
set_field("row", 0, 1)
|
||||
set_field("col", 0, 1)
|
||||
set_field("width", 1)
|
||||
set_field("height", 1)
|
||||
|
||||
if win_updated then
|
||||
if new_conf.row or new_conf.col then
|
||||
new_conf.relative = conf.relative
|
||||
new_conf.row = new_conf.row or conf.row
|
||||
new_conf.col = new_conf.col or conf.col
|
||||
end
|
||||
api.nvim_win_set_config(win, new_conf)
|
||||
end
|
||||
end
|
||||
return win_updated
|
||||
end
|
||||
|
||||
---@return WindowAnimator
|
||||
return function(stages, config)
|
||||
return WindowAnimator:new(stages, config)
|
||||
end
|
||||
@ -0,0 +1,118 @@
|
||||
local pickers = require("telescope.pickers")
|
||||
local finders = require("telescope.finders")
|
||||
local conf = require("telescope.config").values
|
||||
local actions = require("telescope.actions")
|
||||
local action_state = require("telescope.actions.state")
|
||||
local previewers = require("telescope.previewers")
|
||||
local entry_display = require("telescope.pickers.entry_display")
|
||||
local notify = require("notify")
|
||||
|
||||
local widths = {
|
||||
time = 8,
|
||||
title = nil,
|
||||
icon = nil,
|
||||
level = nil,
|
||||
message = nil,
|
||||
}
|
||||
|
||||
local displayer = entry_display.create({
|
||||
separator = " ",
|
||||
items = {
|
||||
{ width = widths.time },
|
||||
{ width = widths.title },
|
||||
{ width = widths.icon },
|
||||
{ width = widths.level },
|
||||
{ width = widths.message },
|
||||
},
|
||||
})
|
||||
|
||||
local telescope_notifications = function(opts)
|
||||
local time_format = require("notify")._config().time_formats().notification
|
||||
local notifs = require("notify").history()
|
||||
local reversed = {}
|
||||
for i, notif in ipairs(notifs) do
|
||||
reversed[#notifs - i + 1] = notif
|
||||
end
|
||||
pickers
|
||||
.new(opts, {
|
||||
results_title = "Notifications",
|
||||
prompt_title = "Filter Notifications",
|
||||
finder = finders.new_table({
|
||||
results = reversed,
|
||||
entry_maker = function(notif)
|
||||
return {
|
||||
value = notif,
|
||||
display = function(entry)
|
||||
return displayer({
|
||||
{ vim.fn.strftime(time_format, entry.value.time), "NotifyLogTime" },
|
||||
{ entry.value.title[1], "NotifyLogTitle" },
|
||||
{ entry.value.icon, "Notify" .. entry.value.level .. "Title" },
|
||||
{ entry.value.level, "Notify" .. entry.value.level .. "Title" },
|
||||
{ entry.value.message[1], "Notify" .. entry.value.level .. "Body" },
|
||||
})
|
||||
end,
|
||||
ordinal = notif.title[1] .. " " .. notif.title[2] .. " " .. table.concat(
|
||||
notif.message,
|
||||
" "
|
||||
),
|
||||
}
|
||||
end,
|
||||
}),
|
||||
sorter = conf.generic_sorter(opts),
|
||||
attach_mappings = function(prompt_bufnr, map)
|
||||
actions.select_default:replace(function()
|
||||
actions.close(prompt_bufnr)
|
||||
local selection = action_state.get_selected_entry()
|
||||
if selection == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local notification = selection.value
|
||||
local opened_buffer = notify.open(notification)
|
||||
|
||||
local lines = vim.opt.lines:get()
|
||||
local cols = vim.opt.columns:get()
|
||||
|
||||
local win = vim.api.nvim_open_win(opened_buffer.buffer, true, {
|
||||
relative = "editor",
|
||||
row = (lines - opened_buffer.height) / 2,
|
||||
col = (cols - opened_buffer.width) / 2,
|
||||
height = opened_buffer.height,
|
||||
width = opened_buffer.width,
|
||||
border = "rounded",
|
||||
style = "minimal",
|
||||
})
|
||||
-- vim.wo does not behave like setlocal, thus we use setwinvar to set local
|
||||
-- only options. Otherwise our changes would affect subsequently opened
|
||||
-- windows.
|
||||
-- see e.g. neovim#14595
|
||||
vim.fn.setwinvar(
|
||||
win,
|
||||
"&winhl",
|
||||
"Normal:"
|
||||
.. opened_buffer.highlights.body
|
||||
.. ",FloatBorder:"
|
||||
.. opened_buffer.highlights.border
|
||||
)
|
||||
vim.fn.setwinvar(win, "&wrap", 0)
|
||||
end)
|
||||
return true
|
||||
end,
|
||||
previewer = previewers.new_buffer_previewer({
|
||||
title = "Message",
|
||||
define_preview = function(self, entry, status)
|
||||
local notification = entry.value
|
||||
local max_width = vim.api.nvim_win_get_config(status.preview_win).width
|
||||
vim.api.nvim_win_set_option(status.preview_win, "wrap", true)
|
||||
notify.open(notification, { buffer = self.state.bufnr, max_width = max_width })
|
||||
end,
|
||||
}),
|
||||
})
|
||||
:find()
|
||||
end
|
||||
|
||||
return require("telescope").register_extension({
|
||||
exports = {
|
||||
notify = telescope_notifications,
|
||||
},
|
||||
})
|
||||
3
config/neovim/store/lazy-plugins/nvim-notify/scripts/docgen
Executable file
3
config/neovim/store/lazy-plugins/nvim-notify/scripts/docgen
Executable file
@ -0,0 +1,3 @@
|
||||
#!/nix/store/306znyj77fv49kwnkpxmb0j2znqpa8bj-bash-5.2p26/bin/bash
|
||||
|
||||
nvim --headless -c "luafile ./scripts/gendocs.lua" -c 'qa'
|
||||
804
config/neovim/store/lazy-plugins/nvim-notify/scripts/gendocs.lua
Normal file
804
config/neovim/store/lazy-plugins/nvim-notify/scripts/gendocs.lua
Normal file
@ -0,0 +1,804 @@
|
||||
-- TODO: A lot of this is private code from minidoc, which could be removed if made public
|
||||
|
||||
local minidoc = require("mini.doc")
|
||||
|
||||
local H = {}
|
||||
--stylua: ignore start
|
||||
H.pattern_sets = {
|
||||
-- Patterns for working with afterlines. At the moment deliberately crafted
|
||||
-- to work only on first line without indent.
|
||||
|
||||
-- Determine if line is a function definition. Captures function name and
|
||||
-- arguments. For reference see '2.5.9 – Function Definitions' in Lua manual.
|
||||
afterline_fundef = {
|
||||
'^function%s+(%S-)(%b())', -- Regular definition
|
||||
'^local%s+function%s+(%S-)(%b())', -- Local definition
|
||||
'^(%S+)%s*=%s*function(%b())', -- Regular assignment
|
||||
'^local%s+(%S+)%s*=%s*function(%b())', -- Local assignment
|
||||
},
|
||||
|
||||
-- Determine if line is a general assignment
|
||||
afterline_assign = {
|
||||
'^(%S-)%s*=', -- General assignment
|
||||
'^local%s+(%S-)%s*=', -- Local assignment
|
||||
},
|
||||
|
||||
-- Patterns to work with type descriptions
|
||||
-- (see https://github.com/sumneko/lua-language-server/wiki/EmmyLua-Annotations#types-and-type)
|
||||
types = {
|
||||
'table%b<>',
|
||||
'fun%b(): %S+', 'fun%b()', 'async fun%b(): %S+', 'async fun%b()',
|
||||
'nil', 'any', 'boolean', 'string', 'number', 'integer', 'function', 'table', 'thread', 'userdata', 'lightuserdata',
|
||||
'%.%.%.',
|
||||
"%S+",
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
H.apply_config = function(config)
|
||||
MiniDoc.config = config
|
||||
end
|
||||
|
||||
H.is_disabled = function()
|
||||
return vim.g.minidoc_disable == true or vim.b.minidoc_disable == true
|
||||
end
|
||||
|
||||
H.get_config = function(config)
|
||||
return vim.tbl_deep_extend("force", MiniDoc.config, vim.b.minidoc_config or {}, config or {})
|
||||
end
|
||||
|
||||
-- Work with project specific script ==========================================
|
||||
H.execute_project_script = function(input, output, config)
|
||||
-- Don't process script if there are more than one active `generate` calls
|
||||
if H.generate_is_active then
|
||||
return
|
||||
end
|
||||
|
||||
-- Don't process script if at least one argument is not default
|
||||
if not (input == nil and output == nil and config == nil) then
|
||||
return
|
||||
end
|
||||
|
||||
-- Store information
|
||||
local global_config_cache = vim.deepcopy(MiniDoc.config)
|
||||
local local_config_cache = vim.b.minidoc_config
|
||||
|
||||
-- Pass information to a possible `generate()` call inside script
|
||||
H.generate_is_active = true
|
||||
H.generate_recent_output = nil
|
||||
|
||||
-- Execute script
|
||||
local success = pcall(vim.cmd, "luafile " .. H.get_config(config).script_path)
|
||||
|
||||
-- Restore information
|
||||
MiniDoc.config = global_config_cache
|
||||
vim.b.minidoc_config = local_config_cache
|
||||
H.generate_is_active = nil
|
||||
|
||||
return success
|
||||
end
|
||||
|
||||
-- Default documentation targets ----------------------------------------------
|
||||
H.default_input = function()
|
||||
-- Search in current and recursively in other directories for files with
|
||||
-- 'lua' extension
|
||||
local res = {}
|
||||
for _, dir_glob in ipairs({ ".", "lua/**", "after/**", "colors/**" }) do
|
||||
local files = vim.fn.globpath(dir_glob, "*.lua", false, true)
|
||||
|
||||
-- Use full paths
|
||||
files = vim.tbl_map(function(x)
|
||||
return vim.fn.fnamemodify(x, ":p")
|
||||
end, files)
|
||||
|
||||
-- Put 'init.lua' first among files from same directory
|
||||
table.sort(files, function(a, b)
|
||||
if vim.fn.fnamemodify(a, ":h") == vim.fn.fnamemodify(b, ":h") then
|
||||
if vim.fn.fnamemodify(a, ":t") == "init.lua" then
|
||||
return true
|
||||
end
|
||||
if vim.fn.fnamemodify(b, ":t") == "init.lua" then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return a < b
|
||||
end)
|
||||
table.insert(res, files)
|
||||
end
|
||||
|
||||
return vim.tbl_flatten(res)
|
||||
end
|
||||
|
||||
H.default_output = function()
|
||||
local cur_dir = vim.fn.fnamemodify(vim.loop.cwd(), ":t:r")
|
||||
return ("doc/%s.txt"):format(cur_dir)
|
||||
end
|
||||
|
||||
-- Parsing --------------------------------------------------------------------
|
||||
H.lines_to_block_arr = function(lines, config)
|
||||
local matched_prev, matched_cur
|
||||
|
||||
local res = {}
|
||||
local block_raw = { annotation = {}, section_id = {}, afterlines = {}, line_begin = 1 }
|
||||
|
||||
for i, l in ipairs(lines) do
|
||||
local from, to, section_id = config.annotation_extractor(l)
|
||||
matched_prev, matched_cur = matched_cur, from ~= nil
|
||||
|
||||
if matched_cur then
|
||||
if not matched_prev then
|
||||
-- Finish current block
|
||||
block_raw.line_end = i - 1
|
||||
table.insert(res, H.raw_block_to_block(block_raw, config))
|
||||
|
||||
-- Start new block
|
||||
block_raw = { annotation = {}, section_id = {}, afterlines = {}, line_begin = i }
|
||||
end
|
||||
|
||||
-- Add annotation line without matched annotation pattern
|
||||
table.insert(block_raw.annotation, ("%s%s"):format(l:sub(0, from - 1), l:sub(to + 1)))
|
||||
|
||||
-- Add section id (it is empty string in case of no section id capture)
|
||||
table.insert(block_raw.section_id, section_id or "")
|
||||
else
|
||||
-- Add afterline
|
||||
table.insert(block_raw.afterlines, l)
|
||||
end
|
||||
end
|
||||
block_raw.line_end = #lines
|
||||
table.insert(res, H.raw_block_to_block(block_raw, config))
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
-- Raw block structure is an intermediate step added for convenience. It is
|
||||
-- a table with the following keys:
|
||||
-- - `annotation` - lines (after removing matched annotation pattern) that were
|
||||
-- parsed as annotation.
|
||||
-- - `section_id` - array with length equal to `annotation` length with strings
|
||||
-- captured as section id. Empty string of no section id was captured.
|
||||
-- - Everything else is used as block info (like `afterlines`, etc.).
|
||||
H.raw_block_to_block = function(block_raw, config)
|
||||
if #block_raw.annotation == 0 and #block_raw.afterlines == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
local block = H.new_struct("block", {
|
||||
afterlines = block_raw.afterlines,
|
||||
line_begin = block_raw.line_begin,
|
||||
line_end = block_raw.line_end,
|
||||
})
|
||||
local block_begin = block.info.line_begin
|
||||
|
||||
-- Parse raw block annotation lines from top to bottom. New section starts
|
||||
-- when section id is detected in that line.
|
||||
local section_cur = H.new_struct(
|
||||
"section",
|
||||
{ id = config.default_section_id, line_begin = block_begin }
|
||||
)
|
||||
|
||||
for i, annotation_line in ipairs(block_raw.annotation) do
|
||||
local id = block_raw.section_id[i]
|
||||
if id ~= "" then
|
||||
-- Finish current section
|
||||
if #section_cur > 0 then
|
||||
section_cur.info.line_end = block_begin + i - 2
|
||||
block:insert(section_cur)
|
||||
end
|
||||
|
||||
-- Start new section
|
||||
section_cur = H.new_struct("section", { id = id, line_begin = block_begin + i - 1 })
|
||||
end
|
||||
|
||||
section_cur:insert(annotation_line)
|
||||
end
|
||||
|
||||
if #section_cur > 0 then
|
||||
section_cur.info.line_end = block_begin + #block_raw.annotation - 1
|
||||
block:insert(section_cur)
|
||||
end
|
||||
|
||||
return block
|
||||
end
|
||||
|
||||
-- Hooks ----------------------------------------------------------------------
|
||||
H.apply_structure_hooks = function(doc, hooks)
|
||||
for _, file in ipairs(doc) do
|
||||
for _, block in ipairs(file) do
|
||||
hooks.block_pre(block)
|
||||
|
||||
for _, section in ipairs(block) do
|
||||
hooks.section_pre(section)
|
||||
|
||||
local hook = hooks.sections[section.info.id]
|
||||
if hook ~= nil then
|
||||
hook(section)
|
||||
end
|
||||
|
||||
hooks.section_post(section)
|
||||
end
|
||||
|
||||
hooks.block_post(block)
|
||||
end
|
||||
|
||||
hooks.file(file)
|
||||
end
|
||||
|
||||
hooks.doc(doc)
|
||||
end
|
||||
|
||||
H.alias_register = function(s)
|
||||
if #s == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- Remove first word (with bits of surrounding whitespace) while capturing it
|
||||
local alias_name
|
||||
s[1] = s[1]:gsub("%s*(%S+) ?", function(x)
|
||||
alias_name = x
|
||||
return ""
|
||||
end, 1)
|
||||
if alias_name == nil then
|
||||
return
|
||||
end
|
||||
|
||||
MiniDoc.current.aliases = MiniDoc.current.aliases or {}
|
||||
MiniDoc.current.aliases[alias_name] = table.concat(s, "\n")
|
||||
end
|
||||
|
||||
H.alias_replace = function(s)
|
||||
if MiniDoc.current.aliases == nil then
|
||||
return
|
||||
end
|
||||
|
||||
for i, _ in ipairs(s) do
|
||||
for alias_name, alias_desc in pairs(MiniDoc.current.aliases) do
|
||||
-- Escape special characters. This is done here and not while registering
|
||||
-- alias to allow user to refer to aliases by its original name.
|
||||
-- Store escaped words in separate variables because `vim.pesc()` returns
|
||||
-- two values which might conflict if outputs are used as arguments.
|
||||
local name_escaped = vim.pesc(alias_name)
|
||||
local desc_escaped = vim.pesc(alias_desc)
|
||||
s[i] = s[i]:gsub(name_escaped, desc_escaped)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
H.toc_register = function(s)
|
||||
MiniDoc.current.toc = MiniDoc.current.toc or {}
|
||||
table.insert(MiniDoc.current.toc, s)
|
||||
end
|
||||
|
||||
H.toc_insert = function(s)
|
||||
if MiniDoc.current.toc == nil then
|
||||
return
|
||||
end
|
||||
|
||||
-- Render table of contents
|
||||
local toc_lines = {}
|
||||
for _, toc_entry in ipairs(MiniDoc.current.toc) do
|
||||
local _, tag_section = toc_entry.parent:has_descendant(function(x)
|
||||
return type(x) == "table" and x.type == "section" and x.info.id == "@tag"
|
||||
end)
|
||||
tag_section = tag_section or {}
|
||||
|
||||
local lines = {}
|
||||
for i = 1, math.max(#toc_entry, #tag_section) do
|
||||
local left = toc_entry[i] or ""
|
||||
-- Use tag refernce instead of tag enclosure
|
||||
local right = string.match(tag_section[i], "%*.*%*"):gsub("%*", "|")
|
||||
-- local right = vim.trim((tag_section[i] or ""):gsub("%*", "|"))
|
||||
-- Add visual line only at first entry (while not adding trailing space)
|
||||
local filler = i == 1 and "." or (right == "" and "" or " ")
|
||||
-- Make padding of 2 spaces at both left and right
|
||||
local n_filler = math.max(74 - H.visual_text_width(left) - H.visual_text_width(right), 3)
|
||||
table.insert(lines, (" %s%s%s"):format(left, filler:rep(n_filler), right))
|
||||
end
|
||||
|
||||
table.insert(toc_lines, lines)
|
||||
|
||||
-- Don't show `toc_entry` lines in output
|
||||
toc_entry:clear_lines()
|
||||
end
|
||||
|
||||
for _, l in ipairs(vim.tbl_flatten(toc_lines)) do
|
||||
s:insert(l)
|
||||
end
|
||||
end
|
||||
|
||||
H.add_section_heading = function(s, heading)
|
||||
if #s == 0 or s.type ~= "section" then
|
||||
return
|
||||
end
|
||||
|
||||
-- Add heading
|
||||
s:insert(1, ("%s~"):format(heading))
|
||||
end
|
||||
|
||||
H.enclose_var_name = function(s)
|
||||
if #s == 0 or s.type ~= "section" then
|
||||
return
|
||||
end
|
||||
|
||||
s[1] = s[1]:gsub("(%S+)", "{%1}", 1)
|
||||
end
|
||||
|
||||
---@param init number Start of searching for first "type-like" string. It is
|
||||
--- needed to not detect type early. Like in `@param a_function function`.
|
||||
---@private
|
||||
H.enclose_type = function(s, enclosure, init)
|
||||
if #s == 0 or s.type ~= "section" then
|
||||
return
|
||||
end
|
||||
enclosure = enclosure or "`%(%1%)`"
|
||||
init = init or 1
|
||||
|
||||
local cur_type = H.match_first_pattern(s[1], H.pattern_sets["types"], init)
|
||||
if #cur_type == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- Add `%S*` to front and back of found pattern to support their combination
|
||||
-- with `|`. Also allows using `[]` and `?` prefixes.
|
||||
local type_pattern = ("(%%S*%s%%S*)"):format(vim.pesc(cur_type[1]))
|
||||
|
||||
-- Avoid replacing possible match before `init`
|
||||
local l_start = s[1]:sub(1, init - 1)
|
||||
local l_end = s[1]:sub(init):gsub(type_pattern, enclosure, 1)
|
||||
s[1] = ("%s%s"):format(l_start, l_end)
|
||||
end
|
||||
|
||||
-- Infer data from afterlines -------------------------------------------------
|
||||
H.infer_header = function(b)
|
||||
local has_signature = b:has_descendant(function(x)
|
||||
return type(x) == "table" and x.type == "section" and x.info.id == "@signature"
|
||||
end)
|
||||
local has_tag = b:has_descendant(function(x)
|
||||
return type(x) == "table" and x.type == "section" and x.info.id == "@tag"
|
||||
end)
|
||||
|
||||
if has_signature and has_tag then
|
||||
return
|
||||
end
|
||||
|
||||
local l_all = table.concat(b.info.afterlines, " ")
|
||||
local tag, signature
|
||||
|
||||
-- Try function definition
|
||||
local fun_capture = H.match_first_pattern(l_all, H.pattern_sets["afterline_fundef"])
|
||||
if #fun_capture > 0 then
|
||||
tag = tag or ("%s()"):format(fun_capture[1])
|
||||
signature = signature or ("%s%s"):format(fun_capture[1], fun_capture[2])
|
||||
end
|
||||
|
||||
-- Try general assignment
|
||||
local assign_capture = H.match_first_pattern(l_all, H.pattern_sets["afterline_assign"])
|
||||
if #assign_capture > 0 then
|
||||
tag = tag or assign_capture[1]
|
||||
signature = signature or assign_capture[1]
|
||||
end
|
||||
|
||||
if tag ~= nil then
|
||||
-- First insert signature (so that it will appear after tag section)
|
||||
if not has_signature then
|
||||
b:insert(1, H.as_struct({ signature }, "section", { id = "@signature" }))
|
||||
end
|
||||
|
||||
-- Insert tag
|
||||
if not has_tag then
|
||||
b:insert(1, H.as_struct({ tag }, "section", { id = "@tag" }))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function H.is_module(name)
|
||||
if string.find(name, "%(") then
|
||||
return false
|
||||
end
|
||||
if string.find(name, "[A-Z]") then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
H.format_signature = function(line)
|
||||
-- Try capture function signature
|
||||
local name, args = line:match("(%S-)(%b())")
|
||||
|
||||
|
||||
-- Otherwise pick first word
|
||||
name = name or line:match("(%S+)")
|
||||
if not args and H.is_module(name) then
|
||||
return ""
|
||||
end
|
||||
local name_elems = vim.split(name, ".", { plain = true })
|
||||
name = name_elems[#name_elems]
|
||||
|
||||
if not name then
|
||||
return ""
|
||||
end
|
||||
|
||||
-- Tidy arguments
|
||||
if args and args ~= "()" then
|
||||
local arg_parts = vim.split(args:sub(2, -2), ",")
|
||||
local arg_list = {}
|
||||
for _, a in ipairs(arg_parts) do
|
||||
-- Enclose argument in `{}` while controlling whitespace
|
||||
table.insert(arg_list, ("{%s}"):format(vim.trim(a)))
|
||||
end
|
||||
args = ("(%s)"):format(table.concat(arg_list, ", "))
|
||||
end
|
||||
|
||||
return ("`%s`%s"):format(name, args or "")
|
||||
end
|
||||
|
||||
-- Work with structures -------------------------------------------------------
|
||||
-- Constructor
|
||||
H.new_struct = function(struct_type, info)
|
||||
local output = {
|
||||
info = info or {},
|
||||
type = struct_type,
|
||||
}
|
||||
|
||||
output.insert = function(self, index, child)
|
||||
-- Allow both `x:insert(child)` and `x:insert(1, child)`
|
||||
if child == nil then
|
||||
child, index = index, #self + 1
|
||||
end
|
||||
|
||||
if type(child) == "table" then
|
||||
child.parent = self
|
||||
child.parent_index = index
|
||||
end
|
||||
|
||||
table.insert(self, index, child)
|
||||
|
||||
H.sync_parent_index(self)
|
||||
end
|
||||
|
||||
output.remove = function(self, index)
|
||||
index = index or #self
|
||||
table.remove(self, index)
|
||||
|
||||
H.sync_parent_index(self)
|
||||
end
|
||||
|
||||
output.has_descendant = function(self, predicate)
|
||||
local bool_res, descendant = false, nil
|
||||
H.apply_recursively(function(x)
|
||||
if not bool_res and predicate(x) then
|
||||
bool_res = true
|
||||
descendant = x
|
||||
end
|
||||
end, self)
|
||||
return bool_res, descendant
|
||||
end
|
||||
|
||||
output.has_lines = function(self)
|
||||
return self:has_descendant(function(x)
|
||||
return type(x) == "string"
|
||||
end)
|
||||
end
|
||||
|
||||
output.clear_lines = function(self)
|
||||
for i, x in ipairs(self) do
|
||||
if type(x) == "string" then
|
||||
self[i] = nil
|
||||
else
|
||||
x:clear_lines()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return output
|
||||
end
|
||||
|
||||
H.sync_parent_index = function(x)
|
||||
for i, _ in ipairs(x) do
|
||||
if type(x[i]) == "table" then
|
||||
x[i].parent_index = i
|
||||
end
|
||||
end
|
||||
return x
|
||||
end
|
||||
|
||||
-- Converter (this ensures that children have proper parent-related data)
|
||||
H.as_struct = function(array, struct_type, info)
|
||||
-- Make default info `info` for cases when structure is created manually
|
||||
local default_info = ({
|
||||
section = { id = "@text", line_begin = -1, line_end = -1 },
|
||||
block = { afterlines = {}, line_begin = -1, line_end = -1 },
|
||||
file = { path = "" },
|
||||
doc = { input = {}, output = "", config = H.get_config() },
|
||||
})[struct_type]
|
||||
info = vim.tbl_deep_extend("force", default_info, info or {})
|
||||
|
||||
local res = H.new_struct(struct_type, info)
|
||||
for _, x in ipairs(array) do
|
||||
res:insert(x)
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
-- Work with text -------------------------------------------------------------
|
||||
H.ensure_indent = function(text, n_indent_target)
|
||||
local lines = vim.split(text, "\n")
|
||||
local n_indent, n_indent_cur = math.huge, math.huge
|
||||
|
||||
-- Find number of characters in indent
|
||||
for _, l in ipairs(lines) do
|
||||
-- Update lines indent: minimum of all indents except empty lines
|
||||
if n_indent > 0 then
|
||||
_, n_indent_cur = l:find("^%s*")
|
||||
-- Condition "current n-indent equals line length" detects empty line
|
||||
if (n_indent_cur < n_indent) and (n_indent_cur < l:len()) then
|
||||
n_indent = n_indent_cur
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Ensure indent
|
||||
local indent = string.rep(" ", n_indent_target)
|
||||
for i, l in ipairs(lines) do
|
||||
if l ~= "" then
|
||||
lines[i] = indent .. l:sub(n_indent + 1)
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
H.align_text = function(text, width, direction)
|
||||
if type(text) ~= "string" then
|
||||
return
|
||||
end
|
||||
text = vim.trim(text)
|
||||
width = width or 78
|
||||
direction = direction or "left"
|
||||
|
||||
-- Don't do anything if aligning left or line is a whitespace
|
||||
if direction == "left" or text:find("^%s*$") then
|
||||
return text
|
||||
end
|
||||
|
||||
local n_left = math.max(0, 78 - H.visual_text_width(text))
|
||||
if direction == "center" then
|
||||
n_left = math.floor(0.5 * n_left)
|
||||
end
|
||||
|
||||
return (" "):rep(n_left) .. text
|
||||
end
|
||||
|
||||
H.visual_text_width = function(text)
|
||||
-- Ignore concealed characters (usually "invisible" in 'help' filetype)
|
||||
local _, n_concealed_chars = text:gsub("([*|`])", "%1")
|
||||
return vim.fn.strdisplaywidth(text) - n_concealed_chars
|
||||
end
|
||||
|
||||
--- Return earliest match among many patterns
|
||||
---
|
||||
--- Logic here is to test among several patterns. If several got a match,
|
||||
--- return one with earliest match.
|
||||
---
|
||||
---@private
|
||||
H.match_first_pattern = function(text, pattern_set, init)
|
||||
local start_tbl = vim.tbl_map(function(pattern)
|
||||
return text:find(pattern, init) or math.huge
|
||||
end, pattern_set)
|
||||
|
||||
local min_start, min_id = math.huge, nil
|
||||
for id, st in ipairs(start_tbl) do
|
||||
if st < min_start then
|
||||
min_start, min_id = st, id
|
||||
end
|
||||
end
|
||||
|
||||
if min_id == nil then
|
||||
return {}
|
||||
end
|
||||
return { text:match(pattern_set[min_id], init) }
|
||||
end
|
||||
|
||||
-- Utilities ------------------------------------------------------------------
|
||||
H.apply_recursively = function(f, x, used)
|
||||
used = used or {}
|
||||
if used[x] then
|
||||
return
|
||||
end
|
||||
f(x)
|
||||
used[x] = true
|
||||
|
||||
if type(x) == "table" then
|
||||
for _, t in ipairs(x) do
|
||||
H.apply_recursively(f, t, used)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
H.collect_strings = function(x)
|
||||
local res = {}
|
||||
H.apply_recursively(function(y)
|
||||
if type(y) == "string" then
|
||||
-- Allow `\n` in strings
|
||||
table.insert(res, vim.split(y, "\n"))
|
||||
end
|
||||
end, x)
|
||||
-- Flatten to only have strings and not table of strings (from `vim.split`)
|
||||
return vim.tbl_flatten(res)
|
||||
end
|
||||
|
||||
H.file_read = function(path)
|
||||
local file = assert(io.open(path))
|
||||
local contents = file:read("*all")
|
||||
file:close()
|
||||
|
||||
return vim.split(contents, "\n")
|
||||
end
|
||||
|
||||
H.file_write = function(path, lines)
|
||||
-- Ensure target directory exists
|
||||
local dir = vim.fn.fnamemodify(path, ":h")
|
||||
vim.fn.mkdir(dir, "p")
|
||||
|
||||
-- Write to file
|
||||
vim.fn.writefile(lines, path, "b")
|
||||
end
|
||||
|
||||
H.full_path = function(path)
|
||||
return vim.fn.resolve(vim.fn.fnamemodify(path, ":p"))
|
||||
end
|
||||
|
||||
H.message = function(msg)
|
||||
vim.cmd("echomsg " .. vim.inspect("(mini.doc) " .. msg))
|
||||
end
|
||||
|
||||
minidoc.setup({})
|
||||
minidoc.generate(
|
||||
{
|
||||
"./lua/notify/init.lua",
|
||||
"./lua/notify/config/init.lua",
|
||||
"./lua/notify/render/init.lua",
|
||||
},
|
||||
nil,
|
||||
{
|
||||
hooks = vim.tbl_extend("force", minidoc.default_hooks, {
|
||||
block_post = function(b)
|
||||
if not b:has_lines() then return end
|
||||
|
||||
local found_param, found_field = false, false
|
||||
local n_tag_sections = 0
|
||||
H.apply_recursively(function(x)
|
||||
if not (type(x) == 'table' and x.type == 'section') then return end
|
||||
|
||||
-- Add headings before first occurence of a section which type usually
|
||||
-- appear several times
|
||||
if not found_param and x.info.id == '@param' then
|
||||
H.add_section_heading(x, 'Parameters')
|
||||
found_param = true
|
||||
end
|
||||
if not found_field and x.info.id == '@field' then
|
||||
H.add_section_heading(x, 'Fields')
|
||||
found_field = true
|
||||
end
|
||||
|
||||
if x.info.id == '@tag' then
|
||||
local text = x[1]
|
||||
local tag = string.match(text, "%*.*%*")
|
||||
local prefix = (string.sub(tag, 2, #tag - 1))
|
||||
if not H.is_module(prefix) then
|
||||
prefix = ""
|
||||
end
|
||||
local n_filler = math.max(78 - H.visual_text_width(prefix) - H.visual_text_width(tag), 3)
|
||||
local line = ("%s%s%s"):format(prefix, (" "):rep(n_filler), tag)
|
||||
x:remove(1)
|
||||
x:insert(1, line)
|
||||
x.parent:remove(x.parent_index)
|
||||
n_tag_sections = n_tag_sections + 1
|
||||
x.parent:insert(n_tag_sections, x)
|
||||
end
|
||||
end, b)
|
||||
|
||||
-- b:insert(1, H.as_struct({ string.rep('=', 78) }, 'section'))
|
||||
b:insert(H.as_struct({ '' }, 'section'))
|
||||
end,
|
||||
|
||||
|
||||
doc = function(d)
|
||||
-- Render table of contents
|
||||
H.apply_recursively(function(x)
|
||||
if not (type(x) == 'table' and x.type == 'section' and x.info.id == '@toc') then return end
|
||||
H.toc_insert(x)
|
||||
end, d)
|
||||
|
||||
-- Insert modeline
|
||||
d:insert(
|
||||
H.as_struct(
|
||||
{ H.as_struct({ H.as_struct({ ' vim:tw=78:ts=8:noet:ft=help:norl:' }, 'section') }, 'block') },
|
||||
'file'
|
||||
)
|
||||
)
|
||||
end,
|
||||
sections = {
|
||||
['@generic'] = function(s)
|
||||
s:remove(1)
|
||||
end,
|
||||
['@field'] = function(s)
|
||||
-- H.mark_optional(s)
|
||||
if string.find(s[1], "^private ") then
|
||||
s:remove(1)
|
||||
return
|
||||
end
|
||||
H.enclose_var_name(s)
|
||||
H.enclose_type(s, '`%(%1%)`', s[1]:find('%s'))
|
||||
end,
|
||||
['@alias'] = function(s)
|
||||
local name = s[1]:match('%s*(%S*)')
|
||||
local alias = s[1]:match('%s(.*)$')
|
||||
s[1] = ("`%s` → `%s`"):format(name, alias)
|
||||
H.add_section_heading(s, 'Alias')
|
||||
s:insert(1, H.as_struct({ ("*%s*"):format(name) }, "section", { id = "@tag" }))
|
||||
end,
|
||||
|
||||
['@param'] = function(s)
|
||||
H.enclose_var_name(s)
|
||||
H.enclose_type(s, '`%(%1%)`', s[1]:find('%s'))
|
||||
end,
|
||||
['@return'] = function(s)
|
||||
H.enclose_type(s, '`%(%1%)`', 1)
|
||||
H.add_section_heading(s, 'Return')
|
||||
end,
|
||||
['@nodoc'] = function(s) s.parent:clear_lines() end,
|
||||
['@class'] = function(s)
|
||||
H.enclose_var_name(s)
|
||||
-- Add heading
|
||||
local line = s[1]
|
||||
s:remove(1)
|
||||
local class_name = string.match(line, "%{(.*)%}")
|
||||
local inherits = string.match(line, ": (.*)")
|
||||
if inherits then
|
||||
s:insert(1, ("Inherits: `%s`"):format(inherits))
|
||||
s:insert(2, "")
|
||||
end
|
||||
s:insert(1, H.as_struct({ ("*%s*"):format(class_name) }, "section", { id = "@tag" }))
|
||||
end,
|
||||
|
||||
['@signature'] = function(s)
|
||||
s[1] = H.format_signature(s[1])
|
||||
if s[1] ~= "" then
|
||||
table.insert(s, "")
|
||||
end
|
||||
end,
|
||||
|
||||
},
|
||||
|
||||
file = function(f)
|
||||
if not f:has_lines() then
|
||||
return
|
||||
end
|
||||
|
||||
if f.info.path ~= "./lua/notify/init.lua" then
|
||||
f:insert(1, H.as_struct({ H.as_struct({ string.rep("=", 78) }, "section") }, "block"))
|
||||
f:insert(H.as_struct({ H.as_struct({ "" }, "section") }, "block"))
|
||||
else
|
||||
f:insert(
|
||||
1,
|
||||
H.as_struct(
|
||||
{
|
||||
H.as_struct(
|
||||
{ "*nvim-notify.txt* A fancy, configurable notification manager for NeoVim" },
|
||||
"section"
|
||||
),
|
||||
},
|
||||
"block"
|
||||
)
|
||||
)
|
||||
f:insert(2, H.as_struct({ H.as_struct({ "" }, "section") }, "block"))
|
||||
f:insert(3, H.as_struct({ H.as_struct({ string.rep("=", 78) }, "section") }, "block"))
|
||||
f:insert(H.as_struct({ H.as_struct({ "" }, "section") }, "block"))
|
||||
end
|
||||
end,
|
||||
}),
|
||||
}
|
||||
)
|
||||
3
config/neovim/store/lazy-plugins/nvim-notify/scripts/style
Executable file
3
config/neovim/store/lazy-plugins/nvim-notify/scripts/style
Executable file
@ -0,0 +1,3 @@
|
||||
#!/nix/store/306znyj77fv49kwnkpxmb0j2znqpa8bj-bash-5.2p26/bin/bash
|
||||
|
||||
stylua lua tests
|
||||
20
config/neovim/store/lazy-plugins/nvim-notify/scripts/test
Executable file
20
config/neovim/store/lazy-plugins/nvim-notify/scripts/test
Executable file
@ -0,0 +1,20 @@
|
||||
#!/nix/store/306znyj77fv49kwnkpxmb0j2znqpa8bj-bash-5.2p26/bin/bash
|
||||
tempfile=".test_output.tmp"
|
||||
|
||||
if [[ -n $1 ]]; then
|
||||
nvim --headless --noplugin -u tests/init.vim -c "PlenaryBustedFile $1" | tee "${tempfile}"
|
||||
else
|
||||
nvim --headless --noplugin -u tests/init.vim -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/init.vim'}" | tee "${tempfile}"
|
||||
fi
|
||||
|
||||
# Plenary doesn't emit exit code 1 when tests have errors during setup
|
||||
errors=$(sed 's/\x1b\[[0-9;]*m//g' "${tempfile}" | awk '/(Errors|Failed) :/ {print $3}' | grep -v '0')
|
||||
|
||||
rm "${tempfile}"
|
||||
|
||||
if [[ -n $errors ]]; then
|
||||
echo "Tests failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
5
config/neovim/store/lazy-plugins/nvim-notify/stylua.toml
Normal file
5
config/neovim/store/lazy-plugins/nvim-notify/stylua.toml
Normal file
@ -0,0 +1,5 @@
|
||||
column_width = 100
|
||||
line_endings = "Unix"
|
||||
indent_type = "Spaces"
|
||||
indent_width = 2
|
||||
quote_style = "AutoPreferDouble"
|
||||
@ -0,0 +1,4 @@
|
||||
set rtp+=.
|
||||
set rtp+=../plenary.nvim
|
||||
set termguicolors
|
||||
runtime! plugin/plenary.vim
|
||||
@ -0,0 +1,160 @@
|
||||
local async = require("plenary.async")
|
||||
async.tests.add_to_env()
|
||||
vim.opt.termguicolors = true
|
||||
A = vim.schedule_wrap(function(...)
|
||||
print(vim.inspect(...))
|
||||
end)
|
||||
|
||||
describe("checking public interface", function()
|
||||
local notify = require("notify")
|
||||
local async_notify = require("notify").async
|
||||
assert:add_formatter(vim.inspect)
|
||||
|
||||
before_each(function()
|
||||
notify.setup({ background_colour = "#000000" })
|
||||
notify.dismiss({ pending = true, silent = true })
|
||||
end)
|
||||
|
||||
describe("notifications", function()
|
||||
it("returns all previous notifications", function()
|
||||
notify.notify("test", "error")
|
||||
local notifs = notify.history()
|
||||
assert.are.same({
|
||||
{
|
||||
icon = "",
|
||||
id = 1,
|
||||
level = "ERROR",
|
||||
message = { "test" },
|
||||
render = notifs[1].render,
|
||||
time = notifs[1].time,
|
||||
title = { "", notifs[1].title[2] },
|
||||
},
|
||||
}, notifs)
|
||||
end)
|
||||
|
||||
describe("rendering", function()
|
||||
a.it("uses custom render in config", function()
|
||||
local called = false
|
||||
notify.setup({
|
||||
background_colour = "#000000",
|
||||
render = function()
|
||||
called = true
|
||||
end,
|
||||
})
|
||||
notify.async("test", "error").events.open()
|
||||
assert.is.True(called)
|
||||
end)
|
||||
|
||||
a.it("uses custom render in call", function()
|
||||
local called = false
|
||||
notify
|
||||
.async("test", "error", {
|
||||
render = function()
|
||||
called = true
|
||||
end,
|
||||
}).events
|
||||
.open()
|
||||
assert.is.True(called)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("replacing", function()
|
||||
it("inherits options", function()
|
||||
local orig = notify.notify("first", "info", { title = "test", icon = "x" })
|
||||
local next = notify.notify("second", nil, { replace = orig })
|
||||
|
||||
assert.are.same(
|
||||
next,
|
||||
vim.tbl_extend("force", orig, { id = next.id, message = next.message })
|
||||
)
|
||||
end)
|
||||
|
||||
a.it("uses same window", function()
|
||||
local orig = async_notify("first", "info", { timeout = false })
|
||||
local win = orig.events.open()
|
||||
async_notify("second", nil, { replace = orig, timeout = 100 })
|
||||
async.util.scheduler()
|
||||
local found = false
|
||||
local bufs = vim.api.nvim_list_bufs()
|
||||
for _, buf in ipairs(bufs) do
|
||||
if vim.api.nvim_buf_get_lines(buf, 0, -1, false)[1] == "second" then
|
||||
assert.Not(found)
|
||||
assert.same(vim.fn.bufwinid(buf), win)
|
||||
found = true
|
||||
end
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
a.it("uses the confgured minimum width", function()
|
||||
notify.setup({
|
||||
background_colour = "#000000",
|
||||
minimum_width = 20,
|
||||
})
|
||||
local win = notify.async("test").events.open()
|
||||
assert.equal(vim.api.nvim_win_get_width(win), 20)
|
||||
end)
|
||||
|
||||
a.it("uses the configured max width", function()
|
||||
notify.setup({
|
||||
background_colour = "#000000",
|
||||
max_width = function()
|
||||
return 3
|
||||
end,
|
||||
})
|
||||
local win = notify.async("test").events.open()
|
||||
assert.equal(vim.api.nvim_win_get_width(win), 3)
|
||||
end)
|
||||
|
||||
a.it("uses the configured max height", function()
|
||||
local instance = notify.instance({
|
||||
background_colour = "#000000",
|
||||
max_height = function()
|
||||
return 3
|
||||
end,
|
||||
}, false)
|
||||
local win = instance.async("test").events.open()
|
||||
assert.equal(vim.api.nvim_win_get_height(win), 3)
|
||||
end)
|
||||
|
||||
a.it("renders title as longest line", function()
|
||||
local instance = notify.instance({
|
||||
background_colour = "#000000",
|
||||
minimum_width = 10,
|
||||
}, false)
|
||||
local win = instance.async("test", nil, { title = { string.rep("a", 16), "" } }).events.open()
|
||||
assert.equal(21, vim.api.nvim_win_get_width(win))
|
||||
end)
|
||||
|
||||
a.it("renders notification above config level", function()
|
||||
local win =
|
||||
notify.async("test", "info", { message = { string.rep("a", 16), "" } }).events.open()
|
||||
assert.Not.Nil(vim.api.nvim_win_get_config(win))
|
||||
end)
|
||||
|
||||
a.it("doesn't render notification below config level", function()
|
||||
async.run(function()
|
||||
local notif = notify.async("test", "debug", { message = { string.rep("a", 16), "" } })
|
||||
local win = notif.events.open()
|
||||
async.api.nvim_buf_set_option(async.api.nvim_win_get_buf(win), "filetype", "test")
|
||||
end)
|
||||
async.util.sleep(100)
|
||||
local bufs = vim.api.nvim_list_bufs()
|
||||
for _, buf in ipairs(bufs) do
|
||||
assert.Not.same(vim.api.nvim_buf_get_option(buf, "filetype"), "test")
|
||||
end
|
||||
end)
|
||||
a.it("refreshes timeout on replace", function()
|
||||
-- Don't want to spend time animating
|
||||
notify.setup({ background_colour = "#000000", stages = "static" })
|
||||
|
||||
local notif = notify.async("test", "error", { timeout = 500 })
|
||||
local win = notif.events.open()
|
||||
a.util.sleep(300)
|
||||
notify.async("test2", "error", { replace = notif })
|
||||
a.util.sleep(300)
|
||||
a.util.scheduler()
|
||||
assert(vim.api.nvim_win_is_valid(win))
|
||||
end)
|
||||
end)
|
||||
Reference in New Issue
Block a user