tui_spec.lua (169749B)
1 -- TUI acceptance tests. 2 -- Uses :terminal as a way to send keys and assert screen state. 3 -- 4 -- "bracketed paste" terminal feature: 5 -- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode 6 7 local t = require('test.testutil') 8 local n = require('test.functional.testnvim')() 9 local Screen = require('test.functional.ui.screen') 10 local tt = require('test.functional.testterm') 11 12 local eq = t.eq 13 local feed_data = tt.feed_data 14 local clear = n.clear 15 local command = n.command 16 local exec = n.exec 17 local exec_lua = n.exec_lua 18 local testprg = n.testprg 19 local retry = t.retry 20 local nvim_prog = n.nvim_prog 21 local nvim_set = n.nvim_set 22 local ok = t.ok 23 local read_file = t.read_file 24 local fn = n.fn 25 local api = n.api 26 local is_ci = t.is_ci 27 local is_os = t.is_os 28 local new_pipename = n.new_pipename 29 local set_session = n.set_session 30 local write_file = t.write_file 31 local eval = n.eval 32 local assert_log = t.assert_log 33 34 local testlog = 'Xtest-tui-log' 35 36 -- Using this to have 'notermguicolors' in Nvim instances without starting a timer 37 -- that causes delay on exit with ASAN/TSAN. 38 local env_notermguicolors = { COLORTERM = 'xterm-256color' } 39 40 describe('TUI', function() 41 it('exit status 1 and error message with server --listen error #34365', function() 42 clear() 43 local addr_in_use = api.nvim_get_vvar('servername') 44 local screen = tt.setup_child_nvim( 45 { '--listen', addr_in_use, '-u', 'NONE', '-i', 'NONE' }, 46 { extra_rows = 10, cols = 60, env = { NVIM_LOG_FILE = testlog } } 47 ) 48 finally(function() 49 os.remove(testlog) 50 end) 51 52 screen:expect({ any = vim.pesc('[Process exited 1]') }) 53 54 -- When the address is very long, the error message may be only partly visible. 55 if #addr_in_use <= 600 then 56 screen:expect({ 57 any = vim.pesc( 58 ('%s: Failed to --listen: address already in use:'):format( 59 fn.fnamemodify(nvim_prog, ':t') 60 ) 61 ), 62 unchanged = true, 63 }) 64 end 65 66 -- Always assert the log for the error message. 67 assert_log( 68 vim.pesc('Failed to start server: address already in use: ' .. addr_in_use), 69 testlog, 70 100 71 ) 72 end) 73 74 it('suspending does not crash or hang', function() 75 clear() 76 local screen = tt.setup_child_nvim({ '--clean' }, { env = env_notermguicolors }) 77 local s0 = [[ 78 ^ | 79 ~ |*3 80 {2:[No Name] 0,0-1 All}| 81 | 82 {5:-- TERMINAL --} | 83 ]] 84 screen:expect(s0) 85 feed_data(':') 86 local s1 = [[ 87 | 88 ~ |*3 89 {2:[No Name] 0,0-1 All}| 90 :^ | 91 {5:-- TERMINAL --} | 92 ]] 93 screen:expect(s1) 94 feed_data('suspend\r') 95 if is_os('win') then -- no-op on Windows 96 screen:expect([[ 97 ^ | 98 ~ |*3 99 {2:[No Name] 0,0-1 All}| 100 :suspend | 101 {5:-- TERMINAL --} | 102 ]]) 103 else -- resuming works on other platforms 104 screen:expect([[ 105 |*5 106 ^[Process suspended] | 107 {5:-- TERMINAL --} | 108 ]]) 109 n.feed('<Space>') 110 screen:expect(s0) 111 end 112 feed_data(':') 113 screen:expect(s1) 114 end) 115 end) 116 117 describe('TUI :detach', function() 118 it('does not stop server', function() 119 local job_opts = { env = t.shallowcopy(env_notermguicolors) } 120 121 n.clear() 122 finally(function() 123 n.check_close() 124 end) 125 126 local child_server = new_pipename() 127 local screen = tt.setup_child_nvim({ 128 '--listen', 129 child_server, 130 '-u', 131 'NONE', 132 '-i', 133 'NONE', 134 '--cmd', 135 'colorscheme vim', 136 '--cmd', 137 nvim_set .. ' laststatus=2 background=dark', 138 }, job_opts) 139 140 tt.feed_data('iHello, World') 141 tt.screen_expect( 142 screen, 143 [[ 144 Hello, World^ | 145 {100:~ }|*3 146 {3:[No Name] [+] }| 147 {5:-- INSERT --} | 148 {5:-- TERMINAL --} | 149 ]] 150 ) 151 152 local child_session = n.connect(child_server) 153 finally(function() 154 -- Avoid a dangling process after :detach. 155 child_session:request('nvim_command', 'qall!') 156 end) 157 local status, child_uis = child_session:request('nvim_list_uis') 158 assert(status) 159 eq(1, #child_uis) 160 161 eq( 162 { false, { 0, 'Vim(detach):E477: No ! allowed: detach!' } }, 163 { child_session:request('nvim_command', 'detach!') } 164 ) 165 eq( 166 { false, { 0, 'Vim(detach):E481: No range allowed: 1detach' } }, 167 { child_session:request('nvim_command', '1detach') } 168 ) 169 eq( 170 { false, { 0, 'Vim(detach):E488: Trailing characters: foo: detach foo' } }, 171 { child_session:request('nvim_command', 'detach foo') } 172 ) 173 174 tt.feed_data('\027\027:detach\013') 175 -- Note: "Process exited" message is misleading; tt.setup_child_nvim() sees the foreground 176 -- process (client) exited, and doesn't know the server is still running? 177 screen:expect { 178 any = [[Process exited 0]], 179 } 180 181 child_uis --[[@type any[] ]] = ({ child_session:request('nvim_list_uis') })[2] 182 eq(0, #child_uis) 183 184 -- NOTE: The tt.setup_child_nvim() screen just wraps :terminal, it's not connected to the child. 185 -- To use it again, we need to detach the old one. 186 screen:detach() 187 188 -- Edit some text on the headless server. 189 status = (child_session:request('nvim_input', 'ddiWe did it, pooky.<Esc><Esc>')) 190 assert(status) 191 192 -- Test reattach by connecting a new TUI. 193 local screen_reattached = tt.setup_child_nvim({ 194 '--remote-ui', 195 '--server', 196 child_server, 197 }, job_opts) 198 199 tt.screen_expect( 200 screen_reattached, 201 [[ 202 We did it, pooky^. | 203 {100:~ }|*3 204 {3:[No Name] [+] }| 205 | 206 {5:-- TERMINAL --} | 207 ]] 208 ) 209 end) 210 end) 211 212 describe('TUI :restart', function() 213 it('validation', function() 214 clear() 215 eq('Vim(restart):E481: No range allowed: :1restart', t.pcall_err(n.command, ':1restart')) 216 end) 217 218 it('works', function() 219 clear() 220 finally(function() 221 n.check_close() 222 end) 223 224 local server_pipe = new_pipename() 225 local screen = tt.setup_child_nvim({ 226 '-u', 227 'NONE', 228 '-i', 229 'NONE', 230 '--listen', 231 server_pipe, 232 '--cmd', 233 'colorscheme vim', 234 '--cmd', 235 nvim_set .. ' notermguicolors laststatus=2 background=dark', 236 '--cmd', 237 'echo getpid()', 238 }, { env = env_notermguicolors }) 239 240 local function screen_expect(s) 241 tt.screen_expect(screen, s) 242 end 243 244 -- The value of has("gui_running") should be 0 before and after :restart. 245 local function assert_no_gui_running() 246 tt.feed_data(':echo "GUI Running: " .. has("gui_running")\013') 247 screen:expect({ any = 'GUI Running: 0' }) 248 end 249 250 local s0 = [[ 251 ^ | 252 {100:~ }|*3 253 {3:[No Name] }| 254 {MATCH:%d+ +}| 255 {5:-- TERMINAL --} | 256 ]] 257 screen_expect(s0) 258 assert_no_gui_running() 259 260 local server_session = n.connect(server_pipe) 261 local _, server_pid = server_session:request('nvim_call_function', 'getpid', {}) 262 263 local function assert_new_pid() 264 server_session:close() 265 server_session = n.connect(server_pipe) 266 local _, new_pid = server_session:request('nvim_call_function', 'getpid', {}) 267 t.neq(server_pid, new_pid) 268 server_pid = new_pid 269 end 270 271 --- Gets the last `argn` items in v:argv as a joined string. 272 local function get_argv(argn) 273 local argv = ({ server_session:request('nvim_eval', 'v:argv') })[2] --[[@type table]] 274 return table.concat(argv, ' ', #argv - argn, #argv) 275 end 276 277 local s1 = [[ 278 | 279 ^Hello1 | 280 {100:~ }|*2 281 {3:[No Name] [+] }| 282 {MATCH:%d+ +}| 283 {5:-- TERMINAL --} | 284 ]] 285 286 tt.feed_data(':set nomodified\013') 287 -- Command is added as "-c" arg. 288 tt.feed_data(":restart put ='Hello1'\013") 289 screen_expect(s1) 290 tt.feed_data('\013') 291 assert_new_pid() 292 assert_no_gui_running() 293 eq("--cmd echo getpid() +::: -c put ='Hello1'", get_argv(4)) 294 295 -- Complex command following +cmd. 296 tt.feed_data(":restart +qall! put ='Hello2' | put ='World2'\013") 297 screen_expect([[ 298 | 299 Hello2 | 300 ^World2 | 301 {100:~ }| 302 {3:[No Name] [+] }| 303 {MATCH:%d+ +}| 304 {5:-- TERMINAL --} | 305 ]]) 306 assert_new_pid() 307 assert_no_gui_running() 308 eq("--cmd echo getpid() +::: -c put ='Hello2' | put ='World2'", get_argv(4)) 309 310 -- Check ":restart" on an unmodified buffer. 311 tt.feed_data(':set nomodified\013') 312 tt.feed_data(':restart\013') 313 screen_expect(s0) 314 assert_new_pid() 315 assert_no_gui_running() 316 317 -- Check ":restart +qall!" on an unmodified buffer. 318 tt.feed_data(':restart +qall!\013') 319 screen_expect(s0) 320 assert_new_pid() 321 assert_no_gui_running() 322 eq('--cmd echo getpid()', get_argv(1)) 323 324 -- Check ":restart +echo" cannot restart server. 325 tt.feed_data(':restart +echo\013') 326 screen:expect({ any = vim.pesc('+cmd did not quit the server') }) 327 328 tt.feed_data('ithis will be removed\027') 329 screen_expect([[ 330 this will be remove^d | 331 {100:~ }|*3 332 {3:[No Name] [+] }| 333 | 334 {5:-- TERMINAL --} | 335 ]]) 336 337 -- Check ":confirm restart" on a modified buffer. 338 tt.feed_data(':confirm restart\013') 339 screen:expect({ any = vim.pesc('Save changes to "Untitled"?') }) 340 341 -- Cancel the operation (abandons restart). 342 tt.feed_data('C\013') 343 screen:expect({ any = vim.pesc('[No Name]') }) 344 345 -- Check :restart respects 'confirm' option. 346 tt.feed_data(':set confirm\013') 347 tt.feed_data(':restart\013') 348 screen:expect({ any = vim.pesc('Save changes to "Untitled"?') }) 349 tt.feed_data('C\013') 350 screen:expect({ any = vim.pesc('[No Name]') }) 351 tt.feed_data(':set noconfirm\013') 352 353 -- Check ":confirm restart <cmd>" on a modified buffer. 354 tt.feed_data(":confirm restart put ='Hello3'\013") 355 screen:expect({ any = vim.pesc('Save changes to "Untitled"?') }) 356 tt.feed_data('N\013') 357 screen:expect({ any = '%^Hello3' }) 358 assert_new_pid() 359 assert_no_gui_running() 360 eq("--cmd echo getpid() +::: -c put ='Hello3'", get_argv(4)) 361 362 -- Check ":confirm restart +echo" correctly ignores ":confirm" 363 tt.feed_data(':confirm restart +echo\013') 364 screen:expect({ any = vim.pesc('+cmd did not quit the server') }) 365 366 -- Check ":restart" on a modified buffer. 367 tt.feed_data('ithis will be removed\027') 368 tt.feed_data(':restart\013') 369 screen:expect({ any = vim.pesc('Vim(qall):E37: No write since last change') }) 370 371 -- Check ":restart +qall!" on a modified buffer. 372 tt.feed_data('ithis will be removed\027') 373 tt.feed_data(':restart +qall!\013') 374 screen_expect(s0) 375 assert_new_pid() 376 assert_no_gui_running() 377 378 -- No --listen conflict when server exit is delayed. 379 feed_data(':lua vim.schedule(function() vim.wait(100) end); vim.cmd.restart()\n') 380 screen_expect(s0) 381 assert_new_pid() 382 assert_no_gui_running() 383 384 screen:try_resize(60, 6) 385 screen_expect([[ 386 ^ | 387 {100:~ }|*2 388 {3:[No Name] }| 389 | 390 {5:-- TERMINAL --} | 391 ]]) 392 393 --- Check that ":restart" uses the updated size after terminal resize. 394 tt.feed_data(':restart\013') 395 screen_expect([[ 396 ^ | 397 {100:~ }|*2 398 {3:[No Name] }| 399 {MATCH:%d+ +}| 400 {5:-- TERMINAL --} | 401 ]]) 402 assert_new_pid() 403 assert_no_gui_running() 404 end) 405 406 it('drops "-" and "-- [files…]" from v:argv #34417', function() 407 t.skip(is_os('win'), 'stdin behavior differs on Windows') 408 clear() 409 local server_session 410 finally(function() 411 if server_session then 412 server_session:close() 413 end 414 n.check_close() 415 end) 416 local server_pipe = new_pipename() 417 local screen = tt.setup_child_nvim({ 418 '-u', 419 'NONE', 420 '-i', 421 'NONE', 422 '--listen', 423 server_pipe, 424 '--cmd', 425 'set notermguicolors', 426 '-s', 427 '-', 428 '-', 429 '--', 430 'Xtest-file1', 431 'Xtest-file2', 432 }, { env = { NVIM_LOG_FILE = testlog } }) 433 screen:expect([[ 434 ^ | 435 ~ |*3 436 {2:Xtest-file1 0,0-1 All}| 437 | 438 {5:-- TERMINAL --} | 439 ]]) 440 -- This error happens as stdin (forwarded as fd 3) is not a pipe. 441 assert_log('Failed to get flags on descriptor 3: Bad file descriptor', testlog, 50) 442 443 server_session = n.connect(server_pipe) 444 local expr = 'index(v:argv, "-") >= 0 || index(v:argv, "--") >= 0 ? v:true : v:false' 445 local has_s = 'index(v:argv, "-s") >= 0 ? v:true : v:false' 446 eq({ true, true }, { server_session:request('nvim_eval', expr) }) 447 eq({ true, true }, { server_session:request('nvim_eval', has_s) }) 448 449 tt.feed_data(":restart put='foo'\013") 450 screen:expect([[ 451 | 452 ^foo | 453 ~ |*2 454 {2:[No Name] [+] 2,1 All}| 455 | 456 {5:-- TERMINAL --} | 457 ]]) 458 server_session:close() 459 server_session = n.connect(server_pipe) 460 461 eq({ true, false }, { server_session:request('nvim_eval', expr) }) 462 eq({ true, false }, { server_session:request('nvim_eval', has_s) }) 463 local argv = ({ server_session:request('nvim_eval', 'v:argv') })[2] --[[@type table]] 464 eq(13, #argv) 465 eq("-c put='foo'", table.concat(argv, ' ', #argv - 1, #argv)) 466 end) 467 end) 468 469 describe('TUI :connect', function() 470 local screen_empty = [[ 471 ^ | 472 {100:~ }|*5 473 | 474 ]] 475 476 it('leaves the current server running', function() 477 n.clear() 478 finally(function() 479 n.check_close() 480 end) 481 482 local server1 = new_pipename() 483 local screen1 = tt.setup_child_nvim({ '--listen', server1, '--clean' }) 484 screen1:expect({ any = vim.pesc('[No Name]') }) 485 486 tt.feed_data(':connect\013') 487 screen1:expect({ any = 'E471: Argument required' }) 488 489 tt.feed_data('iThis is server 1.\027') 490 screen1:expect({ any = vim.pesc('This is server 1^.') }) 491 492 -- Prevent screen2 from receiving the old terminal state. 493 command('enew') 494 screen1:expect(screen_empty) 495 screen1:detach() 496 497 local server2 = new_pipename() 498 local screen2 = tt.setup_child_nvim({ '--listen', server2, '--clean' }) 499 screen2:expect({ any = vim.pesc('[No Name]') }) 500 501 tt.feed_data('iThis is server 2.\027') 502 screen2:expect({ any = vim.pesc('This is server 2^.') }) 503 504 tt.feed_data(':connect ' .. server1 .. '\013') 505 screen2:expect({ any = vim.pesc('This is server 1^.') }) 506 507 local server1_session = n.connect(server1) 508 server1_session:request('nvim_command', 'qall!') 509 screen2:expect({ any = vim.pesc('[Process exited 0]') }) 510 511 screen2:detach() 512 513 local server2_session = n.connect(server2) 514 515 local screen3 = tt.setup_child_nvim({ '--remote-ui', '--server', server2 }) 516 screen3:expect({ any = vim.pesc('This is server 2^.') }) 517 518 screen3:detach() 519 server2_session:request('nvim_command', 'qall!') 520 end) 521 522 it('! stops the current server', function() 523 n.clear() 524 finally(function() 525 n.check_close() 526 end) 527 528 local server1 = new_pipename() 529 local screen1 = tt.setup_child_nvim({ '--listen', server1, '--clean' }) 530 screen1:expect({ any = vim.pesc('[No Name]') }) 531 532 tt.feed_data('iThis is server 1.\027') 533 screen1:expect({ any = vim.pesc('This is server 1^.') }) 534 535 -- Prevent screen2 from receiving the old terminal state. 536 command('enew') 537 screen1:expect(screen_empty) 538 screen1:detach() 539 540 local server2 = new_pipename() 541 local screen2 = tt.setup_child_nvim({ '--listen', server2, '--clean' }) 542 screen2:expect({ any = vim.pesc('[No Name]') }) 543 544 tt.feed_data(':connect! ' .. server1 .. '\013') 545 screen2:expect({ any = vim.pesc('This is server 1^.') }) 546 547 retry(nil, nil, function() 548 eq(nil, vim.uv.fs_stat(server2)) 549 end) 550 551 local server1_session = n.connect(server1) 552 server1_session:request('nvim_command', 'qall!') 553 screen2:expect({ any = vim.pesc('[Process exited 0]') }) 554 555 screen2:detach() 556 end) 557 end) 558 559 describe('TUI', function() 560 local screen --[[@type test.functional.ui.screen]] 561 local child_session --[[@type test.Session]] 562 local child_exec_lua --[[@type fun(code: string, ...):any]] 563 564 before_each(function() 565 clear() 566 local child_server = new_pipename() 567 screen = tt.setup_child_nvim({ 568 '--listen', 569 child_server, 570 '--clean', 571 '--cmd', 572 nvim_set .. ' laststatus=2 background=dark', 573 '--cmd', 574 'colorscheme vim', 575 }, { env = env_notermguicolors }) 576 screen:expect([[ 577 ^ | 578 {100:~ }|*3 579 {3:[No Name] }| 580 | 581 {5:-- TERMINAL --} | 582 ]]) 583 child_session = n.connect(child_server) 584 child_exec_lua = tt.make_lua_executor(child_session) 585 end) 586 587 -- Wait for mode in the child Nvim (avoid "typeahead race" #10826). 588 local function wait_for_mode(mode) 589 retry(nil, nil, function() 590 local _, m = child_session:request('nvim_get_mode') 591 eq(mode, m.mode) 592 end) 593 end 594 595 -- Assert buffer contents in the child Nvim. 596 local function expect_child_buf_lines(expected) 597 assert(type({}) == type(expected)) 598 retry(nil, nil, function() 599 local _, buflines = child_session:request('nvim_buf_get_lines', 0, 0, -1, false) 600 eq(expected, buflines) 601 end) 602 end 603 604 -- Ensure both child client and child server have processed pending events. 605 local function poke_both_eventloop() 606 child_exec_lua([[ 607 _G.termresponse = nil 608 vim.api.nvim_create_autocmd('TermResponse', { 609 once = true, 610 callback = function(ev) _G.termresponse = ev.data.sequence end, 611 }) 612 ]]) 613 feed_data('\027P0$r\027\\') 614 retry(nil, nil, function() 615 eq('\027P0$r', child_exec_lua('return _G.termresponse')) 616 end) 617 end 618 619 it('rapid resize #7572 #7628', function() 620 -- Need buffer rows to provoke the behavior. 621 feed_data(':edit test/functional/fixtures/bigfile.txt\n') 622 screen:expect([[ 623 ^0000;<control>;Cc;0;BN;;;;;N;NULL;;;; | 624 0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;;; | 625 0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; | 626 0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; | 627 {3:test/functional/fixtures/bigfile.txt }| 628 :edit test/functional/fixtures/bigfile.txt | 629 {5:-- TERMINAL --} | 630 ]]) 631 command('call jobresize(b:terminal_job_id, 58, 9)') 632 command('call jobresize(b:terminal_job_id, 62, 13)') 633 command('call jobresize(b:terminal_job_id, 100, 42)') 634 command('call jobresize(b:terminal_job_id, 37, 1000)') 635 -- Resize to <5 columns. 636 screen:try_resize(4, 44) 637 command('call jobresize(b:terminal_job_id, 4, 1000)') 638 -- Resize to 1 row, then to 1 column, then increase rows to 4. 639 screen:try_resize(44, 1) 640 command('call jobresize(b:terminal_job_id, 44, 1)') 641 screen:try_resize(1, 1) 642 command('call jobresize(b:terminal_job_id, 1, 1)') 643 screen:try_resize(1, 4) 644 command('call jobresize(b:terminal_job_id, 1, 4)') 645 screen:try_resize(57, 17) 646 command('call jobresize(b:terminal_job_id, 57, 17)') 647 retry(nil, nil, function() 648 eq({ true, 57 }, { child_session:request('nvim_win_get_width', 0) }) 649 end) 650 end) 651 652 it('accepts resize while pager is active', function() 653 t.skip(is_os('win'), 'FIXME: some spaces have wrong attrs on Windows') 654 child_session:request( 655 'nvim_exec2', 656 [[ 657 set more 658 func! ManyErr() 659 for i in range(20) 660 echoerr "FAIL ".i 661 endfor 662 endfunc 663 ]], 664 {} 665 ) 666 feed_data(':call ManyErr()\r') 667 screen:expect([[ 668 {101:Error in function ManyErr:} | 669 {103:line 2:} | 670 {101:FAIL 0} | 671 {101:FAIL 1} | 672 {101:FAIL 2} | 673 {102:-- More --}^ | 674 {5:-- TERMINAL --} | 675 ]]) 676 677 screen:try_resize(50, 10) 678 screen:expect([[ 679 :call ManyErr() | 680 {101:Error in function ManyErr:} | 681 {103:line 2:} | 682 {101:FAIL 0} | 683 {101:FAIL 1} | 684 {101:FAIL 2} | 685 |*2 686 {102:-- More --}^ | 687 {5:-- TERMINAL --} | 688 ]]) 689 690 feed_data('j') 691 screen:expect([[ 692 {101:Error in function ManyErr:} | 693 {103:line 2:} | 694 {101:FAIL 0} | 695 {101:FAIL 1} | 696 {101:FAIL 2} | 697 {101:FAIL 3} | 698 {101:FAIL 4} | 699 {101:FAIL 5} | 700 {102:-- More --}^ | 701 {5:-- TERMINAL --} | 702 ]]) 703 704 screen:try_resize(50, 7) 705 screen:expect([[ 706 {101:FAIL 1} | 707 {101:FAIL 2} | 708 {101:FAIL 3} | 709 {101:FAIL 4} | 710 {101:FAIL 5} | 711 {102:-- More --}^ | 712 {5:-- TERMINAL --} | 713 ]]) 714 715 screen:try_resize(50, 5) 716 screen:expect([[ 717 {101:FAIL 3} | 718 {101:FAIL 4} | 719 {101:FAIL 5} | 720 {102:-- More --}^ | 721 {5:-- TERMINAL --} | 722 ]]) 723 724 feed_data('g') 725 screen:expect([[ 726 :call ManyErr() | 727 {101:Error in function ManyErr:} | 728 {103:line 2:} | 729 {102:-- More --}^ | 730 {5:-- TERMINAL --} | 731 ]]) 732 733 screen:try_resize(50, 10) 734 screen:expect([[ 735 :call ManyErr() | 736 {101:Error in function ManyErr:} | 737 {103:line 2:} | 738 {101:FAIL 0} | 739 {101:FAIL 1} | 740 {101:FAIL 2} | 741 {101:FAIL 3} | 742 {101:FAIL 4} | 743 {102:-- More --}^ | 744 {5:-- TERMINAL --} | 745 ]]) 746 747 feed_data('\003') 748 screen:expect([[ 749 ^ | 750 {100:~ }|*6 751 {3:[No Name] }| 752 | 753 {5:-- TERMINAL --} | 754 ]]) 755 end) 756 757 it('accepts basic utf-8 input', function() 758 feed_data('iabc\ntest1\ntest2') 759 screen:expect([[ 760 abc | 761 test1 | 762 test2^ | 763 {100:~ }| 764 {3:[No Name] [+] }| 765 {5:-- INSERT --} | 766 {5:-- TERMINAL --} | 767 ]]) 768 feed_data('\027') 769 screen:expect([[ 770 abc | 771 test1 | 772 test^2 | 773 {100:~ }| 774 {3:[No Name] [+] }| 775 | 776 {5:-- TERMINAL --} | 777 ]]) 778 end) 779 780 it('interprets leading <Esc> byte as ALT modifier in normal-mode', function() 781 local keys = 'dfghjkl' 782 for c in keys:gmatch('.') do 783 feed_data(':nnoremap <a-' .. c .. '> ialt-' .. c .. '<cr><esc>\r') 784 feed_data('\027' .. c) 785 end 786 screen:expect([[ 787 alt-j | 788 alt-k | 789 alt-l | 790 ^ | 791 {3:[No Name] [+] }| 792 | 793 {5:-- TERMINAL --} | 794 ]]) 795 feed_data('gg') 796 screen:expect([[ 797 ^alt-d | 798 alt-f | 799 alt-g | 800 alt-h | 801 {3:[No Name] [+] }| 802 | 803 {5:-- TERMINAL --} | 804 ]]) 805 end) 806 807 it('interprets ESC+key as ALT chord in i_CTRL-V', function() 808 -- Vim represents ALT/META by setting the "high bit" of the modified key: 809 -- ALT+j inserts "ê". Nvim does not (#3982). 810 feed_data('i\022\027j') 811 screen:expect([[ 812 <M-j>^ | 813 {100:~ }|*3 814 {3:[No Name] [+] }| 815 {5:-- INSERT --} | 816 {5:-- TERMINAL --} | 817 ]]) 818 end) 819 820 it('interprets <Esc> encoded with kitty keyboard protocol', function() 821 child_session:request( 822 'nvim_exec2', 823 [[ 824 nnoremap <M-;> <Nop> 825 nnoremap <Esc> AESC<Esc> 826 nnoremap <C-Esc> ACtrlEsc<Esc> 827 nnoremap <D-Esc> ASuperEsc<Esc> 828 nnoremap ; Asemicolon<Esc> 829 ]], 830 {} 831 ) 832 -- Works with no modifier 833 feed_data('\027[27u;') 834 expect_child_buf_lines({ 'ESCsemicolon' }) 835 -- Works with Ctrl modifier 836 feed_data('\027[27;5u') 837 expect_child_buf_lines({ 'ESCsemicolonCtrlEsc' }) 838 -- Works with Super modifier 839 feed_data('\027[27;9u') 840 expect_child_buf_lines({ 'ESCsemicolonCtrlEscSuperEsc' }) 841 -- Works with NumLock modifier (which should be the same as no modifier) #33799 842 feed_data('\027[27;129u') 843 expect_child_buf_lines({ 'ESCsemicolonCtrlEscSuperEscESC' }) 844 screen:expect([[ 845 ESCsemicolonCtrlEscSuperEscES^C | 846 {100:~ }|*3 847 {3:[No Name] [+] }| 848 | 849 {5:-- TERMINAL --} | 850 ]]) 851 -- <Esc>; should be recognized as <M-;> when <M-;> is mapped 852 feed_data('\027;') 853 screen:expect_unchanged() 854 expect_child_buf_lines({ 'ESCsemicolonCtrlEscSuperEscESC' }) 855 end) 856 857 it('interprets <Esc><Nul> as <M-C-Space> #17198', function() 858 t.skip(is_os('win'), 'FIXME: does not work on Windows') 859 feed_data('i\022\027\000') 860 screen:expect([[ 861 <M-C-Space>^ | 862 {100:~ }|*3 863 {3:[No Name] [+] }| 864 {5:-- INSERT --} | 865 {5:-- TERMINAL --} | 866 ]]) 867 end) 868 869 it("split sequences work within 'ttimeoutlen' time", function() 870 poke_both_eventloop() -- Make sure startup requests have finished. 871 child_session:request('nvim_set_option_value', 'ttimeoutlen', 250, {}) 872 feed_data('i') 873 screen:expect([[ 874 ^ | 875 {100:~ }|*3 876 {3:[No Name] }| 877 {5:-- INSERT --} | 878 {5:-- TERMINAL --} | 879 ]]) 880 -- Split UTF-8 '⌂' character 881 feed_data('\226') 882 screen:expect_unchanged(false, 25) 883 feed_data('\140') 884 screen:expect_unchanged(false, 25) 885 feed_data('\130') 886 screen:expect([[ 887 ⌂^ | 888 {100:~ }|*3 889 {3:[No Name] [+] }| 890 {5:-- INSERT --} | 891 {5:-- TERMINAL --} | 892 ]]) 893 -- Split CSI u escape sequence for Ctrl-X 894 feed_data('\027') 895 screen:expect_unchanged(false, 25) 896 feed_data('[') 897 screen:expect_unchanged(false, 25) 898 feed_data('120;') 899 screen:expect_unchanged(false, 25) 900 feed_data('5u') 901 screen:expect([[ 902 ⌂^ | 903 {100:~ }|*3 904 {3:[No Name] [+] }| 905 {5:-- ^X mode (^]^D^E^F^I^K^L^N^O^P^Rs^U^V^Y)} | 906 {5:-- TERMINAL --} | 907 ]]) 908 -- <Esc> is sent after 'ttimeoutlen' exceeds. 909 feed_data('\027') 910 screen:expect_unchanged(false, 25) 911 vim.uv.sleep(225) 912 screen:expect([[ 913 ^⌂ | 914 {100:~ }|*3 915 {3:[No Name] [+] }| 916 | 917 {5:-- TERMINAL --} | 918 ]]) 919 end) 920 921 it('accepts ASCII control sequences', function() 922 feed_data('i') 923 feed_data('\022\007') -- ctrl+g 924 feed_data('\022\022') -- ctrl+v 925 feed_data('\022\013') -- ctrl+m 926 screen:expect([[ 927 {104:^G^V^M}^ | 928 {100:~ }|*3 929 {3:[No Name] [+] }| 930 {5:-- INSERT --} | 931 {5:-- TERMINAL --} | 932 ]]) 933 child_session:request('nvim_set_keymap', 'i', '\031', '!!!', {}) 934 feed_data('\031') 935 screen:expect([[ 936 {104:^G^V^M}!!!^ | 937 {100:~ }|*3 938 {3:[No Name] [+] }| 939 {5:-- INSERT --} | 940 {5:-- TERMINAL --} | 941 ]]) 942 child_session:request('nvim_buf_delete', 0, { force = true }) 943 child_session:request('nvim_set_option_value', 'laststatus', 0, {}) 944 child_session:request( 945 'nvim_call_function', 946 'jobstart', 947 { { testprg('shell-test'), 'INTERACT' }, { term = true } } 948 ) 949 screen:expect([[ 950 interact $ ^ | 951 |*4 952 {5:-- TERMINAL --} |*2 953 ]]) 954 -- mappings for C0 control codes should work in Terminal mode #33750 955 child_session:request('nvim_set_keymap', 't', '\031', '<Cmd>new<CR>', {}) 956 feed_data('\031') 957 screen:expect([[ 958 ^ | 959 {100:~ }| 960 {3:[No Name] }| 961 interact $ | 962 |*2 963 {5:-- TERMINAL --} | 964 ]]) 965 end) 966 967 local function test_mouse_wheel(esc) 968 t.skip(is_os('win'), 'FIXME: some spaces have wrong attrs on Windows') 969 child_session:request( 970 'nvim_exec2', 971 [[ 972 set number nostartofline nowrap mousescroll=hor:1,ver:1 973 call setline(1, repeat([join(range(10), '----')], 10)) 974 vsplit 975 ]], 976 {} 977 ) 978 screen:expect([[ 979 {103: 1 }^0----1----2----3----4│{103: 1 }0----1----2----3----| 980 {103: 2 }0----1----2----3----4│{103: 2 }0----1----2----3----| 981 {103: 3 }0----1----2----3----4│{103: 3 }0----1----2----3----| 982 {103: 4 }0----1----2----3----4│{103: 4 }0----1----2----3----| 983 {3:[No Name] [+] }{2:[No Name] [+] }| 984 | 985 {5:-- TERMINAL --} | 986 ]]) 987 -- <ScrollWheelDown> in active window 988 if esc then 989 feed_data('\027[<65;8;1M') 990 else 991 api.nvim_input_mouse('wheel', 'down', '', 0, 0, 7) 992 end 993 screen:expect([[ 994 {103: 2 }^0----1----2----3----4│{103: 1 }0----1----2----3----| 995 {103: 3 }0----1----2----3----4│{103: 2 }0----1----2----3----| 996 {103: 4 }0----1----2----3----4│{103: 3 }0----1----2----3----| 997 {103: 5 }0----1----2----3----4│{103: 4 }0----1----2----3----| 998 {3:[No Name] [+] }{2:[No Name] [+] }| 999 | 1000 {5:-- TERMINAL --} | 1001 ]]) 1002 -- <ScrollWheelDown> in inactive window 1003 if esc then 1004 feed_data('\027[<65;48;1M') 1005 else 1006 api.nvim_input_mouse('wheel', 'down', '', 0, 0, 47) 1007 end 1008 screen:expect([[ 1009 {103: 2 }^0----1----2----3----4│{103: 2 }0----1----2----3----| 1010 {103: 3 }0----1----2----3----4│{103: 3 }0----1----2----3----| 1011 {103: 4 }0----1----2----3----4│{103: 4 }0----1----2----3----| 1012 {103: 5 }0----1----2----3----4│{103: 5 }0----1----2----3----| 1013 {3:[No Name] [+] }{2:[No Name] [+] }| 1014 | 1015 {5:-- TERMINAL --} | 1016 ]]) 1017 -- <ScrollWheelRight> in active window 1018 if esc then 1019 feed_data('\027[<67;8;1M') 1020 else 1021 api.nvim_input_mouse('wheel', 'right', '', 0, 0, 7) 1022 end 1023 screen:expect([[ 1024 {103: 2 }^----1----2----3----4-│{103: 2 }0----1----2----3----| 1025 {103: 3 }----1----2----3----4-│{103: 3 }0----1----2----3----| 1026 {103: 4 }----1----2----3----4-│{103: 4 }0----1----2----3----| 1027 {103: 5 }----1----2----3----4-│{103: 5 }0----1----2----3----| 1028 {3:[No Name] [+] }{2:[No Name] [+] }| 1029 | 1030 {5:-- TERMINAL --} | 1031 ]]) 1032 -- <ScrollWheelRight> in inactive window 1033 if esc then 1034 feed_data('\027[<67;48;1M') 1035 else 1036 api.nvim_input_mouse('wheel', 'right', '', 0, 0, 47) 1037 end 1038 screen:expect([[ 1039 {103: 2 }^----1----2----3----4-│{103: 2 }----1----2----3----4| 1040 {103: 3 }----1----2----3----4-│{103: 3 }----1----2----3----4| 1041 {103: 4 }----1----2----3----4-│{103: 4 }----1----2----3----4| 1042 {103: 5 }----1----2----3----4-│{103: 5 }----1----2----3----4| 1043 {3:[No Name] [+] }{2:[No Name] [+] }| 1044 | 1045 {5:-- TERMINAL --} | 1046 ]]) 1047 -- <S-ScrollWheelDown> in active window 1048 if esc then 1049 feed_data('\027[<69;8;1M') 1050 else 1051 api.nvim_input_mouse('wheel', 'down', 'S', 0, 0, 7) 1052 end 1053 screen:expect([[ 1054 {103: 5 }^----1----2----3----4-│{103: 2 }----1----2----3----4| 1055 {103: 6 }----1----2----3----4-│{103: 3 }----1----2----3----4| 1056 {103: 7 }----1----2----3----4-│{103: 4 }----1----2----3----4| 1057 {103: 8 }----1----2----3----4-│{103: 5 }----1----2----3----4| 1058 {3:[No Name] [+] }{2:[No Name] [+] }| 1059 | 1060 {5:-- TERMINAL --} | 1061 ]]) 1062 -- <S-ScrollWheelDown> in inactive window 1063 if esc then 1064 feed_data('\027[<69;48;1M') 1065 else 1066 api.nvim_input_mouse('wheel', 'down', 'S', 0, 0, 47) 1067 end 1068 screen:expect([[ 1069 {103: 5 }^----1----2----3----4-│{103: 5 }----1----2----3----4| 1070 {103: 6 }----1----2----3----4-│{103: 6 }----1----2----3----4| 1071 {103: 7 }----1----2----3----4-│{103: 7 }----1----2----3----4| 1072 {103: 8 }----1----2----3----4-│{103: 8 }----1----2----3----4| 1073 {3:[No Name] [+] }{2:[No Name] [+] }| 1074 | 1075 {5:-- TERMINAL --} | 1076 ]]) 1077 -- <S-ScrollWheelRight> in active window 1078 if esc then 1079 feed_data('\027[<71;8;1M') 1080 else 1081 api.nvim_input_mouse('wheel', 'right', 'S', 0, 0, 7) 1082 end 1083 screen:expect([[ 1084 {103: 5 }^----6----7----8----9 │{103: 5 }----1----2----3----4| 1085 {103: 6 }----6----7----8----9 │{103: 6 }----1----2----3----4| 1086 {103: 7 }----6----7----8----9 │{103: 7 }----1----2----3----4| 1087 {103: 8 }----6----7----8----9 │{103: 8 }----1----2----3----4| 1088 {3:[No Name] [+] }{2:[No Name] [+] }| 1089 | 1090 {5:-- TERMINAL --} | 1091 ]]) 1092 -- <S-ScrollWheelRight> in inactive window 1093 if esc then 1094 feed_data('\027[<71;48;1M') 1095 else 1096 api.nvim_input_mouse('wheel', 'right', 'S', 0, 0, 47) 1097 end 1098 screen:expect([[ 1099 {103: 5 }^----6----7----8----9 │{103: 5 }5----6----7----8----| 1100 {103: 6 }----6----7----8----9 │{103: 6 }5----6----7----8----| 1101 {103: 7 }----6----7----8----9 │{103: 7 }5----6----7----8----| 1102 {103: 8 }----6----7----8----9 │{103: 8 }5----6----7----8----| 1103 {3:[No Name] [+] }{2:[No Name] [+] }| 1104 | 1105 {5:-- TERMINAL --} | 1106 ]]) 1107 -- <ScrollWheelUp> in active window 1108 if esc then 1109 feed_data('\027[<64;8;1M') 1110 else 1111 api.nvim_input_mouse('wheel', 'up', '', 0, 0, 7) 1112 end 1113 screen:expect([[ 1114 {103: 4 }----6----7----8----9 │{103: 5 }5----6----7----8----| 1115 {103: 5 }^----6----7----8----9 │{103: 6 }5----6----7----8----| 1116 {103: 6 }----6----7----8----9 │{103: 7 }5----6----7----8----| 1117 {103: 7 }----6----7----8----9 │{103: 8 }5----6----7----8----| 1118 {3:[No Name] [+] }{2:[No Name] [+] }| 1119 | 1120 {5:-- TERMINAL --} | 1121 ]]) 1122 -- <ScrollWheelUp> in inactive window 1123 if esc then 1124 feed_data('\027[<64;48;1M') 1125 else 1126 api.nvim_input_mouse('wheel', 'up', '', 0, 0, 47) 1127 end 1128 screen:expect([[ 1129 {103: 4 }----6----7----8----9 │{103: 4 }5----6----7----8----| 1130 {103: 5 }^----6----7----8----9 │{103: 5 }5----6----7----8----| 1131 {103: 6 }----6----7----8----9 │{103: 6 }5----6----7----8----| 1132 {103: 7 }----6----7----8----9 │{103: 7 }5----6----7----8----| 1133 {3:[No Name] [+] }{2:[No Name] [+] }| 1134 | 1135 {5:-- TERMINAL --} | 1136 ]]) 1137 -- <ScrollWheelLeft> in active window 1138 if esc then 1139 feed_data('\027[<66;8;1M') 1140 else 1141 api.nvim_input_mouse('wheel', 'left', '', 0, 0, 7) 1142 end 1143 screen:expect([[ 1144 {103: 4 }5----6----7----8----9│{103: 4 }5----6----7----8----| 1145 {103: 5 }5^----6----7----8----9│{103: 5 }5----6----7----8----| 1146 {103: 6 }5----6----7----8----9│{103: 6 }5----6----7----8----| 1147 {103: 7 }5----6----7----8----9│{103: 7 }5----6----7----8----| 1148 {3:[No Name] [+] }{2:[No Name] [+] }| 1149 | 1150 {5:-- TERMINAL --} | 1151 ]]) 1152 -- <ScrollWheelLeft> in inactive window 1153 if esc then 1154 feed_data('\027[<66;48;1M') 1155 else 1156 api.nvim_input_mouse('wheel', 'left', '', 0, 0, 47) 1157 end 1158 screen:expect([[ 1159 {103: 4 }5----6----7----8----9│{103: 4 }-5----6----7----8---| 1160 {103: 5 }5^----6----7----8----9│{103: 5 }-5----6----7----8---| 1161 {103: 6 }5----6----7----8----9│{103: 6 }-5----6----7----8---| 1162 {103: 7 }5----6----7----8----9│{103: 7 }-5----6----7----8---| 1163 {3:[No Name] [+] }{2:[No Name] [+] }| 1164 | 1165 {5:-- TERMINAL --} | 1166 ]]) 1167 -- <S-ScrollWheelUp> in active window 1168 if esc then 1169 feed_data('\027[<68;8;1M') 1170 else 1171 api.nvim_input_mouse('wheel', 'up', 'S', 0, 0, 7) 1172 end 1173 screen:expect([[ 1174 {103: 1 }5----6----7----8----9│{103: 4 }-5----6----7----8---| 1175 {103: 2 }5----6----7----8----9│{103: 5 }-5----6----7----8---| 1176 {103: 3 }5----6----7----8----9│{103: 6 }-5----6----7----8---| 1177 {103: 4 }5^----6----7----8----9│{103: 7 }-5----6----7----8---| 1178 {3:[No Name] [+] }{2:[No Name] [+] }| 1179 | 1180 {5:-- TERMINAL --} | 1181 ]]) 1182 -- <S-ScrollWheelUp> in inactive window 1183 if esc then 1184 feed_data('\027[<68;48;1M') 1185 else 1186 api.nvim_input_mouse('wheel', 'up', 'S', 0, 0, 47) 1187 end 1188 screen:expect([[ 1189 {103: 1 }5----6----7----8----9│{103: 1 }-5----6----7----8---| 1190 {103: 2 }5----6----7----8----9│{103: 2 }-5----6----7----8---| 1191 {103: 3 }5----6----7----8----9│{103: 3 }-5----6----7----8---| 1192 {103: 4 }5^----6----7----8----9│{103: 4 }-5----6----7----8---| 1193 {3:[No Name] [+] }{2:[No Name] [+] }| 1194 | 1195 {5:-- TERMINAL --} | 1196 ]]) 1197 -- <S-ScrollWheelLeft> in active window 1198 if esc then 1199 feed_data('\027[<70;8;1M') 1200 else 1201 api.nvim_input_mouse('wheel', 'left', 'S', 0, 0, 7) 1202 end 1203 screen:expect([[ 1204 {103: 1 }0----1----2----3----4│{103: 1 }-5----6----7----8---| 1205 {103: 2 }0----1----2----3----4│{103: 2 }-5----6----7----8---| 1206 {103: 3 }0----1----2----3----4│{103: 3 }-5----6----7----8---| 1207 {103: 4 }0----1----2----3----^4│{103: 4 }-5----6----7----8---| 1208 {3:[No Name] [+] }{2:[No Name] [+] }| 1209 | 1210 {5:-- TERMINAL --} | 1211 ]]) 1212 -- <S-ScrollWheelLeft> in inactive window 1213 if esc then 1214 feed_data('\027[<70;48;1M') 1215 else 1216 api.nvim_input_mouse('wheel', 'left', 'S', 0, 0, 47) 1217 end 1218 screen:expect([[ 1219 {103: 1 }0----1----2----3----4│{103: 1 }0----1----2----3----| 1220 {103: 2 }0----1----2----3----4│{103: 2 }0----1----2----3----| 1221 {103: 3 }0----1----2----3----4│{103: 3 }0----1----2----3----| 1222 {103: 4 }0----1----2----3----^4│{103: 4 }0----1----2----3----| 1223 {3:[No Name] [+] }{2:[No Name] [+] }| 1224 | 1225 {5:-- TERMINAL --} | 1226 ]]) 1227 end 1228 1229 describe('accepts mouse wheel events', function() 1230 it('(mouse events sent to host)', function() 1231 test_mouse_wheel(false) 1232 end) 1233 1234 it('(escape sequences sent to child)', function() 1235 test_mouse_wheel(true) 1236 end) 1237 end) 1238 1239 local function test_mouse_popup(esc) 1240 child_session:request( 1241 'nvim_exec2', 1242 [[ 1243 call setline(1, 'popup menu test') 1244 set mouse=a mousemodel=popup 1245 1246 aunmenu PopUp 1247 " Delete the default MenuPopup event handler. 1248 autocmd! nvim.popupmenu 1249 menu PopUp.foo :let g:menustr = 'foo'<CR> 1250 menu PopUp.bar :let g:menustr = 'bar'<CR> 1251 menu PopUp.baz :let g:menustr = 'baz'<CR> 1252 highlight Pmenu ctermbg=NONE ctermfg=NONE cterm=underline,reverse 1253 highlight PmenuSel ctermbg=NONE ctermfg=NONE cterm=underline,reverse,bold 1254 ]], 1255 {} 1256 ) 1257 if esc then 1258 feed_data('\027[<2;5;1M') 1259 else 1260 api.nvim_input_mouse('right', 'press', '', 0, 0, 4) 1261 end 1262 screen:expect([[ 1263 ^popup menu test | 1264 {100:~ }{105: foo }{100: }| 1265 {100:~ }{105: bar }{100: }| 1266 {100:~ }{105: baz }{100: }| 1267 {3:[No Name] [+] }| 1268 | 1269 {5:-- TERMINAL --} | 1270 ]]) 1271 if esc then 1272 feed_data('\027[<2;5;1m') 1273 else 1274 api.nvim_input_mouse('right', 'release', '', 0, 0, 4) 1275 end 1276 screen:expect_unchanged() 1277 if esc then 1278 feed_data('\027[<64;5;1M') 1279 else 1280 api.nvim_input_mouse('wheel', 'up', '', 0, 0, 4) 1281 end 1282 screen:expect([[ 1283 ^popup menu test | 1284 {100:~ }{106: foo }{100: }| 1285 {100:~ }{105: bar }{100: }| 1286 {100:~ }{105: baz }{100: }| 1287 {3:[No Name] [+] }| 1288 | 1289 {5:-- TERMINAL --} | 1290 ]]) 1291 if esc then 1292 feed_data('\027[<35;7;4M') 1293 else 1294 api.nvim_input_mouse('move', '', '', 0, 3, 6) 1295 end 1296 screen:expect([[ 1297 ^popup menu test | 1298 {100:~ }{105: foo }{100: }| 1299 {100:~ }{105: bar }{100: }| 1300 {100:~ }{106: baz }{100: }| 1301 {3:[No Name] [+] }| 1302 | 1303 {5:-- TERMINAL --} | 1304 ]]) 1305 if esc then 1306 feed_data('\027[<65;7;4M') 1307 else 1308 api.nvim_input_mouse('wheel', 'down', '', 0, 3, 6) 1309 end 1310 screen:expect([[ 1311 ^popup menu test | 1312 {100:~ }{105: foo }{100: }| 1313 {100:~ }{106: bar }{100: }| 1314 {100:~ }{105: baz }{100: }| 1315 {3:[No Name] [+] }| 1316 | 1317 {5:-- TERMINAL --} | 1318 ]]) 1319 if esc then 1320 feed_data('\027[<0;7;3M') 1321 else 1322 api.nvim_input_mouse('left', 'press', '', 0, 2, 6) 1323 end 1324 screen:expect([[ 1325 ^popup menu test | 1326 {100:~ }|*3 1327 {3:[No Name] [+] }| 1328 :let g:menustr = 'bar' | 1329 {5:-- TERMINAL --} | 1330 ]]) 1331 if esc then 1332 feed_data('\027[<0;7;3m') 1333 else 1334 api.nvim_input_mouse('left', 'release', '', 0, 2, 6) 1335 end 1336 screen:expect_unchanged() 1337 if esc then 1338 feed_data('\027[<2;45;3M') 1339 else 1340 api.nvim_input_mouse('right', 'press', '', 0, 2, 44) 1341 end 1342 screen:expect([[ 1343 ^popup menu test | 1344 {100:~ }|*2 1345 {100:~ }{105: foo }{100: }| 1346 {3:[No Name] [+] }{105: bar }{3: }| 1347 :let g:menustr = 'bar' {105: baz } | 1348 {5:-- TERMINAL --} | 1349 ]]) 1350 if esc then 1351 feed_data('\027[<34;48;6M') 1352 else 1353 api.nvim_input_mouse('right', 'drag', '', 0, 5, 47) 1354 end 1355 screen:expect([[ 1356 ^popup menu test | 1357 {100:~ }|*2 1358 {100:~ }{105: foo }{100: }| 1359 {3:[No Name] [+] }{105: bar }{3: }| 1360 :let g:menustr = 'bar' {106: baz } | 1361 {5:-- TERMINAL --} | 1362 ]]) 1363 if esc then 1364 feed_data('\027[<2;48;6m') 1365 else 1366 api.nvim_input_mouse('right', 'release', '', 0, 5, 47) 1367 end 1368 screen:expect([[ 1369 ^popup menu test | 1370 {100:~ }|*3 1371 {3:[No Name] [+] }| 1372 :let g:menustr = 'baz' | 1373 {5:-- TERMINAL --} | 1374 ]]) 1375 end 1376 1377 describe('mouse events work with right-click menu', function() 1378 it('(mouse events sent to host)', function() 1379 test_mouse_popup(false) 1380 end) 1381 1382 it('(escape sequences sent to child)', function() 1383 test_mouse_popup(true) 1384 end) 1385 end) 1386 1387 it('accepts keypad keys from kitty keyboard protocol #19180', function() 1388 feed_data('i') 1389 feed_data(fn.nr2char(57399)) -- KP_0 1390 feed_data(fn.nr2char(57400)) -- KP_1 1391 feed_data(fn.nr2char(57401)) -- KP_2 1392 feed_data(fn.nr2char(57402)) -- KP_3 1393 feed_data(fn.nr2char(57403)) -- KP_4 1394 feed_data(fn.nr2char(57404)) -- KP_5 1395 feed_data(fn.nr2char(57405)) -- KP_6 1396 feed_data(fn.nr2char(57406)) -- KP_7 1397 feed_data(fn.nr2char(57407)) -- KP_8 1398 feed_data(fn.nr2char(57408)) -- KP_9 1399 feed_data(fn.nr2char(57409)) -- KP_DECIMAL 1400 feed_data(fn.nr2char(57410)) -- KP_DIVIDE 1401 feed_data(fn.nr2char(57411)) -- KP_MULTIPLY 1402 feed_data(fn.nr2char(57412)) -- KP_SUBTRACT 1403 feed_data(fn.nr2char(57413)) -- KP_ADD 1404 feed_data(fn.nr2char(57414)) -- KP_ENTER 1405 feed_data(fn.nr2char(57415)) -- KP_EQUAL 1406 screen:expect([[ 1407 0123456789./*-+ | 1408 =^ | 1409 {100:~ }|*2 1410 {3:[No Name] [+] }| 1411 {5:-- INSERT --} | 1412 {5:-- TERMINAL --} | 1413 ]]) 1414 feed_data(fn.nr2char(57417)) -- KP_LEFT 1415 screen:expect([[ 1416 0123456789./*-+ | 1417 ^= | 1418 {100:~ }|*2 1419 {3:[No Name] [+] }| 1420 {5:-- INSERT --} | 1421 {5:-- TERMINAL --} | 1422 ]]) 1423 feed_data(fn.nr2char(57418)) -- KP_RIGHT 1424 screen:expect([[ 1425 0123456789./*-+ | 1426 =^ | 1427 {100:~ }|*2 1428 {3:[No Name] [+] }| 1429 {5:-- INSERT --} | 1430 {5:-- TERMINAL --} | 1431 ]]) 1432 feed_data(fn.nr2char(57419)) -- KP_UP 1433 screen:expect([[ 1434 0^123456789./*-+ | 1435 = | 1436 {100:~ }|*2 1437 {3:[No Name] [+] }| 1438 {5:-- INSERT --} | 1439 {5:-- TERMINAL --} | 1440 ]]) 1441 feed_data(fn.nr2char(57420)) -- KP_DOWN 1442 screen:expect([[ 1443 0123456789./*-+ | 1444 =^ | 1445 {100:~ }|*2 1446 {3:[No Name] [+] }| 1447 {5:-- INSERT --} | 1448 {5:-- TERMINAL --} | 1449 ]]) 1450 feed_data(fn.nr2char(57425)) -- KP_INSERT 1451 screen:expect([[ 1452 0123456789./*-+ | 1453 =^ | 1454 {100:~ }|*2 1455 {3:[No Name] [+] }| 1456 {5:-- REPLACE --} | 1457 {5:-- TERMINAL --} | 1458 ]]) 1459 feed_data('\027[27u') -- ESC 1460 screen:expect([[ 1461 0123456789./*-+ | 1462 ^= | 1463 {100:~ }|*2 1464 {3:[No Name] [+] }| 1465 | 1466 {5:-- TERMINAL --} | 1467 ]]) 1468 feed_data('\027[57417;5u') -- CTRL + KP_LEFT 1469 screen:expect([[ 1470 ^0123456789./*-+ | 1471 = | 1472 {100:~ }|*2 1473 {3:[No Name] [+] }| 1474 | 1475 {5:-- TERMINAL --} | 1476 ]]) 1477 feed_data('\027[57418;2u') -- SHIFT + KP_RIGHT 1478 screen:expect([[ 1479 0123456789^./*-+ | 1480 = | 1481 {100:~ }|*2 1482 {3:[No Name] [+] }| 1483 | 1484 {5:-- TERMINAL --} | 1485 ]]) 1486 feed_data(fn.nr2char(57426)) -- KP_DELETE 1487 screen:expect([[ 1488 0123456789^/*-+ | 1489 = | 1490 {100:~ }|*2 1491 {3:[No Name] [+] }| 1492 | 1493 {5:-- TERMINAL --} | 1494 ]]) 1495 feed_data(fn.nr2char(57423)) -- KP_HOME 1496 screen:expect([[ 1497 ^0123456789/*-+ | 1498 = | 1499 {100:~ }|*2 1500 {3:[No Name] [+] }| 1501 | 1502 {5:-- TERMINAL --} | 1503 ]]) 1504 feed_data(fn.nr2char(57424)) -- KP_END 1505 screen:expect([[ 1506 0123456789/*-^+ | 1507 = | 1508 {100:~ }|*2 1509 {3:[No Name] [+] }| 1510 | 1511 {5:-- TERMINAL --} | 1512 ]]) 1513 child_session:request( 1514 'nvim_exec2', 1515 [[ 1516 tab split 1517 tabnew 1518 highlight Tabline ctermbg=NONE ctermfg=NONE cterm=underline 1519 ]], 1520 {} 1521 ) 1522 screen:expect([[ 1523 {107: + [No Name] + [No Name] }{5: [No Name] }{2: }{107:X}| 1524 ^ | 1525 {100:~ }|*2 1526 {3:[No Name] }| 1527 | 1528 {5:-- TERMINAL --} | 1529 ]]) 1530 feed_data('\027[57421;5u') -- CTRL + KP_PAGE_UP 1531 screen:expect([[ 1532 {107: + [No Name] }{5: + [No Name] }{107: [No Name] }{2: }{107:X}| 1533 0123456789/*-^+ | 1534 = | 1535 {100:~ }| 1536 {3:[No Name] [+] }| 1537 | 1538 {5:-- TERMINAL --} | 1539 ]]) 1540 feed_data('\027[57422;5u') -- CTRL + KP_PAGE_DOWN 1541 screen:expect([[ 1542 {107: + [No Name] + [No Name] }{5: [No Name] }{2: }{107:X}| 1543 ^ | 1544 {100:~ }|*2 1545 {3:[No Name] }| 1546 | 1547 {5:-- TERMINAL --} | 1548 ]]) 1549 end) 1550 1551 it('supports Super and Meta modifiers', function() 1552 feed_data('i') 1553 feed_data('\022\027[106;9u') -- Super + j 1554 feed_data('\022\027[107;33u') -- Meta + k 1555 feed_data('\022\027[13;41u') -- Super + Meta + Enter 1556 feed_data('\022\027[127;48u') -- Shift + Alt + Ctrl + Super + Meta + Backspace 1557 feed_data('\n') 1558 feed_data('\022\027[57376;9u') -- Super + F13 1559 feed_data('\022\027[57377;33u') -- Meta + F14 1560 feed_data('\022\027[57378;41u') -- Super + Meta + F15 1561 feed_data('\022\027[57379;48u') -- Shift + Alt + Ctrl + Super + Meta + F16 1562 screen:expect([[ 1563 <D-j><T-k><T-D-CR><M-T-C-S-D-BS> | 1564 <D-F13><T-F14><T-D-F15><M-T-C-S-D-F16>^ | 1565 {100:~ }|*2 1566 {3:[No Name] [+] }| 1567 {5:-- INSERT --} | 1568 {5:-- TERMINAL --} | 1569 ]]) 1570 end) 1571 1572 it('paste: Insert mode', function() 1573 -- "bracketed paste" 1574 feed_data('i""\027i\027[200~') 1575 screen:expect([[ 1576 "^" | 1577 {100:~ }|*3 1578 {3:[No Name] [+] }| 1579 {5:-- INSERT --} | 1580 {5:-- TERMINAL --} | 1581 ]]) 1582 feed_data('pasted from terminal') 1583 expect_child_buf_lines({ '"pasted from terminal"' }) 1584 screen:expect([[ 1585 "pasted from terminal^" | 1586 {100:~ }|*3 1587 {3:[No Name] [+] }| 1588 {5:-- INSERT --} | 1589 {5:-- TERMINAL --} | 1590 ]]) 1591 feed_data('\027[201~') -- End paste. 1592 poke_both_eventloop() 1593 screen:expect_unchanged() 1594 feed_data('\027[27u') -- ESC: go to Normal mode. 1595 wait_for_mode('n') 1596 screen:expect([[ 1597 "pasted from termina^l" | 1598 {100:~ }|*3 1599 {3:[No Name] [+] }| 1600 | 1601 {5:-- TERMINAL --} | 1602 ]]) 1603 -- Dot-repeat/redo. 1604 feed_data('2.') 1605 expect_child_buf_lines({ '"pasted from terminapasted from terminalpasted from terminall"' }) 1606 screen:expect([[ 1607 "pasted from terminapasted from terminalpasted fro| 1608 m termina^ll" | 1609 {100:~ }|*2 1610 {3:[No Name] [+] }| 1611 | 1612 {5:-- TERMINAL --} | 1613 ]]) 1614 -- Undo. 1615 feed_data('u') 1616 expect_child_buf_lines({ '"pasted from terminal"' }) 1617 feed_data('u') 1618 expect_child_buf_lines({ '""' }) 1619 feed_data('u') 1620 expect_child_buf_lines({ '' }) 1621 end) 1622 1623 it('paste: select-mode', function() 1624 feed_data('ithis is line 1\nthis is line 2\nline 3 is here\n\027') 1625 wait_for_mode('n') 1626 screen:expect([[ 1627 this is line 1 | 1628 this is line 2 | 1629 line 3 is here | 1630 ^ | 1631 {3:[No Name] [+] }| 1632 | 1633 {5:-- TERMINAL --} | 1634 ]]) 1635 -- Select-mode. Use <C-n> to move down. 1636 feed_data('gg04lgh\14\14') 1637 screen:expect([[ 1638 this{108: is line 1} | 1639 {108:this is line 2} | 1640 {108:line}^ 3 is here | 1641 | 1642 {3:[No Name] [+] }| 1643 {5:-- SELECT --} | 1644 {5:-- TERMINAL --} | 1645 ]]) 1646 feed_data('\027[200~') 1647 feed_data('just paste it™') 1648 feed_data('\027[201~') 1649 screen:expect([[ 1650 thisjust paste it^™3 is here | 1651 | 1652 {100:~ }|*2 1653 {3:[No Name] [+] }| 1654 | 1655 {5:-- TERMINAL --} | 1656 ]]) 1657 -- Undo. 1658 feed_data('u') 1659 expect_child_buf_lines { 1660 'this is line 1', 1661 'this is line 2', 1662 'line 3 is here', 1663 '', 1664 } 1665 -- Redo. 1666 feed_data('\18') -- <C-r> 1667 expect_child_buf_lines { 1668 'thisjust paste it™3 is here', 1669 '', 1670 } 1671 end) 1672 1673 it('paste: terminal mode', function() 1674 if is_ci('github') then 1675 pending('tty-test complains about not owning the terminal -- actions/runner#241') 1676 end 1677 child_exec_lua('vim.o.statusline="^^^^^^^"') 1678 child_exec_lua('vim.cmd.terminal(...)', testprg('tty-test')) 1679 feed_data('i') 1680 screen:expect([[ 1681 tty ready | 1682 ^ | 1683 |*2 1684 {109:^^^^^^^ }| 1685 {5:-- TERMINAL --} |*2 1686 ]]) 1687 feed_data('\027[200~') 1688 feed_data('hallo') 1689 feed_data('\027[201~') 1690 screen:expect([[ 1691 tty ready | 1692 hallo^ | 1693 |*2 1694 {109:^^^^^^^ }| 1695 {5:-- TERMINAL --} |*2 1696 ]]) 1697 end) 1698 1699 it('paste: normal-mode (+CRLF #10872)', function() 1700 t.skip(is_os('win'), 'FIXME: some spaces have wrong attrs on Windows') 1701 feed_data(':set ruler | echo') 1702 wait_for_mode('c') 1703 feed_data('\n') 1704 wait_for_mode('n') 1705 local expected_lf = { 'line 1', 'ESC:\027 / CR: \rx' } 1706 local expected_crlf = { 'line 1', 'ESC:\027 / CR: ', 'x' } 1707 local expected_grid1 = [[ 1708 line 1 | 1709 ESC:{104:^[} / CR: | 1710 ^x | 1711 {100:~ }| 1712 {3:[No Name] [+] 3,1 All}| 1713 | 1714 {5:-- TERMINAL --} | 1715 ]] 1716 -- "bracketed paste" 1717 feed_data('\027[200~' .. table.concat(expected_lf, '\n') .. '\027[201~') 1718 screen:expect(expected_grid1) 1719 -- Dot-repeat/redo. 1720 feed_data('.') 1721 local expected_grid2 = [[ 1722 ESC:{104:^[} / CR: | 1723 xline 1 | 1724 ESC:{104:^[} / CR: | 1725 ^x | 1726 {3:[No Name] [+] 5,1 Bot}| 1727 | 1728 {5:-- TERMINAL --} | 1729 ]] 1730 screen:expect(expected_grid2) 1731 -- Undo. 1732 feed_data('u') 1733 expect_child_buf_lines(expected_crlf) 1734 feed_data('u') 1735 expect_child_buf_lines({ '' }) 1736 feed_data(':echo') 1737 wait_for_mode('c') 1738 feed_data('\n') 1739 wait_for_mode('n') 1740 -- CRLF input 1741 feed_data('\027[200~' .. table.concat(expected_lf, '\r\n') .. '\027[201~') 1742 screen:expect(expected_grid1) 1743 expect_child_buf_lines(expected_crlf) 1744 -- Dot-repeat/redo. 1745 feed_data('.') 1746 screen:expect(expected_grid2) 1747 -- Undo. 1748 feed_data('u') 1749 expect_child_buf_lines(expected_crlf) 1750 feed_data('u') 1751 expect_child_buf_lines({ '' }) 1752 end) 1753 1754 it('paste: cmdline-mode inserts 1 line', function() 1755 feed_data('ifoo\n') -- Insert some text (for dot-repeat later). 1756 feed_data('\027:""') -- Enter Cmdline-mode. 1757 feed_data('\027[D') -- <Left> to place cursor between quotes. 1758 wait_for_mode('c') 1759 screen:expect([[ 1760 foo | 1761 | 1762 {100:~ }|*2 1763 {3:[No Name] [+] }| 1764 :"^" | 1765 {5:-- TERMINAL --} | 1766 ]]) 1767 -- "bracketed paste" 1768 feed_data('\027[200~line 1\nline 2\n') 1769 wait_for_mode('c') 1770 feed_data('line 3\nline 4\n\027[201~') 1771 poke_both_eventloop() 1772 wait_for_mode('c') 1773 screen:expect([[ 1774 foo | 1775 | 1776 {100:~ }|*2 1777 {3:[No Name] [+] }| 1778 :"line 1^" | 1779 {5:-- TERMINAL --} | 1780 ]]) 1781 -- Dot-repeat/redo. 1782 feed_data('\027[27u') 1783 wait_for_mode('n') 1784 feed_data('.') 1785 screen:expect([[ 1786 foo |*2 1787 ^ | 1788 {100:~ }| 1789 {3:[No Name] [+] }| 1790 | 1791 {5:-- TERMINAL --} | 1792 ]]) 1793 end) 1794 1795 it('paste: cmdline-mode collects chunks of unfinished line', function() 1796 local function expect_cmdline(expected) 1797 retry(nil, nil, function() 1798 local _, cmdline = child_session:request('nvim_call_function', 'getcmdline', {}) 1799 eq(expected, cmdline) 1800 local _, pos = child_session:request('nvim_call_function', 'getcmdpos', {}) 1801 eq(#expected, pos) -- Cursor is just before the last char. 1802 end) 1803 end 1804 feed_data('\027:""') -- Enter Cmdline-mode. 1805 feed_data('\027[D') -- <Left> to place cursor between quotes. 1806 expect_cmdline('""') 1807 feed_data('\027[200~stuff 1 ') 1808 expect_cmdline('"stuff 1 "') 1809 -- Discards everything after the first line. 1810 feed_data('more\nstuff 2\nstuff 3\n') 1811 expect_cmdline('"stuff 1 more"') 1812 feed_data('stuff 3') 1813 expect_cmdline('"stuff 1 more"') 1814 -- End the paste sequence. 1815 feed_data('\027[201~') 1816 poke_both_eventloop() 1817 expect_cmdline('"stuff 1 more"') 1818 feed_data(' typed') 1819 expect_cmdline('"stuff 1 more typed"') 1820 end) 1821 1822 it('paste: recovers from vim.paste() failure', function() 1823 child_exec_lua([[ 1824 _G.save_paste_fn = vim.paste 1825 -- Stack traces for this test are non-deterministic, so disable them 1826 _G.debug.traceback = function(msg) return msg end 1827 vim.paste = function(lines, phase) error("fake fail") end 1828 ]]) 1829 -- Prepare something for dot-repeat/redo. 1830 feed_data('ifoo\n\027[27u') 1831 wait_for_mode('n') 1832 screen:expect([[ 1833 foo | 1834 ^ | 1835 {100:~ }|*2 1836 {3:[No Name] [+] }| 1837 | 1838 {5:-- TERMINAL --} | 1839 ]]) 1840 -- Start pasting... 1841 feed_data('\027[200~line 1\nline 2\n') 1842 screen:expect([[ 1843 foo | 1844 ^ | 1845 {100:~ }|*2 1846 {3:[No Name] [+] }| 1847 {101:paste: Lua: [string "<nvim>"]:4: fake fail} | 1848 {5:-- TERMINAL --} | 1849 ]]) 1850 -- Remaining chunks are discarded after vim.paste() failure. 1851 feed_data('line 3\nline 4\n') 1852 feed_data('line 5\nline 6\n') 1853 feed_data('line 7\nline 8\n') 1854 -- Stop paste. 1855 feed_data('\027[201~') 1856 screen:expect_unchanged() 1857 feed_data('\n') -- <CR> to dismiss hit-enter prompt 1858 expect_child_buf_lines({ 'foo', '' }) 1859 -- Dot-repeat/redo is not modified by failed paste. 1860 feed_data('.') 1861 screen:expect([[ 1862 foo |*2 1863 ^ | 1864 {100:~ }| 1865 {3:[No Name] [+] }| 1866 | 1867 {5:-- TERMINAL --} | 1868 ]]) 1869 -- Editor should still work after failed/drained paste. 1870 feed_data('ityped input...\027[27u') 1871 screen:expect([[ 1872 foo |*2 1873 typed input..^. | 1874 {100:~ }| 1875 {3:[No Name] [+] }| 1876 | 1877 {5:-- TERMINAL --} | 1878 ]]) 1879 -- Paste works if vim.paste() succeeds. 1880 child_exec_lua([[vim.paste = _G.save_paste_fn]]) 1881 feed_data('\027[200~line A\nline B\n\027[201~') 1882 screen:expect([[ 1883 foo | 1884 typed input...line A | 1885 line B | 1886 ^ | 1887 {3:[No Name] [+] }| 1888 | 1889 {5:-- TERMINAL --} | 1890 ]]) 1891 end) 1892 1893 it('paste: vim.paste() cancel (retval=false) #10865', function() 1894 -- This test only exercises the "cancel" case. Use-case would be "dangling 1895 -- paste", but that is not implemented yet. #10865 1896 child_exec_lua([[ 1897 vim.paste = function(lines, phase) return false end 1898 ]]) 1899 feed_data('\027[200~line A\nline B\n\027[201~') 1900 expect_child_buf_lines({ '' }) 1901 feed_data('ifoo\n\027[27u') 1902 expect_child_buf_lines({ 'foo', '' }) 1903 end) 1904 1905 it('paste: vim.paste() cancel (retval=false) with streaming #30462', function() 1906 child_exec_lua([[ 1907 vim.paste = (function(overridden) 1908 return function(lines, phase) 1909 for i, line in ipairs(lines) do 1910 if line:find('!') then 1911 return false 1912 end 1913 end 1914 return overridden(lines, phase) 1915 end 1916 end)(vim.paste) 1917 ]]) 1918 feed_data('A') 1919 wait_for_mode('i') 1920 feed_data('\027[200~aaa') 1921 expect_child_buf_lines({ 'aaa' }) 1922 feed_data('bbb') 1923 expect_child_buf_lines({ 'aaabbb' }) 1924 feed_data('ccc!') -- This chunk is cancelled. 1925 expect_child_buf_lines({ 'aaabbb' }) 1926 feed_data('ddd\027[201~') -- This chunk is ignored. 1927 poke_both_eventloop() 1928 expect_child_buf_lines({ 'aaabbb' }) 1929 feed_data('\027[27u') 1930 wait_for_mode('n') 1931 feed_data('.') -- Dot-repeat only includes chunks actually pasted. 1932 expect_child_buf_lines({ 'aaabbbaaabbb' }) 1933 feed_data('$\027[200~eee\027[201~') -- A following paste works normally. 1934 expect_child_buf_lines({ 'aaabbbaaabbbeee' }) 1935 end) 1936 1937 it("paste: 'nomodifiable' buffer", function() 1938 t.skip(is_os('win'), 'FIXME: some spaces have wrong attrs on Windows') 1939 child_exec_lua([[ 1940 vim.bo.modifiable = false 1941 -- Truncate the error message to hide the line number 1942 _G.debug.traceback = function(msg) return msg:sub(-49) end 1943 ]]) 1944 feed_data('\027[200~fail 1\nfail 2\n\027[201~') 1945 screen:expect([[ 1946 | 1947 {100:~ }| 1948 {3: }| 1949 {101:paste: Lua: Vim:E21: Cannot make changes, 'modifia}| 1950 {101:ble' is off} | 1951 {102:Press ENTER or type command to continue}^ | 1952 {5:-- TERMINAL --} | 1953 ]]) 1954 feed_data('\n') -- <Enter> to dismiss hit-enter prompt 1955 child_exec_lua('vim.bo.modifiable = true') 1956 feed_data('\027[200~success 1\nsuccess 2\n\027[201~') 1957 screen:expect([[ 1958 success 1 | 1959 success 2 | 1960 ^ | 1961 {100:~ }| 1962 {3:[No Name] [+] }| 1963 | 1964 {5:-- TERMINAL --} | 1965 ]]) 1966 end) 1967 1968 it('paste: exactly 64 bytes #10311', function() 1969 local expected = string.rep('z', 64) 1970 feed_data('i') 1971 wait_for_mode('i') 1972 -- "bracketed paste" 1973 feed_data('\027[200~' .. expected .. '\027[201~') 1974 expect_child_buf_lines({ expected }) 1975 feed_data(' end') 1976 expected = expected .. ' end' 1977 screen:expect([[ 1978 zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz| 1979 zzzzzzzzzzzzzz end^ | 1980 {100:~ }|*2 1981 {3:[No Name] [+] }| 1982 {5:-- INSERT --} | 1983 {5:-- TERMINAL --} | 1984 ]]) 1985 expect_child_buf_lines({ expected }) 1986 end) 1987 1988 it('paste: less-than sign in cmdline #11088', function() 1989 local expected = '<' 1990 feed_data(':') 1991 wait_for_mode('c') 1992 -- "bracketed paste" 1993 feed_data('\027[200~' .. expected .. '\027[201~') 1994 screen:expect([[ 1995 | 1996 {100:~ }|*3 1997 {3:[No Name] }| 1998 :<^ | 1999 {5:-- TERMINAL --} | 2000 ]]) 2001 end) 2002 2003 it('paste: big burst of input', function() 2004 feed_data(':set ruler\n') 2005 local q = {} 2006 for i = 1, 3000 do 2007 q[i] = 'item ' .. tostring(i) 2008 end 2009 feed_data('i') 2010 wait_for_mode('i') 2011 -- "bracketed paste" 2012 feed_data('\027[200~' .. table.concat(q, '\n') .. '\027[201~') 2013 expect_child_buf_lines(q) 2014 feed_data(' end') 2015 screen:expect([[ 2016 item 2997 | 2017 item 2998 | 2018 item 2999 | 2019 item 3000 end^ | 2020 {3:[No Name] [+] 3000,14 Bot}| 2021 {5:-- INSERT --} | 2022 {5:-- TERMINAL --} | 2023 ]]) 2024 feed_data('\027[27u') -- ESC: go to Normal mode. 2025 wait_for_mode('n') 2026 -- Dot-repeat/redo. 2027 feed_data('.') 2028 screen:expect([[ 2029 item 2997 | 2030 item 2998 | 2031 item 2999 | 2032 item 3000 en^dd | 2033 {3:[No Name] [+] 5999,13 Bot}| 2034 | 2035 {5:-- TERMINAL --} | 2036 ]]) 2037 end) 2038 2039 it('paste: forwards spurious "start paste" code', function() 2040 -- If multiple "start paste" sequences are sent without a corresponding 2041 -- "stop paste" sequence, only the first occurrence should be consumed. 2042 feed_data('i') 2043 wait_for_mode('i') 2044 -- Send the "start paste" sequence. 2045 feed_data('\027[200~') 2046 feed_data('\npasted from terminal (1)\n') 2047 -- Send spurious "start paste" sequence. 2048 feed_data('\027[200~') 2049 feed_data('\n') 2050 -- Send the "stop paste" sequence. 2051 feed_data('\027[201~') 2052 screen:expect([[ 2053 | 2054 pasted from terminal (1) | 2055 {104:^[}[200~ | 2056 ^ | 2057 {3:[No Name] [+] }| 2058 {5:-- INSERT --} | 2059 {5:-- TERMINAL --} | 2060 ]]) 2061 end) 2062 2063 it('paste: ignores spurious "stop paste" code', function() 2064 -- If "stop paste" sequence is received without a preceding "start paste" 2065 -- sequence, it should be ignored. 2066 feed_data('i') 2067 wait_for_mode('i') 2068 -- Send "stop paste" sequence. 2069 feed_data('\027[201~') 2070 screen:expect([[ 2071 ^ | 2072 {100:~ }|*3 2073 {3:[No Name] }| 2074 {5:-- INSERT --} | 2075 {5:-- TERMINAL --} | 2076 ]]) 2077 end) 2078 2079 it('paste: split "start paste" code', function() 2080 t.skip(is_os('win'), 'FIXME: wrong behavior on Windows') 2081 feed_data('i') 2082 wait_for_mode('i') 2083 -- Send split "start paste" sequence. 2084 feed_data('\027[2') 2085 feed_data('00~pasted from terminal\027[201~') 2086 screen:expect([[ 2087 pasted from terminal^ | 2088 {100:~ }|*3 2089 {3:[No Name] [+] }| 2090 {5:-- INSERT --} | 2091 {5:-- TERMINAL --} | 2092 ]]) 2093 end) 2094 2095 it('paste: split "stop paste" code', function() 2096 t.skip(is_os('win'), 'FIXME: wrong behavior on Windows') 2097 feed_data('i') 2098 wait_for_mode('i') 2099 -- Send split "stop paste" sequence. 2100 feed_data('\027[200~pasted from terminal\027[20') 2101 feed_data('1~') 2102 screen:expect([[ 2103 pasted from terminal^ | 2104 {100:~ }|*3 2105 {3:[No Name] [+] }| 2106 {5:-- INSERT --} | 2107 {5:-- TERMINAL --} | 2108 ]]) 2109 end) 2110 2111 it('paste: streamed paste with isolated "stop paste" code', function() 2112 child_exec_lua([[ 2113 _G.paste_phases = {} 2114 vim.paste = (function(overridden) 2115 return function(lines, phase) 2116 table.insert(_G.paste_phases, phase) 2117 overridden(lines, phase) 2118 end 2119 end)(vim.paste) 2120 ]]) 2121 feed_data('i') 2122 wait_for_mode('i') 2123 feed_data('\027[200~pasted') -- phase 1 2124 screen:expect([[ 2125 pasted^ | 2126 {100:~ }|*3 2127 {3:[No Name] [+] }| 2128 {5:-- INSERT --} | 2129 {5:-- TERMINAL --} | 2130 ]]) 2131 feed_data(' from terminal') -- phase 2 2132 screen:expect([[ 2133 pasted from terminal^ | 2134 {100:~ }|*3 2135 {3:[No Name] [+] }| 2136 {5:-- INSERT --} | 2137 {5:-- TERMINAL --} | 2138 ]]) 2139 -- Send isolated "stop paste" sequence. 2140 feed_data('\027[201~') -- phase 3 2141 poke_both_eventloop() 2142 screen:expect_unchanged() 2143 local rv = child_exec_lua('return _G.paste_phases') 2144 -- In rare cases there may be multiple chunks of phase 2 because of timing. 2145 eq({ 1, 2, 3 }, { rv[1], rv[2], rv[#rv] }) 2146 end) 2147 2148 it('allows termguicolors to be set at runtime', function() 2149 t.skip(is_os('win'), 'FIXME: some spaces have wrong attrs on Windows') 2150 screen:set_option('rgb', true) 2151 feed_data(':hi SpecialKey ctermfg=3 guifg=SeaGreen\n') 2152 feed_data('i') 2153 feed_data('\022\007') -- ctrl+g 2154 feed_data('\028\014') -- crtl+\ ctrl+N 2155 feed_data(':set termguicolors?\n') 2156 screen:expect([[ 2157 {110:^^G} | 2158 {111:~ }|*3 2159 {3:[No Name] [+] }| 2160 notermguicolors | 2161 {5:-- TERMINAL --} | 2162 ]]) 2163 2164 feed_data(':set termguicolors\n') 2165 screen:expect([[ 2166 {113:^^G} | 2167 {1:~}{18: }|*3 2168 {3:[No Name] [+] }| 2169 :set termguicolors | 2170 {5:-- TERMINAL --} | 2171 ]]) 2172 2173 feed_data(':set notermguicolors\n') 2174 screen:expect([[ 2175 {110:^^G} | 2176 {111:~ }|*3 2177 {3:[No Name] [+] }| 2178 :set notermguicolors | 2179 {5:-- TERMINAL --} | 2180 ]]) 2181 end) 2182 2183 it('forwards :term palette colors with termguicolors', function() 2184 if is_ci('github') then 2185 pending('tty-test complains about not owning the terminal -- actions/runner#241') 2186 end 2187 screen:set_rgb_cterm(true) 2188 screen:set_default_attr_ids({ 2189 [1] = { { reverse = true }, { reverse = true } }, 2190 [2] = { 2191 { bold = true, background = Screen.colors.LightGreen, foreground = Screen.colors.Black }, 2192 { bold = true }, 2193 }, 2194 [3] = { { bold = true }, { bold = true } }, 2195 [4] = { { fg_indexed = true, foreground = tonumber('0xe0e000') }, { foreground = 3 } }, 2196 [5] = { { foreground = tonumber('0xff8000') }, {} }, 2197 [6] = { 2198 { 2199 fg_indexed = true, 2200 bg_indexed = true, 2201 bold = true, 2202 background = tonumber('0x66ff99'), 2203 foreground = Screen.colors.Black, 2204 }, 2205 { bold = true, background = 121, foreground = 0 }, 2206 }, 2207 [7] = { 2208 { 2209 fg_indexed = true, 2210 bg_indexed = true, 2211 background = tonumber('0x66ff99'), 2212 foreground = Screen.colors.Black, 2213 }, 2214 { background = 121, foreground = 0 }, 2215 }, 2216 }) 2217 2218 child_exec_lua('vim.o.statusline="^^^^^^^"') 2219 child_exec_lua('vim.o.termguicolors=true') 2220 child_exec_lua('vim.cmd.terminal(...)', testprg('tty-test')) 2221 screen:expect([[ 2222 ^tty ready | 2223 |*3 2224 {2:^^^^^^^ }| 2225 | 2226 {3:-- TERMINAL --} | 2227 ]]) 2228 feed_data( 2229 ':call chansend(&channel, "\\033[38;5;3mtext\\033[38:2:255:128:0mcolor\\033[0;10mtext")\n' 2230 ) 2231 screen:expect([[ 2232 ^tty ready | 2233 {4:text}{5:color}text | 2234 |*2 2235 {2:^^^^^^^ }| 2236 | 2237 {3:-- TERMINAL --} | 2238 ]]) 2239 2240 feed_data(':set notermguicolors\n') 2241 screen:expect([[ 2242 ^tty ready | 2243 {4:text}colortext | 2244 |*2 2245 {6:^^^^^^^}{7: }| 2246 :set notermguicolors | 2247 {3:-- TERMINAL --} | 2248 ]]) 2249 end) 2250 2251 -- Note: libvterm doesn't support colored underline or undercurl. 2252 it('supports undercurl and underdouble when run in :terminal', function() 2253 child_session:request('nvim_set_hl', 0, 'Visual', { undercurl = true }) 2254 feed_data('ifoobar\027V') 2255 screen:expect([[ 2256 {114:fooba}^r | 2257 {100:~ }|*3 2258 {3:[No Name] [+] }| 2259 {5:-- VISUAL LINE --} | 2260 {5:-- TERMINAL --} | 2261 ]]) 2262 child_session:request('nvim_set_hl', 0, 'Visual', { underdouble = true }) 2263 screen:expect([[ 2264 {115:fooba}^r | 2265 {100:~ }|*3 2266 {3:[No Name] [+] }| 2267 {5:-- VISUAL LINE --} | 2268 {5:-- TERMINAL --} | 2269 ]]) 2270 end) 2271 2272 it('in nvim_list_uis(), sets nvim_set_client_info()', function() 2273 -- $TERM in :terminal. 2274 local exp_term = (is_os('bsd') or is_os('win')) and 'xterm' or 'xterm-256color' 2275 local ui_chan = 1 2276 local expected = { 2277 { 2278 chan = ui_chan, 2279 ext_cmdline = false, 2280 ext_hlstate = false, 2281 ext_linegrid = true, 2282 ext_messages = false, 2283 ext_multigrid = false, 2284 ext_popupmenu = false, 2285 ext_tabline = false, 2286 ext_termcolors = true, 2287 ext_wildmenu = false, 2288 height = 6, 2289 override = false, 2290 rgb = false, 2291 stdin_tty = true, 2292 stdout_tty = true, 2293 term_background = '', 2294 term_colors = 256, 2295 term_name = exp_term, 2296 width = 50, 2297 }, 2298 } 2299 local _, rv = child_session:request('nvim_list_uis') 2300 eq(expected, rv) 2301 2302 ---@type table 2303 local expected_version = child_exec_lua('return vim.version()') 2304 -- vim.version() returns `prerelease` string. Coerce it to boolean. 2305 expected_version.prerelease = not not expected_version.prerelease 2306 2307 local expected_chan_info = { 2308 client = { 2309 attributes = { 2310 license = 'Apache 2', 2311 -- pid = 5371, 2312 website = 'https://neovim.io', 2313 }, 2314 methods = {}, 2315 name = 'nvim-tui', 2316 type = 'ui', 2317 version = expected_version, 2318 }, 2319 id = ui_chan, 2320 mode = 'rpc', 2321 stream = 'stdio', 2322 } 2323 2324 local status, chan_info = child_session:request('nvim_get_chan_info', ui_chan) 2325 ok(status) 2326 local info = chan_info.client 2327 ok(info.attributes.pid and info.attributes.pid > 0, 'PID', info.attributes.pid or 'nil') 2328 ok(info.version.major >= 0) 2329 ok(info.version.minor >= 0) 2330 ok(info.version.patch >= 0) 2331 2332 -- Delete variable fields so we can deep-compare. 2333 info.attributes.pid = nil 2334 2335 eq(expected_chan_info, chan_info) 2336 end) 2337 2338 it('allows grid to assume wider ambiwidth chars than host terminal', function() 2339 t.skip(is_os('win'), 'FIXME: some spaces have wrong attrs on Windows') 2340 child_session:request( 2341 'nvim_buf_set_lines', 2342 0, 2343 0, 2344 -1, 2345 true, 2346 { ('℃'):rep(60), ('℃'):rep(60) } 2347 ) 2348 child_session:request('nvim_set_option_value', 'cursorline', true, {}) 2349 child_session:request('nvim_set_option_value', 'list', true, {}) 2350 child_session:request('nvim_set_option_value', 'listchars', 'eol:$', { win = 0 }) 2351 feed_data('gg') 2352 local singlewidth_screen = [[ 2353 {107:^℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃}| 2354 {107:℃℃℃℃℃℃℃℃℃℃}{116:$}{107: }| 2355 ℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃| 2356 ℃℃℃℃℃℃℃℃℃℃{100:$} | 2357 {3:[No Name] [+] }| 2358 | 2359 {5:-- TERMINAL --} | 2360 ]] 2361 -- When grid assumes "℃" to be double-width but host terminal assumes it to be single-width, 2362 -- the second cell of "℃" is a space and the attributes of the "℃" are applied to it. 2363 local doublewidth_screen = [[ 2364 {107:^℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| 2365 {107:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| 2366 {107:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }{116:$}{107: }| 2367 ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ {100:@@@@}| 2368 {3:[No Name] [+] }| 2369 | 2370 {5:-- TERMINAL --} | 2371 ]] 2372 screen:expect(singlewidth_screen) 2373 child_session:request('nvim_set_option_value', 'ambiwidth', 'double', {}) 2374 screen:expect(doublewidth_screen) 2375 child_session:request('nvim_set_option_value', 'ambiwidth', 'single', {}) 2376 screen:expect(singlewidth_screen) 2377 child_session:request('nvim_call_function', 'setcellwidths', { { { 0x2103, 0x2103, 2 } } }) 2378 screen:expect(doublewidth_screen) 2379 child_session:request('nvim_call_function', 'setcellwidths', { { { 0x2103, 0x2103, 1 } } }) 2380 screen:expect(singlewidth_screen) 2381 end) 2382 2383 it('allows grid to assume wider non-ambiwidth chars than host terminal', function() 2384 t.skip(is_os('win'), 'FIXME: some spaces have wrong attrs on Windows') 2385 child_session:request( 2386 'nvim_buf_set_lines', 2387 0, 2388 0, 2389 -1, 2390 true, 2391 { ('✓'):rep(60), ('✓'):rep(60) } 2392 ) 2393 child_session:request('nvim_set_option_value', 'cursorline', true, {}) 2394 child_session:request('nvim_set_option_value', 'list', true, {}) 2395 child_session:request('nvim_set_option_value', 'listchars', 'eol:$', { win = 0 }) 2396 feed_data('gg') 2397 local singlewidth_screen = [[ 2398 {107:^✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓}| 2399 {107:✓✓✓✓✓✓✓✓✓✓}{116:$}{107: }| 2400 ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓| 2401 ✓✓✓✓✓✓✓✓✓✓{100:$} | 2402 {3:[No Name] [+] }| 2403 | 2404 {5:-- TERMINAL --} | 2405 ]] 2406 -- When grid assumes "✓" to be double-width but host terminal assumes it to be single-width, 2407 -- the second cell of "✓" is a space and the attributes of the "✓" are applied to it. 2408 local doublewidth_screen = [[ 2409 {107:^✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }| 2410 {107:✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }| 2411 {107:✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }{116:$}{107: }| 2412 ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ {100:@@@@}| 2413 {3:[No Name] [+] }| 2414 | 2415 {5:-- TERMINAL --} | 2416 ]] 2417 screen:expect(singlewidth_screen) 2418 child_session:request('nvim_set_option_value', 'ambiwidth', 'double', {}) 2419 screen:expect_unchanged() 2420 child_session:request('nvim_call_function', 'setcellwidths', { { { 0x2713, 0x2713, 2 } } }) 2421 screen:expect(doublewidth_screen) 2422 child_session:request('nvim_set_option_value', 'ambiwidth', 'single', {}) 2423 screen:expect_unchanged() 2424 child_session:request('nvim_call_function', 'setcellwidths', { { { 0x2713, 0x2713, 1 } } }) 2425 screen:expect(singlewidth_screen) 2426 end) 2427 2428 it('draws correctly when cursor_address overflows #21643', function() 2429 screen:try_resize(70, 333) 2430 retry(nil, nil, function() 2431 eq({ true, 330 }, { child_session:request('nvim_win_get_height', 0) }) 2432 end) 2433 child_session:request('nvim_set_option_value', 'cursorline', true, {}) 2434 -- Use full screen message so that redrawing afterwards is more deterministic. 2435 child_session:notify('nvim_command', 'intro') 2436 screen:expect({ any = 'Nvim is open source and freely distributable' }) 2437 -- Going to top-left corner needs 3 bytes. 2438 -- Setting underline attribute needs 9 bytes. 2439 -- A Ꝩ character takes 3 bytes. 2440 -- The whole line needs 3 + 9 + 3 * 21838 + 3 = 65529 bytes. 2441 -- The cursor_address that comes after will overflow the 65535-byte buffer. 2442 local line = ('Ꝩ'):rep(21838) .. '℃' 2443 child_session:notify('nvim_buf_set_lines', 0, 0, -1, true, { line, 'b' }) 2444 -- Close the :intro message and redraw the lines. 2445 feed_data('\n') 2446 screen:expect([[ 2447 {107:^ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ}| 2448 {107:ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ}|*310 2449 {107:ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ℃ }| 2450 b | 2451 {100:~ }|*17 2452 {3:[No Name] [+] }| 2453 | 2454 {5:-- TERMINAL --} | 2455 ]]) 2456 end) 2457 2458 it('draws correctly when setting title overflows #30793', function() 2459 screen:try_resize(67, 327) 2460 retry(nil, nil, function() 2461 eq({ true, 324 }, { child_session:request('nvim_win_get_height', 0) }) 2462 end) 2463 child_exec_lua([[ 2464 vim.o.cmdheight = 0 2465 vim.o.laststatus = 0 2466 vim.o.ruler = false 2467 vim.o.showcmd = false 2468 vim.o.termsync = false 2469 vim.o.title = true 2470 ]]) 2471 retry(nil, nil, function() 2472 eq('[No Name] - Nvim', api.nvim_buf_get_var(0, 'term_title')) 2473 eq({ true, 326 }, { child_session:request('nvim_win_get_height', 0) }) 2474 end) 2475 -- Use full screen message so that redrawing afterwards is more deterministic. 2476 child_session:notify('nvim_command', 'intro') 2477 screen:expect({ any = 'Nvim is open source and freely distributable' }) 2478 -- Going to top-left corner needs 3 bytes. 2479 -- A Ꝩ character takes 3 bytes. 2480 -- The whole line needs 3 + 3 * 21842 = 65529 bytes. 2481 -- The title will be updated because the buffer is now modified. 2482 -- The start of the OSC 0 sequence to set title can fit in the 65535-byte buffer, 2483 -- but the title string cannot. 2484 local line = ('Ꝩ'):rep(21842) 2485 child_session:notify('nvim_buf_set_lines', 0, 0, -1, true, { line }) 2486 -- Close the :intro message and redraw the lines. 2487 feed_data('\n') 2488 screen:expect([[ 2489 ^ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ| 2490 ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ|*325 2491 {5:-- TERMINAL --} | 2492 ]]) 2493 retry(nil, nil, function() 2494 eq('[No Name] + - Nvim', api.nvim_buf_get_var(0, 'term_title')) 2495 end) 2496 end) 2497 2498 it('visual bell (padding) does not crash #21610', function() 2499 feed_data ':set visualbell\n' 2500 screen:expect([[ 2501 ^ | 2502 {100:~ }|*3 2503 {3:[No Name] }| 2504 :set visualbell | 2505 {5:-- TERMINAL --} | 2506 ]]) 2507 2508 -- move left is enough to invoke the bell 2509 feed_data 'h' 2510 -- visual change to show we process events after this 2511 feed_data 'i' 2512 screen:expect([[ 2513 ^ | 2514 {100:~ }|*3 2515 {3:[No Name] }| 2516 {5:-- INSERT --} | 2517 {5:-- TERMINAL --} | 2518 ]]) 2519 end) 2520 2521 it('no assert failure on deadly signal #21896', function() 2522 exec_lua([[vim.uv.kill(vim.fn.jobpid(vim.bo.channel), 'sigterm')]]) 2523 screen:expect(is_os('win') and { any = '%[Process exited 1%]' } or [[ 2524 Nvim: Caught deadly signal 'SIGTERM' | 2525 | 2526 [Process exited 1]^ | 2527 |*3 2528 {5:-- TERMINAL --} | 2529 ]]) 2530 end) 2531 2532 it('exit status 1 and error message with deadly signal sent to server', function() 2533 local _, server_pid = child_session:request('nvim_call_function', 'getpid', {}) 2534 exec_lua([[vim.uv.kill(..., 'sigterm')]], server_pid) 2535 if not is_os('win') then 2536 screen:expect({ any = vim.pesc([[Nvim: Caught deadly signal 'SIGTERM']]) }) 2537 end 2538 screen:expect({ any = vim.pesc('[Process exited 1]') }) 2539 end) 2540 2541 it('exits immediately when stdin is closed #35744', function() 2542 local chan = api.nvim_get_option_value('channel', { buf = 0 }) 2543 local pid = fn.jobpid(chan) 2544 fn.chanclose(chan) 2545 retry(nil, 50, function() 2546 eq(vim.NIL, api.nvim_get_proc(pid)) 2547 end) 2548 screen:expect({ any = vim.pesc('[Process exited 1]') }) 2549 end) 2550 2551 it('exits properly when :quit non-last window in event handler #14379', function() 2552 local code = [[ 2553 vim.defer_fn(function() 2554 vim.cmd('vsplit | quit') 2555 end, 0) 2556 vim.cmd('quit') 2557 ]] 2558 child_session:notify('nvim_exec_lua', code, {}) 2559 screen:expect([[ 2560 | 2561 [Process exited 0]^ | 2562 |*4 2563 {5:-- TERMINAL --} | 2564 ]]) 2565 end) 2566 2567 it('no stack-use-after-scope with cursor color #22432', function() 2568 screen:set_option('rgb', true) 2569 command('set termguicolors') 2570 child_session:request( 2571 'nvim_exec2', 2572 [[ 2573 set tgc 2574 hi Cursor guifg=Red guibg=Green 2575 set guicursor=n:block-Cursor/lCursor 2576 ]], 2577 {} 2578 ) 2579 screen:expect([[ 2580 ^ | 2581 {1:~}{18: }|*3 2582 {3:[No Name] }| 2583 | 2584 {5:-- TERMINAL --} | 2585 ]]) 2586 feed_data('i') 2587 screen:expect([[ 2588 ^ | 2589 {1:~}{18: }|*3 2590 {3:[No Name] }| 2591 {5:-- INSERT --} | 2592 {5:-- TERMINAL --} | 2593 ]]) 2594 end) 2595 2596 it('redraws on SIGWINCH even if terminal size is unchanged #23411', function() 2597 -- On Windows, SIGWINCH cannot be sent as a signal with uv_kill(), while 2598 -- SIGWINCH handlers are only called on terminal resize. 2599 t.skip(is_os('win'), 'N/A for Windows') 2600 child_session:request('nvim_echo', { { 'foo' } }, false, {}) 2601 screen:expect([[ 2602 ^ | 2603 {100:~ }|*3 2604 {3:[No Name] }| 2605 foo | 2606 {5:-- TERMINAL --} | 2607 ]]) 2608 exec_lua([[vim.uv.kill(vim.fn.jobpid(vim.bo.channel), 'sigwinch')]]) 2609 screen:expect([[ 2610 ^ | 2611 {100:~ }|*3 2612 {3:[No Name] }| 2613 | 2614 {5:-- TERMINAL --} | 2615 ]]) 2616 end) 2617 2618 it('supports hiding cursor', function() 2619 child_session:request( 2620 'nvim_command', 2621 "let g:id = jobstart([v:progpath, '--clean', '--headless'])" 2622 ) 2623 feed_data(':call jobwait([g:id])\n') 2624 screen:expect([[ 2625 | 2626 {100:~ }|*3 2627 {3:[No Name] }| 2628 :call jobwait([g:id]) | 2629 {5:-- TERMINAL --} | 2630 ]]) 2631 feed_data('\003') 2632 screen:expect([[ 2633 ^ | 2634 {100:~ }|*3 2635 {3:[No Name] }| 2636 Type :qa and press <Enter> to exit Nvim | 2637 {5:-- TERMINAL --} | 2638 ]]) 2639 end) 2640 2641 it('cursor is not hidden on incsearch with no match', function() 2642 feed_data('ifoo\027') 2643 feed_data('/foo') 2644 screen:expect([[ 2645 {2:foo} | 2646 {100:~ }|*3 2647 {3:[No Name] [+] }| 2648 /foo^ | 2649 {5:-- TERMINAL --} | 2650 ]]) 2651 screen:sleep(10) 2652 feed_data('b') 2653 screen:expect([[ 2654 foo | 2655 {100:~ }|*3 2656 {3:[No Name] [+] }| 2657 /foob^ | 2658 {5:-- TERMINAL --} | 2659 ]]) 2660 screen:sleep(10) 2661 feed_data('a') 2662 screen:expect([[ 2663 foo | 2664 {100:~ }|*3 2665 {3:[No Name] [+] }| 2666 /fooba^ | 2667 {5:-- TERMINAL --} | 2668 ]]) 2669 end) 2670 2671 it('emits hyperlinks with OSC 8', function() 2672 t.skip(is_os('win'), 'FIXME: does not work on Windows') 2673 exec_lua([[ 2674 local buf = vim.api.nvim_get_current_buf() 2675 _G.urls = {} 2676 vim.api.nvim_create_autocmd('TermRequest', { 2677 buffer = buf, 2678 callback = function(args) 2679 local req = args.data.sequence 2680 if not req then 2681 return 2682 end 2683 local id, url = req:match('\027]8;id=(%d+);(.*)$') 2684 if id ~= nil and url ~= nil then 2685 table.insert(_G.urls, { id = tonumber(id), url = url }) 2686 end 2687 end, 2688 }) 2689 ]]) 2690 child_exec_lua([[ 2691 vim.api.nvim_buf_set_lines(0, 0, 0, true, {'Hello'}) 2692 _G.NS = vim.api.nvim_create_namespace('test') 2693 vim.api.nvim_buf_set_extmark(0, _G.NS, 0, 1, { 2694 end_col = 3, 2695 url = 'https://example.com', 2696 }) 2697 ]]) 2698 retry(nil, 1000, function() 2699 eq({ { id = 0xE1EA0000, url = 'https://example.com' } }, exec_lua([[return _G.urls]])) 2700 end) 2701 -- No crash with very long URL #30794 2702 child_exec_lua([[ 2703 vim.api.nvim_buf_set_extmark(0, _G.NS, 0, 3, { 2704 end_col = 5, 2705 url = 'https://example.com/' .. ('a'):rep(65536), 2706 }) 2707 ]]) 2708 retry(nil, nil, function() 2709 eq({ 2710 { id = 0xE1EA0000, url = 'https://example.com' }, 2711 { id = 0xE1EA0001, url = 'https://example.com/' .. ('a'):rep(65536) }, 2712 }, exec_lua([[return _G.urls]])) 2713 end) 2714 end) 2715 2716 it('TermResponse works with vim.wait() from another autocommand #32706', function() 2717 child_exec_lua([[ 2718 _G.termresponse = nil 2719 vim.api.nvim_create_autocmd('TermResponse', { 2720 callback = function(ev) 2721 _G.sequence = ev.data.sequence 2722 _G.v_termresponse = vim.v.termresponse 2723 end, 2724 }) 2725 vim.api.nvim_create_autocmd('InsertEnter', { 2726 buffer = 0, 2727 callback = function() 2728 _G.result = vim.wait(3000, function() 2729 local expected = '\027P1+r5463' 2730 return _G.sequence == expected and _G.v_termresponse == expected 2731 end) 2732 end, 2733 }) 2734 ]]) 2735 feed_data('i') 2736 feed_data('\027P1+r5463\027\\') 2737 retry(nil, 4000, function() 2738 eq(true, child_exec_lua('return _G.result')) 2739 end) 2740 end) 2741 2742 it('TermResponse from unblock_autocmds() sets "data"', function() 2743 if not child_exec_lua('return pcall(require, "ffi")') then 2744 pending('N/A: missing LuaJIT FFI') 2745 end 2746 child_exec_lua([[ 2747 local ffi = require('ffi') 2748 ffi.cdef[=[ 2749 void block_autocmds(void); 2750 void unblock_autocmds(void); 2751 ]=] 2752 ffi.C.block_autocmds() 2753 vim.api.nvim_create_autocmd('TermResponse', { 2754 once = true, 2755 callback = function(ev) 2756 _G.data = ev.data 2757 end, 2758 }) 2759 ]]) 2760 feed_data('\027P0$r\027\\') 2761 retry(nil, 4000, function() 2762 eq('\027P0$r', child_exec_lua('return vim.v.termresponse')) 2763 end) 2764 eq(vim.NIL, child_exec_lua('return _G.data')) 2765 child_exec_lua('require("ffi").C.unblock_autocmds()') 2766 eq({ sequence = '\027P0$r' }, child_exec_lua('return _G.data')) 2767 2768 -- If TermResponse during TermResponse changes v:termresponse, data.sequence contains the actual 2769 -- response that triggered the autocommand. 2770 -- The second autocommand below forces a use-after-free when v:termresponse's value changes 2771 -- during TermResponse if data.sequence didn't allocate its own copy. 2772 child_exec_lua([[ 2773 require('ffi').C.block_autocmds() 2774 vim.api.nvim_create_autocmd('TermResponse', { 2775 once = true, 2776 callback = function(ev) 2777 _G.au1_termresponse1 = vim.v.termresponse 2778 _G.au1_sequence1 = ev.data.sequence 2779 local chan = vim.fn.sockconnect('pipe', vim.v.servername, { rpc = true }) 2780 vim.rpcrequest(chan, 'nvim_ui_term_event', 'termresponse', 'baz') 2781 _G.au1_termresponse2 = vim.v.termresponse 2782 _G.au1_sequence2 = ev.data.sequence 2783 end, 2784 }) 2785 _G.au2_sequences = {} 2786 vim.api.nvim_create_autocmd('TermResponse', { 2787 callback = function(ev) 2788 table.insert(_G.au2_sequences, ev.data.sequence) 2789 end, 2790 }) 2791 ]]) 2792 child_session:request('nvim_ui_term_event', 'termresponse', 'foobar') 2793 eq('foobar', child_exec_lua('return vim.v.termresponse')) 2794 -- For good measure, check deferred TermResponse doesn't try to fire if autocmds are still 2795 -- blocked after unblock_autocmds. 2796 child_exec_lua('require("ffi").C.block_autocmds() require("ffi").C.unblock_autocmds()') 2797 eq(vim.NIL, child_exec_lua('return _G.au1_termresponse1')) 2798 child_exec_lua('require("ffi").C.unblock_autocmds()') 2799 eq('foobar', child_exec_lua('return _G.au1_termresponse1')) 2800 eq('foobar', child_exec_lua('return _G.au1_sequence1')) 2801 eq('baz', child_exec_lua('return _G.au1_termresponse2')) 2802 eq('foobar', child_exec_lua('return _G.au1_sequence2')) -- unchanged 2803 -- Second autocmd triggers due to "baz" (via the nested TermResponse), then from "foobar". 2804 eq({ 'baz', 'foobar' }, child_exec_lua('return _G.au2_sequences')) 2805 end) 2806 2807 it('nvim_ui_send works', function() 2808 child_session:request('nvim_ui_send', '\027]2;TEST_TITLE\027\\') 2809 retry(nil, nil, function() 2810 eq('TEST_TITLE', api.nvim_buf_get_var(0, 'term_title')) 2811 end) 2812 end) 2813 end) 2814 2815 describe('TUI', function() 2816 before_each(clear) 2817 2818 it('resize at startup #17285 #15044 #11330', function() 2819 local screen = Screen.new(50, 10) 2820 screen:add_extra_attr_ids({ 2821 [100] = { foreground = tonumber('0x4040ff'), fg_indexed = true }, 2822 [101] = { foreground = Screen.colors.Gray100, background = Screen.colors.DarkGreen }, 2823 }) 2824 fn.jobstart({ 2825 nvim_prog, 2826 '--clean', 2827 '--cmd', 2828 'colorscheme vim', 2829 '--cmd', 2830 'set notermguicolors', 2831 '--cmd', 2832 nvim_set, 2833 '--cmd', 2834 'let start = reltime() | while v:true | if reltimefloat(reltime(start)) > 2 | break | endif | endwhile', 2835 }, { 2836 term = true, 2837 env = { 2838 VIMRUNTIME = os.getenv('VIMRUNTIME'), 2839 }, 2840 }) 2841 exec([[ 2842 sleep 500m 2843 vs new 2844 ]]) 2845 screen:expect(([[ 2846 ^ │ | 2847 {1:~ }│{100:~ }|*6 2848 {1:~ }│ | 2849 {3:new }{101:{MATCH:<.*%s} [-] }| 2850 | 2851 ]]):format(is_os('win') and '[/\\]nvim%.exe' or '/nvim')) 2852 end) 2853 2854 -- #28667, #28668 2855 for _, guicolors in ipairs({ 'notermguicolors', 'termguicolors' }) do 2856 it('has no black flicker when clearing regions during startup with ' .. guicolors, function() 2857 local screen = Screen.new(50, 10) 2858 -- Colorscheme is automatically detected as light in _core/defaults.lua, so fg 2859 -- should be dark except on Windows, where it doesn't respond to the OSC11 query, 2860 -- so bg is dark. 2861 local fg = is_os('win') and Screen.colors.NvimLightGrey2 or Screen.colors.NvimDarkGrey2 2862 local bg = is_os('win') and Screen.colors.NvimDarkGrey2 or Screen.colors.NvimLightGrey2 2863 screen:add_extra_attr_ids({ 2864 [100] = { 2865 foreground = fg, 2866 background = bg, 2867 }, 2868 }) 2869 fn.jobstart({ 2870 nvim_prog, 2871 '--clean', 2872 '--cmd', 2873 'set ' .. guicolors, 2874 '--cmd', 2875 'echo "foo"', 2876 '--cmd', 2877 'sleep 10', 2878 }, { 2879 term = true, 2880 env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, 2881 }) 2882 if guicolors == 'termguicolors' then 2883 screen:expect([[ 2884 {100:^ }| 2885 {100: }|*7 2886 {100:foo }| 2887 | 2888 ]]) 2889 else 2890 screen:expect([[ 2891 ^ | 2892 |*7 2893 foo | 2894 | 2895 ]]) 2896 end 2897 end) 2898 end 2899 2900 it('argv[0] can be overridden #23953', function() 2901 t.skip(is_os('win'), 'N/A for Windows') 2902 local screen = Screen.new(50, 7, { rgb = false }) 2903 fn.jobstart( 2904 { testprg('shell-test'), 'EXECVP', nvim_prog, 'Xargv0nvim', '--clean' }, 2905 { term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') } } 2906 ) 2907 command('startinsert') 2908 screen:expect([[ 2909 ^ | 2910 ~ |*3 2911 [No Name] 0,0-1 All| 2912 | 2913 {5:-- TERMINAL --} | 2914 ]]) 2915 feed_data(':put =v:argv + [v:progname]\n') 2916 screen:expect([[ 2917 Xargv0nvim | 2918 --embed | 2919 --clean | 2920 ^Xargv0nvim | 2921 [No Name] [+] 5,1 Bot| 2922 4 more lines | 2923 {5:-- TERMINAL --} | 2924 ]]) 2925 end) 2926 2927 it("float is still highlighted with 'winblend' over uninitialized cells #34360", function() 2928 write_file( 2929 'Xblend.lua', 2930 [[ 2931 local win = vim.api.nvim_open_win(0, false, { relative = 'editor', width = 3, height = 1, row = 1000, col = 0, zindex = 400 }) 2932 vim.api.nvim_set_option_value('winblend', 30, { win = win }) 2933 vim.fn.setline(1, "foo") 2934 vim.api.nvim_buf_set_extmark(0, vim.api.nvim_create_namespace(''), 0, 0, { end_col = 3, hl_group = 'Title' }) 2935 ]] 2936 ) 2937 finally(function() 2938 os.remove('Xblend.lua') 2939 end) 2940 local screen = tt.setup_child_nvim({ '--clean', '-u', 'Xblend.lua' }) 2941 screen:expect([[ 2942 {5:^foo} | 2943 ~ |*3 2944 [No Name] [+] 1,1 All| 2945 {5:foo} | 2946 {5:-- TERMINAL --} | 2947 ]]) 2948 end) 2949 2950 it('with non-tty (pipe) stdout/stderr', function() 2951 t.skip(is_os('win'), 'N/A for Windows') 2952 finally(function() 2953 os.remove('testF') 2954 os.remove(testlog) 2955 end) 2956 local screen = tt.setup_screen( 2957 0, 2958 ('"%s" --clean --cmd "set noswapfile noshowcmd noruler" --cmd "normal iabc" > /dev/null 2>&1 && cat testF && rm testF'):format( 2959 nvim_prog 2960 ), 2961 nil, 2962 { VIMRUNTIME = os.getenv('VIMRUNTIME'), NVIM_LOG_FILE = testlog } 2963 ) 2964 feed_data(':w testF\n:q\n') 2965 screen:expect([[ 2966 :w testF | 2967 :q | 2968 abc | 2969 | 2970 [Process exited 0]^ | 2971 | 2972 {5:-- TERMINAL --} | 2973 ]]) 2974 assert_log('TUI: timed out waiting for DA1 response', testlog) 2975 end) 2976 2977 it('<C-h> #10134', function() 2978 local screen = tt.setup_child_nvim({ 2979 '--clean', 2980 '--cmd', 2981 'colorscheme vim', 2982 '--cmd', 2983 'set noruler', 2984 '--cmd', 2985 ':nnoremap <C-h> :echomsg "\\<C-h\\>"<CR>', 2986 }, { env = env_notermguicolors }) 2987 screen:expect([[ 2988 ^ | 2989 {100:~ }|*3 2990 {3:[No Name] }| 2991 | 2992 {5:-- TERMINAL --} | 2993 ]]) 2994 2995 command([[call chansend(b:terminal_job_id, "\<C-h>")]]) 2996 screen:expect([[ 2997 ^ | 2998 {100:~ }|*3 2999 {3:[No Name] }| 3000 <C-h> | 3001 {5:-- TERMINAL --} | 3002 ]]) 3003 end) 3004 3005 it('draws line with many trailing spaces correctly #24955', function() 3006 local screen = tt.setup_child_nvim({ 3007 '--clean', 3008 '--cmd', 3009 'colorscheme vim', 3010 '--cmd', 3011 'call setline(1, ["1st line" .. repeat(" ", 153), "2nd line"])', 3012 }, { cols = 80, env = env_notermguicolors }) 3013 screen:expect([[ 3014 ^1st line | 3015 |*2 3016 2nd line | 3017 {3:[No Name] [+] 1,1 All}| 3018 | 3019 {5:-- TERMINAL --} | 3020 ]]) 3021 feed_data('$') 3022 screen:expect([[ 3023 1st line | 3024 | 3025 ^ | 3026 2nd line | 3027 {3:[No Name] [+] 1,161 All}| 3028 | 3029 {5:-- TERMINAL --} | 3030 ]]) 3031 end) 3032 3033 it('draws screen lines with leading spaces correctly #29711', function() 3034 local screen = tt.setup_child_nvim({ 3035 '--clean', 3036 '--cmd', 3037 'set foldcolumn=6 | call setline(1, ["", repeat("aabb", 1000)]) | echo 42', 3038 }, { extra_rows = 10, cols = 66 }) 3039 screen:expect([[ 3040 ^ | 3041 aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabb|*12 3042 aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba@@@| 3043 [No Name] [+] 1,0-1 Top| 3044 42 | 3045 {5:-- TERMINAL --} | 3046 ]]) 3047 feed_data('\12') -- Ctrl-L 3048 -- The first line counts as 3 cells. 3049 -- For the second line, 6 repeated spaces at the start counts as 2 cells, 3050 -- so each screen line of the second line counts as 62 cells. 3051 -- After drawing the first line and 8 screen lines of the second line, 3052 -- 3 + 8 * 62 = 499 cells have been counted. 3053 -- The 6 repeated spaces at the start of the next screen line exceeds the 3054 -- 500-cell limit, so the buffer is flushed after these spaces. 3055 screen:expect([[ 3056 ^ | 3057 aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabb|*12 3058 aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba@@@| 3059 [No Name] [+] 1,0-1 Top| 3060 | 3061 {5:-- TERMINAL --} | 3062 ]]) 3063 end) 3064 3065 it('no heap-buffer-overflow when changing &columns', function() 3066 t.skip(is_os('win'), 'FIXME: does not work on Windows') 3067 -- Set a different bg colour and change $TERM to something dumber so the `print_spaces()` 3068 -- codepath in `clear_region()` is hit. 3069 local screen = tt.setup_child_nvim({ 3070 '--clean', 3071 '--cmd', 3072 'set notermguicolors | highlight Normal ctermbg=red', 3073 '--cmd', 3074 'call setline(1, ["a"->repeat(&columns)])', 3075 }, { env = { TERM = 'ansi' } }) 3076 3077 screen:expect([[ 3078 {117:^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| 3079 {117:~ }|*3 3080 {118:[No Name] [+] 1,1 All}| 3081 {117: }| 3082 {5:-- TERMINAL --} | 3083 ]]) 3084 3085 feed_data(':set columns=12\n') 3086 screen:expect([[ 3087 {117:^aaaaaaaaaaaa }| 3088 {117:aaaaaaaaaaaa }|*3 3089 {118:< All}{117: }| 3090 {117: }| 3091 {5:-- TERMINAL --} | 3092 ]]) 3093 3094 -- Wider than TUI, so screen state will look weird. 3095 -- Wait for the statusline to redraw to confirm that the TUI lives and ASAN is happy. 3096 feed_data(':set columns=99|set stl=redrawn%m\n') 3097 screen:expect({ any = 'redrawn%[%+%]' }) 3098 end) 3099 end) 3100 3101 describe('TUI UIEnter/UILeave', function() 3102 it('fires exactly once, after VimEnter', function() 3103 clear() 3104 local screen = tt.setup_child_nvim({ 3105 '--clean', 3106 '--cmd', 3107 'colorscheme vim', 3108 '--cmd', 3109 'set noswapfile noshowcmd noruler', 3110 '--cmd', 3111 'let g:evs = []', 3112 '--cmd', 3113 'autocmd UIEnter * :call add(g:evs, "UIEnter")', 3114 '--cmd', 3115 'autocmd UILeave * :call add(g:evs, "UILeave")', 3116 '--cmd', 3117 'autocmd VimEnter * :call add(g:evs, "VimEnter")', 3118 }, { env = env_notermguicolors }) 3119 screen:expect([[ 3120 ^ | 3121 {100:~ }|*3 3122 {3:[No Name] }| 3123 | 3124 {5:-- TERMINAL --} | 3125 ]]) 3126 feed_data(':echo g:evs\n') 3127 screen:expect([[ 3128 ^ | 3129 {100:~ }|*3 3130 {3:[No Name] }| 3131 ['VimEnter', 'UIEnter'] | 3132 {5:-- TERMINAL --} | 3133 ]]) 3134 end) 3135 end) 3136 3137 describe('TUI FocusGained/FocusLost', function() 3138 local screen --[[@type test.functional.ui.screen]] 3139 local child_session --[[@type test.Session]] 3140 3141 before_each(function() 3142 clear() 3143 local child_server = new_pipename() 3144 screen = tt.setup_child_nvim({ 3145 '--listen', 3146 child_server, 3147 '-u', 3148 'NONE', 3149 '-i', 3150 'NONE', 3151 '--cmd', 3152 'colorscheme vim', 3153 '--cmd', 3154 'set noswapfile noshowcmd noruler background=dark', 3155 }, { env = env_notermguicolors }) 3156 3157 screen:expect([[ 3158 ^ | 3159 {100:~ }|*3 3160 {3:[No Name] }| 3161 | 3162 {5:-- TERMINAL --} | 3163 ]]) 3164 child_session = n.connect(child_server) 3165 child_session:request( 3166 'nvim_exec2', 3167 [[ 3168 autocmd FocusGained * echo 'gained' 3169 autocmd FocusLost * echo 'lost' 3170 ]], 3171 {} 3172 ) 3173 feed_data('\034\016') -- CTRL-\ CTRL-N 3174 end) 3175 3176 it('in normal-mode', function() 3177 retry(2, 3 * screen.timeout, function() 3178 feed_data('\027[I') 3179 screen:expect([[ 3180 ^ | 3181 {100:~ }|*3 3182 {3:[No Name] }| 3183 gained | 3184 {5:-- TERMINAL --} | 3185 ]]) 3186 3187 feed_data('\027[O') 3188 screen:expect([[ 3189 ^ | 3190 {100:~ }|*3 3191 {3:[No Name] }| 3192 lost | 3193 {5:-- TERMINAL --} | 3194 ]]) 3195 end) 3196 end) 3197 3198 it('in insert-mode', function() 3199 feed_data(':set noshowmode\r') 3200 feed_data('i') 3201 screen:expect([[ 3202 ^ | 3203 {100:~ }|*3 3204 {3:[No Name] }| 3205 :set noshowmode | 3206 {5:-- TERMINAL --} | 3207 ]]) 3208 retry(2, 3 * screen.timeout, function() 3209 feed_data('\027[I') 3210 screen:expect([[ 3211 ^ | 3212 {100:~ }|*3 3213 {3:[No Name] }| 3214 gained | 3215 {5:-- TERMINAL --} | 3216 ]]) 3217 feed_data('\027[O') 3218 screen:expect([[ 3219 ^ | 3220 {100:~ }|*3 3221 {3:[No Name] }| 3222 lost | 3223 {5:-- TERMINAL --} | 3224 ]]) 3225 end) 3226 end) 3227 3228 -- During cmdline-mode we ignore :echo invoked by timers/events. 3229 -- See commit: 5cc87d4dabd02167117be7a978b5c8faaa975419. 3230 it('in cmdline-mode does NOT :echo', function() 3231 feed_data(':') 3232 feed_data('\027[I') 3233 screen:expect([[ 3234 | 3235 {100:~ }|*3 3236 {3:[No Name] }| 3237 :^ | 3238 {5:-- TERMINAL --} | 3239 ]]) 3240 feed_data('\027[O') 3241 screen:expect_unchanged() 3242 end) 3243 3244 it('in cmdline-mode', function() 3245 -- Set up autocmds that modify the buffer, instead of just calling :echo. 3246 -- This is how we can test handling of focus gained/lost during cmdline-mode. 3247 -- See commit: 5cc87d4dabd02167117be7a978b5c8faaa975419. 3248 child_session:request( 3249 'nvim_exec2', 3250 [[ 3251 autocmd! 3252 autocmd FocusLost * call append(line('$'), 'lost') 3253 autocmd FocusGained * call append(line('$'), 'gained') 3254 ]], 3255 {} 3256 ) 3257 retry(2, 3 * screen.timeout, function() 3258 -- Enter cmdline-mode. 3259 feed_data(':') 3260 screen:sleep(1) 3261 -- Send focus lost/gained termcodes. 3262 feed_data('\027[O') 3263 feed_data('\027[I') 3264 screen:sleep(1) 3265 -- Exit cmdline-mode. Redraws from timers/events are blocked during 3266 -- cmdline-mode, so the buffer won't be updated until we exit cmdline-mode. 3267 feed_data('\n') 3268 screen:expect { any = 'lost' .. (' '):rep(46) .. '|\ngained' } 3269 end) 3270 end) 3271 3272 it('in terminal-mode', function() 3273 feed_data(':set shell=' .. testprg('shell-test') .. ' shellcmdflag=EXE\n') 3274 feed_data(':set shellxquote=\n') -- win: avoid extra quotes 3275 feed_data(':set noshowmode laststatus=0\n') 3276 3277 feed_data(':terminal zia\n') 3278 -- Wait for terminal to be ready. 3279 screen:expect([[ 3280 ^ready $ zia | 3281 | 3282 [Process exited 0] | 3283 |*2 3284 :terminal zia | 3285 {5:-- TERMINAL --} | 3286 ]]) 3287 3288 feed_data('\027[I') 3289 screen:expect { 3290 grid = [[ 3291 ^ready $ zia | 3292 | 3293 [Process exited 0] | 3294 |*2 3295 gained | 3296 {5:-- TERMINAL --} | 3297 ]], 3298 timeout = (4 * screen.timeout), 3299 } 3300 3301 feed_data('\027[O') 3302 screen:expect([[ 3303 ^ready $ zia | 3304 | 3305 [Process exited 0] | 3306 |*2 3307 lost | 3308 {5:-- TERMINAL --} | 3309 ]]) 3310 end) 3311 3312 it('in press-enter prompt', function() 3313 t.skip(is_os('win'), 'FIXME: some spaces have wrong attrs on Windows') 3314 feed_data(":echom 'msg1'|echom 'msg2'|echom 'msg3'|echom 'msg4'|echom 'msg5'\n") 3315 -- Execute :messages to provoke the press-enter prompt. 3316 feed_data(':messages\n') 3317 screen:expect([[ 3318 msg1 | 3319 msg2 | 3320 msg3 | 3321 msg4 | 3322 msg5 | 3323 {102:Press ENTER or type command to continue}^ | 3324 {5:-- TERMINAL --} | 3325 ]]) 3326 feed_data('\027[I') 3327 feed_data('\027[I') 3328 screen:expect_unchanged() 3329 end) 3330 end) 3331 3332 -- These tests require `tt` because --headless/--embed does not initialize the TUI. 3333 describe("TUI 't_Co' (terminal colors)", function() 3334 local screen --[[@type test.functional.ui.screen]] 3335 3336 local function assert_term_colors(term, colorterm, maxcolors) 3337 clear({ env = { TERM = term }, args = {} }) 3338 screen = tt.setup_child_nvim({ 3339 '--clean', 3340 '--cmd', 3341 'colorscheme vim', 3342 '--cmd', 3343 nvim_set .. ' notermguicolors', 3344 }, { 3345 env = { 3346 LANG = 'C', 3347 TERM = term or '', 3348 COLORTERM = colorterm or '', 3349 }, 3350 }) 3351 3352 local tline --[[@type string]] 3353 if maxcolors == 8 then 3354 tline = '{112:~ }' 3355 elseif maxcolors == 16 then 3356 tline = '~ ' 3357 else 3358 tline = '{100:~ }' 3359 end 3360 3361 screen:expect(string.format( 3362 [[ 3363 ^ | 3364 %s|*4 3365 | 3366 {5:-- TERMINAL --} | 3367 ]], 3368 tline 3369 )) 3370 3371 feed_data(':echo &t_Co\n') 3372 screen:expect(string.format( 3373 [[ 3374 ^ | 3375 %s|*4 3376 %-3s | 3377 {5:-- TERMINAL --} | 3378 ]], 3379 tline, 3380 tostring(maxcolors and maxcolors or '') 3381 )) 3382 end 3383 3384 -- ansi and no terminal type at all: 3385 3386 if is_os('win') then 3387 it('guessed vtpcon with no TERM uses 256 colors', function() 3388 assert_term_colors(nil, nil, 256) 3389 end) 3390 else 3391 it('no TERM uses 8 colors', function() 3392 assert_term_colors(nil, nil, 8) 3393 end) 3394 end 3395 3396 it('TERM=ansi no COLORTERM uses 8 colors', function() 3397 assert_term_colors('ansi', nil, 8) 3398 end) 3399 3400 it('TERM=ansi with COLORTERM=anything-no-number uses 16 colors', function() 3401 assert_term_colors('ansi', 'yet-another-term', 16) 3402 end) 3403 3404 it('unknown TERM COLORTERM with 256 in name uses 256 colors', function() 3405 assert_term_colors('ansi', 'yet-another-term-256color', 256) 3406 end) 3407 3408 it('TERM=ansi-256color sets 256 colours', function() 3409 assert_term_colors('ansi-256color', nil, 256) 3410 end) 3411 3412 -- Unknown terminal types: 3413 3414 it('unknown TERM no COLORTERM sets 8 colours', function() 3415 assert_term_colors('yet-another-term', nil, 8) 3416 end) 3417 3418 it('unknown TERM with COLORTERM=anything-no-number uses 16 colors', function() 3419 assert_term_colors('yet-another-term', 'yet-another-term', 16) 3420 end) 3421 3422 it('unknown TERM with 256 in name sets 256 colours', function() 3423 assert_term_colors('yet-another-term-256color', nil, 256) 3424 end) 3425 3426 it('unknown TERM COLORTERM with 256 in name uses 256 colors', function() 3427 assert_term_colors('yet-another-term', 'yet-another-term-256color', 256) 3428 end) 3429 3430 -- Linux kernel terminal emulator: 3431 3432 it('TERM=linux uses 256 colors', function() 3433 assert_term_colors('linux', nil, 256) 3434 end) 3435 3436 it('TERM=linux-16color uses 256 colors', function() 3437 assert_term_colors('linux-16color', nil, 256) 3438 end) 3439 3440 it('TERM=linux-256color uses 256 colors', function() 3441 assert_term_colors('linux-256color', nil, 256) 3442 end) 3443 3444 -- screen: 3445 -- 3446 -- FreeBSD and Windows fall back to the built-in screen-256colour entry. 3447 -- Linux and MacOS have a screen entry in external terminfo with 8 colours, 3448 -- which is raised to 16 by COLORTERM. 3449 3450 it('TERM=screen no COLORTERM uses 8/256 colors', function() 3451 if is_os('freebsd') or is_os('win') then 3452 assert_term_colors('screen', nil, 256) 3453 else 3454 assert_term_colors('screen', nil, 8) 3455 end 3456 end) 3457 3458 it('TERM=screen COLORTERM=screen uses 16/256 colors', function() 3459 if is_os('freebsd') or is_os('win') then 3460 assert_term_colors('screen', 'screen', 256) 3461 else 3462 assert_term_colors('screen', 'screen', 16) 3463 end 3464 end) 3465 3466 it('TERM=screen COLORTERM=screen-256color uses 256 colors', function() 3467 assert_term_colors('screen', 'screen-256color', 256) 3468 end) 3469 3470 it('TERM=screen-256color no COLORTERM uses 256 colors', function() 3471 assert_term_colors('screen-256color', nil, 256) 3472 end) 3473 3474 -- tmux: 3475 -- 3476 -- FreeBSD and MacOS fall back to the built-in tmux-256colour entry. 3477 -- Linux has a tmux entry in external terminfo with 8 colours, 3478 -- which is raised to 256. 3479 3480 it('TERM=tmux no COLORTERM uses 256 colors', function() 3481 assert_term_colors('tmux', nil, 256) 3482 end) 3483 3484 it('TERM=tmux COLORTERM=tmux uses 256 colors', function() 3485 assert_term_colors('tmux', 'tmux', 256) 3486 end) 3487 3488 it('TERM=tmux COLORTERM=tmux-256color uses 256 colors', function() 3489 assert_term_colors('tmux', 'tmux-256color', 256) 3490 end) 3491 3492 it('TERM=tmux-256color no COLORTERM uses 256 colors', function() 3493 assert_term_colors('tmux-256color', nil, 256) 3494 end) 3495 3496 -- xterm and imitators: 3497 3498 it('TERM=xterm uses 256 colors', function() 3499 assert_term_colors('xterm', nil, 256) 3500 end) 3501 3502 it('TERM=xterm COLORTERM=gnome-terminal uses 256 colors', function() 3503 assert_term_colors('xterm', 'gnome-terminal', 256) 3504 end) 3505 3506 it('TERM=xterm COLORTERM=mate-terminal uses 256 colors', function() 3507 assert_term_colors('xterm', 'mate-terminal', 256) 3508 end) 3509 3510 it('TERM=xterm-256color uses 256 colors', function() 3511 assert_term_colors('xterm-256color', nil, 256) 3512 end) 3513 3514 -- rxvt and stterm: 3515 -- 3516 -- FreeBSD and MacOS fall back to the built-in rxvt-256color and 3517 -- st-256colour entries. 3518 -- Linux has an rxvt, an st, and an st-16color entry in external terminfo 3519 -- with 8, 8, and 16 colours respectively, which are raised to 256. 3520 3521 it('TERM=rxvt no COLORTERM uses 256 colors', function() 3522 assert_term_colors('rxvt', nil, 256) 3523 end) 3524 3525 it('TERM=rxvt COLORTERM=rxvt uses 256 colors', function() 3526 assert_term_colors('rxvt', 'rxvt', 256) 3527 end) 3528 3529 it('TERM=rxvt-256color uses 256 colors', function() 3530 assert_term_colors('rxvt-256color', nil, 256) 3531 end) 3532 3533 it('TERM=st no COLORTERM uses 256 colors', function() 3534 assert_term_colors('st', nil, 256) 3535 end) 3536 3537 it('TERM=st COLORTERM=st uses 256 colors', function() 3538 assert_term_colors('st', 'st', 256) 3539 end) 3540 3541 it('TERM=st COLORTERM=st-256color uses 256 colors', function() 3542 assert_term_colors('st', 'st-256color', 256) 3543 end) 3544 3545 it('TERM=st-16color no COLORTERM uses 8/256 colors', function() 3546 assert_term_colors('st', nil, 256) 3547 end) 3548 3549 it('TERM=st-16color COLORTERM=st uses 16/256 colors', function() 3550 assert_term_colors('st', 'st', 256) 3551 end) 3552 3553 it('TERM=st-16color COLORTERM=st-256color uses 256 colors', function() 3554 assert_term_colors('st', 'st-256color', 256) 3555 end) 3556 3557 it('TERM=st-256color uses 256 colors', function() 3558 assert_term_colors('st-256color', nil, 256) 3559 end) 3560 3561 -- gnome and vte: 3562 -- 3563 -- FreeBSD and MacOS fall back to the built-in vte-256color entry. 3564 -- Linux has a gnome, a vte, a gnome-256color, and a vte-256color entry in 3565 -- external terminfo with 8, 8, 256, and 256 colours respectively, which are 3566 -- raised to 256. 3567 3568 it('TERM=gnome no COLORTERM uses 256 colors', function() 3569 assert_term_colors('gnome', nil, 256) 3570 end) 3571 3572 it('TERM=gnome COLORTERM=gnome uses 256 colors', function() 3573 assert_term_colors('gnome', 'gnome', 256) 3574 end) 3575 3576 it('TERM=gnome COLORTERM=gnome-256color uses 256 colors', function() 3577 assert_term_colors('gnome', 'gnome-256color', 256) 3578 end) 3579 3580 it('TERM=gnome-256color uses 256 colors', function() 3581 assert_term_colors('gnome-256color', nil, 256) 3582 end) 3583 3584 it('TERM=vte no COLORTERM uses 256 colors', function() 3585 assert_term_colors('vte', nil, 256) 3586 end) 3587 3588 it('TERM=vte COLORTERM=vte uses 256 colors', function() 3589 assert_term_colors('vte', 'vte', 256) 3590 end) 3591 3592 it('TERM=vte COLORTERM=vte-256color uses 256 colors', function() 3593 assert_term_colors('vte', 'vte-256color', 256) 3594 end) 3595 3596 it('TERM=vte-256color uses 256 colors', function() 3597 assert_term_colors('vte-256color', nil, 256) 3598 end) 3599 3600 -- others: 3601 3602 -- TODO(blueyed): this is made pending, since it causes failure + later hang 3603 -- when using non-compatible libvterm (#9494/#10179). 3604 pending('TERM=interix uses 8 colors', function() 3605 assert_term_colors('interix', nil, 8) 3606 end) 3607 3608 it('TERM=iTerm.app uses 256 colors', function() 3609 assert_term_colors('iTerm.app', nil, 256) 3610 end) 3611 3612 it('TERM=iterm uses 256 colors', function() 3613 assert_term_colors('iterm', nil, 256) 3614 end) 3615 end) 3616 3617 -- These tests require `tt` because --headless/--embed does not initialize the TUI. 3618 describe("TUI 'term' option", function() 3619 local screen --[[@type test.functional.ui.screen]] 3620 3621 local function assert_term(term_envvar, term_expected) 3622 clear() 3623 screen = tt.setup_child_nvim({ 3624 '--clean', 3625 '--cmd', 3626 nvim_set .. ' notermguicolors', 3627 }, { 3628 env = { 3629 LANG = 'C', 3630 TERM = term_envvar or '', 3631 }, 3632 }) 3633 3634 local full_timeout = screen.timeout 3635 retry(nil, 2 * full_timeout, function() -- Wait for TUI thread to set 'term'. 3636 feed_data(":echo 'term='.(&term)\n") 3637 screen:expect { any = 'term=' .. term_expected, timeout = 250 } 3638 end) 3639 end 3640 3641 it('gets builtin term if $TERM is invalid', function() 3642 assert_term('foo', 'ansi') 3643 end) 3644 3645 it('gets system-provided term if $TERM is valid', function() 3646 if is_os('openbsd') then 3647 assert_term('xterm', 'xterm') 3648 elseif is_os('bsd') then -- BSD lacks terminfo, builtin is always used. 3649 assert_term('xterm', 'xterm') 3650 elseif is_os('mac') then 3651 local status, _ = pcall(assert_term, 'xterm', 'xterm') 3652 if not status then 3653 pending('macOS: unibilium could not find terminfo') 3654 end 3655 else 3656 assert_term('xterm', 'xterm') 3657 end 3658 end) 3659 3660 it('builtin terms', function() 3661 -- These non-standard terminfos are always builtin. 3662 assert_term('win32con', 'win32con') 3663 assert_term('conemu', 'conemu') 3664 assert_term('vtpcon', 'vtpcon') 3665 end) 3666 end) 3667 3668 -- These tests require `tt` because --headless/--embed does not initialize the TUI. 3669 describe('TUI', function() 3670 local screen --[[@type test.functional.ui.screen]] 3671 3672 -- Runs (child) `nvim` in a TTY (:terminal), to start the builtin TUI. 3673 local function nvim_tui(extra_args) 3674 clear() 3675 screen = tt.setup_child_nvim({ 3676 '--clean', 3677 '--cmd', 3678 'colorscheme vim', 3679 '--cmd', 3680 nvim_set .. ' notermguicolors', 3681 extra_args, 3682 }, { 3683 env = { 3684 LANG = 'C', 3685 }, 3686 }) 3687 end 3688 3689 it('-V3log logs terminfo values', function() 3690 local logfile = 'Xtest_tui_verbose_log' 3691 nvim_tui('-V3' .. logfile) 3692 finally(function() 3693 os.remove(logfile) 3694 end) 3695 3696 -- Wait for TUI to start. 3697 feed_data('Gitext') 3698 screen:expect([[ 3699 text^ | 3700 {100:~ }|*4 3701 {5:-- INSERT --} | 3702 {5:-- TERMINAL --} | 3703 ]]) 3704 3705 retry(nil, 3000, function() -- Wait for log file to be flushed. 3706 local log = read_file(logfile) or '' 3707 eq('--- Terminal info --- {{{\n', string.match(log, '%-%-%- Terminal.-\n')) -- }}} 3708 ok(#log > 50) 3709 end) 3710 end) 3711 3712 it('does not crash on large inputs #26099', function() 3713 nvim_tui() 3714 3715 screen:expect([[ 3716 ^ | 3717 {100:~ }|*4 3718 | 3719 {5:-- TERMINAL --} | 3720 ]]) 3721 3722 feed_data(string.format('\027]52;c;%s\027\\', string.rep('A', 8192))) 3723 3724 screen:expect_unchanged() 3725 end) 3726 3727 it('queries the terminal for truecolor support', function() 3728 t.skip(is_os('win'), 'FIXME: does not work on Windows') 3729 clear() 3730 exec_lua([[ 3731 vim.api.nvim_create_autocmd('TermRequest', { 3732 callback = function(args) 3733 local req = args.data.sequence 3734 local sequence = req:match('^\027P%+q([%x;]+)$') 3735 if sequence then 3736 local t = {} 3737 for cap in vim.gsplit(sequence, ';') do 3738 local resp = string.format('\027P1+r%s\027\\', sequence) 3739 vim.api.nvim_chan_send(vim.bo[args.buf].channel, resp) 3740 t[vim.text.hexdecode(cap)] = true 3741 end 3742 vim.g.xtgettcap = t 3743 return true 3744 end 3745 end, 3746 }) 3747 ]]) 3748 3749 local child_server = new_pipename() 3750 screen = tt.setup_child_nvim({ 3751 '--clean', 3752 '--listen', 3753 child_server, 3754 }, { 3755 env = { 3756 VIMRUNTIME = os.getenv('VIMRUNTIME'), 3757 3758 -- Force COLORTERM to be unset and use a TERM that does not contain Tc or RGB in terminfo. 3759 -- This will force the nested nvim instance to query with XTGETTCAP 3760 COLORTERM = '', 3761 TERM = 'xterm-256colors', 3762 }, 3763 }) 3764 3765 screen:expect({ any = '%[No Name%]' }) 3766 3767 local child_session = n.connect(child_server) 3768 retry(nil, 1000, function() 3769 eq({ 3770 Tc = true, 3771 RGB = true, 3772 setrgbf = true, 3773 setrgbb = true, 3774 }, eval("get(g:, 'xtgettcap', '')")) 3775 eq({ true, 1 }, { child_session:request('nvim_eval', '&termguicolors') }) 3776 end) 3777 end) 3778 3779 it('does not query the terminal for truecolor support if $COLORTERM is set', function() 3780 clear() 3781 exec_lua([[ 3782 vim.api.nvim_create_autocmd('TermRequest', { 3783 callback = function(args) 3784 local req = args.data.sequence 3785 vim.g.termrequest = req 3786 local xtgettcap = req:match('^\027P%+q([%x;]+)$') 3787 if xtgettcap then 3788 local t = {} 3789 for cap in vim.gsplit(xtgettcap, ';') do 3790 local resp = string.format('\027P1+r%s\027\\', xtgettcap) 3791 vim.api.nvim_chan_send(vim.bo[args.buf].channel, resp) 3792 t[vim.text.hexdecode(cap)] = true 3793 end 3794 vim.g.xtgettcap = t 3795 return true 3796 elseif req:match('^\027P$qm\027\\$') then 3797 vim.g.decrqss = true 3798 end 3799 end, 3800 }) 3801 ]]) 3802 3803 local child_server = new_pipename() 3804 screen = tt.setup_child_nvim({ 3805 '--clean', 3806 '--listen', 3807 child_server, 3808 }, { 3809 env = { 3810 VIMRUNTIME = os.getenv('VIMRUNTIME'), 3811 -- With COLORTERM=256, Nvim should not query the terminal and should not set 'tgc' 3812 COLORTERM = '256', 3813 TERM = 'xterm-256colors', 3814 }, 3815 }) 3816 3817 screen:expect({ any = '%[No Name%]' }) 3818 3819 local child_session = n.connect(child_server) 3820 retry(nil, 1000, function() 3821 local xtgettcap = eval("get(g:, 'xtgettcap', {})") 3822 eq(nil, xtgettcap['Tc']) 3823 eq(nil, xtgettcap['RGB']) 3824 eq(nil, xtgettcap['setrgbf']) 3825 eq(nil, xtgettcap['setrgbb']) 3826 eq(0, eval([[get(g:, 'decrqss')]])) 3827 eq({ true, 0 }, { child_session:request('nvim_eval', '&termguicolors') }) 3828 end) 3829 end) 3830 3831 it('queries the terminal for OSC 52 support with XTGETTCAP', function() 3832 t.skip(is_os('win'), 'FIXME: does not work on Windows') 3833 clear() 3834 if not exec_lua('return pcall(require, "ffi")') then 3835 pending('N/A: missing LuaJIT FFI') 3836 end 3837 3838 -- Change vterm's DA1 response so that it doesn't include 52 3839 exec_lua(function() 3840 local ffi = require('ffi') 3841 ffi.cdef [[ 3842 extern char vterm_primary_device_attr[] 3843 ]] 3844 3845 ffi.copy(ffi.C.vterm_primary_device_attr, '61;22') 3846 end) 3847 3848 exec_lua([[ 3849 _G.query = false 3850 vim.api.nvim_create_autocmd('TermRequest', { 3851 callback = function(args) 3852 local req = args.data.sequence 3853 local sequence = req:match('^\027P%+q([%x;]+)$') 3854 if sequence and vim.text.hexdecode(sequence) == 'Ms' then 3855 local resp = string.format('\027P1+r%s=%s\027\\', sequence, vim.text.hexencode('\027]52;;\027\\')) 3856 vim.api.nvim_chan_send(vim.bo[args.buf].channel, resp) 3857 _G.query = true 3858 return true 3859 end 3860 end, 3861 }) 3862 ]]) 3863 3864 local child_server = new_pipename() 3865 screen = tt.setup_child_nvim({ 3866 '--listen', 3867 child_server, 3868 '--clean', 3869 }, { 3870 env = { 3871 VIMRUNTIME = os.getenv('VIMRUNTIME'), 3872 }, 3873 }) 3874 3875 screen:expect({ any = '%[No Name%]' }) 3876 3877 local child_session = n.connect(child_server) 3878 retry(nil, 1000, function() 3879 eq({ true, { osc52 = true } }, { child_session:request('nvim_eval', 'g:termfeatures') }) 3880 end) 3881 eq(true, exec_lua([[return _G.query]])) 3882 3883 -- Attach another (non-TUI) UI to the child instance 3884 local alt = Screen.new(nil, nil, nil, child_session) 3885 finally(function() 3886 alt:detach() 3887 -- Avoid a dangling process after :detach. 3888 child_session:request('nvim_command', 'qall!') 3889 end) 3890 3891 -- Detach the first (primary) client so only the second UI is attached 3892 feed_data(':detach\n') 3893 3894 alt:expect({ any = '%[No Name%]' }) 3895 3896 -- osc52 should be cleared from termfeatures 3897 eq({ true, {} }, { child_session:request('nvim_eval', 'g:termfeatures') }) 3898 end) 3899 3900 it('determines OSC 52 support from DA1 response', function() 3901 t.skip(is_os('win'), 'FIXME: does not work on Windows') 3902 clear() 3903 exec_lua([[ 3904 -- Check that we do not emit an XTGETTCAP request when DA1 indicates support 3905 _G.query = false 3906 vim.api.nvim_create_autocmd('TermRequest', { 3907 callback = function(args) 3908 local req = args.data.sequence 3909 local sequence = req:match('^\027P%+q([%x;]+)$') 3910 if sequence and vim.text.hexdecode(sequence) == 'Ms' then 3911 _G.query = true 3912 return true 3913 end 3914 end, 3915 }) 3916 ]]) 3917 3918 local child_server = new_pipename() 3919 screen = tt.setup_child_nvim({ 3920 '--listen', 3921 child_server, 3922 '--clean', 3923 }, { 3924 env = { 3925 VIMRUNTIME = os.getenv('VIMRUNTIME'), 3926 }, 3927 }) 3928 3929 screen:expect({ any = '%[No Name%]' }) 3930 3931 local child_session = n.connect(child_server) 3932 retry(nil, 1000, function() 3933 eq({ true, { osc52 = true } }, { child_session:request('nvim_eval', 'g:termfeatures') }) 3934 end) 3935 eq(false, exec_lua([[return _G.query]])) 3936 end) 3937 3938 it('does not query the terminal for OSC 52 support when disabled', function() 3939 clear() 3940 exec_lua([[ 3941 _G.query = false 3942 vim.api.nvim_create_autocmd('TermRequest', { 3943 callback = function(args) 3944 local req = args.data.sequence 3945 local sequence = req:match('^\027P%+q([%x;]+)$') 3946 if sequence and vim.text.hexdecode(sequence) == 'Ms' then 3947 _G.query = true 3948 return true 3949 end 3950 end, 3951 }) 3952 ]]) 3953 3954 local child_server = new_pipename() 3955 screen = tt.setup_child_nvim({ 3956 '--listen', 3957 child_server, 3958 '--clean', 3959 '--cmd', 3960 'let g:termfeatures = #{osc52: v:false}', 3961 }, { 3962 env = { 3963 VIMRUNTIME = os.getenv('VIMRUNTIME'), 3964 }, 3965 }) 3966 3967 screen:expect({ any = '%[No Name%]' }) 3968 3969 local child_session = n.connect(child_server) 3970 eq({ true, { osc52 = false } }, { child_session:request('nvim_eval', 'g:termfeatures') }) 3971 eq(false, exec_lua([[return _G.query]])) 3972 end) 3973 end) 3974 3975 describe('TUI bg color', function() 3976 if t.skip(is_os('win')) then 3977 return 3978 end 3979 3980 before_each(clear) 3981 3982 it('is properly set in a nested Nvim instance when background=dark', function() 3983 command('highlight clear Normal') 3984 command('set background=dark') -- set outer Nvim background 3985 local child_server = new_pipename() 3986 local screen = tt.setup_child_nvim({ 3987 '--clean', 3988 '--listen', 3989 child_server, 3990 '--cmd', 3991 'colorscheme vim', 3992 '--cmd', 3993 'set noswapfile', 3994 }) 3995 screen:expect({ any = '%[No Name%]' }) 3996 local child_session = n.connect(child_server) 3997 retry(nil, nil, function() 3998 eq({ true, 'dark' }, { child_session:request('nvim_eval', '&background') }) 3999 end) 4000 end) 4001 4002 it('is properly set in a nested Nvim instance when background=light', function() 4003 command('highlight clear Normal') 4004 command('set background=light') -- set outer Nvim background 4005 local child_server = new_pipename() 4006 local screen = tt.setup_child_nvim({ 4007 '--clean', 4008 '--listen', 4009 child_server, 4010 '--cmd', 4011 'colorscheme vim', 4012 '--cmd', 4013 'set noswapfile', 4014 }) 4015 screen:expect({ any = '%[No Name%]' }) 4016 local child_session = n.connect(child_server) 4017 retry(nil, nil, function() 4018 eq({ true, 'light' }, { child_session:request('nvim_eval', '&background') }) 4019 end) 4020 end) 4021 4022 it('queries the terminal for background color', function() 4023 exec_lua([[ 4024 vim.api.nvim_create_autocmd('TermRequest', { 4025 callback = function(args) 4026 local req = args.data.sequence 4027 if req == '\027]11;?' then 4028 vim.g.oscrequest = true 4029 return true 4030 end 4031 end, 4032 }) 4033 ]]) 4034 tt.setup_child_nvim({ 4035 '--clean', 4036 '--cmd', 4037 'colorscheme vim', 4038 '--cmd', 4039 'set noswapfile', 4040 }) 4041 retry(nil, 1000, function() 4042 eq(true, eval("get(g:, 'oscrequest', v:false)")) 4043 end) 4044 end) 4045 4046 it('triggers OptionSet from automatic background processing', function() 4047 local screen = tt.setup_child_nvim({ 4048 '--clean', 4049 '--cmd', 4050 'colorscheme vim', 4051 '--cmd', 4052 'set noswapfile', 4053 '-c', 4054 'autocmd OptionSet background echo "did OptionSet, yay!"', 4055 }) 4056 screen:expect([[ 4057 ^ | 4058 {5:~} |*3 4059 {3:[No Name] 0,0-1 All}| 4060 did OptionSet, yay! | 4061 {5:-- TERMINAL --} | 4062 ]]) 4063 end) 4064 4065 it('sends theme update notifications when background changes #31652', function() 4066 command('set background=dark') -- set outer Nvim background 4067 local child_server = new_pipename() 4068 local screen = tt.setup_child_nvim({ 4069 '--clean', 4070 '--listen', 4071 child_server, 4072 '--cmd', 4073 'colorscheme vim', 4074 '--cmd', 4075 'set noswapfile', 4076 }) 4077 screen:expect({ any = '%[No Name%]' }) 4078 local child_session = n.connect(child_server) 4079 retry(nil, nil, function() 4080 eq({ true, 'dark' }, { child_session:request('nvim_eval', '&background') }) 4081 end) 4082 command('set background=light') -- set outer Nvim background 4083 retry(nil, nil, function() 4084 eq({ true, 'light' }, { child_session:request('nvim_eval', '&background') }) 4085 end) 4086 end) 4087 end) 4088 4089 describe('TUI client', function() 4090 local function start_tui_and_remote_client() 4091 local server_super = n.clear() 4092 local client_super = n.new_session(true) 4093 finally(function() 4094 client_super:close() 4095 server_super:close() 4096 end) 4097 4098 local server_pipe = new_pipename() 4099 local screen_server = tt.setup_child_nvim({ 4100 '--clean', 4101 '--listen', 4102 server_pipe, 4103 '--cmd', 4104 'colorscheme vim', 4105 '--cmd', 4106 nvim_set .. ' laststatus=2 background=dark', 4107 }, { env = env_notermguicolors }) 4108 screen_server:expect([[ 4109 ^ | 4110 {100:~ }|*3 4111 {3:[No Name] }| 4112 | 4113 {5:-- TERMINAL --} | 4114 ]]) 4115 4116 feed_data('iHello, World') 4117 screen_server:expect([[ 4118 Hello, World^ | 4119 {100:~ }|*3 4120 {3:[No Name] [+] }| 4121 {5:-- INSERT --} | 4122 {5:-- TERMINAL --} | 4123 ]]) 4124 feed_data('\027') 4125 local s0 = [[ 4126 Hello, Worl^d | 4127 {100:~ }|*3 4128 {3:[No Name] [+] }| 4129 | 4130 {5:-- TERMINAL --} | 4131 ]] 4132 screen_server:expect(s0) 4133 4134 feed_data(':echo "GUI Running: " .. has("gui_running")\013') 4135 screen_server:expect({ any = 'GUI Running: 0' }) 4136 4137 set_session(client_super) 4138 local screen_client = tt.setup_child_nvim({ 4139 '--remote-ui', 4140 '--server', 4141 server_pipe, 4142 }, { env = env_notermguicolors }) 4143 screen_client:expect(s0) 4144 4145 return server_super, screen_server, screen_client 4146 end 4147 4148 it('connects to remote instance (with its own TUI)', function() 4149 local _, screen_server, screen_client = start_tui_and_remote_client() 4150 4151 feed_data(':echo "GUI Running: " .. has("gui_running")\013') 4152 screen_client:expect({ any = 'GUI Running: 0' }) 4153 4154 -- grid smaller than containing terminal window is cleared properly 4155 feed_data(":call setline(1,['a'->repeat(&columns)]->repeat(&lines))\n") 4156 feed_data('0:set lines=3\n') 4157 local s1 = [[ 4158 ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| 4159 {3:[No Name] [+] }| 4160 |*4 4161 {5:-- TERMINAL --} | 4162 ]] 4163 screen_client:expect(s1) 4164 screen_server:expect(s1) 4165 end) 4166 4167 it(':restart works when connecting to remote instance (with its own TUI)', function() 4168 local _, screen_server, screen_client = start_tui_and_remote_client() 4169 4170 -- Run :restart on the remote client. 4171 -- The remote client should start a new server while the original one should exit. 4172 feed_data(':restart +qall!\n') 4173 screen_client:expect([[ 4174 ^ | 4175 {100:~ }|*3 4176 {3:[No Name] }| 4177 | 4178 {5:-- TERMINAL --} | 4179 ]]) 4180 screen_server:expect({ any = vim.pesc('[Process exited 0]') }) 4181 4182 feed_data(':echo "GUI Running: " .. has("gui_running")\013') 4183 screen_client:expect({ any = 'GUI Running: 0' }) 4184 4185 feed_data(':q!\r') 4186 screen_client:expect({ any = vim.pesc('[Process exited 0]') }) 4187 end) 4188 4189 local function start_headless_server_and_client(use_testlog) 4190 local server = n.new_session(false, { 4191 args_rm = { '--cmd' }, 4192 args = { 4193 '--cmd', 4194 'colorscheme vim', 4195 '--cmd', 4196 nvim_set .. ' notermguicolors background=dark', 4197 }, 4198 }) 4199 local client_super = 4200 n.new_session(true, use_testlog and { env = { NVIM_LOG_FILE = testlog } } or {}) 4201 finally(function() 4202 client_super:close() 4203 server:close() 4204 os.remove(testlog) 4205 end) 4206 4207 set_session(server) 4208 --- @type string 4209 local server_pipe = api.nvim_get_vvar('servername') 4210 server:request('nvim_input', 'iHalloj!<Esc>') 4211 4212 set_session(client_super) 4213 local screen_client = tt.setup_child_nvim({ 4214 '--remote-ui', 4215 '--server', 4216 server_pipe, 4217 }, { env = env_notermguicolors }) 4218 screen_client:expect([[ 4219 Halloj^! | 4220 {100:~ }|*4 4221 | 4222 {5:-- TERMINAL --} | 4223 ]]) 4224 4225 return server, server_pipe, screen_client 4226 end 4227 4228 it('connects to remote instance (--headless)', function() 4229 local server, server_pipe, screen_client = start_headless_server_and_client(false) 4230 4231 -- No heap-use-after-free when receiving UI events after deadly signal #22184 4232 server:request('nvim_input', ('a'):rep(1000)) 4233 exec_lua([[vim.uv.kill(vim.fn.jobpid(vim.bo.channel), 'sigterm')]]) 4234 screen_client:expect(is_os('win') and { any = '%[Process exited 1%]' } or [[ 4235 Nvim: Caught deadly signal 'SIGTERM' | 4236 | 4237 [Process exited 1]^ | 4238 |*3 4239 {5:-- TERMINAL --} | 4240 ]]) 4241 4242 eq(0, api.nvim_get_vvar('shell_error')) 4243 -- exits on input eof #22244 4244 -- Use system() without input so that stdin is closed. 4245 fn.system({ nvim_prog, '--remote-ui', '--server', server_pipe }) 4246 eq(1, api.nvim_get_vvar('shell_error')) 4247 4248 command('bwipe!') 4249 -- Start another remote client to attach to the same server. 4250 fn.jobstart({ nvim_prog, '--remote-ui', '--server', server_pipe }, { term = true }) 4251 command('startinsert') 4252 screen_client:expect([[ 4253 {100:<<<}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| 4254 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|*3 4255 aaaaaa^ | 4256 {5:-- INSERT --} | 4257 {5:-- TERMINAL --} | 4258 ]]) 4259 feed_data('\027') 4260 4261 feed_data(':echo "GUI Running: " .. has("gui_running")\013') 4262 screen_client:expect({ any = 'GUI Running: 0' }) 4263 end) 4264 4265 it(':restart works when connecting to remote instance (--headless)', function() 4266 local _, server_pipe, screen_client = start_headless_server_and_client(false) 4267 4268 -- Run :restart on the client. 4269 -- The client should start a new server while the original server should exit. 4270 feed_data(':restart +qall!\n') 4271 screen_client:expect([[ 4272 ^ | 4273 {100:~ }|*4 4274 | 4275 {5:-- TERMINAL --} | 4276 ]]) 4277 retry(nil, nil, function() 4278 eq(nil, vim.uv.fs_stat(server_pipe)) 4279 end) 4280 4281 feed_data(':echo "GUI Running: " .. has("gui_running")\013') 4282 screen_client:expect({ any = 'GUI Running: 0' }) 4283 4284 feed_data(':q!\r') 4285 screen_client:expect({ any = vim.pesc('[Process exited 0]') }) 4286 end) 4287 4288 local ffi_str_defs = [[ 4289 local ffi = require('ffi') 4290 local cstr = ffi.typeof('char[?]') 4291 ffi.cdef('typedef struct { char *data; size_t size; } String;') 4292 local function to_api_string(str) 4293 return ffi.new('String', { data = cstr(#str + 1, str), size = #str }) 4294 end 4295 ]] 4296 4297 it('does not crash or hang with a very long title', function() 4298 local server, _, screen_client = start_headless_server_and_client(true) 4299 local server_exec_lua = tt.make_lua_executor(server) 4300 if not server_exec_lua('return pcall(require, "ffi")') then 4301 pending('N/A: missing LuaJIT FFI') 4302 end 4303 4304 local bufname = api.nvim_buf_get_name(0) 4305 local old_title = api.nvim_buf_get_var(0, 'term_title') 4306 if not is_os('win') then 4307 eq(bufname, old_title) 4308 end 4309 -- Normally a title cannot be longer than the 65535-byte buffer as maketitle() 4310 -- limits it length. Use FFI to send a very long title directly. 4311 server_exec_lua(ffi_str_defs .. [[ 4312 ffi.cdef('void ui_call_set_title(String title);') 4313 ffi.C.ui_call_set_title(to_api_string(('a'):rep(65536))) 4314 ]]) 4315 screen_client:expect_unchanged() 4316 assert_log('set_title: title string too long!', testlog) 4317 eq(old_title, api.nvim_buf_get_var(0, 'term_title')) 4318 4319 -- Following escape sequences are not affected. 4320 server:request('nvim_set_option_value', 'title', true, {}) 4321 retry(nil, nil, function() 4322 eq('[No Name] + - Nvim', api.nvim_buf_get_var(0, 'term_title')) 4323 end) 4324 end) 4325 4326 it('logs chdir failure properly', function() 4327 t.skip(is_os('win'), 'N/A for Windows') 4328 local server, _, screen_client = start_headless_server_and_client(true) 4329 local server_exec_lua = tt.make_lua_executor(server) 4330 if not server_exec_lua('return pcall(require, "ffi")') then 4331 pending('N/A: missing LuaJIT FFI') 4332 end 4333 4334 -- Use FFI to send a chdir event to a non-directory path. 4335 server_exec_lua(ffi_str_defs .. [[ 4336 ffi.cdef('void ui_call_chdir(String path);') 4337 ffi.C.ui_call_chdir(to_api_string('README.md')) 4338 ]]) 4339 screen_client:expect_unchanged() 4340 assert_log('Failed to chdir to README%.md: not a directory', testlog) 4341 end) 4342 4343 it('nvim_ui_send works with remote client #36317', function() 4344 local server, _, _ = start_headless_server_and_client(false) 4345 server:request('nvim_ui_send', '\027]2;TEST_TITLE\027\\') 4346 retry(nil, nil, function() 4347 eq('TEST_TITLE', api.nvim_buf_get_var(0, 'term_title')) 4348 end) 4349 end) 4350 4351 it('throws error when no server exists', function() 4352 clear() 4353 local screen = tt.setup_child_nvim({ 4354 '--remote-ui', 4355 '--server', 4356 '127.0.0.1:2436546', 4357 }, { cols = 60 }) 4358 4359 screen:expect([[ 4360 Remote ui failed to start: {MATCH:.*}| 4361 | 4362 [Process exited 1]^ | 4363 |*3 4364 {5:-- TERMINAL --} | 4365 ]]) 4366 end) 4367 4368 local function test_remote_tui_quit(status) 4369 local server_super, screen_server, screen_client = start_tui_and_remote_client() 4370 4371 -- quitting the server 4372 set_session(server_super) 4373 feed_data(status and ':' .. status .. 'cquit!\n' or ':quit!\n') 4374 status = status and status or 0 4375 screen_server:expect({ any = 'Process exited ' .. status }) 4376 screen_client:expect({ any = 'Process exited ' .. status }) 4377 end 4378 4379 describe('exits when server quits', function() 4380 it('with :quit', function() 4381 test_remote_tui_quit() 4382 end) 4383 4384 it('with :cquit', function() 4385 test_remote_tui_quit(42) 4386 end) 4387 end) 4388 4389 it('suspend/resume works with multiple clients', function() 4390 t.skip(is_os('win'), 'N/A for Windows') 4391 local server_super, screen_server, screen_client = start_tui_and_remote_client() 4392 4393 local screen_normal = [[ 4394 Hello, Worl^d | 4395 {100:~ }|*3 4396 {3:[No Name] [+] }| 4397 | 4398 {5:-- TERMINAL --} | 4399 ]] 4400 local screen_suspended = [[ 4401 |*5 4402 ^[Process suspended] | 4403 {5:-- TERMINAL --} | 4404 ]] 4405 4406 screen_client:expect({ grid = screen_normal, unchanged = true }) 4407 screen_server:expect({ grid = screen_normal, unchanged = true }) 4408 4409 -- Suspend both clients. 4410 feed_data(':suspend\r') 4411 screen_client:expect({ grid = screen_suspended }) 4412 screen_server:expect({ grid = screen_suspended }) 4413 4414 -- Resume the remote client. 4415 n.feed('<Space>') 4416 screen_client:expect({ grid = screen_normal }) 4417 screen_server:expect({ grid = screen_suspended, unchanged = true }) 4418 4419 -- Resume the embedding client. 4420 server_super:request('nvim_input', '<Space>') 4421 screen_server:expect({ grid = screen_normal }) 4422 screen_client:expect({ grid = screen_normal, unchanged = true }) 4423 4424 -- Suspend both clients again. 4425 feed_data(':suspend\r') 4426 screen_client:expect({ grid = screen_suspended }) 4427 screen_server:expect({ grid = screen_suspended }) 4428 4429 -- Resume the remote client. 4430 n.feed('<Space>') 4431 screen_client:expect({ grid = screen_normal }) 4432 screen_server:expect({ grid = screen_suspended, unchanged = true }) 4433 4434 -- Suspend the remote client again. 4435 feed_data(':suspend\r') 4436 screen_client:expect({ grid = screen_suspended }) 4437 screen_server:expect({ grid = screen_suspended, unchanged = true }) 4438 4439 -- Resume the embedding client. 4440 server_super:request('nvim_input', '<Space>') 4441 screen_server:expect({ grid = screen_normal }) 4442 screen_client:expect({ grid = screen_suspended, unchanged = true }) 4443 4444 -- Resume the remote client. 4445 n.feed('<Space>') 4446 screen_client:expect({ grid = screen_normal }) 4447 screen_server:expect({ grid = screen_normal, unchanged = true }) 4448 4449 feed_data(':quit!\r') 4450 screen_server:expect({ any = vim.pesc('[Process exited 0]') }) 4451 screen_client:expect({ any = vim.pesc('[Process exited 0]') }) 4452 end) 4453 end)