Refresh generated neovim config
This commit is contained in:
@ -6,7 +6,10 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Before** reporting an issue, make sure to read the [documentation](https://github.com/folke/which-key.nvim) and search [existing issues](https://github.com/folke/which-key.nvim/issues). Usage questions such as ***"How do I...?"*** belong in [Discussions](https://github.com/folke/which-key.nvim/discussions) and will be closed.
|
||||
**Before** reporting an issue, make sure to read the [documentation](https://github.com/folke/which-key.nvim)
|
||||
and search [existing issues](https://github.com/folke/which-key.nvim/issues).
|
||||
|
||||
Usage questions such as ***"How do I...?"*** belong in [Discussions](https://github.com/folke/which-key.nvim/discussions) and will be closed.
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Did you check docs and existing issues?
|
||||
@ -14,6 +17,8 @@ body:
|
||||
options:
|
||||
- label: I have read all the which-key.nvim docs
|
||||
required: true
|
||||
- label: I have updated the plugin to the latest version before submitting this issue
|
||||
required: true
|
||||
- label: I have searched the existing issues of which-key.nvim
|
||||
required: true
|
||||
- label: I have searched the existing issues of plugins related to this issue
|
||||
@ -52,38 +57,30 @@ body:
|
||||
description: A concise description of what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Health
|
||||
description: Attach the output of `:checkhealth which-key` here
|
||||
render: log
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Log
|
||||
description: Please enable logging with `opts.debug = true` and attach the contents of `./wk.log` here
|
||||
render: log
|
||||
- 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")
|
||||
vim.env.LAZY_STDPATH = ".repro"
|
||||
load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))()
|
||||
|
||||
-- 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/which-key.nvim", config = true },
|
||||
-- add any other plugins here
|
||||
}
|
||||
require("lazy").setup(plugins, {
|
||||
root = root .. "/plugins",
|
||||
require("lazy.minit").repro({
|
||||
spec = {
|
||||
{ "folke/which-key.nvim", opts = {} },
|
||||
-- add any other plugins here
|
||||
},
|
||||
})
|
||||
|
||||
vim.cmd.colorscheme("tokyonight")
|
||||
-- add anything else here
|
||||
render: Lua
|
||||
render: lua
|
||||
validations:
|
||||
required: false
|
||||
|
||||
@ -1,72 +1,14 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
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: which-key.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: which-key.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
|
||||
ci:
|
||||
uses: folke/github/.github/workflows/ci.yml@main
|
||||
secrets: inherit
|
||||
with:
|
||||
plugin: which-key.nvim
|
||||
repo: folke/which-key.nvim
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
tt.*
|
||||
.tests
|
||||
doc/tags
|
||||
debug
|
||||
.repro
|
||||
foo.*
|
||||
*.log
|
||||
data
|
||||
/.repro
|
||||
/.tests
|
||||
/build
|
||||
/debug
|
||||
/doc/tags
|
||||
foo.*
|
||||
node_modules
|
||||
tt.*
|
||||
|
||||
@ -1,5 +1,368 @@
|
||||
# Changelog
|
||||
|
||||
## [3.13.2](https://github.com/folke/which-key.nvim/compare/v3.13.1...v3.13.2) (2024-07-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **view:** fix epanded keys. Fixes [#795](https://github.com/folke/which-key.nvim/issues/795) ([f5e0cd5](https://github.com/folke/which-key.nvim/commit/f5e0cd5c7712ac63d8e6184785fb7bdac3b7b50d))
|
||||
|
||||
## [3.13.1](https://github.com/folke/which-key.nvim/compare/v3.13.0...v3.13.1) (2024-07-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **state:** better current buf/mode check ([711453b](https://github.com/folke/which-key.nvim/commit/711453bb945433362636e918a5238172ffd21e43))
|
||||
* **state:** deal with the fact that ModeChanged doesn't always seems to trigger. Fixes [#787](https://github.com/folke/which-key.nvim/issues/787) ([388bd3f](https://github.com/folke/which-key.nvim/commit/388bd3f83de06d1a1758ea6a342cf3ae614401f1))
|
||||
|
||||
## [3.13.0](https://github.com/folke/which-key.nvim/compare/v3.12.1...v3.13.0) (2024-07-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **debug:** add git info to log when using lazy ([550338d](https://github.com/folke/which-key.nvim/commit/550338dc292c014d83687afccb0afb06e3e769f2))
|
||||
|
||||
## [3.12.1](https://github.com/folke/which-key.nvim/compare/v3.12.0...v3.12.1) (2024-07-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **node:** dynamic mappings only support functions as rhs. Fixes [#790](https://github.com/folke/which-key.nvim/issues/790) ([ba91db7](https://github.com/folke/which-key.nvim/commit/ba91db72ffc745983f06ca4e7d969101287a9afe))
|
||||
* **state:** use cached mode. Fixes [#787](https://github.com/folke/which-key.nvim/issues/787). Closes [#789](https://github.com/folke/which-key.nvim/issues/789) ([c1b062a](https://github.com/folke/which-key.nvim/commit/c1b062ae95c3ca3e6eb87c075da991523605d79b))
|
||||
* **triggers:** check for existing keymaps in the correct buffer. Fixes [#783](https://github.com/folke/which-key.nvim/issues/783) ([977fa23](https://github.com/folke/which-key.nvim/commit/977fa23622425e3c8ae837b9f7c710d9c78bdeab))
|
||||
* **triggers:** nil error ([dae3bd2](https://github.com/folke/which-key.nvim/commit/dae3bd271826887771a7fb6deec231d2eb344f02))
|
||||
|
||||
## [3.12.0](https://github.com/folke/which-key.nvim/compare/v3.11.1...v3.12.0) (2024-07-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** allow overriding delay. Closes [#778](https://github.com/folke/which-key.nvim/issues/778) ([e6fea48](https://github.com/folke/which-key.nvim/commit/e6fea4889c20f22a7c6267cf1f1d091bc96f8ca0))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* dont expand nodes without children. Fixes [#782](https://github.com/folke/which-key.nvim/issues/782) ([53a1d2a](https://github.com/folke/which-key.nvim/commit/53a1d2a674df5fb667497fe3bbda625c39a2c0e1))
|
||||
|
||||
## [3.11.1](https://github.com/folke/which-key.nvim/compare/v3.11.0...v3.11.1) (2024-07-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **config:** keys can be any case. Fixes [#771](https://github.com/folke/which-key.nvim/issues/771) ([0a44d55](https://github.com/folke/which-key.nvim/commit/0a44d55d3bcdf75a134ec57c90aaec1055731014))
|
||||
* **config:** normalize keys ([c96be9b](https://github.com/folke/which-key.nvim/commit/c96be9bd54ffbc2ec7fc818001cad712119d778c))
|
||||
|
||||
## [3.11.0](https://github.com/folke/which-key.nvim/compare/v3.10.0...v3.11.0) (2024-07-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **icons:** icon for grug-far ([b2a2a0c](https://github.com/folke/which-key.nvim/commit/b2a2a0c9486211da23acdf18087f8203bfbca0e4))
|
||||
* **state:** detect recursion by users mapping wk manually. Closes [#761](https://github.com/folke/which-key.nvim/issues/761) ([55fa07f](https://github.com/folke/which-key.nvim/commit/55fa07fbbd8a4c6d75399b1d1f9005d146cda22c))
|
||||
* **view:** expand recursively. Closes [#767](https://github.com/folke/which-key.nvim/issues/767) ([5ae87af](https://github.com/folke/which-key.nvim/commit/5ae87af42914afe8b478ad6cdb3cb179fb73a62b))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **config:** disable wk by default for insert/command mode ([9d2b2e7](https://github.com/folke/which-key.nvim/commit/9d2b2e7059547c0481db2e93fd98547b26c7c05a))
|
||||
* **config:** more checks in validate ([bdcc429](https://github.com/folke/which-key.nvim/commit/bdcc429afaecc5896b462b0b07c2b3a9e9c1c60f))
|
||||
* **mappings:** preset descriptions should not override existing keymap descriptions. Fixes [#769](https://github.com/folke/which-key.nvim/issues/769) ([82d628f](https://github.com/folke/which-key.nvim/commit/82d628f4cfa397cf4bb233bd500e9cd9a018ded1))
|
||||
* **state:** don't feed count in insert mode. Fixes [#770](https://github.com/folke/which-key.nvim/issues/770) ([63690ff](https://github.com/folke/which-key.nvim/commit/63690ff34a8921c2de44fad289e2e04ee324b031))
|
||||
* **util:** better `<nop>` check. Fixes [#766](https://github.com/folke/which-key.nvim/issues/766) ([b7b3bd1](https://github.com/folke/which-key.nvim/commit/b7b3bd1b609524472f67e4a69d2bc14b80ea997f))
|
||||
* **view:** dont set title when no border. Fixes [#764](https://github.com/folke/which-key.nvim/issues/764) ([6e61b09](https://github.com/folke/which-key.nvim/commit/6e61b0904e9c038b6c511c43591ae2d811b4975e))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* prevent expanding nodes when not needed ([78cc92c](https://github.com/folke/which-key.nvim/commit/78cc92c6cb7da88df60227bc334a598a7e772e51))
|
||||
|
||||
## [3.10.0](https://github.com/folke/which-key.nvim/compare/v3.9.0...v3.10.0) (2024-07-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **view:** expand all nodes by default when filter.global = false ([c168905](https://github.com/folke/which-key.nvim/commit/c168905d62d9b8859b261de69910dfb7e3438996))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **buf:** always use nowait. Fixes [#755](https://github.com/folke/which-key.nvim/issues/755) ([ae1a235](https://github.com/folke/which-key.nvim/commit/ae1a235c53233c58a2f7cc14e5cdd09346cf27ed))
|
||||
* **buf:** early exit to determine if a trigger is safe to create. Fixes [#754](https://github.com/folke/which-key.nvim/issues/754) ([27e4716](https://github.com/folke/which-key.nvim/commit/27e47163165fee8e45b43d340db9335001403d2f))
|
||||
* **icons:** added frontier pattern for `ai` ([#760](https://github.com/folke/which-key.nvim/issues/760)) ([6fe0657](https://github.com/folke/which-key.nvim/commit/6fe065716e08550328c471689e6f8c1e42a0effc))
|
||||
* list_contains doesn't exists in Neovim < 0.10. Fixes [#758](https://github.com/folke/which-key.nvim/issues/758) ([7e4eae8](https://github.com/folke/which-key.nvim/commit/7e4eae8836e4ad28d478fedc421700b1138d1e0c))
|
||||
* **node:** allow custom mappings to override proxy/plugin/expand mappings ([9820900](https://github.com/folke/which-key.nvim/commit/982090080fa11da06038cf8e71af90d3a4fbd05a))
|
||||
* **node:** is_local check now also includes children ([fdd27f9](https://github.com/folke/which-key.nvim/commit/fdd27f9b6a991586943eb865275b279fb411ff0b))
|
||||
* **registers:** don't try to get `+*` registers when no clipboard is available. Fixes [#754](https://github.com/folke/which-key.nvim/issues/754) ([ae4ec03](https://github.com/folke/which-key.nvim/commit/ae4ec030489d7ecda908e473aea096a7594f84e8))
|
||||
* **state:** always honor defer. Fixes [#690](https://github.com/folke/which-key.nvim/issues/690) ([c512d13](https://github.com/folke/which-key.nvim/commit/c512d135531be81e17c85e254994cc755d3016c5))
|
||||
* **state:** redraw cursor before getchar ([cf6cbf2](https://github.com/folke/which-key.nvim/commit/cf6cbf2fd8f0c6497f130d07f6c88a2833c15d80))
|
||||
* **triggers:** prevent creating triggers for single upper-case alpha keys expect for Z. Fixes [#756](https://github.com/folke/which-key.nvim/issues/756) ([d19fa07](https://github.com/folke/which-key.nvim/commit/d19fa07b6e818ab55c34815784470a6d5f023524))
|
||||
|
||||
## [3.9.0](https://github.com/folke/which-key.nvim/compare/v3.8.0...v3.9.0) (2024-07-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **config:** simplified config. Some options are now deprecated ([8ddf2da](https://github.com/folke/which-key.nvim/commit/8ddf2da5a6aa76f5b3cec976f1d61e7c7fea42b5))
|
||||
* **view:** show and expand localleader mappings with filter.global = false ([ed5f762](https://github.com/folke/which-key.nvim/commit/ed5f7622771d0b5c0ac3a5e286ec6cd17b6be131))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ui:** remove deprecated opts.layout.align option. (wasn't used). Closes [#752](https://github.com/folke/which-key.nvim/issues/752) ([db32ac6](https://github.com/folke/which-key.nvim/commit/db32ac67abb36789a43fe497ff7d0b8ab7e8109e))
|
||||
|
||||
## [3.8.0](https://github.com/folke/which-key.nvim/compare/v3.7.0...v3.8.0) (2024-07-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **mappings:** added health check for invalid modes ([640724a](https://github.com/folke/which-key.nvim/commit/640724a541af75e6bbfe98f78cdebbec701d23a8))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **buf:** never create proxy/plugin mappings when a keymap exists. Fixes [#738](https://github.com/folke/which-key.nvim/issues/738) ([b4c4e36](https://github.com/folke/which-key.nvim/commit/b4c4e3648261399a97bfdc44bb8fa31b485fd3b9))
|
||||
* **registers:** use x instead of v ([#742](https://github.com/folke/which-key.nvim/issues/742)) ([5c3b3e8](https://github.com/folke/which-key.nvim/commit/5c3b3e834852a44efb26725f9c08917145f2c0c6))
|
||||
* **state:** schedule redraw. Fixes [#740](https://github.com/folke/which-key.nvim/issues/740) ([09f21a1](https://github.com/folke/which-key.nvim/commit/09f21a133104b66a5cede8fc0a8082b85b0eee9b))
|
||||
* **triggers:** allow overriding keymaps with empty rhs or <Nop>. Fixes [#748](https://github.com/folke/which-key.nvim/issues/748) ([843a93f](https://github.com/folke/which-key.nvim/commit/843a93fac6bca58167aafa392e6f7fd5a77633c9))
|
||||
* **triggers:** make sure no keymaps exists for triggers ([e8b454f](https://github.com/folke/which-key.nvim/commit/e8b454fb03e3cab398c894e5d462c84595ee57ca))
|
||||
* **typo:** replace 'exras' for 'extras' in README. ([#745](https://github.com/folke/which-key.nvim/issues/745)) ([af48cdc](https://github.com/folke/which-key.nvim/commit/af48cdc4bb8f1982a6124bf6bb5570349f690822))
|
||||
|
||||
## [3.7.0](https://github.com/folke/which-key.nvim/compare/v3.6.0...v3.7.0) (2024-07-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* added `expand` property to create dynamic mappings. An example for `buf` and `win` is included ([02f6e6f](https://github.com/folke/which-key.nvim/commit/02f6e6f4951ff993ad1d5c699784e6847a6c7b4c))
|
||||
* proxy mappings ([c3cfc2b](https://github.com/folke/which-key.nvim/commit/c3cfc2bdb03c1b87943a6d02485ad50b86567341))
|
||||
* **state:** allow defering on certain operators. Closes [#733](https://github.com/folke/which-key.nvim/issues/733) ([984d930](https://github.com/folke/which-key.nvim/commit/984d930711341ac118e6712804e8e22e575ba9d3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **buf:** create triggers for xo anyway. Fixes [#728](https://github.com/folke/which-key.nvim/issues/728) ([96b2e93](https://github.com/folke/which-key.nvim/commit/96b2e93979373744056c921f82b0c356e6f900de))
|
||||
* **icons:** added frontier pattern for `git`. Fixes [#727](https://github.com/folke/which-key.nvim/issues/727) ([bb4e82b](https://github.com/folke/which-key.nvim/commit/bb4e82bdaff50a4a93867e4c90938d18e7615af6))
|
||||
* **state:** dont popup when switching between v and V mode. Fixes [#729](https://github.com/folke/which-key.nvim/issues/729) ([8ddb527](https://github.com/folke/which-key.nvim/commit/8ddb527bcffc6957a59518f11c34a84d91e075f9))
|
||||
* **view:** always show a group as a group ([96a9eb3](https://github.com/folke/which-key.nvim/commit/96a9eb3f0b3299dffc241cf0f9ee5cf0509e6cd2))
|
||||
* **view:** empty icons ([e2cacc6](https://github.com/folke/which-key.nvim/commit/e2cacc6f1e4ba77e82e7a34e0dc6b2ad69cf075b))
|
||||
|
||||
## [3.6.0](https://github.com/folke/which-key.nvim/compare/v3.5.0...v3.6.0) (2024-07-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* added icons for <D mappings ([aaf71ab](https://github.com/folke/which-key.nvim/commit/aaf71ab078d86a48a26fafb5d451af609fd19c64))
|
||||
* added option to disable all mapping icons. Fixes [#721](https://github.com/folke/which-key.nvim/issues/721) ([33f6ac0](https://github.com/folke/which-key.nvim/commit/33f6ac04bdbce855ce43eecacb4c421876e246d7))
|
||||
* make which-key work without setup or calling add/register ([9ca5f4a](https://github.com/folke/which-key.nvim/commit/9ca5f4ab7cb541ef48dcaa4f03d3cd914a5e62fb))
|
||||
* **presets:** added some missing mappings ([6e1b3f2](https://github.com/folke/which-key.nvim/commit/6e1b3f290a3f89ffca68148aa639c866c24e2b77))
|
||||
* **state:** improve trigger/mode logic. Fixes [#715](https://github.com/folke/which-key.nvim/issues/715) ([3617e47](https://github.com/folke/which-key.nvim/commit/3617e47673d027989e9c3caa645edb6412c7fa30))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **config:** replacement for plug mappings ([495f9d9](https://github.com/folke/which-key.nvim/commit/495f9d953a86d630ef308f555ed452e332f417ee))
|
||||
* **icons:** get icons from parent nodes when needed ([3f0a7ed](https://github.com/folke/which-key.nvim/commit/3f0a7ed4401b98764740cbe8e1b954ac6adeca1b))
|
||||
* **icons:** use nerdfont symbol for BS. Fixes [#722](https://github.com/folke/which-key.nvim/issues/722) ([18c1ff5](https://github.com/folke/which-key.nvim/commit/18c1ff5ccb813d95c86f4ead6dac7e6cc5728f08))
|
||||
* **mode:** never create triggers for xo mode ([15d3a70](https://github.com/folke/which-key.nvim/commit/15d3a70304607417b2dc1df3da4992d5b8ce077a))
|
||||
* **presets:** motions in normal mode ([e2ffc26](https://github.com/folke/which-key.nvim/commit/e2ffc263fc05bf20f090ccaae7a06f88fd6e2fee))
|
||||
* tmp fix for op mode ([91641e2](https://github.com/folke/which-key.nvim/commit/91641e2a3af116ffaf739302a65cdb2865fb2415))
|
||||
* **view:** fix format for keymaps with 3+ keys ([#723](https://github.com/folke/which-key.nvim/issues/723)) ([0db7896](https://github.com/folke/which-key.nvim/commit/0db7896057d046576c829a87e2ff2de37c49e0fe))
|
||||
|
||||
## [3.5.0](https://github.com/folke/which-key.nvim/compare/v3.4.0...v3.5.0) (2024-07-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **api:** using wk.show() always assumes you want to see the group, and not the actual mapping in case of overlap. Fixes [#714](https://github.com/folke/which-key.nvim/issues/714) ([f5067d2](https://github.com/folke/which-key.nvim/commit/f5067d2b244c19eca38b5b495b6eb3e361ac565d))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **state:** attach on BufNew as well. Fixes [#681](https://github.com/folke/which-key.nvim/issues/681) ([0f58176](https://github.com/folke/which-key.nvim/commit/0f581764dc2c89c0ac3d8363369152735ae265ab))
|
||||
* **state:** make sure mode always exists even when not safe. See [#681](https://github.com/folke/which-key.nvim/issues/681) ([7915964](https://github.com/folke/which-key.nvim/commit/7915964e73c30ba5657e9a762c6570925dad421b))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **plugin:** only expand plugins when needed ([1fcfc72](https://github.com/folke/which-key.nvim/commit/1fcfc72374c705d68f0607a1dcbbbce13873b4e2))
|
||||
* **view:** set buf/win opts with eventignore ([e81e55b](https://github.com/folke/which-key.nvim/commit/e81e55b647a781f306453734834eb543e1f43c20))
|
||||
|
||||
## [3.4.0](https://github.com/folke/which-key.nvim/compare/v3.3.0...v3.4.0) (2024-07-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* added icons for function keys ([9222280](https://github.com/folke/which-key.nvim/commit/9222280970e8a7d74b4e0f6dab06c2f7a54d668d))
|
||||
* **mode:** allow certain modes to start hidden and only show after keypress. See [#690](https://github.com/folke/which-key.nvim/issues/690) ([b4fa48f](https://github.com/folke/which-key.nvim/commit/b4fa48f473796f5d9e3c9c31e6c9d7d509e51ca6))
|
||||
* **presets:** added gw ([09b80a6](https://github.com/folke/which-key.nvim/commit/09b80a68085c1fc792b595a851f702bc071d6310))
|
||||
* **presets:** better padding defaults for helix preset ([4c36b9b](https://github.com/folke/which-key.nvim/commit/4c36b9b8c722bcf51d038dcfba8b967f0ee818b8))
|
||||
* **presets:** increase default height for helix ([df0ad20](https://github.com/folke/which-key.nvim/commit/df0ad205ebd661ef101666ae21a62b77b3024a83))
|
||||
* simplified/documented/fixed mappings sorting. Closes [#694](https://github.com/folke/which-key.nvim/issues/694) ([eb73f7c](https://github.com/folke/which-key.nvim/commit/eb73f7c05785a83e07f1ea155b3b2833d8bbb532))
|
||||
* **state:** skip mouse keys in debug ([5f85b77](https://github.com/folke/which-key.nvim/commit/5f85b770c386c9435eb8da5db3081aa19078211a))
|
||||
* **ui:** show keys/help in an overlay and added scrolling hint ([50b2c43](https://github.com/folke/which-key.nvim/commit/50b2c43532e6ea5cca3ef4c2838d5a8bb535757f))
|
||||
* **view:** get parent icon if possible ([b9de927](https://github.com/folke/which-key.nvim/commit/b9de9278bdc57adfa69a67d8a3309f07c83951d0))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **buf:** always detach " when executing keys. Fixes [#689](https://github.com/folke/which-key.nvim/issues/689) ([d36f722](https://github.com/folke/which-key.nvim/commit/d36f722f114dfdafc8098496e9b5dcbd9f8fc3e8))
|
||||
* **config:** set expand=0 by default. Fixes [#693](https://github.com/folke/which-key.nvim/issues/693) ([89434aa](https://github.com/folke/which-key.nvim/commit/89434aa356abd4a694d2b89eccb203e0742bc0d7))
|
||||
* **config:** warn when deprecated config options were used. Fixes [#696](https://github.com/folke/which-key.nvim/issues/696) ([81413ef](https://github.com/folke/which-key.nvim/commit/81413ef02dffbe6e4c73f418e4acc920e68b3aa7))
|
||||
* **health:** move deprecated option check to health ([af7a30f](https://github.com/folke/which-key.nvim/commit/af7a30fa24ce0a13dba00cbd7b836330facf9f1a))
|
||||
* **mappings:** allow creating keymaps without desc. Fixes [#695](https://github.com/folke/which-key.nvim/issues/695) ([c442aaa](https://github.com/folke/which-key.nvim/commit/c442aaa6aafe2742c2e92df7ee127df90099ce17))
|
||||
* **plugins:** add existing keymaps to plugin view. Fixes [#681](https://github.com/folke/which-key.nvim/issues/681) ([26f6fd2](https://github.com/folke/which-key.nvim/commit/26f6fd258b66e9656bb86c7269c6497a9ce8a5fa))
|
||||
* **presets:** don't override title setting for classic. See [#649](https://github.com/folke/which-key.nvim/issues/649) ([9a53c1f](https://github.com/folke/which-key.nvim/commit/9a53c1ff46421450b5563baab1599591d81de111))
|
||||
* **presets:** shorter descriptions ([20600e4](https://github.com/folke/which-key.nvim/commit/20600e422277b383e8c921feec2111a281935217))
|
||||
* **state:** always do full update on BufReadPost since buffer-local keymaps would be deleted. Fixes [#709](https://github.com/folke/which-key.nvim/issues/709) ([6068887](https://github.com/folke/which-key.nvim/commit/60688872f4ecc552a5e2bcbd01e7629a155f377f))
|
||||
* **state:** don't show when coming from cmdline mode. Fixes [#692](https://github.com/folke/which-key.nvim/issues/692) ([8cba66b](https://github.com/folke/which-key.nvim/commit/8cba66b5a1a0ea8fe8dd5d3d55a42755924e47d8))
|
||||
* **state:** honor timeoutlen and nowait. Fixes [#648](https://github.com/folke/which-key.nvim/issues/648). Closes [#697](https://github.com/folke/which-key.nvim/issues/697) ([80f20ee](https://github.com/folke/which-key.nvim/commit/80f20ee62311505fe6d675212f7b246900570450))
|
||||
* **state:** properly disable which-key when recording macros. Fixes [#702](https://github.com/folke/which-key.nvim/issues/702) ([b506275](https://github.com/folke/which-key.nvim/commit/b506275acfb4383f678b9ba3aa8db88787c24680))
|
||||
* **state:** scrolling ([dce9167](https://github.com/folke/which-key.nvim/commit/dce9167025a0801e4bab146a2856508a9af52ea2))
|
||||
* **tree:** rawget for existing plugin node children ([c77cda8](https://github.com/folke/which-key.nvim/commit/c77cda8cd2f54965e4316699f1d124a2b3bf9d49))
|
||||
* **util:** when no clipboard provider exists, use the " register as default. Fixes [#687](https://github.com/folke/which-key.nvim/issues/687) ([d077a3f](https://github.com/folke/which-key.nvim/commit/d077a3f36d4b4d29eccc7feb1ba8e78a421df920))
|
||||
* **view:** disable footer on Neovim < 0.10 ([6d544a4](https://github.com/folke/which-key.nvim/commit/6d544a43a21a228482155d65c3ca18fd7038b422))
|
||||
* **view:** ensure highlights get set for title padding ([#684](https://github.com/folke/which-key.nvim/issues/684)) ([2e4f7af](https://github.com/folke/which-key.nvim/commit/2e4f7afa4aa444483d8ade5989d524c7f4131368))
|
||||
* **view:** hide existing title/footer when no trail ([4f589a1](https://github.com/folke/which-key.nvim/commit/4f589a1368e100a6e33aabd904f34716b75360f6))
|
||||
* **view:** include group keymaps in expand results. See [#682](https://github.com/folke/which-key.nvim/issues/682) ([39e703c](https://github.com/folke/which-key.nvim/commit/39e703ceaa9a05dcc664e0ab0ea88c03c3b6bf90))
|
||||
* **view:** overlap protection should keep at least 4 lines ([0d89475](https://github.com/folke/which-key.nvim/commit/0d89475f87756199efc2bc52537fc4d11b0f695a))
|
||||
* **view:** padding & column spacing. Fixes [#704](https://github.com/folke/which-key.nvim/issues/704) ([11eec49](https://github.com/folke/which-key.nvim/commit/11eec49509490c023bf0272efef955f86f18c1d2))
|
||||
* **view:** spacing when more than one box ([89568f3](https://github.com/folke/which-key.nvim/commit/89568f3438f1fbc6c340a8af05ea67feac494c46))
|
||||
* **view:** special handling of `<NL>/<C-J>`. Fixes [#706](https://github.com/folke/which-key.nvim/issues/706) ([f8c91b2](https://github.com/folke/which-key.nvim/commit/f8c91b2b4a2d239d3b1d49f901a393e7326a5da8))
|
||||
|
||||
## [3.3.0](https://github.com/folke/which-key.nvim/compare/v3.2.0...v3.3.0) (2024-07-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **expand:** allow expand to be a function. Closes [#670](https://github.com/folke/which-key.nvim/issues/670) ([dfaa10c](https://github.com/folke/which-key.nvim/commit/dfaa10cd24badb321a4667fb9135f242393e5680))
|
||||
* **mappings:** mapping `desc` and `icon` can now be a function that is evaluated when which-key is show. Fixes [#666](https://github.com/folke/which-key.nvim/issues/666) ([c634af1](https://github.com/folke/which-key.nvim/commit/c634af1295512dc2062fbec38f563f5793de245c))
|
||||
* **mappings:** opts.filter to exclude certain mappings from showing up in which-key. ([763ea00](https://github.com/folke/which-key.nvim/commit/763ea000cce9589124515ba34f6d9a6347a02891))
|
||||
* **view:** add operator to trail in op mode ([5a6eaaa](https://github.com/folke/which-key.nvim/commit/5a6eaaa4ebc072625b9fc906943e3798028bd817))
|
||||
* **view:** when in visual mode, propagate esc. See [#656](https://github.com/folke/which-key.nvim/issues/656) ([30ef44a](https://github.com/folke/which-key.nvim/commit/30ef44a13065a157f97d3fb5bbf23a5c23e513eb))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* default preset ([38987d3](https://github.com/folke/which-key.nvim/commit/38987d3f18a8ffc5eaa404d746fd8ee4017b5f37))
|
||||
* **mappings:** don't show `<SNR>` mappings ([d700244](https://github.com/folke/which-key.nvim/commit/d700244acc1f1474b34737e14a45df2aa3a324ba))
|
||||
* **presets:** max 1 column in helix mode. Fixes [#665](https://github.com/folke/which-key.nvim/issues/665) ([b2a6910](https://github.com/folke/which-key.nvim/commit/b2a6910e9e97526f2327327d2751834049cbd334))
|
||||
* **presets:** shorter descriptions ([9a73d6a](https://github.com/folke/which-key.nvim/commit/9a73d6a0b0d5f456a9768d434a83d6d4cdb83efa))
|
||||
* **state:** cooldown till next tick when not safe to open which-key. Fixes [#672](https://github.com/folke/which-key.nvim/issues/672) ([bdf3b27](https://github.com/folke/which-key.nvim/commit/bdf3b272ea34ac137af3cb1ebcd5cf8c9745abbb))
|
||||
* **util:** nt mode should map to n ([969afc9](https://github.com/folke/which-key.nvim/commit/969afc95d374bc0d6ce397d3d2357d8faa38041a))
|
||||
* **view:** set nowrap for the which-key window ([6e1c098](https://github.com/folke/which-key.nvim/commit/6e1c0987024adf63ab91f281f8f9c355abf3f3d8))
|
||||
* **view:** set winhl groups. Fixes [#661](https://github.com/folke/which-key.nvim/issues/661) ([baff8ea](https://github.com/folke/which-key.nvim/commit/baff8ea846cbb613dee79333aad7a1d2b912a5bc))
|
||||
|
||||
## [3.2.0](https://github.com/folke/which-key.nvim/compare/v3.1.0...v3.2.0) (2024-07-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* added `opts.debug` that writes to wk.log in the current directory ([c23df71](https://github.com/folke/which-key.nvim/commit/c23df711884d97963d0c17ed29f5d8c1064d4adc))
|
||||
* hydra mode. will document later ([65f2e72](https://github.com/folke/which-key.nvim/commit/65f2e7236a3bc278dd163d7c98c9ea5d9ab6e42e))
|
||||
* **icons:** add telescope icon ([#643](https://github.com/folke/which-key.nvim/issues/643)) ([fca3d9e](https://github.com/folke/which-key.nvim/commit/fca3d9eaef57ddb3ce438d208ebc32e23c9f290a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* layout stuff ([7423096](https://github.com/folke/which-key.nvim/commit/742309697cff6aa7f377b72e2f54d34afef09ee1))
|
||||
* **mappings:** always use mapping even when it's creating a keymap. Fixes [#637](https://github.com/folke/which-key.nvim/issues/637) ([2d744cb](https://github.com/folke/which-key.nvim/commit/2d744cb824c0f310be420bf33688bc005f164f46))
|
||||
* **mappings:** make replace_keycodes default to false in v1 spec ([6ec0a1e](https://github.com/folke/which-key.nvim/commit/6ec0a1ef89209680c799269227b4d0c28de1d877))
|
||||
* **state:** dont start which-key during dot repeat. Fixes [#636](https://github.com/folke/which-key.nvim/issues/636) ([5971ecd](https://github.com/folke/which-key.nvim/commit/5971ecdf4465425d6bc6e2277101c6fc896cbe06))
|
||||
* **state:** dont start which-key more than once during the same tick in xo mode. Fixes [#635](https://github.com/folke/which-key.nvim/issues/635) ([0218fce](https://github.com/folke/which-key.nvim/commit/0218fce1c3d54307217391215db28e63de9b8980))
|
||||
* **state:** dont start wk when chars are pending. Fixes [#658](https://github.com/folke/which-key.nvim/issues/658). Fixes [#655](https://github.com/folke/which-key.nvim/issues/655). Fixes [#648](https://github.com/folke/which-key.nvim/issues/648) ([877ce16](https://github.com/folke/which-key.nvim/commit/877ce163d764bbe7c82a7fec5671c32188607754))
|
||||
* **state:** only hide on focus lost when still hidden after 1s. Fixes [#638](https://github.com/folke/which-key.nvim/issues/638) ([649a51b](https://github.com/folke/which-key.nvim/commit/649a51bc81b09443c326d390e3d182e0cdf98c15))
|
||||
* **types:** spec field types ([#645](https://github.com/folke/which-key.nvim/issues/645)) ([c6ffb1c](https://github.com/folke/which-key.nvim/commit/c6ffb1ce63959d5f1effe5924712f36eac1e940e))
|
||||
* **util:** set local window opts for notify. Fixes [#641](https://github.com/folke/which-key.nvim/issues/641) ([63f2112](https://github.com/folke/which-key.nvim/commit/63f2112361a53b0cf68245868977773f210bb5cd))
|
||||
* **view:** check for real overlap instead of just row overlap. See [#649](https://github.com/folke/which-key.nvim/issues/649) ([0427e91](https://github.com/folke/which-key.nvim/commit/0427e91dbbd9c37eb20e6fbc2386f890dc0d7e2a))
|
||||
* **view:** disable folds. Fixes [#99](https://github.com/folke/which-key.nvim/issues/99) ([6860e3b](https://github.com/folke/which-key.nvim/commit/6860e3b681b40e3620049f714ae53a6bad594701))
|
||||
|
||||
## [3.1.0](https://github.com/folke/which-key.nvim/compare/v3.0.0...v3.1.0) (2024-07-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* allow disabling any trigger ([94b7951](https://github.com/folke/which-key.nvim/commit/94b795154fb213db6ed8aeba3d7f53cbce7c147c))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* added support for vim.loop ([54db192](https://github.com/folke/which-key.nvim/commit/54db1928c17ac420e897a40f5ad560ee9f28b186))
|
||||
* automatically do setup if setup wasn't called within 500ms. Fixes [#630](https://github.com/folke/which-key.nvim/issues/630) ([632ad41](https://github.com/folke/which-key.nvim/commit/632ad41b5fcf60fac897d0b6530a699eb980748d))
|
||||
* **buf:** buffer-local mappings were broken (not keymaps). Fixes [#629](https://github.com/folke/which-key.nvim/issues/629) ([58d7f82](https://github.com/folke/which-key.nvim/commit/58d7f822ecc80ca4b43e9c14fd6ec962483e2168))
|
||||
* **colors:** compat with older Neovim vesions. Fixes [#631](https://github.com/folke/which-key.nvim/issues/631) ([4516dc9](https://github.com/folke/which-key.nvim/commit/4516dc9422f571c9e189ff6696853d445a3058d6))
|
||||
|
||||
## [3.0.0](https://github.com/folke/which-key.nvim/compare/v2.1.0...v3.0.0) (2024-07-12)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* v3 release
|
||||
|
||||
### Features
|
||||
|
||||
* added health check back with better wording on what actually gets checked ([97e6e41](https://github.com/folke/which-key.nvim/commit/97e6e4166134aad826454588ae764c7a54f5d298))
|
||||
* added WhichKey command ([7c12ab9](https://github.com/folke/which-key.nvim/commit/7c12ab9c2569a7459932bc19a4e52ea5a48437b2))
|
||||
* automatically use nowait based on delay and timeoutlen ([110ed72](https://github.com/folke/which-key.nvim/commit/110ed728bedd0182e4d11726194f7eb5db63e2fb))
|
||||
* bring config back and create mappings when needed ([add7ab9](https://github.com/folke/which-key.nvim/commit/add7ab92163399c47f7149c96387d382e9d8996b))
|
||||
* buffer-local sort & refactor API ([14309d0](https://github.com/folke/which-key.nvim/commit/14309d0446dcc6a24421c56e914e06b1fe2d4f41))
|
||||
* close which-key on FocusLost ([aa99460](https://github.com/folke/which-key.nvim/commit/aa99460e117d0348c7f1f77ab669398c04fcba6b))
|
||||
* config, and presets ([541989d](https://github.com/folke/which-key.nvim/commit/541989db167e04eb3db24ba57decab0326614f0f))
|
||||
* expand groups with less than n mappings. Closes [#374](https://github.com/folke/which-key.nvim/issues/374). Fixes [#90](https://github.com/folke/which-key.nvim/issues/90). Closes [#208](https://github.com/folke/which-key.nvim/issues/208) ([5caf057](https://github.com/folke/which-key.nvim/commit/5caf057b3a204a94d53b4b0200ce915463b4a922))
|
||||
* fancy key icons ([e4d0134](https://github.com/folke/which-key.nvim/commit/e4d01347434b31e8a90720463076bbbeebbef199))
|
||||
* fix hidden and empty groups ([afc4aa9](https://github.com/folke/which-key.nvim/commit/afc4aa96ae5671f5d4d14f332789dec72dd5db02))
|
||||
* **health:** duplicate mappings check ([4762e06](https://github.com/folke/which-key.nvim/commit/4762e06f9dc45b3470ab5b2efa0a4b3de6148298))
|
||||
* **health:** icon providers & overlapping keys ([dcbf29a](https://github.com/folke/which-key.nvim/commit/dcbf29ae337bd4d621e326b6f1caad66cfe0770a))
|
||||
* initial rewrite ([eb3ad2e](https://github.com/folke/which-key.nvim/commit/eb3ad2eb062392497d0fed3489e2582d4e5bc289))
|
||||
* keep track of virtual mappings ([4537d3e](https://github.com/folke/which-key.nvim/commit/4537d3ea52b2b11b96ca2fdde2bb4573f0ca7c73))
|
||||
* key/desc replacements ([cf34ffe](https://github.com/folke/which-key.nvim/commit/cf34ffe9384941dc833ed2a3bb2a3bf3aa050373))
|
||||
* layout ([347288a](https://github.com/folke/which-key.nvim/commit/347288acd8398ae7c641bd6159261e98f9a6b929))
|
||||
* manual sorting. Closes [#131](https://github.com/folke/which-key.nvim/issues/131), Closes [#362](https://github.com/folke/which-key.nvim/issues/362), Closes [#264](https://github.com/folke/which-key.nvim/issues/264) ([c2daf9d](https://github.com/folke/which-key.nvim/commit/c2daf9dcf48e8c8cca61cfc27b1731272b9bc2c6))
|
||||
* **mappings:** added support for lazy.nvim style mappings ([6f7a945](https://github.com/folke/which-key.nvim/commit/6f7a945f1dc679ce2c35064e12e4dc531ebf2c3c))
|
||||
* **mappings:** added support for setting custom icons from the spec ([951ae7a](https://github.com/folke/which-key.nvim/commit/951ae7a89d164f39f8aa49f51da424539370f6c4))
|
||||
* new spec and migration recommendation for health ([41374bc](https://github.com/folke/which-key.nvim/commit/41374bcae462d897fa98c904a44127e258c0438c))
|
||||
* option to disable icon colors ([79c8ac8](https://github.com/folke/which-key.nvim/commit/79c8ac87139dcb816072c1a5ca1800d9ce5d64aa))
|
||||
* option to disable notify ([4cc46ff](https://github.com/folke/which-key.nvim/commit/4cc46ffa57b8a6ebf6ca7a07128d353f5569a802))
|
||||
* play nice with macros ([1abc2bf](https://github.com/folke/which-key.nvim/commit/1abc2bf96472e7816252719de06d60e9b09035dc))
|
||||
* plugins partially working again ([b925b31](https://github.com/folke/which-key.nvim/commit/b925b31bab1f91507d15a96f226f7f7423c4fced))
|
||||
* **registers:** show non-printable with keytrans ([1832197](https://github.com/folke/which-key.nvim/commit/183219772d01e0ea744c0ff8bf656895f7d7c8d3))
|
||||
* spec parser rewrite & proper typings ([07065fe](https://github.com/folke/which-key.nvim/commit/07065fe345bc9dd20aff11ab9a6a3b078aacd42e))
|
||||
* state management ([e2ee1fa](https://github.com/folke/which-key.nvim/commit/e2ee1fae13f7a6c38652994dedb0cb34e2608918))
|
||||
* state management ([e6beb88](https://github.com/folke/which-key.nvim/commit/e6beb8845e80558194c6027b7a985e1211e76878))
|
||||
* title trail ([aef2e53](https://github.com/folke/which-key.nvim/commit/aef2e535c5b7c8f100b534a4b781a82e36f20e39))
|
||||
* **ui:** added scrolling ([5f1ab35](https://github.com/folke/which-key.nvim/commit/5f1ab35d099a252f204e2806747980c192a9c265))
|
||||
* **ui:** keymap icons ([21d7108](https://github.com/folke/which-key.nvim/commit/21d71081d86872189a3ce90b7c13593f15b78459))
|
||||
* **ui:** sorters ([ffeea79](https://github.com/folke/which-key.nvim/commit/ffeea7933249d5ce33b2b3838171cc5299ef1893))
|
||||
* update ui when new mappings become available ([a8f66f5](https://github.com/folke/which-key.nvim/commit/a8f66f5ebd9b94f409a88c4a77244167f6edd05f))
|
||||
* v3 release ([da258a8](https://github.com/folke/which-key.nvim/commit/da258a89a700916ad0e6af1ad8f9889ff0308253))
|
||||
* **view:** nerd font icons for cmd keys ([2787dbd](https://github.com/folke/which-key.nvim/commit/2787dbd158184af67ead5af5bcc0cbdb17856c31))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** show view immediately when opened through the API ([b0e0af0](https://github.com/folke/which-key.nvim/commit/b0e0af0957a648735a43fae52ef34059721f7b42))
|
||||
* autmatically blacklist all single key hooks except for z and g ([87c5a4b](https://github.com/folke/which-key.nvim/commit/87c5a4b1be1f882c8b27252464d777a76ea15839))
|
||||
* **icons:** check that mini icons hl groups exist in the current colorscheme. If not use which-key default groups ([2336350](https://github.com/folke/which-key.nvim/commit/233635039bf828e341f5ca9b4b8444ac3c56b974))
|
||||
* **icons:** proper icons check ([2eaed99](https://github.com/folke/which-key.nvim/commit/2eaed99585f08787d6b5060c89184973eb5aa276))
|
||||
* **keys:** delete nop keymaps with a description ([ccf0276](https://github.com/folke/which-key.nvim/commit/ccf027625df6c4e22febfdd786c5e1f7521c2ccb))
|
||||
* **layout:** display vs multibyte ellipsis ([0442a73](https://github.com/folke/which-key.nvim/commit/0442a7340cebe13cc5a5fd70dd6cdc989f9086fe))
|
||||
* **layout:** empty columns ([600881a](https://github.com/folke/which-key.nvim/commit/600881a9b0cf8119819a97d8900d99fd7a406d36))
|
||||
* op-mode, count and reg ([e4d54d1](https://github.com/folke/which-key.nvim/commit/e4d54d11cc247edd0ed4bde7a501caa8e119c1ff))
|
||||
* pcall keymap.del ([e47ee13](https://github.com/folke/which-key.nvim/commit/e47ee139b6a082deab16e436cbd2711923e01625))
|
||||
* plugin actions & spelling ([e7da411](https://github.com/folke/which-key.nvim/commit/e7da411b45415e8d0d6a5e14b9c1bd5207d09869))
|
||||
* presets ([bcf52ba](https://github.com/folke/which-key.nvim/commit/bcf52ba08a57a90e85d4397245a0350c34f2b9d1))
|
||||
* readme ([5fe6c91](https://github.com/folke/which-key.nvim/commit/5fe6c91e6f2d7d6dd1a8473ac0cd9bbe311512d9))
|
||||
* respect mappings with `<esc>` and close on cursor moved ([22deda5](https://github.com/folke/which-key.nvim/commit/22deda5458b15a10b02b516c68dd409cbaeb53f4))
|
||||
* set check debounce to 50 ([754bcc7](https://github.com/folke/which-key.nvim/commit/754bcc7be77b9f9ecac02598121eb97a243b7efa))
|
||||
* **state:** dont return or autocmd will cancel ([9a77986](https://github.com/folke/which-key.nvim/commit/9a779869ef557ff6fa84a8b0b478a0f84781c67e))
|
||||
* **state:** keyboard interrupts ([1ed9182](https://github.com/folke/which-key.nvim/commit/1ed91823d47f34ce5c52da9ca14e202606caf215))
|
||||
* **state:** make sure the buffer mode exists when changing modes ([df64366](https://github.com/folke/which-key.nvim/commit/df64366d8633ac13ba2da7134cc6bbe242a97237))
|
||||
* stuff ([f67eb19](https://github.com/folke/which-key.nvim/commit/f67eb192ca6d579add84086d4d1b4ce6ce8732ac))
|
||||
* **tree:** check for which_key_ignore in existing keymaps ([f17d78b](https://github.com/folke/which-key.nvim/commit/f17d78bdf8a0afce5bec97c70e68203a6cddf2b7))
|
||||
* **ui:** box height ([528fc43](https://github.com/folke/which-key.nvim/commit/528fc43b87cfc29bbc1dddc17051a99cdfdf9ad2))
|
||||
* **ui:** make sure the which-key window never overlaps the user's cursor position ([1bb30a7](https://github.com/folke/which-key.nvim/commit/1bb30a7a6901aa842f31c96af7009ef645b29edd))
|
||||
* **ui:** scroll and topline=1 on refresh ([28b648d](https://github.com/folke/which-key.nvim/commit/28b648daeabfd2aad8496ffc7a2096bf7d2441b5))
|
||||
* which_key_ignore ([ab5ffa8](https://github.com/folke/which-key.nvim/commit/ab5ffa83b4f10ea2360a32d855b016f72a2be6b6))
|
||||
* which-key ignore and cleanup ([aeae826](https://github.com/folke/which-key.nvim/commit/aeae826f948cbaeb3a89d9025c423e8300cb5dd3))
|
||||
|
||||
## [2.1.0](https://github.com/folke/which-key.nvim/compare/v2.0.1...v2.1.0) (2024-06-06)
|
||||
|
||||
|
||||
|
||||
@ -1,32 +1,37 @@
|
||||
# 💥 Which Key
|
||||
|
||||
**WhichKey** is a lua plugin for Neovim 0.5 that displays a popup with possible key bindings of
|
||||
the command you started typing. Heavily inspired by the original [emacs-which-key](https://github.com/justbur/emacs-which-key) and [vim-which-key](https://github.com/liuchengxu/vim-which-key).
|
||||
**WhichKey** helps you remember your Neovim keymaps, by showing available keybindings
|
||||
in a popup as you type.
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## ✨ Features
|
||||
|
||||
- for Neovim 0.7 and higher, it uses the `desc` attributes of your mappings as the default label
|
||||
- for Neovim 0.7 and higher, new mappings will be created with a `desc` attribute
|
||||
- opens a popup with suggestions to complete a key binding
|
||||
- works with any setting for [timeoutlen](https://neovim.io/doc/user/options.html#'timeoutlen'), including instantly (`timeoutlen=0`)
|
||||
- works correctly with built-in key bindings
|
||||
- works correctly with buffer-local mappings
|
||||
- extensible plugin architecture
|
||||
- built-in plugins:
|
||||
- **marks:** shows your marks when you hit one of the jump keys.
|
||||
- **registers:** shows the contents of your registers
|
||||
- **presets:** built-in key binding help for `motions`, `text-objects`, `operators`, `windows`, `nav`, `z` and `g`
|
||||
- **spelling:** spelling suggestions inside the which-key popup
|
||||
- 🔍 **Key Binding Help**: show available keybindings in a popup as you type.
|
||||
- ⌨️ **Modes**: works in normal, insert, visual, operator pending, terminal and command mode.
|
||||
Every mode can be enabled/disabled.
|
||||
- 🛠️ **Customizable Layouts**: choose from `classic`, `modern`, and `helix` presets or customize the window.
|
||||
- 🔄 **Flexible Sorting**: sort by `local`, `order`, `group`, `alphanum`, `mod`, `lower`, `icase`, `desc`, or `manual`.
|
||||
- 🎨 **Formatting**: customizable key labels and descriptions
|
||||
- 🖼️ **Icons**: integrates with [mini.icons](https://github.com/echasnovski/mini.icons) and [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons)
|
||||
- ⏱️ **Delay**: delay is independent of `timeoutlen`
|
||||
- 🌐 **Plugins**: built-in plugins for marks, registers, presets, and spelling suggestions
|
||||
- 🚀 **Operators, Motions, Text Objects**: help for operators, motions and text objects
|
||||
- 🐙 **Hydra Mode**: keep the popup open until you hit `<esc>`
|
||||
|
||||
## ⚡️ Requirements
|
||||
|
||||
- Neovim >= 0.5.0
|
||||
- **Neovim** >= 0.9.4
|
||||
- for proper icons support:
|
||||
- [mini.icons](https://github.com/echasnovski/mini.icons) _(optional)_
|
||||
- [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) _(optional)_
|
||||
- a [Nerd Font](https://www.nerdfonts.com/) **_(optional)_**
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
Install the plugin with your preferred package manager:
|
||||
Install the plugin with your package manager:
|
||||
|
||||
### [lazy.nvim](https://github.com/folke/lazy.nvim)
|
||||
|
||||
@ -34,51 +39,68 @@ Install the plugin with your preferred package manager:
|
||||
{
|
||||
"folke/which-key.nvim",
|
||||
event = "VeryLazy",
|
||||
init = function()
|
||||
vim.o.timeout = true
|
||||
vim.o.timeoutlen = 300
|
||||
end,
|
||||
opts = {
|
||||
-- your configuration comes here
|
||||
-- or leave it empty to use the default settings
|
||||
-- refer to the configuration section below
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### [packer](https://github.com/wbthomason/packer.nvim)
|
||||
|
||||
```lua
|
||||
-- Lua
|
||||
use {
|
||||
"folke/which-key.nvim",
|
||||
config = function()
|
||||
vim.o.timeout = true
|
||||
vim.o.timeoutlen = 300
|
||||
require("which-key").setup {
|
||||
-- your configuration comes here
|
||||
-- or leave it empty to use the default settings
|
||||
-- refer to the configuration section below
|
||||
}
|
||||
end
|
||||
},
|
||||
keys = {
|
||||
{
|
||||
"<leader>?",
|
||||
function()
|
||||
require("which-key").show({ global = false })
|
||||
end,
|
||||
desc = "Buffer Local Keymaps (which-key)",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
> ❗️ IMPORTANT: the [timeout](https://neovim.io/doc/user/options.html#'timeout') when **WhichKey** opens is controlled by the vim setting [timeoutlen](https://neovim.io/doc/user/options.html#'timeoutlen').
|
||||
> Please refer to the documentation to properly set it up. Setting it to `0`, will effectively
|
||||
> always show **WhichKey** immediately, but a setting of `500` (500ms) is probably more appropriate.
|
||||
> [!important]
|
||||
> Make sure to run `:checkhealth which-key` if something isn't working properly
|
||||
|
||||
> ❗️ don't create any keymappings yourself to trigger WhichKey. Unlike with _vim-which-key_, we do this fully automatically.
|
||||
> Please remove any left-over triggers you might have from using _vim-which-key_.
|
||||
**WhichKey** is highly configurable. Expand to see the list of all the default options below.
|
||||
|
||||
> 🚑 You can run `:checkhealth which-key` to see if there's any conflicting keymaps that will prevent triggering **WhichKey**
|
||||
<details><summary>Default Options</summary>
|
||||
|
||||
WhichKey comes with the following defaults:
|
||||
<!-- config:start -->
|
||||
|
||||
```lua
|
||||
{
|
||||
---@class wk.Opts
|
||||
local defaults = {
|
||||
---@type false | "classic" | "modern" | "helix"
|
||||
preset = "classic",
|
||||
-- Delay before showing the popup. Can be a number or a function that returns a number.
|
||||
---@type number | fun(ctx: { keys: string, mode: string, plugin?: string }):number
|
||||
delay = function(ctx)
|
||||
return ctx.plugin and 0 or 200
|
||||
end,
|
||||
---@param mapping wk.Mapping
|
||||
filter = function(mapping)
|
||||
-- example to exclude mappings without a description
|
||||
-- return mapping.desc and mapping.desc ~= ""
|
||||
return true
|
||||
end,
|
||||
--- You can add any mappings here, or use `require('which-key').add()` later
|
||||
---@type wk.Spec
|
||||
spec = {},
|
||||
-- show a warning when issues were detected with your mappings
|
||||
notify = true,
|
||||
-- Which-key automatically sets up triggers for your mappings.
|
||||
-- But you can disable this and setup the triggers manually.
|
||||
-- Check the docs for more info.
|
||||
---@type wk.Spec
|
||||
triggers = {
|
||||
{ "<auto>", mode = "nxsot" },
|
||||
},
|
||||
-- Start hidden and wait for a key to be pressed before showing the popup
|
||||
-- Only used by enabled xo mapping modes.
|
||||
---@param ctx { mode: string, operator: string }
|
||||
defer = function(ctx)
|
||||
return ctx.mode == "V" or ctx.mode == "<C-V>"
|
||||
end,
|
||||
plugins = {
|
||||
marks = true, -- shows a list of your marks on ' and `
|
||||
registers = true, -- shows your registers on " in NORMAL or <C-r> in INSERT mode
|
||||
@ -98,205 +120,270 @@ WhichKey comes with the following defaults:
|
||||
g = true, -- bindings for prefixed with g
|
||||
},
|
||||
},
|
||||
-- add operators that will trigger motion and text object completion
|
||||
-- to enable all native operators, set the preset / operators plugin above
|
||||
operators = { gc = "Comments" },
|
||||
key_labels = {
|
||||
-- override the label used to display some keys. It doesn't effect WK in any other way.
|
||||
-- For example:
|
||||
-- ["<space>"] = "SPC",
|
||||
-- ["<cr>"] = "RET",
|
||||
-- ["<tab>"] = "TAB",
|
||||
---@type wk.Win.opts
|
||||
win = {
|
||||
-- don't allow the popup to overlap with the cursor
|
||||
no_overlap = true,
|
||||
-- width = 1,
|
||||
-- height = { min = 4, max = 25 },
|
||||
-- col = 0,
|
||||
-- row = math.huge,
|
||||
-- border = "none",
|
||||
padding = { 1, 2 }, -- extra window padding [top/bottom, right/left]
|
||||
title = true,
|
||||
title_pos = "center",
|
||||
zindex = 1000,
|
||||
-- Additional vim.wo and vim.bo options
|
||||
bo = {},
|
||||
wo = {
|
||||
-- winblend = 10, -- value between 0-100 0 for fully opaque and 100 for fully transparent
|
||||
},
|
||||
},
|
||||
motions = {
|
||||
count = true,
|
||||
layout = {
|
||||
width = { min = 20 }, -- min and max width of the columns
|
||||
spacing = 3, -- spacing between columns
|
||||
},
|
||||
keys = {
|
||||
scroll_down = "<c-d>", -- binding to scroll down inside the popup
|
||||
scroll_up = "<c-u>", -- binding to scroll up inside the popup
|
||||
},
|
||||
---@type (string|wk.Sorter)[]
|
||||
--- Mappings are sorted using configured sorters and natural sort of the keys
|
||||
--- Available sorters:
|
||||
--- * local: buffer-local mappings first
|
||||
--- * order: order of the items (Used by plugins like marks / registers)
|
||||
--- * group: groups last
|
||||
--- * alphanum: alpha-numerical first
|
||||
--- * mod: special modifier keys last
|
||||
--- * manual: the order the mappings were added
|
||||
--- * case: lower-case first
|
||||
sort = { "local", "order", "group", "alphanum", "mod" },
|
||||
---@type number|fun(node: wk.Node):boolean?
|
||||
expand = 0, -- expand groups when <= n mappings
|
||||
-- expand = function(node)
|
||||
-- return not node.desc -- expand all nodes without a description
|
||||
-- end,
|
||||
-- Functions/Lua Patterns for formatting the labels
|
||||
---@type table<string, ({[1]:string, [2]:string}|fun(str:string):string)[]>
|
||||
replace = {
|
||||
key = {
|
||||
function(key)
|
||||
return require("which-key.view").format(key)
|
||||
end,
|
||||
-- { "<Space>", "SPC" },
|
||||
},
|
||||
desc = {
|
||||
{ "<Plug>%(?(.*)%)?", "%1" },
|
||||
{ "^%+", "" },
|
||||
{ "<[cC]md>", "" },
|
||||
{ "<[cC][rR]>", "" },
|
||||
{ "<[sS]ilent>", "" },
|
||||
{ "^lua%s+", "" },
|
||||
{ "^call%s+", "" },
|
||||
{ "^:%s*", "" },
|
||||
},
|
||||
},
|
||||
icons = {
|
||||
breadcrumb = "»", -- symbol used in the command line area that shows your active key combo
|
||||
separator = "➜", -- symbol used between a key and it's label
|
||||
group = "+", -- symbol prepended to a group
|
||||
},
|
||||
popup_mappings = {
|
||||
scroll_down = "<c-d>", -- binding to scroll down inside the popup
|
||||
scroll_up = "<c-u>", -- binding to scroll up inside the popup
|
||||
},
|
||||
window = {
|
||||
border = "none", -- none, single, double, shadow
|
||||
position = "bottom", -- bottom, top
|
||||
margin = { 1, 0, 1, 0 }, -- extra window margin [top, right, bottom, left]. When between 0 and 1, will be treated as a percentage of the screen size.
|
||||
padding = { 1, 2, 1, 2 }, -- extra window padding [top, right, bottom, left]
|
||||
winblend = 0, -- value between 0-100 0 for fully opaque and 100 for fully transparent
|
||||
zindex = 1000, -- positive value to position WhichKey above other floating windows.
|
||||
},
|
||||
layout = {
|
||||
height = { min = 4, max = 25 }, -- min and max height of the columns
|
||||
width = { min = 20, max = 50 }, -- min and max width of the columns
|
||||
spacing = 3, -- spacing between columns
|
||||
align = "left", -- align columns left, center or right
|
||||
},
|
||||
ignore_missing = false, -- enable this to hide mappings for which you didn't specify a label
|
||||
hidden = { "<silent>", "<cmd>", "<Cmd>", "<CR>", "^:", "^ ", "^call ", "^lua " }, -- hide mapping boilerplate
|
||||
show_help = true, -- show a help message in the command line for using WhichKey
|
||||
show_keys = true, -- show the currently pressed key and its label as a message in the command line
|
||||
triggers = "auto", -- automatically setup triggers
|
||||
-- triggers = {"<leader>"} -- or specify a list manually
|
||||
-- list of triggers, where WhichKey should not wait for timeoutlen and show immediately
|
||||
triggers_nowait = {
|
||||
-- marks
|
||||
"`",
|
||||
"'",
|
||||
"g`",
|
||||
"g'",
|
||||
-- registers
|
||||
'"',
|
||||
"<c-r>",
|
||||
-- spelling
|
||||
"z=",
|
||||
},
|
||||
triggers_blacklist = {
|
||||
-- list of mode / prefixes that should never be hooked by WhichKey
|
||||
-- this is mostly relevant for keymaps that start with a native binding
|
||||
i = { "j", "k" },
|
||||
v = { "j", "k" },
|
||||
},
|
||||
-- disable the WhichKey popup for certain buf types and file types.
|
||||
-- Disabled by default for Telescope
|
||||
disable = {
|
||||
buftypes = {},
|
||||
filetypes = {},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## 🪄 Setup
|
||||
|
||||
With the default settings, **WhichKey** will work out of the box for most builtin keybindings,
|
||||
but the real power comes from documenting and organizing your own keybindings.
|
||||
|
||||
To document and/or setup your own mappings, you need to call the `register` method
|
||||
|
||||
```lua
|
||||
local wk = require("which-key")
|
||||
wk.register(mappings, opts)
|
||||
```
|
||||
|
||||
Default options for `opts`
|
||||
|
||||
```lua
|
||||
{
|
||||
mode = "n", -- NORMAL mode
|
||||
-- prefix: use "<leader>f" for example for mapping everything related to finding files
|
||||
-- the prefix is prepended to every mapping part of `mappings`
|
||||
prefix = "",
|
||||
buffer = nil, -- Global mappings. Specify a buffer number for buffer local mappings
|
||||
silent = true, -- use `silent` when creating keymaps
|
||||
noremap = true, -- use `noremap` when creating keymaps
|
||||
nowait = false, -- use `nowait` when creating keymaps
|
||||
expr = false, -- use `expr` when creating keymaps
|
||||
}
|
||||
```
|
||||
|
||||
> ❕ When you specify a command in your mapping that starts with `<Plug>`, then we automatically set `noremap=false`, since you always want recursive keybindings in this case
|
||||
|
||||
### ⌨️ Mappings
|
||||
|
||||
> ⌨ for **Neovim 0.7** and higher, which key will use the `desc` attribute of existing mappings as the default label
|
||||
|
||||
Group names use the special `name` key in the tables. There's multiple ways to define the mappings. `wk.register` can be called multiple times from anywhere in your config files.
|
||||
|
||||
```lua
|
||||
local wk = require("which-key")
|
||||
-- As an example, we will create the following mappings:
|
||||
-- * <leader>ff find files
|
||||
-- * <leader>fr show recent files
|
||||
-- * <leader>fb Foobar
|
||||
-- we'll document:
|
||||
-- * <leader>fn new file
|
||||
-- * <leader>fe edit file
|
||||
-- and hide <leader>1
|
||||
|
||||
wk.register({
|
||||
f = {
|
||||
name = "file", -- optional group name
|
||||
f = { "<cmd>Telescope find_files<cr>", "Find File" }, -- create a binding with label
|
||||
r = { "<cmd>Telescope oldfiles<cr>", "Open Recent File", noremap=false, buffer = 123 }, -- additional options for creating the keymap
|
||||
n = { "New File" }, -- just a label. don't create any mapping
|
||||
e = "Edit File", -- same as above
|
||||
["1"] = "which_key_ignore", -- special label to hide it in the popup
|
||||
b = { function() print("bar") end, "Foobar" } -- you can also pass functions!
|
||||
},
|
||||
}, { prefix = "<leader>" })
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Click to see more examples</summary>
|
||||
|
||||
```lua
|
||||
-- all of the mappings below are equivalent
|
||||
|
||||
-- method 2
|
||||
wk.register({
|
||||
["<leader>"] = {
|
||||
f = {
|
||||
name = "+file",
|
||||
f = { "<cmd>Telescope find_files<cr>", "Find File" },
|
||||
r = { "<cmd>Telescope oldfiles<cr>", "Open Recent File" },
|
||||
n = { "<cmd>enew<cr>", "New File" },
|
||||
ellipsis = "…",
|
||||
-- set to false to disable all mapping icons,
|
||||
-- both those explicitely added in a mapping
|
||||
-- and those from rules
|
||||
mappings = true,
|
||||
--- See `lua/which-key/icons.lua` for more details
|
||||
--- Set to `false` to disable keymap icons from rules
|
||||
---@type wk.IconRule[]|false
|
||||
rules = {},
|
||||
-- use the highlights from mini.icons
|
||||
-- When `false`, it will use `WhichKeyIcon` instead
|
||||
colors = true,
|
||||
-- used by key format
|
||||
keys = {
|
||||
Up = " ",
|
||||
Down = " ",
|
||||
Left = " ",
|
||||
Right = " ",
|
||||
C = " ",
|
||||
M = " ",
|
||||
D = " ",
|
||||
S = " ",
|
||||
CR = " ",
|
||||
Esc = " ",
|
||||
ScrollWheelDown = " ",
|
||||
ScrollWheelUp = " ",
|
||||
NL = " ",
|
||||
BS = "",
|
||||
Space = " ",
|
||||
Tab = " ",
|
||||
F1 = "",
|
||||
F2 = "",
|
||||
F3 = "",
|
||||
F4 = "",
|
||||
F5 = "",
|
||||
F6 = "",
|
||||
F7 = "",
|
||||
F8 = "",
|
||||
F9 = "",
|
||||
F10 = "",
|
||||
F11 = "",
|
||||
F12 = "",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
-- method 3
|
||||
wk.register({
|
||||
["<leader>f"] = {
|
||||
name = "+file",
|
||||
f = { "<cmd>Telescope find_files<cr>", "Find File" },
|
||||
r = { "<cmd>Telescope oldfiles<cr>", "Open Recent File" },
|
||||
n = { "<cmd>enew<cr>", "New File" },
|
||||
show_help = true, -- show a help message in the command line for using WhichKey
|
||||
show_keys = true, -- show the currently pressed key and its label as a message in the command line
|
||||
-- disable WhichKey for certain buf types and file types.
|
||||
disable = {
|
||||
ft = {},
|
||||
bt = {},
|
||||
},
|
||||
})
|
||||
|
||||
-- method 4
|
||||
wk.register({
|
||||
["<leader>f"] = { name = "+file" },
|
||||
["<leader>ff"] = { "<cmd>Telescope find_files<cr>", "Find File" },
|
||||
["<leader>fr"] = { "<cmd>Telescope oldfiles<cr>", "Open Recent File" },
|
||||
["<leader>fn"] = { "<cmd>enew<cr>", "New File" },
|
||||
})
|
||||
debug = false, -- enable wk.log in the current directory
|
||||
}
|
||||
```
|
||||
|
||||
<!-- config:end -->
|
||||
|
||||
</details>
|
||||
|
||||
**Tips:** The default label is `keymap.desc` or `keymap.rhs` or `""`,
|
||||
`:h nvim_set_keymap()` to get more details about `desc` and `rhs`.
|
||||
## ⌨️ Mappings
|
||||
|
||||
### 🚙 Operators, Motions and Text Objects
|
||||
**WhichKey** automatically gets the descriptions of your keymaps from the `desc`
|
||||
attribute of the keymap. So for most use-cases, you don't need to do anything else.
|
||||
|
||||
**WhichKey** provides help to work with operators, motions and text objects.
|
||||
However, the **mapping spec** is still useful to configure group descriptions and mappings that don't really exist as a regular keymap.
|
||||
|
||||
> `[count]operator[count][text-object]`
|
||||
> [!WARNING]
|
||||
> The **mappings spec** changed in `v3`, so make sure to only use the new `add` method if
|
||||
> you updated your existing mappings.
|
||||
|
||||
- operators can be configured with the `operators` option
|
||||
- set `plugins.presets.operators` to `true` to automatically configure vim built-in operators
|
||||
- set this to `false`, to only include the list you configured in the `operators` option.
|
||||
- see [here](https://github.com/folke/which-key.nvim/blob/main/lua/which-key/plugins/presets/init.lua#L5) for the full list part of the preset
|
||||
- text objects are automatically retrieved from **operator pending** key maps (`omap`)
|
||||
- set `plugins.presets.text_objects` to `true` to configure built-in text objects
|
||||
- see [here](https://github.com/folke/which-key.nvim/blob/main/lua/which-key/plugins/presets/init.lua#L43)
|
||||
- motions are part of the preset `plugins.presets.motions` setting
|
||||
- see [here](https://github.com/folke/which-key.nvim/blob/main/lua/which-key/plugins/presets/init.lua#L20)
|
||||
Mappings can be added as part of the config `opts.spec`, or can be added later
|
||||
using `require("which-key").add()`.
|
||||
`wk.add()` can be called multiple times from anywhere in your config files.
|
||||
|
||||
<details>
|
||||
<summary>How to disable some operators? (like v)</summary>
|
||||
A mapping has the following attributes:
|
||||
|
||||
- **[1]**: (`string`) lhs **_(required)_**
|
||||
- **[2]**: (`string|fun()`) rhs **_(optional)_**: when present, it will create the mapping
|
||||
- **desc**: (`string|fun():string`) description **_(required for non-groups)_**
|
||||
- **group**: (`string|fun():string`) group name **_(optional)_**
|
||||
- **mode**: (`string|string[]`) mode **_(optional, defaults to `"n"`)_**
|
||||
- **cond**: (`boolean|fun():boolean`) condition to enable the mapping **_(optional)_**
|
||||
- **hidden**: (`boolean`) hide the mapping **_(optional)_**
|
||||
- **icon**: (`string|wk.Icon|fun():(wk.Icon|string)`) icon spec **_(optional)_**
|
||||
- **proxy**: (`string`) proxy to another mapping **_(optional)_**
|
||||
- **expand**: (`fun():wk.Spec`) nested mappings **_(optional)_**
|
||||
- any other option valid for `vim.keymap.set`. These are only used for creating mappings.
|
||||
|
||||
When `desc`, `group`, or `icon` are functions, they are evaluated every time
|
||||
the popup is shown.
|
||||
|
||||
The `expand` property allows to create dynamic mappings. Only functions as `rhs` are supported for dynamic mappings.
|
||||
Two examples are included in `which-key.extras`:
|
||||
|
||||
- `require("which-key.extras").expand.buf`: creates numerical key to buffer mappings
|
||||
- `require("which-key.extras").expand.win`: creates numerical key to window mappings
|
||||
|
||||
```lua
|
||||
-- make sure to run this code before calling setup()
|
||||
-- refer to the full lists at https://github.com/folke/which-key.nvim/blob/main/lua/which-key/plugins/presets/init.lua
|
||||
local presets = require("which-key.plugins.presets")
|
||||
presets.operators["v"] = nil
|
||||
local wk = require("which-key")
|
||||
wk.add({
|
||||
{ "<leader>f", group = "file" }, -- group
|
||||
{ "<leader>ff", "<cmd>Telescope find_files<cr>", desc = "Find File", mode = "n" },
|
||||
{ "<leader>fb", function() print("hello") end, desc = "Foobar" },
|
||||
{ "<leader>fn", desc = "New File" },
|
||||
{ "<leader>f1", hidden = true }, -- hide this keymap
|
||||
{ "<leader>w", proxy = "<c-w>", group = "windows" }, -- proxy to window mappings
|
||||
{ "<leader>b", group = "buffers", expand = function()
|
||||
return require("which-key.extras").expand.buf()
|
||||
end
|
||||
},
|
||||
{
|
||||
-- Nested mappings are allowed and can be added in any order
|
||||
-- Most attributes can be inherited or overridden on any level
|
||||
-- There's no limit to the depth of nesting
|
||||
mode = { "n", "v" }, -- NORMAL and VISUAL mode
|
||||
{ "<leader>q", "<cmd>q<cr>", desc = "Quit" }, -- no need to specify mode since it's inherited
|
||||
{ "<leader>w", "<cmd>w<cr>", desc = "Write" },
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
</details>
|
||||
## 🎯 Triggers
|
||||
|
||||
There's two ways that **which-key** can be triggered:
|
||||
|
||||
- by a trigger keymap
|
||||
- by a `ModeChanged` event for visual and operator pending mode
|
||||
|
||||
Both can be configured using `opts.triggers` and `opts.defer`.
|
||||
|
||||
By default `opts.triggers` includes `{ "<auto>", mode = "nixsotc" }`, which
|
||||
will setup keymap triggers for every mode automatically and will trigger during
|
||||
`ModeChanged`.
|
||||
|
||||
> [!NOTE]
|
||||
> Auto triggers will never be created for existing keymaps.
|
||||
> That includes every valid single key Neovim builtin mapping.
|
||||
> If you want to trigger on a builtin keymap, you have to add it manually.
|
||||
>
|
||||
> ```lua
|
||||
> triggers = {
|
||||
> { "<auto>", mode = "nixsotc" },
|
||||
> { "a", mode = { "n", "v" } },
|
||||
> }
|
||||
> ```
|
||||
|
||||
> [!TIP]
|
||||
> To manually setup triggers, you can set `opts.triggers` to:
|
||||
>
|
||||
> ```lua
|
||||
> triggers = {
|
||||
> { "<leader>", mode = { "n", "v" } },
|
||||
> }
|
||||
> ```
|
||||
|
||||
For `ModeChanged` triggers, you can configure the `opts.defer` option.
|
||||
When it returns `true`, the popup will be shown only after an additional key is pressed.
|
||||
So `yaf`, would show which-key after pressing `ya`, but not after `y`.
|
||||
|
||||
> [!TIP]
|
||||
> Defer some operators:
|
||||
>
|
||||
> ```lua
|
||||
> ---@param ctx { mode: string, operator: string }
|
||||
> defer = function(ctx)
|
||||
> if vim.list_contains({ "d", "y" }, ctx.operator) then
|
||||
> return true
|
||||
> end
|
||||
> return vim.list_contains({ "<C-V>", "V" }, ctx.mode)
|
||||
> end,
|
||||
> ```
|
||||
|
||||
## 🎨 Icons
|
||||
|
||||
> [!note]
|
||||
> For full support, you need to install either [mini.icons](https://github.com/echasnovski/mini.icons) or [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons)
|
||||
|
||||
There's multiple ways to set icons for your keymaps:
|
||||
|
||||
- if you use lazy.nvim, then some icons will be autodetected for keymaps belonging to certain plugins.
|
||||
- custom rules to decide what icon to use
|
||||
- in your mapping spec, you can specify what icon to use at any level, so at the node for `<leader>g` for example, to apply to all git keymaps.
|
||||
|
||||
The `icon` attribute of a mapping can be a `string`, which will be used as the actual icon,
|
||||
or an `wk.Icon` object, which can have the following attributes:
|
||||
|
||||
- `icon` (`string`): the icon to use **_(optional)_**
|
||||
- `hl` (`string`): the highlight group to use for the icon **_(optional)_**
|
||||
- `color` (`string`): the color to use for the icon **_(optional)_**
|
||||
valid colors are: `azure`, `blue`, `cyan`, `green`, `grey`, `orange`, `purple`, `red`, `yellow`
|
||||
- `cat` (`string`): the category of the icon **_(optional)_**
|
||||
valid categories are: `file`, `filetype`, `extension`
|
||||
- `name` (`string`): the name of the icon in the specified category **_(optional)_**
|
||||
|
||||
> [!TIP]
|
||||
> If you'd rather not use icons, you can disable them
|
||||
> by setting `opts.icons.mappings` to `false`.
|
||||
|
||||
## 🚀 Usage
|
||||
|
||||
@ -308,60 +395,69 @@ When the **WhichKey** popup is open, you can use the following key bindings (the
|
||||
- `<c-d>` scroll down
|
||||
- `<c-u>` scroll up
|
||||
|
||||
Apart from the automatic opening, you can also manually open **WhichKey** for a certain `prefix`:
|
||||
## 🐙 Hydra Mode
|
||||
|
||||
> ❗️ don't create any keymappings yourself to trigger WhichKey. Unlike with _vim-which-key_, we do this fully automatically.
|
||||
> Please remove any left-over triggers you might have from using _vim-which-key_.
|
||||
Hydra mode is a special mode that keeps the popup open until you hit `<esc>`.
|
||||
|
||||
```vim
|
||||
:WhichKey " show all mappings
|
||||
:WhichKey <leader> " show all <leader> mappings
|
||||
:WhichKey <leader> v " show all <leader> mappings for VISUAL mode
|
||||
:WhichKey '' v " show ALL mappings for VISUAL mode
|
||||
```lua
|
||||
-- Show hydra mode for changing windows
|
||||
require("which-key").show({
|
||||
keys = "<c-w>",
|
||||
loop = true, -- this will keep the popup open until you hit <esc>
|
||||
})
|
||||
```
|
||||
|
||||
## 🔥 Plugins
|
||||
|
||||
Four built-in plugins are included with **WhichKey**.
|
||||
|
||||
### Presets
|
||||
|
||||
Built-in key binding help for `motions`, `text-objects`, `operators`, `windows`, `nav`, `z` and `g` and more.
|
||||
|
||||
### Marks
|
||||
|
||||
Shows a list of your buffer local and global marks when you hit \` or '
|
||||
|
||||

|
||||

|
||||
|
||||
### Registers
|
||||
|
||||
Shows a list of your buffer local and global registers when you hit " in _NORMAL_ mode, or `<c-r>` in _INSERT_ mode.
|
||||
|
||||

|
||||
|
||||
### Presets
|
||||
|
||||
Built-in key binding help for `motions`, `text-objects`, `operators`, `windows`, `nav`, `z` and `g`
|
||||
|
||||

|
||||

|
||||
|
||||
### Spelling
|
||||
|
||||
When enabled, this plugin hooks into `z=` and replaces the full-screen spelling suggestions window by a list of suggestions within **WhichKey**.
|
||||
|
||||

|
||||

|
||||
|
||||
## 🎨 Colors
|
||||
|
||||
The table below shows all the highlight groups defined for **WhichKey** with their default link.
|
||||
|
||||
| Highlight Group | Defaults to | Description |
|
||||
| ------------------- | ----------- | ------------------------------------------- |
|
||||
| _WhichKey_ | Function | the key |
|
||||
| _WhichKeyGroup_ | Keyword | a group |
|
||||
| _WhichKeySeparator_ | DiffAdd | the separator between the key and its label |
|
||||
| _WhichKeyDesc_ | Identifier | the label of the key |
|
||||
| _WhichKeyFloat_ | NormalFloat | Normal in the popup window |
|
||||
| _WhichKeyBorder_ | FloatBorder | Normal in the popup window |
|
||||
| _WhichKeyValue_ | Comment | used by plugins that provide values |
|
||||
<!-- colors:start -->
|
||||
|
||||
<!-- markdownlint-disable-file MD033 -->
|
||||
<!-- markdownlint-configure-file { "MD013": { "line_length": 120 } } -->
|
||||
<!-- markdownlint-configure-file { "MD004": { "style": "sublist" } } -->
|
||||
| Highlight Group | Default Group | Description |
|
||||
| --- | --- | --- |
|
||||
| **WhichKey** | ***Function*** | |
|
||||
| **WhichKeyBorder** | ***FloatBorder*** | Border of the which-key window |
|
||||
| **WhichKeyDesc** | ***Identifier*** | description |
|
||||
| **WhichKeyGroup** | ***Keyword*** | group name |
|
||||
| **WhichKeyIcon** | ***@markup.link*** | icons |
|
||||
| **WhichKeyIconAzure** | ***Function*** | |
|
||||
| **WhichKeyIconBlue** | ***DiagnosticInfo*** | |
|
||||
| **WhichKeyIconCyan** | ***DiagnosticHint*** | |
|
||||
| **WhichKeyIconGreen** | ***DiagnosticOk*** | |
|
||||
| **WhichKeyIconGrey** | ***Normal*** | |
|
||||
| **WhichKeyIconOrange** | ***DiagnosticWarn*** | |
|
||||
| **WhichKeyIconPurple** | ***Constant*** | |
|
||||
| **WhichKeyIconRed** | ***DiagnosticError*** | |
|
||||
| **WhichKeyIconYellow** | ***DiagnosticWarn*** | |
|
||||
| **WhichKeyNormal** | ***NormalFloat*** | Normal in th which-key window |
|
||||
| **WhichKeySeparator** | ***Comment*** | the separator between the key and its description |
|
||||
| **WhichKeyTitle** | ***FloatTitle*** | Title of the which-key window |
|
||||
| **WhichKeyValue** | ***Comment*** | values by plugins (like marks, registers, etc) |
|
||||
|
||||
<!-- colors:end -->
|
||||
|
||||
@ -1,21 +1,50 @@
|
||||
# Todo
|
||||
|
||||
* [x] hook into all groups
|
||||
* [x] show mappings without keymap (zz etc)
|
||||
* [x] plugin support for marks, registers, text objects
|
||||
* [x] `<bs>` to go up a level
|
||||
* [x] config modes
|
||||
* [x] update buf only
|
||||
* [x] + thingy for groups
|
||||
* [x] text objects
|
||||
* [x] get label from global when not found for buffer
|
||||
* [x] operators & motions
|
||||
* [x] show window after timeout?
|
||||
* [x] make plugins a list of key value with config in value
|
||||
* [x] cleanup text objects text
|
||||
* [x] buf local mappings seems to interfere with global mappings (push K in help)
|
||||
* [x] fix help in visual mode
|
||||
* [x] Plug>whichkey nop
|
||||
* [x] preset plugin
|
||||
* [x] command should auto stuff
|
||||
* [x] timeoutlen is always respected and should still work when zero
|
||||
- [x] create keymaps in register()
|
||||
- [x] distinction between actual keymap and just a desc
|
||||
- [x] virtual mappings wihtout real children?
|
||||
- [x] registers / counts?
|
||||
- [x] presets / plugins
|
||||
- [x] config?
|
||||
- [x] auto blacklist single keys for default keymaps (for mappings like `aa`, a hook would be created for `a` and `a` would be ignored)
|
||||
- [x] custom sorting
|
||||
- [x] gr doesn't work because of grn and friends
|
||||
- [x] same for gc opmode and gc normal mode
|
||||
- [x] yank and shift-paste hangs
|
||||
- [x] macro recording / macro execution
|
||||
- [x] spell
|
||||
- [x] spell with count, like `1z=`
|
||||
- [x] which-key-ignore
|
||||
- [x] empty groups?
|
||||
- [x] timeoutlen and nowait
|
||||
- [x] ui presets
|
||||
- [x] ui opts & columns etc
|
||||
- [x] scroll window
|
||||
- [x] help text?
|
||||
- [x] plugin layout?
|
||||
- [x] ✅ 🔥🔥🚀
|
||||
- [x] minimize attach
|
||||
- [x] sometimes incorrectly attached `gcc` not working
|
||||
- [x] error handling for view
|
||||
- [x] spelling layout
|
||||
- [x] better mappings parser? Especially needs typings
|
||||
- [x] Mappings with mode `v`
|
||||
- [x] allow register from opts
|
||||
- [x] auto gen docs
|
||||
- [x] health
|
||||
- [x] `<leader>gh_`
|
||||
- [x] devicons support
|
||||
- [x] nowait, timeoutlen and delay
|
||||
- [x] new mappings DSL
|
||||
- [x] News
|
||||
- [x] normal mode mappings in terminal mode?
|
||||
- [x] dynamic size
|
||||
- [x] situation with visual mode
|
||||
- [x] fix timeoutlen
|
||||
- [x] document hydra mode
|
||||
- [x] floating help text?
|
||||
- [x] move old option check to checkhealth
|
||||
- [x] show scrolling hint when can't fit all mappings
|
||||
- [ ] more tests
|
||||
- [ ] hint characters in desc
|
||||
- [ ] intgerate with lazy.nvim. Get description there if set
|
||||
|
||||
@ -4,9 +4,12 @@ which-key.nvim-which-key which-key.nvim.txt /*which-key.nvim-which-key*
|
||||
which-key.nvim-which-key-colors which-key.nvim.txt /*which-key.nvim-which-key-colors*
|
||||
which-key.nvim-which-key-configuration which-key.nvim.txt /*which-key.nvim-which-key-configuration*
|
||||
which-key.nvim-which-key-features which-key.nvim.txt /*which-key.nvim-which-key-features*
|
||||
which-key.nvim-which-key-hydra-mode which-key.nvim.txt /*which-key.nvim-which-key-hydra-mode*
|
||||
which-key.nvim-which-key-icons which-key.nvim.txt /*which-key.nvim-which-key-icons*
|
||||
which-key.nvim-which-key-installation which-key.nvim.txt /*which-key.nvim-which-key-installation*
|
||||
which-key.nvim-which-key-mappings which-key.nvim.txt /*which-key.nvim-which-key-mappings*
|
||||
which-key.nvim-which-key-plugins which-key.nvim.txt /*which-key.nvim-which-key-plugins*
|
||||
which-key.nvim-which-key-requirements which-key.nvim.txt /*which-key.nvim-which-key-requirements*
|
||||
which-key.nvim-which-key-setup which-key.nvim.txt /*which-key.nvim-which-key-setup*
|
||||
which-key.nvim-which-key-triggers which-key.nvim.txt /*which-key.nvim-which-key-triggers*
|
||||
which-key.nvim-which-key-usage which-key.nvim.txt /*which-key.nvim-which-key-usage*
|
||||
which-key.nvim.txt which-key.nvim.txt /*which-key.nvim.txt*
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
*which-key.nvim.txt* For Neovim >= 0.8.0 Last change: 2024 June 07
|
||||
*which-key.nvim.txt* For Neovim Last change: 2024 July 24
|
||||
|
||||
==============================================================================
|
||||
Table of Contents *which-key.nvim-table-of-contents*
|
||||
@ -8,8 +8,11 @@ Table of Contents *which-key.nvim-table-of-contents*
|
||||
- Requirements |which-key.nvim-which-key-requirements|
|
||||
- Installation |which-key.nvim-which-key-installation|
|
||||
- Configuration |which-key.nvim-which-key-configuration|
|
||||
- Setup |which-key.nvim-which-key-setup|
|
||||
- Mappings |which-key.nvim-which-key-mappings|
|
||||
- Triggers |which-key.nvim-which-key-triggers|
|
||||
- Icons |which-key.nvim-which-key-icons|
|
||||
- Usage |which-key.nvim-which-key-usage|
|
||||
- Hydra Mode |which-key.nvim-which-key-hydra-mode|
|
||||
- Plugins |which-key.nvim-which-key-plugins|
|
||||
- Colors |which-key.nvim-which-key-colors|
|
||||
2. Links |which-key.nvim-links|
|
||||
@ -17,36 +20,39 @@ Table of Contents *which-key.nvim-table-of-contents*
|
||||
==============================================================================
|
||||
1. Which Key *which-key.nvim-which-key*
|
||||
|
||||
**WhichKey** is a lua plugin for Neovim 0.5 that displays a popup with possible
|
||||
key bindings of the command you started typing. Heavily inspired by the
|
||||
original emacs-which-key <https://github.com/justbur/emacs-which-key> and
|
||||
vim-which-key <https://github.com/liuchengxu/vim-which-key>.
|
||||
**WhichKey** helps you remember your Neovim keymaps, by showing available
|
||||
keybindings in a popup as you type.
|
||||
|
||||
|
||||
|
||||
|
||||
FEATURES *which-key.nvim-which-key-features*
|
||||
|
||||
- for Neovim 0.7 and higher, it uses the `desc` attributes of your mappings as the default label
|
||||
- for Neovim 0.7 and higher, new mappings will be created with a `desc` attribute
|
||||
- opens a popup with suggestions to complete a key binding
|
||||
- works with any setting for |timeoutlen|, including instantly (`timeoutlen=0`)
|
||||
- works correctly with built-in key bindings
|
||||
- works correctly with buffer-local mappings
|
||||
- extensible plugin architecture
|
||||
- built-in plugins:
|
||||
- **marks:** shows your marks when you hit one of the jump keys.
|
||||
- **registers:** shows the contents of your registers
|
||||
- **presets:** built-in key binding help for `motions`, `text-objects`, `operators`, `windows`, `nav`, `z` and `g`
|
||||
- **spelling:** spelling suggestions inside the which-key popup
|
||||
- **Key Binding Help**show available keybindings in a popup as you type.
|
||||
- **Modes**works in normal, insert, visual, operator pending, terminal and command mode.
|
||||
Every mode can be enabled/disabled.
|
||||
- **Customizable Layouts**choose from `classic`, `modern`, and `helix` presets or customize the window.
|
||||
- **Flexible Sorting**sort by `local`, `order`, `group`, `alphanum`, `mod`, `lower`, `icase`, `desc`, or `manual`.
|
||||
- **Formatting**customizable key labels and descriptions
|
||||
- **Icons**integrates with mini.icons <https://github.com/echasnovski/mini.icons> and nvim-web-devicons <https://github.com/nvim-tree/nvim-web-devicons>
|
||||
- **Delay**delay is independent of `timeoutlen`
|
||||
- **Plugins**built-in plugins for marks, registers, presets, and spelling suggestions
|
||||
- **Operators, Motions, Text Objects**help for operators, motions and text objects
|
||||
- **Hydra Mode**keep the popup open until you hit `<esc>`
|
||||
|
||||
|
||||
REQUIREMENTS *which-key.nvim-which-key-requirements*
|
||||
|
||||
- Neovim >= 0.5.0
|
||||
- **Neovim** >= 0.9.4
|
||||
- for proper icons support:
|
||||
- mini.icons <https://github.com/echasnovski/mini.icons> _(optional)_
|
||||
- nvim-web-devicons <https://github.com/nvim-tree/nvim-web-devicons> _(optional)_
|
||||
- a Nerd Font <https://www.nerdfonts.com/> **(optional)**
|
||||
|
||||
|
||||
INSTALLATION *which-key.nvim-which-key-installation*
|
||||
|
||||
Install the plugin with your preferred package manager:
|
||||
Install the plugin with your package manager:
|
||||
|
||||
|
||||
LAZY.NVIM ~
|
||||
@ -55,34 +61,20 @@ LAZY.NVIM ~
|
||||
{
|
||||
"folke/which-key.nvim",
|
||||
event = "VeryLazy",
|
||||
init = function()
|
||||
vim.o.timeout = true
|
||||
vim.o.timeoutlen = 300
|
||||
end,
|
||||
opts = {
|
||||
-- your configuration comes here
|
||||
-- or leave it empty to use the default settings
|
||||
-- refer to the configuration section below
|
||||
}
|
||||
}
|
||||
<
|
||||
|
||||
|
||||
PACKER ~
|
||||
|
||||
>lua
|
||||
-- Lua
|
||||
use {
|
||||
"folke/which-key.nvim",
|
||||
config = function()
|
||||
vim.o.timeout = true
|
||||
vim.o.timeoutlen = 300
|
||||
require("which-key").setup {
|
||||
-- your configuration comes here
|
||||
-- or leave it empty to use the default settings
|
||||
-- refer to the configuration section below
|
||||
}
|
||||
end
|
||||
},
|
||||
keys = {
|
||||
{
|
||||
"<leader>?",
|
||||
function()
|
||||
require("which-key").show({ global = false })
|
||||
end,
|
||||
desc = "Buffer Local Keymaps (which-key)",
|
||||
},
|
||||
},
|
||||
}
|
||||
<
|
||||
|
||||
@ -90,21 +82,47 @@ PACKER ~
|
||||
CONFIGURATION *which-key.nvim-which-key-configuration*
|
||||
|
||||
|
||||
IMPORTANT: the |timeout| when **WhichKey** opens is controlled by the vim
|
||||
setting |timeoutlen|. Please refer to the documentation to properly set it up.
|
||||
Setting it to `0`, will effectively always show **WhichKey** immediately, but a
|
||||
setting of `500` (500ms) is probably more appropriate.
|
||||
[!important] Make sure to run `:checkhealth which-key` if something isn’t
|
||||
working properly
|
||||
**WhichKey** is highly configurable. Expand to see the list of all the default
|
||||
options below.
|
||||
|
||||
don’t create any keymappings yourself to trigger WhichKey. Unlike with
|
||||
_vim-which-key_, we do this fully automatically. Please remove any left-over
|
||||
triggers you might have from using _vim-which-key_.
|
||||
|
||||
You can run `:checkhealth which-key` to see if there’s any conflicting
|
||||
keymaps that will prevent triggering **WhichKey**
|
||||
WhichKey comes with the following defaults:
|
||||
Default Options ~
|
||||
|
||||
>lua
|
||||
{
|
||||
---@class wk.Opts
|
||||
local defaults = {
|
||||
---@type false | "classic" | "modern" | "helix"
|
||||
preset = "classic",
|
||||
-- Delay before showing the popup. Can be a number or a function that returns a number.
|
||||
---@type number | fun(ctx: { keys: string, mode: string, plugin?: string }):number
|
||||
delay = function(ctx)
|
||||
return ctx.plugin and 0 or 200
|
||||
end,
|
||||
---@param mapping wk.Mapping
|
||||
filter = function(mapping)
|
||||
-- example to exclude mappings without a description
|
||||
-- return mapping.desc and mapping.desc ~= ""
|
||||
return true
|
||||
end,
|
||||
--- You can add any mappings here, or use `require('which-key').add()` later
|
||||
---@type wk.Spec
|
||||
spec = {},
|
||||
-- show a warning when issues were detected with your mappings
|
||||
notify = true,
|
||||
-- Which-key automatically sets up triggers for your mappings.
|
||||
-- But you can disable this and setup the triggers manually.
|
||||
-- Check the docs for more info.
|
||||
---@type wk.Spec
|
||||
triggers = {
|
||||
{ "<auto>", mode = "nxsot" },
|
||||
},
|
||||
-- Start hidden and wait for a key to be pressed before showing the popup
|
||||
-- Only used by enabled xo mapping modes.
|
||||
---@param ctx { mode: string, operator: string }
|
||||
defer = function(ctx)
|
||||
return ctx.mode == "V" or ctx.mode == "<C-V>"
|
||||
end,
|
||||
plugins = {
|
||||
marks = true, -- shows a list of your marks on ' and `
|
||||
registers = true, -- shows your registers on " in NORMAL or <C-r> in INSERT mode
|
||||
@ -124,211 +142,267 @@ WhichKey comes with the following defaults:
|
||||
g = true, -- bindings for prefixed with g
|
||||
},
|
||||
},
|
||||
-- add operators that will trigger motion and text object completion
|
||||
-- to enable all native operators, set the preset / operators plugin above
|
||||
operators = { gc = "Comments" },
|
||||
key_labels = {
|
||||
-- override the label used to display some keys. It doesn't effect WK in any other way.
|
||||
-- For example:
|
||||
-- ["<space>"] = "SPC",
|
||||
-- ["<cr>"] = "RET",
|
||||
-- ["<tab>"] = "TAB",
|
||||
---@type wk.Win.opts
|
||||
win = {
|
||||
-- don't allow the popup to overlap with the cursor
|
||||
no_overlap = true,
|
||||
-- width = 1,
|
||||
-- height = { min = 4, max = 25 },
|
||||
-- col = 0,
|
||||
-- row = math.huge,
|
||||
-- border = "none",
|
||||
padding = { 1, 2 }, -- extra window padding [top/bottom, right/left]
|
||||
title = true,
|
||||
title_pos = "center",
|
||||
zindex = 1000,
|
||||
-- Additional vim.wo and vim.bo options
|
||||
bo = {},
|
||||
wo = {
|
||||
-- winblend = 10, -- value between 0-100 0 for fully opaque and 100 for fully transparent
|
||||
},
|
||||
},
|
||||
motions = {
|
||||
count = true,
|
||||
layout = {
|
||||
width = { min = 20 }, -- min and max width of the columns
|
||||
spacing = 3, -- spacing between columns
|
||||
},
|
||||
keys = {
|
||||
scroll_down = "<c-d>", -- binding to scroll down inside the popup
|
||||
scroll_up = "<c-u>", -- binding to scroll up inside the popup
|
||||
},
|
||||
---@type (string|wk.Sorter)[]
|
||||
--- Mappings are sorted using configured sorters and natural sort of the keys
|
||||
--- Available sorters:
|
||||
--- * local: buffer-local mappings first
|
||||
--- * order: order of the items (Used by plugins like marks / registers)
|
||||
--- * group: groups last
|
||||
--- * alphanum: alpha-numerical first
|
||||
--- * mod: special modifier keys last
|
||||
--- * manual: the order the mappings were added
|
||||
--- * case: lower-case first
|
||||
sort = { "local", "order", "group", "alphanum", "mod" },
|
||||
---@type number|fun(node: wk.Node):boolean?
|
||||
expand = 0, -- expand groups when <= n mappings
|
||||
-- expand = function(node)
|
||||
-- return not node.desc -- expand all nodes without a description
|
||||
-- end,
|
||||
-- Functions/Lua Patterns for formatting the labels
|
||||
---@type table<string, ({[1]:string, [2]:string}|fun(str:string):string)[]>
|
||||
replace = {
|
||||
key = {
|
||||
function(key)
|
||||
return require("which-key.view").format(key)
|
||||
end,
|
||||
-- { "<Space>", "SPC" },
|
||||
},
|
||||
desc = {
|
||||
{ "<Plug>%(?(.*)%)?", "%1" },
|
||||
{ "^%+", "" },
|
||||
{ "<[cC]md>", "" },
|
||||
{ "<[cC][rR]>", "" },
|
||||
{ "<[sS]ilent>", "" },
|
||||
{ "^lua%s+", "" },
|
||||
{ "^call%s+", "" },
|
||||
{ "^:%s*", "" },
|
||||
},
|
||||
},
|
||||
icons = {
|
||||
breadcrumb = "»", -- symbol used in the command line area that shows your active key combo
|
||||
separator = "➜", -- symbol used between a key and it's label
|
||||
group = "+", -- symbol prepended to a group
|
||||
},
|
||||
popup_mappings = {
|
||||
scroll_down = "<c-d>", -- binding to scroll down inside the popup
|
||||
scroll_up = "<c-u>", -- binding to scroll up inside the popup
|
||||
},
|
||||
window = {
|
||||
border = "none", -- none, single, double, shadow
|
||||
position = "bottom", -- bottom, top
|
||||
margin = { 1, 0, 1, 0 }, -- extra window margin [top, right, bottom, left]. When between 0 and 1, will be treated as a percentage of the screen size.
|
||||
padding = { 1, 2, 1, 2 }, -- extra window padding [top, right, bottom, left]
|
||||
winblend = 0, -- value between 0-100 0 for fully opaque and 100 for fully transparent
|
||||
zindex = 1000, -- positive value to position WhichKey above other floating windows.
|
||||
},
|
||||
layout = {
|
||||
height = { min = 4, max = 25 }, -- min and max height of the columns
|
||||
width = { min = 20, max = 50 }, -- min and max width of the columns
|
||||
spacing = 3, -- spacing between columns
|
||||
align = "left", -- align columns left, center or right
|
||||
},
|
||||
ignore_missing = false, -- enable this to hide mappings for which you didn't specify a label
|
||||
hidden = { "<silent>", "<cmd>", "<Cmd>", "<CR>", "^:", "^ ", "^call ", "^lua " }, -- hide mapping boilerplate
|
||||
show_help = true, -- show a help message in the command line for using WhichKey
|
||||
show_keys = true, -- show the currently pressed key and its label as a message in the command line
|
||||
triggers = "auto", -- automatically setup triggers
|
||||
-- triggers = {"<leader>"} -- or specify a list manually
|
||||
-- list of triggers, where WhichKey should not wait for timeoutlen and show immediately
|
||||
triggers_nowait = {
|
||||
-- marks
|
||||
"`",
|
||||
"'",
|
||||
"g`",
|
||||
"g'",
|
||||
-- registers
|
||||
'"',
|
||||
"<c-r>",
|
||||
-- spelling
|
||||
"z=",
|
||||
},
|
||||
triggers_blacklist = {
|
||||
-- list of mode / prefixes that should never be hooked by WhichKey
|
||||
-- this is mostly relevant for keymaps that start with a native binding
|
||||
i = { "j", "k" },
|
||||
v = { "j", "k" },
|
||||
},
|
||||
-- disable the WhichKey popup for certain buf types and file types.
|
||||
-- Disabled by default for Telescope
|
||||
disable = {
|
||||
buftypes = {},
|
||||
filetypes = {},
|
||||
},
|
||||
}
|
||||
<
|
||||
|
||||
|
||||
SETUP *which-key.nvim-which-key-setup*
|
||||
|
||||
With the default settings, **WhichKey** will work out of the box for most
|
||||
builtin keybindings, but the real power comes from documenting and organizing
|
||||
your own keybindings.
|
||||
|
||||
To document and/or setup your own mappings, you need to call the `register`
|
||||
method
|
||||
|
||||
>lua
|
||||
local wk = require("which-key")
|
||||
wk.register(mappings, opts)
|
||||
<
|
||||
|
||||
Default options for `opts`
|
||||
|
||||
>lua
|
||||
{
|
||||
mode = "n", -- NORMAL mode
|
||||
-- prefix: use "<leader>f" for example for mapping everything related to finding files
|
||||
-- the prefix is prepended to every mapping part of `mappings`
|
||||
prefix = "",
|
||||
buffer = nil, -- Global mappings. Specify a buffer number for buffer local mappings
|
||||
silent = true, -- use `silent` when creating keymaps
|
||||
noremap = true, -- use `noremap` when creating keymaps
|
||||
nowait = false, -- use `nowait` when creating keymaps
|
||||
expr = false, -- use `expr` when creating keymaps
|
||||
}
|
||||
<
|
||||
|
||||
|
||||
When you specify a command in your mapping that starts with `<Plug>`, then we
|
||||
automatically set `noremap=false`, since you always want recursive keybindings
|
||||
in this case
|
||||
|
||||
MAPPINGS ~
|
||||
|
||||
|
||||
for **Neovim 0.7** and higher, which key will use the `desc` attribute of
|
||||
existing mappings as the default label
|
||||
Group names use the special `name` key in the tables. There’s multiple ways
|
||||
to define the mappings. `wk.register` can be called multiple times from
|
||||
anywhere in your config files.
|
||||
|
||||
>lua
|
||||
local wk = require("which-key")
|
||||
-- As an example, we will create the following mappings:
|
||||
-- * <leader>ff find files
|
||||
-- * <leader>fr show recent files
|
||||
-- * <leader>fb Foobar
|
||||
-- we'll document:
|
||||
-- * <leader>fn new file
|
||||
-- * <leader>fe edit file
|
||||
-- and hide <leader>1
|
||||
|
||||
wk.register({
|
||||
f = {
|
||||
name = "file", -- optional group name
|
||||
f = { "<cmd>Telescope find_files<cr>", "Find File" }, -- create a binding with label
|
||||
r = { "<cmd>Telescope oldfiles<cr>", "Open Recent File", noremap=false, buffer = 123 }, -- additional options for creating the keymap
|
||||
n = { "New File" }, -- just a label. don't create any mapping
|
||||
e = "Edit File", -- same as above
|
||||
["1"] = "which_key_ignore", -- special label to hide it in the popup
|
||||
b = { function() print("bar") end, "Foobar" } -- you can also pass functions!
|
||||
},
|
||||
}, { prefix = "<leader>" })
|
||||
<
|
||||
|
||||
Click to see more examples ~
|
||||
|
||||
>lua
|
||||
-- all of the mappings below are equivalent
|
||||
|
||||
-- method 2
|
||||
wk.register({
|
||||
["<leader>"] = {
|
||||
f = {
|
||||
name = "+file",
|
||||
f = { "<cmd>Telescope find_files<cr>", "Find File" },
|
||||
r = { "<cmd>Telescope oldfiles<cr>", "Open Recent File" },
|
||||
n = { "<cmd>enew<cr>", "New File" },
|
||||
ellipsis = "…",
|
||||
-- set to false to disable all mapping icons,
|
||||
-- both those explicitely added in a mapping
|
||||
-- and those from rules
|
||||
mappings = true,
|
||||
--- See `lua/which-key/icons.lua` for more details
|
||||
--- Set to `false` to disable keymap icons from rules
|
||||
---@type wk.IconRule[]|false
|
||||
rules = {},
|
||||
-- use the highlights from mini.icons
|
||||
-- When `false`, it will use `WhichKeyIcon` instead
|
||||
colors = true,
|
||||
-- used by key format
|
||||
keys = {
|
||||
Up = " ",
|
||||
Down = " ",
|
||||
Left = " ",
|
||||
Right = " ",
|
||||
C = " ",
|
||||
M = " ",
|
||||
D = " ",
|
||||
S = " ",
|
||||
CR = " ",
|
||||
Esc = " ",
|
||||
ScrollWheelDown = " ",
|
||||
ScrollWheelUp = " ",
|
||||
NL = " ",
|
||||
BS = "",
|
||||
Space = " ",
|
||||
Tab = " ",
|
||||
F1 = "",
|
||||
F2 = "",
|
||||
F3 = "",
|
||||
F4 = "",
|
||||
F5 = "",
|
||||
F6 = "",
|
||||
F7 = "",
|
||||
F8 = "",
|
||||
F9 = "",
|
||||
F10 = "",
|
||||
F11 = "",
|
||||
F12 = "",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
-- method 3
|
||||
wk.register({
|
||||
["<leader>f"] = {
|
||||
name = "+file",
|
||||
f = { "<cmd>Telescope find_files<cr>", "Find File" },
|
||||
r = { "<cmd>Telescope oldfiles<cr>", "Open Recent File" },
|
||||
n = { "<cmd>enew<cr>", "New File" },
|
||||
show_help = true, -- show a help message in the command line for using WhichKey
|
||||
show_keys = true, -- show the currently pressed key and its label as a message in the command line
|
||||
-- disable WhichKey for certain buf types and file types.
|
||||
disable = {
|
||||
ft = {},
|
||||
bt = {},
|
||||
},
|
||||
})
|
||||
|
||||
-- method 4
|
||||
wk.register({
|
||||
["<leader>f"] = { name = "+file" },
|
||||
["<leader>ff"] = { "<cmd>Telescope find_files<cr>", "Find File" },
|
||||
["<leader>fr"] = { "<cmd>Telescope oldfiles<cr>", "Open Recent File" },
|
||||
["<leader>fn"] = { "<cmd>enew<cr>", "New File" },
|
||||
})
|
||||
debug = false, -- enable wk.log in the current directory
|
||||
}
|
||||
<
|
||||
|
||||
**Tips:** The default label is `keymap.desc` or `keymap.rhs` or `""`,
|
||||
|nvim_set_keymap()| to get more details about `desc` and `rhs`.
|
||||
|
||||
MAPPINGS *which-key.nvim-which-key-mappings*
|
||||
|
||||
**WhichKey** automatically gets the descriptions of your keymaps from the
|
||||
`desc` attribute of the keymap. So for most use-cases, you don’t need to do
|
||||
anything else.
|
||||
|
||||
However, the **mapping spec** is still useful to configure group descriptions
|
||||
and mappings that don’t really exist as a regular keymap.
|
||||
|
||||
|
||||
OPERATORS, MOTIONS AND TEXT OBJECTS ~
|
||||
[!WARNING] The **mappings spec** changed in `v3`, so make sure to only use the
|
||||
new `add` method if you updated your existing mappings.
|
||||
Mappings can be added as part of the config `opts.spec`, or can be added later
|
||||
using `require("which-key").add()`. `wk.add()` can be called multiple times
|
||||
from anywhere in your config files.
|
||||
|
||||
**WhichKey** provides help to work with operators, motions and text objects.
|
||||
A mapping has the following attributes:
|
||||
|
||||
- **[1]**(`string`) lhs **(required)**
|
||||
- **[2]**(`string|fun()`) rhs **(optional)**when present, it will create the mapping
|
||||
- **desc**(`string|fun():string`) description **(required for non-groups)**
|
||||
- **group**(`string|fun():string`) group name **(optional)**
|
||||
- **mode**(`string|string[]`) mode **(optional, defaults to "n")**
|
||||
- **cond**(`boolean|fun():boolean`) condition to enable the mapping **(optional)**
|
||||
- **hidden**(`boolean`) hide the mapping **(optional)**
|
||||
- **icon**(`string|wk.Icon|fun():(wk.Icon|string)`) icon spec **(optional)**
|
||||
- **proxy**(`string`) proxy to another mapping **(optional)**
|
||||
- **expand**(`fun():wk.Spec`) nested mappings **(optional)**
|
||||
- any other option valid for `vim.keymap.set`. These are only used for creating mappings.
|
||||
|
||||
`[count]operator[count][text-object]`
|
||||
- operators can be configured with the `operators` option
|
||||
- set `plugins.presets.operators` to `true` to automatically configure vim built-in operators
|
||||
- set this to `false`, to only include the list you configured in the `operators` option.
|
||||
- see here <https://github.com/folke/which-key.nvim/blob/main/lua/which-key/plugins/presets/init.lua#L5> for the full list part of the preset
|
||||
- text objects are automatically retrieved from **operator pending** key maps (`omap`)
|
||||
- set `plugins.presets.text_objects` to `true` to configure built-in text objects
|
||||
- see here <https://github.com/folke/which-key.nvim/blob/main/lua/which-key/plugins/presets/init.lua#L43>
|
||||
- motions are part of the preset `plugins.presets.motions` setting
|
||||
- see here <https://github.com/folke/which-key.nvim/blob/main/lua/which-key/plugins/presets/init.lua#L20>
|
||||
When `desc`, `group`, or `icon` are functions, they are evaluated every time
|
||||
the popup is shown.
|
||||
|
||||
How to disable some operators? (like v) ~
|
||||
The `expand` property allows to create dynamic mappings. Only functions as
|
||||
`rhs` are supported for dynamic mappings. Two examples are included in
|
||||
`which-key.extras`
|
||||
|
||||
- `require("which-key.extras").expand.buf`creates numerical key to buffer mappings
|
||||
- `require("which-key.extras").expand.win`creates numerical key to window mappings
|
||||
|
||||
>lua
|
||||
-- make sure to run this code before calling setup()
|
||||
-- refer to the full lists at https://github.com/folke/which-key.nvim/blob/main/lua/which-key/plugins/presets/init.lua
|
||||
local presets = require("which-key.plugins.presets")
|
||||
presets.operators["v"] = nil
|
||||
local wk = require("which-key")
|
||||
wk.add({
|
||||
{ "<leader>f", group = "file" }, -- group
|
||||
{ "<leader>ff", "<cmd>Telescope find_files<cr>", desc = "Find File", mode = "n" },
|
||||
{ "<leader>fb", function() print("hello") end, desc = "Foobar" },
|
||||
{ "<leader>fn", desc = "New File" },
|
||||
{ "<leader>f1", hidden = true }, -- hide this keymap
|
||||
{ "<leader>w", proxy = "<c-w>", group = "windows" }, -- proxy to window mappings
|
||||
{ "<leader>b", group = "buffers", expand = function()
|
||||
return require("which-key.extras").expand.buf()
|
||||
end
|
||||
},
|
||||
{
|
||||
-- Nested mappings are allowed and can be added in any order
|
||||
-- Most attributes can be inherited or overridden on any level
|
||||
-- There's no limit to the depth of nesting
|
||||
mode = { "n", "v" }, -- NORMAL and VISUAL mode
|
||||
{ "<leader>q", "<cmd>q<cr>", desc = "Quit" }, -- no need to specify mode since it's inherited
|
||||
{ "<leader>w", "<cmd>w<cr>", desc = "Write" },
|
||||
}
|
||||
})
|
||||
<
|
||||
|
||||
|
||||
TRIGGERS *which-key.nvim-which-key-triggers*
|
||||
|
||||
There’s two ways that **which-key** can be triggered:
|
||||
|
||||
- by a trigger keymap
|
||||
- by a `ModeChanged` event for visual and operator pending mode
|
||||
|
||||
Both can be configured using `opts.triggers` and `opts.defer`.
|
||||
|
||||
By default `opts.triggers` includes `{ "<auto>", mode = "nixsotc" }`, which
|
||||
will setup keymap triggers for every mode automatically and will trigger during
|
||||
`ModeChanged`.
|
||||
|
||||
|
||||
[!NOTE] Auto triggers will never be created for existing keymaps. That includes
|
||||
every valid single key Neovim builtin mapping. If you want to trigger on a
|
||||
builtin keymap, you have to add it manually.
|
||||
>lua
|
||||
triggers = {
|
||||
{ "<auto>", mode = "nixsotc" },
|
||||
{ "a", mode = { "n", "v" } },
|
||||
}
|
||||
<
|
||||
|
||||
[!TIP] To manually setup triggers, you can set `opts.triggers` to:
|
||||
>lua
|
||||
triggers = {
|
||||
{ "<leader>", mode = { "n", "v" } },
|
||||
}
|
||||
<
|
||||
For `ModeChanged` triggers, you can configure the `opts.defer` option. When it
|
||||
returns `true`, the popup will be shown only after an additional key is
|
||||
pressed. So `yaf`, would show which-key after pressing `ya`, but not after `y`.
|
||||
|
||||
|
||||
[!TIP] Defer some operators:
|
||||
>lua
|
||||
---@param ctx { mode: string, operator: string }
|
||||
defer = function(ctx)
|
||||
if vim.list_contains({ "d", "y" }, ctx.operator) then
|
||||
return true
|
||||
end
|
||||
return vim.list_contains({ "<C-V>", "V" }, ctx.mode)
|
||||
end,
|
||||
<
|
||||
|
||||
ICONS *which-key.nvim-which-key-icons*
|
||||
|
||||
|
||||
[!note] For full support, you need to install either mini.icons
|
||||
<https://github.com/echasnovski/mini.icons> or nvim-web-devicons
|
||||
<https://github.com/nvim-tree/nvim-web-devicons>
|
||||
There’s multiple ways to set icons for your keymaps:
|
||||
|
||||
- if you use lazy.nvim, then some icons will be autodetected for keymaps belonging to certain plugins.
|
||||
- custom rules to decide what icon to use
|
||||
- in your mapping spec, you can specify what icon to use at any level, so at the node for `<leader>g` for example, to apply to all git keymaps.
|
||||
|
||||
The `icon` attribute of a mapping can be a `string`, which will be used as the
|
||||
actual icon, or an `wk.Icon` object, which can have the following attributes:
|
||||
|
||||
- `icon` (`string`): the icon to use **(optional)**
|
||||
- `hl` (`string`): the highlight group to use for the icon **(optional)**
|
||||
- `color` (`string`): the color to use for the icon **(optional)**
|
||||
valid colors are: `azure`, `blue`, `cyan`, `green`, `grey`, `orange`, `purple`, `red`, `yellow`
|
||||
- `cat` (`string`): the category of the icon **(optional)**
|
||||
valid categories are: `file`, `filetype`, `extension`
|
||||
- `name` (`string`): the name of the icon in the specified category **(optional)**
|
||||
|
||||
|
||||
[!TIP] If you’d rather not use icons, you can disable them by setting
|
||||
`opts.icons.mappings` to `false`.
|
||||
|
||||
USAGE *which-key.nvim-which-key-usage*
|
||||
|
||||
When the **WhichKey** popup is open, you can use the following key bindings
|
||||
@ -340,18 +414,17 @@ When the **WhichKey** popup is open, you can use the following key bindings
|
||||
- `<c-d>` scroll down
|
||||
- `<c-u>` scroll up
|
||||
|
||||
Apart from the automatic opening, you can also manually open **WhichKey** for a
|
||||
certain `prefix`
|
||||
|
||||
HYDRA MODE *which-key.nvim-which-key-hydra-mode*
|
||||
|
||||
don’t create any keymappings yourself to trigger WhichKey. Unlike with
|
||||
_vim-which-key_, we do this fully automatically. Please remove any left-over
|
||||
triggers you might have from using _vim-which-key_.
|
||||
>vim
|
||||
:WhichKey " show all mappings
|
||||
:WhichKey <leader> " show all <leader> mappings
|
||||
:WhichKey <leader> v " show all <leader> mappings for VISUAL mode
|
||||
:WhichKey '' v " show ALL mappings for VISUAL mode
|
||||
Hydra mode is a special mode that keeps the popup open until you hit `<esc>`.
|
||||
|
||||
>lua
|
||||
-- Show hydra mode for changing windows
|
||||
require("which-key").show({
|
||||
keys = "<c-w>",
|
||||
loop = true, -- this will keep the popup open until you hit <esc>
|
||||
})
|
||||
<
|
||||
|
||||
|
||||
@ -360,6 +433,12 @@ PLUGINS *which-key.nvim-which-key-plugins*
|
||||
Four built-in plugins are included with **WhichKey**.
|
||||
|
||||
|
||||
PRESETS ~
|
||||
|
||||
Built-in key binding help for `motions`, `text-objects`, `operators`,
|
||||
`windows`, `nav`, `z` and `g` and more.
|
||||
|
||||
|
||||
MARKS ~
|
||||
|
||||
Shows a list of your buffer local and global marks when you hit ` or ’
|
||||
@ -371,12 +450,6 @@ Shows a list of your buffer local and global registers when you hit ” in
|
||||
_NORMAL_ mode, or `<c-r>` in _INSERT_ mode.
|
||||
|
||||
|
||||
PRESETS ~
|
||||
|
||||
Built-in key binding help for `motions`, `text-objects`, `operators`,
|
||||
`windows`, `nav`, `z` and `g`
|
||||
|
||||
|
||||
SPELLING ~
|
||||
|
||||
When enabled, this plugin hooks into `z=` and replaces the full-screen spelling
|
||||
@ -388,32 +461,60 @@ COLORS *which-key.nvim-which-key-colors*
|
||||
The table below shows all the highlight groups defined for **WhichKey** with
|
||||
their default link.
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
Highlight Group Defaults to Description
|
||||
------------------- ------------- -----------------------------------------
|
||||
WhichKey Function the key
|
||||
-----------------------------------------------------------------------
|
||||
Highlight Group Default Group Description
|
||||
----------------------- ----------------------- -----------------------
|
||||
WhichKey Function
|
||||
|
||||
WhichKeyGroup Keyword a group
|
||||
WhichKeyBorder FloatBorder Border of the which-key
|
||||
window
|
||||
|
||||
WhichKeySeparator DiffAdd the separator between the key and its
|
||||
label
|
||||
WhichKeyDesc Identifier description
|
||||
|
||||
WhichKeyDesc Identifier the label of the key
|
||||
WhichKeyGroup Keyword group name
|
||||
|
||||
WhichKeyFloat NormalFloat Normal in the popup window
|
||||
WhichKeyIcon @markup.link icons
|
||||
|
||||
WhichKeyBorder FloatBorder Normal in the popup window
|
||||
WhichKeyIconAzure Function
|
||||
|
||||
WhichKeyValue Comment used by plugins that provide values
|
||||
---------------------------------------------------------------------------
|
||||
WhichKeyIconBlue DiagnosticInfo
|
||||
|
||||
WhichKeyIconCyan DiagnosticHint
|
||||
|
||||
WhichKeyIconGreen DiagnosticOk
|
||||
|
||||
WhichKeyIconGrey Normal
|
||||
|
||||
WhichKeyIconOrange DiagnosticWarn
|
||||
|
||||
WhichKeyIconPurple Constant
|
||||
|
||||
WhichKeyIconRed DiagnosticError
|
||||
|
||||
WhichKeyIconYellow DiagnosticWarn
|
||||
|
||||
WhichKeyNormal NormalFloat Normal in th which-key
|
||||
window
|
||||
|
||||
WhichKeySeparator Comment the separator between
|
||||
the key and its
|
||||
description
|
||||
|
||||
WhichKeyTitle FloatTitle Title of the which-key
|
||||
window
|
||||
|
||||
WhichKeyValue Comment values by plugins (like
|
||||
marks, registers, etc)
|
||||
-----------------------------------------------------------------------
|
||||
==============================================================================
|
||||
2. Links *which-key.nvim-links*
|
||||
|
||||
1. *image*: https://user-images.githubusercontent.com/292349/116439438-669f8d00-a804-11eb-9b5b-c7122bd9acac.png
|
||||
2. *image*: https://user-images.githubusercontent.com/292349/116439573-8f278700-a804-11eb-80ca-bb9263e6d937.png
|
||||
3. *image*: https://user-images.githubusercontent.com/292349/116439609-98b0ef00-a804-11eb-9385-97c7d5ff4113.png
|
||||
4. *image*: https://user-images.githubusercontent.com/292349/116439871-df9ee480-a804-11eb-9529-800e167db65c.png
|
||||
5. *image*: https://user-images.githubusercontent.com/292349/118102022-1c361880-b38d-11eb-8e82-79ad266d9bb8.png
|
||||
1. *image*: https://github.com/user-attachments/assets/89277334-dcdc-4b0f-9fd4-02f27012f589
|
||||
2. *image*: https://github.com/user-attachments/assets/f8d71a75-312e-4a42-add8-d153493b2633
|
||||
3. *image*: https://github.com/user-attachments/assets/e4400a1d-7e71-4439-b6ff-6cbc40647a6f
|
||||
4. *image*: https://github.com/user-attachments/assets/43fb0874-7f79-4521-aee9-03e2b0841758
|
||||
5. *image*: https://github.com/user-attachments/assets/d8077dcb-56fb-47b0-ad9e-1aba5db16950
|
||||
6. *image*: https://github.com/user-attachments/assets/102c7963-329a-40b9-b0a8-72c8656318b7
|
||||
|
||||
Generated by panvimdoc <https://github.com/kdheepak/panvimdoc>
|
||||
|
||||
|
||||
@ -1,19 +1,56 @@
|
||||
local M = {}
|
||||
|
||||
local links = {
|
||||
[""] = "Function",
|
||||
Separator = "Comment",
|
||||
Group = "Keyword",
|
||||
Desc = "Identifier",
|
||||
Float = "NormalFloat",
|
||||
Border = "FloatBorder",
|
||||
Value = "Comment",
|
||||
M.colors = {
|
||||
[""] = "Function", -- the key
|
||||
Separator = "Comment", -- the separator between the key and its description
|
||||
Group = "Keyword", -- group name
|
||||
Desc = "Identifier", -- description
|
||||
Normal = "NormalFloat", -- Normal in th which-key window
|
||||
Title = "FloatTitle", -- Title of the which-key window
|
||||
Border = "FloatBorder", -- Border of the which-key window
|
||||
Value = "Comment", -- values by plugins (like marks, registers, etc)
|
||||
Icon = "@markup.link", -- icons
|
||||
IconAzure = "Function",
|
||||
IconBlue = "DiagnosticInfo",
|
||||
IconCyan = "DiagnosticHint",
|
||||
IconGreen = "DiagnosticOk",
|
||||
IconGrey = "Normal",
|
||||
IconOrange = "DiagnosticWarn",
|
||||
IconPurple = "Constant",
|
||||
IconRed = "DiagnosticError",
|
||||
IconYellow = "DiagnosticWarn",
|
||||
}
|
||||
|
||||
function M.setup()
|
||||
for k, v in pairs(links) do
|
||||
for k, v in pairs(M.colors) do
|
||||
vim.api.nvim_set_hl(0, "WhichKey" .. k, { link = v, default = true })
|
||||
end
|
||||
M.fix_colors()
|
||||
vim.api.nvim_create_autocmd("ColorScheme", {
|
||||
group = vim.api.nvim_create_augroup("wk-colors", { clear = true }),
|
||||
callback = M.fix_colors,
|
||||
})
|
||||
end
|
||||
|
||||
function M.fix_colors()
|
||||
for k in pairs(M.colors) do
|
||||
if k:find("^Icon") then
|
||||
local color = k:gsub("^Icon", "")
|
||||
local wk_hl_group = "WhichKeyIcon" .. color
|
||||
local mini_hl_group = "MiniIcons" .. color
|
||||
local wk_hl = vim.api.nvim_get_hl(0, {
|
||||
name = wk_hl_group,
|
||||
link = true,
|
||||
})
|
||||
local mini_hl = vim.api.nvim_get_hl(0, {
|
||||
name = mini_hl_group,
|
||||
link = true,
|
||||
})
|
||||
if wk_hl.default and not vim.tbl_isempty(mini_hl) then
|
||||
vim.api.nvim_set_hl(0, wk_hl_group, { link = mini_hl_group })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@ -1,9 +1,42 @@
|
||||
---@class wk.Config: wk.Opts
|
||||
---@field triggers {mappings: wk.Mapping[], modes: table<string,boolean>}
|
||||
local M = {}
|
||||
|
||||
M.namespace = vim.api.nvim_create_namespace("WhichKey")
|
||||
M.version = "3.13.2" -- x-release-please-version
|
||||
|
||||
---@class Options
|
||||
---@class wk.Opts
|
||||
local defaults = {
|
||||
---@type false | "classic" | "modern" | "helix"
|
||||
preset = "classic",
|
||||
-- Delay before showing the popup. Can be a number or a function that returns a number.
|
||||
---@type number | fun(ctx: { keys: string, mode: string, plugin?: string }):number
|
||||
delay = function(ctx)
|
||||
return ctx.plugin and 0 or 200
|
||||
end,
|
||||
---@param mapping wk.Mapping
|
||||
filter = function(mapping)
|
||||
-- example to exclude mappings without a description
|
||||
-- return mapping.desc and mapping.desc ~= ""
|
||||
return true
|
||||
end,
|
||||
--- You can add any mappings here, or use `require('which-key').add()` later
|
||||
---@type wk.Spec
|
||||
spec = {},
|
||||
-- show a warning when issues were detected with your mappings
|
||||
notify = true,
|
||||
-- Which-key automatically sets up triggers for your mappings.
|
||||
-- But you can disable this and setup the triggers manually.
|
||||
-- Check the docs for more info.
|
||||
---@type wk.Spec
|
||||
triggers = {
|
||||
{ "<auto>", mode = "nxsot" },
|
||||
},
|
||||
-- Start hidden and wait for a key to be pressed before showing the popup
|
||||
-- Only used by enabled xo mapping modes.
|
||||
---@param ctx { mode: string, operator: string }
|
||||
defer = function(ctx)
|
||||
return ctx.mode == "V" or ctx.mode == "<C-V>"
|
||||
end,
|
||||
plugins = {
|
||||
marks = true, -- shows a list of your marks on ' and `
|
||||
registers = true, -- shows your registers on " in NORMAL or <C-r> in INSERT mode
|
||||
@ -23,86 +56,288 @@ local defaults = {
|
||||
g = true, -- bindings for prefixed with g
|
||||
},
|
||||
},
|
||||
-- add operators that will trigger motion and text object completion
|
||||
-- to enable all native operators, set the preset / operators plugin above
|
||||
operators = { gc = "Comments" },
|
||||
key_labels = {
|
||||
-- override the label used to display some keys. It doesn't effect WK in any other way.
|
||||
-- For example:
|
||||
-- ["<space>"] = "SPC",
|
||||
-- ["<cr>"] = "RET",
|
||||
-- ["<tab>"] = "TAB",
|
||||
---@type wk.Win.opts
|
||||
win = {
|
||||
-- don't allow the popup to overlap with the cursor
|
||||
no_overlap = true,
|
||||
-- width = 1,
|
||||
-- height = { min = 4, max = 25 },
|
||||
-- col = 0,
|
||||
-- row = math.huge,
|
||||
-- border = "none",
|
||||
padding = { 1, 2 }, -- extra window padding [top/bottom, right/left]
|
||||
title = true,
|
||||
title_pos = "center",
|
||||
zindex = 1000,
|
||||
-- Additional vim.wo and vim.bo options
|
||||
bo = {},
|
||||
wo = {
|
||||
-- winblend = 10, -- value between 0-100 0 for fully opaque and 100 for fully transparent
|
||||
},
|
||||
},
|
||||
motions = {
|
||||
count = true,
|
||||
layout = {
|
||||
width = { min = 20 }, -- min and max width of the columns
|
||||
spacing = 3, -- spacing between columns
|
||||
},
|
||||
keys = {
|
||||
scroll_down = "<c-d>", -- binding to scroll down inside the popup
|
||||
scroll_up = "<c-u>", -- binding to scroll up inside the popup
|
||||
},
|
||||
---@type (string|wk.Sorter)[]
|
||||
--- Mappings are sorted using configured sorters and natural sort of the keys
|
||||
--- Available sorters:
|
||||
--- * local: buffer-local mappings first
|
||||
--- * order: order of the items (Used by plugins like marks / registers)
|
||||
--- * group: groups last
|
||||
--- * alphanum: alpha-numerical first
|
||||
--- * mod: special modifier keys last
|
||||
--- * manual: the order the mappings were added
|
||||
--- * case: lower-case first
|
||||
sort = { "local", "order", "group", "alphanum", "mod" },
|
||||
---@type number|fun(node: wk.Node):boolean?
|
||||
expand = 0, -- expand groups when <= n mappings
|
||||
-- expand = function(node)
|
||||
-- return not node.desc -- expand all nodes without a description
|
||||
-- end,
|
||||
-- Functions/Lua Patterns for formatting the labels
|
||||
---@type table<string, ({[1]:string, [2]:string}|fun(str:string):string)[]>
|
||||
replace = {
|
||||
key = {
|
||||
function(key)
|
||||
return require("which-key.view").format(key)
|
||||
end,
|
||||
-- { "<Space>", "SPC" },
|
||||
},
|
||||
desc = {
|
||||
{ "<Plug>%(?(.*)%)?", "%1" },
|
||||
{ "^%+", "" },
|
||||
{ "<[cC]md>", "" },
|
||||
{ "<[cC][rR]>", "" },
|
||||
{ "<[sS]ilent>", "" },
|
||||
{ "^lua%s+", "" },
|
||||
{ "^call%s+", "" },
|
||||
{ "^:%s*", "" },
|
||||
},
|
||||
},
|
||||
icons = {
|
||||
breadcrumb = "»", -- symbol used in the command line area that shows your active key combo
|
||||
separator = "➜", -- symbol used between a key and it's label
|
||||
group = "+", -- symbol prepended to a group
|
||||
ellipsis = "…",
|
||||
-- set to false to disable all mapping icons,
|
||||
-- both those explicitely added in a mapping
|
||||
-- and those from rules
|
||||
mappings = true,
|
||||
--- See `lua/which-key/icons.lua` for more details
|
||||
--- Set to `false` to disable keymap icons from rules
|
||||
---@type wk.IconRule[]|false
|
||||
rules = {},
|
||||
-- use the highlights from mini.icons
|
||||
-- When `false`, it will use `WhichKeyIcon` instead
|
||||
colors = true,
|
||||
-- used by key format
|
||||
keys = {
|
||||
Up = " ",
|
||||
Down = " ",
|
||||
Left = " ",
|
||||
Right = " ",
|
||||
C = " ",
|
||||
M = " ",
|
||||
D = " ",
|
||||
S = " ",
|
||||
CR = " ",
|
||||
Esc = " ",
|
||||
ScrollWheelDown = " ",
|
||||
ScrollWheelUp = " ",
|
||||
NL = " ",
|
||||
BS = "",
|
||||
Space = " ",
|
||||
Tab = " ",
|
||||
F1 = "",
|
||||
F2 = "",
|
||||
F3 = "",
|
||||
F4 = "",
|
||||
F5 = "",
|
||||
F6 = "",
|
||||
F7 = "",
|
||||
F8 = "",
|
||||
F9 = "",
|
||||
F10 = "",
|
||||
F11 = "",
|
||||
F12 = "",
|
||||
},
|
||||
},
|
||||
popup_mappings = {
|
||||
scroll_down = "<c-d>", -- binding to scroll down inside the popup
|
||||
scroll_up = "<c-u>", -- binding to scroll up inside the popup
|
||||
},
|
||||
window = {
|
||||
border = "none", -- none, single, double, shadow
|
||||
position = "bottom", -- bottom, top
|
||||
margin = { 1, 0, 1, 0 }, -- extra window margin [top, right, bottom, left]. When between 0 and 1, will be treated as a percentage of the screen size.
|
||||
padding = { 1, 2, 1, 2 }, -- extra window padding [top, right, bottom, left]
|
||||
winblend = 0, -- value between 0-100 0 for fully opaque and 100 for fully transparent
|
||||
zindex = 1000, -- positive value to position WhichKey above other floating windows.
|
||||
},
|
||||
layout = {
|
||||
height = { min = 4, max = 25 }, -- min and max height of the columns
|
||||
width = { min = 20, max = 50 }, -- min and max width of the columns
|
||||
spacing = 3, -- spacing between columns
|
||||
align = "left", -- align columns left, center or right
|
||||
},
|
||||
ignore_missing = false, -- enable this to hide mappings for which you didn't specify a label
|
||||
hidden = { "<silent>", "<cmd>", "<Cmd>", "<CR>", "^:", "^ ", "^call ", "^lua " }, -- hide mapping boilerplate
|
||||
show_help = true, -- show a help message in the command line for using WhichKey
|
||||
show_keys = true, -- show the currently pressed key and its label as a message in the command line
|
||||
triggers = "auto", -- automatically setup triggers
|
||||
-- triggers = {"<leader>"} -- or specify a list manually
|
||||
-- list of triggers, where WhichKey should not wait for timeoutlen and show immediately
|
||||
triggers_nowait = {
|
||||
-- marks
|
||||
"`",
|
||||
"'",
|
||||
"g`",
|
||||
"g'",
|
||||
-- registers
|
||||
'"',
|
||||
"<c-r>",
|
||||
-- spelling
|
||||
"z=",
|
||||
},
|
||||
triggers_blacklist = {
|
||||
-- list of mode / prefixes that should never be hooked by WhichKey
|
||||
-- this is mostly relevant for keymaps that start with a native binding
|
||||
i = { "j", "k" },
|
||||
v = { "j", "k" },
|
||||
},
|
||||
-- disable the WhichKey popup for certain buf types and file types.
|
||||
-- Disabled by default for Telescope
|
||||
-- disable WhichKey for certain buf types and file types.
|
||||
disable = {
|
||||
buftypes = {},
|
||||
filetypes = {},
|
||||
ft = {},
|
||||
bt = {},
|
||||
},
|
||||
debug = false, -- enable wk.log in the current directory
|
||||
}
|
||||
|
||||
---@type Options
|
||||
M.options = {}
|
||||
M.loaded = false
|
||||
|
||||
---@param options? Options
|
||||
function M.setup(options)
|
||||
if vim.fn.has("nvim-0.9") == 0 then
|
||||
return vim.notify("WhichKey.nvim requires Neovim 0.9 or higher", vim.log.levels.ERROR)
|
||||
---@type wk.Keymap[]
|
||||
M.mappings = {}
|
||||
|
||||
---@type wk.Opts
|
||||
M.options = nil
|
||||
|
||||
---@type {opt:string, msg:string}[]
|
||||
M.issues = {}
|
||||
|
||||
function M.validate()
|
||||
local deprecated = {
|
||||
["operators"] = "see `opts.defer`",
|
||||
["key_labels"] = "see `opts.replace`",
|
||||
"motions",
|
||||
["popup_mappings"] = "see `opts.keys`",
|
||||
["window"] = "see `opts.win`",
|
||||
["ignore_missing"] = "see `opts.filter`",
|
||||
"hidden",
|
||||
["triggers_nowait"] = "see `opts.delay`",
|
||||
["triggers_blacklist"] = "see `opts.triggers`",
|
||||
["disable.trigger"] = "see `opts.triggers`",
|
||||
["modes"] = "see `opts.triggers`",
|
||||
}
|
||||
for k, v in pairs(deprecated) do
|
||||
local opt = type(k) == "number" and v or k
|
||||
local msg = "option is deprecated." .. (type(k) == "number" and "" or " " .. v)
|
||||
local parts = vim.split(opt, ".", { plain = true })
|
||||
if vim.tbl_get(M.options, unpack(parts)) ~= nil then
|
||||
table.insert(M.issues, { opt = opt, msg = msg })
|
||||
end
|
||||
end
|
||||
if type(M.options.triggers) ~= "table" then
|
||||
table.insert(M.issues, { opt = "triggers", msg = "triggers must be a table" })
|
||||
end
|
||||
M.options = vim.tbl_deep_extend("force", {}, defaults, options or {})
|
||||
end
|
||||
|
||||
M.setup()
|
||||
---@param opts? wk.Opts
|
||||
function M.setup(opts)
|
||||
if vim.fn.has("nvim-0.9.4") == 0 then
|
||||
return vim.notify("which-key.nvim requires Neovim >= 0.9.4", vim.log.levels.ERROR)
|
||||
end
|
||||
M.options = vim.tbl_deep_extend("force", {}, defaults, opts or {})
|
||||
|
||||
return M
|
||||
local function load()
|
||||
if M.loaded then
|
||||
return
|
||||
end
|
||||
local Util = require("which-key.util")
|
||||
|
||||
if M.options.preset then
|
||||
local Presets = require("which-key.presets")
|
||||
M.options = vim.tbl_deep_extend("force", {}, defaults, Presets[M.options.preset] or {}, opts or {})
|
||||
end
|
||||
|
||||
M.validate()
|
||||
if #M.issues > 0 then
|
||||
Util.warn({
|
||||
"There are issues with your config.",
|
||||
"Use `:checkhealth which-key` to find out more.",
|
||||
}, { once = true })
|
||||
end
|
||||
|
||||
for k, v in pairs(M.options.keys) do
|
||||
M.options.keys[k] = Util.norm(v)
|
||||
end
|
||||
|
||||
if M.options.debug then
|
||||
Util.debug("\n\nDebug Started for v" .. M.version)
|
||||
if package.loaded.lazy then
|
||||
local Git = require("lazy.manage.git")
|
||||
local plugin = require("lazy.core.config").plugins["which-key.nvim"]
|
||||
Util.debug(vim.inspect(Git.info(plugin.dir)))
|
||||
end
|
||||
end
|
||||
|
||||
local wk = require("which-key")
|
||||
|
||||
-- replace by the real add function
|
||||
wk.add = M.add
|
||||
|
||||
if type(M.options.triggers) ~= "table" then
|
||||
---@diagnostic disable-next-line: inject-field
|
||||
M.options.triggers = defaults.triggers
|
||||
end
|
||||
|
||||
M.triggers = {
|
||||
mappings = require("which-key.mappings").parse(M.options.triggers),
|
||||
modes = {},
|
||||
}
|
||||
---@param m wk.Mapping
|
||||
M.triggers.mappings = vim.tbl_filter(function(m)
|
||||
if m.lhs == "<auto>" then
|
||||
M.triggers.modes[m.mode] = true
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end, M.triggers.mappings)
|
||||
|
||||
-- load presets first so that they can be overriden by the user
|
||||
require("which-key.plugins").setup()
|
||||
|
||||
-- process mappings queue
|
||||
for _, todo in ipairs(wk._queue) do
|
||||
M.add(todo.spec, todo.opts)
|
||||
end
|
||||
wk._queue = {}
|
||||
|
||||
-- finally, add the mapppings from the config
|
||||
M.add(M.options.spec)
|
||||
|
||||
-- setup colors and start which-key
|
||||
require("which-key.colors").setup()
|
||||
require("which-key.state").setup()
|
||||
|
||||
M.loaded = true
|
||||
end
|
||||
load = vim.schedule_wrap(load)
|
||||
|
||||
if vim.v.vim_did_enter == 1 then
|
||||
load()
|
||||
else
|
||||
vim.api.nvim_create_autocmd("VimEnter", { once = true, callback = load })
|
||||
end
|
||||
|
||||
vim.api.nvim_create_user_command("WhichKey", function(cmd)
|
||||
local mode, keys = cmd.args:match("^([nixsotc]?)%s*(.*)$")
|
||||
if not mode then
|
||||
return require("which-key.util").error("Usage: WhichKey [mode] [keys]")
|
||||
end
|
||||
if mode == "" then
|
||||
mode = "n"
|
||||
end
|
||||
require("which-key").show({ mode = mode, keys = keys })
|
||||
end, {
|
||||
nargs = "*",
|
||||
})
|
||||
end
|
||||
|
||||
---@param opts? wk.Parse
|
||||
---@param mappings wk.Spec
|
||||
function M.add(mappings, opts)
|
||||
opts = opts or {}
|
||||
opts.create = opts.create ~= false
|
||||
local Mappings = require("which-key.mappings")
|
||||
for _, km in ipairs(Mappings.parse(mappings, opts)) do
|
||||
table.insert(M.mappings, km)
|
||||
km.idx = #M.mappings
|
||||
end
|
||||
if M.loaded then
|
||||
require("which-key.buf").clear()
|
||||
end
|
||||
end
|
||||
|
||||
return setmetatable(M, {
|
||||
__index = function(_, k)
|
||||
if rawget(M, "options") == nil then
|
||||
M.setup()
|
||||
end
|
||||
local opts = rawget(M, "options")
|
||||
return k == "options" and opts or opts[k]
|
||||
end,
|
||||
})
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
local Keys = require("which-key.keys")
|
||||
local Buf = require("which-key.buf")
|
||||
local Config = require("which-key.config")
|
||||
local Icons = require("which-key.icons")
|
||||
local Mappings = require("which-key.mappings")
|
||||
local Migrate = require("which-key.migrate")
|
||||
local Tree = require("which-key.tree")
|
||||
local Util = require("which-key.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
@ -8,49 +14,169 @@ local warn = vim.health.warn or vim.health.report_warn
|
||||
local error = vim.health.error or vim.health.report_error
|
||||
local info = vim.health.info or vim.health.report_info
|
||||
|
||||
function M.check()
|
||||
start("WhichKey: checking conflicting keymaps")
|
||||
local conflicts = 0
|
||||
for _, tree in pairs(Keys.mappings) do
|
||||
Keys.update_keymaps(tree.mode, tree.buf)
|
||||
tree.tree:walk(
|
||||
---@param node Node
|
||||
function(node)
|
||||
local count = 0
|
||||
for _ in pairs(node.children) do
|
||||
count = count + 1
|
||||
end
|
||||
-- TODO: Add more checks
|
||||
-- * duplicate desc
|
||||
-- * mapping.desc ~= keymap.desc
|
||||
-- * check for old-style mappings
|
||||
|
||||
local auto_prefix = not node.mapping or (node.mapping.group == true and not node.mapping.cmd)
|
||||
if node.prefix_i ~= "" and count > 0 and not auto_prefix then
|
||||
conflicts = conflicts + 1
|
||||
local msg = ("conflicting keymap exists for mode **%q**, lhs: **%q**"):format(tree.mode, node.mapping.prefix)
|
||||
warn(msg)
|
||||
local cmd = node.mapping.cmd or " "
|
||||
info(("rhs: `%s`"):format(cmd))
|
||||
end
|
||||
end
|
||||
function M.check()
|
||||
ok(
|
||||
"Most of these checks are for informational purposes only.\n"
|
||||
.. "WARNINGS should be treated as a warning, and don't necessarily indicate a problem with your config.\n"
|
||||
.. "Please |DON't| report these warnings as an issue."
|
||||
)
|
||||
|
||||
start("Checking your config")
|
||||
|
||||
if #Config.issues > 0 then
|
||||
local msg = {
|
||||
"There are issues with your config:",
|
||||
}
|
||||
vim.list_extend(
|
||||
msg,
|
||||
vim.tbl_map(function(issue)
|
||||
return "- `opts." .. issue.opt .. "`: " .. issue.msg
|
||||
end, Config.issues)
|
||||
)
|
||||
msg[#msg + 1] = "Please refer to the docs for more info."
|
||||
warn(table.concat(msg, "\n"))
|
||||
end
|
||||
if conflicts == 0 then
|
||||
ok("No conflicting keymaps found")
|
||||
return
|
||||
|
||||
local have_icons = false
|
||||
for _, provider in ipairs(Icons.providers) do
|
||||
if provider.available == nil then
|
||||
provider.available = pcall(require, provider.name)
|
||||
end
|
||||
if provider.available then
|
||||
ok("|" .. provider.name .. "| is installed")
|
||||
have_icons = true
|
||||
else
|
||||
warn("|" .. provider.name .. "| is not installed")
|
||||
end
|
||||
end
|
||||
for _, dup in ipairs(Keys.duplicates) do
|
||||
local msg = ""
|
||||
if dup.buf == dup.other.buffer then
|
||||
msg = "duplicate keymap"
|
||||
else
|
||||
msg = "buffer-local keymap overriding global"
|
||||
if not have_icons then
|
||||
warn("Keymap icon support will be limited.")
|
||||
end
|
||||
|
||||
start("Checking for issues with your mappings")
|
||||
if #Mappings.notifs == 0 then
|
||||
ok("No issues reported")
|
||||
end
|
||||
for _, notif in ipairs(Mappings.notifs) do
|
||||
local msg = notif.msg
|
||||
if notif.spec then
|
||||
msg = msg .. ": >\n" .. vim.inspect(notif.spec)
|
||||
if msg:find("old version") then
|
||||
local fixed = Migrate.migrate(notif.spec)
|
||||
msg = msg .. "\n\n-- Suggested Spec:\n" .. fixed
|
||||
end
|
||||
end
|
||||
msg = (msg .. " for mode **%q**, buf: %d, lhs: **%q**"):format(dup.mode, dup.buf or 0, dup.prefix)
|
||||
if dup.buf == dup.other.buffer then
|
||||
error(msg)
|
||||
else
|
||||
warn(msg)
|
||||
(notif.level >= vim.log.levels.ERROR and error or warn)(msg)
|
||||
end
|
||||
|
||||
start("checking for overlapping keymaps")
|
||||
local found = false
|
||||
|
||||
Buf.cleanup()
|
||||
|
||||
---@type table<string, boolean>
|
||||
local reported = {}
|
||||
|
||||
local mapmodes = vim.split("nixsotc", "")
|
||||
|
||||
for _, buf in pairs(Buf.bufs) do
|
||||
for _, mapmode in ipairs(mapmodes) do
|
||||
local mode = buf:get({ mode = mapmode })
|
||||
if mode then
|
||||
mode.tree:walk(function(node)
|
||||
local km = node.keymap
|
||||
if not km or Util.is_nop(km.rhs) or node.keys:sub(1, 6) == "<Plug>" then
|
||||
return
|
||||
end
|
||||
if node.keymap and node:count() > 0 then
|
||||
local id = mode.mode .. ":" .. node.keys
|
||||
if reported[id] then
|
||||
return
|
||||
end
|
||||
reported[id] = true
|
||||
local overlaps = {}
|
||||
local descs = {}
|
||||
if node.desc and node.desc ~= "" then
|
||||
descs[#descs + 1] = "- <" .. node.keys .. ">: " .. node.desc
|
||||
end
|
||||
local queue = node:children()
|
||||
while #queue > 0 do
|
||||
local child = table.remove(queue)
|
||||
if child.keymap then
|
||||
table.insert(overlaps, "<" .. child.keys .. ">")
|
||||
if child.desc and child.desc ~= "" then
|
||||
descs[#descs + 1] = "- <" .. child.keys .. ">: " .. child.desc
|
||||
end
|
||||
end
|
||||
vim.list_extend(queue, child:children())
|
||||
end
|
||||
if #overlaps > 0 then
|
||||
found = true
|
||||
warn(
|
||||
"In mode `"
|
||||
.. mode.mode
|
||||
.. "`, <"
|
||||
.. node.keys
|
||||
.. "> overlaps with "
|
||||
.. table.concat(overlaps, ", ")
|
||||
.. ":\n"
|
||||
.. table.concat(descs, "\n")
|
||||
)
|
||||
end
|
||||
return false
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
info(("old rhs: `%s`"):format(dup.other.rhs or ""))
|
||||
info(("new rhs: `%s`"):format(dup.cmd or ""))
|
||||
end
|
||||
|
||||
if found then
|
||||
ok(
|
||||
"Overlapping keymaps are only reported for informational purposes.\n"
|
||||
.. "This doesn't necessarily mean there is a problem with your config."
|
||||
)
|
||||
else
|
||||
ok("No overlapping keymaps found")
|
||||
end
|
||||
|
||||
start("Checking for duplicate mappings")
|
||||
|
||||
if vim.tbl_isempty(Tree.dups) then
|
||||
ok("No duplicate mappings found")
|
||||
else
|
||||
for _, mappings in pairs(Tree.dups) do
|
||||
---@type wk.Mapping[]
|
||||
mappings = vim.tbl_keys(mappings)
|
||||
local first = mappings[1]
|
||||
warn(
|
||||
"Duplicates for <"
|
||||
.. first.lhs
|
||||
.. "> in mode `"
|
||||
.. first.mode
|
||||
.. "`:\n"
|
||||
.. table.concat(
|
||||
vim.tbl_map(function(m)
|
||||
m = vim.deepcopy(m)
|
||||
local desc = (m.desc and (m.desc .. ": ") or "")
|
||||
m.desc = nil
|
||||
m.idx = nil
|
||||
m.mode = nil
|
||||
m.lhs = nil
|
||||
return "* " .. desc .. "`" .. vim.inspect(m):gsub("%s+", " ") .. "`"
|
||||
end, mappings),
|
||||
"\n"
|
||||
)
|
||||
)
|
||||
end
|
||||
ok(
|
||||
"Duplicate mappings are only reported for informational purposes.\n"
|
||||
.. "This doesn't necessarily mean there is a problem with your config."
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -1,96 +1,52 @@
|
||||
local Keys = require("which-key.keys")
|
||||
local Util = require("which-key.util")
|
||||
|
||||
---@class WhichKey
|
||||
---@class wk
|
||||
---@field private _queue {spec: wk.Spec, opts?: wk.Parse}[]
|
||||
local M = {}
|
||||
|
||||
local loaded = false -- once we loaded everything
|
||||
local scheduled = false
|
||||
M._queue = {}
|
||||
M.did_setup = false
|
||||
|
||||
local function schedule_load()
|
||||
if scheduled then
|
||||
return
|
||||
end
|
||||
scheduled = true
|
||||
if vim.v.vim_did_enter == 0 then
|
||||
vim.cmd([[au VimEnter * ++once lua require("which-key").load()]])
|
||||
else
|
||||
M.load()
|
||||
end
|
||||
end
|
||||
|
||||
---@param options? Options
|
||||
function M.setup(options)
|
||||
require("which-key.config").setup(options)
|
||||
schedule_load()
|
||||
end
|
||||
|
||||
function M.show(keys, opts)
|
||||
--- Open which-key
|
||||
---@param opts? wk.Filter|string
|
||||
function M.show(opts)
|
||||
opts = opts or {}
|
||||
if type(opts) == "string" then
|
||||
opts = { mode = opts }
|
||||
opts = type(opts) == "string" and { keys = opts } or opts
|
||||
if opts.delay == nil then
|
||||
opts.delay = 0
|
||||
end
|
||||
|
||||
keys = keys or ""
|
||||
|
||||
opts.mode = opts.mode or Util.get_mode()
|
||||
local buf = vim.api.nvim_get_current_buf()
|
||||
-- make sure the trees exist for update
|
||||
Keys.get_tree(opts.mode)
|
||||
Keys.get_tree(opts.mode, buf)
|
||||
-- update only trees related to buf
|
||||
Keys.update(buf)
|
||||
-- trigger which key
|
||||
require("which-key.view").open(keys, opts)
|
||||
end
|
||||
|
||||
function M.show_command(keys, mode)
|
||||
keys = keys or ""
|
||||
keys = (keys == '""' or keys == "''") and "" or keys
|
||||
mode = (mode == '""' or mode == "''") and "" or mode
|
||||
mode = mode or "n"
|
||||
keys = Util.t(keys)
|
||||
if not Util.check_mode(mode) then
|
||||
Util.error(
|
||||
"Invalid mode passed to :WhichKey (Don't create any keymappings to trigger WhichKey. WhichKey does this automatically)"
|
||||
opts.waited = vim.o.timeoutlen
|
||||
---@diagnostic disable-next-line: param-type-mismatch
|
||||
if not require("which-key.state").start(opts) then
|
||||
require("which-key.util").warn(
|
||||
"No mappings found for mode `" .. (opts.mode or "n") .. "` and keys `" .. (opts.keys or "") .. "`"
|
||||
)
|
||||
else
|
||||
M.show(keys, { mode = mode })
|
||||
end
|
||||
end
|
||||
|
||||
local queue = {}
|
||||
---@param opts? wk.Opts
|
||||
function M.setup(opts)
|
||||
M.did_setup = true
|
||||
require("which-key.config").setup(opts)
|
||||
end
|
||||
|
||||
-- Defer registering keymaps until VimEnter
|
||||
-- Use `require("which-key").add()` instead.
|
||||
-- The spec is different though, so check the docs!
|
||||
---@deprecated
|
||||
---@param mappings wk.Spec
|
||||
---@param opts? wk.Mapping
|
||||
function M.register(mappings, opts)
|
||||
schedule_load()
|
||||
if loaded then
|
||||
Keys.register(mappings, opts)
|
||||
Keys.update()
|
||||
else
|
||||
table.insert(queue, { mappings, opts })
|
||||
if opts then
|
||||
for k, v in pairs(opts) do
|
||||
mappings[k] = v
|
||||
end
|
||||
end
|
||||
M.add(mappings, { version = 1 })
|
||||
end
|
||||
|
||||
-- Load mappings and update only once
|
||||
function M.load()
|
||||
if loaded then
|
||||
return
|
||||
end
|
||||
require("which-key.plugins").setup()
|
||||
require("which-key.colors").setup()
|
||||
Keys.register({}, { prefix = "<leader>", mode = "n" })
|
||||
Keys.register({}, { prefix = "<leader>", mode = "v" })
|
||||
Keys.setup()
|
||||
|
||||
for _, reg in pairs(queue) do
|
||||
local opts = reg[2] or {}
|
||||
opts.update = false
|
||||
Keys.register(reg[1], opts)
|
||||
end
|
||||
Keys.update()
|
||||
queue = {}
|
||||
loaded = true
|
||||
--- Add mappings to which-key
|
||||
---@param mappings wk.Spec
|
||||
---@param opts? wk.Parse
|
||||
function M.add(mappings, opts)
|
||||
table.insert(M._queue, { spec = mappings, opts = opts })
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@ -1,449 +0,0 @@
|
||||
local Tree = require("which-key.tree")
|
||||
local Util = require("which-key.util")
|
||||
local Config = require("which-key.config")
|
||||
|
||||
-- secret character that will be used to create <nop> mappings
|
||||
local secret = "Þ"
|
||||
|
||||
---@class Keys
|
||||
local M = {}
|
||||
|
||||
M.operators = {} ---@type table<string, boolean>
|
||||
M.nowait = {} ---@type table<string, boolean>
|
||||
M.blacklist = {} ---@type table<string, table<string, boolean>>
|
||||
|
||||
function M.setup()
|
||||
local builtin_ops = require("which-key.plugins.presets").operators
|
||||
for op, _ in pairs(builtin_ops) do
|
||||
M.operators[op] = true
|
||||
end
|
||||
local mappings = {}
|
||||
for op, label in pairs(Config.options.operators) do
|
||||
M.operators[op] = true
|
||||
if builtin_ops[op] then
|
||||
mappings[op] = { name = label, i = { name = "inside" }, a = { name = "around" } }
|
||||
end
|
||||
end
|
||||
for _, t in pairs(Config.options.triggers_nowait) do
|
||||
M.nowait[t] = true
|
||||
end
|
||||
M.register(mappings, { mode = "n", preset = true })
|
||||
M.register({ i = { name = "inside" }, a = { name = "around" } }, { mode = "v", preset = true })
|
||||
for mode, blacklist in pairs(Config.options.triggers_blacklist) do
|
||||
for _, prefix_n in ipairs(blacklist) do
|
||||
M.blacklist[mode] = M.blacklist[mode] or {}
|
||||
M.blacklist[mode][prefix_n] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.get_operator(prefix_i)
|
||||
local ret = { i = nil, n = nil, len = nil }
|
||||
for op_n, _ in pairs(Config.options.operators) do
|
||||
local op_i = Util.t(op_n)
|
||||
if prefix_i:sub(1, #op_i) == op_i and (ret.len == nil or #op_i > ret.len) then
|
||||
ret = { i = op_i, n = op_n, len = #op_i }
|
||||
end
|
||||
end
|
||||
return ret.i, ret.n
|
||||
end
|
||||
|
||||
function M.process_motions(ret, mode, prefix_i, buf)
|
||||
local op_i, op_n = "", ""
|
||||
if mode ~= "v" then
|
||||
op_i, op_n = M.get_operator(prefix_i)
|
||||
end
|
||||
if (mode == "n" or mode == "v") and op_i then
|
||||
local op_prefix_i = prefix_i:sub(#op_i + 1)
|
||||
local op_count = op_prefix_i:match("^(%d+)")
|
||||
if op_count == "0" then
|
||||
op_count = nil
|
||||
end
|
||||
if Config.options.motions.count == false then
|
||||
op_count = nil
|
||||
end
|
||||
if op_count then
|
||||
op_prefix_i = op_prefix_i:sub(#op_count + 1)
|
||||
end
|
||||
local op_results = M.get_mappings("o", op_prefix_i, buf)
|
||||
|
||||
if not ret.mapping and op_results.mapping then
|
||||
ret.mapping = op_results.mapping
|
||||
ret.mapping.prefix = op_n .. (op_count or "") .. ret.mapping.prefix
|
||||
ret.mapping.keys = Util.parse_keys(ret.mapping.prefix)
|
||||
end
|
||||
|
||||
for _, mapping in pairs(op_results.mappings) do
|
||||
mapping.prefix = op_n .. (op_count or "") .. mapping.prefix
|
||||
mapping.keys = Util.parse_keys(mapping.prefix)
|
||||
table.insert(ret.mappings, mapping)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@return MappingGroup
|
||||
function M.get_mappings(mode, prefix_i, buf)
|
||||
---@class MappingGroup
|
||||
---@field mode string
|
||||
---@field prefix_i string
|
||||
---@field buf number
|
||||
---@field mapping? Mapping
|
||||
---@field mappings VisualMapping[]
|
||||
local ret
|
||||
ret = { mapping = nil, mappings = {}, mode = mode, buf = buf, prefix_i = prefix_i }
|
||||
|
||||
local prefix_len = #Util.parse_internal(prefix_i)
|
||||
|
||||
---@param node? Node
|
||||
local function add(node)
|
||||
if node then
|
||||
if node.mapping then
|
||||
ret.mapping = vim.tbl_deep_extend("force", {}, ret.mapping or {}, node.mapping)
|
||||
end
|
||||
for k, child in pairs(node.children) do
|
||||
if
|
||||
child.mapping
|
||||
and child.mapping.desc ~= "which_key_ignore"
|
||||
and not (child.mapping.group and vim.tbl_isempty(child.children))
|
||||
then
|
||||
local child_mapping = vim.deepcopy(child.mapping)
|
||||
ret.mappings[k] = vim.tbl_deep_extend("force", ret.mappings[k] or {}, child_mapping)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local plugin_context = { buf = buf, mode = mode }
|
||||
add(M.get_tree(mode).tree:get(prefix_i, nil, plugin_context))
|
||||
add(M.get_tree(mode, buf).tree:get(prefix_i, nil, plugin_context))
|
||||
|
||||
-- Handle motions
|
||||
M.process_motions(ret, mode, prefix_i, buf)
|
||||
|
||||
-- Fix labels
|
||||
local tmp = {}
|
||||
for _, value in pairs(ret.mappings) do
|
||||
value.key = value.keys.notation[prefix_len + 1]
|
||||
if Config.options.key_labels[value.key] then
|
||||
value.key = Config.options.key_labels[value.key]
|
||||
end
|
||||
local skip = not value.desc and Config.options.ignore_missing == true
|
||||
if Util.t(value.key) == Util.t("<esc>") then
|
||||
skip = true
|
||||
end
|
||||
if not skip then
|
||||
if value.group then
|
||||
value.desc = value.desc or "+prefix"
|
||||
value.desc = value.desc:gsub("^%+", "")
|
||||
value.desc = Config.options.icons.group .. value.desc
|
||||
elseif not value.desc then
|
||||
value.desc = value.cmd or ""
|
||||
for _, v in ipairs(Config.options.hidden) do
|
||||
value.desc = value.desc:gsub(v, "")
|
||||
end
|
||||
end
|
||||
if value.value then
|
||||
value.value = vim.fn.strtrans(value.value)
|
||||
end
|
||||
-- remove duplicated keymap
|
||||
local exists = false
|
||||
for k, v in pairs(tmp) do
|
||||
if type(v) == "table" and v.key == value.key then
|
||||
tmp[k] = value
|
||||
exists = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not exists then
|
||||
table.insert(tmp, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Sort items, but not for plugins
|
||||
table.sort(tmp, function(a, b)
|
||||
if a.order and b.order then
|
||||
return a.order < b.order
|
||||
end
|
||||
if a.group == b.group then
|
||||
local ak = (a.key or ""):lower()
|
||||
local bk = (b.key or ""):lower()
|
||||
local aw = ak:match("[a-z]") and 1 or 0
|
||||
local bw = bk:match("[a-z]") and 1 or 0
|
||||
if aw == bw then
|
||||
return ak < bk
|
||||
end
|
||||
return aw < bw
|
||||
else
|
||||
return (a.group and 1 or 0) < (b.group and 1 or 0)
|
||||
end
|
||||
end)
|
||||
ret.mappings = tmp
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
---@type table<string, MappingTree>
|
||||
M.mappings = {}
|
||||
M.duplicates = {}
|
||||
|
||||
function M.map(mode, prefix_n, cmd, buf, opts)
|
||||
local other = vim.api.nvim_buf_call(buf or 0, function()
|
||||
local ret = vim.fn.maparg(prefix_n, mode, false, true)
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
return (ret and ret.lhs and ret.rhs and ret.rhs ~= cmd) and ret or nil
|
||||
end)
|
||||
if other and other.buffer == buf then
|
||||
table.insert(M.duplicates, { mode = mode, prefix = prefix_n, cmd = cmd, buf = buf, other = other })
|
||||
end
|
||||
if buf ~= nil then
|
||||
pcall(vim.api.nvim_buf_set_keymap, buf, mode, prefix_n, cmd, opts)
|
||||
else
|
||||
pcall(vim.api.nvim_set_keymap, mode, prefix_n, cmd, opts)
|
||||
end
|
||||
end
|
||||
|
||||
function M.register(mappings, opts)
|
||||
opts = opts or {}
|
||||
|
||||
mappings = require("which-key.mappings").parse(mappings, opts)
|
||||
|
||||
-- always create the root node for the mode, even if there's no mappings,
|
||||
-- to ensure we have at least a trigger hooked for non documented keymaps
|
||||
local modes = {}
|
||||
|
||||
for _, mapping in pairs(mappings) do
|
||||
if not modes[mapping.mode] then
|
||||
modes[mapping.mode] = true
|
||||
M.get_tree(mapping.mode)
|
||||
end
|
||||
if mapping.cmd ~= nil then
|
||||
M.map(mapping.mode, mapping.prefix, mapping.cmd, mapping.buf, mapping.opts)
|
||||
end
|
||||
M.get_tree(mapping.mode, mapping.buf).tree:add(mapping)
|
||||
end
|
||||
end
|
||||
|
||||
M.hooked = {}
|
||||
|
||||
function M.hook_id(prefix_n, mode, buf)
|
||||
return mode .. (buf or "") .. Util.t(prefix_n)
|
||||
end
|
||||
|
||||
function M.is_hooked(prefix_n, mode, buf)
|
||||
return M.hooked[M.hook_id(prefix_n, mode, buf)]
|
||||
end
|
||||
|
||||
function M.hook_del(prefix_n, mode, buf)
|
||||
local id = M.hook_id(prefix_n, mode, buf)
|
||||
M.hooked[id] = nil
|
||||
if buf then
|
||||
pcall(vim.api.nvim_buf_del_keymap, buf, mode, prefix_n)
|
||||
pcall(vim.api.nvim_buf_del_keymap, buf, mode, prefix_n .. secret)
|
||||
else
|
||||
pcall(vim.api.nvim_del_keymap, mode, prefix_n)
|
||||
pcall(vim.api.nvim_del_keymap, mode, prefix_n .. secret)
|
||||
end
|
||||
end
|
||||
|
||||
function M.hook_add(prefix_n, mode, buf, secret_only)
|
||||
-- check if this trigger is blacklisted
|
||||
if M.blacklist[mode] and M.blacklist[mode][prefix_n] then
|
||||
return
|
||||
end
|
||||
-- don't hook numbers. See #118
|
||||
if tonumber(prefix_n) then
|
||||
return
|
||||
end
|
||||
-- don't hook to j or k in INSERT mode
|
||||
if mode == "i" and (prefix_n == "j" or prefix_n == "k") then
|
||||
return
|
||||
end
|
||||
-- never hook q
|
||||
if mode == "n" and prefix_n == "q" then
|
||||
return
|
||||
end
|
||||
-- never hook into select mode
|
||||
if mode == "s" then
|
||||
return
|
||||
end
|
||||
-- never hook into operator pending mode
|
||||
-- this is handled differently
|
||||
if mode == "o" then
|
||||
return
|
||||
end
|
||||
if Util.t(prefix_n) == Util.t("<esc>") then
|
||||
return
|
||||
end
|
||||
-- never hook into operators in visual mode
|
||||
if (mode == "v" or mode == "x") and (prefix_n == "a" or prefix_n == "i" or M.operators[prefix_n]) then
|
||||
return
|
||||
end
|
||||
|
||||
-- Check if we need to create the hook
|
||||
if type(Config.options.triggers) == "string" and Config.options.triggers ~= "auto" then
|
||||
if Util.t(prefix_n) ~= Util.t(Config.options.triggers) then
|
||||
return
|
||||
end
|
||||
end
|
||||
if type(Config.options.triggers) == "table" then
|
||||
local ok = false
|
||||
for _, trigger in pairs(Config.options.triggers) do
|
||||
if Util.t(trigger) == Util.t(prefix_n) then
|
||||
ok = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not ok then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local opts = { noremap = true, silent = true }
|
||||
local id = M.hook_id(prefix_n, mode, buf)
|
||||
local id_global = M.hook_id(prefix_n, mode)
|
||||
-- hook up if needed
|
||||
if not M.hooked[id] and not M.hooked[id_global] then
|
||||
local cmd = [[<cmd>lua require("which-key").show(%q, {mode = %q, auto = true})<cr>]]
|
||||
cmd = string.format(cmd, Util.t(prefix_n), mode)
|
||||
-- map group triggers and nops
|
||||
-- nops are needed, so that WhichKey always respects timeoutlen
|
||||
|
||||
local mapmode = mode == "v" and "x" or mode
|
||||
if secret_only ~= true then
|
||||
M.map(mapmode, prefix_n, cmd, buf, opts)
|
||||
end
|
||||
if not M.nowait[prefix_n] then
|
||||
M.map(mapmode, prefix_n .. secret, "<nop>", buf, opts)
|
||||
end
|
||||
|
||||
M.hooked[id] = true
|
||||
end
|
||||
end
|
||||
|
||||
function M.update(buf)
|
||||
for k, tree in pairs(M.mappings) do
|
||||
if tree.buf and not vim.api.nvim_buf_is_valid(tree.buf) then
|
||||
-- remove group for invalid buffers
|
||||
M.mappings[k] = nil
|
||||
elseif not buf or not tree.buf or buf == tree.buf then
|
||||
-- only update buffer maps, if:
|
||||
-- 1. we dont pass a buffer
|
||||
-- 2. this is a global node
|
||||
-- 3. this is a local buffer node for the passed buffer
|
||||
M.update_keymaps(tree.mode, tree.buf)
|
||||
M.add_hooks(tree.mode, tree.buf, tree.tree.root)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param node Node
|
||||
function M.add_hooks(mode, buf, node, secret_only)
|
||||
if not node.mapping then
|
||||
node.mapping = { prefix = node.prefix_n, group = true, keys = Util.parse_keys(node.prefix_n) }
|
||||
end
|
||||
if node.prefix_n ~= "" and node.mapping.group == true and not (node.mapping.cmd or node.mapping.callback) then
|
||||
-- first non-cmd level, so create hook and make all descendents secret only
|
||||
M.hook_add(node.prefix_n, mode, buf, secret_only)
|
||||
secret_only = true
|
||||
end
|
||||
for _, child in pairs(node.children) do
|
||||
M.add_hooks(mode, buf, child, secret_only)
|
||||
end
|
||||
end
|
||||
|
||||
function M.dump()
|
||||
local ok = {}
|
||||
local todo = {}
|
||||
for _, tree in pairs(M.mappings) do
|
||||
M.update_keymaps(tree.mode, tree.buf)
|
||||
tree.tree:walk(
|
||||
---@param node Node
|
||||
function(node)
|
||||
if node.mapping then
|
||||
if node.mapping.desc then
|
||||
ok[node.mapping.prefix] = true
|
||||
todo[node.mapping.prefix] = nil
|
||||
elseif not ok[node.mapping.prefix] then
|
||||
todo[node.mapping.prefix] = { node.mapping.cmd or "" }
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
return todo
|
||||
end
|
||||
|
||||
---@param mode string
|
||||
---@param buf? number
|
||||
function M.get_tree(mode, buf)
|
||||
if mode == "s" or mode == "x" then
|
||||
mode = "v"
|
||||
end
|
||||
Util.check_mode(mode, buf)
|
||||
local idx = mode .. (buf or "")
|
||||
if not M.mappings[idx] then
|
||||
M.mappings[idx] = { mode = mode, buf = buf, tree = Tree:new() }
|
||||
end
|
||||
return M.mappings[idx]
|
||||
end
|
||||
|
||||
---@param prefix string
|
||||
---@param cmd string?
|
||||
function M.is_hook(prefix, cmd)
|
||||
-- skip mappings with our secret nop command
|
||||
if prefix:find(secret, 1, true) then
|
||||
return true
|
||||
end
|
||||
-- skip auto which-key mappings
|
||||
return cmd and cmd:find("which-key", 1, true) and cmd:find("auto", 1, true)
|
||||
end
|
||||
|
||||
---@param mode string
|
||||
---@param buf? number
|
||||
function M.update_keymaps(mode, buf)
|
||||
---@type Keymap[]
|
||||
local keymaps = buf and vim.api.nvim_buf_get_keymap(buf, mode) or vim.api.nvim_get_keymap(mode)
|
||||
local tree = M.get_tree(mode, buf).tree
|
||||
|
||||
local function is_nop(keymap)
|
||||
return not keymap.callback and (keymap.rhs == "" or keymap.rhs:lower() == "<nop>")
|
||||
end
|
||||
|
||||
for _, keymap in pairs(keymaps) do
|
||||
local skip = M.is_hook(keymap.lhs, keymap.rhs)
|
||||
local is_group = false
|
||||
|
||||
if not skip and is_nop(keymap) then
|
||||
if keymap.desc then
|
||||
pcall(vim.keymap.del, { mode }, keymap.lhs, { buffer = buf })
|
||||
is_group = true
|
||||
else
|
||||
skip = true
|
||||
end
|
||||
end
|
||||
|
||||
if not skip then
|
||||
---@type Mapping
|
||||
local mapping = {
|
||||
prefix = keymap.lhs,
|
||||
cmd = not is_group and keymap.rhs or nil,
|
||||
desc = keymap.desc,
|
||||
group = is_group,
|
||||
callback = keymap.callback,
|
||||
keys = Util.parse_keys(keymap.lhs),
|
||||
buf = buf,
|
||||
mode = mode,
|
||||
}
|
||||
-- don't include Plug keymaps
|
||||
if mapping.keys.notation[1]:lower() ~= "<plug>" then
|
||||
local node = tree:add(mapping)
|
||||
if node.mapping and node.mapping.preset and mapping.desc then
|
||||
node.mapping.desc = mapping.desc
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@ -1,216 +1,147 @@
|
||||
local Config = require("which-key.config")
|
||||
local Text = require("which-key.text")
|
||||
local Keys = require("which-key.keys")
|
||||
local Util = require("which-key.util")
|
||||
local M = {}
|
||||
|
||||
---@class Layout
|
||||
---@field mapping Mapping
|
||||
---@field items VisualMapping[]
|
||||
---@field options Options
|
||||
---@field text Text
|
||||
---@field results MappingGroup
|
||||
local Layout = {}
|
||||
Layout.__index = Layout
|
||||
local dw = vim.fn.strdisplaywidth
|
||||
|
||||
---@param mappings MappingGroup
|
||||
---@param options? Options
|
||||
function Layout:new(mappings, options)
|
||||
options = options or Config.options
|
||||
local this = {
|
||||
results = mappings,
|
||||
mapping = mappings.mapping,
|
||||
items = mappings.mappings,
|
||||
options = options,
|
||||
text = Text:new(),
|
||||
}
|
||||
setmetatable(this, self)
|
||||
return this
|
||||
end
|
||||
--- When `size` is a number, it is returned as is (fixed dize).
|
||||
--- Otherwise, it is a percentage of `parent` (relative size).
|
||||
--- If `size` is negative, it is subtracted from `parent`.
|
||||
--- If `size` is a table, it is a range of values.
|
||||
---@alias wk.Dim number|{min:number, max:number}
|
||||
|
||||
function Layout:max_width(key)
|
||||
local max = 0
|
||||
for _, item in pairs(self.items) do
|
||||
if item[key] and Text.len(item[key]) > max then
|
||||
max = Text.len(item[key])
|
||||
---@param size number
|
||||
---@param parent number
|
||||
---@param ... wk.Dim
|
||||
---@return number
|
||||
function M.dim(size, parent, ...)
|
||||
size = math.abs(size) < 1 and parent * size or size
|
||||
size = size < 0 and parent + size or size
|
||||
for _, dim in ipairs({ ... } --[[ @as wk.Dim[] ]]) do
|
||||
if type(dim) == "number" then
|
||||
size = M.dim(dim, parent)
|
||||
else
|
||||
local min = dim.min and M.dim(dim.min, parent) or 0
|
||||
local max = dim.max and M.dim(dim.max, parent) or parent
|
||||
size = math.max(min, math.min(max, size))
|
||||
end
|
||||
end
|
||||
return max
|
||||
return math.floor(math.max(0, math.min(parent, size)) + 0.5)
|
||||
end
|
||||
|
||||
function Layout:trail()
|
||||
local prefix_i = self.results.prefix_i
|
||||
local buf_path = Keys.get_tree(self.results.mode, self.results.buf).tree:path(prefix_i)
|
||||
local path = Keys.get_tree(self.results.mode).tree:path(prefix_i)
|
||||
local len = #self.results.mapping.keys.notation
|
||||
local cmd_line = { { " " } }
|
||||
for i = 1, len, 1 do
|
||||
local node = buf_path[i]
|
||||
if not (node and node.mapping and node.mapping.desc) then
|
||||
node = path[i]
|
||||
---@class wk.Table: wk.Table.opts
|
||||
local Table = {}
|
||||
Table.__index = Table
|
||||
|
||||
---@param opts wk.Table.opts
|
||||
function Table.new(opts)
|
||||
local self = setmetatable({}, Table)
|
||||
self.cols = opts.cols
|
||||
self.rows = opts.rows
|
||||
return self
|
||||
end
|
||||
|
||||
---@param opts? {spacing?: number}
|
||||
---@return string[][], number[], number
|
||||
function Table:cells(opts)
|
||||
opts = opts or {}
|
||||
opts.spacing = opts.spacing or 1
|
||||
|
||||
local widths = {} ---@type number[] actual column widths
|
||||
|
||||
local cells = {} ---@type string[][]
|
||||
|
||||
local total = 0
|
||||
for c, col in ipairs(self.cols) do
|
||||
widths[c] = 0
|
||||
local all_ws = true
|
||||
for r, row in ipairs(self.rows) do
|
||||
cells[r] = cells[r] or {}
|
||||
local value = row[col.key] or col.default or ""
|
||||
value = tostring(value)
|
||||
value = value:gsub("%s*$", "")
|
||||
value = value:gsub("\n", Config.icons.keys.NL)
|
||||
value = vim.fn.strtrans(value)
|
||||
if value:find("%S") then
|
||||
all_ws = false
|
||||
end
|
||||
if col.padding then
|
||||
value = (" "):rep(col.padding[1] or 0) .. value .. (" "):rep(col.padding[2] or 0)
|
||||
end
|
||||
if c ~= #self.cols then
|
||||
value = value .. (" "):rep(opts.spacing)
|
||||
end
|
||||
cells[r][c] = value
|
||||
widths[c] = math.max(widths[c], dw(value))
|
||||
end
|
||||
local step = self.mapping.keys.notation[i]
|
||||
if node and node.mapping and node.mapping.desc then
|
||||
step = self.options.icons.group .. node.mapping.desc
|
||||
end
|
||||
if Config.options.key_labels[step] then
|
||||
step = Config.options.key_labels[step]
|
||||
end
|
||||
if Config.options.show_keys then
|
||||
table.insert(cmd_line, { step, "WhichKeyGroup" })
|
||||
if i ~= #self.mapping.keys.notation then
|
||||
table.insert(cmd_line, { " " .. self.options.icons.breadcrumb .. " ", "WhichKeySeparator" })
|
||||
if all_ws then
|
||||
widths[c] = 0
|
||||
for _, cell in pairs(cells) do
|
||||
cell[c] = ""
|
||||
end
|
||||
end
|
||||
end
|
||||
local width = 0
|
||||
if Config.options.show_keys then
|
||||
for _, line in pairs(cmd_line) do
|
||||
width = width + Text.len(line[1])
|
||||
end
|
||||
end
|
||||
local help = { --
|
||||
["<bs>"] = "go up one level",
|
||||
["<esc>"] = "close",
|
||||
}
|
||||
if #self.text.lines > self.options.layout.height.max then
|
||||
help[Config.options.popup_mappings.scroll_down] = "scroll down"
|
||||
help[Config.options.popup_mappings.scroll_up] = "scroll up"
|
||||
end
|
||||
local help_line = {}
|
||||
local help_width = 0
|
||||
for key, label in pairs(help) do
|
||||
help_width = help_width + Text.len(key) + Text.len(label) + 2
|
||||
table.insert(help_line, { key .. " ", "WhichKey" })
|
||||
table.insert(help_line, { label .. " ", "WhichKeySeparator" })
|
||||
end
|
||||
if Config.options.show_keys then
|
||||
table.insert(cmd_line, { string.rep(" ", math.floor(vim.o.columns / 2 - help_width / 2) - width) })
|
||||
total = total + widths[c]
|
||||
end
|
||||
|
||||
if self.options.show_help then
|
||||
for _, l in pairs(help_line) do
|
||||
table.insert(cmd_line, l)
|
||||
end
|
||||
end
|
||||
if vim.o.cmdheight > 0 then
|
||||
vim.api.nvim_echo(cmd_line, false, {})
|
||||
vim.cmd([[redraw]])
|
||||
else
|
||||
local col = 1
|
||||
self.text:nl()
|
||||
local row = #self.text.lines
|
||||
for _, text in ipairs(cmd_line) do
|
||||
self.text:set(row, col, text[1], text[2] and text[2]:gsub("WhichKey", "") or nil)
|
||||
col = col + vim.fn.strwidth(text[1])
|
||||
end
|
||||
end
|
||||
return cells, widths, total
|
||||
end
|
||||
|
||||
function Layout:layout(win)
|
||||
local pad_top, pad_right, pad_bot, pad_left = unpack(self.options.window.padding)
|
||||
local window_width = vim.api.nvim_win_get_width(win)
|
||||
local width = window_width
|
||||
width = width - pad_right - pad_left
|
||||
---@param opts {width: number, spacing?: number}
|
||||
function Table:layout(opts)
|
||||
local cells, widths = self:cells(opts)
|
||||
|
||||
local max_key_width = self:max_width("key")
|
||||
local max_label_width = self:max_width("desc")
|
||||
local max_value_width = self:max_width("value")
|
||||
local free = opts.width
|
||||
|
||||
local intro_width = max_key_width + 2 + Text.len(self.options.icons.separator) + self.options.layout.spacing
|
||||
local max_width = max_label_width + intro_width + max_value_width
|
||||
if max_width > width then
|
||||
max_width = width
|
||||
end
|
||||
|
||||
local column_width = max_width
|
||||
|
||||
if max_value_width == 0 then
|
||||
if column_width > self.options.layout.width.max then
|
||||
column_width = self.options.layout.width.max
|
||||
for c, col in ipairs(self.cols) do
|
||||
if not col.width then
|
||||
free = free - widths[c]
|
||||
end
|
||||
if column_width < self.options.layout.width.min then
|
||||
column_width = self.options.layout.width.min
|
||||
end
|
||||
else
|
||||
max_value_width = math.min(max_value_width, math.floor((column_width - intro_width) / 2))
|
||||
end
|
||||
free = math.max(free, 0)
|
||||
|
||||
max_label_width = column_width - (intro_width + max_value_width)
|
||||
|
||||
local columns = math.floor(width / column_width)
|
||||
|
||||
local height = math.ceil(#self.items / columns)
|
||||
if height < self.options.layout.height.min then
|
||||
height = self.options.layout.height.min
|
||||
end
|
||||
-- if height > self.options.layout.height.max then height = self.options.layout.height.max end
|
||||
|
||||
local col = 1
|
||||
local row = 1
|
||||
|
||||
local columns_used = math.min(columns, math.ceil(#self.items / height))
|
||||
local offset_x = 0
|
||||
if columns_used < columns then
|
||||
if self.options.layout.align == "right" then
|
||||
offset_x = (columns - columns_used) * column_width
|
||||
elseif self.options.layout.align == "center" then
|
||||
offset_x = math.floor((columns - columns_used) * column_width / 2)
|
||||
for c, col in ipairs(self.cols) do
|
||||
if col.width then
|
||||
widths[c] = M.dim(widths[c], free, { max = col.width })
|
||||
free = free - widths[c]
|
||||
end
|
||||
end
|
||||
|
||||
for _, item in pairs(self.items) do
|
||||
local start = (col - 1) * column_width + self.options.layout.spacing + offset_x + pad_left
|
||||
local key = item.key or ""
|
||||
if key == "<lt>" then
|
||||
key = "<"
|
||||
end
|
||||
if key == Util.t("<esc>") then
|
||||
key = "<esc>"
|
||||
end
|
||||
if Text.len(key) < max_key_width then
|
||||
key = string.rep(" ", max_key_width - Text.len(key)) .. key
|
||||
end
|
||||
---@type {value: string, hl?:string}[][]
|
||||
local ret = {}
|
||||
|
||||
self.text:set(row + pad_top, start, key, "")
|
||||
start = start + Text.len(key) + 1
|
||||
|
||||
self.text:set(row + pad_top, start, self.options.icons.separator, "Separator")
|
||||
start = start + Text.len(self.options.icons.separator) + 1
|
||||
|
||||
if item.value then
|
||||
local value = item.value
|
||||
start = start + 1
|
||||
if Text.len(value) > max_value_width then
|
||||
value = vim.fn.strcharpart(value, 0, max_value_width - 2) .. " …"
|
||||
end
|
||||
self.text:set(row + pad_top, start, value, "Value")
|
||||
if item.highlights then
|
||||
for _, hl in pairs(item.highlights) do
|
||||
self.text:highlight(row + pad_top, start + hl[1] - 1, start + hl[2] - 1, hl[3])
|
||||
for _, row in ipairs(cells) do
|
||||
---@type {value: string, hl?:string}[]
|
||||
local line = {}
|
||||
for c, col in ipairs(self.cols) do
|
||||
local value = row[c]
|
||||
local width = dw(value)
|
||||
if width > widths[c] then
|
||||
local old = value
|
||||
value = ""
|
||||
for i = 0, vim.fn.strchars(old) do
|
||||
value = value .. vim.fn.strcharpart(old, i, 1)
|
||||
if dw(value) >= widths[c] - 1 - (opts.spacing or 1) then
|
||||
break
|
||||
end
|
||||
end
|
||||
value = value .. Config.icons.ellipsis .. string.rep(" ", opts.spacing or 1)
|
||||
else
|
||||
local align = col.align or "left"
|
||||
if align == "left" then
|
||||
value = value .. (" "):rep(widths[c] - width)
|
||||
elseif align == "right" then
|
||||
value = (" "):rep(widths[c] - width) .. value
|
||||
elseif align == "center" then
|
||||
local pad = (widths[c] - width) / 2
|
||||
value = (" "):rep(math.floor(pad)) .. value .. (" "):rep(math.ceil(pad))
|
||||
end
|
||||
end
|
||||
start = start + max_value_width + 2
|
||||
end
|
||||
|
||||
local label = item.desc
|
||||
if Text.len(label) > max_label_width then
|
||||
label = vim.fn.strcharpart(label, 0, max_label_width - 2) .. " …"
|
||||
end
|
||||
self.text:set(row + pad_top, start, label, item.group and "Group" or "Desc")
|
||||
|
||||
if row % height == 0 then
|
||||
col = col + 1
|
||||
row = 1
|
||||
else
|
||||
row = row + 1
|
||||
line[#line + 1] = { value = value, hl = col.hl }
|
||||
end
|
||||
ret[#ret + 1] = line
|
||||
end
|
||||
|
||||
for _ = 1, pad_bot, 1 do
|
||||
self.text:nl()
|
||||
end
|
||||
self:trail()
|
||||
return self.text
|
||||
return ret
|
||||
end
|
||||
|
||||
return Layout
|
||||
M.new = Table.new
|
||||
|
||||
return M
|
||||
|
||||
@ -1,215 +1,320 @@
|
||||
local Config = require("which-key.config")
|
||||
local Util = require("which-key.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
local function lookup(...)
|
||||
local ret = {}
|
||||
for _, t in ipairs({ ... }) do
|
||||
for _, v in ipairs(t) do
|
||||
ret[v] = v
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
M.VERSION = 2
|
||||
M.notifs = {} ---@type {msg:string, level:number, spec?:wk.Spec}[]
|
||||
|
||||
local mapargs = {
|
||||
"noremap",
|
||||
"desc",
|
||||
"expr",
|
||||
"silent",
|
||||
"nowait",
|
||||
"script",
|
||||
"unique",
|
||||
"callback",
|
||||
"replace_keycodes", -- TODO: add config setting for default value
|
||||
---@class wk.Field
|
||||
---@field transform? string|(fun(value: any, parent:table): (value:any, key:string?))
|
||||
---@field inherit? boolean
|
||||
---@field deprecated? boolean
|
||||
|
||||
---@class wk.Parse
|
||||
---@field version? number
|
||||
---@field create? boolean
|
||||
---@field notify? boolean
|
||||
|
||||
M.notify = true
|
||||
|
||||
---@type table<string, wk.Field>
|
||||
M.fields = {
|
||||
-- map args
|
||||
rhs = {},
|
||||
lhs = {},
|
||||
buffer = { inherit = true },
|
||||
callback = { transform = "rhs" },
|
||||
desc = {},
|
||||
expr = { inherit = true },
|
||||
mode = { inherit = true },
|
||||
noremap = {
|
||||
transform = function(value)
|
||||
return not value, "remap"
|
||||
end,
|
||||
},
|
||||
nowait = { inherit = true },
|
||||
remap = { inherit = true },
|
||||
replace_keycodes = { inherit = true },
|
||||
script = {},
|
||||
silent = { inherit = true },
|
||||
unique = { inherit = true },
|
||||
-- wk args
|
||||
plugin = { inherit = true },
|
||||
group = {},
|
||||
hidden = { inherit = true },
|
||||
cond = { inherit = true },
|
||||
preset = { inherit = true },
|
||||
icon = { inherit = true },
|
||||
proxy = {},
|
||||
expand = {},
|
||||
-- deprecated
|
||||
name = { transform = "group", deprecated = true },
|
||||
prefix = { inherit = true, deprecated = true },
|
||||
cmd = { transform = "rhs", deprecated = true },
|
||||
}
|
||||
local wkargs = {
|
||||
"prefix",
|
||||
"mode",
|
||||
"plugin",
|
||||
"buffer",
|
||||
"remap",
|
||||
"cmd",
|
||||
"name",
|
||||
"group",
|
||||
"preset",
|
||||
"cond",
|
||||
}
|
||||
local transargs = lookup({
|
||||
"noremap",
|
||||
"expr",
|
||||
"silent",
|
||||
"nowait",
|
||||
"script",
|
||||
"unique",
|
||||
"prefix",
|
||||
"mode",
|
||||
"buffer",
|
||||
"preset",
|
||||
"replace_keycodes",
|
||||
})
|
||||
local args = lookup(mapargs, wkargs)
|
||||
|
||||
function M.child_opts(opts)
|
||||
local ret = {}
|
||||
for k, v in pairs(opts) do
|
||||
if transargs[k] then
|
||||
ret[k] = v
|
||||
end
|
||||
end
|
||||
return ret
|
||||
---@param msg string
|
||||
---@param spec? wk.Spec
|
||||
function M.error(msg, spec)
|
||||
M.log(msg, vim.log.levels.ERROR, spec)
|
||||
end
|
||||
|
||||
function M._process(value, opts)
|
||||
local list = {}
|
||||
local children = {}
|
||||
for k, v in pairs(value) do
|
||||
if type(k) == "number" then
|
||||
if type(v) == "table" then
|
||||
-- nested child, without key
|
||||
table.insert(children, v)
|
||||
else
|
||||
-- list value
|
||||
table.insert(list, v)
|
||||
end
|
||||
elseif args[k] then
|
||||
-- option
|
||||
opts[k] = v
|
||||
else
|
||||
-- nested child, with key
|
||||
children[k] = v
|
||||
end
|
||||
end
|
||||
return list, children
|
||||
---@param msg string
|
||||
---@param spec? wk.Spec
|
||||
function M.warn(msg, spec)
|
||||
M.log(msg, vim.log.levels.WARN, spec)
|
||||
end
|
||||
|
||||
function M._parse(value, mappings, opts)
|
||||
if type(value) ~= "table" then
|
||||
value = { value }
|
||||
---@param msg string
|
||||
---@param level number
|
||||
---@param spec? wk.Spec
|
||||
function M.log(msg, level, spec)
|
||||
if not M.notify then
|
||||
return
|
||||
end
|
||||
|
||||
local list, children = M._process(value, opts)
|
||||
|
||||
if opts.plugin then
|
||||
opts.group = true
|
||||
end
|
||||
if opts.name then
|
||||
-- remove + from group names
|
||||
opts.name = opts.name and opts.name:gsub("^%+", "")
|
||||
opts.group = true
|
||||
end
|
||||
|
||||
-- fix remap
|
||||
if opts.remap then
|
||||
opts.noremap = not opts.remap
|
||||
opts.remap = nil
|
||||
end
|
||||
|
||||
-- fix buffer
|
||||
if opts.buffer == 0 then
|
||||
opts.buffer = vim.api.nvim_get_current_buf()
|
||||
end
|
||||
|
||||
if opts.cond ~= nil then
|
||||
if type(opts.cond) == "function" then
|
||||
if not opts.cond() then
|
||||
return
|
||||
end
|
||||
elseif not opts.cond then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- process any array child mappings
|
||||
for k, v in pairs(children) do
|
||||
local o = M.child_opts(opts)
|
||||
if type(k) == "string" then
|
||||
o.prefix = (o.prefix or "") .. k
|
||||
end
|
||||
M._try_parse(v, mappings, o)
|
||||
end
|
||||
|
||||
-- { desc }
|
||||
if #list == 1 then
|
||||
if type(list[1]) ~= "string" then
|
||||
error("Invalid mapping for " .. vim.inspect({ value = value, opts = opts }))
|
||||
end
|
||||
opts.desc = opts.desc or list[1]
|
||||
-- { cmd, desc }
|
||||
elseif #list == 2 then
|
||||
-- desc
|
||||
assert(type(list[2]) == "string")
|
||||
opts.desc = list[2]
|
||||
|
||||
-- cmd
|
||||
if type(list[1]) == "string" then
|
||||
opts.cmd = list[1]
|
||||
elseif type(list[1]) == "function" then
|
||||
opts.cmd = ""
|
||||
opts.callback = list[1]
|
||||
else
|
||||
error("Incorrect mapping " .. vim.inspect(list))
|
||||
end
|
||||
elseif #list > 2 then
|
||||
error("Incorrect mapping " .. vim.inspect(list))
|
||||
end
|
||||
|
||||
if opts.desc or opts.group then
|
||||
if type(opts.mode) == "table" then
|
||||
for _, mode in pairs(opts.mode) do
|
||||
local mode_opts = vim.deepcopy(opts)
|
||||
mode_opts.mode = mode
|
||||
table.insert(mappings, mode_opts)
|
||||
end
|
||||
else
|
||||
table.insert(mappings, opts)
|
||||
end
|
||||
M.notifs[#M.notifs + 1] = { msg = msg, level = level, spec = spec }
|
||||
if Config.notify then
|
||||
Util.warn({
|
||||
"There were issues reported with your **which-key** mappings.",
|
||||
"Use `:checkhealth which-key` to find out more.",
|
||||
}, { once = true })
|
||||
end
|
||||
end
|
||||
|
||||
---@return Mapping
|
||||
function M.to_mapping(mapping)
|
||||
mapping.silent = mapping.silent ~= false
|
||||
mapping.noremap = mapping.noremap ~= false
|
||||
if mapping.cmd and mapping.cmd:lower():find("^<plug>") then
|
||||
mapping.noremap = false
|
||||
---@param spec wk.Spec
|
||||
---@param field string|number
|
||||
---@param types string|string[]
|
||||
function M.expect(spec, field, types)
|
||||
types = type(types) == "string" and { types } or types
|
||||
local ok = false
|
||||
for _, t in ipairs(types) do
|
||||
if type(spec[field]) == t then
|
||||
ok = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
mapping.buf = mapping.buffer
|
||||
mapping.buffer = nil
|
||||
|
||||
mapping.mode = mapping.mode or "n"
|
||||
mapping.desc = mapping.desc or mapping.name
|
||||
mapping.name = nil
|
||||
mapping.keys = Util.parse_keys(mapping.prefix or "")
|
||||
|
||||
local opts = {}
|
||||
for _, o in ipairs(mapargs) do
|
||||
opts[o] = mapping[o]
|
||||
mapping[o] = nil
|
||||
end
|
||||
-- restore desc
|
||||
mapping.desc = opts.desc
|
||||
|
||||
mapping.opts = opts
|
||||
return mapping
|
||||
end
|
||||
|
||||
function M._try_parse(value, mappings, opts)
|
||||
local ok, err = pcall(M._parse, value, mappings, opts)
|
||||
if not ok then
|
||||
Util.error(err)
|
||||
M.error("Expected `" .. field .. "` to be " .. table.concat(types, ", "), spec)
|
||||
end
|
||||
return ok
|
||||
end
|
||||
|
||||
---@param spec wk.Spec
|
||||
---@param ret? wk.Mapping[]
|
||||
---@param opts? wk.Parse
|
||||
function M._parse(spec, ret, opts)
|
||||
opts = opts or {}
|
||||
opts.version = opts.version or M.VERSION
|
||||
if spec.version then
|
||||
opts.version = spec.version
|
||||
spec.version = nil
|
||||
end
|
||||
|
||||
if ret == nil and opts.version ~= M.VERSION then
|
||||
M.warn(
|
||||
"You're using an old version of the which-key spec.\n"
|
||||
.. "Your mappings will work, but it's recommended to update them to the new version.\n"
|
||||
.. "Please check the docs and suggested spec below for more info.\nMappings",
|
||||
vim.deepcopy(spec)
|
||||
)
|
||||
end
|
||||
|
||||
ret = ret or {}
|
||||
|
||||
spec = type(spec) == "string" and { desc = spec } or spec
|
||||
|
||||
---@type wk.Mapping
|
||||
local mapping = {}
|
||||
|
||||
---@type wk.Spec[]
|
||||
local children = {}
|
||||
|
||||
local keys = vim.tbl_keys(spec)
|
||||
|
||||
table.sort(keys, function(a, b)
|
||||
local ta, tb = type(a), type(b)
|
||||
if type(a) == type(b) then
|
||||
return a < b
|
||||
end
|
||||
return ta < tb
|
||||
end)
|
||||
|
||||
-- process fields
|
||||
for _, k in ipairs(keys) do
|
||||
local v = spec[k]
|
||||
local field = M.fields[k] ---@type wk.Field?
|
||||
if field then
|
||||
if type(field.transform) == "string" then
|
||||
k = field.transform --[[@as string]]
|
||||
elseif type(field.transform) == "function" then
|
||||
local vv, kk = field.transform(v, spec)
|
||||
v, k = vv, (kk or k)
|
||||
end
|
||||
mapping[k] = v
|
||||
elseif type(k) == "string" then
|
||||
if opts.version == 1 then
|
||||
if M.expect(spec, k, { "string", "table" }) then
|
||||
if type(v) == "string" then
|
||||
table.insert(children, { prefix = (spec.prefix or "") .. k, desc = v })
|
||||
elseif type(v) == "table" then
|
||||
v.prefix = (spec.prefix or "") .. k
|
||||
table.insert(children, v)
|
||||
end
|
||||
end
|
||||
else
|
||||
M.error("Invalid field `" .. k .. "`", spec)
|
||||
end
|
||||
elseif type(k) == "number" and type(v) == "table" then
|
||||
if opts.version == 1 then
|
||||
v.prefix = spec.prefix or ""
|
||||
end
|
||||
table.insert(children, v)
|
||||
spec[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local count = #spec
|
||||
|
||||
-- process mapping
|
||||
if opts.version == M.VERSION then
|
||||
if count == 1 then
|
||||
if M.expect(spec, 1, "string") then
|
||||
mapping.lhs = spec[1] --[[@as string]]
|
||||
end
|
||||
elseif count == 2 then
|
||||
if M.expect(spec, 1, "string") and M.expect(spec, 2, { "string", "function" }) then
|
||||
mapping.lhs = spec[1] --[[@as string]]
|
||||
mapping.rhs = spec[2] --[[@as string]]
|
||||
end
|
||||
elseif count > 2 then
|
||||
M.error("expected 1 or 2 elements, got " .. count, spec)
|
||||
end
|
||||
elseif opts.version == 1 then
|
||||
if mapping.expr and mapping.replace_keycodes == nil then
|
||||
mapping.replace_keycodes = false
|
||||
end
|
||||
if count == 1 then
|
||||
if M.expect(spec, 1, "string") then
|
||||
if mapping.desc then
|
||||
M.warn("overwriting desc", spec)
|
||||
end
|
||||
mapping.desc = spec[1] --[[@as string]]
|
||||
end
|
||||
elseif count == 2 then
|
||||
if M.expect(spec, 1, { "string", "function" }) and M.expect(spec, 2, "string") then
|
||||
if mapping.desc then
|
||||
M.warn("overwriting desc", spec)
|
||||
end
|
||||
mapping.rhs = spec[1] --[[@as string]]
|
||||
mapping.desc = spec[2] --[[@as string]]
|
||||
end
|
||||
elseif count > 2 then
|
||||
M.error("expected 1 or 2 elements, got " .. count, spec)
|
||||
end
|
||||
end
|
||||
|
||||
-- add mapping
|
||||
M.add(mapping, ret, opts)
|
||||
|
||||
-- process children
|
||||
for _, child in ipairs(children) do
|
||||
for k, v in pairs(mapping) do
|
||||
if M.fields[k] and M.fields[k].inherit and child[k] == nil then
|
||||
child[k] = v
|
||||
end
|
||||
end
|
||||
M._parse(child, ret, opts)
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
---@param mapping wk.Spec
|
||||
---@param opts? wk.Parse
|
||||
---@param ret wk.Mapping[]
|
||||
function M.add(mapping, ret, opts)
|
||||
opts = opts or {}
|
||||
if mapping.cond == false or ((type(mapping.cond) == "function") and not mapping.cond()) then
|
||||
return
|
||||
end
|
||||
---@cast mapping wk.Mapping|wk.Spec
|
||||
mapping.cond = nil
|
||||
if mapping.desc == "which_key_ignore" then
|
||||
mapping.hidden = true
|
||||
mapping.desc = nil
|
||||
end
|
||||
if type(mapping.group) == "string" or type(mapping.group) == "function" then
|
||||
mapping.desc = mapping.group --[[@as string]]
|
||||
mapping.group = true
|
||||
end
|
||||
if mapping.plugin then
|
||||
mapping.group = true
|
||||
end
|
||||
if mapping.group and mapping.desc then
|
||||
mapping.desc = mapping.desc
|
||||
if type(mapping.desc) == "string" then
|
||||
mapping.desc = mapping.desc:gsub("^%+", "")
|
||||
end
|
||||
end
|
||||
if mapping.buffer == 0 or mapping.buffer == true then
|
||||
mapping.buffer = vim.api.nvim_get_current_buf()
|
||||
end
|
||||
if mapping.rhs then
|
||||
mapping.silent = mapping.silent ~= false
|
||||
end
|
||||
mapping.lhs = mapping.lhs or mapping.prefix
|
||||
if not mapping.lhs then
|
||||
return
|
||||
end
|
||||
mapping.prefix = nil
|
||||
|
||||
local has_desc = mapping.desc ~= nil
|
||||
Util.getters(mapping, { "desc", "icon" })
|
||||
|
||||
if has_desc or mapping.group or mapping.hidden or mapping.rhs or (opts.version == M.VERSION and mapping.lhs) then
|
||||
local modes = mapping.mode or { "n" } --[[@as string|string[] ]]
|
||||
modes = type(modes) == "string" and vim.split(modes, "") or modes --[[@as string[] ]]
|
||||
for _, mode in ipairs(modes) do
|
||||
if mode ~= "v" and mode ~= Util.mapmode(mode) then
|
||||
M.warn("Invalid mode `" .. mode .. "`", mapping)
|
||||
end
|
||||
local m = vim.deepcopy(mapping)
|
||||
m.mode = mode
|
||||
table.insert(ret, m)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@return Mapping[]
|
||||
function M.parse(mappings, opts)
|
||||
---@param mapping wk.Mapping
|
||||
function M.create(mapping)
|
||||
assert(mapping.lhs, "Missing lhs")
|
||||
assert(mapping.mode, "Missing mode")
|
||||
assert(mapping.rhs, "Missing rhs")
|
||||
local valid =
|
||||
{ "remap", "noremap", "buffer", "silent", "nowait", "expr", "unique", "script", "desc", "replace_keycodes" }
|
||||
local opts = {} ---@type vim.keymap.set.Opts
|
||||
for _, k in ipairs(valid) do
|
||||
if mapping[k] ~= nil then
|
||||
opts[k] = mapping[k]
|
||||
end
|
||||
end
|
||||
vim.keymap.set(mapping.mode, mapping.lhs, mapping.rhs, opts)
|
||||
end
|
||||
|
||||
---@param spec wk.Spec
|
||||
---@param opts? wk.Parse
|
||||
function M.parse(spec, opts)
|
||||
opts = opts or {}
|
||||
local ret = {}
|
||||
M._try_parse(mappings, ret, opts)
|
||||
return vim.tbl_map(function(m)
|
||||
return M.to_mapping(m)
|
||||
end, ret)
|
||||
M.notify = opts.notify ~= false
|
||||
local ret = M._parse(spec, nil, opts)
|
||||
M.notify = true
|
||||
for _, m in ipairs(ret) do
|
||||
if m.rhs and opts.create then
|
||||
M.create(m)
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
local Keys = require("which-key.keys")
|
||||
local Util = require("which-key.util")
|
||||
local Config = require("which-key.config")
|
||||
local Util = require("which-key.util")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@type table<string, wk.Plugin>
|
||||
M.plugins = {}
|
||||
|
||||
function M.setup()
|
||||
for name, opts in pairs(Config.options.plugins) do
|
||||
for name, opts in pairs(Config.plugins) do
|
||||
-- only setup plugin if we didnt load it before
|
||||
if not M.plugins[name] then
|
||||
if type(opts) == "boolean" then
|
||||
@ -22,38 +22,27 @@ function M.setup()
|
||||
end
|
||||
end
|
||||
|
||||
---@param plugin Plugin
|
||||
---@param plugin wk.Plugin
|
||||
function M._setup(plugin, opts)
|
||||
if plugin.actions then
|
||||
for _, trigger in pairs(plugin.actions) do
|
||||
local prefix = trigger.trigger
|
||||
local mode = trigger.mode or "n"
|
||||
local label = trigger.label or plugin.name
|
||||
Keys.register({ [prefix] = { label, plugin = plugin.name } }, { mode = mode })
|
||||
end
|
||||
if plugin.mappings then
|
||||
Config.add(plugin.mappings)
|
||||
end
|
||||
|
||||
if plugin.setup then
|
||||
plugin.setup(require("which-key"), opts, Config.options)
|
||||
plugin.setup(opts)
|
||||
end
|
||||
end
|
||||
|
||||
---@param mapping Mapping
|
||||
function M.invoke(mapping, context)
|
||||
local plugin = M.plugins[mapping.plugin]
|
||||
local prefix = mapping.prefix
|
||||
local items = plugin.run(prefix, context.mode, context.buf)
|
||||
|
||||
local ret = {}
|
||||
for i, item in
|
||||
ipairs(items --[[@as VisualMapping[] ]])
|
||||
do
|
||||
item.order = i
|
||||
item.keys = Util.parse_keys(prefix .. item.key)
|
||||
item.prefix = prefix .. item.key
|
||||
table.insert(ret, item)
|
||||
end
|
||||
---@param name string
|
||||
function M.cols(name)
|
||||
local plugin = M.plugins[name]
|
||||
assert(plugin, "plugin not found")
|
||||
local ret = {} ---@type wk.Col[]
|
||||
vim.list_extend(ret, plugin.cols or {})
|
||||
ret[#ret + 1] = { key = "value", hl = "WhichKeyValue", width = 0.5 }
|
||||
return ret
|
||||
end
|
||||
|
||||
---@class wk.Node.plugin.item: wk.Node,wk.Plugin.item
|
||||
|
||||
return M
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
---@diagnostic disable: missing-fields, inject-field
|
||||
---@type wk.Plugin
|
||||
local M = {}
|
||||
|
||||
M.name = "marks"
|
||||
|
||||
M.actions = {
|
||||
{ trigger = "`", mode = "n" },
|
||||
{ trigger = "'", mode = "n" },
|
||||
{ trigger = "g`", mode = "n" },
|
||||
{ trigger = "g'", mode = "n" },
|
||||
M.mappings = {
|
||||
icon = { icon = " ", color = "orange" },
|
||||
plugin = "marks",
|
||||
{ "`", desc = "marks" },
|
||||
{ "'", desc = "marks" },
|
||||
{ "g`", desc = "marks" },
|
||||
{ "g'", desc = "marks" },
|
||||
}
|
||||
|
||||
function M.setup(_wk, _config, options) end
|
||||
|
||||
local labels = {
|
||||
["^"] = "Last position of cursor in insert mode",
|
||||
["."] = "Last change in current buffer",
|
||||
@ -24,42 +26,38 @@ local labels = {
|
||||
[">"] = "To end of last visual selection",
|
||||
}
|
||||
|
||||
---@type Plugin
|
||||
---@return PluginItem[]
|
||||
function M.run(_trigger, _mode, buf)
|
||||
local items = {}
|
||||
M.cols = {
|
||||
{ key = "lnum", hl = "Number", align = "right" },
|
||||
}
|
||||
|
||||
local marks = {}
|
||||
function M.expand()
|
||||
local buf = vim.api.nvim_get_current_buf()
|
||||
local items = {} ---@type wk.Plugin.item[]
|
||||
|
||||
local marks = {} ---@type vim.fn.getmarklist.ret.item[]
|
||||
vim.list_extend(marks, vim.fn.getmarklist(buf))
|
||||
vim.list_extend(marks, vim.fn.getmarklist())
|
||||
|
||||
for _, mark in pairs(marks) do
|
||||
local key = mark.mark:sub(2, 2)
|
||||
if key == "<" then
|
||||
key = "<lt>"
|
||||
end
|
||||
local lnum = mark.pos[2]
|
||||
|
||||
local line
|
||||
local line ---@type string?
|
||||
if mark.pos[1] and mark.pos[1] ~= 0 then
|
||||
local lines = vim.fn.getbufline(mark.pos[1], lnum)
|
||||
if lines and lines[1] then
|
||||
line = lines[1]
|
||||
end
|
||||
line = vim.api.nvim_buf_get_lines(mark.pos[1], lnum - 1, lnum, false)[1]
|
||||
end
|
||||
|
||||
local file = mark.file and vim.fn.fnamemodify(mark.file, ":p:~:.")
|
||||
|
||||
local value = string.format("%4d ", lnum)
|
||||
value = value .. (line or file or "")
|
||||
|
||||
table.insert(items, {
|
||||
key = key,
|
||||
desc = labels[key] or file and ("file: " .. file) or "",
|
||||
value = value,
|
||||
value = vim.trim(line or file or ""),
|
||||
highlights = { { 1, 5, "Number" } },
|
||||
lnum = lnum,
|
||||
})
|
||||
end
|
||||
|
||||
return items
|
||||
end
|
||||
|
||||
|
||||
@ -1,107 +0,0 @@
|
||||
local M = {}
|
||||
|
||||
M.name = "presets"
|
||||
|
||||
M.operators = {
|
||||
d = "Delete",
|
||||
c = "Change",
|
||||
y = "Yank (copy)",
|
||||
["g~"] = "Toggle case",
|
||||
["gu"] = "Lowercase",
|
||||
["gU"] = "Uppercase",
|
||||
[">"] = "Indent right",
|
||||
["<lt>"] = "Indent left",
|
||||
["zf"] = "Create fold",
|
||||
["!"] = "Filter through external program",
|
||||
["v"] = "Visual Character Mode",
|
||||
-- ["V"] = "Visual Line Mode",
|
||||
}
|
||||
|
||||
M.motions = {
|
||||
["h"] = "Left",
|
||||
["j"] = "Down",
|
||||
["k"] = "Up",
|
||||
["l"] = "Right",
|
||||
["w"] = "Next word",
|
||||
["%"] = "Matching character: '()', '{}', '[]'",
|
||||
["b"] = "Previous word",
|
||||
["e"] = "Next end of word",
|
||||
["ge"] = "Previous end of word",
|
||||
["0"] = "Start of line",
|
||||
["^"] = "Start of line (non-blank)",
|
||||
["$"] = "End of line",
|
||||
["f"] = "Move to next char",
|
||||
["F"] = "Move to previous char",
|
||||
["t"] = "Move before next char",
|
||||
["T"] = "Move before previous char",
|
||||
["gg"] = "First line",
|
||||
["G"] = "Last line",
|
||||
["{"] = "Previous empty line",
|
||||
["}"] = "Next empty line",
|
||||
}
|
||||
|
||||
M.objects = {
|
||||
a = { name = "around" },
|
||||
i = { name = "inside" },
|
||||
['a"'] = [[double quoted string]],
|
||||
["a'"] = [[single quoted string]],
|
||||
["a("] = [[same as ab]],
|
||||
["a)"] = [[same as ab]],
|
||||
["a<lt>"] = [[a <> from '<' to the matching '>']],
|
||||
["a>"] = [[same as a<]],
|
||||
["aB"] = [[a Block from [{ to ]} (with brackets)]],
|
||||
["aW"] = [[a WORD (with white space)]],
|
||||
["a["] = [[a [] from '[' to the matching ']']],
|
||||
["a]"] = [[same as a[]],
|
||||
["a`"] = [[string in backticks]],
|
||||
["ab"] = [[a block from [( to ]) (with braces)]],
|
||||
["ap"] = [[a paragraph (with white space)]],
|
||||
["as"] = [[a sentence (with white space)]],
|
||||
["at"] = [[a tag block (with white space)]],
|
||||
["aw"] = [[a word (with white space)]],
|
||||
["a{"] = [[same as aB]],
|
||||
["a}"] = [[same as aB]],
|
||||
['i"'] = [[double quoted string without the quotes]],
|
||||
["i'"] = [[single quoted string without the quotes]],
|
||||
["i("] = [[same as ib]],
|
||||
["i)"] = [[same as ib]],
|
||||
["i<lt>"] = [[inner <> from '<' to the matching '>']],
|
||||
["i>"] = [[same as i<]],
|
||||
["iB"] = [[inner Block from [{ and ]}]],
|
||||
["iW"] = [[inner WORD]],
|
||||
["i["] = [[inner [] from '[' to the matching ']']],
|
||||
["i]"] = [[same as i[]],
|
||||
["i`"] = [[string in backticks without the backticks]],
|
||||
["ib"] = [[inner block from [( to ])]],
|
||||
["ip"] = [[inner paragraph]],
|
||||
["is"] = [[inner sentence]],
|
||||
["it"] = [[inner tag block]],
|
||||
["iw"] = [[inner word]],
|
||||
["i{"] = [[same as iB]],
|
||||
["i}"] = [[same as iB]],
|
||||
}
|
||||
|
||||
---@param config Options
|
||||
function M.setup(wk, opts, config)
|
||||
require("which-key.plugins.presets.misc").setup(wk, opts)
|
||||
|
||||
-- Operators
|
||||
if opts.operators then
|
||||
for op, label in pairs(M.operators) do
|
||||
config.operators[op] = label
|
||||
end
|
||||
end
|
||||
|
||||
-- Motions
|
||||
if opts.motions then
|
||||
wk.register(M.motions, { mode = "n", prefix = "", preset = true })
|
||||
wk.register(M.motions, { mode = "o", prefix = "", preset = true })
|
||||
end
|
||||
|
||||
-- Text objects
|
||||
if opts.text_objects then
|
||||
wk.register(M.objects, { mode = "o", prefix = "", preset = true })
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@ -1,101 +0,0 @@
|
||||
local M = {}
|
||||
|
||||
M.name = "misc"
|
||||
|
||||
local misc = {
|
||||
windows = {
|
||||
["<c-w>"] = {
|
||||
name = "window",
|
||||
s = "Split window",
|
||||
v = "Split window vertically",
|
||||
w = "Switch windows",
|
||||
q = "Quit a window",
|
||||
o = "Close all other windows",
|
||||
T = "Break out into a new tab",
|
||||
x = "Swap current with next",
|
||||
["-"] = "Decrease height",
|
||||
["+"] = "Increase height",
|
||||
["<lt>"] = "Decrease width",
|
||||
[">"] = "Increase width",
|
||||
["|"] = "Max out the width",
|
||||
["_"] = "Max out the height",
|
||||
["="] = "Equally high and wide",
|
||||
h = "Go to the left window",
|
||||
l = "Go to the right window",
|
||||
k = "Go to the up window",
|
||||
j = "Go to the down window",
|
||||
},
|
||||
},
|
||||
z = {
|
||||
["z"] = {
|
||||
o = "Open fold under cursor",
|
||||
O = "Open all folds under cursor",
|
||||
c = "Close fold under cursor",
|
||||
C = "Close all folds under cursor",
|
||||
d = "Delete fold under cursor",
|
||||
D = "Delete all folds under cursor",
|
||||
E = "Delete all folds in file",
|
||||
a = "Toggle fold under cursor",
|
||||
A = "Toggle all folds under cursor",
|
||||
v = "Show cursor line",
|
||||
M = "Close all folds",
|
||||
R = "Open all folds",
|
||||
m = "Fold more",
|
||||
r = "Fold less",
|
||||
x = "Update folds",
|
||||
z = "Center this line",
|
||||
t = "Top this line",
|
||||
["<CR>"] = "Top this line, 1st non-blank col",
|
||||
b = "Bottom this line",
|
||||
g = "Add word to spell list",
|
||||
w = "Mark word as bad/misspelling",
|
||||
e = "Right this line",
|
||||
s = "Left this line",
|
||||
H = "Half screen to the left",
|
||||
L = "Half screen to the right",
|
||||
i = "Toggle folding",
|
||||
["="] = "Spelling suggestions",
|
||||
},
|
||||
},
|
||||
nav = {
|
||||
["[{"] = "Previous {",
|
||||
["[("] = "Previous (",
|
||||
["[<lt>"] = "Previous <",
|
||||
["[m"] = "Previous method start",
|
||||
["[M"] = "Previous method end",
|
||||
["[%"] = "Previous unmatched group",
|
||||
["[s"] = "Previous misspelled word",
|
||||
["]{"] = "Next {",
|
||||
["]("] = "Next (",
|
||||
["]<lt>"] = "Next <",
|
||||
["]m"] = "Next method start",
|
||||
["]M"] = "Next method end",
|
||||
["]%"] = "Next unmatched group",
|
||||
["]s"] = "Next misspelled word",
|
||||
["H"] = "Home line of window (top)",
|
||||
["M"] = "Middle line of window",
|
||||
["L"] = "Last line of window",
|
||||
},
|
||||
g = {
|
||||
["gf"] = "Go to file under cursor",
|
||||
["gx"] = "Open the file under cursor with system app",
|
||||
["gi"] = "Move to the last insertion and INSERT",
|
||||
["gv"] = "Switch to VISUAL using last selection",
|
||||
["gn"] = "Search forwards and select",
|
||||
["gN"] = "Search backwards and select",
|
||||
["g%"] = "Cycle backwards through results",
|
||||
["gt"] = "Go to next tab page",
|
||||
["gT"] = "Go to previous tab page",
|
||||
},
|
||||
}
|
||||
|
||||
function M.setup(wk, config)
|
||||
for key, mappings in pairs(misc) do
|
||||
if config[key] ~= false then
|
||||
wk.register(mappings, { mode = "n", prefix = "", preset = true })
|
||||
end
|
||||
end
|
||||
wk.register({ ["zf"] = "Create fold from selection" }, { mode = "x", prefix = "", preset = true })
|
||||
end
|
||||
|
||||
return M
|
||||
@ -1,18 +1,18 @@
|
||||
---@type Plugin
|
||||
local Util = require("which-key.util")
|
||||
|
||||
---@diagnostic disable: missing-fields, inject-field
|
||||
---@type wk.Plugin
|
||||
local M = {}
|
||||
|
||||
M.name = "registers"
|
||||
|
||||
M.actions = {
|
||||
{ trigger = '"', mode = "n" },
|
||||
{ trigger = '"', mode = "v" },
|
||||
-- { trigger = "@", mode = "n" },
|
||||
{ trigger = "<c-r>", mode = "i" },
|
||||
{ trigger = "<c-r>", mode = "c" },
|
||||
M.mappings = {
|
||||
icon = { icon = " ", color = "blue" },
|
||||
plugin = "registers",
|
||||
{ '"', mode = { "n", "x" }, desc = "registers" },
|
||||
{ "<c-r>", mode = { "i", "c" }, desc = "registers" },
|
||||
}
|
||||
|
||||
function M.setup(_wk, _config, options) end
|
||||
|
||||
M.registers = '*+"-:.%/#=_abcdefghijklmnopqrstuvwxyz0123456789'
|
||||
|
||||
local labels = {
|
||||
@ -30,43 +30,33 @@ local labels = {
|
||||
["/"] = "last search pattern",
|
||||
}
|
||||
|
||||
-- This function makes the assumption that OSC 52 is set up per :help osc-52
|
||||
M.osc52_active = function()
|
||||
-- If no clipboard set, can't be OSC 52
|
||||
if not vim.g.clipboard then
|
||||
return false
|
||||
end
|
||||
M.replace = {
|
||||
["<Space>"] = " ",
|
||||
["<lt>"] = "<",
|
||||
["<NL>"] = "\n",
|
||||
["\r"] = "",
|
||||
}
|
||||
|
||||
-- Per the docs, OSC 52 should be set up with a name field in the table
|
||||
if vim.g.clipboard.name == "OSC 52" then
|
||||
return true
|
||||
end
|
||||
function M.expand()
|
||||
local items = {} ---@type wk.Plugin.item[]
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
---@type Plugin
|
||||
---@return PluginItem[]
|
||||
function M.run(_trigger, _mode, _buf)
|
||||
local items = {}
|
||||
|
||||
local osc52_skip_keys = { "+", "*" }
|
||||
local is_osc52 = vim.g.clipboard and vim.g.clipboard.name == "OSC 52"
|
||||
local has_clipboard = vim.g.loaded_clipboard_provider == 2
|
||||
|
||||
for i = 1, #M.registers, 1 do
|
||||
local key = M.registers:sub(i, i)
|
||||
|
||||
local value = ""
|
||||
|
||||
if M.osc52_active() and vim.tbl_contains(osc52_skip_keys, key) then
|
||||
if is_osc52 and key:match("[%+%*]") then
|
||||
value = "OSC 52 detected, register not checked to maintain compatibility"
|
||||
else
|
||||
elseif has_clipboard or not key:match("[%+%*]") then
|
||||
local ok, reg_value = pcall(vim.fn.getreg, key, 1)
|
||||
if ok then
|
||||
value = reg_value
|
||||
end
|
||||
value = (ok and reg_value or "") --[[@as string]]
|
||||
end
|
||||
|
||||
if value ~= "" then
|
||||
value = vim.fn.keytrans(value) --[[@as string]]
|
||||
for k, v in pairs(M.replace) do
|
||||
value = value:gsub(k, v) --[[@as string]]
|
||||
end
|
||||
table.insert(items, { key = key, desc = labels[key] or "", value = value })
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,38 +1,42 @@
|
||||
---@diagnostic disable: missing-fields, inject-field
|
||||
---@type wk.Plugin
|
||||
local M = {}
|
||||
|
||||
M.name = "spelling"
|
||||
|
||||
M.actions = { { trigger = "z=", mode = "n" } }
|
||||
M.mappings = {
|
||||
{
|
||||
"z=",
|
||||
icon = { icon = " ", color = "red" },
|
||||
plugin = "spelling",
|
||||
desc = "Spelling Suggestions",
|
||||
},
|
||||
}
|
||||
|
||||
---@type table<string, any>
|
||||
M.opts = {}
|
||||
|
||||
function M.setup(_, config, options)
|
||||
M.opts = config
|
||||
function M.setup(opts)
|
||||
M.opts = opts
|
||||
end
|
||||
|
||||
---@type Plugin
|
||||
---@return PluginItem[]
|
||||
function M.run()
|
||||
function M.expand()
|
||||
-- if started with a count, let the default keybinding work
|
||||
local count = vim.api.nvim_get_vvar("count")
|
||||
local count = vim.v.count
|
||||
if count and count > 0 then
|
||||
return {}
|
||||
end
|
||||
|
||||
---@diagnostic disable-next-line: missing-parameter
|
||||
local cursor_word = vim.fn.expand("<cword>")
|
||||
-- get a misspelled word from under the cursor, if not found, then use the cursor_word instead
|
||||
---@diagnostic disable-next-line: redundant-parameter
|
||||
local bad = vim.fn.spellbadword(cursor_word)
|
||||
local word = bad[1]
|
||||
if word == "" then
|
||||
word = cursor_word
|
||||
end
|
||||
local word = bad[1] == "" and cursor_word or bad[1]
|
||||
|
||||
---@type string[]
|
||||
local suggestions = vim.fn.spellsuggest(word, M.opts.suggestions or 20, bad[2] == "caps" and 1 or 0)
|
||||
|
||||
local items = {}
|
||||
local keys = "1234567890abcdefghijklmnopqrstuvwxyz"
|
||||
local items = {} ---@type wk.Plugin.item[]
|
||||
local keys = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
for i, label in ipairs(suggestions) do
|
||||
local key = keys:sub(i, i)
|
||||
@ -40,8 +44,8 @@ function M.run()
|
||||
table.insert(items, {
|
||||
key = key,
|
||||
desc = label,
|
||||
fn = function()
|
||||
vim.cmd('norm! "_ciw' .. label)
|
||||
action = function()
|
||||
vim.cmd("norm! " .. i .. "z=")
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
@ -1,72 +1,164 @@
|
||||
---@class Highlight
|
||||
---@field group string
|
||||
---@field line number
|
||||
---@field from number
|
||||
---@field to number
|
||||
local Util = require("which-key.util")
|
||||
|
||||
---@class Text
|
||||
---@field lines string[]
|
||||
---@field hl Highlight[]
|
||||
---@field lineNr number
|
||||
---@field current string
|
||||
local Text = {}
|
||||
Text.__index = Text
|
||||
---@class wk.Segment
|
||||
---@field str string Text
|
||||
---@field hl? string Extmark hl group
|
||||
---@field line? number line number in a multiline segment
|
||||
---@field width? number
|
||||
|
||||
function Text.len(str)
|
||||
return vim.fn.strwidth(str)
|
||||
---@class wk.Text.opts
|
||||
---@field multiline? boolean
|
||||
---@field indent? boolean
|
||||
|
||||
---@class wk.Text
|
||||
---@field _lines wk.Segment[][]
|
||||
---@field _col number
|
||||
---@field _indents string[]
|
||||
---@field _opts wk.Text.opts
|
||||
local M = {}
|
||||
M.__index = M
|
||||
|
||||
M.ns = vim.api.nvim_create_namespace("wk.text")
|
||||
|
||||
---@param opts? wk.Text.opts
|
||||
function M.new(opts)
|
||||
local self = setmetatable({}, M)
|
||||
self._lines = {}
|
||||
self._col = 0
|
||||
self._opts = opts or {}
|
||||
self._indents = {}
|
||||
for i = 0, 100, 1 do
|
||||
self._indents[i] = (" "):rep(i)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Text:new()
|
||||
local this = { lines = {}, hl = {}, lineNr = 0, current = "" }
|
||||
setmetatable(this, self)
|
||||
return this
|
||||
function M:height()
|
||||
return #self._lines
|
||||
end
|
||||
|
||||
function Text:fix_nl(line)
|
||||
return line:gsub("[\n]", "")
|
||||
---@return number
|
||||
function M:width()
|
||||
local width = 0
|
||||
for _, line in ipairs(self._lines) do
|
||||
local w = 0
|
||||
for _, segment in ipairs(line) do
|
||||
w = w + vim.fn.strdisplaywidth(segment.str)
|
||||
end
|
||||
width = math.max(width, w)
|
||||
end
|
||||
return width
|
||||
end
|
||||
|
||||
function Text:nl()
|
||||
local line = self:fix_nl(self.current)
|
||||
table.insert(self.lines, line)
|
||||
self.current = ""
|
||||
self.lineNr = self.lineNr + 1
|
||||
---@param text string|wk.Segment[]
|
||||
---@param opts? string|{hl?:string, line?:number}
|
||||
function M:append(text, opts)
|
||||
opts = opts or {}
|
||||
if #self._lines == 0 then
|
||||
self:nl()
|
||||
end
|
||||
|
||||
if type(text) == "table" then
|
||||
for _, s in ipairs(text) do
|
||||
s.width = s.width or #s.str
|
||||
self._col = self._col + s.width
|
||||
table.insert(self._lines[#self._lines], s)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
opts = type(opts) == "string" and { hl = opts } or opts
|
||||
|
||||
for l, line in ipairs(vim.split(text, "\n", { plain = true })) do
|
||||
local width = #line
|
||||
self._col = self._col + width
|
||||
table.insert(self._lines[#self._lines], {
|
||||
str = line,
|
||||
width = width,
|
||||
hl = opts.hl,
|
||||
line = opts.line or l,
|
||||
})
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Text:set(row, col, str, group)
|
||||
str = self:fix_nl(str)
|
||||
function M:nl()
|
||||
table.insert(self._lines, {})
|
||||
self._col = 0
|
||||
return self
|
||||
end
|
||||
|
||||
-- extend lines if needed
|
||||
for i = 1, row, 1 do
|
||||
if not self.lines[i] then
|
||||
self.lines[i] = ""
|
||||
---@param opts? {sep?:string}
|
||||
function M:statusline(opts)
|
||||
local sep = opts and opts.sep or " "
|
||||
local lines = {} ---@type string[]
|
||||
for _, line in ipairs(self._lines) do
|
||||
local parts = {}
|
||||
for _, segment in ipairs(line) do
|
||||
local str = segment.str:gsub("%%", "%%%%")
|
||||
if segment.hl then
|
||||
str = ("%%#%s#%s%%*"):format(segment.hl, str)
|
||||
end
|
||||
parts[#parts + 1] = str
|
||||
end
|
||||
table.insert(lines, table.concat(parts, ""))
|
||||
end
|
||||
return table.concat(lines, sep)
|
||||
end
|
||||
|
||||
function M:render(buf)
|
||||
local lines = {}
|
||||
|
||||
for _, line in ipairs(self._lines) do
|
||||
local parts = {} ---@type string[]
|
||||
for _, segment in ipairs(line) do
|
||||
parts[#parts + 1] = segment.str
|
||||
end
|
||||
table.insert(lines, table.concat(parts, ""))
|
||||
end
|
||||
|
||||
vim.bo[buf].modifiable = true
|
||||
vim.api.nvim_buf_clear_namespace(buf, M.ns, 0, -1)
|
||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
||||
|
||||
for l, line in ipairs(self._lines) do
|
||||
local col = 0
|
||||
local row = l - 1
|
||||
|
||||
for _, segment in ipairs(line) do
|
||||
local width = segment.width
|
||||
if segment.hl then
|
||||
Util.set_extmark(buf, M.ns, row, col, {
|
||||
hl_group = segment.hl,
|
||||
end_col = col + width,
|
||||
})
|
||||
end
|
||||
col = col + width
|
||||
end
|
||||
end
|
||||
|
||||
-- extend columns when needed
|
||||
local width = Text.len(self.lines[row])
|
||||
if width < col then
|
||||
self.lines[row] = self.lines[row] .. string.rep(" ", col - width)
|
||||
end
|
||||
|
||||
local before = vim.fn.strcharpart(self.lines[row], 0, col)
|
||||
local after = vim.fn.strcharpart(self.lines[row], col)
|
||||
self.lines[row] = before .. str .. after
|
||||
|
||||
if not group then
|
||||
return
|
||||
end
|
||||
-- set highlights
|
||||
self:highlight(row, col, col + Text.len(str), "WhichKey" .. group)
|
||||
vim.bo[buf].modifiable = false
|
||||
end
|
||||
|
||||
function Text:highlight(row, from, to, group)
|
||||
local line = self.lines[row]
|
||||
local before = vim.fn.strcharpart(line, 0, from)
|
||||
local str = vim.fn.strcharpart(line, 0, to)
|
||||
from = vim.fn.strlen(before)
|
||||
to = vim.fn.strlen(str)
|
||||
table.insert(self.hl, { line = row - 1, from = from, to = to, group = group })
|
||||
function M:trim()
|
||||
while #self._lines > 0 and #self._lines[#self._lines] == 0 do
|
||||
table.remove(self._lines)
|
||||
end
|
||||
end
|
||||
|
||||
return Text
|
||||
function M:row()
|
||||
return #self._lines == 0 and 1 or #self._lines
|
||||
end
|
||||
|
||||
---@param opts? {display:boolean}
|
||||
function M:col(opts)
|
||||
if opts and opts.display then
|
||||
local ret = 0
|
||||
for _, segment in ipairs(self._lines[#self._lines] or {}) do
|
||||
ret = ret + vim.fn.strdisplaywidth(segment.str)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
return self._col
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@ -1,112 +1,103 @@
|
||||
local Config = require("which-key.config")
|
||||
local Node = require("which-key.node")
|
||||
local Util = require("which-key.util")
|
||||
|
||||
---@class Tree
|
||||
---@field root Node
|
||||
---@field nodes table<string, Node>
|
||||
local Tree = {}
|
||||
Tree.__index = Tree
|
||||
---@class wk.Tree
|
||||
---@field root wk.Node
|
||||
local M = {}
|
||||
M.__index = M
|
||||
|
||||
---@class Node
|
||||
---@field mapping Mapping
|
||||
---@field prefix_i string
|
||||
---@field prefix_n string
|
||||
---@field children table<string, Node>
|
||||
-- selene: allow(unused_variable)
|
||||
local Node
|
||||
---@type table<string, table<wk.Mapping|wk.Keymap, true>>
|
||||
M.dups = {}
|
||||
|
||||
---@return Tree
|
||||
function Tree:new()
|
||||
local this = { root = { children = {}, prefix_i = "", prefix_n = "" }, nodes = {} }
|
||||
setmetatable(this, self)
|
||||
return this
|
||||
function M.new()
|
||||
local self = setmetatable({}, M)
|
||||
self:clear()
|
||||
return self
|
||||
end
|
||||
|
||||
---@param prefix_i string
|
||||
---@param index? number defaults to last. If < 0, then offset from last
|
||||
---@param plugin_context? any
|
||||
---@return Node?
|
||||
function Tree:get(prefix_i, index, plugin_context)
|
||||
local prefix = Util.parse_internal(prefix_i)
|
||||
local node = self.root
|
||||
index = index or #prefix
|
||||
if index < 0 then
|
||||
index = #prefix + index
|
||||
end
|
||||
for i = 1, index, 1 do
|
||||
node = node.children[prefix[i]]
|
||||
if node and plugin_context and node.mapping and node.mapping.plugin then
|
||||
local children = require("which-key.plugins").invoke(node.mapping, plugin_context)
|
||||
node.children = {}
|
||||
for _, child in pairs(children) do
|
||||
self:add(child, { cache = false })
|
||||
end
|
||||
end
|
||||
if not node then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
return node
|
||||
function M:clear()
|
||||
self.root = Node.new()
|
||||
end
|
||||
|
||||
-- Returns the path (possibly incomplete) for the prefix
|
||||
---@param prefix_i string
|
||||
---@return Node[]
|
||||
function Tree:path(prefix_i)
|
||||
local prefix = Util.parse_internal(prefix_i)
|
||||
local node = self.root
|
||||
local path = {}
|
||||
for i = 1, #prefix, 1 do
|
||||
node = node.children[prefix[i]]
|
||||
table.insert(path, node)
|
||||
if not node then
|
||||
break
|
||||
end
|
||||
---@param keymap wk.Mapping|wk.Keymap
|
||||
---@param virtual? boolean
|
||||
function M:add(keymap, virtual)
|
||||
if not Config.filter(keymap) then
|
||||
return
|
||||
end
|
||||
local keys = Util.keys(keymap.lhs, { norm = true })
|
||||
local node = assert(self.root:find(keys, { create = true }))
|
||||
node.plugin = node.plugin or keymap.plugin
|
||||
if virtual then
|
||||
---@cast node wk.Node
|
||||
if node.mapping and not keymap.preset and not node.mapping.preset then
|
||||
local id = keymap.mode .. ":" .. node.keys
|
||||
M.dups[id] = M.dups[id] or {}
|
||||
M.dups[id][keymap] = true
|
||||
M.dups[id][node.mapping] = true
|
||||
end
|
||||
if not (keymap.preset and node.keymap and node.keymap.desc) then
|
||||
node.mapping = keymap --[[@as wk.Mapping]]
|
||||
end
|
||||
else
|
||||
node.keymap = keymap
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
---@param mapping Mapping
|
||||
---@param opts? {cache?: boolean}
|
||||
---@return Node
|
||||
function Tree:add(mapping, opts)
|
||||
---@param node wk.Node
|
||||
function M:del(node)
|
||||
if node == self.root then
|
||||
return self:clear()
|
||||
end
|
||||
local parent = node.parent
|
||||
assert(parent, "node has no parent")
|
||||
parent._children[node.key] = nil
|
||||
if not self:keep(parent) then
|
||||
self:del(parent)
|
||||
end
|
||||
end
|
||||
|
||||
---@param node wk.Node
|
||||
function M:keep(node)
|
||||
if node.hidden or (node.keymap and node.keymap.desc == "which_key_ignore") then
|
||||
return false
|
||||
end
|
||||
return node:can_expand() or node.keymap or node:is_group() or (node.mapping and not node.group)
|
||||
end
|
||||
|
||||
function M:fix()
|
||||
self:walk(function(node)
|
||||
if not self:keep(node) then
|
||||
self:del(node)
|
||||
return false
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
---@param keys string|string[]
|
||||
---@param opts? { create?: boolean, expand?: boolean }
|
||||
---@return wk.Node?
|
||||
function M:find(keys, opts)
|
||||
keys = type(keys) == "string" and Util.keys(keys) or keys
|
||||
return self.root:find(keys, opts)
|
||||
end
|
||||
|
||||
---@param fn fun(node: wk.Node):boolean?
|
||||
---@param opts? {expand?: boolean}
|
||||
function M:walk(fn, opts)
|
||||
opts = opts or {}
|
||||
opts.cache = opts.cache ~= false
|
||||
local node_key = mapping.keys.keys
|
||||
local node = opts.cache and self.nodes[node_key]
|
||||
if not node then
|
||||
local prefix_i = mapping.keys.internal
|
||||
local prefix_n = mapping.keys.notation
|
||||
node = self.root
|
||||
local path_i = ""
|
||||
local path_n = ""
|
||||
for i = 1, #prefix_i, 1 do
|
||||
path_i = path_i .. prefix_i[i]
|
||||
path_n = path_n .. prefix_n[i]
|
||||
if not node.children[prefix_i[i]] then
|
||||
node.children[prefix_i[i]] = {
|
||||
children = {},
|
||||
prefix_i = path_i,
|
||||
prefix_n = path_n,
|
||||
}
|
||||
---@type wk.Node[]
|
||||
local queue = { self.root }
|
||||
while #queue > 0 do
|
||||
local node = table.remove(queue, 1) ---@type wk.Node
|
||||
if node == self.root or fn(node) ~= false then
|
||||
local children = opts.expand and node:children() or node._children
|
||||
for _, child in pairs(children) do
|
||||
queue[#queue + 1] = child
|
||||
end
|
||||
node = node.children[prefix_i[i]]
|
||||
end
|
||||
if opts.cache then
|
||||
self.nodes[node_key] = node
|
||||
end
|
||||
end
|
||||
node.mapping = vim.tbl_deep_extend("force", node.mapping or {}, mapping)
|
||||
return node
|
||||
end
|
||||
|
||||
---@param cb fun(node:Node)
|
||||
---@param node? Node
|
||||
function Tree:walk(cb, node)
|
||||
node = node or self.root
|
||||
cb(node)
|
||||
for _, child in pairs(node.children) do
|
||||
self:walk(cb, child)
|
||||
end
|
||||
end
|
||||
|
||||
return Tree
|
||||
return M
|
||||
|
||||
@ -2,72 +2,115 @@
|
||||
|
||||
--# selene: allow(unused_variable)
|
||||
|
||||
---@class Keymap
|
||||
---@field rhs string
|
||||
---@field lhs string
|
||||
---@field buffer number
|
||||
---@field expr number
|
||||
---@field lnum number
|
||||
---@field mode string
|
||||
---@field noremap number
|
||||
---@field nowait number
|
||||
---@field script number
|
||||
---@field sid number
|
||||
---@field silent number
|
||||
---@field callback fun()|nil
|
||||
---@field id string terminal keycodes for lhs
|
||||
---@field desc string
|
||||
|
||||
---@class KeyCodes
|
||||
---@field keys string
|
||||
---@field internal string[]
|
||||
---@field notation string[]
|
||||
|
||||
---@class MappingOptions
|
||||
---@field noremap boolean
|
||||
---@field silent boolean
|
||||
---@field nowait boolean
|
||||
---@field expr boolean
|
||||
|
||||
---@class Mapping
|
||||
---@field buf number
|
||||
---@field group boolean
|
||||
---@field desc string
|
||||
---@field prefix string
|
||||
---@field cmd string
|
||||
---@field opts MappingOptions
|
||||
---@field keys KeyCodes
|
||||
---@class wk.Filter
|
||||
---@field mode? string
|
||||
---@field callback fun()|nil
|
||||
---@field preset boolean
|
||||
---@field plugin string
|
||||
---@field fn fun()
|
||||
|
||||
---@class MappingTree
|
||||
---@field mode string
|
||||
---@field buf? number
|
||||
---@field tree Tree
|
||||
---@field keys? string
|
||||
---@field global? boolean
|
||||
---@field local? boolean
|
||||
---@field update? boolean
|
||||
---@field delay? number
|
||||
---@field loop? boolean
|
||||
---@field defer? boolean don't show the popup immediately. Wait for the first key to be pressed
|
||||
---@field waited? number
|
||||
---@field check? boolean
|
||||
---@field expand? boolean
|
||||
|
||||
---@class VisualMapping : Mapping
|
||||
---@field key string
|
||||
---@field highlights table
|
||||
---@field value string
|
||||
---@class wk.Icon
|
||||
---@field icon? string
|
||||
---@field hl? string
|
||||
---@field cat? "file" | "filetype" | "extension"
|
||||
---@field name? string
|
||||
---@field color? false | "azure" | "blue" | "cyan" | "green" | "grey" | "orange" | "purple" | "red" | "yellow"
|
||||
|
||||
---@class PluginItem
|
||||
---@field key string
|
||||
---@field label string
|
||||
---@field value string
|
||||
---@field cmd string
|
||||
---@field highlights table
|
||||
|
||||
---@class PluginAction
|
||||
---@field trigger string
|
||||
---@field mode string
|
||||
---@field label? string
|
||||
---@field delay? boolean
|
||||
|
||||
---@class Plugin
|
||||
---@class wk.IconProvider
|
||||
---@field name string
|
||||
---@field actions PluginAction[]
|
||||
---@field run fun(trigger:string, mode:string, buf:number):PluginItem[]
|
||||
---@field setup fun(wk, opts, Options)
|
||||
---@field available? boolean
|
||||
---@field get fun(icon: wk.Icon):(icon: string?, hl: string?)
|
||||
|
||||
---@class wk.IconRule: wk.Icon
|
||||
---@field pattern? string
|
||||
---@field plugin? string
|
||||
|
||||
---@class wk.Keymap: vim.api.keyset.keymap
|
||||
---@field lhs string
|
||||
---@field mode string
|
||||
---@field rhs? string|fun()
|
||||
---@field lhsraw? string
|
||||
---@field buffer? number
|
||||
|
||||
--- Represents a node in the which-key tree
|
||||
---@class wk.Node: wk.Mapping
|
||||
---@field key string single key of the node
|
||||
---@field path string[] path to the node (all keys leading to this node)
|
||||
---@field keys string full key sequence
|
||||
---@field parent? wk.Node parent node
|
||||
---@field keymap? wk.Keymap Real keymap
|
||||
---@field mapping? wk.Mapping Mapping info supplied by user
|
||||
---@field action? fun() action to execute when node is selected (used by plugins)
|
||||
|
||||
---@class wk.Mapping: wk.Keymap
|
||||
---@field idx? number
|
||||
---@field plugin? string
|
||||
---@field group? boolean
|
||||
---@field remap? boolean
|
||||
---@field hidden? boolean
|
||||
---@field preset? boolean
|
||||
---@field icon? wk.Icon|string
|
||||
---@field proxy? string
|
||||
---@field expand? fun():wk.Spec
|
||||
|
||||
---@class wk.Spec: {[number]: wk.Spec} , wk.Mapping
|
||||
---@field [1]? string
|
||||
---@field [2]? string|fun()
|
||||
---@field lhs? string
|
||||
---@field group? string|fun():string
|
||||
---@field desc? string|fun():string
|
||||
---@field icon? wk.Icon|string|fun():(wk.Icon|string)
|
||||
---@field buffer? number|boolean
|
||||
---@field mode? string|string[]
|
||||
---@field cond? boolean|fun():boolean?
|
||||
|
||||
---@class wk.Win.opts: vim.api.keyset.win_config
|
||||
---@field width? wk.Dim
|
||||
---@field height? wk.Dim
|
||||
---@field wo? vim.wo
|
||||
---@field bo? vim.bo
|
||||
---@field padding? {[1]: number, [2]:number}
|
||||
---@field no_overlap? boolean
|
||||
|
||||
---@class wk.Col
|
||||
---@field key string
|
||||
---@field hl? string
|
||||
---@field width? number
|
||||
---@field padding? number[]
|
||||
---@field default? string
|
||||
---@field align? "left"|"right"|"center"
|
||||
|
||||
---@class wk.Table.opts
|
||||
---@field cols wk.Col[]
|
||||
---@field rows table<string, string>[]
|
||||
|
||||
---@class wk.Plugin.item
|
||||
---@field key string
|
||||
---@field value string
|
||||
---@field desc string
|
||||
---@field order? number
|
||||
---@field action? fun()
|
||||
|
||||
---@class wk.Plugin
|
||||
---@field name string
|
||||
---@field cols? wk.Col[]
|
||||
---@field mappings? wk.Spec
|
||||
---@field expand fun():wk.Plugin.item[]
|
||||
---@field setup fun(opts: table<string, any>)
|
||||
|
||||
---@class wk.Item: wk.Node
|
||||
---@field node wk.Node
|
||||
---@field key string
|
||||
---@field raw_key string
|
||||
---@field desc string
|
||||
---@field group? boolean
|
||||
---@field order? number
|
||||
---@field icon? string
|
||||
---@field icon_hl? string
|
||||
|
||||
@ -1,196 +1,301 @@
|
||||
---@class Util
|
||||
local M = {}
|
||||
local strbyte = string.byte
|
||||
local strsub = string.sub
|
||||
---@type table<string, KeyCodes>
|
||||
local cache = {}
|
||||
---@type table<string,string>
|
||||
local tcache = {}
|
||||
local cache_leaders = ""
|
||||
|
||||
function M.check_cache()
|
||||
---@type string
|
||||
local leaders = (vim.g.mapleader or "") .. ":" .. (vim.g.maplocalleader or "")
|
||||
if leaders ~= cache_leaders then
|
||||
cache = {}
|
||||
tcache = {}
|
||||
cache_leaders = leaders
|
||||
end
|
||||
end
|
||||
|
||||
function M.count(tab)
|
||||
local ret = 0
|
||||
for _, _ in pairs(tab) do
|
||||
ret = ret + 1
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function M.get_mode()
|
||||
local mode = vim.api.nvim_get_mode().mode
|
||||
mode = mode:gsub(M.t("<C-V>"), "v")
|
||||
mode = mode:gsub(M.t("<C-S>"), "s")
|
||||
return mode:lower()
|
||||
end
|
||||
|
||||
function M.is_empty(tab)
|
||||
return M.count(tab) == 0
|
||||
end
|
||||
M.cache = {
|
||||
keys = {}, ---@type table<string, string[]>
|
||||
norm = {}, ---@type table<string, string>
|
||||
termcodes = {}, ---@type table<string, string>
|
||||
}
|
||||
|
||||
function M.t(str)
|
||||
M.check_cache()
|
||||
if not tcache[str] then
|
||||
-- https://github.com/neovim/neovim/issues/17369
|
||||
tcache[str] = vim.api.nvim_replace_termcodes(str, false, true, true):gsub("\128\254X", "\128")
|
||||
end
|
||||
return tcache[str]
|
||||
M.cache.termcodes[str] = M.cache.termcodes[str] or vim.api.nvim_replace_termcodes(str, true, true, true)
|
||||
return M.cache.termcodes[str]
|
||||
end
|
||||
|
||||
-- stylua: ignore start
|
||||
local utf8len_tab = {
|
||||
-- ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 0?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 1?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 2?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 3?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 4?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 5?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 6?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 7?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 8?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- 9?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- A?
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -- B?
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -- C?
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -- D?
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, -- E?
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1, -- F?
|
||||
}
|
||||
-- stylua: ignore 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.CMD = "\x80\253h"
|
||||
|
||||
local Tokens = {
|
||||
["<"] = strbyte("<"),
|
||||
[">"] = strbyte(">"),
|
||||
["-"] = strbyte("-"),
|
||||
}
|
||||
---@return KeyCodes
|
||||
function M.parse_keys(keystr)
|
||||
M.check_cache()
|
||||
if cache[keystr] then
|
||||
return cache[keystr]
|
||||
function M.exit()
|
||||
vim.api.nvim_feedkeys(M.EXIT, "n", false)
|
||||
vim.api.nvim_feedkeys(M.ESC, "n", false)
|
||||
end
|
||||
|
||||
---@param rhs string|fun()
|
||||
function M.is_nop(rhs)
|
||||
return type(rhs) == "string" and (rhs == "" or rhs:lower() == "<nop>")
|
||||
end
|
||||
|
||||
--- Normalizes (and fixes) the lhs of a keymap
|
||||
---@param lhs string
|
||||
function M.norm(lhs)
|
||||
M.cache.norm[lhs] = M.cache.norm[lhs] or vim.fn.keytrans(M.t(lhs))
|
||||
return M.cache.norm[lhs]
|
||||
end
|
||||
|
||||
-- Default register
|
||||
function M.reg()
|
||||
-- this will be set to 2 if there is a non-empty clipboard
|
||||
-- tool available
|
||||
if vim.g.loaded_clipboard_provider ~= 2 then
|
||||
return '"'
|
||||
end
|
||||
local cb = vim.o.clipboard
|
||||
return cb:find("unnamedplus") and "+" or cb:find("unnamed") and "*" or '"'
|
||||
end
|
||||
|
||||
local keys = M.t(keystr)
|
||||
local internal = M.parse_internal(keys)
|
||||
|
||||
if #internal == 0 then
|
||||
local ret = { keys = keys, internal = {}, notation = {} }
|
||||
cache[keystr] = ret
|
||||
return ret
|
||||
--- Returns the keys of a keymap, taking multibyte and special keys into account
|
||||
---@param lhs string
|
||||
---@param opts? {norm?: boolean}
|
||||
function M.keys(lhs, opts)
|
||||
lhs = opts and opts.norm == false and lhs or M.norm(lhs)
|
||||
if M.cache.keys[lhs] then
|
||||
return M.cache.keys[lhs]
|
||||
end
|
||||
|
||||
local keystr_orig = keystr
|
||||
keystr = keystr:gsub("<lt>", "<")
|
||||
local notation = {}
|
||||
---@alias ParseState
|
||||
--- | "Character"
|
||||
--- | "Special"
|
||||
--- | "SpecialNoClose"
|
||||
local start = 1
|
||||
local i = start
|
||||
---@type ParseState
|
||||
local state = "Character"
|
||||
while i <= #keystr do
|
||||
local c = strbyte(keystr, i, i)
|
||||
|
||||
if state == "Character" then
|
||||
start = i
|
||||
-- Only interpret special tokens if neovim also replaces it
|
||||
state = c == Tokens["<"] and internal[#notation + 1] ~= "<" and "Special" or state
|
||||
elseif state == "Special" then
|
||||
state = (c == Tokens["-"] and "SpecialNoClose") or (c == Tokens[">"] and "Character") or state
|
||||
local ret = {} ---@type string[]
|
||||
local bytes = vim.fn.str2list(lhs) ---@type number[]
|
||||
local special = nil ---@type string?
|
||||
for _, byte in ipairs(bytes) do
|
||||
local char = vim.fn.nr2char(byte) ---@type string
|
||||
if char == "<" then
|
||||
special = "<"
|
||||
elseif special then
|
||||
special = special .. char
|
||||
if char == ">" then
|
||||
ret[#ret + 1] = special == "<lt>" and "<" or special
|
||||
special = nil
|
||||
end
|
||||
else
|
||||
state = "Special"
|
||||
end
|
||||
|
||||
i = i + utf8len_tab[c + 1]
|
||||
if state == "Character" then
|
||||
local k = strsub(keystr, start, i - 1)
|
||||
notation[#notation + 1] = k == " " and "<space>" or k
|
||||
ret[#ret + 1] = char
|
||||
end
|
||||
end
|
||||
|
||||
local mapleader = vim.g.mapleader
|
||||
mapleader = mapleader and M.t(mapleader)
|
||||
notation[1] = internal[1] == mapleader and "<leader>" or notation[1]
|
||||
|
||||
if #notation ~= #internal then
|
||||
error(vim.inspect({ keystr = keystr, internal = internal, notation = notation }))
|
||||
end
|
||||
|
||||
local ret = {
|
||||
keys = keys,
|
||||
internal = internal,
|
||||
notation = notation,
|
||||
}
|
||||
|
||||
cache[keystr_orig] = ret
|
||||
|
||||
M.cache.keys[lhs] = ret
|
||||
return ret
|
||||
end
|
||||
|
||||
-- @return string[]
|
||||
function M.parse_internal(keystr)
|
||||
local keys = {}
|
||||
---@alias ParseInternalState
|
||||
--- | "Character"
|
||||
--- | "Special"
|
||||
---@type ParseInternalState
|
||||
local state = "Character"
|
||||
local start = 1
|
||||
local i = 1
|
||||
while i <= #keystr do
|
||||
local c = strbyte(keystr, i, i)
|
||||
|
||||
if state == "Character" then
|
||||
state = c == 128 and "Special" or state
|
||||
i = i + utf8len_tab[c + 1]
|
||||
|
||||
if state == "Character" then
|
||||
keys[#keys + 1] = strsub(keystr, start, i - 1)
|
||||
start = i
|
||||
end
|
||||
else
|
||||
-- This state is entered on the second byte of K_SPECIAL sequence.
|
||||
if c == 252 then
|
||||
-- K_SPECIAL KS_MODIFIER: skip this byte and the next
|
||||
i = i + 2
|
||||
else
|
||||
-- K_SPECIAL _: skip this byte
|
||||
i = i + 1
|
||||
end
|
||||
-- The last byte of this sequence should be between 0x02 and 0x7f,
|
||||
-- switch to Character state to collect.
|
||||
state = "Character"
|
||||
end
|
||||
---@param mode? string
|
||||
function M.mapmode(mode)
|
||||
mode = mode or vim.api.nvim_get_mode().mode
|
||||
mode = mode:gsub(M.t("<C-V>"), "v"):gsub(M.t("<C-S>"), "s"):lower()
|
||||
if mode:sub(1, 2) == "no" then
|
||||
return "o"
|
||||
end
|
||||
return keys
|
||||
if mode:sub(1, 1) == "v" then
|
||||
return "x" -- mapmode is actually "x" for visual only mappings
|
||||
end
|
||||
return mode:sub(1, 1):match("[ncitsxo]") or "n"
|
||||
end
|
||||
|
||||
function M.warn(msg)
|
||||
vim.notify(msg, vim.log.levels.WARN, { title = "WhichKey" })
|
||||
function M.xo()
|
||||
return M.mapmode():find("[xo]") ~= nil
|
||||
end
|
||||
|
||||
function M.error(msg)
|
||||
vim.notify(msg, vim.log.levels.ERROR, { title = "WhichKey" })
|
||||
---@alias NotifyOpts {level?: number, title?: string, once?: boolean, id?:string}
|
||||
|
||||
---@param msg string|string[]
|
||||
---@param opts? NotifyOpts
|
||||
function M.notify(msg, opts)
|
||||
opts = opts or {}
|
||||
msg = type(msg) == "table" and table.concat(msg, "\n") or msg
|
||||
---@cast msg string
|
||||
msg = vim.trim(msg)
|
||||
return vim[opts.once and "notify_once" or "notify"](msg, opts.level, {
|
||||
title = opts.title or "which-key.nvim",
|
||||
on_open = function(win)
|
||||
M.wo(win, { conceallevel = 3, spell = false, concealcursor = "n" })
|
||||
vim.treesitter.start(vim.api.nvim_win_get_buf(win), "markdown")
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
function M.check_mode(mode, buf)
|
||||
if not ("nvsxoiRct"):find(mode) then
|
||||
M.error(string.format("Invalid mode %q for buf %d", mode, buf or 0))
|
||||
---@param msg string|string[]
|
||||
---@param opts? NotifyOpts
|
||||
function M.warn(msg, opts)
|
||||
M.notify(msg, vim.tbl_extend("keep", { level = vim.log.levels.WARN }, opts or {}))
|
||||
end
|
||||
|
||||
---@param msg string|string[]
|
||||
---@param opts? NotifyOpts
|
||||
function M.info(msg, opts)
|
||||
M.notify(msg, vim.tbl_extend("keep", { level = vim.log.levels.INFO }, opts or {}))
|
||||
end
|
||||
|
||||
---@param msg string|string[]
|
||||
---@param opts? NotifyOpts
|
||||
function M.error(msg, opts)
|
||||
M.notify(msg, vim.tbl_extend("keep", { level = vim.log.levels.ERROR }, opts or {}))
|
||||
end
|
||||
|
||||
---@generic F: fun()
|
||||
---@param ms number
|
||||
---@param fn F
|
||||
---@return F
|
||||
function M.debounce(ms, fn)
|
||||
local timer = (vim.uv or vim.loop).new_timer()
|
||||
return function(...)
|
||||
local args = { ... }
|
||||
timer:start(
|
||||
ms,
|
||||
0,
|
||||
vim.schedule_wrap(function()
|
||||
fn(args)
|
||||
end)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
---@param opts? {msg?: string}
|
||||
function M.try(fn, opts)
|
||||
local ok, err = pcall(fn)
|
||||
if not ok then
|
||||
local msg = opts and opts.msg or "Something went wrong:"
|
||||
msg = msg .. "\n" .. err
|
||||
M.error(msg)
|
||||
end
|
||||
end
|
||||
|
||||
---@param buf number
|
||||
---@param row number
|
||||
---@param ns number
|
||||
---@param col number
|
||||
---@param opts vim.api.keyset.set_extmark
|
||||
---@param debug_info? any
|
||||
function M.set_extmark(buf, ns, row, col, opts, debug_info)
|
||||
local ok, err = pcall(vim.api.nvim_buf_set_extmark, buf, ns, row, col, opts)
|
||||
if not ok then
|
||||
M.error(
|
||||
"Failed to set extmark for preview:\n"
|
||||
.. vim.inspect({ info = debug_info, row = row, col = col, opts = opts, error = err })
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
---@param n number buffer or window number
|
||||
---@param type "win" | "buf"
|
||||
---@param opts vim.wo | vim.bo
|
||||
local function set_opts(n, type, opts)
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
for k, v in pairs(opts or {}) do
|
||||
---@diagnostic disable-next-line: no-unknown
|
||||
pcall(vim.api.nvim_set_option_value, k, v, type == "win" and {
|
||||
scope = "local",
|
||||
win = n,
|
||||
} or { buf = n })
|
||||
end
|
||||
end
|
||||
|
||||
---@param win number
|
||||
---@param opts vim.wo
|
||||
function M.wo(win, opts)
|
||||
set_opts(win, "win", opts)
|
||||
end
|
||||
|
||||
---@param buf number
|
||||
---@param opts vim.bo
|
||||
function M.bo(buf, opts)
|
||||
set_opts(buf, "buf", opts)
|
||||
end
|
||||
|
||||
local trace_level = 0
|
||||
---@param msg? string
|
||||
---@param ...? any
|
||||
function M.trace(msg, ...)
|
||||
if not msg then
|
||||
trace_level = trace_level - 1
|
||||
return
|
||||
end
|
||||
trace_level = math.max(trace_level, 0)
|
||||
M.debug(msg, ...)
|
||||
trace_level = trace_level + 1
|
||||
end
|
||||
|
||||
---@param msg? string
|
||||
---@param ...? any
|
||||
function M.debug(msg, ...)
|
||||
if not require("which-key.config").debug then
|
||||
return
|
||||
end
|
||||
local data = { ... }
|
||||
if #data == 0 then
|
||||
data = nil
|
||||
elseif #data == 1 then
|
||||
data = data[1]
|
||||
end
|
||||
if type(data) == "function" then
|
||||
data = data()
|
||||
end
|
||||
if type(data) == "table" then
|
||||
data = table.concat(
|
||||
vim.tbl_map(function(value)
|
||||
return type(value) == "string" and value or vim.inspect(value):gsub("%s+", " ")
|
||||
end, data),
|
||||
" "
|
||||
)
|
||||
end
|
||||
if data and type(data) ~= "string" then
|
||||
data = vim.inspect(data):gsub("%s+", " ")
|
||||
end
|
||||
msg = data and ("%s: %s"):format(msg, data) or msg
|
||||
msg = string.rep(" ", trace_level) .. msg
|
||||
M.log(msg .. "\n")
|
||||
end
|
||||
|
||||
function M.log(msg)
|
||||
local file = "./wk.log"
|
||||
local fd = io.open(file, "a+")
|
||||
if not fd then
|
||||
error(("Could not open file %s for writing"):format(file))
|
||||
end
|
||||
fd:write(msg)
|
||||
fd:close()
|
||||
end
|
||||
|
||||
--- Returns a function that returns true if the cooldown is active.
|
||||
--- The cooldown will be active for the given duration or 0 if no duration is given.
|
||||
--- Runs in the main loop.
|
||||
--- cooldown(true) will wait till the next tick.
|
||||
---@return fun(cooldown?: number|boolean): boolean
|
||||
function M.cooldown()
|
||||
local waiting = false
|
||||
---@param cooldown? number|boolean
|
||||
return function(cooldown)
|
||||
if waiting then
|
||||
return true
|
||||
elseif cooldown then
|
||||
waiting = true
|
||||
vim.defer_fn(function()
|
||||
waiting = false
|
||||
end, type(cooldown) == "number" and cooldown or 0)
|
||||
end
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
---@generic T: table
|
||||
---@param t T
|
||||
---@param fields string[]
|
||||
---@return T
|
||||
function M.getters(t, fields)
|
||||
local getters = {} ---@type table<string, fun():any>
|
||||
for _, prop in ipairs(fields) do
|
||||
if type(t[prop]) == "function" then
|
||||
getters[prop] = t[prop]
|
||||
rawset(t, prop, nil)
|
||||
end
|
||||
end
|
||||
|
||||
if not vim.tbl_isempty(getters) then
|
||||
setmetatable(t, {
|
||||
__index = function(_, key)
|
||||
if getters[key] then
|
||||
return getters[key](t)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@ -1,349 +1,508 @@
|
||||
local Keys = require("which-key.keys")
|
||||
local config = require("which-key.config")
|
||||
local Buf = require("which-key.buf")
|
||||
local Config = require("which-key.config")
|
||||
local Icons = require("which-key.icons")
|
||||
local Layout = require("which-key.layout")
|
||||
local Plugins = require("which-key.plugins")
|
||||
local State = require("which-key.state")
|
||||
local Text = require("which-key.text")
|
||||
local Tree = require("which-key.tree")
|
||||
local Util = require("which-key.util")
|
||||
local Win = require("which-key.win")
|
||||
|
||||
local highlight = vim.api.nvim_buf_add_highlight
|
||||
|
||||
---@class View
|
||||
local M = {}
|
||||
M.view = nil ---@type wk.Win?
|
||||
M.footer = nil ---@type wk.Win?
|
||||
M.timer = (vim.uv or vim.loop).new_timer()
|
||||
|
||||
M.keys = ""
|
||||
M.mode = "n"
|
||||
M.reg = nil
|
||||
M.auto = false
|
||||
M.count = 0
|
||||
M.buf = nil
|
||||
M.win = nil
|
||||
---@alias wk.Sorter fun(node:wk.Item): (string|number)
|
||||
|
||||
function M.is_valid()
|
||||
return M.buf
|
||||
and M.win
|
||||
and vim.api.nvim_buf_is_valid(M.buf)
|
||||
and vim.api.nvim_buf_is_loaded(M.buf)
|
||||
and vim.api.nvim_win_is_valid(M.win)
|
||||
end
|
||||
---@type table<string, wk.Sorter>
|
||||
M.fields = {
|
||||
order = function(item)
|
||||
return item.order and item.order or 1000
|
||||
end,
|
||||
["local"] = function(item)
|
||||
return item.keymap and item.keymap.buffer ~= 0 and 0 or 1000
|
||||
end,
|
||||
manual = function(item)
|
||||
return item.mapping and item.mapping.idx or 10000
|
||||
end,
|
||||
desc = function(item)
|
||||
return item.desc or "~"
|
||||
end,
|
||||
group = function(item)
|
||||
return item.group and 1 or 0
|
||||
end,
|
||||
alphanum = function(item)
|
||||
return item.key:find("^%w+$") and 0 or 1
|
||||
end,
|
||||
mod = function(item)
|
||||
return item.key:find("^<.*>$") and 0 or 1
|
||||
end,
|
||||
case = function(item)
|
||||
return item.key:lower() == item.key and 0 or 1
|
||||
end,
|
||||
natural = function(item)
|
||||
local ret = item.key:gsub("%d+", function(d)
|
||||
return ("%09d"):format(tonumber(d))
|
||||
end)
|
||||
return ret:lower()
|
||||
end,
|
||||
}
|
||||
|
||||
function M.show()
|
||||
if vim.b.visual_multi then
|
||||
vim.b.VM_skip_reset_once_on_bufleave = true
|
||||
end
|
||||
if M.is_valid() then
|
||||
return
|
||||
end
|
||||
|
||||
-- non-floating windows
|
||||
local wins = vim.tbl_filter(function(w)
|
||||
return vim.api.nvim_win_is_valid(w) and vim.api.nvim_win_get_config(w).relative == ""
|
||||
end, vim.api.nvim_list_wins())
|
||||
|
||||
---@type number[]
|
||||
local margins = {}
|
||||
for i, m in ipairs(config.options.window.margin) do
|
||||
if m > 0 and m < 1 then
|
||||
if i % 2 == 0 then
|
||||
m = math.floor(vim.o.columns * m)
|
||||
else
|
||||
m = math.floor(vim.o.lines * m)
|
||||
---@param lhs string
|
||||
function M.format(lhs)
|
||||
local keys = Util.keys(lhs)
|
||||
local ret = vim.tbl_map(function(key)
|
||||
local inner = key:match("^<(.*)>$")
|
||||
if not inner then
|
||||
return key
|
||||
end
|
||||
if inner == "NL" then
|
||||
inner = "C-J"
|
||||
end
|
||||
local parts = vim.split(inner, "-", { plain = true })
|
||||
for i, part in ipairs(parts) do
|
||||
if i == 1 or i ~= #parts or not part:match("^%w$") then
|
||||
parts[i] = Config.icons.keys[part] or parts[i]
|
||||
end
|
||||
end
|
||||
margins[i] = m
|
||||
end
|
||||
|
||||
local opts = {
|
||||
relative = "editor",
|
||||
width = vim.o.columns - margins[2] - margins[4],
|
||||
height = config.options.layout.height.min,
|
||||
focusable = false,
|
||||
anchor = "SW",
|
||||
border = config.options.window.border,
|
||||
row = vim.o.lines
|
||||
- margins[3]
|
||||
+ ((vim.o.laststatus == 0 or vim.o.laststatus == 1 and #wins == 1) and 1 or 0)
|
||||
- vim.o.cmdheight,
|
||||
col = margins[4],
|
||||
style = "minimal",
|
||||
noautocmd = true,
|
||||
zindex = config.options.window.zindex,
|
||||
}
|
||||
if config.options.window.position == "top" then
|
||||
opts.anchor = "NW"
|
||||
opts.row = margins[1]
|
||||
end
|
||||
M.buf = vim.api.nvim_create_buf(false, true)
|
||||
M.win = vim.api.nvim_open_win(M.buf, false, opts)
|
||||
vim.bo[M.buf].filetype = "WhichKey"
|
||||
vim.bo[M.buf].buftype = "nofile"
|
||||
vim.bo[M.buf].bufhidden = "wipe"
|
||||
vim.bo[M.buf].modifiable = true
|
||||
vim.wo[M.win].winhighlight = "NormalFloat:WhichKeyFloat,FloatBorder:WhichKeyBorder"
|
||||
vim.wo[M.win].foldmethod = "manual"
|
||||
vim.wo[M.win].winblend = config.options.window.winblend
|
||||
return table.concat(parts, "")
|
||||
end, keys)
|
||||
return table.concat(ret, "")
|
||||
end
|
||||
|
||||
function M.read_pending()
|
||||
local esc = ""
|
||||
while true do
|
||||
local n = vim.fn.getchar(0)
|
||||
if n == 0 then
|
||||
break
|
||||
end
|
||||
local c = (type(n) == "number" and vim.fn.nr2char(n) or n)
|
||||
|
||||
-- HACK: for some reason, when executing a :norm command,
|
||||
-- vim keeps feeding <esc> at the end
|
||||
if c == Util.t("<esc>") then
|
||||
esc = esc .. c
|
||||
-- more than 10 <esc> in a row? most likely the norm bug
|
||||
if #esc > 10 then
|
||||
return
|
||||
end
|
||||
else
|
||||
-- we have <esc> characters, so add them to keys
|
||||
if esc ~= "" then
|
||||
M.keys = M.keys .. esc
|
||||
esc = ""
|
||||
end
|
||||
M.keys = M.keys .. c
|
||||
end
|
||||
end
|
||||
if esc ~= "" then
|
||||
M.keys = M.keys .. esc
|
||||
esc = ""
|
||||
end
|
||||
end
|
||||
|
||||
function M.getchar()
|
||||
local ok, n = pcall(vim.fn.getchar)
|
||||
|
||||
-- bail out on keyboard interrupt
|
||||
if not ok then
|
||||
return Util.t("<esc>")
|
||||
end
|
||||
|
||||
local c = (type(n) == "number" and vim.fn.nr2char(n) or n)
|
||||
return c
|
||||
end
|
||||
|
||||
function M.scroll(up)
|
||||
local height = vim.api.nvim_win_get_height(M.win)
|
||||
local cursor = vim.api.nvim_win_get_cursor(M.win)
|
||||
if up then
|
||||
cursor[1] = math.max(cursor[1] - height, 1)
|
||||
else
|
||||
cursor[1] = math.min(cursor[1] + height, vim.api.nvim_buf_line_count(M.buf))
|
||||
end
|
||||
vim.api.nvim_win_set_cursor(M.win, cursor)
|
||||
end
|
||||
|
||||
function M.on_close()
|
||||
M.hide()
|
||||
end
|
||||
|
||||
function M.hide()
|
||||
vim.api.nvim_echo({ { "" } }, false, {})
|
||||
M.hide_cursor()
|
||||
if M.buf and vim.api.nvim_buf_is_valid(M.buf) then
|
||||
vim.api.nvim_buf_delete(M.buf, { force = true })
|
||||
M.buf = nil
|
||||
end
|
||||
if M.win and vim.api.nvim_win_is_valid(M.win) then
|
||||
vim.api.nvim_win_close(M.win, true)
|
||||
M.win = nil
|
||||
end
|
||||
vim.cmd("redraw")
|
||||
end
|
||||
|
||||
function M.show_cursor()
|
||||
local buf = vim.api.nvim_get_current_buf()
|
||||
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||
vim.api.nvim_buf_add_highlight(buf, config.namespace, "Cursor", cursor[1] - 1, cursor[2], cursor[2] + 1)
|
||||
end
|
||||
|
||||
function M.hide_cursor()
|
||||
local buf = vim.api.nvim_get_current_buf()
|
||||
vim.api.nvim_buf_clear_namespace(buf, config.namespace, 0, -1)
|
||||
end
|
||||
|
||||
function M.back()
|
||||
local node = Keys.get_tree(M.mode, M.buf).tree:get(M.keys, -1) or Keys.get_tree(M.mode).tree:get(M.keys, -1)
|
||||
if node then
|
||||
M.keys = node.prefix_i
|
||||
end
|
||||
end
|
||||
|
||||
function M.execute(prefix_i, mode, buf)
|
||||
local global_node = Keys.get_tree(mode).tree:get(prefix_i)
|
||||
local buf_node = buf and Keys.get_tree(mode, buf).tree:get(prefix_i) or nil
|
||||
|
||||
if global_node and global_node.mapping and Keys.is_hook(prefix_i, global_node.mapping.cmd) then
|
||||
return
|
||||
end
|
||||
if buf_node and buf_node.mapping and Keys.is_hook(prefix_i, buf_node.mapping.cmd) then
|
||||
return
|
||||
end
|
||||
|
||||
local hooks = {}
|
||||
|
||||
local function unhook(nodes, nodes_buf)
|
||||
for _, node in pairs(nodes) do
|
||||
if Keys.is_hooked(node.mapping.prefix, mode, nodes_buf) then
|
||||
table.insert(hooks, { node.mapping.prefix, nodes_buf })
|
||||
Keys.hook_del(node.mapping.prefix, mode, nodes_buf)
|
||||
---@param nodes wk.Item[]
|
||||
---@param fields? (string|wk.Sorter)[]
|
||||
function M.sort(nodes, fields)
|
||||
fields = vim.deepcopy(fields or Config.sort)
|
||||
vim.list_extend(fields, { "natural", "case" })
|
||||
table.sort(nodes, function(a, b)
|
||||
for _, f in ipairs(fields) do
|
||||
local field = type(f) == "function" and f or M.fields[f]
|
||||
if field then
|
||||
local aa = field(a)
|
||||
local bb = field(b)
|
||||
if aa ~= bb then
|
||||
return aa < bb
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- make sure we remove all WK hooks before executing the sequence
|
||||
-- this is to make existing keybindings work and prevent recursion
|
||||
unhook(Keys.get_tree(mode).tree:path(prefix_i))
|
||||
if buf then
|
||||
unhook(Keys.get_tree(mode, buf).tree:path(prefix_i), buf)
|
||||
end
|
||||
|
||||
-- feed CTRL-O again if called from CTRL-O
|
||||
local full_mode = Util.get_mode()
|
||||
if full_mode == "nii" or full_mode == "nir" or full_mode == "niv" or full_mode == "vs" then
|
||||
vim.api.nvim_feedkeys(Util.t("<C-O>"), "n", false)
|
||||
end
|
||||
|
||||
-- handle registers that were passed when opening the popup
|
||||
if M.reg ~= '"' and M.mode ~= "i" and M.mode ~= "c" then
|
||||
vim.api.nvim_feedkeys('"' .. M.reg, "n", false)
|
||||
end
|
||||
|
||||
if M.count and M.count ~= 0 then
|
||||
prefix_i = M.count .. prefix_i
|
||||
end
|
||||
|
||||
-- feed the keys with remap
|
||||
vim.api.nvim_feedkeys(prefix_i, "m", true)
|
||||
|
||||
-- defer hooking WK until after the keys were executed
|
||||
vim.defer_fn(function()
|
||||
for _, hook in pairs(hooks) do
|
||||
Keys.hook_add(hook[1], mode, hook[2])
|
||||
end
|
||||
end, 0)
|
||||
end
|
||||
|
||||
function M.open(keys, opts)
|
||||
opts = opts or {}
|
||||
M.keys = keys or ""
|
||||
M.mode = opts.mode or Util.get_mode()
|
||||
M.count = vim.api.nvim_get_vvar("count")
|
||||
M.reg = vim.api.nvim_get_vvar("register")
|
||||
|
||||
if string.find(vim.o.clipboard, "unnamedplus") and M.reg == "+" then
|
||||
M.reg = '"'
|
||||
end
|
||||
|
||||
if string.find(vim.o.clipboard, "unnamed") and M.reg == "*" then
|
||||
M.reg = '"'
|
||||
end
|
||||
|
||||
M.show_cursor()
|
||||
M.on_keys(opts)
|
||||
end
|
||||
|
||||
function M.is_enabled(buf)
|
||||
local buftype = vim.bo[buf].buftype
|
||||
for _, bt in ipairs(config.options.disable.buftypes) do
|
||||
if bt == buftype then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local filetype = vim.bo[buf].filetype
|
||||
for _, bt in ipairs(config.options.disable.filetypes) do
|
||||
if bt == filetype then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if vim.fn.getcmdwintype() ~= "" then
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function M.on_keys(opts)
|
||||
local buf = vim.api.nvim_get_current_buf()
|
||||
|
||||
while true do
|
||||
-- loop
|
||||
M.read_pending()
|
||||
|
||||
local results = Keys.get_mappings(M.mode, M.keys, buf)
|
||||
|
||||
--- Check for an exact match. Feedkeys with remap
|
||||
if results.mapping and not results.mapping.group and #results.mappings == 0 then
|
||||
M.hide()
|
||||
if results.mapping.fn then
|
||||
results.mapping.fn()
|
||||
else
|
||||
M.execute(M.keys, M.mode, buf)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
-- Check for no mappings found. Feedkeys without remap
|
||||
if #results.mappings == 0 then
|
||||
M.hide()
|
||||
-- only execute if an actual key was typed while WK was open
|
||||
if opts.auto then
|
||||
M.execute(M.keys, M.mode, buf)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local layout = Layout:new(results)
|
||||
|
||||
if M.is_enabled(buf) then
|
||||
if not M.is_valid() then
|
||||
M.show()
|
||||
end
|
||||
|
||||
M.render(layout:layout(M.win))
|
||||
end
|
||||
vim.cmd([[redraw]])
|
||||
|
||||
local c = M.getchar()
|
||||
|
||||
if c == Util.t("<esc>") then
|
||||
M.hide()
|
||||
break
|
||||
elseif c == Util.t(config.options.popup_mappings.scroll_down) then
|
||||
M.scroll(false)
|
||||
elseif c == Util.t(config.options.popup_mappings.scroll_up) then
|
||||
M.scroll(true)
|
||||
elseif c == Util.t("<bs>") then
|
||||
M.back()
|
||||
else
|
||||
M.keys = M.keys .. c
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param text Text
|
||||
function M.render(text)
|
||||
local view = vim.api.nvim_win_call(M.win, vim.fn.winsaveview)
|
||||
vim.api.nvim_buf_set_lines(M.buf, 0, -1, false, text.lines)
|
||||
local height = #text.lines
|
||||
if height > config.options.layout.height.max then
|
||||
height = config.options.layout.height.max
|
||||
end
|
||||
vim.api.nvim_win_set_height(M.win, height)
|
||||
if vim.api.nvim_buf_is_valid(M.buf) then
|
||||
vim.api.nvim_buf_clear_namespace(M.buf, config.namespace, 0, -1)
|
||||
end
|
||||
for _, data in ipairs(text.hl) do
|
||||
highlight(M.buf, config.namespace, data.group, data.line, data.from, data.to)
|
||||
end
|
||||
vim.api.nvim_win_call(M.win, function()
|
||||
vim.fn.winrestview(view)
|
||||
return a.raw_key < b.raw_key
|
||||
end)
|
||||
end
|
||||
|
||||
function M.valid()
|
||||
return M.view and M.view:valid()
|
||||
end
|
||||
|
||||
---@param opts? {delay?: number, schedule?: boolean, waited?: number}
|
||||
function M.update(opts)
|
||||
local state = State.state
|
||||
|
||||
if not state then
|
||||
M.hide()
|
||||
return
|
||||
end
|
||||
|
||||
opts = opts or {}
|
||||
if M.valid() then
|
||||
M.show()
|
||||
elseif opts.schedule ~= false then
|
||||
local delay = opts.delay
|
||||
or State.delay({
|
||||
mode = state.mode.mode,
|
||||
keys = state.node.keys,
|
||||
plugin = state.node.plugin,
|
||||
waited = opts.waited,
|
||||
})
|
||||
M.timer:start(
|
||||
delay,
|
||||
0,
|
||||
vim.schedule_wrap(function()
|
||||
Util.try(M.show)
|
||||
end)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function M.hide()
|
||||
if M.view then
|
||||
M.view:hide()
|
||||
M.view = nil
|
||||
end
|
||||
if M.footer then
|
||||
M.footer:hide()
|
||||
M.footer = nil
|
||||
end
|
||||
end
|
||||
|
||||
---@param field string
|
||||
---@param value string
|
||||
---@return string
|
||||
function M.replace(field, value)
|
||||
for _, repl in pairs(Config.replace[field]) do
|
||||
value = type(repl) == "function" and (repl(value) or value) or value:gsub(repl[1], repl[2])
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
---@param node wk.Node
|
||||
function M.icon(node)
|
||||
-- plugin items should not get icons
|
||||
if node.parent and node.parent.plugin then
|
||||
return
|
||||
end
|
||||
if node.mapping and node.mapping.icon then
|
||||
return Icons.get(node.mapping.icon)
|
||||
end
|
||||
local icon, icon_hl = Icons.get({ keymap = node.keymap, desc = node.desc })
|
||||
if icon then
|
||||
return icon, icon_hl
|
||||
end
|
||||
if node.parent then
|
||||
return M.icon(node.parent)
|
||||
end
|
||||
end
|
||||
|
||||
---@param node wk.Node
|
||||
---@param opts? {default?: "count"|"path", parent?: wk.Node, group?: boolean}
|
||||
function M.item(node, opts)
|
||||
opts = opts or {}
|
||||
opts.default = opts.default or "count"
|
||||
local child_count = (node:can_expand() or opts.group == false) and 0 or node:count()
|
||||
local desc = node.desc
|
||||
if not desc and node.keymap and node.keymap.rhs ~= "" and type(node.keymap.rhs) == "string" then
|
||||
desc = node.keymap.rhs --[[@as string]]
|
||||
end
|
||||
if not desc and opts.default == "count" and child_count > 0 then
|
||||
desc = child_count .. " keymap" .. (child_count > 1 and "s" or "")
|
||||
end
|
||||
if not desc and opts.default == "path" then
|
||||
desc = node.keys
|
||||
end
|
||||
desc = M.replace("desc", desc or "")
|
||||
local icon, icon_hl = M.icon(node)
|
||||
|
||||
local raw_key = node.key
|
||||
if opts.parent and opts.parent ~= node and node.keys:find(opts.parent.keys, 1, true) == 1 then
|
||||
raw_key = node.keys:sub(opts.parent.keys:len() + 1)
|
||||
end
|
||||
|
||||
local group = node:is_group()
|
||||
---@type wk.Item
|
||||
return setmetatable({
|
||||
node = node,
|
||||
icon = icon or "",
|
||||
icon_hl = icon_hl,
|
||||
key = M.replace("key", raw_key),
|
||||
raw_key = raw_key,
|
||||
desc = group and Config.icons.group .. desc or desc,
|
||||
group = group,
|
||||
}, { __index = node })
|
||||
end
|
||||
|
||||
---@param node wk.Node
|
||||
---@param opts? {title?: boolean}
|
||||
function M.trail(node, opts)
|
||||
opts = opts or {}
|
||||
|
||||
---@param group? string
|
||||
local function hl(group)
|
||||
return opts.title and "WhichKeyTitle" or (group and ("WhichKey" .. group) or "WhichKeyGroup")
|
||||
end
|
||||
|
||||
local trail = {} ---@type string[][]
|
||||
local did_op = false
|
||||
while node do
|
||||
local desc = node.desc and (Config.icons.group .. M.replace("desc", node.desc))
|
||||
or node.key and M.replace("key", node.key)
|
||||
or ""
|
||||
node = node.parent
|
||||
if desc ~= "" then
|
||||
if node and #trail > 0 then
|
||||
table.insert(trail, 1, { " " .. Config.icons.breadcrumb .. " ", hl("Separator") })
|
||||
end
|
||||
table.insert(trail, 1, { desc, hl() })
|
||||
end
|
||||
local m = State.state.mode.mode
|
||||
if not did_op and not node and (m == "x" or m == "o") then
|
||||
did_op = true
|
||||
local mode = Buf.get({ buf = State.state.mode.buf.buf, mode = "n" })
|
||||
if mode then
|
||||
node = mode.tree:find(m == "x" and "v" or vim.v.operator)
|
||||
end
|
||||
end
|
||||
end
|
||||
if #trail > 0 then
|
||||
table.insert(trail, 1, { " ", hl() })
|
||||
table.insert(trail, { " ", hl() })
|
||||
return trail
|
||||
end
|
||||
end
|
||||
|
||||
---@param root wk.Node
|
||||
---@param node wk.Node
|
||||
---@param expand fun(node:wk.Node): boolean
|
||||
---@param filter fun(node:wk.Node): boolean
|
||||
---@param ret? wk.Item[]
|
||||
function M.expand(root, node, expand, filter, ret)
|
||||
ret = ret or {}
|
||||
if not filter(node) then
|
||||
return ret
|
||||
end
|
||||
if not node:is_plugin() and expand(node) then
|
||||
if node.keymap then
|
||||
ret[#ret + 1] = M.item(node, { group = false, parent = root })
|
||||
end
|
||||
for _, child in ipairs(node:children()) do
|
||||
M.expand(root, child, expand, filter, ret)
|
||||
end
|
||||
else
|
||||
ret[#ret + 1] = M.item(node, { parent = root })
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
function M.show()
|
||||
local state = State.state
|
||||
if not (state and state.show and state.node:is_group()) then
|
||||
M.hide()
|
||||
return
|
||||
end
|
||||
local text = Text.new()
|
||||
|
||||
---@type wk.Node[]
|
||||
local children = state.node:children()
|
||||
|
||||
if state.filter.global == false and state.filter.expand == nil then
|
||||
state.filter.expand = true
|
||||
end
|
||||
|
||||
---@param node wk.Node
|
||||
local function filter(node)
|
||||
local l = state.filter["local"] ~= false
|
||||
local g = state.filter.global ~= false
|
||||
if not g and not l then
|
||||
return false
|
||||
end
|
||||
if g and l then
|
||||
return true
|
||||
end
|
||||
local is_local = node:is_local()
|
||||
return l and is_local or g and not is_local
|
||||
end
|
||||
|
||||
---@param node wk.Node
|
||||
local function expand(node)
|
||||
if node:is_plugin() then
|
||||
return false
|
||||
end
|
||||
if state.filter.expand then
|
||||
return true
|
||||
end
|
||||
if node:can_expand() then
|
||||
return false
|
||||
end
|
||||
if type(Config.expand) == "function" then
|
||||
return Config.expand(node)
|
||||
end
|
||||
local child_count = node:count()
|
||||
return child_count > 0 and child_count <= Config.expand
|
||||
end
|
||||
|
||||
---@type wk.Item[]
|
||||
local items = {}
|
||||
for _, node in ipairs(children) do
|
||||
vim.list_extend(items, M.expand(state.node, node, expand, filter))
|
||||
end
|
||||
|
||||
M.sort(items)
|
||||
|
||||
---@type wk.Col[]
|
||||
local cols = {
|
||||
{ key = "key", hl = "WhichKey", align = "right" },
|
||||
{ key = "sep", hl = "WhichKeySeparator", default = Config.icons.separator },
|
||||
{ key = "icon", padding = { 0, 0 } },
|
||||
}
|
||||
if state.node.plugin then
|
||||
vim.list_extend(cols, Plugins.cols(state.node.plugin))
|
||||
end
|
||||
cols[#cols + 1] = { key = "desc", width = math.huge }
|
||||
|
||||
local t = Layout.new({ cols = cols, rows = items })
|
||||
|
||||
local opts = Win.defaults(Config.win)
|
||||
local container = {
|
||||
width = Layout.dim(vim.o.columns, vim.o.columns, opts.width),
|
||||
height = Layout.dim(vim.o.lines, vim.o.lines, opts.height),
|
||||
}
|
||||
local _, _, max_row_width = t:cells()
|
||||
local box_width = Layout.dim(max_row_width, container.width, Config.layout.width)
|
||||
local box_count = math.max(math.floor(container.width / (box_width + Config.layout.spacing)), 1)
|
||||
box_width = math.floor(container.width / box_count)
|
||||
local box_height = math.max(math.ceil(#items / box_count), 2)
|
||||
|
||||
local rows = t:layout({ width = box_width - Config.layout.spacing })
|
||||
|
||||
for _ = 1, Config.win.padding[1] + 1 do
|
||||
text:nl()
|
||||
end
|
||||
|
||||
for l = 1, box_height do
|
||||
text:append(string.rep(" ", Config.win.padding[2]))
|
||||
for b = 1, box_count do
|
||||
local i = (b - 1) * box_height + l
|
||||
local item = items[i]
|
||||
local row = rows[i]
|
||||
if b ~= 1 or box_count > 1 then
|
||||
text:append(string.rep(" ", Config.layout.spacing))
|
||||
end
|
||||
if item then
|
||||
for c, col in ipairs(row) do
|
||||
local hl = col.hl
|
||||
if cols[c].key == "desc" then
|
||||
hl = item.group and "WhichKeyGroup" or "WhichKeyDesc"
|
||||
end
|
||||
if cols[c].key == "icon" then
|
||||
hl = item.icon_hl
|
||||
end
|
||||
text:append(col.value, hl)
|
||||
end
|
||||
end
|
||||
end
|
||||
text:append(string.rep(" ", Config.win.padding[2]))
|
||||
text:nl()
|
||||
end
|
||||
text:trim()
|
||||
|
||||
for _ = 1, Config.win.padding[1] do
|
||||
text:nl()
|
||||
end
|
||||
|
||||
local show_keys = Config.show_keys
|
||||
|
||||
local has_border = opts.border and opts.border ~= "none"
|
||||
if has_border then
|
||||
if opts.title == true then
|
||||
opts.title = M.trail(state.node, { title = true })
|
||||
show_keys = false
|
||||
end
|
||||
if opts.footer == true then
|
||||
opts.footer = M.trail(state.node, { title = true })
|
||||
show_keys = false
|
||||
end
|
||||
if not opts.title then
|
||||
opts.title = ""
|
||||
opts.title_pos = nil
|
||||
end
|
||||
if not opts.footer then
|
||||
opts.footer = ""
|
||||
opts.footer_pos = nil
|
||||
end
|
||||
else
|
||||
opts.footer = nil
|
||||
opts.footer_pos = nil
|
||||
opts.title = nil
|
||||
opts.title_pos = nil
|
||||
end
|
||||
|
||||
local bw = has_border and 2 or 0
|
||||
|
||||
opts.width = Layout.dim(text:width() + bw, vim.o.columns, opts.width)
|
||||
opts.height = Layout.dim(text:height() + bw, vim.o.lines, opts.height)
|
||||
|
||||
if Config.show_help then
|
||||
opts.height = opts.height + 1
|
||||
end
|
||||
|
||||
-- top-left
|
||||
opts.col = Layout.dim(opts.col, vim.o.columns - opts.width)
|
||||
opts.row = Layout.dim(opts.row, vim.o.lines - opts.height - vim.o.cmdheight)
|
||||
|
||||
opts.width = opts.width - bw
|
||||
opts.height = opts.height - bw
|
||||
M.check_overlap(opts)
|
||||
|
||||
M.view = M.view or Win.new(opts)
|
||||
M.view:show(opts)
|
||||
|
||||
if Config.show_help or show_keys then
|
||||
text:nl()
|
||||
local footer = Text.new()
|
||||
if show_keys then
|
||||
footer:append(" ")
|
||||
for _, segment in ipairs(M.trail(state.node) or {}) do
|
||||
footer:append(segment[1], segment[2])
|
||||
end
|
||||
end
|
||||
if Config.show_help then
|
||||
---@type {key: string, desc: string}[]
|
||||
local keys = {
|
||||
{ key = "<esc>", desc = "close" },
|
||||
}
|
||||
if state.node.parent then
|
||||
keys[#keys + 1] = { key = "<bs>", desc = "back" }
|
||||
end
|
||||
if opts.height < text:height() then
|
||||
keys[#keys + 1] = { key = "<c-d>/<c-u>", desc = "scroll" }
|
||||
end
|
||||
local help = Text.new()
|
||||
for k, key in ipairs(keys) do
|
||||
help:append(M.replace("key", Util.norm(key.key)), "WhichKey"):append(" " .. key.desc, "WhichKeySeparator")
|
||||
if k < #keys then
|
||||
help:append(" ")
|
||||
end
|
||||
end
|
||||
local col = footer:col({ display = true })
|
||||
local ws = string.rep(" ", math.floor((opts.width - help:width()) / 2) - col)
|
||||
footer:append(ws)
|
||||
footer:append(help._lines[1])
|
||||
end
|
||||
footer:trim()
|
||||
M.footer = M.footer or Win.new()
|
||||
M.footer:show({
|
||||
relative = "win",
|
||||
win = M.view.win,
|
||||
col = 0,
|
||||
row = opts.height - 1,
|
||||
width = opts.width,
|
||||
height = 1,
|
||||
zindex = M.view.opts.zindex + 1,
|
||||
})
|
||||
footer:render(M.footer.buf)
|
||||
end
|
||||
|
||||
text:render(M.view.buf)
|
||||
vim.api.nvim_win_call(M.view.win, function()
|
||||
vim.fn.winrestview({ topline = 1 })
|
||||
end)
|
||||
vim.cmd.redraw()
|
||||
end
|
||||
|
||||
---@param opts wk.Win.opts
|
||||
function M.check_overlap(opts)
|
||||
if Config.win.no_overlap == false then
|
||||
return
|
||||
end
|
||||
local row, col = vim.fn.screenrow(), vim.fn.screencol()
|
||||
local overlaps = (row >= opts.row and row <= opts.row + opts.height)
|
||||
and (col >= opts.col and col <= opts.col + opts.width)
|
||||
-- dd(overlaps and "overlaps" or "no overlap", {
|
||||
-- editor = { lines = vim.o.lines, columns = vim.o.columns },
|
||||
-- cursor = { col = col, row = row },
|
||||
-- win = { row = opts.row, col = opts.col, height = opts.height, width = opts.width },
|
||||
-- overlaps = overlaps,
|
||||
-- })
|
||||
if overlaps then
|
||||
opts.row = row + 1
|
||||
opts.height = math.max(vim.o.lines - opts.row, 4)
|
||||
end
|
||||
end
|
||||
|
||||
---@param up boolean
|
||||
function M.scroll(up)
|
||||
return M.view and M.view:scroll(up)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@ -1 +0,0 @@
|
||||
command! -nargs=* WhichKey lua require('which-key').show_command(<f-args>)
|
||||
@ -1 +1,4 @@
|
||||
std="lua51+vim"
|
||||
std="vim"
|
||||
|
||||
[lints]
|
||||
mixed_table="allow"
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
indent_type = "Spaces"
|
||||
indent_width = 2
|
||||
column_width = 120
|
||||
column_width = 120
|
||||
[sort_requires]
|
||||
enabled = true
|
||||
|
||||
|
||||
@ -1,2 +1,21 @@
|
||||
[selene]
|
||||
base = "lua51"
|
||||
name = "vim"
|
||||
|
||||
[vim]
|
||||
any = true
|
||||
|
||||
[jit]
|
||||
any = true
|
||||
|
||||
[assert]
|
||||
any = true
|
||||
|
||||
[describe]
|
||||
any = true
|
||||
|
||||
[it]
|
||||
any = true
|
||||
|
||||
[before_each.args]
|
||||
any = true
|
||||
|
||||
Reference in New Issue
Block a user