1

Regenerate nvim config

This commit is contained in:
2024-06-02 03:29:20 +02:00
parent 75eea0c030
commit ef2e28883d
5576 changed files with 604886 additions and 503 deletions

View File

@ -0,0 +1,89 @@
name: Bug Report
description: File a bug/issue
title: "bug: "
labels: [bug]
body:
- type: markdown
attributes:
value: |
**Before** reporting an issue, make sure to read the [documentation](https://github.com/folke/noice.nvim) and search [existing issues](https://github.com/folke/noice.nvim/issues). Usage questions such as ***"How do I...?"*** belong in [Discussions](https://github.com/folke/noice.nvim/discussions) and will be closed.
- type: checkboxes
attributes:
label: Did you check docs and existing issues?
description: Make sure you checked all of the below before submitting an issue
options:
- label: I have read all the noice.nvim docs
required: true
- label: I have searched the existing issues of noice.nvim
required: true
- label: I have searched the existing issues of plugins related to this issue
required: true
- type: input
attributes:
label: "Neovim version (nvim -v)"
placeholder: "0.8.0 commit db1b0ee3b30f"
validations:
required: true
- type: input
attributes:
label: "Operating system/version"
placeholder: "MacOS 11.5"
validations:
required: true
- type: textarea
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is. Please include any related errors you see in Neovim.
validations:
required: true
- type: textarea
attributes:
label: Steps To Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1.
2.
3.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Repro
description: Minimal `init.lua` to reproduce this issue. Save as `repro.lua` and run with `nvim -u repro.lua`
value: |
-- DO NOT change the paths and don't remove the colorscheme
local root = vim.fn.fnamemodify("./.repro", ":p")
-- set stdpaths to use .repro
for _, name in ipairs({ "config", "data", "state", "cache" }) do
vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
end
-- bootstrap lazy
local lazypath = root .. "/plugins/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", lazypath, })
end
vim.opt.runtimepath:prepend(lazypath)
-- install plugins
local plugins = {
"folke/tokyonight.nvim",
"folke/noice.nvim",
-- add any other plugins here
}
require("lazy").setup(plugins, {
root = root .. "/plugins",
})
vim.cmd.colorscheme("tokyonight")
-- add anything else here
render: Lua
validations:
required: false

View File

@ -0,0 +1,36 @@
name: Feature Request
description: Suggest a new feature
title: "feature: "
labels: [enhancement]
body:
- type: checkboxes
attributes:
label: Did you check the docs?
description: Make sure you read all the docs before submitting a feature request
options:
- label: I have read all the noice.nvim docs
required: true
- type: textarea
validations:
required: true
attributes:
label: Is your feature request related to a problem? Please describe.
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
- type: textarea
validations:
required: true
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
- type: textarea
validations:
required: true
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
- type: textarea
validations:
required: false
attributes:
label: Additional context
description: Add any other context or screenshots about the feature request here.

View File

@ -0,0 +1,72 @@
name: CI
on:
push:
pull_request:
jobs:
tests:
strategy:
matrix:
# os: [ubuntu-latest, windows-latest]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Install Neovim
shell: bash
run: |
mkdir -p /tmp/nvim
wget -q https://github.com/neovim/neovim/releases/download/nightly/nvim.appimage -O /tmp/nvim/nvim.appimage
cd /tmp/nvim
chmod a+x ./nvim.appimage
./nvim.appimage --appimage-extract
echo "/tmp/nvim/squashfs-root/usr/bin/" >> $GITHUB_PATH
- name: Run Tests
run: |
nvim --version
[ ! -d tests ] && exit 0
nvim --headless -u tests/init.lua -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/init.lua', sequential = true}"
docs:
runs-on: ubuntu-latest
needs: tests
if: ${{ github.ref == 'refs/heads/main' }}
steps:
- uses: actions/checkout@v3
- name: panvimdoc
uses: kdheepak/panvimdoc@main
with:
vimdoc: noice.nvim
version: "Neovim >= 0.8.0"
demojify: true
treesitter: true
- name: Push changes
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: "chore(build): auto-generate vimdoc"
commit_user_name: "github-actions[bot]"
commit_user_email: "github-actions[bot]@users.noreply.github.com"
commit_author: "github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
release:
name: release
if: ${{ github.ref == 'refs/heads/main' }}
needs:
- docs
- tests
runs-on: ubuntu-latest
steps:
- uses: google-github-actions/release-please-action@v3
id: release
with:
release-type: simple
package-name: noice.nvim
- uses: actions/checkout@v3
- name: tag stable versions
if: ${{ steps.release.outputs.release_created }}
run: |
git config user.name github-actions[bot]
git config user.email github-actions[bot]@users.noreply.github.com
git remote add gh-token "https://${{ secrets.GITHUB_TOKEN }}@github.com/google-github-actions/release-please-action.git"
git tag -d stable || true
git push origin :stable || true
git tag -a stable -m "Last Stable Release"
git push origin stable

View File

@ -0,0 +1,8 @@
tt.*
.tests
doc/tags
debug
.repro
foo.*
*.log
data

View File

@ -0,0 +1,12 @@
MD013:
line_length: 120
tables: false
MD033:
allowed_elements:
- "details"
- "summary"
- "b"
- "table"
- "tr"
- "td"
- "a"

View File

@ -0,0 +1,19 @@
{
"neodev": {
"library": {
"plugins": [
"nui.nvim",
"nvim-cmp",
"nvim-notify",
"telescope.nvim",
"plenary.nvim"
]
}
},
"lspconfig": {
"sumneko_lua": {
"Lua.runtime.version": "LuaJIT",
"Lua.workspace.checkThirdParty": false
}
}
}

View File

@ -0,0 +1,873 @@
# Changelog
## [3.0.2](https://github.com/folke/noice.nvim/compare/v3.0.1...v3.0.2) (2024-05-22)
### Bug Fixes
* **markdown:** keys when buf is invalid ([0dc97cb](https://github.com/folke/noice.nvim/commit/0dc97cb21edac79a1d575541c57a31a3e3bd5b88))
## [3.0.1](https://github.com/folke/noice.nvim/compare/v3.0.0...v3.0.1) (2024-05-18)
### Bug Fixes
* typo with hist messages. Fixes [#815](https://github.com/folke/noice.nvim/issues/815) ([a0a40d3](https://github.com/folke/noice.nvim/commit/a0a40d3e5fdb2eab005e2ee08c4af09c99fcb9a4))
## [3.0.0](https://github.com/folke/noice.nvim/compare/v2.1.1...v3.0.0) (2024-05-18)
### ⚠ BREAKING CHANGES
* bump required Neovim version to >= 0.9
### Features
* bump required Neovim version to &gt;= 0.9 ([6c5290a](https://github.com/folke/noice.nvim/commit/6c5290ad947a97c34889debe59bafe771a9f9578))
* **util:** better debug log ([217c684](https://github.com/folke/noice.nvim/commit/217c6848a6656c5e187d66a2b3caed9a9676d448))
### Bug Fixes
* **cmdline:** use other work-around for cmdpreview ([6a3721b](https://github.com/folke/noice.nvim/commit/6a3721b03dbeb10c3b1c47f63fdc2d77bb550550))
* **msg:** add historical messages as a msg_show instead of history so it doesnt popup ([b9b4818](https://github.com/folke/noice.nvim/commit/b9b481864d0d91c7df539ecebb30a2235a3678da))
* **ui:** dont try updating the ui during textlock ([4ef75a3](https://github.com/folke/noice.nvim/commit/4ef75a3c3893f9efcaf078e60280a9247b9876ff))
## [2.1.1](https://github.com/folke/noice.nvim/compare/v2.1.0...v2.1.1) (2024-05-16)
### Bug Fixes
* fixed check on get_clients. Fixes [#806](https://github.com/folke/noice.nvim/issues/806) ([588471b](https://github.com/folke/noice.nvim/commit/588471bdf26d8fde3fa1672339339a18e1b8568a))
## [2.1.0](https://github.com/folke/noice.nvim/compare/v2.0.3...v2.1.0) (2024-05-16)
### Features
* **config:** added `Noice all` to show ALL messages captured by Noice. See [#769](https://github.com/folke/noice.nvim/issues/769) ([72f72d3](https://github.com/folke/noice.nvim/commit/72f72d3271109ea37128681766de068b62947647))
### Bug Fixes
* **cmdline:** yet another work-around that no longer temporarily changes the cmdline ([68b9c53](https://github.com/folke/noice.nvim/commit/68b9c5395a5e0f4462b4f38791018a48e6929cd9))
* depraction warnings on Neovim 0.11 ([9946087](https://github.com/folke/noice.nvim/commit/9946087bb51a5bfb99efcd1527ef7fe46574cf2f))
* **format:** config.format doesn't work ([#772](https://github.com/folke/noice.nvim/issues/772)) ([09708be](https://github.com/folke/noice.nvim/commit/09708be5668762062d619fddf6cb2b68d165a9c2))
* **messages:** include any messages before Noice was started as one history_show message. Fixes [#799](https://github.com/folke/noice.nvim/issues/799) ([61947de](https://github.com/folke/noice.nvim/commit/61947de3d5904375ea94e0c13db2537488ad9829))
* **messages:** only add previous messages once when Noice starts. Fixes [#804](https://github.com/folke/noice.nvim/issues/804) ([269de18](https://github.com/folke/noice.nvim/commit/269de18e0c4682c8964f278b4fbb9f6ef9a52cd3))
* **msg:** update router when blocking ([ee433a7](https://github.com/folke/noice.nvim/commit/ee433a724c31858537a7d8c80d09c7686810da4a))
* **router:** add additional updates on `SafeState` when available ([fff989f](https://github.com/folke/noice.nvim/commit/fff989f7e5fd3c1e307ac8b92de26be837b18c81))
* **router:** don't use `SafeState` since apparently this is a nightly thing and seems to work without. Fixes [#805](https://github.com/folke/noice.nvim/issues/805) ([ef085e9](https://github.com/folke/noice.nvim/commit/ef085e9cf9ab15a679a403dcfbafba08b9c6cbec))
* **router:** remove SafeState again, since it breaks incsearch ([3c3a8f3](https://github.com/folke/noice.nvim/commit/3c3a8f30061bfbb73308e221d760a3c7c6526473))
### Performance Improvements
* **cmdline:** prevent unneeded redraws during cmdline preview (substitute). Fixes [#803](https://github.com/folke/noice.nvim/issues/803) ([8d924eb](https://github.com/folke/noice.nvim/commit/8d924ebc8e5c28ee30793c30b57d9670884bf05b))
* **ui_attach:** router now only queues messages in `vim.ui_attach`. Use `SafeState` to execute queue when needed. ([4c26991](https://github.com/folke/noice.nvim/commit/4c2699111730a14144224d7b193bede6b707b1bc))
## [2.0.3](https://github.com/folke/noice.nvim/compare/v2.0.2...v2.0.3) (2024-05-15)
### Bug Fixes
* **hacks:** use feedkeys instead of input to force redraw ([dbf8d70](https://github.com/folke/noice.nvim/commit/dbf8d708a46c9d5a6c79f8fc77ac4ffa04d3f0d9))
* **nui:** safely destroy any create window/buffers during E565 errors. Fixes command preview ([a0c6203](https://github.com/folke/noice.nvim/commit/a0c6203d551242322ac7995b26d4e320140e05b1))
## [2.0.2](https://github.com/folke/noice.nvim/compare/v2.0.1...v2.0.2) (2024-05-09)
### Bug Fixes
* **cmdpreview:** read the variable `cmdpreview` in nvim-0.9+ on windows ([#774](https://github.com/folke/noice.nvim/issues/774)) ([a35003d](https://github.com/folke/noice.nvim/commit/a35003dbdd9c8d6d05aa47064de2b76ace872ec4))
* disable incsearch hack for nightly (no longer needed) ([02d698a](https://github.com/folke/noice.nvim/commit/02d698ac0d294e2195429662d0d1cae6cd9a5621))
* **progress:** Change LspProgress data field `result` to `params` ([#785](https://github.com/folke/noice.nvim/issues/785)) ([89de3b5](https://github.com/folke/noice.nvim/commit/89de3b56abee53783dca3500115891fb827669d6))
* retry rendering only once to prevent rendering loops ([f4decbc](https://github.com/folke/noice.nvim/commit/f4decbc7a80229ccc9f86026b74bdcf0c39e38a7))
* **router:** make sure we retry views that could not render due to E565. Fixes [#783](https://github.com/folke/noice.nvim/issues/783) ([6df3d8a](https://github.com/folke/noice.nvim/commit/6df3d8acea83b531deca29e18c64783a3eac106d))
* use `vim.api.nvim__redraw` to update cursor on nightly instead of ffi. Fixes [#781](https://github.com/folke/noice.nvim/issues/781) ([37c8124](https://github.com/folke/noice.nvim/commit/37c8124ee8aac0614a7221c335a97db9ed5e40f3))
## [2.0.1](https://github.com/folke/noice.nvim/compare/v2.0.0...v2.0.1) (2024-03-26)
### Bug Fixes
* **input:** don't filter ^M and &lt;cr&gt; for the command line ([#734](https://github.com/folke/noice.nvim/issues/734)) ([d29b26c](https://github.com/folke/noice.nvim/commit/d29b26c329558ee4bb2e7f3cc25078929ef89b2f))
* lsp message view not effective ([#747](https://github.com/folke/noice.nvim/issues/747)) ([2640d39](https://github.com/folke/noice.nvim/commit/2640d3975d47156c412db6e974a1b1280ff46aab))
* **popup:** don't make the window column go below negative 1 ([#737](https://github.com/folke/noice.nvim/issues/737)) ([01b2b53](https://github.com/folke/noice.nvim/commit/01b2b5316eb6986cc763e7b4f0f0e0f60d2a344b))
### Performance Improvements
* ignore events when setting buf options during render. Fixes [#694](https://github.com/folke/noice.nvim/issues/694) ([bf67d70](https://github.com/folke/noice.nvim/commit/bf67d70bd7265d075191e7812d8eb42b9791f737))
* **lsp:** update lsp progress messages at most every 100ms ([9a9756d](https://github.com/folke/noice.nvim/commit/9a9756d6999abc016c087b9d3fcea3c9de99be98))
## [2.0.0](https://github.com/folke/noice.nvim/compare/v1.16.3...v2.0.0) (2023-10-25)
### ⚠ BREAKING CHANGES
* command redirection! Redirect messages generated by a command or function to a different view
* lsp hover/signatureHelp and message are now enabled by default. Disable in the config if you don't want this.
### Features
* :Noice now has subcommands, full commands and a lua api require("noice").cmd("last") ([88767a6](https://github.com/folke/noice.nvim/commit/88767a64c6b6665b8eca09271d6e0a7b9f53ccaa))
* add `circleFull` spinner ([#495](https://github.com/folke/noice.nvim/issues/495)) ([5427398](https://github.com/folke/noice.nvim/commit/54273980749ceb4396501300bf4c86f3bb818f75))
* add scrollbar to all nui views ([0630e94](https://github.com/folke/noice.nvim/commit/0630e94375c53ecdb64b4d53e1013a0897625d25))
* added `Filter.cond` to conditionally use a route ([29a2e05](https://github.com/folke/noice.nvim/commit/29a2e052d2653443716a8eece89300e9b36b5f2a))
* added `Noice dismiss` to hide all visible messages. Fixes [#417](https://github.com/folke/noice.nvim/issues/417) ([a32bc89](https://github.com/folke/noice.nvim/commit/a32bc892aadb26668fd0161962ae4eccb1bf5854))
* added cmdline formatter help ([7f0ecd8](https://github.com/folke/noice.nvim/commit/7f0ecd88ee3e3ac3d11a8646f1e9a209b4579715))
* added config options for input() cmdline. Fixes [#115](https://github.com/folke/noice.nvim/issues/115) ([b645e30](https://github.com/folke/noice.nvim/commit/b645e30dc228e75b64b28374aa72b6c8ebbbe977))
* added config.lsp.hover. Undocumented for now and disabled by default. ([c2f37ed](https://github.com/folke/noice.nvim/commit/c2f37ed040238573e7a2542e14a814b2c19164bd))
* added configurable commands ([e9ccf78](https://github.com/folke/noice.nvim/commit/e9ccf782c0b4e9f7df47cc30972185e048e0ea67))
* added custom handlers for markdown links and help tags ([37e7203](https://github.com/folke/noice.nvim/commit/37e72034cf336bb4748e15585ab805b3ec2752f4))
* added deactivate ([bf216e0](https://github.com/folke/noice.nvim/commit/bf216e017979f8be712b1ada62736a58e75b0fe3))
* added different closing and focusing ways to lsp hover ([0402182](https://github.com/folke/noice.nvim/commit/04021821b74a8588e20764beb222b71a5fce037c))
* added health check for GUI's with multigrid enabled ([0fdedab](https://github.com/folke/noice.nvim/commit/0fdedab33ac84b133b9c93c0e4d12a51f18d6981))
* added health checks to see if other plugins have overwritten Noice handlers ([906c6c8](https://github.com/folke/noice.nvim/commit/906c6c827dd36176baf9521a0653ca9f29410683))
* added markdown to treesitter health checks ([7c955cc](https://github.com/folke/noice.nvim/commit/7c955cced94bd4eb78d689eddd6528012dcc4bdb))
* added methods that can be mapped for scrolling the hover/signatureHelp windows ([a6ad24e](https://github.com/folke/noice.nvim/commit/a6ad24ed8b9d0b6b0dcde311865547a48cea21da))
* added notify.timeout. Set it on a view to change the deafault timeout ([feaf5c4](https://github.com/folke/noice.nvim/commit/feaf5c43c22a14f5405e7dabd0112974589a6998))
* added option to disable smart_move ([8600a03](https://github.com/folke/noice.nvim/commit/8600a03bf88a2bd817d71829344a8100deaed8b1))
* added option to disable the health checker ([a2f6e4c](https://github.com/folke/noice.nvim/commit/a2f6e4cd831c1c72e92d0f6dc778f68635e82855))
* added optional col to start highlighting with treesitter/syntax ([ccb7e56](https://github.com/folke/noice.nvim/commit/ccb7e561248aab93f67b2b6ab31d75e621b4211c))
* added overrides for default lsp and cmp formatters :) Enable with config.lsp.override ([6d9fb12](https://github.com/folke/noice.nvim/commit/6d9fb1242cd782edfb77ac06b12d72ed24e3888f))
* added preset to set hover docs and signature help border ([e34db67](https://github.com/folke/noice.nvim/commit/e34db671490ebfdc4268fb83602e1a7e06b91e69))
* added routing of LSP window/showMessage ([75730f4](https://github.com/folke/noice.nvim/commit/75730f4b660af682d376c218a16822c782451241))
* added suport for Luasnip jump targets to signature help ([c09d197](https://github.com/folke/noice.nvim/commit/c09d1973e7542ca0201464e053c857e15a2b9f22))
* added support for &lt;pre&gt;{lang} code blocks used in the Neovim codebase ([de48a45](https://github.com/folke/noice.nvim/commit/de48a4528aad5c7b50cf4b4ec1b419762a95934d))
* added support for filtering messages generated by a command on the commandline ([b3ee385](https://github.com/folke/noice.nvim/commit/b3ee38584609d6bf63aceca04fe5463d5ed080ed))
* added support for negative positions. Fixes [#179](https://github.com/folke/noice.nvim/issues/179) ([8d7a63c](https://github.com/folke/noice.nvim/commit/8d7a63c8f003d92c1484045ebe667a62d7625594))
* added title option to cmdline format ([42d771a](https://github.com/folke/noice.nvim/commit/42d771a32ddeb20866907d88d2e95a4730c34807))
* always use the global notify instance and set animate=false when blocking ([edc8df6](https://github.com/folke/noice.nvim/commit/edc8df60a16bb0f2709110b29fef98c6cc616d42))
* automatically move out of the way of existing floating windows. Fixes [#117](https://github.com/folke/noice.nvim/issues/117) ([a810700](https://github.com/folke/noice.nvim/commit/a810700bb8189fe7fb699f388fda6fdb9869fac8))
* better defaults for history and last ([cdb25b8](https://github.com/folke/noice.nvim/commit/cdb25b8a398dae6d72fea3e5721a7c367b2a19f1))
* **cmdline:** added cmdline support for `:lua=` and `:=` ([acfa513](https://github.com/folke/noice.nvim/commit/acfa5133da31a35ec24fca0757ad1c85edc4c585))
* **cmdline:** added support for FloatTitle and added proper default ([79c7059](https://github.com/folke/noice.nvim/commit/79c70594aefb4efecbce4528174fdd0227baaf3e))
* **cmp:** incude item.detail when it's not part of item.documentation ([c2a745a](https://github.com/folke/noice.nvim/commit/c2a745a26ae562f1faecbf6177ac53377d2658d5))
* command redirection! Redirect messages generated by a command or function to a different view ([a8b3117](https://github.com/folke/noice.nvim/commit/a8b3117f22e49dcb44853ca9df84feaa663077bb))
* conceal markdown escape characters. Fixes [#170](https://github.com/folke/noice.nvim/issues/170) ([6824794](https://github.com/folke/noice.nvim/commit/6824794d01fb27e428f642440517fe066bf47105))
* **config:** add calculator to cmdline formats ([#240](https://github.com/folke/noice.nvim/issues/240)) ([fa21685](https://github.com/folke/noice.nvim/commit/fa21685e23cbb72bb573eecf48dd3644bc1513ba))
* don't hide mini messages when blocking [#112](https://github.com/folke/noice.nvim/issues/112) ([6b9144d](https://github.com/folke/noice.nvim/commit/6b9144d3f9873e1651d783c6b862177d5647bd20))
* don't process events that are disabled in config & disable ext that are enabled in the attached GUI ([abb5721](https://github.com/folke/noice.nvim/commit/abb5721129b3f54bb6f38713f45029c61c0e5f64))
* dont show lsp doc when empty ([a61a07f](https://github.com/folke/noice.nvim/commit/a61a07f51398defd6a922e457eabf272ff15ccf3))
* **format:** allow `config.format.level.icons` to be false. Fixes [#274](https://github.com/folke/noice.nvim/issues/274) ([aa68eb6](https://github.com/folke/noice.nvim/commit/aa68eb6f83c48df41bab8ae36623e5af3f224c66))
* **health:** added check for vim-sleuth ([2d11c5b](https://github.com/folke/noice.nvim/commit/2d11c5bfe77c7b93ab5ad7e156e239b8279cc5ee))
* **health:** added markdown_inline to treesitter checks ([d525285](https://github.com/folke/noice.nvim/commit/d5252857db3b295d1b34150531f58f3cc6251192))
* **health:** show what other plugin is overriding a Noice handler ([84dcdf3](https://github.com/folke/noice.nvim/commit/84dcdf3bf453ae1bc4d05a2110e3202e1eb4eafa))
* improved smart move. Added filetype exclusions and only move out when overlap &gt; 30%. Fixes [#130](https://github.com/folke/noice.nvim/issues/130) ([c63267d](https://github.com/folke/noice.nvim/commit/c63267d3649d3b9c5992a4db96a954c8f581cd73))
* keep track of where messages are displayed ([13097dc](https://github.com/folke/noice.nvim/commit/13097dccc8228f07b2aa9191081cc8fa3944af98))
* lsp hover/signatureHelp and message are now enabled by default. Disable in the config if you don't want this. ([9130fd1](https://github.com/folke/noice.nvim/commit/9130fd17a19f95b55ca9362919fef31ab54ef841))
* lsp signature help ([2a19f32](https://github.com/folke/noice.nvim/commit/2a19f32b3d22b0b31812fab76e576e04f06cd2e9))
* **lsp:** added config.lsp.hover.silent. Fixes [#412](https://github.com/folke/noice.nvim/issues/412) ([e2a53cf](https://github.com/folke/noice.nvim/commit/e2a53cf946d88d87cd0123711afce5ddad047b7b))
* **lsp:** added custom formatters for lsp hover and made :help work in hover text ([63c70a9](https://github.com/folke/noice.nvim/commit/63c70a90b033dbd17016bf7e254f7427596dbc3b))
* **lsp:** fallback to buffer filetype for code blocks without lang. Fixes [#378](https://github.com/folke/noice.nvim/issues/378) ([cab2c80](https://github.com/folke/noice.nvim/commit/cab2c80497388735c9795f496a36e76bc5c7c4bf))
* manage message state without `msg_clear` ([#209](https://github.com/folke/noice.nvim/issues/209)) ([fb0e3b0](https://github.com/folke/noice.nvim/commit/fb0e3b0bb3028050b61a45e53809af03b15a24bc))
* markdown formatter ([6ea06c9](https://github.com/folke/noice.nvim/commit/6ea06c926fc3bc495e07ab5a1b51bcb19628d771))
* new markdown renderer without empty lines around code blocks. Fixes [#158](https://github.com/folke/noice.nvim/issues/158) ([111fe5e](https://github.com/folke/noice.nvim/commit/111fe5eaa9d1c5f77ccd427c0139676917c9c162))
* noice presets ([c43d82b](https://github.com/folke/noice.nvim/commit/c43d82b3344e73e85c1af8c994a3dd7a3b4849b5))
* **notify:** added plain renderer ([345fccc](https://github.com/folke/noice.nvim/commit/345fccc9e17bc4fd74361fb0a953d0e87195a657))
* open :messages and history in a split with enter=true ([7beef93](https://github.com/folke/noice.nvim/commit/7beef93fabceefb028a42998150ccd3b5c9b4ea5))
* **popupmenu:** allow different views for regular/cmdline popupmenu ([af706c4](https://github.com/folke/noice.nvim/commit/af706c4b443cf1c416ef7288ec3434f3f1ab6cf1))
* preset for inc_rename ([91c79a0](https://github.com/folke/noice.nvim/commit/91c79a0c51ebb070163668c1156d5cc20c02af3a))
* properly calculate buf height taking wrap into account ([add20ee](https://github.com/folke/noice.nvim/commit/add20ee9ffabc80b34e1b80a53ef68df9969d02e))
* properly calculate layout in case of max_width and wrap ([83c837e](https://github.com/folke/noice.nvim/commit/83c837e1cf39820f275d347e5d65c11e8398e102))
* removed default `&lt;esc&gt;` handler from `split` view ([fb10fa2](https://github.com/folke/noice.nvim/commit/fb10fa2ef7d78953ffb8b9c5fd0f42c389cbedaf))
* replace html entities. Fixes [#168](https://github.com/folke/noice.nvim/issues/168) ([7e3e958](https://github.com/folke/noice.nvim/commit/7e3e9584601a216069f92ab3a33dd2a1af30283f))
* set default view for hover to hover ([dafcddd](https://github.com/folke/noice.nvim/commit/dafcddd583e34444ba4f893d3bece1c12f3a739a))
* set default zindex for hover to 45. Lower than cmp and notify ([0bf5f9e](https://github.com/folke/noice.nvim/commit/0bf5f9e447c5ede0fe7b5c42583e206ed7073b48))
* show warning when running with TUI rework ([cf2231b](https://github.com/folke/noice.nvim/commit/cf2231bfb691b3b58d2685f48da11596cec1cfa3))
* **signature:** added signature param docs. Fixes [#421](https://github.com/folke/noice.nvim/issues/421) ([e76ae13](https://github.com/folke/noice.nvim/commit/e76ae13dd272dc23d0154b93172d445aeabad8f1))
* smart positioning of the hover window. Make sure to update Nui as well to use this. ([5bd6e30](https://github.com/folke/noice.nvim/commit/5bd6e308a10324e0c3d8a320aae234d9ce0a9610))
* Support hide scrollbar for view ([#603](https://github.com/folke/noice.nvim/issues/603)) ([f700175](https://github.com/folke/noice.nvim/commit/f700175b91948e4f71cf73872cea364247cf2dbd))
* **ui:** added hybrid messages functionality, but not needed for now ([addc0a2](https://github.com/folke/noice.nvim/commit/addc0a2521ce666a1f546f9a04574a63a858c6a5))
### Bug Fixes
* accept preset as a table ([#582](https://github.com/folke/noice.nvim/issues/582)) ([53d613c](https://github.com/folke/noice.nvim/commit/53d613cd0031e83987964947b1bad8b5047c9d0e))
* activate vim-sleuth hack. See [#139](https://github.com/folke/noice.nvim/issues/139) ([e99990f](https://github.com/folke/noice.nvim/commit/e99990ffdbf6b65275dd6f51089352d45e7197bc))
* added debugging info for [#220](https://github.com/folke/noice.nvim/issues/220) ([6de461b](https://github.com/folke/noice.nvim/commit/6de461b8475c4f56bc5b8ed93c819c8b15f3e067))
* Allow mapping &lt;esc&gt; ([#329](https://github.com/folke/noice.nvim/issues/329)) ([b7e9054](https://github.com/folke/noice.nvim/commit/b7e9054b02b5958db8bb5ad7675e92bfb5a8e903))
* always show cursorline and reset to line for popupmenu. Fixes [#239](https://github.com/folke/noice.nvim/issues/239) ([c7f666c](https://github.com/folke/noice.nvim/commit/c7f666cff4160a9f9f31e5de112429d6db7956d2))
* anchor popupmenu to SW for bottom cmdline. Fixes [#134](https://github.com/folke/noice.nvim/issues/134) ([9102aef](https://github.com/folke/noice.nvim/commit/9102aef70431cd9959add7bc50d507c18f0089de))
* better handling of splitkeep for nui splits ([84d1904](https://github.com/folke/noice.nvim/commit/84d1904c64759a82149e003bd83442572d588769))
* better way of showing/hiding cursor ([10a97a0](https://github.com/folke/noice.nvim/commit/10a97a0fadfc8929c8aecd9b69dda67b7b2f7a33))
* **block:** better deal with carriage return characters (take 2) ([ee24b36](https://github.com/folke/noice.nvim/commit/ee24b36743b18e53bdc6b49bbfa426fc18ea337a))
* calculate negative offsets based off minmax. Fixes [#179](https://github.com/folke/noice.nvim/issues/179) ([cf2912d](https://github.com/folke/noice.nvim/commit/cf2912d08eb7442c52347d3427a61557e0783f02))
* call update_screen during cmdpreview to fix cursor movement. Fixes [#131](https://github.com/folke/noice.nvim/issues/131) ([23da4ed](https://github.com/folke/noice.nvim/commit/23da4eddea9b78ab0846c5b2a1cda5a10f49b4a4))
* check for nil on zindex. Fixes [#129](https://github.com/folke/noice.nvim/issues/129) ([07465b3](https://github.com/folke/noice.nvim/commit/07465b3486508a233701e32c948c589985d9cd48))
* check if loader returned a function before loading ([66946c7](https://github.com/folke/noice.nvim/commit/66946c72f0a36f37e480b5eae97aac3cdcd5961d))
* check if plugin is noice ([7b62ccf](https://github.com/folke/noice.nvim/commit/7b62ccfc236e51e78e5b2fc7d3068eacd65e4590))
* check item.detail is type of table ([#595](https://github.com/folke/noice.nvim/issues/595)) ([3670766](https://github.com/folke/noice.nvim/commit/3670766b10fded979fcb00606801edc585a65f2a))
* cleanup progress messages from lsp clients that died. Fixes [#175](https://github.com/folke/noice.nvim/issues/175) ([e084d3b](https://github.com/folke/noice.nvim/commit/e084d3bed43ad1372b6a638218e19c83330af403))
* **cmdline:** better and safer way to trigger redraw during cmdpreview ([02ed6d4](https://github.com/folke/noice.nvim/commit/02ed6d4c0c1e4d21fc4e79bbe54961023ee9badb))
* **cmdline:** dont use normal commands so ModeChanged will trigger correctly. Fixes [#390](https://github.com/folke/noice.nvim/issues/390) ([fa7b6a1](https://github.com/folke/noice.nvim/commit/fa7b6a18c5cdc23961038bc56b93495efcd0f5c7))
* **cmdline:** fixed a byte offset issue with the new virtual inline text ([a13a88f](https://github.com/folke/noice.nvim/commit/a13a88fb2016b6cfea8f56238566b345f537e47a))
* **cmdline:** make sure cursor is always visible ([2f0a427](https://github.com/folke/noice.nvim/commit/2f0a42701b4aa65b55fff8f32878d9adc7e7ac77))
* cmp popupmenu position ([#183](https://github.com/folke/noice.nvim/issues/183)) ([8db0420](https://github.com/folke/noice.nvim/commit/8db0420c55ea813d721f3c08c6d61ad68fb366e5))
* config.cmdline.opts now overrides any option from the cmdline formats ([2851fc2](https://github.com/folke/noice.nvim/commit/2851fc23a15a3651402ce6adfde92d0d35990e32))
* **config:** allow overriding options set by presets ([5a1bf17](https://github.com/folke/noice.nvim/commit/5a1bf1707f592fefff4cb3da903b17369e088cc1))
* **config:** correctly set the presets ([e5cb84f](https://github.com/folke/noice.nvim/commit/e5cb84f1ed524f850fa92e3a256e830ed07fadee))
* **config:** properly deal with preset routes. Fixes [#517](https://github.com/folke/noice.nvim/issues/517) ([fea7f1c](https://github.com/folke/noice.nvim/commit/fea7f1cf15b04ec9b8dd071aa3510f693156ce59))
* **confirm:** keep newlines in confirm message. Fixes [#422](https://github.com/folke/noice.nvim/issues/422) ([051111f](https://github.com/folke/noice.nvim/commit/051111f98d7128c833eaa32423426829981b2aa3))
* correctly apply padding based on four numbers ([c9c1fbd](https://github.com/folke/noice.nvim/commit/c9c1fbd605388badcfa62c0b7f58d184f19e1484))
* correctly calculate popupmenu width based on border and padding. Fixes [#122](https://github.com/folke/noice.nvim/issues/122) ([b152bf5](https://github.com/folke/noice.nvim/commit/b152bf5281a0e974b53ce1011b0938134573760e))
* **debug:** calculate stacktrace outisde of vim schedule to make it useful ([a5de1ca](https://github.com/folke/noice.nvim/commit/a5de1ca0eaecd21fd33a0a191d1a0b9dd97cb54a))
* **debug:** only concat debug info that is a string ([78ec5c6](https://github.com/folke/noice.nvim/commit/78ec5c6eefb9b61056a8545ded33b99f7a9a9f72))
* disable all signature help when signature is disabled. Fixes [#104](https://github.com/folke/noice.nvim/issues/104) ([58f52e3](https://github.com/folke/noice.nvim/commit/58f52e345d8a2fe4d9b56829986a6c5b3661fdf6))
* disable vim-sleuth for Noice buffers ([d148e1a](https://github.com/folke/noice.nvim/commit/d148e1ab5844eb0b9efa09f77772feb20cd4b660))
* disable winbar on Noice windows ([1b37f74](https://github.com/folke/noice.nvim/commit/1b37f74423bb6b2e8bf4ad3ec7f1545960f142a7))
* do render after mount Fixes [#150](https://github.com/folke/noice.nvim/issues/150) ([7e2e449](https://github.com/folke/noice.nvim/commit/7e2e4491afb02422b26c241f9fb7f8960b51fe05))
* don't try smart move between noice windows ([4a46ba3](https://github.com/folke/noice.nvim/commit/4a46ba33d8b2309d2cbcfb725e958d2e0bdc77d6))
* dont check cmdpreview on windows. Fixes [#229](https://github.com/folke/noice.nvim/issues/229) ([b10bbbb](https://github.com/folke/noice.nvim/commit/b10bbbb91215d0324d7d7f4cd0d8de3d1332648f))
* dont check lazyredraw when Noice is running ([a202a22](https://github.com/folke/noice.nvim/commit/a202a2226e91f62706ebff63a5b0d4fdf2d2e370))
* dont error if cmp not loaded when overriding ([4bae487](https://github.com/folke/noice.nvim/commit/4bae48798424d300e204cce2eb73b087854472d5))
* dont error in checkhealth if nvim-treesitter is not installed ([044767a](https://github.com/folke/noice.nvim/commit/044767a01d38208c32d97b0214cce66c41e8f7c8))
* dont hide cursor on input. Fixes [#585](https://github.com/folke/noice.nvim/issues/585). Fixes [#566](https://github.com/folke/noice.nvim/issues/566) ([819a5bf](https://github.com/folke/noice.nvim/commit/819a5bf62fa31c893c9d0c6da17ef93a810a1e8c))
* dont make scrollbars focusable ([e226401](https://github.com/folke/noice.nvim/commit/e226401ba577808528e76861edc6a04c36ccbce1))
* dont render views when not running. Fixes [#200](https://github.com/folke/noice.nvim/issues/200) ([9dc2508](https://github.com/folke/noice.nvim/commit/9dc250851eff005fe94f37c12c05bfddf2cedca8))
* dont restore cursor when exiting. Fixes [#230](https://github.com/folke/noice.nvim/issues/230) ([0115097](https://github.com/folke/noice.nvim/commit/0115097e5008bae01e512f30cea66e2d7182f19b))
* dont show if window is closed while showing. Fixes [#208](https://github.com/folke/noice.nvim/issues/208) ([a8402e8](https://github.com/folke/noice.nvim/commit/a8402e84c1923885896ee83eefb0f492a9f9b620))
* export scroll in noice.lsp. See [#161](https://github.com/folke/noice.nvim/issues/161) ([0fe5a1e](https://github.com/folke/noice.nvim/commit/0fe5a1ea053ee086327eab386594c5f2e7f15e77))
* fallback to syntax if treesitter parser is not available ([882e58c](https://github.com/folke/noice.nvim/commit/882e58c2dccce0adba30a7d483af15fbe40dd70b))
* fix lsp showMessage. Fixes [#220](https://github.com/folke/noice.nvim/issues/220) ([5ff75a5](https://github.com/folke/noice.nvim/commit/5ff75a5196d3ebe3148ca0c8c20ac750664ddfb6))
* fixed flickering during substitute & cmdpreview ([1702772](https://github.com/folke/noice.nvim/commit/170277257a976e2f669351676974a7bc51e88b5a))
* force cmdline redraw only when pum is not visible. Fixes [#188](https://github.com/folke/noice.nvim/issues/188). Closes [#189](https://github.com/folke/noice.nvim/issues/189) ([412594c](https://github.com/folke/noice.nvim/commit/412594c23090b107aeb839b49e23fc508a4e3b8b))
* guicursor behaves weird, when resetting too fast. Delay by 100ms. Fixes [#114](https://github.com/folke/noice.nvim/issues/114) ([3710528](https://github.com/folke/noice.nvim/commit/37105289ac5a4fd7b977a9c307c1a95f3e38acf6))
* **hacks:** make sure the cursor is properly updated before getchar ([0cca940](https://github.com/folke/noice.nvim/commit/0cca940561d4b723cb52ba9e4ec239fceb36f146))
* handle vim.notify nil messages for nvim-notify. Fixes [#109](https://github.com/folke/noice.nvim/issues/109) ([83b60f2](https://github.com/folke/noice.nvim/commit/83b60f2cced7428419fa41cfc30cda53082739ee))
* **health:** added info on how to disable overwritten by other plugins ([a4c3d48](https://github.com/folke/noice.nvim/commit/a4c3d484dcf925de7d27110f198c45d06cf062ac))
* **health:** allow running in GUIs with multigrid ([71a7591](https://github.com/folke/noice.nvim/commit/71a75913d680af9f1f26a2886518e390cd8f73e1))
* **health:** correctly check if lsp.message is enabled ([#381](https://github.com/folke/noice.nvim/issues/381)) ([1ff6b10](https://github.com/folke/noice.nvim/commit/1ff6b10471590331cc1585ad64f084f19cd4bcb7))
* **health:** dont use nvim-treesitter to check if a lang exists ([585d24e](https://github.com/folke/noice.nvim/commit/585d24ec6e3fb4288414f864cfe2de7d025e8216))
* **health:** fix deprecated health checks healthcheck ([#438](https://github.com/folke/noice.nvim/issues/438)) ([0f12ed3](https://github.com/folke/noice.nvim/commit/0f12ed399e79aa49f283aa954468b92be65e03ed))
* **health:** only check for lazyredraw during startup ([46a40bd](https://github.com/folke/noice.nvim/commit/46a40bde471a15d4c5bdd959327bd35aafe0030f))
* hide cursor with vim.schedule to keep Neovide from crashing ([c4ba29d](https://github.com/folke/noice.nvim/commit/c4ba29d90e15eef3603f5acb7c8526fad5092969))
* hide scroll if it was shown before. Fixes [#216](https://github.com/folke/noice.nvim/issues/216) ([f5ac589](https://github.com/folke/noice.nvim/commit/f5ac589abf503a2033e1c5b4fad40f326ccab2b4))
* **icons:** removed all obsolete icons thanks to nerdfix ([cf6a194](https://github.com/folke/noice.nvim/commit/cf6a194f9280cda1fdcc36d271fccd4a24082df3))
* improved markdown empty line handling ([8cd47e0](https://github.com/folke/noice.nvim/commit/8cd47e0080cb095f00765ef3a73e3b4e6c8fe627))
* incorrect active param for some lsp. Fixes [#162](https://github.com/folke/noice.nvim/issues/162) ([c7f1fca](https://github.com/folke/noice.nvim/commit/c7f1fca4e1a940f6964d732dfa85ac7ff0942c2d))
* keep correct case for prefix match in popupmenu ([4757fd9](https://github.com/folke/noice.nvim/commit/4757fd93160815adec33e17b8c04c762c2b12ff3))
* keep signature help open as long as the last non-whitespace character is a trigger character ([5f4544f](https://github.com/folke/noice.nvim/commit/5f4544f4e9d6481e6187d665654bcaaa19573a2e))
* let all the cmdline hl groups fallback to the default one ([040fca5](https://github.com/folke/noice.nvim/commit/040fca50b477a2eda9033860f46a5661a00ed340))
* load defaults as function to prevent require loops on older neovim versions ([48ffb9b](https://github.com/folke/noice.nvim/commit/48ffb9bfd5db6208a015b09e93472b47eabb2f08))
* **lsp:** add non-nil guard to setup function ([#454](https://github.com/folke/noice.nvim/issues/454)) ([4524216](https://github.com/folke/noice.nvim/commit/4524216d7484c7b183ca1f654d8e66dff28a5680))
* **lsp:** allow whitespace info string in markdown ([#535](https://github.com/folke/noice.nvim/issues/535)) ([1343acc](https://github.com/folke/noice.nvim/commit/1343acc592c3f138d7ffb88f9b8be1c4969b30d3))
* **lsp:** dont show lsp progress for lsp clients that died. Fixes [#207](https://github.com/folke/noice.nvim/issues/207) ([6afc974](https://github.com/folke/noice.nvim/commit/6afc9741808fb03c938afcdc5a355243ba15066b))
* **lsp:** return true if scrolling succeeded ([5750c09](https://github.com/folke/noice.nvim/commit/5750c097e7b9563ee07b83d7933f7954945f9397))
* make ffi behave with plugin reloaders ([7e78236](https://github.com/folke/noice.nvim/commit/7e782362a85132e32803bfbce77a99032abc54fd))
* make help match :help as well. Fixes [#135](https://github.com/folke/noice.nvim/issues/135) ([dc33efa](https://github.com/folke/noice.nvim/commit/dc33efa6e840eac71055d1066e54095e5c196178))
* make nui views nomodifiable ([f674f03](https://github.com/folke/noice.nvim/commit/f674f030acad42f5afd3059198fc7ad1e5e2b598))
* make search_count work with nohlsearch. Fixes [#217](https://github.com/folke/noice.nvim/issues/217) ([80ec5b8](https://github.com/folke/noice.nvim/commit/80ec5b8c5838e8583081a0bdeec5e7151c4f46d3))
* make split views behave with splitkeep and restore cursor position after re-render ([0b1fb33](https://github.com/folke/noice.nvim/commit/0b1fb33327de070f702912362578f7734ba6b5d6))
* make sure we always have an encoding for getting signatureHelp ([ba36767](https://github.com/folke/noice.nvim/commit/ba367673b3728ba2dc0672775421d13a94357dca))
* **markdown:** better check to see if a ts parser is available. Fixes [#397](https://github.com/folke/noice.nvim/issues/397) ([d60bee1](https://github.com/folke/noice.nvim/commit/d60bee1b85af1882768af80385bc2500d495feba))
* **markdown:** code block, rule, codeblock should only render one rule ([feb8e4d](https://github.com/folke/noice.nvim/commit/feb8e4d19ea6ef2a99248d608bda873e3a7d2707))
* **markdown:** conceal escaping forward slashes. Fixes [#455](https://github.com/folke/noice.nvim/issues/455) ([a7246aa](https://github.com/folke/noice.nvim/commit/a7246aa99fde34fb9d5e13c62c83ac7226514d67))
* **markdown:** replace `&lt;code&gt;`. Fixes [#424](https://github.com/folke/noice.nvim/issues/424) ([38fb652](https://github.com/folke/noice.nvim/commit/38fb652d0a95780d20a551a6ec44b01226476c99))
* **markdown:** replace additional html entities ([#448](https://github.com/folke/noice.nvim/issues/448)) ([d31fe94](https://github.com/folke/noice.nvim/commit/d31fe940e0866686718822aaac45527412c45134)), closes [#447](https://github.com/folke/noice.nvim/issues/447)
* **markdown:** revert ([d767be9](https://github.com/folke/noice.nvim/commit/d767be960e8660b19595ccff2dad6abd7aae2d4a))
* **markdown:** strip "\r" chars ([4d2801b](https://github.com/folke/noice.nvim/commit/4d2801babc4026229c58f0c77a20ff5b7b4c0d07))
* mini focusable=false by default ([04794f6](https://github.com/folke/noice.nvim/commit/04794f6d1ea2be83bcc5617c78aa7b7fab46fcdd))
* noautocmd for ze in cmdline. Fixes [#206](https://github.com/folke/noice.nvim/issues/206) ([8d088aa](https://github.com/folke/noice.nvim/commit/8d088aa6083d0332ced778aab0e1fa2f2533902c))
* **notify_send:** properly close file descriptors from spwaning notifysend ([f5132fa](https://github.com/folke/noice.nvim/commit/f5132fa6eb71e96d9f0cd7148b186b324b142d15))
* **notify:** better way of finding rendering offset. Fixes [#181](https://github.com/folke/noice.nvim/issues/181) ([dbadd10](https://github.com/folke/noice.nvim/commit/dbadd106e42b6344243405614ae3583fb5b17efe))
* **notify:** nvim-notify replace can be an id or a record ([a9cc87b](https://github.com/folke/noice.nvim/commit/a9cc87b14e18bc3717746b45d79157c0adb43a4d))
* **notify:** take col offsets into account for nvim-notify renderers. Fixes [#375](https://github.com/folke/noice.nvim/issues/375) ([20596d9](https://github.com/folke/noice.nvim/commit/20596d96551605f7462f5722198b188e4047b605))
* nui windows must have nowrap by default. See [#196](https://github.com/folke/noice.nvim/issues/196) ([48003c5](https://github.com/folke/noice.nvim/commit/48003c550a03bd15169b35c5b3dc066f0e8f541d))
* nui.menu._tree -&gt; nui.menu.tree ([ab151eb](https://github.com/folke/noice.nvim/commit/ab151eb85b15d13176d1b2a4c37113530637a058))
* **nui:** dont trigger autocmds when doing zt ([d176765](https://github.com/folke/noice.nvim/commit/d176765ceabae9a12bf09a5c785d3dcb3859e1b6))
* **nui:** make sure nui recreates buffer and window when needed ([3e6dfd8](https://github.com/folke/noice.nvim/commit/3e6dfd8bb00d98399704a020ab7892234ce80fdb))
* **nui:** mount if buffer is no longer valid ([71d7b5c](https://github.com/folke/noice.nvim/commit/71d7b5cf8f24b9bdc425934c36cfda784fcd10f2))
* **nui:** nui broke noice. added a temporary work-around till the problem is solved ([4db3c8f](https://github.com/folke/noice.nvim/commit/4db3c8f14302273b842f73a8facf1999169c1e41))
* **nui:** remove border text when style is `nil`, `"none"`, or `"shadow"` ([d85a4d0](https://github.com/folke/noice.nvim/commit/d85a4d01774b5649dbcda8526a26f201dff5ade4))
* **nui:** remove padding when border is `shadow` ([1515007](https://github.com/folke/noice.nvim/commit/151500759722c12fb6a3931c5243d68f01af007a))
* **nui:** removed work-around for padding and border style shadow ([4f34d33](https://github.com/folke/noice.nvim/commit/4f34d33fc3dc0d6f4da9b4b8c63b9714fd4eea79))
* **nui:** reset close events on remount ([1558c48](https://github.com/folke/noice.nvim/commit/1558c48257e6d1c99edd15fd12fbd1dbc4fa22c1))
* **nui:** set mounted=false if buffer is no longer valid ([3353a7a](https://github.com/folke/noice.nvim/commit/3353a7ab4bae6c22f61fd646c10e336b4582f0ea))
* **nui:** umount on hide, to fix rendering issue while blocking ([119682b](https://github.com/folke/noice.nvim/commit/119682b44f6b5303e141fdbdbdd25c261a89d306))
* one-off error for highlighting with treesitter/syntax ([1602ce8](https://github.com/folke/noice.nvim/commit/1602ce897e66672c43da95c9175d103f1a69e2aa))
* only relayout with real width and height ([2462809](https://github.com/folke/noice.nvim/commit/24628097307e5e8453e691192bed45ec6da1bebd))
* only set noice buffer names if debug. Fixes [#197](https://github.com/folke/noice.nvim/issues/197) ([ea1079d](https://github.com/folke/noice.nvim/commit/ea1079deb6ffdc326b3da0dc2e84371b330dfdae))
* only show/hide scrollbar when needed ([d01cd10](https://github.com/folke/noice.nvim/commit/d01cd1052a865588bbebbd4015ad778b81312fe9))
* **overrides:** noice was leaking extmarks for stylize_markdown ([9b148a1](https://github.com/folke/noice.nvim/commit/9b148a141df7fefc66705e2e7219d11536b99288))
* popupmenu default relative=editor ([7ef41aa](https://github.com/folke/noice.nvim/commit/7ef41aa92e2ca0bdfe972bc6a68350c8be06044e))
* **popupmenu:** always show the regular (non-cmdline) popupmenu near the cursor ([e3936cc](https://github.com/folke/noice.nvim/commit/e3936ccbbd32f6ce4a4f55a77ec556b116c0b928))
* **popupmenu:** only use popupmenu hl group for known item kinds. Fixes [#453](https://github.com/folke/noice.nvim/issues/453) ([0b86a7b](https://github.com/folke/noice.nvim/commit/0b86a7bfbf84927909ed81e9616a5e24602fe6fb))
* **popupmenu:** properly close pmenu when cmdline window is open. Fixes [#542](https://github.com/folke/noice.nvim/issues/542) ([d19e5cb](https://github.com/folke/noice.nvim/commit/d19e5cb58e33c6d74e9005d1965d6e8ebd6b057b))
* **popupmenu:** replace any newlines by space. Fixes [#265](https://github.com/folke/noice.nvim/issues/265) ([5199089](https://github.com/folke/noice.nvim/commit/51990892e1dd5ee1a1444b1cf3ccf0aca377e0c4))
* potential endless loop in markdown key handlers ([078cbd9](https://github.com/folke/noice.nvim/commit/078cbd9087fd358df8de54072253c0e2ae7c89c9))
* **preset:** palette now configures cmdline_popupmenu instead of popupmenu ([294097a](https://github.com/folke/noice.nvim/commit/294097a239ec943587e5707b678142c52a9b318e))
* progress use vim.lsp.get_client_by_id ([#529](https://github.com/folke/noice.nvim/issues/529)) ([397619d](https://github.com/folke/noice.nvim/commit/397619d5351d650e9879d18c9312a5add5729815))
* properly handle carriage returns. Fixes [#190](https://github.com/folke/noice.nvim/issues/190) ([c14b064](https://github.com/folke/noice.nvim/commit/c14b0649c2356a4e86aeea63449859ee0b39ef6d))
* properly handle MarkedString[] mix. Fixes [#178](https://github.com/folke/noice.nvim/issues/178) ([14cff19](https://github.com/folke/noice.nvim/commit/14cff1957fe00ec8f6d2d0951369bbccad74c7c2))
* re-use existing views with same backend and opts ([1d1e9ed](https://github.com/folke/noice.nvim/commit/1d1e9ed9ce34895c7dfb80d44aa5b7a90863ef06))
* read conceal setting after sort ([#558](https://github.com/folke/noice.nvim/issues/558)) ([24c09cc](https://github.com/folke/noice.nvim/commit/24c09cc0263054cb3d8dedf2c54b570e655850f5))
* reduce some flickering when updating views ([f39b657](https://github.com/folke/noice.nvim/commit/f39b657bff3d224b41fbe6b21fd3ce24e6b9b4e2))
* remove old neovide compatibility warning ([#545](https://github.com/folke/noice.nvim/issues/545)) ([dfbe27c](https://github.com/folke/noice.nvim/commit/dfbe27cfafd6bcb5605dc6c6b82174c33fc9b09e))
* reopen scrollbar windows if they were deleted somehow. Fixes [#235](https://github.com/folke/noice.nvim/issues/235) ([74c7e29](https://github.com/folke/noice.nvim/commit/74c7e29aba2cc32e5a51fa1828813e08f327ef3a))
* reset preloader before trying to load the module ([08655e9](https://github.com/folke/noice.nvim/commit/08655e9f1bed638f9871d76b05928da74d1eeb68))
* reset view when update_layout fails. Fixes [#155](https://github.com/folke/noice.nvim/issues/155) ([7d08ed5](https://github.com/folke/noice.nvim/commit/7d08ed5bcc2d4a5fc540f096dd1d8fa800faed42))
* restore cursor with vim.schedule to be safe ([973659e](https://github.com/folke/noice.nvim/commit/973659e01fafa5b8e5808dce443bfddb63e5eb54))
* return lines in stylize_markdown ([f9bf77d](https://github.com/folke/noice.nvim/commit/f9bf77ddd5d6d78c37391696e05ba6e7d0c9830f))
* return message id in vim.notify, so it can be used for replace if the view supports it. Fixes [#109](https://github.com/folke/noice.nvim/issues/109) ([0b0e8cf](https://github.com/folke/noice.nvim/commit/0b0e8cf85ab5fcb891b7cfd44c0c4908c0e1ef46))
* return nil when Noice is not running for statusline ([289ce14](https://github.com/folke/noice.nvim/commit/289ce145dca5300f9a30684221b9ebe375bb4aae))
* **router:** properly disable updater when disabling Noice. Fixes [#423](https://github.com/folke/noice.nvim/issues/423) ([3bed83b](https://github.com/folke/noice.nvim/commit/3bed83b4d2e4fce03a27071c39be0d9e04313332))
* scroll cmdline to make sure cursor is always visible. Fixes [#196](https://github.com/folke/noice.nvim/issues/196) ([e023c5f](https://github.com/folke/noice.nvim/commit/e023c5f005807e8bbcab2d1f614e75a9f62edc59))
* scrollbar destructs itself, so make a copy to see if there are any remnants left ([8d80a69](https://github.com/folke/noice.nvim/commit/8d80a692d5a045a3ec995536782f2b4c2b8d901b))
* scrollbar for popups was off if there was padding on the window ([deda89a](https://github.com/folke/noice.nvim/commit/deda89a17c1e9dc1d4886b4c1d3704dfddefb1f2))
* **scrollbar:** zindex + 1 + 2 for bar and thumb ([0daa539](https://github.com/folke/noice.nvim/commit/0daa5390eb14e0c19ac64debaea638bcf5fb70aa))
* set conceallevel local. Fixes [#634](https://github.com/folke/noice.nvim/issues/634) ([c1591df](https://github.com/folke/noice.nvim/commit/c1591dfc8e2177402eff82be5c9cddc72de28c16))
* set cursor to top when opening view. Fixes [#165](https://github.com/folke/noice.nvim/issues/165) ([c20f38e](https://github.com/folke/noice.nvim/commit/c20f38e7c1b6d368af2bb599667b473879316c89))
* show unstable message after loading noice ([2613a16](https://github.com/folke/noice.nvim/commit/2613a16b5009acbf2adabb34b029b1c4c57101e3))
* showMessage error with kinds. Fixes [#222](https://github.com/folke/noice.nvim/issues/222) ([d4d653a](https://github.com/folke/noice.nvim/commit/d4d653a894d1f1fbc422db1f8351d86fdb1f16f8))
* **signature:** nil check for parameter label. Fixes [#435](https://github.com/folke/noice.nvim/issues/435) ([9d778e7](https://github.com/folke/noice.nvim/commit/9d778e7ce29c519ca0285b054e7c3b679bc9d3b9))
* **signature:** nil check on lsp signature.parameters. Fixes [#162](https://github.com/folke/noice.nvim/issues/162) ([1dc7f26](https://github.com/folke/noice.nvim/commit/1dc7f26a68efbd5f0da081d98d938c587de0ba25))
* **signature:** safer lsp signature parameters ([#449](https://github.com/folke/noice.nvim/issues/449)) ([e33c346](https://github.com/folke/noice.nvim/commit/e33c34642a7b02db3db03bfc2bec7799bbc2034e))
* **signature:** show signature in correct window. Fixes [#593](https://github.com/folke/noice.nvim/issues/593) ([2f0993e](https://github.com/folke/noice.nvim/commit/2f0993ee97f98cacde179a1f431881b2758e2138))
* **signature:** support label offsets ([9649d9f](https://github.com/folke/noice.nvim/commit/9649d9fd4d8fa8a8654e1e9c293718ae8d62e73b))
* **signature:** when loading, attach to existing lsp clients. Fixes [#342](https://github.com/folke/noice.nvim/issues/342) ([f69f1a5](https://github.com/folke/noice.nvim/commit/f69f1a577615a5a6527f133df0aa40e596bd1707))
* silence treesitter errors of invalid langs. Covered by checkhealth ([db1628f](https://github.com/folke/noice.nvim/commit/db1628fc81cf2029eea9dfe2e8e121143b6b35dc))
* **smart_move:** Dont move `cmdline` view ([#123](https://github.com/folke/noice.nvim/issues/123)) ([3da3f6d](https://github.com/folke/noice.nvim/commit/3da3f6d5be613ff7eb590c3e7f31c4e0cddf75d8))
* statusline messages no longer need active state. Fixes [#211](https://github.com/folke/noice.nvim/issues/211) ([e5092c2](https://github.com/folke/noice.nvim/commit/e5092c284a6ffd44e8d09f5efbe29395190c77f9))
* stop processing messages when Neovim is exiting. Fixes [#237](https://github.com/folke/noice.nvim/issues/237) ([8c8acf7](https://github.com/folke/noice.nvim/commit/8c8acf74c09374e48a8fa1835560c3913d57243f))
* support older Neovim versions ([4a1ec5e](https://github.com/folke/noice.nvim/commit/4a1ec5ec0b163a365d7593d93450676b9cbcbebd))
* **swap:** additionally check for updates when a swap file was found ([1165d3e](https://github.com/folke/noice.nvim/commit/1165d3e727bdd226eefffcc801d563bcb30e71c4))
* tag popupmenu border ([d2064a5](https://github.com/folke/noice.nvim/commit/d2064a50495f21f14ce52419660672f1e7c489bf))
* tag scrollbar and popupmenu ([13bed57](https://github.com/folke/noice.nvim/commit/13bed5763abd9a0c4c84abd794f26772cb850fe8))
* **telescope:** Correct index for finder ([#136](https://github.com/folke/noice.nvim/issues/136)) ([99bbfe7](https://github.com/folke/noice.nvim/commit/99bbfe7ee49263577f6a941ad61677199a02eaf7))
* **telescope:** wrap text in telescope's previewer ([#514](https://github.com/folke/noice.nvim/issues/514)) ([a7f611e](https://github.com/folke/noice.nvim/commit/a7f611ef740a45b995d4a8e6d237643ac6ad0093))
* **text:** better (correct) way of dealing with `\r` characters. Fixes [#483](https://github.com/folke/noice.nvim/issues/483) ([520a737](https://github.com/folke/noice.nvim/commit/520a73760030f1293bbee41b0dcd041f47d1ecae))
* **text:** temp fixup for CRLF handling ([3e1400f](https://github.com/folke/noice.nvim/commit/3e1400f172cf67041f6845d86413de56fb90e685))
* **treesitter:** deprecated call. Fixes [#408](https://github.com/folke/noice.nvim/issues/408) ([1ded575](https://github.com/folke/noice.nvim/commit/1ded575928752861558a729fcbbd1e6e53c76652))
* **treesitter:** dont allow recursive injections. Fixes [#286](https://github.com/folke/noice.nvim/issues/286) ([a31b41a](https://github.com/folke/noice.nvim/commit/a31b41a739731988fc30a48a3099586a884bdf61))
* **treesitter:** fixed treesitter.query.get. Fixes [#539](https://github.com/folke/noice.nvim/issues/539) ([e91a31c](https://github.com/folke/noice.nvim/commit/e91a31c32c0eef6a338030ac51eaed14ac49ce2e))
* **treesitter:** ignore weird invalid end_col errors. Fixes [#473](https://github.com/folke/noice.nvim/issues/473) ([7e2692b](https://github.com/folke/noice.nvim/commit/7e2692b0c461da182a54ff2af4a35aea2bf8ea5c))
* **treesitter:** only disable injections for php and html ([0e1bf11](https://github.com/folke/noice.nvim/commit/0e1bf11d46054b8ab04eb62b53c5ac81b44f14df))
* **treesitter:** parse injections ([#571](https://github.com/folke/noice.nvim/issues/571)) ([3ec6e42](https://github.com/folke/noice.nvim/commit/3ec6e4221e9b80f914ed6774abb4e82d8f5c3b39))
* **treesitter:** use the new treesitter ft to lang API if availble. Fixes [#378](https://github.com/folke/noice.nvim/issues/378) ([36d141b](https://github.com/folke/noice.nvim/commit/36d141bd5852b10e32058e259982182b9e5e8060))
* truncate log file if it becomes too big ([79a5262](https://github.com/folke/noice.nvim/commit/79a526210233808e7c73714998f56c9f059c11d8))
* **ui_attach:** dont update router during `ext_messages` and disable/enable Noice during confirm. [#298](https://github.com/folke/noice.nvim/issues/298) ([a4cbc0f](https://github.com/folke/noice.nvim/commit/a4cbc0f0cebdaa9529a749f4463aedc5a2cdcf1b))
* **ui:** cmdline is always blocking. Fixes [#347](https://github.com/folke/noice.nvim/issues/347) ([6702d97](https://github.com/folke/noice.nvim/commit/6702d97d3c37c3a363ffc7c890578109f82f9f20))
* **ui:** disable debug logging ([bfb0cdb](https://github.com/folke/noice.nvim/commit/bfb0cdb56cc3b2eb5f00b6c1747d04540d76799a))
* **ui:** dont propagate events handled by Noice to other uis. Fixes [#17](https://github.com/folke/noice.nvim/issues/17) ([1cff24c](https://github.com/folke/noice.nvim/commit/1cff24cabf1076916be1e83de16514be99827678))
* **ui:** dont update on msg_ruler. Fixes [#588](https://github.com/folke/noice.nvim/issues/588) ([ec19fc0](https://github.com/folke/noice.nvim/commit/ec19fc0fd27fcfa8661d71bcfb61f6b84a6b7f98))
* **ui:** exclude search_count from realtime updates ([73caffa](https://github.com/folke/noice.nvim/commit/73caffa74550e98ae3f61520a4af526ac593609b))
* **ui:** safer adding of winhl ([36b1935](https://github.com/folke/noice.nvim/commit/36b1935660988b4b6034ef9fb8454a1427990675))
* **ui:** work-around for segfaults in TUI. Fixes [#298](https://github.com/folke/noice.nvim/issues/298) ([176ec31](https://github.com/folke/noice.nvim/commit/176ec31026ec4baf64638fba1a180701257380f1))
* use modeline=false for popupmenu scroll events. Fixes [#572](https://github.com/folke/noice.nvim/issues/572) ([1f087c2](https://github.com/folke/noice.nvim/commit/1f087c2495bbc824b556329eb389dfff8964e5a3))
* use vim.F.unpack_len and vim.F.pack_len. Fixes a number of issues... ([51c2179](https://github.com/folke/noice.nvim/commit/51c2179d3535d4c61d0ceb80e2938884fa73181c))
* **views:** don't override winbar and foldenable for every view ([a7d60f7](https://github.com/folke/noice.nvim/commit/a7d60f73b1325137b34c630bc0af76fa6598ba1f))
* **views:** dont highlight CurSearch for some views. Fixes [#399](https://github.com/folke/noice.nvim/issues/399) ([0c493e5](https://github.com/folke/noice.nvim/commit/0c493e5d243c39adf3d6ce7683a16e610cc44e0a))
* **views:** increase zindex for cmdline popup, popupmenu and confirm from 60 to 200 ([d71c1de](https://github.com/folke/noice.nvim/commit/d71c1deabf78db16262f5388fe12930fc16bd93e))
* **virtual:** extra check if buffer still valid that has virtual text extmark ([7ed897d](https://github.com/folke/noice.nvim/commit/7ed897d77d13eb4a9f4ab576f58db9bdda9af6ec))
* **virtual:** remove extmark from correct buffer where it was set. Fixes [#464](https://github.com/folke/noice.nvim/issues/464) ([e5a4c7a](https://github.com/folke/noice.nvim/commit/e5a4c7a6ac3ef4c32f97a50c0b9b21e21c445c04))
* wait to override cmp till it loaded ([712180f](https://github.com/folke/noice.nvim/commit/712180f94684b7ce56957df60d037c81784e69c3))
* workaround E36: Not enough room errors ([#522](https://github.com/folke/noice.nvim/issues/522)) ([f43775f](https://github.com/folke/noice.nvim/commit/f43775f0e427b0c7e9d30e6bc51fdebea6979b37))
### Performance Improvements
* cache highlighter queries ([b4eb215](https://github.com/folke/noice.nvim/commit/b4eb2155f3347377eb0c14458755ce7b7966cdb7))
* do redrawstatus before resetting cursor ([5aa862f](https://github.com/folke/noice.nvim/commit/5aa862f380c65fbe2833b2b9e869822e7e9f8257))
* don't bufload when highlighting a buffer ([8df4cbd](https://github.com/folke/noice.nvim/commit/8df4cbdae15a915d460828710bf9ff1befb3f12d))
* dont update popupmenu state when it hasn't changed ([cafdddb](https://github.com/folke/noice.nvim/commit/cafdddb6eb3105cac1220337f91c21aebe998485))
* **lazy:** set package.loaded when real module was loaded ([f6cc07a](https://github.com/folke/noice.nvim/commit/f6cc07af4d9329c48fed9e22044f2a0ed8ac7d31))
* make noice a bit more robust when exiting to prevent possible delays on exit ([35e3664](https://github.com/folke/noice.nvim/commit/35e3664297096d8e24ca17f590bc793482f5182d))
* **popupmenu:** re-use existing nui menu for rendering the popupmenu ([fdd78c2](https://github.com/folke/noice.nvim/commit/fdd78c25f64482c4c92ca84ba9c5814a5aa2788e))
* re-use existing popupmenu instead of unmount/create ([b209e0b](https://github.com/folke/noice.nvim/commit/b209e0bbb1666081eb70cfa919d315d892e73cb7))
* shutdown Noice on VimLeave to prevent close delay ([de1d5dc](https://github.com/folke/noice.nvim/commit/de1d5dc1f9446221674cee6b69b81a28b13dfa62))
## [1.16.3](https://github.com/folke/noice.nvim/compare/v1.16.2...v1.16.3) (2023-10-24)
### Bug Fixes
* **hacks:** make sure the cursor is properly updated before getchar ([0cca940](https://github.com/folke/noice.nvim/commit/0cca940561d4b723cb52ba9e4ec239fceb36f146))
* use modeline=false for popupmenu scroll events. Fixes [#572](https://github.com/folke/noice.nvim/issues/572) ([1f087c2](https://github.com/folke/noice.nvim/commit/1f087c2495bbc824b556329eb389dfff8964e5a3))
## [1.16.2](https://github.com/folke/noice.nvim/compare/v1.16.1...v1.16.2) (2023-10-15)
### Bug Fixes
* **cmdline:** better and safer way to trigger redraw during cmdpreview ([02ed6d4](https://github.com/folke/noice.nvim/commit/02ed6d4c0c1e4d21fc4e79bbe54961023ee9badb))
* **signature:** show signature in correct window. Fixes [#593](https://github.com/folke/noice.nvim/issues/593) ([2f0993e](https://github.com/folke/noice.nvim/commit/2f0993ee97f98cacde179a1f431881b2758e2138))
* **virtual:** extra check if buffer still valid that has virtual text extmark ([7ed897d](https://github.com/folke/noice.nvim/commit/7ed897d77d13eb4a9f4ab576f58db9bdda9af6ec))
* **virtual:** remove extmark from correct buffer where it was set. Fixes [#464](https://github.com/folke/noice.nvim/issues/464) ([e5a4c7a](https://github.com/folke/noice.nvim/commit/e5a4c7a6ac3ef4c32f97a50c0b9b21e21c445c04))
## [1.16.1](https://github.com/folke/noice.nvim/compare/v1.16.0...v1.16.1) (2023-10-07)
### Bug Fixes
* **ui:** exclude search_count from realtime updates ([73caffa](https://github.com/folke/noice.nvim/commit/73caffa74550e98ae3f61520a4af526ac593609b))
## [1.16.0](https://github.com/folke/noice.nvim/compare/v1.15.11...v1.16.0) (2023-10-04)
### Features
* Support hide scrollbar for view ([#603](https://github.com/folke/noice.nvim/issues/603)) ([f700175](https://github.com/folke/noice.nvim/commit/f700175b91948e4f71cf73872cea364247cf2dbd))
### Bug Fixes
* **ui:** disable debug logging ([bfb0cdb](https://github.com/folke/noice.nvim/commit/bfb0cdb56cc3b2eb5f00b6c1747d04540d76799a))
* **ui:** dont update on msg_ruler. Fixes [#588](https://github.com/folke/noice.nvim/issues/588) ([ec19fc0](https://github.com/folke/noice.nvim/commit/ec19fc0fd27fcfa8661d71bcfb61f6b84a6b7f98))
## [1.15.11](https://github.com/folke/noice.nvim/compare/v1.15.10...v1.15.11) (2023-09-25)
### Bug Fixes
* accept preset as a table ([#582](https://github.com/folke/noice.nvim/issues/582)) ([53d613c](https://github.com/folke/noice.nvim/commit/53d613cd0031e83987964947b1bad8b5047c9d0e))
* check item.detail is type of table ([#595](https://github.com/folke/noice.nvim/issues/595)) ([3670766](https://github.com/folke/noice.nvim/commit/3670766b10fded979fcb00606801edc585a65f2a))
* dont hide cursor on input. Fixes [#585](https://github.com/folke/noice.nvim/issues/585). Fixes [#566](https://github.com/folke/noice.nvim/issues/566) ([819a5bf](https://github.com/folke/noice.nvim/commit/819a5bf62fa31c893c9d0c6da17ef93a810a1e8c))
* read conceal setting after sort ([#558](https://github.com/folke/noice.nvim/issues/558)) ([24c09cc](https://github.com/folke/noice.nvim/commit/24c09cc0263054cb3d8dedf2c54b570e655850f5))
## [1.15.10](https://github.com/folke/noice.nvim/compare/v1.15.9...v1.15.10) (2023-08-26)
### Bug Fixes
* **treesitter:** parse injections ([#571](https://github.com/folke/noice.nvim/issues/571)) ([3ec6e42](https://github.com/folke/noice.nvim/commit/3ec6e4221e9b80f914ed6774abb4e82d8f5c3b39))
## [1.15.9](https://github.com/folke/noice.nvim/compare/v1.15.8...v1.15.9) (2023-07-25)
### Bug Fixes
* **health:** allow running in GUIs with multigrid ([71a7591](https://github.com/folke/noice.nvim/commit/71a75913d680af9f1f26a2886518e390cd8f73e1))
## [1.15.8](https://github.com/folke/noice.nvim/compare/v1.15.7...v1.15.8) (2023-07-21)
### Bug Fixes
* remove old neovide compatibility warning ([#545](https://github.com/folke/noice.nvim/issues/545)) ([dfbe27c](https://github.com/folke/noice.nvim/commit/dfbe27cfafd6bcb5605dc6c6b82174c33fc9b09e))
## [1.15.7](https://github.com/folke/noice.nvim/compare/v1.15.6...v1.15.7) (2023-07-20)
### Bug Fixes
* **popupmenu:** properly close pmenu when cmdline window is open. Fixes [#542](https://github.com/folke/noice.nvim/issues/542) ([d19e5cb](https://github.com/folke/noice.nvim/commit/d19e5cb58e33c6d74e9005d1965d6e8ebd6b057b))
## [1.15.6](https://github.com/folke/noice.nvim/compare/v1.15.5...v1.15.6) (2023-07-18)
### Bug Fixes
* **treesitter:** fixed treesitter.query.get. Fixes [#539](https://github.com/folke/noice.nvim/issues/539) ([e91a31c](https://github.com/folke/noice.nvim/commit/e91a31c32c0eef6a338030ac51eaed14ac49ce2e))
## [1.15.5](https://github.com/folke/noice.nvim/compare/v1.15.4...v1.15.5) (2023-07-17)
### Bug Fixes
* **lsp:** allow whitespace info string in markdown ([#535](https://github.com/folke/noice.nvim/issues/535)) ([1343acc](https://github.com/folke/noice.nvim/commit/1343acc592c3f138d7ffb88f9b8be1c4969b30d3))
* **ui:** dont propagate events handled by Noice to other uis. Fixes [#17](https://github.com/folke/noice.nvim/issues/17) ([1cff24c](https://github.com/folke/noice.nvim/commit/1cff24cabf1076916be1e83de16514be99827678))
## [1.15.4](https://github.com/folke/noice.nvim/compare/v1.15.3...v1.15.4) (2023-06-30)
### Bug Fixes
* progress use vim.lsp.get_client_by_id ([#529](https://github.com/folke/noice.nvim/issues/529)) ([397619d](https://github.com/folke/noice.nvim/commit/397619d5351d650e9879d18c9312a5add5729815))
* workaround E36: Not enough room errors ([#522](https://github.com/folke/noice.nvim/issues/522)) ([f43775f](https://github.com/folke/noice.nvim/commit/f43775f0e427b0c7e9d30e6bc51fdebea6979b37))
## [1.15.3](https://github.com/folke/noice.nvim/compare/v1.15.2...v1.15.3) (2023-06-24)
### Bug Fixes
* **config:** properly deal with preset routes. Fixes [#517](https://github.com/folke/noice.nvim/issues/517) ([fea7f1c](https://github.com/folke/noice.nvim/commit/fea7f1cf15b04ec9b8dd071aa3510f693156ce59))
## [1.15.2](https://github.com/folke/noice.nvim/compare/v1.15.1...v1.15.2) (2023-06-22)
### Bug Fixes
* **telescope:** wrap text in telescope's previewer ([#514](https://github.com/folke/noice.nvim/issues/514)) ([a7f611e](https://github.com/folke/noice.nvim/commit/a7f611ef740a45b995d4a8e6d237643ac6ad0093))
* **views:** don't override winbar and foldenable for every view ([a7d60f7](https://github.com/folke/noice.nvim/commit/a7d60f73b1325137b34c630bc0af76fa6598ba1f))
* **views:** increase zindex for cmdline popup, popupmenu and confirm from 60 to 200 ([d71c1de](https://github.com/folke/noice.nvim/commit/d71c1deabf78db16262f5388fe12930fc16bd93e))
## [1.15.1](https://github.com/folke/noice.nvim/compare/v1.15.0...v1.15.1) (2023-06-10)
### Bug Fixes
* **nui:** nui broke noice. added a temporary work-around till the problem is solved ([4db3c8f](https://github.com/folke/noice.nvim/commit/4db3c8f14302273b842f73a8facf1999169c1e41))
* **ui:** safer adding of winhl ([36b1935](https://github.com/folke/noice.nvim/commit/36b1935660988b4b6034ef9fb8454a1427990675))
## [1.15.0](https://github.com/folke/noice.nvim/compare/v1.14.2...v1.15.0) (2023-06-06)
### Features
* add `circleFull` spinner ([#495](https://github.com/folke/noice.nvim/issues/495)) ([5427398](https://github.com/folke/noice.nvim/commit/54273980749ceb4396501300bf4c86f3bb818f75))
* **popupmenu:** allow different views for regular/cmdline popupmenu ([af706c4](https://github.com/folke/noice.nvim/commit/af706c4b443cf1c416ef7288ec3434f3f1ab6cf1))
### Bug Fixes
* **popupmenu:** always show the regular (non-cmdline) popupmenu near the cursor ([e3936cc](https://github.com/folke/noice.nvim/commit/e3936ccbbd32f6ce4a4f55a77ec556b116c0b928))
* **preset:** palette now configures cmdline_popupmenu instead of popupmenu ([294097a](https://github.com/folke/noice.nvim/commit/294097a239ec943587e5707b678142c52a9b318e))
### Performance Improvements
* **popupmenu:** re-use existing nui menu for rendering the popupmenu ([fdd78c2](https://github.com/folke/noice.nvim/commit/fdd78c25f64482c4c92ca84ba9c5814a5aa2788e))
## [1.14.2](https://github.com/folke/noice.nvim/compare/v1.14.1...v1.14.2) (2023-05-27)
### Bug Fixes
* **block:** better deal with carriage return characters (take 2) ([ee24b36](https://github.com/folke/noice.nvim/commit/ee24b36743b18e53bdc6b49bbfa426fc18ea337a))
* **text:** temp fixup for CRLF handling ([3e1400f](https://github.com/folke/noice.nvim/commit/3e1400f172cf67041f6845d86413de56fb90e685))
## [1.14.1](https://github.com/folke/noice.nvim/compare/v1.14.0...v1.14.1) (2023-05-27)
### Bug Fixes
* **text:** better (correct) way of dealing with `\r` characters. Fixes [#483](https://github.com/folke/noice.nvim/issues/483) ([520a737](https://github.com/folke/noice.nvim/commit/520a73760030f1293bbee41b0dcd041f47d1ecae))
## [1.14.0](https://github.com/folke/noice.nvim/compare/v1.13.4...v1.14.0) (2023-05-25)
### Features
* **cmdline:** added support for FloatTitle and added proper default ([79c7059](https://github.com/folke/noice.nvim/commit/79c70594aefb4efecbce4528174fdd0227baaf3e))
## [1.13.4](https://github.com/folke/noice.nvim/compare/v1.13.3...v1.13.4) (2023-05-24)
### Bug Fixes
* **cmdline:** fixed a byte offset issue with the new virtual inline text ([a13a88f](https://github.com/folke/noice.nvim/commit/a13a88fb2016b6cfea8f56238566b345f537e47a))
## [1.13.3](https://github.com/folke/noice.nvim/compare/v1.13.2...v1.13.3) (2023-05-24)
### Bug Fixes
* **overrides:** noice was leaking extmarks for stylize_markdown ([9b148a1](https://github.com/folke/noice.nvim/commit/9b148a141df7fefc66705e2e7219d11536b99288))
## [1.13.2](https://github.com/folke/noice.nvim/compare/v1.13.1...v1.13.2) (2023-05-22)
### Bug Fixes
* **treesitter:** ignore weird invalid end_col errors. Fixes [#473](https://github.com/folke/noice.nvim/issues/473) ([7e2692b](https://github.com/folke/noice.nvim/commit/7e2692b0c461da182a54ff2af4a35aea2bf8ea5c))
### Performance Improvements
* don't bufload when highlighting a buffer ([8df4cbd](https://github.com/folke/noice.nvim/commit/8df4cbdae15a915d460828710bf9ff1befb3f12d))
## [1.13.1](https://github.com/folke/noice.nvim/compare/v1.13.0...v1.13.1) (2023-05-21)
### Bug Fixes
* support older Neovim versions ([4a1ec5e](https://github.com/folke/noice.nvim/commit/4a1ec5ec0b163a365d7593d93450676b9cbcbebd))
## [1.13.0](https://github.com/folke/noice.nvim/compare/v1.12.4...v1.13.0) (2023-05-21)
### Features
* **cmp:** incude item.detail when it's not part of item.documentation ([c2a745a](https://github.com/folke/noice.nvim/commit/c2a745a26ae562f1faecbf6177ac53377d2658d5))
### Bug Fixes
* **notify:** nvim-notify replace can be an id or a record ([a9cc87b](https://github.com/folke/noice.nvim/commit/a9cc87b14e18bc3717746b45d79157c0adb43a4d))
### Performance Improvements
* cache highlighter queries ([b4eb215](https://github.com/folke/noice.nvim/commit/b4eb2155f3347377eb0c14458755ce7b7966cdb7))
## [1.12.4](https://github.com/folke/noice.nvim/compare/v1.12.3...v1.12.4) (2023-05-07)
### Bug Fixes
* **lsp:** add non-nil guard to setup function ([#454](https://github.com/folke/noice.nvim/issues/454)) ([4524216](https://github.com/folke/noice.nvim/commit/4524216d7484c7b183ca1f654d8e66dff28a5680))
* **markdown:** conceal escaping forward slashes. Fixes [#455](https://github.com/folke/noice.nvim/issues/455) ([a7246aa](https://github.com/folke/noice.nvim/commit/a7246aa99fde34fb9d5e13c62c83ac7226514d67))
## [1.12.3](https://github.com/folke/noice.nvim/compare/v1.12.2...v1.12.3) (2023-05-04)
### Bug Fixes
* **health:** fix deprecated health checks healthcheck ([#438](https://github.com/folke/noice.nvim/issues/438)) ([0f12ed3](https://github.com/folke/noice.nvim/commit/0f12ed399e79aa49f283aa954468b92be65e03ed))
* **markdown:** replace additional html entities ([#448](https://github.com/folke/noice.nvim/issues/448)) ([d31fe94](https://github.com/folke/noice.nvim/commit/d31fe940e0866686718822aaac45527412c45134)), closes [#447](https://github.com/folke/noice.nvim/issues/447)
* **popupmenu:** only use popupmenu hl group for known item kinds. Fixes [#453](https://github.com/folke/noice.nvim/issues/453) ([0b86a7b](https://github.com/folke/noice.nvim/commit/0b86a7bfbf84927909ed81e9616a5e24602fe6fb))
* **signature:** safer lsp signature parameters ([#449](https://github.com/folke/noice.nvim/issues/449)) ([e33c346](https://github.com/folke/noice.nvim/commit/e33c34642a7b02db3db03bfc2bec7799bbc2034e))
## [1.12.2](https://github.com/folke/noice.nvim/compare/v1.12.1...v1.12.2) (2023-04-18)
### Bug Fixes
* **signature:** nil check for parameter label. Fixes [#435](https://github.com/folke/noice.nvim/issues/435) ([9d778e7](https://github.com/folke/noice.nvim/commit/9d778e7ce29c519ca0285b054e7c3b679bc9d3b9))
* **signature:** support label offsets ([9649d9f](https://github.com/folke/noice.nvim/commit/9649d9fd4d8fa8a8654e1e9c293718ae8d62e73b))
## [1.12.1](https://github.com/folke/noice.nvim/compare/v1.12.0...v1.12.1) (2023-04-17)
### Bug Fixes
* **router:** properly disable updater when disabling Noice. Fixes [#423](https://github.com/folke/noice.nvim/issues/423) ([3bed83b](https://github.com/folke/noice.nvim/commit/3bed83b4d2e4fce03a27071c39be0d9e04313332))
## [1.12.0](https://github.com/folke/noice.nvim/compare/v1.11.0...v1.12.0) (2023-04-16)
### Features
* added `Noice dismiss` to hide all visible messages. Fixes [#417](https://github.com/folke/noice.nvim/issues/417) ([a32bc89](https://github.com/folke/noice.nvim/commit/a32bc892aadb26668fd0161962ae4eccb1bf5854))
## [1.11.0](https://github.com/folke/noice.nvim/compare/v1.10.2...v1.11.0) (2023-04-16)
### Features
* **lsp:** added config.lsp.hover.silent. Fixes [#412](https://github.com/folke/noice.nvim/issues/412) ([e2a53cf](https://github.com/folke/noice.nvim/commit/e2a53cf946d88d87cd0123711afce5ddad047b7b))
* **signature:** added signature param docs. Fixes [#421](https://github.com/folke/noice.nvim/issues/421) ([e76ae13](https://github.com/folke/noice.nvim/commit/e76ae13dd272dc23d0154b93172d445aeabad8f1))
### Bug Fixes
* **confirm:** keep newlines in confirm message. Fixes [#422](https://github.com/folke/noice.nvim/issues/422) ([051111f](https://github.com/folke/noice.nvim/commit/051111f98d7128c833eaa32423426829981b2aa3))
* **markdown:** replace `&lt;code&gt;`. Fixes [#424](https://github.com/folke/noice.nvim/issues/424) ([38fb652](https://github.com/folke/noice.nvim/commit/38fb652d0a95780d20a551a6ec44b01226476c99))
* **markdown:** revert ([d767be9](https://github.com/folke/noice.nvim/commit/d767be960e8660b19595ccff2dad6abd7aae2d4a))
## [1.10.2](https://github.com/folke/noice.nvim/compare/v1.10.1...v1.10.2) (2023-03-26)
### Bug Fixes
* **icons:** removed all obsolete icons thanks to nerdfix ([cf6a194](https://github.com/folke/noice.nvim/commit/cf6a194f9280cda1fdcc36d271fccd4a24082df3))
## [1.10.1](https://github.com/folke/noice.nvim/compare/v1.10.0...v1.10.1) (2023-03-24)
### Bug Fixes
* **treesitter:** deprecated call. Fixes [#408](https://github.com/folke/noice.nvim/issues/408) ([1ded575](https://github.com/folke/noice.nvim/commit/1ded575928752861558a729fcbbd1e6e53c76652))
## [1.10.0](https://github.com/folke/noice.nvim/compare/v1.9.5...v1.10.0) (2023-03-23)
### Features
* **cmdline:** added cmdline support for `:lua=` and `:=` ([acfa513](https://github.com/folke/noice.nvim/commit/acfa5133da31a35ec24fca0757ad1c85edc4c585))
## [1.9.5](https://github.com/folke/noice.nvim/compare/v1.9.4...v1.9.5) (2023-03-19)
### Bug Fixes
* **views:** dont highlight CurSearch for some views. Fixes [#399](https://github.com/folke/noice.nvim/issues/399) ([0c493e5](https://github.com/folke/noice.nvim/commit/0c493e5d243c39adf3d6ce7683a16e610cc44e0a))
## [1.9.4](https://github.com/folke/noice.nvim/compare/v1.9.3...v1.9.4) (2023-03-15)
### Bug Fixes
* **markdown:** better check to see if a ts parser is available. Fixes [#397](https://github.com/folke/noice.nvim/issues/397) ([d60bee1](https://github.com/folke/noice.nvim/commit/d60bee1b85af1882768af80385bc2500d495feba))
* **markdown:** strip "\r" chars ([4d2801b](https://github.com/folke/noice.nvim/commit/4d2801babc4026229c58f0c77a20ff5b7b4c0d07))
## [1.9.3](https://github.com/folke/noice.nvim/compare/v1.9.2...v1.9.3) (2023-03-14)
### Bug Fixes
* **cmdline:** dont use normal commands so ModeChanged will trigger correctly. Fixes [#390](https://github.com/folke/noice.nvim/issues/390) ([fa7b6a1](https://github.com/folke/noice.nvim/commit/fa7b6a18c5cdc23961038bc56b93495efcd0f5c7))
## [1.9.2](https://github.com/folke/noice.nvim/compare/v1.9.1...v1.9.2) (2023-03-12)
### Bug Fixes
* **cmdline:** make sure cursor is always visible ([2f0a427](https://github.com/folke/noice.nvim/commit/2f0a42701b4aa65b55fff8f32878d9adc7e7ac77))
* **config:** allow overriding options set by presets ([5a1bf17](https://github.com/folke/noice.nvim/commit/5a1bf1707f592fefff4cb3da903b17369e088cc1))
* **config:** correctly set the presets ([e5cb84f](https://github.com/folke/noice.nvim/commit/e5cb84f1ed524f850fa92e3a256e830ed07fadee))
## [1.9.1](https://github.com/folke/noice.nvim/compare/v1.9.0...v1.9.1) (2023-03-03)
### Bug Fixes
* **health:** correctly check if lsp.message is enabled ([#381](https://github.com/folke/noice.nvim/issues/381)) ([1ff6b10](https://github.com/folke/noice.nvim/commit/1ff6b10471590331cc1585ad64f084f19cd4bcb7))
## [1.9.0](https://github.com/folke/noice.nvim/compare/v1.8.3...v1.9.0) (2023-03-03)
### Features
* **lsp:** fallback to buffer filetype for code blocks without lang. Fixes [#378](https://github.com/folke/noice.nvim/issues/378) ([cab2c80](https://github.com/folke/noice.nvim/commit/cab2c80497388735c9795f496a36e76bc5c7c4bf))
### Bug Fixes
* **treesitter:** use the new treesitter ft to lang API if availble. Fixes [#378](https://github.com/folke/noice.nvim/issues/378) ([36d141b](https://github.com/folke/noice.nvim/commit/36d141bd5852b10e32058e259982182b9e5e8060))
## [1.8.3](https://github.com/folke/noice.nvim/compare/v1.8.2...v1.8.3) (2023-03-02)
### Bug Fixes
* **notify:** take col offsets into account for nvim-notify renderers. Fixes [#375](https://github.com/folke/noice.nvim/issues/375) ([20596d9](https://github.com/folke/noice.nvim/commit/20596d96551605f7462f5722198b188e4047b605))
## [1.8.2](https://github.com/folke/noice.nvim/compare/v1.8.1...v1.8.2) (2023-02-07)
### Bug Fixes
* **signature:** when loading, attach to existing lsp clients. Fixes [#342](https://github.com/folke/noice.nvim/issues/342) ([f69f1a5](https://github.com/folke/noice.nvim/commit/f69f1a577615a5a6527f133df0aa40e596bd1707))
## [1.8.1](https://github.com/folke/noice.nvim/compare/v1.8.0...v1.8.1) (2023-02-06)
### Bug Fixes
* **ui:** cmdline is always blocking. Fixes [#347](https://github.com/folke/noice.nvim/issues/347) ([6702d97](https://github.com/folke/noice.nvim/commit/6702d97d3c37c3a363ffc7c890578109f82f9f20))
## [1.8.0](https://github.com/folke/noice.nvim/compare/v1.7.1...v1.8.0) (2023-01-24)
### Features
* added deactivate ([bf216e0](https://github.com/folke/noice.nvim/commit/bf216e017979f8be712b1ada62736a58e75b0fe3))
### Bug Fixes
* Allow mapping &lt;esc&gt; ([#329](https://github.com/folke/noice.nvim/issues/329)) ([b7e9054](https://github.com/folke/noice.nvim/commit/b7e9054b02b5958db8bb5ad7675e92bfb5a8e903))
## [1.7.1](https://github.com/folke/noice.nvim/compare/v1.7.0...v1.7.1) (2023-01-23)
### Bug Fixes
* **nui:** make sure nui recreates buffer and window when needed ([3e6dfd8](https://github.com/folke/noice.nvim/commit/3e6dfd8bb00d98399704a020ab7892234ce80fdb))
* **nui:** mount if buffer is no longer valid ([71d7b5c](https://github.com/folke/noice.nvim/commit/71d7b5cf8f24b9bdc425934c36cfda784fcd10f2))
* **nui:** set mounted=false if buffer is no longer valid ([3353a7a](https://github.com/folke/noice.nvim/commit/3353a7ab4bae6c22f61fd646c10e336b4582f0ea))
### Performance Improvements
* make noice a bit more robust when exiting to prevent possible delays on exit ([35e3664](https://github.com/folke/noice.nvim/commit/35e3664297096d8e24ca17f590bc793482f5182d))
## [1.7.0](https://github.com/folke/noice.nvim/compare/v1.6.2...v1.7.0) (2023-01-14)
### Features
* **ui:** added hybrid messages functionality, but not needed for now ([addc0a2](https://github.com/folke/noice.nvim/commit/addc0a2521ce666a1f546f9a04574a63a858c6a5))
### Bug Fixes
* **swap:** additionally check for updates when a swap file was found ([1165d3e](https://github.com/folke/noice.nvim/commit/1165d3e727bdd226eefffcc801d563bcb30e71c4))
* **ui:** work-around for segfaults in TUI. Fixes [#298](https://github.com/folke/noice.nvim/issues/298) ([176ec31](https://github.com/folke/noice.nvim/commit/176ec31026ec4baf64638fba1a180701257380f1))
## [1.6.2](https://github.com/folke/noice.nvim/compare/v1.6.1...v1.6.2) (2023-01-13)
### Bug Fixes
* **ui_attach:** dont update router during `ext_messages` and disable/enable Noice during confirm. [#298](https://github.com/folke/noice.nvim/issues/298) ([a4cbc0f](https://github.com/folke/noice.nvim/commit/a4cbc0f0cebdaa9529a749f4463aedc5a2cdcf1b))
## [1.6.1](https://github.com/folke/noice.nvim/compare/v1.6.0...v1.6.1) (2023-01-10)
### Bug Fixes
* show unstable message after loading noice ([2613a16](https://github.com/folke/noice.nvim/commit/2613a16b5009acbf2adabb34b029b1c4c57101e3))
## [1.6.0](https://github.com/folke/noice.nvim/compare/v1.5.2...v1.6.0) (2023-01-10)
### Features
* show warning when running with TUI rework ([cf2231b](https://github.com/folke/noice.nvim/commit/cf2231bfb691b3b58d2685f48da11596cec1cfa3))
## [1.5.2](https://github.com/folke/noice.nvim/compare/v1.5.1...v1.5.2) (2023-01-01)
### Bug Fixes
* **treesitter:** only disable injections for php and html ([0e1bf11](https://github.com/folke/noice.nvim/commit/0e1bf11d46054b8ab04eb62b53c5ac81b44f14df))
## [1.5.1](https://github.com/folke/noice.nvim/compare/v1.5.0...v1.5.1) (2022-12-31)
### Bug Fixes
* dont error in checkhealth if nvim-treesitter is not installed ([044767a](https://github.com/folke/noice.nvim/commit/044767a01d38208c32d97b0214cce66c41e8f7c8))
* **health:** dont use nvim-treesitter to check if a lang exists ([585d24e](https://github.com/folke/noice.nvim/commit/585d24ec6e3fb4288414f864cfe2de7d025e8216))
* **notify_send:** properly close file descriptors from spwaning notifysend ([f5132fa](https://github.com/folke/noice.nvim/commit/f5132fa6eb71e96d9f0cd7148b186b324b142d15))
* **nui:** dont trigger autocmds when doing zt ([d176765](https://github.com/folke/noice.nvim/commit/d176765ceabae9a12bf09a5c785d3dcb3859e1b6))
* **popupmenu:** replace any newlines by space. Fixes [#265](https://github.com/folke/noice.nvim/issues/265) ([5199089](https://github.com/folke/noice.nvim/commit/51990892e1dd5ee1a1444b1cf3ccf0aca377e0c4))
* **treesitter:** dont allow recursive injections. Fixes [#286](https://github.com/folke/noice.nvim/issues/286) ([a31b41a](https://github.com/folke/noice.nvim/commit/a31b41a739731988fc30a48a3099586a884bdf61))
## [1.5.0](https://github.com/folke/noice.nvim/compare/v1.4.2...v1.5.0) (2022-12-21)
### Features
* added `Filter.cond` to conditionally use a route ([29a2e05](https://github.com/folke/noice.nvim/commit/29a2e052d2653443716a8eece89300e9b36b5f2a))
* **format:** allow `config.format.level.icons` to be false. Fixes [#274](https://github.com/folke/noice.nvim/issues/274) ([aa68eb6](https://github.com/folke/noice.nvim/commit/aa68eb6f83c48df41bab8ae36623e5af3f224c66))
### Bug Fixes
* correctly apply padding based on four numbers ([c9c1fbd](https://github.com/folke/noice.nvim/commit/c9c1fbd605388badcfa62c0b7f58d184f19e1484))
* **nui:** removed work-around for padding and border style shadow ([4f34d33](https://github.com/folke/noice.nvim/commit/4f34d33fc3dc0d6f4da9b4b8c63b9714fd4eea79))
## [1.4.2](https://github.com/folke/noice.nvim/compare/v1.4.1...v1.4.2) (2022-12-16)
### Bug Fixes
* **debug:** calculate stacktrace outisde of vim schedule to make it useful ([a5de1ca](https://github.com/folke/noice.nvim/commit/a5de1ca0eaecd21fd33a0a191d1a0b9dd97cb54a))
* **debug:** only concat debug info that is a string ([78ec5c6](https://github.com/folke/noice.nvim/commit/78ec5c6eefb9b61056a8545ded33b99f7a9a9f72))
* **nui:** remove border text when style is `nil`, `"none"`, or `"shadow"` ([d85a4d0](https://github.com/folke/noice.nvim/commit/d85a4d01774b5649dbcda8526a26f201dff5ade4))
* **nui:** remove padding when border is `shadow` ([1515007](https://github.com/folke/noice.nvim/commit/151500759722c12fb6a3931c5243d68f01af007a))
## [1.4.1](https://github.com/folke/noice.nvim/compare/v1.4.0...v1.4.1) (2022-12-03)
### Bug Fixes
* scrollbar destructs itself, so make a copy to see if there are any remnants left ([8d80a69](https://github.com/folke/noice.nvim/commit/8d80a692d5a045a3ec995536782f2b4c2b8d901b))
* stop processing messages when Neovim is exiting. Fixes [#237](https://github.com/folke/noice.nvim/issues/237) ([8c8acf7](https://github.com/folke/noice.nvim/commit/8c8acf74c09374e48a8fa1835560c3913d57243f))
## [1.4.0](https://github.com/folke/noice.nvim/compare/v1.3.1...v1.4.0) (2022-12-03)
### Features
* added support for &lt;pre&gt;{lang} code blocks used in the Neovim codebase ([de48a45](https://github.com/folke/noice.nvim/commit/de48a4528aad5c7b50cf4b4ec1b419762a95934d))
### Bug Fixes
* check if loader returned a function before loading ([66946c7](https://github.com/folke/noice.nvim/commit/66946c72f0a36f37e480b5eae97aac3cdcd5961d))
* reset preloader before trying to load the module ([08655e9](https://github.com/folke/noice.nvim/commit/08655e9f1bed638f9871d76b05928da74d1eeb68))
## [1.3.1](https://github.com/folke/noice.nvim/compare/v1.3.0...v1.3.1) (2022-12-01)
### Bug Fixes
* dont error if cmp not loaded when overriding ([4bae487](https://github.com/folke/noice.nvim/commit/4bae48798424d300e204cce2eb73b087854472d5))
* wait to override cmp till it loaded ([712180f](https://github.com/folke/noice.nvim/commit/712180f94684b7ce56957df60d037c81784e69c3))

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,813 @@
# 💥 Noice _(Nice, Noise, Notice)_
Highly experimental plugin that completely replaces the UI for `messages`, `cmdline` and the `popupmenu`.
![image](https://user-images.githubusercontent.com/292349/193263220-791847b2-516c-4f23-9802-31dd6bec5f6a.png)
## ✨ Features
- 🌅 fully **configurable views** like [nvim-notify](https://github.com/rcarriga/nvim-notify),
splits, popups, virtual text, ..
- 🔍 use **filters** to **route messages** to different views
- 🌈 message **highlights** are preserved in the views (like the colors of `:hi`)
- 📝 command output like [:messages](https://neovim.io/doc/user/message.html#:messages)
is shown in normal buffers, which makes it much easier to work with
- 📚 `:Noice` command to show a full message history
- ⌨️ no more [:h more-prompt](https://neovim.io/doc/user/message.html#more-prompt)
- 💻 fully customizable **cmdline** with icons
- 💅 **syntax highlighting** for `vim` and `lua` on the **cmdline**
- 🚥 **statusline** components
- 🔭 open message history in [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim)
## 🔥 Status
**Noice** is using the new experimental `vim.ui_attach` API, so issues are to be expected.
It is highly recommended to use Neovim nightly, since a bunch of issues have already been fixed upstream.
Check this [tracking issue](https://github.com/folke/noice.nvim/issues/6) for a list of known issues.
## ⚡️ Requirements
- Neovim >= 0.9.0 **_(nightly highly recommended)_**
- [nui.nvim](https://github.com/MunifTanjim/nui.nvim): used for proper rendering and multiple views
- [nvim-notify](https://github.com/rcarriga/nvim-notify): notification view _**(optional)**_
- a [Nerd Font](https://www.nerdfonts.com/) **_(optional)_**
- [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter/) **_(optional, but highly recommended)_**
used for highlighting the cmdline and lsp docs. Make sure to install the parsers for
`vim`, `regex`, `lua`, `bash`, `markdown` and `markdown_inline`
## 📦 Installation
Install the plugin with your preferred package manager:
```lua
-- lazy.nvim
{
"folke/noice.nvim",
event = "VeryLazy",
opts = {
-- add any options here
},
dependencies = {
-- if you lazy-load any plugin below, make sure to add proper `module="..."` entries
"MunifTanjim/nui.nvim",
-- OPTIONAL:
-- `nvim-notify` is only needed, if you want to use the notification view.
-- If not available, we use `mini` as the fallback
"rcarriga/nvim-notify",
}
}
```
Suggested setup:
```lua
require("noice").setup({
lsp = {
-- override markdown rendering so that **cmp** and other plugins use **Treesitter**
override = {
["vim.lsp.util.convert_input_to_markdown_lines"] = true,
["vim.lsp.util.stylize_markdown"] = true,
["cmp.entry.get_documentation"] = true, -- requires hrsh7th/nvim-cmp
},
},
-- you can enable a preset for easier configuration
presets = {
bottom_search = true, -- use a classic bottom cmdline for search
command_palette = true, -- position the cmdline and popupmenu together
long_message_to_split = true, -- long messages will be sent to a split
inc_rename = false, -- enables an input dialog for inc-rename.nvim
lsp_doc_border = false, -- add a border to hover docs and signature help
},
})
```
It's a good idea to run `:checkhealth noice` after installing to check for common issues.
<details><summary>vim-plug</summary>
```vim
" vim-plug
call plug#begin()
Plug 'folke/noice.nvim'
Plug 'MunifTanjim/nui.nvim'
call plug#end()
lua require("noice").setup()
```
</details>
## ⚙️ Configuration
**noice.nvim** comes with the following defaults:
Check the [wiki](https://github.com/folke/noice.nvim/wiki/Configuration-Recipes) for configuration recipes.
```lua
{
cmdline = {
enabled = true, -- enables the Noice cmdline UI
view = "cmdline_popup", -- view for rendering the cmdline. Change to `cmdline` to get a classic cmdline at the bottom
opts = {}, -- global options for the cmdline. See section on views
---@type table<string, CmdlineFormat>
format = {
-- conceal: (default=true) This will hide the text in the cmdline that matches the pattern.
-- view: (default is cmdline view)
-- opts: any options passed to the view
-- icon_hl_group: optional hl_group for the icon
-- title: set to anything or empty string to hide
cmdline = { pattern = "^:", icon = "", lang = "vim" },
search_down = { kind = "search", pattern = "^/", icon = " ", lang = "regex" },
search_up = { kind = "search", pattern = "^%?", icon = " ", lang = "regex" },
filter = { pattern = "^:%s*!", icon = "$", lang = "bash" },
lua = { pattern = { "^:%s*lua%s+", "^:%s*lua%s*=%s*", "^:%s*=%s*" }, icon = "", lang = "lua" },
help = { pattern = "^:%s*he?l?p?%s+", icon = "" },
input = {}, -- Used by input()
-- lua = false, -- to disable a format, set to `false`
},
},
messages = {
-- NOTE: If you enable messages, then the cmdline is enabled automatically.
-- This is a current Neovim limitation.
enabled = true, -- enables the Noice messages UI
view = "notify", -- default view for messages
view_error = "notify", -- view for errors
view_warn = "notify", -- view for warnings
view_history = "messages", -- view for :messages
view_search = "virtualtext", -- view for search count messages. Set to `false` to disable
},
popupmenu = {
enabled = true, -- enables the Noice popupmenu UI
---@type 'nui'|'cmp'
backend = "nui", -- backend to use to show regular cmdline completions
---@type NoicePopupmenuItemKind|false
-- Icons for completion item kinds (see defaults at noice.config.icons.kinds)
kind_icons = {}, -- set to `false` to disable icons
},
-- default options for require('noice').redirect
-- see the section on Command Redirection
---@type NoiceRouteConfig
redirect = {
view = "popup",
filter = { event = "msg_show" },
},
-- You can add any custom commands below that will be available with `:Noice command`
---@type table<string, NoiceCommand>
commands = {
history = {
-- options for the message history that you get with `:Noice`
view = "split",
opts = { enter = true, format = "details" },
filter = {
any = {
{ event = "notify" },
{ error = true },
{ warning = true },
{ event = "msg_show", kind = { "" } },
{ event = "lsp", kind = "message" },
},
},
},
-- :Noice last
last = {
view = "popup",
opts = { enter = true, format = "details" },
filter = {
any = {
{ event = "notify" },
{ error = true },
{ warning = true },
{ event = "msg_show", kind = { "" } },
{ event = "lsp", kind = "message" },
},
},
filter_opts = { count = 1 },
},
-- :Noice errors
errors = {
-- options for the message history that you get with `:Noice`
view = "popup",
opts = { enter = true, format = "details" },
filter = { error = true },
filter_opts = { reverse = true },
},
all = {
-- options for the message history that you get with `:Noice`
view = "split",
opts = { enter = true, format = "details" },
filter = {},
},
},
notify = {
-- Noice can be used as `vim.notify` so you can route any notification like other messages
-- Notification messages have their level and other properties set.
-- event is always "notify" and kind can be any log level as a string
-- The default routes will forward notifications to nvim-notify
-- Benefit of using Noice for this is the routing and consistent history view
enabled = true,
view = "notify",
},
lsp = {
progress = {
enabled = true,
-- Lsp Progress is formatted using the builtins for lsp_progress. See config.format.builtin
-- See the section on formatting for more details on how to customize.
--- @type NoiceFormat|string
format = "lsp_progress",
--- @type NoiceFormat|string
format_done = "lsp_progress_done",
throttle = 1000 / 30, -- frequency to update lsp progress message
view = "mini",
},
override = {
-- override the default lsp markdown formatter with Noice
["vim.lsp.util.convert_input_to_markdown_lines"] = false,
-- override the lsp markdown formatter with Noice
["vim.lsp.util.stylize_markdown"] = false,
-- override cmp documentation with Noice (needs the other options to work)
["cmp.entry.get_documentation"] = false,
},
hover = {
enabled = true,
silent = false, -- set to true to not show a message if hover is not available
view = nil, -- when nil, use defaults from documentation
---@type NoiceViewOptions
opts = {}, -- merged with defaults from documentation
},
signature = {
enabled = true,
auto_open = {
enabled = true,
trigger = true, -- Automatically show signature help when typing a trigger character from the LSP
luasnip = true, -- Will open signature help when jumping to Luasnip insert nodes
throttle = 50, -- Debounce lsp signature help request by 50ms
},
view = nil, -- when nil, use defaults from documentation
---@type NoiceViewOptions
opts = {}, -- merged with defaults from documentation
},
message = {
-- Messages shown by lsp servers
enabled = true,
view = "notify",
opts = {},
},
-- defaults for hover and signature help
documentation = {
view = "hover",
---@type NoiceViewOptions
opts = {
lang = "markdown",
replace = true,
render = "plain",
format = { "{message}" },
win_options = { concealcursor = "n", conceallevel = 3 },
},
},
},
markdown = {
hover = {
["|(%S-)|"] = vim.cmd.help, -- vim help links
["%[.-%]%((%S-)%)"] = require("noice.util").open, -- markdown links
},
highlights = {
["|%S-|"] = "@text.reference",
["@%S+"] = "@parameter",
["^%s*(Parameters:)"] = "@text.title",
["^%s*(Return:)"] = "@text.title",
["^%s*(See also:)"] = "@text.title",
["{%S-}"] = "@parameter",
},
},
health = {
checker = true, -- Disable if you don't want health checks to run
},
smart_move = {
-- noice tries to move out of the way of existing floating windows.
enabled = true, -- you can disable this behaviour here
-- add any filetypes here, that shouldn't trigger smart move.
excluded_filetypes = { "cmp_menu", "cmp_docs", "notify" },
},
---@type NoicePresets
presets = {
-- you can enable a preset by setting it to true, or a table that will override the preset config
-- you can also add custom presets that you can enable/disable with enabled=true
bottom_search = false, -- use a classic bottom cmdline for search
command_palette = false, -- position the cmdline and popupmenu together
long_message_to_split = false, -- long messages will be sent to a split
inc_rename = false, -- enables an input dialog for inc-rename.nvim
lsp_doc_border = false, -- add a border to hover docs and signature help
},
throttle = 1000 / 30, -- how frequently does Noice need to check for ui updates? This has no effect when in blocking mode.
---@type NoiceConfigViews
views = {}, ---@see section on views
---@type NoiceRouteConfig[]
routes = {}, --- @see section on routes
---@type table<string, NoiceFilter>
status = {}, --- @see section on statusline components
---@type NoiceFormatOptions
format = {}, --- @see section on formatting
}
```
<details>
<summary>If you don't want to use a Nerd Font, you can replace the icons with Unicode symbols.</summary>
```lua
require("noice").setup({
cmdline = {
format = {
cmdline = { icon = ">" },
search_down = { icon = "🔍⌄" },
search_up = { icon = "🔍⌃" },
filter = { icon = "$" },
lua = { icon = "☾" },
help = { icon = "?" },
},
},
format = {
level = {
icons = {
error = "✖",
warn = "▼",
info = "●",
},
},
},
popupmenu = {
kind_icons = false,
},
inc_rename = {
cmdline = {
format = {
IncRename = { icon = "⟳" },
},
},
},
})
```
</details>
## 🔍 Filters
**Noice** uses filters to route messages to specific views.
| Name | Type | Description |
| -------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| **any** | `filter[]` | checks that at least one of the filters matches |
| **blocking** | `boolean` | are we in blocking mode? |
| **cleared** | `boolean` | checks if the message is cleared, meaning it's in the history |
| **cmdline** | `boolean` or `string` | checks if the message was generated by executing a cmdline. When `string`, then it is used as a pattern |
| **error** | `boolean` | all error-like kinds from `ext_messages` |
| **event** | `string` or `string[]` | any of the events from `ext_messages` or `cmdline`. See [:h ui-messages](https://neovim.io/doc/user/ui.html#ui-messages) |
| **find** | `string` | uses lua `string.find` to match the pattern |
| **has** | `boolean` | checks if the message is exists, meaning it's in the history |
| **kind** | `string` or `string[]` | any of the kinds from `ext_messages`. See [:h ui-messages](https://neovim.io/doc/user/ui.html#ui-messages) |
| **max_height** | `number` | maximum height of the message |
| **max_length** | `number` | maximum length of the message (total width of all the lines) |
| **max_width** | `number` | maximum width of the message |
| **min_height** | `number` | minimum height of the message |
| **min_length** | `number` | minimum length of the message (total width of all the lines) |
| **min_width** | `number` | minimum width of the message |
| **mode** | `string` | checks if `vim.api.nvim_get_mode()` contains the given mode |
| **not** | `filter` | checks whether the filter matches or not |
| **warning** | `boolean` | all warning-like kinds from `ext_messages` |
<details>
<summary>Example:</summary>
```lua
-- all messages over 10 lines, excluding echo and search_count
local filter = {
event = "msg_show",
min_height = 10,
["not"] = { kind = { "search_count", "echo" } },
}
```
</details>
## 🌅 Views
**Noice** comes with the following built-in backends:
- **popup**: powered by [nui.nvim](https://github.com/MunifTanjim/nui.nvim)
- **split**: powered by [nui.nvim](https://github.com/MunifTanjim/nui.nvim)
- **notify**: powered by [nvim-notify](https://github.com/rcarriga/nvim-notify)
- **virtualtext**: shows the message as virtualtext (for example for `search_count`)
- **mini**: similar to [notifier.nvim](https://github.com/vigoux/notifier.nvim) & [fidget.nvim](https://github.com/j-hui/fidget.nvim)
- **notify_send**: generate a desktop notification
A **View** (`config.views`) is a combination of a `backend` and options.
**Noice** comes with the following built-in views with sane defaults:
| View | Backend | Description |
| ------------------ | ---------- | ---------------------------------------------------------------------------------- |
| **notify** | `notify` | _nvim-notify_ with `level=nil`, `replace=false`, `merge=false` |
| **split** | `split` | horizontal split |
| **vsplit** | `split` | vertical split |
| **popup** | `popup` | simple popup |
| **mini** | `mini` | minimal view, by default bottom right, right-aligned |
| **cmdline** | `popup` | bottom line, similar to the classic cmdline |
| **cmdline_popup** | `popup` | fancy cmdline popup, with different styles according to the cmdline mode |
| **cmdline_output** | `split` | split used by `config.presets.cmdline_output_to_split` |
| **messages** | `split` | split used for `:messages` |
| **confirm** | `popup` | popup used for `confirm` events |
| **hover** | `popup` | popup used for lsp signature help and hover |
| **popupmenu** | `nui.menu` | special view with the options used to render the popupmenu when backend is **nui** |
Please refer to [noice.config.views](https://github.com/folke/noice.nvim/blob/main/lua/noice/config/views.lua)
to see the options.
Any options passed to existing views in `config.views`, will override those options only.
You can configure completely new views and use them in custom routes.
<details>
<summary>Example:</summary>
```lua
-- override the default split view to always enter the split when it opens
require("noice").setup({
views = {
split = {
enter = true,
},
},
})
```
</details>
> All built-in Noice views have the filetype `noice`
### Nui Options
See the Nui documentation for [Popup](https://github.com/MunifTanjim/nui.nvim/tree/main/lua/nui/popup)
and [Split](https://github.com/MunifTanjim/nui.nvim/tree/main/lua/nui/split).
<table>
<tr><td>Option</td><td>Description</td></tr>
<tr>
<td> <b>size, position</b> </td>
<td>Size, position and their constituents can additionally be specified as <b>"auto"</b>, to use the message height and width.</td>
</tr>
<tr>
<td><b>win_options.winhighlight</b></td>
<td>
String or can also be a table like:
```lua
{
win_options = {
winhighlight = {
Normal = "NormalFloat",
FloatBorder = "FloatBorder"
},
}
}
```
</td>
</tr>
<td> <b>scrollbar</b> </td>
<td>Set to <code>false</code> to hide the scrollbar.</td>
</tr>
<tr>
</table>
### Notify Options
| Option | Type | Default | Description |
| ----------- | ---------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| **title** | `string` | `"Notification"` | title to be used for the notification. Uses `Message.title` if available. |
| **replace** | `boolean` | `false` | when true, messages routing to the same notify instance will replace existing messages instead of pushing a new notification every time |
| **merge** | `boolean` | `false` | Merge messages into one Notification or create separate notifications |
| **level** | `number\|string` | `nil` | notification level. Uses `Message.level` if available. |
### Virtual Text Options
Right now there's only an option to set the `hl_group` used to render the virtual text.
## 🎨 Formatting
Formatting options can be specified with `config.format`.
For a list of the defaults, please refer to [config.format](https://github.com/folke/noice.nvim/blob/main/lua/noice/config/format.lua)
**Noice** includes the following formatters:
- **level**: message level with optional `icon` and `hl_group` per level
- **text**: any text with optional `hl_group`
- **title**: message title with optional `hl_group`
- **event**: message event with optional `hl_group`
- **kind**: message kind with optional `hl_group`
- **date**: formatted date with optional date format string
- **message**: message content itself with optional `hl_group` to override message highlights
- **confirm**: only useful for `confirm` messages. Will format the choices as buttons.
- **cmdline**: will render the cmdline in the message that generated the message.
- **progress**: progress bar used by lsp progress
- **spinner**: spinners used by lsp progress
- **data**: render any custom data from `Message.opts`. Useful in combination with the opts passed to `vim.notify`
Formatters are used in `format` definitions. **Noice** includes the following built-in formats:
```lua
{
-- default format
default = { "{level} ", "{title} ", "{message}" },
-- default format for vim.notify views
notify = { "{message}" },
-- default format for the history
details = {
"{level} ",
"{date} ",
"{event}",
{ "{kind}", before = { ".", hl_group = "NoiceFormatKind" } },
" ",
"{title} ",
"{cmdline} ",
"{message}",
},
telescope = ..., -- formatter used to display telescope results
telescope_preview = ..., -- formatter used to preview telescope results
lsp_progress = ..., -- formatter used by lsp progress
lsp_progress_done = ..., -- formatter used by lsp progress
}
```
Text before/after the formatter or in the before/after options, will only be rendered if the formatter itself rendered something.
The `format` view option, can be either a `string` (one of the built-in formats), or a table with a custom format definition.
To align text, you can use the `align` option for a view. Can be `center`, `left` or `right`.
## 🚗 Routes
A **route** has a `filter`, `view` and optional `opts` attribute.
- **view**: one of the views (built-in or custom)
- **filter** a filter for messages matching this route
- **opts**: options for the view and the route
Route options can be any of the view options above, or one of:
| Option | Type | Default | Description |
| -------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **skip** | `boolean` | `false` | messages matching this filter will be skipped and not shown in any views |
| **stop** | `boolean` | `true` | When `false` and a route matches the filter, then other routes can still process the message too. Useful if you want certain messages to be shown in multiple views. |
Please refer to [noice.config.routes](https://github.com/folke/noice.nvim/blob/main/lua/noice/config/routes.lua)
for an overview of the default routes.
**Routes** passed to `setup()` will be prepended to the default routes.
<details>
<summary>Example</summary>
```lua
-- skip search_count messages instead of showing them as virtual text
require("noice").setup({
routes = {
{
filter = { event = "msg_show", kind = "search_count" },
opts = { skip = true },
},
},
})
-- always route any messages with more than 20 lines to the split view
require("noice").setup({
routes = {
{
view = "split",
filter = { event = "msg_show", min_height = 20 },
},
},
})
```
</details>
## 🚥 Statusline Components
**Noice** comes with the following statusline components:
- **ruler**
- **message**: last line of the last message (`event=show_msg`)
- **command**: `showcmd`
- **mode**: `showmode` (@recording messages)
- **search**: search count messages
See [noice.config.status](https://github.com/folke/noice.nvim/blob/main/lua/noice/config/status.lua) for the default config.
You can add custom statusline components in setup under the `status` key.
Statusline components have the following methods:
- **get**: gets the content of the message **without** highlights
- **get_hl**: gets the content of the message **with** highlights
- **has**: checks if the component is available
<details>
<summary>Example of configuring <a href="https://github.com/nvim-lualine/lualine.nvim">lualine.nvim</a></summary>
```lua
require("lualine").setup({
sections = {
lualine_x = {
{
require("noice").api.status.message.get_hl,
cond = require("noice").api.status.message.has,
},
{
require("noice").api.status.command.get,
cond = require("noice").api.status.command.has,
color = { fg = "#ff9e64" },
},
{
require("noice").api.status.mode.get,
cond = require("noice").api.status.mode.has,
color = { fg = "#ff9e64" },
},
{
require("noice").api.status.search.get,
cond = require("noice").api.status.search.has,
color = { fg = "#ff9e64" },
},
},
},
})
```
</details>
## 🔭 Telescope
In order to use **Noice** in **Telescope**, you can either do `:Noice telescope`,
or register the extension and use `:Telescope noice`.
The results panel is formatted using `config.format.formatters.telescope`. The preview is formatted with `config.format.formatters.telescope_preview`
```lua
require("telescope").load_extension("noice")
```
## 🚀 Usage
- `:Noice` or `:Noice history` shows the message history
- `:Noice last` shows the last message in a popup
- `:Noice dismiss` dismiss all visible messages
- `:Noice errors` shows the error messages in a split. Last errors on top
- `:Noice disable` disables **Noice**
- `:Noice enable` enables **Noice**
- `:Noice stats` shows debugging stats
- `:Noice telescope` opens message history in Telescope
Alternatively, all commands also exist as a full name like `:NoiceLast`, `:NoiceDisable`.
You can also use `Lua` equivalents.
```lua
vim.keymap.set("n", "<leader>nl", function()
require("noice").cmd("last")
end)
vim.keymap.set("n", "<leader>nh", function()
require("noice").cmd("history")
end)
```
> You can add custom commands with `config.commands`
### ↪️ Command Redirection
Sometimes it's useful to redirect the messages generated by a command or function
to a different view. That can be easily achieved with command redirection.
The redirect API can taken an optional `routes` parameter, which defaults to `{config.redirect}`.
```lua
-- redirect ":hi"
require("noice").redirect("hi")
-- redirect some function
require("noice").redirect(function()
print("test")
end)
```
Adding the following keymap, will redirect the active cmdline when pressing `<S-Enter>`.
The cmdline stays open, so you can change the command and execute it again.
When exiting the cmdline, the popup window will be focused.
```lua
vim.keymap.set("c", "<S-Enter>", function()
require("noice").redirect(vim.fn.getcmdline())
end, { desc = "Redirect Cmdline" })
```
### Lsp Hover Doc Scrolling
```lua
vim.keymap.set({ "n", "i", "s" }, "<c-f>", function()
if not require("noice.lsp").scroll(4) then
return "<c-f>"
end
end, { silent = true, expr = true })
vim.keymap.set({ "n", "i", "s" }, "<c-b>", function()
if not require("noice.lsp").scroll(-4) then
return "<c-b>"
end
end, { silent = true, expr = true })
```
## 🌈 Highlight Groups
<details>
<summary>Click to see all highlight groups</summary>
<!-- hl_start -->
| Highlight Group | Default Group | Description |
| -------------------------------------- | -------------------------------- | -------------------------------------------------- |
| **NoiceCmdline** | _MsgArea_ | Normal for the classic cmdline area at the bottom" |
| **NoiceCmdlineIcon** | _DiagnosticSignInfo_ | Cmdline icon |
| **NoiceCmdlineIconCalculator** | _NoiceCmdlineIcon_ | |
| **NoiceCmdlineIconCmdline** | _NoiceCmdlineIcon_ | |
| **NoiceCmdlineIconFilter** | _NoiceCmdlineIcon_ | |
| **NoiceCmdlineIconHelp** | _NoiceCmdlineIcon_ | |
| **NoiceCmdlineIconIncRename** | _NoiceCmdlineIcon_ | |
| **NoiceCmdlineIconInput** | _NoiceCmdlineIcon_ | |
| **NoiceCmdlineIconLua** | _NoiceCmdlineIcon_ | |
| **NoiceCmdlineIconSearch** | _DiagnosticSignWarn_ | Cmdline search icon (`/` and `?`) |
| **NoiceCmdlinePopup** | _Normal_ | Normal for the cmdline popup |
| **NoiceCmdlinePopupBorder** | _DiagnosticSignInfo_ | Cmdline popup border |
| **NoiceCmdlinePopupBorderCalculator** | _NoiceCmdlinePopupBorder_ | |
| **NoiceCmdlinePopupBorderCmdline** | _NoiceCmdlinePopupBorder_ | |
| **NoiceCmdlinePopupBorderFilter** | _NoiceCmdlinePopupBorder_ | |
| **NoiceCmdlinePopupBorderHelp** | _NoiceCmdlinePopupBorder_ | |
| **NoiceCmdlinePopupBorderIncRename** | _NoiceCmdlinePopupBorder_ | |
| **NoiceCmdlinePopupBorderInput** | _NoiceCmdlinePopupBorder_ | |
| **NoiceCmdlinePopupBorderLua** | _NoiceCmdlinePopupBorder_ | |
| **NoiceCmdlinePopupBorderSearch** | _DiagnosticSignWarn_ | Cmdline popup border for search |
| **NoiceCmdlinePopupTitle** | _DiagnosticSignInfo_ | Cmdline popup border |
| **NoiceCmdlinePrompt** | _Title_ | prompt for input() |
| **NoiceCompletionItemKindClass** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindColor** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindConstant** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindConstructor** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindDefault** | _Special_ | |
| **NoiceCompletionItemKindEnum** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindEnumMember** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindField** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindFile** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindFolder** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindFunction** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindInterface** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindKeyword** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindMethod** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindModule** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindProperty** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindSnippet** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindStruct** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindText** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindUnit** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindValue** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemKindVariable** | _NoiceCompletionItemKindDefault_ | |
| **NoiceCompletionItemMenu** | _none_ | Normal for the popupmenu |
| **NoiceCompletionItemWord** | _none_ | Normal for the popupmenu |
| **NoiceConfirm** | _Normal_ | Normal for the confirm view |
| **NoiceConfirmBorder** | _DiagnosticSignInfo_ | Border for the confirm view |
| **NoiceCursor** | _Cursor_ | Fake Cursor |
| **NoiceFormatConfirm** | _CursorLine_ | |
| **NoiceFormatConfirmDefault** | _Visual_ | |
| **NoiceFormatDate** | _Special_ | |
| **NoiceFormatEvent** | _NonText_ | |
| **NoiceFormatKind** | _NonText_ | |
| **NoiceFormatLevelDebug** | _NonText_ | |
| **NoiceFormatLevelError** | _DiagnosticVirtualTextError_ | |
| **NoiceFormatLevelInfo** | _DiagnosticVirtualTextInfo_ | |
| **NoiceFormatLevelOff** | _NonText_ | |
| **NoiceFormatLevelTrace** | _NonText_ | |
| **NoiceFormatLevelWarn** | _DiagnosticVirtualTextWarn_ | |
| **NoiceFormatProgressDone** | _Search_ | Progress bar done |
| **NoiceFormatProgressTodo** | _CursorLine_ | progress bar todo |
| **NoiceFormatTitle** | _Title_ | |
| **NoiceLspProgressClient** | _Title_ | Lsp progress client name |
| **NoiceLspProgressSpinner** | _Constant_ | Lsp progress spinner |
| **NoiceLspProgressTitle** | _NonText_ | Lsp progress title |
| **NoiceMini** | _MsgArea_ | Normal for mini view |
| **NoicePopup** | _NormalFloat_ | Normal for popup views |
| **NoicePopupBorder** | _FloatBorder_ | Border for popup views |
| **NoicePopupmenu** | _Pmenu_ | Normal for the popupmenu |
| **NoicePopupmenuBorder** | _FloatBorder_ | Popupmenu border |
| **NoicePopupmenuMatch** | _Special_ | Part of the item that matches the input |
| **NoicePopupmenuSelected** | _PmenuSel_ | Selected item in the popupmenu |
| **NoiceScrollbar** | _PmenuSbar_ | Normal for scrollbar |
| **NoiceScrollbarThumb** | _PmenuThumb_ | Scrollbar thumb |
| **NoiceSplit** | _NormalFloat_ | Normal for split views |
| **NoiceSplitBorder** | _FloatBorder_ | Border for split views |
| **NoiceVirtualText** | _DiagnosticVirtualTextInfo_ | Default hl group for virtualtext views |
<!-- hl_end -->
</details>

View File

@ -0,0 +1,975 @@
*noice.nvim.txt* For Neovim >= 0.8.0 Last change: 2024 May 23
==============================================================================
Table of Contents *noice.nvim-table-of-contents*
1. Noice (Nice, Noise, Notice) |noice.nvim-noice-(nice,-noise,-notice)|
- Features |noice.nvim-noice-(nice,-noise,-notice)-features|
- Status |noice.nvim-noice-(nice,-noise,-notice)-status|
- Requirements |noice.nvim-noice-(nice,-noise,-notice)-requirements|
- Installation |noice.nvim-noice-(nice,-noise,-notice)-installation|
- Configuration |noice.nvim-noice-(nice,-noise,-notice)-configuration|
- Filters |noice.nvim-noice-(nice,-noise,-notice)-filters|
- Views |noice.nvim-noice-(nice,-noise,-notice)-views|
- Formatting |noice.nvim-noice-(nice,-noise,-notice)-formatting|
- Routes |noice.nvim-noice-(nice,-noise,-notice)-routes|
- Statusline Components|noice.nvim-noice-(nice,-noise,-notice)-statusline-components|
- Telescope |noice.nvim-noice-(nice,-noise,-notice)-telescope|
- Usage |noice.nvim-noice-(nice,-noise,-notice)-usage|
- Highlight Groups |noice.nvim-noice-(nice,-noise,-notice)-highlight-groups|
2. Links |noice.nvim-links|
==============================================================================
1. Noice (Nice, Noise, Notice) *noice.nvim-noice-(nice,-noise,-notice)*
Highly experimental plugin that completely replaces the UI for `messages`,
`cmdline` and the `popupmenu`.
FEATURES *noice.nvim-noice-(nice,-noise,-notice)-features*
- fully **configurable views** like nvim-notify <https://github.com/rcarriga/nvim-notify>,
splits, popups, virtual text, ..
- use **filters** to **route messages** to different views
- message **highlights** are preserved in the views (like the colors of `:hi`)
- command output like |:messages|
is shown in normal buffers, which makes it much easier to work with
- `:Noice` command to show a full message history
- no more |:h more-prompt|
- fully customizable **cmdline** with icons
- **syntax highlighting** for `vim` and `lua` on the **cmdline**
- **statusline** components
- open message history in telescope.nvim <https://github.com/nvim-telescope/telescope.nvim>
STATUS *noice.nvim-noice-(nice,-noise,-notice)-status*
**Noice** is using the new experimental `vim.ui_attach` API, so issues are to
be expected. It is highly recommended to use Neovim nightly, since a bunch of
issues have already been fixed upstream. Check this tracking issue
<https://github.com/folke/noice.nvim/issues/6> for a list of known issues.
REQUIREMENTS *noice.nvim-noice-(nice,-noise,-notice)-requirements*
- Neovim >= 0.9.0 **(nightly highly recommended)**
- nui.nvim <https://github.com/MunifTanjim/nui.nvim>used for proper rendering and multiple views
- nvim-notify <https://github.com/rcarriga/nvim-notify>notification view _(optional)_
- a Nerd Font <https://www.nerdfonts.com/> **(optional)**
- nvim-treesitter <https://github.com/nvim-treesitter/nvim-treesitter/> **(optional, but highly recommended)**
used for highlighting the cmdline and lsp docs. Make sure to install the parsers for
`vim`, `regex`, `lua`, `bash`, `markdown` and `markdown_inline`
INSTALLATION *noice.nvim-noice-(nice,-noise,-notice)-installation*
Install the plugin with your preferred package manager:
>lua
-- lazy.nvim
{
"folke/noice.nvim",
event = "VeryLazy",
opts = {
-- add any options here
},
dependencies = {
-- if you lazy-load any plugin below, make sure to add proper `module="..."` entries
"MunifTanjim/nui.nvim",
-- OPTIONAL:
-- `nvim-notify` is only needed, if you want to use the notification view.
-- If not available, we use `mini` as the fallback
"rcarriga/nvim-notify",
}
}
<
Suggested setup:
>lua
require("noice").setup({
lsp = {
-- override markdown rendering so that **cmp** and other plugins use **Treesitter**
override = {
["vim.lsp.util.convert_input_to_markdown_lines"] = true,
["vim.lsp.util.stylize_markdown"] = true,
["cmp.entry.get_documentation"] = true, -- requires hrsh7th/nvim-cmp
},
},
-- you can enable a preset for easier configuration
presets = {
bottom_search = true, -- use a classic bottom cmdline for search
command_palette = true, -- position the cmdline and popupmenu together
long_message_to_split = true, -- long messages will be sent to a split
inc_rename = false, -- enables an input dialog for inc-rename.nvim
lsp_doc_border = false, -- add a border to hover docs and signature help
},
})
<
Its a good idea to run `:checkhealth noice` after installing to check for
common issues.
vim-plug ~
>vim
" vim-plug
call plug#begin()
Plug 'folke/noice.nvim'
Plug 'MunifTanjim/nui.nvim'
call plug#end()
lua require("noice").setup()
<
CONFIGURATION *noice.nvim-noice-(nice,-noise,-notice)-configuration*
**noice.nvim** comes with the following defaults:
Check the wiki <https://github.com/folke/noice.nvim/wiki/Configuration-Recipes>
for configuration recipes.
>lua
{
cmdline = {
enabled = true, -- enables the Noice cmdline UI
view = "cmdline_popup", -- view for rendering the cmdline. Change to `cmdline` to get a classic cmdline at the bottom
opts = {}, -- global options for the cmdline. See section on views
---@type table<string, CmdlineFormat>
format = {
-- conceal: (default=true) This will hide the text in the cmdline that matches the pattern.
-- view: (default is cmdline view)
-- opts: any options passed to the view
-- icon_hl_group: optional hl_group for the icon
-- title: set to anything or empty string to hide
cmdline = { pattern = "^:", icon = "", lang = "vim" },
search_down = { kind = "search", pattern = "^/", icon = " ", lang = "regex" },
search_up = { kind = "search", pattern = "^%?", icon = " ", lang = "regex" },
filter = { pattern = "^:%s*!", icon = "$", lang = "bash" },
lua = { pattern = { "^:%s*lua%s+", "^:%s*lua%s*=%s*", "^:%s*=%s*" }, icon = "", lang = "lua" },
help = { pattern = "^:%s*he?l?p?%s+", icon = "" },
input = {}, -- Used by input()
-- lua = false, -- to disable a format, set to `false`
},
},
messages = {
-- NOTE: If you enable messages, then the cmdline is enabled automatically.
-- This is a current Neovim limitation.
enabled = true, -- enables the Noice messages UI
view = "notify", -- default view for messages
view_error = "notify", -- view for errors
view_warn = "notify", -- view for warnings
view_history = "messages", -- view for :messages
view_search = "virtualtext", -- view for search count messages. Set to `false` to disable
},
popupmenu = {
enabled = true, -- enables the Noice popupmenu UI
---@type 'nui'|'cmp'
backend = "nui", -- backend to use to show regular cmdline completions
---@type NoicePopupmenuItemKind|false
-- Icons for completion item kinds (see defaults at noice.config.icons.kinds)
kind_icons = {}, -- set to `false` to disable icons
},
-- default options for require('noice').redirect
-- see the section on Command Redirection
---@type NoiceRouteConfig
redirect = {
view = "popup",
filter = { event = "msg_show" },
},
-- You can add any custom commands below that will be available with `:Noice command`
---@type table<string, NoiceCommand>
commands = {
history = {
-- options for the message history that you get with `:Noice`
view = "split",
opts = { enter = true, format = "details" },
filter = {
any = {
{ event = "notify" },
{ error = true },
{ warning = true },
{ event = "msg_show", kind = { "" } },
{ event = "lsp", kind = "message" },
},
},
},
-- :Noice last
last = {
view = "popup",
opts = { enter = true, format = "details" },
filter = {
any = {
{ event = "notify" },
{ error = true },
{ warning = true },
{ event = "msg_show", kind = { "" } },
{ event = "lsp", kind = "message" },
},
},
filter_opts = { count = 1 },
},
-- :Noice errors
errors = {
-- options for the message history that you get with `:Noice`
view = "popup",
opts = { enter = true, format = "details" },
filter = { error = true },
filter_opts = { reverse = true },
},
all = {
-- options for the message history that you get with `:Noice`
view = "split",
opts = { enter = true, format = "details" },
filter = {},
},
},
notify = {
-- Noice can be used as `vim.notify` so you can route any notification like other messages
-- Notification messages have their level and other properties set.
-- event is always "notify" and kind can be any log level as a string
-- The default routes will forward notifications to nvim-notify
-- Benefit of using Noice for this is the routing and consistent history view
enabled = true,
view = "notify",
},
lsp = {
progress = {
enabled = true,
-- Lsp Progress is formatted using the builtins for lsp_progress. See config.format.builtin
-- See the section on formatting for more details on how to customize.
--- @type NoiceFormat|string
format = "lsp_progress",
--- @type NoiceFormat|string
format_done = "lsp_progress_done",
throttle = 1000 / 30, -- frequency to update lsp progress message
view = "mini",
},
override = {
-- override the default lsp markdown formatter with Noice
["vim.lsp.util.convert_input_to_markdown_lines"] = false,
-- override the lsp markdown formatter with Noice
["vim.lsp.util.stylize_markdown"] = false,
-- override cmp documentation with Noice (needs the other options to work)
["cmp.entry.get_documentation"] = false,
},
hover = {
enabled = true,
silent = false, -- set to true to not show a message if hover is not available
view = nil, -- when nil, use defaults from documentation
---@type NoiceViewOptions
opts = {}, -- merged with defaults from documentation
},
signature = {
enabled = true,
auto_open = {
enabled = true,
trigger = true, -- Automatically show signature help when typing a trigger character from the LSP
luasnip = true, -- Will open signature help when jumping to Luasnip insert nodes
throttle = 50, -- Debounce lsp signature help request by 50ms
},
view = nil, -- when nil, use defaults from documentation
---@type NoiceViewOptions
opts = {}, -- merged with defaults from documentation
},
message = {
-- Messages shown by lsp servers
enabled = true,
view = "notify",
opts = {},
},
-- defaults for hover and signature help
documentation = {
view = "hover",
---@type NoiceViewOptions
opts = {
lang = "markdown",
replace = true,
render = "plain",
format = { "{message}" },
win_options = { concealcursor = "n", conceallevel = 3 },
},
},
},
markdown = {
hover = {
["|(%S-)|"] = vim.cmd.help, -- vim help links
["%[.-%]%((%S-)%)"] = require("noice.util").open, -- markdown links
},
highlights = {
["|%S-|"] = "@text.reference",
["@%S+"] = "@parameter",
["^%s*(Parameters:)"] = "@text.title",
["^%s*(Return:)"] = "@text.title",
["^%s*(See also:)"] = "@text.title",
["{%S-}"] = "@parameter",
},
},
health = {
checker = true, -- Disable if you don't want health checks to run
},
smart_move = {
-- noice tries to move out of the way of existing floating windows.
enabled = true, -- you can disable this behaviour here
-- add any filetypes here, that shouldn't trigger smart move.
excluded_filetypes = { "cmp_menu", "cmp_docs", "notify" },
},
---@type NoicePresets
presets = {
-- you can enable a preset by setting it to true, or a table that will override the preset config
-- you can also add custom presets that you can enable/disable with enabled=true
bottom_search = false, -- use a classic bottom cmdline for search
command_palette = false, -- position the cmdline and popupmenu together
long_message_to_split = false, -- long messages will be sent to a split
inc_rename = false, -- enables an input dialog for inc-rename.nvim
lsp_doc_border = false, -- add a border to hover docs and signature help
},
throttle = 1000 / 30, -- how frequently does Noice need to check for ui updates? This has no effect when in blocking mode.
---@type NoiceConfigViews
views = {}, ---@see section on views
---@type NoiceRouteConfig[]
routes = {}, --- @see section on routes
---@type table<string, NoiceFilter>
status = {}, --- @see section on statusline components
---@type NoiceFormatOptions
format = {}, --- @see section on formatting
}
<
If you dont want to use a Nerd Font, you can replace the icons with Unicode symbols. ~
>lua
require("noice").setup({
cmdline = {
format = {
cmdline = { icon = ">" },
search_down = { icon = "🔍⌄" },
search_up = { icon = "🔍⌃" },
filter = { icon = "$" },
lua = { icon = "☾" },
help = { icon = "?" },
},
},
format = {
level = {
icons = {
error = "✖",
warn = "▼",
info = "●",
},
},
},
popupmenu = {
kind_icons = false,
},
inc_rename = {
cmdline = {
format = {
IncRename = { icon = "⟳" },
},
},
},
})
<
FILTERS *noice.nvim-noice-(nice,-noise,-notice)-filters*
**Noice** uses filters to route messages to specific views.
------------------------------------------------------------------------------
Name Type Description
------------ ---------- ------------------------------------------------------
any filter[] checks that at least one of the filters matches
blocking boolean are we in blocking mode?
cleared boolean checks if the message is cleared, meaning its in the
history
cmdline boolean or checks if the message was generated by executing a
string cmdline. When string, then it is used as a pattern
error boolean all error-like kinds from ext_messages
event string or any of the events from ext_messages or cmdline. See :h
string[] ui-messages
find string uses lua string.find to match the pattern
has boolean checks if the message is exists, meaning its in the
history
kind string or any of the kinds from ext_messages. See :h ui-messages
string[]
max_height number maximum height of the message
max_length number maximum length of the message (total width of all the
lines)
max_width number maximum width of the message
min_height number minimum height of the message
min_length number minimum length of the message (total width of all the
lines)
min_width number minimum width of the message
mode string checks if vim.api.nvim_get_mode() contains the given
mode
not filter checks whether the filter matches or not
warning boolean all warning-like kinds from ext_messages
------------------------------------------------------------------------------
Example: ~
>lua
-- all messages over 10 lines, excluding echo and search_count
local filter = {
event = "msg_show",
min_height = 10,
["not"] = { kind = { "search_count", "echo" } },
}
<
VIEWS *noice.nvim-noice-(nice,-noise,-notice)-views*
**Noice** comes with the following built-in backends:
- **popup**powered by nui.nvim <https://github.com/MunifTanjim/nui.nvim>
- **split**powered by nui.nvim <https://github.com/MunifTanjim/nui.nvim>
- **notify**powered by nvim-notify <https://github.com/rcarriga/nvim-notify>
- **virtualtext**shows the message as virtualtext (for example for `search_count`)
- **mini**similar to notifier.nvim <https://github.com/vigoux/notifier.nvim> & fidget.nvim <https://github.com/j-hui/fidget.nvim>
- **notify_send**generate a desktop notification
A **View** (`config.views`) is a combination of a `backend` and options.
**Noice** comes with the following built-in views with sane defaults:
--------------------------------------------------------------------------------
View Backend Description
---------------- ---------- ----------------------------------------------------
notify notify nvim-notify with level=nil, replace=false,
merge=false
split split horizontal split
vsplit split vertical split
popup popup simple popup
mini mini minimal view, by default bottom right, right-aligned
cmdline popup bottom line, similar to the classic cmdline
cmdline_popup popup fancy cmdline popup, with different styles according
to the cmdline mode
cmdline_output split split used by config.presets.cmdline_output_to_split
messages split split used for :messages
confirm popup popup used for confirm events
hover popup popup used for lsp signature help and hover
popupmenu nui.menu special view with the options used to render the
popupmenu when backend is nui
--------------------------------------------------------------------------------
Please refer to noice.config.views
<https://github.com/folke/noice.nvim/blob/main/lua/noice/config/views.lua> to
see the options. Any options passed to existing views in `config.views`, will
override those options only. You can configure completely new views and use
them in custom routes.
Example: ~
>lua
-- override the default split view to always enter the split when it opens
require("noice").setup({
views = {
split = {
enter = true,
},
},
})
<
All built-in Noice views have the filetype `noice`
NUI OPTIONS ~
See the Nui documentation for Popup
<https://github.com/MunifTanjim/nui.nvim/tree/main/lua/nui/popup> and Split
<https://github.com/MunifTanjim/nui.nvim/tree/main/lua/nui/split>.
OptionDescriptionsize, positionSize, position and their constituents can additionally be specified as "auto", to use the message height and width.win_options.winhighlightString or can also be a table like:
>lua
{
win_options = {
winhighlight = {
Normal = "NormalFloat",
FloatBorder = "FloatBorder"
},
}
}
<
scrollbarSet to false to hide the scrollbar.
NOTIFY OPTIONS ~
-------------------------------------------------------------------------------------------------
Option Type Default Description
--------- ---------------- ---------------- -----------------------------------------------------
title string "Notification" title to be used for the notification. Uses
Message.title if available.
replace boolean false when true, messages routing to the same notify
instance will replace existing messages instead of
pushing a new notification every time
merge boolean false Merge messages into one Notification or create
separate notifications
level number\|string nil notification level. Uses Message.level if available.
-------------------------------------------------------------------------------------------------
VIRTUAL TEXT OPTIONS ~
Right now theres only an option to set the `hl_group` used to render the
virtual text.
FORMATTING *noice.nvim-noice-(nice,-noise,-notice)-formatting*
Formatting options can be specified with `config.format`. For a list of the
defaults, please refer to config.format
<https://github.com/folke/noice.nvim/blob/main/lua/noice/config/format.lua>
**Noice** includes the following formatters:
- **level**message level with optional `icon` and `hl_group` per level
- **text**any text with optional `hl_group`
- **title**message title with optional `hl_group`
- **event**message event with optional `hl_group`
- **kind**message kind with optional `hl_group`
- **date**formatted date with optional date format string
- **message**message content itself with optional `hl_group` to override message highlights
- **confirm**only useful for `confirm` messages. Will format the choices as buttons.
- **cmdline**will render the cmdline in the message that generated the message.
- **progress**progress bar used by lsp progress
- **spinner**spinners used by lsp progress
- **data**render any custom data from `Message.opts`. Useful in combination with the opts passed to `vim.notify`
Formatters are used in `format` definitions. **Noice** includes the following
built-in formats:
>lua
{
-- default format
default = { "{level} ", "{title} ", "{message}" },
-- default format for vim.notify views
notify = { "{message}" },
-- default format for the history
details = {
"{level} ",
"{date} ",
"{event}",
{ "{kind}", before = { ".", hl_group = "NoiceFormatKind" } },
" ",
"{title} ",
"{cmdline} ",
"{message}",
},
telescope = ..., -- formatter used to display telescope results
telescope_preview = ..., -- formatter used to preview telescope results
lsp_progress = ..., -- formatter used by lsp progress
lsp_progress_done = ..., -- formatter used by lsp progress
}
<
Text before/after the formatter or in the before/after options, will only be
rendered if the formatter itself rendered something.
The `format` view option, can be either a `string` (one of the built-in
formats), or a table with a custom format definition.
To align text, you can use the `align` option for a view. Can be `center`,
`left` or `right`.
ROUTES *noice.nvim-noice-(nice,-noise,-notice)-routes*
A **route** has a `filter`, `view` and optional `opts` attribute.
- **view**one of the views (built-in or custom)
- **filter** a filter for messages matching this route
- **opts**options for the view and the route
Route options can be any of the view options above, or one of:
------------------------------------------------------------------------------------------
Option Type Default Description
-------- --------- --------- -------------------------------------------------------------
skip boolean false messages matching this filter will be skipped and not shown
in any views
stop boolean true When false and a route matches the filter, then other routes
can still process the message too. Useful if you want certain
messages to be shown in multiple views.
------------------------------------------------------------------------------------------
Please refer to noice.config.routes
<https://github.com/folke/noice.nvim/blob/main/lua/noice/config/routes.lua> for
an overview of the default routes. **Routes** passed to `setup()` will be
prepended to the default routes.
Example ~
>lua
-- skip search_count messages instead of showing them as virtual text
require("noice").setup({
routes = {
{
filter = { event = "msg_show", kind = "search_count" },
opts = { skip = true },
},
},
})
-- always route any messages with more than 20 lines to the split view
require("noice").setup({
routes = {
{
view = "split",
filter = { event = "msg_show", min_height = 20 },
},
},
})
<
STATUSLINE COMPONENTS*noice.nvim-noice-(nice,-noise,-notice)-statusline-components*
**Noice** comes with the following statusline components:
- **ruler**
- **message**last line of the last message (`event=show_msg`)
- **command**`showcmd`
- **mode**`showmode` (@recording messages)
- **search**search count messages
See noice.config.status
<https://github.com/folke/noice.nvim/blob/main/lua/noice/config/status.lua> for
the default config.
You can add custom statusline components in setup under the `status` key.
Statusline components have the following methods:
- **get**gets the content of the message **without** highlights
- **get_hl**gets the content of the message **with** highlights
- **has**checks if the component is available
Example of configuring lualine.nvim ~
>lua
require("lualine").setup({
sections = {
lualine_x = {
{
require("noice").api.status.message.get_hl,
cond = require("noice").api.status.message.has,
},
{
require("noice").api.status.command.get,
cond = require("noice").api.status.command.has,
color = { fg = "#ff9e64" },
},
{
require("noice").api.status.mode.get,
cond = require("noice").api.status.mode.has,
color = { fg = "#ff9e64" },
},
{
require("noice").api.status.search.get,
cond = require("noice").api.status.search.has,
color = { fg = "#ff9e64" },
},
},
},
})
<
TELESCOPE *noice.nvim-noice-(nice,-noise,-notice)-telescope*
In order to use **Noice** in **Telescope**, you can either do `:Noice
telescope`, or register the extension and use `:Telescope noice`. The results
panel is formatted using `config.format.formatters.telescope`. The preview is
formatted with `config.format.formatters.telescope_preview`
>lua
require("telescope").load_extension("noice")
<
USAGE *noice.nvim-noice-(nice,-noise,-notice)-usage*
- `:Noice` or `:Noice history` shows the message history
- `:Noice last` shows the last message in a popup
- `:Noice dismiss` dismiss all visible messages
- `:Noice errors` shows the error messages in a split. Last errors on top
- `:Noice disable` disables **Noice**
- `:Noice enable` enables **Noice**
- `:Noice stats` shows debugging stats
- `:Noice telescope` opens message history in Telescope
Alternatively, all commands also exist as a full name like `:NoiceLast`,
`:NoiceDisable`.
You can also use `Lua` equivalents.
>lua
vim.keymap.set("n", "<leader>nl", function()
require("noice").cmd("last")
end)
vim.keymap.set("n", "<leader>nh", function()
require("noice").cmd("history")
end)
<
You can add custom commands with `config.commands`
COMMAND REDIRECTION ~
Sometimes its useful to redirect the messages generated by a command or
function to a different view. That can be easily achieved with command
redirection.
The redirect API can taken an optional `routes` parameter, which defaults to
`{config.redirect}`.
>lua
-- redirect ":hi"
require("noice").redirect("hi")
-- redirect some function
require("noice").redirect(function()
print("test")
end)
<
Adding the following keymap, will redirect the active cmdline when pressing
`<S-Enter>`. The cmdline stays open, so you can change the command and execute
it again. When exiting the cmdline, the popup window will be focused.
>lua
vim.keymap.set("c", "<S-Enter>", function()
require("noice").redirect(vim.fn.getcmdline())
end, { desc = "Redirect Cmdline" })
<
LSP HOVER DOC SCROLLING ~
>lua
vim.keymap.set({ "n", "i", "s" }, "<c-f>", function()
if not require("noice.lsp").scroll(4) then
return "<c-f>"
end
end, { silent = true, expr = true })
vim.keymap.set({ "n", "i", "s" }, "<c-b>", function()
if not require("noice.lsp").scroll(-4) then
return "<c-b>"
end
end, { silent = true, expr = true })
<
HIGHLIGHT GROUPS *noice.nvim-noice-(nice,-noise,-notice)-highlight-groups*
Click to see all highlight groups ~
---------------------------------------------------------------------------------------------------
Highlight Group Default Group Description
------------------------------------ -------------------------------- -----------------------------
NoiceCmdline MsgArea Normal for the classic
cmdline area at the bottom”
NoiceCmdlineIcon DiagnosticSignInfo Cmdline icon
NoiceCmdlineIconCalculator NoiceCmdlineIcon
NoiceCmdlineIconCmdline NoiceCmdlineIcon
NoiceCmdlineIconFilter NoiceCmdlineIcon
NoiceCmdlineIconHelp NoiceCmdlineIcon
NoiceCmdlineIconIncRename NoiceCmdlineIcon
NoiceCmdlineIconInput NoiceCmdlineIcon
NoiceCmdlineIconLua NoiceCmdlineIcon
NoiceCmdlineIconSearch DiagnosticSignWarn Cmdline search icon (/ and ?)
NoiceCmdlinePopup Normal Normal for the cmdline popup
NoiceCmdlinePopupBorder DiagnosticSignInfo Cmdline popup border
NoiceCmdlinePopupBorderCalculator NoiceCmdlinePopupBorder
NoiceCmdlinePopupBorderCmdline NoiceCmdlinePopupBorder
NoiceCmdlinePopupBorderFilter NoiceCmdlinePopupBorder
NoiceCmdlinePopupBorderHelp NoiceCmdlinePopupBorder
NoiceCmdlinePopupBorderIncRename NoiceCmdlinePopupBorder
NoiceCmdlinePopupBorderInput NoiceCmdlinePopupBorder
NoiceCmdlinePopupBorderLua NoiceCmdlinePopupBorder
NoiceCmdlinePopupBorderSearch DiagnosticSignWarn Cmdline popup border for
search
NoiceCmdlinePopupTitle DiagnosticSignInfo Cmdline popup border
NoiceCmdlinePrompt Title prompt for input()
NoiceCompletionItemKindClass NoiceCompletionItemKindDefault
NoiceCompletionItemKindColor NoiceCompletionItemKindDefault
NoiceCompletionItemKindConstant NoiceCompletionItemKindDefault
NoiceCompletionItemKindConstructor NoiceCompletionItemKindDefault
NoiceCompletionItemKindDefault Special
NoiceCompletionItemKindEnum NoiceCompletionItemKindDefault
NoiceCompletionItemKindEnumMember NoiceCompletionItemKindDefault
NoiceCompletionItemKindField NoiceCompletionItemKindDefault
NoiceCompletionItemKindFile NoiceCompletionItemKindDefault
NoiceCompletionItemKindFolder NoiceCompletionItemKindDefault
NoiceCompletionItemKindFunction NoiceCompletionItemKindDefault
NoiceCompletionItemKindInterface NoiceCompletionItemKindDefault
NoiceCompletionItemKindKeyword NoiceCompletionItemKindDefault
NoiceCompletionItemKindMethod NoiceCompletionItemKindDefault
NoiceCompletionItemKindModule NoiceCompletionItemKindDefault
NoiceCompletionItemKindProperty NoiceCompletionItemKindDefault
NoiceCompletionItemKindSnippet NoiceCompletionItemKindDefault
NoiceCompletionItemKindStruct NoiceCompletionItemKindDefault
NoiceCompletionItemKindText NoiceCompletionItemKindDefault
NoiceCompletionItemKindUnit NoiceCompletionItemKindDefault
NoiceCompletionItemKindValue NoiceCompletionItemKindDefault
NoiceCompletionItemKindVariable NoiceCompletionItemKindDefault
NoiceCompletionItemMenu none Normal for the popupmenu
NoiceCompletionItemWord none Normal for the popupmenu
NoiceConfirm Normal Normal for the confirm view
NoiceConfirmBorder DiagnosticSignInfo Border for the confirm view
NoiceCursor Cursor Fake Cursor
NoiceFormatConfirm CursorLine
NoiceFormatConfirmDefault Visual
NoiceFormatDate Special
NoiceFormatEvent NonText
NoiceFormatKind NonText
NoiceFormatLevelDebug NonText
NoiceFormatLevelError DiagnosticVirtualTextError
NoiceFormatLevelInfo DiagnosticVirtualTextInfo
NoiceFormatLevelOff NonText
NoiceFormatLevelTrace NonText
NoiceFormatLevelWarn DiagnosticVirtualTextWarn
NoiceFormatProgressDone Search Progress bar done
NoiceFormatProgressTodo CursorLine progress bar todo
NoiceFormatTitle Title
NoiceLspProgressClient Title Lsp progress client name
NoiceLspProgressSpinner Constant Lsp progress spinner
NoiceLspProgressTitle NonText Lsp progress title
NoiceMini MsgArea Normal for mini view
NoicePopup NormalFloat Normal for popup views
NoicePopupBorder FloatBorder Border for popup views
NoicePopupmenu Pmenu Normal for the popupmenu
NoicePopupmenuBorder FloatBorder Popupmenu border
NoicePopupmenuMatch Special Part of the item that matches
the input
NoicePopupmenuSelected PmenuSel Selected item in the
popupmenu
NoiceScrollbar PmenuSbar Normal for scrollbar
NoiceScrollbarThumb PmenuThumb Scrollbar thumb
NoiceSplit NormalFloat Normal for split views
NoiceSplitBorder FloatBorder Border for split views
NoiceVirtualText DiagnosticVirtualTextInfo Default hl group for
virtualtext views
---------------------------------------------------------------------------------------------------
==============================================================================
2. Links *noice.nvim-links*
1. *image*: https://user-images.githubusercontent.com/292349/193263220-791847b2-516c-4f23-9802-31dd6bec5f6a.png
2. *@recording*:
Generated by panvimdoc <https://github.com/kdheepak/panvimdoc>
vim:tw=78:ts=8:noet:ft=help:norl:

View File

@ -0,0 +1,18 @@
local require = require("noice.util.lazy")
local Cmdline = require("noice.ui.cmdline")
local Status = require("noice.api.status")
local M = {}
M.status = Status
---@deprecated
M.statusline = Status
---@return CmdlinePosition?
function M.get_cmdline_position()
return Cmdline.position and vim.deepcopy(Cmdline.position)
end
return M

View File

@ -0,0 +1,66 @@
local require = require("noice.util.lazy")
local Manager = require("noice.message.manager")
local Config = require("noice.config")
---@type NoiceFilter
local nothing = { ["not"] = {} }
---@param str string
local function escape(str)
return str:gsub("%%", "%%%%")
end
---@param name string
---@return NoiceStatus
local function NoiceStatus(name)
local function _get()
if not Config.is_running() then
return
end
local filter = Config.options.status[name] or nothing
return Manager.get(filter, {
count = 1,
sort = true,
history = true,
})[1]
end
---@class NoiceStatus
return {
has = function()
return _get() ~= nil
end,
get = function()
local message = _get()
if message then
return escape(vim.trim(message:content()))
end
end,
get_hl = function()
local message = _get()
if message and message._lines[1] then
local ret = ""
local line = message._lines[#message._lines]
for _, text in ipairs(line._texts) do
if text.extmark and text.extmark.hl_group then
-- use hl_group
ret = ret .. "%#" .. text.extmark.hl_group .. "#" .. escape(text:content())
else
-- or reset to StatusLine
ret = ret .. "%#StatusLine#" .. escape(text:content())
end
end
return ret
end
end,
}
end
---@type table<string, NoiceStatus>
local status = {}
return setmetatable(status, {
__index = function(_, key)
return NoiceStatus(key)
end,
})

View File

@ -0,0 +1,110 @@
local require = require("noice.util.lazy")
local View = require("noice.view")
local Manager = require("noice.message.manager")
local Config = require("noice.config")
local Util = require("noice.util")
local Message = require("noice.message")
local Router = require("noice.message.router")
---@class NoiceCommand: NoiceRouteConfig
---@field filter_opts NoiceMessageOpts
local M = {}
---@type table<string, fun()>
M.commands = {}
---@param command NoiceCommand
function M.command(command)
return function()
local view = View.get_view(command.view, command.opts)
view:set(Manager.get(
command.filter,
vim.tbl_deep_extend("force", {
history = true,
sort = true,
}, command.filter_opts or {})
))
view:display()
end
end
function M.cmd(cmd)
if M.commands[cmd] then
M.commands[cmd]()
else
M.commands.history()
end
end
function M.setup()
M.commands = {
debug = function()
Config.options.debug = not Config.options.debug
end,
dismiss = function()
Router.dismiss()
end,
log = function()
vim.cmd.edit(Config.options.log)
end,
enable = function()
require("noice").enable()
end,
disable = function()
require("noice").disable()
end,
telescope = function()
require("telescope").extensions.noice.noice({})
end,
stats = function()
Manager.add(Util.stats.message())
end,
routes = function()
local message = Message("noice", "debug")
message:set(vim.inspect(Config.options.routes))
Manager.add(message)
end,
config = function()
local message = Message("noice", "debug")
message:set(vim.inspect(Config.options))
Manager.add(message)
end,
viewstats = function()
local message = Message("noice", "debug")
message:set(vim.inspect(require("noice.message.router").view_stats()))
Manager.add(message)
end,
}
for name, command in pairs(Config.options.commands) do
M.commands[name] = M.command(command)
end
vim.api.nvim_create_user_command("Noice", function(args)
local cmd = vim.trim(args.args or "")
M.cmd(cmd)
end, {
nargs = "?",
desc = "Noice",
complete = function(_, line)
if line:match("^%s*Noice %w+ ") then
return {}
end
local prefix = line:match("^%s*Noice (%w*)") or ""
return vim.tbl_filter(function(key)
return key:find(prefix) == 1
end, vim.tbl_keys(M.commands))
end,
})
for name in pairs(M.commands) do
local cmd = "Noice" .. name:sub(1, 1):upper() .. name:sub(2)
vim.api.nvim_create_user_command(cmd, function()
M.cmd(name)
end, { desc = "Noice " .. name })
end
end
return M

View File

@ -0,0 +1,54 @@
local require = require("noice.util.lazy")
local Config = require("noice.config")
local Highlights = require("noice.config.highlights")
local M = {}
function M.setup()
local formats = Config.options.cmdline.format
for name, format in pairs(formats) do
if format == false then
formats[name] = nil
else
local kind = format.kind or name
local kind_cc = kind:sub(1, 1):upper() .. kind:sub(2)
local hl_group_icon = "CmdlineIcon" .. kind_cc
Highlights.add(hl_group_icon, "NoiceCmdlineIcon")
local hl_group_border = "CmdlinePopupBorder" .. kind_cc
Highlights.add(hl_group_border, "NoiceCmdlinePopupBorder")
format = vim.tbl_deep_extend("force", {
conceal = format.conceal ~= false,
kind = kind,
icon_hl_group = "Noice" .. hl_group_icon,
view = Config.options.cmdline.view,
lang = format.lang or format.ft,
opts = {
---@diagnostic disable-next-line: undefined-field
border = {
text = {
top = format.title or (" " .. kind_cc .. " "),
},
},
win_options = {
winhighlight = {
FloatBorder = "Noice" .. hl_group_border,
},
},
},
}, { opts = vim.deepcopy(Config.options.cmdline.opts) }, format)
formats[name] = format
table.insert(Config.options.routes, {
view = format.view,
opts = format.opts,
filter = { event = "cmdline", kind = format.kind },
})
end
end
end
return M

View File

@ -0,0 +1,129 @@
local M = {}
---@type table<string, NoiceFormat>
M.builtin = {
default = { "{level} ", "{title} ", "{message}" },
notify = { "{message}" },
details = {
"{level} ",
"{date} ",
"{event}",
{ "{kind}", before = { ".", hl_group = "NoiceFormatKind" } },
" ",
"{title} ",
"{cmdline} ",
"{message}",
},
telescope = {
"{level} ",
"{date} ",
"{title} ",
"{message}",
},
telescope_preview = {
"{level} ",
"{date} ",
"{event}",
{ "{kind}", before = { ".", hl_group = "NoiceFormatKind" } },
"\n",
"{title}\n",
"\n",
"{message}",
},
lsp_progress = {
{
"{progress} ",
key = "progress.percentage",
contents = {
{ "{data.progress.message} " },
},
},
"({data.progress.percentage}%) ",
{ "{spinner} ", hl_group = "NoiceLspProgressSpinner" },
{ "{data.progress.title} ", hl_group = "NoiceLspProgressTitle" },
{ "{data.progress.client} ", hl_group = "NoiceLspProgressClient" },
},
lsp_progress_done = {
{ "", hl_group = "NoiceLspProgressSpinner" },
{ "{data.progress.title} ", hl_group = "NoiceLspProgressTitle" },
{ "{data.progress.client} ", hl_group = "NoiceLspProgressClient" },
},
}
---@class NoiceFormatOptions
M.defaults = {
---@class NoiceFormatOptions.debug
debug = {
enabled = true,
},
---@class NoiceFormatOptions.cmdline
cmdline = {},
---@class NoiceFormatOptions.level
level = {
hl_group = {
trace = "NoiceFormatLevelTrace",
debug = "NoiceFormatLevelDebug",
info = "NoiceFormatLevelInfo",
warn = "NoiceFormatLevelWarn",
error = "NoiceFormatLevelError",
off = "NoiceFormatLevelOff",
},
icons = { error = "", warn = "", info = "" },
},
---@class NoiceFormatOptions.progress
progress = {
---@type NoiceFormat
contents = {},
width = 20,
align = "right",
key = "progress", -- key in message.opts For example: "progress.percentage"
hl_group = "NoiceFormatProgressTodo",
hl_group_done = "NoiceFormatProgressDone",
},
---@class NoiceFormatOptions.text
text = {
text = nil,
hl_group = nil,
},
---@class NoiceFormatOptions.spinner
spinner = {
---@type Spinner
name = "dots",
hl_group = nil,
},
---@class NoiceFormatOptions.data
data = {
key = nil, -- Key in the message.opts object.
hl_group = nil, -- Optional hl_group
},
---@class NoiceFormatOptions.title
title = {
hl_group = "NoiceFormatTitle",
},
---@class NoiceFormatOptions.event
event = {
hl_group = "NoiceFormatEvent",
},
---@class NoiceFormatOptions.kind
kind = {
hl_group = "NoiceFormatKind",
},
---@class NoiceFormatOptions.date
date = {
format = "%X", --- @see https://www.lua.org/pil/22.1.html
hl_group = "NoiceFormatDate",
},
---@class NoiceFormatOptions.message
message = {
hl_group = nil, -- if set, then the hl_group will be used instead of the message highlights
},
---@class NoiceFormatOptions.confirm
confirm = {
hl_group = {
choice = "NoiceFormatConfirm",
default_choice = "NoiceFormatConfirmDefault",
},
},
}
return M

View File

@ -0,0 +1,145 @@
local require = require("noice.util.lazy")
local Util = require("noice.util")
-- Build docs with:
-- lua require("noice.config.highlights").docs()
local M = {}
M.defaults = {
Cmdline = "MsgArea", -- Normal for the classic cmdline area at the bottom"
CmdlineIcon = "DiagnosticSignInfo", -- Cmdline icon
CmdlineIconSearch = "DiagnosticSignWarn", -- Cmdline search icon (`/` and `?`)
CmdlinePrompt = "Title", -- prompt for input()
CmdlinePopup = "Normal", -- Normal for the cmdline popup
CmdlinePopupBorder = "DiagnosticSignInfo", -- Cmdline popup border
CmdlinePopupTitle = "DiagnosticSignInfo", -- Cmdline popup border
CmdlinePopupBorderSearch = "DiagnosticSignWarn", -- Cmdline popup border for search
Confirm = "Normal", -- Normal for the confirm view
ConfirmBorder = "DiagnosticSignInfo", -- Border for the confirm view
Cursor = "Cursor", -- Fake Cursor
Mini = "MsgArea", -- Normal for mini view
Popup = "NormalFloat", -- Normal for popup views
PopupBorder = "FloatBorder", -- Border for popup views
Popupmenu = "Pmenu", -- Normal for the popupmenu
PopupmenuBorder = "FloatBorder", -- Popupmenu border
PopupmenuMatch = "Special", -- Part of the item that matches the input
PopupmenuSelected = "PmenuSel", -- Selected item in the popupmenu
Scrollbar = "PmenuSbar", -- Normal for scrollbar
ScrollbarThumb = "PmenuThumb", -- Scrollbar thumb
Split = "NormalFloat", -- Normal for split views
SplitBorder = "FloatBorder", -- Border for split views
VirtualText = "DiagnosticVirtualTextInfo", -- Default hl group for virtualtext views
FormatProgressDone = "Search", -- Progress bar done
FormatProgressTodo = "CursorLine", -- progress bar todo
FormatEvent = "NonText",
FormatKind = "NonText",
FormatDate = "Special",
FormatConfirm = "CursorLine",
FormatConfirmDefault = "Visual",
FormatTitle = "Title",
FormatLevelDebug = "NonText",
FormatLevelTrace = "NonText",
FormatLevelOff = "NonText",
FormatLevelInfo = "DiagnosticVirtualTextInfo",
FormatLevelWarn = "DiagnosticVirtualTextWarn",
FormatLevelError = "DiagnosticVirtualTextError",
LspProgressSpinner = "Constant", -- Lsp progress spinner
LspProgressTitle = "NonText", -- Lsp progress title
LspProgressClient = "Title", -- Lsp progress client name
CompletionItemMenu = "none", -- Normal for the popupmenu
CompletionItemWord = "none", -- Normal for the popupmenu
CompletionItemKindDefault = "Special",
CompletionItemKindColor = "NoiceCompletionItemKindDefault",
CompletionItemKindFunction = "NoiceCompletionItemKindDefault",
CompletionItemKindClass = "NoiceCompletionItemKindDefault",
CompletionItemKindMethod = "NoiceCompletionItemKindDefault",
CompletionItemKindConstructor = "NoiceCompletionItemKindDefault",
CompletionItemKindInterface = "NoiceCompletionItemKindDefault",
CompletionItemKindModule = "NoiceCompletionItemKindDefault",
CompletionItemKindStruct = "NoiceCompletionItemKindDefault",
CompletionItemKindKeyword = "NoiceCompletionItemKindDefault",
CompletionItemKindValue = "NoiceCompletionItemKindDefault",
CompletionItemKindProperty = "NoiceCompletionItemKindDefault",
CompletionItemKindConstant = "NoiceCompletionItemKindDefault",
CompletionItemKindSnippet = "NoiceCompletionItemKindDefault",
CompletionItemKindFolder = "NoiceCompletionItemKindDefault",
CompletionItemKindText = "NoiceCompletionItemKindDefault",
CompletionItemKindEnumMember = "NoiceCompletionItemKindDefault",
CompletionItemKindUnit = "NoiceCompletionItemKindDefault",
CompletionItemKindField = "NoiceCompletionItemKindDefault",
CompletionItemKindFile = "NoiceCompletionItemKindDefault",
CompletionItemKindVariable = "NoiceCompletionItemKindDefault",
CompletionItemKindEnum = "NoiceCompletionItemKindDefault",
}
function M.add(hl_group, link)
if not M.defaults[hl_group] then
M.defaults[hl_group] = link
end
end
function M.get_link(hl_group)
local ok, opts = pcall(vim.api.nvim_get_hl_by_name, hl_group, true)
if opts then
opts[vim.type_idx] = nil
end
if not ok or vim.tbl_isempty(opts) then
opts = { link = hl_group }
end
opts.default = true
return opts
end
function M.setup()
for hl, link in pairs(M.defaults) do
if link ~= "none" then
local opts = { link = link, default = true }
if vim.tbl_contains({ "IncSearch", "Search" }, link) then
opts = M.get_link(link)
end
vim.api.nvim_set_hl(0, "Noice" .. hl, opts)
end
end
vim.api.nvim_set_hl(0, "NoiceHiddenCursor", { blend = 100, nocombine = true })
end
function M.docs()
local me = debug.getinfo(1, "S").source:sub(2)
---@type table<string,string>
local docs = {}
local lines = io.open(me, "r"):lines()
for line in lines do
---@type string, string
local hl, comment = line:match("%s*([a-zA-Z]+)%s*=.*%-%-%s*(.*)")
if hl then
docs[hl] = comment
end
end
local rows = {}
table.insert(rows, { "Highlight Group", "Default Group", "Description" })
table.insert(rows, { "---", "---", "---" })
Util.for_each(M.defaults, function(hl, link)
table.insert(rows, { "**Noice" .. hl .. "**", "_" .. link .. "_", docs[hl] or "" })
end)
local text = table.concat(
vim.tbl_map(function(row)
return "| " .. table.concat(row, " | ") .. " |"
end, rows),
"\n"
)
text = "<!-- hl_start -->\n" .. text .. "\n<!-- hl_end -->"
local readme = Util.read_file("README.md")
readme = readme:gsub("<%!%-%- hl_start %-%->.*<%!%-%- hl_end %-%->", text)
Util.write_file("README.md", readme)
end
return M

View File

@ -0,0 +1,28 @@
local M = {}
---@class NoicePopupmenuItemKind
M.kinds = {
Class = "",
Color = "",
Constant = "",
Constructor = "",
Enum = "",
EnumMember = "",
Field = "",
File = "",
Folder = "",
Function = "",
Interface = "",
Keyword = "",
Method = "ƒ ",
Module = "",
Property = "",
Snippet = "",
Struct = "",
Text = "",
Unit = "",
Value = "",
Variable = "",
}
return M

View File

@ -0,0 +1,301 @@
local require = require("noice.util.lazy")
local Routes = require("noice.config.routes")
local M = {}
M.ns = vim.api.nvim_create_namespace("noice")
function M.defaults()
---@class NoiceConfig
local defaults = {
cmdline = {
enabled = true, -- enables the Noice cmdline UI
view = "cmdline_popup", -- view for rendering the cmdline. Change to `cmdline` to get a classic cmdline at the bottom
opts = {}, -- global options for the cmdline. See section on views
---@type table<string, CmdlineFormat>
format = {
-- conceal: (default=true) This will hide the text in the cmdline that matches the pattern.
-- view: (default is cmdline view)
-- opts: any options passed to the view
-- icon_hl_group: optional hl_group for the icon
-- title: set to anything or empty string to hide
cmdline = { pattern = "^:", icon = "", lang = "vim" },
search_down = { kind = "search", pattern = "^/", icon = " ", lang = "regex" },
search_up = { kind = "search", pattern = "^%?", icon = " ", lang = "regex" },
filter = { pattern = "^:%s*!", icon = "$", lang = "bash" },
lua = { pattern = { "^:%s*lua%s+", "^:%s*lua%s*=%s*", "^:%s*=%s*" }, icon = "", lang = "lua" },
help = { pattern = "^:%s*he?l?p?%s+", icon = "" },
calculator = { pattern = "^=", icon = "", lang = "vimnormal" },
input = {}, -- Used by input()
-- lua = false, -- to disable a format, set to `false`
},
},
messages = {
-- NOTE: If you enable messages, then the cmdline is enabled automatically.
-- This is a current Neovim limitation.
enabled = true, -- enables the Noice messages UI
view = "notify", -- default view for messages
view_error = "notify", -- view for errors
view_warn = "notify", -- view for warnings
view_history = "messages", -- view for :messages
view_search = "virtualtext", -- view for search count messages. Set to `false` to disable
},
popupmenu = {
enabled = true, -- enables the Noice popupmenu UI
---@type 'nui'|'cmp'
backend = "nui", -- backend to use to show regular cmdline completions
---@type NoicePopupmenuItemKind|false
-- Icons for completion item kinds (see defaults at noice.config.icons.kinds)
kind_icons = {}, -- set to `false` to disable icons
},
-- default options for require('noice').redirect
-- see the section on Command Redirection
---@type NoiceRouteConfig
redirect = {
view = "popup",
filter = { event = "msg_show" },
},
-- You can add any custom commands below that will be available with `:Noice command`
---@type table<string, NoiceCommand>
commands = {
history = {
-- options for the message history that you get with `:Noice`
view = "split",
opts = { enter = true, format = "details" },
filter = {
any = {
{ event = "notify" },
{ error = true },
{ warning = true },
{ event = "msg_show", kind = { "" } },
{ event = "lsp", kind = "message" },
},
},
},
-- :Noice last
last = {
view = "popup",
opts = { enter = true, format = "details" },
filter = {
any = {
{ event = "notify" },
{ error = true },
{ warning = true },
{ event = "msg_show", kind = { "" } },
{ event = "lsp", kind = "message" },
},
},
filter_opts = { count = 1 },
},
-- :Noice errors
errors = {
-- options for the message history that you get with `:Noice`
view = "popup",
opts = { enter = true, format = "details" },
filter = { error = true },
filter_opts = { reverse = true },
},
all = {
-- options for the message history that you get with `:Noice`
view = "split",
opts = { enter = true, format = "details" },
filter = {},
},
},
notify = {
-- Noice can be used as `vim.notify` so you can route any notification like other messages
-- Notification messages have their level and other properties set.
-- event is always "notify" and kind can be any log level as a string
-- The default routes will forward notifications to nvim-notify
-- Benefit of using Noice for this is the routing and consistent history view
enabled = true,
view = "notify",
},
lsp = {
progress = {
enabled = true,
-- Lsp Progress is formatted using the builtins for lsp_progress. See config.format.builtin
-- See the section on formatting for more details on how to customize.
--- @type NoiceFormat|string
format = "lsp_progress",
--- @type NoiceFormat|string
format_done = "lsp_progress_done",
throttle = 1000 / 10, -- frequency to update lsp progress message
view = "mini",
},
override = {
-- override the default lsp markdown formatter with Noice
["vim.lsp.util.convert_input_to_markdown_lines"] = false,
-- override the lsp markdown formatter with Noice
["vim.lsp.util.stylize_markdown"] = false,
-- override cmp documentation with Noice (needs the other options to work)
["cmp.entry.get_documentation"] = false,
},
hover = {
enabled = true,
silent = false, -- set to true to not show a message if hover is not available
view = nil, -- when nil, use defaults from documentation
---@type NoiceViewOptions
opts = {}, -- merged with defaults from documentation
},
signature = {
enabled = true,
auto_open = {
enabled = true,
trigger = true, -- Automatically show signature help when typing a trigger character from the LSP
luasnip = true, -- Will open signature help when jumping to Luasnip insert nodes
throttle = 50, -- Debounce lsp signature help request by 50ms
},
view = nil, -- when nil, use defaults from documentation
---@type NoiceViewOptions
opts = {}, -- merged with defaults from documentation
},
message = {
-- Messages shown by lsp servers
enabled = true,
view = "notify",
opts = {},
},
-- defaults for hover and signature help
documentation = {
view = "hover",
---@type NoiceViewOptions
opts = {
replace = true,
render = "plain",
format = { "{message}" },
win_options = { concealcursor = "n", conceallevel = 3 },
},
},
},
markdown = {
hover = {
["|(%S-)|"] = vim.cmd.help, -- vim help links
["%[.-%]%((%S-)%)"] = require("noice.util").open, -- markdown links
},
highlights = {
["|%S-|"] = "@text.reference",
["@%S+"] = "@parameter",
["^%s*(Parameters:)"] = "@text.title",
["^%s*(Return:)"] = "@text.title",
["^%s*(See also:)"] = "@text.title",
["{%S-}"] = "@parameter",
},
},
health = {
checker = true, -- Disable if you don't want health checks to run
},
smart_move = {
-- noice tries to move out of the way of existing floating windows.
enabled = true, -- you can disable this behaviour here
-- add any filetypes here, that shouldn't trigger smart move.
excluded_filetypes = { "cmp_menu", "cmp_docs", "notify" },
},
---@type NoicePresets
presets = {
-- you can enable a preset by setting it to true, or a table that will override the preset config
-- you can also add custom presets that you can enable/disable with enabled=true
bottom_search = false, -- use a classic bottom cmdline for search
command_palette = false, -- position the cmdline and popupmenu together
long_message_to_split = false, -- long messages will be sent to a split
inc_rename = false, -- enables an input dialog for inc-rename.nvim
lsp_doc_border = false, -- add a border to hover docs and signature help
cmdline_output_to_split = false, -- send the output of a command you executed in the cmdline to a split
},
throttle = 1000 / 30, -- how frequently does Noice need to check for ui updates? This has no effect when in blocking mode.
---@type NoiceConfigViews
views = {}, ---@see section on views
---@type NoiceRouteConfig[]
routes = {}, --- @see section on routes
---@type table<string, NoiceFilter>
status = {}, --- @see section on statusline components
---@type NoiceFormatOptions
format = {}, --- @see section on formatting
debug = false,
log = vim.fn.stdpath("state") .. "/noice.log",
log_max_size = 1024 * 1024 * 2, -- 10MB
}
return defaults
end
--- @type NoiceConfig
M.options = {}
M._running = false
function M.is_running()
return M._running
end
function M.setup(options)
options = options or {}
M.fix_legacy(options)
if options.popupmenu and options.popupmenu.kind_icons == true then
options.popupmenu.kind_icons = nil
end
M.options = vim.tbl_deep_extend("force", {}, M.defaults(), {
views = require("noice.config.views").defaults,
status = require("noice.config.status").defaults,
format = require("noice.config.format").defaults,
popupmenu = {
kind_icons = require("noice.config.icons").kinds,
},
})
M.truncate_log()
require("noice.config.preset").setup(options)
local routes = M.options.routes
M.options = vim.tbl_deep_extend("force", M.options, options)
vim.list_extend(M.options.routes, routes)
if M.options.popupmenu.kind_icons == false then
M.options.popupmenu.kind_icons = {}
end
require("noice.config.cmdline").setup()
M.options.routes = Routes.get(M.options.routes)
require("noice.config.highlights").setup()
vim.api.nvim_create_autocmd("ColorScheme", {
callback = function()
require("noice.config.highlights").setup()
end,
})
require("noice.lsp").setup()
M._running = true
end
function M.truncate_log()
local stat = vim.loop.fs_stat(M.options.log)
if stat and stat.size > M.options.log_max_size then
io.open(M.options.log, "w+"):close()
end
end
---@param opts NoiceConfig
function M.fix_legacy(opts)
if opts.lsp and opts.lsp.signature and type(opts.lsp.signature.auto_open) == "boolean" then
opts.lsp.signature.auto_open = {
enabled = opts.lsp.signature.auto_open,
}
end
if opts.lsp_progress then
opts.lsp = opts.lsp or {}
opts.lsp.progress = opts.lsp_progress
opts.lsp_progress = nil
end
if opts.history then
opts.commands = opts.commands or {}
opts.commands.history = opts.history
opts.history = nil
end
end
return M

View File

@ -0,0 +1,133 @@
local require = require("noice.util.lazy")
local Util = require("noice.util")
local Config = require("noice.config")
---@class NoicePreset: NoiceConfig
---@field enabled? boolean
local M = {}
function M.setup(options)
for name, preset in pairs(options.presets or {}) do
if preset ~= false then
M.load(name, preset)
end
end
end
---@param name string
---@param preset NoiceConfig|boolean
function M.load(name, preset)
if preset == true and not M.presets[name] then
return Util.panic("Unknown preset " .. name)
end
preset = vim.tbl_deep_extend("force", {}, M.presets[name] or {}, type(preset) == "table" and preset or {})
---@cast preset NoicePreset
if preset.enabled == false then
return
end
local routes = preset.routes
preset.routes = nil
Config.options = vim.tbl_deep_extend("force", Config.options, preset)
if routes then
vim.list_extend(Config.options.routes, routes)
end
end
---@class NoicePresets: table<string, NoicePreset|boolean>
M.presets = {
bottom_search = {
cmdline = {
format = {
search_down = {
view = "cmdline",
},
search_up = {
view = "cmdline",
},
},
},
},
lsp_doc_border = {
views = {
hover = {
border = {
style = "rounded",
},
position = { row = 2, col = 2 },
},
},
},
command_palette = {
views = {
cmdline_popup = {
position = {
row = 3,
col = "50%",
},
size = {
min_width = 60,
width = "auto",
height = "auto",
},
},
cmdline_popupmenu = {
relative = "editor",
position = {
row = 6,
col = "50%",
},
size = {
width = 60,
height = "auto",
max_height = 15,
},
border = {
style = "rounded",
padding = { 0, 1 },
},
win_options = {
winhighlight = { Normal = "Normal", FloatBorder = "NoiceCmdlinePopupBorder" },
},
},
},
},
long_message_to_split = {
routes = {
{
filter = { event = "msg_show", min_height = 20 },
view = "cmdline_output",
},
},
},
inc_rename = {
cmdline = {
format = {
IncRename = {
pattern = "^:%s*IncRename%s+",
icon = "",
conceal = true,
opts = {
relative = "cursor",
size = { min_width = 20 },
position = { row = -3, col = 0 },
},
},
},
},
},
cmdline_output_to_split = {
routes = {
{
view = "cmdline_output",
filter = { cmdline = "^:" },
},
},
},
}
return M

View File

@ -0,0 +1,125 @@
local require = require("noice.util.lazy")
local Config = require("noice.config")
local M = {}
---@param routes? NoiceRouteConfig[]
function M.get(routes)
---@type NoiceRouteConfig[]
local ret = {}
-- add custom routes
vim.list_extend(ret, routes or {})
-- add default routes
vim.list_extend(ret, M.defaults())
return ret
end
---@return NoiceRouteConfig[]
function M.defaults()
---@type NoiceRouteConfig[]
local ret = {}
for _, kind in ipairs({ "signature", "hover" }) do
table.insert(ret, {
view = Config.options.lsp[kind].view or Config.options.lsp.documentation.view,
filter = { event = "lsp", kind = kind },
opts = vim.tbl_deep_extend(
"force",
{},
Config.options.lsp.documentation.opts,
Config.options.lsp[kind].opts or {}
),
})
end
return vim.list_extend(ret, {
{
view = Config.options.cmdline.view,
opts = Config.options.cmdline.opts,
filter = { event = "cmdline" },
},
{
view = "confirm",
filter = {
any = {
{ event = "msg_show", kind = "confirm" },
{ event = "msg_show", kind = "confirm_sub" },
-- { event = "msg_show", kind = { "echo", "echomsg", "" }, before = true },
-- { event = "msg_show", kind = { "echo", "echomsg" }, instant = true },
-- { event = "msg_show", find = "E325" },
-- { event = "msg_show", find = "Found a swap file" },
},
},
},
{
view = Config.options.messages.view_history,
filter = {
any = {
{ event = "msg_history_show" },
-- { min_height = 20 },
},
},
},
{
view = Config.options.messages.view_search,
filter = {
event = "msg_show",
kind = "search_count",
},
},
{
filter = {
any = {
{ event = { "msg_showmode", "msg_showcmd", "msg_ruler" } },
{ event = "msg_show", kind = "search_count" },
},
},
opts = { skip = true },
},
{
view = Config.options.messages.view,
filter = {
event = "msg_show",
kind = { "", "echo", "echomsg" },
},
opts = { replace = true, merge = true, title = "Messages" },
},
{
view = Config.options.messages.view_error,
filter = { error = true },
opts = { title = "Error" },
},
{
view = Config.options.messages.view_warn,
filter = { warning = true },
opts = { title = "Warning" },
},
{
view = Config.options.notify.view,
filter = { event = "notify" },
opts = { title = "Notify" },
},
{
view = Config.options.notify.view,
filter = {
event = "noice",
kind = { "stats", "debug" },
},
opts = { lang = "lua", replace = true, title = "Noice" },
},
{
view = Config.options.lsp.progress.view,
filter = { event = "lsp", kind = "progress" },
},
{
view = Config.options.lsp.message.view,
opts = Config.options.lsp.message.opts,
filter = { event = "lsp", kind = "message" },
},
})
end
return M

View File

@ -0,0 +1,16 @@
local require = require("noice.util.lazy")
local Msg = require("noice.ui.msg")
local M = {}
---@type table<string, NoiceFilter>
M.defaults = {
ruler = { event = Msg.events.ruler },
message = { event = Msg.events.show },
command = { event = Msg.events.showcmd },
mode = { event = Msg.events.showmode },
search = { event = Msg.events.show, kind = Msg.kinds.search_count },
}
return M

View File

@ -0,0 +1,259 @@
local require = require("noice.util.lazy")
local Util = require("noice.util")
local Config = require("noice.config")
local M = {}
---@param view string
---@return NoiceViewOptions
function M.get_options(view)
if not view then
Util.panic("View is missing?")
end
local opts = { view = view }
local done = {}
while opts.view and not done[opts.view] do
done[opts.view] = true
local view_opts = vim.deepcopy(Config.options.views[opts.view] or {})
opts = vim.tbl_deep_extend("keep", opts, view_opts)
opts.view = view_opts.view
end
return opts
end
---@class NoiceConfigViews: table<string, NoiceViewOptions>
M.defaults = {
popupmenu = {
relative = "editor",
zindex = 65,
position = "auto", -- when auto, then it will be positioned to the cmdline or cursor
size = {
width = "auto",
height = "auto",
max_height = 20,
-- min_width = 10,
},
win_options = {
winbar = "",
foldenable = false,
cursorline = true,
cursorlineopt = "line",
winhighlight = {
Normal = "NoicePopupmenu", -- change to NormalFloat to make it look like other floats
FloatBorder = "NoicePopupmenuBorder", -- border highlight
CursorLine = "NoicePopupmenuSelected", -- used for highlighting the selected item
PmenuMatch = "NoicePopupmenuMatch", -- used to highlight the part of the item that matches the input
},
},
border = {
padding = { 0, 1 },
},
},
cmdline_popupmenu = {
view = "popupmenu",
zindex = 200,
},
virtualtext = {
backend = "virtualtext",
format = { "{message}" },
hl_group = "NoiceVirtualText",
},
notify = {
backend = "notify",
fallback = "mini",
format = "notify",
replace = false,
merge = false,
},
split = {
backend = "split",
enter = false,
relative = "editor",
position = "bottom",
size = "20%",
close = {
keys = { "q" },
},
win_options = {
winhighlight = { Normal = "NoiceSplit", FloatBorder = "NoiceSplitBorder" },
wrap = true,
},
},
cmdline_output = {
format = "details",
view = "split",
},
messages = {
view = "split",
enter = true,
},
vsplit = {
view = "split",
position = "right",
},
popup = {
backend = "popup",
relative = "editor",
close = {
events = { "BufLeave" },
keys = { "q" },
},
enter = true,
border = {
style = "rounded",
},
position = "50%",
size = {
width = "120",
height = "20",
},
win_options = {
winhighlight = { Normal = "NoicePopup", FloatBorder = "NoicePopupBorder" },
winbar = "",
foldenable = false,
},
},
hover = {
view = "popup",
relative = "cursor",
zindex = 45,
enter = false,
anchor = "auto",
size = {
width = "auto",
height = "auto",
max_height = 20,
max_width = 120,
},
border = {
style = "none",
padding = { 0, 2 },
},
position = { row = 1, col = 0 },
win_options = {
wrap = true,
linebreak = true,
},
},
cmdline = {
backend = "popup",
relative = "editor",
position = {
row = "100%",
col = 0,
},
size = {
height = "auto",
width = "100%",
},
border = {
style = "none",
},
win_options = {
winhighlight = {
Normal = "NoiceCmdline",
IncSearch = "",
CurSearch = "",
Search = "",
},
},
},
mini = {
backend = "mini",
relative = "editor",
align = "message-right",
timeout = 2000,
reverse = true,
focusable = false,
position = {
row = -1,
col = "100%",
-- col = 0,
},
size = "auto",
border = {
style = "none",
},
zindex = 60,
win_options = {
winbar = "",
foldenable = false,
winblend = 30,
winhighlight = {
Normal = "NoiceMini",
IncSearch = "",
CurSearch = "",
Search = "",
},
},
},
cmdline_popup = {
backend = "popup",
relative = "editor",
focusable = false,
enter = false,
zindex = 200,
position = {
row = "50%",
col = "50%",
},
size = {
min_width = 60,
width = "auto",
height = "auto",
},
border = {
style = "rounded",
padding = { 0, 1 },
},
win_options = {
winhighlight = {
Normal = "NoiceCmdlinePopup",
FloatTitle = "NoiceCmdlinePopupTitle",
FloatBorder = "NoiceCmdlinePopupBorder",
IncSearch = "",
CurSearch = "",
Search = "",
},
winbar = "",
foldenable = false,
cursorline = false,
},
},
confirm = {
backend = "popup",
relative = "editor",
focusable = false,
align = "center",
enter = false,
zindex = 210,
format = { "{confirm}" },
position = {
row = "50%",
col = "50%",
},
size = "auto",
border = {
style = "rounded",
padding = { 0, 1 },
text = {
top = " Confirm ",
},
},
win_options = {
winhighlight = {
Normal = "NoiceConfirm",
FloatBorder = "NoiceConfirmBorder",
},
winbar = "",
foldenable = false,
},
},
}
return M

View File

@ -0,0 +1,242 @@
local require = require("noice.util.lazy")
local Util = require("noice.util")
local Config = require("noice.config")
local Lsp = require("noice.lsp")
local Treesitter = require("noice.text.treesitter")
local start = vim.health.start or vim.health.report_start
local ok = vim.health.ok or vim.health.report_ok
local warn = vim.health.warn or vim.health.report_warn
local error = vim.health.error or vim.health.report_error
local M = {}
M.checks = {}
M.log = {
---@class NoiceHealthLog
checkhealth = {
start = function(msg)
start(msg or "noice.nvim")
end,
info = function(msg, ...)
info(msg:format(...))
end,
ok = function(msg, ...)
ok(msg:format(...))
end,
warn = function(msg, ...)
warn(msg:format(...))
end,
error = function(msg, ...)
error(msg:format(...))
end,
},
---@type NoiceHealthLog
notify = {
start = function(msg) end,
info = function(msg, ...)
Util.info(msg:format(...))
end,
ok = function(msg, ...) end,
warn = function(msg, ...)
Util.warn_once(msg:format(...))
end,
error = function(msg, ...)
Util.error_once(msg:format(...))
end,
},
}
---@param opts? {checkhealth?: boolean}
function M.check(opts)
opts = opts or {}
opts.checkhealth = opts.checkhealth == nil and true or opts.checkhealth
local log = opts.checkhealth and M.log.checkhealth or M.log.notify
log.start()
if vim.fn.has("nvim-0.9.0") ~= 1 then
log.error("Noice requires Neovim >= 0.9.0")
-- require("noice.util").error("Noice needs Neovim >= 0.9.0 (nightly)")
if not opts.checkhealth then
return
end
else
log.ok("**Neovim** >= 0.9.0")
if opts.checkhealth and vim.fn.has("nvim-0.10.0") ~= 1 then
log.warn("**Neovim** >= 0.10 is highly recommended, since it fixes some issues related to `vim.ui_attach`")
end
end
local uis = vim.api.nvim_list_uis()
for _, ui in ipairs(uis) do
local ok = true
for _, ext in ipairs({ "ext_cmdline", "ext_popupmenu", "ext_messages" }) do
if ui[ext] then
ok = false
log.error(
"You're using a GUI that uses " .. ext .. ". Noice can't work when the GUI has " .. ext .. " enabled."
)
end
end
if ok then
if ui.chan == 0 then
log.ok("You're not using a GUI")
else
log.ok("You're using a GUI that should work ok")
end
end
end
if vim.go.lazyredraw then
if not Config.is_running() then
log.warn(
"You have enabled 'lazyredraw' (see `:h 'lazyredraw'`)\nThis is only meant to be set temporarily.\nYou'll experience issues using Noice."
)
end
else
log.ok("**vim.go.lazyredraw** is not enabled")
end
if opts.checkhealth then
if not Util.module_exists("notify") then
log.warn("Noice needs nvim-notify for routes using the `notify` view")
if not opts.checkhealth then
return
end
else
log.ok("**nvim-notify** is installed")
end
if vim.o.shortmess:find("S") then
log.warn(
"You added `S` to `vim.opt.shortmess`. Search count messages will not be handled by Noice. So no virtual text for search count."
)
end
for _, lang in ipairs({ "vim", "regex", "lua", "bash", "markdown", "markdown_inline" }) do
if Treesitter.has_lang(lang) then
log.ok("**TreeSitter " .. lang .. "** parser is installed")
else
log.warn(
"**TreeSitter "
.. lang
.. "** parser is not installed. Highlighting of the cmdline for "
.. lang
.. " might be broken"
)
end
end
end
if Config.is_running() then
---@type {opt:string[], opt_str?:string, handler:fun(), handler_str:string}
local checks = {
{
opt = "notify",
enabled = Config.options.notify.enabled,
handler = vim.notify,
handler_str = "vim.notify",
},
{
opt = "lsp.hover",
enabled = Config.options.lsp.hover.enabled,
handler = vim.lsp.handlers["textDocument/hover"],
handler_str = 'vim.lsp.handlers["textDocument/hover"]',
},
{
opt = "lsp.signature",
enabled = Config.options.lsp.signature.enabled,
handler = vim.lsp.handlers["textDocument/signatureHelp"],
handler_str = 'vim.lsp.handlers["textDocument/signatureHelp"]',
},
{
opt = "lsp.message",
enabled = Config.options.lsp.message.enabled,
handler = vim.lsp.handlers["window/showMessage"],
handler_str = 'vim.lsp.handlers["window/showMessage"]',
},
{
opt = 'lsp.override["vim.lsp.util.convert_input_to_markdown_lines"]',
enabled = Config.options.lsp.override["vim.lsp.util.convert_input_to_markdown_lines"],
handler = vim.lsp.util.convert_input_to_markdown_lines,
handler_str = "vim.lsp.util.convert_input_to_markdown_lines",
},
{
opt = 'lsp.override["vim.lsp.util.stylize_markdown"]',
enabled = Config.options.lsp.override["vim.lsp.util.stylize_markdown"],
handler = vim.lsp.util.stylize_markdown,
handler_str = "vim.lsp.util.stylize_markdown",
},
}
if package.loaded["cmp.entry"] then
local mod = package.loaded["cmp.entry"]
table.insert(checks, {
opt = 'lsp.override["cmp.entry.get_documentation"]',
enabled = Config.options.lsp.override["cmp.entry.get_documentation"],
handler = mod.get_documentation,
handler_str = "cmp.entry.get_documentation",
})
end
for _, check in ipairs(checks) do
if check.handler then
if check.enabled then
local source = M.get_source(check.handler)
if source.plugin ~= "noice.nvim" then
log.error(([[`%s` has been overwritten by another plugin?
Either disable the other plugin or set `config.%s.enabled = false` in your **Noice** config.
- plugin: %s
- file: %s
- line: %s]]):format(check.handler_str, check.opt, source.plugin, source.source, source.line))
else
log.ok(("`%s` is set to **Noice**"):format(check.handler_str))
end
elseif opts.checkhealth then
log.warn("`" .. check.handler_str .. "` is not configured to be handled by **Noice**")
end
end
end
end
return true
end
function M.get_source(fn)
local info = debug.getinfo(fn, "S")
local source = info.source:sub(2)
---@class FunSource
local ret = {
line = info.linedefined,
source = source,
plugin = "unknown",
}
if source:find("noice") then
ret.plugin = "noice.nvim"
elseif source:find("/runtime/lua/") then
ret.plugin = "nvim"
else
local opt = source:match("/pack/[^%/]-/opt/([^%/]-)/")
local start = source:match("/pack/[^%/]-/start/([^%/]-)/")
ret.plugin = opt or start or "unknown"
end
return ret
end
M.check({ checkhealth = false })
M.checker = Util.interval(1000, function()
if Config.is_running() then
M.check({ checkhealth = false })
end
end, {
enabled = function()
return Config.is_running()
end,
})
return M

View File

@ -0,0 +1,83 @@
local require = require("noice.util.lazy")
local Health = require("noice.health")
local Api = require("noice.api")
local Config = require("noice.config")
local M = {}
M.api = Api
---@param opts? NoiceConfig
function M.setup(opts)
-- run some checks before setting up
if not Health.check({ checkhealth = false, loaded = false }) then
return
end
local function load()
require("noice.util").try(function()
require("noice.config").setup(opts)
require("noice.commands").setup()
require("noice.message.router").setup()
M.enable()
end)
end
if vim.v.vim_did_enter == 0 then
-- Schedule loading after VimEnter. Get the UI up and running first.
vim.api.nvim_create_autocmd("VimEnter", {
once = true,
callback = load,
})
else
-- Schedule on the event loop
vim.schedule(load)
end
end
function M.disable()
Config._running = false
if Config.options.notify.enabled then
require("noice.source.notify").disable()
end
require("noice.message.router").disable()
require("noice.ui").disable()
require("noice.util.hacks").disable()
end
M.deactivate = M.disable
function M.cmd(name)
require("noice.commands").cmd(name)
end
function M.enable()
Config._running = true
if Config.options.notify.enabled then
require("noice.source.notify").enable()
end
require("noice.util.hacks").enable()
require("noice.ui").enable()
require("noice.message.router").enable()
if Config.options.health.checker then
Health.checker()
end
end
-- Redirect any messages generated by a command or function
---@param cmd string|fun() command or function to execute
---@param routes? NoiceRouteConfig[] custom routes. Defaults to `config.redirect`
function M.redirect(cmd, routes)
return require("noice.message.router").redirect(cmd, routes)
end
---@param msg string
---@param level number|string
---@param opts? table<string, any>
function M.notify(msg, level, opts)
return require("noice.source.notify").notify(msg, level, opts)
end
return M

View File

@ -0,0 +1,82 @@
local require = require("noice.util.lazy")
local Manager = require("noice.message.manager")
local Util = require("noice.util")
local Message = require("noice.message")
local M = {}
---@type table<LspKind, NoiceMessage>
M._messages = {}
---@type number?
M._autohide = nil
function M.autohide()
if not M._autohide then
M._autohide = vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI", "InsertEnter" }, {
group = vim.api.nvim_create_augroup("noice_lsp_docs", { clear = true }),
callback = function()
vim.defer_fn(M.on_close, 10)
end,
})
end
end
---@param kind LspKind
function M.get(kind)
if not M._messages[kind] then
M._messages[kind] = Message("lsp", kind)
M._messages[kind].opts.title = kind
end
M._messages[kind]:clear()
return M._messages[kind]
end
function M.on_close()
for _, message in pairs(M._messages) do
-- close the message if we're not in it's buffer (focus)
local keep = message:on_buf(vim.api.nvim_get_current_buf()) or (message.opts.stay and message.opts.stay())
if not keep then
M.hide(message)
end
end
end
function M.scroll(delta)
for _, kind in ipairs({ "hover", "signature" }) do
local message = M.get(kind)
local win = message:win()
if win then
Util.nui.scroll(win, delta)
return true
end
end
end
---@param message NoiceMessage
function M.hide(message)
message.opts.keep = function()
return false
end
Manager.remove(message)
end
---@param message NoiceMessage
---@param stay? fun():boolean
function M.show(message, stay)
M.autohide()
message.opts.timeout = 100
message.opts.keep = function()
return true
end
message.opts.stay = stay
for _, m in pairs(M._messages) do
if m ~= message then
M.hide(m)
end
end
Manager.add(message)
end
return M

View File

@ -0,0 +1,53 @@
local require = require("noice.util.lazy")
local Util = require("noice.util")
local Markdown = require("noice.text.markdown")
---@alias MarkedString string | { language: string; value: string }
---@alias MarkupContent { kind: ('plaintext' | 'markdown'), value: string}
---@alias MarkupContents MarkedString | MarkedString[] | MarkupContent
local M = {}
-- Formats the content and adds it to the message
---@param contents MarkupContents Markup content
function M.format_markdown(contents)
if type(contents) ~= "table" or not Util.islist(contents) then
contents = { contents }
end
local parts = {}
for _, content in ipairs(contents) do
if type(content) == "string" then
table.insert(parts, content)
elseif content.language then
table.insert(parts, ("```%s\n%s\n```"):format(content.language, content.value))
elseif content.kind == "markdown" then
table.insert(parts, content.value)
elseif content.kind == "plaintext" then
table.insert(parts, ("```\n%s\n```"):format(content.value))
elseif Util.islist(content) then
vim.list_extend(parts, M.format_markdown(content))
elseif type(content) == "table" and next(content) == nil then
goto continue
else
error("Unknown markup " .. vim.inspect(content))
end
::continue::
end
return vim.split(table.concat(parts, "\n"), "\n")
end
-- Formats the content and adds it to the message
---@param contents MarkupContents Markup content
---@param message NoiceMessage Noice message
---@param opts? MarkdownFormatOptions
function M.format(message, contents, opts)
local text = table.concat(M.format_markdown(contents), "\n")
Markdown.format(message, text, opts)
return message
end
return M

View File

@ -0,0 +1,37 @@
local require = require("noice.util.lazy")
local Format = require("noice.lsp.format")
local Util = require("noice.util")
local Docs = require("noice.lsp.docs")
local Config = require("noice.config")
local M = {}
function M.setup()
vim.lsp.handlers["textDocument/hover"] = M.on_hover
end
function M.on_hover(_, result, ctx)
if not (result and result.contents) then
if Config.options.lsp.hover.silent ~= true then
vim.notify("No information available")
end
return
end
local message = Docs.get("hover")
if not message:focus() then
Format.format(message, result.contents, { ft = vim.bo[ctx.bufnr].filetype })
if message:is_empty() then
if Config.options.lsp.hover.silent ~= true then
vim.notify("No information available")
end
return
end
Docs.show(message)
end
end
M.on_hover = Util.protect(M.on_hover)
return M

View File

@ -0,0 +1,64 @@
local require = require("noice.util.lazy")
local Config = require("noice.config")
local M = {}
---@alias LspEvent "lsp"
M.event = "lsp"
---@enum LspKind
M.kinds = {
progress = "progress",
hover = "hover",
message = "message",
signature = "signature",
}
function M.setup()
if Config.options.lsp.hover.enabled then
require("noice.lsp.hover").setup()
end
if Config.options.lsp.signature.enabled then
require("noice.lsp.signature").setup()
end
if Config.options.lsp.message.enabled then
require("noice.lsp.message").setup()
end
if Config.options.lsp.progress.enabled then
require("noice.lsp.progress").setup()
end
local overrides = vim.tbl_filter(
---@param v boolean
function(v)
return v
end,
Config.options.lsp.override
)
if #overrides > 0 then
require("noice.lsp.override").setup()
end
end
function M.scroll(delta)
return require("noice.lsp.docs").scroll(delta)
end
function M.hover()
---@diagnostic disable-next-line: missing-parameter
local params = vim.lsp.util.make_position_params()
vim.lsp.buf_request(0, "textDocument/hover", params, require("noice.lsp.hover").on_hover)
end
function M.signature()
---@diagnostic disable-next-line: missing-parameter
local params = vim.lsp.util.make_position_params()
vim.lsp.buf_request(0, "textDocument/signatureHelp", params, require("noice.lsp.signature").on_signature)
end
return M

View File

@ -0,0 +1,40 @@
local require = require("noice.util.lazy")
local Manager = require("noice.message.manager")
local Message = require("noice.message")
local Util = require("noice.util")
local M = {}
---@enum MessageType
M.message_type = {
error = 1,
warn = 2,
info = 3,
debug = 4,
}
---@alias ShowMessageParams {type:MessageType, message:string}
function M.setup()
vim.lsp.handlers["window/showMessage"] = Util.protect(M.on_message)
end
---@param result ShowMessageParams
function M.on_message(_, result, ctx)
---@type number
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
local client_name = client and client.name or string.format("lsp id=%d", client_id)
local message = Message("lsp", "message", result.message)
message.opts.title = "LSP Message (" .. client_name .. ")"
for level, type in pairs(M.message_type) do
if type == result.type then
message.level = level
end
end
Manager.add(message)
end
return M

View File

@ -0,0 +1,59 @@
local require = require("noice.util.lazy")
local Markdown = require("noice.text.markdown")
local Config = require("noice.config")
local Format = require("noice.lsp.format")
local Message = require("noice.message")
local Hacks = require("noice.util.hacks")
local M = {}
function M.setup()
if Config.options.lsp.override["cmp.entry.get_documentation"] then
Hacks.on_module("cmp.entry", function(mod)
mod.get_documentation = function(self)
local item = self:get_completion_item()
local lines = item.documentation and Format.format_markdown(item.documentation) or {}
local ret = table.concat(lines, "\n")
local detail = item.detail
if detail and type(detail) == "table" then
detail = table.concat(detail, "\n")
end
if detail and not ret:find(detail, 1, true) then
local ft = self.context.filetype
local dot_index = string.find(ft, "%.")
if dot_index ~= nil then
ft = string.sub(ft, 0, dot_index - 1)
end
ret = ("```%s\n%s\n```\n%s"):format(ft, vim.trim(detail), ret)
end
return vim.split(ret, "\n")
end
end)
end
if Config.options.lsp.override["vim.lsp.util.convert_input_to_markdown_lines"] then
vim.lsp.util.convert_input_to_markdown_lines = function(input, contents)
contents = contents or {}
local ret = Format.format_markdown(input)
vim.list_extend(contents, ret)
return contents
end
end
if Config.options.lsp.override["vim.lsp.util.stylize_markdown"] then
vim.lsp.util.stylize_markdown = function(buf, contents, _opts)
vim.api.nvim_buf_clear_namespace(buf, Config.ns, 0, -1)
local text = table.concat(contents, "\n")
local message = Message("lsp")
Markdown.format(message, text)
message:render(buf, Config.ns)
Markdown.keys(buf)
return vim.api.nvim_buf_get_lines(buf, 0, -1, false)
end
end
end
return M

View File

@ -0,0 +1,113 @@
local require = require("noice.util.lazy")
local Message = require("noice.message")
local Manager = require("noice.message.manager")
local Router = require("noice.message.router")
local Format = require("noice.text.format")
local Config = require("noice.config")
local Util = require("noice.util")
local M = {}
---@type table<string, NoiceMessage>
M._progress = {}
M._running = false
---@param data {client_id: integer, params: lsp.ProgressParams}
function M.progress(data)
local client_id = data.client_id
local params = data.params or data.result -- TODO: Remove data.result after nvim 0.10 release
local id = client_id .. "." .. params.token
local message = M._progress[id]
if not message then
local client = vim.lsp.get_client_by_id(client_id)
-- should not happen, but it does for some reason
if not client then
return
end
message = Message("lsp", "progress")
message.opts.progress = {
client_id = client_id,
---@type string
client = client and client.name or ("lsp-" .. client_id),
}
M._progress[id] = message
end
message.opts.progress = vim.tbl_deep_extend("force", message.opts.progress, params.value)
message.opts.progress.id = id
if params.value.kind == "end" then
if message.opts.progress.percentage then
message.opts.progress.percentage = 100
end
vim.defer_fn(function()
M.close(id)
end, 100)
end
M.update()
end
function M.close(id)
local message = M._progress[id]
if message then
M.update()
Router.update()
Manager.remove(message)
M._progress[id] = nil
end
end
function M._update()
if not vim.tbl_isempty(M._progress) then
for id, message in pairs(M._progress) do
local client = vim.lsp.get_client_by_id(message.opts.progress.client_id)
if not client then
M.close(id)
end
if message.opts.progress.kind == "end" then
Manager.add(Format.format(message, Config.options.lsp.progress.format_done))
else
Manager.add(Format.format(message, Config.options.lsp.progress.format))
end
end
return
end
end
function M.update()
error("should never be called")
end
function M.setup()
M.update = Util.interval(Config.options.lsp.progress.throttle, M._update, {
enabled = function()
return not vim.tbl_isempty(M._progress)
end,
})
-- Neovim >= 0.10.0
local ok = pcall(vim.api.nvim_create_autocmd, "LspProgress", {
group = vim.api.nvim_create_augroup("noice_lsp_progress", { clear = true }),
callback = function(event)
M.progress(event.data)
end,
})
-- Neovim < 0.10.0
if not ok then
local orig = vim.lsp.handlers["$/progress"]
vim.lsp.handlers["$/progress"] = function(...)
local params = select(2, ...)
local ctx = select(3, ...)
Util.try(function()
M.progress({ client_id = ctx.client_id, params = params })
end)
orig(...)
end
end
end
return M

View File

@ -0,0 +1,247 @@
local require = require("noice.util.lazy")
local NoiceText = require("noice.text")
local Format = require("noice.lsp.format")
local Markdown = require("noice.text.markdown")
local Config = require("noice.config")
local Util = require("noice.util")
local Docs = require("noice.lsp.docs")
---@class SignatureInformation
---@field label string
---@field documentation? string|MarkupContent
---@field parameters? ParameterInformation[]
---@field activeParameter? integer
---@class ParameterInformation
---@field label string|{[1]:integer, [2]:integer}
---@field documentation? string|MarkupContent
---@class SignatureHelpContext
---@field triggerKind SignatureHelpTriggerKind
---@field triggerCharacter? string
---@field isRetrigger boolean
---@field activeSignatureHelp? SignatureHelp
---@class SignatureHelp
---@field signatures SignatureInformation[]
---@field activeSignature? integer
---@field activeParameter? integer
---@field ft? string
---@field message NoiceMessage
local M = {}
M.__index = M
---@enum SignatureHelpTriggerKind
M.trigger_kind = {
invoked = 1,
trigger_character = 2,
content_change = 3,
}
function M.setup()
vim.lsp.handlers["textDocument/signatureHelp"] = M.on_signature
if Config.options.lsp.signature.auto_open.enabled then
-- attach to existing buffers
for _, client in ipairs((vim.lsp.get_clients or vim.lsp.get_active_clients)()) do
for _, buf in ipairs(vim.lsp.get_buffers_by_client_id(client.id)) do
M.on_attach(buf, client)
end
end
-- attach to new buffers
vim.api.nvim_create_autocmd("LspAttach", {
group = vim.api.nvim_create_augroup("noice_lsp_signature", { clear = true }),
callback = function(args)
if args.data ~= nil then
M.on_attach(args.buf, vim.lsp.get_client_by_id(args.data.client_id))
end
end,
})
end
end
function M.get_char(buf)
local current_win = vim.api.nvim_get_current_win()
local win = buf == vim.api.nvim_win_get_buf(current_win) and current_win or vim.fn.bufwinid(buf)
local cursor = vim.api.nvim_win_get_cursor(win == -1 and 0 or win)
local row = cursor[1] - 1
local col = cursor[2]
local _, lines = pcall(vim.api.nvim_buf_get_text, buf, row, 0, row, col, {})
local line = vim.trim(lines and lines[1] or "")
return line:sub(-1, -1)
end
---@param result SignatureHelp
function M.on_signature(_, result, ctx, config)
config = config or {}
if not (result and result.signatures) then
if not config.trigger then
vim.notify("No signature help available")
end
return
end
local message = Docs.get("signature")
if config.trigger or not message:focus() then
result.ft = vim.bo[ctx.bufnr].filetype
result.message = message
M.new(result):format()
if message:is_empty() then
if not config.trigger then
vim.notify("No signature help available")
end
return
end
Docs.show(message, config.stay)
end
end
M.on_signature = Util.protect(M.on_signature)
function M.on_attach(buf, client)
if client.server_capabilities.signatureHelpProvider then
---@type string[]
local chars = client.server_capabilities.signatureHelpProvider.triggerCharacters
if chars and #chars > 0 then
local callback = M.check(buf, chars, client.offset_encoding)
if Config.options.lsp.signature.auto_open.luasnip then
vim.api.nvim_create_autocmd("User", {
pattern = "LuasnipInsertNodeEnter",
callback = callback,
})
end
if Config.options.lsp.signature.auto_open.trigger then
vim.api.nvim_create_autocmd({ "TextChangedI", "TextChangedP", "InsertEnter" }, {
buffer = buf,
callback = callback,
})
end
end
end
end
function M.check(buf, chars, encoding)
encoding = encoding or "utf-16"
return Util.debounce(Config.options.lsp.signature.auto_open.throttle, function(_event)
if vim.api.nvim_get_current_buf() ~= buf then
return
end
if vim.tbl_contains(chars, M.get_char(buf)) then
local params = vim.lsp.util.make_position_params(0, encoding)
vim.lsp.buf_request(buf, "textDocument/signatureHelp", params, function(err, result, ctx)
M.on_signature(err, result, ctx, {
trigger = true,
stay = function()
return vim.tbl_contains(chars, M.get_char(buf))
end,
})
end)
end
end)
end
---@param help SignatureHelp
function M.new(help)
return setmetatable(help, M)
end
function M:active_parameter(sig_index)
if self.activeSignature and self.signatures[self.activeSignature + 1] and sig_index ~= self.activeSignature + 1 then
return
end
local sig = self.signatures[sig_index]
if sig.activeParameter and sig.parameters and sig.parameters[sig.activeParameter + 1] then
return sig.parameters[sig.activeParameter + 1]
end
if self.activeParameter and sig.parameters and sig.parameters[self.activeParameter + 1] then
return sig.parameters[self.activeParameter + 1]
end
return sig.parameters and sig.parameters[1] or nil
end
---@param sig SignatureInformation
---@param param ParameterInformation
function M:format_active_parameter(sig, param)
local label = param.label
if type(label) == "string" then
local from = sig.label:find(label, 1, true)
if from then
self.message:append(NoiceText("", {
hl_group = "LspSignatureActiveParameter",
col = from - 1,
length = vim.fn.strlen(label),
}))
end
else
self.message:append(NoiceText("", {
hl_group = "LspSignatureActiveParameter",
col = label[1],
length = label[2] - label[1],
}))
end
end
--- dddd
-- function M:format_signature(boo) end
---@param sig SignatureInformation
---@overload fun() # goooo
function M:format_signature(sig_index, sig)
if sig_index ~= 1 then
self.message:newline()
self.message:newline()
Markdown.horizontal_line(self.message)
self.message:newline()
end
local count = self.message:height()
self.message:append(sig.label)
self.message:append(NoiceText.syntax(self.ft, self.message:height() - count))
local param = self:active_parameter(sig_index)
if param then
self:format_active_parameter(sig, param)
end
self.message:newline()
if sig.documentation then
Markdown.horizontal_line(self.message)
Format.format(self.message, sig.documentation, { ft = self.ft })
end
---@type ParameterInformation[]
local params = vim.tbl_filter(function(p)
return p.documentation
end, sig.parameters or {})
local lines = {}
if #params > 0 then
for _, p in ipairs(sig.parameters) do
if p.documentation then
local pdoc = table.concat(Format.format_markdown(p.documentation or ""), "\n")
local line = { "-" }
if p.label then
local label = p.label
if type(label) == "table" then
label = sig.label:sub(label[1] + 1, label[2])
end
line[#line + 1] = "`[" .. label .. "]`"
end
line[#line + 1] = pdoc
lines[#lines + 1] = table.concat(line, " ")
end
end
end
Format.format(self.message, table.concat(lines, "\n"), { ft = self.ft })
end
function M:format()
for s, sig in ipairs(self.signatures) do
self:format_signature(s, sig)
end
end
return M

View File

@ -0,0 +1,183 @@
local require = require("noice.util.lazy")
local Util = require("noice.util")
local Manager = require("noice.message.manager")
local M = {}
---@alias NoiceFilterFun fun(message: NoiceMessage, ...): boolean
---@class NoiceFilter
---@field any? NoiceFilter[]
---@field blocking? boolean
---@field cleared? boolean
---@field cmdline? string|boolean
---@field error? boolean
---@field event? NoiceEvent|NoiceEvent[]
---@field find? string
---@field has? boolean
---@field kind? NoiceKind|NoiceKind[]
---@field max_height? integer
---@field max_length? integer
---@field max_width? integer
---@field message? NoiceMessage|NoiceMessage[]
---@field min_height? integer
---@field min_length? integer
---@field min_width? integer
---@field mode? string
---@field not? NoiceFilter
---@field warning? boolean
---@field cond? fun(message:NoiceMessage):boolean
-----@type table<string, NoiceFilterFun>
M.filters = {
cleared = function(message, cleared)
---@cast message NoiceMessage
return cleared == not Manager.has(message)
end,
has = function(message, has)
---@cast message NoiceMessage
return has == Manager.has(message, { history = true })
end,
cond = function(message, cond)
return cond(message)
end,
mode = function(_, mode)
return vim.api.nvim_get_mode().mode:find(mode)
end,
blocking = function(_, blocking)
return blocking == Util.is_blocking()
end,
event = function(message, event)
---@cast message NoiceMessage
event = type(event) == "table" and event or { event }
return vim.tbl_contains(event, message.event)
end,
kind = function(message, kind)
---@cast message NoiceMessage
kind = type(kind) == "table" and kind or { kind }
return vim.tbl_contains(kind, message.kind)
end,
cmdline = function(message, cmdline)
---@cast message NoiceMessage
---@cast cmdline string|boolean
if type(cmdline) == "boolean" then
return (message.cmdline ~= nil) == cmdline
end
if message.cmdline then
local str = message.cmdline.state.firstc .. message.cmdline:get()
return str:find(cmdline)
end
return false
end,
message = function(message, other)
---@cast message NoiceMessage
other = Util.islist(other) and other or { other }
---@cast other NoiceMessage[]
for _, m in ipairs(other) do
if m.id == message.id then
return true
end
end
return false
end,
error = function(message, error)
---@cast message NoiceMessage
return error == (message.level == "error")
end,
warning = function(message, warning)
---@cast message NoiceMessage
return warning == (message.level == "warn")
end,
find = function(message, find)
---@cast message NoiceMessage
return message:content():find(find)
end,
min_height = function(message, min_height)
---@cast message NoiceMessage
return message:height() >= min_height
end,
max_height = function(message, max_height)
---@cast message NoiceMessage
return message:height() <= max_height
end,
min_width = function(message, min_width)
---@cast message NoiceMessage
return message:width() >= min_width
end,
max_width = function(message, max_width)
---@cast message NoiceMessage
return message:width() <= max_width
end,
min_length = function(message, min_length)
---@cast message NoiceMessage
return message:length() >= min_length
end,
max_length = function(message, max_length)
---@cast message NoiceMessage
return message:length() <= max_length
end,
any = function(message, any)
---@cast message NoiceMessage
---@cast any NoiceFilter[]
for _, f in ipairs(any) do
if message:is(f) then
return true
end
end
return false
end,
["not"] = function(message, filter)
---@cast message NoiceMessage
return not message:is(filter)
end,
}
---@type table<string,boolean>
M._unknown_notified = {}
---@param message NoiceMessage
---@param filter NoiceFilter
function M.is(message, filter)
for k, v in pairs(filter) do
if M.filters[k] then
if not M.filters[k](message, v) then
return false
end
else
if not M._unknown_notified[k] then
M._unknown_notified[k] = true
Util.error("Unknown filter key " .. k .. " for " .. vim.inspect(filter))
end
return false
end
end
return true
end
---@param messages NoiceMessage[]
---@param filter NoiceFilter
---@param invert? boolean
---@return NoiceMessage[]
function M.filter(messages, filter, invert)
return vim.tbl_filter(
---@param message NoiceMessage
function(message)
local is = M.is(message, filter)
if invert then
is = not is
end
return is
end,
messages
)
end
---@param messages NoiceMessage[]
---@param filter NoiceFilter
---@param invert? boolean
function M.has(messages, filter, invert)
return #M.filter(messages, filter, invert) > 0
end
return M

View File

@ -0,0 +1,116 @@
local require = require("noice.util.lazy")
local Block = require("noice.text.block")
local Filter = require("noice.message.filter")
local _id = 0
---@class NoiceMessage: NoiceBlock
---@field super NoiceBlock
---@field id number
---@field event NoiceEvent
---@field ctime number
---@field mtime number
---@field tick number
---@field level? NotifyLevel
---@field kind? NoiceKind
---@field cmdline? NoiceCmdline
---@field _debug? boolean
---@field opts table<string, any>
---@overload fun(event: NoiceEvent, kind?: NoiceKind, content?: NoiceContent|NoiceContent[]): NoiceMessage
---@diagnostic disable-next-line: undefined-field
local Message = Block:extend("NoiceBlock")
---@param event NoiceEvent
---@param kind? NoiceKind
---@param content? NoiceContent|NoiceContent[]
function Message:init(event, kind, content)
_id = _id + 1
self.id = _id
self.tick = 1
self.ctime = vim.fn.localtime()
self.mtime = vim.fn.localtime()
self.event = event
self.kind = kind
self.opts = {}
Message.super.init(self, content)
end
-- Returns the first buffer that has rendered the message
---@return buffer?
function Message:buf()
return self:bufs()[1]
end
function Message:bufs()
return vim.tbl_filter(function(buf)
return vim.api.nvim_buf_is_valid(buf) and self.on_buf(buf)
end, vim.api.nvim_list_bufs())
end
function Message:wins()
return vim.tbl_filter(function(win)
return vim.api.nvim_win_is_valid(win) and self:on_win(win)
end, vim.api.nvim_list_wins())
end
-- Returns the first window that displays the message
---@return window?
function Message:win()
return self:wins()[1]
end
function Message:focus()
local win = self:win()
if win then
vim.api.nvim_set_current_win(win)
-- switch to normal mode
vim.cmd("stopinsert")
return true
end
end
function Message:on_remove()
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
if self:on_buf(buf) then
vim.b[buf].messages = vim.tbl_filter(function(b)
return b ~= buf
end, vim.b[buf].messages)
end
end
end
function Message:on_win(win)
return self:on_buf(vim.api.nvim_win_get_buf(win))
end
function Message:on_buf(buf)
return vim.b[buf].messages and vim.tbl_contains(vim.b[buf].messages, self.id)
end
function Message:_add_buf(buf)
local bufs = vim.b[buf].messages or {}
table.insert(bufs, self.id)
vim.b[buf].messages = bufs
end
---@param bufnr number buffer number
---@param ns_id number namespace id
---@param linenr_start? number line number (1-indexed)
function Message:highlight(bufnr, ns_id, linenr_start)
self:_add_buf(bufnr)
return Message.super.highlight(self, bufnr, ns_id, linenr_start)
end
---@param bufnr number buffer number
---@param ns_id number namespace id
---@param linenr_start? number start line number (1-indexed)
---@param linenr_end? number end line number (1-indexed)
function Message:render(bufnr, ns_id, linenr_start, linenr_end)
self:_add_buf(bufnr)
return Message.super.render(self, bufnr, ns_id, linenr_start, linenr_end)
end
Message.is = Filter.is
return Message

View File

@ -0,0 +1,129 @@
local M = {}
local _tick = 1
local function next_tick()
_tick = _tick + 1
return _tick
end
---@type table<number, NoiceMessage>
M._history = {}
---@type table<number, NoiceMessage>
M._messages = {}
function M.tick()
return _tick
end
---@param message NoiceMessage
function M.add(message)
if not (message:is_empty() and vim.tbl_isempty(message.opts)) then
message.tick = next_tick()
message.mtime = vim.fn.localtime()
M._history[message.id] = message
M._messages[message.id] = message
end
end
---@param message NoiceMessage
---@param opts? { history: boolean } # defaults to `{ history = false }`
function M.has(message, opts)
opts = opts or {}
return (opts.history and M._history or M._messages)[message.id] ~= nil
end
---@param message NoiceMessage
function M.remove(message)
if M._history[message.id] then
M._history[message.id] = nil
next_tick()
end
if M._messages[message.id] then
M._messages[message.id] = nil
next_tick()
end
message:on_remove()
end
---@param filter? NoiceFilter
function M.clear(filter)
M.with(function(message)
M._messages[message.id] = nil
next_tick()
end, filter)
end
---@param max number
function M.prune(max)
local keep = M.get(nil, { count = max })
M._messages = {}
for _, message in ipairs(keep) do
M._messages[message.id] = message
end
end
-- Sorts messages in-place by mtime & id
---@param messages NoiceMessage[]
function M.sort(messages, reverse)
table.sort(
messages,
---@param a NoiceMessage
---@param b NoiceMessage
function(a, b)
local ret = (a.mtime == b.mtime) and (a.id < b.id) or (a.mtime < b.mtime)
if reverse then
ret = not ret
end
return ret
end
)
end
function M.get_by_id(id)
return M._history[id]
end
---@class NoiceMessageOpts
---@field history? boolean
---@field sort? boolean
---@field reverse? boolean
---@field count? number
---@field messages? NoiceMessage[]
---@param filter? NoiceFilter
---@param opts? NoiceMessageOpts
---@return NoiceMessage[]
function M.get(filter, opts)
opts = opts or {}
local messages = opts.messages or opts.history and M._history or M._messages
local ret = {}
for _, message in pairs(messages) do
if not filter or message:is(filter) then
table.insert(ret, message)
end
end
if opts.sort then
M.sort(ret, opts.reverse)
end
if opts.count and #ret > opts.count then
local last = {}
for i = #ret - opts.count + 1, #ret do
table.insert(last, ret[i])
end
ret = last
end
return ret
end
---@param fn fun(message: NoiceMessage)
---@param filter? NoiceFilter
---@param opts? { history: boolean, sort: boolean } # defaults to `{ history = false, sort = false }`
function M.with(fn, filter, opts)
for _, message in ipairs(M.get(filter, opts)) do
fn(message)
end
end
return M

View File

@ -0,0 +1,260 @@
local require = require("noice.util.lazy")
local Config = require("noice.config")
local Util = require("noice.util")
local View = require("noice.view")
local Manager = require("noice.message.manager")
---@class NoiceRoute
---@field view NoiceView
---@field filter NoiceFilter
---@field opts? NoiceRouteOptions|NoiceViewOptions
---@class NoiceRouteOptions
---@field stop boolean
---@field skip boolean
---@class NoiceRouteConfig
---@field view string
---@field filter NoiceFilter
---@field opts? NoiceRouteOptions|NoiceViewOptions
local M = {}
---@type NoiceRoute[]
M._routes = {}
M._tick = 0
M._need_redraw = false
---@type fun()|Interval?
M._updater = nil
M._updating = false
function M.enable()
if not M._updater then
M._updater = Util.interval(Config.options.throttle, Util.protect(M.update))
end
M._updater()
end
function M.disable()
if M._updater then
M._updater.stop()
M._updater = nil
Manager.clear()
M.update()
end
vim.api.nvim_create_augroup("NoiceRouter", { clear = true })
end
---@param route NoiceRouteConfig
---@param pos? number
function M.add(route, pos)
local ret = {
filter = route.filter,
opts = route.opts or {},
view = route.view and View.get_view(route.view, route.opts) or nil,
}
if ret.view == nil then
ret.opts.skip = true
end
if pos then
table.insert(M._routes, pos, ret)
else
table.insert(M._routes, ret)
end
return ret
end
-- Redirect any messages generated by a command or function
---@param cmd string|fun() command or function to execute
---@param routes? NoiceRouteConfig[] custom routes. Defaults to `config.redirect`
function M.redirect(cmd, routes)
routes = routes or { Config.options.redirect }
if type(cmd) == "string" then
local cmd_str = cmd
cmd = function()
vim.cmd(cmd_str)
end
end
-- process any pending messages
M.update()
local added = {}
local pos = 1
-- add temporary routes
for _, route in ipairs(routes) do
table.insert(added, M.add(route, pos))
pos = pos + 1
end
-- execute callback
Util.try(cmd)
-- force a redraw to make sure we received all msg_show events
vim.cmd.redraw()
-- process messages
M.update()
-- remove temporary routes
M._routes = vim.tbl_filter(function(r)
return not vim.tbl_contains(added, r)
end, M._routes)
end
function M.setup()
for _, route in ipairs(Config.options.routes) do
M.add(route)
end
end
function M.check_redraw()
if Util.is_blocking() and M._need_redraw then
-- NOTE: set to false before actually calling redraw to prevent a loop with ui
M._need_redraw = false
Util.redraw()
end
end
function M.view_stats()
local views = M.get_views()
---@type table<string,number>
local ret = {}
-- remove deleted messages and new messages from the views
for view, _ in pairs(views) do
if #view._messages > 0 then
if not ret[view._opts.view] then
ret[view._opts.view] = 0
end
ret[view._opts.view] = ret[view._opts.view] + #view._messages
end
end
return ret
end
function M.get_views()
---@type table<NoiceView, boolean>
local views = {}
for _, route in ipairs(M._routes) do
if route.view then
views[route.view] = true
end
end
return views
end
function M.dismiss()
Manager.clear()
local views = M.get_views()
for view, _ in pairs(views) do
view:dismiss()
view:display()
end
M.update()
end
function M.update()
if Util.is_exiting() then
return
end
if M._updating then
return
end
-- only update on changes
if M._tick == Manager.tick() then
M.check_redraw()
return
end
M._updating = true
Util.stats.track("router.update")
---@type table<NoiceView,boolean>
local updates = {}
local messages = Manager.get(nil, { sort = true })
local views = M.get_views()
-- remove deleted messages and new messages from the views
for view, _ in pairs(views) do
local count = #view._messages
view._messages = Manager.get({
-- remove any deleted messages
has = true,
-- remove messages that we are adding
["not"] = {
message = messages,
},
}, { messages = view._messages })
-- retry errors only once
if view._errors > 1 then
view._errors = 0
end
if #view._messages ~= count or view._errors > 0 then
updates[view] = true
end
end
-- add messages
for _, message in ipairs(messages) do
for _, route in ipairs(M._routes) do
if message:is(route.filter) then
if not route.opts.skip then
route.view:push(message)
route.view._route_opts = vim.tbl_deep_extend("force", route.view._route_opts or {}, route.opts or {})
updates[route.view] = true
end
if route.opts.stop ~= false then
break
end
end
end
end
Manager.clear()
local dirty = false
for view, _ in pairs(updates) do
view:display()
if view._errors > 0 then
dirty = true
end
end
if not dirty then
M._tick = Manager.tick()
end
if not vim.tbl_isempty(updates) then
Util.stats.track("router.update.updated")
M._need_redraw = true
end
M.check_redraw()
M._updating = false
end
function M.echo_pending()
local messages = Manager.get({ event = "msg_show" }, { sort = true })
local chunks = {}
for _, message in ipairs(messages) do
for _, line in ipairs(message._lines) do
---@param t NuiText
local chunk = vim.tbl_map(function(t)
return { t:content(), t.extmark.hl_group }
end, line._texts)
vim.list_extend(chunks, chunk)
end
end
chunks[#chunks + 1] = { "foobar", "Normal" }
-- vim.opt.cmdheight = 10
-- vim.opt.more = false
vim.api.nvim_echo(chunks, true, {})
end
return M

View File

@ -0,0 +1,69 @@
local require = require("noice.util.lazy")
local Message = require("noice.message")
local Manager = require("noice.message.manager")
local Router = require("noice.message.router")
local Util = require("noice.util")
local M = {}
---@alias NotifyEvent "notify"
---@alias NotifyLevel "trace"|"debug"|"info"|"warn"|"error"|"off"
M._orig = nil
function M.enable()
if vim.notify ~= M.notify then
M._orig = vim.notify
vim.notify = M.notify
end
end
function M.disable()
if M._orig then
vim.notify = M._orig
M._orig = nil
end
end
function M.get_level(level)
if type(level) == "string" then
return level
end
for k, v in pairs(vim.log.levels) do
if v == level then
return k:lower()
end
end
return "info"
end
---@param msg string
---@param level? number|string
---@param opts? table<string, any>
function M.notify(msg, level, opts)
if vim.in_fast_event() then
vim.schedule(function()
M.notify(msg, level, opts)
end)
return
end
level = M.get_level(level)
local message = Message("notify", level, msg)
message.opts = opts or {}
message.level = level
if msg == nil then
-- special case for some destinations like nvim-notify
message.opts.is_nil = true
end
Manager.add(message)
if Util.is_blocking() then
Router.update()
end
return { id = message.id }
end
return M

View File

@ -0,0 +1,216 @@
local require = require("noice.util.lazy")
local Highlight = require("noice.text.highlight")
local NuiLine = require("nui.line")
local Object = require("nui.object")
---@alias NoiceChunk { [0]: integer, [1]: string}
---@alias NoiceContent string|NoiceChunk|NuiLine|NuiText|NoiceBlock
---@class NoiceBlock
---@field _lines NuiLine[]
---@field fix_cr boolean?
---@overload fun(content?: NoiceContent|NoiceContent[], highlight?: string|table): NoiceBlock
local Block = Object("Block")
---@param content? NoiceContent|NoiceContent[]
---@param highlight? string|table data for highlight
function Block:init(content, highlight)
self._lines = {}
if content then
self:append(content, highlight)
end
end
function Block:clear()
self._lines = {}
end
function Block:content()
return table.concat(
vim.tbl_map(
---@param line NuiLine
function(line)
return line:content()
end,
self._lines
),
"\n"
)
end
function Block:width()
local ret = 0
for _, line in ipairs(self._lines) do
ret = math.max(ret, line:width())
end
return ret
end
function Block:length()
local ret = 0
for _, line in ipairs(self._lines) do
ret = ret + line:width()
end
return ret
end
function Block:height()
return #self._lines
end
function Block:is_empty()
return #self._lines == 0
end
---@param bufnr number buffer number
---@param ns_id number namespace id
---@param linenr_start? number line number (1-indexed)
function Block:highlight(bufnr, ns_id, linenr_start)
self:_fix_extmarks()
linenr_start = linenr_start or 1
Highlight.update()
for _, line in ipairs(self._lines) do
line:highlight(bufnr, ns_id, linenr_start)
linenr_start = linenr_start + 1
end
end
function Block:_fix_extmarks()
for _, line in ipairs(self._lines) do
for _, text in ipairs(line._texts) do
if text.extmark then
text.extmark.id = nil
end
end
end
end
---@param bufnr number buffer number
---@param ns_id number namespace id
---@param linenr_start? number start line number (1-indexed)
---@param linenr_end? number end line number (1-indexed)
function Block:render(bufnr, ns_id, linenr_start, linenr_end)
self:_fix_extmarks()
linenr_start = linenr_start or 1
Highlight.update()
for _, line in ipairs(self._lines) do
line:render(bufnr, ns_id, linenr_start, linenr_end)
linenr_start = linenr_start + 1
if linenr_end then
linenr_end = linenr_end + 1
end
end
end
---@param content string|NuiText|NuiLine
---@param highlight? string|table data for highlight
---@return NuiText|NuiLine
function Block:_append(content, highlight)
if #self._lines == 0 then
table.insert(self._lines, NuiLine())
end
if type(content) == "string" and self.fix_cr ~= false then
-- handle carriage returns. They overwrite the line from the first character
local cr = content:match("^.*()[\r]")
if cr then
table.remove(self._lines)
table.insert(self._lines, NuiLine())
content = content:sub(cr + 1)
end
end
return self._lines[#self._lines]:append(content, highlight)
end
---@param contents NoiceContent|NoiceContent[]
---@param highlight? string|table data for highlight
function Block:set(contents, highlight)
self:clear()
self:append(contents, highlight)
end
---@param contents NoiceContent|NoiceContent[]
---@param highlight? string|table data for highlight
function Block:append(contents, highlight)
if type(contents) == "string" then
contents = { { highlight or 0, contents } }
end
if contents._texts or contents._content or contents._lines or type(contents[1]) == "number" then
contents = { contents }
end
---@cast contents NoiceContent[]
for _, content in ipairs(contents) do
if content._texts then
---@cast content NuiLine
for _, t in ipairs(content._texts) do
self:_append(t)
end
elseif content._content then
---@cast content NuiText
self:_append(content)
elseif content._lines then
---@cast content NoiceBlock
for l, line in ipairs(content._lines) do
if l == 1 then
-- first line should be appended to the existing line
self:append(line)
else
-- other lines are appended as new lines
table.insert(self._lines, line)
end
end
else
---@cast content NoiceChunk
-- Handle newlines
---@type number|string|table, string
local attr_id, text = unpack(content)
-- msg_show messages can contain invalid \r characters
if self.fix_cr ~= false then
text = text:gsub("%^M", "\r")
text = text:gsub("\r\n", "\n")
end
---@type string|table|nil
local hl_group
if type(attr_id) == "number" then
hl_group = attr_id ~= 0 and Highlight.get_hl_group(attr_id) or nil
else
hl_group = attr_id
end
while text ~= "" do
local nl = text:find("\n")
local line = nl and text:sub(1, nl - 1) or text
self:_append(line, hl_group)
if nl then
self:newline()
text = text:sub(nl + 1)
else
break
end
end
end
end
end
function Block:last_line()
return self._lines[#self._lines]
end
-- trim empty lines at the beginning and the end of the block
function Block:trim_empty_lines()
while #self._lines > 0 and vim.trim(self._lines[1]:content()) == "" do
table.remove(self._lines, 1)
end
while #self._lines > 0 and vim.trim(self._lines[#self._lines]:content()) == "" do
table.remove(self._lines)
end
end
function Block:newline()
table.insert(self._lines, NuiLine())
end
return Block

View File

@ -0,0 +1,198 @@
local require = require("noice.util.lazy")
local Util = require("noice.util")
local NoiceText = require("noice.text")
local M = {}
---@param message NoiceMessage
---@param input NoiceMessage
---@param opts NoiceFormatOptions.message
function M.message(message, opts, input)
if opts.hl_group then
message:append(input:content(), opts.hl_group)
else
message:append(input)
end
end
---@param message NoiceMessage
---@param opts NoiceFormatOptions.text
function M.text(message, opts)
if opts.text and opts.text ~= "" then
message:append(opts.text, opts.hl_group)
end
end
---@param message NoiceMessage
---@param opts NoiceFormatOptions.progress
function M.progress(message, opts)
local contents = require("noice.text.format").format(message, opts.contents, {
debug = { enabled = false },
})
local value = vim.tbl_get(message.opts, unpack(vim.split(opts.key, ".", { plain = true })))
if type(value) == "number" then
local width = math.max(opts.width, contents:width() + 2)
local done_length = math.floor(value / 100 * width + 0.5)
local todo_length = width - done_length
if opts.align == "left" then
message:append(contents)
end
if width > contents:width() then
message:append(string.rep(" ", width - contents:width()))
end
if opts.align == "right" then
message:append(contents)
end
message:append(NoiceText("", {
hl_group = opts.hl_group_done,
hl_mode = "replace",
relative = true,
col = -width,
length = done_length,
}))
message:append(NoiceText("", {
hl_group = opts.hl_group,
hl_mode = "replace",
relative = true,
col = -width + done_length,
length = todo_length,
}))
else
message:append(contents)
end
end
---@param message NoiceMessage
---@param opts NoiceFormatOptions.level
function M.level(message, opts)
if message.level then
local str = message.level:sub(1, 1):upper() .. message.level:sub(2)
if opts.icons and opts.icons[message.level] then
str = opts.icons[message.level] .. " " .. str
end
message:append(" " .. str .. " ", opts.hl_group[message.level])
end
end
---@param message NoiceMessage
---@param opts NoiceFormatOptions.kind
function M.kind(message, opts)
if message.kind and message.kind ~= "" then
message:append(message.kind, opts.hl_group)
end
end
---@param message NoiceMessage
---@param opts NoiceFormatOptions.title
function M.title(message, opts)
if message.opts.title then
message:append(message.opts.title, opts.hl_group)
end
end
---@param message NoiceMessage
---@param opts NoiceFormatOptions.event
function M.event(message, opts)
if message.event then
message:append(message.event, opts.hl_group)
end
end
---@param message NoiceMessage
---@param opts NoiceFormatOptions.date
function M.date(message, opts)
message:append(os.date(opts.format, message.ctime), opts.hl_group)
end
---@param message NoiceMessage
---@param opts NoiceFormatOptions.debug
function M.debug(message, opts)
if not opts.enabled then
return
end
local blocking, reason = Util.is_blocking()
local debug = {
message:is({ cleared = true }) and "" or "",
"#" .. message.id,
message.event .. (message.kind and message.kind ~= "" and ("." .. message.kind) or ""),
blocking and "" .. reason,
}
message:append(NoiceText.virtual_text(" " .. table.concat(
vim.tbl_filter(
---@param t string
function(t)
return type(t) == "string"
end,
debug
),
" "
) .. " ", "DiagnosticVirtualTextInfo"))
if message.event == "cmdline" then
message:newline()
else
message:append(" ")
end
end
---@param message NoiceMessage
---@param opts NoiceFormatOptions.data
function M.data(message, opts)
local value = vim.tbl_get(message.opts, unpack(vim.split(opts.key, ".", { plain = true })))
if value then
message:append("" .. value, opts.hl_group)
end
end
---@param message NoiceMessage
---@param opts NoiceFormatOptions.spinner
function M.spinner(message, opts)
message:append(require("noice.util.spinners").spin(opts.name), opts.hl_group)
end
---@param message NoiceMessage
---@param _opts NoiceFormatOptions.cmdline
function M.cmdline(message, _opts)
if message.cmdline then
message.cmdline:format(message, true)
end
end
---@param message NoiceMessage
---@param input NoiceMessage
---@param opts NoiceFormatOptions.confirm
function M.confirm(message, opts, input)
if message.kind ~= "confirm" then
return message:append(input)
end
for l, line in ipairs(input._lines) do
if l ~= #input._lines then
message:append(line)
message:newline()
end
end
message:trim_empty_lines()
message:newline()
message:newline()
local _, _, buttons = input:last_line():content():find("(.*):")
if buttons then
buttons = vim.split(buttons, ", ")
for b, button in ipairs(buttons) do
local hl_group = button:find("%[") and opts.hl_group.default_choice or opts.hl_group.choice
message:append(" " .. button .. " ", hl_group)
if b ~= #buttons then
message:append(" ")
end
end
local padding = math.floor((message:width() - message:last_line():width()) / 2)
table.insert(message:last_line()._texts, 1, NoiceText((" "):rep(padding)))
end
end
return M

View File

@ -0,0 +1,172 @@
local require = require("noice.util.lazy")
local Util = require("noice.util")
local Config = require("noice.config")
local FormatConfig = require("noice.config.format")
local Formatters = require("noice.text.format.formatters")
local NuiText = require("nui.text")
local M = {}
---@alias NoiceFormatter fun(message:NoiceMessage, opts: table, input: NoiceMessage): boolean
---@alias NoiceFormat (string|table)[]
---@class NoiceFormatEntry
---@field formatter string
---@field before? NoiceFormatEntry
---@field after? NoiceFormatEntry
---@field opts table
---@param entry string|table<string, any>
---@return NoiceFormatEntry?
function M.parse_entry(entry)
if type(entry) == "string" then
entry = { entry }
end
if #entry ~= 1 then
Util.panic("Invalid format entry %s", vim.inspect(entry))
return
end
local text = entry[1]
---@type NoiceFormatEntry
local ret = {
formatter = "text",
opts = { text = text },
}
local before, name, after = text:match("^(.*){(.-)}(.*)$")
if before then
ret.formatter = name
ret.before = M.parse_entry(before)
ret.after = M.parse_entry(after)
end
local opts_key = ret.formatter:match("^data%.(.*)")
if opts_key then
entry.key = opts_key
ret.formatter = "data"
end
if not Formatters[ret.formatter] then
Util.panic("Invalid formatter %s", ret.formatter)
return
end
for k, v in pairs(entry) do
if k == "before" then
ret.before = M.parse_entry(v)
elseif k == "after" then
ret.after = M.parse_entry(v)
elseif type(k) ~= "number" then
---@diagnostic disable-next-line: no-unknown
ret.opts[k] = v
end
end
return ret
end
---@param message NoiceMessage
---@param format? NoiceFormat|string
---@param opts? NoiceFormatOptions
---@return NoiceMessage
function M.format(message, format, opts)
opts = vim.tbl_deep_extend("force", vim.deepcopy(Config.options.format), opts or {})
format = format or "default"
if type(format) == "string" then
format = vim.deepcopy(opts[format] or FormatConfig.builtin[format])
end
-- use existing message, with a separate _lines array
local ret = setmetatable({ _lines = {}, _debug = false }, { __index = message })
if Config.options.debug and not message._debug then
table.insert(format, 1, "{debug}")
ret._debug = true
end
for _, entry in ipairs(format) do
entry = M.parse_entry(entry)
if entry then
entry.opts = vim.tbl_deep_extend("force", vim.deepcopy(opts[entry.formatter] or {}), entry.opts)
local formatted = setmetatable({ _lines = {} }, { __index = message })
Formatters[entry.formatter](formatted, entry.opts, message)
if not formatted:is_empty() then
if entry.before then
Formatters[entry.before.formatter](ret, entry.before.opts, message)
end
ret:append(formatted)
if entry.after then
-- Else, add after
Formatters[entry.after.formatter](ret, entry.after.opts, message)
end
end
end
end
return ret
end
---@alias NoiceAlign "center" | "left" | "right" | "message-center" | "message-left" | "message-right" | "line-center" | "line-left" | "line-right"
---@param messages NoiceMessage[]
---@param align? NoiceAlign
function M.align(messages, align)
local width = 0
for _, m in ipairs(messages) do
for _, line in ipairs(m._lines) do
---@diagnostic disable-next-line: undefined-field
if line._texts[1] and line._texts[1].padding then
table.remove(line._texts, 1)
end
end
width = math.max(width, m:width())
end
for _, m in ipairs(messages) do
M._align(m, width, align)
end
end
---@param message NoiceMessage
---@param width integer
---@param align? NoiceAlign
function M._align(message, width, align)
if align == nil or align == "left" then
return
end
local align_object = "message"
---@type string, string
local ao, a = align:match("^(.-)%-(.-)$")
if a then
align = a
align_object = ao
end
for _, line in ipairs(message._lines) do
local w = align_object == "line" and line:width() or message:width()
if w < width then
if align == "right" then
table.insert(line._texts, 1, NuiText(string.rep(" ", width - w)))
---@diagnostic disable-next-line: no-unknown
line._texts[1].padding = true
elseif align == "center" then
table.insert(line._texts, 1, NuiText(string.rep(" ", math.floor((width - w) / 2 + 0.5))))
---@diagnostic disable-next-line: no-unknown
line._texts[1].padding = true
end
end
end
end
return M

View File

@ -0,0 +1,85 @@
local require = require("noice.util.lazy")
local ffi = require("noice.util.ffi")
local M = {}
---@class HLAttrs
---@field rgb_ae_attr number
---@field rgb_fg_color number
---@field rgb_bg_color number
---@field rgb_sp_color number
---@field hl_blend number
function M.setup()
M.attr2entry = ffi.syn_attr2entry --[[@as fun(attr: number): HLAttrs]]
vim.api.nvim_create_autocmd("ColorScheme", {
pattern = "*",
callback = function()
M.hl = {}
for attr_id, _ in pairs(M.hl_attrs) do
M._create_hl(attr_id)
end
end,
})
end
---@param attr_id number
function M.attr2entry(attr_id)
M.setup()
return M.attr2entry(attr_id)
end
---@type table<number, number>
M.hl = {}
---@type table<string, table>
M.hl_attrs = {}
---@type table<number, number>
M.queue = {}
function M.get_hl_group(attr_id)
if attr_id == 0 then
return "Normal"
end
M.queue[attr_id] = attr_id
return "NoiceAttr" .. tostring(attr_id)
end
function M.update()
for attr_id, _ in pairs(M.queue) do
M._create_hl(attr_id)
end
M.queue = {}
end
function M._create_hl(attr_id)
if attr_id == 0 then
return
end
if not M.hl_attrs[attr_id] then
local attrs = M.attr2entry(attr_id)
M.hl_attrs[attr_id] = {
fg = attrs.rgb_fg_color,
bg = attrs.rgb_bg_color,
sp = attrs.rgb_sp_color,
bold = bit.band(attrs.rgb_ae_attr, 0x02),
standout = bit.band(attrs.rgb_ae_attr, 0x0100),
italic = bit.band(attrs.rgb_ae_attr, 0x04),
underline = bit.band(attrs.rgb_ae_attr, 0x08),
undercurl = bit.band(attrs.rgb_ae_attr, 0x10),
nocombine = bit.band(attrs.rgb_ae_attr, 0x0200),
reverse = bit.band(attrs.rgb_ae_attr, 0x01),
blend = attrs.hl_blend ~= -1 and attrs.hl_blend or nil,
}
end
if not M.hl[attr_id] then
local hl_group = M.get_hl_group(attr_id)
vim.api.nvim_set_hl(0, hl_group, M.hl_attrs[attr_id])
M.hl[attr_id] = attr_id
end
end
return M

View File

@ -0,0 +1,124 @@
local require = require("noice.util.lazy")
local NuiText = require("nui.text")
local Treesitter = require("noice.text.treesitter")
local Syntax = require("noice.text.syntax")
local Markdown = require("noice.text.markdown")
---@class NoiceExtmark
---@field col? number
---@field end_col? number
---@field id? number
---@field hl_group? string
---@field virt_self_win_col? number
---@field relative? boolean
---@field lang? string
---@field lines? number
---@class NoiceText: NuiText
---@field super NuiText
---@field on_render? fun(text: NoiceText, buf:number, line: number, byte:number, col:number)
---@overload fun(content:string, highlight?:string|NoiceExtmark):NoiceText
---@diagnostic disable-next-line: undefined-field
local NoiceText = NuiText:extend("NoiceText")
function NoiceText.virtual_text(text, hl_group)
local content = (" "):rep(vim.api.nvim_strwidth(text))
return NoiceText(content, {
virt_text = { { text, hl_group } },
virt_text_win_col = 0,
relative = true,
})
end
function NoiceText.cursor(col)
return NoiceText(" ", {
hl_group = "NoiceCursor",
col = col,
relative = true,
})
end
---@param col? number
function NoiceText.syntax(lang, lines, col)
return NoiceText("", {
lang = lang,
col = col,
lines = lines,
})
end
---@param bufnr number buffer number
---@param ns_id number namespace id
---@param linenr number line number (1-indexed)
---@param byte_start number start byte position (0-indexed)
---@return nil
function NoiceText:highlight(bufnr, ns_id, linenr, byte_start)
if not self.extmark then
return
end
if self.extmark.lang then
local range = { linenr - self.extmark.lines, 0, linenr, byte_start + 1 }
if self.extmark.col then
range[2] = byte_start + self.extmark.col - 1
end
if Treesitter.has_lang(self.extmark.lang) then
Treesitter.highlight(bufnr, ns_id, range, self.extmark.lang)
else
Syntax.highlight(bufnr, ns_id, range, self.extmark.lang)
end
if self.extmark.lang == "markdown" then
Markdown.keys(bufnr)
Markdown.conceal_escape_characters(bufnr, ns_id, range)
end
return
end
local byte_start_orig = byte_start
---@type NoiceExtmark
local orig = vim.deepcopy(self.extmark)
local extmark = self.extmark
local col_start = 0
if extmark.relative or self.on_render then
---@type string
local line = vim.api.nvim_buf_get_text(bufnr, linenr - 1, 0, linenr - 1, byte_start, {})[1]
col_start = vim.api.nvim_strwidth(line)
end
if extmark.relative then
if extmark.virt_text_win_col then
extmark.virt_text_win_col = extmark.virt_text_win_col + col_start
end
if extmark.col then
extmark.col = extmark.col + byte_start
end
extmark.relative = nil
end
local length = self._length
if extmark.length then
self._length = extmark.length
extmark.length = nil
end
if extmark.col then
---@type number
byte_start = extmark.col
extmark.col = nil
end
NoiceText.super.highlight(self, bufnr, ns_id, linenr, byte_start)
if self.on_render then
self.on_render(self, bufnr, linenr, byte_start_orig, col_start)
end
self._length = length
self.extmark = orig
end
return NoiceText

View File

@ -0,0 +1,258 @@
local require = require("noice.util.lazy")
local NoiceText = require("noice.text")
local Config = require("noice.config")
---@alias MarkdownBlock {line:string}
---@alias MarkdownCodeBlock {code:string[], lang:string}
---@alias Markdown (MarkdownBlock|MarkdownCodeBlock)[]
local M = {}
function M.is_rule(line)
return line and line:find("^%s*[%*%-_][%*%-_][%*%-_]+%s*$")
end
function M.is_code_block(line)
return line and line:find("^%s*```")
end
function M.is_empty(line)
return line and line:find("^%s*$")
end
-- TODO:: upstream to treesitter
-- ((backslash_escape) @conceal (#set! conceal "_") (#contains? @conceal "\_"))
---@param text string
function M.html_entities(text)
local entities = { nbsp = "", lt = "<", gt = ">", amp = "&", quot = '"', apos = "'", ensp = " ", emsp = " " }
for entity, char in pairs(entities) do
text = text:gsub("&" .. entity .. ";", char)
end
return text
end
--- test\_foo
---@param buf buffer
---@param range number[]
function M.conceal_escape_characters(buf, ns, range)
local chars = "\\`*_{}[]()#+-.!/"
local regex = "\\["
for i = 1, #chars do
regex = regex .. "%" .. chars:sub(i, i)
end
regex = regex .. "]"
local lines = vim.api.nvim_buf_get_lines(buf, range[1], range[3] + 1, false)
for l, line in ipairs(lines) do
local c = line:find(regex)
while c do
vim.api.nvim_buf_set_extmark(buf, ns, range[1] + l - 1, c - 1, {
end_col = c,
conceal = "",
})
c = line:find(regex, c + 1)
end
end
end
-- This is a <code>test</code> **booo**
---@param text string
---@param opts? MarkdownFormatOptions
function M.parse(text, opts)
opts = opts or {}
---@type string
text = text:gsub("</?pre>", "```"):gsub("\r", "")
-- text = text:gsub("</?code>", "`")
text = M.html_entities(text)
---@type Markdown
local ret = {}
local lines = vim.split(text, "\n")
local l = 1
local function eat_nl()
while M.is_empty(lines[l + 1]) do
l = l + 1
end
end
while l <= #lines do
local line = lines[l]
if M.is_empty(line) then
local is_start = l == 1
eat_nl()
local is_end = l == #lines
if not (M.is_code_block(lines[l + 1]) or M.is_rule(lines[l + 1]) or is_start or is_end) then
table.insert(ret, { line = "" })
end
elseif M.is_code_block(line) then
---@type string
local lang = line:match("```%s*(%S+)") or opts.ft or "text"
local block = { lang = lang, code = {} }
while lines[l + 1] and not M.is_code_block(lines[l + 1]) do
table.insert(block.code, lines[l + 1])
l = l + 1
end
local prev = ret[#ret]
if prev and not M.is_rule(prev.line) then
table.insert(ret, { line = "" })
end
table.insert(ret, block)
l = l + 1
eat_nl()
elseif M.is_rule(line) then
table.insert(ret, { line = "---" })
eat_nl()
else
local prev = ret[#ret]
if prev and prev.code then
table.insert(ret, { line = "" })
end
table.insert(ret, { line = line })
end
l = l + 1
end
return ret
end
function M.get_highlights(line)
---@type NoiceText[]
local ret = {}
for pattern, hl_group in pairs(Config.options.markdown.highlights) do
local from = 1
while from do
---@type number, string?
local to, match
---@type number, number, string?
from, to, match = line:find(pattern, from)
if match then
---@type number, number
from, to = line:find(match, from)
end
if from then
table.insert(
ret,
NoiceText("", {
hl_group = hl_group,
col = from - 1,
length = to - from + 1,
-- priority = 120,
})
)
end
from = to and to + 1 or nil
end
end
return ret
end
---@alias MarkdownFormatOptions {ft?: string}
---@param message NoiceMessage
---@param text string
---@param opts? MarkdownFormatOptions
--```lua
--local a = 1
--local b = true
--```
--foo tex
function M.format(message, text, opts)
opts = opts or {}
local blocks = M.parse(text, opts)
local md_lines = 0
local function emit_md()
if md_lines > 0 then
message:append(NoiceText.syntax("markdown", md_lines))
md_lines = 0
end
end
for l = 1, #blocks do
local block = blocks[l]
if block.code then
emit_md()
message:newline()
---@cast block MarkdownCodeBlock
for c, line in ipairs(block.code) do
message:append(line)
if c == #block.code then
message:append(NoiceText.syntax(block.lang, #block.code))
else
message:newline()
end
end
else
---@cast block MarkdownBlock
message:newline()
if M.is_rule(block.line) then
M.horizontal_line(message)
else
message:append(block.line)
for _, t in ipairs(M.get_highlights(block.line)) do
message:append(t)
end
md_lines = md_lines + 1
end
end
end
emit_md()
end
function M.keys(buf)
if not vim.api.nvim_buf_is_valid(buf) then
return
end
if vim.b[buf].markdown_keys then
return
end
local function map(lhs)
vim.keymap.set("n", lhs, function()
local line = vim.api.nvim_get_current_line()
local pos = vim.api.nvim_win_get_cursor(0)
local col = pos[2] + 1
for pattern, handler in pairs(Config.options.markdown.hover) do
local from = 1
local to, url
while from do
from, to, url = line:find(pattern, from)
if from and col >= from and col <= to then
return handler(url)
end
if from then
from = to + 1
end
end
end
vim.api.nvim_feedkeys(lhs, "n", false)
end, { buffer = buf, silent = true })
end
map("gx")
map("K")
vim.b[buf].markdown_keys = true
end
---@param message NoiceMessage
function M.horizontal_line(message)
message:append(NoiceText("", {
virt_text_win_col = 0,
virt_text = { { string.rep("", vim.go.columns), "@punctuation.special.markdown" } },
priority = 100,
}))
end
return M

View File

@ -0,0 +1,29 @@
local M = {}
--- Highlights a region of the buffer with a given language
---@param buf buffer buffer to highlight. Defaults to the current buffer if 0
---@param ns number namespace for the highlights
---@param range {[1]:number, [2]:number, [3]: number, [4]: number} (table) Region to highlight {start_row, start_col, end_row, end_col}
---@param lang string treesitter language
function M.highlight(buf, ns, range, lang)
vim.api.nvim_buf_call(buf, function()
local group = "@" .. lang:upper()
-- HACK: reset current_syntax, since some syntax files like markdown won't load if it is already set
pcall(vim.api.nvim_buf_del_var, buf, "current_syntax")
if not pcall(vim.cmd, string.format("syntax include %s syntax/%s.vim", group, lang)) then
return
end
vim.cmd(
string.format(
"syntax region %s start=+\\%%%dl+ end=+\\%%%dl+ contains=%s keepend",
lang .. range[1],
range[1] + 1,
range[3] + 1,
group
)
)
end)
end
return M

View File

@ -0,0 +1,83 @@
local M = {}
M.queries = {}
function M.get_query(lang)
if not M.queries[lang] then
---@diagnostic disable-next-line: deprecated
M.queries[lang] = (vim.treesitter.query.get or vim.treesitter.query.get_query)(lang, "highlights")
end
return M.queries[lang]
end
function M.get_lang(ft)
return vim.treesitter.language.get_lang and vim.treesitter.language.get_lang(ft) or ft
end
function M.has_lang(lang)
if vim.treesitter.language.get_lang then
lang = vim.treesitter.language.get_lang(lang) or lang
return pcall(vim.treesitter.language.add, lang)
end
---@diagnostic disable-next-line: deprecated
return vim.treesitter.language.require_language(lang, nil, true)
end
--- Highlights a region of the buffer with a given language
---@param buf integer? buffer to highlight. Defaults to the current buffer if 0
---@param ns number namespace for the highlights
---@param range {[1]:number, [2]:number, [3]: number, [4]: number} (table) Region to highlight {start_row, start_col, end_row, end_col}
---@param lang string treesitter language
-- luacheck: no redefined
function M.highlight(buf, ns, range, lang)
lang = M.get_lang(lang)
buf = (buf == 0 or buf == nil) and vim.api.nvim_get_current_buf() or buf
-- we can't use a cached parser here since that could interfere with the existing parser of the buffer
local LanguageTree = require("vim.treesitter.languagetree")
local opts = { injections = { php = "", html = "" } }
local parser = LanguageTree.new(buf --[[@as integer]], lang, opts)
---@diagnostic disable-next-line: invisible
parser:set_included_regions({ { range } })
parser:parse(true)
parser:for_each_tree(function(tstree, tree)
if not tstree then
return
end
local highlighter_query = M.get_query(tree:lang())
-- Some injected languages may not have highlight queries.
if not highlighter_query then
return
end
local iter = highlighter_query:iter_captures(tstree:root(), buf, range[1], range[3])
for capture, node, metadata in iter do
---@type number, number, number, number
local start_row, start_col, end_row, end_col = node:range()
---@type string
local name = highlighter_query.captures[capture]
local hl = 0
if not vim.startswith(name, "_") then
hl = vim.api.nvim_get_hl_id_by_name("@" .. name .. "." .. lang)
end
local is_spell = name == "spell"
if hl and not is_spell then
pcall(vim.api.nvim_buf_set_extmark, buf, ns, start_row, start_col, {
end_line = end_row,
end_col = end_col,
hl_group = hl,
priority = (tonumber(metadata.priority) or 100) + 10, -- add 10, so it will be higher than the standard highlighter of the buffer
conceal = metadata.conceal,
})
end
end
end)
end
return M

View File

@ -0,0 +1,54 @@
---@class NuiRelative
---@field type "'cursor'"|"'editor'"|"'win'"
---@field winid? number
---@field position? { row: number, col: number }
---@alias _.NuiBorderStyle "'double'"|"'none'"|"'rounded'"|"'shadow'"|"'single'"|"'solid'"
---@alias _.NuiBorderPadding {top:number, right:number, bottom:number, left:number}
---@class _.NuiBorder
---@field padding? _.NuiBorderPadding
---@field style? _.NuiBorderStyle
---@field text? { top: string|boolean, bottom: string|boolean }
---@class NuiBorder: _.NuiBorder
---@field padding? _.NuiBorderPadding|number[]
---@class _.NuiBaseOptions
---@field relative? NuiRelative
---@field enter? boolean
---@field timeout? number
---@field buf_options? vim.bo
---@field win_options? vim.wo
---@field close? {events?:string[], keys?:string[]}
---@class NuiBaseOptions: _.NuiBaseOptions
---@field relative "'cursor'"|"'editor'"|"'win'"|NuiRelative
---@alias NuiAnchor "NW"|"NE"|"SW"|"SE"
---@class _.NuiPopupOptions: _.NuiBaseOptions
---@field position { row: number|string, col: number|string}
---@field size { width: number|string, height: number|string, max_width:number, max_height:number}
---@field border? _.NuiBorder
---@field anchor? NuiAnchor|"auto"
---@field focusable boolean
---@field zindex? number
---@class NuiPopupOptions: NuiBaseOptions,_.NuiPopupOptions
---@field position number|string|{ row: number|string, col: number|string}
---@field size number|string|{ row: number|string, col: number|string}
---@field border? NuiBorder|_.NuiBorderStyle
---@class _.NuiSplitOptions: _.NuiBaseOptions
---@field position "top"|"right"|"bottom"|"left"
---@field scrollbar? boolean
---@field min_size? number
---@field max_size? number
---@field size number|string
---@class NuiSplitOptions: NuiBaseOptions,_.NuiSplitOptions
---@alias NoiceNuiOptions NuiSplitOptions|NuiPopupOptions|{type: "split"|"popup"}
---@alias _.NoiceNuiOptions _.NuiSplitOptions|_.NuiPopupOptions|{type: "split"|"popup"}

View File

@ -0,0 +1,276 @@
local require = require("noice.util.lazy")
local Message = require("noice.message")
local Manager = require("noice.message.manager")
local Config = require("noice.config")
local NoiceText = require("noice.text")
local Hacks = require("noice.util.hacks")
local Object = require("nui.object")
local M = {}
M.message = Message("cmdline", nil)
---@enum CmdlineEvent
M.events = {
cmdline = "cmdline",
show = "cmdline_show",
hide = "cmdline_hide",
pos = "cmdline_pos",
special_char = "cmdline_special_char",
block_show = "cmdline_block_show",
block_append = "cmdline_block_append",
block_hide = "cmdline_block_hide",
}
---@type NoiceCmdline?
M.active = nil
---@alias NoiceCmdlineFormatter fun(cmdline: NoiceCmdline): {icon?:string, offset?:number, view?:NoiceViewOptions}
---@class CmdlineState
---@field content {[1]: integer, [2]: string}[]
---@field pos number
---@field firstc string
---@field prompt string
---@field indent number
---@field level number
---@field block table
---@class CmdlineFormat
---@field kind string
---@field pattern? string|string[]
---@field view string
---@field conceal? boolean
---@field icon? string
---@field icon_hl_group? string
---@field opts? NoiceViewOptions
---@field title? string
---@field lang? string
---@class NoiceCmdline
---@field state CmdlineState
---@field offset integer
---@overload fun(state:CmdlineState): NoiceCmdline
local Cmdline = Object("NoiceCmdline")
---@param state CmdlineState
function Cmdline:init(state)
self.state = state or {}
self.offset = 0
end
function Cmdline:get()
return table.concat(
vim.tbl_map(function(c)
return c[2]
end, self.state.content),
""
)
end
---@return CmdlineFormat
function Cmdline:get_format()
if self.state.prompt and self.state.prompt ~= "" then
return Config.options.cmdline.format.input
end
local line = self.state.firstc .. self:get()
---@type {offset:number, format: CmdlineFormat}[]
local ret = {}
for _, format in pairs(Config.options.cmdline.format) do
local patterns = type(format.pattern) == "table" and format.pattern or { format.pattern }
---@cast patterns string[]
for _, pattern in ipairs(patterns) do
local from, to = line:find(pattern)
-- if match and cmdline pos is visible
if from and self.state.pos >= to - 1 then
ret[#ret + 1] = {
offset = to or 0,
format = format,
}
end
end
end
table.sort(ret, function(a, b)
return a.offset > b.offset
end)
local format = ret[1]
if format then
self.offset = format.format.conceal and format.offset or 0
return format.format
end
self.offset = 0
return {
kind = self.state.firstc,
view = "cmdline_popup",
}
end
---@param message NoiceMessage
---@param text_only? boolean
function Cmdline:format(message, text_only)
local format = self:get_format()
message.fix_cr = false
if format.icon then
message:append(NoiceText.virtual_text(format.icon, format.icon_hl_group))
message:append(" ")
end
if not text_only then
message.kind = format.kind
end
-- FIXME: prompt
if self.state.prompt ~= "" then
message:append(self.state.prompt, "NoiceCmdlinePrompt")
end
if not format.conceal then
message:append(self.state.firstc)
end
local cmd = self:get():sub(self.offset)
message:append(cmd)
if format.lang then
message:append(NoiceText.syntax(format.lang, 1, -vim.fn.strlen(cmd)))
end
if not text_only then
local cursor = NoiceText.cursor(-self:length() + self.state.pos)
cursor.on_render = M.on_render
message:append(cursor)
end
end
function Cmdline:width()
return vim.api.nvim_strwidth(self:get())
end
function Cmdline:length()
return vim.fn.strlen(self:get())
end
---@type NoiceCmdline[]
M.cmdlines = {}
M.skipped = false
function M.on_show(event, content, pos, firstc, prompt, indent, level)
local c = Cmdline({
event = event,
content = content,
pos = pos,
firstc = firstc,
prompt = prompt,
indent = indent,
level = level,
})
-- This was triggered by a force redraw, so skip it
if c:get():find(Hacks.SPECIAL, 1, true) then
M.skipped = true
return
end
M.skipped = false
local last = M.cmdlines[level] and M.cmdlines[level].state
if not vim.deep_equal(c.state, last) then
M.active = c
M.cmdlines[level] = c
M.update()
end
end
function M.on_hide(_, level)
if M.cmdlines[level] then
M.cmdlines[level] = nil
local active = M.active
vim.defer_fn(function()
if M.active == active then
M.active = nil
end
end, 100)
M.update()
end
end
function M.on_pos(_, pos, level)
if M.skipped then
return
end
local c = M.cmdlines[level]
if c and c.state.pos ~= pos then
M.cmdlines[level].state.pos = pos
M.update()
end
end
---@class CmdlinePosition
---@field win number Window containing the cmdline
---@field buf number Buffer containing the cmdline
---@field bufpos {row:number, col:number} (1-0)-indexed position of the cmdline in the buffer
---@field screenpos {row:number, col:number} (1-0)-indexed screen position of the cmdline
M.position = nil
---@param buf number
---@param line number
---@param byte number
function M.on_render(_, buf, line, byte)
Hacks.cmdline_force_redraw()
local win = vim.fn.bufwinid(buf)
if win ~= -1 then
-- FIXME: check with cmp
-- FIXME: state.pos?
local cmdline_start = byte - (M.last():length() - M.last().offset)
local cursor = byte - M.last():length() + M.last().state.pos
vim.schedule(function()
if vim.api.nvim_win_is_valid(win) then
vim.api.nvim_win_set_cursor(win, { 1, cursor })
vim.api.nvim_win_call(win, function()
local width = vim.api.nvim_win_get_width(win)
local leftcol = math.max(cursor - width + 1, 0)
vim.fn.winrestview({ leftcol = leftcol })
end)
end
end)
local pos = vim.fn.screenpos(win, line, cmdline_start)
M.position = {
buf = buf,
win = win,
bufpos = {
row = line,
col = cmdline_start,
},
screenpos = {
row = pos.row,
col = pos.col - 1,
},
}
end
end
function M.last()
local last = math.max(1, unpack(vim.tbl_keys(M.cmdlines)))
return M.cmdlines[last]
end
function M.update()
M.message:clear()
local cmdline = M.last()
if cmdline then
cmdline:format(M.message)
Hacks.hide_cursor()
Manager.add(M.message)
else
Manager.remove(M.message)
Hacks.show_cursor()
end
end
return M

View File

@ -0,0 +1,5 @@
local M = {}
function M.on_destroy() end
return M

View File

@ -0,0 +1,193 @@
local require = require("noice.util.lazy")
local Config = require("noice.config")
local Util = require("noice.util")
local Router = require("noice.message.router")
local Manager = require("noice.message.manager")
---@alias NoiceEvent MsgEvent|CmdlineEvent|NotifyEvent|LspEvent
---@alias NoiceKind MsgKind|NotifyLevel|LspKind
local M = {}
M._attached = false
---@type table<string, table|false>
M._handlers = {}
function M.setup()
local widgets = {
messages = "msg",
cmdline = "cmdline",
popupmenu = "popupmenu",
}
-- Check if we're running inside a GUI that already externalizes some widgets
---@type table<string, boolean>
local ui_widgets = {}
local uis = vim.api.nvim_list_uis()
for _, ui in ipairs(uis) do
for ext, _ in pairs(widgets) do
if ui["ext_" .. ext] then
ui_widgets[ext] = true
end
end
end
M._handlers = {}
---@type table<string, boolean>
local options = {}
for ext, widget in pairs(widgets) do
-- only enable if configured and not enabled in the GUI
if Config.options[ext].enabled and not ui_widgets[ext] then
options["ext_" .. ext] = true
M._handlers[widget] = _G.require("noice.ui." .. widget)
else
if ui_widgets[ext] and Config.options.debug then
Util.warn("Disabling ext_" .. ext)
end
M._handlers[widget] = false
end
end
return options
end
function M.enable()
local options = M.setup()
if vim.tbl_isempty(options) then
if Config.options.debug then
vim.schedule(function()
Util.warn("No extensions enabled")
end)
end
return
end
if options.ext_messages then
require("noice.ui.msg").setup()
end
local safe_handle = Util.protect(M.handle, { msg = "An error happened while handling a ui event" })
M._attached = true
local stack_level = 0
---@diagnostic disable-next-line: redundant-parameter
vim.ui_attach(Config.ns, options, function(event, kind, ...)
if Util.is_exiting() then
return true
end
local handler = M.get_handler(event, kind, ...)
if not handler then
return
end
if stack_level > 50 then
Util.panic("Event loop detected. Shutting down...")
return true
end
stack_level = stack_level + 1
local tick = Manager.tick()
safe_handle(handler, event, kind, ...)
-- check if we need to update the ui
if Manager.tick() > tick then
Util.debug(function()
local _, blocking = Util.is_blocking()
return { event, "sl:" .. stack_level, "tick:" .. tick, blocking or false, kind }
end)
if
require("noice.util.ffi").textlock == 0
and Util.is_blocking()
and event ~= "msg_ruler"
and kind ~= "search_count"
then
Util.try(Router.update)
end
else
local widget = M.parse_event(event)
Util.stats.track(widget .. ".skipped")
end
stack_level = stack_level - 1
-- make sure only Noice handles these events
return true
end)
vim.api.nvim_create_autocmd("SwapExists", {
group = vim.api.nvim_create_augroup("noice-swap-exists", { clear = true }),
callback = function()
Util.try(Router.update)
end,
})
end
function M.redirect()
M.disable()
Router.echo_pending()
vim.schedule(M.enable)
end
function M.disable()
if M._attached then
M._attached = false
vim.ui_detach(Config.ns)
end
end
---@return string, string
function M.parse_event(event)
return event:match("([a-z]+)_(.*)")
end
---@param event string
function M.get_handler(event, ...)
local event_group, event_type = event:match("([a-z]+)_(.*)")
local on = "on_" .. event_type
local handler = M._handlers[event_group]
-- false means this is a disabled handler
if handler == false then
return
end
if not handler then
if Config.options.debug then
vim.schedule(function()
Util.error_once("No ui router for " .. event_group)
end)
end
return
end
if type(handler[on]) ~= "function" then
local args = { ... }
if Config.options.debug then
vim.schedule(function()
Util.error_once(
"No ui router for **"
.. event
.. "** events\n```lua\n"
.. vim.inspect({ group = event_group, on = on, args = args })
.. "\n```"
)
end)
end
return
end
return handler[on]
end
---@param handler fun(...)
---@param event string
function M.handle(handler, event, ...)
handler(event, ...)
end
return M

View File

@ -0,0 +1,182 @@
local require = require("noice.util.lazy")
local Manager = require("noice.message.manager")
local Message = require("noice.message")
local Hacks = require("noice.util.hacks")
local State = require("noice.ui.state")
local Cmdline = require("noice.ui.cmdline")
local M = {}
---@enum MsgEvent
M.events = {
show = "msg_show",
clear = "msg_clear",
showmode = "msg_showmode",
showcmd = "msg_showcmd",
ruler = "msg_ruler",
history_show = "msg_history_show",
history_clear = "msg_history_clear",
}
---@enum MsgKind
M.kinds = {
-- echo
empty = "", -- (empty) Unknown (consider a feature-request: |bugs|)
echo = "echo", -- |:echo| message
echomsg = "echomsg", -- |:echomsg| message
-- input related
confirm = "confirm", -- |confirm()| or |:confirm| dialog
confirm_sub = "confirm_sub", -- |:substitute| confirm dialog |:s_c|
return_prompt = "return_prompt", -- |press-enter| prompt after a multiple messages
-- error/warnings
emsg = "emsg", -- Error (|errors|, internal error, |:throw|, …)
echoerr = "echoerr", -- |:echoerr| message
lua_error = "lua_error", -- Error in |:lua| code
rpc_error = "rpc_error", -- Error response from |rpcrequest()|
wmsg = "wmsg", -- Warning ("search hit BOTTOM", |W10|, …)
-- hints
quickfix = "quickfix", -- Quickfix navigation message
search_count = "search_count", -- Search count message ("S" flag of 'shortmess')
}
---@type NoiceMessage
M.last = nil
---@type NoiceMessage[]
M._messages = {}
M._did_setup = false
function M.setup()
if M._did_setup then
return
end
M._did_setup = true
local hist = vim.trim(vim.api.nvim_cmd({ cmd = "messages" }, { output = true }))
if hist == "" then
return
end
local message = M.get(M.events.show)
message:set(hist)
Manager.add(message)
end
function M.is_error(kind)
return vim.tbl_contains({ M.kinds.echoerr, M.kinds.lua_error, M.kinds.rpc_error, M.kinds.emsg }, kind)
end
function M.is_warning(kind)
return kind == M.kinds.wmsg
end
function M.get(event, kind)
local id = event .. "." .. (kind or "")
if not M._messages[id] then
M._messages[id] = Message(event, kind)
end
return M._messages[id]
end
---@param kind MsgKind
---@param content NoiceContent[]
function M.on_show(event, kind, content, replace_last)
if kind == M.kinds.return_prompt then
return M.on_return_prompt()
elseif kind == M.kinds.confirm or kind == M.kinds.confirm_sub then
return M.on_confirm(event, kind, content)
end
if State.skip(event, kind, content, replace_last) then
return
end
if M.last and replace_last then
Manager.clear({ message = M.last })
M.last = nil
end
local message
if kind == M.kinds.search_count then
message = M.get(event, kind)
Hacks.fix_nohlsearch()
else
message = Message(event, kind)
message.cmdline = Cmdline.active
end
message:set(content)
message:trim_empty_lines()
if M.is_error(kind) then
message.level = "error"
elseif M.is_warning(kind) then
message.level = "warn"
end
M.last = message
Manager.add(message)
end
function M.on_clear()
State.clear("msg_show")
M.last = nil
local message = M.get(M.events.show, M.kinds.search_count)
Manager.remove(message)
end
-- mode like recording...
function M.on_showmode(event, content)
if State.skip(event, content) then
return
end
local message = M.get(event)
if vim.tbl_isempty(content) then
if event == "msg_showmode" then
Manager.remove(message)
end
else
message:set(content)
Manager.add(message)
end
end
M.on_showcmd = M.on_showmode
M.on_ruler = M.on_showmode
function M.on_return_prompt()
return vim.api.nvim_input("<cr>")
end
---@param content NoiceChunk[]
function M.on_confirm(event, kind, content)
if State.skip(event, kind, content) then
return
end
local message = Message(event, kind, content)
if not message:content():find("%s+$") then
message:append(" ")
end
message:append(" ", "NoiceCursor")
Manager.add(message)
vim.schedule(function()
Manager.remove(message)
end)
end
---@param entries { [1]: string, [2]: NoiceChunk[]}[]
function M.on_history_show(event, entries)
local contents = {}
for _, e in pairs(entries) do
local content = e[2]
table.insert(contents, { 0, "\n" })
vim.list_extend(contents, content)
end
local message = M.get(event)
message:set(contents)
Manager.add(message)
end
function M.on_history_clear() end
return M

View File

@ -0,0 +1,70 @@
local require = require("noice.util.lazy")
local Popupmenu = require("noice.ui.popupmenu")
local cmp = require("cmp")
local cmp_config = require("cmp.config")
---@class NoiceCmpSource: cmp.Source
---@field before_line string
---@field items {label: string}[]
local source = {}
source.new = function()
return setmetatable({
items = {},
}, { __index = source })
end
function source:complete(_params, callback)
if not Popupmenu.state.visible then
return callback()
end
local items = {}
for i, item in ipairs(Popupmenu.state.items) do
table.insert(items, {
label = item.word,
kind = cmp.lsp.CompletionItemKind.Variable,
preselect = i == (Popupmenu.state.selected + 1),
})
end
callback({ items = items, isIncomplete = true })
end
local M = {}
function M.setup()
cmp.register_source("noice_popupmenu", source.new())
for _, mode in ipairs({ ":" }) do
if not cmp_config.cmdline[mode] then
cmp.setup.cmdline(mode, {
mapping = cmp.mapping.preset.cmdline(),
sources = cmp.config.sources({
{ name = "noice_popupmenu" },
}),
})
cmp.core:prepare()
end
end
end
function M.on_show()
local config = vim.deepcopy(cmp.get_config())
config.sources = cmp.config.sources({ { name = "noice_popupmenu" } })
cmp.core:prepare()
cmp.complete({
config = config,
})
end
function M.on_select()
M.on_show()
end
function M.on_hide()
-- cmp.close()
end
return M

View File

@ -0,0 +1,98 @@
local require = require("noice.util.lazy")
local Util = require("noice.util")
local Config = require("noice.config")
local M = {}
---@class PopupmenuBackend
---@field setup fun()
---@field on_show fun(state: Popupmenu)
---@field on_select fun(state: Popupmenu)
---@field on_hide fun()
---@class CompleteItem
---@field word string the text that will be inserted, mandatory
---@field abbr? string abbreviation of "word"; when not empty it is used in the menu instead of "word"
---@field menu? string extra text for the popup menu, displayed after "word" or "abbr"
---@field info? string more information about the item, can be displayed in a preview window
---@field kind? string single letter indicating the type of completion
---@field icase? boolean when non-zero case is to be ignored when comparing items to be equal; when omitted zero is used, thus items that only differ in case are added
---@field equal? boolean when non-zero, always treat this item to be equal when comparing. Which means, "equal=1" disables filtering of this item.
---@field dup? boolean when non-zero this match will be added even when an item with the same word is already present.
---@field empty? boolean when non-zero this match will be added even when it is an empty string
---@field user_data? any custom data which is associated with the item and available in |v:completed_item|; it can be any type; defaults to an empty string
---@field text? NuiLine
---@class Popupmenu
---@field selected number
---@field col number
---@field row number
---@field grid number
---@field items CompleteItem[]
M.state = {
visible = false,
items = {},
}
---@type PopupmenuBackend
M.backend = nil
function M.setup()
if Config.options.popupmenu.backend == "cmp" then
M.backend = require("noice.ui.popupmenu.cmp")
elseif Config.options.popupmenu.backend == "nui" then
M.backend = require("noice.ui.popupmenu.nui")
end
M.backend.setup()
end
M.setup = Util.once(M.setup)
---@param items string[][]
function M.on_show(_, items, selected, row, col, grid)
local state = {
items = vim.tbl_map(
---@param item string[]
function(item)
return {
word = item[1],
kind = item[2],
menu = item[3],
info = item[4],
}
end,
items
),
visible = true,
selected = selected,
row = row,
col = col,
grid = grid,
}
if not vim.deep_equal(state, M.state) then
M.state = state
M.setup()
M.backend.on_show(M.state)
end
end
function M.on_select(_, selected)
if M.state.selected ~= selected then
M.state.selected = selected
M.state.visible = true
M.backend.on_select(M.state)
end
end
function M.on_hide()
if M.state.visible then
M.state.visible = false
vim.schedule(function()
if not M.state.visible then
M.backend.on_hide()
end
end)
end
end
return M

View File

@ -0,0 +1,270 @@
local require = require("noice.util.lazy")
local Util = require("noice.util")
local Config = require("noice.config")
local Menu = require("nui.menu")
local Api = require("noice.api")
local NuiLine = require("nui.line")
local Scrollbar = require("noice.view.scrollbar")
local Highlights = require("noice.config.highlights")
local M = {}
---@type NuiMenu|NuiTree
M.menu = nil
---@type NoiceScrollbar
M.scroll = nil
function M.setup() end
---@param state Popupmenu
function M.align(state)
local max_width = 0
for _, item in ipairs(state.items) do
max_width = math.max(max_width, item.text:width())
end
for _, item in ipairs(state.items) do
local width = item.text:width()
if width < max_width then
item.text:append(string.rep(" ", max_width - width))
end
end
return max_width
end
---@param item CompleteItem
---@param prefix? string
function M.format_abbr(item, prefix)
local text = item.abbr or item.word
if prefix and text:lower():find(prefix:lower(), 1, true) == 1 then
item.text:append(text:sub(1, #prefix), "NoicePopupmenuMatch")
item.text:append(text:sub(#prefix + 1), "NoiceCompletionItemWord")
else
item.text:append(text, "NoiceCompletionItemWord")
end
end
---@param item CompleteItem
function M.format_menu(item)
if item.menu and item.menu ~= "" then
item.text:append(" ")
item.text:append(item.menu, "NoiceCompletionItemMenu")
end
end
---@param item CompleteItem
function M.format_kind(item)
if item.kind and item.kind ~= "" then
local hl_group = "CompletionItemKind" .. item.kind
hl_group = Highlights.defaults[hl_group] and ("Noice" .. hl_group) or "NoiceCompletionItemKindDefault"
local icon = Config.options.popupmenu.kind_icons[item.kind]
item.text:append(" ")
if icon then
item.text:append(vim.trim(icon) .. " ", hl_group)
end
item.text:append(item.kind, hl_group)
end
end
---@param state Popupmenu
function M.opts(state)
local is_cmdline = state.grid == -1
local view = require("noice.config.views").get_options(is_cmdline and "cmdline_popupmenu" or "popupmenu")
local _opts = vim.deepcopy(view or {})
_opts.enter = false
_opts.type = "popup"
local opts = Util.nui.normalize(_opts)
---@cast opts _.NuiPopupOptions
local padding = opts.border and opts.border.padding or {
left = 0,
right = 0,
top = 0,
bottom = 0,
}
local position_auto = not opts.position or opts.position.col == "auto"
if is_cmdline then
if position_auto then
-- Anchor to the cmdline
local pos = Api.get_cmdline_position()
if pos then
opts.relative = { type = "editor" }
opts.position = {
row = pos.screenpos.row,
col = pos.screenpos.col + state.col - padding.left,
}
-- the padding might get the window below 0
if opts.position.col < 0 then
opts.position.col = 0
end
if pos.screenpos.row == vim.go.lines then
opts.position.row = opts.position.row - 1
opts.anchor = "SW"
end
end
end
else
opts.relative = { type = "cursor" }
local border = vim.tbl_get(opts, "border", "style")
local offset = (border == nil or border == "none") and 0 or 1
opts.position = {
row = 1 + offset,
col = -padding.left,
}
end
-- manage left/right padding on the line
-- otherwise the selected CursorLine does not extend to the edges
if opts.border and opts.border.padding then
opts.border.padding = vim.tbl_deep_extend("force", {}, padding, { left = 0, right = 0 })
if opts.size and type(opts.size.width) == "number" then
opts.size.width = opts.size.width + padding.left + padding.right
end
end
return opts, padding
end
---@param state Popupmenu
function M.show(state)
-- M.on_hide()
local is_cmdline = state.grid == -1
local opts, padding = M.opts(state)
---@type string?
local prefix = nil
if is_cmdline then
prefix = vim.fn.getcmdline():sub(state.col + 1, vim.fn.getcmdpos() - 1)
elseif #state.items > 0 then
prefix = state.items[1].word
for _, item in ipairs(state.items) do
for i = 1, #prefix do
if prefix:sub(i, i) ~= item.word:sub(i, i) then
prefix = prefix:sub(1, i - 1)
break
end
end
end
end
for _, item in ipairs(state.items) do
if type(item) == "string" then
item = { word = item }
end
item.text = NuiLine()
if padding.left then
item.text:append(string.rep(" ", padding.left))
end
end
local max_width = 0
for _, format in ipairs({ M.format_abbr, M.format_menu, M.format_kind }) do
for _, item in ipairs(state.items) do
format(item, prefix)
end
max_width = M.align(state)
end
for _, item in ipairs(state.items) do
if padding.right then
item.text:append(string.rep(" ", padding.right))
end
for _, t in ipairs(item.text._texts) do
t._content = t._content:gsub("[\n\r]+", " ")
end
end
opts = vim.tbl_deep_extend(
"force",
opts,
Util.nui.get_layout({
width = max_width + 1, -- +1 for scrollbar
height = #state.items,
}, opts)
)
---@type NuiTreeNode[]
local items = vim.tbl_map(function(item)
return Menu.item(item)
end, state.items)
for i, item in ipairs(items) do
item._index = i
end
if M.menu then
M.menu._.items = items
M.menu.tree:set_nodes(items)
M.menu.tree:render()
M.menu:update_layout(opts)
else
M.create(items, opts)
end
-- redraw is needed when in blocking mode
if Util.is_blocking() then
Util.redraw()
end
M.on_select(state)
end
---@param opts _.NuiPopupOptions
---@param items NuiTreeNode[]
function M.create(items, opts)
M.menu = Menu(opts, {
lines = items,
})
M.menu:mount()
Util.tag(M.menu.bufnr, "popupmenu")
if M.menu.border then
Util.tag(M.menu.border.bufnr, "popupmenu.border")
end
M.scroll = Scrollbar({
winnr = M.menu.winid,
padding = Util.nui.normalize_padding(opts.border),
})
M.scroll:mount()
end
---@param state Popupmenu
function M.on_show(state)
M.show(state)
end
---@param state Popupmenu
function M.on_select(state)
if M.menu and state.selected ~= -1 then
vim.api.nvim_win_set_cursor(M.menu.winid, { state.selected + 1, 0 })
vim.api.nvim_exec_autocmds("WinScrolled", { modeline = false })
end
end
function M.on_hide()
Util.protect(function()
if M.menu then
M.menu:unmount()
M.menu = nil
end
if M.scroll then
M.scroll:unmount()
M.scroll = nil
end
end, {
finally = function()
if M.menu then
M.menu._.loading = false
end
end,
retry_on_E11 = true,
retry_on_E565 = true,
})()
end
return M

View File

@ -0,0 +1,27 @@
local M = {}
---@type table<string, any>
M.state = {}
function M.set(event, ...)
local msg = { event, ... }
M.state[event] = msg
end
function M.clear(event)
M.state[event] = nil
end
function M.is_equal(event, ...)
local msg = { event, ... }
return vim.deep_equal(M.state[event], msg)
end
function M.skip(event, ...)
if M.is_equal(event, ...) then
return true
end
M.set(event, ...)
end
return M

View File

@ -0,0 +1,168 @@
local require = require("noice.util.lazy")
local Config = require("noice.config")
local Util = require("noice.util")
---@class CallOptions
---@field catch? fun(err:string)
---@field finally? fun()
---@field msg? string
---@field retry_on_vim_errors? boolean
---@field retry_on_E11? boolean Retry on errors due to illegal operations while the cmdline window is open
local defaults = {
retry_on_vim_errors = true,
retry_on_E11 = false,
ignore_E565 = true,
retry_on_E565 = false,
ignore_keyboard_interrupt = true,
}
---@class Call
---@field _fn fun()
---@field _opts CallOptions
---@field _retry boolean
---@field _defer_retry boolean
local M = {}
M.__index = M
M._errors = 0
M._max_errors = 20
function M.reset()
M.reset = Util.debounce(200, function()
M._errors = 0
end)
M.reset()
end
---@generic F: fun()
---@param fn F
---@param opts? CallOptions
---@return F
function M.protect(fn, opts)
if not fn then
local trace = debug.traceback()
Util.panic("nil passed to noice.util.call.protect. This should not happen.\n%s", trace)
end
local self = setmetatable({}, M)
self._opts = vim.tbl_deep_extend("force", defaults, opts or {})
self._fn = fn
self._retry = false
return function(...)
return self(...)
end
end
function M:on_error(err)
M._errors = M._errors + 1
if M._errors > M._max_errors then
Util.panic("Too many errors. Disabling Noice")
end
M.reset()
if self._opts.catch then
pcall(self._opts.catch, err)
end
if err then
if self._opts.ignore_keyboard_interrupt and err:lower():find("keyboard interrupt") then
M._errors = M._errors - 1
return
end
if self._opts.retry_on_E565 and err:find("E565") then
M._errors = M._errors - 1
self._defer_retry = true
return
end
if self._opts.ignore_E565 and err:find("E565") then
M._errors = M._errors - 1
return
end
-- catch any Vim Errors and retry once
if not self._retry and err:find("Vim:E%d+") and self._opts.retry_on_vim_errors then
self._retry = true
return
end
if self._opts.retry_on_E11 and err:find("E11:") then
M._errors = M._errors - 1
self._defer_retry = true
return
end
end
pcall(M.log, self, err)
self:notify(err)
end
function M:log(data)
local file = Config.options.log
local fd = io.open(file, "a+")
if not fd then
error(("Could not open file %s for writing"):format(file))
end
fd:write("\n\n" .. os.date() .. "\n" .. self:format(data, true))
fd:close()
end
function M:format(err, stack)
local lines = {}
table.insert(lines, self._opts.msg or err)
if stack then
if self._opts.msg then
table.insert(lines, err)
end
table.insert(lines, debug.traceback("", 5))
end
return table.concat(lines, "\n")
end
function M:notify(err)
local msg = self:format(err, Config.options.debug)
vim.schedule(function()
if not pcall(Util.error, msg) then
vim.notify(msg, vim.log.levels.ERROR, { title = "noice.nvim" })
end
end)
end
function M:__call(...)
local args = vim.F.pack_len(...)
-- wrap the function and call with args
local wrapped = function()
return self._fn(vim.F.unpack_len(args))
end
-- error handler
local error_handler = function(err)
pcall(self.on_error, self, err)
return err
end
---@type boolean, any
local ok, result = xpcall(wrapped, error_handler)
if self._retry then
---@type boolean, any
ok, result = xpcall(wrapped, error_handler)
end
if self._opts.finally then
pcall(self._opts.finally)
end
if not ok and self._defer_retry then
vim.defer_fn(function()
self(vim.F.unpack_len(args))
end, 100)
end
return ok and result or nil
end
return M

View File

@ -0,0 +1,40 @@
local M = {}
---@return ffi.namespace*
function M.load()
-- Put in a global var to make sure we dont reload
-- when plugin reloaders do their thing
if not _G.noice_C then
local ffi = require("ffi")
local ok, err = pcall(
ffi.cdef,
[[typedef int32_t RgbValue;
typedef struct attr_entry {
int16_t rgb_ae_attr, cterm_ae_attr;
RgbValue rgb_fg_color, rgb_bg_color, rgb_sp_color;
int cterm_fg_color, cterm_bg_color;
int hl_blend;
} HlAttrs;
HlAttrs syn_attr2entry(int attr);
bool cmdpreview;
extern int textlock;
void setcursor_mayforce(bool force);
]]
)
---@diagnostic disable-next-line: need-check-nil
if not ok then
error(err)
end
_G.noice_C = ffi.C
end
return _G.noice_C
end
return setmetatable(M, {
__index = function(_, key)
return M.load()[key]
end,
__newindex = function(_, k, v)
M.load()[k] = v
end,
})

View File

@ -0,0 +1,317 @@
local require = require("noice.util.lazy")
local Util = require("noice.util")
local Router = require("noice.message.router")
local Api = require("noice.api")
local Cmdline = require("noice.ui.cmdline")
-- HACK: a bunch of hacks to make Noice behave
local M = {}
---@type fun()[]
M._disable = {}
function M.reset_augroup()
M.group = vim.api.nvim_create_augroup("noice.hacks", { clear = true })
end
function M.enable()
M.reset_augroup()
M.fix_input()
M.fix_redraw()
M.fix_cmp()
M.fix_vim_sleuth()
-- M.fix_cmdpreview()
-- Hacks for Neovim < 0.10
if vim.fn.has("nvim-0.10") == 0 then
M.fix_incsearch()
end
end
function M.fix_vim_sleuth()
vim.g.sleuth_noice_heuristics = 0
end
function M.disable()
M.reset_augroup()
for _, fn in pairs(M._disable) do
fn()
end
M._disable = {}
end
-- start a timer that checks for vim.v.hlsearch.
-- Clears search count and stops timer when hlsearch==0
function M.fix_nohlsearch()
M.fix_nohlsearch = Util.interval(30, function()
if vim.o.hlsearch and vim.v.hlsearch == 0 then
local m = require("noice.ui.msg").get("msg_show", "search_count")
require("noice.message.manager").remove(m)
end
end, {
enabled = function()
return vim.o.hlsearch and vim.v.hlsearch == 1
end,
})
M.fix_nohlsearch()
end
---@see https://github.com/neovim/neovim/issues/20793
function M.draw_cursor()
if vim.api.nvim__redraw then
vim.api.nvim__redraw({ cursor = true })
else
require("noice.util.ffi").setcursor_mayforce(true)
end
end
---@see https://github.com/neovim/neovim/issues/17810
function M.fix_incsearch()
---@type integer|nil
local conceallevel
vim.api.nvim_create_autocmd("CmdlineEnter", {
group = M.group,
callback = function(event)
if event.match == "/" or event.match == "?" then
conceallevel = vim.wo.conceallevel
vim.opt_local.conceallevel = 0
end
end,
})
vim.api.nvim_create_autocmd("CmdlineLeave", {
group = M.group,
callback = function(event)
if conceallevel and (event.match == "/" or event.match == "?") then
vim.opt_local.conceallevel = conceallevel
conceallevel = nil
end
end,
})
end
-- we need to intercept redraw so we can safely ignore message triggered by redraw
-- This wraps vim.cmd, nvim_cmd, nvim_command and nvim_exec
---@see https://github.com/neovim/neovim/issues/20416
M.inside_redraw = false
function M.fix_redraw()
local nvim_cmd = vim.api.nvim_cmd
local function wrap(fn, ...)
local inside_redraw = M.inside_redraw
M.inside_redraw = true
---@type boolean, any
local ok, ret = pcall(fn, ...)
-- check if the ui needs updating
Util.try(Router.update)
if not inside_redraw then
M.inside_redraw = false
end
if ok then
return ret
end
error(ret)
end
vim.api.nvim_cmd = function(cmd, ...)
if type(cmd) == "table" and cmd.cmd and cmd.cmd == "redraw" then
return wrap(nvim_cmd, cmd, ...)
else
return nvim_cmd(cmd, ...)
end
end
local nvim_command = vim.api.nvim_command
vim.api.nvim_command = function(cmd, ...)
if cmd == "redraw" then
return wrap(nvim_command, cmd, ...)
else
return nvim_command(cmd, ...)
end
end
local nvim_exec = vim.api.nvim_exec
vim.api.nvim_exec = function(cmd, ...)
if type(cmd) == "string" and cmd:find("redraw") then
-- WARN: this will potentially lose messages before or after the redraw ex command
-- example: echo "foo" | redraw | echo "bar"
-- the 'foo' message will be lost
return wrap(nvim_exec, cmd, ...)
else
return nvim_exec(cmd, ...)
end
end
table.insert(M._disable, function()
vim.api.nvim_cmd = nvim_cmd
vim.api.nvim_command = nvim_command
vim.api.nvim_exec = nvim_exec
end)
end
---@see https://github.com/neovim/neovim/issues/20311
M.before_input = false
function M.fix_input()
local function wrap(fn, skip)
return function(...)
if skip and skip(...) then
return fn(...)
end
-- make sure the cursor is drawn before blocking
M.draw_cursor()
local Manager = require("noice.message.manager")
-- do any updates now before blocking
M.before_input = true
Router.update()
---@type boolean, any
local ok, ret = pcall(fn, ...)
-- clear any message right after input
Manager.clear({ event = "msg_show", kind = { "echo", "echomsg", "" } })
M.before_input = false
if ok then
return ret
end
error(ret)
end
end
local function skip(expr)
return expr ~= nil
end
local getchar = vim.fn.getchar
local getcharstr = vim.fn.getcharstr
local inputlist = vim.fn.inputlist
-- local confirm = vim.fn.confirm
vim.fn.getchar = wrap(vim.fn.getchar, skip)
vim.fn.getcharstr = wrap(vim.fn.getcharstr, skip)
vim.fn.inputlist = wrap(vim.fn.inputlist, nil)
-- vim.fn.confirm = wrap(vim.fn.confirm, nil)
table.insert(M._disable, function()
vim.fn.getchar = getchar
vim.fn.getcharstr = getcharstr
vim.fn.inputlist = inputlist
-- vim.fn.confirm = confirm
end)
end
-- Fixes cmp cmdline position
function M.fix_cmp()
M.on_module("cmp.utils.api", function(api)
local get_cursor = api.get_cursor
api.get_cursor = function()
if api.is_cmdline_mode() then
local pos = Api.get_cmdline_position()
if pos then
return { pos.bufpos.row, vim.fn.getcmdpos() - 1 }
end
end
return get_cursor()
end
local get_screen_cursor = api.get_screen_cursor
api.get_screen_cursor = function()
if api.is_cmdline_mode() then
local pos = Api.get_cmdline_position()
if pos then
local col = vim.fn.getcmdpos() - Cmdline.last().offset
return { pos.screenpos.row, pos.screenpos.col + col }
end
end
return get_screen_cursor()
end
table.insert(M._disable, function()
api.get_cursor = get_cursor
api.get_screen_cursor = get_screen_cursor
end)
end)
end
function M.fix_cmdpreview()
vim.api.nvim_create_autocmd("CmdlineChanged", {
group = M.group,
callback = function()
local ffi = require("noice.util.ffi")
ffi.cmdpreview = false
vim.cmd([[redraw]])
Util.try(require("noice.message.router").update)
end,
})
end
M.SPECIAL = "Þ"
function M.cmdline_force_redraw()
if not require("noice.util.ffi").cmdpreview then
return
end
-- HACK: this will trigger redraw during substitute and cmdpreview
vim.api.nvim_feedkeys(M.SPECIAL .. Util.BS, "n", true)
end
---@type string?
M._guicursor = nil
function M.hide_cursor()
if M._guicursor == nil then
M._guicursor = vim.go.guicursor
end
-- schedule this, since otherwise Neovide crashes
vim.schedule(function()
if M._guicursor then
vim.go.guicursor = "a:NoiceHiddenCursor"
end
end)
M._disable.guicursor = M.show_cursor
end
function M.show_cursor()
if M._guicursor then
if not Util.is_exiting() then
vim.schedule(function()
if M._guicursor and not Util.is_exiting() then
-- we need to reset all first and then wait for some time before resetting the guicursor. See #114
vim.go.guicursor = "a:"
vim.cmd.redrawstatus()
vim.go.guicursor = M._guicursor
M._guicursor = nil
end
end)
end
end
end
---@param fn fun(mod)
function M.on_module(module, fn)
if package.loaded[module] then
return fn(package.loaded[module])
end
package.preload[module] = function()
package.preload[module] = nil
for _, loader in pairs(package.loaders) do
local ret = loader(module)
if type(ret) == "function" then
local mod = ret()
fn(mod)
return mod
end
end
end
end
return M

View File

@ -0,0 +1,383 @@
local require = require("noice.util.lazy")
local Hacks = require("noice.util.hacks")
local Config = require("noice.config")
local M = {}
M.islist = vim.islist or vim.tbl_islist
M.stats = require("noice.util.stats")
M.call = require("noice.util.call")
M.nui = require("noice.util.nui")
function M.t(str)
return vim.api.nvim_replace_termcodes(str, true, true, true)
end
M.CR = M.t("<cr>")
M.ESC = M.t("<esc>")
M.BS = M.t("<bs>")
M.EXIT = M.t("<C-\\><C-n>")
M.LUA_CALLBACK = "\x80\253g"
M.RIGHT = M.t("<right>")
M.LEFT = M.t("<left>")
M.CMD = "\x80\253h"
---@generic F: fun()
---@param fn F
---@return F
function M.once(fn)
local done = false
return function(...)
if not done then
fn(...)
done = true
end
end
end
function M.open(uri)
local cmd
if vim.fn.has("win32") == 1 then
cmd = { "cmd.exe", "/c", "start", '""', vim.fn.shellescape(uri) }
elseif vim.fn.has("macunix") == 1 then
cmd = { "open", uri }
else
cmd = { "xdg-open", uri }
end
local ret = vim.fn.system(cmd)
if vim.v.shell_error ~= 0 then
local msg = {
"Failed to open uri",
ret,
vim.inspect(cmd),
}
vim.notify(table.concat(msg, "\n"), vim.log.levels.ERROR)
end
end
---@param fn fun():any
function M.ignore_events(fn)
local ei = vim.go.eventignore
vim.go.eventignore = "all"
local ret = fn()
vim.go.eventignore = ei
return ret
end
function M.tag(buf, tag)
local ft = vim.bo[buf].filetype
if ft == "" then
M.ignore_events(function()
vim.bo[buf].filetype = "noice"
end)
end
if Config.options.debug and vim.api.nvim_buf_get_name(buf) == "" then
local path = "noice://" .. buf .. "/" .. tag
local params = {}
if ft ~= "" and ft ~= "noice" then
table.insert(params, "filetype=" .. ft)
end
if #params > 0 then
path = path .. "?" .. table.concat(params, "&")
end
vim.api.nvim_buf_set_name(buf, path)
end
end
---@param win window
---@param options table<string, any>
function M.wo(win, options)
for k, v in pairs(options) do
if vim.api.nvim_set_option_value then
vim.api.nvim_set_option_value(k, v, { scope = "local", win = win })
else
vim.wo[win][k] = v
end
end
end
function M.debounce(ms, fn)
local timer = vim.loop.new_timer()
return function(...)
local argv = vim.F.pack_len(...)
timer:start(ms, 0, function()
timer:stop()
vim.schedule_wrap(fn)(vim.F.unpack_len(argv))
end)
end
end
---@generic F: fun()
---@param fn F
---@param ms integer
---@param opts? {enabled?:fun():boolean}
---@return F|Interval
function M.interval(ms, fn, opts)
opts = opts or {}
---@type vim.loop.Timer?
local timer = nil
---@class Interval
local T = {}
function T.keep()
if M.is_exiting() then
return false
end
return opts.enabled == nil or opts.enabled()
end
function T.running()
return timer and not timer:is_closing()
end
function T.stop()
if timer and T.running() then
timer:stop()
end
end
function T.fn()
pcall(fn)
if timer and T.running() and not T.keep() then
timer:stop()
elseif T.keep() and not T.running() then
timer = vim.defer_fn(T.fn, ms)
end
end
function T.__call()
if not T.running() and T.keep() then
timer = vim.defer_fn(T.fn, ms)
end
end
return setmetatable(T, T)
end
---@param a table<string, any>
---@param b table<string, any>
---@return string[]
function M.diff_keys(a, b)
local diff = {}
for k, _ in pairs(a) do
if not vim.deep_equal(a[k], b[k]) then
diff[k] = true
end
end
for k, _ in pairs(b) do
if not vim.deep_equal(a[k], b[k]) then
diff[k] = true
end
end
return vim.tbl_keys(diff)
end
function M.module_exists(mod)
return pcall(_G.require, mod) == true
end
function M.diff(a, b)
a = vim.deepcopy(a)
b = vim.deepcopy(b)
M._diff(a, b)
return { left = a, right = b }
end
function M._diff(a, b)
if a == b then
return true
end
if type(a) ~= type(b) then
return false
end
if type(a) == "table" then
local equal = true
for k, v in pairs(a) do
if M._diff(v, b[k]) then
a[k] = nil
b[k] = nil
else
equal = false
end
end
for k, _ in pairs(b) do
if a[k] == nil then
equal = false
break
end
end
return equal
end
return false
end
---@param opts? {blocking:boolean, mode:boolean, input:boolean, redraw:boolean}
function M.is_blocking(opts)
opts = vim.tbl_deep_extend("force", {
blocking = true,
mode = true,
input = true,
redraw = true,
}, opts or {})
local mode = vim.api.nvim_get_mode()
local blocking_mode = false
for _, m in ipairs({ "ic", "ix", "c", "no", "r%?", "rm" }) do
if mode.mode:find(m) == 1 then
blocking_mode = true
end
end
local reason = opts.blocking and mode.blocking and "blocking"
or opts.mode and blocking_mode and ("mode:" .. mode.mode)
or opts.input and Hacks.before_input and "input"
or opts.redraw and Hacks.inside_redraw and "redraw"
or #require("noice.ui.cmdline").cmdlines > 0 and "cmdline"
or nil
return reason ~= nil, reason
end
function M.redraw()
vim.cmd.redraw()
M.stats.track("redraw")
end
M.protect = M.call.protect
function M.try(fn, ...)
return M.call.protect(fn)(...)
end
function M.win_apply_config(win, opts)
opts = vim.tbl_deep_extend("force", vim.api.nvim_win_get_config(win), opts or {})
vim.api.nvim_win_set_config(win, opts)
end
---@param msg string
---@param level number
---@param ... any
function M.notify(msg, level, ...)
if M.module_exists("notify") then
require("notify").notify(msg:format(...), level, {
title = "noice.nvim",
on_open = function(win)
vim.api.nvim_win_set_option(win, "conceallevel", 3)
local buf = vim.api.nvim_win_get_buf(win)
vim.api.nvim_buf_set_option(buf, "filetype", "markdown")
vim.api.nvim_win_set_option(win, "spell", false)
end,
})
else
vim.notify(msg:format(...), level, {
title = "noice.nvim",
})
end
end
---@type table<string, boolean>
M._once = {}
---@param msg string
---@param level number
---@param ... any
function M.notify_once(msg, level, ...)
msg = msg:format(...)
local once = level .. msg
if not M._once[once] then
M.notify(msg, level)
M._once[once] = true
end
end
function M.warn_once(msg, ...)
M.notify_once(msg, vim.log.levels.WARN, ...)
end
function M.error_once(msg, ...)
M.notify_once(msg, vim.log.levels.ERROR, ...)
end
function M.warn(msg, ...)
M.notify(msg, vim.log.levels.WARN, ...)
end
function M.error(msg, ...)
M.notify(msg, vim.log.levels.ERROR, ...)
end
--- Will stop Noice and show error
function M.panic(msg, ...)
require("noice").disable()
require("noice.view.backend.notify").dismiss()
vim.notify(msg:format(...), vim.log.levels.ERROR)
error("Noice was stopped to prevent further errors", 0)
end
function M.info(msg, ...)
M.notify(msg, vim.log.levels.INFO, ...)
end
---@param data any
function M.debug(data)
if not Config.options.debug then
return
end
if type(data) == "function" then
data = data()
end
if type(data) ~= "string" then
data = vim.inspect(data)
end
local file = "./noice.log"
local fd = io.open(file, "a+")
if not fd then
error(("Could not open file %s for writing"):format(file))
end
fd:write(data .. "\n")
fd:close()
end
---@return string
function M.read_file(file)
local fd = io.open(file, "r")
if not fd then
error(("Could not open file %s for reading"):format(file))
end
local data = fd:read("*a")
fd:close()
return data
end
function M.is_exiting()
return vim.v.exiting ~= vim.NIL
end
function M.write_file(file, data)
local fd = io.open(file, "w+")
if not fd then
error(("Could not open file %s for writing"):format(file))
end
fd:write(data)
fd:close()
end
---@generic K
---@generic V
---@param tbl table<K, V>
---@param fn fun(key: K, value: V)
---@param sorter? fun(a:V, b:V): boolean
function M.for_each(tbl, fn, sorter)
local keys = vim.tbl_keys(tbl)
table.sort(keys, sorter)
for _, key in ipairs(keys) do
fn(key, tbl[key])
end
end
return M

View File

@ -0,0 +1,25 @@
return function(module)
local mod = nil
local function load()
if not mod then
mod = require(module)
package.loaded[module] = mod
end
return mod
end
-- if already loaded, return the module
-- otherwise return a lazy module
return type(package.loaded[module]) == "table" and package.loaded[module]
or setmetatable({}, {
__index = function(_, key)
return load()[key]
end,
__newindex = function(_, key, value)
load()[key] = value
end,
__call = function(_, ...)
return load()(...)
end,
})
end

View File

@ -0,0 +1,254 @@
local require = require("noice.util.lazy")
local Util = require("noice.util")
local _ = require("nui.utils")._
local M = {}
---@param opts? NoiceNuiOptions
---@return _.NoiceNuiOptions
function M.normalize(opts)
opts = opts or {}
M.normalize_win_options(opts)
if opts.type == "split" then
---@cast opts NuiSplitOptions
return M.normalize_split_options(opts)
elseif opts.type == "popup" then
---@cast opts NuiPopupOptions
return M.normalize_popup_options(opts)
end
error("Missing type for " .. vim.inspect(opts))
end
---@param opts? NoiceNuiOptions
function M.normalize_win_options(opts)
opts = opts or {}
if opts.win_options and opts.win_options.winhighlight then
opts.win_options.winhighlight = Util.nui.get_win_highlight(opts.win_options.winhighlight)
end
end
---@return {xmin:integer, xmax:integer, ymin:integer, ymax:integer}
function M.bounds(win)
local pos = vim.api.nvim_win_get_position(win)
local height = vim.api.nvim_win_get_height(win)
local width = vim.api.nvim_win_get_width(win)
return {
xmin = pos[2],
xmax = pos[2] + width,
ymin = pos[1],
ymax = pos[1] + height,
}
end
function M.overlap(win1, win2)
local bb1 = M.bounds(win1)
local bb2 = M.bounds(win2)
-- # determine the coordinates of the intersection rectangle
local x_left = math.max(bb1["xmin"], bb2["xmin"])
local y_top = math.max(bb1["ymin"], bb2["ymin"])
local x_right = math.min(bb1["xmax"], bb2["xmax"])
local y_bottom = math.min(bb1["ymax"], bb2["ymax"])
if x_right < x_left or y_bottom < y_top then
return 0.0
end
-- # The intersection of two axis-aligned bounding boxes is always an
-- # axis-aligned bounding box
local intersection_area = math.max(x_right - x_left, 1) * math.max(y_bottom - y_top, 1)
-- # compute the area of both AABBs
local bb1_area = (bb1["xmax"] - bb1["xmin"]) * (bb1["ymax"] - bb1["ymin"])
local bb2_area = (bb2["xmax"] - bb2["xmin"]) * (bb2["ymax"] - bb2["ymin"])
-- # compute the intersection over union by taking the intersection
-- # area and dividing it by the sum of prediction + ground-truth
-- # areas - the intersection area
return intersection_area / (bb1_area + bb2_area - intersection_area)
end
---@param opts? NuiPopupOptions
---@return _.NuiPopupOptions
function M.normalize_popup_options(opts)
opts = vim.deepcopy(opts or {})
-- relative, position, size
_.normalize_layout_options(opts)
-- border
local border = opts.border
if type(border) == "string" then
opts.border = { style = border }
end
-- border padding
if opts.border then
opts.border.padding = M.normalize_padding(opts.border)
end
-- fix border text
if opts.border and (not opts.border.style or opts.border.style == "none" or opts.border.style == "shadow") then
opts.border.text = nil
end
return opts
end
---@param opts? NuiSplitOptions
---@return _.NuiSplitOptions
function M.normalize_split_options(opts)
opts = vim.deepcopy(opts or {})
-- relative
require("nui.split.utils").normalize_options(opts)
return opts
end
---@param hl string|table<string,string>
function M.get_win_highlight(hl)
if type(hl) == "string" then
return hl
end
local ret = {}
for key, value in pairs(hl) do
table.insert(ret, key .. ":" .. value)
end
return table.concat(ret, ",")
end
---@param opts? NuiBorder|_.NuiBorderStyle|_.NuiBorder
---@return _.NuiBorderPadding
function M.normalize_padding(opts)
opts = opts or {}
if type(opts) == "string" then
opts = { style = opts }
end
if Util.islist(opts.padding) then
if #opts.padding == 2 then
return {
top = opts.padding[1],
bottom = opts.padding[1],
left = opts.padding[2],
right = opts.padding[2],
}
elseif #opts.padding == 4 then
return {
top = opts.padding[1],
right = opts.padding[2],
bottom = opts.padding[3],
left = opts.padding[4],
}
end
end
return vim.tbl_deep_extend("force", {
left = 0,
right = 0,
top = 0,
bottom = 0,
}, opts.padding or {})
end
function M.win_buf_height(win)
local buf = vim.api.nvim_win_get_buf(win)
if not vim.wo[win].wrap then
return vim.api.nvim_buf_line_count(buf)
end
local width = vim.api.nvim_win_get_width(win)
local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
local height = 0
for _, l in ipairs(lines) do
height = height + math.max(1, (math.ceil(vim.fn.strwidth(l) / width)))
end
return height
end
---@param dim {width: number, height:number}
---@param _opts NoiceNuiOptions
---@return _.NoiceNuiOptions
function M.get_layout(dim, _opts)
---@type _.NoiceNuiOptions
local opts = M.normalize(_opts)
local position = vim.deepcopy(opts.position)
local size = vim.deepcopy(opts.size)
---@return number
local function minmax(min, max, value)
return math.max(min or 1, math.min(value, max or 1000))
end
if opts.type == "split" then
---@cast opts _.NuiSplitOptions
if size == "auto" then
if position == "top" or position == "bottom" then
size = minmax(opts.min_size, opts.max_size, dim.height)
else
size = minmax(opts.min_size, opts.max_size, dim.width)
end
end
elseif opts.type == "popup" then
if size.width == "auto" then
size.width = minmax(size.min_width, size.max_width, dim.width)
dim.width = size.width
end
if size.height == "auto" then
size.height = minmax(size.min_height, size.max_height, dim.height)
dim.height = size.height
end
if position and not (opts.relative and opts.relative.type == "cursor") then
if type(position.col) == "number" and position.col < 0 then
position.col = vim.o.columns + position.col - dim.width
end
if type(position.row) == "number" and position.row < 0 then
position.row = vim.o.lines + position.row - dim.height
end
end
end
return { size = size, position = position, relative = opts.relative }
end
function M.anchor(width, height)
local anchor = ""
local lines_above = vim.fn.screenrow() - 1
local lines_below = vim.fn.winheight(0) - lines_above
if height < lines_below then
anchor = anchor .. "N"
else
anchor = anchor .. "S"
end
if vim.go.columns - vim.fn.screencol() > width then
anchor = anchor .. "W"
else
anchor = anchor .. "E"
end
return anchor
end
function M.scroll(win, delta)
local info = vim.fn.getwininfo(win)[1] or {}
local top = info.topline or 1
local buf = vim.api.nvim_win_get_buf(win)
top = top + delta
top = math.max(top, 1)
top = math.min(top, M.win_buf_height(win) - info.height + 1)
vim.defer_fn(function()
vim.api.nvim_buf_call(buf, function()
vim.api.nvim_command("noautocmd silent! normal! " .. top .. "zt")
vim.api.nvim_exec_autocmds("WinScrolled", { modeline = false })
end)
end, 0)
end
return M

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
local require = require("noice.util.lazy")
local Message = require("noice.message")
local M = {}
---@class NoiceStat
---@field event string
---@field count number
---@type table<string, NoiceStat>
M._stats = {}
function M.reset()
M._stats = {}
end
function M.track(event)
if not M._stats[event] then
M._stats[event] = {
event = event,
count = 0,
}
end
M._stats[event].count = M._stats[event].count + 1
end
---@type NoiceMessage
M._message = nil
function M.message()
if not M._message then
M._message = Message("noice", "stats")
end
M._message:set(vim.inspect(M._stats))
return M._message
end
return M

View File

@ -0,0 +1,101 @@
local require = require("noice.util.lazy")
local View = require("noice.view")
local NuiView = require("noice.view.nui")
local Util = require("noice.util")
---@class NoiceMiniOptions
---@field timeout integer
---@field reverse? boolean
local defaults = { timeout = 5000 }
---@class MiniView: NoiceView
---@field active table<number, NoiceMessage>
---@field super NoiceView
---@field view? NuiView
---@field timers table<number, vim.loop.Timer>
---@diagnostic disable-next-line: undefined-field
local MiniView = View:extend("MiniView")
function MiniView:init(opts)
MiniView.super.init(self, opts)
self.active = {}
self.timers = {}
self._instance = "view"
local view_opts = vim.deepcopy(self._opts)
view_opts.type = "popup"
view_opts.format = { "{message}" }
view_opts.timeout = nil
self.view = NuiView(view_opts)
end
function MiniView:update_options()
self._opts = vim.tbl_deep_extend("force", defaults, self._opts)
end
---@param message NoiceMessage
function MiniView:can_hide(message)
if message.opts.keep and message.opts.keep() then
return false
end
return not Util.is_blocking()
end
function MiniView:autohide(id)
if not self.timers[id] then
self.timers[id] = vim.loop.new_timer()
end
self.timers[id]:start(self._opts.timeout, 0, function()
if not self.active[id] then
return
end
if not self:can_hide(self.active[id]) then
return self:autohide(id)
end
self.active[id] = nil
self.timers[id] = nil
vim.schedule(function()
self:update()
end)
end)
end
function MiniView:show()
for _, message in ipairs(self._messages) do
-- we already have debug info,
-- so make sure we dont regen it in the child view
message._debug = true
self.active[message.id] = message
self:autohide(message.id)
end
self:clear()
self:update()
end
function MiniView:dismiss()
self:clear()
self.active = {}
self:update()
end
function MiniView:update()
local active = vim.tbl_values(self.active)
table.sort(
active,
---@param a NoiceMessage
---@param b NoiceMessage
function(a, b)
local ret = a.id < b.id
if self._opts.reverse then
return not ret
end
return ret
end
)
self.view:set(active)
self.view:display()
end
function MiniView:hide() end
return MiniView

View File

@ -0,0 +1,210 @@
local require = require("noice.util.lazy")
local Util = require("noice.util")
local View = require("noice.view")
local Manager = require("noice.message.manager")
local NuiText = require("nui.text")
---@class NoiceNotifyOptions
---@field title string
---@field level? string|number Message log level
---@field merge boolean Merge messages into one Notification or create separate notifications
---@field replace boolean Replace existing notification or create a new one
---@field render? notify.RenderFun|string
---@field timeout? integer
local defaults = {
title = "Notification",
merge = false,
level = nil, -- vim.log.levels.INFO,
replace = false,
}
---@class NotifyInstance
---@field notify fun(msg:string?, level?:string|number, opts?:table): notify.Record}
---@alias notify.RenderFun fun(buf:buffer, notif: Notification, hl: NotifyBufHighlights, config: notify.Config)
---@class NotifyView: NoiceView
---@field win? number
---@field buf? number
---@field notif notify.Record
---@field super NoiceView
---@diagnostic disable-next-line: undefined-field
local NotifyView = View:extend("NotifyView")
function NotifyView.dismiss()
require("notify").dismiss({ pending = true, silent = true })
end
function NotifyView:is_available()
return pcall(_G.require, "notify") == true
end
function NotifyView:update_options()
self._opts = vim.tbl_deep_extend("force", defaults, self._opts)
end
function NotifyView:plain()
return function(bufnr, notif)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, notif.message)
end
end
---@param config notify.Config
---@param render? notify.RenderFun|string
---@return notify.RenderFun
function NotifyView:get_render(config, render)
---@type string|notify.RenderFun
local ret = render or config.render()
if type(ret) == "string" then
if ret == "plain" then
ret = self:plain()
else
---@type notify.RenderFun
ret = require("notify.render")[ret]
end
end
return ret
end
---@param messages NoiceMessage[]
---@param render? notify.RenderFun|string
---@param content? string
function NotifyView:notify_render(messages, render, content)
---@param config notify.Config
return function(buf, notif, hl, config)
-- run notify view
self:get_render(config, render)(buf, notif, hl, config)
Util.tag(buf, "notify")
---@type string[]
local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
local text = table.concat(lines, "\n")
local idx = content and text:find(content, 1, true) or nil
if idx then
-- we found the offset of the content as a string
local before = text:sub(1, idx - 1)
local offset = #vim.split(before, "\n")
local offset_col = #before:match("[^\n]*$")
-- in case the content starts in the middle of the line,
-- we need to add a fake prefix to the first line of the first message
-- see #375
if offset_col > 0 then
messages = vim.deepcopy(messages)
table.insert(messages[1]._lines[1]._texts, 1, NuiText(string.rep(" ", offset_col)))
end
-- do our rendering
self:render(buf, { offset = offset, highlight = true, messages = messages })
-- in case we didn't find the offset, we won't highlight anything
end
-- resize notification
local win = vim.fn.bufwinid(buf)
if win ~= -1 then
---@type number
local width = config.minimum_width()
for _, line in pairs(lines) do
width = math.max(width, vim.str_utfindex(line))
end
width = math.min(config.max_width() or 1000, width)
local height = math.min(config.max_height() or 1000, #lines)
Util.win_apply_config(win, { width = width, height = height })
end
end
end
---@alias NotifyMsg {content:string, messages:NoiceMessage[], title?:string, level?:string, opts?: table}
---@param msg NotifyMsg
function NotifyView:_notify(msg)
local level = self._opts.level or msg.level
local opts = {
title = msg.title or self._opts.title,
animate = not Util.is_blocking(),
timeout = self._opts.timeout,
replace = self._opts.replace and self.notif,
keep = function()
return Util.is_blocking()
end,
on_open = function(win)
self:set_win_options(win)
if self._opts.merge then
self.win = win
end
end,
on_close = function()
self.notif = nil
for _, m in ipairs(msg.messages) do
m.opts.notify_id = nil
end
self.win = nil
end,
render = Util.protect(self:notify_render(msg.messages, self._opts.render, msg.content)),
}
if msg.opts then
opts = vim.tbl_deep_extend("force", opts, msg.opts)
if type(msg.opts.replace) == "table" then
local m = Manager.get_by_id(msg.opts.replace.id)
opts.replace = m and m.opts.notify_id or nil
elseif type(msg.opts.replace) == "number" then
local m = Manager.get_by_id(msg.opts.replace)
opts.replace = m and m.opts.notify_id or nil
end
end
---@type string?
local content = msg.content
if msg.opts and msg.opts.is_nil then
content = nil
end
local id = require("notify")(content, level, opts)
self.notif = id
for _, m in ipairs(msg.messages) do
m.opts.notify_id = id
end
end
function NotifyView:show()
---@type NotifyMsg[]
local todo = {}
if self._opts.merge then
table.insert(todo, {
content = self:content(),
messages = self._messages,
})
else
for _, m in ipairs(self._messages) do
table.insert(todo, {
content = m:content(),
messages = { m },
title = m.opts.title,
level = m.level,
opts = m.opts,
})
end
end
self:clear()
for _, msg in ipairs(todo) do
self:_notify(msg)
end
end
function NotifyView:hide()
if self.win and vim.api.nvim_win_is_valid(self.win) then
vim.api.nvim_win_close(self.win, true)
self.win = nil
end
end
return NotifyView

View File

@ -0,0 +1,165 @@
local require = require("noice.util.lazy")
local Util = require("noice.util")
local View = require("noice.view")
---@class NoiceNotifySendOptions
---@field title string
---@field level? string|number Message log level
---@field merge boolean Merge messages into one Notification or create separate notifications
---@field replace boolean Replace existing notification or create a new one
local defaults = {
title = "Notification",
merge = false,
level = nil, -- vim.log.levels.INFO,
replace = false,
}
---@class NotifySendArgs
---@field title? string
---@field body string
---@field app_name? string
---@field urgency? string
---@field expire_time? integer
---@field icon? string
---@field category? string
---@field hint? string
---@field print_id? boolean
---@field replace_id? string
---@class NotifySendView: NoiceView
---@field win? number
---@field buf? number
---@field notif? string
---@field super NoiceView
---@diagnostic disable-next-line: undefined-field
local NotifySendView = View:extend("NotifySendView")
function NotifySendView:init(opts)
NotifySendView.super.init(self, opts)
end
function NotifySendView:is_available()
return vim.fn.executable("notify-send") == 1
end
function NotifySendView:update_options()
self._opts = vim.tbl_deep_extend("force", defaults, self._opts)
end
---@alias NotifySendMsg {content:string, messages:NoiceMessage[], title?:string, level?:NotifyLevel, opts?: table}
---@param level? NotifyLevel|number
function NotifySendView:get_urgency(level)
if level then
local l = type(level) == "number" and level or vim.log.levels[level:lower()] or vim.log.levels.INFO
if l <= 1 then
return "low"
end
if l >= 4 then
return "critical"
end
end
return "normal"
end
---@param msg NotifySendMsg
function NotifySendView:_notify(msg)
local level = self._opts.level or msg.level
---@type NotifySendArgs
local opts = {
app_name = "nvim",
icon = "nvim",
title = msg.title or self._opts.title,
body = msg.content,
replace_id = self._opts.replace and self.notif or nil,
urgency = self:get_urgency(level),
}
local args = { "--print-id" }
for k, v in pairs(opts) do
if not (k == "title" or k == "body") then
table.insert(args, "--" .. k:gsub("_", "-"))
table.insert(args, v)
end
end
if opts.title then
table.insert(args, vim.trim(opts.title))
end
if opts.body then
table.insert(args, vim.trim(opts.body))
end
local stdout = vim.loop.new_pipe()
local stderr = vim.loop.new_pipe()
local out = ""
local err = ""
local proc
proc = vim.loop.spawn(
"notify-send",
{
stdio = { nil, stdout, stderr },
args = args,
},
vim.schedule_wrap(function(code, _signal) -- on exit
stdout:close()
stderr:close()
proc:close()
if code ~= 0 then
return Util.error("notify-send failed: %s", err)
else
self.notif = vim.trim(out)
end
end)
)
vim.loop.read_start(stdout, function(_, data)
if data then
out = out .. data
end
end)
vim.loop.read_start(stderr, function(_, data)
if data then
err = err .. data
end
end)
end
function NotifySendView:show()
---@type NotifySendMsg[]
local todo = {}
if self._opts.merge then
table.insert(todo, {
content = self:content(),
messages = self._messages,
})
else
for _, m in ipairs(self._messages) do
table.insert(todo, {
content = m:content(),
messages = { m },
title = m.opts.title,
level = m.level,
opts = m.opts,
})
end
end
self:clear()
for _, msg in ipairs(todo) do
self:_notify(msg)
end
end
function NotifySendView:hide()
if self.win and vim.api.nvim_win_is_valid(self.win) then
vim.api.nvim_win_close(self.win, true)
self.win = nil
end
end
return NotifySendView

View File

@ -0,0 +1,5 @@
---@param opts? NoiceViewOptions
return function(opts)
opts.type = "popup"
return require("noice.view.nui")(opts)
end

View File

@ -0,0 +1,5 @@
---@param opts? NoiceViewOptions
return function(opts)
opts.type = "split"
return require("noice.view.nui")(opts)
end

View File

@ -0,0 +1,35 @@
local require = require("noice.util.lazy")
local Config = require("noice.config")
local View = require("noice.view")
---@class VirtualText: NoiceView
---@field extmark? number
---@field buf? number
---@diagnostic disable-next-line: undefined-field
local VirtualText = View:extend("VirtualTextView")
function VirtualText:show()
self:hide()
self.buf = vim.api.nvim_get_current_buf()
---@type number, number
local line, col = unpack(vim.api.nvim_win_get_cursor(0))
line = line - 1
if self._messages[1] then
self.extmark = vim.api.nvim_buf_set_extmark(self.buf, Config.ns, line, col, {
virt_text_pos = "eol",
virt_text = { { vim.trim(self._messages[1]:content()), self._opts.hl_group or "DiagnosticVirtualTextInfo" } },
hl_mode = "combine",
})
end
end
function VirtualText:hide()
if self.extmark and vim.api.nvim_buf_is_valid(self.buf) then
vim.api.nvim_buf_del_extmark(self.buf, Config.ns, self.extmark)
end
end
return VirtualText

View File

@ -0,0 +1,286 @@
local require = require("noice.util.lazy")
local Config = require("noice.config")
local ConfigViews = require("noice.config.views")
local Util = require("noice.util")
local Object = require("nui.object")
local Format = require("noice.text.format")
---@class NoiceViewBaseOptions
---@field buf_options? table<string,any>
---@field backend string
---@field fallback string Fallback view in case the backend could not be loaded
---@field format? NoiceFormat|string
---@field align? NoiceAlign
---@field lang? string
---@field view string
---@alias NoiceViewOptions NoiceViewBaseOptions|NoiceNuiOptions|NoiceNotifyOptions
---@class NoiceView
---@field _tick number
---@field _messages NoiceMessage[]
---@field _id integer
---@field _opts NoiceViewOptions
---@field _view_opts NoiceViewOptions
---@field _route_opts NoiceViewOptions
---@field _visible boolean
---@field _instance "opts" | "view" | "backend"
---@field _errors integer
---@overload fun(opts?: NoiceViewOptions): NoiceView
local View = Object("NoiceView")
---@type {view:NoiceView, opts:NoiceViewOptions}[]
View._views = {}
---@param view string
---@param opts NoiceViewOptions
function View.get_view(view, opts)
local opts_orig = vim.deepcopy(opts)
opts = vim.tbl_deep_extend("force", ConfigViews.get_options(view), opts or {}, { view = view })
---@diagnostic disable-next-line: undefined-field
opts.backend = opts.backend or opts.render or view
-- check if we already loaded this backend
for _, v in ipairs(View._views) do
if v.opts.view == opts.view then
if v.view._instance == "opts" and vim.deep_equal(opts, v.opts) then
return v.view
end
if v.view._instance == "view" then
return v.view
end
end
if v.opts.backend == opts.backend then
if v.view._instance == "backend" then
return v.view
end
end
end
---@type NoiceView
local mod = require("noice.view.backend." .. opts.backend)
local init_opts = vim.deepcopy(opts)
local ret = mod(opts)
if not ret:is_available() and opts.fallback then
return View.get_view(opts.fallback, opts_orig)
end
table.insert(View._views, { view = ret, opts = init_opts })
return ret
end
local _id = 0
---@param opts? NoiceViewOptions
function View:init(opts)
_id = _id + 1
self._id = _id
self._tick = 0
self._messages = {}
self._opts = opts or {}
self._visible = false
self._view_opts = vim.deepcopy(self._opts)
self._instance = "opts"
self._errors = 0
self:update_options()
end
function View:is_available()
return true
end
function View:update_options() end
---@param messages NoiceMessage|NoiceMessage[]
---@param opts? {format?: boolean}
function View:push(messages, opts)
opts = opts or {}
messages = Util.islist(messages) and messages or { messages }
---@cast messages NoiceMessage[]
for _, message in ipairs(messages) do
if opts.format ~= false then
message = Format.format(message, self._opts.format)
end
table.insert(self._messages, message)
end
end
function View:clear()
self._messages = {}
self._route_opts = {}
end
function View:dismiss()
self:clear()
end
function View:check_options()
---@type NoiceViewOptions
local old = vim.deepcopy(self._opts)
self._opts = vim.tbl_deep_extend("force", vim.deepcopy(self._view_opts), self._route_opts or {})
self:update_options()
if not vim.deep_equal(old, self._opts) then
self:reset(old, self._opts)
end
end
---@param messages NoiceMessage[]
---@param opts? {format?: boolean}
function View:set(messages, opts)
opts = opts or {}
self:clear()
self:push(messages, opts)
end
function View:debug(msg)
if Config.options.debug then
Util.debug(("[%s] %s"):format(self._opts.view, vim.inspect(msg)))
Util.debug(debug.traceback())
end
end
-- Safely destroys any create windows and buffers.
-- This is needed to properly re-create views in case of E565 errors
function View:destroy() end
function View:display()
if #self._messages > 0 then
Format.align(self._messages, self._opts.align)
self:check_options()
Util.protect(function()
self._errors = self._errors + 1
self:show()
self._errors = 0
end, {
catch = function(err)
self:debug(err)
self:destroy()
end,
})()
self._visible = true
else
if self._visible then
self:hide()
end
self._visible = false
end
return true
end
---@param old NoiceViewOptions
---@param new NoiceViewOptions
function View:reset(old, new) end
function View:show()
Util.error("Missing implementation `View:show()` for %s", self)
end
function View:hide()
Util.error("Missing implementation `View:hide()` for %s", self)
end
---@param messages? NoiceMessage[]
function View:height(messages)
local ret = 0
for _, m in ipairs(messages or self._messages) do
ret = ret + m:height()
end
return ret
end
---@param messages? NoiceMessage[]
function View:width(messages)
local ret = 0
for _, m in ipairs(messages or self._messages) do
ret = math.max(ret, m:width())
end
return ret
end
function View:content()
return table.concat(
vim.tbl_map(
---@param m NoiceMessage
function(m)
return m:content()
end,
self._messages
),
"\n"
)
end
function View:set_win_options(win)
if self._opts.win_options then
Util.wo(win, self._opts.win_options)
end
-- reset cursor on show
vim.api.nvim_win_set_cursor(win, { 1, 0 })
if self._opts.type == "split" then
vim.schedule(function()
-- this is needed to make the nui split behave with vim.go.splitkeep
if win and vim.api.nvim_win_is_valid(win) then
vim.api.nvim_win_set_cursor(win, { 1, 0 })
vim.api.nvim_win_call(win, function()
vim.cmd([[noautocmd silent! normal! zt]])
end)
end
end)
end
end
---@param buf number buffer number
---@param opts? {offset: number, highlight: boolean, messages?: NoiceMessage[]} line number (1-indexed), if `highlight`, then only highlight
function View:render(buf, opts)
if not Config.is_running() then
return
end
opts = opts or {}
local linenr = opts.offset or 1
if self._opts.buf_options then
Util.ignore_events(function()
require("nui.utils")._.set_buf_options(buf, self._opts.buf_options)
end)
end
if self._opts.lang and not vim.b[buf].ts_highlight then
if not pcall(vim.treesitter.start, buf, self._opts.lang) then
vim.bo[buf].syntax = self._opts.lang
end
end
vim.api.nvim_buf_clear_namespace(buf, Config.ns, linenr - 1, -1)
vim.b[buf].messages = {}
---@type number?
local win = vim.fn.bufwinid(buf)
if win == -1 then
win = nil
end
local cursor = win and vim.api.nvim_win_get_cursor(win)
if not opts.highlight then
vim.api.nvim_buf_set_lines(buf, linenr - 1, -1, false, {})
end
for _, m in ipairs(opts.messages or self._messages) do
if opts.highlight then
m:highlight(buf, Config.ns, linenr)
else
m:render(buf, Config.ns, linenr)
end
linenr = linenr + m:height()
end
if cursor then
-- restore cursor
pcall(vim.api.nvim_win_set_cursor, win, cursor)
end
end
return View

View File

@ -0,0 +1,337 @@
local require = require("noice.util.lazy")
local View = require("noice.view")
local Util = require("noice.util")
local Scrollbar = require("noice.view.scrollbar")
local Config = require("noice.config")
---@class NuiView: NoiceView
---@field _nui? NuiPopup|NuiSplit
---@field _loading? boolean
---@field super NoiceView
---@field _hider fun()
---@field _timeout_timer vim.loop.Timer
---@field _scroll NoiceScrollbar
---@diagnostic disable-next-line: undefined-field
local NuiView = View:extend("NuiView")
function NuiView:init(opts)
NuiView.super.init(self, opts)
self._timer = vim.loop.new_timer()
end
function NuiView:autohide()
if self._opts.timeout then
self._timer:start(self._opts.timeout, 0, function()
if self._visible then
vim.schedule(function()
self:hide()
end)
end
self._timer:stop()
end)
end
end
function NuiView:update_options()
self._opts = vim.tbl_deep_extend("force", {}, {
buf_options = {
buftype = "nofile",
filetype = "noice",
},
win_options = {
wrap = false,
foldenable = false,
scrolloff = 0,
sidescrolloff = 0,
},
}, self._opts, self:get_layout())
self._opts = Util.nui.normalize(self._opts)
if self._opts.anchor == "auto" then
if self._opts.type == "popup" and self._opts.size then
local width = self._opts.size.width
local height = self._opts.size.height
if type(width) == "number" and type(height) == "number" then
local col = self._opts.position and self._opts.position.col
local row = self._opts.position and self._opts.position.row
self._opts.anchor = Util.nui.anchor(width, height)
if self._opts.anchor:find("S") and row then
self._opts.position.row = -row + 1
end
if self._opts.anchor:find("E") and col then
self._opts.position.col = -col
end
end
else
self._opts.anchor = "NW"
end
end
end
-- Check if other floating windows are overlapping and move out of the way
function NuiView:smart_move()
if not Config.options.smart_move.enabled then
return
end
if not (self._opts.type == "popup" and self._opts.relative and self._opts.relative.type == "editor") then
return
end
if not (self._nui.winid and vim.api.nvim_win_is_valid(self._nui.winid)) then
return
end
if not (self._nui.border.winid and vim.api.nvim_win_is_valid(self._nui.border.winid)) then
return
end
local nui_win = self._nui.border._.type == "complex" and self._nui.border.winid or self._nui.winid
local wins = vim.tbl_filter(function(win)
local ft = vim.bo[vim.api.nvim_win_get_buf(win)].filetype
return win ~= self._nui.winid
and ft ~= "noice"
and not vim.tbl_contains(Config.options.smart_move.excluded_filetypes, ft)
and not (self._nui.border and self._nui.border.winid == win)
and vim.api.nvim_win_is_valid(win)
and vim.api.nvim_win_get_config(win).relative == "editor"
and Util.nui.overlap(nui_win, win) > 0.3
end, vim.api.nvim_list_wins())
if #wins > 0 then
-- local info = vim.tbl_map(function(win)
-- local buf = vim.api.nvim_win_get_buf(win)
-- return {
-- win = win,
-- buftype = vim.bo[buf].buftype,
-- ft = vim.bo[buf].filetype,
-- syntax = vim.bo[buf].syntax,
-- text = table.concat(vim.api.nvim_buf_get_lines(buf, 0, -1, false), "\n"),
-- name = vim.api.nvim_buf_get_name(buf),
-- -- config = vim.api.nvim_win_get_config(win),
-- area = Util.nui.overlap(nui_win, win),
-- }
-- end, wins)
-- dumpp(info)
local layout = self:get_layout()
layout.position.row = 2
self._nui:update_layout(layout)
end
end
function NuiView:create()
if self._loading then
return
end
self._loading = true
-- needed, since Nui mutates the options
local opts = vim.deepcopy(self._opts)
self._nui = self._opts.type == "split" and require("nui.split")(opts) or require("nui.popup")(opts)
self:mount()
self:update_layout()
if self._opts.scrollbar ~= false then
self._scroll = Scrollbar({
winnr = self._nui.winid,
padding = Util.nui.normalize_padding(self._opts.border),
})
self._scroll:mount()
end
self._loading = false
end
function NuiView:mount()
self._nui:mount()
if self._opts.close and self._opts.close.events then
self._nui:on(self._opts.close.events, function()
self:hide()
end, { once = false })
end
if self._opts.close and self._opts.close.keys then
self._nui:map("n", self._opts.close.keys, function()
self:hide()
end, { remap = false, nowait = true })
end
end
---@param old NoiceNuiOptions
---@param new NoiceNuiOptions
function NuiView:reset(old, new)
self._timer:stop()
if self._nui then
local layout = false
local diff = vim.tbl_filter(function(key)
if vim.tbl_contains({ "relative", "size", "position" }, key) then
layout = true
return false
end
if key == "timeout" then
return false
end
return true
end, Util.diff_keys(old, new))
if #diff > 0 then
self._nui:unmount()
self._nui = nil
self._visible = false
elseif layout then
if not pcall(self.update_layout, self) then
self._nui:unmount()
self._nui = nil
self._visible = false
end
end
end
end
-- Destroys any create windows and buffers with vim.schedule
-- This is needed to properly re-create views in case of E565 errors
function NuiView:destroy()
local nui = self._nui
local scroll = self._scroll
vim.schedule(function()
if nui then
nui._.loading = false
nui._.mounted = true
nui:unmount()
end
if scroll then
scroll:hide()
end
end)
self._nui = nil
self._scroll = nil
self._loading = false
end
function NuiView:hide()
self._timer:stop()
if self._nui then
self._visible = false
Util.protect(function()
if self._nui and not self._visible then
self:clear()
self._nui:unmount()
if self._scroll then
self._scroll:hide()
end
end
end, {
finally = function()
if self._nui then
self._nui._.loading = false
end
end,
retry_on_E11 = true,
retry_on_E565 = true,
})()
end
end
function NuiView:get_layout()
local layout = Util.nui.get_layout({ width = self:width(), height = self:height() }, self._opts)
if self._opts.type == "popup" then
---@cast layout _.NuiPopupOptions
if
layout.size
and type(layout.size.width) == "number"
and layout.size.width < self:width()
and self._opts.win_options
and self._opts.win_options.wrap
then
local height = 0
for _, m in ipairs(self._messages) do
for _, l in ipairs(m._lines) do
height = height + math.max(1, (math.ceil(l:width() / layout.size.width)))
end
end
layout = Util.nui.get_layout({ width = self:width(), height = height }, self._opts)
end
end
return layout
end
function NuiView:tag()
Util.tag(self._nui.bufnr, "nui." .. self._opts.type)
if self._nui.border and self._nui.border.bufnr then
Util.tag(self._nui.border.bufnr, "nui." .. self._opts.type .. ".border")
end
end
function NuiView:fix_border()
if
self._nui
and self._nui.border
and self._nui.border.winid
and vim.api.nvim_win_is_valid(self._nui.border.winid)
then
local winhl = vim.api.nvim_win_get_option(self._nui.border.winid, "winhighlight") or ""
if not winhl:find("IncSearch") then
local hl = vim.split(winhl, ",")
hl[#hl + 1] = "Search:"
hl[#hl + 1] = "IncSearch:"
winhl = table.concat(hl, ",")
vim.api.nvim_win_set_option(self._nui.border.winid, "winhighlight", winhl)
end
end
end
function NuiView:update_layout()
self._nui:update_layout(self:get_layout())
end
function NuiView:is_mounted()
if self._nui and self._nui.bufnr and not vim.api.nvim_buf_is_valid(self._nui.bufnr) then
self._nui.bufnr = nil
end
if self._nui and self._nui.winid and not vim.api.nvim_win_is_valid(self._nui.winid) then
self._nui.winid = nil
end
if self._nui and self._nui._.mounted and not self._nui.bufnr then
self._nui._.mounted = false
end
return self._nui and self._nui._.mounted and self._nui.bufnr
end
function NuiView:show()
if self._loading then
return
end
if not self._nui then
self:create()
end
if not self:is_mounted() then
self:mount()
end
vim.bo[self._nui.bufnr].modifiable = true
self:render(self._nui.bufnr)
vim.bo[self._nui.bufnr].modifiable = false
self._nui:show()
if not self._nui.winid then
return
end
self:tag()
if not self._visible then
self:set_win_options(self._nui.winid)
self:update_layout()
self:smart_move()
end
if self._scroll then
self._scroll.winnr = self._nui.winid
self._scroll:show()
end
self:fix_border()
self:autohide()
end
return NuiView

View File

@ -0,0 +1,163 @@
local require = require("noice.util.lazy")
local Object = require("nui.object")
local Util = require("noice.util")
---@class NoiceScrollbar
---@field winnr integer
---@field ns_id integer
---@field autocmd_id integer
---@field bar {bufnr:integer, winnr:integer}?
---@field thumb {bufnr:integer, winnr:integer}?
---@field visible boolean
---@field opts ScrollbarOptions
---@overload fun(opts?:ScrollbarOptions):NoiceScrollbar
local Scrollbar = Object("NuiScrollbar")
---@class ScrollbarOptions
local defaults = {
winnr = 0,
autohide = true,
hl_group = {
bar = "NoiceScrollbar",
thumb = "NoiceScrollbarThumb",
},
---@type _.NuiBorderPadding
padding = {
top = 0,
right = 0,
bottom = 0,
left = 0,
},
}
---@param opts? ScrollbarOptions
function Scrollbar:init(opts)
self.opts = vim.tbl_deep_extend("force", defaults, opts or {})
self.winnr = self.opts.winnr == 0 and vim.api.nvim_get_current_win() or self.opts.winnr
self.visible = false
end
function Scrollbar:mount()
self.autocmd_id = vim.api.nvim_create_autocmd({ "WinScrolled", "CursorMoved" }, {
callback = function()
self:update()
end,
})
self:update()
end
function Scrollbar:unmount()
if self.autocmd_id then
vim.api.nvim_del_autocmd(self.autocmd_id)
self.autocmd_id = nil
end
self:hide()
end
function Scrollbar:show()
if not self.visible then
self.visible = true
self.bar = self:_open_win({ normal = self.opts.hl_group.bar })
self.thumb = self:_open_win({ normal = self.opts.hl_group.thumb })
end
self:update()
end
function Scrollbar:hide()
if self.visible then
self.visible = false
local bar = self.bar
if bar then
pcall(vim.api.nvim_buf_delete, bar.bufnr, { force = true })
pcall(vim.api.nvim_win_close, bar.winnr, true)
self.bar = nil
end
local thumb = self.thumb
if thumb then
pcall(vim.api.nvim_buf_delete, thumb.bufnr, { force = true })
pcall(vim.api.nvim_win_close, thumb.winnr, true)
self.thumb = nil
end
end
end
function Scrollbar:update()
if not vim.api.nvim_win_is_valid(self.winnr) then
return self:hide()
end
local pos = vim.api.nvim_win_get_position(self.winnr)
local dim = {
row = pos[1] - self.opts.padding.top,
col = pos[2] - self.opts.padding.left,
width = vim.api.nvim_win_get_width(self.winnr) + self.opts.padding.left + self.opts.padding.right,
height = vim.api.nvim_win_get_height(self.winnr) + self.opts.padding.top + self.opts.padding.bottom,
}
local buf_height = Util.nui.win_buf_height(self.winnr)
if self.opts.autohide and dim.height >= buf_height then
self:hide()
return
elseif not self.visible then
self:show()
end
if not (vim.api.nvim_win_is_valid(self.bar.winnr) and vim.api.nvim_win_is_valid(self.thumb.winnr)) then
self:hide()
self:show()
end
local zindex = vim.api.nvim_win_get_config(self.winnr).zindex or 50
Util.win_apply_config(self.bar.winnr, {
height = dim.height,
width = 1,
col = dim.col + dim.width - 1,
row = dim.row,
zindex = zindex + 1,
})
local thumb_height = math.floor(dim.height * dim.height / buf_height + 0.5)
thumb_height = math.max(1, thumb_height)
local pct = vim.api.nvim_win_get_cursor(self.winnr)[1] / buf_height
local thumb_offset = math.floor(pct * (dim.height - thumb_height) + 0.5)
Util.win_apply_config(self.thumb.winnr, {
width = 1,
height = thumb_height,
row = dim.row + thumb_offset,
col = dim.col + dim.width - 1, -- info.col was already added scrollbar offset.
zindex = zindex + 2,
})
end
function Scrollbar:_open_win(opts)
local bufnr = vim.api.nvim_create_buf(false, true)
Util.tag(bufnr, "scrollbar")
local ret = {
bufnr = bufnr,
winnr = vim.api.nvim_open_win(bufnr, false, {
relative = "editor",
focusable = false,
width = 1,
-- HACK: height should be >=2 in case of winbar, which is inherited from the parent window
-- Change back to 1 when the upstream issue is fixed.
-- See https://github.com/neovim/neovim/issues/19464
height = 2,
row = 0,
col = 0,
style = "minimal",
noautocmd = true,
}),
}
vim.api.nvim_win_set_option(ret.winnr, "winhighlight", "Normal:" .. opts.normal)
return ret
end
return Scrollbar

View File

@ -0,0 +1,78 @@
local require = require("noice.util.lazy")
local Config = require("noice.config")
local Manager = require("noice.message.manager")
local Format = require("noice.text.format")
local pickers = require("telescope.pickers")
local finders = require("telescope.finders")
local conf = require("telescope.config").values
local previewers = require("telescope.previewers")
local M = {}
---@param message NoiceMessage
function M.display(message)
message = Format.format(message, "telescope")
local line = message._lines[1]
local hl = {}
local byte = 0
for _, text in ipairs(line._texts) do
local hl_group = text.extmark and text.extmark.hl_group
if hl_group then
table.insert(hl, { { byte, byte + text:length() }, hl_group })
end
byte = byte + text:length()
end
return line:content(), hl
end
function M.finder()
local messages = Manager.get(Config.options.commands.history.filter, {
history = true,
sort = true,
reverse = true,
})
return finders.new_table({
results = messages,
entry_maker = function(message)
return {
message = message,
display = function(entry)
return M.display(entry.message)
end,
ordinal = Format.format(message, "telescope"):content(),
}
end,
})
end
function M.previewer()
return previewers.new_buffer_previewer({
title = "Message",
define_preview = function(self, entry, _status)
vim.api.nvim_win_set_option(self.state.winid, "wrap", true)
---@type NoiceMessage
local message = Format.format(entry.message, "telescope_preview")
message:render(self.state.bufnr, Config.ns)
end,
})
end
function M.telescope(opts)
pickers
.new(opts, {
results_title = "Noice",
prompt_title = "Filter Noice",
finder = M.finder(),
sorter = conf.generic_sorter(opts),
previewer = M.previewer(),
})
:find()
end
return require("telescope").register_extension({
exports = {
noice = M.telescope,
},
})

View File

@ -0,0 +1 @@
std="vim"

View File

@ -0,0 +1,3 @@
indent_type = "Spaces"
indent_width = 2
column_width = 120

View File

@ -0,0 +1,35 @@
local M = {}
function M.root(root)
local f = debug.getinfo(1, "S").source:sub(2)
return vim.fn.fnamemodify(f, ":p:h:h") .. "/" .. (root or "")
end
---@param plugin string
function M.load(plugin)
local name = plugin:match(".*/(.*)")
local package_root = M.root(".tests/site/pack/deps/start/")
if not vim.loop.fs_stat(package_root .. name) then
print("Installing " .. plugin)
vim.fn.mkdir(package_root, "p")
vim.fn.system({
"git",
"clone",
"--depth=1",
"https://github.com/" .. plugin .. ".git",
package_root .. "/" .. name,
})
end
end
function M.setup()
vim.cmd([[set runtimepath=$VIMRUNTIME]])
vim.opt.runtimepath:append(M.root())
vim.opt.packpath = { M.root(".tests/site") }
M.load("MunifTanjim/nui.nvim")
M.load("nvim-lua/plenary.nvim")
M.load("rcarriga/nvim-notify")
end
M.setup()

View File

@ -0,0 +1,3 @@
#!/nix/store/306znyj77fv49kwnkpxmb0j2znqpa8bj-bash-5.2p26/bin/sh
nvim --headless -u tests/init.lua -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/init.lua', sequential = true}"

View File

@ -0,0 +1,213 @@
local Markdown = require("noice.text.markdown")
local M = {}
local ns = vim.api.nvim_create_namespace("noice_test")
function M.test()
describe("markdown", function()
it("parse", function()
for _, test in ipairs(M.tests) do
assert.same(test.output, Markdown.parse(test.input))
end
end)
it("conceal escape characters", function()
local chars = "\\`*_{}[]()#+-.!"
local buf = vim.api.nvim_create_buf(false, true)
---@type string[]
local lines = {}
for i = 1, #chars do
local char = chars:sub(i, i)
table.insert(lines, "\\" .. char)
end
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
Markdown.conceal_escape_characters(buf, ns, { 0, 0, #lines - 1, 1 })
local extmarks = vim.api.nvim_buf_get_extmarks(buf, ns, 0, -1, {})
assert.equal(chars:len(), #extmarks)
end)
end)
end
M.tests = {
{
input = [[
foo
]],
output = {
{ line = " foo" },
},
},
{
input = [[
bar
---
foo
]],
output = {
{ line = " bar" },
{ line = "---" },
{ line = " foo" },
},
},
{
input = [[
```lua
local a
```
foo
```lua
local b
```
]],
output = {
{ code = { "local a" }, lang = "lua" },
{ line = "" },
{ line = "foo" },
{ line = "" },
{ code = { "local b" }, lang = "lua" },
},
},
{
input = [[
```lua
local a
```
```lua
local b
```
]],
output = {
{ code = { "local a" }, lang = "lua" },
{ line = "" },
{ code = { "local b" }, lang = "lua" },
},
},
{
input = [[
&lt;foo
]],
output = {
{ line = "<foo" },
},
},
{
input = [[
```lua
local a
```
]],
output = {
{ code = { "local a" }, lang = "lua" },
},
},
{
input = [[
```lua
local a
local b
]],
output = {
{ code = { "local a", "local b", "" }, lang = "lua" },
},
},
{
input = [[
***
```lua
local a
```
---
]],
output = {
{ line = "---" },
{ code = { "local a" }, lang = "lua" },
{ line = "---" },
},
},
{
input = [[
***
``` lua
local a
```
---
]],
output = {
{ line = "---" },
{ code = { "local a" }, lang = "lua" },
{ line = "---" },
},
},
{
input = [[
```lua
local a
```
foo
bar
```
local b
```
]],
output = {
{ code = { "local a" }, lang = "lua" },
{ line = "" },
{ line = "foo" },
{ line = "" },
{ line = "bar" },
{ line = "" },
{ code = { "local b" }, lang = "text" },
},
},
{
input = [[
1 &lt; 2
3 &gt; 2
&quot;quoted&quot;
&apos;apos&apos;
&ensp;&emsp;indented
&amp;
]],
output = {
{ line = "1 < 2" },
{ line = "3 > 2" },
{ line = '"quoted"' },
{ line = "'apos'" },
{ line = " indented" },
{ line = "&" },
},
},
}
M.test()

View File

@ -0,0 +1,7 @@
local lazy = require("noice.util.lazy")
describe("ffi", function()
it("cmdpreview is false", function()
assert(lazy("noice.util.ffi").cmdpreview == false)
end)
end)

View File

@ -0,0 +1,12 @@
local View = require("noice.view")
local Config = require("noice.config")
Config.setup()
describe("checking views", function()
it("view is loaded only once", function()
local opts = { enter = true, format = "details" }
local view1 = View.get_view("split", opts)
local view2 = View.get_view("split", opts)
assert.equal(view1, view2)
end)
end)

View File

@ -0,0 +1,46 @@
[selene]
base = "lua51"
name = "vim"
[vim]
any = true
[[describe.args]]
type = "string"
[[describe.args]]
type = "function"
[[it.args]]
type = "string"
[[it.args]]
type = "function"
[[before_each.args]]
type = "function"
[[after_each.args]]
type = "function"
[assert.is_not]
any = true
[[assert.equals.args]]
type = "any"
[[assert.equals.args]]
type = "any"
[[assert.equals.args]]
type = "any"
required = false
[[assert.same.args]]
type = "any"
[[assert.same.args]]
type = "any"
[[assert.truthy.args]]
type = "any"
[[assert.spy.args]]
type = "any"
[[assert.stub.args]]
type = "any"