vim_spec.lua (210296B)
1 local t = require('test.testutil') 2 local n = require('test.functional.testnvim')() 3 local Screen = require('test.functional.ui.screen') 4 local uv = vim.uv 5 6 local fmt = string.format 7 local dedent = t.dedent 8 local assert_alive = n.assert_alive 9 local NIL = vim.NIL 10 local clear, eq, neq = n.clear, t.eq, t.neq 11 local command = n.command 12 local command_output = n.api.nvim_command_output 13 local exec = n.exec 14 local exec_capture = n.exec_capture 15 local eval = n.eval 16 local expect = n.expect 17 local fn = n.fn 18 local api = n.api 19 local matches = t.matches 20 local pesc = vim.pesc 21 local mkdir_p = n.mkdir_p 22 local ok, nvim_async, feed = t.ok, n.nvim_async, n.feed 23 local async_meths = n.async_meths 24 local is_os = t.is_os 25 local parse_context = n.parse_context 26 local request = n.request 27 local rmdir = n.rmdir 28 local source = n.source 29 local next_msg = n.next_msg 30 local tmpname = t.tmpname 31 local write_file = t.write_file 32 local exec_lua = n.exec_lua 33 local exc_exec = n.exc_exec 34 local insert = n.insert 35 local skip = t.skip 36 37 local pcall_err = t.pcall_err 38 local format_string = require('test.format_string').format_string 39 local intchar2lua = t.intchar2lua 40 local mergedicts_copy = t.mergedicts_copy 41 local endswith = vim.endswith 42 43 describe('API', function() 44 before_each(clear) 45 46 it('validates requests', function() 47 -- RPC 48 matches('Invalid method: bogus$', pcall_err(request, 'bogus')) 49 matches('Invalid method: … の り 。…$', pcall_err(request, '… の り 。…')) 50 matches('Invalid method: <empty>$', pcall_err(request, '')) 51 52 -- Non-RPC: rpcrequest(v:servername) uses internal channel. 53 matches( 54 'Invalid method: … の り 。…$', 55 pcall_err( 56 request, 57 'nvim_eval', 58 [=[rpcrequest(sockconnect('pipe', v:servername, {'rpc':1}), '… の り 。…')]=] 59 ) 60 ) 61 matches( 62 'Invalid method: bogus$', 63 pcall_err( 64 request, 65 'nvim_eval', 66 [=[rpcrequest(sockconnect('pipe', v:servername, {'rpc':1}), 'bogus')]=] 67 ) 68 ) 69 70 -- XXX: This must be the last one, else next one will fail: 71 -- "Packer instance already working. Use another Packer ..." 72 matches("can't serialize object of type .$", pcall_err(request, nil)) 73 end) 74 75 it('handles errors in async requests', function() 76 local error_types = api.nvim_get_api_info()[2].error_types 77 nvim_async('bogus') 78 eq({ 79 'notification', 80 'nvim_error_event', 81 { error_types.Exception.id, 'Invalid method: bogus' }, 82 }, next_msg()) 83 -- error didn't close channel. 84 assert_alive() 85 end) 86 87 it('failed async request emits nvim_error_event', function() 88 local error_types = api.nvim_get_api_info()[2].error_types 89 async_meths.nvim_command('bogus') 90 eq({ 91 'notification', 92 'nvim_error_event', 93 { error_types.Exception.id, 'Vim:E492: Not an editor command: bogus' }, 94 }, next_msg()) 95 -- error didn't close channel. 96 assert_alive() 97 end) 98 99 it('input is processed first if followed immediately by non-fast events', function() 100 api.nvim_set_current_line('ab') 101 async_meths.nvim_input('x') 102 async_meths.nvim_exec_lua('_G.res1 = vim.api.nvim_get_current_line()', {}) 103 async_meths.nvim_exec_lua('_G.res2 = vim.api.nvim_get_current_line()', {}) 104 eq({ 'b', 'b' }, exec_lua('return { _G.res1, _G.res2 }')) 105 -- Also test with getchar() 106 async_meths.nvim_command('let g:getchar = 1 | call getchar() | let g:getchar = 0') 107 eq(1, api.nvim_get_var('getchar')) 108 async_meths.nvim_input('x') 109 async_meths.nvim_exec_lua('_G.res1 = vim.g.getchar', {}) 110 async_meths.nvim_exec_lua('_G.res2 = vim.g.getchar', {}) 111 eq({ 0, 0 }, exec_lua('return { _G.res1, _G.res2 }')) 112 end) 113 114 it('does not set CA_COMMAND_BUSY #7254', function() 115 command('split') 116 command('autocmd WinEnter * startinsert') 117 command('wincmd w') 118 eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) 119 end) 120 121 describe('nvim_exec2', function() 122 it('always returns table', function() 123 -- In built version this results into `vim.empty_dict()` 124 eq({}, api.nvim_exec2('echo "Hello"', {})) 125 eq({}, api.nvim_exec2('echo "Hello"', { output = false })) 126 eq({ output = 'Hello' }, api.nvim_exec2('echo "Hello"', { output = true })) 127 end) 128 129 it('default options', function() 130 -- Should be equivalent to { output = false } 131 api.nvim_exec2("let x0 = 'a'", {}) 132 eq('a', api.nvim_get_var('x0')) 133 end) 134 135 it('one-line input', function() 136 api.nvim_exec2("let x1 = 'a'", { output = false }) 137 eq('a', api.nvim_get_var('x1')) 138 end) 139 140 it(':verbose set {option}?', function() 141 api.nvim_exec2('set nowrap', { output = false }) 142 eq( 143 { output = 'nowrap\n\tLast set from anonymous :source line 1' }, 144 api.nvim_exec2('verbose set wrap?', { output = true }) 145 ) 146 147 -- Using script var to force creation of a script item 148 api.nvim_exec2( 149 [[ 150 let s:a = 1 151 set nowrap 152 ]], 153 { output = false } 154 ) 155 eq( 156 { output = 'nowrap\n\tLast set from anonymous :source (script id 1) line 2' }, 157 api.nvim_exec2('verbose set wrap?', { output = true }) 158 ) 159 end) 160 161 it('multiline input', function() 162 -- Heredoc + empty lines. 163 api.nvim_exec2("let x2 = 'a'\n", { output = false }) 164 eq('a', api.nvim_get_var('x2')) 165 api.nvim_exec2('lua <<EOF\n\n\n\ny=3\n\n\nEOF', { output = false }) 166 eq(3, api.nvim_eval("luaeval('y')")) 167 168 eq({}, api.nvim_exec2('lua <<EOF\ny=3\nEOF', { output = false })) 169 eq(3, api.nvim_eval("luaeval('y')")) 170 171 -- Multiple statements 172 api.nvim_exec2('let x1=1\nlet x2=2\nlet x3=3\n', { output = false }) 173 eq(1, api.nvim_eval('x1')) 174 eq(2, api.nvim_eval('x2')) 175 eq(3, api.nvim_eval('x3')) 176 177 -- Functions 178 api.nvim_exec2('function Foo()\ncall setline(1,["xxx"])\nendfunction', { output = false }) 179 eq('', api.nvim_get_current_line()) 180 api.nvim_exec2('call Foo()', { output = false }) 181 eq('xxx', api.nvim_get_current_line()) 182 183 -- Autocmds 184 api.nvim_exec2('autocmd BufAdd * :let x1 = "Hello"', { output = false }) 185 command('new foo') 186 eq('Hello', request('nvim_eval', 'g:x1')) 187 188 -- Line continuations 189 api.nvim_exec2( 190 [[ 191 let abc = #{ 192 \ a: 1, 193 "\ b: 2, 194 \ c: 3 195 \ }]], 196 { output = false } 197 ) 198 eq({ a = 1, c = 3 }, request('nvim_eval', 'g:abc')) 199 200 -- try no spaces before continuations to catch off-by-one error 201 api.nvim_exec2('let ab = #{\n\\a: 98,\n"\\ b: 2\n\\}', { output = false }) 202 eq({ a = 98 }, request('nvim_eval', 'g:ab')) 203 204 -- Script scope (s:) 205 eq( 206 { output = 'ahoy! script-scoped varrrrr' }, 207 api.nvim_exec2( 208 [[ 209 let s:pirate = 'script-scoped varrrrr' 210 function! s:avast_ye_hades(s) abort 211 return a:s .. ' ' .. s:pirate 212 endfunction 213 echo <sid>avast_ye_hades('ahoy!') 214 ]], 215 { output = true } 216 ) 217 ) 218 219 eq( 220 { output = "{'output': 'ahoy! script-scoped varrrrr'}" }, 221 api.nvim_exec2( 222 [[ 223 let s:pirate = 'script-scoped varrrrr' 224 function! Avast_ye_hades(s) abort 225 return a:s .. ' ' .. s:pirate 226 endfunction 227 echo nvim_exec2('echo Avast_ye_hades(''ahoy!'')', {'output': v:true}) 228 ]], 229 { output = true } 230 ) 231 ) 232 233 matches( 234 'Vim%(echo%):E121: Undefined variable: s:pirate$', 235 pcall_err( 236 request, 237 'nvim_exec2', 238 [[ 239 let s:pirate = 'script-scoped varrrrr' 240 call nvim_exec2('echo s:pirate', {'output': v:true}) 241 ]], 242 { output = false } 243 ) 244 ) 245 246 -- Script items are created only on script var access 247 eq( 248 { output = '1\n0' }, 249 api.nvim_exec2( 250 [[ 251 echo expand("<SID>")->empty() 252 let s:a = 123 253 echo expand("<SID>")->empty() 254 ]], 255 { output = true } 256 ) 257 ) 258 259 eq( 260 { output = '1\n0' }, 261 api.nvim_exec2( 262 [[ 263 echo expand("<SID>")->empty() 264 function s:a() abort 265 endfunction 266 echo expand("<SID>")->empty() 267 ]], 268 { output = true } 269 ) 270 ) 271 end) 272 273 it('non-ASCII input', function() 274 api.nvim_exec2( 275 [=[ 276 new 277 exe "normal! i ax \n Ax " 278 :%s/ax/--a1234--/g | :%s/Ax/--A1234--/g 279 ]=], 280 { output = false } 281 ) 282 command('1') 283 eq(' --a1234-- ', api.nvim_get_current_line()) 284 command('2') 285 eq(' --A1234-- ', api.nvim_get_current_line()) 286 287 api.nvim_exec2( 288 [[ 289 new 290 call setline(1,['xxx']) 291 call feedkeys('r') 292 call feedkeys('ñ', 'xt') 293 ]], 294 { output = false } 295 ) 296 eq('ñxx', api.nvim_get_current_line()) 297 end) 298 299 it('can use :finish', function() 300 api.nvim_exec2('let g:var = 123\nfinish\nlet g:var = 456', {}) 301 eq(123, api.nvim_get_var('var')) 302 end) 303 304 it('execution error', function() 305 eq( 306 'nvim_exec2(), line 1: Vim:E492: Not an editor command: bogus_command', 307 pcall_err(request, 'nvim_exec2', 'bogus_command', {}) 308 ) 309 eq('', api.nvim_eval('v:errmsg')) -- v:errmsg was not updated. 310 eq('', eval('v:exception')) 311 312 eq( 313 'nvim_exec2(), line 1: Vim(buffer):E86: Buffer 23487 does not exist', 314 pcall_err(request, 'nvim_exec2', 'buffer 23487', {}) 315 ) 316 eq('', eval('v:errmsg')) -- v:errmsg was not updated. 317 eq('', eval('v:exception')) 318 end) 319 320 it('recursion', function() 321 local fname = tmpname() 322 write_file(fname, 'let x1 = "set from :source file"\n') 323 -- nvim_exec2 324 -- :source 325 -- nvim_exec2 326 request('nvim_exec2', [[ 327 let x2 = substitute('foo','o','X','g') 328 let x4 = 'should be overwritten' 329 call nvim_exec2("source ]] .. fname .. [[\nlet x3 = substitute('foo','foo','set by recursive nvim_exec2','g')\nlet x5='overwritten'\nlet x4=x5\n", {'output': v:false}) 330 ]], { output = false }) 331 eq('set from :source file', request('nvim_get_var', 'x1')) 332 eq('fXX', request('nvim_get_var', 'x2')) 333 eq('set by recursive nvim_exec2', request('nvim_get_var', 'x3')) 334 eq('overwritten', request('nvim_get_var', 'x4')) 335 eq('overwritten', request('nvim_get_var', 'x5')) 336 os.remove(fname) 337 end) 338 339 it('traceback', function() 340 local fname = tmpname() 341 write_file(fname, 'echo "hello"\n') 342 local sourcing_fname = tmpname() 343 write_file(sourcing_fname, 'call nvim_exec2("source ' .. fname .. '", {"output": v:false})\n') 344 api.nvim_exec2('set verbose=2', { output = false }) 345 local traceback_output = dedent([[ 346 sourcing "nvim_exec2()" 347 line 1: sourcing "nvim_exec2() called at nvim_exec2():1" 348 line 1: sourcing "%s" 349 line 1: sourcing "nvim_exec2() called at %s:1" 350 line 1: sourcing "%s" 351 hello 352 finished sourcing %s 353 continuing in nvim_exec2() called at %s:1 354 finished sourcing nvim_exec2() called at %s:1 355 continuing in %s 356 finished sourcing %s 357 continuing in nvim_exec2() called at nvim_exec2():1 358 finished sourcing nvim_exec2() called at nvim_exec2():1 359 continuing in nvim_exec2() 360 finished sourcing nvim_exec2()]]):format( 361 sourcing_fname, 362 sourcing_fname, 363 fname, 364 fname, 365 sourcing_fname, 366 sourcing_fname, 367 sourcing_fname, 368 sourcing_fname 369 ) 370 eq( 371 { output = traceback_output }, 372 api.nvim_exec2( 373 'call nvim_exec2("source ' .. sourcing_fname .. '", {"output": v:false})', 374 { output = true } 375 ) 376 ) 377 os.remove(fname) 378 os.remove(sourcing_fname) 379 end) 380 381 it('returns output', function() 382 eq( 383 { output = 'this is spinal tap' }, 384 api.nvim_exec2('lua <<EOF\n\n\nprint("this is spinal tap")\n\n\nEOF', { output = true }) 385 ) 386 eq({ output = '' }, api.nvim_exec2('echo', { output = true })) 387 eq({ output = 'foo 42' }, api.nvim_exec2('echo "foo" 42', { output = true })) 388 -- Returns output in cmdline mode #35321 389 feed(':') 390 eq({ output = 'foo 42' }, api.nvim_exec2('echo "foo" 42', { output = true })) 391 end) 392 393 it('displays messages when opts.output=false', function() 394 local screen = Screen.new(40, 8) 395 api.nvim_exec2("echo 'hello'", { output = false }) 396 screen:expect { 397 grid = [[ 398 ^ | 399 {1:~ }|*6 400 hello | 401 ]], 402 } 403 end) 404 405 it("doesn't display messages when output=true", function() 406 local screen = Screen.new(40, 6) 407 api.nvim_exec2("echo 'hello'", { output = true }) 408 screen:expect { 409 grid = [[ 410 ^ | 411 {1:~ }|*4 412 | 413 ]], 414 } 415 exec([[ 416 func Print() 417 call nvim_exec2('echo "hello"', { 'output': v:true }) 418 endfunc 419 ]]) 420 feed([[:echon 1 | call Print() | echon 5<CR>]]) 421 screen:expect { 422 grid = [[ 423 ^ | 424 {1:~ }|*4 425 15 | 426 ]], 427 } 428 end) 429 430 it('errors properly when command too recursive', function() 431 exec_lua([[ 432 _G.success = false 433 vim.api.nvim_create_user_command('Test', function() 434 vim.api.nvim_exec2('Test', {}) 435 _G.success = true 436 end, {}) 437 ]]) 438 pcall_err(command, 'Test') 439 assert_alive() 440 eq(false, exec_lua('return _G.success')) 441 end) 442 443 it('redir_write() message column is reset with ext_messages', function() 444 exec_lua('vim.ui_attach(1, { ext_messages = true }, function() end)') 445 api.nvim_exec2('hi VisualNC', { output = true }) 446 eq('VisualNC xxx cleared', api.nvim_exec2('hi VisualNC', { output = true }).output) 447 end) 448 end) 449 450 describe('nvim_command', function() 451 it('works', function() 452 local fname = tmpname() 453 command('new') 454 command('edit ' .. fname) 455 command('normal itesting\napi') 456 command('w') 457 local f = assert(io.open(fname)) 458 if is_os('win') then 459 eq('testing\r\napi\r\n', f:read('*a')) 460 else 461 eq('testing\napi\n', f:read('*a')) 462 end 463 f:close() 464 os.remove(fname) 465 end) 466 467 it('Vimscript validation error: fails with specific error', function() 468 local status, rv = pcall(command, 'bogus_command') 469 eq(false, status) -- nvim_command() failed. 470 eq('E492:', string.match(rv, 'E%d*:')) -- Vimscript error was returned. 471 eq('', api.nvim_eval('v:errmsg')) -- v:errmsg was not updated. 472 eq('', eval('v:exception')) 473 end) 474 475 it('Vimscript execution error: fails with specific error', function() 476 local status, rv = pcall(command, 'buffer 23487') 477 eq(false, status) -- nvim_command() failed. 478 eq('E86: Buffer 23487 does not exist', string.match(rv, 'E%d*:.*')) 479 eq('', eval('v:errmsg')) -- v:errmsg was not updated. 480 eq('', eval('v:exception')) 481 end) 482 483 it('gives E493 instead of prompting on backwards range', function() 484 command('split') 485 eq( 486 'Vim(windo):E493: Backwards range given: 2,1windo echo', 487 pcall_err(command, '2,1windo echo') 488 ) 489 end) 490 end) 491 492 describe('nvim_command_output', function() 493 it('does not induce hit-enter prompt', function() 494 api.nvim_ui_attach(80, 20, {}) 495 -- Induce a hit-enter prompt use nvim_input (non-blocking). 496 command('set cmdheight=1') 497 api.nvim_input([[:echo "hi\nhi2"<CR>]]) 498 499 -- Verify hit-enter prompt. 500 eq({ mode = 'r', blocking = true }, api.nvim_get_mode()) 501 api.nvim_input([[<C-c>]]) 502 503 -- Verify NO hit-enter prompt. 504 command_output([[echo "hi\nhi2"]]) 505 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 506 end) 507 508 it('captures command output', function() 509 eq('this is\nspinal tap', command_output([[echo "this is\nspinal tap"]])) 510 eq('no line ending!', command_output([[echon "no line ending!"]])) 511 end) 512 513 it('captures empty command output', function() 514 eq('', command_output('echo')) 515 end) 516 517 it('captures single-char command output', function() 518 eq('x', command_output('echo "x"')) 519 end) 520 521 it('captures multiple commands', function() 522 eq('foo\n 1 %a "[No Name]" line 1', command_output('echo "foo" | ls')) 523 end) 524 525 it('captures nested execute()', function() 526 eq( 527 '\nnested1\nnested2\n 1 %a "[No Name]" line 1', 528 command_output([[echo execute('echo "nested1\nnested2"') | ls]]) 529 ) 530 end) 531 532 it('captures nested nvim_command_output()', function() 533 eq( 534 'nested1\nnested2\n 1 %a "[No Name]" line 1', 535 command_output([[echo nvim_command_output('echo "nested1\nnested2"') | ls]]) 536 ) 537 end) 538 539 it('returns shell |:!| output', function() 540 local win_lf = is_os('win') and '\r' or '' 541 eq(':!echo foo\r\n\nfoo' .. win_lf .. '\n', command_output([[!echo foo]])) 542 end) 543 544 it('Vimscript validation error: fails with specific error', function() 545 local status, rv = pcall(command_output, 'bogus commannnd') 546 eq(false, status) -- nvim_command_output() failed. 547 eq('E492: Not an editor command: bogus commannnd', string.match(rv, 'E%d*:.*')) 548 eq('', eval('v:errmsg')) -- v:errmsg was not updated. 549 -- Verify NO hit-enter prompt. 550 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 551 end) 552 553 it('Vimscript execution error: fails with specific error', function() 554 local status, rv = pcall(command_output, 'buffer 42') 555 eq(false, status) -- nvim_command_output() failed. 556 eq('E86: Buffer 42 does not exist', string.match(rv, 'E%d*:.*')) 557 eq('', eval('v:errmsg')) -- v:errmsg was not updated. 558 -- Verify NO hit-enter prompt. 559 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 560 end) 561 562 it('does not cause heap buffer overflow with large output', function() 563 eq(eval('string(range(1000000))'), command_output('echo range(1000000)')) 564 end) 565 end) 566 567 describe('nvim_eval', function() 568 it('works', function() 569 command('let g:v1 = "a"') 570 command('let g:v2 = [1, 2, {"v3": 3}]') 571 eq({ v1 = 'a', v2 = { 1, 2, { v3 = 3 } } }, api.nvim_eval('g:')) 572 end) 573 574 it('handles NULL-initialized strings correctly', function() 575 eq(1, api.nvim_eval("matcharg(1) == ['', '']")) 576 eq({ '', '' }, api.nvim_eval('matcharg(1)')) 577 end) 578 579 it('works under deprecated name', function() 580 eq(2, request('vim_eval', '1+1')) 581 end) 582 583 it('Vimscript error: returns error details, does NOT update v:errmsg', function() 584 eq('Vim:E121: Undefined variable: bogus', pcall_err(request, 'nvim_eval', 'bogus expression')) 585 eq('', eval('v:errmsg')) -- v:errmsg was not updated. 586 end) 587 588 it('can return Lua function to Lua code', function() 589 eq( 590 [["a string with \"double quotes\" and 'single quotes'"]], 591 exec_lua([=[ 592 local fun = vim.api.nvim_eval([[luaeval('string.format')]]) 593 return fun('%q', [[a string with "double quotes" and 'single quotes']]) 594 ]=]) 595 ) 596 end) 597 end) 598 599 describe('nvim_call_function', function() 600 it('works', function() 601 api.nvim_call_function('setqflist', { { { filename = 'something', lnum = 17 } }, 'r' }) 602 eq(17, api.nvim_call_function('getqflist', {})[1].lnum) 603 eq(17, api.nvim_call_function('eval', { 17 })) 604 eq('foo', api.nvim_call_function('simplify', { 'this/./is//redundant/../../../foo' })) 605 end) 606 607 it('Vimscript validation error: returns specific error, does NOT update v:errmsg', function() 608 eq( 609 'Vim:E117: Unknown function: bogus function', 610 pcall_err(request, 'nvim_call_function', 'bogus function', { 'arg1' }) 611 ) 612 eq( 613 'Vim:E119: Not enough arguments for function: atan', 614 pcall_err(request, 'nvim_call_function', 'atan', {}) 615 ) 616 eq('', eval('v:exception')) 617 eq('', eval('v:errmsg')) -- v:errmsg was not updated. 618 end) 619 620 it('Vimscript error: returns error details, does NOT update v:errmsg', function() 621 eq( 622 'Vim:E808: Number or Float required', 623 pcall_err(request, 'nvim_call_function', 'atan', { 'foo' }) 624 ) 625 eq( 626 'Vim:Invalid channel stream "xxx"', 627 pcall_err(request, 'nvim_call_function', 'chanclose', { 999, 'xxx' }) 628 ) 629 eq( 630 'Vim:E900: Invalid channel id', 631 pcall_err(request, 'nvim_call_function', 'chansend', { 999, 'foo' }) 632 ) 633 eq('', eval('v:exception')) 634 eq('', eval('v:errmsg')) -- v:errmsg was not updated. 635 end) 636 637 it('Vimscript exception: returns exception details, does NOT update v:errmsg', function() 638 source([[ 639 function! Foo() abort 640 throw 'wtf' 641 endfunction 642 ]]) 643 eq('function Foo, line 1: wtf', pcall_err(request, 'nvim_call_function', 'Foo', {})) 644 eq('', eval('v:exception')) 645 eq('', eval('v:errmsg')) -- v:errmsg was not updated. 646 end) 647 648 it('validation', function() 649 -- stylua: ignore 650 local too_many_args = { 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x' } 651 source([[ 652 function! Foo(...) abort 653 echo a:000 654 endfunction 655 ]]) 656 -- E740 657 eq( 658 'Function called with too many arguments', 659 pcall_err(request, 'nvim_call_function', 'Foo', too_many_args) 660 ) 661 end) 662 663 it('can return Lua function to Lua code', function() 664 eq( 665 [["a string with \"double quotes\" and 'single quotes'"]], 666 exec_lua([=[ 667 local fun = vim.api.nvim_call_function('luaeval', { 'string.format' }) 668 return fun('%q', [[a string with "double quotes" and 'single quotes']]) 669 ]=]) 670 ) 671 end) 672 end) 673 674 describe('nvim_call_dict_function', function() 675 it('invokes Vimscript dict function', function() 676 source([[ 677 function! F(name) dict 678 return self.greeting.', '.a:name.'!' 679 endfunction 680 let g:test_dict_fn = { 'greeting':'Hello', 'F':function('F') } 681 682 let g:test_dict_fn2 = { 'greeting':'Hi' } 683 function g:test_dict_fn2.F2(name) 684 return self.greeting.', '.a:name.' ...' 685 endfunction 686 ]]) 687 688 -- :help Dictionary-function 689 eq('Hello, World!', api.nvim_call_dict_function('g:test_dict_fn', 'F', { 'World' })) 690 -- Funcref is sent as NIL over RPC. 691 eq({ greeting = 'Hello', F = NIL }, api.nvim_get_var('test_dict_fn')) 692 693 -- :help numbered-function 694 eq('Hi, Moon ...', api.nvim_call_dict_function('g:test_dict_fn2', 'F2', { 'Moon' })) 695 -- Funcref is sent as NIL over RPC. 696 eq({ greeting = 'Hi', F2 = NIL }, api.nvim_get_var('test_dict_fn2')) 697 698 -- Function specified via RPC dict. 699 source('function! G() dict\n return "@".(self.result)."@"\nendfunction') 700 eq('@it works@', api.nvim_call_dict_function({ result = 'it works', G = 'G' }, 'G', {})) 701 end) 702 703 it('validation', function() 704 command('let g:d={"baz":"zub","meep":[]}') 705 eq( 706 'Not found: bogus', 707 pcall_err(request, 'nvim_call_dict_function', 'g:d', 'bogus', { 1, 2 }) 708 ) 709 eq( 710 'Not a function: baz', 711 pcall_err(request, 'nvim_call_dict_function', 'g:d', 'baz', { 1, 2 }) 712 ) 713 eq( 714 'Not a function: meep', 715 pcall_err(request, 'nvim_call_dict_function', 'g:d', 'meep', { 1, 2 }) 716 ) 717 eq( 718 'Vim:E117: Unknown function: f', 719 pcall_err(request, 'nvim_call_dict_function', { f = '' }, 'f', { 1, 2 }) 720 ) 721 eq( 722 'Not a function: f', 723 pcall_err(request, 'nvim_call_dict_function', "{ 'f': '' }", 'f', { 1, 2 }) 724 ) 725 eq( 726 'dict argument type must be String or Dict', 727 pcall_err(request, 'nvim_call_dict_function', 42, 'f', { 1, 2 }) 728 ) 729 eq( 730 'Vim:E121: Undefined variable: foo', 731 pcall_err(request, 'nvim_call_dict_function', 'foo', 'f', { 1, 2 }) 732 ) 733 eq('dict not found', pcall_err(request, 'nvim_call_dict_function', '42', 'f', { 1, 2 })) 734 eq( 735 'Invalid (empty) function name', 736 pcall_err(request, 'nvim_call_dict_function', "{ 'f': '' }", '', { 1, 2 }) 737 ) 738 end) 739 end) 740 741 describe('nvim_set_current_dir', function() 742 local start_dir 743 local test_dir = 'Xtest_set_current_dir' 744 745 before_each(function() 746 fn.mkdir(test_dir) 747 start_dir = fn.getcwd() 748 end) 749 750 after_each(function() 751 n.rmdir(test_dir) 752 end) 753 754 it('works', function() 755 api.nvim_set_current_dir(test_dir) 756 eq(start_dir .. n.get_pathsep() .. test_dir, fn.getcwd()) 757 end) 758 759 it('sets previous directory', function() 760 api.nvim_set_current_dir(test_dir) 761 command('cd -') 762 eq(start_dir, fn.getcwd()) 763 end) 764 end) 765 766 describe('nvim_exec_lua', function() 767 it('works', function() 768 api.nvim_exec_lua('vim.api.nvim_set_var("test", 3)', {}) 769 eq(3, api.nvim_get_var('test')) 770 771 eq(17, api.nvim_exec_lua('a, b = ...\nreturn a + b', { 10, 7 })) 772 773 eq(NIL, api.nvim_exec_lua('function xx(a,b)\nreturn a..b\nend', {})) 774 eq('xy', api.nvim_exec_lua('return xx(...)', { 'x', 'y' })) 775 776 -- Deprecated name: nvim_execute_lua. 777 eq('xy', api.nvim_execute_lua('return xx(...)', { 'x', 'y' })) 778 end) 779 780 it('reports errors', function() 781 eq([['=' expected near '+']], pcall_err(api.nvim_exec_lua, 'a+*b', {})) 782 eq([[unexpected symbol near '1']], pcall_err(api.nvim_exec_lua, '1+2', {})) 783 eq([[unexpected symbol]], pcall_err(api.nvim_exec_lua, 'aa=bb\0', {})) 784 eq( 785 [[attempt to call global 'bork' (a nil value)]], 786 pcall_err(api.nvim_exec_lua, 'bork()', {}) 787 ) 788 eq('did\nthe\nfail', pcall_err(api.nvim_exec_lua, 'error("did\\nthe\\nfail")', {})) 789 end) 790 791 it('uses native float values', function() 792 eq(2.5, api.nvim_exec_lua('return select(1, ...)', { 2.5 })) 793 eq('2.5', api.nvim_exec_lua('return vim.inspect(...)', { 2.5 })) 794 795 -- "special" float values are still accepted as return values. 796 eq(2.5, api.nvim_exec_lua("return vim.api.nvim_eval('2.5')", {})) 797 eq( 798 '{\n [false] = 2.5,\n [true] = 3\n}', 799 api.nvim_exec_lua("return vim.inspect(vim.api.nvim_eval('2.5'))", {}) 800 ) 801 end) 802 end) 803 804 describe('nvim_input', function() 805 it('Vimscript error: does NOT fail, updates v:errmsg', function() 806 local status, _ = pcall(api.nvim_input, ':call bogus_fn()<CR>') 807 local v_errnum = string.match(api.nvim_eval('v:errmsg'), 'E%d*:') 808 eq(true, status) -- nvim_input() did not fail. 809 eq('E117:', v_errnum) -- v:errmsg was updated. 810 end) 811 812 it('does not crash even if trans_special result is largest #11788, #12287', function() 813 command("call nvim_input('<M-'.nr2char(0x40000000).'>')") 814 eq(1, eval('1')) 815 end) 816 end) 817 818 describe('nvim_paste', function() 819 it('validation', function() 820 eq("Invalid 'phase': -2", pcall_err(request, 'nvim_paste', 'foo', true, -2)) 821 eq("Invalid 'phase': 4", pcall_err(request, 'nvim_paste', 'foo', true, 4)) 822 end) 823 local function run_streamed_paste_tests() 824 it('stream: multiple chunks form one undo-block', function() 825 api.nvim_paste('1/chunk 1 (start)\n', true, 1) 826 api.nvim_paste('1/chunk 2 (end)\n', true, 3) 827 local expected1 = [[ 828 1/chunk 1 (start) 829 1/chunk 2 (end) 830 ]] 831 expect(expected1) 832 api.nvim_paste('2/chunk 1 (start)\n', true, 1) 833 api.nvim_paste('2/chunk 2\n', true, 2) 834 expect([[ 835 1/chunk 1 (start) 836 1/chunk 2 (end) 837 2/chunk 1 (start) 838 2/chunk 2 839 ]]) 840 api.nvim_paste('2/chunk 3\n', true, 2) 841 api.nvim_paste('2/chunk 4 (end)\n', true, 3) 842 expect([[ 843 1/chunk 1 (start) 844 1/chunk 2 (end) 845 2/chunk 1 (start) 846 2/chunk 2 847 2/chunk 3 848 2/chunk 4 (end) 849 ]]) 850 feed('u') -- Undo. 851 expect(expected1) 852 end) 853 it("stream: multiple chunks sets correct '[ mark", function() 854 -- Pastes single chunk 855 api.nvim_paste('aaaaaa\n', true, -1) 856 eq({ 0, 1, 1, 0 }, fn.getpos("'[")) 857 -- Pastes an empty chunk 858 api.nvim_paste('', true, -1) 859 eq({ 0, 2, 1, 0 }, fn.getpos("'[")) 860 -- Pastes some chunks on empty line 861 api.nvim_paste('1/chunk 1 (start)\n', true, 1) 862 eq({ 0, 2, 1, 0 }, fn.getpos("'[")) 863 api.nvim_paste('1/chunk 2\n', true, 2) 864 eq({ 0, 2, 1, 0 }, fn.getpos("'[")) 865 api.nvim_paste('1/chunk 3 (end)\n', true, 3) 866 eq({ 0, 2, 1, 0 }, fn.getpos("'[")) 867 -- Pastes some chunks on non-empty line 868 api.nvim_paste('aaaaaa', true, -1) 869 eq({ 0, 5, 1, 0 }, fn.getpos("'[")) 870 api.nvim_paste('bbbbbb', true, 1) 871 eq({ 0, 5, 7, 0 }, fn.getpos("'[")) 872 api.nvim_paste('cccccc', true, 2) 873 eq({ 0, 5, 7, 0 }, fn.getpos("'[")) 874 api.nvim_paste('dddddd\n', true, 3) 875 eq({ 0, 5, 7, 0 }, fn.getpos("'[")) 876 -- Pastes some empty chunks between non-empty chunks 877 api.nvim_paste('', true, 1) 878 eq({ 0, 5, 7, 0 }, fn.getpos("'[")) 879 api.nvim_paste('a', true, 2) 880 eq({ 0, 6, 1, 0 }, fn.getpos("'[")) 881 api.nvim_paste('', true, 2) 882 eq({ 0, 6, 1, 0 }, fn.getpos("'[")) 883 api.nvim_paste('a', true, 3) 884 eq({ 0, 6, 1, 0 }, fn.getpos("'[")) 885 end) 886 it('stream: Insert mode', function() 887 -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. 888 feed('afoo<Esc>u') 889 feed('i') 890 api.nvim_paste('aaaaaa', false, 1) 891 api.nvim_paste('bbbbbb', false, 2) 892 api.nvim_paste('cccccc', false, 2) 893 api.nvim_paste('dddddd', false, 3) 894 expect('aaaaaabbbbbbccccccdddddd') 895 feed('<Esc>u') 896 expect('') 897 end) 898 describe('stream: Normal mode', function() 899 describe('on empty line', function() 900 before_each(function() 901 -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. 902 feed('afoo<Esc>u') 903 end) 904 after_each(function() 905 feed('u') 906 expect('') 907 end) 908 it('pasting one line', function() 909 api.nvim_paste('aaaaaa', false, 1) 910 api.nvim_paste('bbbbbb', false, 2) 911 api.nvim_paste('cccccc', false, 2) 912 api.nvim_paste('dddddd', false, 3) 913 expect('aaaaaabbbbbbccccccdddddd') 914 end) 915 it('pasting multiple lines', function() 916 api.nvim_paste('aaaaaa\n', false, 1) 917 api.nvim_paste('bbbbbb\n', false, 2) 918 api.nvim_paste('cccccc\n', false, 2) 919 api.nvim_paste('dddddd', false, 3) 920 expect([[ 921 aaaaaa 922 bbbbbb 923 cccccc 924 dddddd]]) 925 end) 926 end) 927 describe('not at the end of a line', function() 928 before_each(function() 929 feed('i||<Esc>') 930 -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. 931 feed('afoo<Esc>u') 932 feed('0') 933 end) 934 after_each(function() 935 feed('u') 936 expect('||') 937 end) 938 it('pasting one line', function() 939 api.nvim_paste('aaaaaa', false, 1) 940 api.nvim_paste('bbbbbb', false, 2) 941 api.nvim_paste('cccccc', false, 2) 942 api.nvim_paste('dddddd', false, 3) 943 expect('|aaaaaabbbbbbccccccdddddd|') 944 end) 945 it('pasting multiple lines', function() 946 api.nvim_paste('aaaaaa\n', false, 1) 947 api.nvim_paste('bbbbbb\n', false, 2) 948 api.nvim_paste('cccccc\n', false, 2) 949 api.nvim_paste('dddddd', false, 3) 950 expect([[ 951 |aaaaaa 952 bbbbbb 953 cccccc 954 dddddd|]]) 955 end) 956 end) 957 describe('at the end of a line', function() 958 before_each(function() 959 feed('i||<Esc>') 960 -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. 961 feed('afoo<Esc>u') 962 feed('2|') 963 end) 964 after_each(function() 965 feed('u') 966 expect('||') 967 end) 968 it('pasting one line', function() 969 api.nvim_paste('aaaaaa', false, 1) 970 api.nvim_paste('bbbbbb', false, 2) 971 api.nvim_paste('cccccc', false, 2) 972 api.nvim_paste('dddddd', false, 3) 973 expect('||aaaaaabbbbbbccccccdddddd') 974 end) 975 it('pasting multiple lines', function() 976 api.nvim_paste('aaaaaa\n', false, 1) 977 api.nvim_paste('bbbbbb\n', false, 2) 978 api.nvim_paste('cccccc\n', false, 2) 979 api.nvim_paste('dddddd', false, 3) 980 expect([[ 981 ||aaaaaa 982 bbbbbb 983 cccccc 984 dddddd]]) 985 end) 986 end) 987 end) 988 describe('stream: Visual mode', function() 989 describe('neither end at the end of a line', function() 990 before_each(function() 991 feed('i|xxx<CR>xxx|<Esc>') 992 -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. 993 feed('afoo<Esc>u') 994 feed('3|vhk') 995 end) 996 after_each(function() 997 feed('u') 998 expect([[ 999 |xxx 1000 xxx|]]) 1001 end) 1002 it('with non-empty chunks', function() 1003 api.nvim_paste('aaaaaa', false, 1) 1004 api.nvim_paste('bbbbbb', false, 2) 1005 api.nvim_paste('cccccc', false, 2) 1006 api.nvim_paste('dddddd', false, 3) 1007 expect('|aaaaaabbbbbbccccccdddddd|') 1008 end) 1009 it('with empty first chunk', function() 1010 api.nvim_paste('', false, 1) 1011 api.nvim_paste('bbbbbb', false, 2) 1012 api.nvim_paste('cccccc', false, 2) 1013 api.nvim_paste('dddddd', false, 3) 1014 expect('|bbbbbbccccccdddddd|') 1015 end) 1016 it('with all chunks empty', function() 1017 api.nvim_paste('', false, 1) 1018 api.nvim_paste('', false, 2) 1019 api.nvim_paste('', false, 2) 1020 api.nvim_paste('', false, 3) 1021 expect('||') 1022 end) 1023 end) 1024 describe('cursor at the end of a line', function() 1025 before_each(function() 1026 feed('i||xxx<CR>xxx<Esc>') 1027 -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. 1028 feed('afoo<Esc>u') 1029 feed('3|vko') 1030 end) 1031 after_each(function() 1032 feed('u') 1033 expect([[ 1034 ||xxx 1035 xxx]]) 1036 end) 1037 it('with non-empty chunks', function() 1038 api.nvim_paste('aaaaaa', false, 1) 1039 api.nvim_paste('bbbbbb', false, 2) 1040 api.nvim_paste('cccccc', false, 2) 1041 api.nvim_paste('dddddd', false, 3) 1042 expect('||aaaaaabbbbbbccccccdddddd') 1043 end) 1044 it('with empty first chunk', function() 1045 api.nvim_paste('', false, 1) 1046 api.nvim_paste('bbbbbb', false, 2) 1047 api.nvim_paste('cccccc', false, 2) 1048 api.nvim_paste('dddddd', false, 3) 1049 expect('||bbbbbbccccccdddddd') 1050 end) 1051 end) 1052 describe('other end at the end of a line', function() 1053 before_each(function() 1054 feed('i||xxx<CR>xxx<Esc>') 1055 -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. 1056 feed('afoo<Esc>u') 1057 feed('3|vk') 1058 end) 1059 after_each(function() 1060 feed('u') 1061 expect([[ 1062 ||xxx 1063 xxx]]) 1064 end) 1065 it('with non-empty chunks', function() 1066 api.nvim_paste('aaaaaa', false, 1) 1067 api.nvim_paste('bbbbbb', false, 2) 1068 api.nvim_paste('cccccc', false, 2) 1069 api.nvim_paste('dddddd', false, 3) 1070 expect('||aaaaaabbbbbbccccccdddddd') 1071 end) 1072 it('with empty first chunk', function() 1073 api.nvim_paste('', false, 1) 1074 api.nvim_paste('bbbbbb', false, 2) 1075 api.nvim_paste('cccccc', false, 2) 1076 api.nvim_paste('dddddd', false, 3) 1077 expect('||bbbbbbccccccdddddd') 1078 end) 1079 end) 1080 end) 1081 describe('stream: linewise Visual mode', function() 1082 before_each(function() 1083 feed('i123456789<CR>987654321<CR>123456789<Esc>') 1084 -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. 1085 feed('afoo<Esc>u') 1086 end) 1087 after_each(function() 1088 feed('u') 1089 expect([[ 1090 123456789 1091 987654321 1092 123456789]]) 1093 end) 1094 describe('selecting the start of a file', function() 1095 before_each(function() 1096 feed('ggV') 1097 end) 1098 it('pasting text without final new line', function() 1099 api.nvim_paste('aaaaaa\n', false, 1) 1100 api.nvim_paste('bbbbbb\n', false, 2) 1101 api.nvim_paste('cccccc\n', false, 2) 1102 api.nvim_paste('dddddd', false, 3) 1103 expect([[ 1104 aaaaaa 1105 bbbbbb 1106 cccccc 1107 dddddd987654321 1108 123456789]]) 1109 end) 1110 it('pasting text with final new line', function() 1111 api.nvim_paste('aaaaaa\n', false, 1) 1112 api.nvim_paste('bbbbbb\n', false, 2) 1113 api.nvim_paste('cccccc\n', false, 2) 1114 api.nvim_paste('dddddd\n', false, 3) 1115 expect([[ 1116 aaaaaa 1117 bbbbbb 1118 cccccc 1119 dddddd 1120 987654321 1121 123456789]]) 1122 end) 1123 end) 1124 describe('selecting the middle of a file', function() 1125 before_each(function() 1126 feed('2ggV') 1127 end) 1128 it('pasting text without final new line', function() 1129 api.nvim_paste('aaaaaa\n', false, 1) 1130 api.nvim_paste('bbbbbb\n', false, 2) 1131 api.nvim_paste('cccccc\n', false, 2) 1132 api.nvim_paste('dddddd', false, 3) 1133 expect([[ 1134 123456789 1135 aaaaaa 1136 bbbbbb 1137 cccccc 1138 dddddd123456789]]) 1139 end) 1140 it('pasting text with final new line', function() 1141 api.nvim_paste('aaaaaa\n', false, 1) 1142 api.nvim_paste('bbbbbb\n', false, 2) 1143 api.nvim_paste('cccccc\n', false, 2) 1144 api.nvim_paste('dddddd\n', false, 3) 1145 expect([[ 1146 123456789 1147 aaaaaa 1148 bbbbbb 1149 cccccc 1150 dddddd 1151 123456789]]) 1152 end) 1153 end) 1154 describe('selecting the end of a file', function() 1155 before_each(function() 1156 feed('3ggV') 1157 end) 1158 it('pasting text without final new line', function() 1159 api.nvim_paste('aaaaaa\n', false, 1) 1160 api.nvim_paste('bbbbbb\n', false, 2) 1161 api.nvim_paste('cccccc\n', false, 2) 1162 api.nvim_paste('dddddd', false, 3) 1163 expect([[ 1164 123456789 1165 987654321 1166 aaaaaa 1167 bbbbbb 1168 cccccc 1169 dddddd]]) 1170 end) 1171 it('pasting text with final new line', function() 1172 api.nvim_paste('aaaaaa\n', false, 1) 1173 api.nvim_paste('bbbbbb\n', false, 2) 1174 api.nvim_paste('cccccc\n', false, 2) 1175 api.nvim_paste('dddddd\n', false, 3) 1176 expect([[ 1177 123456789 1178 987654321 1179 aaaaaa 1180 bbbbbb 1181 cccccc 1182 dddddd 1183 ]]) 1184 end) 1185 end) 1186 describe('selecting the whole file', function() 1187 before_each(function() 1188 feed('ggVG') 1189 end) 1190 it('pasting text without final new line', function() 1191 api.nvim_paste('aaaaaa\n', false, 1) 1192 api.nvim_paste('bbbbbb\n', false, 2) 1193 api.nvim_paste('cccccc\n', false, 2) 1194 api.nvim_paste('dddddd', false, 3) 1195 expect([[ 1196 aaaaaa 1197 bbbbbb 1198 cccccc 1199 dddddd]]) 1200 end) 1201 it('pasting text with final new line', function() 1202 api.nvim_paste('aaaaaa\n', false, 1) 1203 api.nvim_paste('bbbbbb\n', false, 2) 1204 api.nvim_paste('cccccc\n', false, 2) 1205 api.nvim_paste('dddddd\n', false, 3) 1206 expect([[ 1207 aaaaaa 1208 bbbbbb 1209 cccccc 1210 dddddd 1211 ]]) 1212 end) 1213 end) 1214 end) 1215 end 1216 describe('without virtualedit,', function() 1217 run_streamed_paste_tests() 1218 end) 1219 describe('with virtualedit=onemore,', function() 1220 before_each(function() 1221 command('set virtualedit=onemore') 1222 end) 1223 run_streamed_paste_tests() 1224 end) 1225 it('non-streaming', function() 1226 -- With final "\n". 1227 api.nvim_paste('line 1\nline 2\nline 3\n', true, -1) 1228 expect([[ 1229 line 1 1230 line 2 1231 line 3 1232 ]]) 1233 eq({ 0, 4, 1, 0 }, fn.getpos('.')) -- Cursor follows the paste. 1234 eq(false, api.nvim_get_option_value('paste', {})) 1235 command('%delete _') 1236 -- Without final "\n". 1237 api.nvim_paste('line 1\nline 2\nline 3', true, -1) 1238 expect([[ 1239 line 1 1240 line 2 1241 line 3]]) 1242 eq({ 0, 3, 6, 0 }, fn.getpos('.')) 1243 command('%delete _') 1244 -- CRLF #10872 1245 api.nvim_paste('line 1\r\nline 2\r\nline 3\r\n', true, -1) 1246 expect([[ 1247 line 1 1248 line 2 1249 line 3 1250 ]]) 1251 eq({ 0, 4, 1, 0 }, fn.getpos('.')) 1252 command('%delete _') 1253 -- CRLF without final "\n". 1254 api.nvim_paste('line 1\r\nline 2\r\nline 3\r', true, -1) 1255 expect([[ 1256 line 1 1257 line 2 1258 line 3 1259 ]]) 1260 eq({ 0, 4, 1, 0 }, fn.getpos('.')) 1261 command('%delete _') 1262 -- CRLF without final "\r\n". 1263 api.nvim_paste('line 1\r\nline 2\r\nline 3', true, -1) 1264 expect([[ 1265 line 1 1266 line 2 1267 line 3]]) 1268 eq({ 0, 3, 6, 0 }, fn.getpos('.')) 1269 command('%delete _') 1270 -- Various other junk. 1271 api.nvim_paste('line 1\r\n\r\rline 2\nline 3\rline 4\r', true, -1) 1272 expect('line 1\n\n\nline 2\nline 3\nline 4\n') 1273 eq({ 0, 7, 1, 0 }, fn.getpos('.')) 1274 eq(false, api.nvim_get_option_value('paste', {})) 1275 end) 1276 it('Replace-mode', function() 1277 -- Within single line 1278 api.nvim_put({ 'aabbccdd', 'eeffgghh', 'iijjkkll' }, 'c', true, false) 1279 command('normal l') 1280 command('startreplace') 1281 api.nvim_paste('123456', true, -1) 1282 expect([[ 1283 a123456d 1284 eeffgghh 1285 iijjkkll]]) 1286 command('%delete _') 1287 -- Across lines 1288 api.nvim_put({ 'aabbccdd', 'eeffgghh', 'iijjkkll' }, 'c', true, false) 1289 command('normal l') 1290 command('startreplace') 1291 api.nvim_paste('123\n456', true, -1) 1292 expect([[ 1293 a123 1294 456d 1295 eeffgghh 1296 iijjkkll]]) 1297 end) 1298 it('when searching in Visual mode', function() 1299 feed('v/') 1300 api.nvim_paste('aabbccdd', true, -1) 1301 eq('aabbccdd', fn.getcmdline()) 1302 expect('') 1303 end) 1304 it('mappings are disabled in Cmdline mode', function() 1305 command('cnoremap a b') 1306 feed(':') 1307 api.nvim_paste('a', true, -1) 1308 eq('a', fn.getcmdline()) 1309 end) 1310 it('pasted text is saved in cmdline history when <CR> comes from mapping #20957', function() 1311 command('cnoremap <CR> <CR>') 1312 feed(':') 1313 api.nvim_paste('echo', true, -1) 1314 eq('', fn.histget(':')) 1315 feed('<CR>') 1316 eq('echo', fn.histget(':')) 1317 end) 1318 it('pasting with empty last chunk in Cmdline mode', function() 1319 local screen = Screen.new(20, 4) 1320 feed(':') 1321 api.nvim_paste('Foo', true, 1) 1322 api.nvim_paste('', true, 3) 1323 screen:expect([[ 1324 | 1325 {1:~ }|*2 1326 :Foo^ | 1327 ]]) 1328 end) 1329 it('pasting text with control characters in Cmdline mode', function() 1330 local screen = Screen.new(20, 4) 1331 feed(':') 1332 api.nvim_paste('normal! \023\022\006\027', true, -1) 1333 screen:expect([[ 1334 | 1335 {1:~ }|*2 1336 :normal! {18:^W^V^F^[}^ | 1337 ]]) 1338 end) 1339 it('crlf=false does not break lines at CR, CRLF', function() 1340 api.nvim_paste('line 1\r\n\r\rline 2\nline 3\rline 4\r', false, -1) 1341 local expected = 'line 1\r\n\r\rline 2\nline 3\rline 4\r' 1342 expect(expected) 1343 eq({ 0, 3, 14, 0 }, fn.getpos('.')) 1344 feed('u') -- Undo. 1345 expect('') 1346 feed('.') -- Dot-repeat. 1347 expect(expected) 1348 end) 1349 describe('repeating a paste via redo/recording', function() 1350 -- Test with indent and control chars and multibyte chars containing 0x80 bytes 1351 local text = dedent(([[ 1352 foo 1353 bar 1354 baz 1355 !!!%s!!!%s!!!%s!!! 1356 最…倒…倀… 1357 ]]):format('\0', '\2\3\6\21\22\23\24\27', '\127')) 1358 before_each(function() 1359 api.nvim_set_option_value('autoindent', true, {}) 1360 end) 1361 local function test_paste_repeat_normal_insert(is_insert) 1362 feed('qr' .. (is_insert and 'i' or '')) 1363 eq('r', fn.reg_recording()) 1364 api.nvim_paste(text, true, -1) 1365 feed(is_insert and '<Esc>' or '') 1366 expect(text) 1367 feed('.') 1368 expect(text:rep(2)) 1369 feed('q') 1370 eq('', fn.reg_recording()) 1371 feed('3.') 1372 expect(text:rep(5)) 1373 feed('2@r') 1374 expect(text:rep(9)) 1375 end 1376 it('works in Normal mode', function() 1377 test_paste_repeat_normal_insert(false) 1378 end) 1379 it('works in Insert mode', function() 1380 test_paste_repeat_normal_insert(true) 1381 end) 1382 local function test_paste_repeat_visual_select(is_select) 1383 insert(('xxx\n'):rep(5)) 1384 feed('ggqr' .. (is_select and 'gH' or 'V')) 1385 api.nvim_paste(text, true, -1) 1386 feed('q') 1387 expect(text .. ('xxx\n'):rep(4)) 1388 feed('2@r') 1389 expect(text:rep(3) .. ('xxx\n'):rep(2)) 1390 end 1391 it('works in Visual mode (recording only)', function() 1392 test_paste_repeat_visual_select(false) 1393 end) 1394 it('works in Select mode (recording only)', function() 1395 test_paste_repeat_visual_select(true) 1396 end) 1397 end) 1398 it('in a mapping recorded in a macro', function() 1399 command([[nnoremap <F2> <Cmd>call nvim_paste('foo', v:false, -1)<CR>]]) 1400 feed('qr<F2>$q') 1401 expect('foo') 1402 feed('@r') -- repeating a macro containing the mapping should only paste once 1403 expect('foofoo') 1404 end) 1405 local function test_paste_cancel_error(is_error) 1406 before_each(function() 1407 exec_lua(([[ 1408 vim.paste = (function(overridden) 1409 return function(lines, phase) 1410 for i, line in ipairs(lines) do 1411 if line == 'CANCEL' then 1412 %s 1413 end 1414 end 1415 return overridden(lines, phase) 1416 end 1417 end)(vim.paste) 1418 ]]):format(is_error and 'error("fake fail")' or 'return false')) 1419 end) 1420 local function check_paste_cancel_error(data, crlf, phase) 1421 if is_error then 1422 eq('fake fail', pcall_err(api.nvim_paste, data, crlf, phase)) 1423 else 1424 eq(false, api.nvim_paste(data, crlf, phase)) 1425 end 1426 end 1427 it('in phase -1', function() 1428 feed('A') 1429 check_paste_cancel_error('CANCEL', true, -1) 1430 feed('<Esc>') 1431 expect('') 1432 feed('.') 1433 expect('') 1434 end) 1435 it('in phase 1', function() 1436 feed('A') 1437 check_paste_cancel_error('CANCEL', true, 1) 1438 feed('<Esc>') 1439 expect('') 1440 feed('.') 1441 expect('') 1442 end) 1443 it('in phase 2', function() 1444 feed('A') 1445 eq(true, api.nvim_paste('aaa', true, 1)) 1446 expect('aaa') 1447 check_paste_cancel_error('CANCEL', true, 2) 1448 feed('<Esc>') 1449 expect('aaa') 1450 feed('.') 1451 expect('aaaaaa') 1452 end) 1453 it('in phase 3', function() 1454 feed('A') 1455 eq(true, api.nvim_paste('aaa', true, 1)) 1456 expect('aaa') 1457 eq(true, api.nvim_paste('bbb', true, 2)) 1458 expect('aaabbb') 1459 check_paste_cancel_error('CANCEL', true, 3) 1460 feed('<Esc>') 1461 expect('aaabbb') 1462 feed('.') 1463 expect('aaabbbaaabbb') 1464 end) 1465 end 1466 describe('vim.paste() cancel', function() 1467 test_paste_cancel_error(false) 1468 end) 1469 describe('vim.paste() error', function() 1470 test_paste_cancel_error(true) 1471 end) 1472 end) 1473 1474 describe('nvim_put', function() 1475 it('validation', function() 1476 eq( 1477 "Invalid 'line': expected String, got Integer", 1478 pcall_err(request, 'nvim_put', { 42 }, 'l', false, false) 1479 ) 1480 eq("Invalid 'type': 'x'", pcall_err(request, 'nvim_put', { 'foo' }, 'x', false, false)) 1481 end) 1482 it("fails if 'nomodifiable'", function() 1483 command('set nomodifiable') 1484 eq( 1485 [[Vim:E21: Cannot make changes, 'modifiable' is off]], 1486 pcall_err(request, 'nvim_put', { 'a', 'b' }, 'l', true, true) 1487 ) 1488 end) 1489 it('inserts text', function() 1490 -- linewise 1491 api.nvim_put({ 'line 1', 'line 2', 'line 3' }, 'l', true, true) 1492 expect([[ 1493 1494 line 1 1495 line 2 1496 line 3]]) 1497 eq({ 0, 4, 1, 0 }, fn.getpos('.')) 1498 command('%delete _') 1499 -- charwise 1500 api.nvim_put({ 'line 1', 'line 2', 'line 3' }, 'c', true, false) 1501 expect([[ 1502 line 1 1503 line 2 1504 line 3]]) 1505 eq({ 0, 1, 1, 0 }, fn.getpos('.')) -- follow=false 1506 -- blockwise 1507 api.nvim_put({ 'AA', 'BB' }, 'b', true, true) 1508 expect([[ 1509 lAAine 1 1510 lBBine 2 1511 line 3]]) 1512 eq({ 0, 2, 4, 0 }, fn.getpos('.')) 1513 command('%delete _') 1514 -- Empty lines list. 1515 api.nvim_put({}, 'c', true, true) 1516 eq({ 0, 1, 1, 0 }, fn.getpos('.')) 1517 expect([[]]) 1518 -- Single empty line. 1519 api.nvim_put({ '' }, 'c', true, true) 1520 eq({ 0, 1, 1, 0 }, fn.getpos('.')) 1521 expect([[ 1522 ]]) 1523 api.nvim_put({ 'AB' }, 'c', true, true) 1524 -- after=false, follow=true 1525 api.nvim_put({ 'line 1', 'line 2' }, 'c', false, true) 1526 expect([[ 1527 Aline 1 1528 line 2B]]) 1529 eq({ 0, 2, 7, 0 }, fn.getpos('.')) 1530 command('%delete _') 1531 api.nvim_put({ 'AB' }, 'c', true, true) 1532 -- after=false, follow=false 1533 api.nvim_put({ 'line 1', 'line 2' }, 'c', false, false) 1534 expect([[ 1535 Aline 1 1536 line 2B]]) 1537 eq({ 0, 1, 2, 0 }, fn.getpos('.')) 1538 eq('', api.nvim_eval('v:errmsg')) 1539 end) 1540 1541 it('detects charwise/linewise text (empty {type})', function() 1542 -- linewise (final item is empty string) 1543 api.nvim_put({ 'line 1', 'line 2', 'line 3', '' }, '', true, true) 1544 expect([[ 1545 1546 line 1 1547 line 2 1548 line 3]]) 1549 eq({ 0, 4, 1, 0 }, fn.getpos('.')) 1550 command('%delete _') 1551 -- charwise (final item is non-empty) 1552 api.nvim_put({ 'line 1', 'line 2', 'line 3' }, '', true, true) 1553 expect([[ 1554 line 1 1555 line 2 1556 line 3]]) 1557 eq({ 0, 3, 6, 0 }, fn.getpos('.')) 1558 end) 1559 1560 it('allows block width', function() 1561 -- behave consistently with setreg(); support "\022{NUM}" return by getregtype() 1562 api.nvim_put({ 'line 1', 'line 2', 'line 3' }, 'l', false, false) 1563 expect([[ 1564 line 1 1565 line 2 1566 line 3 1567 ]]) 1568 1569 -- larger width create spaces 1570 api.nvim_put({ 'a', 'bc' }, 'b3', false, false) 1571 expect([[ 1572 a line 1 1573 bc line 2 1574 line 3 1575 ]]) 1576 -- smaller width is ignored 1577 api.nvim_put({ 'xxx', 'yyy' }, '\0221', false, true) 1578 expect([[ 1579 xxxa line 1 1580 yyybc line 2 1581 line 3 1582 ]]) 1583 eq("Invalid 'type': 'bx'", pcall_err(api.nvim_put, { 'xxx', 'yyy' }, 'bx', false, true)) 1584 eq("Invalid 'type': 'b3x'", pcall_err(api.nvim_put, { 'xxx', 'yyy' }, 'b3x', false, true)) 1585 end) 1586 1587 it('computes block width correctly when not specified #35034', function() 1588 api.nvim_put({ 'line 1', 'line 2', 'line 3' }, 'l', false, false) 1589 -- block width should be 4 1590 api.nvim_put({ 'あい', 'xxx', 'xx' }, 'b', false, false) 1591 expect([[ 1592 あいline 1 1593 xxx line 2 1594 xx line 3 1595 ]]) 1596 end) 1597 end) 1598 1599 describe('nvim_strwidth', function() 1600 it('works', function() 1601 eq(3, api.nvim_strwidth('abc')) 1602 -- 6 + (neovim) 1603 -- 19 * 2 (each japanese character occupies two cells) 1604 eq(44, api.nvim_strwidth('neovimのデザインかなりまともなのになってる。')) 1605 end) 1606 1607 it('cannot handle NULs', function() 1608 eq(0, api.nvim_strwidth('\0abc')) 1609 end) 1610 1611 it('can handle emoji with variant selectors and ZWJ', function() 1612 local selector = '❤️' 1613 eq(2, fn.strchars(selector)) 1614 eq(1, fn.strcharlen(selector)) 1615 eq(2, api.nvim_strwidth(selector)) 1616 1617 local no_selector = '❤' 1618 eq(1, fn.strchars(no_selector)) 1619 eq(1, fn.strcharlen(no_selector)) 1620 eq(1, api.nvim_strwidth(no_selector)) 1621 1622 local selector_zwj_selector = '🏳️⚧️' 1623 eq(5, fn.strchars(selector_zwj_selector)) 1624 eq(1, fn.strcharlen(selector_zwj_selector)) 1625 eq(2, api.nvim_strwidth(selector_zwj_selector)) 1626 1627 local emoji_zwj_emoji = '🧑🌾' 1628 eq(3, fn.strchars(emoji_zwj_emoji)) 1629 eq(1, fn.strcharlen(emoji_zwj_emoji)) 1630 eq(2, api.nvim_strwidth(emoji_zwj_emoji)) 1631 end) 1632 end) 1633 1634 describe('nvim_get_current_line, nvim_set_current_line', function() 1635 it('works', function() 1636 eq('', api.nvim_get_current_line()) 1637 api.nvim_set_current_line('abc') 1638 eq('abc', api.nvim_get_current_line()) 1639 end) 1640 end) 1641 1642 describe('set/get/del variables', function() 1643 it('validation', function() 1644 eq('Key not found: bogus', pcall_err(api.nvim_get_var, 'bogus')) 1645 eq('Key not found: bogus', pcall_err(api.nvim_del_var, 'bogus')) 1646 end) 1647 1648 it('nvim_get_var, nvim_set_var, nvim_del_var', function() 1649 api.nvim_set_var('lua', { 1, 2, { ['3'] = 1 } }) 1650 eq({ 1, 2, { ['3'] = 1 } }, api.nvim_get_var('lua')) 1651 eq({ 1, 2, { ['3'] = 1 } }, api.nvim_eval('g:lua')) 1652 eq(1, fn.exists('g:lua')) 1653 api.nvim_del_var('lua') 1654 eq(0, fn.exists('g:lua')) 1655 eq('Key not found: lua', pcall_err(api.nvim_del_var, 'lua')) 1656 api.nvim_set_var('lua', 1) 1657 1658 -- Empty keys are allowed in Vim dicts (and msgpack). 1659 api.nvim_set_var('dict_empty_key', { [''] = 'empty key' }) 1660 eq({ [''] = 'empty key' }, api.nvim_get_var('dict_empty_key')) 1661 1662 -- Set locked g: var. 1663 command('lockvar lua') 1664 eq('Key is locked: lua', pcall_err(api.nvim_del_var, 'lua')) 1665 eq('Key is locked: lua', pcall_err(api.nvim_set_var, 'lua', 1)) 1666 1667 exec([[ 1668 function Test() 1669 endfunction 1670 function s:Test() 1671 endfunction 1672 let g:Unknown_func = function('Test') 1673 let g:Unknown_script_func = function('s:Test') 1674 ]]) 1675 eq(NIL, api.nvim_get_var('Unknown_func')) 1676 eq(NIL, api.nvim_get_var('Unknown_script_func')) 1677 1678 -- Check if autoload works properly 1679 local pathsep = n.get_pathsep() 1680 local xhome = 'Xhome_api' 1681 local xconfig = xhome .. pathsep .. 'Xconfig' 1682 local xdata = xhome .. pathsep .. 'Xdata' 1683 local autoload_folder = table.concat({ xconfig, 'nvim', 'autoload' }, pathsep) 1684 local autoload_file = table.concat({ autoload_folder, 'testload.vim' }, pathsep) 1685 mkdir_p(autoload_folder) 1686 write_file(autoload_file, [[let testload#value = 2]]) 1687 1688 clear { args_rm = { '-u' }, env = { XDG_CONFIG_HOME = xconfig, XDG_DATA_HOME = xdata } } 1689 eq(2, api.nvim_get_var('testload#value')) 1690 rmdir(xhome) 1691 end) 1692 1693 it('nvim_get_vvar, nvim_set_vvar', function() 1694 eq('Key is read-only: count', pcall_err(request, 'nvim_set_vvar', 'count', 42)) 1695 eq('Dict is locked', pcall_err(request, 'nvim_set_vvar', 'nosuchvar', 42)) 1696 api.nvim_set_vvar('errmsg', 'set by API') 1697 eq('set by API', api.nvim_get_vvar('errmsg')) 1698 api.nvim_set_vvar('completed_item', { word = 'a', user_data = vim.empty_dict() }) 1699 eq({}, api.nvim_get_vvar('completed_item')['user_data']) 1700 api.nvim_set_vvar('errmsg', 42) 1701 eq('42', eval('v:errmsg')) 1702 api.nvim_set_vvar('oldfiles', { 'one', 'two' }) 1703 eq({ 'one', 'two' }, eval('v:oldfiles')) 1704 api.nvim_set_vvar('oldfiles', {}) 1705 eq({}, eval('v:oldfiles')) 1706 eq( 1707 'Setting v:oldfiles to value with wrong type', 1708 pcall_err(api.nvim_set_vvar, 'oldfiles', 'a') 1709 ) 1710 eq({}, eval('v:oldfiles')) 1711 1712 feed('i foo foo foo<Esc>0/foo<CR>') 1713 eq({ 1, 1 }, api.nvim_win_get_cursor(0)) 1714 eq(1, eval('v:searchforward')) 1715 feed('n') 1716 eq({ 1, 5 }, api.nvim_win_get_cursor(0)) 1717 api.nvim_set_vvar('searchforward', 0) 1718 eq(0, eval('v:searchforward')) 1719 feed('n') 1720 eq({ 1, 1 }, api.nvim_win_get_cursor(0)) 1721 api.nvim_set_vvar('searchforward', 1) 1722 eq(1, eval('v:searchforward')) 1723 feed('n') 1724 eq({ 1, 5 }, api.nvim_win_get_cursor(0)) 1725 1726 local screen = Screen.new(60, 3) 1727 eq(1, eval('v:hlsearch')) 1728 screen:expect { 1729 grid = [[ 1730 {10:foo} {10:^foo} {10:foo} | 1731 {1:~ }| 1732 | 1733 ]], 1734 } 1735 api.nvim_set_vvar('hlsearch', 0) 1736 eq(0, eval('v:hlsearch')) 1737 screen:expect { 1738 grid = [[ 1739 foo ^foo foo | 1740 {1:~ }| 1741 | 1742 ]], 1743 } 1744 api.nvim_set_vvar('hlsearch', 1) 1745 eq(1, eval('v:hlsearch')) 1746 screen:expect { 1747 grid = [[ 1748 {10:foo} {10:^foo} {10:foo} | 1749 {1:~ }| 1750 | 1751 ]], 1752 } 1753 end) 1754 1755 it('vim_set_var returns the old value', function() 1756 local val1 = { 1, 2, { ['3'] = 1 } } 1757 local val2 = { 4, 7 } 1758 eq(NIL, request('vim_set_var', 'lua', val1)) 1759 eq(val1, request('vim_set_var', 'lua', val2)) 1760 end) 1761 1762 it('vim_del_var returns the old value', function() 1763 local val1 = { 1, 2, { ['3'] = 1 } } 1764 local val2 = { 4, 7 } 1765 eq(NIL, request('vim_set_var', 'lua', val1)) 1766 eq(val1, request('vim_set_var', 'lua', val2)) 1767 eq(val2, request('vim_del_var', 'lua')) 1768 end) 1769 1770 it('preserves values with NULs in them', function() 1771 api.nvim_set_var('xxx', 'ab\0cd') 1772 eq('ab\000cd', api.nvim_get_var('xxx')) 1773 end) 1774 end) 1775 1776 describe('nvim_get_option_value, nvim_set_option_value', function() 1777 it('works', function() 1778 ok(api.nvim_get_option_value('equalalways', {})) 1779 api.nvim_set_option_value('equalalways', false, {}) 1780 ok(not api.nvim_get_option_value('equalalways', {})) 1781 end) 1782 1783 it('works to get global value of local options', function() 1784 eq(false, api.nvim_get_option_value('lisp', {})) 1785 eq(8, api.nvim_get_option_value('shiftwidth', {})) 1786 end) 1787 1788 it('works to set global value of local options', function() 1789 api.nvim_set_option_value('lisp', true, { scope = 'global' }) 1790 eq(true, api.nvim_get_option_value('lisp', { scope = 'global' })) 1791 eq(false, api.nvim_get_option_value('lisp', {})) 1792 eq(nil, command_output('setglobal lisp?'):match('nolisp')) 1793 eq('nolisp', command_output('setlocal lisp?'):match('nolisp')) 1794 api.nvim_set_option_value('shiftwidth', 20, { scope = 'global' }) 1795 eq('20', command_output('setglobal shiftwidth?'):match('%d+')) 1796 eq('8', command_output('setlocal shiftwidth?'):match('%d+')) 1797 end) 1798 1799 it('updates where the option was last set from', function() 1800 api.nvim_set_option_value('equalalways', false, {}) 1801 local status, rv = pcall(command_output, 'verbose set equalalways?') 1802 eq(true, status) 1803 matches('noequalalways\n' .. '\tLast set from API client %(channel id %d+%)', rv) 1804 1805 api.nvim_exec_lua('vim.api.nvim_set_option_value("equalalways", true, {})', {}) 1806 status, rv = pcall(command_output, 'verbose set equalalways?') 1807 eq(true, status) 1808 eq(' equalalways\n\tLast set from Lua (run Nvim with -V1 for more details)', rv) 1809 end) 1810 1811 it('updates whether the option has ever been set #25025', function() 1812 eq(false, api.nvim_get_option_info2('autochdir', {}).was_set) 1813 api.nvim_set_option_value('autochdir', true, {}) 1814 eq(true, api.nvim_get_option_info2('autochdir', {}).was_set) 1815 1816 eq(false, api.nvim_get_option_info2('cmdwinheight', {}).was_set) 1817 api.nvim_set_option_value('cmdwinheight', 10, {}) 1818 eq(true, api.nvim_get_option_info2('cmdwinheight', {}).was_set) 1819 1820 eq(false, api.nvim_get_option_info2('debug', {}).was_set) 1821 api.nvim_set_option_value('debug', 'beep', {}) 1822 eq(true, api.nvim_get_option_info2('debug', {}).was_set) 1823 end) 1824 1825 it('validation', function() 1826 eq("Unknown option 'foobar'", pcall_err(api.nvim_set_option_value, 'foobar', 'baz', {})) 1827 eq( 1828 "Unknown option 'foobar'", 1829 pcall_err(api.nvim_set_option_value, 'foobar', 'baz', { win = api.nvim_get_current_win() }) 1830 ) 1831 eq( 1832 "Invalid 'scope': expected 'local' or 'global'", 1833 pcall_err(api.nvim_get_option_value, 'scrolloff', { scope = 'bogus' }) 1834 ) 1835 eq( 1836 "Invalid 'scope': expected 'local' or 'global'", 1837 pcall_err(api.nvim_set_option_value, 'scrolloff', 1, { scope = 'bogus' }) 1838 ) 1839 eq( 1840 "Invalid 'scope': expected String, got Integer", 1841 pcall_err(api.nvim_get_option_value, 'scrolloff', { scope = 42 }) 1842 ) 1843 eq( 1844 "Invalid 'value': expected valid option type, got Array", 1845 pcall_err(api.nvim_set_option_value, 'scrolloff', {}, {}) 1846 ) 1847 eq( 1848 "Invalid value for option 'scrolloff': expected number, got boolean true", 1849 pcall_err(api.nvim_set_option_value, 'scrolloff', true, {}) 1850 ) 1851 eq( 1852 'Invalid value for option \'scrolloff\': expected number, got string "wrong"', 1853 pcall_err(api.nvim_set_option_value, 'scrolloff', 'wrong', {}) 1854 ) 1855 end) 1856 1857 it('can get local values when global value is set', function() 1858 eq(0, api.nvim_get_option_value('scrolloff', {})) 1859 eq(-1, api.nvim_get_option_value('scrolloff', { scope = 'local' })) 1860 end) 1861 1862 it('can set global and local values', function() 1863 api.nvim_set_option_value('makeprg', 'hello', {}) 1864 eq('hello', api.nvim_get_option_value('makeprg', {})) 1865 eq('', api.nvim_get_option_value('makeprg', { scope = 'local' })) 1866 api.nvim_set_option_value('makeprg', 'world', { scope = 'local' }) 1867 eq('world', api.nvim_get_option_value('makeprg', { scope = 'local' })) 1868 api.nvim_set_option_value('makeprg', 'goodbye', { scope = 'global' }) 1869 eq('goodbye', api.nvim_get_option_value('makeprg', { scope = 'global' })) 1870 api.nvim_set_option_value('makeprg', 'hello', {}) 1871 eq('hello', api.nvim_get_option_value('makeprg', { scope = 'global' })) 1872 eq('hello', api.nvim_get_option_value('makeprg', {})) 1873 eq('', api.nvim_get_option_value('makeprg', { scope = 'local' })) 1874 end) 1875 1876 it('clears the local value of an option with nil', function() 1877 -- Set global value 1878 api.nvim_set_option_value('shiftwidth', 42, {}) 1879 eq(42, api.nvim_get_option_value('shiftwidth', {})) 1880 1881 -- Set local value 1882 api.nvim_set_option_value('shiftwidth', 8, { scope = 'local' }) 1883 eq(8, api.nvim_get_option_value('shiftwidth', {})) 1884 eq(8, api.nvim_get_option_value('shiftwidth', { scope = 'local' })) 1885 eq(42, api.nvim_get_option_value('shiftwidth', { scope = 'global' })) 1886 1887 -- Clear value without scope 1888 api.nvim_set_option_value('shiftwidth', NIL, {}) 1889 eq(42, api.nvim_get_option_value('shiftwidth', {})) 1890 eq(42, api.nvim_get_option_value('shiftwidth', { scope = 'local' })) 1891 1892 -- Clear value with explicit scope 1893 api.nvim_set_option_value('shiftwidth', 8, { scope = 'local' }) 1894 api.nvim_set_option_value('shiftwidth', NIL, { scope = 'local' }) 1895 eq(42, api.nvim_get_option_value('shiftwidth', {})) 1896 eq(42, api.nvim_get_option_value('shiftwidth', { scope = 'local' })) 1897 1898 -- Now try with options with a special "local is unset" value (e.g. 'undolevels') 1899 api.nvim_set_option_value('undolevels', 1000, {}) 1900 api.nvim_set_option_value('undolevels', 1200, { scope = 'local' }) 1901 eq(1200, api.nvim_get_option_value('undolevels', { scope = 'local' })) 1902 api.nvim_set_option_value('undolevels', NIL, { scope = 'local' }) 1903 eq(-123456, api.nvim_get_option_value('undolevels', { scope = 'local' })) 1904 eq(1000, api.nvim_get_option_value('undolevels', {})) 1905 1906 api.nvim_set_option_value('autoread', true, {}) 1907 api.nvim_set_option_value('autoread', false, { scope = 'local' }) 1908 eq(false, api.nvim_get_option_value('autoread', { scope = 'local' })) 1909 api.nvim_set_option_value('autoread', NIL, { scope = 'local' }) 1910 eq(NIL, api.nvim_get_option_value('autoread', { scope = 'local' })) 1911 eq(true, api.nvim_get_option_value('autoread', {})) 1912 end) 1913 1914 it('set window options', function() 1915 api.nvim_set_option_value('colorcolumn', '4,3', {}) 1916 eq('4,3', api.nvim_get_option_value('colorcolumn', { scope = 'local' })) 1917 command('set modified hidden') 1918 command('enew') -- edit new buffer, window option is preserved 1919 eq('4,3', api.nvim_get_option_value('colorcolumn', { scope = 'local' })) 1920 end) 1921 1922 it('set local window options', function() 1923 api.nvim_set_option_value('colorcolumn', '4,3', { win = 0, scope = 'local' }) 1924 eq('4,3', api.nvim_get_option_value('colorcolumn', { win = 0, scope = 'local' })) 1925 command('set modified hidden') 1926 command('enew') -- edit new buffer, window option is reset 1927 eq('', api.nvim_get_option_value('colorcolumn', { win = 0, scope = 'local' })) 1928 end) 1929 1930 it('get buffer or window-local options', function() 1931 command('new') 1932 local buf = api.nvim_get_current_buf() 1933 api.nvim_set_option_value('tagfunc', 'foobar', { buf = buf }) 1934 eq('foobar', api.nvim_get_option_value('tagfunc', { buf = buf })) 1935 1936 local win = api.nvim_get_current_win() 1937 api.nvim_set_option_value('number', true, { win = win }) 1938 eq(true, api.nvim_get_option_value('number', { win = win })) 1939 end) 1940 1941 it('getting current buffer option does not adjust cursor #19381', function() 1942 command('new') 1943 local buf = api.nvim_get_current_buf() 1944 print(vim.inspect(api.nvim_get_current_buf())) 1945 local win = api.nvim_get_current_win() 1946 insert('some text') 1947 feed('0v$') 1948 eq({ 1, 9 }, api.nvim_win_get_cursor(win)) 1949 api.nvim_get_option_value('filetype', { buf = buf }) 1950 eq({ 1, 9 }, api.nvim_win_get_cursor(win)) 1951 end) 1952 1953 it('can get default option values for filetypes', function() 1954 command('filetype plugin on') 1955 for ft, opts in pairs { 1956 lua = { commentstring = '-- %s' }, 1957 vim = { commentstring = '"%s' }, 1958 man = { tagfunc = "v:lua.require'man'.goto_tag" }, 1959 xml = { formatexpr = 'xmlformat#Format()' }, 1960 } do 1961 for option, value in pairs(opts) do 1962 eq(value, api.nvim_get_option_value(option, { filetype = ft })) 1963 end 1964 end 1965 1966 command 'au FileType lua setlocal commentstring=NEW\\ %s' 1967 1968 eq('NEW %s', api.nvim_get_option_value('commentstring', { filetype = 'lua' })) 1969 end) 1970 1971 it('errors for bad FileType autocmds', function() 1972 command 'au FileType lua setlocal commentstring=BAD' 1973 eq( 1974 [[FileType Autocommands for "lua": Vim(setlocal):E537: 'commentstring' must be empty or contain %s: commentstring=BAD]], 1975 pcall_err(api.nvim_get_option_value, 'commentstring', { filetype = 'lua' }) 1976 ) 1977 end) 1978 1979 it("value of 'modified' is always false for scratch buffers", function() 1980 api.nvim_set_current_buf(api.nvim_create_buf(true, true)) 1981 insert([[ 1982 foo 1983 bar 1984 baz 1985 ]]) 1986 eq(false, api.nvim_get_option_value('modified', {})) 1987 end) 1988 1989 it('errors if autocmds wipe the dummy buffer', function() 1990 -- Wipe the dummy buffer. This will throw E813, but the buffer will still be wiped; check that 1991 -- such errors from setting the filetype have priority. 1992 command 'autocmd FileType * ++once bwipeout!' 1993 eq( 1994 'FileType Autocommands for "*": Vim(bwipeout):E813: Cannot close autocmd window', 1995 pcall_err(api.nvim_get_option_value, 'formatexpr', { filetype = 'lua' }) 1996 ) 1997 1998 -- Silence E813 to check that the error for wiping the dummy buffer is set. 1999 command 'autocmd FileType * ++once silent! bwipeout!' 2000 eq( 2001 'Internal buffer was deleted', 2002 pcall_err(api.nvim_get_option_value, 'formatexpr', { filetype = 'lua' }) 2003 ) 2004 end) 2005 2006 it('does not crash if autocmds open dummy buffer in other windows', function() 2007 exec [[ 2008 autocmd FileType * ++once let g:dummy_buf = bufnr() | split 2009 2010 " Autocommands should be blocked while Nvim attempts to wipe the buffer. 2011 let g:wipe_events = [] 2012 autocmd WinClosed * if winbufnr(expand('<amatch>')) == g:dummy_buf 2013 \| let g:wipe_events += ['WinClosed'] 2014 \| endif 2015 autocmd BufWipeout * if expand('<abuf>') == g:dummy_buf 2016 \| let g:wipe_events += ['BufWipeout'] 2017 \| endif 2018 ]] 2019 api.nvim_get_option_value('formatexpr', { filetype = 'lua' }) 2020 eq(0, eval('bufexists(g:dummy_buf)')) 2021 eq({}, eval('win_findbuf(g:dummy_buf)')) 2022 eq({}, eval('g:wipe_events')) 2023 2024 -- Be an ABSOLUTE nuisance and make it the only window to prevent it from wiping. 2025 -- Do it this way to avoid E813 from :only trying to close the autocmd window. 2026 command('autocmd FileType * ++once let g:dummy_buf = bufnr() | split | wincmd w | quit') 2027 api.nvim_get_option_value('formatexpr', { filetype = 'lua' }) 2028 eq(1, eval('bufexists(g:dummy_buf)')) 2029 2030 -- Ensure the buffer does not remain as a dummy by checking that we can switch to it. 2031 local old_win = api.nvim_get_current_win() 2032 command('execute g:dummy_buf "sbuffer"') 2033 eq(eval('g:dummy_buf'), api.nvim_get_current_buf()) 2034 neq(old_win, api.nvim_get_current_win()) 2035 eq({}, eval('g:wipe_events')) 2036 end) 2037 2038 it('does not crash if dummy buffer wiped after autocommands', function() 2039 -- Autocommands are blocked while Nvim attempts to wipe the buffer, but check something like 2040 -- &bufhidden = "wipe" causing a premature wipe doesn't crash. 2041 command('autocmd FileType * ++once setlocal bufhidden=wipe | split') 2042 api.nvim_get_option_value('formatexpr', { filetype = 'lua' }) 2043 assert_alive() 2044 end) 2045 2046 it('sets dummy buffer options without side-effects', function() 2047 exec [[ 2048 let g:events = [] 2049 autocmd OptionSet * let g:events += [expand("<amatch>")] 2050 autocmd FileType * ++once let g:bufhidden = &l:bufhidden 2051 \| let g:buftype = &l:buftype 2052 \| let g:swapfile = &l:swapfile 2053 \| let g:modeline = &l:modeline 2054 \| let g:bufloaded = bufloaded(bufnr()) 2055 ]] 2056 api.nvim_get_option_value('formatexpr', { filetype = 'lua' }) 2057 eq({}, eval('g:events')) 2058 eq('hide', eval('g:bufhidden')) 2059 eq('nofile', eval('g:buftype')) 2060 eq(0, eval('g:swapfile')) 2061 eq(0, eval('g:modeline')) 2062 eq(1, eval('g:bufloaded')) 2063 end) 2064 end) 2065 2066 describe('nvim_{get,set}_current_buf, nvim_list_bufs', function() 2067 it('works', function() 2068 eq(1, #api.nvim_list_bufs()) 2069 eq(api.nvim_list_bufs()[1], api.nvim_get_current_buf()) 2070 command('new') 2071 eq(2, #api.nvim_list_bufs()) 2072 eq(api.nvim_list_bufs()[2], api.nvim_get_current_buf()) 2073 api.nvim_set_current_buf(api.nvim_list_bufs()[1]) 2074 eq(api.nvim_list_bufs()[1], api.nvim_get_current_buf()) 2075 end) 2076 end) 2077 2078 describe('nvim_{get,set}_current_win, nvim_list_wins', function() 2079 it('works', function() 2080 eq(1, #api.nvim_list_wins()) 2081 eq(api.nvim_list_wins()[1], api.nvim_get_current_win()) 2082 command('vsplit') 2083 command('split') 2084 eq(3, #api.nvim_list_wins()) 2085 eq(api.nvim_list_wins()[1], api.nvim_get_current_win()) 2086 api.nvim_set_current_win(api.nvim_list_wins()[2]) 2087 eq(api.nvim_list_wins()[2], api.nvim_get_current_win()) 2088 end) 2089 2090 it('resets Visual mode when switching to different buffer #37072', function() 2091 command('new | wincmd w') 2092 api.nvim_buf_set_lines(0, 0, -1, true, { 'a', 'b' }) 2093 api.nvim_win_set_cursor(0, { 2, 0 }) 2094 feed('<C-q>') 2095 eq({ mode = '\022', blocking = false }, api.nvim_get_mode()) 2096 api.nvim_set_current_win(fn.win_getid(fn.winnr('#'))) 2097 eq(true, pcall(command, 'redraw')) 2098 end) 2099 2100 it('failure modes', function() 2101 n.command('split') 2102 2103 eq('Invalid window id: 9999', pcall_err(api.nvim_set_current_win, 9999)) 2104 2105 -- XXX: force nvim_set_current_win to fail somehow. 2106 n.command("au WinLeave * throw 'foo'") 2107 eq('WinLeave Autocommands for "*": foo', pcall_err(api.nvim_set_current_win, 1000)) 2108 end) 2109 end) 2110 2111 describe('nvim_{get,set}_current_tabpage, nvim_list_tabpages', function() 2112 it('works', function() 2113 eq(1, #api.nvim_list_tabpages()) 2114 eq(api.nvim_list_tabpages()[1], api.nvim_get_current_tabpage()) 2115 command('tabnew') 2116 eq(2, #api.nvim_list_tabpages()) 2117 eq(2, #api.nvim_list_wins()) 2118 eq(api.nvim_list_wins()[2], api.nvim_get_current_win()) 2119 eq(api.nvim_list_tabpages()[2], api.nvim_get_current_tabpage()) 2120 api.nvim_set_current_win(api.nvim_list_wins()[1]) 2121 -- Switching window also switches tabpages if necessary 2122 eq(api.nvim_list_tabpages()[1], api.nvim_get_current_tabpage()) 2123 eq(api.nvim_list_wins()[1], api.nvim_get_current_win()) 2124 api.nvim_set_current_tabpage(api.nvim_list_tabpages()[2]) 2125 eq(api.nvim_list_tabpages()[2], api.nvim_get_current_tabpage()) 2126 eq(api.nvim_list_wins()[2], api.nvim_get_current_win()) 2127 end) 2128 2129 it('failure modes', function() 2130 n.command('tabnew') 2131 2132 eq('Invalid tabpage id: 999', pcall_err(api.nvim_set_current_tabpage, 999)) 2133 2134 -- XXX: force nvim_set_current_tabpage to fail somehow. 2135 n.command("au TabLeave * throw 'foo'") 2136 eq('TabLeave Autocommands for "*": foo', pcall_err(api.nvim_set_current_tabpage, 1)) 2137 end) 2138 end) 2139 2140 describe('nvim_get_mode', function() 2141 it('during normal-mode `g` returns blocking=true', function() 2142 api.nvim_input('o') -- add a line 2143 eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) 2144 api.nvim_input([[<C-\><C-N>]]) 2145 eq(2, api.nvim_eval("line('.')")) 2146 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 2147 2148 api.nvim_input('g') 2149 eq({ mode = 'n', blocking = true }, api.nvim_get_mode()) 2150 2151 api.nvim_input('k') -- complete the operator 2152 eq(1, api.nvim_eval("line('.')")) -- verify the completed operator 2153 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 2154 end) 2155 2156 it('returns the correct result multiple consecutive times', function() 2157 for _ = 1, 5 do 2158 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 2159 end 2160 api.nvim_input('g') 2161 for _ = 1, 4 do 2162 eq({ mode = 'n', blocking = true }, api.nvim_get_mode()) 2163 end 2164 api.nvim_input('g') 2165 for _ = 1, 7 do 2166 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 2167 end 2168 end) 2169 2170 it('during normal-mode CTRL-W, returns blocking=true', function() 2171 api.nvim_input('<C-W>') 2172 eq({ mode = 'n', blocking = true }, api.nvim_get_mode()) 2173 2174 api.nvim_input('s') -- complete the operator 2175 eq(2, api.nvim_eval("winnr('$')")) -- verify the completed operator 2176 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 2177 end) 2178 2179 it('during press-enter prompt without UI returns blocking=false', function() 2180 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 2181 command("echom 'msg1'") 2182 command("echom 'msg2'") 2183 command("echom 'msg3'") 2184 command("echom 'msg4'") 2185 command("echom 'msg5'") 2186 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 2187 api.nvim_input(':messages<CR>') 2188 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 2189 end) 2190 2191 it('during press-enter prompt returns blocking=true', function() 2192 api.nvim_ui_attach(80, 20, {}) 2193 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 2194 command("echom 'msg1'") 2195 command("echom 'msg2'") 2196 command("echom 'msg3'") 2197 command("echom 'msg4'") 2198 command("echom 'msg5'") 2199 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 2200 api.nvim_input(':messages<CR>') 2201 eq({ mode = 'r', blocking = true }, api.nvim_get_mode()) 2202 end) 2203 2204 it('during getchar() returns blocking=false', function() 2205 api.nvim_input(':let g:test_input = nr2char(getchar())<CR>') 2206 -- Events are enabled during getchar(), RPC calls are *not* blocked. #5384 2207 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 2208 eq(0, api.nvim_eval("exists('g:test_input')")) 2209 api.nvim_input('J') 2210 eq('J', api.nvim_eval('g:test_input')) 2211 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 2212 end) 2213 2214 -- TODO: bug #6247#issuecomment-286403810 2215 it('batched with input', function() 2216 api.nvim_ui_attach(80, 20, {}) 2217 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 2218 command("echom 'msg1'") 2219 command("echom 'msg2'") 2220 command("echom 'msg3'") 2221 command("echom 'msg4'") 2222 command("echom 'msg5'") 2223 2224 local req = { 2225 { 'nvim_get_mode', {} }, 2226 { 'nvim_input', { ':messages<CR>' } }, 2227 { 'nvim_get_mode', {} }, 2228 { 'nvim_eval', { '1' } }, 2229 } 2230 eq({ 2231 { 2232 { mode = 'n', blocking = false }, 2233 13, 2234 { mode = 'n', blocking = false }, -- TODO: should be blocked=true ? 2235 1, 2236 }, 2237 NIL, 2238 }, api.nvim_call_atomic(req)) 2239 eq({ mode = 'r', blocking = true }, api.nvim_get_mode()) 2240 end) 2241 it('during insert-mode map-pending, returns blocking=true #6166', function() 2242 command('inoremap xx foo') 2243 api.nvim_input('ix') 2244 eq({ mode = 'i', blocking = true }, api.nvim_get_mode()) 2245 end) 2246 it('during normal-mode gU, returns blocking=false #6166', function() 2247 api.nvim_input('gu') 2248 eq({ mode = 'no', blocking = false }, api.nvim_get_mode()) 2249 end) 2250 2251 it("at '-- More --' prompt returns blocking=true #11899", function() 2252 command('set more') 2253 feed(':digraphs<cr>') 2254 eq({ mode = 'rm', blocking = true }, api.nvim_get_mode()) 2255 end) 2256 2257 it('after <Nop> mapping returns blocking=false #17257', function() 2258 command('nnoremap <F2> <Nop>') 2259 feed('<F2>') 2260 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 2261 end) 2262 2263 it('after empty string <expr> mapping returns blocking=false #17257', function() 2264 command('nnoremap <expr> <F2> ""') 2265 feed('<F2>') 2266 eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) 2267 end) 2268 2269 it('returns "c" during number prompt', function() 2270 feed('ifoo<Esc>z=') 2271 eq({ mode = 'c', blocking = false }, api.nvim_get_mode()) 2272 end) 2273 end) 2274 2275 describe('RPC (K_EVENT)', function() 2276 it('does not complete ("interrupt") normal-mode operator-pending #6166', function() 2277 n.insert([[ 2278 FIRST LINE 2279 SECOND LINE]]) 2280 api.nvim_input('gg') 2281 api.nvim_input('gu') 2282 -- Make any RPC request (can be non-async: op-pending does not block). 2283 api.nvim_get_current_buf() 2284 -- Buffer should not change. 2285 expect([[ 2286 FIRST LINE 2287 SECOND LINE]]) 2288 -- Now send input to complete the operator. 2289 api.nvim_input('j') 2290 expect([[ 2291 first line 2292 second line]]) 2293 end) 2294 2295 it('does not complete ("interrupt") `d` #3732', function() 2296 local screen = Screen.new(20, 4) 2297 command('set listchars=eol:$') 2298 command('set list') 2299 feed('ia<cr>b<cr>c<cr><Esc>kkk') 2300 feed('d') 2301 -- Make any RPC request (can be non-async: op-pending does not block). 2302 api.nvim_get_current_buf() 2303 screen:expect([[ 2304 ^a{1:$} | 2305 b{1:$} | 2306 c{1:$} | 2307 | 2308 ]]) 2309 end) 2310 2311 it('does not complete ("interrupt") normal-mode map-pending #6166', function() 2312 command("nnoremap dd :let g:foo='it worked...'<CR>") 2313 n.insert([[ 2314 FIRST LINE 2315 SECOND LINE]]) 2316 api.nvim_input('gg') 2317 api.nvim_input('d') 2318 -- Make any RPC request (must be async, because map-pending blocks). 2319 api.nvim_get_api_info() 2320 -- Send input to complete the mapping. 2321 api.nvim_input('d') 2322 expect([[ 2323 FIRST LINE 2324 SECOND LINE]]) 2325 eq('it worked...', n.eval('g:foo')) 2326 end) 2327 2328 it('does not complete ("interrupt") insert-mode map-pending #6166', function() 2329 command('inoremap xx foo') 2330 command('set timeoutlen=9999') 2331 n.insert([[ 2332 FIRST LINE 2333 SECOND LINE]]) 2334 api.nvim_input('ix') 2335 -- Make any RPC request (must be async, because map-pending blocks). 2336 api.nvim_get_api_info() 2337 -- Send input to complete the mapping. 2338 api.nvim_input('x') 2339 expect([[ 2340 FIRST LINE 2341 SECOND LINfooE]]) 2342 end) 2343 2344 it('does not interrupt Insert mode i_CTRL-O #10035', function() 2345 feed('iHello World<c-o>') 2346 eq({ mode = 'niI', blocking = false }, api.nvim_get_mode()) -- fast event 2347 eq(2, eval('1+1')) -- causes K_EVENT key 2348 eq({ mode = 'niI', blocking = false }, api.nvim_get_mode()) -- still in ctrl-o mode 2349 feed('dd') 2350 eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) -- left ctrl-o mode 2351 expect('') -- executed the command 2352 end) 2353 2354 it('does not interrupt Select mode v_CTRL-O #15688', function() 2355 feed('iHello World<esc>gh<c-o>') 2356 eq({ mode = 'vs', blocking = false }, api.nvim_get_mode()) -- fast event 2357 eq({ mode = 'vs', blocking = false }, api.nvim_get_mode()) -- again #15288 2358 eq(2, eval('1+1')) -- causes K_EVENT key 2359 eq({ mode = 'vs', blocking = false }, api.nvim_get_mode()) -- still in ctrl-o mode 2360 feed('^') 2361 eq({ mode = 's', blocking = false }, api.nvim_get_mode()) -- left ctrl-o mode 2362 feed('h') 2363 eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) -- entered insert mode 2364 expect('h') -- selection is the whole line and is replaced 2365 end) 2366 2367 it('does not interrupt Insert mode i_0_CTRL-D #13997', function() 2368 command('set timeoutlen=9999') 2369 feed('i<Tab><Tab>a0') 2370 eq(2, eval('1+1')) -- causes K_EVENT key 2371 feed('<C-D>') 2372 expect('a') -- recognized i_0_CTRL-D 2373 end) 2374 2375 it("does not interrupt with 'digraph'", function() 2376 command('set digraph') 2377 feed('i,') 2378 eq(2, eval('1+1')) -- causes K_EVENT key 2379 feed('<BS>') 2380 eq(2, eval('1+1')) -- causes K_EVENT key 2381 feed('.') 2382 expect('…') -- digraph ",." worked 2383 feed('<Esc>') 2384 feed(':,') 2385 eq(2, eval('1+1')) -- causes K_EVENT key 2386 feed('<BS>') 2387 eq(2, eval('1+1')) -- causes K_EVENT key 2388 feed('.') 2389 eq('…', fn.getcmdline()) -- digraph ",." worked 2390 end) 2391 end) 2392 2393 describe('nvim_get_context', function() 2394 it('validation', function() 2395 eq("Invalid key: 'blah'", pcall_err(api.nvim_get_context, { blah = {} })) 2396 eq( 2397 "Invalid 'types': expected Array, got Integer", 2398 pcall_err(api.nvim_get_context, { types = 42 }) 2399 ) 2400 eq( 2401 "Invalid 'type': 'zub'", 2402 pcall_err(api.nvim_get_context, { types = { 'jumps', 'zub', 'zam' } }) 2403 ) 2404 end) 2405 it('returns map of current editor state', function() 2406 local opts = { types = { 'regs', 'jumps', 'bufs', 'gvars' } } 2407 eq({}, parse_context(api.nvim_get_context({}))) 2408 2409 feed('i1<cr>2<cr>3<c-[>ddddddqahjklquuu') 2410 feed('gg') 2411 feed('G') 2412 command('edit! BUF1') 2413 command('edit BUF2') 2414 api.nvim_set_var('one', 1) 2415 api.nvim_set_var('Two', 2) 2416 api.nvim_set_var('THREE', 3) 2417 2418 local expected_ctx = { 2419 ['regs'] = { 2420 { ['rt'] = 1, ['rc'] = { '1' }, ['n'] = 49, ['ru'] = true }, 2421 { ['rt'] = 1, ['rc'] = { '2' }, ['n'] = 50 }, 2422 { ['rt'] = 1, ['rc'] = { '3' }, ['n'] = 51 }, 2423 { ['rc'] = { 'hjkl' }, ['n'] = 97 }, 2424 }, 2425 2426 ['jumps'] = eval((([[ 2427 filter(map(add( 2428 getjumplist()[0], { 'bufnr': bufnr('%'), 'lnum': getcurpos()[1] }), 2429 'filter( 2430 { "f": expand("#".v:val.bufnr.":p"), "l": v:val.lnum }, 2431 { k, v -> k != "l" || v != 1 })'), '!empty(v:val.f)') 2432 ]]):gsub('\n', ''))), 2433 2434 ['bufs'] = eval([[ 2435 filter(map(getbufinfo(), '{ "f": v:val.name }'), '!empty(v:val.f)') 2436 ]]), 2437 2438 ['gvars'] = { { 'one', 1 }, { 'Two', 2 }, { 'THREE', 3 } }, 2439 } 2440 2441 eq(expected_ctx, parse_context(api.nvim_get_context(opts))) 2442 eq(expected_ctx, parse_context(api.nvim_get_context({}))) 2443 eq(expected_ctx, parse_context(api.nvim_get_context({ types = {} }))) 2444 end) 2445 end) 2446 2447 describe('nvim_load_context', function() 2448 it('sets current editor state to given context dict', function() 2449 local opts = { types = { 'regs', 'jumps', 'bufs', 'gvars' } } 2450 eq({}, parse_context(api.nvim_get_context(opts))) 2451 2452 api.nvim_set_var('one', 1) 2453 api.nvim_set_var('Two', 2) 2454 api.nvim_set_var('THREE', 3) 2455 local ctx = api.nvim_get_context(opts) 2456 api.nvim_set_var('one', 'a') 2457 api.nvim_set_var('Two', 'b') 2458 api.nvim_set_var('THREE', 'c') 2459 eq({ 'a', 'b', 'c' }, eval('[g:one, g:Two, g:THREE]')) 2460 api.nvim_load_context(ctx) 2461 eq({ 1, 2, 3 }, eval('[g:one, g:Two, g:THREE]')) 2462 end) 2463 2464 it('errors when context dict is invalid', function() 2465 eq( 2466 'E474: Failed to convert list to msgpack string buffer', 2467 pcall_err(api.nvim_load_context, { regs = { {} }, jumps = { {} } }) 2468 ) 2469 eq( 2470 'E474: Failed to convert list to msgpack string buffer', 2471 pcall_err(api.nvim_load_context, { regs = { { [''] = '' } } }) 2472 ) 2473 end) 2474 end) 2475 2476 describe('nvim_replace_termcodes', function() 2477 it('escapes K_SPECIAL as K_SPECIAL KS_SPECIAL KE_FILLER', function() 2478 eq('\128\254X', n.api.nvim_replace_termcodes('\128', true, true, true)) 2479 end) 2480 2481 it('leaves non-K_SPECIAL string unchanged', function() 2482 eq('abc', n.api.nvim_replace_termcodes('abc', true, true, true)) 2483 end) 2484 2485 it('converts <expressions>', function() 2486 eq('\\', n.api.nvim_replace_termcodes('<Leader>', true, true, true)) 2487 end) 2488 2489 it('converts <LeftMouse> to K_SPECIAL KS_EXTRA KE_LEFTMOUSE', function() 2490 -- K_SPECIAL KS_EXTRA KE_LEFTMOUSE 2491 -- 0x80 0xfd 0x2c 2492 -- 128 253 44 2493 eq('\128\253\44', n.api.nvim_replace_termcodes('<LeftMouse>', true, true, true)) 2494 end) 2495 2496 it('converts keycodes', function() 2497 eq('\nx\27x\rx<x', n.api.nvim_replace_termcodes('<NL>x<Esc>x<CR>x<lt>x', true, true, true)) 2498 end) 2499 2500 it('does not convert keycodes if special=false', function() 2501 eq( 2502 '<NL>x<Esc>x<CR>x<lt>x', 2503 n.api.nvim_replace_termcodes('<NL>x<Esc>x<CR>x<lt>x', true, true, false) 2504 ) 2505 end) 2506 2507 it('does not crash when transforming an empty string', function() 2508 -- Actually does not test anything, because current code will use NULL for 2509 -- an empty string. 2510 -- 2511 -- Problem here is that if String argument has .data in allocated memory 2512 -- then `return str` in vim_replace_termcodes body will make Neovim free 2513 -- `str.data` twice: once when freeing arguments, then when freeing return 2514 -- value. 2515 eq('', api.nvim_replace_termcodes('', true, true, true)) 2516 end) 2517 2518 -- Not exactly the case, as nvim_replace_termcodes() escapes K_SPECIAL in Unicode 2519 it('translates the result of keytrans() on string with 0x80 byte back', function() 2520 local s = 'ff\128\253\097tt' 2521 eq(s, api.nvim_replace_termcodes(fn.keytrans(s), true, true, true)) 2522 end) 2523 end) 2524 2525 describe('nvim_feedkeys', function() 2526 it('K_SPECIAL escaping', function() 2527 local function on_setup() 2528 -- notice the special char(…) \xe2\80\xa6 2529 api.nvim_feedkeys(':let x1="…"\n', '', true) 2530 2531 -- Both nvim_replace_termcodes and nvim_feedkeys escape \x80 2532 local inp = n.api.nvim_replace_termcodes(':let x2="…"<CR>', true, true, true) 2533 api.nvim_feedkeys(inp, '', true) -- escape_ks=true 2534 2535 -- nvim_feedkeys with K_SPECIAL escaping disabled 2536 inp = n.api.nvim_replace_termcodes(':let x3="…"<CR>', true, true, true) 2537 api.nvim_feedkeys(inp, '', false) -- escape_ks=false 2538 2539 n.stop() 2540 end 2541 2542 -- spin the loop a bit 2543 n.run(nil, nil, on_setup) 2544 2545 eq('…', api.nvim_get_var('x1')) 2546 -- Because of the double escaping this is neq 2547 neq('…', api.nvim_get_var('x2')) 2548 eq('…', api.nvim_get_var('x3')) 2549 end) 2550 end) 2551 2552 describe('nvim_out_write', function() 2553 local screen 2554 2555 before_each(function() 2556 screen = Screen.new(40, 8) 2557 end) 2558 2559 it('prints long messages correctly #20534', function() 2560 exec([[ 2561 set more 2562 redir => g:out 2563 silent! call nvim_out_write('a') 2564 silent! call nvim_out_write('a') 2565 silent! call nvim_out_write('a') 2566 silent! call nvim_out_write("\n") 2567 silent! call nvim_out_write('a') 2568 silent! call nvim_out_write('a') 2569 silent! call nvim_out_write(repeat('a', 5000) .. "\n") 2570 silent! call nvim_out_write('a') 2571 silent! call nvim_out_write('a') 2572 silent! call nvim_out_write('a') 2573 silent! call nvim_out_write("\n") 2574 redir END 2575 ]]) 2576 eq('\naaa\n' .. ('a'):rep(5002) .. '\naaa', api.nvim_get_var('out')) 2577 end) 2578 2579 it('blank line in message', function() 2580 feed([[:call nvim_out_write("\na\n")<CR>]]) 2581 screen:expect { 2582 grid = [[ 2583 | 2584 {1:~ }|*3 2585 {3: }| 2586 | 2587 a | 2588 {6:Press ENTER or type command to continue}^ | 2589 ]], 2590 } 2591 feed('<CR>') 2592 feed([[:call nvim_out_write("b\n\nc\n")<CR>]]) 2593 screen:expect { 2594 grid = [[ 2595 | 2596 {1:~ }|*2 2597 {3: }| 2598 b | 2599 | 2600 c | 2601 {6:Press ENTER or type command to continue}^ | 2602 ]], 2603 } 2604 end) 2605 2606 it('NUL bytes in message', function() 2607 feed([[:lua vim.api.nvim_out_write('aaa\0bbb\0\0ccc\nddd\0\0\0eee\n')<CR>]]) 2608 screen:expect { 2609 grid = [[ 2610 | 2611 {1:~ }|*3 2612 {3: }| 2613 aaa{18:^@}bbb{18:^@^@}ccc | 2614 ddd{18:^@^@^@}eee | 2615 {6:Press ENTER or type command to continue}^ | 2616 ]], 2617 } 2618 end) 2619 end) 2620 2621 describe('nvim_err_write', function() 2622 local screen 2623 2624 before_each(function() 2625 screen = Screen.new(40, 8) 2626 end) 2627 2628 it('can show one line', function() 2629 async_meths.nvim_err_write('has bork\n') 2630 screen:expect([[ 2631 ^ | 2632 {1:~ }|*6 2633 {9:has bork} | 2634 ]]) 2635 end) 2636 2637 it('shows return prompt when more than &cmdheight lines', function() 2638 async_meths.nvim_err_write('something happened\nvery bad\n') 2639 screen:expect([[ 2640 | 2641 {1:~ }|*3 2642 {3: }| 2643 {9:something happened} | 2644 {9:very bad} | 2645 {6:Press ENTER or type command to continue}^ | 2646 ]]) 2647 end) 2648 2649 it('shows return prompt after all lines are shown', function() 2650 async_meths.nvim_err_write('FAILURE\nERROR\nEXCEPTION\nTRACEBACK\n') 2651 screen:expect([[ 2652 | 2653 {1:~ }| 2654 {3: }| 2655 {9:FAILURE} | 2656 {9:ERROR} | 2657 {9:EXCEPTION} | 2658 {9:TRACEBACK} | 2659 {6:Press ENTER or type command to continue}^ | 2660 ]]) 2661 end) 2662 2663 it('handles multiple calls', function() 2664 -- without linebreak text is joined to one line 2665 async_meths.nvim_err_write('very ') 2666 async_meths.nvim_err_write('fail\n') 2667 screen:expect([[ 2668 ^ | 2669 {1:~ }|*6 2670 {9:very fail} | 2671 ]]) 2672 n.poke_eventloop() 2673 2674 -- shows up to &cmdheight lines 2675 async_meths.nvim_err_write('more fail\ntoo fail\n') 2676 screen:expect([[ 2677 | 2678 {1:~ }|*3 2679 {3: }| 2680 {9:more fail} | 2681 {9:too fail} | 2682 {6:Press ENTER or type command to continue}^ | 2683 ]]) 2684 feed('<cr>') -- exit the press ENTER screen 2685 end) 2686 2687 it('NUL bytes in message', function() 2688 async_meths.nvim_err_write('aaa\0bbb\0\0ccc\nddd\0\0\0eee\n') 2689 screen:expect { 2690 grid = [[ 2691 | 2692 {1:~ }|*3 2693 {3: }| 2694 {9:aaa^@bbb^@^@ccc} | 2695 {9:ddd^@^@^@eee} | 2696 {6:Press ENTER or type command to continue}^ | 2697 ]], 2698 } 2699 end) 2700 end) 2701 2702 describe('nvim_err_writeln', function() 2703 local screen 2704 2705 before_each(function() 2706 screen = Screen.new(40, 8) 2707 end) 2708 2709 it('shows only one return prompt after all lines are shown', function() 2710 async_meths.nvim_err_writeln('FAILURE\nERROR\nEXCEPTION\nTRACEBACK') 2711 screen:expect([[ 2712 | 2713 {1:~ }| 2714 {3: }| 2715 {9:FAILURE} | 2716 {9:ERROR} | 2717 {9:EXCEPTION} | 2718 {9:TRACEBACK} | 2719 {6:Press ENTER or type command to continue}^ | 2720 ]]) 2721 feed('<CR>') 2722 screen:expect([[ 2723 ^ | 2724 {1:~ }|*6 2725 | 2726 ]]) 2727 end) 2728 end) 2729 2730 describe('nvim_list_chans, nvim_get_chan_info', function() 2731 before_each(function() 2732 command('autocmd ChanOpen * let g:opened_event = deepcopy(v:event)') 2733 command('autocmd ChanInfo * let g:info_event = deepcopy(v:event)') 2734 end) 2735 local testinfo = { 2736 stream = 'stdio', 2737 id = 1, 2738 mode = 'rpc', 2739 client = {}, 2740 } 2741 local stderr = { 2742 stream = 'stderr', 2743 id = 2, 2744 mode = 'bytes', 2745 } 2746 2747 it('returns {} for invalid channel', function() 2748 eq({}, api.nvim_get_chan_info(-1)) 2749 -- more preallocated numbers might be added, try something high 2750 eq({}, api.nvim_get_chan_info(10)) 2751 end) 2752 2753 it('stream=stdio channel', function() 2754 eq({ [1] = testinfo, [2] = stderr }, api.nvim_list_chans()) 2755 -- 0 should return current channel 2756 eq(testinfo, api.nvim_get_chan_info(0)) 2757 eq(testinfo, api.nvim_get_chan_info(1)) 2758 eq(stderr, api.nvim_get_chan_info(2)) 2759 2760 api.nvim_set_client_info( 2761 'functionaltests', 2762 { major = 0, minor = 3, patch = 17 }, 2763 'ui', 2764 { do_stuff = { n_args = { 2, 3 } } }, 2765 { license = 'Apache2' } 2766 ) 2767 local info = { 2768 stream = 'stdio', 2769 id = 1, 2770 mode = 'rpc', 2771 client = { 2772 name = 'functionaltests', 2773 version = { major = 0, minor = 3, patch = 17 }, 2774 type = 'ui', 2775 methods = { do_stuff = { n_args = { 2, 3 } } }, 2776 attributes = { license = 'Apache2' }, 2777 }, 2778 } 2779 eq({ info = info }, api.nvim_get_var('info_event')) 2780 eq({ [1] = info, [2] = stderr }, api.nvim_list_chans()) 2781 eq(info, api.nvim_get_chan_info(1)) 2782 end) 2783 2784 it('stream=job channel', function() 2785 eq(3, eval("jobstart(['cat'], {'rpc': v:true})")) 2786 local catpath = eval('exepath("cat")') 2787 local info = { 2788 stream = 'job', 2789 id = 3, 2790 argv = { catpath }, 2791 mode = 'rpc', 2792 client = {}, 2793 } 2794 eq({ info = info }, api.nvim_get_var('opened_event')) 2795 eq({ [1] = testinfo, [2] = stderr, [3] = info }, api.nvim_list_chans()) 2796 eq(info, api.nvim_get_chan_info(3)) 2797 eval( 2798 'rpcrequest(3, "nvim_set_client_info", "amazing-cat", {}, "remote",' 2799 .. '{"nvim_command":{"n_args":1}},' -- and so on 2800 .. '{"description":"The Amazing Cat"})' 2801 ) 2802 info = { 2803 stream = 'job', 2804 id = 3, 2805 argv = { catpath }, 2806 mode = 'rpc', 2807 client = { 2808 name = 'amazing-cat', 2809 version = { major = 0 }, 2810 type = 'remote', 2811 methods = { nvim_command = { n_args = 1 } }, 2812 attributes = { description = 'The Amazing Cat' }, 2813 }, 2814 } 2815 eq({ info = info }, api.nvim_get_var('info_event')) 2816 eq({ [1] = testinfo, [2] = stderr, [3] = info }, api.nvim_list_chans()) 2817 2818 eq( 2819 "Vim:Invoking 'nvim_set_current_buf' on channel 3 (amazing-cat):\nWrong type for argument 1 when calling nvim_set_current_buf, expecting Buffer", 2820 pcall_err(eval, 'rpcrequest(3, "nvim_set_current_buf", -1)') 2821 ) 2822 eq(info, eval('rpcrequest(3, "nvim_get_chan_info", 0)')) 2823 end) 2824 2825 it('stream=job :terminal channel', function() 2826 command(':terminal') 2827 eq(1, api.nvim_get_current_buf()) 2828 eq(3, api.nvim_get_option_value('channel', { buf = 1 })) 2829 2830 local info = { 2831 stream = 'job', 2832 id = 3, 2833 argv = { eval('exepath(&shell)') }, 2834 mode = 'terminal', 2835 buffer = 1, 2836 pty = '?', 2837 } 2838 local event = api.nvim_get_var('opened_event') 2839 if not is_os('win') then 2840 info.pty = event.info.pty 2841 neq(nil, string.match(info.pty, '^/dev/')) 2842 end 2843 eq({ info = info }, event) 2844 info.buffer = 1 2845 eq({ [1] = testinfo, [2] = stderr, [3] = info }, api.nvim_list_chans()) 2846 eq(info, api.nvim_get_chan_info(3)) 2847 2848 -- :terminal with args + running process. 2849 command('enew') 2850 local progpath_esc = eval('shellescape(v:progpath)') 2851 fn.jobstart(('%s -u NONE -i NONE'):format(progpath_esc), { 2852 term = true, 2853 env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, 2854 }) 2855 eq(-1, eval('jobwait([&channel], 0)[0]')) -- Running? 2856 local expected2 = { 2857 stream = 'job', 2858 id = 4, 2859 argv = (is_os('win') and { 2860 eval('&shell'), 2861 '/s', 2862 '/c', 2863 fmt('"%s -u NONE -i NONE"', progpath_esc), 2864 } or { 2865 eval('&shell'), 2866 eval('&shellcmdflag'), 2867 fmt('%s -u NONE -i NONE', progpath_esc), 2868 }), 2869 mode = 'terminal', 2870 buffer = 2, 2871 pty = '?', 2872 } 2873 local actual2 = eval('nvim_get_chan_info(&channel)') 2874 expected2.pty = actual2.pty 2875 eq(expected2, actual2) 2876 2877 -- :terminal with args + stopped process. 2878 eq(1, eval('jobstop(&channel)')) 2879 eval('jobwait([&channel], 1000)') -- Wait. 2880 expected2.pty = (is_os('win') and '?' or '') -- pty stream was closed. 2881 eq(expected2, eval('nvim_get_chan_info(&channel)')) 2882 end) 2883 end) 2884 2885 describe('nvim_call_atomic', function() 2886 it('works', function() 2887 api.nvim_buf_set_lines(0, 0, -1, true, { 'first' }) 2888 local req = { 2889 { 'nvim_get_current_line', {} }, 2890 { 'nvim_set_current_line', { 'second' } }, 2891 } 2892 eq({ { 'first', NIL }, NIL }, api.nvim_call_atomic(req)) 2893 eq({ 'second' }, api.nvim_buf_get_lines(0, 0, -1, true)) 2894 end) 2895 2896 it('allows multiple return values', function() 2897 local req = { 2898 { 'nvim_set_var', { 'avar', true } }, 2899 { 'nvim_set_var', { 'bvar', 'string' } }, 2900 { 'nvim_get_var', { 'avar' } }, 2901 { 'nvim_get_var', { 'bvar' } }, 2902 } 2903 eq({ { NIL, NIL, true, 'string' }, NIL }, api.nvim_call_atomic(req)) 2904 end) 2905 2906 it('is aborted by errors in call', function() 2907 local error_types = api.nvim_get_api_info()[2].error_types 2908 local req = { 2909 { 'nvim_set_var', { 'one', 1 } }, 2910 { 'nvim_buf_set_lines', {} }, 2911 { 'nvim_set_var', { 'two', 2 } }, 2912 } 2913 eq({ 2914 { NIL }, 2915 { 2916 1, 2917 error_types.Exception.id, 2918 'Wrong number of arguments: expecting 5 but got 0', 2919 }, 2920 }, api.nvim_call_atomic(req)) 2921 eq(1, api.nvim_get_var('one')) 2922 eq(false, pcall(api.nvim_get_var, 'two')) 2923 2924 -- still returns all previous successful calls 2925 req = { 2926 { 'nvim_set_var', { 'avar', 5 } }, 2927 { 'nvim_set_var', { 'bvar', 'string' } }, 2928 { 'nvim_get_var', { 'avar' } }, 2929 { 'nvim_buf_get_lines', { 0, 10, 20, true } }, 2930 { 'nvim_get_var', { 'bvar' } }, 2931 } 2932 eq( 2933 { { NIL, NIL, 5 }, { 3, error_types.Validation.id, 'Index out of bounds' } }, 2934 api.nvim_call_atomic(req) 2935 ) 2936 2937 req = { 2938 { 'i_am_not_a_method', { 'xx' } }, 2939 { 'nvim_set_var', { 'avar', 10 } }, 2940 } 2941 eq( 2942 { {}, { 0, error_types.Exception.id, 'Invalid method: i_am_not_a_method' } }, 2943 api.nvim_call_atomic(req) 2944 ) 2945 eq(5, api.nvim_get_var('avar')) 2946 end) 2947 2948 it('validation', function() 2949 local req = { 2950 { 'nvim_set_var', { 'avar', 1 } }, 2951 { 'nvim_set_var' }, 2952 { 'nvim_set_var', { 'avar', 2 } }, 2953 } 2954 eq("Invalid 'calls' item: expected 2-item Array", pcall_err(api.nvim_call_atomic, req)) 2955 -- call before was done, but not after 2956 eq(1, api.nvim_get_var('avar')) 2957 2958 req = { 2959 { 'nvim_set_var', { 'bvar', { 2, 3 } } }, 2960 12, 2961 } 2962 eq("Invalid 'calls' item: expected Array, got Integer", pcall_err(api.nvim_call_atomic, req)) 2963 eq({ 2, 3 }, api.nvim_get_var('bvar')) 2964 2965 req = { 2966 { 'nvim_set_current_line', 'little line' }, 2967 { 'nvim_set_var', { 'avar', 3 } }, 2968 } 2969 eq('Invalid call args: expected Array, got String', pcall_err(api.nvim_call_atomic, req)) 2970 -- call before was done, but not after 2971 eq(1, api.nvim_get_var('avar')) 2972 eq({ '' }, api.nvim_buf_get_lines(0, 0, -1, true)) 2973 end) 2974 end) 2975 2976 describe('nvim_list_runtime_paths', function() 2977 local test_dir = 'Xtest_list_runtime_paths' 2978 2979 setup(function() 2980 local pathsep = n.get_pathsep() 2981 mkdir_p(test_dir .. pathsep .. 'a') 2982 mkdir_p(test_dir .. pathsep .. 'b') 2983 end) 2984 teardown(function() 2985 rmdir(test_dir) 2986 end) 2987 before_each(function() 2988 api.nvim_set_current_dir(test_dir) 2989 end) 2990 2991 it('returns nothing with empty &runtimepath', function() 2992 api.nvim_set_option_value('runtimepath', '', {}) 2993 eq({}, api.nvim_list_runtime_paths()) 2994 end) 2995 it('returns single runtimepath', function() 2996 api.nvim_set_option_value('runtimepath', 'a', {}) 2997 eq({ 'a' }, api.nvim_list_runtime_paths()) 2998 end) 2999 it('returns two runtimepaths', function() 3000 api.nvim_set_option_value('runtimepath', 'a,b', {}) 3001 eq({ 'a', 'b' }, api.nvim_list_runtime_paths()) 3002 end) 3003 it('returns empty strings when appropriate', function() 3004 api.nvim_set_option_value('runtimepath', 'a,,b', {}) 3005 eq({ 'a', '', 'b' }, api.nvim_list_runtime_paths()) 3006 api.nvim_set_option_value('runtimepath', ',a,b', {}) 3007 eq({ '', 'a', 'b' }, api.nvim_list_runtime_paths()) 3008 -- Trailing "," is ignored. Use ",," if you really really want CWD. 3009 api.nvim_set_option_value('runtimepath', 'a,b,', {}) 3010 eq({ 'a', 'b' }, api.nvim_list_runtime_paths()) 3011 api.nvim_set_option_value('runtimepath', 'a,b,,', {}) 3012 eq({ 'a', 'b', '' }, api.nvim_list_runtime_paths()) 3013 end) 3014 it('truncates too long paths', function() 3015 local long_path = ('/a'):rep(8192) 3016 api.nvim_set_option_value('runtimepath', long_path, {}) 3017 local paths_list = api.nvim_list_runtime_paths() 3018 eq({}, paths_list) 3019 end) 3020 end) 3021 3022 it('can throw exceptions', function() 3023 local status, err = pcall(api.nvim_get_option_value, 'invalid-option', {}) 3024 eq(false, status) 3025 matches("Unknown option 'invalid%-option'", err) 3026 end) 3027 3028 it('does not truncate error message <1 MB #5984', function() 3029 local very_long_name = 'A' .. ('x'):rep(10000) .. 'Z' 3030 local status, err = pcall(api.nvim_get_option_value, very_long_name, {}) 3031 eq(false, status) 3032 eq(very_long_name, err:match('Ax+Z?')) 3033 end) 3034 3035 it('does not leak memory on incorrect argument types', function() 3036 local status, err = pcall(api.nvim_set_current_dir, { 'not', 'a', 'dir' }) 3037 eq(false, status) 3038 matches(': Wrong type for argument 1 when calling nvim_set_current_dir, expecting String', err) 3039 end) 3040 3041 describe('nvim_parse_expression', function() 3042 before_each(function() 3043 api.nvim_set_option_value('isident', '', {}) 3044 end) 3045 3046 local function simplify_east_api_node(line, east_api_node) 3047 if east_api_node == NIL then 3048 return nil 3049 end 3050 if east_api_node.children then 3051 for k, v in pairs(east_api_node.children) do 3052 east_api_node.children[k] = simplify_east_api_node(line, v) 3053 end 3054 end 3055 local typ = east_api_node.type 3056 if typ == 'Register' then 3057 typ = typ .. ('(name=%s)'):format(tostring(intchar2lua(east_api_node.name))) 3058 east_api_node.name = nil 3059 elseif typ == 'PlainIdentifier' then 3060 typ = typ 3061 .. ('(scope=%s,ident=%s)'):format( 3062 tostring(intchar2lua(east_api_node.scope)), 3063 east_api_node.ident 3064 ) 3065 east_api_node.scope = nil 3066 east_api_node.ident = nil 3067 elseif typ == 'PlainKey' then 3068 typ = typ .. ('(key=%s)'):format(east_api_node.ident) 3069 east_api_node.ident = nil 3070 elseif typ == 'Comparison' then 3071 typ = typ 3072 .. ('(type=%s,inv=%u,ccs=%s)'):format( 3073 east_api_node.cmp_type, 3074 east_api_node.invert and 1 or 0, 3075 east_api_node.ccs_strategy 3076 ) 3077 east_api_node.ccs_strategy = nil 3078 east_api_node.cmp_type = nil 3079 east_api_node.invert = nil 3080 elseif typ == 'Integer' then 3081 typ = typ .. ('(val=%u)'):format(east_api_node.ivalue) 3082 east_api_node.ivalue = nil 3083 elseif typ == 'Float' then 3084 typ = typ .. format_string('(val=%e)', east_api_node.fvalue) 3085 east_api_node.fvalue = nil 3086 elseif typ == 'SingleQuotedString' or typ == 'DoubleQuotedString' then 3087 typ = format_string('%s(val=%q)', typ, east_api_node.svalue) 3088 east_api_node.svalue = nil 3089 elseif typ == 'Option' then 3090 typ = ('%s(scope=%s,ident=%s)'):format( 3091 typ, 3092 tostring(intchar2lua(east_api_node.scope)), 3093 east_api_node.ident 3094 ) 3095 east_api_node.ident = nil 3096 east_api_node.scope = nil 3097 elseif typ == 'Environment' then 3098 typ = ('%s(ident=%s)'):format(typ, east_api_node.ident) 3099 east_api_node.ident = nil 3100 elseif typ == 'Assignment' then 3101 local aug = east_api_node.augmentation 3102 if aug == '' then 3103 aug = 'Plain' 3104 end 3105 typ = ('%s(%s)'):format(typ, aug) 3106 east_api_node.augmentation = nil 3107 end 3108 typ = ('%s:%u:%u:%s'):format( 3109 typ, 3110 east_api_node.start[1], 3111 east_api_node.start[2], 3112 line:sub(east_api_node.start[2] + 1, east_api_node.start[2] + 1 + east_api_node.len - 1) 3113 ) 3114 assert(east_api_node.start[2] + east_api_node.len - 1 <= #line) 3115 for k, _ in pairs(east_api_node.start) do 3116 assert(({ true, true })[k]) 3117 end 3118 east_api_node.start = nil 3119 east_api_node.type = nil 3120 east_api_node.len = nil 3121 local can_simplify = true 3122 for _, _ in pairs(east_api_node) do 3123 if can_simplify then 3124 can_simplify = false 3125 end 3126 end 3127 if can_simplify then 3128 return typ 3129 else 3130 east_api_node[1] = typ 3131 return east_api_node 3132 end 3133 end 3134 local function simplify_east_api(line, east_api) 3135 if east_api.error then 3136 east_api.err = east_api.error 3137 east_api.error = nil 3138 east_api.err.msg = east_api.err.message 3139 east_api.err.message = nil 3140 end 3141 if east_api.ast then 3142 east_api.ast = { simplify_east_api_node(line, east_api.ast) } 3143 if #east_api.ast == 0 then 3144 east_api.ast = nil 3145 end 3146 end 3147 if east_api.len == #line then 3148 east_api.len = nil 3149 end 3150 return east_api 3151 end 3152 local function simplify_east_hl(line, east_hl) 3153 for i, v in ipairs(east_hl) do 3154 east_hl[i] = ('%s:%u:%u:%s'):format(v[4], v[1], v[2], line:sub(v[2] + 1, v[3])) 3155 end 3156 return east_hl 3157 end 3158 local FLAGS_TO_STR = { 3159 [0] = '', 3160 [1] = 'm', 3161 [2] = 'E', 3162 [3] = 'mE', 3163 [4] = 'l', 3164 [5] = 'lm', 3165 [6] = 'lE', 3166 [7] = 'lmE', 3167 } 3168 local function _check_parsing(opts, str, exp_ast, exp_highlighting_fs, nz_flags_exps) 3169 if type(str) ~= 'string' then 3170 return 3171 end 3172 local zflags = opts.flags[1] 3173 nz_flags_exps = nz_flags_exps or {} 3174 for _, flags in ipairs(opts.flags) do 3175 local err, msg = pcall(function() 3176 local east_api = api.nvim_parse_expression(str, FLAGS_TO_STR[flags], true) 3177 local east_hl = east_api.highlight 3178 east_api.highlight = nil 3179 local ast = simplify_east_api(str, east_api) 3180 local hls = simplify_east_hl(str, east_hl) 3181 local exps = { 3182 ast = exp_ast, 3183 hl_fs = exp_highlighting_fs, 3184 } 3185 local add_exps = nz_flags_exps[flags] 3186 if not add_exps and flags == 3 + zflags then 3187 add_exps = nz_flags_exps[1 + zflags] or nz_flags_exps[2 + zflags] 3188 end 3189 if add_exps then 3190 if add_exps.ast then 3191 exps.ast = mergedicts_copy(exps.ast, add_exps.ast) 3192 end 3193 if add_exps.hl_fs then 3194 exps.hl_fs = mergedicts_copy(exps.hl_fs, add_exps.hl_fs) 3195 end 3196 end 3197 eq(exps.ast, ast) 3198 if exp_highlighting_fs then 3199 local exp_highlighting = {} 3200 local next_col = 0 3201 for i, h in ipairs(exps.hl_fs) do 3202 exp_highlighting[i], next_col = h(next_col) 3203 end 3204 eq(exp_highlighting, hls) 3205 end 3206 end) 3207 if not err then 3208 if type(msg) == 'table' then 3209 local merr, new_msg = pcall(format_string, 'table error:\n%s\n\n(%r)', msg.message, msg) 3210 if merr then 3211 msg = new_msg 3212 else 3213 msg = format_string('table error without .message:\n(%r)', msg) 3214 end 3215 elseif type(msg) ~= 'string' then 3216 msg = format_string('non-string non-table error:\n%r', msg) 3217 end 3218 error( 3219 format_string( 3220 'Error while processing test (%r, %s):\n%s', 3221 str, 3222 FLAGS_TO_STR[flags], 3223 msg 3224 ) 3225 ) 3226 end 3227 end 3228 end 3229 local function hl(group, str, shift) 3230 return function(next_col) 3231 local col = next_col + (shift or 0) 3232 return (('%s:%u:%u:%s'):format('Nvim' .. group, 0, col, str)), (col + #str) 3233 end 3234 end 3235 local function fmtn(typ, args, rest) 3236 if 3237 typ == 'UnknownFigure' 3238 or typ == 'DictLiteral' 3239 or typ == 'CurlyBracesIdentifier' 3240 or typ == 'Lambda' 3241 then 3242 return ('%s%s'):format(typ, rest) 3243 elseif typ == 'DoubleQuotedString' or typ == 'SingleQuotedString' then 3244 if args:sub(-4) == 'NULL' then 3245 args = args:sub(1, -5) .. '""' 3246 end 3247 return ('%s(%s)%s'):format(typ, args, rest) 3248 end 3249 end 3250 3251 it('does not crash parsing invalid VimL expression', function() 3252 api.nvim_input(':<C-r>=') 3253 api.nvim_input('1bork/') -- #29648 3254 assert_alive() 3255 api.nvim_input('<C-u>];') 3256 assert_alive() 3257 api.nvim_parse_expression('a{b}', '', false) 3258 assert_alive() 3259 end) 3260 3261 require('test.unit.viml.expressions.parser_tests')(it, _check_parsing, hl, fmtn) 3262 end) 3263 3264 describe('nvim_list_uis', function() 3265 it('returns empty if --headless', function() 3266 -- Test runner defaults to --headless. 3267 eq({}, api.nvim_list_uis()) 3268 end) 3269 it('returns attached UIs', function() 3270 local screen = Screen.new(20, 4, { override = true }) 3271 local expected = { 3272 { 3273 chan = 1, 3274 ext_cmdline = false, 3275 ext_hlstate = false, 3276 ext_linegrid = screen._options.ext_linegrid or false, 3277 ext_messages = false, 3278 ext_multigrid = false, 3279 ext_popupmenu = false, 3280 ext_tabline = false, 3281 ext_termcolors = false, 3282 ext_wildmenu = false, 3283 height = 4, 3284 override = true, 3285 rgb = true, 3286 stdin_tty = false, 3287 stdout_tty = false, 3288 term_background = '', 3289 term_colors = 0, 3290 term_name = '', 3291 width = 20, 3292 }, 3293 } 3294 3295 eq(expected, api.nvim_list_uis()) 3296 3297 screen:detach() 3298 screen = Screen.new(44, 99, { rgb = false }) -- luacheck: ignore 3299 expected[1].rgb = false 3300 expected[1].override = false 3301 expected[1].width = 44 3302 expected[1].height = 99 3303 eq(expected, api.nvim_list_uis()) 3304 end) 3305 end) 3306 3307 describe('nvim_create_namespace', function() 3308 it('works', function() 3309 local orig = api.nvim_get_namespaces() 3310 local base = vim.iter(orig):fold(0, function(acc, _, v) 3311 return math.max(acc, v) 3312 end) 3313 eq(base + 1, api.nvim_create_namespace('ns-1')) 3314 eq(base + 2, api.nvim_create_namespace('ns-2')) 3315 eq(base + 1, api.nvim_create_namespace('ns-1')) 3316 3317 local expected = vim.tbl_extend('error', orig, { 3318 ['ns-1'] = base + 1, 3319 ['ns-2'] = base + 2, 3320 }) 3321 3322 eq(expected, api.nvim_get_namespaces()) 3323 eq(base + 3, api.nvim_create_namespace('')) 3324 eq(base + 4, api.nvim_create_namespace('')) 3325 eq(expected, api.nvim_get_namespaces()) 3326 end) 3327 end) 3328 3329 describe('nvim_create_buf', function() 3330 it('works', function() 3331 eq(2, api.nvim_create_buf(true, false)) 3332 eq(3, api.nvim_create_buf(false, false)) 3333 eq( 3334 ' 1 %a "[No Name]" line 1\n' 3335 .. ' 2 h "[No Name]" line 0', 3336 command_output('ls') 3337 ) 3338 -- current buffer didn't change 3339 eq(1, api.nvim_get_current_buf()) 3340 3341 local screen = Screen.new(20, 4) 3342 api.nvim_buf_set_lines(2, 0, -1, true, { 'some text' }) 3343 api.nvim_set_current_buf(2) 3344 screen:expect( 3345 [[ 3346 ^some text | 3347 {1:~ }|*2 3348 | 3349 ]], 3350 { 3351 [1] = { bold = true, foreground = Screen.colors.Blue1 }, 3352 } 3353 ) 3354 end) 3355 3356 it('can change buftype before visiting', function() 3357 api.nvim_set_option_value('hidden', false, {}) 3358 eq(2, api.nvim_create_buf(true, false)) 3359 api.nvim_set_option_value('buftype', 'nofile', { buf = 2 }) 3360 api.nvim_buf_set_lines(2, 0, -1, true, { 'test text' }) 3361 command('split | buffer 2') 3362 eq(2, api.nvim_get_current_buf()) 3363 -- if the buf_set_option("buftype") didn't work, this would error out. 3364 command('close') 3365 eq(1, api.nvim_get_current_buf()) 3366 end) 3367 3368 it('does not trigger BufEnter, BufWinEnter', function() 3369 command('let g:fired = v:false') 3370 command('au BufEnter,BufWinEnter * let g:fired = v:true') 3371 3372 eq(2, api.nvim_create_buf(true, false)) 3373 api.nvim_buf_set_lines(2, 0, -1, true, { 'test', 'text' }) 3374 3375 eq(false, eval('g:fired')) 3376 end) 3377 3378 it('TextChanged and TextChangedI do not trigger without changes', function() 3379 local buf = api.nvim_create_buf(true, false) 3380 command([[let g:changed = '']]) 3381 api.nvim_create_autocmd({ 'TextChanged', 'TextChangedI' }, { 3382 buffer = buf, 3383 command = 'let g:changed ..= mode()', 3384 }) 3385 api.nvim_set_current_buf(buf) 3386 feed('i') 3387 eq('', api.nvim_get_var('changed')) 3388 end) 3389 3390 it('scratch-buffer', function() 3391 eq(2, api.nvim_create_buf(false, true)) 3392 eq(3, api.nvim_create_buf(true, true)) 3393 eq(4, api.nvim_create_buf(true, true)) 3394 local scratch_bufs = { 2, 3, 4 } 3395 eq( 3396 ' 1 %a "[No Name]" line 1\n' 3397 .. ' 3 h "[Scratch]" line 0\n' 3398 .. ' 4 h "[Scratch]" line 0', 3399 exec_capture('ls') 3400 ) 3401 -- current buffer didn't change 3402 eq(1, api.nvim_get_current_buf()) 3403 3404 local screen = Screen.new(20, 4) 3405 3406 -- 3407 -- Editing a scratch-buffer does NOT change its properties. 3408 -- 3409 local edited_buf = 2 3410 api.nvim_buf_set_lines(edited_buf, 0, -1, true, { 'some text' }) 3411 for _, b in ipairs(scratch_bufs) do 3412 eq('nofile', api.nvim_get_option_value('buftype', { buf = b })) 3413 eq('hide', api.nvim_get_option_value('bufhidden', { buf = b })) 3414 eq(false, api.nvim_get_option_value('swapfile', { buf = b })) 3415 eq(false, api.nvim_get_option_value('modeline', { buf = b })) 3416 end 3417 3418 -- 3419 -- Visiting a scratch-buffer DOES NOT change its properties. 3420 -- 3421 api.nvim_set_current_buf(edited_buf) 3422 screen:expect([[ 3423 ^some text | 3424 {1:~ }|*2 3425 | 3426 ]]) 3427 eq('nofile', api.nvim_get_option_value('buftype', { buf = edited_buf })) 3428 eq('hide', api.nvim_get_option_value('bufhidden', { buf = edited_buf })) 3429 eq(false, api.nvim_get_option_value('swapfile', { buf = edited_buf })) 3430 eq(false, api.nvim_get_option_value('modeline', { buf = edited_buf })) 3431 3432 -- Scratch buffer can be wiped without error. 3433 command('bwipe') 3434 screen:expect([[ 3435 ^ | 3436 {1:~ }|*2 3437 | 3438 ]]) 3439 end) 3440 3441 it('does not cause heap-use-after-free on exit while setting options', function() 3442 command('au OptionSet * q') 3443 command('silent! call nvim_create_buf(0, 1)') 3444 -- nowadays this works because we don't execute any spurious autocmds at all #24824 3445 assert_alive() 3446 end) 3447 3448 it('no memory leak when autocommands load the buffer immediately', function() 3449 exec([[ 3450 autocmd BufNew * ++once call bufload(expand("<abuf>")->str2nr()) 3451 \| let loaded = bufloaded(expand("<abuf>")->str2nr()) 3452 ]]) 3453 api.nvim_create_buf(false, true) 3454 eq(1, eval('g:loaded')) 3455 end) 3456 3457 it('creating scratch buffer where autocommands set &swapfile works', function() 3458 exec([[ 3459 autocmd BufNew * ++once execute expand("<abuf>") "buffer" 3460 \| file foobar 3461 \| setlocal swapfile 3462 ]]) 3463 local new_buf = api.nvim_create_buf(false, true) 3464 neq('', fn.swapname(new_buf)) 3465 end) 3466 3467 it('fires expected autocommands', function() 3468 exec([=[ 3469 " Append the &buftype to check autocommands trigger *after* the buffer was configured to be 3470 " scratch, if applicable. 3471 autocmd BufNew * let fired += [["BufNew", expand("<abuf>")->str2nr(), 3472 \ getbufvar(expand("<abuf>")->str2nr(), "&buftype")]] 3473 autocmd BufAdd * let fired += [["BufAdd", expand("<abuf>")->str2nr(), 3474 \ getbufvar(expand("<abuf>")->str2nr(), "&buftype")]] 3475 3476 " Don't want to see OptionSet; buffer options set from passing true for "scratch", etc. 3477 " should be configured invisibly, and before autocommands. 3478 autocmd OptionSet * let fired += [["OptionSet", expand("<amatch>")]] 3479 3480 let fired = [] 3481 ]=]) 3482 local new_buf = api.nvim_create_buf(false, false) 3483 eq({ { 'BufNew', new_buf, '' } }, eval('g:fired')) 3484 3485 command('let fired = []') 3486 new_buf = api.nvim_create_buf(false, true) 3487 eq({ { 'BufNew', new_buf, 'nofile' } }, eval('g:fired')) 3488 3489 command('let fired = []') 3490 new_buf = api.nvim_create_buf(true, false) 3491 eq({ { 'BufNew', new_buf, '' }, { 'BufAdd', new_buf, '' } }, eval('g:fired')) 3492 3493 command('let fired = []') 3494 new_buf = api.nvim_create_buf(true, true) 3495 eq({ { 'BufNew', new_buf, 'nofile' }, { 'BufAdd', new_buf, 'nofile' } }, eval('g:fired')) 3496 end) 3497 end) 3498 3499 describe('nvim_get_runtime_file', function() 3500 local p = t.fix_slashes 3501 it('can find files', function() 3502 eq({}, api.nvim_get_runtime_file('bork.borkbork', false)) 3503 eq({}, api.nvim_get_runtime_file('bork.borkbork', true)) 3504 eq(1, #api.nvim_get_runtime_file('autoload/msgpack.vim', false)) 3505 eq(1, #api.nvim_get_runtime_file('autoload/msgpack.vim', true)) 3506 local val = api.nvim_get_runtime_file('autoload/remote/*.vim', true) 3507 eq(2, #val) 3508 if endswith(val[1], 'define.vim') then 3509 ok(endswith(p(val[1]), 'autoload/remote/define.vim')) 3510 ok(endswith(p(val[2]), 'autoload/remote/host.vim')) 3511 else 3512 ok(endswith(p(val[1]), 'autoload/remote/host.vim')) 3513 ok(endswith(p(val[2]), 'autoload/remote/define.vim')) 3514 end 3515 val = api.nvim_get_runtime_file('autoload/remote/*.vim', false) 3516 eq(1, #val) 3517 ok( 3518 endswith(p(val[1]), 'autoload/remote/define.vim') 3519 or endswith(p(val[1]), 'autoload/remote/host.vim') 3520 ) 3521 3522 val = api.nvim_get_runtime_file('lua', true) 3523 eq(1, #val) 3524 ok(endswith(p(val[1]), 'lua')) 3525 3526 val = api.nvim_get_runtime_file('lua/vim', true) 3527 eq(1, #val) 3528 ok(endswith(p(val[1]), 'lua/vim')) 3529 end) 3530 3531 it('can find directories', function() 3532 local val = api.nvim_get_runtime_file('lua/', true) 3533 eq(1, #val) 3534 ok(endswith(p(val[1]), 'lua/')) 3535 3536 val = api.nvim_get_runtime_file('lua/vim/', true) 3537 eq(1, #val) 3538 ok(endswith(p(val[1]), 'lua/vim/')) 3539 3540 eq({}, api.nvim_get_runtime_file('foobarlang/', true)) 3541 end) 3542 it('can handle bad patterns', function() 3543 skip(is_os('win')) 3544 3545 eq('Vim:E220: Missing }.', pcall_err(api.nvim_get_runtime_file, '{', false)) 3546 3547 eq( 3548 'Vim(echo):E5555: API call: Vim:E220: Missing }.', 3549 exc_exec("echo nvim_get_runtime_file('{', v:false)") 3550 ) 3551 end) 3552 it('preserves order of runtimepath', function() 3553 local vimruntime = fn.getenv('VIMRUNTIME') 3554 local rtp = string.format('%s/syntax,%s/ftplugin', vimruntime, vimruntime) 3555 api.nvim_set_option_value('runtimepath', rtp, {}) 3556 3557 local val = api.nvim_get_runtime_file('vim.vim', true) 3558 eq(2, #val) 3559 eq(p(val[1]), vimruntime .. '/syntax/vim.vim') 3560 eq(p(val[2]), vimruntime .. '/ftplugin/vim.vim') 3561 end) 3562 end) 3563 3564 describe('nvim_get_all_options_info', function() 3565 it('should have key value pairs of option names', function() 3566 local options_info = api.nvim_get_all_options_info() 3567 neq(nil, options_info.listchars) 3568 neq(nil, options_info.tabstop) 3569 3570 eq(api.nvim_get_option_info 'winhighlight', options_info.winhighlight) 3571 end) 3572 3573 it('should not crash when echoed', function() 3574 api.nvim_exec2('echo nvim_get_all_options_info()', { output = true }) 3575 end) 3576 end) 3577 3578 describe('nvim_get_option_info', function() 3579 it('should error for unknown options', function() 3580 eq("Invalid option (not found): 'bogus'", pcall_err(api.nvim_get_option_info, 'bogus')) 3581 end) 3582 3583 it('should return the same options for short and long name', function() 3584 eq(api.nvim_get_option_info 'winhl', api.nvim_get_option_info 'winhighlight') 3585 end) 3586 3587 it('should have information about window options', function() 3588 eq({ 3589 allows_duplicates = false, 3590 commalist = true, 3591 default = '', 3592 flaglist = false, 3593 global_local = false, 3594 last_set_chan = 0, 3595 last_set_linenr = 0, 3596 last_set_sid = 0, 3597 name = 'winhighlight', 3598 scope = 'win', 3599 shortname = 'winhl', 3600 type = 'string', 3601 was_set = false, 3602 }, api.nvim_get_option_info 'winhl') 3603 end) 3604 3605 it('should have information about buffer options', function() 3606 eq({ 3607 allows_duplicates = true, 3608 commalist = false, 3609 default = '', 3610 flaglist = false, 3611 global_local = false, 3612 last_set_chan = 0, 3613 last_set_linenr = 0, 3614 last_set_sid = 0, 3615 name = 'filetype', 3616 scope = 'buf', 3617 shortname = 'ft', 3618 type = 'string', 3619 was_set = false, 3620 }, api.nvim_get_option_info 'filetype') 3621 end) 3622 3623 it('should have information about global options', function() 3624 -- precondition: the option was changed from its default 3625 -- in test setup. 3626 eq(false, api.nvim_get_option_value('showcmd', {})) 3627 3628 eq({ 3629 allows_duplicates = true, 3630 commalist = false, 3631 default = true, 3632 flaglist = false, 3633 global_local = false, 3634 last_set_chan = 0, 3635 last_set_linenr = 0, 3636 last_set_sid = -2, 3637 name = 'showcmd', 3638 scope = 'global', 3639 shortname = 'sc', 3640 type = 'boolean', 3641 was_set = true, 3642 }, api.nvim_get_option_info 'showcmd') 3643 3644 api.nvim_set_option_value('showcmd', true, {}) 3645 3646 eq({ 3647 allows_duplicates = true, 3648 commalist = false, 3649 default = true, 3650 flaglist = false, 3651 global_local = false, 3652 last_set_chan = 1, 3653 last_set_linenr = 0, 3654 last_set_sid = -9, 3655 name = 'showcmd', 3656 scope = 'global', 3657 shortname = 'sc', 3658 type = 'boolean', 3659 was_set = true, 3660 }, api.nvim_get_option_info 'showcmd') 3661 end) 3662 end) 3663 3664 describe('nvim_get_option_info2', function() 3665 local fname 3666 local bufs 3667 local wins 3668 3669 before_each(function() 3670 fname = tmpname() 3671 write_file( 3672 fname, 3673 [[ 3674 setglobal dictionary=mydict " 1, global-local (buffer) 3675 setlocal formatprg=myprg " 2, global-local (buffer) 3676 setglobal equalprg=prg1 " 3, global-local (buffer) 3677 setlocal equalprg=prg2 " 4, global-local (buffer) 3678 setglobal fillchars=stl:x " 5, global-local (window) 3679 setlocal listchars=eol:c " 6, global-local (window) 3680 setglobal showbreak=aaa " 7, global-local (window) 3681 setlocal showbreak=bbb " 8, global-local (window) 3682 setglobal completeopt=menu " 9, global 3683 ]] 3684 ) 3685 3686 exec_lua 'vim.cmd.vsplit()' 3687 api.nvim_create_buf(false, false) 3688 3689 bufs = api.nvim_list_bufs() 3690 wins = api.nvim_list_wins() 3691 3692 api.nvim_win_set_buf(wins[1], bufs[1]) 3693 api.nvim_win_set_buf(wins[2], bufs[2]) 3694 3695 api.nvim_set_current_win(wins[2]) 3696 api.nvim_exec('source ' .. fname, false) 3697 3698 api.nvim_set_current_win(wins[1]) 3699 end) 3700 3701 after_each(function() 3702 os.remove(fname) 3703 end) 3704 3705 it('should return option information', function() 3706 eq(api.nvim_get_option_info('dictionary'), api.nvim_get_option_info2('dictionary', {})) -- buffer 3707 eq(api.nvim_get_option_info('fillchars'), api.nvim_get_option_info2('fillchars', {})) -- window 3708 eq(api.nvim_get_option_info('completeopt'), api.nvim_get_option_info2('completeopt', {})) -- global 3709 end) 3710 3711 describe('last set', function() 3712 -- stylua: ignore 3713 local tests = { 3714 {desc="(buf option, global requested, global set) points to global", linenr=1, sid=1, args={'dictionary', {scope='global'}}}, 3715 {desc="(buf option, global requested, local set) is not set", linenr=0, sid=0, args={'formatprg', {scope='global'}}}, 3716 {desc="(buf option, global requested, both set) points to global", linenr=3, sid=1, args={'equalprg', {scope='global'}}}, 3717 {desc="(buf option, local requested, global set) is not set", linenr=0, sid=0, args={'dictionary', {scope='local'}}}, 3718 {desc="(buf option, local requested, local set) points to local", linenr=2, sid=1, args={'formatprg', {scope='local'}}}, 3719 {desc="(buf option, local requested, both set) points to local", linenr=4, sid=1, args={'equalprg', {scope='local'}}}, 3720 {desc="(buf option, fallback requested, global set) points to global", linenr=1, sid=1, args={'dictionary', {}}}, 3721 {desc="(buf option, fallback requested, local set) points to local", linenr=2, sid=1, args={'formatprg', {}}}, 3722 {desc="(buf option, fallback requested, both set) points to local", linenr=4, sid=1, args={'equalprg', {}}}, 3723 {desc="(win option, global requested, global set) points to global", linenr=5, sid=1, args={'fillchars', {scope='global'}}}, 3724 {desc="(win option, global requested, local set) is not set", linenr=0, sid=0, args={'listchars', {scope='global'}}}, 3725 {desc="(win option, global requested, both set) points to global", linenr=7, sid=1, args={'showbreak', {scope='global'}}}, 3726 {desc="(win option, local requested, global set) is not set", linenr=0, sid=0, args={'fillchars', {scope='local'}}}, 3727 {desc="(win option, local requested, local set) points to local", linenr=6, sid=1, args={'listchars', {scope='local'}}}, 3728 {desc="(win option, local requested, both set) points to local", linenr=8, sid=1, args={'showbreak', {scope='local'}}}, 3729 {desc="(win option, fallback requested, global set) points to global", linenr=5, sid=1, args={'fillchars', {}}}, 3730 {desc="(win option, fallback requested, local set) points to local", linenr=6, sid=1, args={'listchars', {}}}, 3731 {desc="(win option, fallback requested, both set) points to local", linenr=8, sid=1, args={'showbreak', {}}}, 3732 {desc="(global option, global requested) points to global", linenr=9, sid=1, args={'completeopt', {scope='global'}}}, 3733 {desc="(global option, local requested) is not set", linenr=0, sid=0, args={'completeopt', {scope='local'}}}, 3734 {desc="(global option, fallback requested) points to global", linenr=9, sid=1, args={'completeopt', {}}}, 3735 } 3736 3737 for _, test in pairs(tests) do 3738 it(test.desc, function() 3739 -- Switch to the target buffer/window so that curbuf/curwin are used. 3740 api.nvim_set_current_win(wins[2]) 3741 local info = api.nvim_get_option_info2(unpack(test.args)) 3742 eq(test.linenr, info.last_set_linenr) 3743 eq(test.sid, info.last_set_sid) 3744 end) 3745 end 3746 3747 it('is provided for cross-buffer requests', function() 3748 local info = api.nvim_get_option_info2('formatprg', { buf = bufs[2] }) 3749 eq(2, info.last_set_linenr) 3750 eq(1, info.last_set_sid) 3751 end) 3752 3753 it('is provided for cross-window requests', function() 3754 local info = api.nvim_get_option_info2('listchars', { win = wins[2] }) 3755 eq(6, info.last_set_linenr) 3756 eq(1, info.last_set_sid) 3757 end) 3758 end) 3759 end) 3760 3761 describe('nvim_echo', function() 3762 local screen 3763 3764 before_each(function() 3765 screen = Screen.new(40, 8) 3766 command('highlight Statement gui=bold guifg=Brown') 3767 command('highlight Special guifg=SlateBlue') 3768 end) 3769 3770 it('validation', function() 3771 eq("Invalid 'chunk': expected Array, got String", pcall_err(api.nvim_echo, { 'msg' }, 1, {})) 3772 eq( 3773 'Invalid chunk: expected Array with 1 or 2 Strings', 3774 pcall_err(api.nvim_echo, { { '', '', '' } }, 1, {}) 3775 ) 3776 eq('Invalid hl_group: text highlight', pcall_err(api.nvim_echo, { { '', false } }, 1, {})) 3777 end) 3778 3779 it('should clear cmdline message before echo', function() 3780 feed(':call nvim_echo([["msg"]], v:false, {})<CR>') 3781 screen:expect { 3782 grid = [[ 3783 ^ | 3784 {1:~ }|*6 3785 msg | 3786 ]], 3787 } 3788 end) 3789 3790 it('can show highlighted line', function() 3791 async_meths.nvim_echo( 3792 { { 'msg_a' }, { 'msg_b', 'Statement' }, { 'msg_c', 'Special' } }, 3793 true, 3794 {} 3795 ) 3796 screen:expect { 3797 grid = [[ 3798 ^ | 3799 {1:~ }|*6 3800 msg_a{15:msg_b}{16:msg_c} | 3801 ]], 3802 } 3803 async_meths.nvim_echo({ 3804 { 'msg_d' }, 3805 { 'msg_e', api.nvim_get_hl_id_by_name('Statement') }, 3806 { 'msg_f', api.nvim_get_hl_id_by_name('Special') }, 3807 }, true, {}) 3808 screen:expect { 3809 grid = [[ 3810 ^ | 3811 {1:~ }|*6 3812 msg_d{15:msg_e}{16:msg_f} | 3813 ]], 3814 } 3815 end) 3816 3817 it('can show highlighted multiline', function() 3818 async_meths.nvim_echo({ { 'msg_a\nmsg_a', 'Statement' }, { 'msg_b', 'Special' } }, true, {}) 3819 screen:expect { 3820 grid = [[ 3821 | 3822 {1:~ }|*3 3823 {3: }| 3824 {15:msg_a} | 3825 {15:msg_a}{16:msg_b} | 3826 {6:Press ENTER or type command to continue}^ | 3827 ]], 3828 } 3829 end) 3830 3831 it('can save message history', function() 3832 command('set cmdheight=2') -- suppress Press ENTER 3833 api.nvim_echo({ { 'msg\nmsg' }, { 'msg' } }, true, {}) 3834 eq('msg\nmsgmsg', exec_capture('messages')) 3835 end) 3836 3837 it('can disable saving message history', function() 3838 command('set cmdheight=2') -- suppress Press ENTER 3839 async_meths.nvim_echo({ { 'msg\nmsg' }, { 'msg' } }, false, {}) 3840 eq('', exec_capture('messages')) 3841 end) 3842 3843 it('can print error message', function() 3844 async_meths.nvim_echo({ { 'Error\nMessage' } }, false, { err = true }) 3845 screen:expect([[ 3846 | 3847 {1:~ }|*3 3848 {3: }| 3849 {9:Error} | 3850 {9:Message} | 3851 {6:Press ENTER or type command to continue}^ | 3852 ]]) 3853 feed(':messages<CR>') 3854 screen:expect([[ 3855 ^ | 3856 {1:~ }|*6 3857 | 3858 ]]) 3859 async_meths.nvim_echo({ { 'Error' }, { 'Message', 'Special' } }, false, { err = true }) 3860 screen:expect([[ 3861 ^ | 3862 {1:~ }|*6 3863 {9:Error}{16:Message} | 3864 ]]) 3865 end) 3866 3867 it('increments message ID', function() 3868 eq(1, api.nvim_echo({ { 'foo' } }, false, {})) 3869 eq(4, api.nvim_echo({ { 'foo' } }, false, { id = 4 })) 3870 eq(5, api.nvim_echo({ { 'foo' } }, false, {})) 3871 end) 3872 end) 3873 3874 describe('nvim_open_term', function() 3875 local screen 3876 3877 before_each(function() 3878 screen = Screen.new(100, 35) 3879 screen:add_extra_attr_ids { 3880 [100] = { background = tonumber('0xffff40'), bg_indexed = true }, 3881 [101] = { 3882 background = Screen.colors.LightMagenta, 3883 foreground = tonumber('0x00e000'), 3884 fg_indexed = true, 3885 }, 3886 [102] = { background = Screen.colors.LightMagenta, reverse = true }, 3887 [103] = { background = Screen.colors.LightMagenta, bold = true, reverse = true }, 3888 [104] = { fg_indexed = true, foreground = tonumber('0xe00000') }, 3889 [105] = { fg_indexed = true, foreground = tonumber('0xe0e000') }, 3890 } 3891 end) 3892 3893 it('can batch process sequences', function() 3894 local b = api.nvim_create_buf(true, true) 3895 api.nvim_open_win( 3896 b, 3897 false, 3898 { width = 79, height = 31, row = 1, col = 1, relative = 'editor' } 3899 ) 3900 local term = api.nvim_open_term(b, {}) 3901 3902 api.nvim_chan_send(term, io.open('test/functional/fixtures/smile2.cat', 'r'):read('*a')) 3903 screen:expect { 3904 grid = [[ 3905 ^ | 3906 {1:~}{4::smile }{1: }| 3907 {1:~}{4: }{100:oooo$$$$$$$$$$$$oooo}{4: }{1: }| 3908 {1:~}{4: }{100:oo$$$$$$$$$$$$$$$$$$$$$$$$o}{4: }{1: }| 3909 {1:~}{4: }{100:oo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o}{4: }{100:o$}{4: }{100:$$}{4: }{100:o$}{4: }{1: }| 3910 {1:~}{4: }{100:o}{4: }{100:$}{4: }{100:oo}{4: }{100:o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o}{4: }{100:$$}{4: }{100:$$}{4: }{100:$$o$}{4: }{1: }| 3911 {1:~}{4: }{100:oo}{4: }{100:$}{4: }{100:$}{4: "}{100:$}{4: }{100:o$$$$$$$$$}{4: }{100:$$$$$$$$$$$$$}{4: }{100:$$$$$$$$$o}{4: }{100:$$$o$$o$}{4: }{1: }| 3912 {1:~}{4: "}{100:$$$$$$o$}{4: }{100:o$$$$$$$$$}{4: }{100:$$$$$$$$$$$}{4: }{100:$$$$$$$$$$o}{4: }{100:$$$$$$$$}{4: }{1: }| 3913 {1:~}{4: }{100:$$$$$$$}{4: }{100:$$$$$$$$$$$}{4: }{100:$$$$$$$$$$$}{4: }{100:$$$$$$$$$$$$$$$$$$$$$$$}{4: }{1: }| 3914 {1:~}{4: }{100:$$$$$$$$$$$$$$$$$$$$$$$}{4: }{100:$$$$$$$$$$$$$}{4: }{100:$$$$$$$$$$$$$$}{4: """}{100:$$$}{4: }{1: }| 3915 {1:~}{4: "}{100:$$$}{4:""""}{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4: "}{100:$$$}{4: }{1: }| 3916 {1:~}{4: }{100:$$$}{4: }{100:o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4: "}{100:$$$o}{4: }{1: }| 3917 {1:~}{4: }{100:o$$}{4:" }{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4: }{100:$$$o}{4: }{1: }| 3918 {1:~}{4: }{100:$$$}{4: }{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4:" "}{100:$$$$$$ooooo$$$$o}{4: }{1: }| 3919 {1:~}{4: }{100:o$$$oooo$$$$$}{4: }{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4: }{100:o$$$$$$$$$$$$$$$$$}{4: }{1: }| 3920 {1:~}{4: }{100:$$$$$$$$}{4:"}{100:$$$$}{4: }{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4: }{100:$$$$}{4:"""""""" }{1: }| 3921 {1:~}{4: """" }{100:$$$$}{4: "}{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4:" }{100:o$$$}{4: }{1: }| 3922 {1:~}{4: "}{100:$$$o}{4: """}{100:$$$$$$$$$$$$$$$$$$}{4:"}{100:$$}{4:" }{100:$$$}{4: }{1: }| 3923 {1:~}{4: }{100:$$$o}{4: "}{100:$$}{4:""}{100:$$$$$$}{4:"""" }{100:o$$$}{4: }{1: }| 3924 {1:~}{4: }{100:$$$$o}{4: }{100:o$$$}{4:" }{1: }| 3925 {1:~}{4: "}{100:$$$$o}{4: }{100:o$$$$$$o}{4:"}{100:$$$$o}{4: }{100:o$$$$}{4: }{1: }| 3926 {1:~}{4: "}{100:$$$$$oo}{4: ""}{100:$$$$o$$$$$o}{4: }{100:o$$$$}{4:"" }{1: }| 3927 {1:~}{4: ""}{100:$$$$$oooo}{4: "}{100:$$$o$$$$$$$$$}{4:""" }{1: }| 3928 {1:~}{4: ""}{100:$$$$$$$oo}{4: }{100:$$$$$$$$$$}{4: }{1: }| 3929 {1:~}{4: """"}{100:$$$$$$$$$$$}{4: }{1: }| 3930 {1:~}{4: }{100:$$$$$$$$$$$$}{4: }{1: }| 3931 {1:~}{4: }{100:$$$$$$$$$$}{4:" }{1: }| 3932 {1:~}{4: "}{100:$$$}{4:"""" }{1: }| 3933 {1:~}{4: }{1: }| 3934 {1:~}{101:Press ENTER or type command to continue}{4: }{1: }| 3935 {1:~}{103:term://~/config2/docs/pres//32693:vim --clean +smile 29,39 All}{1: }| 3936 {1:~}{4::call nvim__screenshot("smile2.cat") }{1: }| 3937 {1:~ }|*2 3938 | 3939 ]], 3940 } 3941 end) 3942 3943 it('can handle input', function() 3944 screen:try_resize(50, 10) 3945 eq( 3946 { 3, 2 }, 3947 exec_lua [[ 3948 buf = vim.api.nvim_create_buf(1,1) 3949 3950 stream = '' 3951 do_the_echo = false 3952 function input(_,t1,b1,data) 3953 stream = stream .. data 3954 _G.vals = {t1, b1} 3955 if do_the_echo then 3956 vim.api.nvim_chan_send(t1, data) 3957 end 3958 end 3959 3960 term = vim.api.nvim_open_term(buf, {on_input=input}) 3961 vim.api.nvim_open_win(buf, true, {width=40, height=5, row=1, col=1, relative='editor'}) 3962 return {term, buf} 3963 ]] 3964 ) 3965 3966 screen:expect { 3967 grid = [[ 3968 | 3969 {1:~}{4:^ }{1: }| 3970 {1:~}{4: }{1: }|*4 3971 {1:~ }|*3 3972 | 3973 ]], 3974 } 3975 3976 feed 'iba<c-x>bla' 3977 screen:expect { 3978 grid = [[ 3979 | 3980 {1:~}{4:^ }{1: }| 3981 {1:~}{4: }{1: }|*4 3982 {1:~ }|*3 3983 {5:-- TERMINAL --} | 3984 ]], 3985 } 3986 3987 eq('ba\024bla', exec_lua [[ return stream ]]) 3988 eq({ 3, 2 }, exec_lua [[ return vals ]]) 3989 3990 exec_lua [[ do_the_echo = true ]] 3991 feed 'herrejösses!' 3992 3993 screen:expect { 3994 grid = [[ 3995 | 3996 {1:~}{4:herrejösses!^ }{1: }| 3997 {1:~}{4: }{1: }|*4 3998 {1:~ }|*3 3999 {5:-- TERMINAL --} | 4000 ]], 4001 } 4002 eq('ba\024blaherrejösses!', exec_lua [[ return stream ]]) 4003 end) 4004 4005 it('parses text from the current buffer', function() 4006 local b = api.nvim_create_buf(true, true) 4007 api.nvim_buf_set_lines(b, 0, -1, true, { '\027[31mHello\000\027[0m', '\027[33mworld\027[0m' }) 4008 api.nvim_set_current_buf(b) 4009 screen:expect([[ 4010 {18:^^[}[31mHello{18:^@^[}[0m | 4011 {18:^[}[33mworld{18:^[}[0m | 4012 {1:~ }|*32 4013 | 4014 ]]) 4015 api.nvim_open_term(b, {}) 4016 screen:expect([[ 4017 {104:^Hello} | 4018 {105:world} | 4019 |*33 4020 ]]) 4021 end) 4022 end) 4023 4024 describe('nvim_del_mark', function() 4025 it('works', function() 4026 local buf = api.nvim_create_buf(false, true) 4027 api.nvim_buf_set_lines(buf, -1, -1, true, { 'a', 'bit of', 'text' }) 4028 eq(true, api.nvim_buf_set_mark(buf, 'F', 2, 2, {})) 4029 eq(true, api.nvim_del_mark('F')) 4030 eq({ 0, 0 }, api.nvim_buf_get_mark(buf, 'F')) 4031 end) 4032 it('validation', function() 4033 eq("Invalid mark name (must be file/uppercase): 'f'", pcall_err(api.nvim_del_mark, 'f')) 4034 eq("Invalid mark name (must be file/uppercase): '!'", pcall_err(api.nvim_del_mark, '!')) 4035 eq("Invalid mark name (must be a single char): 'fail'", pcall_err(api.nvim_del_mark, 'fail')) 4036 end) 4037 end) 4038 describe('nvim_get_mark', function() 4039 it('works', function() 4040 local buf = api.nvim_create_buf(false, true) 4041 api.nvim_buf_set_lines(buf, -1, -1, true, { 'a', 'bit of', 'text' }) 4042 api.nvim_buf_set_mark(buf, 'F', 2, 2, {}) 4043 api.nvim_buf_set_name(buf, 'mybuf') 4044 local mark = api.nvim_get_mark('F', {}) 4045 -- Compare the path tail only 4046 matches('mybuf$', mark[4]) 4047 eq({ 2, 2, buf, mark[4] }, mark) 4048 end) 4049 it('validation', function() 4050 eq("Invalid mark name (must be file/uppercase): 'f'", pcall_err(api.nvim_get_mark, 'f', {})) 4051 eq("Invalid mark name (must be file/uppercase): '!'", pcall_err(api.nvim_get_mark, '!', {})) 4052 eq( 4053 "Invalid mark name (must be a single char): 'fail'", 4054 pcall_err(api.nvim_get_mark, 'fail', {}) 4055 ) 4056 end) 4057 it('returns the expected when mark is not set', function() 4058 eq(true, api.nvim_del_mark('A')) 4059 eq({ 0, 0, 0, '' }, api.nvim_get_mark('A', {})) 4060 end) 4061 it('works with deleted buffers', function() 4062 local fname = tmpname() 4063 write_file(fname, 'a\nbit of\text') 4064 command('edit ' .. fname) 4065 local buf = api.nvim_get_current_buf() 4066 4067 api.nvim_buf_set_mark(buf, 'F', 2, 2, {}) 4068 command('new') -- Create new buf to avoid :bd failing 4069 command('bd! ' .. buf) 4070 os.remove(fname) 4071 4072 local mark = api.nvim_get_mark('F', {}) 4073 -- To avoid comparing relative vs absolute path 4074 local mfname = mark[4] 4075 local tail_patt = [[[\/][^\/]*$]] 4076 -- tail of paths should be equals 4077 eq(fname:match(tail_patt), mfname:match(tail_patt)) 4078 eq({ 2, 2, buf, mark[4] }, mark) 4079 end) 4080 end) 4081 4082 describe('nvim_eval_statusline', function() 4083 it('works', function() 4084 eq({ 4085 str = '%StatusLineStringWithHighlights', 4086 width = 31, 4087 }, api.nvim_eval_statusline('%%StatusLineString%#WarningMsg#WithHighlights', {})) 4088 end) 4089 4090 it("doesn't exceed maxwidth", function() 4091 eq({ 4092 str = 'Should be trun>', 4093 width = 15, 4094 }, api.nvim_eval_statusline('Should be truncated%<', { maxwidth = 15 })) 4095 end) 4096 4097 it('has correct default fillchar', function() 4098 local oldwin = api.nvim_get_current_win() 4099 command('set fillchars=stl:#,stlnc:$,wbr:%') 4100 command('new') 4101 eq({ str = 'a###b', width = 5 }, api.nvim_eval_statusline('a%=b', { maxwidth = 5 })) 4102 eq( 4103 { str = 'a$$$b', width = 5 }, 4104 api.nvim_eval_statusline('a%=b', { winid = oldwin, maxwidth = 5 }) 4105 ) 4106 eq( 4107 { str = 'a%%%b', width = 5 }, 4108 api.nvim_eval_statusline('a%=b', { use_winbar = true, maxwidth = 5 }) 4109 ) 4110 eq( 4111 { str = 'a b', width = 5 }, 4112 api.nvim_eval_statusline('a%=b', { use_tabline = true, maxwidth = 5 }) 4113 ) 4114 eq( 4115 { str = 'a b', width = 5 }, 4116 api.nvim_eval_statusline('a%=b', { use_statuscol_lnum = 1, maxwidth = 5 }) 4117 ) 4118 end) 4119 4120 for fc, desc in pairs({ 4121 ['~'] = 'supports ASCII fillchar', 4122 ['━'] = 'supports single-width multibyte fillchar', 4123 ['c̳'] = 'supports single-width fillchar with composing', 4124 ['哦'] = 'treats double-width fillchar as single-width', 4125 ['\031'] = 'treats control character fillchar as single-width', 4126 }) do 4127 it(desc, function() 4128 eq( 4129 { str = 'a' .. fc:rep(3) .. 'b', width = 5 }, 4130 api.nvim_eval_statusline('a%=b', { fillchar = fc, maxwidth = 5 }) 4131 ) 4132 eq( 4133 { str = 'a' .. fc:rep(3) .. 'b', width = 5 }, 4134 api.nvim_eval_statusline('a%=b', { fillchar = fc, use_winbar = true, maxwidth = 5 }) 4135 ) 4136 eq( 4137 { str = 'a' .. fc:rep(3) .. 'b', width = 5 }, 4138 api.nvim_eval_statusline('a%=b', { fillchar = fc, use_tabline = true, maxwidth = 5 }) 4139 ) 4140 eq( 4141 { str = 'a' .. fc:rep(3) .. 'b', width = 5 }, 4142 api.nvim_eval_statusline('a%=b', { fillchar = fc, use_statuscol_lnum = 1, maxwidth = 5 }) 4143 ) 4144 end) 4145 end 4146 4147 it('rejects multiple-character fillchar', function() 4148 eq( 4149 "Invalid 'fillchar': expected single character", 4150 pcall_err(api.nvim_eval_statusline, '', { fillchar = 'aa' }) 4151 ) 4152 end) 4153 4154 it('rejects empty string fillchar', function() 4155 eq( 4156 "Invalid 'fillchar': expected single character", 4157 pcall_err(api.nvim_eval_statusline, '', { fillchar = '' }) 4158 ) 4159 end) 4160 4161 it('rejects non-string fillchar', function() 4162 eq( 4163 "Invalid 'fillchar': expected String, got Integer", 4164 pcall_err(api.nvim_eval_statusline, '', { fillchar = 1 }) 4165 ) 4166 end) 4167 4168 it('rejects invalid string', function() 4169 eq('E539: Illegal character <}>', pcall_err(api.nvim_eval_statusline, '%{%}', {})) 4170 end) 4171 4172 it('supports various items', function() 4173 eq({ str = '0', width = 1 }, api.nvim_eval_statusline('%l', { maxwidth = 5 })) 4174 command('set readonly') 4175 eq({ str = '[RO]', width = 4 }, api.nvim_eval_statusline('%r', { maxwidth = 5 })) 4176 local screen = Screen.new(80, 24) 4177 command('set showcmd') 4178 feed('1234') 4179 screen:expect({ any = '1234' }) 4180 eq({ str = '1234', width = 4 }, api.nvim_eval_statusline('%S', { maxwidth = 5 })) 4181 feed('56') 4182 screen:expect({ any = '123456' }) 4183 eq({ str = '<3456', width = 5 }, api.nvim_eval_statusline('%S', { maxwidth = 5 })) 4184 end) 4185 4186 describe('highlight parsing', function() 4187 it('works', function() 4188 eq( 4189 { 4190 str = 'TextWithWarningHighlightTextWithUserHighlight', 4191 width = 45, 4192 highlights = { 4193 { start = 0, group = 'WarningMsg', groups = { 'StatusLine', 'WarningMsg' } }, 4194 { start = 24, group = 'User1', groups = { 'StatusLine', 'User1' } }, 4195 }, 4196 }, 4197 api.nvim_eval_statusline( 4198 '%#WarningMsg#TextWithWarningHighlight%1*TextWithUserHighlight', 4199 { highlights = true } 4200 ) 4201 ) 4202 end) 4203 4204 it('works with no highlight', function() 4205 eq({ 4206 str = 'TextWithNoHighlight', 4207 width = 19, 4208 highlights = { 4209 { start = 0, group = 'StatusLine', groups = { 'StatusLine' } }, 4210 }, 4211 }, api.nvim_eval_statusline('TextWithNoHighlight', { highlights = true })) 4212 end) 4213 4214 it('works with inactive statusline', function() 4215 command('split') 4216 eq( 4217 { 4218 str = 'TextWithNoHighlightTextWithWarningHighlight', 4219 width = 43, 4220 highlights = { 4221 { start = 0, group = 'StatusLineNC', groups = { 'StatusLineNC' } }, 4222 { start = 19, group = 'WarningMsg', groups = { 'StatusLineNC', 'WarningMsg' } }, 4223 }, 4224 }, 4225 api.nvim_eval_statusline( 4226 'TextWithNoHighlight%#WarningMsg#TextWithWarningHighlight', 4227 { winid = api.nvim_list_wins()[2], highlights = true } 4228 ) 4229 ) 4230 end) 4231 4232 it('works with tabline', function() 4233 eq( 4234 { 4235 str = 'TextWithNoHighlightTextWithWarningHighlight', 4236 width = 43, 4237 highlights = { 4238 { start = 0, group = 'TabLineFill', groups = { 'TabLineFill' } }, 4239 { start = 19, group = 'WarningMsg', groups = { 'TabLineFill', 'WarningMsg' } }, 4240 }, 4241 }, 4242 api.nvim_eval_statusline( 4243 'TextWithNoHighlight%#WarningMsg#TextWithWarningHighlight', 4244 { use_tabline = true, highlights = true } 4245 ) 4246 ) 4247 end) 4248 4249 it('works with winbar', function() 4250 eq( 4251 { 4252 str = 'TextWithNoHighlightTextWithWarningHighlight', 4253 width = 43, 4254 highlights = { 4255 { start = 0, group = 'WinBar', groups = { 'WinBar' } }, 4256 { start = 19, group = 'WarningMsg', groups = { 'WinBar', 'WarningMsg' } }, 4257 }, 4258 }, 4259 api.nvim_eval_statusline( 4260 'TextWithNoHighlight%#WarningMsg#TextWithWarningHighlight', 4261 { use_winbar = true, highlights = true } 4262 ) 4263 ) 4264 end) 4265 4266 it('works with statuscolumn', function() 4267 exec([[ 4268 let &stc='%C%s%=%l ' 4269 " should not use "stl" from 'fillchars' 4270 set cul nu nuw=3 scl=yes:2 fdc=2 fillchars=stl:# 4271 call setline(1, repeat(['aaaaa'], 5)) 4272 let g:ns = nvim_create_namespace('') 4273 call sign_define('a', {'text':'aa', 'texthl':'IncSearch', 'numhl':'Normal'}) 4274 call sign_place(2, 1, 'a', bufnr(), {'lnum':4}) 4275 call nvim_buf_set_extmark(0, g:ns, 3, 1, { 'sign_text':'bb', 'sign_hl_group':'ErrorMsg' }) 4276 1,5fold | 1,5 fold | foldopen! 4277 norm 4G 4278 ]]) 4279 eq({ 4280 str = '││bbaa 4 ', 4281 width = 9, 4282 highlights = { 4283 { group = 'CursorLineFold', start = 0, groups = { 'CursorLineFold' } }, 4284 { group = 'Normal', start = 6, groups = { 'Normal' } }, 4285 { group = 'ErrorMsg', start = 6, groups = { 'CursorLineSign', 'ErrorMsg' } }, 4286 { group = 'IncSearch', start = 8, groups = { 'CursorLineSign', 'IncSearch' } }, 4287 { group = 'Normal', start = 10, groups = { 'Normal' } }, 4288 }, 4289 }, api.nvim_eval_statusline( 4290 '%C%s%=%l ', 4291 { use_statuscol_lnum = 4, highlights = true } 4292 )) 4293 eq( 4294 { 4295 str = ' 3 ', 4296 width = 9, 4297 highlights = { 4298 { group = 'LineNr', start = 0, groups = { 'LineNr' } }, 4299 { group = 'ErrorMsg', start = 8, groups = { 'LineNr', 'ErrorMsg' } }, 4300 }, 4301 }, 4302 api.nvim_eval_statusline('%l%#ErrorMsg# ', { use_statuscol_lnum = 3, highlights = true }) 4303 ) 4304 end) 4305 4306 it('no memory leak with click functions', function() 4307 api.nvim_eval_statusline('%@ClickFunc@StatusLineStringWithClickFunc%T', {}) 4308 eq({ 4309 str = 'StatusLineStringWithClickFunc', 4310 width = 29, 4311 }, api.nvim_eval_statusline('%@ClickFunc@StatusLineStringWithClickFunc%T', {})) 4312 end) 4313 end) 4314 end) 4315 4316 describe('nvim_parse_cmd', function() 4317 it('works', function() 4318 eq({ 4319 cmd = 'echo', 4320 args = { 'foo' }, 4321 bang = false, 4322 addr = 'none', 4323 magic = { 4324 file = false, 4325 bar = false, 4326 }, 4327 nargs = '*', 4328 nextcmd = '', 4329 mods = { 4330 browse = false, 4331 confirm = false, 4332 emsg_silent = false, 4333 filter = { 4334 pattern = '', 4335 force = false, 4336 }, 4337 hide = false, 4338 horizontal = false, 4339 keepalt = false, 4340 keepjumps = false, 4341 keepmarks = false, 4342 keeppatterns = false, 4343 lockmarks = false, 4344 noautocmd = false, 4345 noswapfile = false, 4346 sandbox = false, 4347 silent = false, 4348 split = '', 4349 tab = -1, 4350 unsilent = false, 4351 verbose = -1, 4352 vertical = false, 4353 }, 4354 }, api.nvim_parse_cmd('echo foo', {})) 4355 end) 4356 it('works with ranges', function() 4357 eq({ 4358 cmd = 'substitute', 4359 args = { '/math.random/math.max/' }, 4360 bang = false, 4361 range = { 4, 6 }, 4362 addr = 'line', 4363 magic = { 4364 file = false, 4365 bar = false, 4366 }, 4367 nargs = '*', 4368 nextcmd = '', 4369 mods = { 4370 browse = false, 4371 confirm = false, 4372 emsg_silent = false, 4373 filter = { 4374 pattern = '', 4375 force = false, 4376 }, 4377 hide = false, 4378 horizontal = false, 4379 keepalt = false, 4380 keepjumps = false, 4381 keepmarks = false, 4382 keeppatterns = false, 4383 lockmarks = false, 4384 noautocmd = false, 4385 noswapfile = false, 4386 sandbox = false, 4387 silent = false, 4388 split = '', 4389 tab = -1, 4390 unsilent = false, 4391 verbose = -1, 4392 vertical = false, 4393 }, 4394 }, api.nvim_parse_cmd('4,6s/math.random/math.max/', {})) 4395 end) 4396 it('works with count', function() 4397 eq({ 4398 cmd = 'buffer', 4399 args = {}, 4400 bang = false, 4401 range = { 1 }, 4402 count = 1, 4403 addr = 'buf', 4404 magic = { 4405 file = false, 4406 bar = true, 4407 }, 4408 nargs = '*', 4409 nextcmd = '', 4410 mods = { 4411 browse = false, 4412 confirm = false, 4413 emsg_silent = false, 4414 filter = { 4415 pattern = '', 4416 force = false, 4417 }, 4418 hide = false, 4419 horizontal = false, 4420 keepalt = false, 4421 keepjumps = false, 4422 keepmarks = false, 4423 keeppatterns = false, 4424 lockmarks = false, 4425 noautocmd = false, 4426 noswapfile = false, 4427 sandbox = false, 4428 silent = false, 4429 split = '', 4430 tab = -1, 4431 unsilent = false, 4432 verbose = -1, 4433 vertical = false, 4434 }, 4435 }, api.nvim_parse_cmd('buffer 1', {})) 4436 end) 4437 it('works with register', function() 4438 eq({ 4439 cmd = 'put', 4440 args = {}, 4441 bang = false, 4442 reg = '+', 4443 addr = 'line', 4444 magic = { 4445 file = false, 4446 bar = true, 4447 }, 4448 nargs = '0', 4449 nextcmd = '', 4450 mods = { 4451 browse = false, 4452 confirm = false, 4453 emsg_silent = false, 4454 filter = { 4455 pattern = '', 4456 force = false, 4457 }, 4458 hide = false, 4459 horizontal = false, 4460 keepalt = false, 4461 keepjumps = false, 4462 keepmarks = false, 4463 keeppatterns = false, 4464 lockmarks = false, 4465 noautocmd = false, 4466 noswapfile = false, 4467 sandbox = false, 4468 silent = false, 4469 split = '', 4470 tab = -1, 4471 unsilent = false, 4472 verbose = -1, 4473 vertical = false, 4474 }, 4475 }, api.nvim_parse_cmd('put +', {})) 4476 eq({ 4477 cmd = 'put', 4478 args = {}, 4479 bang = false, 4480 reg = '', 4481 addr = 'line', 4482 magic = { 4483 file = false, 4484 bar = true, 4485 }, 4486 nargs = '0', 4487 nextcmd = '', 4488 mods = { 4489 browse = false, 4490 confirm = false, 4491 emsg_silent = false, 4492 filter = { 4493 pattern = '', 4494 force = false, 4495 }, 4496 hide = false, 4497 horizontal = false, 4498 keepalt = false, 4499 keepjumps = false, 4500 keepmarks = false, 4501 keeppatterns = false, 4502 lockmarks = false, 4503 noautocmd = false, 4504 noswapfile = false, 4505 sandbox = false, 4506 silent = false, 4507 split = '', 4508 tab = -1, 4509 unsilent = false, 4510 verbose = -1, 4511 vertical = false, 4512 }, 4513 }, api.nvim_parse_cmd('put', {})) 4514 end) 4515 it('works with range, count and register', function() 4516 eq({ 4517 cmd = 'delete', 4518 args = {}, 4519 bang = false, 4520 range = { 3, 7 }, 4521 count = 7, 4522 reg = '*', 4523 addr = 'line', 4524 magic = { 4525 file = false, 4526 bar = true, 4527 }, 4528 nargs = '0', 4529 nextcmd = '', 4530 mods = { 4531 browse = false, 4532 confirm = false, 4533 emsg_silent = false, 4534 filter = { 4535 pattern = '', 4536 force = false, 4537 }, 4538 hide = false, 4539 horizontal = false, 4540 keepalt = false, 4541 keepjumps = false, 4542 keepmarks = false, 4543 keeppatterns = false, 4544 lockmarks = false, 4545 noautocmd = false, 4546 noswapfile = false, 4547 sandbox = false, 4548 silent = false, 4549 split = '', 4550 tab = -1, 4551 unsilent = false, 4552 verbose = -1, 4553 vertical = false, 4554 }, 4555 }, api.nvim_parse_cmd('1,3delete * 5', {})) 4556 end) 4557 it('works with bang', function() 4558 eq({ 4559 cmd = 'write', 4560 args = {}, 4561 bang = true, 4562 addr = 'line', 4563 magic = { 4564 file = true, 4565 bar = true, 4566 }, 4567 nargs = '?', 4568 nextcmd = '', 4569 mods = { 4570 browse = false, 4571 confirm = false, 4572 emsg_silent = false, 4573 filter = { 4574 pattern = '', 4575 force = false, 4576 }, 4577 hide = false, 4578 horizontal = false, 4579 keepalt = false, 4580 keepjumps = false, 4581 keepmarks = false, 4582 keeppatterns = false, 4583 lockmarks = false, 4584 noautocmd = false, 4585 noswapfile = false, 4586 sandbox = false, 4587 silent = false, 4588 split = '', 4589 tab = -1, 4590 unsilent = false, 4591 verbose = -1, 4592 vertical = false, 4593 }, 4594 }, api.nvim_parse_cmd('w!', {})) 4595 end) 4596 it('works with modifiers', function() 4597 eq( 4598 { 4599 cmd = 'split', 4600 args = { 'foo.txt' }, 4601 bang = false, 4602 addr = '?', 4603 magic = { 4604 file = true, 4605 bar = true, 4606 }, 4607 nargs = '?', 4608 nextcmd = '', 4609 mods = { 4610 browse = false, 4611 confirm = false, 4612 emsg_silent = true, 4613 filter = { 4614 pattern = 'foo', 4615 force = false, 4616 }, 4617 hide = false, 4618 horizontal = true, 4619 keepalt = false, 4620 keepjumps = false, 4621 keepmarks = false, 4622 keeppatterns = false, 4623 lockmarks = false, 4624 noautocmd = false, 4625 noswapfile = false, 4626 sandbox = false, 4627 silent = true, 4628 split = 'topleft', 4629 tab = 1, 4630 unsilent = false, 4631 verbose = 15, 4632 vertical = false, 4633 }, 4634 }, 4635 api.nvim_parse_cmd( 4636 '15verbose silent! horizontal topleft tab filter /foo/ split foo.txt', 4637 {} 4638 ) 4639 ) 4640 eq( 4641 { 4642 cmd = 'split', 4643 args = { 'foo.txt' }, 4644 bang = false, 4645 addr = '?', 4646 magic = { 4647 file = true, 4648 bar = true, 4649 }, 4650 nargs = '?', 4651 nextcmd = '', 4652 mods = { 4653 browse = false, 4654 confirm = true, 4655 emsg_silent = false, 4656 filter = { 4657 pattern = 'foo', 4658 force = true, 4659 }, 4660 hide = false, 4661 horizontal = false, 4662 keepalt = false, 4663 keepjumps = false, 4664 keepmarks = false, 4665 keeppatterns = false, 4666 lockmarks = false, 4667 noautocmd = false, 4668 noswapfile = false, 4669 sandbox = false, 4670 silent = false, 4671 split = 'botright', 4672 tab = 0, 4673 unsilent = true, 4674 verbose = 0, 4675 vertical = false, 4676 }, 4677 }, 4678 api.nvim_parse_cmd( 4679 '0verbose unsilent botright 0tab confirm filter! /foo/ split foo.txt', 4680 {} 4681 ) 4682 ) 4683 end) 4684 it('works with user commands', function() 4685 command('command -bang -nargs=+ -range -addr=lines MyCommand echo foo') 4686 eq({ 4687 cmd = 'MyCommand', 4688 args = { 'test', 'it' }, 4689 bang = true, 4690 range = { 4, 6 }, 4691 addr = 'line', 4692 magic = { 4693 file = false, 4694 bar = false, 4695 }, 4696 nargs = '+', 4697 nextcmd = '', 4698 mods = { 4699 browse = false, 4700 confirm = false, 4701 emsg_silent = false, 4702 filter = { 4703 pattern = '', 4704 force = false, 4705 }, 4706 hide = false, 4707 horizontal = false, 4708 keepalt = false, 4709 keepjumps = false, 4710 keepmarks = false, 4711 keeppatterns = false, 4712 lockmarks = false, 4713 noautocmd = false, 4714 noswapfile = false, 4715 sandbox = false, 4716 silent = false, 4717 split = '', 4718 tab = -1, 4719 unsilent = false, 4720 verbose = -1, 4721 vertical = false, 4722 }, 4723 }, api.nvim_parse_cmd('4,6MyCommand! test it', {})) 4724 end) 4725 it('sets nextcmd for bar-separated commands', function() 4726 eq({ 4727 cmd = 'argadd', 4728 args = { 'a.txt' }, 4729 bang = false, 4730 addr = 'arg', 4731 magic = { 4732 file = true, 4733 bar = true, 4734 }, 4735 nargs = '*', 4736 nextcmd = 'argadd b.txt', 4737 mods = { 4738 browse = false, 4739 confirm = false, 4740 emsg_silent = false, 4741 filter = { 4742 pattern = '', 4743 force = false, 4744 }, 4745 hide = false, 4746 horizontal = false, 4747 keepalt = false, 4748 keepjumps = false, 4749 keepmarks = false, 4750 keeppatterns = false, 4751 lockmarks = false, 4752 noautocmd = false, 4753 noswapfile = false, 4754 sandbox = false, 4755 silent = false, 4756 split = '', 4757 tab = -1, 4758 unsilent = false, 4759 verbose = -1, 4760 vertical = false, 4761 }, 4762 }, api.nvim_parse_cmd('argadd a.txt | argadd b.txt', {})) 4763 end) 4764 it('sets nextcmd after expr-arg commands #36029', function() 4765 local result = api.nvim_parse_cmd('exe "ls"|edit foo', {}) 4766 eq({ '"ls"' }, result.args) 4767 eq('execute', result.cmd) 4768 eq('edit foo', result.nextcmd) 4769 end) 4770 it('parses :map commands with space in RHS', function() 4771 eq({ 4772 addr = 'none', 4773 args = { 'a', 'b c' }, 4774 bang = false, 4775 cmd = 'map', 4776 magic = { 4777 bar = true, 4778 file = false, 4779 }, 4780 mods = { 4781 browse = false, 4782 confirm = false, 4783 emsg_silent = false, 4784 filter = { 4785 force = false, 4786 pattern = '', 4787 }, 4788 hide = false, 4789 horizontal = false, 4790 keepalt = false, 4791 keepjumps = false, 4792 keepmarks = false, 4793 keeppatterns = false, 4794 lockmarks = false, 4795 noautocmd = false, 4796 noswapfile = false, 4797 sandbox = false, 4798 silent = false, 4799 split = '', 4800 tab = -1, 4801 unsilent = false, 4802 verbose = -1, 4803 vertical = false, 4804 }, 4805 nargs = '*', 4806 nextcmd = '', 4807 }, api.nvim_parse_cmd('map a b c', {})) 4808 end) 4809 it('works for nargs=1', function() 4810 command('command -nargs=1 MyCommand echo <q-args>') 4811 eq({ 4812 cmd = 'MyCommand', 4813 args = { 'test it' }, 4814 bang = false, 4815 addr = 'none', 4816 magic = { 4817 file = false, 4818 bar = false, 4819 }, 4820 nargs = '1', 4821 nextcmd = '', 4822 mods = { 4823 browse = false, 4824 confirm = false, 4825 emsg_silent = false, 4826 filter = { 4827 pattern = '', 4828 force = false, 4829 }, 4830 hide = false, 4831 horizontal = false, 4832 keepalt = false, 4833 keepjumps = false, 4834 keepmarks = false, 4835 keeppatterns = false, 4836 lockmarks = false, 4837 noautocmd = false, 4838 noswapfile = false, 4839 sandbox = false, 4840 silent = false, 4841 split = '', 4842 tab = -1, 4843 unsilent = false, 4844 verbose = -1, 4845 vertical = false, 4846 }, 4847 }, api.nvim_parse_cmd('MyCommand test it', {})) 4848 end) 4849 it('validates command', function() 4850 eq('Parsing command-line', pcall_err(api.nvim_parse_cmd, '', {})) 4851 eq('Parsing command-line', pcall_err(api.nvim_parse_cmd, '" foo', {})) 4852 eq( 4853 'Parsing command-line: E492: Not an editor command: Fubar', 4854 pcall_err(api.nvim_parse_cmd, 'Fubar', {}) 4855 ) 4856 command('command! Fubar echo foo') 4857 eq('Parsing command-line: E477: No ! allowed', pcall_err(api.nvim_parse_cmd, 'Fubar!', {})) 4858 eq( 4859 'Parsing command-line: E481: No range allowed', 4860 pcall_err(api.nvim_parse_cmd, '4,6Fubar', {}) 4861 ) 4862 command('command! Foobar echo foo') 4863 eq( 4864 'Parsing command-line: E464: Ambiguous use of user-defined command', 4865 pcall_err(api.nvim_parse_cmd, 'F', {}) 4866 ) 4867 end) 4868 it('does not interfere with printing line in Ex mode #19400', function() 4869 local screen = Screen.new(60, 7) 4870 insert([[ 4871 foo 4872 bar]]) 4873 feed('gQ1') 4874 screen:expect([[ 4875 foo | 4876 bar | 4877 {1:~ }|*2 4878 {3: }| 4879 Entering Ex mode. Type "visual" to go to Normal mode. | 4880 :1^ | 4881 ]]) 4882 eq('Parsing command-line', pcall_err(api.nvim_parse_cmd, '', {})) 4883 feed('<CR>') 4884 screen:expect([[ 4885 foo | 4886 bar | 4887 {3: }| 4888 Entering Ex mode. Type "visual" to go to Normal mode. | 4889 :1 | 4890 foo | 4891 :^ | 4892 ]]) 4893 end) 4894 it('does not move cursor or change search history/pattern #19878 #19890', function() 4895 api.nvim_buf_set_lines(0, 0, -1, true, { 'foo', 'bar', 'foo', 'bar' }) 4896 eq({ 1, 0 }, api.nvim_win_get_cursor(0)) 4897 eq('', fn.getreg('/')) 4898 eq('', fn.histget('search')) 4899 feed(':') -- call the API in cmdline mode to test whether it changes search history 4900 eq({ 4901 cmd = 'normal', 4902 args = { 'x' }, 4903 bang = true, 4904 range = { 3, 4 }, 4905 addr = 'line', 4906 magic = { 4907 file = false, 4908 bar = false, 4909 }, 4910 nargs = '+', 4911 nextcmd = '', 4912 mods = { 4913 browse = false, 4914 confirm = false, 4915 emsg_silent = false, 4916 filter = { 4917 pattern = '', 4918 force = false, 4919 }, 4920 hide = false, 4921 horizontal = false, 4922 keepalt = false, 4923 keepjumps = false, 4924 keepmarks = false, 4925 keeppatterns = false, 4926 lockmarks = false, 4927 noautocmd = false, 4928 noswapfile = false, 4929 sandbox = false, 4930 silent = false, 4931 split = '', 4932 tab = -1, 4933 unsilent = false, 4934 verbose = -1, 4935 vertical = false, 4936 }, 4937 }, api.nvim_parse_cmd('+2;/bar/normal! x', {})) 4938 eq({ 1, 0 }, api.nvim_win_get_cursor(0)) 4939 eq('', fn.getreg('/')) 4940 eq('', fn.histget('search')) 4941 end) 4942 it('result can be used directly by nvim_cmd #20051', function() 4943 eq('foo', api.nvim_cmd(api.nvim_parse_cmd('echo "foo"', {}), { output = true })) 4944 api.nvim_cmd(api.nvim_parse_cmd('set cursorline', {}), {}) 4945 eq(true, api.nvim_get_option_value('cursorline', {})) 4946 -- Roundtrip on :bdelete which does not accept "range". #33394 4947 api.nvim_cmd(api.nvim_parse_cmd('bdelete', {}), {}) 4948 end) 4949 it('no side-effects (error messages) in pcall() #20339', function() 4950 eq( 4951 { false, 'Parsing command-line: E16: Invalid range' }, 4952 exec_lua([=[return {pcall(vim.api.nvim_parse_cmd, "'<,'>n", {})}]=]) 4953 ) 4954 eq('', eval('v:errmsg')) 4955 end) 4956 it('does not include count field when no count provided for builtin commands', function() 4957 local result = api.nvim_parse_cmd('copen', {}) 4958 eq(nil, result.count) 4959 api.nvim_cmd(result, {}) 4960 eq(10, api.nvim_win_get_height(0)) 4961 result = api.nvim_parse_cmd('copen 5', {}) 4962 eq(5, result.count) 4963 end) 4964 end) 4965 4966 describe('nvim_cmd', function() 4967 it('works', function() 4968 api.nvim_cmd({ cmd = 'set', args = { 'cursorline' } }, {}) 4969 eq(true, api.nvim_get_option_value('cursorline', {})) 4970 end) 4971 4972 it('validation', function() 4973 eq("Invalid 'cmd': expected non-empty String", pcall_err(api.nvim_cmd, { cmd = '' }, {})) 4974 eq("Invalid 'cmd': expected String, got Array", pcall_err(api.nvim_cmd, { cmd = {} }, {})) 4975 eq( 4976 "Invalid 'args': expected Array, got Boolean", 4977 pcall_err(api.nvim_cmd, { cmd = 'set', args = true }, {}) 4978 ) 4979 eq( 4980 'Invalid command arg: expected non-whitespace', 4981 pcall_err(api.nvim_cmd, { cmd = 'set', args = { ' ' } }, {}) 4982 ) 4983 eq( 4984 'Invalid command arg: expected valid type, got Array', 4985 pcall_err(api.nvim_cmd, { cmd = 'set', args = { {} } }, {}) 4986 ) 4987 eq('Wrong number of arguments', pcall_err(api.nvim_cmd, { cmd = 'aboveleft', args = {} }, {})) 4988 eq( 4989 'Command cannot accept bang: print', 4990 pcall_err(api.nvim_cmd, { cmd = 'print', args = {}, bang = true }, {}) 4991 ) 4992 4993 eq( 4994 'Command cannot accept range: set', 4995 pcall_err(api.nvim_cmd, { cmd = 'set', args = {}, range = { 1 } }, {}) 4996 ) 4997 eq( 4998 "Invalid 'range': expected Array, got Boolean", 4999 pcall_err(api.nvim_cmd, { cmd = 'print', args = {}, range = true }, {}) 5000 ) 5001 eq( 5002 "Invalid 'range': expected <=2 elements", 5003 pcall_err(api.nvim_cmd, { cmd = 'print', args = {}, range = { 1, 2, 3, 4 } }, {}) 5004 ) 5005 eq( 5006 'Invalid range element: expected non-negative Integer', 5007 pcall_err(api.nvim_cmd, { cmd = 'print', args = {}, range = { -1 } }, {}) 5008 ) 5009 5010 eq( 5011 'Command cannot accept count: set', 5012 pcall_err(api.nvim_cmd, { cmd = 'set', args = {}, count = 1 }, {}) 5013 ) 5014 eq( 5015 "Invalid 'count': expected Integer, got Boolean", 5016 pcall_err(api.nvim_cmd, { cmd = 'print', args = {}, count = true }, {}) 5017 ) 5018 eq( 5019 "Invalid 'count': expected non-negative Integer", 5020 pcall_err(api.nvim_cmd, { cmd = 'print', args = {}, count = -1 }, {}) 5021 ) 5022 5023 eq( 5024 'Command cannot accept register: set', 5025 pcall_err(api.nvim_cmd, { cmd = 'set', args = {}, reg = 'x' }, {}) 5026 ) 5027 eq( 5028 'Cannot use register "=', 5029 pcall_err(api.nvim_cmd, { cmd = 'put', args = {}, reg = '=' }, {}) 5030 ) 5031 eq( 5032 "Invalid 'reg': expected single character, got xx", 5033 pcall_err(api.nvim_cmd, { cmd = 'put', args = {}, reg = 'xx' }, {}) 5034 ) 5035 5036 -- #20681 5037 eq('Invalid command: "win_getid"', pcall_err(api.nvim_cmd, { cmd = 'win_getid' }, {})) 5038 eq('Invalid command: "echo "hi""', pcall_err(api.nvim_cmd, { cmd = 'echo "hi"' }, {})) 5039 matches('Invalid command: "win_getid"$', pcall_err(exec_lua, [[return vim.cmd.win_getid{}]])) 5040 5041 -- Lua call allows empty {} for dict item. 5042 eq('', exec_lua([[return vim.cmd{ cmd = "set", args = {}, magic = {} }]])) 5043 eq('', exec_lua([[return vim.cmd{ cmd = "set", args = {}, mods = {} }]])) 5044 eq('', api.nvim_cmd({ cmd = 'set', args = {}, magic = {} }, {})) 5045 5046 -- Lua call does not allow non-empty list-like {} for dict item. 5047 matches( 5048 "Invalid 'magic': Expected Dict%-like Lua table$", 5049 pcall_err(exec_lua, [[return vim.cmd{ cmd = "set", args = {}, magic = { 'a' } }]]) 5050 ) 5051 matches( 5052 "Invalid key: 'bogus'$", 5053 pcall_err(exec_lua, [[return vim.cmd{ cmd = "set", args = {}, magic = { bogus = true } }]]) 5054 ) 5055 matches( 5056 "Invalid key: 'bogus'$", 5057 pcall_err(exec_lua, [[return vim.cmd{ cmd = "set", args = {}, mods = { bogus = true } }]]) 5058 ) 5059 end) 5060 5061 it('captures output', function() 5062 eq('foo', api.nvim_cmd({ cmd = 'echo', args = { '"foo"' } }, { output = true })) 5063 -- Returns output in cmdline mode #35321 5064 feed(':') 5065 eq('foo', api.nvim_cmd({ cmd = 'echo', args = { '"foo"' } }, { output = true })) 5066 end) 5067 5068 it('sets correct script context', function() 5069 api.nvim_cmd({ cmd = 'set', args = { 'cursorline' } }, {}) 5070 local str = exec_capture([[verbose set cursorline?]]) 5071 neq(nil, str:find('cursorline\n\tLast set from API client %(channel id %d+%)')) 5072 end) 5073 5074 it('works with range', function() 5075 insert [[ 5076 line1 5077 line2 5078 line3 5079 line4 5080 you didn't expect this 5081 line5 5082 line6 5083 ]] 5084 api.nvim_cmd({ cmd = 'del', range = { 2, 4 } }, {}) 5085 expect [[ 5086 line1 5087 you didn't expect this 5088 line5 5089 line6 5090 ]] 5091 end) 5092 5093 it('works with count', function() 5094 insert [[ 5095 line1 5096 line2 5097 line3 5098 line4 5099 you didn't expect this 5100 line5 5101 line6 5102 ]] 5103 api.nvim_cmd({ cmd = 'del', range = { 2 }, count = 4 }, {}) 5104 expect [[ 5105 line1 5106 line5 5107 line6 5108 ]] 5109 end) 5110 5111 it('works with register', function() 5112 insert [[ 5113 line1 5114 line2 5115 line3 5116 line4 5117 you didn't expect this 5118 line5 5119 line6 5120 ]] 5121 api.nvim_cmd({ cmd = 'del', range = { 2, 4 }, reg = 'a' }, {}) 5122 command('1put a') 5123 expect [[ 5124 line1 5125 line2 5126 line3 5127 line4 5128 you didn't expect this 5129 line5 5130 line6 5131 ]] 5132 end) 5133 5134 it('works with bang', function() 5135 api.nvim_create_user_command('Foo', 'echo "<bang>"', { bang = true }) 5136 eq('!', api.nvim_cmd({ cmd = 'Foo', bang = true }, { output = true })) 5137 eq('', api.nvim_cmd({ cmd = 'Foo', bang = false }, { output = true })) 5138 end) 5139 5140 it('works with modifiers', function() 5141 -- with silent = true output is still captured 5142 eq( 5143 '1', 5144 api.nvim_cmd( 5145 { cmd = 'echomsg', args = { '1' }, mods = { silent = true } }, 5146 { output = true } 5147 ) 5148 ) 5149 -- but message isn't added to message history 5150 eq('', api.nvim_cmd({ cmd = 'messages' }, { output = true })) 5151 5152 api.nvim_create_user_command('Foo', 'set verbose', {}) 5153 eq(' verbose=1', api.nvim_cmd({ cmd = 'Foo', mods = { verbose = 1 } }, { output = true })) 5154 5155 api.nvim_create_user_command('Mods', "echo '<mods>'", {}) 5156 eq( 5157 'keepmarks keeppatterns silent 3verbose aboveleft horizontal', 5158 api.nvim_cmd({ 5159 cmd = 'Mods', 5160 mods = { 5161 horizontal = true, 5162 keepmarks = true, 5163 keeppatterns = true, 5164 silent = true, 5165 split = 'aboveleft', 5166 verbose = 3, 5167 }, 5168 }, { output = true }) 5169 ) 5170 eq(0, api.nvim_get_option_value('verbose', {})) 5171 5172 command('edit foo.txt | edit bar.txt') 5173 eq( 5174 ' 1 #h "foo.txt" line 1', 5175 api.nvim_cmd( 5176 { cmd = 'buffers', mods = { filter = { pattern = 'foo', force = false } } }, 5177 { output = true } 5178 ) 5179 ) 5180 eq( 5181 ' 2 %a "bar.txt" line 1', 5182 api.nvim_cmd( 5183 { cmd = 'buffers', mods = { filter = { pattern = 'foo', force = true } } }, 5184 { output = true } 5185 ) 5186 ) 5187 5188 -- with emsg_silent = true error is suppressed 5189 feed([[:lua vim.api.nvim_cmd({ cmd = 'call', mods = { emsg_silent = true } }, {})<CR>]]) 5190 eq('', api.nvim_cmd({ cmd = 'messages' }, { output = true })) 5191 -- error from the next command typed is not suppressed #21420 5192 feed(':call<CR><CR>') 5193 eq('E471: Argument required', api.nvim_cmd({ cmd = 'messages' }, { output = true })) 5194 end) 5195 5196 it('works with magic.file', function() 5197 exec_lua([[ 5198 vim.api.nvim_create_user_command("Foo", function(opts) 5199 vim.api.nvim_echo({{ opts.fargs[1] }}, false, {}) 5200 end, { nargs = 1 }) 5201 ]]) 5202 eq( 5203 uv.cwd(), 5204 api.nvim_cmd( 5205 { cmd = 'Foo', args = { '%:p:h' }, magic = { file = true } }, 5206 { output = true } 5207 ) 5208 ) 5209 end) 5210 5211 it('splits arguments correctly', function() 5212 exec([[ 5213 function! FooFunc(...) 5214 echo a:000 5215 endfunction 5216 ]]) 5217 api.nvim_create_user_command('Foo', 'call FooFunc(<f-args>)', { nargs = '+' }) 5218 eq( 5219 [=[['a quick', 'brown fox', 'jumps over the', 'lazy dog']]=], 5220 api.nvim_cmd( 5221 { cmd = 'Foo', args = { 'a quick', 'brown fox', 'jumps over the', 'lazy dog' } }, 5222 { output = true } 5223 ) 5224 ) 5225 eq( 5226 [=[['test \ \\ \"""\', 'more\ tests\" ']]=], 5227 api.nvim_cmd( 5228 { cmd = 'Foo', args = { [[test \ \\ \"""\]], [[more\ tests\" ]] } }, 5229 { output = true } 5230 ) 5231 ) 5232 end) 5233 5234 it('splits arguments correctly for Lua callback', function() 5235 api.nvim_exec_lua( 5236 [[ 5237 local function FooFunc(opts) 5238 vim.print(opts.fargs) 5239 end 5240 5241 vim.api.nvim_create_user_command("Foo", FooFunc, { nargs = '+' }) 5242 ]], 5243 {} 5244 ) 5245 eq( 5246 [[{ "a quick", "brown fox", "jumps over the", "lazy dog" }]], 5247 api.nvim_cmd( 5248 { cmd = 'Foo', args = { 'a quick', 'brown fox', 'jumps over the', 'lazy dog' } }, 5249 { output = true } 5250 ) 5251 ) 5252 eq( 5253 [[{ 'test \\ \\\\ \\"""\\', 'more\\ tests\\" ' }]], 5254 api.nvim_cmd( 5255 { cmd = 'Foo', args = { [[test \ \\ \"""\]], [[more\ tests\" ]] } }, 5256 { output = true } 5257 ) 5258 ) 5259 end) 5260 5261 it('works with buffer names', function() 5262 command('edit foo.txt | edit bar.txt') 5263 api.nvim_cmd({ cmd = 'buffer', args = { 'foo.txt' } }, {}) 5264 eq('foo.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t')) 5265 api.nvim_cmd({ cmd = 'buffer', args = { 'bar.txt' } }, {}) 5266 eq('bar.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t')) 5267 end) 5268 5269 it('triggers CmdUndefined event if command is not found', function() 5270 api.nvim_exec_lua( 5271 [[ 5272 vim.api.nvim_create_autocmd("CmdUndefined", 5273 { pattern = "Foo", 5274 callback = function() 5275 vim.api.nvim_create_user_command("Foo", "echo 'foo'", {}) 5276 end 5277 }) 5278 ]], 5279 {} 5280 ) 5281 eq('foo', api.nvim_cmd({ cmd = 'Foo' }, { output = true })) 5282 end) 5283 5284 it('errors if command is not implemented', function() 5285 eq('Command not implemented: winpos', pcall_err(api.nvim_cmd, { cmd = 'winpos' }, {})) 5286 end) 5287 5288 it('works with empty arguments list', function() 5289 api.nvim_cmd({ cmd = 'update' }, {}) 5290 api.nvim_cmd({ cmd = 'buffer', count = 0 }, {}) 5291 end) 5292 5293 it("doesn't suppress errors when used in keymapping", function() 5294 api.nvim_exec_lua( 5295 [[ 5296 vim.keymap.set("n", "[l", 5297 function() vim.api.nvim_cmd({ cmd = "echo", args = {"foo"} }, {}) end) 5298 ]], 5299 {} 5300 ) 5301 feed('[l') 5302 neq(nil, string.find(eval('v:errmsg'), 'E5108:')) 5303 end) 5304 5305 it('handles 0 range #19608', function() 5306 api.nvim_buf_set_lines(0, 0, -1, false, { 'aa' }) 5307 api.nvim_cmd({ cmd = 'delete', range = { 0 } }, {}) 5308 command('undo') 5309 eq({ 'aa' }, api.nvim_buf_get_lines(0, 0, 1, false)) 5310 assert_alive() 5311 end) 5312 5313 it('supports filename expansion', function() 5314 api.nvim_cmd({ cmd = 'argadd', args = { '%:p:h:t', '%:p:h:t' } }, {}) 5315 local arg = fn.expand('%:p:h:t') 5316 eq({ arg, arg }, fn.argv()) 5317 end) 5318 5319 it(":make command works when argument count isn't 1 #19696", function() 5320 command('set makeprg=echo') 5321 command('set shellquote=') 5322 matches('^:!echo ', api.nvim_cmd({ cmd = 'make' }, { output = true })) 5323 assert_alive() 5324 matches( 5325 '^:!echo foo bar', 5326 api.nvim_cmd({ cmd = 'make', args = { 'foo', 'bar' } }, { output = true }) 5327 ) 5328 assert_alive() 5329 local arg_pesc = pesc(fn.expand('%:p:h:t')) 5330 matches( 5331 ('^:!echo %s %s'):format(arg_pesc, arg_pesc), 5332 api.nvim_cmd({ cmd = 'make', args = { '%:p:h:t', '%:p:h:t' } }, { output = true }) 5333 ) 5334 assert_alive() 5335 end) 5336 5337 it("doesn't display messages when output=true", function() 5338 local screen = Screen.new(40, 6) 5339 api.nvim_cmd({ cmd = 'echo', args = { [['hello']] } }, { output = true }) 5340 screen:expect { 5341 grid = [[ 5342 ^ | 5343 {1:~ }|*4 5344 | 5345 ]], 5346 } 5347 exec([[ 5348 func Print() 5349 call nvim_cmd(#{cmd: 'echo', args: ['"hello"']}, #{output: v:true}) 5350 endfunc 5351 ]]) 5352 feed([[:echon 1 | call Print() | echon 5<CR>]]) 5353 screen:expect { 5354 grid = [[ 5355 ^ | 5356 {1:~ }|*4 5357 15 | 5358 ]], 5359 } 5360 end) 5361 5362 it('works with non-String args', function() 5363 eq('2', api.nvim_cmd({ cmd = 'echo', args = { 2 } }, { output = true })) 5364 eq('1', api.nvim_cmd({ cmd = 'echo', args = { true } }, { output = true })) 5365 end) 5366 5367 describe('first argument as count', function() 5368 it('works', function() 5369 command('vsplit | enew') 5370 api.nvim_cmd({ cmd = 'bdelete', args = { api.nvim_get_current_buf() } }, {}) 5371 eq(1, api.nvim_get_current_buf()) 5372 end) 5373 5374 it('works with :sleep using milliseconds', function() 5375 local start = uv.now() 5376 api.nvim_cmd({ cmd = 'sleep', args = { '100m' } }, {}) 5377 ok(uv.now() - start <= 300) 5378 end) 5379 end) 5380 5381 it(':call with unknown function does not crash #26289', function() 5382 eq( 5383 'Vim:E117: Unknown function: UnknownFunc', 5384 pcall_err(api.nvim_cmd, { cmd = 'call', args = { 'UnknownFunc()' } }, {}) 5385 ) 5386 end) 5387 5388 it(':throw does not crash #24556', function() 5389 eq('42', pcall_err(api.nvim_cmd, { cmd = 'throw', args = { '42' } }, {})) 5390 end) 5391 5392 it('can use :return #24556', function() 5393 exec([[ 5394 func Foo() 5395 let g:pos = 'before' 5396 call nvim_cmd({'cmd': 'return', 'args': ['[1, 2, 3]']}, {}) 5397 let g:pos = 'after' 5398 endfunc 5399 let g:result = Foo() 5400 ]]) 5401 eq('before', api.nvim_get_var('pos')) 5402 eq({ 1, 2, 3 }, api.nvim_get_var('result')) 5403 end) 5404 5405 it('errors properly when command too recursive #27210', function() 5406 exec_lua([[ 5407 _G.success = false 5408 vim.api.nvim_create_user_command('Test', function() 5409 vim.api.nvim_cmd({ cmd = 'Test' }, {}) 5410 _G.success = true 5411 end, {}) 5412 ]]) 5413 pcall_err(command, 'Test') 5414 assert_alive() 5415 eq(false, exec_lua('return _G.success')) 5416 end) 5417 5418 it('handles +flags correctly', function() 5419 -- Write a file for testing +flags 5420 t.write_file('testfile', 'Line 1\nLine 2\nLine 3', false, false) 5421 5422 -- Test + command (go to the last line) 5423 local result = exec_lua([[ 5424 local parsed = vim.api.nvim_parse_cmd('edit + testfile', {}) 5425 vim.cmd(parsed) 5426 return { vim.fn.line('.'), parsed.args, parsed.cmd } 5427 ]]) 5428 eq({ 3, { '+ testfile' }, 'edit' }, result) 5429 5430 -- Test +{num} command (go to line number) 5431 result = exec_lua([[ 5432 vim.cmd(vim.api.nvim_parse_cmd('edit +1 testfile', {})) 5433 return vim.fn.line('.') 5434 ]]) 5435 eq(1, result) 5436 5437 -- Test +/{pattern} command (go to line with pattern) 5438 result = exec_lua([[ 5439 local parsed = vim.api.nvim_parse_cmd('edit +/Line\\ 2 testfile', {}) 5440 vim.cmd(parsed) 5441 return {vim.fn.line('.'), parsed.args} 5442 ]]) 5443 eq({ 2, { '+/Line\\ 2 testfile' } }, result) 5444 5445 -- Test +{command} command (execute a command after opening the file) 5446 result = exec_lua([[ 5447 vim.cmd(vim.api.nvim_parse_cmd('edit +set\\ nomodifiable testfile', {})) 5448 return vim.bo.modifiable 5449 ]]) 5450 eq(false, result) 5451 5452 -- Test ++ flags structure in parsed command 5453 result = exec_lua([[ 5454 local parsed = vim.api.nvim_parse_cmd('botright edit + testfile', {}) 5455 vim.cmd(parsed) 5456 return { vim.fn.line('.'), parsed.cmd, parsed.args, parsed.mods.split } 5457 ]]) 5458 eq({ 3, 'edit', { '+ testfile' }, 'botright' }, result) 5459 5460 -- Clean up 5461 os.remove('testfile') 5462 end) 5463 5464 it('handles various ++ flags correctly', function() 5465 -- Test ++ff flag 5466 local result = exec_lua [[ 5467 local parsed = vim.api.nvim_parse_cmd('edit ++ff=mac test_ff_mac.txt', {}) 5468 vim.cmd(parsed) 5469 return parsed.args 5470 ]] 5471 eq({ '++ff=mac test_ff_mac.txt' }, result) 5472 eq('mac', api.nvim_get_option_value('fileformat', {})) 5473 eq('test_ff_mac.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t')) 5474 5475 exec_lua [[ 5476 vim.cmd(vim.api.nvim_parse_cmd('edit ++fileformat=unix test_ff_unix.txt', {})) 5477 ]] 5478 eq('unix', api.nvim_get_option_value('fileformat', {})) 5479 eq('test_ff_unix.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t')) 5480 5481 -- Test ++enc flag 5482 exec_lua [[ 5483 vim.cmd(vim.api.nvim_parse_cmd('edit ++enc=utf-32 test_enc.txt', {})) 5484 ]] 5485 eq('ucs-4', api.nvim_get_option_value('fileencoding', {})) 5486 eq('test_enc.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t')) 5487 5488 -- Test ++bin and ++nobin flags 5489 exec_lua [[ 5490 vim.cmd(vim.api.nvim_parse_cmd('edit ++bin test_bin.txt', {})) 5491 ]] 5492 eq(true, api.nvim_get_option_value('binary', {})) 5493 eq('test_bin.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t')) 5494 5495 exec_lua [[ 5496 vim.cmd(vim.api.nvim_parse_cmd('edit ++nobin test_nobin.txt', {})) 5497 ]] 5498 eq(false, api.nvim_get_option_value('binary', {})) 5499 eq('test_nobin.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t')) 5500 5501 -- Test multiple flags together 5502 exec_lua [[ 5503 vim.cmd(vim.api.nvim_parse_cmd('edit ++ff=mac ++enc=utf-32 ++bin test_multi.txt', {})) 5504 ]] 5505 eq(true, api.nvim_get_option_value('binary', {})) 5506 eq('mac', api.nvim_get_option_value('fileformat', {})) 5507 eq('ucs-4', api.nvim_get_option_value('fileencoding', {})) 5508 eq('test_multi.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t')) 5509 end) 5510 5511 it('handles invalid and incorrect ++ flags gracefully', function() 5512 -- Test invalid ++ff flag 5513 local result = exec_lua [[ 5514 local cmd = vim.api.nvim_parse_cmd('edit ++ff=invalid test_invalid_ff.txt', {}) 5515 local _, err = pcall(vim.cmd, cmd) 5516 return err 5517 ]] 5518 matches("Invalid argument : '%+%+ff=invalid'$", result) 5519 5520 -- Test incorrect ++ syntax 5521 result = exec_lua [[ 5522 local cmd = vim.api.nvim_parse_cmd('edit ++unknown=test_unknown.txt', {}) 5523 local _, err = pcall(vim.cmd, cmd) 5524 return err 5525 ]] 5526 matches("Invalid argument : '%+%+unknown=test_unknown.txt'$", result) 5527 5528 -- Test invalid ++bin flag 5529 result = exec_lua [[ 5530 local cmd = vim.api.nvim_parse_cmd('edit ++binabc test_invalid_bin.txt', {}) 5531 local _, err = pcall(vim.cmd, cmd) 5532 return err 5533 ]] 5534 matches("Invalid argument : '%+%+binabc test_invalid_bin.txt'$", result) 5535 end) 5536 5537 it('handles ++p for creating parent directory', function() 5538 exec_lua [[ 5539 vim.cmd('edit flags_dir/test_create.txt') 5540 vim.cmd(vim.api.nvim_parse_cmd('write! ++p', {})) 5541 ]] 5542 eq(true, fn.isdirectory('flags_dir') == 1) 5543 fn.delete('flags_dir', 'rf') 5544 end) 5545 5546 it('tests editing files with bad utf8 sequences', function() 5547 -- Write a file with bad utf8 sequences 5548 local file = io.open('Xfile', 'wb') 5549 file:write('[\255][\192][\226\137\240][\194\194]') 5550 file:close() 5551 5552 exec_lua([[ 5553 vim.cmd(vim.api.nvim_parse_cmd('edit! ++enc=utf8 Xfile', {})) 5554 ]]) 5555 eq('[?][?][???][??]', api.nvim_get_current_line()) 5556 5557 exec_lua([[ 5558 vim.cmd(vim.api.nvim_parse_cmd('edit! ++enc=utf8 ++bad=_ Xfile', {})) 5559 ]]) 5560 eq('[_][_][___][__]', api.nvim_get_current_line()) 5561 5562 exec_lua([[ 5563 vim.cmd(vim.api.nvim_parse_cmd('edit! ++enc=utf8 ++bad=drop Xfile', {})) 5564 ]]) 5565 eq('[][][][]', api.nvim_get_current_line()) 5566 5567 exec_lua([[ 5568 vim.cmd(vim.api.nvim_parse_cmd('edit! ++enc=utf8 ++bad=keep Xfile', {})) 5569 ]]) 5570 eq('[\255][\192][\226\137\240][\194\194]', api.nvim_get_current_line()) 5571 5572 local result = exec_lua([[ 5573 local _, err = pcall(vim.cmd, vim.api.nvim_parse_cmd('edit ++enc=utf8 ++bad=foo Xfile', {})) 5574 return err 5575 ]]) 5576 matches("Invalid argument : '%+%+bad=foo'$", result) 5577 -- Clean up 5578 os.remove('Xfile') 5579 end) 5580 it('interprets numeric args as count for count-only commands', function() 5581 api.nvim_cmd({ cmd = 'copen', args = { 8 } }, {}) 5582 local height1 = api.nvim_win_get_height(0) 5583 command('cclose') 5584 api.nvim_cmd({ cmd = 'copen', count = 8 }, {}) 5585 local height2 = api.nvim_win_get_height(0) 5586 command('cclose') 5587 eq(height1, height2) 5588 5589 exec_lua 'vim.cmd.copen(5)' 5590 height2 = api.nvim_win_get_height(0) 5591 command('cclose') 5592 eq(5, height2) 5593 5594 -- should reject both count and numeric arg 5595 eq( 5596 "Cannot specify both 'count' and numeric argument", 5597 pcall_err(api.nvim_cmd, { cmd = 'copen', args = { 5 }, count = 10 }, {}) 5598 ) 5599 end) 5600 it('handles string numeric arguments correctly', function() 5601 -- Valid string numbers should work 5602 api.nvim_cmd({ cmd = 'copen', args = { '6' } }, {}) 5603 eq(6, api.nvim_win_get_height(0)) 5604 command('cclose') 5605 -- Invalid strings should be rejected 5606 eq( 5607 'Wrong number of arguments', 5608 pcall_err(api.nvim_cmd, { cmd = 'copen', args = { 'abc' } }, {}) 5609 ) 5610 -- Partial numbers should be rejected 5611 eq( 5612 'Wrong number of arguments', 5613 pcall_err(api.nvim_cmd, { cmd = 'copen', args = { '8abc' } }, {}) 5614 ) 5615 -- Empty string should be rejected 5616 eq( 5617 'Invalid command arg: expected non-whitespace', 5618 pcall_err(api.nvim_cmd, { cmd = 'copen', args = { '' } }, {}) 5619 ) 5620 -- Negative string numbers should be rejected 5621 eq( 5622 'Wrong number of arguments', 5623 pcall_err(api.nvim_cmd, { cmd = 'copen', args = { '-5' } }, {}) 5624 ) 5625 -- Leading/trailing spaces should be rejected 5626 eq( 5627 'Wrong number of arguments', 5628 pcall_err(api.nvim_cmd, { cmd = 'copen', args = { ' 5 ' } }, {}) 5629 ) 5630 end) 5631 end) 5632 5633 it('nvim__redraw', function() 5634 local screen = Screen.new(60, 5) 5635 eq('at least one action required', pcall_err(api.nvim__redraw, {})) 5636 eq('at least one action required', pcall_err(api.nvim__redraw, { buf = 0 })) 5637 eq('at least one action required', pcall_err(api.nvim__redraw, { win = 0 })) 5638 eq("cannot use both 'buf' and 'win'", pcall_err(api.nvim__redraw, { buf = 0, win = 0 })) 5639 local win = api.nvim_get_current_win() 5640 -- Can move cursor to recently opened window and window is flushed #28868 5641 feed(':echo getchar()<CR>') 5642 local newwin = api.nvim_open_win(0, false, { 5643 relative = 'editor', 5644 width = 1, 5645 height = 1, 5646 row = 1, 5647 col = 10, 5648 }) 5649 api.nvim__redraw({ win = newwin, cursor = true }) 5650 screen:expect({ 5651 grid = [[ 5652 | 5653 {1:~ }{4:^ }{1: }| 5654 {1:~ }|*2 5655 :echo getchar() | 5656 ]], 5657 }) 5658 fn.setline(1, 'foobar') 5659 command('vnew') 5660 fn.setline(1, 'foobaz') 5661 -- Can flush pending screen updates 5662 api.nvim__redraw({ flush = true }) 5663 screen:expect({ 5664 grid = [[ 5665 foobaz │foobar | 5666 {1:~ }{4:^f}{1: }│{1:~ }| 5667 {1:~ }│{1:~ }| 5668 {3:[No Name] [+] }{2:[No Name] [+] }| 5669 :echo getchar() | 5670 ]], 5671 }) 5672 api.nvim_win_close(newwin, true) 5673 -- Can update the grid cursor position #20793 5674 api.nvim__redraw({ cursor = true }) 5675 screen:expect({ 5676 grid = [[ 5677 ^foobaz │foobar | 5678 {1:~ }│{1:~ }|*2 5679 {3:[No Name] [+] }{2:[No Name] [+] }| 5680 :echo getchar() | 5681 ]], 5682 }) 5683 -- Also in non-current window 5684 api.nvim__redraw({ cursor = true, win = win }) 5685 screen:expect({ 5686 grid = [[ 5687 foobaz │^foobar | 5688 {1:~ }│{1:~ }|*2 5689 {3:[No Name] [+] }{2:[No Name] [+] }| 5690 :echo getchar() | 5691 ]], 5692 }) 5693 -- Can update the 'statusline' in a single window 5694 api.nvim_set_option_value('statusline', 'statusline1', { win = 0 }) 5695 api.nvim_set_option_value('statusline', 'statusline2', { win = win }) 5696 api.nvim__redraw({ cursor = true, win = 0, statusline = true }) 5697 screen:expect({ 5698 grid = [[ 5699 ^foobaz │foobar | 5700 {1:~ }│{1:~ }|*2 5701 {3:statusline1 }{2:[No Name] [+] }| 5702 :echo getchar() | 5703 ]], 5704 }) 5705 api.nvim__redraw({ win = win, statusline = true }) 5706 screen:expect({ 5707 grid = [[ 5708 ^foobaz │foobar | 5709 {1:~ }│{1:~ }|*2 5710 {3:statusline1 }{2:statusline2 }| 5711 :echo getchar() | 5712 ]], 5713 }) 5714 -- Can update the 'statusline' in all windows 5715 api.nvim_set_option_value('statusline', '', { win = win }) 5716 api.nvim_set_option_value('statusline', 'statusline3', {}) 5717 api.nvim__redraw({ statusline = true }) 5718 screen:expect({ 5719 grid = [[ 5720 ^foobaz │foobar | 5721 {1:~ }│{1:~ }|*2 5722 {3:statusline3 }{2:statusline3 }| 5723 :echo getchar() | 5724 ]], 5725 }) 5726 -- Can update the 'statuscolumn' 5727 api.nvim_set_option_value('statuscolumn', 'statuscolumn', { win = win }) 5728 api.nvim__redraw({ statuscolumn = true }) 5729 screen:expect({ 5730 grid = [[ 5731 ^foobaz │{8:statuscolumn}foobar | 5732 {1:~ }│{1:~ }|*2 5733 {3:statusline3 }{2:statusline3 }| 5734 :echo getchar() | 5735 ]], 5736 }) 5737 -- Can update the 'winbar' 5738 api.nvim_set_option_value('winbar', 'winbar', { win = 0 }) 5739 api.nvim__redraw({ win = 0, winbar = true }) 5740 screen:expect({ 5741 grid = [[ 5742 {5:^winbar }│{8:statuscolumn}foobar | 5743 foobaz │{1:~ }| 5744 {1:~ }│{1:~ }| 5745 {3:statusline3 }{2:statusline3 }| 5746 :echo getchar() | 5747 ]], 5748 }) 5749 -- Can update the 'tabline' 5750 api.nvim_set_option_value('showtabline', 2, {}) 5751 api.nvim_set_option_value('tabline', 'tabline', {}) 5752 api.nvim__redraw({ tabline = true }) 5753 screen:expect({ 5754 grid = [[ 5755 {2:^tabline }| 5756 {5:winbar }│{8:statuscolumn}foobar | 5757 foobaz │{1:~ }| 5758 {3:statusline3 }{2:statusline3 }| 5759 :echo getchar() | 5760 ]], 5761 }) 5762 -- Can update multiple status widgets 5763 api.nvim_set_option_value('tabline', 'tabline2', {}) 5764 api.nvim_set_option_value('statusline', 'statusline4', {}) 5765 api.nvim__redraw({ statusline = true, tabline = true }) 5766 screen:expect({ 5767 grid = [[ 5768 {2:^tabline2 }| 5769 {5:winbar }│{8:statuscolumn}foobar | 5770 foobaz │{1:~ }| 5771 {3:statusline4 }{2:statusline4 }| 5772 :echo getchar() | 5773 ]], 5774 }) 5775 -- Can update all status widgets 5776 api.nvim_set_option_value('tabline', 'tabline3', {}) 5777 api.nvim_set_option_value('statusline', 'statusline5', {}) 5778 api.nvim_set_option_value('statuscolumn', 'statuscolumn2', {}) 5779 api.nvim_set_option_value('winbar', 'winbar2', {}) 5780 api.nvim__redraw({ statuscolumn = true, statusline = true, tabline = true, winbar = true }) 5781 screen:expect({ 5782 grid = [[ 5783 {2:^tabline3 }| 5784 {5:winbar2 }│{5:winbar2 }| 5785 {8:statuscolumn2}foobaz │{8:statuscolumn}foobar | 5786 {3:statusline5 }{2:statusline5 }| 5787 :echo getchar() | 5788 ]], 5789 }) 5790 -- Can update status widget for a specific window 5791 feed('<CR><CR>') 5792 command('let g:status=0') 5793 api.nvim_set_option_value('statusline', '%{%g:status%}', { win = 0 }) 5794 command('vsplit') 5795 screen:expect({ 5796 grid = [[ 5797 {2:tabline3 }| 5798 {5:winbar2 }│{5:winbar2 }│{5:winbar2 }| 5799 {8:statuscolumn2}^foobaz │{8:statuscolumn2}foobaz│{8:statuscolumn}foobar | 5800 {3:0 }{2:0 statusline5 }| 5801 13 | 5802 ]], 5803 }) 5804 command('let g:status=1') 5805 api.nvim__redraw({ win = 0, statusline = true }) 5806 screen:expect({ 5807 grid = [[ 5808 {2:tabline3 }| 5809 {5:winbar2 }│{5:winbar2 }│{5:winbar2 }| 5810 {8:statuscolumn2}^foobaz │{8:statuscolumn2}foobaz│{8:statuscolumn}foobar | 5811 {3:1 }{2:0 statusline5 }| 5812 13 | 5813 ]], 5814 }) 5815 -- Can update status widget for a specific buffer 5816 command('let g:status=2') 5817 api.nvim__redraw({ buf = 0, statusline = true }) 5818 screen:expect({ 5819 grid = [[ 5820 {2:tabline3 }| 5821 {5:winbar2 }│{5:winbar2 }│{5:winbar2 }| 5822 {8:statuscolumn2}^foobaz │{8:statuscolumn2}foobaz│{8:statuscolumn}foobar | 5823 {3:2 }{2:2 statusline5 }| 5824 13 | 5825 ]], 5826 }) 5827 -- valid = true does not draw any lines on its own 5828 exec_lua([[ 5829 _G.lines = 0 5830 ns = vim.api.nvim_create_namespace('') 5831 vim.api.nvim_set_decoration_provider(ns, { 5832 on_win = function() 5833 if _G.do_win then 5834 vim.api.nvim_buf_set_extmark(0, ns, 0, 0, { hl_group = 'IncSearch', end_col = 6 }) 5835 end 5836 end, 5837 on_line = function() 5838 _G.lines = _G.lines + 1 5839 end, 5840 }) 5841 ]]) 5842 local lines = exec_lua('return lines') 5843 api.nvim__redraw({ buf = 0, valid = true, flush = true }) 5844 eq(lines, exec_lua('return _G.lines')) 5845 -- valid = false does 5846 api.nvim__redraw({ buf = 0, valid = false, flush = true }) 5847 neq(lines, exec_lua('return _G.lines')) 5848 -- valid = true does redraw lines if affected by on_win callback 5849 exec_lua('_G.do_win = true') 5850 api.nvim__redraw({ buf = 0, valid = true, flush = true }) 5851 screen:expect({ 5852 grid = [[ 5853 {2:tabline3 }| 5854 {5:winbar2 }│{5:winbar2 }│{5:winbar2 }| 5855 {8:statuscolumn2}{2:^foobaz} │{8:statuscolumn2}{2:foobaz}│{8:statuscolumn}foobar | 5856 {3:2 }{2:2 statusline5 }| 5857 13 | 5858 ]], 5859 }) 5860 end) 5861 5862 it('nvim__redraw range parameter', function() 5863 Screen.new(10, 5) 5864 fn.setline(1, fn.range(4)) 5865 5866 exec_lua([[ 5867 _G.lines_list = {} 5868 ns = vim.api.nvim_create_namespace('') 5869 vim.api.nvim_set_decoration_provider(ns, { 5870 on_win = function() 5871 end, 5872 on_line = function(_, _, _, line) 5873 table.insert(_G.lines_list, line) 5874 end, 5875 }) 5876 function _G.get_lines() 5877 local lines = _G.lines_list 5878 _G.lines_list = {} 5879 return lines 5880 end 5881 ]]) 5882 5883 api.nvim__redraw({ flush = true, valid = false }) 5884 exec_lua('_G.get_lines()') 5885 5886 local actual_lines = {} 5887 local function test(range) 5888 api.nvim__redraw({ win = 0, range = range }) 5889 table.insert(actual_lines, exec_lua('return _G.get_lines()')) 5890 end 5891 5892 test({ 0, -1 }) 5893 test({ 2, 2 ^ 31 }) 5894 test({ 2, 2 ^ 32 }) 5895 test({ 2 ^ 31 - 1, 2 }) 5896 test({ 2 ^ 32 - 1, 2 }) 5897 5898 local expected_lines = { 5899 { 0, 1, 2, 3 }, 5900 { 2, 3 }, 5901 { 2, 3 }, 5902 {}, 5903 {}, 5904 } 5905 eq(expected_lines, actual_lines) 5906 5907 n.assert_alive() 5908 end) 5909 5910 it('nvim__redraw updates topline', function() 5911 local screen = Screen.new(40, 8) 5912 fn.setline(1, fn.range(100)) 5913 feed(':call getchar()<CR>') 5914 fn.cursor(50, 1) 5915 screen:expect([[ 5916 0 | 5917 1 | 5918 2 | 5919 3 | 5920 4 | 5921 5 | 5922 6 | 5923 ^:call getchar() | 5924 ]]) 5925 api.nvim__redraw({ flush = true }) 5926 screen:expect([[ 5927 46 | 5928 47 | 5929 48 | 5930 49 | 5931 50 | 5932 51 | 5933 52 | 5934 ^:call getchar() | 5935 ]]) 5936 end) 5937 end)