keymap_spec.lua (45908B)
1 local t = require('test.testutil') 2 local n = require('test.functional.testnvim')() 3 4 local clear = n.clear 5 local command = n.command 6 local eq, neq = t.eq, t.neq 7 local exec_lua = n.exec_lua 8 local exec = n.exec 9 local feed = n.feed 10 local fn = n.fn 11 local api = n.api 12 local matches = t.matches 13 local source = n.source 14 local pcall_err = t.pcall_err 15 16 local shallowcopy = t.shallowcopy 17 local sleep = vim.uv.sleep 18 19 local sid_api_client = -9 20 local sid_lua = -8 21 22 local mode_bits_map = { 23 ['n'] = 0x01, 24 ['x'] = 0x02, 25 ['o'] = 0x04, 26 ['c'] = 0x08, 27 ['i'] = 0x10, 28 ['l'] = 0x20, 29 ['s'] = 0x40, 30 ['t'] = 0x80, 31 [' '] = 0x47, 32 ['v'] = 0x42, 33 ['!'] = 0x18, 34 } 35 36 describe('nvim_get_keymap', function() 37 before_each(clear) 38 39 -- Basic mapping and table to be used to describe results 40 local foo_bar_string = 'nnoremap foo bar' 41 local foo_bar_map_table = { 42 lhs = 'foo', 43 lhsraw = 'foo', 44 script = 0, 45 silent = 0, 46 rhs = 'bar', 47 expr = 0, 48 replace_keycodes = 0, 49 sid = 0, 50 scriptversion = 1, 51 buffer = 0, 52 nowait = 0, 53 mode = 'n', 54 mode_bits = 0x01, 55 abbr = 0, 56 noremap = 1, 57 lnum = 0, 58 } 59 60 it('returns empty list when no map', function() 61 eq({}, api.nvim_get_keymap('n')) 62 end) 63 64 it('returns list of all applicable mappings', function() 65 command(foo_bar_string) 66 -- Only one mapping available 67 -- Should be the same as the dictionary we supplied earlier 68 -- and the dictionary you would get from maparg 69 -- since this is a global map, and not script local 70 eq({ foo_bar_map_table }, api.nvim_get_keymap('n')) 71 eq({ fn.maparg('foo', 'n', false, true) }, api.nvim_get_keymap('n')) 72 73 -- Add another mapping 74 command('nnoremap foo_longer bar_longer') 75 local foolong_bar_map_table = shallowcopy(foo_bar_map_table) 76 foolong_bar_map_table['lhs'] = 'foo_longer' 77 foolong_bar_map_table['lhsraw'] = 'foo_longer' 78 foolong_bar_map_table['rhs'] = 'bar_longer' 79 80 eq({ foolong_bar_map_table, foo_bar_map_table }, api.nvim_get_keymap('n')) 81 82 -- Remove a mapping 83 command('unmap foo_longer') 84 eq({ foo_bar_map_table }, api.nvim_get_keymap('n')) 85 end) 86 87 it('works for other modes', function() 88 -- Add two mappings, one in insert and one normal 89 -- We'll only check the insert mode one 90 command('nnoremap not_going to_check') 91 92 command('inoremap foo bar') 93 -- The table will be the same except for the mode 94 local insert_table = shallowcopy(foo_bar_map_table) 95 insert_table['mode'] = 'i' 96 insert_table['mode_bits'] = 0x10 97 98 eq({ insert_table }, api.nvim_get_keymap('i')) 99 end) 100 101 it('considers scope', function() 102 -- change the map slightly 103 command('nnoremap foo_longer bar_longer') 104 local foolong_bar_map_table = shallowcopy(foo_bar_map_table) 105 foolong_bar_map_table['lhs'] = 'foo_longer' 106 foolong_bar_map_table['lhsraw'] = 'foo_longer' 107 foolong_bar_map_table['rhs'] = 'bar_longer' 108 109 local buffer_table = shallowcopy(foo_bar_map_table) 110 buffer_table['buffer'] = 1 111 112 command('nnoremap <buffer> foo bar') 113 114 -- The buffer mapping should not show up 115 eq({ foolong_bar_map_table }, api.nvim_get_keymap('n')) 116 eq({ buffer_table }, api.nvim_buf_get_keymap(0, 'n')) 117 end) 118 119 it('considers scope for overlapping maps', function() 120 command('nnoremap foo bar') 121 122 local buffer_table = shallowcopy(foo_bar_map_table) 123 buffer_table['buffer'] = 1 124 125 command('nnoremap <buffer> foo bar') 126 127 eq({ foo_bar_map_table }, api.nvim_get_keymap('n')) 128 eq({ buffer_table }, api.nvim_buf_get_keymap(0, 'n')) 129 end) 130 131 it('can retrieve mapping for different buffers', function() 132 local original_buffer = api.nvim_buf_get_number(0) 133 -- Place something in each of the buffers to make sure they stick around 134 -- and set hidden so we can leave them 135 command('set hidden') 136 command('new') 137 command('normal! ihello 2') 138 command('new') 139 command('normal! ihello 3') 140 141 local final_buffer = api.nvim_buf_get_number(0) 142 143 command('nnoremap <buffer> foo bar') 144 -- Final buffer will have buffer mappings 145 local buffer_table = shallowcopy(foo_bar_map_table) 146 buffer_table['buffer'] = final_buffer 147 eq({ buffer_table }, api.nvim_buf_get_keymap(final_buffer, 'n')) 148 eq({ buffer_table }, api.nvim_buf_get_keymap(0, 'n')) 149 150 command('buffer ' .. original_buffer) 151 eq(original_buffer, api.nvim_buf_get_number(0)) 152 -- Original buffer won't have any mappings 153 eq({}, api.nvim_get_keymap('n')) 154 eq({}, api.nvim_buf_get_keymap(0, 'n')) 155 eq({ buffer_table }, api.nvim_buf_get_keymap(final_buffer, 'n')) 156 end) 157 158 -- Test toggle switches for basic options 159 -- @param option The key represented in the `maparg()` result dict 160 local function global_and_buffer_test( 161 map, 162 option, 163 option_token, 164 global_on_result, 165 buffer_on_result, 166 global_off_result, 167 buffer_off_result, 168 new_windows 169 ) 170 local function make_new_windows(number_of_windows) 171 if new_windows == nil then 172 return nil 173 end 174 175 for _ = 1, number_of_windows do 176 command('new') 177 end 178 end 179 180 local mode = string.sub(map, 1, 1) 181 -- Don't run this for the <buffer> mapping, since it doesn't make sense 182 if option_token ~= '<buffer>' then 183 it( 184 string.format( 185 'returns %d for the key "%s" when %s is used globally with %s (%s)', 186 global_on_result, 187 option, 188 option_token, 189 map, 190 mode 191 ), 192 function() 193 make_new_windows(new_windows) 194 command(map .. ' ' .. option_token .. ' foo bar') 195 local result = api.nvim_get_keymap(mode)[1][option] 196 eq(global_on_result, result) 197 end 198 ) 199 end 200 201 it( 202 string.format( 203 'returns %d for the key "%s" when %s is used for buffers with %s (%s)', 204 buffer_on_result, 205 option, 206 option_token, 207 map, 208 mode 209 ), 210 function() 211 make_new_windows(new_windows) 212 command(map .. ' <buffer> ' .. option_token .. ' foo bar') 213 local result = api.nvim_buf_get_keymap(0, mode)[1][option] 214 eq(buffer_on_result, result) 215 end 216 ) 217 218 -- Don't run these for the <buffer> mapping, since it doesn't make sense 219 if option_token ~= '<buffer>' then 220 it( 221 string.format( 222 'returns %d for the key "%s" when %s is not used globally with %s (%s)', 223 global_off_result, 224 option, 225 option_token, 226 map, 227 mode 228 ), 229 function() 230 make_new_windows(new_windows) 231 command(map .. ' baz bat') 232 local result = api.nvim_get_keymap(mode)[1][option] 233 eq(global_off_result, result) 234 end 235 ) 236 237 it( 238 string.format( 239 'returns %d for the key "%s" when %s is not used for buffers with %s (%s)', 240 buffer_off_result, 241 option, 242 option_token, 243 map, 244 mode 245 ), 246 function() 247 make_new_windows(new_windows) 248 command(map .. ' <buffer> foo bar') 249 250 local result = api.nvim_buf_get_keymap(0, mode)[1][option] 251 eq(buffer_off_result, result) 252 end 253 ) 254 end 255 end 256 257 -- Standard modes and returns the same values in the dictionary as maparg() 258 local mode_list = { 'nnoremap', 'nmap', 'imap', 'inoremap', 'cnoremap' } 259 for mode in pairs(mode_list) do 260 global_and_buffer_test(mode_list[mode], 'silent', '<silent>', 1, 1, 0, 0) 261 global_and_buffer_test(mode_list[mode], 'nowait', '<nowait>', 1, 1, 0, 0) 262 global_and_buffer_test(mode_list[mode], 'expr', '<expr>', 1, 1, 0, 0) 263 end 264 265 -- noremap will now be 2 if script was used, which is not the same as maparg() 266 global_and_buffer_test('nmap', 'noremap', '<script>', 2, 2, 0, 0) 267 global_and_buffer_test('nnoremap', 'noremap', '<script>', 2, 2, 1, 1) 268 269 -- buffer will return the buffer ID, which is not the same as maparg() 270 -- Three of these tests won't run 271 global_and_buffer_test('nnoremap', 'buffer', '<buffer>', nil, 3, nil, nil, 2) 272 273 it('returns script numbers for global maps', function() 274 source([[ 275 function! s:maparg_test_function() abort 276 return 'testing' 277 endfunction 278 279 nnoremap fizz :call <SID>maparg_test_function()<CR> 280 ]]) 281 local sid_result = api.nvim_get_keymap('n')[1]['sid'] 282 eq(1, sid_result) 283 eq('testing', api.nvim_call_function('<SNR>' .. sid_result .. '_maparg_test_function', {})) 284 end) 285 286 it('returns script numbers for buffer maps', function() 287 source([[ 288 function! s:maparg_test_function() abort 289 return 'testing' 290 endfunction 291 292 nnoremap <buffer> fizz :call <SID>maparg_test_function()<CR> 293 ]]) 294 local sid_result = api.nvim_buf_get_keymap(0, 'n')[1]['sid'] 295 eq(1, sid_result) 296 eq('testing', api.nvim_call_function('<SNR>' .. sid_result .. '_maparg_test_function', {})) 297 end) 298 299 it('works with <F12> and others', function() 300 command('nnoremap <F12> :let g:maparg_test_var = 1<CR>') 301 eq('<F12>', api.nvim_get_keymap('n')[1]['lhs']) 302 eq(':let g:maparg_test_var = 1<CR>', api.nvim_get_keymap('n')[1]['rhs']) 303 end) 304 305 it('works correctly despite various &cpo settings', function() 306 local cpo_table = { 307 script = 0, 308 silent = 0, 309 expr = 0, 310 replace_keycodes = 0, 311 sid = 0, 312 scriptversion = 1, 313 buffer = 0, 314 nowait = 0, 315 abbr = 0, 316 noremap = 1, 317 lnum = 0, 318 } 319 local function cpomap(lhs, rhs, mode) 320 local ret = shallowcopy(cpo_table) 321 local lhsraw = api.nvim_eval(('"%s"'):format(lhs:gsub('\\', '\\\\'):gsub('<', '\\<*'))) 322 local lhsrawalt = api.nvim_eval(('"%s"'):format(lhs:gsub('\\', '\\\\'):gsub('<', '\\<'))) 323 ret.lhs = lhs 324 ret.lhsraw = lhsraw 325 ret.lhsrawalt = lhsrawalt ~= lhsraw and lhsrawalt or nil 326 ret.rhs = rhs 327 ret.mode = mode 328 ret.mode_bits = mode_bits_map[mode] 329 return ret 330 end 331 332 command('set cpo+=B') 333 command('nnoremap \\<C-a><C-a><LT>C-a>\\ \\<C-b><C-b><LT>C-b>\\') 334 command('nnoremap <special> \\<C-c><C-c><LT>C-c>\\ \\<C-d><C-d><LT>C-d>\\') 335 336 command('set cpo+=B') 337 command('xnoremap \\<C-a><C-a><LT>C-a>\\ \\<C-b><C-b><LT>C-b>\\') 338 command('xnoremap <special> \\<C-c><C-c><LT>C-c>\\ \\<C-d><C-d><LT>C-d>\\') 339 340 command('set cpo-=B') 341 command('snoremap \\<C-a><C-a><LT>C-a>\\ \\<C-b><C-b><LT>C-b>\\') 342 command('snoremap <special> \\<C-c><C-c><LT>C-c>\\ \\<C-d><C-d><LT>C-d>\\') 343 344 command('set cpo-=B') 345 command('onoremap \\<C-a><C-a><LT>C-a>\\ \\<C-b><C-b><LT>C-b>\\') 346 command('onoremap <special> \\<C-c><C-c><LT>C-c>\\ \\<C-d><C-d><LT>C-d>\\') 347 348 for _, cmd in ipairs({ 349 'set cpo-=B', 350 'set cpo+=B', 351 }) do 352 command(cmd) 353 eq({ 354 cpomap('\\<C-C><C-C><lt>C-c>\\', '\\<C-D><C-D><lt>C-d>\\', 'n'), 355 cpomap('\\<C-A><C-A><lt>C-a>\\', '\\<C-B><C-B><lt>C-b>\\', 'n'), 356 }, api.nvim_get_keymap('n')) 357 eq({ 358 cpomap('\\<C-C><C-C><lt>C-c>\\', '\\<C-D><C-D><lt>C-d>\\', 'x'), 359 cpomap('\\<C-A><C-A><lt>C-a>\\', '\\<C-B><C-B><lt>C-b>\\', 'x'), 360 }, api.nvim_get_keymap('x')) 361 eq({ 362 cpomap('<lt>C-c><C-C><lt>C-c> ', '<lt>C-d><C-D><lt>C-d>', 's'), 363 cpomap('<lt>C-a><C-A><lt>C-a> ', '<lt>C-b><C-B><lt>C-b>', 's'), 364 }, api.nvim_get_keymap('s')) 365 eq({ 366 cpomap('<lt>C-c><C-C><lt>C-c> ', '<lt>C-d><C-D><lt>C-d>', 'o'), 367 cpomap('<lt>C-a><C-A><lt>C-a> ', '<lt>C-b><C-B><lt>C-b>', 'o'), 368 }, api.nvim_get_keymap('o')) 369 end 370 end) 371 372 it('always uses space for space and bar for bar', function() 373 local space_table = { 374 lhs = '| |', 375 lhsraw = '| |', 376 rhs = '| |', 377 mode = 'n', 378 mode_bits = 0x01, 379 abbr = 0, 380 script = 0, 381 silent = 0, 382 expr = 0, 383 replace_keycodes = 0, 384 sid = 0, 385 scriptversion = 1, 386 buffer = 0, 387 nowait = 0, 388 noremap = 1, 389 lnum = 0, 390 } 391 command('nnoremap \\|<Char-0x20><Char-32><Space><Bar> \\|<Char-0x20><Char-32><Space> <Bar>') 392 eq({ space_table }, api.nvim_get_keymap('n')) 393 end) 394 395 it('can handle lua mappings', function() 396 eq( 397 0, 398 exec_lua([[ 399 GlobalCount = 0 400 vim.api.nvim_set_keymap('n', 'asdf', '', { 401 callback = function() GlobalCount = GlobalCount + 1 end, 402 }) 403 return GlobalCount 404 ]]) 405 ) 406 407 feed('asdf\n') 408 eq(1, exec_lua([[return GlobalCount]])) 409 410 eq( 411 2, 412 exec_lua([[ 413 vim.api.nvim_get_keymap('n')[1].callback() 414 return GlobalCount 415 ]]) 416 ) 417 418 exec([[ 419 call nvim_get_keymap('n')[0].callback() 420 ]]) 421 eq(3, exec_lua([[return GlobalCount]])) 422 423 local mapargs = api.nvim_get_keymap('n') 424 mapargs[1].callback = nil 425 eq({ 426 lhs = 'asdf', 427 lhsraw = 'asdf', 428 script = 0, 429 silent = 0, 430 expr = 0, 431 replace_keycodes = 0, 432 sid = sid_lua, 433 scriptversion = 1, 434 buffer = 0, 435 nowait = 0, 436 mode = 'n', 437 mode_bits = 0x01, 438 abbr = 0, 439 noremap = 0, 440 lnum = 0, 441 }, mapargs[1]) 442 end) 443 444 it('can handle map descriptions', function() 445 api.nvim_set_keymap('n', 'lhs', 'rhs', { desc = 'map description' }) 446 eq({ 447 lhs = 'lhs', 448 lhsraw = 'lhs', 449 rhs = 'rhs', 450 script = 0, 451 silent = 0, 452 expr = 0, 453 replace_keycodes = 0, 454 sid = sid_api_client, 455 scriptversion = 1, 456 buffer = 0, 457 nowait = 0, 458 mode = 'n', 459 mode_bits = 0x01, 460 abbr = 0, 461 noremap = 0, 462 lnum = 0, 463 desc = 'map description', 464 }, api.nvim_get_keymap('n')[1]) 465 end) 466 467 it('can get abbreviations', function() 468 command('inoreabbr foo bar') 469 command('cnoreabbr <buffer> foo baz') 470 471 local mapargs_i = { 472 abbr = 1, 473 buffer = 0, 474 expr = 0, 475 replace_keycodes = 0, 476 lhs = 'foo', 477 lhsraw = 'foo', 478 lnum = 0, 479 mode = 'i', 480 mode_bits = 0x10, 481 noremap = 1, 482 nowait = 0, 483 rhs = 'bar', 484 script = 0, 485 scriptversion = 1, 486 sid = 0, 487 silent = 0, 488 } 489 local mapargs_c = { 490 abbr = 1, 491 buffer = 1, 492 expr = 0, 493 replace_keycodes = 0, 494 lhs = 'foo', 495 lhsraw = 'foo', 496 lnum = 0, 497 mode = 'c', 498 mode_bits = 0x08, 499 noremap = 1, 500 nowait = 0, 501 rhs = 'baz', 502 script = 0, 503 scriptversion = 1, 504 sid = 0, 505 silent = 0, 506 } 507 508 local curbuf = api.nvim_get_current_buf() 509 510 eq({ mapargs_i }, api.nvim_get_keymap('ia')) 511 eq({}, api.nvim_buf_get_keymap(curbuf, 'ia')) 512 513 eq({}, api.nvim_get_keymap('ca')) 514 eq({ mapargs_c }, api.nvim_buf_get_keymap(curbuf, 'ca')) 515 516 eq({ mapargs_i }, api.nvim_get_keymap('!a')) 517 eq({ mapargs_c }, api.nvim_buf_get_keymap(curbuf, '!a')) 518 end) 519 end) 520 521 describe('nvim_set_keymap, nvim_del_keymap', function() 522 before_each(clear) 523 524 -- `generate_expected` is truthy: for generating an expected output for 525 -- maparg(), which does not accept "!" (though it returns "!" in its output 526 -- if getting a mapping set with |:map!|). 527 local function normalize_mapmode(mode, generate_expected) 528 if mode:sub(-1) == 'a' then 529 mode = mode:sub(1, -2) 530 end 531 if not generate_expected and mode == '!' then 532 -- Cannot retrieve mapmode-ic mappings with "!", but can with "i" or "c". 533 mode = 'i' 534 elseif mode == '' then 535 mode = generate_expected and ' ' or mode 536 end 537 return mode 538 end 539 540 -- Generate a mapargs dict, for comparison against the mapping that was 541 -- actually set 542 local function generate_mapargs(mode, lhs, rhs, opts) 543 if not opts then 544 opts = {} 545 end 546 547 local to_return = {} 548 local expected_mode = normalize_mapmode(mode, true) 549 to_return.mode = expected_mode 550 to_return.mode_bits = mode_bits_map[expected_mode] 551 to_return.abbr = mode:sub(-1) == 'a' and 1 or 0 552 to_return.noremap = not opts.noremap and 0 or 1 553 to_return.lhs = lhs 554 to_return.rhs = rhs 555 to_return.script = 0 556 to_return.silent = not opts.silent and 0 or 1 557 to_return.nowait = not opts.nowait and 0 or 1 558 to_return.expr = not opts.expr and 0 or 1 559 to_return.replace_keycodes = not opts.replace_keycodes and 0 or 1 560 to_return.sid = not opts.sid and sid_api_client or opts.sid 561 to_return.scriptversion = 1 562 to_return.buffer = not opts.buffer and 0 or opts.buffer 563 to_return.lnum = not opts.lnum and 0 or opts.lnum 564 to_return.desc = opts.desc 565 566 return to_return 567 end 568 569 -- Gets a maparg() dict from Nvim, if one exists. 570 local function get_mapargs(mode, lhs) 571 local mapargs = fn.maparg(lhs, normalize_mapmode(mode), mode:sub(-1) == 'a', true) 572 -- drop "lhsraw" and "lhsrawalt" which are hard to check 573 mapargs.lhsraw = nil 574 mapargs.lhsrawalt = nil 575 return mapargs 576 end 577 578 it('error on empty LHS', function() 579 -- escape parentheses in lua string, else comparison fails erroneously 580 eq('Invalid (empty) LHS', pcall_err(api.nvim_set_keymap, '', '', 'rhs', {})) 581 eq('Invalid (empty) LHS', pcall_err(api.nvim_set_keymap, '', '', '', {})) 582 eq('Invalid (empty) LHS', pcall_err(api.nvim_del_keymap, '', '')) 583 end) 584 585 it('error if LHS longer than MAXMAPLEN', function() 586 -- assume MAXMAPLEN of 50 chars, as declared in mapping_defs.h 587 local MAXMAPLEN = 50 588 local lhs = '' 589 for i = 1, MAXMAPLEN do 590 lhs = lhs .. (i % 10) 591 end 592 593 -- exactly 50 chars should be fine 594 api.nvim_set_keymap('', lhs, 'rhs', {}) 595 596 -- del_keymap should unmap successfully 597 api.nvim_del_keymap('', lhs) 598 eq({}, get_mapargs('', lhs)) 599 600 -- 51 chars should produce an error 601 lhs = lhs .. '1' 602 eq( 603 'LHS exceeds maximum map length: ' .. lhs, 604 pcall_err(api.nvim_set_keymap, '', lhs, 'rhs', {}) 605 ) 606 eq('LHS exceeds maximum map length: ' .. lhs, pcall_err(api.nvim_del_keymap, '', lhs)) 607 end) 608 609 it('does not throw errors when rhs is longer than MAXMAPLEN', function() 610 local MAXMAPLEN = 50 611 local rhs = '' 612 for i = 1, MAXMAPLEN do 613 rhs = rhs .. (i % 10) 614 end 615 rhs = rhs .. '1' 616 api.nvim_set_keymap('', 'lhs', rhs, {}) 617 eq(generate_mapargs('', 'lhs', rhs), get_mapargs('', 'lhs')) 618 end) 619 620 it('error on invalid mode shortname', function() 621 eq('Invalid mode shortname: " "', pcall_err(api.nvim_set_keymap, ' ', 'lhs', 'rhs', {})) 622 eq('Invalid mode shortname: "m"', pcall_err(api.nvim_set_keymap, 'm', 'lhs', 'rhs', {})) 623 eq('Invalid mode shortname: "?"', pcall_err(api.nvim_set_keymap, '?', 'lhs', 'rhs', {})) 624 eq('Invalid mode shortname: "y"', pcall_err(api.nvim_set_keymap, 'y', 'lhs', 'rhs', {})) 625 eq('Invalid mode shortname: "p"', pcall_err(api.nvim_set_keymap, 'p', 'lhs', 'rhs', {})) 626 eq('Invalid mode shortname: "a"', pcall_err(api.nvim_set_keymap, 'a', 'lhs', 'rhs', {})) 627 eq('Invalid mode shortname: "oa"', pcall_err(api.nvim_set_keymap, 'oa', 'lhs', 'rhs', {})) 628 eq('Invalid mode shortname: "!o"', pcall_err(api.nvim_set_keymap, '!o', 'lhs', 'rhs', {})) 629 eq('Invalid mode shortname: "!i"', pcall_err(api.nvim_set_keymap, '!i', 'lhs', 'rhs', {})) 630 eq('Invalid mode shortname: "!!"', pcall_err(api.nvim_set_keymap, '!!', 'lhs', 'rhs', {})) 631 eq('Invalid mode shortname: "map"', pcall_err(api.nvim_set_keymap, 'map', 'lhs', 'rhs', {})) 632 eq('Invalid mode shortname: "vmap"', pcall_err(api.nvim_set_keymap, 'vmap', 'lhs', 'rhs', {})) 633 eq( 634 'Invalid mode shortname: "xnoremap"', 635 pcall_err(api.nvim_set_keymap, 'xnoremap', 'lhs', 'rhs', {}) 636 ) 637 eq('Invalid mode shortname: " "', pcall_err(api.nvim_del_keymap, ' ', 'lhs')) 638 eq('Invalid mode shortname: "m"', pcall_err(api.nvim_del_keymap, 'm', 'lhs')) 639 eq('Invalid mode shortname: "?"', pcall_err(api.nvim_del_keymap, '?', 'lhs')) 640 eq('Invalid mode shortname: "y"', pcall_err(api.nvim_del_keymap, 'y', 'lhs')) 641 eq('Invalid mode shortname: "p"', pcall_err(api.nvim_del_keymap, 'p', 'lhs')) 642 eq('Invalid mode shortname: "a"', pcall_err(api.nvim_del_keymap, 'a', 'lhs')) 643 eq('Invalid mode shortname: "oa"', pcall_err(api.nvim_del_keymap, 'oa', 'lhs')) 644 eq('Invalid mode shortname: "!o"', pcall_err(api.nvim_del_keymap, '!o', 'lhs')) 645 eq('Invalid mode shortname: "!i"', pcall_err(api.nvim_del_keymap, '!i', 'lhs')) 646 eq('Invalid mode shortname: "!!"', pcall_err(api.nvim_del_keymap, '!!', 'lhs')) 647 eq('Invalid mode shortname: "map"', pcall_err(api.nvim_del_keymap, 'map', 'lhs')) 648 eq('Invalid mode shortname: "vmap"', pcall_err(api.nvim_del_keymap, 'vmap', 'lhs')) 649 eq('Invalid mode shortname: "xnoremap"', pcall_err(api.nvim_del_keymap, 'xnoremap', 'lhs')) 650 end) 651 652 it('error on invalid optnames', function() 653 eq( 654 "Invalid key: 'silentt'", 655 pcall_err(api.nvim_set_keymap, 'n', 'lhs', 'rhs', { silentt = true }) 656 ) 657 eq("Invalid key: 'sidd'", pcall_err(api.nvim_set_keymap, 'n', 'lhs', 'rhs', { sidd = false })) 658 eq( 659 "Invalid key: 'nowaiT'", 660 pcall_err(api.nvim_set_keymap, 'n', 'lhs', 'rhs', { nowaiT = false }) 661 ) 662 end) 663 664 it('error on <buffer> option key', function() 665 eq( 666 "Invalid key: 'buffer'", 667 pcall_err(api.nvim_set_keymap, 'n', 'lhs', 'rhs', { buffer = true }) 668 ) 669 end) 670 671 it('error when "replace_keycodes" is used without "expr"', function() 672 eq( 673 '"replace_keycodes" requires "expr"', 674 pcall_err(api.nvim_set_keymap, 'n', 'lhs', 'rhs', { replace_keycodes = true }) 675 ) 676 end) 677 678 local optnames = { 'nowait', 'silent', 'script', 'expr', 'unique' } 679 for _, opt in ipairs(optnames) do 680 -- note: need '%' to escape hyphens, which have special meaning in lua 681 it('throws an error when given non-boolean value for ' .. opt, function() 682 local opts = {} 683 opts[opt] = 'fooo' 684 eq(opt .. ' is not a boolean', pcall_err(api.nvim_set_keymap, 'n', 'lhs', 'rhs', opts)) 685 end) 686 end 687 688 -- Perform tests of basic functionality 689 it('sets ordinary mappings', function() 690 api.nvim_set_keymap('n', 'lhs', 'rhs', {}) 691 eq(generate_mapargs('n', 'lhs', 'rhs'), get_mapargs('n', 'lhs')) 692 693 api.nvim_set_keymap('v', 'lhs', 'rhs', {}) 694 eq(generate_mapargs('v', 'lhs', 'rhs'), get_mapargs('v', 'lhs')) 695 end) 696 697 it('does not throw when LHS or RHS have leading/trailing whitespace', function() 698 api.nvim_set_keymap('n', ' lhs', 'rhs', {}) 699 eq(generate_mapargs('n', '<Space><Space><Space>lhs', 'rhs'), get_mapargs('n', ' lhs')) 700 701 api.nvim_set_keymap('n', 'lhs ', 'rhs', {}) 702 eq(generate_mapargs('n', 'lhs<Space><Space><Space><Space>', 'rhs'), get_mapargs('n', 'lhs ')) 703 704 api.nvim_set_keymap('v', ' lhs ', '\trhs\t\f', {}) 705 eq(generate_mapargs('v', '<Space>lhs<Space><Space>', '\trhs\t\f'), get_mapargs('v', ' lhs ')) 706 end) 707 708 it('can set noremap mappings', function() 709 api.nvim_set_keymap('x', 'lhs', 'rhs', { noremap = true }) 710 eq(generate_mapargs('x', 'lhs', 'rhs', { noremap = true }), get_mapargs('x', 'lhs')) 711 712 api.nvim_set_keymap('t', 'lhs', 'rhs', { noremap = true }) 713 eq(generate_mapargs('t', 'lhs', 'rhs', { noremap = true }), get_mapargs('t', 'lhs')) 714 end) 715 716 it('can unmap mappings', function() 717 api.nvim_set_keymap('v', 'lhs', 'rhs', {}) 718 api.nvim_del_keymap('v', 'lhs') 719 eq({}, get_mapargs('v', 'lhs')) 720 721 api.nvim_set_keymap('t', 'lhs', 'rhs', { noremap = true }) 722 api.nvim_del_keymap('t', 'lhs') 723 eq({}, get_mapargs('t', 'lhs')) 724 end) 725 726 -- Test some edge cases 727 it('"!" and empty string are synonyms for mapmode-nvo', function() 728 local nvo_shortnames = { '', '!' } 729 for _, name in ipairs(nvo_shortnames) do 730 api.nvim_set_keymap(name, 'lhs', 'rhs', {}) 731 api.nvim_del_keymap(name, 'lhs') 732 eq({}, get_mapargs(name, 'lhs')) 733 end 734 end) 735 736 local special_chars = { '<C-U>', '<S-Left>', '<F12><F2><Tab>', '<Space><Tab>' } 737 for _, lhs in ipairs(special_chars) do 738 for _, rhs in ipairs(special_chars) do 739 local mapmode = '!' 740 it('can set mappings with special characters, lhs: ' .. lhs .. ', rhs: ' .. rhs, function() 741 api.nvim_set_keymap(mapmode, lhs, rhs, {}) 742 eq(generate_mapargs(mapmode, lhs, rhs), get_mapargs(mapmode, lhs)) 743 end) 744 end 745 end 746 747 it('can set mappings containing C0 control codes', function() 748 api.nvim_set_keymap('n', '\n\r\n', 'rhs', {}) 749 local expected = generate_mapargs('n', '<NL><CR><NL>', 'rhs') 750 eq(expected, get_mapargs('n', '<NL><CR><NL>')) 751 end) 752 753 it('can set mappings whose RHS is a <Nop>', function() 754 api.nvim_set_keymap('i', 'lhs', '<Nop>', {}) 755 command('normal ilhs') 756 eq({ '' }, api.nvim_buf_get_lines(0, 0, -1, 0)) -- imap to <Nop> does nothing 757 eq(generate_mapargs('i', 'lhs', '<Nop>', {}), get_mapargs('i', 'lhs')) 758 759 -- also test for case insensitivity 760 api.nvim_set_keymap('i', 'lhs', '<nOp>', {}) 761 command('normal ilhs') 762 eq({ '' }, api.nvim_buf_get_lines(0, 0, -1, 0)) 763 -- note: RHS in returned mapargs() dict reflects the original RHS 764 -- provided by the user 765 eq(generate_mapargs('i', 'lhs', '<nOp>', {}), get_mapargs('i', 'lhs')) 766 767 api.nvim_set_keymap('i', 'lhs', '<NOP>', {}) 768 command('normal ilhs') 769 eq({ '' }, api.nvim_buf_get_lines(0, 0, -1, 0)) 770 eq(generate_mapargs('i', 'lhs', '<NOP>', {}), get_mapargs('i', 'lhs')) 771 772 -- a single ^V in RHS is also <Nop> (see :h map-empty-rhs) 773 api.nvim_set_keymap('i', 'lhs', '\022', {}) 774 command('normal ilhs') 775 eq({ '' }, api.nvim_buf_get_lines(0, 0, -1, 0)) 776 eq(generate_mapargs('i', 'lhs', '\022', {}), get_mapargs('i', 'lhs')) 777 end) 778 779 it('treats an empty RHS in a mapping like a <Nop>', function() 780 api.nvim_set_keymap('i', 'lhs', '', {}) 781 command('normal ilhs') 782 eq({ '' }, api.nvim_buf_get_lines(0, 0, -1, 0)) 783 eq(generate_mapargs('i', 'lhs', '', {}), get_mapargs('i', 'lhs')) 784 end) 785 786 it('can set and unset <M-">', function() 787 -- Taken from the legacy test: test_mapping.vim. Exposes a bug in which 788 -- replace_termcodes changes the length of the mapping's LHS, but 789 -- do_map continues to use the *old* length of LHS. 790 api.nvim_set_keymap('i', '<M-">', 'foo', {}) 791 api.nvim_del_keymap('i', '<M-">') 792 eq({}, get_mapargs('i', '<M-">')) 793 end) 794 795 it( 796 'interprets control sequences in expr-quotes correctly when called ' .. 'inside vim', 797 function() 798 command([[call nvim_set_keymap('i', "\<space>", "\<tab>", {})]]) 799 eq(generate_mapargs('i', '<Space>', '\t', { sid = 0 }), get_mapargs('i', '<Space>')) 800 feed('i ') 801 eq({ '\t' }, api.nvim_buf_get_lines(0, 0, -1, 0)) 802 end 803 ) 804 805 it('throws appropriate error messages when setting <unique> maps', function() 806 api.nvim_set_keymap('l', 'lhs', 'rhs', {}) 807 eq( 808 'E227: Mapping already exists for lhs', 809 pcall_err(api.nvim_set_keymap, 'l', 'lhs', 'rhs', { unique = true }) 810 ) 811 -- different mapmode, no error should be thrown 812 api.nvim_set_keymap('t', 'lhs', 'rhs', { unique = true }) 813 814 api.nvim_set_keymap('n', '<tab>', 'rhs', {}) 815 eq( 816 'E227: Mapping already exists for <tab>', 817 pcall_err(api.nvim_set_keymap, 'n', '<tab>', 'rhs', { unique = true }) 818 ) 819 820 api.nvim_set_keymap('ia', 'lhs', 'rhs', {}) 821 eq( 822 'E226: Abbreviation already exists for lhs', 823 pcall_err(api.nvim_set_keymap, 'ia', 'lhs', 'rhs', { unique = true }) 824 ) 825 end) 826 827 it('can set <expr> mappings whose RHS change dynamically', function() 828 exec([[ 829 function! FlipFlop() abort 830 if !exists('g:flip') | let g:flip = 0 | endif 831 let g:flip = !g:flip 832 return g:flip 833 endfunction 834 ]]) 835 eq(1, api.nvim_call_function('FlipFlop', {})) 836 eq(0, api.nvim_call_function('FlipFlop', {})) 837 eq(1, api.nvim_call_function('FlipFlop', {})) 838 eq(0, api.nvim_call_function('FlipFlop', {})) 839 840 api.nvim_set_keymap('i', 'lhs', 'FlipFlop()', { expr = true }) 841 command('normal ilhs') 842 eq({ '1' }, api.nvim_buf_get_lines(0, 0, -1, 0)) 843 844 command('normal! ggVGd') 845 846 command('normal ilhs') 847 eq({ '0' }, api.nvim_buf_get_lines(0, 0, -1, 0)) 848 end) 849 850 it('can set mappings that do trigger other mappings', function() 851 api.nvim_set_keymap('i', 'mhs', 'rhs', {}) 852 api.nvim_set_keymap('i', 'lhs', 'mhs', {}) 853 854 command('normal imhs') 855 eq({ 'rhs' }, api.nvim_buf_get_lines(0, 0, -1, 0)) 856 857 command('normal! ggVGd') 858 859 command('normal ilhs') 860 eq({ 'rhs' }, api.nvim_buf_get_lines(0, 0, -1, 0)) 861 end) 862 863 it("can set noremap mappings that don't trigger other mappings", function() 864 api.nvim_set_keymap('i', 'mhs', 'rhs', {}) 865 api.nvim_set_keymap('i', 'lhs', 'mhs', { noremap = true }) 866 867 command('normal imhs') 868 eq({ 'rhs' }, api.nvim_buf_get_lines(0, 0, -1, 0)) 869 870 command('normal! ggVGd') 871 872 command('normal ilhs') -- shouldn't trigger mhs-to-rhs mapping 873 eq({ 'mhs' }, api.nvim_buf_get_lines(0, 0, -1, 0)) 874 end) 875 876 it('can set nowait mappings that fire without waiting', function() 877 api.nvim_set_keymap('i', '123456', 'longer', {}) 878 api.nvim_set_keymap('i', '123', 'shorter', { nowait = true }) 879 880 -- feed keys one at a time; if all keys arrive atomically, the longer 881 -- mapping will trigger 882 local keys = 'i123456' 883 for c in string.gmatch(keys, '.') do 884 feed(c) 885 sleep(5) 886 end 887 eq({ 'shorter456' }, api.nvim_buf_get_lines(0, 0, -1, 0)) 888 end) 889 890 -- Perform exhaustive tests of basic functionality 891 local mapmodes = { 'n', 'v', 'x', 's', 'o', '!', 'i', 'l', 'c', 't', '', 'ia', 'ca', '!a' } 892 for _, mapmode in ipairs(mapmodes) do 893 it('can set/unset normal mappings in mapmode ' .. mapmode, function() 894 api.nvim_set_keymap(mapmode, 'lhs', 'rhs', {}) 895 eq(generate_mapargs(mapmode, 'lhs', 'rhs'), get_mapargs(mapmode, 'lhs')) 896 897 -- some mapmodes (like 'o') will prevent other mapmodes (like '!') from 898 -- taking effect, so unmap after each mapping 899 api.nvim_del_keymap(mapmode, 'lhs') 900 eq({}, get_mapargs(mapmode, 'lhs')) 901 end) 902 end 903 904 for _, mapmode in ipairs(mapmodes) do 905 it('can set/unset noremap mappings using mapmode ' .. mapmode, function() 906 api.nvim_set_keymap(mapmode, 'lhs', 'rhs', { noremap = true }) 907 eq(generate_mapargs(mapmode, 'lhs', 'rhs', { noremap = true }), get_mapargs(mapmode, 'lhs')) 908 909 api.nvim_del_keymap(mapmode, 'lhs') 910 eq({}, get_mapargs(mapmode, 'lhs')) 911 end) 912 end 913 914 -- Test map-arguments, using optnames from above 915 -- remove some map arguments that are harder to test, or were already tested 916 optnames = { 'nowait', 'silent', 'expr', 'noremap' } 917 for _, mapmode in ipairs(mapmodes) do 918 -- Test with single mappings 919 for _, maparg in ipairs(optnames) do 920 it('can set/unset ' .. mapmode .. '-mappings with maparg: ' .. maparg, function() 921 api.nvim_set_keymap(mapmode, 'lhs', 'rhs', { [maparg] = true }) 922 eq( 923 generate_mapargs(mapmode, 'lhs', 'rhs', { [maparg] = true }), 924 get_mapargs(mapmode, 'lhs') 925 ) 926 api.nvim_del_keymap(mapmode, 'lhs') 927 eq({}, get_mapargs(mapmode, 'lhs')) 928 end) 929 it( 930 'can set/unset ' 931 .. mapmode 932 .. '-mode mappings with maparg ' 933 .. maparg 934 .. ', whose value is false', 935 function() 936 api.nvim_set_keymap(mapmode, 'lhs', 'rhs', { [maparg] = false }) 937 eq(generate_mapargs(mapmode, 'lhs', 'rhs'), get_mapargs(mapmode, 'lhs')) 938 api.nvim_del_keymap(mapmode, 'lhs') 939 eq({}, get_mapargs(mapmode, 'lhs')) 940 end 941 ) 942 end 943 944 -- Test with triplets of mappings, one of which is false 945 for i = 1, (#optnames - 2) do 946 local opt1, opt2, opt3 = optnames[i], optnames[i + 1], optnames[i + 2] 947 it( 948 'can set/unset ' 949 .. mapmode 950 .. '-mode mappings with mapargs ' 951 .. opt1 952 .. ', ' 953 .. opt2 954 .. ', ' 955 .. opt3, 956 function() 957 local opts = { [opt1] = true, [opt2] = false, [opt3] = true } 958 api.nvim_set_keymap(mapmode, 'lhs', 'rhs', opts) 959 eq(generate_mapargs(mapmode, 'lhs', 'rhs', opts), get_mapargs(mapmode, 'lhs')) 960 api.nvim_del_keymap(mapmode, 'lhs') 961 eq({}, get_mapargs(mapmode, 'lhs')) 962 end 963 ) 964 end 965 end 966 967 it('can make lua mappings', function() 968 eq( 969 0, 970 exec_lua [[ 971 GlobalCount = 0 972 vim.api.nvim_set_keymap('n', 'asdf', '', { 973 callback = function() GlobalCount = GlobalCount + 1 end, 974 }) 975 return GlobalCount 976 ]] 977 ) 978 979 feed('asdf\n') 980 981 eq(1, exec_lua [[return GlobalCount]]) 982 end) 983 984 it(':map command shows lua mapping correctly', function() 985 exec_lua [[ 986 vim.api.nvim_set_keymap('n', 'asdf', '', { 987 callback = function() print('jkl;') end, 988 }) 989 ]] 990 matches( 991 '^\nn asdf <Lua %d+>', 992 exec_lua [[return vim.api.nvim_exec2(':nmap asdf', { output = true }).output]] 993 ) 994 end) 995 996 it('mapcheck() returns lua mapping correctly', function() 997 exec_lua [[ 998 vim.api.nvim_set_keymap('n', 'asdf', '', { 999 callback = function() print('jkl;') end, 1000 }) 1001 ]] 1002 matches('^<Lua %d+>', fn.mapcheck('asdf', 'n')) 1003 end) 1004 1005 it('maparg() and maplist() return lua mapping correctly', function() 1006 eq( 1007 0, 1008 exec_lua([[ 1009 GlobalCount = 0 1010 vim.api.nvim_set_keymap('n', 'asdf', '', { 1011 callback = function() GlobalCount = GlobalCount + 1 end, 1012 }) 1013 return GlobalCount 1014 ]]) 1015 ) 1016 1017 matches('^<Lua %d+>', fn.maparg('asdf', 'n')) 1018 1019 local mapargs = fn.maparg('asdf', 'n', false, true) 1020 mapargs.callback = nil 1021 mapargs.lhsraw = nil 1022 mapargs.lhsrawalt = nil 1023 eq(generate_mapargs('n', 'asdf', nil, { sid = sid_lua }), mapargs) 1024 1025 eq( 1026 1, 1027 exec_lua([[ 1028 vim.fn.maparg('asdf', 'n', false, true).callback() 1029 return GlobalCount 1030 ]]) 1031 ) 1032 1033 exec([[ 1034 call maparg('asdf', 'n', v:false, v:true).callback() 1035 ]]) 1036 eq(2, exec_lua([[return GlobalCount]])) 1037 1038 api.nvim_eval([[ 1039 maplist()->filter({_, m -> m.lhs == 'asdf'})->foreach({_, m -> m.callback()}) 1040 ]]) 1041 eq(3, exec_lua([[return GlobalCount]])) 1042 end) 1043 1044 it('can make lua expr mappings replacing keycodes', function() 1045 exec_lua [[ 1046 vim.api.nvim_set_keymap('n', 'aa', '', { 1047 callback = function() return '<Insert>π<C-V><M-π>foo<lt><Esc>' end, 1048 expr = true, 1049 replace_keycodes = true, 1050 }) 1051 ]] 1052 1053 feed('aa') 1054 1055 eq({ 'π<M-π>foo<' }, api.nvim_buf_get_lines(0, 0, -1, false)) 1056 end) 1057 1058 it('can make lua expr mappings without replacing keycodes', function() 1059 exec_lua [[ 1060 vim.api.nvim_set_keymap('i', 'aa', '', { 1061 callback = function() return '<space>' end, 1062 expr = true, 1063 }) 1064 ]] 1065 1066 feed('iaa<esc>') 1067 1068 eq({ '<space>' }, api.nvim_buf_get_lines(0, 0, -1, false)) 1069 end) 1070 1071 it('lua expr mapping returning nil is equivalent to returning an empty string', function() 1072 exec_lua [[ 1073 vim.api.nvim_set_keymap('i', 'aa', '', { 1074 callback = function() return nil end, 1075 expr = true, 1076 }) 1077 ]] 1078 1079 feed('iaa<esc>') 1080 1081 eq({ '' }, api.nvim_buf_get_lines(0, 0, -1, false)) 1082 end) 1083 1084 it('does not reset pum in lua mapping', function() 1085 eq( 1086 0, 1087 exec_lua [[ 1088 VisibleCount = 0 1089 vim.api.nvim_set_keymap('i', '<F2>', '', { 1090 callback = function() VisibleCount = VisibleCount + vim.fn.pumvisible() end, 1091 }) 1092 return VisibleCount 1093 ]] 1094 ) 1095 feed('i<C-X><C-V><F2><F2><esc>') 1096 eq(2, exec_lua [[return VisibleCount]]) 1097 end) 1098 1099 it('redo of lua mappings in op-pending mode work', function() 1100 eq( 1101 0, 1102 exec_lua [[ 1103 OpCount = 0 1104 vim.api.nvim_set_keymap('o', '<F2>', '', { 1105 callback = function() OpCount = OpCount + 1 end, 1106 }) 1107 return OpCount 1108 ]] 1109 ) 1110 feed('d<F2>') 1111 eq(1, exec_lua [[return OpCount]]) 1112 feed('.') 1113 eq(2, exec_lua [[return OpCount]]) 1114 end) 1115 1116 it('can overwrite lua mappings', function() 1117 eq( 1118 0, 1119 exec_lua [[ 1120 GlobalCount = 0 1121 vim.api.nvim_set_keymap('n', 'asdf', '', { 1122 callback = function() GlobalCount = GlobalCount + 1 end, 1123 }) 1124 return GlobalCount 1125 ]] 1126 ) 1127 1128 feed('asdf\n') 1129 1130 eq(1, exec_lua [[return GlobalCount]]) 1131 1132 exec_lua [[ 1133 vim.api.nvim_set_keymap('n', 'asdf', '', { 1134 callback = function() GlobalCount = GlobalCount - 1 end, 1135 }) 1136 ]] 1137 1138 feed('asdf\n') 1139 1140 eq(0, exec_lua [[return GlobalCount]]) 1141 end) 1142 1143 it('can unmap lua mappings', function() 1144 eq( 1145 0, 1146 exec_lua [[ 1147 GlobalCount = 0 1148 vim.api.nvim_set_keymap('n', 'asdf', '', { 1149 callback = function() GlobalCount = GlobalCount + 1 end, 1150 }) 1151 return GlobalCount 1152 ]] 1153 ) 1154 1155 feed('asdf\n') 1156 1157 eq(1, exec_lua [[return GlobalCount]]) 1158 1159 exec_lua [[vim.api.nvim_del_keymap('n', 'asdf' )]] 1160 1161 feed('asdf\n') 1162 1163 eq(1, exec_lua [[return GlobalCount]]) 1164 eq('\nNo mapping found', n.exec_capture('nmap asdf')) 1165 end) 1166 1167 it('no double-free when unmapping simplifiable lua mappings', function() 1168 eq( 1169 0, 1170 exec_lua [[ 1171 GlobalCount = 0 1172 vim.api.nvim_set_keymap('n', '<C-I>', '', { 1173 callback = function() GlobalCount = GlobalCount + 1 end, 1174 }) 1175 return GlobalCount 1176 ]] 1177 ) 1178 1179 feed('<C-I>\n') 1180 1181 eq(1, exec_lua [[return GlobalCount]]) 1182 1183 exec_lua [[vim.api.nvim_del_keymap('n', '<C-I>')]] 1184 1185 feed('<C-I>\n') 1186 1187 eq(1, exec_lua [[return GlobalCount]]) 1188 eq('\nNo mapping found', n.exec_capture('nmap <C-I>')) 1189 end) 1190 1191 it('can set descriptions on mappings', function() 1192 api.nvim_set_keymap('n', 'lhs', 'rhs', { desc = 'map description' }) 1193 eq(generate_mapargs('n', 'lhs', 'rhs', { desc = 'map description' }), get_mapargs('n', 'lhs')) 1194 eq('\nn lhs rhs\n map description', n.exec_capture('nmap lhs')) 1195 end) 1196 1197 it('can define !-mode abbreviations with lua callbacks', function() 1198 exec_lua [[ 1199 GlobalCount = 0 1200 vim.api.nvim_set_keymap('!a', 'foo', '', { 1201 expr = true, 1202 callback = function() 1203 GlobalCount = GlobalCount + 1 1204 return tostring(GlobalCount) 1205 end, 1206 }) 1207 ]] 1208 1209 feed 'iThe foo and the bar and the foo again<esc>' 1210 eq('The 1 and the bar and the 2 again', api.nvim_get_current_line()) 1211 1212 feed ':let x = "The foo is the one"<cr>' 1213 eq('The 3 is the one', api.nvim_eval 'x') 1214 end) 1215 1216 it('can define insert mode abbreviations with lua callbacks', function() 1217 exec_lua [[ 1218 GlobalCount = 0 1219 vim.api.nvim_set_keymap('ia', 'foo', '', { 1220 expr = true, 1221 callback = function() 1222 GlobalCount = GlobalCount + 1 1223 return tostring(GlobalCount) 1224 end, 1225 }) 1226 ]] 1227 1228 feed 'iThe foo and the bar and the foo again<esc>' 1229 eq('The 1 and the bar and the 2 again', api.nvim_get_current_line()) 1230 1231 feed ':let x = "The foo is the one"<cr>' 1232 eq('The foo is the one', api.nvim_eval 'x') 1233 end) 1234 1235 it('can define cmdline mode abbreviations with lua callbacks', function() 1236 exec_lua [[ 1237 GlobalCount = 0 1238 vim.api.nvim_set_keymap('ca', 'foo', '', { 1239 expr = true, 1240 callback = function() 1241 GlobalCount = GlobalCount + 1 1242 return tostring(GlobalCount) 1243 end, 1244 }) 1245 ]] 1246 1247 feed 'iThe foo and the bar and the foo again<esc>' 1248 eq('The foo and the bar and the foo again', api.nvim_get_current_line()) 1249 1250 feed ':let x = "The foo is the one"<cr>' 1251 eq('The 1 is the one', api.nvim_eval 'x') 1252 end) 1253 end) 1254 1255 describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() 1256 before_each(clear) 1257 1258 -- nvim_set_keymap is implemented as a wrapped call to nvim_buf_set_keymap, 1259 -- so its tests also effectively test nvim_buf_set_keymap 1260 1261 -- here, we mainly test for buffer specificity and other special cases 1262 1263 -- switch to the given buffer, abandoning any changes in the current buffer 1264 local function switch_to_buf(bufnr) 1265 command(bufnr .. 'buffer!') 1266 end 1267 1268 -- `set hidden`, then create two buffers and return their bufnr's 1269 -- If start_from_first is truthy, the first buffer will be open when 1270 -- the function returns; if falsy, the second buffer will be open. 1271 local function make_two_buffers(start_from_first) 1272 command('set hidden') 1273 1274 local first_buf = api.nvim_call_function('bufnr', { '%' }) 1275 command('new') 1276 local second_buf = api.nvim_call_function('bufnr', { '%' }) 1277 neq(second_buf, first_buf) -- sanity check 1278 1279 if start_from_first then 1280 switch_to_buf(first_buf) 1281 end 1282 1283 return first_buf, second_buf 1284 end 1285 1286 it('rejects negative bufnr values', function() 1287 eq( 1288 'Wrong type for argument 1 when calling nvim_buf_set_keymap, expecting Buffer', 1289 pcall_err(api.nvim_buf_set_keymap, -1, '', 'lhs', 'rhs', {}) 1290 ) 1291 end) 1292 1293 it('can set mappings active in the current buffer but not others', function() 1294 local first, second = make_two_buffers(true) 1295 1296 api.nvim_buf_set_keymap(0, '', 'lhs', 'irhs<Esc>', {}) 1297 command('normal lhs') 1298 eq({ 'rhs' }, api.nvim_buf_get_lines(0, 0, 1, 1)) 1299 1300 -- mapping should have no effect in new buffer 1301 switch_to_buf(second) 1302 command('normal lhs') 1303 eq({ '' }, api.nvim_buf_get_lines(0, 0, 1, 1)) 1304 1305 -- mapping should remain active in old buffer 1306 switch_to_buf(first) 1307 command('normal ^lhs') 1308 eq({ 'rhsrhs' }, api.nvim_buf_get_lines(0, 0, 1, 1)) 1309 end) 1310 1311 it('can set local mappings in buffer other than current', function() 1312 local first = make_two_buffers(false) 1313 api.nvim_buf_set_keymap(first, '', 'lhs', 'irhs<Esc>', {}) 1314 1315 -- shouldn't do anything 1316 command('normal lhs') 1317 eq({ '' }, api.nvim_buf_get_lines(0, 0, 1, 1)) 1318 1319 -- should take effect 1320 switch_to_buf(first) 1321 command('normal lhs') 1322 eq({ 'rhs' }, api.nvim_buf_get_lines(0, 0, 1, 1)) 1323 end) 1324 1325 it('can disable mappings made in another buffer, inside that buffer', function() 1326 local first = make_two_buffers(false) 1327 api.nvim_buf_set_keymap(first, '', 'lhs', 'irhs<Esc>', {}) 1328 api.nvim_buf_del_keymap(first, '', 'lhs') 1329 switch_to_buf(first) 1330 1331 -- shouldn't do anything 1332 command('normal lhs') 1333 eq({ '' }, api.nvim_buf_get_lines(0, 0, 1, 1)) 1334 end) 1335 1336 it("can't disable mappings given wrong buffer handle", function() 1337 local first, second = make_two_buffers(false) 1338 api.nvim_buf_set_keymap(first, '', 'lhs', 'irhs<Esc>', {}) 1339 eq('E31: No such mapping', pcall_err(api.nvim_buf_del_keymap, second, '', 'lhs')) 1340 1341 -- should still work 1342 switch_to_buf(first) 1343 command('normal lhs') 1344 eq({ 'rhs' }, api.nvim_buf_get_lines(0, 0, 1, 1)) 1345 end) 1346 1347 it('does not crash when setting mapping in a non-existing buffer #13541', function() 1348 pcall_err(api.nvim_buf_set_keymap, 100, '', 'lsh', 'irhs<Esc>', {}) 1349 n.assert_alive() 1350 end) 1351 1352 it('can make lua mappings', function() 1353 eq( 1354 0, 1355 exec_lua [[ 1356 GlobalCount = 0 1357 vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', { 1358 callback = function() GlobalCount = GlobalCount + 1 end, 1359 }) 1360 return GlobalCount 1361 ]] 1362 ) 1363 1364 feed('asdf\n') 1365 1366 eq(1, exec_lua [[return GlobalCount]]) 1367 end) 1368 1369 it('can make lua expr mappings replacing keycodes', function() 1370 exec_lua [[ 1371 vim.api.nvim_buf_set_keymap(0, 'n', 'aa', '', { 1372 callback = function() return '<Insert>π<C-V><M-π>foo<lt><Esc>' end, 1373 expr = true, 1374 replace_keycodes = true, 1375 }) 1376 ]] 1377 1378 feed('aa') 1379 1380 eq({ 'π<M-π>foo<' }, api.nvim_buf_get_lines(0, 0, -1, false)) 1381 end) 1382 1383 it('can make lua expr mappings without replacing keycodes', function() 1384 exec_lua [[ 1385 vim.api.nvim_buf_set_keymap(0, 'i', 'aa', '', { 1386 callback = function() return '<space>' end, 1387 expr = true, 1388 }) 1389 ]] 1390 1391 feed('iaa<esc>') 1392 1393 eq({ '<space>' }, api.nvim_buf_get_lines(0, 0, -1, false)) 1394 end) 1395 1396 it('can overwrite lua mappings', function() 1397 eq( 1398 0, 1399 exec_lua [[ 1400 GlobalCount = 0 1401 vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', { 1402 callback = function() GlobalCount = GlobalCount + 1 end, 1403 }) 1404 return GlobalCount 1405 ]] 1406 ) 1407 1408 feed('asdf\n') 1409 1410 eq(1, exec_lua [[return GlobalCount]]) 1411 1412 exec_lua [[ 1413 vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', { 1414 callback = function() GlobalCount = GlobalCount - 1 end, 1415 }) 1416 ]] 1417 1418 feed('asdf\n') 1419 1420 eq(0, exec_lua [[return GlobalCount]]) 1421 end) 1422 1423 it('can unmap lua mappings', function() 1424 eq( 1425 0, 1426 exec_lua [[ 1427 GlobalCount = 0 1428 vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', { 1429 callback = function() GlobalCount = GlobalCount + 1 end, 1430 }) 1431 return GlobalCount 1432 ]] 1433 ) 1434 1435 feed('asdf\n') 1436 1437 eq(1, exec_lua [[return GlobalCount]]) 1438 1439 exec_lua [[vim.api.nvim_buf_del_keymap(0, 'n', 'asdf' )]] 1440 1441 feed('asdf\n') 1442 1443 eq(1, exec_lua [[return GlobalCount]]) 1444 eq('\nNo mapping found', n.exec_capture('nmap asdf')) 1445 end) 1446 1447 it('no double-free when unmapping simplifiable lua mappings', function() 1448 eq( 1449 0, 1450 exec_lua [[ 1451 GlobalCount = 0 1452 vim.api.nvim_buf_set_keymap(0, 'n', '<C-I>', '', { 1453 callback = function() GlobalCount = GlobalCount + 1 end, 1454 }) 1455 return GlobalCount 1456 ]] 1457 ) 1458 1459 feed('<C-I>\n') 1460 1461 eq(1, exec_lua [[return GlobalCount]]) 1462 1463 exec_lua [[vim.api.nvim_buf_del_keymap(0, 'n', '<C-I>')]] 1464 1465 feed('<C-I>\n') 1466 1467 eq(1, exec_lua [[return GlobalCount]]) 1468 eq('\nNo mapping found', n.exec_capture('nmap <C-I>')) 1469 end) 1470 1471 it('does not overwrite in <unique> mappings', function() 1472 api.nvim_buf_set_keymap(0, 'i', 'lhs', 'rhs', {}) 1473 eq( 1474 'E227: Mapping already exists for lhs', 1475 pcall_err(api.nvim_buf_set_keymap, 0, 'i', 'lhs', 'rhs', { unique = true }) 1476 ) 1477 1478 api.nvim_buf_set_keymap(0, 'ia', 'lhs2', 'rhs2', {}) 1479 eq( 1480 'E226: Abbreviation already exists for lhs2', 1481 pcall_err(api.nvim_buf_set_keymap, 0, 'ia', 'lhs2', 'rhs2', { unique = true }) 1482 ) 1483 1484 api.nvim_set_keymap('n', 'lhs', 'rhs', {}) 1485 eq( 1486 'E225: Global mapping already exists for lhs', 1487 pcall_err(api.nvim_buf_set_keymap, 0, 'n', 'lhs', 'rhs', { unique = true }) 1488 ) 1489 end) 1490 end)