Skip to content

Commit

Permalink
feat: new keymap dsl (folke#352) Docs to come
Browse files Browse the repository at this point in the history
* feat: use vim.notify for logging

* feat!: new mapping DSL. Backwards compatible with previous versions

* chore(docs): auto generate vimdoc

Co-authored-by: folke <[email protected]>
  • Loading branch information
folke and folke authored Oct 21, 2022
1 parent 6885b66 commit fbf0381
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 129 deletions.
14 changes: 14 additions & 0 deletions .neoconf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"lspconfig": {
"sumneko_lua": {
"Lua.format.defaultConfig": {
"indent_style": "space",
"indent_size": "2",
"continuation_indent_size": "2"
},
"Lua.diagnostics.neededFileStatus": {
// "codestyle-check": "Any"
}
}
}
}
2 changes: 1 addition & 1 deletion doc/which-key.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
*which-key.txt* For NVIM v0.5.0 Last change: 2022 September 18
*which-key.txt* For NVIM v0.5.0 Last change: 2022 September 24

==============================================================================
Table of Contents *which-key-table-of-contents*
Expand Down
125 changes: 7 additions & 118 deletions lua/which-key/keys.lua
Original file line number Diff line number Diff line change
Expand Up @@ -166,94 +166,6 @@ function M.get_mappings(mode, prefix_i, buf)
return ret
end

---@param mappings Mapping[]
---@return Mapping[]
function M.parse_mappings(mappings, value, prefix_n)
prefix_n = prefix_n or ""
if type(value) == "string" then
table.insert(mappings, { prefix = prefix_n, label = value })
elseif type(value) == "table" then
if #value == 0 then
-- key group
for k, v in pairs(value) do
if k ~= "name" then
M.parse_mappings(mappings, v, prefix_n .. k)
end
end
if prefix_n ~= "" then
if value.name then
value.name = value.name:gsub("^%+", "")
end
table.insert(mappings, { prefix = prefix_n, label = value.name, group = true })
end
else
-- key mapping
---@type Mapping
local mapping
mapping = { prefix = prefix_n, opts = {}, buf = M.get_buf_option(value) }
for k, v in pairs(value) do
if k == 1 then
mapping.label = v
elseif k == 2 then
mapping.cmd = value[1]
mapping.label = v
elseif k == "noremap" then
mapping.opts.noremap = v
elseif k == "remap" then
mapping.opts.noremap = not v
elseif k == "silent" then
mapping.opts.silent = v
elseif k == "mode" then
mapping.mode = v
elseif k == "expr" then
mapping.opts.expr = v
elseif k == "plugin" then
mapping.group = true
mapping.plugin = v
else
error("Invalid key mapping: " .. vim.inspect(value))
end
end
if mapping.cmd and type(mapping.cmd) == "function" then
if vim.fn.has("nvim-0.7.0") == 1 then
---@diagnostic disable-next-line: assign-type-mismatch
mapping.callback = mapping.cmd
mapping.cmd = ""
else
table.insert(M.functions, mapping.cmd)
if mapping.opts.expr then
mapping.cmd = string.format([[luaeval('require("which-key").execute(%d)')]], #M.functions)
else
mapping.cmd = string.format([[<cmd>lua require("which-key").execute(%d)<cr>]], #M.functions)
end
end
end
table.insert(mappings, mapping)
end
else
error("Invalid mapping " .. vim.inspect(value))
end
return mappings
end

function M.get_buf_option(opts)
for _, k in pairs({ "buffer", "bufnr", "buf" }) do
if opts[k] then
local v = opts[k]
if v == 0 then
v = vim.api.nvim_get_current_buf()
end
opts[k] = nil
if k == "buffer" then
return v
elseif k == "bufnr" or k == "buf" then
Util.warn(string.format([[please use "buffer" instead of %q for buffer mappings]], k))
return v
end
end
end
end

---@type table<string, MappingTree>
M.mappings = {}
M.duplicates = {}
Expand All @@ -278,42 +190,19 @@ end
function M.register(mappings, opts)
opts = opts or {}

local prefix_n = opts.prefix or ""
local mode = opts.mode or "n"

opts.buffer = M.get_buf_option(opts)

mappings = M.parse_mappings({}, mappings, prefix_n)
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
M.get_tree(mode)
local modes = {}

for _, mapping in pairs(mappings) do
if opts.buffer and not mapping.buf then
mapping.buf = opts.buffer
end
if opts.preset then
mapping.preset = true
if not modes[mapping.mode] then
modes[mapping.mode] = true
M.get_tree(mapping.mode)
end
mapping.keys = Util.parse_keys(mapping.prefix)
mapping.mode = mapping.mode or mode
if mapping.cmd or mapping.callback then
mapping.opts = vim.tbl_deep_extend("force", { silent = true, noremap = true }, opts, mapping.opts or {})
local keymap_opts = {
callback = mapping.callback,
silent = mapping.opts.silent,
noremap = mapping.opts.noremap,
nowait = mapping.opts.nowait or false,
expr = mapping.opts.expr or false,
}
if vim.fn.has("nvim-0.7.0") == 1 then
keymap_opts.desc = mapping.label
end
if mapping.cmd and mapping.cmd:lower():sub(1, #"<plug>") == "<plug>" then
keymap_opts.noremap = false
end
M.map(mapping.mode, mapping.prefix, mapping.cmd, mapping.buf, keymap_opts)
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
Expand Down
221 changes: 221 additions & 0 deletions lua/which-key/mappings.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
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

local mapargs = {
"noremap",
"desc",
"expr",
"silent",
"nowait",
"script",
"unique",
"callback",
"replace_keycodes", -- TODO: add config setting for default value
}
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
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
end

function M._parse(value, mappings, opts)
if type(value) ~= "table" then
value = { value }
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
assert(type(list[1]) == "string", "Invalid mapping for " .. vim.inspect({ value = value, opts = opts }))
opts.desc = 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
table.insert(mappings, opts)
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
end

mapping.buf = mapping.buffer
mapping.buffer = nil

mapping.mode = mapping.mode or "n"
mapping.label = mapping.desc or mapping.name
mapping.keys = Util.parse_keys(mapping.prefix or "")

local opts = {}
for _, o in ipairs(mapargs) do
opts[o] = mapping[o]
mapping[o] = nil
end

if vim.fn.has("nvim-0.7.0") == 0 then
opts.replace_keycodes = nil

-- Neovim < 0.7.0 doesn't support descriptions
opts.desc = nil

-- use lua functions proxy for Neovim < 0.7.0
if opts.callback then
local functions = require("which-key.keys").functions
table.insert(functions, opts.callback)
if opts.expr then
opts.cmd = string.format([[luaeval('require("which-key").execute(%d)')]], #functions)
else
opts.cmd = string.format([[<cmd>lua require("which-key").execute(%d)<cr>]], #functions)
end
opts.callback = nil
end
end

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)
end
end

---@return Mapping[]
function M.parse(mappings, 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)
end

return M
1 change: 1 addition & 0 deletions lua/which-key/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
---@field cmd string
---@field opts MappingOptions
---@field keys KeyCodes
---@field mode? string
---@field callback fun()|nil
---@field preset boolean
---@field plugin string
Expand Down
Loading

0 comments on commit fbf0381

Please sign in to comment.