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'}, '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'}, 'lr', vim.lsp.buf.references, { buffer=true }) + + -- Can add mapping for specific buffer + vim.keymap.set('n', 'w', "w", { silent = true, buffer = 5 }) + + -- Expr mappings + vim.keymap.set('i', '', function() + return vim.fn.pumvisible() == 1 and "" or "" + end, { expr = true }) + -- mappings + vim.keymap.set('n', '[%', '(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 `` + (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: +---
+---   -- 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'}, 'lr', vim.lsp.buf.references, { buffer=true })
+---
+---   -- Can add mapping for specific buffer
+---   vim.keymap.set('n', 'w', "w", { silent = true, buffer = 5 })
+---
+---   -- Expr mappings
+---   vim.keymap.set('i', '', function()
+---     return vim.fn.pumvisible() == 1 and "" or ""
+---   end, { expr = true })
+---   --  mappings
+---   vim.keymap.set('n', '[%%', '(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)
+--- 
+--- +---@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 `` (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 mappings and don't otherwise. + opts.noremap = is_rhs_luaref or rhs:lower():match("^") == 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: +---
+---   vim.keymap.del('n', 'lhs')
+---
+---   vim.keymap.del({'n', 'i', 'v'}, 'w', { buffer = 5 })
+--- 
+---@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' 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 mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.keymap.set('n', '(asdf)', function() GlobalCount = GlobalCount + 1 end) + vim.keymap.set('n', 'ww', '(asdf)') + return GlobalCount + ]]) + + feed('ww\n') + + eq(1, exec_lua[[return GlobalCount]]) + end) + +end)