fix(api): add lhs option for keymap deletion

This commit is contained in:
Barrett Ruth
2026-07-25 10:26:01 -07:00
parent 1facf1e081
commit 22d44ef875
13 changed files with 120 additions and 31 deletions

View File

@@ -620,7 +620,7 @@ nvim_del_current_line() *nvim_del_current_line()*
not allowed when |textlock| is active
Since: 0.1.0
nvim_del_keymap({mode}, {lhs}) *nvim_del_keymap()*
nvim_del_keymap({mode}, {lhs}, {opts}) *nvim_del_keymap()*
Unmaps a global |mapping| for the given mode.
To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|.
@@ -629,8 +629,10 @@ nvim_del_keymap({mode}, {lhs}) *nvim_del_keymap()*
Since: 0.4.0
Parameters: ~
• {mode} (`string`)
• {lhs} (`string`)
• {mode} (`string`) Mode short-name ("n", "i", "v", ...)
• {lhs} (`string`) Left-hand-side |{lhs}| of the mapping.
• {opts} (`vim.api.keyset.keymap_del?`) Optional parameters.
• lhs: When true, only match {lhs}, not {rhs}.
See also: ~
• |nvim_set_keymap()|
@@ -2447,7 +2449,8 @@ nvim_buf_call({buf}, {fn}) *nvim_buf_call()*
Return: ~
(`any`) Value(s) returned by `fn()`.
nvim_buf_del_keymap({buf}, {mode}, {lhs}) *nvim_buf_del_keymap()*
*nvim_buf_del_keymap()*
nvim_buf_del_keymap({buf}, {mode}, {lhs}, {opts})
Unmaps a buffer-local |mapping| for the given mode.
Attributes: ~
@@ -2455,8 +2458,10 @@ nvim_buf_del_keymap({buf}, {mode}, {lhs}) *nvim_buf_del_keymap()*
Parameters: ~
• {buf} (`integer`) Buffer id, or 0 for current buffer
• {mode} (`string`)
• {lhs} (`string`)
• {mode} (`string`) Mode short-name ("n", "i", "v", ...)
• {lhs} (`string`) Left-hand-side |{lhs}| of the mapping.
• {opts} (`vim.api.keyset.keymap_del?`) Optional parameters.
• lhs: When true, only match {lhs}, not {rhs}.
See also: ~
• |nvim_del_keymap()|

View File

@@ -3798,6 +3798,7 @@ Lua module: vim.keymap *vim.keymap*
vim.keymap.del({modes}, {lhs}, {opts}) *vim.keymap.del()*
Removes a mapping, or removes each (mode, lhs) pair if `lhs` is a list.
Examples: >lua
vim.keymap.del('n', 'lhs')
vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buf = 5 })
@@ -3809,6 +3810,7 @@ vim.keymap.del({modes}, {lhs}, {opts}) *vim.keymap.del()*
• {opts} (`table?`) A table with the following fields:
• {buf}? (`integer`) Remove a mapping from the given buffer.
`0` for current.
• {lhs}? (`boolean`) When true, only match {lhs}, not {rhs}.
See also: ~
• |vim.keymap.set()|

View File

@@ -86,8 +86,10 @@ modes.
Remove the mapping of {lhs} for the modes where the
map command applies. The mapping may remain defined
for other modes where it applies.
It also works when {lhs} matches the {rhs} of a
mapping. This is for when an abbreviation applied.
If no mapping with {lhs} exists, it also works when
{lhs} matches the {rhs} of a mapping (see
|vim.keymap.del()| to avoid that). This is for
when an abbreviation applied.
Note: Trailing spaces are included in the {lhs}.
See |map-trailing-white|.

View File

@@ -432,6 +432,8 @@ These existing features changed their behavior.
• |vim.lsp.util.open_floating_preview()| windows (e.g. |vim.lsp.buf.hover()|)
converted to normal windows (e.g. with |CTRL-W_H|) are no longer closed
automatically.
• |nvim_del_keymap()|, |nvim_buf_del_keymap()| and |vim.keymap.del()| can
match only {lhs}, not {rhs}, with `opts.lhs=true`.
==============================================================================
REMOVED FEATURES *news-removed*

View File

