with_spec.lua (54074B)
1 local t = require('test.testutil') 2 local n = require('test.functional.testnvim')() 3 local Screen = require('test.functional.ui.screen') 4 5 local fn = n.fn 6 local api = n.api 7 local command = n.command 8 local eq = t.eq 9 local exec_lua = n.exec_lua 10 local exec_capture = n.exec_capture 11 local matches = t.matches 12 local pcall_err = t.pcall_err 13 14 describe('vim._with', function() 15 before_each(function() 16 n.clear() 17 exec_lua([[ 18 _G.fn = vim.fn 19 _G.api = vim.api 20 21 _G.setup_buffers = function() 22 return api.nvim_create_buf(false, true), api.nvim_get_current_buf() 23 end 24 25 _G.setup_windows = function() 26 local other_win = api.nvim_get_current_win() 27 vim.cmd.new() 28 return other_win, api.nvim_get_current_win() 29 end 30 ]]) 31 end) 32 33 local assert_events_trigger = function() 34 local out = exec_lua [[ 35 -- Needs three global values defined: 36 -- - `test_events` - array of events which are tested. 37 -- - `test_context` - context to be tested. 38 -- - `test_trig_event` - callable triggering at least one tested event. 39 _G.n_events = 0 40 local opts = { callback = function() _G.n_events = _G.n_events + 1 end } 41 api.nvim_create_autocmd(_G.test_events, opts) 42 43 local context = { bo = { commentstring = '-- %s' } } 44 45 -- Should not trigger events on its own 46 vim._with(_G.test_context, function() end) 47 local is_no_events = _G.n_events == 0 48 49 -- Should trigger events if specifically asked inside callback 50 local is_events = vim._with(_G.test_context, function() 51 _G.test_trig_event() 52 return _G.n_events > 0 53 end) 54 return { is_no_events, is_events } 55 ]] 56 eq({ true, true }, out) 57 end 58 59 describe('`bo` context', function() 60 before_each(function() 61 exec_lua [[ 62 _G.other_buf, _G.cur_buf = setup_buffers() 63 64 -- 'commentstring' is local to buffer and string 65 vim.bo[other_buf].commentstring = '## %s' 66 vim.bo[cur_buf].commentstring = '// %s' 67 vim.go.commentstring = '$$ %s' 68 69 -- 'undolevels' is global or local to buffer (global-local) and number 70 vim.bo[other_buf].undolevels = 100 71 vim.bo[cur_buf].undolevels = 250 72 vim.go.undolevels = 500 73 74 -- 'autoread' is global or local to buffer (global-local) and boolean 75 vim.bo[other_buf].autoread = false 76 vim.bo[cur_buf].autoread = false 77 vim.go.autoread = true 78 79 _G.get_state = function() 80 return { 81 bo = { 82 cms_cur = vim.bo[cur_buf].commentstring, 83 cms_other = vim.bo[other_buf].commentstring, 84 ul_cur = vim.bo[cur_buf].undolevels, 85 ul_other = vim.bo[other_buf].undolevels, 86 ar_cur = vim.bo[cur_buf].autoread, 87 ar_other = vim.bo[other_buf].autoread, 88 }, 89 go = { 90 cms = vim.go.commentstring, 91 ul = vim.go.undolevels, 92 ar = vim.go.autoread, 93 }, 94 } 95 end 96 ]] 97 end) 98 99 it('works', function() 100 local out = exec_lua [[ 101 local context = { bo = { commentstring = '-- %s', undolevels = 0, autoread = true } } 102 103 local before = get_state() 104 local inner = vim._with(context, function() 105 assert(api.nvim_get_current_buf() == cur_buf) 106 return get_state() 107 end) 108 109 return { before = before, inner = inner, after = get_state() } 110 ]] 111 112 eq({ 113 bo = { 114 cms_cur = '-- %s', 115 cms_other = '## %s', 116 ul_cur = 0, 117 ul_other = 100, 118 ar_cur = true, 119 ar_other = false, 120 }, 121 go = { cms = '$$ %s', ul = 500, ar = true }, 122 }, out.inner) 123 eq(out.before, out.after) 124 end) 125 126 it('sets options in `buf` context', function() 127 local out = exec_lua [[ 128 local context = { buf = other_buf, bo = { commentstring = '-- %s', undolevels = 0, autoread = true } } 129 130 local before = get_state() 131 local inner = vim._with(context, function() 132 assert(api.nvim_get_current_buf() == other_buf) 133 return get_state() 134 end) 135 136 return { before = before, inner = inner, after = get_state() } 137 ]] 138 139 eq({ 140 bo = { 141 cms_cur = '// %s', 142 cms_other = '-- %s', 143 ul_cur = 250, 144 ul_other = 0, 145 ar_cur = false, 146 ar_other = true, 147 }, 148 go = { cms = '$$ %s', ul = 500, ar = true }, 149 }, out.inner) 150 eq(out.before, out.after) 151 end) 152 153 it('restores only options from context', function() 154 local out = exec_lua [[ 155 local context = { bo = { commentstring = '-- %s' } } 156 157 local inner = vim._with(context, function() 158 assert(api.nvim_get_current_buf() == cur_buf) 159 vim.bo[cur_buf].undolevels = 750 160 vim.bo[cur_buf].commentstring = '!! %s' 161 return get_state() 162 end) 163 164 return { inner = inner, after = get_state() } 165 ]] 166 167 eq({ 168 bo = { 169 cms_cur = '!! %s', 170 cms_other = '## %s', 171 ul_cur = 750, 172 ul_other = 100, 173 ar_cur = false, 174 ar_other = false, 175 }, 176 go = { cms = '$$ %s', ul = 500, ar = true }, 177 }, out.inner) 178 eq({ 179 bo = { 180 cms_cur = '// %s', 181 cms_other = '## %s', 182 ul_cur = 750, 183 ul_other = 100, 184 ar_cur = false, 185 ar_other = false, 186 }, 187 go = { cms = '$$ %s', ul = 500, ar = true }, 188 }, out.after) 189 end) 190 191 it('does not trigger events', function() 192 exec_lua [[ 193 _G.test_events = { 'BufEnter', 'BufLeave', 'BufWinEnter', 'BufWinLeave' } 194 _G.test_context = { bo = { commentstring = '-- %s' } } 195 _G.test_trig_event = function() vim.cmd.new() end 196 ]] 197 assert_events_trigger() 198 end) 199 200 it('can be nested', function() 201 local out = exec_lua [[ 202 local before, before_inner, after_inner = get_state(), nil, nil 203 vim._with({ bo = { commentstring = '-- %s', undolevels = 0, autoread = true } }, function() 204 before_inner = get_state() 205 inner = vim._with({ bo = { commentstring = '!! %s', autoread = false } }, get_state) 206 after_inner = get_state() 207 end) 208 return { 209 before = before, before_inner = before_inner, 210 inner = inner, 211 after_inner = after_inner, after = get_state(), 212 } 213 ]] 214 eq('!! %s', out.inner.bo.cms_cur) 215 eq(0, out.inner.bo.ul_cur) 216 eq(false, out.inner.bo.ar_cur) 217 eq(out.before_inner, out.after_inner) 218 eq(out.before, out.after) 219 end) 220 end) 221 222 describe('`buf` context', function() 223 it('works', function() 224 local out = exec_lua [[ 225 local other_buf, cur_buf = setup_buffers() 226 local inner = vim._with({ buf = other_buf }, function() 227 return api.nvim_get_current_buf() 228 end) 229 return { inner == other_buf, api.nvim_get_current_buf() == cur_buf } 230 ]] 231 eq({ true, true }, out) 232 end) 233 234 it('does not trigger events', function() 235 exec_lua [[ 236 _G.test_events = { 'BufEnter', 'BufLeave', 'BufWinEnter', 'BufWinLeave' } 237 _G.test_context = { buf = other_buf } 238 _G.test_trig_event = function() vim.cmd.new() end 239 ]] 240 assert_events_trigger() 241 end) 242 243 it('can access buffer options', function() 244 local out = exec_lua [[ 245 other_buf, cur_buf = setup_buffers() 246 vim.bo[other_buf].commentstring = '## %s' 247 vim.bo[cur_buf].commentstring = '// %s' 248 249 vim._with({ buf = other_buf }, function() 250 vim.cmd.set('commentstring=--\\ %s') 251 end) 252 253 return vim.bo[other_buf].commentstring == '-- %s' and 254 vim.bo[cur_buf].commentstring == '// %s' 255 ]] 256 eq(true, out) 257 end) 258 259 it('works with different kinds of buffers', function() 260 exec_lua [[ 261 local assert_buf = function(buf) 262 vim._with({ buf = buf }, function() 263 assert(api.nvim_get_current_buf() == buf) 264 end) 265 end 266 267 -- Current 268 assert_buf(api.nvim_get_current_buf()) 269 270 -- Hidden listed 271 local listed = api.nvim_create_buf(true, true) 272 assert_buf(listed) 273 274 -- Visible 275 local other_win, cur_win = setup_windows() 276 api.nvim_win_set_buf(other_win, listed) 277 assert_buf(listed) 278 279 -- Shown but not visible 280 vim.cmd.tabnew() 281 assert_buf(listed) 282 283 -- Shown in several windows 284 api.nvim_win_set_buf(0, listed) 285 assert_buf(listed) 286 287 -- Shown in floating window 288 local float_buf = api.nvim_create_buf(false, true) 289 local config = { relative = 'editor', row = 1, col = 1, width = 5, height = 5 } 290 api.nvim_open_win(float_buf, false, config) 291 assert_buf(float_buf) 292 ]] 293 end) 294 295 it('does not cause ml_get errors with invalid visual selection', function() 296 exec_lua [[ 297 api.nvim_buf_set_lines(0, 0, -1, true, { 'a', 'b', 'c' }) 298 api.nvim_feedkeys(vim.keycode('G<C-V>'), 'txn', false) 299 local other_buf, _ = setup_buffers() 300 vim._with({ buf = buf }, function() vim.cmd.redraw() end) 301 ]] 302 end) 303 304 it('can be nested', function() 305 exec_lua [[ 306 local other_buf, cur_buf = setup_buffers() 307 vim._with({ buf = other_buf }, function() 308 assert(api.nvim_get_current_buf() == other_buf) 309 inner = vim._with({ buf = cur_buf }, function() 310 assert(api.nvim_get_current_buf() == cur_buf) 311 end) 312 assert(api.nvim_get_current_buf() == other_buf) 313 end) 314 assert(api.nvim_get_current_buf() == cur_buf) 315 ]] 316 end) 317 318 it('can be nested crazily with hidden buffers', function() 319 local out = exec_lua([[ 320 local n = 0 321 local function with_recursive_nested_bufs() 322 n = n + 1 323 if n > 20 then return true end 324 325 local other_buf, _ = setup_buffers() 326 vim.bo[other_buf].commentstring = '## %s' 327 local callback = function() 328 return api.nvim_get_current_buf() == other_buf 329 and vim.bo[other_buf].commentstring == '## %s' 330 and with_recursive_nested_bufs() 331 end 332 return vim._with({ buf = other_buf }, callback) and 333 api.nvim_buf_delete(other_buf, {}) == nil 334 end 335 336 return with_recursive_nested_bufs() 337 ]]) 338 eq(true, out) 339 end) 340 end) 341 342 describe('`emsg_silent` context', function() 343 pending('works', function() 344 local ok = pcall( 345 exec_lua, 346 [[ 347 _G.f = function() 348 error('This error should not interfer with execution', 0) 349 end 350 -- Should not produce error same as `vim.cmd('silent! lua _G.f()')` 351 vim._with({ emsg_silent = true }, f) 352 ]] 353 ) 354 eq(true, ok) 355 356 -- Should properly report errors afterwards 357 ok = pcall(exec_lua, 'lua _G.f()') 358 eq(false, ok) 359 end) 360 361 it('can be nested', function() 362 local ok = pcall( 363 exec_lua, 364 [[ 365 _G.f = function() 366 error('This error should not interfere with execution', 0) 367 end 368 -- Should produce error same as `_G.f()` 369 vim._with({ emsg_silent = true }, function() 370 vim._with( { emsg_silent = false }, f) 371 end) 372 ]] 373 ) 374 eq(false, ok) 375 end) 376 end) 377 378 describe('`env` context', function() 379 before_each(function() 380 exec_lua [[ 381 vim.fn.setenv('aaa', 'hello') 382 _G.get_state = function() 383 return { aaa = vim.fn.getenv('aaa'), bbb = vim.fn.getenv('bbb') } 384 end 385 ]] 386 end) 387 388 it('works', function() 389 local out = exec_lua [[ 390 local context = { env = { aaa = 'inside', bbb = 'wow' } } 391 local before = get_state() 392 local inner = vim._with(context, get_state) 393 return { before = before, inner = inner, after = get_state() } 394 ]] 395 396 eq({ aaa = 'inside', bbb = 'wow' }, out.inner) 397 eq(out.before, out.after) 398 end) 399 400 it('restores only variables from context', function() 401 local out = exec_lua [[ 402 local context = { env = { bbb = 'wow' } } 403 local before = get_state() 404 local inner = vim._with(context, function() 405 vim.env.aaa = 'inside' 406 return get_state() 407 end) 408 return { before = before, inner = inner, after = get_state() } 409 ]] 410 411 eq({ aaa = 'inside', bbb = 'wow' }, out.inner) 412 eq({ aaa = 'inside', bbb = vim.NIL }, out.after) 413 end) 414 415 it('can be nested', function() 416 local out = exec_lua [[ 417 local before, before_inner, after_inner = get_state(), nil, nil 418 vim._with({ env = { aaa = 'inside', bbb = 'wow' } }, function() 419 before_inner = get_state() 420 inner = vim._with({ env = { aaa = 'more inside' } }, get_state) 421 after_inner = get_state() 422 end) 423 return { 424 before = before, before_inner = before_inner, 425 inner = inner, 426 after_inner = after_inner, after = get_state(), 427 } 428 ]] 429 eq('more inside', out.inner.aaa) 430 eq('wow', out.inner.bbb) 431 eq(out.before_inner, out.after_inner) 432 eq(out.before, out.after) 433 end) 434 end) 435 436 describe('`go` context', function() 437 before_each(function() 438 exec_lua [[ 439 vim.bo.commentstring = '## %s' 440 vim.go.commentstring = '$$ %s' 441 vim.wo.winblend = 25 442 vim.go.winblend = 50 443 vim.go.langmap = 'xy,yx' 444 vim.go.confirm = false 445 446 _G.get_state = function() 447 return { 448 bo = { cms = vim.bo.commentstring }, 449 wo = { winbl = vim.wo.winblend }, 450 go = { 451 cms = vim.go.commentstring, 452 winbl = vim.go.winblend, 453 lmap = vim.go.langmap, 454 cf = vim.go.confirm, 455 }, 456 } 457 end 458 ]] 459 end) 460 461 it('works', function() 462 local out = exec_lua [[ 463 local context = { 464 go = { commentstring = '-- %s', winblend = 75, langmap = 'ab,ba', cf = true }, 465 } 466 local before = get_state() 467 local inner = vim._with(context, get_state) 468 return { before = before, inner = inner, after = get_state() } 469 ]] 470 471 eq({ 472 bo = { cms = '## %s' }, 473 wo = { winbl = 25 }, 474 go = { cms = '-- %s', winbl = 75, lmap = 'ab,ba', cf = true }, 475 }, out.inner) 476 eq(out.before, out.after) 477 end) 478 479 it('works with `eventignore`', function() 480 -- This might be an issue if saving and restoring option context is done 481 -- to account for triggering `OptionSet`, but in not a good way 482 local out = exec_lua [[ 483 vim.go.eventignore = 'ModeChanged' 484 local inner = vim._with({ go = { eventignore = 'CursorMoved' } }, function() 485 return vim.go.eventignore 486 end) 487 return { inner = inner, after = vim.go.eventignore } 488 ]] 489 eq({ inner = 'CursorMoved', after = 'ModeChanged' }, out) 490 end) 491 492 it('restores only options from context', function() 493 local out = exec_lua [[ 494 local context = { go = { langmap = 'ab,ba' } } 495 496 local inner = vim._with(context, function() 497 vim.go.commentstring = '!! %s' 498 vim.go.winblend = 75 499 vim.go.langmap = 'uv,vu' 500 return get_state() 501 end) 502 503 return { inner = inner, after = get_state() } 504 ]] 505 506 eq({ 507 bo = { cms = '## %s' }, 508 wo = { winbl = 25 }, 509 go = { cms = '!! %s', winbl = 75, lmap = 'uv,vu', cf = false }, 510 }, out.inner) 511 eq({ 512 bo = { cms = '## %s' }, 513 wo = { winbl = 25 }, 514 go = { cms = '!! %s', winbl = 75, lmap = 'xy,yx', cf = false }, 515 }, out.after) 516 end) 517 518 it('does not trigger events', function() 519 exec_lua [[ 520 _G.test_events = { 521 'BufEnter', 'BufLeave', 'BufWinEnter', 'BufWinLeave', 'WinEnter', 'WinLeave' 522 } 523 _G.test_context = { go = { commentstring = '-- %s', winblend = 75, langmap = 'ab,ba' } } 524 _G.test_trig_event = function() vim.cmd.new() end 525 ]] 526 assert_events_trigger() 527 end) 528 529 it('can be nested', function() 530 local out = exec_lua [[ 531 local before, before_inner, after_inner = get_state(), nil, nil 532 vim._with({ go = { langmap = 'ab,ba', commentstring = '-- %s' } }, function() 533 before_inner = get_state() 534 inner = vim._with({ go = { langmap = 'uv,vu' } }, get_state) 535 after_inner = get_state() 536 end) 537 return { 538 before = before, before_inner = before_inner, 539 inner = inner, 540 after_inner = after_inner, after = get_state(), 541 } 542 ]] 543 eq('uv,vu', out.inner.go.lmap) 544 eq('-- %s', out.inner.go.cms) 545 eq(out.before_inner, out.after_inner) 546 eq(out.before, out.after) 547 end) 548 end) 549 550 describe('`hide` context', function() 551 pending('works', function() 552 local ok = pcall( 553 exec_lua, 554 [[ 555 vim.o.hidden = false 556 vim.bo.modified = true 557 local init_buf = api.nvim_get_current_buf() 558 -- Should not produce error same as `vim.cmd('hide enew')` 559 vim._with({ hide = true }, function() 560 vim.cmd.enew() 561 end) 562 assert(api.nvim_get_current_buf() ~= init_buf) 563 ]] 564 ) 565 eq(true, ok) 566 end) 567 568 it('can be nested', function() 569 local ok = pcall( 570 exec_lua, 571 [[ 572 vim.o.hidden = false 573 vim.bo.modified = true 574 -- Should produce error same as `vim.cmd.enew()` 575 vim._with({ hide = true }, function() 576 vim._with({ hide = false }, function() 577 vim.cmd.enew() 578 end) 579 end) 580 ]] 581 ) 582 eq(false, ok) 583 end) 584 end) 585 586 describe('`horizontal` context', function() 587 local is_approx_eq = function(dim, id_1, id_2) 588 local f = dim == 'height' and api.nvim_win_get_height or api.nvim_win_get_width 589 return math.abs(f(id_1) - f(id_2)) <= 1 590 end 591 592 local win_id_1, win_id_2, win_id_3 593 before_each(function() 594 win_id_1 = api.nvim_get_current_win() 595 command('wincmd v | wincmd 5>') 596 win_id_2 = api.nvim_get_current_win() 597 command('wincmd s | wincmd 5+') 598 win_id_3 = api.nvim_get_current_win() 599 600 eq(is_approx_eq('width', win_id_1, win_id_2), false) 601 eq(is_approx_eq('height', win_id_3, win_id_2), false) 602 end) 603 604 pending('works', function() 605 exec_lua [[ 606 -- Should be same as `vim.cmd('horizontal wincmd =')` 607 vim._with({ horizontal = true }, function() 608 vim.cmd.wincmd('=') 609 end) 610 ]] 611 eq(is_approx_eq('width', win_id_1, win_id_2), true) 612 eq(is_approx_eq('height', win_id_3, win_id_2), false) 613 end) 614 615 pending('can be nested', function() 616 exec_lua [[ 617 -- Should be same as `vim.cmd.wincmd('=')` 618 vim._with({ horizontal = true }, function() 619 vim._with({ horizontal = false }, function() 620 vim.cmd.wincmd('=') 621 end) 622 end) 623 ]] 624 eq(is_approx_eq('width', win_id_1, win_id_2), true) 625 eq(is_approx_eq('height', win_id_3, win_id_2), true) 626 end) 627 end) 628 629 describe('`keepalt` context', function() 630 pending('works', function() 631 local out = exec_lua [[ 632 vim.cmd('edit alt') 633 vim.cmd('edit new') 634 assert(fn.bufname('#') == 'alt') 635 636 -- Should work as `vim.cmd('keepalt edit very-new')` 637 vim._with({ keepalt = true }, function() 638 vim.cmd.edit('very-new') 639 end) 640 return fn.bufname('#') == 'alt' 641 ]] 642 eq(true, out) 643 end) 644 645 it('can be nested', function() 646 local out = exec_lua [[ 647 vim.cmd('edit alt') 648 vim.cmd('edit new') 649 assert(fn.bufname('#') == 'alt') 650 651 -- Should work as `vim.cmd.edit('very-new')` 652 vim._with({ keepalt = true }, function() 653 vim._with({ keepalt = false }, function() 654 vim.cmd.edit('very-new') 655 end) 656 end) 657 return fn.bufname('#') == 'alt' 658 ]] 659 eq(false, out) 660 end) 661 end) 662 663 describe('`keepjumps` context', function() 664 pending('works', function() 665 local out = exec_lua [[ 666 api.nvim_buf_set_lines(0, 0, -1, false, { 'aaa', 'bbb', 'ccc' }) 667 local jumplist_before = fn.getjumplist() 668 -- Should work as `vim.cmd('keepjumps normal! Ggg')` 669 vim._with({ keepjumps = true }, function() 670 vim.cmd('normal! Ggg') 671 end) 672 return vim.deep_equal(jumplist_before, fn.getjumplist()) 673 ]] 674 eq(true, out) 675 end) 676 677 it('can be nested', function() 678 local out = exec_lua [[ 679 api.nvim_buf_set_lines(0, 0, -1, false, { 'aaa', 'bbb', 'ccc' }) 680 local jumplist_before = fn.getjumplist() 681 vim._with({ keepjumps = true }, function() 682 vim._with({ keepjumps = false }, function() 683 vim.cmd('normal! Ggg') 684 end) 685 end) 686 return vim.deep_equal(jumplist_before, fn.getjumplist()) 687 ]] 688 eq(false, out) 689 end) 690 end) 691 692 describe('`keepmarks` context', function() 693 pending('works', function() 694 local out = exec_lua [[ 695 vim.cmd('set cpoptions+=R') 696 api.nvim_buf_set_lines(0, 0, -1, false, { 'bbb', 'ccc', 'aaa' }) 697 api.nvim_buf_set_mark(0, 'm', 2, 2, {}) 698 699 -- Should be the same as `vim.cmd('keepmarks %!sort')` 700 vim._with({ keepmarks = true }, function() 701 vim.cmd('%!sort') 702 end) 703 return api.nvim_buf_get_mark(0, 'm') 704 ]] 705 eq({ 2, 2 }, out) 706 end) 707 708 it('can be nested', function() 709 local out = exec_lua [[ 710 vim.cmd('set cpoptions+=R') 711 api.nvim_buf_set_lines(0, 0, -1, false, { 'bbb', 'ccc', 'aaa' }) 712 api.nvim_buf_set_mark(0, 'm', 2, 2, {}) 713 714 vim._with({ keepmarks = true }, function() 715 vim._with({ keepmarks = false }, function() 716 vim.cmd('%!sort') 717 end) 718 end) 719 return api.nvim_buf_get_mark(0, 'm') 720 ]] 721 eq({ 0, 2 }, out) 722 end) 723 end) 724 725 describe('`keepatterns` context', function() 726 pending('works', function() 727 local out = exec_lua [[ 728 api.nvim_buf_set_lines(0, 0, -1, false, { 'aaa', 'bbb' }) 729 vim.cmd('/aaa') 730 -- Should be the same as `vim.cmd('keeppatterns /bbb')` 731 vim._with({ keeppatterns = true }, function() 732 vim.cmd('/bbb') 733 end) 734 return fn.getreg('/') 735 ]] 736 eq('aaa', out) 737 end) 738 739 it('can be nested', function() 740 local out = exec_lua [[ 741 api.nvim_buf_set_lines(0, 0, -1, false, { 'aaa', 'bbb' }) 742 vim.cmd('/aaa') 743 vim._with({ keeppatterns = true }, function() 744 vim._with({ keeppatterns = false }, function() 745 vim.cmd('/bbb') 746 end) 747 end) 748 return fn.getreg('/') 749 ]] 750 eq('bbb', out) 751 end) 752 end) 753 754 describe('`lockmarks` context', function() 755 it('works', function() 756 local mark = exec_lua [[ 757 api.nvim_buf_set_lines(0, 0, 0, false, { 'aaa', 'bbb', 'ccc' }) 758 api.nvim_buf_set_mark(0, 'm', 2, 2, {}) 759 -- Should be same as `:lockmarks lua api.nvim_buf_set_lines(...)` 760 vim._with({ lockmarks = true }, function() 761 api.nvim_buf_set_lines(0, 0, 2, false, { 'uuu', 'vvv', 'www' }) 762 end) 763 return api.nvim_buf_get_mark(0, 'm') 764 ]] 765 eq({ 2, 2 }, mark) 766 end) 767 768 it('can be nested', function() 769 local mark = exec_lua [[ 770 api.nvim_buf_set_lines(0, 0, 0, false, { 'aaa', 'bbb', 'ccc' }) 771 api.nvim_buf_set_mark(0, 'm', 2, 2, {}) 772 vim._with({ lockmarks = true }, function() 773 vim._with({ lockmarks = false }, function() 774 api.nvim_buf_set_lines(0, 0, 2, false, { 'uuu', 'vvv', 'www' }) 775 end) 776 end) 777 return api.nvim_buf_get_mark(0, 'm') 778 ]] 779 eq({ 0, 2 }, mark) 780 end) 781 end) 782 783 describe('`noautocmd` context', function() 784 it('works', function() 785 local out = exec_lua [[ 786 _G.n_events = 0 787 vim.cmd('au ModeChanged * lua _G.n_events = _G.n_events + 1') 788 -- Should be the same as `vim.cmd('noautocmd normal! vv')` 789 vim._with({ noautocmd = true }, function() 790 vim.cmd('normal! vv') 791 end) 792 return _G.n_events 793 ]] 794 eq(0, out) 795 end) 796 797 it('works with User events', function() 798 local out = exec_lua [[ 799 _G.n_events = 0 800 vim.cmd('au User MyEvent lua _G.n_events = _G.n_events + 1') 801 -- Should be the same as `vim.cmd('noautocmd doautocmd User MyEvent')` 802 vim._with({ noautocmd = true }, function() 803 api.nvim_exec_autocmds('User', { pattern = 'MyEvent' }) 804 end) 805 return _G.n_events 806 ]] 807 eq(0, out) 808 end) 809 810 pending('can be nested', function() 811 local out = exec_lua [[ 812 _G.n_events = 0 813 vim.cmd('au ModeChanged * lua _G.n_events = _G.n_events + 1') 814 vim._with({ noautocmd = true }, function() 815 vim._with({ noautocmd = false }, function() 816 vim.cmd('normal! vv') 817 end) 818 end) 819 return _G.n_events 820 ]] 821 eq(2, out) 822 end) 823 end) 824 825 describe('`o` context', function() 826 before_each(function() 827 exec_lua [[ 828 _G.other_win, _G.cur_win = setup_windows() 829 _G.other_buf, _G.cur_buf = setup_buffers() 830 831 vim.bo[other_buf].commentstring = '## %s' 832 vim.bo[cur_buf].commentstring = '// %s' 833 vim.go.commentstring = '$$ %s' 834 835 vim.bo[other_buf].undolevels = 100 836 vim.bo[cur_buf].undolevels = 250 837 vim.go.undolevels = 500 838 839 vim.wo[other_win].virtualedit = 'block' 840 vim.wo[cur_win].virtualedit = 'insert' 841 vim.go.virtualedit = 'none' 842 843 vim.wo[other_win].winblend = 10 844 vim.wo[cur_win].winblend = 25 845 vim.go.winblend = 50 846 847 vim.go.langmap = 'xy,yx' 848 vim.go.confirm = false 849 850 _G.get_state = function() 851 return { 852 bo = { 853 cms_cur = vim.bo[cur_buf].commentstring, 854 cms_other = vim.bo[other_buf].commentstring, 855 ul_cur = vim.bo[cur_buf].undolevels, 856 ul_other = vim.bo[other_buf].undolevels, 857 }, 858 wo = { 859 ve_cur = vim.wo[cur_win].virtualedit, 860 ve_other = vim.wo[other_win].virtualedit, 861 winbl_cur = vim.wo[cur_win].winblend, 862 winbl_other = vim.wo[other_win].winblend, 863 }, 864 go = { 865 cms = vim.go.commentstring, 866 ul = vim.go.undolevels, 867 ve = vim.go.virtualedit, 868 winbl = vim.go.winblend, 869 lmap = vim.go.langmap, 870 cf = vim.go.confirm, 871 }, 872 } 873 end 874 ]] 875 end) 876 877 it('works', function() 878 local out = exec_lua [[ 879 local context = { 880 o = { 881 commentstring = '-- %s', 882 undolevels = 0, 883 virtualedit = 'all', 884 winblend = 75, 885 langmap = 'ab,ba', 886 confirm = true, 887 }, 888 } 889 890 local before = get_state() 891 local inner = vim._with(context, function() 892 assert(api.nvim_get_current_buf() == cur_buf) 893 assert(api.nvim_get_current_win() == cur_win) 894 return get_state() 895 end) 896 897 return { before = before, inner = inner, after = get_state() } 898 ]] 899 900 -- Options in context are set with `vim.o`, so usually both local 901 -- and global values are affected. Yet all of them should be later 902 -- restored to pre-context values. 903 eq({ 904 bo = { cms_cur = '-- %s', cms_other = '## %s', ul_cur = -123456, ul_other = 100 }, 905 wo = { ve_cur = 'all', ve_other = 'block', winbl_cur = 75, winbl_other = 10 }, 906 go = { cms = '-- %s', ul = 0, ve = 'all', winbl = 75, lmap = 'ab,ba', cf = true }, 907 }, out.inner) 908 eq(out.before, out.after) 909 end) 910 911 it('sets options in `buf` context', function() 912 local out = exec_lua [[ 913 local context = { buf = other_buf, o = { commentstring = '-- %s', undolevels = 0 } } 914 915 local before = get_state() 916 local inner = vim._with(context, function() 917 assert(api.nvim_get_current_buf() == other_buf) 918 return get_state() 919 end) 920 921 return { before = before, inner = inner, after = get_state() } 922 ]] 923 924 eq({ 925 bo = { cms_cur = '// %s', cms_other = '-- %s', ul_cur = 250, ul_other = -123456 }, 926 wo = { ve_cur = 'insert', ve_other = 'block', winbl_cur = 25, winbl_other = 10 }, 927 -- Global `winbl` inside context ideally should be untouched and equal 928 -- to 50. It seems to be equal to 0 because `context.buf` uses 929 -- `aucmd_prepbuf` C approach which has no guarantees about window or 930 -- window option values inside context. 931 go = { cms = '-- %s', ul = 0, ve = 'none', winbl = 0, lmap = 'xy,yx', cf = false }, 932 }, out.inner) 933 eq(out.before, out.after) 934 end) 935 936 it('sets options in `win` context', function() 937 local out = exec_lua [[ 938 local context = { win = other_win, o = { winblend = 75, virtualedit = 'all' } } 939 940 local before = get_state() 941 local inner = vim._with(context, function() 942 assert(api.nvim_get_current_win() == other_win) 943 return get_state() 944 end) 945 946 return { before = before, inner = inner, after = get_state() } 947 ]] 948 949 eq({ 950 bo = { cms_cur = '// %s', cms_other = '## %s', ul_cur = 250, ul_other = 100 }, 951 wo = { winbl_cur = 25, winbl_other = 75, ve_cur = 'insert', ve_other = 'all' }, 952 go = { cms = '$$ %s', ul = 500, winbl = 75, ve = 'all', lmap = 'xy,yx', cf = false }, 953 }, out.inner) 954 eq(out.before, out.after) 955 end) 956 957 it('restores only options from context', function() 958 local out = exec_lua [[ 959 local context = { o = { undolevels = 0, winblend = 75, langmap = 'ab,ba' } } 960 961 local inner = vim._with(context, function() 962 assert(api.nvim_get_current_buf() == cur_buf) 963 assert(api.nvim_get_current_win() == cur_win) 964 965 vim.o.commentstring = '!! %s' 966 vim.o.undolevels = 750 967 vim.o.virtualedit = 'onemore' 968 vim.o.winblend = 99 969 vim.o.langmap = 'uv,vu' 970 return get_state() 971 end) 972 973 return { inner = inner, after = get_state() } 974 ]] 975 976 eq({ 977 bo = { cms_cur = '!! %s', cms_other = '## %s', ul_cur = -123456, ul_other = 100 }, 978 wo = { ve_cur = 'onemore', ve_other = 'block', winbl_cur = 99, winbl_other = 10 }, 979 go = { cms = '!! %s', ul = 750, ve = 'onemore', winbl = 99, lmap = 'uv,vu', cf = false }, 980 }, out.inner) 981 eq({ 982 bo = { cms_cur = '!! %s', cms_other = '## %s', ul_cur = 250, ul_other = 100 }, 983 wo = { ve_cur = 'onemore', ve_other = 'block', winbl_cur = 25, winbl_other = 10 }, 984 go = { cms = '!! %s', ul = 500, ve = 'onemore', winbl = 50, lmap = 'xy,yx', cf = false }, 985 }, out.after) 986 end) 987 988 it('does not trigger events', function() 989 exec_lua [[ 990 _G.test_events = { 991 'BufEnter', 'BufLeave', 'WinEnter', 'WinLeave', 'BufWinEnter', 'BufWinLeave' 992 } 993 _G.test_context = { o = { undolevels = 0, winblend = 75, langmap = 'ab,ba' } } 994 _G.test_trig_event = function() vim.cmd.new() end 995 ]] 996 assert_events_trigger() 997 end) 998 999 it('can be nested', function() 1000 local out = exec_lua [[ 1001 local before, before_inner, after_inner = get_state(), nil, nil 1002 local cxt_o = { commentstring = '-- %s', winblend = 75, langmap = 'ab,ba', undolevels = 0 } 1003 vim._with({ o = cxt_o }, function() 1004 before_inner = get_state() 1005 local inner_cxt_o = { commentstring = '!! %s', winblend = 99, langmap = 'uv,vu' } 1006 inner = vim._with({ o = inner_cxt_o }, get_state) 1007 after_inner = get_state() 1008 end) 1009 return { 1010 before = before, before_inner = before_inner, 1011 inner = inner, 1012 after_inner = after_inner, after = get_state(), 1013 } 1014 ]] 1015 eq('!! %s', out.inner.bo.cms_cur) 1016 eq(99, out.inner.wo.winbl_cur) 1017 eq('uv,vu', out.inner.go.lmap) 1018 eq(0, out.inner.go.ul) 1019 eq(out.before_inner, out.after_inner) 1020 eq(out.before, out.after) 1021 end) 1022 end) 1023 1024 describe('`sandbox` context', function() 1025 it('works', function() 1026 local ok, err = pcall( 1027 exec_lua, 1028 [[ 1029 -- Should work as `vim.cmd('sandbox call append(0, "aaa")')` 1030 vim._with({ sandbox = true }, function() 1031 fn.append(0, 'aaa') 1032 end) 1033 ]] 1034 ) 1035 eq(false, ok) 1036 matches('Not allowed in sandbox', err) 1037 end) 1038 1039 it('can NOT be nested', function() 1040 -- This behavior is intentionally different from other flags as allowing 1041 -- disabling `sandbox` from nested function seems to be against the point 1042 -- of using `sandbox` context in the first place 1043 local ok, err = pcall( 1044 exec_lua, 1045 [[ 1046 vim._with({ sandbox = true }, function() 1047 vim._with({ sandbox = false }, function() 1048 fn.append(0, 'aaa') 1049 end) 1050 end) 1051 ]] 1052 ) 1053 eq(false, ok) 1054 matches('Not allowed in sandbox', err) 1055 end) 1056 end) 1057 1058 describe('`silent` context', function() 1059 it('works', function() 1060 exec_lua [[ 1061 -- Should be same as `vim.cmd('silent lua print("aaa")')` 1062 vim._with({ silent = true }, function() print('aaa') end) 1063 ]] 1064 eq('', exec_capture('messages')) 1065 1066 exec_lua [[ vim._with({ silent = true }, function() vim.cmd.echomsg('"bbb"') end) ]] 1067 eq('', exec_capture('messages')) 1068 1069 local screen = Screen.new(20, 5) 1070 screen:set_default_attr_ids { 1071 [1] = { bold = true, reverse = true }, 1072 [2] = { bold = true, foreground = Screen.colors.Blue }, 1073 } 1074 exec_lua [[ vim._with({ silent = true }, function() vim.cmd.echo('"ccc"') end) ]] 1075 screen:expect [[ 1076 ^ | 1077 {2:~ }|*3 1078 | 1079 ]] 1080 end) 1081 1082 pending('can be nested', function() 1083 exec_lua [[ vim._with({ silent = true }, function() 1084 vim._with({ silent = false }, function() 1085 print('aaa') 1086 end) 1087 end)]] 1088 eq('aaa', exec_capture('messages')) 1089 end) 1090 end) 1091 1092 describe('`unsilent` context', function() 1093 it('works', function() 1094 exec_lua [[ 1095 _G.f = function() 1096 -- Should be same as `vim.cmd('unsilent lua print("aaa")')` 1097 vim._with({ unsilent = true }, function() print('aaa') end) 1098 end 1099 ]] 1100 command('silent lua f()') 1101 eq('aaa', exec_capture('messages')) 1102 end) 1103 1104 pending('can be nested', function() 1105 exec_lua [[ 1106 _G.f = function() 1107 vim._with({ unsilent = true }, function() 1108 vim._with({ unsilent = false }, function() print('aaa') end) 1109 end) 1110 end 1111 ]] 1112 command('silent lua f()') 1113 eq('', exec_capture('messages')) 1114 end) 1115 end) 1116 1117 describe('`win` context', function() 1118 it('works', function() 1119 local out = exec_lua [[ 1120 local other_win, cur_win = setup_windows() 1121 local inner = vim._with({ win = other_win }, function() 1122 return api.nvim_get_current_win() 1123 end) 1124 return { inner == other_win, api.nvim_get_current_win() == cur_win } 1125 ]] 1126 eq({ true, true }, out) 1127 end) 1128 1129 it('does not trigger events', function() 1130 exec_lua [[ 1131 _G.test_events = { 'WinEnter', 'WinLeave', 'BufWinEnter', 'BufWinLeave' } 1132 _G.test_context = { win = other_win } 1133 _G.test_trig_event = function() vim.cmd.new() end 1134 ]] 1135 assert_events_trigger() 1136 end) 1137 1138 it('can access window options', function() 1139 local out = exec_lua [[ 1140 local other_win, cur_win = setup_windows() 1141 vim.wo[other_win].winblend = 10 1142 vim.wo[cur_win].winblend = 25 1143 1144 vim._with({ win = other_win }, function() 1145 vim.cmd.setlocal('winblend=0') 1146 end) 1147 1148 return vim.wo[other_win].winblend == 0 and vim.wo[cur_win].winblend == 25 1149 ]] 1150 eq(true, out) 1151 end) 1152 1153 it('works with different kinds of windows', function() 1154 exec_lua [[ 1155 local assert_win = function(win) 1156 vim._with({ win = win }, function() 1157 assert(api.nvim_get_current_win() == win) 1158 end) 1159 end 1160 1161 -- Current 1162 assert_win(api.nvim_get_current_win()) 1163 1164 -- Not visible 1165 local other_win, cur_win = setup_windows() 1166 vim.cmd.tabnew() 1167 assert_win(other_win) 1168 1169 -- Floating 1170 local float_win = api.nvim_open_win( 1171 api.nvim_create_buf(false, true), 1172 false, 1173 { relative = 'editor', row = 1, col = 1, height = 5, width = 5} 1174 ) 1175 assert_win(float_win) 1176 ]] 1177 end) 1178 1179 it('does not cause ml_get errors with invalid visual selection', function() 1180 exec_lua [[ 1181 local feedkeys = function(keys) api.nvim_feedkeys(vim.keycode(keys), 'txn', false) end 1182 1183 -- Add lines to the current buffer and make another window looking into an empty buffer. 1184 local win_empty, win_lines = setup_windows() 1185 api.nvim_buf_set_lines(0, 0, -1, true, { 'a', 'b', 'c' }) 1186 1187 -- Start Visual in current window, redraw in other window with fewer lines. 1188 -- Should be fixed by vim-patch:8.2.4018. 1189 feedkeys('G<C-V>') 1190 vim._with({ win = win_empty }, function() vim.cmd.redraw() end) 1191 1192 -- Start Visual in current window, extend it in other window with more lines. 1193 -- Fixed for win_execute by vim-patch:8.2.4026, but nvim_win_call should also not be affected. 1194 feedkeys('<Esc>gg') 1195 api.nvim_set_current_win(win_empty) 1196 feedkeys('gg<C-V>') 1197 vim._with({ win = win_lines }, function() feedkeys('G<C-V>') end) 1198 vim.cmd.redraw() 1199 ]] 1200 end) 1201 1202 it('can be nested', function() 1203 exec_lua [[ 1204 local other_win, cur_win = setup_windows() 1205 vim._with({ win = other_win }, function() 1206 assert(api.nvim_get_current_win() == other_win) 1207 inner = vim._with({ win = cur_win }, function() 1208 assert(api.nvim_get_current_win() == cur_win) 1209 end) 1210 assert(api.nvim_get_current_win() == other_win) 1211 end) 1212 assert(api.nvim_get_current_win() == cur_win) 1213 ]] 1214 end) 1215 1216 it('updates ruler if cursor moved', function() 1217 local screen = Screen.new(30, 5) 1218 screen:set_default_attr_ids { 1219 [1] = { reverse = true }, 1220 [2] = { bold = true, reverse = true }, 1221 } 1222 exec_lua [[ 1223 vim.opt.ruler = true 1224 local lines = {} 1225 for i = 0, 499 do lines[#lines + 1] = tostring(i) end 1226 api.nvim_buf_set_lines(0, 0, -1, true, lines) 1227 api.nvim_win_set_cursor(0, { 20, 0 }) 1228 vim.cmd 'split' 1229 _G.win = api.nvim_get_current_win() 1230 vim.cmd "wincmd w | redraw" 1231 ]] 1232 screen:expect [[ 1233 19 | 1234 {1:< Name] [+] 20,1 3%}| 1235 ^19 | 1236 {2:< Name] [+] 20,1 3%}| 1237 | 1238 ]] 1239 exec_lua [[ 1240 vim._with({ win = win }, function() api.nvim_win_set_cursor(0, { 100, 0 }) end) 1241 vim.cmd "redraw" 1242 ]] 1243 screen:expect [[ 1244 99 | 1245 {1:< Name] [+] 100,1 19%}| 1246 ^19 | 1247 {2:< Name] [+] 20,1 3%}| 1248 | 1249 ]] 1250 end) 1251 1252 it('layout in current tabpage does not affect windows in others', function() 1253 command('tab split') 1254 local t2_move_win = api.nvim_get_current_win() 1255 command('vsplit') 1256 local t2_other_win = api.nvim_get_current_win() 1257 command('tabprevious') 1258 matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)')) 1259 command('vsplit') 1260 1261 exec_lua('vim._with({ win = ... }, function() vim.cmd.wincmd "J" end)', t2_move_win) 1262 eq({ 'col', { { 'leaf', t2_other_win }, { 'leaf', t2_move_win } } }, fn.winlayout(2)) 1263 end) 1264 end) 1265 1266 describe('`wo` context', function() 1267 before_each(function() 1268 exec_lua [[ 1269 _G.other_win, _G.cur_win = setup_windows() 1270 1271 -- 'virtualedit' is global or local to window (global-local) and string 1272 vim.wo[other_win].virtualedit = 'block' 1273 vim.wo[cur_win].virtualedit = 'insert' 1274 vim.go.virtualedit = 'none' 1275 1276 -- 'winblend' is local to window and number 1277 vim.wo[other_win].winblend = 10 1278 vim.wo[cur_win].winblend = 25 1279 vim.go.winblend = 50 1280 1281 -- 'number' is local to window and boolean 1282 vim.wo[other_win].number = false 1283 vim.wo[cur_win].number = false 1284 vim.go.number = true 1285 1286 _G.get_state = function() 1287 return { 1288 wo = { 1289 ve_cur = vim.wo[cur_win].virtualedit, 1290 ve_other = vim.wo[other_win].virtualedit, 1291 winbl_cur = vim.wo[cur_win].winblend, 1292 winbl_other = vim.wo[other_win].winblend, 1293 nu_cur = vim.wo[cur_win].number, 1294 nu_other = vim.wo[other_win].number, 1295 }, 1296 go = { 1297 ve = vim.go.virtualedit, 1298 winbl = vim.go.winblend, 1299 nu = vim.go.number, 1300 }, 1301 } 1302 end 1303 ]] 1304 end) 1305 1306 it('works', function() 1307 local out = exec_lua [[ 1308 local context = { wo = { virtualedit = 'all', winblend = 75, number = true } } 1309 1310 local before = get_state() 1311 local inner = vim._with(context, function() 1312 assert(api.nvim_get_current_win() == cur_win) 1313 return get_state() 1314 end) 1315 1316 return { before = before, inner = inner, after = get_state() } 1317 ]] 1318 1319 eq({ 1320 wo = { 1321 ve_cur = 'all', 1322 ve_other = 'block', 1323 winbl_cur = 75, 1324 winbl_other = 10, 1325 nu_cur = true, 1326 nu_other = false, 1327 }, 1328 go = { ve = 'none', winbl = 75, nu = true }, 1329 }, out.inner) 1330 eq(out.before, out.after) 1331 end) 1332 1333 it('sets options in `win` context', function() 1334 local out = exec_lua [[ 1335 local context = { win = other_win, wo = { virtualedit = 'all', winblend = 75, number = true } } 1336 1337 local before = get_state() 1338 local inner = vim._with(context, function() 1339 assert(api.nvim_get_current_win() == other_win) 1340 return get_state() 1341 end) 1342 1343 return { before = before, inner = inner, after = get_state() } 1344 ]] 1345 1346 eq({ 1347 wo = { 1348 ve_cur = 'insert', 1349 ve_other = 'all', 1350 winbl_cur = 25, 1351 winbl_other = 75, 1352 nu_cur = false, 1353 nu_other = true, 1354 }, 1355 go = { ve = 'none', winbl = 75, nu = true }, 1356 }, out.inner) 1357 eq(out.before, out.after) 1358 end) 1359 1360 it('restores only options from context', function() 1361 local out = exec_lua [[ 1362 local context = { wo = { winblend = 75 } } 1363 1364 local inner = vim._with(context, function() 1365 assert(api.nvim_get_current_win() == cur_win) 1366 vim.wo[cur_win].virtualedit = 'onemore' 1367 vim.wo[cur_win].winblend = 99 1368 return get_state() 1369 end) 1370 1371 return { inner = inner, after = get_state() } 1372 ]] 1373 1374 eq({ 1375 wo = { 1376 ve_cur = 'onemore', 1377 ve_other = 'block', 1378 winbl_cur = 99, 1379 winbl_other = 10, 1380 nu_cur = false, 1381 nu_other = false, 1382 }, 1383 go = { ve = 'none', winbl = 99, nu = true }, 1384 }, out.inner) 1385 eq({ 1386 wo = { 1387 ve_cur = 'onemore', 1388 ve_other = 'block', 1389 winbl_cur = 25, 1390 winbl_other = 10, 1391 nu_cur = false, 1392 nu_other = false, 1393 }, 1394 go = { ve = 'none', winbl = 50, nu = true }, 1395 }, out.after) 1396 end) 1397 1398 it('does not trigger events', function() 1399 exec_lua [[ 1400 _G.test_events = { 'WinEnter', 'WinLeave', 'BufWinEnter', 'BufWinLeave' } 1401 _G.test_context = { wo = { winblend = 75 } } 1402 _G.test_trig_event = function() vim.cmd.new() end 1403 ]] 1404 assert_events_trigger() 1405 end) 1406 1407 it('can be nested', function() 1408 local out = exec_lua [[ 1409 local before, before_inner, after_inner = get_state(), nil, nil 1410 vim._with({ wo = { winblend = 75, virtualedit = 'all', number = true } }, function() 1411 before_inner = get_state() 1412 inner = vim._with({ wo = { winblend = 99, number = false } }, get_state) 1413 after_inner = get_state() 1414 end) 1415 return { 1416 before = before, before_inner = before_inner, 1417 inner = inner, 1418 after_inner = after_inner, after = get_state(), 1419 } 1420 ]] 1421 eq(99, out.inner.wo.winbl_cur) 1422 eq('all', out.inner.wo.ve_cur) 1423 eq(false, out.inner.wo.nu_cur) 1424 eq(out.before_inner, out.after_inner) 1425 eq(out.before, out.after) 1426 end) 1427 end) 1428 1429 it('returns what callback returns', function() 1430 local out_verify = exec_lua [[ 1431 out = { vim._with({}, function() 1432 return 'a', 2, nil, { 4 }, function() end 1433 end) } 1434 return { 1435 out[1] == 'a', out[2] == 2, out[3] == nil, 1436 vim.deep_equal(out[4], { 4 }), 1437 type(out[5]) == 'function', 1438 vim.tbl_count(out), 1439 } 1440 ]] 1441 eq({ true, true, true, true, true, 4 }, out_verify) 1442 end) 1443 1444 it('can return values by reference', function() 1445 local out = exec_lua [[ 1446 local val = { 4, 10 } 1447 local ref = vim._with({}, function() return val end) 1448 ref[1] = 7 1449 return val 1450 ]] 1451 eq({ 7, 10 }, out) 1452 end) 1453 1454 it('can not work with conflicting `buf` and `win`', function() 1455 local out = exec_lua [[ 1456 local other_buf, cur_buf = setup_buffers() 1457 local other_win, cur_win = setup_windows() 1458 assert(api.nvim_win_get_buf(other_win) ~= other_buf) 1459 local _, err = pcall(vim._with, { buf = other_buf, win = other_win }, function() end) 1460 return err 1461 ]] 1462 matches('Can not set both `buf` and `win`', out) 1463 end) 1464 1465 it('works with several contexts at once', function() 1466 local out = exec_lua [[ 1467 local other_buf, cur_buf = setup_buffers() 1468 vim.bo[other_buf].commentstring = '## %s' 1469 api.nvim_buf_set_lines(other_buf, 0, -1, false, { 'aaa', 'bbb', 'ccc' }) 1470 api.nvim_buf_set_mark(other_buf, 'm', 2, 2, {}) 1471 1472 vim.go.commentstring = '// %s' 1473 vim.go.langmap = 'xy,yx' 1474 1475 local context = { 1476 buf = other_buf, 1477 bo = { commentstring = '-- %s' }, 1478 go = { langmap = 'ab,ba' }, 1479 lockmarks = true, 1480 } 1481 1482 local inner = vim._with(context, function() 1483 api.nvim_buf_set_lines(0, 0, -1, false, { 'uuu', 'vvv', 'www' }) 1484 return { 1485 buf = api.nvim_get_current_buf(), 1486 bo = { cms = vim.bo.commentstring }, 1487 go = { cms = vim.go.commentstring, lmap = vim.go.langmap }, 1488 mark = api.nvim_buf_get_mark(0, 'm') 1489 } 1490 end) 1491 1492 local after = { 1493 buf = api.nvim_get_current_buf(), 1494 bo = { cms = vim.bo[other_buf].commentstring }, 1495 go = { cms = vim.go.commentstring, lmap = vim.go.langmap }, 1496 mark = api.nvim_buf_get_mark(other_buf, 'm') 1497 } 1498 1499 return { 1500 context_buf = other_buf, cur_buf = cur_buf, 1501 inner = inner, after = after 1502 } 1503 ]] 1504 1505 eq({ 1506 buf = out.context_buf, 1507 bo = { cms = '-- %s' }, 1508 go = { cms = '// %s', lmap = 'ab,ba' }, 1509 mark = { 2, 2 }, 1510 }, out.inner) 1511 eq({ 1512 buf = out.cur_buf, 1513 bo = { cms = '## %s' }, 1514 go = { cms = '// %s', lmap = 'xy,yx' }, 1515 mark = { 2, 2 }, 1516 }, out.after) 1517 end) 1518 1519 it('works with same option set in different contexts', function() 1520 local out = exec_lua [[ 1521 local get_state = function() 1522 return { 1523 bo = { cms = vim.bo.commentstring }, 1524 wo = { ve = vim.wo.virtualedit }, 1525 go = { cms = vim.go.commentstring, ve = vim.go.virtualedit }, 1526 } 1527 end 1528 1529 vim.bo.commentstring = '// %s' 1530 vim.go.commentstring = '$$ %s' 1531 vim.wo.virtualedit = 'insert' 1532 vim.go.virtualedit = 'none' 1533 1534 local before = get_state() 1535 local context_no_go = { 1536 o = { commentstring = '-- %s', virtualedit = 'all' }, 1537 bo = { commentstring = '!! %s' }, 1538 wo = { virtualedit = 'onemore' }, 1539 } 1540 local inner_no_go = vim._with(context_no_go, get_state) 1541 local middle = get_state() 1542 local context_with_go = { 1543 o = { commentstring = '-- %s', virtualedit = 'all' }, 1544 bo = { commentstring = '!! %s' }, 1545 wo = { virtualedit = 'onemore' }, 1546 go = { commentstring = '@@ %s', virtualedit = 'block' }, 1547 } 1548 local inner_with_go = vim._with(context_with_go, get_state) 1549 return { 1550 before = before, 1551 inner_no_go = inner_no_go, 1552 middle = middle, 1553 inner_with_go = inner_with_go, 1554 after = get_state(), 1555 } 1556 ]] 1557 1558 -- Should prefer explicit local scopes instead of `o` 1559 eq({ 1560 bo = { cms = '!! %s' }, 1561 wo = { ve = 'onemore' }, 1562 go = { cms = '-- %s', ve = 'all' }, 1563 }, out.inner_no_go) 1564 eq(out.before, out.middle) 1565 1566 -- Should prefer explicit global scopes instead of `o` 1567 eq({ 1568 bo = { cms = '!! %s' }, 1569 wo = { ve = 'onemore' }, 1570 go = { cms = '@@ %s', ve = 'block' }, 1571 }, out.inner_with_go) 1572 eq(out.middle, out.after) 1573 end) 1574 1575 pending('can forward command modifiers to user command', function() 1576 local out = exec_lua [[ 1577 local test_flags = { 1578 'emsg_silent', 1579 'hide', 1580 'keepalt', 1581 'keepjumps', 1582 'keepmarks', 1583 'keeppatterns', 1584 'lockmarks', 1585 'noautocmd', 1586 'silent', 1587 'unsilent', 1588 } 1589 1590 local used_smods 1591 local command = function(data) 1592 used_smods = data.smods 1593 end 1594 api.nvim_create_user_command('DummyLog', command, {}) 1595 1596 local res = {} 1597 for _, flag in ipairs(test_flags) do 1598 used_smods = nil 1599 vim._with({ [flag] = true }, function() vim.cmd('DummyLog') end) 1600 res[flag] = used_smods[flag] 1601 end 1602 return res 1603 ]] 1604 for k, v in pairs(out) do 1605 eq({ k, true }, { k, v }) 1606 end 1607 end) 1608 1609 it('handles error in callback', function() 1610 -- Should still restore initial context 1611 local out_buf = exec_lua [[ 1612 local other_buf, cur_buf = setup_buffers() 1613 vim.bo[other_buf].commentstring = '## %s' 1614 1615 local context = { buf = other_buf, bo = { commentstring = '-- %s' } } 1616 local ok, err = pcall(vim._with, context, function() error('Oops buf', 0) end) 1617 1618 return { 1619 ok, 1620 err, 1621 api.nvim_get_current_buf() == cur_buf, 1622 vim.bo[other_buf].commentstring, 1623 } 1624 ]] 1625 eq({ false, 'Oops buf', true, '## %s' }, out_buf) 1626 1627 local out_win = exec_lua [[ 1628 local other_win, cur_win = setup_windows() 1629 vim.wo[other_win].winblend = 25 1630 1631 local context = { win = other_win, wo = { winblend = 50 } } 1632 local ok, err = pcall(vim._with, context, function() error('Oops win', 0) end) 1633 1634 return { 1635 ok, 1636 err, 1637 api.nvim_get_current_win() == cur_win, 1638 vim.wo[other_win].winblend, 1639 } 1640 ]] 1641 eq({ false, 'Oops win', true, 25 }, out_win) 1642 end) 1643 1644 it('handles not supported option', function() 1645 local out = exec_lua [[ 1646 -- Should still restore initial state 1647 vim.bo.commentstring = '## %s' 1648 1649 local context = { o = { commentstring = '-- %s' }, bo = { winblend = 10 } } 1650 local ok, err = pcall(vim._with, context, function() end) 1651 1652 return { ok = ok, err = err, cms = vim.bo.commentstring } 1653 ]] 1654 eq(false, out.ok) 1655 matches('window.*option.*winblend', out.err) 1656 eq('## %s', out.cms) 1657 end) 1658 1659 it('validates arguments', function() 1660 exec_lua [[ 1661 _G.get_error = function(...) 1662 local _, err = pcall(vim._with, ...) 1663 return err or '' 1664 end 1665 ]] 1666 local get_error = function(string_args) 1667 return exec_lua('return get_error(' .. string_args .. ')') 1668 end 1669 1670 matches('context.*table', get_error("'a', function() end")) 1671 matches('f.*function', get_error('{}, 1')) 1672 1673 local assert_context = function(bad_context, expected_type) 1674 local bad_field = vim.tbl_keys(bad_context)[1] 1675 matches( 1676 'context%.' .. bad_field .. '.*' .. expected_type, 1677 get_error(vim.inspect(bad_context) .. ', function() end') 1678 ) 1679 end 1680 1681 assert_context({ bo = 1 }, 'table') 1682 assert_context({ buf = 'a' }, 'number') 1683 assert_context({ emsg_silent = 1 }, 'boolean') 1684 assert_context({ env = 1 }, 'table') 1685 assert_context({ go = 1 }, 'table') 1686 assert_context({ hide = 1 }, 'boolean') 1687 assert_context({ keepalt = 1 }, 'boolean') 1688 assert_context({ keepjumps = 1 }, 'boolean') 1689 assert_context({ keepmarks = 1 }, 'boolean') 1690 assert_context({ keeppatterns = 1 }, 'boolean') 1691 assert_context({ lockmarks = 1 }, 'boolean') 1692 assert_context({ noautocmd = 1 }, 'boolean') 1693 assert_context({ o = 1 }, 'table') 1694 assert_context({ sandbox = 1 }, 'boolean') 1695 assert_context({ silent = 1 }, 'boolean') 1696 assert_context({ unsilent = 1 }, 'boolean') 1697 assert_context({ win = 'a' }, 'number') 1698 assert_context({ wo = 1 }, 'table') 1699 1700 matches('Invalid buffer', get_error('{ buf = -1 }, function() end')) 1701 matches('Invalid window', get_error('{ win = -1 }, function() end')) 1702 end) 1703 1704 it('no double-free when called from :filter browse oldfiles #31501', function() 1705 exec_lua([=[ 1706 vim.api.nvim_create_autocmd('BufEnter', { 1707 callback = function() 1708 vim._with({ lockmarks = true }, function() end) 1709 end, 1710 }) 1711 vim.cmd([[ 1712 let v:oldfiles = ['Xoldfile'] 1713 call nvim_input('1<CR>') 1714 noswapfile filter /Xoldfile/ browse oldfiles 1715 ]]) 1716 ]=]) 1717 n.assert_alive() 1718 eq('Xoldfile', fn.bufname('%')) 1719 end) 1720 end)