aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorshadmansaleh <13149513+shadmansaleh@users.noreply.github.com>2021-12-30 13:30:49 +0600
committershadmansaleh <13149513+shadmansaleh@users.noreply.github.com>2022-01-04 22:31:59 +0600
commit6d41f65aa45f10a93ad476db01413abaac21f27d (patch)
treee83abc5fb3f7f878c02c1628bef0c1e1775bbc11
parent3fd454bd4a6ceb1989d15cf2d3d5e11d7a253b2d (diff)
downloadrneovim-6d41f65aa45f10a93ad476db01413abaac21f27d.tar.gz
rneovim-6d41f65aa45f10a93ad476db01413abaac21f27d.tar.bz2
rneovim-6d41f65aa45f10a93ad476db01413abaac21f27d.zip
feat(lua): add vim.keymap
This introduces two new functions `vim.keymap.set` & `vim.keymap.del` differences compared to regular set_keymap: - remap is used as opposite of noremap. By default it's true for <Plug> keymaps and false for others. - rhs can be lua function. - mode can be a list of modes. - replace_keycodes option for lua function expr maps. (Default: true) - handles buffer specific keymaps Examples: ```lua vim.keymap.set('n', 'asdf', function() print("real lua function") end) vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, {buffer=true}) vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", {silent = true, buffer = 5 }) vim.keymap.set('i', '<Tab>', function() return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>" end, {expr = true}) vim.keymap.set('n', '[%', '<Plug>(MatchitNormalMultiBackward)') vim.keymap.del('n', 'asdf') vim.keymap.del({'n', 'i', 'v'}, '<leader>w', {buffer = 5 }) ```
-rw-r--r--runtime/doc/lua.txt77
-rw-r--r--runtime/lua/vim/keymap.lua135
-rwxr-xr-xscripts/gen_vimdoc.py3
-rw-r--r--src/nvim/lua/vim.lua3
-rw-r--r--test/functional/lua/vim_spec.lua79
5 files changed, 297 insertions, 0 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 395aa54d50..c6bca087c8 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -1847,4 +1847,81 @@ add({filetypes}) *vim.filetype.add()*
{filetypes} table A table containing new filetype maps
(see example).
+
+==============================================================================
+Lua module: keymap *lua-keymap*
+
+del({modes}, {lhs}, {opts}) *vim.keymap.del()*
+ Remove an existing mapping. Examples: >
+
+ vim.keymap.del('n', 'lhs')
+
+ vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 })
+<
+
+ Parameters: ~
+ {opts} table A table of optional arguments:
+ • buffer: (number or boolean) Remove a mapping
+ from the given buffer. When "true" or 0, use the
+ current buffer.
+
+ See also: ~
+ |vim.keymap.set()|
+
+set({mode}, {lhs}, {rhs}, {opts}) *vim.keymap.set()*
+ Add a new |mapping|. Examples: >
+
+ -- Can add mapping to Lua functions
+ vim.keymap.set('n', 'lhs', function() print("real lua function") end)
+
+ -- Can use it to map multiple modes
+ vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, { buffer=true })
+
+ -- Can add mapping for specific buffer
+ vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", { silent = true, buffer = 5 })
+
+ -- Expr mappings
+ vim.keymap.set('i', '<Tab>', function()
+ return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>"
+ end, { expr = true })
+ -- <Plug> mappings
+ vim.keymap.set('n', '[%', '<Plug>(MatchitNormalMultiBackward)')
+<
+
+ Note that in a mapping like: >
+
+ vim.keymap.set('n', 'asdf', require('jkl').my_fun)
+<
+
+ the require('jkl') gets evaluated during this call in order to
+ access the function. If you want to avoid this cost at startup
+ you can wrap it in a function, for example: >
+
+ vim.keymap.set('n', 'asdf', function() return require('jkl').my_fun() end)
+<
+
+ Parameters: ~
+ {mode} string|table Same mode short names as
+ |nvim_set_keymap()|. Can also be list of modes to
+ create mapping on multiple modes.
+ {lhs} string Left-hand side |{lhs}| of the mapping.
+ {rhs} string|function Right-hand side |{rhs}| of the
+ mapping. Can also be a Lua function.
+ {opts} table A table of |:map-arguments| such as
+ "silent". In addition to the options listed in
+ |nvim_set_keymap()|, this table also accepts the
+ following keys:
+ • replace_keycodes: (boolean, default true) When
+ both this and expr is "true",
+ |nvim_replace_termcodes()| is applied to the
+ result of Lua expr maps.
+ • remap: (boolean) Make the mapping recursive.
+ This is the inverse of the "noremap" option from
+ |nvim_set_keymap()|. Default `true` if `lhs` is
+ a string starting with `<plug>`
+ (case-insensitive), `false` otherwise.
+
+ See also: ~
+ |nvim_set_keymap()|
+
vim:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua
new file mode 100644
index 0000000000..d53b790746
--- /dev/null
+++ b/runtime/lua/vim/keymap.lua
@@ -0,0 +1,135 @@
+local keymap = {}
+
+--- Add a new |mapping|.
+--- Examples:
+--- <pre>
+--- -- Can add mapping to Lua functions
+--- vim.keymap.set('n', 'lhs', function() print("real lua function") end)
+---
+--- -- Can use it to map multiple modes
+--- vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, { buffer=true })
+---
+--- -- Can add mapping for specific buffer
+--- vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", { silent = true, buffer = 5 })
+---
+--- -- Expr mappings
+--- vim.keymap.set('i', '<Tab>', function()
+--- return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>"
+--- end, { expr = true })
+--- -- <Plug> mappings
+--- vim.keymap.set('n', '[%%', '<Plug>(MatchitNormalMultiBackward)')
+--- </pre>
+---
+--- Note that in a mapping like:
+--- <pre>
+--- vim.keymap.set('n', 'asdf', require('jkl').my_fun)
+--- </pre>
+---
+--- the require('jkl') gets evaluated during this call in order to access the function. If you want to
+--- avoid this cost at startup you can wrap it in a function, for example:
+--- <pre>
+--- vim.keymap.set('n', 'asdf', function() return require('jkl').my_fun() end)
+--- </pre>
+---
+---@param mode string|table Same mode short names as |nvim_set_keymap()|.
+--- Can also be list of modes to create mapping on multiple modes.
+---@param lhs string Left-hand side |{lhs}| of the mapping.
+---@param rhs string|function Right-hand side |{rhs}| of the mapping. Can also be a Lua function.
+--
+---@param opts table A table of |:map-arguments| such as "silent". In addition to the options
+--- listed in |nvim_set_keymap()|, this table also accepts the following keys:
+--- - replace_keycodes: (boolean, default true) When both this and expr is "true",
+--- |nvim_replace_termcodes()| is applied to the result of Lua expr maps.
+--- - remap: (boolean) Make the mapping recursive. This is the
+--- inverse of the "noremap" option from |nvim_set_keymap()|.
+--- Default `true` if `lhs` is a string starting with `<plug>` (case-insensitive), `false` otherwise.
+---@see |nvim_set_keymap()|
+function keymap.set(mode, lhs, rhs, opts)
+ vim.validate {
+ mode = {mode, {'s', 't'}},
+ lhs = {lhs, 's'},
+ rhs = {rhs, {'s', 'f'}},
+ opts = {opts, 't', true}
+ }
+
+ opts = vim.deepcopy(opts) or {}
+ local is_rhs_luaref = type(rhs) == "function"
+ mode = type(mode) == 'string' and {mode} or mode
+
+ if is_rhs_luaref and opts.expr and opts.replace_keycodes ~= false then
+ local user_rhs = rhs
+ rhs = function ()
+ return vim.api.nvim_replace_termcodes(user_rhs(), true, true, true)
+ end
+ end
+ -- clear replace_keycodes from opts table
+ opts.replace_keycodes = nil
+
+ if opts.remap == nil then
+ -- remap by default on <plug> mappings and don't otherwise.
+ opts.noremap = is_rhs_luaref or rhs:lower():match("^<plug>") == nil
+ else
+ -- remaps behavior is opposite of noremap option.
+ opts.noremap = not opts.remap
+ opts.remap = nil
+ end
+
+ if is_rhs_luaref then
+ opts.callback = rhs
+ rhs = ''
+ end
+
+ if opts.buffer then
+ local bufnr = opts.buffer == true and 0 or opts.buffer
+ opts.buffer = nil
+ for _, m in ipairs(mode) do
+ vim.api.nvim_buf_set_keymap(bufnr, m, lhs, rhs, opts)
+ end
+ else
+ opts.buffer = nil
+ for _, m in ipairs(mode) do
+ vim.api.nvim_set_keymap(m, lhs, rhs, opts)
+ end
+ end
+end
+
+--- Remove an existing mapping.
+--- Examples:
+--- <pre>
+--- vim.keymap.del('n', 'lhs')
+---
+--- vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 })
+--- </pre>
+---@param opts table A table of optional arguments:
+--- - buffer: (number or boolean) Remove a mapping from the given buffer.
+--- When "true" or 0, use the current buffer.
+---@see |vim.keymap.set()|
+---
+function keymap.del(modes, lhs, opts)
+ vim.validate {
+ mode = {modes, {'s', 't'}},
+ lhs = {lhs, 's'},
+ opts = {opts, 't', true}
+ }
+
+ opts = opts or {}
+ modes = type(modes) == 'string' and {modes} or modes
+
+ local buffer = false
+ if opts.buffer ~= nil then
+ buffer = opts.buffer == true and 0 or opts.buffer
+ opts.buffer = nil
+ end
+
+ if buffer == false then
+ for _, mode in ipairs(modes) do
+ vim.api.nvim_del_keymap(mode, lhs)
+ end
+ else
+ for _, mode in ipairs(modes) do
+ vim.api.nvim_buf_del_keymap(buffer, mode, lhs)
+ end
+ end
+end
+
+return keymap
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
index 3810ebd9d0..3e9fb21039 100755
--- a/scripts/gen_vimdoc.py
+++ b/scripts/gen_vimdoc.py
@@ -129,6 +129,7 @@ CONFIG = {
'uri.lua',
'ui.lua',
'filetype.lua',
+ 'keymap.lua',
],
'files': ' '.join([
os.path.join(base_dir, 'src/nvim/lua/vim.lua'),
@@ -136,6 +137,7 @@ CONFIG = {
os.path.join(base_dir, 'runtime/lua/vim/uri.lua'),
os.path.join(base_dir, 'runtime/lua/vim/ui.lua'),
os.path.join(base_dir, 'runtime/lua/vim/filetype.lua'),
+ os.path.join(base_dir, 'runtime/lua/vim/keymap.lua'),
]),
'file_patterns': '*.lua',
'fn_name_prefix': '',
@@ -151,6 +153,7 @@ CONFIG = {
'uri': 'vim',
'ui': 'vim.ui',
'filetype': 'vim.filetype',
+ 'keymap': 'vim.keymap',
},
'append_only': [
'shared.lua',
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index 6ef46ee844..abf382484a 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -115,6 +115,9 @@ setmetatable(vim, {
elseif key == 'ui' then
t.ui = require('vim.ui')
return t.ui
+ elseif key == 'keymap' then
+ t.keymap = require('vim.keymap')
+ return t.keymap
end
end
})
diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua
index 9bf376b6fe..53c1de7b01 100644
--- a/test/functional/lua/vim_spec.lua
+++ b/test/functional/lua/vim_spec.lua
@@ -2298,3 +2298,82 @@ describe('lua: require("mod") from packages', function()
eq('I am fancy_z.lua', exec_lua [[ return require'fancy_z' ]])
end)
end)
+
+describe('vim.keymap', function()
+ it('can make a mapping', function()
+ eq(0, exec_lua [[
+ GlobalCount = 0
+ vim.keymap.set('n', 'asdf', function() GlobalCount = GlobalCount + 1 end)
+ return GlobalCount
+ ]])
+
+ feed('asdf\n')
+
+ eq(1, exec_lua[[return GlobalCount]])
+ end)
+
+ it('can make an expr mapping', function()
+ exec_lua [[
+ vim.keymap.set('n', 'aa', function() return ':lua SomeValue = 99<cr>' end, {expr = true})
+ ]]
+
+ feed('aa')
+
+ eq(99, exec_lua[[return SomeValue]])
+ end)
+
+ it('can overwrite a mapping', function()
+ eq(0, exec_lua [[
+ GlobalCount = 0
+ vim.keymap.set('n', 'asdf', function() GlobalCount = GlobalCount + 1 end)
+ return GlobalCount
+ ]])
+
+ feed('asdf\n')
+
+ eq(1, exec_lua[[return GlobalCount]])
+
+ exec_lua [[
+ vim.keymap.set('n', 'asdf', function() GlobalCount = GlobalCount - 1 end)
+ ]]
+
+ feed('asdf\n')
+
+ eq(0, exec_lua[[return GlobalCount]])
+ end)
+
+ it('can unmap a mapping', function()
+ eq(0, exec_lua [[
+ GlobalCount = 0
+ vim.keymap.set('n', 'asdf', function() GlobalCount = GlobalCount + 1 end)
+ return GlobalCount
+ ]])
+
+ feed('asdf\n')
+
+ eq(1, exec_lua[[return GlobalCount]])
+
+ exec_lua [[
+ vim.keymap.del('n', 'asdf')
+ ]]
+
+ feed('asdf\n')
+
+ eq(1, exec_lua[[return GlobalCount]])
+ eq('\nNo mapping found', helpers.exec_capture('nmap asdf'))
+ end)
+
+ it('can do <Plug> mappings', function()
+ eq(0, exec_lua [[
+ GlobalCount = 0
+ vim.keymap.set('n', '<plug>(asdf)', function() GlobalCount = GlobalCount + 1 end)
+ vim.keymap.set('n', 'ww', '<plug>(asdf)')
+ return GlobalCount
+ ]])
+
+ feed('ww\n')
+
+ eq(1, exec_lua[[return GlobalCount]])
+ end)
+
+end)