From 04d299c17014b0802c79613252e4de26da84a7c9 Mon Sep 17 00:00:00 2001
From: zeertzjq <zeertzjq@outlook.com>
Date: Thu, 9 Nov 2023 20:42:55 +0800
Subject: [PATCH] vim-patch:8.2.4932: not easy to filter the output of
 maplist()

Problem:    Not easy to filter the output of maplist().
Solution:   Add mode_bits to the dictionary. (Ernie Rael, closes vim/vim#10356)

https://github.com/vim/vim/commit/d8f5f766219273a8579947cc80b92580b6988a4b

Co-authored-by: Ernie Rael <errael@raelity.com>
---
 runtime/doc/builtin.txt                       | 25 ++++++++
 runtime/lua/vim/_meta/vimfn.lua               | 25 ++++++++
 src/nvim/eval.lua                             | 25 ++++++++
 src/nvim/mapping.c                            |  1 +
 test/functional/api/keymap_spec.lua           | 58 ++++++++++++-------
 .../vimscript/map_functions_spec.lua          |  2 +
 test/old/testdir/test_map_functions.vim       | 10 ++--
 7 files changed, 120 insertions(+), 26 deletions(-)

diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 1eb173af58..bc4d1f30c9 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -4186,6 +4186,11 @@ maparg({name} [, {mode} [, {abbr} [, {dict}]]])                       *maparg()*
 		  "nowait"   Do not wait for other, longer mappings.
 			     (|:map-<nowait>|).
 		  "abbr"     True if this is an |abbreviation|.
+		  "mode_bits" Nvim's internal binary representation of "mode".
+			     |mapset()| ignores this; only "mode" is used.
+			     See |maplist()| for usage examples. The values
+			     are from src/nvim/vim.h and may change in the
+			     future.
 
 		The dictionary can be used to restore a mapping with
 		|mapset()|.
@@ -4239,6 +4244,26 @@ maplist([{abbr}])                                                    *maplist()*
 			echo maplist()->filter({_, m ->
 				\ match(get(m, 'rhs', ''), 'MultiMatch') >= 0
 				\ })
+<		It can be tricky to find mappings for particular |:map-modes|.
+		|mapping-dict|'s "mode_bits" can simplify this. For example,
+		the mode_bits for Normal, Insert or Command-line modes are
+		0x19. To find all the mappings available in those modes you
+		can do: >vim
+			let saved_maps = []
+			for m in maplist()
+			    if and(m.mode_bits, 0x19) != 0
+				eval saved_maps->add(m)
+			    endif
+			endfor
+			echo saved_maps->mapnew({_, m -> m.lhs})
+<		The values of the mode_bits are defined in Nvim's
+		src/nvim/vim.h file and they can be discovered at runtime
+		using |:map-commands| and "maplist()". Example: >vim
+			omap xyzzy <Nop>
+			let op_bit = maplist()->filter(
+			    \ {_, m -> m.lhs == 'xyzzy'})[0].mode_bits
+			ounmap xyzzy
+			echo printf("Operator-pending mode bit: 0x%x", op_bit)
 
 mapnew({expr1}, {expr2})                                              *mapnew()*
 		Like |map()| but instead of replacing items in {expr1} a new
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index dea2e476b6..d1e676ef70 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -5047,6 +5047,11 @@ function vim.fn.map(expr1, expr2) end
 ---   "nowait"   Do not wait for other, longer mappings.
 ---        (|:map-<nowait>|).
 ---   "abbr"     True if this is an |abbreviation|.
+---   "mode_bits" Nvim's internal binary representation of "mode".
+---        |mapset()| ignores this; only "mode" is used.
+---        See |maplist()| for usage examples. The values
+---        are from src/nvim/vim.h and may change in the
+---        future.
 ---
 --- The dictionary can be used to restore a mapping with
 --- |mapset()|.
@@ -5111,6 +5116,26 @@ function vim.fn.mapcheck(name, mode, abbr) end
 ---   echo maplist()->filter({_, m ->
 ---     \ match(get(m, 'rhs', ''), 'MultiMatch') >= 0
 ---     \ })
+--- <It can be tricky to find mappings for particular |:map-modes|.
+--- |mapping-dict|'s "mode_bits" can simplify this. For example,
+--- the mode_bits for Normal, Insert or Command-line modes are
+--- 0x19. To find all the mappings available in those modes you
+--- can do: >vim
+---   let saved_maps = []
+---   for m in maplist()
+---       if and(m.mode_bits, 0x19) != 0
+---     eval saved_maps->add(m)
+---       endif
+---   endfor
+---   echo saved_maps->mapnew({_, m -> m.lhs})
+--- <The values of the mode_bits are defined in Nvim's
+--- src/nvim/vim.h file and they can be discovered at runtime
+--- using |:map-commands| and "maplist()". Example: >vim
+---   omap xyzzy <Nop>
+---   let op_bit = maplist()->filter(
+---       \ {_, m -> m.lhs == 'xyzzy'})[0].mode_bits
+---   ounmap xyzzy
+---   echo printf("Operator-pending mode bit: 0x%x", op_bit)
 ---
 --- @return any
 function vim.fn.maplist() end
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index d65b584faa..a2ba0f0189 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -6195,6 +6195,11 @@ M.funcs = {
         "nowait"   Do not wait for other, longer mappings.
       	     (|:map-<nowait>|).
         "abbr"     True if this is an |abbreviation|.
+        "mode_bits" Nvim's internal binary representation of "mode".
+      	     |mapset()| ignores this; only "mode" is used.
+      	     See |maplist()| for usage examples. The values
+      	     are from src/nvim/vim.h and may change in the
+      	     future.
 
       The dictionary can be used to restore a mapping with
       |mapset()|.
@@ -6269,6 +6274,26 @@ M.funcs = {
       	echo maplist()->filter({_, m ->
       		\ match(get(m, 'rhs', ''), 'MultiMatch') >= 0
       		\ })
+      <It can be tricky to find mappings for particular |:map-modes|.
+      |mapping-dict|'s "mode_bits" can simplify this. For example,
+      the mode_bits for Normal, Insert or Command-line modes are
+      0x19. To find all the mappings available in those modes you
+      can do: >vim
+      	let saved_maps = []
+      	for m in maplist()
+      	    if and(m.mode_bits, 0x19) != 0
+      		eval saved_maps->add(m)
+      	    endif
+      	endfor
+      	echo saved_maps->mapnew({_, m -> m.lhs})
+      <The values of the mode_bits are defined in Nvim's
+      src/nvim/vim.h file and they can be discovered at runtime
+      using |:map-commands| and "maplist()". Example: >vim
+      	omap xyzzy <Nop>
+      	let op_bit = maplist()->filter(
+      	    \ {_, m -> m.lhs == 'xyzzy'})[0].mode_bits
+      	ounmap xyzzy
+      	echo printf("Operator-pending mode bit: 0x%x", op_bit)
     ]],
     name = 'maplist',
     params = {},
diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c
index c90c0c5529..2ee1e2e02d 100644
--- a/src/nvim/mapping.c
+++ b/src/nvim/mapping.c
@@ -2125,6 +2125,7 @@ static Dictionary mapblock_fill_dict(const mapblock_T *const mp, const char *lhs
   }
   PUT(dict, "mode", CSTR_AS_OBJ(mapmode));
   PUT(dict, "abbr", INTEGER_OBJ(abbr ? 1 : 0));
+  PUT(dict, "mode_bits", INTEGER_OBJ(mp->m_mode));
 
   return dict;
 }
diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua
index f105a31ed9..3af1faaece 100644
--- a/test/functional/api/keymap_spec.lua
+++ b/test/functional/api/keymap_spec.lua
@@ -19,6 +19,20 @@ local sleep = helpers.sleep
 local sid_api_client = -9
 local sid_lua = -8
 
+local mode_bits_map = {
+  ['n'] = 0x01,
+  ['x'] = 0x02,
+  ['o'] = 0x04,
+  ['c'] = 0x08,
+  ['i'] = 0x10,
+  ['l'] = 0x20,
+  ['s'] = 0x40,
+  ['t'] = 0x80,
+  [' '] = 0x47,
+  ['v'] = 0x42,
+  ['!'] = 0x18,
+}
+
 describe('nvim_get_keymap', function()
   before_each(clear)
 
@@ -36,6 +50,7 @@ describe('nvim_get_keymap', function()
     buffer=0,
     nowait=0,
     mode='n',
+    mode_bits=0x01,
     abbr=0,
     noremap=1,
     lnum=0,
@@ -83,6 +98,7 @@ describe('nvim_get_keymap', function()
     -- The table will be the same except for the mode
     local insert_table = shallowcopy(foo_bar_map_table)
     insert_table['mode'] = 'i'
+    insert_table['mode_bits'] = 0x10
 
     eq({insert_table}, meths.get_keymap('i'))
   end)
@@ -272,6 +288,7 @@ describe('nvim_get_keymap', function()
       ret.lhs = lhs
       ret.rhs = rhs
       ret.mode = mode
+      ret.mode_bits = mode_bits_map[mode]
       return ret
     end
 
@@ -327,6 +344,7 @@ describe('nvim_get_keymap', function()
       lhsraw='|   |',
       rhs='|    |',
       mode='n',
+      mode_bits=0x01,
       abbr=0,
       script=0,
       silent=0,
@@ -375,6 +393,7 @@ describe('nvim_get_keymap', function()
       buffer=0,
       nowait=0,
       mode='n',
+      mode_bits=0x01,
       abbr=0,
       noremap=0,
       lnum=0,
@@ -395,6 +414,7 @@ describe('nvim_get_keymap', function()
       buffer=0,
       nowait=0,
       mode='n',
+      mode_bits=0x01,
       abbr=0,
       noremap=0,
       lnum=0,
@@ -430,7 +450,9 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
     end
 
     local to_return = {}
-    to_return.mode = normalize_mapmode(mode, true)
+    local expected_mode = normalize_mapmode(mode, true)
+    to_return.mode = expected_mode
+    to_return.mode_bits = mode_bits_map[expected_mode]
     to_return.abbr = mode:sub(-1) == 'a' and 1 or 0
     to_return.noremap = not opts.noremap and 0 or 1
     to_return.lhs = lhs
@@ -499,7 +521,12 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
        get_mapargs('', 'lhs'))
   end)
 
-  it('throws errors when given too-long mode shortnames', function()
+  it('error on invalid mode shortname', function()
+    eq('Invalid mode shortname: " "', pcall_err(meths.set_keymap, ' ', 'lhs', 'rhs', {}))
+    eq('Invalid mode shortname: "m"', pcall_err(meths.set_keymap, 'm', 'lhs', 'rhs', {}))
+    eq('Invalid mode shortname: "?"', pcall_err(meths.set_keymap, '?', 'lhs', 'rhs', {}))
+    eq('Invalid mode shortname: "y"', pcall_err(meths.set_keymap, 'y', 'lhs', 'rhs', {}))
+    eq('Invalid mode shortname: "p"', pcall_err(meths.set_keymap, 'p', 'lhs', 'rhs', {}))
     eq('Invalid mode shortname: "a"', pcall_err(meths.set_keymap, 'a', 'lhs', 'rhs', {}))
     eq('Invalid mode shortname: "oa"', pcall_err(meths.set_keymap, 'oa', 'lhs', 'rhs', {}))
     eq('Invalid mode shortname: "!o"', pcall_err(meths.set_keymap, '!o', 'lhs', 'rhs', {}))
@@ -508,6 +535,11 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
     eq('Invalid mode shortname: "map"', pcall_err(meths.set_keymap, 'map', 'lhs', 'rhs', {}))
     eq('Invalid mode shortname: "vmap"', pcall_err(meths.set_keymap, 'vmap', 'lhs', 'rhs', {}))
     eq('Invalid mode shortname: "xnoremap"', pcall_err(meths.set_keymap, 'xnoremap', 'lhs', 'rhs', {}))
+    eq('Invalid mode shortname: " "', pcall_err(meths.del_keymap, ' ', 'lhs'))
+    eq('Invalid mode shortname: "m"', pcall_err(meths.del_keymap, 'm', 'lhs'))
+    eq('Invalid mode shortname: "?"', pcall_err(meths.del_keymap, '?', 'lhs'))
+    eq('Invalid mode shortname: "y"', pcall_err(meths.del_keymap, 'y', 'lhs'))
+    eq('Invalid mode shortname: "p"', pcall_err(meths.del_keymap, 'p', 'lhs'))
     eq('Invalid mode shortname: "a"', pcall_err(meths.del_keymap, 'a', 'lhs'))
     eq('Invalid mode shortname: "oa"', pcall_err(meths.del_keymap, 'oa', 'lhs'))
     eq('Invalid mode shortname: "!o"', pcall_err(meths.del_keymap, '!o', 'lhs'))
@@ -518,22 +550,6 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
     eq('Invalid mode shortname: "xnoremap"', pcall_err(meths.del_keymap, 'xnoremap', 'lhs'))
   end)
 
-  it('error on invalid mode shortname', function()
-    eq('Invalid mode shortname: " "',
-      pcall_err(meths.set_keymap, ' ', 'lhs', 'rhs', {}))
-    eq('Invalid mode shortname: "m"',
-      pcall_err(meths.set_keymap, 'm', 'lhs', 'rhs', {}))
-    eq('Invalid mode shortname: "?"',
-      pcall_err(meths.set_keymap, '?', 'lhs', 'rhs', {}))
-    eq('Invalid mode shortname: "y"',
-      pcall_err(meths.set_keymap, 'y', 'lhs', 'rhs', {}))
-    eq('Invalid mode shortname: "p"',
-      pcall_err(meths.set_keymap, 'p', 'lhs', 'rhs', {}))
-    eq('Invalid mode shortname: "?"', pcall_err(meths.del_keymap, '?', 'lhs'))
-    eq('Invalid mode shortname: "y"', pcall_err(meths.del_keymap, 'y', 'lhs'))
-    eq('Invalid mode shortname: "p"', pcall_err(meths.del_keymap, 'p', 'lhs'))
-  end)
-
   it('error on invalid optnames', function()
     eq("Invalid key: 'silentt'",
       pcall_err(meths.set_keymap, 'n', 'lhs', 'rhs', {silentt = true}))
@@ -839,7 +855,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
     eq(1, exec_lua[[return GlobalCount]])
   end)
 
-  it (':map command shows lua mapping correctly', function()
+  it(':map command shows lua mapping correctly', function()
     exec_lua [[
       vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() print('jkl;') end })
     ]]
@@ -851,7 +867,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
     )
   end)
 
-  it ('mapcheck() returns lua mapping correctly', function()
+  it('mapcheck() returns lua mapping correctly', function()
     exec_lua [[
       vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() print('jkl;') end })
     ]]
@@ -859,7 +875,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
                   "^<Lua %d+>"))
   end)
 
