Update generated neovim config
This commit is contained in:
@ -0,0 +1,7 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
3
config/neovim/store/lazy-plugins/which-key.nvim/.github/.release-please-manifest.json
vendored
Normal file
3
config/neovim/store/lazy-plugins/which-key.nvim/.github/.release-please-manifest.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
".": "3.13.2"
|
||||
}
|
||||
5
config/neovim/store/lazy-plugins/which-key.nvim/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
config/neovim/store/lazy-plugins/which-key.nvim/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Ask a question
|
||||
url: https://github.com/folke/which-key.nvim/discussions
|
||||
about: Use Github discussions instead
|
||||
16
config/neovim/store/lazy-plugins/which-key.nvim/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
16
config/neovim/store/lazy-plugins/which-key.nvim/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
## Description
|
||||
|
||||
<!-- Describe the big picture of your changes to communicate to the maintainers
|
||||
why we should accept this pull request. -->
|
||||
|
||||
## Related Issue(s)
|
||||
|
||||
<!--
|
||||
If this PR fixes any issues, please link to the issue here.
|
||||
- Fixes #<issue_number>
|
||||
-->
|
||||
|
||||
## Screenshots
|
||||
|
||||
<!-- Add screenshots of the changes if applicable. -->
|
||||
|
||||
6
config/neovim/store/lazy-plugins/which-key.nvim/.github/dependabot.yml
vendored
Normal file
6
config/neovim/store/lazy-plugins/which-key.nvim/.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
9
config/neovim/store/lazy-plugins/which-key.nvim/.github/release-please-config.json
vendored
Normal file
9
config/neovim/store/lazy-plugins/which-key.nvim/.github/release-please-config.json
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
|
||||
"packages": {
|
||||
".": {
|
||||
"release-type": "simple",
|
||||
"extra-files": ["lua/which-key/config.lua"]
|
||||
}
|
||||
}
|
||||
}
|
||||
8
config/neovim/store/lazy-plugins/which-key.nvim/.github/workflows/labeler.yml
vendored
Normal file
8
config/neovim/store/lazy-plugins/which-key.nvim/.github/workflows/labeler.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
name: "PR Labeler"
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
uses: folke/github/.github/workflows/labeler.yml@main
|
||||
secrets: inherit
|
||||
18
config/neovim/store/lazy-plugins/which-key.nvim/.github/workflows/pr.yml
vendored
Normal file
18
config/neovim/store/lazy-plugins/which-key.nvim/.github/workflows/pr.yml
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
name: PR Title
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
- reopened
|
||||
- ready_for_review
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
pr-title:
|
||||
uses: folke/github/.github/workflows/pr.yml@main
|
||||
secrets: inherit
|
||||
11
config/neovim/store/lazy-plugins/which-key.nvim/.github/workflows/stale.yml
vendored
Normal file
11
config/neovim/store/lazy-plugins/which-key.nvim/.github/workflows/stale.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
name: Stale Issues & PRs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
if: contains(fromJSON('["folke", "LazyVim"]'), github.repository_owner)
|
||||
uses: folke/github/.github/workflows/stale.yml@main
|
||||
secrets: inherit
|
||||
13
config/neovim/store/lazy-plugins/which-key.nvim/.github/workflows/update.yml
vendored
Normal file
13
config/neovim/store/lazy-plugins/which-key.nvim/.github/workflows/update.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
name: Update Repo
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# Run every hour
|
||||
- cron: "0 * * * *"
|
||||
|
||||
jobs:
|
||||
update:
|
||||
if: contains(fromJSON('["folke", "LazyVim"]'), github.repository_owner)
|
||||
uses: folke/github/.github/workflows/update.yml@main
|
||||
secrets: inherit
|
||||
35
config/neovim/store/lazy-plugins/which-key.nvim/NEWS.md
Normal file
35
config/neovim/store/lazy-plugins/which-key.nvim/NEWS.md
Normal file
@ -0,0 +1,35 @@
|
||||
# 💥 What's New in 3.0?
|
||||
|
||||
Major update for [which-key.nvim](https://github.com/folke/which-key.nvim)! This release includes a complete rewrite and several new features.
|
||||
**which-key** was my very first plugin, so it was time for a fresh start. 🎉
|
||||
|
||||
- ✨ **Full Rewrite**: Improved performance and functionality.
|
||||
- 👀 **Visual & Operator Pending Mode Integration**: Now uses `ModeChanged`, eliminating the need for operator remappings.
|
||||
- 🔧 **Simplified Mappings**: Removed obscure secret mappings.
|
||||
- 🔒 **Safer Auto Triggers**: Auto triggers are now never created for single keys apart from `g` and `z`. All other letters are unsafe.
|
||||
- ⏱️ **Delay**: Set delay independently of `timeoutlen`.
|
||||
- 🛠️ **Layout**:
|
||||
- Presets: `classic`, `modern`, and `helix`.
|
||||
- Enable/disable which-key for specific modes.
|
||||
- Configurable sorting with options like `local`, `order`, `group`, `alphanum`, `mod`, `lower`, `icase`, `desc`, and `manual`.
|
||||
- Expand groups with fewer keymaps.
|
||||
- Customizable string replacements for `key` and `desc`.
|
||||
- 🎨 **Icon Support**:
|
||||
- Auto-detect icons for keymaps using `lazy.nvim`.
|
||||
- Custom icon rules and specifications for mapping levels.
|
||||
- 🚫 **Never Get in the Way**: Avoids overlapping with the cursor.
|
||||
- 🗂️ **New Mapping Spec**: New and better mappings spec, more in line with `vim.keymap.set` and how you define keymaps with [lazy.nvim](https://github.com/folke/lazy.nvim)
|
||||
- 🐛 New Bugs: Lots of new and exciting bugs to discover! 🐞
|
||||
|
||||
## Screenshots
|
||||
|
||||
**Classic Mode**
|
||||

|
||||
|
||||
**Modern Mode**
|
||||

|
||||
|
||||
**Helix Mode**
|
||||

|
||||
|
||||
For detailed configuration and usage instructions, refer to the updated README.
|
||||
@ -0,0 +1,247 @@
|
||||
local Config = require("which-key.config")
|
||||
local Tree = require("which-key.tree")
|
||||
local Triggers = require("which-key.triggers")
|
||||
local Util = require("which-key.util")
|
||||
|
||||
---@class wk.Mode
|
||||
---@field buf wk.Buffer
|
||||
---@field mode string
|
||||
---@field tree wk.Tree
|
||||
---@field triggers wk.Node[]
|
||||
local Mode = {}
|
||||
Mode.__index = Mode
|
||||
|
||||
---@param node wk.Node
|
||||
local function is_special(node)
|
||||
return (node:is_plugin() or node:is_proxy()) and not node.keymap
|
||||
end
|
||||
|
||||
--- Checks if it's safe to add a trigger for the given node
|
||||
---@param node wk.Node
|
||||
---@param no_single? boolean
|
||||
local function is_safe(node, no_single)
|
||||
if node.keymap or is_special(node) or node:count() == 0 then
|
||||
return false
|
||||
end
|
||||
if no_single and #node.path == 1 then
|
||||
local key = node.path[1]
|
||||
-- only z or g are safe
|
||||
if key:match("^[a-z]$") and not key:match("^[gz]$") then
|
||||
return false
|
||||
end
|
||||
-- only Z is safe
|
||||
if key:match("^[A-Z]$") and not key:match("^[Z]$") then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Mode:__tostring()
|
||||
return string.format("Mode(%s:%d)", self.mode, self.buf.buf)
|
||||
end
|
||||
|
||||
---@param buf wk.Buffer
|
||||
---@param mode string
|
||||
function Mode.new(buf, mode)
|
||||
local self = setmetatable({}, Mode)
|
||||
self.buf = buf
|
||||
self.mode = mode
|
||||
self.tree = Tree.new()
|
||||
self.triggers = {}
|
||||
self:update()
|
||||
return self
|
||||
end
|
||||
|
||||
function Mode:attach()
|
||||
self.triggers = {}
|
||||
|
||||
-- NOTE: order is important for nowait to work!
|
||||
-- * first add plugin mappings
|
||||
-- * then add triggers
|
||||
self.tree:walk(function(node)
|
||||
if is_special(node) then
|
||||
table.insert(self.triggers, node)
|
||||
return false
|
||||
end
|
||||
end)
|
||||
|
||||
if Config.triggers.modes[self.mode] then
|
||||
-- Auto triggers
|
||||
self.tree:walk(function(node)
|
||||
if is_safe(node, true) then
|
||||
table.insert(self.triggers, node)
|
||||
return false
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Manual triggers
|
||||
for _, t in ipairs(Config.triggers.mappings) do
|
||||
if self:has(t) then
|
||||
local node = self.tree:find(t.lhs)
|
||||
if node and is_safe(node) then
|
||||
table.insert(self.triggers, node)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Triggers.schedule(self)
|
||||
end
|
||||
|
||||
function Mode:xo()
|
||||
return self.mode:find("[xo]") ~= nil
|
||||
end
|
||||
|
||||
function Mode:clear()
|
||||
Triggers.detach(self)
|
||||
self.tree:clear()
|
||||
end
|
||||
|
||||
---@param mode string
|
||||
function Mode:is(mode)
|
||||
if mode == "v" then
|
||||
return self.mode == "x" or self.mode == "s"
|
||||
end
|
||||
return self.mode == mode
|
||||
end
|
||||
|
||||
---@param mapping wk.Keymap
|
||||
function Mode:has(mapping)
|
||||
return self:is(mapping.mode) and (not mapping.buffer or mapping.buffer == self.buf.buf)
|
||||
end
|
||||
|
||||
function Mode:update()
|
||||
self.tree:clear()
|
||||
|
||||
local mappings = vim.api.nvim_get_keymap(self.mode)
|
||||
vim.list_extend(mappings, vim.api.nvim_buf_get_keymap(self.buf.buf, self.mode))
|
||||
---@cast mappings wk.Keymap[]
|
||||
|
||||
for _, mapping in ipairs(mappings) do
|
||||
if mapping.desc and mapping.desc:find("which-key-trigger", 1, true) then
|
||||
-- ignore which-key triggers
|
||||
elseif Util.is_nop(mapping.rhs) then
|
||||
self.tree:add(mapping, true)
|
||||
elseif mapping.lhs:sub(1, 6) ~= "<Plug>" and mapping.lhs:sub(1, 5) ~= "<SNR>" then
|
||||
self.tree:add(mapping)
|
||||
end
|
||||
end
|
||||
|
||||
for _, m in ipairs(Config.mappings) do
|
||||
if self:has(m) then
|
||||
self.tree:add(m, true)
|
||||
end
|
||||
end
|
||||
|
||||
self.tree:fix()
|
||||
self:attach()
|
||||
vim.schedule(function()
|
||||
require("which-key.state").update()
|
||||
end)
|
||||
end
|
||||
|
||||
---@class wk.Buffer
|
||||
---@field buf number
|
||||
---@field modes table<string, wk.Mode>
|
||||
local Buf = {}
|
||||
Buf.__index = Buf
|
||||
|
||||
---@param buf? number
|
||||
function Buf.new(buf)
|
||||
local self = setmetatable({}, Buf)
|
||||
buf = buf or 0
|
||||
self.buf = buf == 0 and vim.api.nvim_get_current_buf() or buf
|
||||
self.modes = {}
|
||||
return self
|
||||
end
|
||||
|
||||
---@param opts? wk.Filter
|
||||
function Buf:clear(opts)
|
||||
opts = opts or {}
|
||||
assert(not opts.buf or opts.buf == self.buf, "buffer mismatch")
|
||||
---@type string[]
|
||||
local modes = opts.mode and { opts.mode } or vim.tbl_keys(self.modes)
|
||||
for _, m in ipairs(modes) do
|
||||
local mode = self.modes[m]
|
||||
if mode then
|
||||
mode:clear()
|
||||
self.modes[m] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Buf:valid()
|
||||
return vim.api.nvim_buf_is_valid(self.buf)
|
||||
end
|
||||
|
||||
---@param opts? wk.Filter
|
||||
---@return wk.Mode?
|
||||
function Buf:get(opts)
|
||||
if not self:valid() then
|
||||
return
|
||||
end
|
||||
opts = opts or {}
|
||||
local mode = opts.mode or Util.mapmode()
|
||||
local ret = self.modes[mode]
|
||||
if not ret then
|
||||
self.modes[mode] = Mode.new(self, mode)
|
||||
Util.debug("new " .. tostring(self.modes[mode]))
|
||||
return self.modes[mode]
|
||||
elseif opts.update then
|
||||
Util.debug("update " .. tostring(ret))
|
||||
ret:update()
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
local M = {}
|
||||
M.Buf = Buf
|
||||
M.bufs = {} ---@type table<number,wk.Buffer>
|
||||
|
||||
---@param opts? wk.Filter
|
||||
function M.get(opts)
|
||||
M.cleanup()
|
||||
opts = opts or {}
|
||||
local buf = opts.buf or vim.api.nvim_get_current_buf()
|
||||
|
||||
if not vim.api.nvim_buf_is_valid(buf) then
|
||||
return
|
||||
end
|
||||
|
||||
local ft = vim.bo[buf].filetype
|
||||
local bt = vim.bo[buf].buftype
|
||||
|
||||
if vim.tbl_contains(Config.disable.ft, ft) then
|
||||
return
|
||||
end
|
||||
if vim.tbl_contains(Config.disable.bt, bt) then
|
||||
return
|
||||
end
|
||||
|
||||
M.bufs[buf] = M.bufs[buf] or Buf.new(buf)
|
||||
return M.bufs[buf]:get(opts)
|
||||
end
|
||||
|
||||
function M.cleanup()
|
||||
for buf, _ in pairs(M.bufs) do
|
||||
if not vim.api.nvim_buf_is_valid(buf) then
|
||||
M.bufs[buf] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param opts? wk.Filter
|
||||
function M.clear(opts)
|
||||
M.cleanup()
|
||||
opts = opts or {}
|
||||
---@type number[]
|
||||
local bufs = opts.buf and { opts.buf } or vim.tbl_keys(M.bufs)
|
||||
for _, b in ipairs(bufs) do
|
||||
if M.bufs[b] then
|
||||
M.bufs[b]:clear(opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,21 @@
|
||||
local Docs = require("lazy.docs")
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.update()
|
||||
local config = Docs.extract("lua/which-key/config.lua", "\n(--@class wk%.Opts.-\n})")
|
||||
config = config:gsub("%s*debug = false.\n", "\n")
|
||||
Docs.save({
|
||||
config = config,
|
||||
colors = Docs.colors({
|
||||
modname = "which-key.colors",
|
||||
path = "lua/which-key/colors.lua",
|
||||
name = "WhichKey",
|
||||
}),
|
||||
})
|
||||
end
|
||||
|
||||
M.update()
|
||||
print("Updated docs")
|
||||
|
||||
return M
|
||||
@ -0,0 +1,70 @@
|
||||
local M = {}
|
||||
|
||||
---@type table<string, fun():wk.Spec>
|
||||
M.expand = {}
|
||||
|
||||
---@return number[]
|
||||
function M.bufs()
|
||||
local current = vim.api.nvim_get_current_buf()
|
||||
return vim.tbl_filter(function(buf)
|
||||
return buf ~= current and vim.bo[buf].buflisted
|
||||
end, vim.api.nvim_list_bufs())
|
||||
end
|
||||
|
||||
function M.bufname(buf)
|
||||
local name = vim.api.nvim_buf_get_name(buf)
|
||||
return name == "" and "[No Name]" or vim.fn.fnamemodify(name, ":~:.")
|
||||
end
|
||||
|
||||
---@param spec wk.Spec[]
|
||||
function M.add_keys(spec)
|
||||
table.sort(spec, function(a, b)
|
||||
return a.desc < b.desc
|
||||
end)
|
||||
spec = vim.list_slice(spec, 1, 10)
|
||||
for i, v in ipairs(spec) do
|
||||
v[1] = tostring(i - 1)
|
||||
end
|
||||
return spec
|
||||
end
|
||||
|
||||
function M.expand.buf()
|
||||
local ret = {} ---@type wk.Spec[]
|
||||
|
||||
for _, buf in ipairs(M.bufs()) do
|
||||
local name = M.bufname(buf)
|
||||
ret[#ret + 1] = {
|
||||
"",
|
||||
function()
|
||||
vim.api.nvim_set_current_buf(buf)
|
||||
end,
|
||||
desc = name,
|
||||
icon = { cat = "file", name = name },
|
||||
}
|
||||
end
|
||||
return M.add_keys(ret)
|
||||
end
|
||||
|
||||
function M.expand.win()
|
||||
---@type wk.Spec[]
|
||||
local ret = {}
|
||||
local current = vim.api.nvim_get_current_win()
|
||||
for _, win in ipairs(vim.api.nvim_list_wins()) do
|
||||
local is_float = vim.api.nvim_win_get_config(win).relative ~= ""
|
||||
if win ~= current and not is_float then
|
||||
local buf = vim.api.nvim_win_get_buf(win)
|
||||
local name = M.bufname(buf)
|
||||
ret[#ret + 1] = {
|
||||
"",
|
||||
function()
|
||||
vim.api.nvim_set_current_win(win)
|
||||
end,
|
||||
desc = name,
|
||||
icon = { cat = "file", name = name },
|
||||
}
|
||||
end
|
||||
end
|
||||
return M.add_keys(ret)
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,209 @@
|
||||
local Config = require("which-key.config")
|
||||
|
||||
local M = {}
|
||||
|
||||
--- * `WhichKeyColorAzure` - azure.
|
||||
--- * `WhichKeyColorBlue` - blue.
|
||||
--- * `WhichKeyColorCyan` - cyan.
|
||||
--- * `WhichKeyColorGreen` - green.
|
||||
--- * `WhichKeyColorGrey` - grey.
|
||||
--- * `WhichKeyColorOrange` - orange.
|
||||
--- * `WhichKeyColorPurple` - purple.
|
||||
--- * `WhichKeyColorRed` - red.
|
||||
--- * `WhichKeyColorYellow` - yellow.
|
||||
|
||||
---@type wk.IconRule[]
|
||||
M.rules = {
|
||||
{ plugin = "fzf-lua", cat = "filetype", name = "fzf" },
|
||||
{ plugin = "neo-tree.nvim", cat = "filetype", name = "neo-tree" },
|
||||
{ plugin = "octo.nvim", cat = "filetype", name = "git" },
|
||||
{ plugin = "yanky.nvim", icon = "", color = "yellow" },
|
||||
{ plugin = "zen-mode.nvim", icon = " ", color = "cyan" },
|
||||
{ plugin = "telescope.nvim", pattern = "telescope", icon = "", color = "blue" },
|
||||
{ plugin = "trouble.nvim", cat = "filetype", name = "trouble" },
|
||||
{ plugin = "todo-comments.nvim", cat = "file", name = "TODO" },
|
||||
{ plugin = "nvim-spectre", icon = " ", color = "blue" },
|
||||
{ plugin = "grug-far.nvim", pattern = "grug", icon = " ", color = "blue" },
|
||||
{ plugin = "noice.nvim", pattern = "noice", icon = "", color = "orange" },
|
||||
{ plugin = "persistence.nvim", icon = " ", color = "azure" },
|
||||
{ plugin = "neotest", cat = "filetype", name = "neotest-summary" },
|
||||
{ plugin = "lazy.nvim", cat = "filetype", name = "lazy" },
|
||||
{ plugin = "CopilotChat.nvim", icon = " ", color = "orange" },
|
||||
{ pattern = "%f[%a]git", cat = "filetype", name = "git" },
|
||||
{ pattern = "terminal", icon = " ", color = "red" },
|
||||
{ pattern = "find", icon = " ", color = "green" },
|
||||
{ pattern = "search", icon = " ", color = "green" },
|
||||
{ pattern = "test", cat = "filetype", name = "neotest-summary" },
|
||||
{ pattern = "lazy", cat = "filetype", name = "lazy" },
|
||||
{ pattern = "buffer", icon = "", color = "cyan" },
|
||||
{ pattern = "file", icon = "", color = "cyan" },
|
||||
{ pattern = "window", icon = " ", color = "blue" },
|
||||
{ pattern = "diagnostic", icon = " ", color = "green" },
|
||||
{ pattern = "format", icon = " ", color = "cyan" },
|
||||
{ pattern = "debug", icon = " ", color = "red" },
|
||||
{ pattern = "code", icon = " ", color = "orange" },
|
||||
{ pattern = "notif", icon = " ", color = "blue" },
|
||||
{ pattern = "toggle", icon = " ", color = "yellow" },
|
||||
{ pattern = "session", icon = " ", color = "azure" },
|
||||
{ pattern = "exit", icon = " ", color = "red" },
|
||||
{ pattern = "quit", icon = " ", color = "red" },
|
||||
{ pattern = "tab", icon = " ", color = "purple" },
|
||||
{ pattern = "%f[%a]ai", icon = " ", color = "green" },
|
||||
{ pattern = "ui", icon = " ", color = "cyan" },
|
||||
}
|
||||
|
||||
---@type wk.IconProvider[]
|
||||
M.providers = {
|
||||
{
|
||||
name = "mini.icons",
|
||||
get = function(icon)
|
||||
local Icons = require("mini.icons")
|
||||
local ico, ico_hl, ico_def = Icons.get(icon.cat, icon.name) --[[@as string, string, boolean]]
|
||||
if not ico_def then
|
||||
return ico, ico_hl
|
||||
end
|
||||
end,
|
||||
},
|
||||
{
|
||||
name = "nvim-web-devicons",
|
||||
get = function(icon)
|
||||
local Icons = require("nvim-web-devicons")
|
||||
if icon.cat == "filetype" then
|
||||
return Icons.get_icon_by_filetype(icon.name, { default = false })
|
||||
elseif icon.cat == "file" then
|
||||
return Icons.get_icon(icon.name, nil, { default = false }) --[[@as string, string]]
|
||||
elseif icon.cat == "extension" then
|
||||
return Icons.get_icon(nil, icon.name, { default = false }) --[[@as string, string]]
|
||||
end
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
---@return wk.IconProvider?
|
||||
function M.get_provider()
|
||||
for _, provider in ipairs(M.providers) do
|
||||
if provider.available == nil then
|
||||
provider.available = pcall(require, provider.name)
|
||||
end
|
||||
if provider.available then
|
||||
return provider
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.have()
|
||||
return M.get_provider() ~= nil
|
||||
end
|
||||
|
||||
---@param icon wk.Icon|string
|
||||
---@return string?, string?
|
||||
function M.get_icon(icon)
|
||||
icon = type(icon) == "string" and { cat = "filetype", name = icon } or icon --[[@as wk.Icon]]
|
||||
---@type string?, string?
|
||||
local ret, hl
|
||||
if icon.icon then
|
||||
ret, hl = icon.icon, icon.hl
|
||||
elseif icon.cat and icon.name then
|
||||
local provider = M.get_provider()
|
||||
if provider then
|
||||
ret, hl = provider.get(icon)
|
||||
end
|
||||
end
|
||||
if ret then
|
||||
if icon.color then
|
||||
hl = "WhichKeyIcon" .. icon.color:sub(1, 1):upper() .. icon.color:sub(2)
|
||||
end
|
||||
if not hl or Config.icons.colors == false or icon.color == false then
|
||||
hl = "WhichKeyIcon"
|
||||
end
|
||||
return ret, hl
|
||||
end
|
||||
end
|
||||
|
||||
---@param rules wk.IconRule[]
|
||||
---@param opts? {keymap?: wk.Keymap, desc?: string, ft?:string|string[]}|wk.Icon
|
||||
---@param check_ft? boolean
|
||||
---@return string?, string?
|
||||
function M._get(rules, opts, check_ft)
|
||||
opts = opts or {}
|
||||
opts.ft = type(opts.ft) == "string" and { opts.ft } or opts.ft
|
||||
|
||||
---@type string?
|
||||
local plugin
|
||||
local fts = opts.ft or {} --[[@as string[] ]]
|
||||
|
||||
if opts.keymap and package.loaded.lazy then
|
||||
local LazyUtil = require("lazy.core.util")
|
||||
local Keys = require("lazy.core.handler").handlers.keys --[[@as LazyKeysHandler]]
|
||||
local keys = Keys.parse(opts.keymap.lhs, opts.keymap.mode)
|
||||
plugin = Keys.managed[keys.id]
|
||||
if plugin then
|
||||
fts[#fts + 1] = LazyUtil.normname(plugin)
|
||||
end
|
||||
end
|
||||
|
||||
-- plugin icons
|
||||
if plugin then
|
||||
for _, icon in ipairs(rules) do
|
||||
if icon.plugin == plugin then
|
||||
local ico, hl = M.get_icon(icon)
|
||||
if ico then
|
||||
return ico, hl
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- filetype icons
|
||||
if check_ft then
|
||||
if opts.keymap and opts.keymap.buffer and opts.keymap.buffer ~= 0 then
|
||||
pcall(function()
|
||||
fts[#fts + 1] = vim.bo[opts.keymap.buffer].filetype
|
||||
end)
|
||||
end
|
||||
for _, ft in ipairs(fts) do
|
||||
local icon, hl = M.get_icon({ cat = "filetype", name = ft })
|
||||
if icon then
|
||||
return icon, hl
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- pattern icons
|
||||
if opts.desc then
|
||||
for _, icon in ipairs(rules) do
|
||||
if icon.pattern and opts.desc:lower():find(icon.pattern) then
|
||||
local ico, hl = M.get_icon(icon)
|
||||
if ico then
|
||||
return ico, hl
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param opts {keymap?: wk.Keymap, desc?: string, ft?:string|string[]}|wk.Icon|string
|
||||
function M.get(opts)
|
||||
if not Config.icons.mappings then
|
||||
return
|
||||
end
|
||||
if type(opts) == "string" then
|
||||
opts = { icon = opts }
|
||||
end
|
||||
|
||||
if opts.icon or opts.cat then
|
||||
return M.get_icon(opts)
|
||||
end
|
||||
|
||||
if Config.icons.rules == false then
|
||||
return
|
||||
end
|
||||
local icon, hl = M._get(Config.icons.rules, opts)
|
||||
if icon then
|
||||
return icon, hl
|
||||
end
|
||||
|
||||
return M._get(M.rules, opts, true)
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,76 @@
|
||||
local Mappings = require("which-key.mappings")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@param spec wk.Spec
|
||||
function M.migrate(spec)
|
||||
spec = vim.deepcopy(spec)
|
||||
local mappings = Mappings.parse(spec, { version = 1, notify = false })
|
||||
---@type table<string, {m:wk.Mapping, mode:string[]}>
|
||||
local mapping_modes = {}
|
||||
|
||||
for _, m in ipairs(mappings) do
|
||||
m.preset = nil
|
||||
m[1] = m.lhs:gsub("<lt>", "<")
|
||||
m[2] = m.rhs
|
||||
m.lhs = nil
|
||||
m.rhs = nil
|
||||
local mode = m.mode
|
||||
m.mode = nil
|
||||
if m.silent then
|
||||
m.silent = nil
|
||||
end
|
||||
if m.group then
|
||||
m.group = m.desc
|
||||
m.desc = nil
|
||||
end
|
||||
local id = vim.inspect(m)
|
||||
mapping_modes[id] = mapping_modes[id] or { m = m, mode = {} }
|
||||
table.insert(mapping_modes[id].mode, mode)
|
||||
end
|
||||
|
||||
mappings = vim.tbl_map(function(v)
|
||||
local m = v.m
|
||||
if not vim.deep_equal(v.mode, { "n" }) then
|
||||
m.mode = v.mode
|
||||
end
|
||||
return m
|
||||
end, vim.tbl_values(mapping_modes))
|
||||
|
||||
table.sort(mappings, function(a, b)
|
||||
return a[1] < b[1]
|
||||
end)
|
||||
|
||||
-- Group by modes
|
||||
---@type table<string, wk.Mapping[]>
|
||||
local modes = {}
|
||||
for _, m in pairs(mappings) do
|
||||
local mode = m.mode or {}
|
||||
table.sort(mode)
|
||||
local id = table.concat(mode)
|
||||
modes[id] = modes[id] or {}
|
||||
table.insert(modes[id], m)
|
||||
end
|
||||
local lines = {}
|
||||
for mode, maps in pairs(modes) do
|
||||
if #maps > 2 and mode ~= "" then
|
||||
lines[#lines + 1] = " {"
|
||||
lines[#lines + 1] = " mode = " .. vim.inspect(maps[1].mode) .. ","
|
||||
for _, m in ipairs(maps) do
|
||||
m.mode = nil
|
||||
lines[#lines + 1] = " " .. vim.inspect(m):gsub("%s+", " ") .. ","
|
||||
end
|
||||
lines[#lines + 1] = " },"
|
||||
else
|
||||
for _, m in ipairs(maps) do
|
||||
if m.mode and #m.mode == 1 then
|
||||
m.mode = m.mode[1]
|
||||
end
|
||||
lines[#lines + 1] = " " .. vim.inspect(m):gsub("%s+", " ") .. ","
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return "{\n" .. table.concat(lines, "\n") .. "\n}"
|
||||
end
|
||||
return M
|
||||
@ -0,0 +1,205 @@
|
||||
local Util = require("which-key.util")
|
||||
|
||||
---@class wk.Node
|
||||
---@field _children table<string, wk.Node>
|
||||
local M = {}
|
||||
|
||||
---@param parent? wk.Node
|
||||
---@param key? string
|
||||
---@return wk.Node
|
||||
function M.new(parent, key)
|
||||
local self = setmetatable({}, M)
|
||||
self.parent = parent
|
||||
self.key = key or ""
|
||||
self.path = {}
|
||||
self.global = true
|
||||
self._children = {}
|
||||
self.keys = (parent and parent.keys or "") .. self.key
|
||||
for _, p in ipairs(parent and parent.path or {}) do
|
||||
table.insert(self.path, p)
|
||||
end
|
||||
if key then
|
||||
table.insert(self.path, key)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function M:is_local()
|
||||
if self.path[1] == Util.norm("<localleader>") then
|
||||
return true
|
||||
end
|
||||
if self.buffer and self.buffer > 0 then
|
||||
return true
|
||||
end
|
||||
for _, child in pairs(self._children) do
|
||||
if child:is_local() then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function M:__index(k)
|
||||
if k == "mapping" or k == "keymap" then
|
||||
return
|
||||
end
|
||||
local v = rawget(M, k)
|
||||
if v ~= nil then
|
||||
return v
|
||||
end
|
||||
for _, m in ipairs({ "mapping", "keymap" }) do
|
||||
local mm = rawget(self, m)
|
||||
if k == m then
|
||||
return mm
|
||||
end
|
||||
if mm and mm[k] ~= nil then
|
||||
return mm[k]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M:__tostring()
|
||||
local info = { "Node(" .. self.keys .. ")" }
|
||||
if self:is_plugin() then
|
||||
info[#info + 1] = "Plugin(" .. self.plugin .. ")"
|
||||
end
|
||||
if self:is_proxy() then
|
||||
info[#info + 1] = "Proxy(" .. self.mapping.proxy .. ")"
|
||||
end
|
||||
return table.concat(info, " ")
|
||||
end
|
||||
|
||||
---@param depth? number
|
||||
function M:inspect(depth)
|
||||
local indent = (" "):rep(depth or 0)
|
||||
local ret = { indent .. tostring(self) }
|
||||
for _, child in ipairs(self:children()) do
|
||||
table.insert(ret, child:inspect((depth or 0) + 1))
|
||||
end
|
||||
return table.concat(ret, "\n")
|
||||
end
|
||||
|
||||
function M:count()
|
||||
return #self:children()
|
||||
end
|
||||
|
||||
function M:is_group()
|
||||
return self:can_expand() or self:count() > 0
|
||||
end
|
||||
|
||||
function M:is_proxy()
|
||||
return self.mapping and self.mapping.proxy
|
||||
end
|
||||
|
||||
function M:is_plugin()
|
||||
return self.plugin ~= nil
|
||||
end
|
||||
|
||||
function M:can_expand()
|
||||
return self.plugin or self:is_proxy() or (self.mapping and self.mapping.expand)
|
||||
end
|
||||
|
||||
---@return wk.Node[]
|
||||
function M:children()
|
||||
return vim.tbl_values(self:expand())
|
||||
end
|
||||
|
||||
---@return table<string, wk.Node>
|
||||
function M:expand()
|
||||
if not self:can_expand() then
|
||||
return self._children
|
||||
end
|
||||
|
||||
---@type table<string, wk.Node>
|
||||
local ret = {}
|
||||
|
||||
-- plugin mappings
|
||||
if self.plugin then
|
||||
local plugin = require("which-key.plugins").plugins[self.plugin or ""]
|
||||
assert(plugin, "plugin not found")
|
||||
Util.debug(("Plugin(%q).expand"):format(self.plugin))
|
||||
|
||||
for i, item in ipairs(plugin.expand()) do
|
||||
item.order = i
|
||||
local child = M.new(self, item.key) --[[@as wk.Node.plugin.item]]
|
||||
setmetatable(child, { __index = setmetatable(item, M) })
|
||||
ret[item.key] = child
|
||||
end
|
||||
end
|
||||
|
||||
-- proxy mappings
|
||||
local proxy = self.mapping.proxy
|
||||
if proxy then
|
||||
local keys = Util.keys(proxy)
|
||||
local root = self:root()
|
||||
local node = root:find(keys, { expand = true })
|
||||
if node then
|
||||
for k, v in pairs(node:expand()) do
|
||||
ret[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- expand mappings
|
||||
local expand = self.mapping and self.mapping.expand
|
||||
if expand then
|
||||
local Tree = require("which-key.tree")
|
||||
local tmp = Tree.new()
|
||||
local mappings = require("which-key.mappings").parse(expand())
|
||||
for _, mapping in ipairs(mappings) do
|
||||
tmp:add(mapping, true)
|
||||
end
|
||||
for _, child in ipairs(tmp.root:children()) do
|
||||
local action = child.mapping and child.mapping.rhs
|
||||
if type(action) == "function" then
|
||||
child.action = action
|
||||
elseif type(action) == "string" then
|
||||
Util.error("expand mappings only support functions as rhs:\n" .. vim.inspect(child.mapping))
|
||||
end
|
||||
ret[child.key] = child
|
||||
end
|
||||
end
|
||||
|
||||
-- custom mappings
|
||||
for k, v in pairs(self._children) do
|
||||
ret[k] = v
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
function M:root()
|
||||
local node = self
|
||||
while node.parent do
|
||||
node = node.parent
|
||||
end
|
||||
return node
|
||||
end
|
||||
|
||||
---@param path string[]|string
|
||||
---@param opts? { create?: boolean, expand?: boolean }
|
||||
---@return wk.Node?
|
||||
function M:find(path, opts)
|
||||
path = (type(path) == "string" and { path } or path) --[[@as string[] ]]
|
||||
opts = opts or {}
|
||||
local node = self
|
||||
for _, key in ipairs(path) do
|
||||
local child ---@type wk.Node?
|
||||
if opts.expand then
|
||||
child = node:expand()[key]
|
||||
else
|
||||
child = node._children[key]
|
||||
end
|
||||
if not child then
|
||||
if not opts.create then
|
||||
return
|
||||
end
|
||||
child = M.new(node, key)
|
||||
node._children[key] = child
|
||||
end
|
||||
node = child
|
||||
end
|
||||
return node
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,215 @@
|
||||
local M = {}
|
||||
|
||||
M.name = "presets"
|
||||
|
||||
M.operators = {
|
||||
preset = true,
|
||||
mode = { "n", "x" },
|
||||
{ "!", desc = "Run program" },
|
||||
{ "<", desc = "Indent left" },
|
||||
{ ">", desc = "Indent right" },
|
||||
{ "V", desc = "Visual Line" },
|
||||
{ "c", desc = "Change" },
|
||||
{ "d", desc = "Delete" },
|
||||
{ "gU", desc = "Uppercase" },
|
||||
{ "gu", desc = "Lowercase" },
|
||||
{ "g~", desc = "Toggle case" },
|
||||
{ "gw", desc = "Format" },
|
||||
{ "r", desc = "Replace" },
|
||||
{ "v", desc = "Visual" },
|
||||
{ "y", desc = "Yank" },
|
||||
{ "zf", desc = "Create fold" },
|
||||
{ "~", desc = "Toggle case" },
|
||||
}
|
||||
|
||||
M.motions = {
|
||||
mode = { "o", "x", "n" },
|
||||
preset = true,
|
||||
{ "$", desc = "End of line" },
|
||||
{ "%", desc = "Matching (){}[]" },
|
||||
{ "0", desc = "Start of line" },
|
||||
{ "F", desc = "Move to prev char" },
|
||||
{ "G", desc = "Last line" },
|
||||
{ "T", desc = "Move before prev char" },
|
||||
{ "^", desc = "Start of line (non ws)" },
|
||||
{ "b", desc = "Prev word" },
|
||||
{ "e", desc = "Next end of word" },
|
||||
{ "f", desc = "Move to next char" },
|
||||
{ "ge", desc = "Prev end of word" },
|
||||
{ "gg", desc = "First line" },
|
||||
{ "h", desc = "Left" },
|
||||
{ "j", desc = "Down" },
|
||||
{ "k", desc = "Up" },
|
||||
{ "l", desc = "Right" },
|
||||
{ "t", desc = "Move before next char" },
|
||||
{ "w", desc = "Next word" },
|
||||
{ "{", desc = "Prev empty line" },
|
||||
{ "}", desc = "Next empty line" },
|
||||
{ ";", desc = "Next ftFT" },
|
||||
{ ",", desc = "Prev ftFT" },
|
||||
{ "/", desc = "Search forward" },
|
||||
{ "?", desc = "Search backward" },
|
||||
{ "B", desc = "Prev WORD" },
|
||||
{ "E", desc = "Next end of WORD" },
|
||||
{ "W", desc = "Next WORD" },
|
||||
}
|
||||
|
||||
M.text_objects = {
|
||||
mode = { "o", "x" },
|
||||
preset = true,
|
||||
{ "a", group = "around" },
|
||||
{ 'a"', desc = '" string' },
|
||||
{ "a'", desc = "' string" },
|
||||
{ "a(", desc = "[(]) block" },
|
||||
{ "a)", desc = "[(]) block" },
|
||||
{ "a<", desc = "<> block" },
|
||||
{ "a>", desc = "<> block" },
|
||||
{ "aB", desc = "[{]} block" },
|
||||
{ "aW", desc = "WORD with ws" },
|
||||
{ "a[", desc = "[] block" },
|
||||
{ "a]", desc = "[] block" },
|
||||
{ "a`", desc = "` string" },
|
||||
{ "ab", desc = "[(]) block" },
|
||||
{ "ap", desc = "paragraph" },
|
||||
{ "as", desc = "sentence" },
|
||||
{ "at", desc = "tag block" },
|
||||
{ "aw", desc = "word with ws" },
|
||||
{ "a{", desc = "[{]} block" },
|
||||
{ "a}", desc = "[{]} block" },
|
||||
{ "i", group = "inside" },
|
||||
{ 'i"', desc = 'inner " string' },
|
||||
{ "i'", desc = "inner ' string" },
|
||||
{ "i(", desc = "inner [(])" },
|
||||
{ "i)", desc = "inner [(])" },
|
||||
{ "i<", desc = "inner <>" },
|
||||
{ "i>", desc = "inner <>" },
|
||||
{ "iB", desc = "inner [{]}" },
|
||||
{ "iW", desc = "inner WORD" },
|
||||
{ "i[", desc = "inner []" },
|
||||
{ "i]", desc = "inner []" },
|
||||
{ "i`", desc = "inner ` string" },
|
||||
{ "ib", desc = "inner [(])" },
|
||||
{ "ip", desc = "inner paragraph" },
|
||||
{ "is", desc = "inner sentence" },
|
||||
{ "it", desc = "inner tag block" },
|
||||
{ "iw", desc = "inner word" },
|
||||
{ "i{", desc = "inner [{]}" },
|
||||
{ "i}", desc = "inner [{]}" },
|
||||
}
|
||||
|
||||
M.windows = {
|
||||
preset = true,
|
||||
mode = { "n", "x" },
|
||||
{ "<c-w>", group = "window" },
|
||||
{ "<c-w>+", desc = "Increase height" },
|
||||
{ "<c-w>-", desc = "Decrease height" },
|
||||
{ "<c-w><", desc = "Decrease width" },
|
||||
{ "<c-w>=", desc = "Equally high and wide" },
|
||||
{ "<c-w>>", desc = "Increase width" },
|
||||
{ "<c-w>T", desc = "Break out into a new tab" },
|
||||
{ "<c-w>_", desc = "Max out the height" },
|
||||
{ "<c-w>h", desc = "Go to the left window" },
|
||||
{ "<c-w>j", desc = "Go to the down window" },
|
||||
{ "<c-w>k", desc = "Go to the up window" },
|
||||
{ "<c-w>l", desc = "Go to the right window" },
|
||||
{ "<c-w>o", desc = "Close all other windows" },
|
||||
{ "<c-w>q", desc = "Quit a window" },
|
||||
{ "<c-w>s", desc = "Split window" },
|
||||
{ "<c-w>v", desc = "Split window vertically" },
|
||||
{ "<c-w>w", desc = "Switch windows" },
|
||||
{ "<c-w>x", desc = "Swap current with next" },
|
||||
{ "<c-w>|", desc = "Max out the width" },
|
||||
}
|
||||
|
||||
M.z = {
|
||||
preset = true,
|
||||
{ "z<CR>", desc = "Top this line" },
|
||||
{ "z=", desc = "Spelling suggestions" },
|
||||
{ "zA", desc = "Toggle all folds under cursor" },
|
||||
{ "zC", desc = "Close all folds under cursor" },
|
||||
{ "zD", desc = "Delete all folds under cursor" },
|
||||
{ "zE", desc = "Delete all folds in file" },
|
||||
{ "zH", desc = "Half screen to the left" },
|
||||
{ "zL", desc = "Half screen to the right" },
|
||||
{ "zM", desc = "Close all folds" },
|
||||
{ "zO", desc = "Open all folds under cursor" },
|
||||
{ "zR", desc = "Open all folds" },
|
||||
{ "za", desc = "Toggle fold under cursor" },
|
||||
{ "zb", desc = "Bottom this line" },
|
||||
{ "zc", desc = "Close fold under cursor" },
|
||||
{ "zd", desc = "Delete fold under cursor" },
|
||||
{ "ze", desc = "Right this line" },
|
||||
{ "zg", desc = "Add word to spell list" },
|
||||
{ "zi", desc = "Toggle folding" },
|
||||
{ "zm", desc = "Fold more" },
|
||||
{ "zo", desc = "Open fold under cursor" },
|
||||
{ "zr", desc = "Fold less" },
|
||||
{ "zs", desc = "Left this line" },
|
||||
{ "zt", desc = "Top this line" },
|
||||
{ "zv", desc = "Show cursor line" },
|
||||
{ "zw", desc = "Mark word as bad/misspelling" },
|
||||
{ "zx", desc = "Update folds" },
|
||||
{ "zz", desc = "Center this line" },
|
||||
}
|
||||
|
||||
M.nav = {
|
||||
preset = true,
|
||||
{ "H", desc = "Home line of window (top)" },
|
||||
{ "L", desc = "Last line of window" },
|
||||
{ "M", desc = "Middle line of window" },
|
||||
{ "[%", desc = "Previous unmatched group" },
|
||||
{ "[(", desc = "Previous (" },
|
||||
{ "[<", desc = "Previous <" },
|
||||
{ "[M", desc = "Previous method end" },
|
||||
{ "[m", desc = "Previous method start" },
|
||||
{ "[s", desc = "Previous misspelled word" },
|
||||
{ "[{", desc = "Previous {" },
|
||||
{ "]%", desc = "Next unmatched group" },
|
||||
{ "](", desc = "Next (" },
|
||||
{ "]<", desc = "Next <" },
|
||||
{ "]M", desc = "Next method end" },
|
||||
{ "]m", desc = "Next method start" },
|
||||
{ "]s", desc = "Next misspelled word" },
|
||||
{ "]{", desc = "Next {" },
|
||||
}
|
||||
|
||||
M.g = {
|
||||
preset = true,
|
||||
{ "g%", desc = "Cycle backwards through results" },
|
||||
{ "gN", desc = "Search backwards and select" },
|
||||
{ "gT", desc = "Go to previous tab page" },
|
||||
{ "gf", desc = "Go to file under cursor" },
|
||||
{ "gi", desc = "Go to last insert" },
|
||||
{ "gn", desc = "Search forwards and select" },
|
||||
{ "gt", desc = "Go to next tab page" },
|
||||
{ "gv", desc = "Last visual selection" },
|
||||
{ "gx", desc = "Open file with system app" },
|
||||
}
|
||||
|
||||
function M.setup(opts)
|
||||
local wk = require("which-key")
|
||||
|
||||
-- Operators
|
||||
if opts.operators then
|
||||
wk.add(M.operators)
|
||||
end
|
||||
|
||||
-- Motions
|
||||
if opts.motions then
|
||||
wk.add(M.motions)
|
||||
end
|
||||
|
||||
-- Text objects
|
||||
if opts.text_objects then
|
||||
wk.add(M.text_objects)
|
||||
end
|
||||
|
||||
-- Misc
|
||||
for _, preset in pairs({ "windows", "nav", "z", "g" }) do
|
||||
if opts[preset] ~= false then
|
||||
wk.add(M[preset])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,38 @@
|
||||
---@type table<string, wk.Opts>
|
||||
return {
|
||||
helix = {
|
||||
win = {
|
||||
width = { min = 30, max = 60 },
|
||||
height = { min = 4, max = 0.75 },
|
||||
padding = { 0, 1 },
|
||||
col = -1,
|
||||
row = -1,
|
||||
border = "rounded",
|
||||
title = true,
|
||||
title_pos = "left",
|
||||
},
|
||||
layout = {
|
||||
width = { min = 30 },
|
||||
},
|
||||
},
|
||||
modern = {
|
||||
win = {
|
||||
width = 0.9,
|
||||
height = { min = 4, max = 25 },
|
||||
col = 0.5,
|
||||
row = -1,
|
||||
border = "rounded",
|
||||
title = true,
|
||||
title_pos = "center",
|
||||
},
|
||||
},
|
||||
classic = {
|
||||
win = {
|
||||
width = math.huge,
|
||||
height = { min = 4, max = 25 },
|
||||
col = 0,
|
||||
row = -1,
|
||||
border = "none",
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -0,0 +1,386 @@
|
||||
local Buf = require("which-key.buf")
|
||||
local Config = require("which-key.config")
|
||||
local Triggers = require("which-key.triggers")
|
||||
local Util = require("which-key.util")
|
||||
|
||||
local uv = vim.uv or vim.loop
|
||||
|
||||
local M = {}
|
||||
|
||||
---@class wk.State
|
||||
---@field mode wk.Mode
|
||||
---@field node wk.Node
|
||||
---@field filter wk.Filter
|
||||
---@field started number
|
||||
---@field show boolean
|
||||
|
||||
---@type wk.State?
|
||||
M.state = nil
|
||||
M.recursion = 0
|
||||
M.recursion_timer = uv.new_timer()
|
||||
|
||||
---@return boolean safe, string? reason
|
||||
function M.safe(mode_change)
|
||||
local old, _new = unpack(vim.split(mode_change, ":", { plain = true }))
|
||||
if old == "c" then
|
||||
return false, "command-mode"
|
||||
elseif vim.fn.reg_recording() ~= "" then
|
||||
return false, "recording"
|
||||
elseif vim.fn.reg_executing() ~= "" then
|
||||
return false, "executing"
|
||||
elseif mode_change:lower() == "v:v" then
|
||||
return false, "visual-block"
|
||||
end
|
||||
local pending = vim.fn.getcharstr(1)
|
||||
if pending ~= "" then
|
||||
return false, "pending " .. ("%q"):format(vim.fn.strtrans(pending))
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
local group = vim.api.nvim_create_augroup("wk", { clear = true })
|
||||
|
||||
if Config.debug then
|
||||
vim.on_key(function(_raw, key)
|
||||
if key and #key > 0 then
|
||||
key = vim.fn.keytrans(key)
|
||||
if not key:find("ScrollWheel") and not key:find("Mouse") then
|
||||
Util.debug("on_key", key)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
vim.api.nvim_create_autocmd({ "RecordingEnter", "RecordingLeave" }, {
|
||||
group = group,
|
||||
callback = function(ev)
|
||||
Util.debug(ev.event)
|
||||
if ev.event == "RecordingEnter" then
|
||||
Buf.clear({ buf = ev.buf })
|
||||
M.stop()
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
local hide = uv.new_timer()
|
||||
vim.api.nvim_create_autocmd({ "FocusLost", "FocusGained" }, {
|
||||
group = group,
|
||||
callback = function(ev)
|
||||
if ev.event == "FocusGained" then
|
||||
hide:stop()
|
||||
elseif M.state then
|
||||
hide:start(5000, 0, function()
|
||||
vim.api.nvim_input("<esc>")
|
||||
end)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
local function defer()
|
||||
local mode = vim.api.nvim_get_mode().mode
|
||||
local mode_keys = Util.keys(mode)
|
||||
local ctx = {
|
||||
operator = mode:find("o") and vim.v.operator or nil,
|
||||
mode = mode_keys[1],
|
||||
}
|
||||
return Config.defer(ctx)
|
||||
end
|
||||
|
||||
local cooldown = Util.cooldown()
|
||||
-- this prevents restarting which-key in the same tick
|
||||
vim.api.nvim_create_autocmd("ModeChanged", {
|
||||
group = group,
|
||||
callback = function(ev)
|
||||
Util.trace("ModeChanged(" .. ev.match .. ")")
|
||||
local mode = Buf.get()
|
||||
|
||||
if cooldown() then
|
||||
Util.debug("cooldown")
|
||||
Util.trace()
|
||||
return
|
||||
end
|
||||
|
||||
local safe, reason = M.safe(ev.match)
|
||||
Util.debug(safe and "Safe(true)" or ("Unsafe(" .. reason .. ")"))
|
||||
if not safe then
|
||||
if mode then
|
||||
Triggers.suspend(mode)
|
||||
end
|
||||
-- dont start when recording or when chars are pending
|
||||
cooldown(true) -- cooldown till next tick
|
||||
M.stop()
|
||||
-- make sure the buffer mode exists
|
||||
elseif mode and Util.xo() then
|
||||
if Config.triggers.modes[mode.mode] and not M.state then
|
||||
M.start({ defer = defer() })
|
||||
end
|
||||
elseif not ev.match:find("c") then
|
||||
M.stop()
|
||||
end
|
||||
Util.trace()
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd({ "LspAttach", "LspDetach" }, {
|
||||
group = group,
|
||||
callback = function(ev)
|
||||
Util.trace(ev.event .. "(" .. ev.buf .. ")")
|
||||
Buf.clear({ buf = ev.buf })
|
||||
Util.trace()
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd({ "BufReadPost", "BufNew" }, {
|
||||
group = group,
|
||||
callback = function(ev)
|
||||
Util.trace(ev.event .. "(" .. ev.buf .. ")")
|
||||
Buf.clear({ buf = ev.buf })
|
||||
Util.trace()
|
||||
end,
|
||||
})
|
||||
|
||||
local current_buf = vim.api.nvim_get_current_buf()
|
||||
vim.api.nvim_create_autocmd({ "BufEnter" }, {
|
||||
group = group,
|
||||
callback = function(ev)
|
||||
current_buf = ev.buf ---@type number
|
||||
Util.trace(ev.event .. "(" .. ev.buf .. ")")
|
||||
Buf.get()
|
||||
Util.trace()
|
||||
end,
|
||||
})
|
||||
|
||||
-- HACK: ModeChanged does not always trigger, so we need to manually
|
||||
-- check for mode changes. This seems to be due to the usage of `:norm` in autocmds.
|
||||
-- See https://github.com/folke/which-key.nvim/issues/787
|
||||
local timer = uv.new_timer()
|
||||
timer:start(0, 50, function()
|
||||
local mode = Util.mapmode()
|
||||
-- check if the mode exists for the current buffer
|
||||
if Buf.bufs[current_buf] and Buf.bufs[current_buf].modes[mode] then
|
||||
return
|
||||
end
|
||||
vim.schedule(Buf.get)
|
||||
end)
|
||||
end
|
||||
|
||||
function M.stop()
|
||||
if M.state == nil then
|
||||
return
|
||||
end
|
||||
Util.debug("state:stop")
|
||||
M.state = nil
|
||||
vim.schedule(function()
|
||||
if not M.state then
|
||||
require("which-key.view").hide()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
---@param state wk.State
|
||||
---@param key? string
|
||||
---@return wk.Node? node
|
||||
function M.check(state, key)
|
||||
local View = require("which-key.view")
|
||||
local node = key == nil and state.node or (key and state.node:find(key, { expand = true }))
|
||||
|
||||
local delta = uv.hrtime() / 1e6 - state.started
|
||||
local timedout = vim.o.timeout and delta > vim.o.timeoutlen
|
||||
|
||||
if node then
|
||||
-- NOTE: a node can be both a keymap and a group
|
||||
-- when it's both, we honor timeoutlen and nowait to decide what to do
|
||||
local has_children = node:count() > 0
|
||||
local is_nowait = node.keymap and (node.keymap.nowait == 1 or not timedout)
|
||||
local is_action = node.action ~= nil
|
||||
if has_children and not is_nowait and not is_action then
|
||||
Util.debug("continue", node.keys, tostring(state.mode), node.plugin)
|
||||
return node
|
||||
end
|
||||
elseif key == "<Esc>" then
|
||||
if state.mode:xo() then
|
||||
Util.exit() -- cancel and exit if in xo mode
|
||||
end
|
||||
return
|
||||
elseif key == "<BS>" then
|
||||
return state.node.parent or state.mode.tree.root
|
||||
elseif View.valid() and key == Config.keys.scroll_down then
|
||||
View.scroll(false)
|
||||
return state.node
|
||||
elseif View.valid() and key == Config.keys.scroll_up then
|
||||
View.scroll(true)
|
||||
return state.node
|
||||
end
|
||||
M.execute(state, key, node)
|
||||
end
|
||||
|
||||
---@param state wk.State
|
||||
---@param key? string
|
||||
---@param node? wk.Node
|
||||
---@return false|wk.Node?
|
||||
function M.execute(state, key, node)
|
||||
Triggers.suspend(state.mode)
|
||||
|
||||
if node and node.action then
|
||||
return node.action()
|
||||
end
|
||||
|
||||
local keystr = node and node.keys or (state.node.keys .. (key or ""))
|
||||
if not state.mode:xo() then
|
||||
if vim.v.count > 0 and state.mode.mode ~= "i" and state.mode.mode ~= "c" then
|
||||
keystr = vim.v.count .. keystr
|
||||
end
|
||||
if vim.v.register ~= Util.reg() and state.mode.mode ~= "i" and state.mode.mode ~= "c" then
|
||||
keystr = '"' .. vim.v.register .. keystr
|
||||
end
|
||||
end
|
||||
Util.debug("feedkeys", tostring(state.mode), keystr)
|
||||
local feed = vim.api.nvim_replace_termcodes(keystr, true, true, true)
|
||||
vim.api.nvim_feedkeys(feed, "mit", false)
|
||||
end
|
||||
|
||||
---@param state wk.State
|
||||
---@return wk.Node? node, boolean? exit
|
||||
function M.step(state)
|
||||
vim.schedule(function()
|
||||
vim.cmd.redraw()
|
||||
if vim.api.nvim__redraw then
|
||||
vim.api.nvim__redraw({ cursor = true })
|
||||
end
|
||||
end)
|
||||
Util.debug("getchar")
|
||||
local ok, char = pcall(vim.fn.getcharstr)
|
||||
if not ok then
|
||||
Util.debug("nok", char)
|
||||
return nil, true
|
||||
end
|
||||
local key = vim.fn.keytrans(char)
|
||||
Util.debug("got", key)
|
||||
|
||||
local node = M.check(state, key)
|
||||
if node == state.node then
|
||||
return M.step(state) -- same node, so try again (scrolling)
|
||||
end
|
||||
return node, key == "<Esc>"
|
||||
end
|
||||
|
||||
---@param opts? wk.Filter
|
||||
function M.start(opts)
|
||||
Util.trace("State(start)", function()
|
||||
local mode = opts and opts.mode or Util.mapmode()
|
||||
local buf = opts and opts.buf or 0
|
||||
local keys = opts and opts.keys or ""
|
||||
return { "Mode(" .. mode .. ":" .. buf .. ") Node(" .. keys .. ")", opts }
|
||||
end)
|
||||
|
||||
opts = opts or {}
|
||||
opts.update = true
|
||||
local mode = Buf.get(opts)
|
||||
opts.update = nil
|
||||
if not mode then
|
||||
Util.debug("no mode")
|
||||
Util.trace()
|
||||
return false
|
||||
end
|
||||
local node = mode.tree:find(opts.keys or {}, { expand = true })
|
||||
if not node then
|
||||
Util.debug("no node")
|
||||
Util.trace()
|
||||
return false
|
||||
end
|
||||
|
||||
local mapmode = mode.mode
|
||||
M.recursion = M.recursion + 1
|
||||
M.recursion_timer:start(500, 0, function()
|
||||
M.recursion = 0
|
||||
end)
|
||||
|
||||
if M.recursion > 50 then
|
||||
Util.error({
|
||||
"Recursion detected.",
|
||||
"Are you manually loading which-key in a keymap?",
|
||||
"Use `opts.triggers` instad.",
|
||||
"Please check the docs.",
|
||||
})
|
||||
Util.debug("recursion detected. Aborting")
|
||||
Util.trace()
|
||||
return false
|
||||
end
|
||||
|
||||
local View = require("which-key.view")
|
||||
|
||||
M.state = {
|
||||
mode = mode,
|
||||
node = node,
|
||||
filter = opts,
|
||||
started = uv.hrtime() / 1e6 - (opts.waited or 0),
|
||||
show = opts.defer ~= true,
|
||||
}
|
||||
|
||||
if not M.check(M.state) then
|
||||
Util.debug("executed")
|
||||
Util.trace()
|
||||
return true
|
||||
end
|
||||
|
||||
local exit = false
|
||||
|
||||
while M.state do
|
||||
mode = Buf.get(opts)
|
||||
if not mode or mode.mode ~= mapmode then
|
||||
break
|
||||
end
|
||||
if M.state.show then
|
||||
View.update(opts)
|
||||
end
|
||||
local child, _exit = M.step(M.state)
|
||||
if child and M.state then
|
||||
M.state.node = child
|
||||
M.state.show = true
|
||||
else
|
||||
exit = _exit or false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if opts.loop and not exit then
|
||||
-- NOTE: flush pending keys to prevent a trigger loop
|
||||
vim.api.nvim_feedkeys("", "x", false)
|
||||
vim.schedule(function()
|
||||
M.start(opts)
|
||||
end)
|
||||
else
|
||||
M.state = nil
|
||||
View.hide()
|
||||
end
|
||||
Util.trace()
|
||||
return true
|
||||
end
|
||||
|
||||
function M.update()
|
||||
if not M.state then
|
||||
return
|
||||
end
|
||||
local mode = Buf.get()
|
||||
if not mode or mode.mode ~= M.state.mode.mode then
|
||||
return
|
||||
end
|
||||
local node = mode.tree:find(M.state.node.path)
|
||||
if not node then
|
||||
return
|
||||
end
|
||||
M.state.node = node
|
||||
require("which-key.view").update({ schedule = false })
|
||||
end
|
||||
|
||||
---@param opts {delay?:number, mode:string, keys:string, plugin?:string, waited?: number}
|
||||
function M.delay(opts)
|
||||
local delay = opts.delay or type(Config.delay) == "function" and Config.delay(opts) or Config.delay --[[@as number]]
|
||||
if opts.waited then
|
||||
delay = delay - opts.waited
|
||||
end
|
||||
return math.max(0, delay)
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,171 @@
|
||||
local Util = require("which-key.util")
|
||||
|
||||
---@class wk.Trigger
|
||||
---@field buf number
|
||||
---@field mode string
|
||||
---@field keys string
|
||||
---@field plugin? string
|
||||
|
||||
local M = {}
|
||||
M._triggers = {} ---@type table<string, wk.Trigger>
|
||||
M.suspended = {} ---@type table<wk.Mode, boolean>
|
||||
|
||||
M.timer = (vim.uv or vim.loop).new_timer()
|
||||
|
||||
--- Checks if a mapping already exists that is not a which-key trigger.
|
||||
---@param trigger wk.Trigger
|
||||
function M.is_mapped(trigger)
|
||||
---@type wk.Keymap?
|
||||
local km
|
||||
pcall(vim.api.nvim_buf_call, trigger.buf, function()
|
||||
km = vim.fn.maparg(trigger.keys, trigger.mode, false, true) --[[@as wk.Keymap]]
|
||||
end)
|
||||
-- not mapped
|
||||
if not km or vim.tbl_isempty(km) then
|
||||
return false
|
||||
end
|
||||
-- ignore <Nop> mappings
|
||||
if Util.is_nop(km.rhs) then
|
||||
return false
|
||||
end
|
||||
-- ignore which-key triggers
|
||||
if km.desc and km.desc:find("which-key-trigger", 1, true) then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
---@param trigger wk.Trigger
|
||||
function M.add(trigger)
|
||||
if M.is_mapped(trigger) then
|
||||
return
|
||||
end
|
||||
vim.keymap.set(trigger.mode, trigger.keys, function()
|
||||
require("which-key.state").start({
|
||||
keys = trigger.keys,
|
||||
})
|
||||
end, {
|
||||
buffer = trigger.buf,
|
||||
nowait = true,
|
||||
desc = "which-key-trigger" .. (trigger.plugin and " " .. trigger.plugin or ""),
|
||||
})
|
||||
M._triggers[M.id(trigger)] = trigger
|
||||
end
|
||||
|
||||
function M.is_active()
|
||||
return vim.tbl_isempty(M._triggers)
|
||||
end
|
||||
|
||||
---@param trigger wk.Trigger
|
||||
function M.del(trigger)
|
||||
M._triggers[M.id(trigger)] = nil
|
||||
if not vim.api.nvim_buf_is_valid(trigger.buf) then
|
||||
return
|
||||
end
|
||||
if M.is_mapped(trigger) then
|
||||
return
|
||||
end
|
||||
pcall(vim.keymap.del, trigger.mode, trigger.keys, { buffer = trigger.buf })
|
||||
end
|
||||
|
||||
---@param trigger wk.Trigger
|
||||
function M.id(trigger)
|
||||
return trigger.buf .. ":" .. trigger.mode .. ":" .. trigger.keys
|
||||
end
|
||||
|
||||
---@param trigger wk.Trigger
|
||||
function M.has(trigger)
|
||||
return M._triggers[M.id(trigger)] ~= nil
|
||||
end
|
||||
|
||||
---@param mode wk.Mode
|
||||
---@param triggers? wk.Trigger[]
|
||||
function M.update(mode, triggers)
|
||||
M.cleanup()
|
||||
if not mode.buf:valid() then
|
||||
for _, trigger in pairs(M._triggers) do
|
||||
if trigger.buf == mode.buf.buf then
|
||||
M.del(trigger)
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
local adds = {} ---@type string[]
|
||||
local dels = {} ---@type string[]
|
||||
local keep = {} ---@type table<string, boolean>
|
||||
for _, node in ipairs(triggers or mode.triggers) do
|
||||
---@type wk.Trigger
|
||||
local trigger = {
|
||||
buf = mode.buf.buf,
|
||||
mode = mode.mode,
|
||||
keys = node.keys,
|
||||
plugin = node.plugin,
|
||||
}
|
||||
local id = M.id(trigger)
|
||||
keep[id] = true
|
||||
if not M.has(trigger) then
|
||||
adds[#adds + 1] = trigger.keys
|
||||
M.add(trigger)
|
||||
end
|
||||
end
|
||||
for id, trigger in pairs(M._triggers) do
|
||||
if trigger.buf == mode.buf.buf and trigger.mode == mode.mode and not keep[id] then
|
||||
M.del(trigger)
|
||||
dels[#dels + 1] = trigger.keys
|
||||
end
|
||||
end
|
||||
if #adds > 0 then
|
||||
Util.debug("Trigger(add) " .. tostring(mode) .. " " .. table.concat(adds, " "))
|
||||
end
|
||||
if #dels > 0 then
|
||||
Util.debug("Trigger(del) " .. tostring(mode) .. " " .. table.concat(dels, " "))
|
||||
end
|
||||
end
|
||||
|
||||
---@param mode wk.Mode
|
||||
function M.attach(mode)
|
||||
if M.suspended[mode] then
|
||||
return
|
||||
end
|
||||
M.update(mode)
|
||||
end
|
||||
|
||||
---@param mode wk.Mode
|
||||
function M.detach(mode)
|
||||
M.update(mode, {})
|
||||
end
|
||||
|
||||
---@param mode? wk.Mode
|
||||
function M.schedule(mode)
|
||||
if mode then
|
||||
M.suspended[mode] = true
|
||||
end
|
||||
M.timer:start(
|
||||
0,
|
||||
0,
|
||||
vim.schedule_wrap(function()
|
||||
for m, _ in pairs(M.suspended) do
|
||||
M.suspended[m] = nil
|
||||
M.attach(m)
|
||||
end
|
||||
end)
|
||||
)
|
||||
end
|
||||
|
||||
function M.cleanup()
|
||||
for _, trigger in pairs(M._triggers) do
|
||||
if not vim.api.nvim_buf_is_valid(trigger.buf) then
|
||||
M.del(trigger)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param mode wk.Mode
|
||||
function M.suspend(mode)
|
||||
Util.debug("suspend", tostring(mode))
|
||||
M.detach(mode)
|
||||
M.suspended[mode] = true
|
||||
M.schedule()
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,120 @@
|
||||
local Util = require("which-key.util")
|
||||
|
||||
---@class wk.Win
|
||||
---@field win? number
|
||||
---@field buf? number
|
||||
---@field opts wk.Win.opts
|
||||
local M = {}
|
||||
M.__index = M
|
||||
|
||||
---@class wk.Win.opts
|
||||
local override = {
|
||||
relative = "editor",
|
||||
style = "minimal",
|
||||
focusable = false,
|
||||
noautocmd = true,
|
||||
wo = {
|
||||
scrolloff = 0,
|
||||
foldenable = false,
|
||||
winhighlight = "Normal:WhichKeyNormal,FloatBorder:WhichKeyBorder,FloatTitle:WhichKeyTitle",
|
||||
winbar = "",
|
||||
statusline = "",
|
||||
wrap = false,
|
||||
},
|
||||
bo = {
|
||||
buftype = "nofile",
|
||||
bufhidden = "wipe",
|
||||
filetype = "wk",
|
||||
},
|
||||
}
|
||||
|
||||
---@type wk.Win.opts
|
||||
local defaults = { col = 0, row = math.huge, zindex = 1000 }
|
||||
|
||||
---@param opts? wk.Win.opts
|
||||
function M.defaults(opts)
|
||||
return vim.tbl_deep_extend("force", {}, defaults, opts or {}, override)
|
||||
end
|
||||
|
||||
---@param opts? wk.Win.opts
|
||||
function M.new(opts)
|
||||
local self = setmetatable({}, M)
|
||||
self.opts = M.defaults(opts)
|
||||
return self
|
||||
end
|
||||
|
||||
function M:valid()
|
||||
return self.buf and vim.api.nvim_buf_is_valid(self.buf) and self.win and vim.api.nvim_win_is_valid(self.win) or false
|
||||
end
|
||||
|
||||
function M:hide()
|
||||
if not (self.buf or self.win) then
|
||||
return
|
||||
end
|
||||
|
||||
---@type number?, number?
|
||||
local buf, win = self.buf, self.win
|
||||
self.buf, self.win = nil, nil
|
||||
|
||||
local function try_close()
|
||||
pcall(vim.api.nvim_win_close, win, true)
|
||||
pcall(vim.api.nvim_buf_delete, buf, { force = true })
|
||||
win = win and vim.api.nvim_win_is_valid(win) and win or nil
|
||||
buf = buf and vim.api.nvim_buf_is_valid(buf) and buf or nil
|
||||
if win or buf then
|
||||
vim.schedule(try_close)
|
||||
end
|
||||
end
|
||||
|
||||
try_close()
|
||||
end
|
||||
|
||||
---@param opts? wk.Win.opts
|
||||
function M:show(opts)
|
||||
if opts then
|
||||
self.opts = vim.tbl_deep_extend("force", self.opts, opts)
|
||||
end
|
||||
local win_opts = vim.deepcopy(self.opts)
|
||||
win_opts.wo = nil
|
||||
win_opts.bo = nil
|
||||
win_opts.padding = nil
|
||||
win_opts.no_overlap = nil
|
||||
|
||||
if vim.fn.has("nvim-0.10") == 0 then
|
||||
win_opts.footer = nil
|
||||
end
|
||||
|
||||
if self:valid() then
|
||||
win_opts.noautocmd = nil
|
||||
return vim.api.nvim_win_set_config(self.win, win_opts)
|
||||
end
|
||||
|
||||
local ei = vim.go.eventignore
|
||||
vim.go.eventignore = "all"
|
||||
|
||||
self.buf = vim.api.nvim_create_buf(false, true)
|
||||
Util.bo(self.buf, self.opts.bo or {})
|
||||
self.win = vim.api.nvim_open_win(self.buf, false, win_opts)
|
||||
Util.wo(self.win, self.opts.wo or {})
|
||||
|
||||
vim.go.eventignore = ei
|
||||
end
|
||||
|
||||
---@param up boolean
|
||||
function M:scroll(up)
|
||||
if not self:valid() then
|
||||
return
|
||||
end
|
||||
local height = vim.api.nvim_win_get_height(self.win)
|
||||
local delta = math.ceil((up and -1 or 1) * height / 2)
|
||||
local view = vim.api.nvim_win_call(self.win, vim.fn.winsaveview)
|
||||
local top = view.topline ---@type number
|
||||
top = top + delta
|
||||
top = math.max(top, 1)
|
||||
top = math.min(top, vim.api.nvim_buf_line_count(self.buf) - height + 1)
|
||||
vim.api.nvim_win_call(self.win, function()
|
||||
vim.fn.winrestview({ topline = top, lnum = top })
|
||||
end)
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,11 @@
|
||||
local timer = (vim.uv or vim.loop).new_timer()
|
||||
timer:start(
|
||||
500,
|
||||
0,
|
||||
vim.schedule_wrap(function()
|
||||
local wk = require("which-key")
|
||||
if not wk.did_setup then
|
||||
wk.setup()
|
||||
end
|
||||
end)
|
||||
)
|
||||
3
config/neovim/store/lazy-plugins/which-key.nvim/scripts/docs
Executable file
3
config/neovim/store/lazy-plugins/which-key.nvim/scripts/docs
Executable file
@ -0,0 +1,3 @@
|
||||
#!/nix/store/4bj2kxdm1462fzcc2i2s4dn33g2angcc-bash-5.2p32/bin/bash
|
||||
|
||||
nvim -u tests/minit.lua -l lua/which-key/docs.lua
|
||||
3
config/neovim/store/lazy-plugins/which-key.nvim/scripts/test
Executable file
3
config/neovim/store/lazy-plugins/which-key.nvim/scripts/test
Executable file
@ -0,0 +1,3 @@
|
||||
#!/nix/store/4bj2kxdm1462fzcc2i2s4dn33g2angcc-bash-5.2p32/bin/bash
|
||||
|
||||
nvim -l tests/minit.lua --minitest
|
||||
@ -0,0 +1,14 @@
|
||||
local Buf = require("which-key.buf")
|
||||
|
||||
before_each(function()
|
||||
require("helpers").reset()
|
||||
end)
|
||||
|
||||
describe("triggers", function()
|
||||
it("does not create hooks for default mappings", function()
|
||||
vim.keymap.set("n", "aa", "<nop>")
|
||||
Buf.get({ mode = "n" })
|
||||
local m = vim.fn.maparg("a", "n", false, true)
|
||||
assert.same(vim.empty_dict(), m)
|
||||
end)
|
||||
end)
|
||||
@ -0,0 +1,12 @@
|
||||
local M = {}
|
||||
|
||||
---@param lines? string[]
|
||||
function M.reset(lines)
|
||||
vim.o.showmode = false
|
||||
vim.api.nvim_feedkeys(vim.keycode("<Ignore><C-\\><C-n><esc>"), "nx", false)
|
||||
vim.cmd("enew")
|
||||
vim.cmd("normal! <c-w>o")
|
||||
vim.api.nvim_buf_set_lines(0, 0, -1, false, lines or {})
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,38 @@
|
||||
local layout = require("which-key.layout")
|
||||
|
||||
describe("dim", function()
|
||||
local tests = {
|
||||
{ 100, { 200 }, 100 },
|
||||
{ 0.2, { 100 }, 20 },
|
||||
{ -0.2, { 100 }, 80 },
|
||||
{ -20, { 100 }, 80 },
|
||||
{ 1, { 100 }, 1 },
|
||||
{ 100, { 200, { min = 50 } }, 100 },
|
||||
{ 100, { 200, { max = 150 } }, 100 },
|
||||
{ 100, { 200, { max = 150, min = 50 } }, 100 },
|
||||
{ 100, { 200, { max = 150, min = 150 } }, 150 },
|
||||
{ 0.2, { 100, { max = 150, min = 20 } }, 20 },
|
||||
{ 0.2, { 100, { max = 50, min = 20 } }, 20 },
|
||||
{ math.huge, { 200 }, 200 },
|
||||
{ -0.5, { 200 }, 100 },
|
||||
{ 0.5, { 200 }, 100 },
|
||||
{ 0.5, { 200, { min = 150 } }, 150 },
|
||||
{ -0.5, { 200, { max = 50 } }, 50 },
|
||||
{ 300, { 200, { max = 250 } }, 200 },
|
||||
{ 300, { 200, { min = 250 } }, 200 },
|
||||
{ -100, { 100, { max = 90, min = 20 } }, 20 },
|
||||
{ -200, { 100, { max = -50, min = -50 } }, 0 },
|
||||
{ 0.2, { 100, { min = 0.5 } }, 50 },
|
||||
{ -200, { 100 }, 0 },
|
||||
{ -1, { 100 }, 99 },
|
||||
{ -0.1, { 100 }, 90 },
|
||||
{ 0.1, { 100 }, 10 },
|
||||
{ 14, { 212, 0.9 }, 191 },
|
||||
}
|
||||
|
||||
for _, test in ipairs(tests) do
|
||||
it("size=" .. test[1] .. ", parent=" .. test[2][1] .. ", result = " .. test[3], function()
|
||||
assert.are.equal(test[3], layout.dim(test[1], unpack(test[2])))
|
||||
end)
|
||||
end
|
||||
end)
|
||||
@ -0,0 +1,101 @@
|
||||
local Mappings = require("which-key.mappings")
|
||||
|
||||
before_each(function()
|
||||
Mappings.notifs = {}
|
||||
end)
|
||||
|
||||
describe("specs v1", function()
|
||||
local tests = {
|
||||
{
|
||||
spec = {
|
||||
["<leader>"] = {
|
||||
name = "leader",
|
||||
["a"] = { "a" },
|
||||
["b"] = { "b" },
|
||||
["c"] = { "c" },
|
||||
},
|
||||
},
|
||||
mappings = {
|
||||
{ lhs = "<leader>", group = true, desc = "leader", mode = "n" },
|
||||
{ lhs = "<leader>a", desc = "a", mode = "n" },
|
||||
{ lhs = "<leader>b", desc = "b", mode = "n" },
|
||||
{ lhs = "<leader>c", desc = "c", mode = "n" },
|
||||
},
|
||||
},
|
||||
{
|
||||
spec = {
|
||||
mode = "v",
|
||||
["<leader>"] = {
|
||||
name = "leader",
|
||||
["a"] = { "a" },
|
||||
["b"] = { "b" },
|
||||
["c"] = { "c" },
|
||||
},
|
||||
},
|
||||
mappings = {
|
||||
{ lhs = "<leader>", group = true, desc = "leader", mode = "v" },
|
||||
{ lhs = "<leader>a", desc = "a", mode = "v" },
|
||||
{ lhs = "<leader>b", desc = "b", mode = "v" },
|
||||
{ lhs = "<leader>c", desc = "c", mode = "v" },
|
||||
},
|
||||
},
|
||||
{
|
||||
spec = { desc = "foo", noremap = true },
|
||||
mappings = {},
|
||||
},
|
||||
{
|
||||
spec = { a = { desc = "which_key_ignore" } },
|
||||
mappings = {
|
||||
{ lhs = "a", hidden = true, mode = "n" },
|
||||
},
|
||||
},
|
||||
{
|
||||
spec = { a = { "foo", cond = false } },
|
||||
mappings = {},
|
||||
},
|
||||
{
|
||||
spec = { a = { "foo", cond = true } },
|
||||
mappings = {
|
||||
{ desc = "foo", lhs = "a", mode = "n" },
|
||||
},
|
||||
},
|
||||
{
|
||||
spec = {
|
||||
a = { "a", cmd = "aa" },
|
||||
b = { "b", callback = "bb" },
|
||||
c = { "cc", "c" },
|
||||
d = { "dd", desc = "d" },
|
||||
},
|
||||
mappings = {
|
||||
{ lhs = "a", desc = "a", rhs = "aa", mode = "n", silent = true },
|
||||
{ lhs = "b", desc = "b", rhs = "bb", mode = "n", silent = true },
|
||||
{ lhs = "c", desc = "c", rhs = "cc", mode = "n", silent = true },
|
||||
{ lhs = "d", desc = "dd", mode = "n" },
|
||||
},
|
||||
},
|
||||
{
|
||||
spec = {
|
||||
a = { "a1" },
|
||||
b = { "b1", "b2" },
|
||||
c = { "c1", desc = "c2" },
|
||||
},
|
||||
mappings = {
|
||||
{ lhs = "a", desc = "a1", mode = "n" },
|
||||
{ lhs = "b", desc = "b2", rhs = "b1", mode = "n", silent = true },
|
||||
{ lhs = "c", desc = "c1", mode = "n" },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
-- Function to run the tests
|
||||
for t, test in ipairs(tests) do
|
||||
it(tostring(t), function()
|
||||
local result = Mappings.parse(test.spec, { version = 1 })
|
||||
assert.same(test.mappings, result)
|
||||
local errors = vim.tbl_filter(function(n)
|
||||
return n.level >= vim.log.levels.ERROR
|
||||
end, Mappings.notifs)
|
||||
assert.same({}, errors)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env -S nvim -l
|
||||
|
||||
vim.env.LAZY_STDPATH = ".tests"
|
||||
vim.env.LAZY_PATH = vim.fs.normalize("~/projects/lazy.nvim")
|
||||
load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))()
|
||||
|
||||
-- Setup lazy.nvim
|
||||
require("lazy.minit").setup({
|
||||
spec = {
|
||||
{
|
||||
dir = vim.uv.cwd(),
|
||||
opts = {
|
||||
notify = false,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ -0,0 +1,125 @@
|
||||
---@module 'luassert'
|
||||
|
||||
local Util = require("which-key.util")
|
||||
|
||||
describe("parse keys", function()
|
||||
local tests = {
|
||||
[" <c-a><esc>Ä<lt>🔥foo"] = {
|
||||
"<Space>",
|
||||
"<C-A>",
|
||||
"<Esc>",
|
||||
"Ä",
|
||||
"<",
|
||||
"🔥",
|
||||
"f",
|
||||
"o",
|
||||
"o",
|
||||
},
|
||||
["\1<esc>Ä<lt>🔥foo"] = {
|
||||
"<C-A>",
|
||||
"<Esc>",
|
||||
"Ä",
|
||||
"<",
|
||||
"🔥",
|
||||
"f",
|
||||
"o",
|
||||
"o",
|
||||
},
|
||||
["<esc>"] = { "<Esc>" },
|
||||
["foo<baz>"] = { "f", "o", "o", "<", "b", "a", "z", ">" },
|
||||
["foo<bar>"] = { "f", "o", "o", "|" },
|
||||
["foo<a-2>"] = { "f", "o", "o", "<M-2>" },
|
||||
["foo<A-2>"] = { "f", "o", "o", "<M-2>" },
|
||||
["foo<m-2>"] = { "f", "o", "o", "<M-2>" },
|
||||
["foo<M-2>"] = { "f", "o", "o", "<M-2>" },
|
||||
["foo<"] = { "f", "o", "o", "<" },
|
||||
["foo<bar"] = { "f", "o", "o", "<", "b", "a", "r" },
|
||||
["foo>"] = { "f", "o", "o", ">" },
|
||||
-- test with japanese chars
|
||||
["fooあ"] = { "f", "o", "o", "あ" },
|
||||
["fooあ<lt>"] = { "f", "o", "o", "あ", "<" },
|
||||
["fooあ<lt>bar"] = { "f", "o", "o", "あ", "<", "b", "a", "r" },
|
||||
["fooあ<lt>bar<lt>"] = { "f", "o", "o", "あ", "<", "b", "a", "r", "<" },
|
||||
["fooあ<lt>bar<lt>baz"] = { "f", "o", "o", "あ", "<", "b", "a", "r", "<", "b", "a", "z" },
|
||||
["fooあ<lt>bar<lt>baz<lt>"] = { "f", "o", "o", "あ", "<", "b", "a", "r", "<", "b", "a", "z", "<" },
|
||||
["fooあ<lt>bar<lt>baz<lt>qux"] = {
|
||||
"f",
|
||||
"o",
|
||||
"o",
|
||||
"あ",
|
||||
"<",
|
||||
"b",
|
||||
"a",
|
||||
"r",
|
||||
"<",
|
||||
"b",
|
||||
"a",
|
||||
"z",
|
||||
"<",
|
||||
"q",
|
||||
"u",
|
||||
"x",
|
||||
},
|
||||
["fooあ<lt>bar<lt>baz<lt>qux<lt>"] = {
|
||||
"f",
|
||||
"o",
|
||||
"o",
|
||||
"あ",
|
||||
"<",
|
||||
"b",
|
||||
"a",
|
||||
"r",
|
||||
"<",
|
||||
"b",
|
||||
"a",
|
||||
"z",
|
||||
"<",
|
||||
"q",
|
||||
"u",
|
||||
"x",
|
||||
"<",
|
||||
},
|
||||
}
|
||||
|
||||
for input, output in pairs(tests) do
|
||||
it(("should parse %q"):format(input), function()
|
||||
local keys = Util.keys(input)
|
||||
assert.same(output, keys)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
describe("modes", function()
|
||||
before_each(function()
|
||||
require("helpers").reset()
|
||||
end)
|
||||
|
||||
local tests = {
|
||||
["gg"] = "n",
|
||||
["vl"] = "x",
|
||||
["<c-v>j"] = "x",
|
||||
["gh"] = "s",
|
||||
["aa"] = "i",
|
||||
["ciw"] = "o",
|
||||
["c"] = "n",
|
||||
["<cmd>terminal exit<cr>"] = "n",
|
||||
}
|
||||
|
||||
local inputs = vim.tbl_keys(tests)
|
||||
table.sort(inputs)
|
||||
for _, input in ipairs(inputs) do
|
||||
local output = tests[input]
|
||||
it(("should return %q for %q"):format(output, input), function()
|
||||
local mode = "n"
|
||||
assert.same(mode, Util.mapmode())
|
||||
vim.api.nvim_create_autocmd("ModeChanged", {
|
||||
once = true,
|
||||
callback = function()
|
||||
mode = Util.mapmode()
|
||||
end,
|
||||
})
|
||||
vim.api.nvim_feedkeys(vim.keycode(input), "nitx", false)
|
||||
assert.same(output, mode)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
Reference in New Issue
Block a user