cmd_map_spec.lua (29069B)
1 local t = require('test.testutil') 2 local n = require('test.functional.testnvim')() 3 local Screen = require('test.functional.ui.screen') 4 5 local clear = n.clear 6 local feed = n.feed 7 local eq = t.eq 8 local expect = n.expect 9 local eval = n.eval 10 local fn = n.fn 11 local insert = n.insert 12 local write_file = t.write_file 13 local exc_exec = n.exc_exec 14 local command = n.command 15 16 describe('mappings with <Cmd>', function() 17 local screen 18 local tmpfile = 'X_ex_cmds_cmd_map' 19 20 local function cmdmap(lhs, rhs) 21 command('noremap ' .. lhs .. ' <Cmd>' .. rhs .. '<cr>') 22 command('noremap! ' .. lhs .. ' <Cmd>' .. rhs .. '<cr>') 23 end 24 25 before_each(function() 26 clear() 27 screen = Screen.new(65, 8) 28 screen:set_default_attr_ids({ 29 [1] = { bold = true, foreground = Screen.colors.Blue1 }, 30 [2] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, 31 [3] = { bold = true, foreground = Screen.colors.SeaGreen4 }, 32 [4] = { bold = true }, 33 [5] = { foreground = Screen.colors.Black, background = Screen.colors.LightGrey }, 34 [6] = { foreground = Screen.colors.Blue1 }, 35 [7] = { bold = true, reverse = true }, 36 [8] = { background = Screen.colors.WebGray }, 37 [9] = { background = Screen.colors.LightMagenta }, 38 [10] = { foreground = Screen.colors.Red }, 39 }) 40 41 cmdmap('<F3>', 'let m = mode(1)') 42 cmdmap('<F4>', 'normal! ww') 43 cmdmap('<F5>', 'normal! "ay') 44 cmdmap('<F6>', 'throw "very error"') 45 command([[ 46 function! TextObj() 47 if mode() !=# "v" 48 normal! v 49 end 50 call cursor(1,3) 51 normal! o 52 call cursor(2,4) 53 endfunction]]) 54 cmdmap('<F7>', 'call TextObj()') 55 insert([[ 56 some short lines 57 of test text]]) 58 feed('gg') 59 cmdmap('<F8>', 'startinsert') 60 cmdmap('<F9>', 'stopinsert') 61 command('abbr foo <Cmd>let g:y = 17<cr>bar') 62 end) 63 64 after_each(function() 65 os.remove(tmpfile) 66 end) 67 68 it('can be displayed', function() 69 command('map <F3>') 70 screen:expect([[ 71 ^some short lines | 72 of test text | 73 {1:~ }|*5 74 {6:<F3>} {6:*} {6:<Cmd>}let m = mode(1){6:<CR>} | 75 ]]) 76 end) 77 78 it('handles invalid mappings', function() 79 command('let x = 0') 80 command('noremap <F3> <Cmd><Cmd>let x = 1<cr>') 81 feed('<F3>') 82 screen:expect([[ 83 ^some short lines | 84 of test text | 85 {1:~ }|*5 86 {2:E1136: <Cmd> mapping must end with <CR> before second <Cmd>} | 87 ]]) 88 89 command('noremap <F3> <Cmd>let x = 3') 90 feed('<F3>') 91 screen:expect([[ 92 ^some short lines | 93 of test text | 94 {1:~ }|*5 95 {2:E1255: <Cmd> mapping must end with <CR>} | 96 ]]) 97 eq(0, eval('x')) 98 end) 99 100 it('allows special keys and modifiers', function() 101 command('noremap <F3> <Cmd>normal! <Down><CR>') 102 feed('<F3>') 103 screen:expect([[ 104 some short lines | 105 ^of test text | 106 {1:~ }|*5 107 | 108 ]]) 109 110 command('noremap <F3> <Cmd>normal! <C-Right><CR>') 111 feed('<F3>') 112 screen:expect([[ 113 some short lines | 114 of ^test text | 115 {1:~ }|*5 116 | 117 ]]) 118 end) 119 120 it('handles string containing K_SPECIAL (0x80) bytes correctly', function() 121 command([[noremap <F3> <Cmd>let g:str = 'foo…bar'<CR>]]) 122 feed('<F3>') 123 eq('foo…bar', eval('g:str')) 124 local str = eval([["foo\<D-…>bar"]]) 125 command([[noremap <F3> <Cmd>let g:str = ']] .. str .. [['<CR>]]) 126 feed('<F3>') 127 eq(str, eval('g:str')) 128 command([[noremap <F3> <Cmd>let g:str = 'foo<D-…>bar'<CR>]]) 129 feed('<F3>') 130 eq(str, eval('g:str')) 131 end) 132 133 it('works in various modes and sees correct `mode()` value', function() 134 -- normal mode 135 feed('<F3>') 136 eq('n', eval('m')) 137 138 -- visual mode 139 feed('v<F3>') 140 eq('v', eval('m')) 141 -- didn't leave visual mode 142 eq('v', eval('mode(1)')) 143 feed('<esc>') 144 eq('n', eval('mode(1)')) 145 146 -- visual mapping in select mode 147 feed('gh<F3>') 148 eq('v', eval('m')) 149 -- didn't leave select mode 150 eq('s', eval('mode(1)')) 151 feed('<esc>') 152 eq('n', eval('mode(1)')) 153 154 -- select mode mapping 155 command('snoremap <F3> <Cmd>let m = mode(1)<cr>') 156 feed('gh<F3>') 157 eq('s', eval('m')) 158 -- didn't leave select mode 159 eq('s', eval('mode(1)')) 160 feed('<esc>') 161 eq('n', eval('mode(1)')) 162 163 -- operator-pending mode 164 feed('d<F3>') 165 eq('no', eval('m')) 166 -- did leave operator-pending mode 167 eq('n', eval('mode(1)')) 168 169 --insert mode 170 feed('i<F3>') 171 eq('i', eval('m')) 172 eq('i', eval('mode(1)')) 173 174 -- replace mode 175 feed('<Ins><F3>') 176 eq('R', eval('m')) 177 eq('R', eval('mode(1)')) 178 feed('<esc>') 179 eq('n', eval('mode(1)')) 180 181 -- virtual replace mode 182 feed('gR<F3>') 183 eq('Rv', eval('m')) 184 eq('Rv', eval('mode(1)')) 185 feed('<esc>') 186 eq('n', eval('mode(1)')) 187 188 -- langmap works, but is not distinguished in mode(1) 189 feed(':set iminsert=1<cr>i<F3>') 190 eq('i', eval('m')) 191 eq('i', eval('mode(1)')) 192 feed('<esc>') 193 eq('n', eval('mode(1)')) 194 195 feed(':<F3>') 196 eq('c', eval('m')) 197 eq('c', eval('mode(1)')) 198 feed('<esc>') 199 eq('n', eval('mode(1)')) 200 201 -- terminal mode 202 command('tnoremap <F3> <Cmd>let m = mode(1)<cr>') 203 command('split | terminal') 204 feed('i') 205 eq('t', eval('mode(1)')) 206 feed('<F3>') 207 eq('t', eval('m')) 208 eq('t', eval('mode(1)')) 209 end) 210 211 it('works in normal mode', function() 212 cmdmap('<F2>', 'let s = [mode(1), v:count, v:register]') 213 214 -- check v:count and v:register works 215 feed('<F2>') 216 eq({ 'n', 0, '"' }, eval('s')) 217 feed('7<F2>') 218 eq({ 'n', 7, '"' }, eval('s')) 219 feed('"e<F2>') 220 eq({ 'n', 0, 'e' }, eval('s')) 221 feed('5"k<F2>') 222 eq({ 'n', 5, 'k' }, eval('s')) 223 feed('"+2<F2>') 224 eq({ 'n', 2, '+' }, eval('s')) 225 226 -- text object enters visual mode 227 feed('<F7>') 228 screen:expect([[ 229 so{5:me short lines} | 230 {5:of }^test text | 231 {1:~ }|*5 232 {4:-- VISUAL --} | 233 ]]) 234 feed('<esc>') 235 236 -- startinsert 237 feed('<F8>') 238 eq('i', eval('mode(1)')) 239 feed('<esc>') 240 241 eq('n', eval('mode(1)')) 242 cmdmap(',a', 'call feedkeys("aalpha") \\| let g:a = getline(2)') 243 cmdmap(',b', 'call feedkeys("abeta", "x") \\| let g:b = getline(2)') 244 245 feed(',a<F3>') 246 screen:expect([[ 247 some short lines | 248 of alpha^test text | 249 {1:~ }|*5 250 {4:-- INSERT --} | 251 ]]) 252 -- feedkeys were not executed immediately 253 eq({ 'n', 'of test text' }, eval('[m,a]')) 254 eq('i', eval('mode(1)')) 255 feed('<esc>') 256 257 feed(',b<F3>') 258 screen:expect([[ 259 some short lines | 260 of alphabet^atest text | 261 {1:~ }|*5 262 | 263 ]]) 264 -- feedkeys(..., 'x') was executed immediately, but insert mode gets aborted 265 eq({ 'n', 'of alphabetatest text' }, eval('[m,b]')) 266 eq('n', eval('mode(1)')) 267 end) 268 269 it('works in :normal command', function() 270 command('noremap ,x <Cmd>call append(1, "xx")\\| call append(1, "aa")<cr>') 271 command('noremap ,f <Cmd>nosuchcommand<cr>') 272 command('noremap ,e <Cmd>throw "very error"\\| call append(1, "yy")<cr>') 273 command('noremap ,m <Cmd>echoerr "The message."\\| call append(1, "zz")<cr>') 274 command( 275 'noremap ,w <Cmd>for i in range(5)\\|if i==1\\|echoerr "Err"\\|endif\\|call append(1, i)\\|endfor<cr>' 276 ) 277 278 feed(':normal ,x<cr>') 279 screen:expect([[ 280 ^some short lines | 281 aa | 282 xx | 283 of test text | 284 {1:~ }|*3 285 :normal ,x | 286 ]]) 287 288 eq('Vim:E492: Not an editor command: nosuchcommand', exc_exec('normal ,f')) 289 eq('very error', exc_exec('normal ,e')) 290 eq('Vim(echoerr):The message.', exc_exec('normal ,m')) 291 feed('w') 292 screen:expect([[ 293 some ^short lines | 294 aa | 295 xx | 296 of test text | 297 {1:~ }|*3 298 :normal ,x | 299 ]]) 300 301 command(':%d') 302 eq('Vim(echoerr):Err', exc_exec('normal ,w')) 303 screen:expect([[ 304 ^ | 305 0 | 306 {1:~ }|*5 307 --No lines in buffer-- | 308 ]]) 309 310 command(':%d') 311 feed(':normal ,w<cr>') 312 screen:expect([[ 313 ^ | 314 4 | 315 3 | 316 2 | 317 1 | 318 0 | 319 {1:~ }| 320 {2:Err} | 321 ]]) 322 end) 323 324 it('works in visual mode', function() 325 -- can extend visual mode 326 feed('v<F4>') 327 screen:expect([[ 328 {5:some short }^lines | 329 of test text | 330 {1:~ }|*5 331 {4:-- VISUAL --} | 332 ]]) 333 eq('v', fn.mode(1)) 334 335 -- can invoke operator, ending visual mode 336 feed('<F5>') 337 eq('n', fn.mode(1)) 338 eq({ 'some short l' }, fn.getreg('a', 1, 1)) 339 340 -- error doesn't interrupt visual mode 341 feed('ggvw<F6>') 342 screen:expect([[ 343 {5:some }short lines | 344 of test text | 345 {1:~ }|*2 346 {7: }| 347 {2:Error in :} | 348 {2:E605: Exception not caught: very error} | 349 {3:Press ENTER or type command to continue}^ | 350 ]]) 351 feed('<cr>') 352 eq('E605: Exception not caught: very error', eval('v:errmsg')) 353 -- still in visual mode, <cr> was consumed by the error prompt 354 screen:expect([[ 355 {5:some }^short lines | 356 of test text | 357 {1:~ }|*5 358 {4:-- VISUAL --} | 359 ]]) 360 eq('v', fn.mode(1)) 361 feed('<F7>') 362 screen:expect([[ 363 so{5:me short lines} | 364 {5:of }^test text | 365 {1:~ }|*5 366 {4:-- VISUAL --} | 367 ]]) 368 eq('v', fn.mode(1)) 369 370 -- startinsert gives "-- (insert) VISUAL --" mode 371 feed('<F8>') 372 screen:expect([[ 373 so{5:me short lines} | 374 {5:of }^test text | 375 {1:~ }|*5 376 {4:-- (insert) VISUAL --} | 377 ]]) 378 eq('v', eval('mode(1)')) 379 feed('<esc>') 380 eq('i', eval('mode(1)')) 381 end) 382 383 it('works in select mode', function() 384 command('snoremap <F1> <cmd>throw "very error"<cr>') 385 command('snoremap <F2> <cmd>normal! <c-g>"by<cr>') 386 -- can extend select mode 387 feed('gh<F4>') 388 screen:expect([[ 389 {5:some short }^lines | 390 of test text | 391 {1:~ }|*5 392 {4:-- SELECT --} | 393 ]]) 394 eq('s', fn.mode(1)) 395 396 -- visual mapping in select mode restart select mode after operator 397 feed('<F5>') 398 eq('s', fn.mode(1)) 399 eq({ 'some short l' }, fn.getreg('a', 1, 1)) 400 401 -- select mode mapping works, and does not restart select mode 402 feed('<F2>') 403 eq('n', fn.mode(1)) 404 eq({ 'some short l' }, fn.getreg('b', 1, 1)) 405 406 -- error doesn't interrupt temporary visual mode 407 feed('<esc>ggvw<c-g><F6>') 408 screen:expect([[ 409 {5:some }short lines | 410 of test text | 411 {1:~ }|*2 412 {7: }| 413 {2:Error in :} | 414 {2:E605: Exception not caught: very error} | 415 {3:Press ENTER or type command to continue}^ | 416 ]]) 417 feed('<cr>') 418 eq('E605: Exception not caught: very error', eval('v:errmsg')) 419 -- still in visual mode, <cr> was consumed by the error prompt 420 screen:expect([[ 421 {5:some }^short lines | 422 of test text | 423 {1:~ }|*5 424 {4:-- VISUAL --} | 425 ]]) 426 -- quirk: restoration of select mode is not performed 427 eq('v', fn.mode(1)) 428 429 -- error doesn't interrupt select mode 430 feed('<esc>ggvw<c-g><F1>') 431 screen:expect([[ 432 {5:some }short lines | 433 of test text | 434 {1:~ }|*2 435 {7: }| 436 {2:Error in :} | 437 {2:E605: Exception not caught: very error} | 438 {3:Press ENTER or type command to continue}^ | 439 ]]) 440 feed('<cr>') 441 eq('E605: Exception not caught: very error', eval('v:errmsg')) 442 -- still in select mode, <cr> was consumed by the error prompt 443 screen:expect([[ 444 {5:some }^short lines | 445 of test text | 446 {1:~ }|*5 447 {4:-- SELECT --} | 448 ]]) 449 -- quirk: restoration of select mode is not performed 450 eq('s', fn.mode(1)) 451 452 feed('<F7>') 453 screen:expect([[ 454 so{5:me short lines} | 455 {5:of }^test text | 456 {1:~ }|*5 457 {4:-- SELECT --} | 458 ]]) 459 eq('s', fn.mode(1)) 460 461 -- startinsert gives "-- SELECT (insert) --" mode 462 feed('<F8>') 463 screen:expect([[ 464 so{5:me short lines} | 465 {5:of }^test text | 466 {1:~ }|*5 467 {4:-- (insert) SELECT --} | 468 ]]) 469 eq('s', eval('mode(1)')) 470 feed('<esc>') 471 eq('i', eval('mode(1)')) 472 end) 473 474 it('works in operator-pending mode', function() 475 feed('d<F4>') 476 expect([[ 477 lines 478 of test text]]) 479 eq({ 'some short ' }, fn.getreg('"', 1, 1)) 480 feed('.') 481 expect([[ 482 test text]]) 483 eq({ 'lines', 'of ' }, fn.getreg('"', 1, 1)) 484 feed('uu') 485 expect([[ 486 some short lines 487 of test text]]) 488 489 -- error aborts operator-pending, operator not performed 490 feed('d<F6>') 491 screen:expect([[ 492 some short lines | 493 of test text | 494 {1:~ }|*2 495 {7: }| 496 {2:Error in :} | 497 {2:E605: Exception not caught: very error} | 498 {3:Press ENTER or type command to continue}^ | 499 ]]) 500 feed('<cr>') 501 eq('E605: Exception not caught: very error', eval('v:errmsg')) 502 expect([[ 503 some short lines 504 of test text]]) 505 506 feed('"bd<F7>') 507 expect([[ 508 soest text]]) 509 eq({ 'me short lines', 'of t' }, fn.getreg('b', 1, 1)) 510 511 -- startinsert aborts operator 512 feed('d<F8>') 513 eq('i', eval('mode(1)')) 514 expect([[ 515 soest text]]) 516 end) 517 518 it('works in insert mode', function() 519 -- works the same as <c-o>w<c-o>w 520 feed('iindeed <F4>little ') 521 screen:expect([[ 522 indeed some short little ^lines | 523 of test text | 524 {1:~ }|*5 525 {4:-- INSERT --} | 526 ]]) 527 528 feed('<F6>') 529 screen:expect([[ 530 indeed some short little lines | 531 of test text | 532 {1:~ }|*2 533 {7: }| 534 {2:Error in :} | 535 {2:E605: Exception not caught: very error} | 536 {3:Press ENTER or type command to continue}^ | 537 ]]) 538 539 feed('<cr>') 540 eq('E605: Exception not caught: very error', eval('v:errmsg')) 541 -- still in insert 542 screen:expect([[ 543 indeed some short little ^lines | 544 of test text | 545 {1:~ }|*5 546 {4:-- INSERT --} | 547 ]]) 548 eq('i', eval('mode(1)')) 549 550 -- When entering visual mode from InsertEnter autocmd, an async event, or 551 -- a <cmd> mapping, vim ends up in undocumented "INSERT VISUAL" mode. If a 552 -- vim patch decides to disable this mode, this test is expected to fail. 553 feed('<F7>stuff ') 554 screen:expect([[ 555 in{5:deed some short little lines} | 556 {5:of stuff }^test text | 557 {1:~ }|*5 558 {4:-- INSERT VISUAL --} | 559 ]]) 560 expect([[ 561 indeed some short little lines 562 of stuff test text]]) 563 564 feed('<F5>') 565 eq({ 'deed some short little lines', 'of stuff t' }, fn.getreg('a', 1, 1)) 566 567 -- still in insert 568 screen:expect([[ 569 in^deed some short little lines | 570 of stuff test text | 571 {1:~ }|*5 572 {4:-- INSERT --} | 573 ]]) 574 eq('i', eval('mode(1)')) 575 576 -- also works as part of abbreviation 577 feed(' foo ') 578 screen:expect([[ 579 in bar ^deed some short little lines | 580 of stuff test text | 581 {1:~ }|*5 582 {4:-- INSERT --} | 583 ]]) 584 eq(17, eval('g:y')) 585 586 -- :startinsert does nothing 587 feed('<F8>') 588 eq('i', eval('mode(1)')) 589 590 -- :stopinsert works 591 feed('<F9>') 592 eq('n', eval('mode(1)')) 593 end) 594 595 it('works in insert completion (Ctrl-X) mode', function() 596 feed('os<c-x><c-n>') 597 screen:expect([[ 598 some short lines | 599 some^ | 600 {8:some } | 601 {9:short }{1: }| 602 {1:~ }|*3 603 {4:-- Keyword Local completion (^N^P) }{3:match 1 of 2} | 604 ]]) 605 606 feed('<f3>') 607 eq('ic', eval('m')) 608 609 -- ensure a redraw, this would have moved the cursor 610 -- instead if CTRL-X mode was left. 611 feed('<up>') 612 screen:expect([[ 613 some short lines | 614 some^ | 615 {9:some } | 616 {9:short }{1: }| 617 {1:~ }|*3 618 {4:-- Keyword Local completion (^N^P) }{10:Back at original} | 619 ]]) 620 end) 621 622 it('works in cmdline mode', function() 623 feed(':text<F3>') 624 eq('c', eval('m')) 625 -- didn't leave cmdline mode 626 eq('c', eval('mode(1)')) 627 feed('<cr>') 628 eq('n', eval('mode(1)')) 629 screen:expect([[ 630 ^some short lines | 631 of test text | 632 {1:~ }|*5 633 {2:E492: Not an editor command: text} | 634 ]]) 635 636 feed(':echo 2<F6>') 637 screen:expect([[ 638 some short lines | 639 of test text | 640 {1:~ }| 641 {7: }| 642 :echo 2 | 643 {2:Error in :} | 644 {2:E605: Exception not caught: very error} | 645 :echo 2^ | 646 ]]) 647 eq('E605: Exception not caught: very error', eval('v:errmsg')) 648 -- didn't leave cmdline mode 649 eq('c', eval('mode(1)')) 650 feed('+2<cr>') 651 screen:expect([[ 652 some short lines | 653 of test text | 654 {7: }| 655 :echo 2 | 656 {2:Error in :} | 657 {2:E605: Exception not caught: very error} | 658 4 | 659 {3:Press ENTER or type command to continue}^ | 660 ]]) 661 -- however, message scrolling may cause extra CR prompt 662 -- This is consistent with output from async events. 663 feed('<cr>') 664 screen:expect([[ 665 ^some short lines | 666 of test text | 667 {1:~ }|*5 668 | 669 ]]) 670 eq('n', eval('mode(1)')) 671 672 feed(':let g:x = 3<F4>') 673 screen:expect([[ 674 some short lines | 675 of test text | 676 {1:~ }|*5 677 :let g:x = 3^ | 678 ]]) 679 feed('+2<cr>') 680 -- cursor was moved in the background 681 screen:expect([[ 682 some short ^lines | 683 of test text | 684 {1:~ }|*5 685 :let g:x = 3+2 | 686 ]]) 687 eq(5, eval('g:x')) 688 689 feed(':let g:y = 7<F8>') 690 screen:expect([[ 691 some short lines | 692 of test text | 693 {1:~ }|*5 694 :let g:y = 7^ | 695 ]]) 696 eq('c', eval('mode(1)')) 697 feed('+2<cr>') 698 -- startinsert takes effect after leaving cmdline mode 699 screen:expect([[ 700 some short ^lines | 701 of test text | 702 {1:~ }|*5 703 {4:-- INSERT --} | 704 ]]) 705 eq('i', eval('mode(1)')) 706 eq(9, eval('g:y')) 707 end) 708 709 it("doesn't crash when invoking cmdline mode recursively #8859", function() 710 cmdmap('<F2>', 'norm! :foo') 711 feed(':bar') 712 screen:expect([[ 713 some short lines | 714 of test text | 715 {1:~ }|*5 716 :bar^ | 717 ]]) 718 719 feed('<f2>x') 720 screen:expect([[ 721 some short lines | 722 of test text | 723 {1:~ }|*5 724 :barx^ | 725 ]]) 726 end) 727 728 it('works with <SID> mappings', function() 729 command('new!') 730 write_file( 731 tmpfile, 732 [[ 733 map <f2> <Cmd>call <SID>do_it()<Cr> 734 function! s:do_it() 735 let g:x = 10 736 endfunction 737 ]] 738 ) 739 command('source ' .. tmpfile) 740 feed('<f2>') 741 eq('', eval('v:errmsg')) 742 eq(10, eval('g:x')) 743 end) 744 end)