-  it ('maparg() returns lua mapping correctly', function()
+  it('maparg() returns lua mapping correctly', function()
     eq(0, exec_lua([[
       GlobalCount = 0
       vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
diff --git a/test/functional/vimscript/map_functions_spec.lua b/test/functional/vimscript/map_functions_spec.lua
index 14818d9141..acba5e9708 100644
--- a/test/functional/vimscript/map_functions_spec.lua
+++ b/test/functional/vimscript/map_functions_spec.lua
@@ -30,6 +30,7 @@ describe('maparg()', function()
       buffer=0,
       nowait=0,
       mode='n',
+      mode_bits=0x01,
       abbr=0,
       noremap=1,
       lnum=0,
@@ -157,6 +158,7 @@ describe('maparg()', function()
         buffer = 0,
         expr = 0,
         mode = 'n',
+        mode_bits = 0x01,
         abbr = 0,
         noremap = 1,
         nowait = 0,
diff --git a/test/old/testdir/test_map_functions.vim b/test/old/testdir/test_map_functions.vim
index 7e3ff4c88d..2aeb470a0d 100644
--- a/test/old/testdir/test_map_functions.vim
+++ b/test/old/testdir/test_map_functions.vim
@@ -21,13 +21,13 @@ func Test_maparg()
         \ 'lhsraw': "foo\x80\xfc\x04V", 'lhsrawalt': "foo\x16",
         \ 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': sid, 'scriptversion': 1,
         \ 'lnum': lnum + 1, 
-	\ 'rhs': 'is<F4>foo', 'buffer': 0, 'abbr': 0},
+	\ 'rhs': 'is<F4>foo', 'buffer': 0, 'abbr': 0, 'mode_bits': 0x47},
 	\ maparg('foo<C-V>', '', 0, 1))
   call assert_equal({'silent': 1, 'noremap': 1, 'script': 1, 'lhs': 'bar',
         \ 'lhsraw': 'bar', 'mode': 'v',
         \ 'nowait': 0, 'expr': 1, 'sid': sid, 'scriptversion': 1,
         \ 'lnum': lnum + 2,
-	\ 'rhs': 'isbar', 'buffer': 1, 'abbr': 0},
+	\ 'rhs': 'isbar', 'buffer': 1, 'abbr': 0, 'mode_bits': 0x42},
         \ 'bar'->maparg('', 0, 1))
   let lnum = expand('<sflnum>')
   map <buffer> <nowait> foo bar
@@ -35,7 +35,7 @@ func Test_maparg()
         \ 'lhsraw': 'foo', 'mode': ' ',
         \ 'nowait': 1, 'expr': 0, 'sid': sid, 'scriptversion': 1,
         \ 'lnum': lnum + 1, 'rhs': 'bar',
-	\ 'buffer': 1, 'abbr': 0},
+	\ 'buffer': 1, 'abbr': 0, 'mode_bits': 0x47},
         \ maparg('foo', '', 0, 1))
   let lnum = expand('<sflnum>')
   tmap baz foo
@@ -43,7 +43,7 @@ func Test_maparg()
         \ 'lhsraw': 'baz', 'mode': 't',
         \ 'nowait': 0, 'expr': 0, 'sid': sid, 'scriptversion': 1,
         \ 'lnum': lnum + 1, 'rhs': 'foo',
-	\ 'buffer': 0, 'abbr': 0},
+        \ 'buffer': 0, 'abbr': 0, 'mode_bits': 0x80},
         \ maparg('baz', 't', 0, 1))
   let lnum = expand('<sflnum>')
   iab A B
@@ -51,7 +51,7 @@ func Test_maparg()
         \ 'lhsraw': 'A', 'mode': 'i',
         \ 'nowait': 0, 'expr': 0, 'sid': sid, 'scriptversion': 1,
         \ 'lnum': lnum + 1, 'rhs': 'B',
-	\ 'buffer': 0, 'abbr': 1},
+	\ 'buffer': 0, 'abbr': 1, 'mode_bits': 0x0010},
         \ maparg('A', 'i', 1, 1))
   iuna A