@@ -333,9 +333,11 @@ function vim.api.nvim_buf_del_extmark(buf, ns_id, id) end
---
--- @see vim.api.nvim_del_keymap
--- @param buf integer Buffer id, or 0 for current buffer
--- @param mode string
--- @param lhs string
function vim.api.nvim_buf_del_keymap(buf, mode, lhs) end
--- @param mode string Mode short-name ("n", "i", "v", ...)
--- @param lhs string Left-hand-side `{lhs}` of the mapping.
--- @param opts vim.api.keyset.keymap_del? Optional parameters.
--- - lhs: When true, only match {lhs}, not {rhs}.
function vim.api.nvim_buf_del_keymap(buf, mode, lhs, opts) end
--- Deletes a named mark in the buffer. See `mark-motions`.
---
@@ -1069,9 +1071,11 @@ function vim.api.nvim_del_current_line() end
--- To unmap a buffer-local mapping, use `nvim_buf_del_keymap()`.
---
--- @see vim.api.nvim_set_keymap
--- @param mode string
--- @param lhs string
function vim.api.nvim_del_keymap(mode, lhs) end
--- @param mode string Mode short-name ("n", "i", "v", ...)
--- @param lhs string Left-hand-side `{lhs}` of the mapping.
--- @param opts vim.api.keyset.keymap_del? Optional parameters.
--- - lhs: When true, only match {lhs}, not {rhs}.
function vim.api.nvim_del_keymap(mode, lhs, opts) end
--- Deletes an uppercase/file named mark. See `mark-motions`.
---

View File

@@ -373,6 +373,9 @@ error('Cannot require a meta file')
--- @field silent? boolean
--- @field unique? boolean
--- @class vim.api.keyset.keymap_del
--- @field lhs? boolean
--- @class vim.api.keyset.ns_opts
--- @field wins? any[]

View File

@@ -119,8 +119,11 @@ end
---
--- Remove a mapping from the given buffer. `0` for current.
--- @field buf? integer
--- When true, only match {lhs}, not {rhs}.
--- @field lhs? boolean
--- Removes a mapping, or removes each (mode, lhs) pair if `lhs` is a list.
---
--- Examples:
---
--- ```lua
@@ -152,12 +155,14 @@ function keymap.del(modes, lhs, opts)
buf = opts.buffer == true and 0 or opts.buffer --[[@as integer?]]
end
local api_opts = { lhs = opts.lhs }
for _, m in ipairs(modes) do
for _, l in ipairs(lhs) do
if buf then
vim.api.nvim_buf_del_keymap(buf, m, l)
vim.api.nvim_buf_del_keymap(buf, m, l, api_opts)
else
vim.api.nvim_del_keymap(m, l)
vim.api.nvim_del_keymap(m, l, api_opts)
end
end
end

View File

@@ -890,19 +890,25 @@ void nvim_buf_set_keymap(uint64_t channel_id, Buffer buf, String mode, String lh
Dict(keymap) *opts, Error *err)
FUNC_API_SINCE(6)
{
modify_keymap(channel_id, buf, false, mode, lhs, rhs, opts, err);
modify_keymap(channel_id, buf, MAPTYPE_MAP, mode, lhs, rhs, opts, err);
}
/// Unmaps a buffer-local |mapping| for the given mode.
///
/// @see |nvim_del_keymap()|
///
/// @param buf Buffer id, or 0 for current buffer
void nvim_buf_del_keymap(uint64_t channel_id, Buffer buf, String mode, String lhs, Error *err)
/// @param buf Buffer id, or 0 for current buffer
/// @param mode Mode short-name ("n", "i", "v", ...)
/// @param lhs Left-hand-side |{lhs}| of the mapping.
/// @param opts Optional parameters.
/// - lhs: When true, only match {lhs}, not {rhs}.
void nvim_buf_del_keymap(uint64_t channel_id, Buffer buf, String mode, String lhs,
Dict(keymap_del) *opts, Error *err)
FUNC_API_SINCE(6)
{
String rhs = { .data = "", .size = 0 };
modify_keymap(channel_id, buf, true, mode, lhs, rhs, NULL, err);
int maptype = opts && opts->lhs ? MAPTYPE_UNMAP_LHS : MAPTYPE_UNMAP;
modify_keymap(channel_id, buf, maptype, mode, lhs, rhs, NULL, err);
}
/// Sets a buffer-scoped (b:) variable

View File

@@ -94,6 +94,11 @@ typedef struct {
Boolean replace_keycodes;
} Dict(keymap);
typedef struct {
OptionalKeys is_set__keymap_del_;
Boolean lhs;
} Dict(keymap_del);
typedef struct {
Boolean builtin;
} Dict(get_commands);

View File

@@ -1630,18 +1630,24 @@ void nvim_set_keymap(uint64_t channel_id, String mode, String lhs, String rhs, D
Error *err)
FUNC_API_SINCE(6)
{
modify_keymap(channel_id, -1, false, mode, lhs, rhs, opts, err);
modify_keymap(channel_id, -1, MAPTYPE_MAP, mode, lhs, rhs, opts, err);
}
/// Unmaps a global |mapping| for the given mode.
///
/// To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|.
///
/// @param mode Mode short-name ("n", "i", "v", ...)
/// @param lhs Left-hand-side |{lhs}| of the mapping.
/// @param opts Optional parameters.
/// - lhs: When true, only match {lhs}, not {rhs}.
///
/// @see |nvim_set_keymap()|
void nvim_del_keymap(uint64_t channel_id, String mode, String lhs, Error *err)
void nvim_del_keymap(uint64_t channel_id, String mode, String lhs, Dict(keymap_del) *opts,
Error *err)
FUNC_API_SINCE(6)
{
nvim_buf_del_keymap(channel_id, -1, mode, lhs, err);
nvim_buf_del_keymap(channel_id, -1, mode, lhs, opts, err);
}
/// Returns a 2-tuple (Array), where item 0 is the current channel id and item

View File

@@ -2738,11 +2738,13 @@ void ex_abclear(exarg_T *eap)
/// Arguments are handled like @ref nvim_set_keymap unless noted.
/// @param buffer Buffer handle for a specific buffer, or 0 for the current
/// buffer, or -1 to signify global behavior ("all buffers")
/// @param is_unmap When true, removes the mapping that matches {lhs}.
void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mode, String lhs,
/// @param maptype MAPTYPE_MAP to set a mapping, MAPTYPE_UNMAP to remove the mapping that
/// matches {lhs} or {rhs}, MAPTYPE_UNMAP_LHS to only match {lhs}.
void modify_keymap(uint64_t channel_id, Buffer buffer, int maptype, String mode, String lhs,
String rhs, Dict(keymap) *opts, Error *err)
{
LuaRef lua_funcref = LUA_NOREF;
bool is_unmap = maptype == MAPTYPE_UNMAP || maptype == MAPTYPE_UNMAP_LHS;
bool global = (buffer == -1);
if (global) {
buffer = 0;
@@ -2834,14 +2836,11 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
}
// buf_do_map() reads noremap/unmap as its own argument.
int maptype_val = MAPTYPE_MAP;
if (is_unmap) {
maptype_val = MAPTYPE_UNMAP;
} else if (is_noremap) {
maptype_val = MAPTYPE_NOREMAP;
if (maptype == MAPTYPE_MAP && is_noremap) {
maptype = MAPTYPE_NOREMAP;
}
switch (buf_do_map(maptype_val, &parsed_args, mode_val, is_abbrev, target_buf)) {
switch (buf_do_map(maptype, &parsed_args, mode_val, is_abbrev, target_buf)) {
case 0:
break;
case 1:

View File

@@ -594,6 +594,39 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
eq('Invalid (empty) LHS', pcall_err(api.nvim_del_keymap, '', ''))
end)
it('del_keymap preserves {rhs} fallback by default #30258', function()
api.nvim_set_keymap('n', 'ge', 'q', {})
api.nvim_del_keymap('n', 'q')
eq({}, get_mapargs('n', 'ge'))
end)
it('del_keymap with opts.lhs matches only {lhs}, never {rhs} #30258', function()
api.nvim_set_keymap('n', 'ge', 'q', {})
eq('E31: No such mapping', pcall_err(api.nvim_del_keymap, 'n', 'q', { lhs = true }))
eq(generate_mapargs('n', 'ge', 'q'), get_mapargs('n', 'ge'))
api.nvim_del_keymap('n', 'ge', { lhs = true })
eq({}, get_mapargs('n', 'ge'))
end)
it('del_keymap for abbreviations with opts.lhs matches only {lhs}, never {rhs} #30258', function()
api.nvim_set_keymap('ia', 'foo', 'bar', {})
eq('E31: No such mapping', pcall_err(api.nvim_del_keymap, 'ia', 'bar', { lhs = true }))
eq(generate_mapargs('ia', 'foo', 'bar'), get_mapargs('ia', 'foo'))
api.nvim_del_keymap('ia', 'foo', { lhs = true })
eq({}, get_mapargs('ia', 'foo'))
end)
it('buf_del_keymap with opts.lhs matches only {lhs}, never {rhs} #30258', function()
api.nvim_buf_set_keymap(0, 'n', 'ge', 'q', {})
eq('E31: No such mapping', pcall_err(api.nvim_buf_del_keymap, 0, 'n', 'q', { lhs = true }))
eq(generate_mapargs('n', 'ge', 'q', { buffer = 1 }), get_mapargs('n', 'ge'))
api.nvim_buf_del_keymap(0, 'n', 'ge', { lhs = true })
eq({}, get_mapargs('n', 'ge'))
end)
it('error if LHS longer than MAXMAPLEN', function()
-- assume MAXMAPLEN of 50 chars, as declared in mapping_defs.h
local MAXMAPLEN = 50

View File

@@ -3049,6 +3049,23 @@ describe('vim.keymap', function()
eq('\nNo mapping found', n.exec_capture('nmap qwer'))
end)
it('unmap with lhs option', function()
eq(
'q',
exec_lua [[
vim.keymap.set('n', 'ge', 'q')
local ok, err = pcall(vim.keymap.del, 'n', 'q', { lhs = true })
assert(not ok and err:match('E31: No such mapping'), err)
return vim.fn.maparg('ge', 'n')
]]
)
exec_lua [[
vim.keymap.del('n', 'ge', { lhs = true })
]]
eq('\nNo mapping found', n.exec_capture('nmap ge'))
end)
it('buffer-local mappings', function()
eq(
0,