window_spec.lua (121449B)
1 local t = require('test.testutil') 2 local n = require('test.functional.testnvim')() 3 local Screen = require('test.functional.ui.screen') 4 5 local clear, curbuf, curbuf_contents, curwin, eq, neq, matches, ok, feed, insert, eval = 6 n.clear, 7 n.api.nvim_get_current_buf, 8 n.curbuf_contents, 9 n.api.nvim_get_current_win, 10 t.eq, 11 t.neq, 12 t.matches, 13 t.ok, 14 n.feed, 15 n.insert, 16 n.eval 17 local poke_eventloop = n.poke_eventloop 18 local exec = n.exec 19 local exec_lua = n.exec_lua 20 local fn = n.fn 21 local request = n.request 22 local NIL = vim.NIL 23 local api = n.api 24 local command = n.command 25 local pcall_err = t.pcall_err 26 local assert_alive = n.assert_alive 27 28 describe('API/win', function() 29 before_each(clear) 30 31 describe('get_buf', function() 32 it('works', function() 33 eq(curbuf(), api.nvim_win_get_buf(api.nvim_list_wins()[1])) 34 command('new') 35 api.nvim_set_current_win(api.nvim_list_wins()[2]) 36 eq(curbuf(), api.nvim_win_get_buf(api.nvim_list_wins()[2])) 37 neq( 38 api.nvim_win_get_buf(api.nvim_list_wins()[1]), 39 api.nvim_win_get_buf(api.nvim_list_wins()[2]) 40 ) 41 end) 42 end) 43 44 describe('set_buf', function() 45 it('works', function() 46 command('new') 47 local windows = api.nvim_list_wins() 48 neq(api.nvim_win_get_buf(windows[2]), api.nvim_win_get_buf(windows[1])) 49 api.nvim_win_set_buf(windows[2], api.nvim_win_get_buf(windows[1])) 50 eq(api.nvim_win_get_buf(windows[2]), api.nvim_win_get_buf(windows[1])) 51 end) 52 53 it('validates args', function() 54 eq('Invalid buffer id: 23', pcall_err(api.nvim_win_set_buf, api.nvim_get_current_win(), 23)) 55 eq('Invalid window id: 23', pcall_err(api.nvim_win_set_buf, 23, api.nvim_get_current_buf())) 56 end) 57 58 it('disallowed in cmdwin if win=cmdwin_{old_cur}win or buf=cmdwin_buf', function() 59 local new_buf = api.nvim_create_buf(true, true) 60 local old_win = api.nvim_get_current_win() 61 local new_win = api.nvim_open_win(new_buf, false, { 62 relative = 'editor', 63 row = 10, 64 col = 10, 65 width = 50, 66 height = 10, 67 }) 68 feed('q:') 69 eq( 70 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', 71 pcall_err(api.nvim_win_set_buf, 0, new_buf) 72 ) 73 eq( 74 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', 75 pcall_err(api.nvim_win_set_buf, old_win, new_buf) 76 ) 77 eq( 78 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', 79 pcall_err(api.nvim_win_set_buf, new_win, 0) 80 ) 81 matches( 82 'E11: Invalid in command%-line window; <CR> executes, CTRL%-C quits$', 83 pcall_err( 84 exec_lua, 85 [[ 86 local cmdwin_buf = vim.api.nvim_get_current_buf() 87 local new_win, new_buf = ... 88 vim._with({buf = new_buf}, function() 89 vim.api.nvim_win_set_buf(new_win, cmdwin_buf) 90 end) 91 ]], 92 new_win, 93 new_buf 94 ) 95 ) 96 matches( 97 'E11: Invalid in command%-line window; <CR> executes, CTRL%-C quits$', 98 pcall_err( 99 exec_lua, 100 [[ 101 local cmdwin_win = vim.api.nvim_get_current_win() 102 local new_win, new_buf = ... 103 vim._with({win = new_win}, function() 104 vim.api.nvim_win_set_buf(cmdwin_win, new_buf) 105 end) 106 ]], 107 new_win, 108 new_buf 109 ) 110 ) 111 112 local next_buf = api.nvim_create_buf(true, true) 113 api.nvim_win_set_buf(new_win, next_buf) 114 eq(next_buf, api.nvim_win_get_buf(new_win)) 115 end) 116 117 describe("with 'autochdir'", function() 118 local topdir 119 local otherbuf 120 local oldwin 121 local newwin 122 123 before_each(function() 124 command('set shellslash') 125 topdir = fn.getcwd() 126 t.mkdir(topdir .. '/Xacd') 127 t.mkdir(topdir .. '/Xacd/foo') 128 otherbuf = api.nvim_create_buf(false, true) 129 api.nvim_buf_set_name(otherbuf, topdir .. '/Xacd/baz.txt') 130 131 command('set autochdir') 132 command('edit Xacd/foo/bar.txt') 133 eq(topdir .. '/Xacd/foo', fn.getcwd()) 134 135 oldwin = api.nvim_get_current_win() 136 command('vsplit') 137 newwin = api.nvim_get_current_win() 138 end) 139 140 after_each(function() 141 n.rmdir(topdir .. '/Xacd') 142 end) 143 144 it('does not change cwd with non-current window', function() 145 api.nvim_win_set_buf(oldwin, otherbuf) 146 eq(topdir .. '/Xacd/foo', fn.getcwd()) 147 end) 148 149 it('changes cwd with current window', function() 150 api.nvim_win_set_buf(newwin, otherbuf) 151 eq(topdir .. '/Xacd', fn.getcwd()) 152 end) 153 end) 154 end) 155 156 describe('{get,set}_cursor', function() 157 it('works', function() 158 eq({ 1, 0 }, api.nvim_win_get_cursor(0)) 159 command('normal ityping\027o some text') 160 eq('typing\n some text', curbuf_contents()) 161 eq({ 2, 10 }, api.nvim_win_get_cursor(0)) 162 api.nvim_win_set_cursor(0, { 2, 6 }) 163 command('normal i dumb') 164 eq('typing\n some dumb text', curbuf_contents()) 165 end) 166 167 it('no memory leak when using invalid window ID with invalid pos', function() 168 eq('Invalid window id: 1', pcall_err(api.nvim_win_set_cursor, 1, { 'b\na' })) 169 end) 170 171 it('updates the screen, and also when the window is unfocused', function() 172 local screen = Screen.new(30, 9) 173 174 insert('prologue') 175 feed('100o<esc>') 176 insert('epilogue') 177 local win = curwin() 178 feed('gg') 179 180 local s1 = [[ 181 ^prologue | 182 |*8 183 ]] 184 screen:expect(s1) 185 -- cursor position is at beginning 186 eq({ 1, 0 }, api.nvim_win_get_cursor(win)) 187 188 -- move cursor to end 189 api.nvim_win_set_cursor(win, { 101, 0 }) 190 screen:expect([[ 191 |*7 192 ^epilogue | 193 | 194 ]]) 195 196 -- move cursor to the beginning again 197 api.nvim_win_set_cursor(win, { 1, 0 }) 198 screen:expect(s1) 199 200 -- move focus to new window 201 command('new') 202 neq(win, curwin()) 203 204 -- sanity check, cursor position is kept 205 eq({ 1, 0 }, api.nvim_win_get_cursor(win)) 206 local s2 = [[ 207 ^ | 208 {1:~ }|*2 209 {3:[No Name] }| 210 prologue | 211 |*2 212 {2:[No Name] [+] }| 213 | 214 ]] 215 screen:expect(s2) 216 217 -- move cursor to end 218 api.nvim_win_set_cursor(win, { 101, 0 }) 219 screen:expect([[ 220 ^ | 221 {1:~ }|*2 222 {3:[No Name] }| 223 |*2 224 epilogue | 225 {2:[No Name] [+] }| 226 | 227 ]]) 228 229 -- move cursor to the beginning again 230 api.nvim_win_set_cursor(win, { 1, 0 }) 231 screen:expect(s2) 232 233 -- curwin didn't change back 234 neq(win, curwin()) 235 end) 236 237 it('remembers what column it wants to be in', function() 238 insert('first line') 239 feed('o<esc>') 240 insert('second line') 241 242 feed('gg') 243 poke_eventloop() -- let nvim process the 'gg' command 244 245 -- cursor position is at beginning 246 local win = curwin() 247 eq({ 1, 0 }, api.nvim_win_get_cursor(win)) 248 249 -- move cursor to column 5 250 api.nvim_win_set_cursor(win, { 1, 5 }) 251 252 -- move down a line 253 feed('j') 254 poke_eventloop() -- let nvim process the 'j' command 255 256 -- cursor is still in column 5 257 eq({ 2, 5 }, api.nvim_win_get_cursor(win)) 258 end) 259 260 it('updates cursorline and statusline ruler in non-current window', function() 261 local screen = Screen.new(60, 8) 262 command('set ruler') 263 command('set cursorline') 264 insert([[ 265 aaa 266 bbb 267 ccc 268 ddd]]) 269 local oldwin = curwin() 270 command('vsplit') 271 screen:expect([[ 272 aaa │aaa | 273 bbb │bbb | 274 ccc │ccc | 275 {21:dd^d }│{21:ddd }| 276 {1:~ }│{1:~ }|*2 277 {3:< Name] [+] 4,3 All }{2:<Name] [+] 4,3 All}| 278 | 279 ]]) 280 api.nvim_win_set_cursor(oldwin, { 1, 0 }) 281 screen:expect([[ 282 aaa │{21:aaa }| 283 bbb │bbb | 284 ccc │ccc | 285 {21:dd^d }│ddd | 286 {1:~ }│{1:~ }|*2 287 {3:< Name] [+] 4,3 All }{2:<Name] [+] 1,1 All}| 288 | 289 ]]) 290 end) 291 292 it('updates cursorcolumn in non-current window', function() 293 local screen = Screen.new(60, 8) 294 command('set cursorcolumn') 295 insert([[ 296 aaa 297 bbb 298 ccc 299 ddd]]) 300 local oldwin = curwin() 301 command('vsplit') 302 screen:expect([[ 303 aa{21:a} │aa{21:a} | 304 bb{21:b} │bb{21:b} | 305 cc{21:c} │cc{21:c} | 306 dd^d │ddd | 307 {1:~ }│{1:~ }|*2 308 {3:[No Name] [+] }{2:[No Name] [+] }| 309 | 310 ]]) 311 api.nvim_win_set_cursor(oldwin, { 2, 0 }) 312 screen:expect([[ 313 aa{21:a} │{21:a}aa | 314 bb{21:b} │bbb | 315 cc{21:c} │{21:c}cc | 316 dd^d │{21:d}dd | 317 {1:~ }│{1:~ }|*2 318 {3:[No Name] [+] }{2:[No Name] [+] }| 319 | 320 ]]) 321 end) 322 end) 323 324 describe('{get,set}_height', function() 325 it('works', function() 326 command('vsplit') 327 eq( 328 api.nvim_win_get_height(api.nvim_list_wins()[2]), 329 api.nvim_win_get_height(api.nvim_list_wins()[1]) 330 ) 331 api.nvim_set_current_win(api.nvim_list_wins()[2]) 332 command('split') 333 eq( 334 api.nvim_win_get_height(api.nvim_list_wins()[2]), 335 math.floor(api.nvim_win_get_height(api.nvim_list_wins()[1]) / 2) 336 ) 337 api.nvim_win_set_height(api.nvim_list_wins()[2], 2) 338 eq(2, api.nvim_win_get_height(api.nvim_list_wins()[2])) 339 end) 340 341 it('failure modes', function() 342 command('split') 343 eq('Invalid window id: 999999', pcall_err(api.nvim_win_set_height, 999999, 10)) 344 eq( 345 'Wrong type for argument 2 when calling nvim_win_set_height, expecting Integer', 346 pcall_err(api.nvim_win_set_height, 0, 0.9) 347 ) 348 end) 349 350 it('correctly handles height=1', function() 351 command('split') 352 api.nvim_set_current_win(api.nvim_list_wins()[1]) 353 api.nvim_win_set_height(api.nvim_list_wins()[2], 1) 354 eq(1, api.nvim_win_get_height(api.nvim_list_wins()[2])) 355 end) 356 357 it('correctly handles height=1 with a winbar', function() 358 command('set winbar=foobar') 359 command('set winminheight=0') 360 command('split') 361 api.nvim_set_current_win(api.nvim_list_wins()[1]) 362 api.nvim_win_set_height(api.nvim_list_wins()[2], 1) 363 eq(1, api.nvim_win_get_height(api.nvim_list_wins()[2])) 364 end) 365 366 it('do not cause ml_get errors with foldmethod=expr #19989', function() 367 insert([[ 368 aaaaa 369 bbbbb 370 ccccc]]) 371 command('set foldmethod=expr') 372 exec([[ 373 new 374 let w = nvim_get_current_win() 375 wincmd w 376 call nvim_win_set_height(w, 5) 377 ]]) 378 feed('l') 379 eq('', api.nvim_get_vvar('errmsg')) 380 end) 381 end) 382 383 describe('{get,set}_width', function() 384 it('works', function() 385 command('split') 386 eq( 387 api.nvim_win_get_width(api.nvim_list_wins()[2]), 388 api.nvim_win_get_width(api.nvim_list_wins()[1]) 389 ) 390 api.nvim_set_current_win(api.nvim_list_wins()[2]) 391 command('vsplit') 392 eq( 393 api.nvim_win_get_width(api.nvim_list_wins()[2]), 394 math.floor(api.nvim_win_get_width(api.nvim_list_wins()[1]) / 2) 395 ) 396 api.nvim_win_set_width(api.nvim_list_wins()[2], 2) 397 eq(2, api.nvim_win_get_width(api.nvim_list_wins()[2])) 398 end) 399 400 it('failure modes', function() 401 command('vsplit') 402 eq('Invalid window id: 999999', pcall_err(api.nvim_win_set_width, 999999, 10)) 403 eq( 404 'Wrong type for argument 2 when calling nvim_win_set_width, expecting Integer', 405 pcall_err(api.nvim_win_set_width, 0, 0.9) 406 ) 407 end) 408 409 it('do not cause ml_get errors with foldmethod=expr #19989', function() 410 insert([[ 411 aaaaa 412 bbbbb 413 ccccc]]) 414 command('set foldmethod=expr') 415 exec([[ 416 vnew 417 let w = nvim_get_current_win() 418 wincmd w 419 call nvim_win_set_width(w, 5) 420 ]]) 421 feed('l') 422 eq('', api.nvim_get_vvar('errmsg')) 423 end) 424 end) 425 426 describe('{get,set,del}_var', function() 427 it('works', function() 428 api.nvim_win_set_var(0, 'lua', { 1, 2, { ['3'] = 1 } }) 429 eq({ 1, 2, { ['3'] = 1 } }, api.nvim_win_get_var(0, 'lua')) 430 eq({ 1, 2, { ['3'] = 1 } }, api.nvim_eval('w:lua')) 431 eq(1, fn.exists('w:lua')) 432 api.nvim_win_del_var(0, 'lua') 433 eq(0, fn.exists('w:lua')) 434 eq('Key not found: lua', pcall_err(api.nvim_win_del_var, 0, 'lua')) 435 api.nvim_win_set_var(0, 'lua', 1) 436 command('lockvar w:lua') 437 eq('Key is locked: lua', pcall_err(api.nvim_win_del_var, 0, 'lua')) 438 eq('Key is locked: lua', pcall_err(api.nvim_win_set_var, 0, 'lua', 1)) 439 end) 440 441 it('window_set_var returns the old value', function() 442 local val1 = { 1, 2, { ['3'] = 1 } } 443 local val2 = { 4, 7 } 444 eq(NIL, request('window_set_var', 0, 'lua', val1)) 445 eq(val1, request('window_set_var', 0, 'lua', val2)) 446 end) 447 448 it('window_del_var returns the old value', function() 449 local val1 = { 1, 2, { ['3'] = 1 } } 450 local val2 = { 4, 7 } 451 eq(NIL, request('window_set_var', 0, 'lua', val1)) 452 eq(val1, request('window_set_var', 0, 'lua', val2)) 453 eq(val2, request('window_del_var', 0, 'lua')) 454 end) 455 end) 456 457 describe('nvim_get_option_value, nvim_set_option_value', function() 458 it('works', function() 459 api.nvim_set_option_value('colorcolumn', '4,3', {}) 460 eq('4,3', api.nvim_get_option_value('colorcolumn', {})) 461 command('set modified hidden') 462 command('enew') -- edit new buffer, window option is preserved 463 eq('4,3', api.nvim_get_option_value('colorcolumn', {})) 464 465 -- global-local option 466 api.nvim_set_option_value('statusline', 'window-status', { win = 0 }) 467 eq('window-status', api.nvim_get_option_value('statusline', { win = 0 })) 468 eq( 469 "%<%f %{%nvim_eval_statusline('%h%w%m%r', {'maxwidth': 30}).width > 0 ? '%h%w%m%r ' : ''%}%=%{% &showcmdloc == 'statusline' ? '%-10.S ' : '' %}%{% exists('b:keymap_name') ? '<'..b:keymap_name..'> ' : '' %}%{% &ruler ? ( &rulerformat == '' ? '%-14.(%l,%c%V%) %P' : &rulerformat ) : '' %}", 470 api.nvim_get_option_value('statusline', { scope = 'global' }) 471 ) 472 command('set modified') 473 command('enew') -- global-local: not preserved in new buffer 474 -- confirm local value was not copied 475 eq( 476 "%<%f %{%nvim_eval_statusline('%h%w%m%r', {'maxwidth': 30}).width > 0 ? '%h%w%m%r ' : ''%}%=%{% &showcmdloc == 'statusline' ? '%-10.S ' : '' %}%{% exists('b:keymap_name') ? '<'..b:keymap_name..'> ' : '' %}%{% &ruler ? ( &rulerformat == '' ? '%-14.(%l,%c%V%) %P' : &rulerformat ) : '' %}", 477 api.nvim_get_option_value('statusline', { win = 0 }) 478 ) 479 eq('', eval('&l:statusline')) 480 end) 481 482 it('after switching windows #15390', function() 483 command('tabnew') 484 local tab1 = unpack(api.nvim_list_tabpages()) 485 local win1 = unpack(api.nvim_tabpage_list_wins(tab1)) 486 api.nvim_set_option_value('statusline', 'window-status', { win = win1 }) 487 command('split') 488 command('wincmd J') 489 command('wincmd j') 490 eq('window-status', api.nvim_get_option_value('statusline', { win = win1 })) 491 assert_alive() 492 end) 493 494 describe('after closing', function() 495 local buf, win0, win1, win2 496 497 before_each(function() 498 win0 = api.nvim_get_current_win() 499 command('new') 500 buf = api.nvim_get_current_buf() 501 win1 = api.nvim_get_current_win() 502 command('set numberwidth=10') 503 command('split') 504 win2 = api.nvim_get_current_win() 505 command('set numberwidth=15') 506 command('enew') 507 api.nvim_set_current_win(win1) 508 command('normal ix') 509 command('enew') 510 api.nvim_set_current_win(win0) 511 eq(4, api.nvim_get_option_value('numberwidth', {})) 512 end) 513 514 -- at this point buffer `buf` is current in no windows. Closing shouldn't affect its defaults 515 it('0 windows', function() 516 api.nvim_set_current_buf(buf) 517 eq(10, api.nvim_get_option_value('numberwidth', {})) 518 end) 519 520 it('1 window', function() 521 api.nvim_win_close(win1, false) 522 523 api.nvim_set_current_buf(buf) 524 eq(10, api.nvim_get_option_value('numberwidth', {})) 525 end) 526 527 it('2 windows', function() 528 api.nvim_win_close(win1, false) 529 api.nvim_win_close(win2, false) 530 531 api.nvim_set_current_buf(buf) 532 eq(10, api.nvim_get_option_value('numberwidth', {})) 533 end) 534 end) 535 536 it('returns values for unset local options', function() 537 eq(-1, api.nvim_get_option_value('scrolloff', { win = 0, scope = 'local' })) 538 end) 539 end) 540 541 describe('get_position', function() 542 it('works', function() 543 local height = api.nvim_win_get_height(api.nvim_list_wins()[1]) 544 local width = api.nvim_win_get_width(api.nvim_list_wins()[1]) 545 command('split') 546 command('vsplit') 547 eq({ 0, 0 }, api.nvim_win_get_position(api.nvim_list_wins()[1])) 548 local vsplit_pos = math.floor(width / 2) 549 local split_pos = math.floor(height / 2) 550 local win2row, win2col = unpack(api.nvim_win_get_position(api.nvim_list_wins()[2])) 551 local win3row, win3col = unpack(api.nvim_win_get_position(api.nvim_list_wins()[3])) 552 eq(0, win2row) 553 eq(0, win3col) 554 ok(vsplit_pos - 1 <= win2col and win2col <= vsplit_pos + 1) 555 ok(split_pos - 1 <= win3row and win3row <= split_pos + 1) 556 end) 557 end) 558 559 describe('get_position', function() 560 it('works', function() 561 command('tabnew') 562 command('vsplit') 563 eq(api.nvim_win_get_tabpage(api.nvim_list_wins()[1]), api.nvim_list_tabpages()[1]) 564 eq(api.nvim_win_get_tabpage(api.nvim_list_wins()[2]), api.nvim_list_tabpages()[2]) 565 eq(api.nvim_win_get_tabpage(api.nvim_list_wins()[3]), api.nvim_list_tabpages()[2]) 566 end) 567 end) 568 569 describe('get_number', function() 570 it('works', function() 571 local wins = api.nvim_list_wins() 572 eq(1, api.nvim_win_get_number(wins[1])) 573 574 command('split') 575 local win1, win2 = unpack(api.nvim_list_wins()) 576 eq(1, api.nvim_win_get_number(win1)) 577 eq(2, api.nvim_win_get_number(win2)) 578 579 command('wincmd J') 580 eq(2, api.nvim_win_get_number(win1)) 581 eq(1, api.nvim_win_get_number(win2)) 582 583 command('tabnew') 584 local win3 = api.nvim_list_wins()[3] 585 -- First tab page 586 eq(2, api.nvim_win_get_number(win1)) 587 eq(1, api.nvim_win_get_number(win2)) 588 -- Second tab page 589 eq(1, api.nvim_win_get_number(win3)) 590 end) 591 end) 592 593 describe('is_valid', function() 594 it('works', function() 595 command('split') 596 local win = api.nvim_list_wins()[2] 597 api.nvim_set_current_win(win) 598 ok(api.nvim_win_is_valid(win)) 599 command('close') 600 ok(not api.nvim_win_is_valid(win)) 601 end) 602 end) 603 604 describe('close', function() 605 it('can close current window', function() 606 local oldwin = api.nvim_get_current_win() 607 command('split') 608 local newwin = api.nvim_get_current_win() 609 api.nvim_win_close(newwin, false) 610 eq({ oldwin }, api.nvim_list_wins()) 611 end) 612 613 it('can close noncurrent window', function() 614 local oldwin = api.nvim_get_current_win() 615 command('split') 616 local newwin = api.nvim_get_current_win() 617 api.nvim_win_close(oldwin, false) 618 eq({ newwin }, api.nvim_list_wins()) 619 end) 620 621 it("handles changed buffer when 'hidden' is unset", function() 622 command('set nohidden') 623 local oldwin = api.nvim_get_current_win() 624 insert('text') 625 command('new') 626 local newwin = api.nvim_get_current_win() 627 eq( 628 'Vim:E37: No write since last change (add ! to override)', 629 pcall_err(api.nvim_win_close, oldwin, false) 630 ) 631 eq({ newwin, oldwin }, api.nvim_list_wins()) 632 end) 633 634 it('handles changed buffer with force', function() 635 local oldwin = api.nvim_get_current_win() 636 insert('text') 637 command('new') 638 local newwin = api.nvim_get_current_win() 639 api.nvim_win_close(oldwin, true) 640 eq({ newwin }, api.nvim_list_wins()) 641 end) 642 643 it('in cmdline-window #9767', function() 644 command('split') 645 eq(2, #api.nvim_list_wins()) 646 local oldbuf = api.nvim_get_current_buf() 647 local oldwin = api.nvim_get_current_win() 648 local otherwin = api.nvim_open_win(0, false, { 649 relative = 'editor', 650 row = 10, 651 col = 10, 652 width = 10, 653 height = 10, 654 }) 655 -- Open cmdline-window. 656 feed('q:') 657 eq(4, #api.nvim_list_wins()) 658 eq(':', fn.getcmdwintype()) 659 -- Not allowed to close previous window from cmdline-window. 660 eq( 661 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', 662 pcall_err(api.nvim_win_close, oldwin, true) 663 ) 664 -- Closing other windows is fine. 665 api.nvim_win_close(otherwin, true) 666 eq(false, api.nvim_win_is_valid(otherwin)) 667 -- Close cmdline-window. 668 api.nvim_win_close(0, true) 669 eq(2, #api.nvim_list_wins()) 670 eq('', fn.getcmdwintype()) 671 672 -- Closing curwin in context of a different window shouldn't close cmdwin. 673 otherwin = api.nvim_open_win(0, false, { 674 relative = 'editor', 675 row = 10, 676 col = 10, 677 width = 10, 678 height = 10, 679 }) 680 feed('q:') 681 exec_lua( 682 [[ 683 vim._with({win = ...}, function() 684 vim.api.nvim_win_close(0, true) 685 end) 686 ]], 687 otherwin 688 ) 689 eq(false, api.nvim_win_is_valid(otherwin)) 690 eq(':', fn.getcmdwintype()) 691 -- Closing cmdwin in context of a non-previous window is still OK. 692 otherwin = api.nvim_open_win(oldbuf, false, { 693 relative = 'editor', 694 row = 10, 695 col = 10, 696 width = 10, 697 height = 10, 698 }) 699 exec_lua( 700 [[ 701 local otherwin, cmdwin = ... 702 vim._with({win = otherwin}, function() 703 vim.api.nvim_win_close(cmdwin, true) 704 end) 705 ]], 706 otherwin, 707 api.nvim_get_current_win() 708 ) 709 eq('', fn.getcmdwintype()) 710 eq(true, api.nvim_win_is_valid(otherwin)) 711 end) 712 713 it('closing current (float) window of another tabpage #15313', function() 714 command('tabedit') 715 command('botright split') 716 local prevwin = curwin() 717 eq(2, eval('tabpagenr()')) 718 local win = api.nvim_open_win(0, true, { 719 relative = 'editor', 720 row = 10, 721 col = 10, 722 width = 50, 723 height = 10, 724 }) 725 local tab = eval('tabpagenr()') 726 command('tabprevious') 727 eq(1, eval('tabpagenr()')) 728 api.nvim_win_close(win, false) 729 730 eq(prevwin, api.nvim_tabpage_get_win(tab)) 731 assert_alive() 732 end) 733 734 it('closing a float does not enter unfocusable or hidden prevwin', function() 735 local firstwin = api.nvim_get_current_win() 736 local wins = {} ---@type integer[] 737 for _ = 1, 4 do 738 wins[#wins + 1] = api.nvim_open_win(0, true, { 739 relative = 'editor', 740 row = 10, 741 col = 10, 742 width = 50, 743 height = 10, 744 }) 745 end 746 api.nvim_win_set_config(wins[3], { hide = true }) 747 api.nvim_win_close(0, false) 748 eq(firstwin, api.nvim_get_current_win()) 749 api.nvim_set_current_win(wins[2]) 750 api.nvim_set_current_win(wins[3]) 751 api.nvim_win_set_config(wins[2], { focusable = false }) 752 api.nvim_win_close(0, false) 753 eq(firstwin, api.nvim_get_current_win()) 754 api.nvim_set_current_win(wins[1]) 755 api.nvim_set_current_win(wins[2]) 756 api.nvim_win_close(0, false) 757 eq(wins[1], api.nvim_get_current_win()) 758 end) 759 end) 760 761 describe('hide', function() 762 it('can hide current window', function() 763 local oldwin = api.nvim_get_current_win() 764 command('split') 765 local newwin = api.nvim_get_current_win() 766 api.nvim_win_hide(newwin) 767 eq({ oldwin }, api.nvim_list_wins()) 768 end) 769 it('can hide noncurrent window', function() 770 local oldwin = api.nvim_get_current_win() 771 command('split') 772 local newwin = api.nvim_get_current_win() 773 api.nvim_win_hide(oldwin) 774 eq({ newwin }, api.nvim_list_wins()) 775 end) 776 it('does not close the buffer', function() 777 local oldwin = api.nvim_get_current_win() 778 local oldbuf = api.nvim_get_current_buf() 779 local buf = api.nvim_create_buf(true, false) 780 local newwin = api.nvim_open_win(buf, true, { 781 relative = 'win', 782 row = 3, 783 col = 3, 784 width = 12, 785 height = 3, 786 }) 787 api.nvim_win_hide(newwin) 788 eq({ oldwin }, api.nvim_list_wins()) 789 eq({ oldbuf, buf }, api.nvim_list_bufs()) 790 end) 791 it('deletes the buffer when bufhidden=wipe', function() 792 local oldwin = api.nvim_get_current_win() 793 local oldbuf = api.nvim_get_current_buf() 794 local buf = api.nvim_create_buf(true, false) 795 local newwin = api.nvim_open_win(buf, true, { 796 relative = 'win', 797 row = 3, 798 col = 3, 799 width = 12, 800 height = 3, 801 }) 802 api.nvim_set_option_value('bufhidden', 'wipe', { buf = buf }) 803 api.nvim_win_hide(newwin) 804 eq({ oldwin }, api.nvim_list_wins()) 805 eq({ oldbuf }, api.nvim_list_bufs()) 806 end) 807 it('in the cmdwin', function() 808 feed('q:') 809 -- Can close the cmdwin. 810 api.nvim_win_hide(0) 811 eq('', fn.getcmdwintype()) 812 813 local old_buf = api.nvim_get_current_buf() 814 local old_win = api.nvim_get_current_win() 815 local other_win = api.nvim_open_win(0, false, { 816 relative = 'win', 817 row = 3, 818 col = 3, 819 width = 12, 820 height = 3, 821 }) 822 feed('q:') 823 -- Cannot close the previous window. 824 eq( 825 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', 826 pcall_err(api.nvim_win_hide, old_win) 827 ) 828 -- Can close other windows. 829 api.nvim_win_hide(other_win) 830 eq(false, api.nvim_win_is_valid(other_win)) 831 832 -- Closing curwin in context of a different window shouldn't close cmdwin. 833 other_win = api.nvim_open_win(old_buf, false, { 834 relative = 'editor', 835 row = 10, 836 col = 10, 837 width = 10, 838 height = 10, 839 }) 840 exec_lua( 841 [[ 842 vim._with({win = ...}, function() 843 vim.api.nvim_win_hide(0) 844 end) 845 ]], 846 other_win 847 ) 848 eq(false, api.nvim_win_is_valid(other_win)) 849 eq(':', fn.getcmdwintype()) 850 -- Closing cmdwin in context of a non-previous window is still OK. 851 other_win = api.nvim_open_win(old_buf, false, { 852 relative = 'editor', 853 row = 10, 854 col = 10, 855 width = 10, 856 height = 10, 857 }) 858 exec_lua( 859 [[ 860 local otherwin, cmdwin = ... 861 vim._with({win = otherwin}, function() 862 vim.api.nvim_win_hide(cmdwin) 863 end) 864 ]], 865 other_win, 866 api.nvim_get_current_win() 867 ) 868 eq('', fn.getcmdwintype()) 869 eq(true, api.nvim_win_is_valid(other_win)) 870 end) 871 end) 872 873 describe('text_height', function() 874 local screen, ns, X 875 before_each(function() 876 screen = Screen.new(45, 22) 877 ns = api.nvim_create_namespace('') 878 X = api.nvim_get_vvar('maxcol') 879 end) 880 881 it('validation', function() 882 insert([[ 883 aaa 884 bbb 885 ccc 886 ddd 887 eee]]) 888 eq('Invalid window id: 23', pcall_err(api.nvim_win_text_height, 23, {})) 889 eq('Line index out of bounds', pcall_err(api.nvim_win_text_height, 0, { start_row = 5 })) 890 eq('Line index out of bounds', pcall_err(api.nvim_win_text_height, 0, { start_row = -6 })) 891 eq('Line index out of bounds', pcall_err(api.nvim_win_text_height, 0, { end_row = 5 })) 892 eq('Line index out of bounds', pcall_err(api.nvim_win_text_height, 0, { end_row = -6 })) 893 eq( 894 "'start_row' is higher than 'end_row'", 895 pcall_err(api.nvim_win_text_height, 0, { start_row = 3, end_row = 1 }) 896 ) 897 eq( 898 "'start_vcol' specified without 'start_row'", 899 pcall_err(api.nvim_win_text_height, 0, { end_row = 2, start_vcol = 0 }) 900 ) 901 eq( 902 "'end_vcol' specified without 'end_row'", 903 pcall_err(api.nvim_win_text_height, 0, { start_row = 2, end_vcol = 0 }) 904 ) 905 eq( 906 "Invalid 'start_vcol': out of range", 907 pcall_err(api.nvim_win_text_height, 0, { start_row = 2, start_vcol = -1 }) 908 ) 909 eq( 910 "Invalid 'start_vcol': out of range", 911 pcall_err(api.nvim_win_text_height, 0, { start_row = 2, start_vcol = X + 1 }) 912 ) 913 eq( 914 "Invalid 'end_vcol': out of range", 915 pcall_err(api.nvim_win_text_height, 0, { end_row = 2, end_vcol = -1 }) 916 ) 917 eq( 918 "Invalid 'end_vcol': out of range", 919 pcall_err(api.nvim_win_text_height, 0, { end_row = 2, end_vcol = X + 1 }) 920 ) 921 eq( 922 "Invalid 'max_height': out of range", 923 pcall_err(api.nvim_win_text_height, 0, { max_height = 0 }) 924 ) 925 eq( 926 "'start_vcol' is higher than 'end_vcol'", 927 pcall_err( 928 api.nvim_win_text_height, 929 0, 930 { start_row = 2, end_row = 2, start_vcol = 10, end_vcol = 5 } 931 ) 932 ) 933 end) 934 935 it('with two diff windows', function() 936 exec([[ 937 set diffopt+=context:2 number 938 let expr = 'printf("%08d", v:val) .. repeat("!", v:val)' 939 call setline(1, map(range(1, 20) + range(25, 45), expr)) 940 vnew 941 call setline(1, map(range(3, 20) + range(28, 50), expr)) 942 windo diffthis 943 ]]) 944 feed('24gg') 945 screen:expect([[ 946 {7: }{8: }{23:----------------}│{7: }{8: 1 }{22:00000001! }| 947 {7: }{8: }{23:----------------}│{7: }{8: 2 }{22:00000002!! }| 948 {7: }{8: 1 }00000003!!! │{7: }{8: 3 }00000003!!! | 949 {7: }{8: 2 }00000004!!!! │{7: }{8: 4 }00000004!!!! | 950 {7:+ }{8: 3 }{13:+-- 14 lines: 00}│{7:+ }{8: 5 }{13:+-- 14 lines: 00}| 951 {7: }{8: 17 }00000019!!!!!!!!│{7: }{8: 19 }00000019!!!!!!!!| 952 {7: }{8: 18 }00000020!!!!!!!!│{7: }{8: 20 }00000020!!!!!!!!| 953 {7: }{8: }{23:----------------}│{7: }{8: 21 }{22:00000025!!!!!!!!}| 954 {7: }{8: }{23:----------------}│{7: }{8: 22 }{22:00000026!!!!!!!!}| 955 {7: }{8: }{23:----------------}│{7: }{8: 23 }{22:00000027!!!!!!!!}| 956 {7: }{8: 19 }00000028!!!!!!!!│{7: }{8: 24 }^00000028!!!!!!!!| 957 {7: }{8: 20 }00000029!!!!!!!!│{7: }{8: 25 }00000029!!!!!!!!| 958 {7:+ }{8: 21 }{13:+-- 14 lines: 00}│{7:+ }{8: 26 }{13:+-- 14 lines: 00}| 959 {7: }{8: 35 }00000044!!!!!!!!│{7: }{8: 40 }00000044!!!!!!!!| 960 {7: }{8: 36 }00000045!!!!!!!!│{7: }{8: 41 }00000045!!!!!!!!| 961 {7: }{8: 37 }{22:00000046!!!!!!!!}│{7: }{8: }{23:----------------}| 962 {7: }{8: 38 }{22:00000047!!!!!!!!}│{7: }{8: }{23:----------------}| 963 {7: }{8: 39 }{22:00000048!!!!!!!!}│{7: }{8: }{23:----------------}| 964 {7: }{8: 40 }{22:00000049!!!!!!!!}│{7: }{8: }{23:----------------}| 965 {7: }{8: 41 }{22:00000050!!!!!!!!}│{7: }{8: }{23:----------------}| 966 {2:[No Name] [+] }{3:[No Name] [+] }| 967 | 968 ]]) 969 screen:try_resize(45, 3) 970 screen:expect([[ 971 {7: }{8: 19 }00000028!!!!!!!!│{7: }{8: 24 }^00000028!!!!!!!!| 972 {2:[No Name] [+] }{3:[No Name] [+] }| 973 | 974 ]]) 975 eq({ all = 20, fill = 5, end_row = 40, end_vcol = 53 }, api.nvim_win_text_height(1000, {})) 976 eq({ all = 20, fill = 5, end_row = 40, end_vcol = 58 }, api.nvim_win_text_height(1001, {})) 977 eq( 978 { all = 20, fill = 5, end_row = 40, end_vcol = 53 }, 979 api.nvim_win_text_height(1000, { start_row = 0 }) 980 ) 981 eq( 982 { all = 20, fill = 5, end_row = 40, end_vcol = 58 }, 983 api.nvim_win_text_height(1001, { start_row = 0 }) 984 ) 985 eq( 986 { all = 15, fill = 0, end_row = 40, end_vcol = 53 }, 987 api.nvim_win_text_height(1000, { end_row = -1 }) 988 ) 989 eq( 990 { all = 15, fill = 0, end_row = 40, end_vcol = 53 }, 991 api.nvim_win_text_height(1000, { end_row = 40 }) 992 ) 993 eq( 994 { all = 20, fill = 5, end_row = 40, end_vcol = 58 }, 995 api.nvim_win_text_height(1001, { end_row = -1 }) 996 ) 997 eq( 998 { all = 20, fill = 5, end_row = 40, end_vcol = 58 }, 999 api.nvim_win_text_height(1001, { end_row = 40 }) 1000 ) 1001 eq( 1002 { all = 10, fill = 5, end_row = 40, end_vcol = 53 }, 1003 api.nvim_win_text_height(1000, { start_row = 23 }) 1004 ) 1005 eq( 1006 { all = 13, fill = 3, end_row = 40, end_vcol = 58 }, 1007 api.nvim_win_text_height(1001, { start_row = 18 }) 1008 ) 1009 eq( 1010 { all = 11, fill = 0, end_row = 23, end_vcol = 36 }, 1011 api.nvim_win_text_height(1000, { end_row = 23 }) 1012 ) 1013 eq( 1014 { all = 11, fill = 5, end_row = 18, end_vcol = 36 }, 1015 api.nvim_win_text_height(1001, { end_row = 18 }) 1016 ) 1017 eq( 1018 { all = 11, fill = 0, end_row = 39, end_vcol = 52 }, 1019 api.nvim_win_text_height(1000, { start_row = 3, end_row = 39 }) 1020 ) 1021 eq( 1022 { all = 11, fill = 3, end_row = 34, end_vcol = 52 }, 1023 api.nvim_win_text_height(1001, { start_row = 1, end_row = 34 }) 1024 ) 1025 eq( 1026 { all = 9, fill = 0, end_row = 25, end_vcol = 0 }, 1027 api.nvim_win_text_height(1000, { start_row = 4, end_row = 38 }) 1028 ) 1029 eq( 1030 { all = 9, fill = 3, end_row = 20, end_vcol = 0 }, 1031 api.nvim_win_text_height(1001, { start_row = 2, end_row = 33 }) 1032 ) 1033 eq( 1034 { all = 9, fill = 0, end_row = 25, end_vcol = 0 }, 1035 api.nvim_win_text_height(1000, { start_row = 5, end_row = 37 }) 1036 ) 1037 eq( 1038 { all = 9, fill = 3, end_row = 20, end_vcol = 0 }, 1039 api.nvim_win_text_height(1001, { start_row = 3, end_row = 32 }) 1040 ) 1041 eq( 1042 { all = 9, fill = 0, end_row = 25, end_vcol = 0 }, 1043 api.nvim_win_text_height(1000, { start_row = 17, end_row = 25 }) 1044 ) 1045 eq( 1046 { all = 9, fill = 3, end_row = 20, end_vcol = 0 }, 1047 api.nvim_win_text_height(1001, { start_row = 15, end_row = 20 }) 1048 ) 1049 eq( 1050 { all = 7, fill = 0, end_row = 24, end_vcol = 37 }, 1051 api.nvim_win_text_height(1000, { start_row = 18, end_row = 24 }) 1052 ) 1053 eq( 1054 { all = 7, fill = 3, end_row = 19, end_vcol = 37 }, 1055 api.nvim_win_text_height(1001, { start_row = 16, end_row = 19 }) 1056 ) 1057 eq( 1058 { all = 6, fill = 5, end_row = 40, end_vcol = 53 }, 1059 api.nvim_win_text_height(1000, { start_row = -1 }) 1060 ) 1061 eq( 1062 { all = 5, fill = 5, end_row = 40, end_vcol = 53 }, 1063 api.nvim_win_text_height(1000, { start_row = -1, start_vcol = X }) 1064 ) 1065 eq( 1066 { all = 0, fill = 0, end_row = 40, end_vcol = 53 }, 1067 api.nvim_win_text_height(1000, { start_row = -1, start_vcol = X, end_row = -1 }) 1068 ) 1069 eq( 1070 { all = 0, fill = 0, end_row = 40, end_vcol = 53 }, 1071 api.nvim_win_text_height( 1072 1000, 1073 { start_row = -1, start_vcol = X, end_row = -1, end_vcol = X } 1074 ) 1075 ) 1076 eq( 1077 { all = 1, fill = 0, end_row = 40, end_vcol = 53 }, 1078 api.nvim_win_text_height( 1079 1000, 1080 { start_row = -1, start_vcol = 0, end_row = -1, end_vcol = X } 1081 ) 1082 ) 1083 eq( 1084 { all = 3, fill = 2, end_row = 0, end_vcol = 11 }, 1085 api.nvim_win_text_height(1001, { end_row = 0 }) 1086 ) 1087 eq( 1088 { all = 2, fill = 2, end_row = 0, end_vcol = 0 }, 1089 api.nvim_win_text_height(1001, { end_row = 0, end_vcol = 0 }) 1090 ) 1091 eq( 1092 { all = 2, fill = 2, end_row = 0, end_vcol = 0 }, 1093 api.nvim_win_text_height(1001, { start_row = 0, end_row = 0, end_vcol = 0 }) 1094 ) 1095 eq( 1096 { all = 0, fill = 0, end_row = 0, end_vcol = 0 }, 1097 api.nvim_win_text_height(1001, { start_row = 0, start_vcol = 0, end_row = 0, end_vcol = 0 }) 1098 ) 1099 eq( 1100 { all = 1, fill = 0, end_row = 0, end_vcol = 11 }, 1101 api.nvim_win_text_height(1001, { start_row = 0, start_vcol = 0, end_row = 0, end_vcol = X }) 1102 ) 1103 eq( 1104 { all = 11, fill = 5, end_row = 18, end_vcol = 36 }, 1105 api.nvim_win_text_height(1001, { end_row = 18 }) 1106 ) 1107 eq( 1108 { all = 9, fill = 3, end_row = 18, end_vcol = 36 }, 1109 api.nvim_win_text_height(1001, { start_row = 0, start_vcol = 0, end_row = 18 }) 1110 ) 1111 eq( 1112 { all = 10, fill = 5, end_row = 18, end_vcol = 0 }, 1113 api.nvim_win_text_height(1001, { end_row = 18, end_vcol = 0 }) 1114 ) 1115 eq( 1116 { all = 8, fill = 3, end_row = 18, end_vcol = 0 }, 1117 api.nvim_win_text_height( 1118 1001, 1119 { start_row = 0, start_vcol = 0, end_row = 18, end_vcol = 0 } 1120 ) 1121 ) 1122 end) 1123 1124 it('with wrapped lines', function() 1125 exec([[ 1126 set number cpoptions+=n 1127 call setline(1, repeat([repeat('foobar-', 36)], 3)) 1128 ]]) 1129 api.nvim_buf_set_extmark( 1130 0, 1131 ns, 1132 1, 1133 100, 1134 { virt_text = { { ('?'):rep(15), 'Search' } }, virt_text_pos = 'inline' } 1135 ) 1136 api.nvim_buf_set_extmark( 1137 0, 1138 ns, 1139 2, 1140 200, 1141 { virt_text = { { ('!'):rep(75), 'Search' } }, virt_text_pos = 'inline' } 1142 ) 1143 screen:expect([[ 1144 {8: 1 }^foobar-foobar-foobar-foobar-foobar-foobar| 1145 -foobar-foobar-foobar-foobar-foobar-foobar-fo| 1146 obar-foobar-foobar-foobar-foobar-foobar-fooba| 1147 r-foobar-foobar-foobar-foobar-foobar-foobar-f| 1148 oobar-foobar-foobar-foobar-foobar-foobar-foob| 1149 ar-foobar-foobar-foobar-foobar- | 1150 {8: 2 }foobar-foobar-foobar-foobar-foobar-foobar| 1151 -foobar-foobar-foobar-foobar-foobar-foobar-fo| 1152 obar-foobar-fo{10:???????????????}obar-foobar-foob| 1153 ar-foobar-foobar-foobar-foobar-foobar-foobar-| 1154 foobar-foobar-foobar-foobar-foobar-foobar-foo| 1155 bar-foobar-foobar-foobar-foobar-foobar-foobar| 1156 - | 1157 {8: 3 }foobar-foobar-foobar-foobar-foobar-foobar| 1158 -foobar-foobar-foobar-foobar-foobar-foobar-fo| 1159 obar-foobar-foobar-foobar-foobar-foobar-fooba| 1160 r-foobar-foobar-foobar-foobar-foobar-foobar-f| 1161 oobar-foobar-foobar-foob{10:!!!!!!!!!!!!!!!!!!!!!}| 1162 {10:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!}| 1163 {10:!!!!!!!!!}ar-foobar-foobar-foobar-foobar-fooba| 1164 r-foobar-foobar- | 1165 | 1166 ]]) 1167 screen:try_resize(45, 2) 1168 screen:expect([[ 1169 {8: 1 }^foobar-foobar-foobar-foobar-foobar-foobar| 1170 | 1171 ]]) 1172 eq({ all = 21, fill = 0, end_row = 2, end_vcol = 327 }, api.nvim_win_text_height(0, {})) 1173 eq( 1174 { all = 6, fill = 0, end_row = 0, end_vcol = 252 }, 1175 api.nvim_win_text_height(0, { start_row = 0, end_row = 0 }) 1176 ) 1177 eq( 1178 { all = 7, fill = 0, end_row = 1, end_vcol = 267 }, 1179 api.nvim_win_text_height(0, { start_row = 1, end_row = 1 }) 1180 ) 1181 eq( 1182 { all = 8, fill = 0, end_row = 2, end_vcol = 327 }, 1183 api.nvim_win_text_height(0, { start_row = 2, end_row = 2 }) 1184 ) 1185 eq( 1186 { all = 0, fill = 0, end_row = 1, end_vcol = 0 }, 1187 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 0 }) 1188 ) 1189 eq( 1190 { all = 1, fill = 0, end_row = 1, end_vcol = 41 }, 1191 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 41 }) 1192 ) 1193 eq( 1194 { all = 2, fill = 0, end_row = 1, end_vcol = 42 }, 1195 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 42 }) 1196 ) 1197 eq( 1198 { all = 2, fill = 0, end_row = 1, end_vcol = 86 }, 1199 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 86 }) 1200 ) 1201 eq( 1202 { all = 3, fill = 0, end_row = 1, end_vcol = 87 }, 1203 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 87 }) 1204 ) 1205 eq( 1206 { all = 6, fill = 0, end_row = 1, end_vcol = 266 }, 1207 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 266 }) 1208 ) 1209 eq( 1210 { all = 7, fill = 0, end_row = 1, end_vcol = 267 }, 1211 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 267 }) 1212 ) 1213 eq( 1214 { all = 7, fill = 0, end_row = 1, end_vcol = 267 }, 1215 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 311 }) 1216 ) 1217 eq( 1218 { all = 7, fill = 0, end_row = 1, end_vcol = 267 }, 1219 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 312 }) 1220 ) 1221 eq( 1222 { all = 7, fill = 0, end_row = 1, end_vcol = 267 }, 1223 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = X }) 1224 ) 1225 eq( 1226 { all = 7, fill = 0, end_row = 1, end_vcol = 267 }, 1227 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 40, end_row = 1, end_vcol = X }) 1228 ) 1229 eq( 1230 { all = 6, fill = 0, end_row = 1, end_vcol = 267 }, 1231 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 41, end_row = 1, end_vcol = X }) 1232 ) 1233 eq( 1234 { all = 6, fill = 0, end_row = 1, end_vcol = 267 }, 1235 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 85, end_row = 1, end_vcol = X }) 1236 ) 1237 eq( 1238 { all = 5, fill = 0, end_row = 1, end_vcol = 267 }, 1239 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 86, end_row = 1, end_vcol = X }) 1240 ) 1241 eq( 1242 { all = 2, fill = 0, end_row = 1, end_vcol = 267 }, 1243 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 265, end_row = 1, end_vcol = X }) 1244 ) 1245 eq( 1246 { all = 1, fill = 0, end_row = 1, end_vcol = 267 }, 1247 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 266, end_row = 1, end_vcol = X }) 1248 ) 1249 eq( 1250 { all = 1, fill = 0, end_row = 1, end_vcol = 267 }, 1251 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 310, end_row = 1, end_vcol = X }) 1252 ) 1253 eq( 1254 { all = 0, fill = 0, end_row = 1, end_vcol = 267 }, 1255 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 311, end_row = 1, end_vcol = X }) 1256 ) 1257 eq( 1258 { all = 1, fill = 0, end_row = 1, end_vcol = 131 }, 1259 api.nvim_win_text_height(0, { start_row = 1, start_vcol = 86, end_row = 1, end_vcol = 131 }) 1260 ) 1261 eq( 1262 { all = 1, fill = 0, end_row = 1, end_vcol = 266 }, 1263 api.nvim_win_text_height( 1264 0, 1265 { start_row = 1, start_vcol = 221, end_row = 1, end_vcol = 266 } 1266 ) 1267 ) 1268 eq( 1269 { all = 18, fill = 0, end_row = 2, end_vcol = 327 }, 1270 api.nvim_win_text_height(0, { start_row = 0, start_vcol = 131 }) 1271 ) 1272 eq( 1273 { all = 19, fill = 0, end_row = 2, end_vcol = 327 }, 1274 api.nvim_win_text_height(0, { start_row = 0, start_vcol = 130 }) 1275 ) 1276 eq( 1277 { all = 20, fill = 0, end_row = 2, end_vcol = 311 }, 1278 api.nvim_win_text_height(0, { end_row = 2, end_vcol = 311 }) 1279 ) 1280 eq( 1281 { all = 21, fill = 0, end_row = 2, end_vcol = 312 }, 1282 api.nvim_win_text_height(0, { end_row = 2, end_vcol = 312 }) 1283 ) 1284 eq( 1285 { all = 17, fill = 0, end_row = 2, end_vcol = 311 }, 1286 api.nvim_win_text_height( 1287 0, 1288 { start_row = 0, start_vcol = 131, end_row = 2, end_vcol = 311 } 1289 ) 1290 ) 1291 eq( 1292 { all = 19, fill = 0, end_row = 2, end_vcol = 312 }, 1293 api.nvim_win_text_height( 1294 0, 1295 { start_row = 0, start_vcol = 130, end_row = 2, end_vcol = 312 } 1296 ) 1297 ) 1298 eq( 1299 { all = 16, fill = 0, end_row = 2, end_vcol = 327 }, 1300 api.nvim_win_text_height(0, { start_row = 0, start_vcol = 221 }) 1301 ) 1302 eq( 1303 { all = 17, fill = 0, end_row = 2, end_vcol = 327 }, 1304 api.nvim_win_text_height(0, { start_row = 0, start_vcol = 220 }) 1305 ) 1306 eq( 1307 { all = 14, fill = 0, end_row = 2, end_vcol = 41 }, 1308 api.nvim_win_text_height(0, { end_row = 2, end_vcol = 41 }) 1309 ) 1310 eq( 1311 { all = 15, fill = 0, end_row = 2, end_vcol = 42 }, 1312 api.nvim_win_text_height(0, { end_row = 2, end_vcol = 42 }) 1313 ) 1314 eq( 1315 { all = 9, fill = 0, end_row = 2, end_vcol = 41 }, 1316 api.nvim_win_text_height(0, { start_row = 0, start_vcol = 221, end_row = 2, end_vcol = 41 }) 1317 ) 1318 eq( 1319 { all = 11, fill = 0, end_row = 2, end_vcol = 42 }, 1320 api.nvim_win_text_height(0, { start_row = 0, start_vcol = 220, end_row = 2, end_vcol = 42 }) 1321 ) 1322 exec('call setline(1, "foo")') 1323 eq( 1324 { all = 1, fill = 0, end_row = 0, end_vcol = 3 }, 1325 api.nvim_win_text_height(0, { max_height = 1 }) 1326 ) 1327 eq( 1328 { all = 8, fill = 0, end_row = 1, end_vcol = 41 }, 1329 api.nvim_win_text_height(0, { max_height = 2 }) 1330 ) 1331 eq( 1332 { all = 2, fill = 0, end_row = 1, end_vcol = 1 }, 1333 api.nvim_win_text_height(0, { max_height = 2, end_row = 1, end_vcol = 1 }) 1334 ) 1335 eq( 1336 { all = 8, fill = 0, end_row = 1, end_vcol = 41 }, 1337 api.nvim_win_text_height(0, { max_height = 2, end_row = 2, end_vcol = 1 }) 1338 ) 1339 end) 1340 1341 it('with virtual lines around a fold', function() 1342 screen:try_resize(45, 10) 1343 exec([[ 1344 call setline(1, range(1, 8)) 1345 3,6fold 1346 ]]) 1347 api.nvim_buf_set_extmark( 1348 0, 1349 ns, 1350 1, 1351 0, 1352 { virt_lines = { { { 'VIRT LINE 1' } }, { { 'VIRT LINE 2' } } } } 1353 ) 1354 api.nvim_buf_set_extmark( 1355 0, 1356 ns, 1357 6, 1358 0, 1359 { virt_lines = { { { 'VIRT LINE 3' } } }, virt_lines_above = true } 1360 ) 1361 screen:expect([[ 1362 ^1 | 1363 2 | 1364 VIRT LINE 1 | 1365 VIRT LINE 2 | 1366 {13:+-- 4 lines: 3······························}| 1367 VIRT LINE 3 | 1368 7 | 1369 8 | 1370 {1:~ }| 1371 | 1372 ]]) 1373 eq({ all = 8, fill = 3, end_row = 7, end_vcol = 1 }, api.nvim_win_text_height(0, {})) 1374 eq( 1375 { all = 5, fill = 2, end_row = 2, end_vcol = 0 }, 1376 api.nvim_win_text_height(0, { end_row = 2 }) 1377 ) 1378 eq( 1379 { all = 5, fill = 2, end_row = 2, end_vcol = 0 }, 1380 api.nvim_win_text_height(0, { end_row = 2, end_vcol = X }) 1381 ) 1382 eq( 1383 { all = 5, fill = 2, end_row = 2, end_vcol = 0 }, 1384 api.nvim_win_text_height(0, { end_row = 2, end_vcol = 90 }) 1385 ) 1386 eq( 1387 { all = 5, fill = 2, end_row = 2, end_vcol = 0 }, 1388 api.nvim_win_text_height(0, { end_row = 2, end_vcol = 46 }) 1389 ) 1390 eq( 1391 { all = 5, fill = 2, end_row = 2, end_vcol = 0 }, 1392 api.nvim_win_text_height(0, { end_row = 2, end_vcol = 45 }) 1393 ) 1394 eq( 1395 { all = 5, fill = 2, end_row = 2, end_vcol = 0 }, 1396 api.nvim_win_text_height(0, { end_row = 2, end_vcol = 1 }) 1397 ) 1398 eq( 1399 { all = 4, fill = 2, end_row = 2, end_vcol = 0 }, 1400 api.nvim_win_text_height(0, { end_row = 2, end_vcol = 0 }) 1401 ) 1402 eq( 1403 { all = 6, fill = 3, end_row = 7, end_vcol = 1 }, 1404 api.nvim_win_text_height(0, { start_row = 2 }) 1405 ) 1406 eq( 1407 { all = 4, fill = 1, end_row = 7, end_vcol = 1 }, 1408 api.nvim_win_text_height(0, { start_row = 2, start_vcol = 0 }) 1409 ) 1410 eq( 1411 { all = 4, fill = 1, end_row = 7, end_vcol = 1 }, 1412 api.nvim_win_text_height(0, { start_row = 2, start_vcol = 44 }) 1413 ) 1414 eq( 1415 { all = 3, fill = 1, end_row = 7, end_vcol = 1 }, 1416 api.nvim_win_text_height(0, { start_row = 2, start_vcol = 45 }) 1417 ) 1418 eq( 1419 { all = 3, fill = 1, end_row = 7, end_vcol = 1 }, 1420 api.nvim_win_text_height(0, { start_row = 2, start_vcol = 89 }) 1421 ) 1422 eq( 1423 { all = 3, fill = 1, end_row = 7, end_vcol = 1 }, 1424 api.nvim_win_text_height(0, { start_row = 2, start_vcol = 90 }) 1425 ) 1426 eq( 1427 { all = 3, fill = 1, end_row = 7, end_vcol = 1 }, 1428 api.nvim_win_text_height(0, { start_row = 2, start_vcol = X }) 1429 ) 1430 end) 1431 1432 it('with virt_lines above max_height row', function() 1433 screen:try_resize(45, 10) 1434 exec('call setline(1, range(1, 7) + ["foo"->repeat(20)])') 1435 api.nvim_buf_set_extmark(0, ns, 6, 0, { virt_lines = { { { 'VIRT LINE 1' } } } }) 1436 screen:expect([[ 1437 ^1 | 1438 2 | 1439 3 | 1440 4 | 1441 5 | 1442 6 | 1443 7 | 1444 VIRT LINE 1 | 1445 foofoofoofoofoofoofoofoofoofoofoofoofoofoo{1:@@@}| 1446 | 1447 ]]) 1448 eq( 1449 { all = 10, fill = 1, end_row = 7, end_vcol = 45 }, 1450 api.nvim_win_text_height(0, { max_height = api.nvim_win_get_height(0) }) 1451 ) 1452 end) 1453 end) 1454 1455 describe('open_win', function() 1456 it('disallowed in cmdwin if enter=true or buf=cmdwin_buf', function() 1457 local new_buf = api.nvim_create_buf(true, true) 1458 feed('q:') 1459 eq( 1460 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', 1461 pcall_err(api.nvim_open_win, new_buf, true, { 1462 relative = 'editor', 1463 row = 5, 1464 col = 5, 1465 width = 5, 1466 height = 5, 1467 }) 1468 ) 1469 eq( 1470 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', 1471 pcall_err(api.nvim_open_win, 0, false, { 1472 relative = 'editor', 1473 row = 5, 1474 col = 5, 1475 width = 5, 1476 height = 5, 1477 }) 1478 ) 1479 matches( 1480 'E11: Invalid in command%-line window; <CR> executes, CTRL%-C quits$', 1481 pcall_err( 1482 exec_lua, 1483 [[ 1484 local cmdwin_buf = vim.api.nvim_get_current_buf() 1485 vim._with({buf = vim.api.nvim_create_buf(false, true)}, function() 1486 vim.api.nvim_open_win(cmdwin_buf, false, { 1487 relative='editor', row=5, col=5, width=5, height=5, 1488 }) 1489 end) 1490 ]] 1491 ) 1492 ) 1493 1494 eq( 1495 new_buf, 1496 api.nvim_win_get_buf(api.nvim_open_win(new_buf, false, { 1497 relative = 'editor', 1498 row = 5, 1499 col = 5, 1500 width = 5, 1501 height = 5, 1502 })) 1503 ) 1504 end) 1505 1506 it('aborts if buffer is invalid', function() 1507 local wins_before = api.nvim_list_wins() 1508 eq( 1509 'Invalid buffer id: 1337', 1510 pcall_err(api.nvim_open_win, 1337, false, { 1511 relative = 'editor', 1512 row = 5, 1513 col = 5, 1514 width = 5, 1515 height = 5, 1516 }) 1517 ) 1518 eq(wins_before, api.nvim_list_wins()) 1519 end) 1520 1521 describe('creates a split window above', function() 1522 local function test_open_win_split_above(key, val) 1523 local initial_win = api.nvim_get_current_win() 1524 local win = api.nvim_open_win(0, true, { 1525 [key] = val, 1526 height = 10, 1527 }) 1528 eq('', api.nvim_win_get_config(win).relative) 1529 eq(10, api.nvim_win_get_height(win)) 1530 local layout = fn.winlayout() 1531 eq({ 1532 'col', 1533 { 1534 { 'leaf', win }, 1535 { 'leaf', initial_win }, 1536 }, 1537 }, layout) 1538 end 1539 1540 it("with split = 'above'", function() 1541 test_open_win_split_above('split', 'above') 1542 end) 1543 1544 it("with vertical = false and 'nosplitbelow'", function() 1545 api.nvim_set_option_value('splitbelow', false, {}) 1546 test_open_win_split_above('vertical', false) 1547 end) 1548 end) 1549 1550 describe('creates a split window below', function() 1551 local function test_open_win_split_below(key, val) 1552 local initial_win = api.nvim_get_current_win() 1553 local win = api.nvim_open_win(0, true, { 1554 [key] = val, 1555 height = 15, 1556 }) 1557 eq('', api.nvim_win_get_config(win).relative) 1558 eq(15, api.nvim_win_get_height(win)) 1559 local layout = fn.winlayout() 1560 eq({ 1561 'col', 1562 { 1563 { 'leaf', initial_win }, 1564 { 'leaf', win }, 1565 }, 1566 }, layout) 1567 end 1568 1569 it("with split = 'below'", function() 1570 test_open_win_split_below('split', 'below') 1571 end) 1572 1573 it("with vertical = false and 'splitbelow'", function() 1574 api.nvim_set_option_value('splitbelow', true, {}) 1575 test_open_win_split_below('vertical', false) 1576 end) 1577 end) 1578 1579 describe('creates a split window to the left', function() 1580 local function test_open_win_split_left(key, val) 1581 local initial_win = api.nvim_get_current_win() 1582 local win = api.nvim_open_win(0, true, { 1583 [key] = val, 1584 width = 25, 1585 }) 1586 eq('', api.nvim_win_get_config(win).relative) 1587 eq(25, api.nvim_win_get_width(win)) 1588 local layout = fn.winlayout() 1589 eq({ 1590 'row', 1591 { 1592 { 'leaf', win }, 1593 { 'leaf', initial_win }, 1594 }, 1595 }, layout) 1596 end 1597 1598 it("with split = 'left'", function() 1599 test_open_win_split_left('split', 'left') 1600 end) 1601 1602 it("with vertical = true and 'nosplitright'", function() 1603 api.nvim_set_option_value('splitright', false, {}) 1604 test_open_win_split_left('vertical', true) 1605 end) 1606 end) 1607 1608 describe('creates a split window to the right', function() 1609 local function test_open_win_split_right(key, val) 1610 local initial_win = api.nvim_get_current_win() 1611 local win = api.nvim_open_win(0, true, { 1612 [key] = val, 1613 width = 30, 1614 }) 1615 eq('', api.nvim_win_get_config(win).relative) 1616 eq(30, api.nvim_win_get_width(win)) 1617 local layout = fn.winlayout() 1618 eq({ 1619 'row', 1620 { 1621 { 'leaf', initial_win }, 1622 { 'leaf', win }, 1623 }, 1624 }, layout) 1625 end 1626 1627 it("with split = 'right'", function() 1628 test_open_win_split_right('split', 'right') 1629 end) 1630 1631 it("with vertical = true and 'splitright'", function() 1632 api.nvim_set_option_value('splitright', true, {}) 1633 test_open_win_split_right('vertical', true) 1634 end) 1635 end) 1636 1637 it("doesn't change tp_curwin when splitting window in another tab with enter=false", function() 1638 local tab1 = api.nvim_get_current_tabpage() 1639 local tab1_win = api.nvim_get_current_win() 1640 1641 n.command('tabnew') 1642 local tab2 = api.nvim_get_current_tabpage() 1643 local tab2_win = api.nvim_get_current_win() 1644 1645 eq({ tab1_win, tab2_win }, api.nvim_list_wins()) 1646 eq({ tab1, tab2 }, api.nvim_list_tabpages()) 1647 1648 api.nvim_set_current_tabpage(tab1) 1649 eq(tab1_win, api.nvim_get_current_win()) 1650 1651 local tab2_prevwin = fn.tabpagewinnr(tab2, '#') 1652 1653 -- split in tab2 whine in tab2, with enter = false 1654 local tab2_win2 = api.nvim_open_win(api.nvim_create_buf(false, true), false, { 1655 win = tab2_win, 1656 split = 'right', 1657 }) 1658 eq(tab1_win, api.nvim_get_current_win()) -- we should still be in the first tp 1659 eq(tab1_win, api.nvim_tabpage_get_win(tab1)) 1660 1661 eq(tab2_win, api.nvim_tabpage_get_win(tab2)) -- tab2's tp_curwin should not have changed 1662 eq(tab2_prevwin, fn.tabpagewinnr(tab2, '#')) -- tab2's tp_prevwin should not have changed 1663 eq({ tab1_win, tab2_win, tab2_win2 }, api.nvim_list_wins()) 1664 eq({ tab2_win, tab2_win2 }, api.nvim_tabpage_list_wins(tab2)) 1665 end) 1666 1667 it('creates splits in the correct location', function() 1668 local first_win = api.nvim_get_current_win() 1669 -- specifying window 0 should create a split next to the current window 1670 local win = api.nvim_open_win(0, true, { 1671 vertical = false, 1672 }) 1673 local layout = fn.winlayout() 1674 eq({ 1675 'col', 1676 { 1677 { 'leaf', win }, 1678 { 'leaf', first_win }, 1679 }, 1680 }, layout) 1681 -- not specifying a window should create a top-level split 1682 local win2 = api.nvim_open_win(0, true, { 1683 split = 'left', 1684 win = -1, 1685 }) 1686 layout = fn.winlayout() 1687 eq({ 1688 'row', 1689 { 1690 { 'leaf', win2 }, 1691 { 1692 'col', 1693 { 1694 { 'leaf', win }, 1695 { 'leaf', first_win }, 1696 }, 1697 }, 1698 }, 1699 }, layout) 1700 1701 -- specifying a window should create a split next to that window 1702 local win3 = api.nvim_open_win(0, true, { 1703 win = win, 1704 vertical = false, 1705 }) 1706 layout = fn.winlayout() 1707 eq({ 1708 'row', 1709 { 1710 { 'leaf', win2 }, 1711 { 1712 'col', 1713 { 1714 { 'leaf', win3 }, 1715 { 'leaf', win }, 1716 { 'leaf', first_win }, 1717 }, 1718 }, 1719 }, 1720 }, layout) 1721 end) 1722 1723 it('opens floating windows in other tabpages', function() 1724 local first_win = api.nvim_get_current_win() 1725 local first_tab = api.nvim_get_current_tabpage() 1726 1727 command('tabnew') 1728 local new_tab = api.nvim_get_current_tabpage() 1729 local win = api.nvim_open_win(0, false, { 1730 relative = 'win', 1731 win = first_win, 1732 width = 5, 1733 height = 5, 1734 row = 1, 1735 col = 1, 1736 }) 1737 eq(api.nvim_win_get_tabpage(win), first_tab) 1738 eq(api.nvim_get_current_tabpage(), new_tab) 1739 end) 1740 1741 it('switches to new windows in non-current tabpages when enter=true', function() 1742 local first_win = api.nvim_get_current_win() 1743 local first_tab = api.nvim_get_current_tabpage() 1744 command('tabnew') 1745 local win = api.nvim_open_win(0, true, { 1746 relative = 'win', 1747 win = first_win, 1748 width = 5, 1749 height = 5, 1750 row = 1, 1751 col = 1, 1752 }) 1753 eq(api.nvim_win_get_tabpage(win), first_tab) 1754 eq(api.nvim_get_current_tabpage(), first_tab) 1755 end) 1756 1757 local function setup_tabbed_autocmd_test() 1758 local info = {} 1759 info.orig_buf = api.nvim_get_current_buf() 1760 info.other_buf = api.nvim_create_buf(true, true) 1761 info.tab1_curwin = api.nvim_get_current_win() 1762 info.tab1 = api.nvim_get_current_tabpage() 1763 command('tab split | split') 1764 info.tab2_curwin = api.nvim_get_current_win() 1765 info.tab2 = api.nvim_get_current_tabpage() 1766 exec([=[ 1767 tabfirst 1768 let result = [] 1769 autocmd TabEnter * let result += [["TabEnter", nvim_get_current_tabpage()]] 1770 autocmd TabLeave * let result += [["TabLeave", nvim_get_current_tabpage()]] 1771 autocmd WinEnter * let result += [["WinEnter", win_getid()]] 1772 autocmd WinLeave * let result += [["WinLeave", win_getid()]] 1773 autocmd WinNew * let result += [["WinNew", win_getid()]] 1774 autocmd WinClosed * let result += [["WinClosed", str2nr(expand("<afile>"))]] 1775 autocmd BufEnter * let result += [["BufEnter", win_getid(), bufnr()]] 1776 autocmd BufLeave * let result += [["BufLeave", win_getid(), bufnr()]] 1777 autocmd BufWinEnter * let result += [["BufWinEnter", win_getid(), bufnr()]] 1778 autocmd BufWinLeave * let result += [["BufWinLeave", win_getid(), bufnr()]] 1779 ]=]) 1780 return info 1781 end 1782 1783 it('noautocmd option works', function() 1784 local info = setup_tabbed_autocmd_test() 1785 1786 api.nvim_open_win( 1787 info.other_buf, 1788 true, 1789 { split = 'left', win = info.tab2_curwin, noautocmd = true } 1790 ) 1791 eq({}, eval('result')) 1792 1793 api.nvim_open_win( 1794 info.orig_buf, 1795 true, 1796 { relative = 'editor', row = 0, col = 0, width = 10, height = 10, noautocmd = true } 1797 ) 1798 eq({}, eval('result')) 1799 end) 1800 1801 it('fires expected autocmds when creating splits without entering', function() 1802 local info = setup_tabbed_autocmd_test() 1803 1804 -- For these, don't want BufWinEnter if visiting the same buffer, like :{s}buffer. 1805 -- Same tabpage, same buffer. 1806 local new_win = api.nvim_open_win(0, false, { split = 'left', win = info.tab1_curwin }) 1807 eq({ 1808 { 'WinNew', new_win }, 1809 }, eval('result')) 1810 eq(info.tab1_curwin, api.nvim_get_current_win()) 1811 1812 -- Other tabpage, same buffer. 1813 command('let result = []') 1814 new_win = api.nvim_open_win(0, false, { split = 'left', win = info.tab2_curwin }) 1815 eq({ 1816 { 'WinNew', new_win }, 1817 }, eval('result')) 1818 eq(info.tab1_curwin, api.nvim_get_current_win()) 1819 1820 -- Same tabpage, other buffer. 1821 command('let result = []') 1822 new_win = api.nvim_open_win(info.other_buf, false, { split = 'left', win = info.tab1_curwin }) 1823 eq({ 1824 { 'WinNew', new_win }, 1825 { 'BufWinEnter', new_win, info.other_buf }, 1826 }, eval('result')) 1827 eq(info.tab1_curwin, api.nvim_get_current_win()) 1828 1829 -- Other tabpage, other buffer. 1830 command('let result = []') 1831 new_win = api.nvim_open_win(info.other_buf, false, { split = 'left', win = info.tab2_curwin }) 1832 eq({ 1833 { 'WinNew', new_win }, 1834 { 'BufWinEnter', new_win, info.other_buf }, 1835 }, eval('result')) 1836 eq(info.tab1_curwin, api.nvim_get_current_win()) 1837 end) 1838 1839 it('fires expected autocmds when creating and entering splits', function() 1840 local info = setup_tabbed_autocmd_test() 1841 1842 -- Same tabpage, same buffer. 1843 local new_win = api.nvim_open_win(0, true, { split = 'left', win = info.tab1_curwin }) 1844 eq({ 1845 { 'WinNew', new_win }, 1846 { 'WinLeave', info.tab1_curwin }, 1847 { 'WinEnter', new_win }, 1848 }, eval('result')) 1849 1850 -- Same tabpage, other buffer. 1851 api.nvim_set_current_win(info.tab1_curwin) 1852 command('let result = []') 1853 new_win = api.nvim_open_win(info.other_buf, true, { split = 'left', win = info.tab1_curwin }) 1854 eq({ 1855 { 'WinNew', new_win }, 1856 { 'WinLeave', info.tab1_curwin }, 1857 { 'WinEnter', new_win }, 1858 { 'BufLeave', new_win, info.orig_buf }, 1859 { 'BufEnter', new_win, info.other_buf }, 1860 { 'BufWinEnter', new_win, info.other_buf }, 1861 }, eval('result')) 1862 1863 -- For these, the other tabpage's prevwin and curwin will change like we switched from its old 1864 -- curwin to the new window, so the extra events near TabEnter reflect that. 1865 -- Other tabpage, same buffer. 1866 api.nvim_set_current_win(info.tab1_curwin) 1867 command('let result = []') 1868 new_win = api.nvim_open_win(0, true, { split = 'left', win = info.tab2_curwin }) 1869 eq({ 1870 { 'WinNew', new_win }, 1871 { 'WinLeave', info.tab1_curwin }, 1872 { 'TabLeave', info.tab1 }, 1873 1874 { 'WinEnter', info.tab2_curwin }, 1875 { 'TabEnter', info.tab2 }, 1876 { 'WinLeave', info.tab2_curwin }, 1877 { 'WinEnter', new_win }, 1878 }, eval('result')) 1879 1880 -- Other tabpage, other buffer. 1881 api.nvim_set_current_win(info.tab2_curwin) 1882 api.nvim_set_current_win(info.tab1_curwin) 1883 command('let result = []') 1884 new_win = api.nvim_open_win(info.other_buf, true, { split = 'left', win = info.tab2_curwin }) 1885 eq({ 1886 { 'WinNew', new_win }, 1887 { 'WinLeave', info.tab1_curwin }, 1888 { 'TabLeave', info.tab1 }, 1889 1890 { 'WinEnter', info.tab2_curwin }, 1891 { 'TabEnter', info.tab2 }, 1892 { 'WinLeave', info.tab2_curwin }, 1893 { 'WinEnter', new_win }, 1894 1895 { 'BufLeave', new_win, info.orig_buf }, 1896 { 'BufEnter', new_win, info.other_buf }, 1897 { 'BufWinEnter', new_win, info.other_buf }, 1898 }, eval('result')) 1899 1900 -- Other tabpage, other buffer; but other tabpage's curwin has a new buffer active. 1901 api.nvim_set_current_win(info.tab2_curwin) 1902 local new_buf = api.nvim_create_buf(true, true) 1903 api.nvim_set_current_buf(new_buf) 1904 api.nvim_set_current_win(info.tab1_curwin) 1905 command('let result = []') 1906 new_win = api.nvim_open_win(info.other_buf, true, { split = 'left', win = info.tab2_curwin }) 1907 eq({ 1908 { 'WinNew', new_win }, 1909 { 'BufLeave', info.tab1_curwin, info.orig_buf }, 1910 { 'WinLeave', info.tab1_curwin }, 1911 { 'TabLeave', info.tab1 }, 1912 1913 { 'WinEnter', info.tab2_curwin }, 1914 { 'TabEnter', info.tab2 }, 1915 { 'BufEnter', info.tab2_curwin, new_buf }, 1916 { 'WinLeave', info.tab2_curwin }, 1917 { 'WinEnter', new_win }, 1918 { 'BufLeave', new_win, new_buf }, 1919 { 'BufEnter', new_win, info.other_buf }, 1920 { 'BufWinEnter', new_win, info.other_buf }, 1921 }, eval('result')) 1922 end) 1923 1924 it('OK when new window is moved to other tabpage by autocommands', function() 1925 -- Use nvim_win_set_config in the autocommands, as other methods of moving a window to a 1926 -- different tabpage (e.g: wincmd T) actually creates a new window. 1927 local tab0 = api.nvim_get_current_tabpage() 1928 local tab0_win = api.nvim_get_current_win() 1929 command('tabnew') 1930 local new_buf = api.nvim_create_buf(true, true) 1931 local tab1 = api.nvim_get_current_tabpage() 1932 local tab1_parent = api.nvim_get_current_win() 1933 command( 1934 'tabfirst | autocmd WinNew * ++once call nvim_win_set_config(0, #{split: "left", win: ' 1935 .. tab1_parent 1936 .. '})' 1937 ) 1938 local new_win = api.nvim_open_win(new_buf, true, { split = 'left' }) 1939 eq(tab1, api.nvim_get_current_tabpage()) 1940 eq(new_win, api.nvim_get_current_win()) 1941 eq(new_buf, api.nvim_get_current_buf()) 1942 1943 -- nvim_win_set_config called after entering. It doesn't follow a curwin that is moved to a 1944 -- different tabpage, but instead moves to the win filling the space, which is tab0_win. 1945 command( 1946 'tabfirst | autocmd WinEnter * ++once call nvim_win_set_config(0, #{split: "left", win: ' 1947 .. tab1_parent 1948 .. '})' 1949 ) 1950 new_win = api.nvim_open_win(new_buf, true, { split = 'left' }) 1951 eq(tab0, api.nvim_get_current_tabpage()) 1952 eq(tab0_win, api.nvim_get_current_win()) 1953 eq(tab1, api.nvim_win_get_tabpage(new_win)) 1954 eq(new_buf, api.nvim_win_get_buf(new_win)) 1955 1956 command( 1957 'tabfirst | autocmd BufEnter * ++once call nvim_win_set_config(0, #{split: "left", win: ' 1958 .. tab1_parent 1959 .. '})' 1960 ) 1961 new_win = api.nvim_open_win(new_buf, true, { split = 'left' }) 1962 eq(tab0, api.nvim_get_current_tabpage()) 1963 eq(tab0_win, api.nvim_get_current_win()) 1964 eq(tab1, api.nvim_win_get_tabpage(new_win)) 1965 eq(new_buf, api.nvim_win_get_buf(new_win)) 1966 end) 1967 1968 it('does not fire BufWinEnter if win_set_buf fails', function() 1969 exec([[ 1970 set nohidden modified 1971 autocmd WinNew * ++once only! 1972 let fired = v:false 1973 autocmd BufWinEnter * ++once let fired = v:true 1974 ]]) 1975 eq( 1976 'Vim:E37: No write since last change (add ! to override)', 1977 pcall_err(api.nvim_open_win, api.nvim_create_buf(true, true), false, { split = 'left' }) 1978 ) 1979 eq(false, eval('fired')) 1980 end) 1981 1982 it('fires Buf* autocommands when `!enter` if window is entered via autocommands', function() 1983 exec([[ 1984 autocmd WinNew * ++once only! 1985 let fired = v:false 1986 autocmd BufEnter * ++once let fired = v:true 1987 ]]) 1988 api.nvim_open_win(api.nvim_create_buf(true, true), false, { split = 'left' }) 1989 eq(true, eval('fired')) 1990 end) 1991 1992 it('no heap-use-after-free if target buffer deleted by autocommands', function() 1993 local cur_buf = api.nvim_get_current_buf() 1994 local new_buf = api.nvim_create_buf(true, true) 1995 command('autocmd WinNew * ++once call nvim_buf_delete(' .. new_buf .. ', #{force: 1})') 1996 api.nvim_open_win(new_buf, true, { split = 'left' }) 1997 eq(cur_buf, api.nvim_get_current_buf()) 1998 end) 1999 2000 it('checks if splitting disallowed', function() 2001 command('split | autocmd WinEnter * ++once call nvim_open_win(0, 0, #{split: "right"})') 2002 matches("E242: Can't split a window while closing another$", pcall_err(command, 'quit')) 2003 -- E242 is not needed for floats. 2004 exec([[ 2005 split 2006 autocmd WinEnter * ++once let g:win = nvim_open_win(0, 0, #{relative: "editor", row: 0, col: 0, width: 5, height: 5}) 2007 quit 2008 ]]) 2009 eq('editor', eval('nvim_win_get_config(g:win).relative')) 2010 2011 command('only | autocmd BufHidden * ++once call nvim_open_win(0, 0, #{split: "left"})') 2012 matches( 2013 'E1159: Cannot split a window when closing the buffer$', 2014 pcall_err(command, 'new | quit') 2015 ) 2016 2017 local w = api.nvim_get_current_win() 2018 command( 2019 'only | new | autocmd BufHidden * ++once call nvim_open_win(0, 0, #{split: "left", win: ' 2020 .. w 2021 .. '})' 2022 ) 2023 matches( 2024 'E1159: Cannot split a window when closing the buffer$', 2025 pcall_err(api.nvim_win_close, w, true) 2026 ) 2027 2028 -- OK when using a buffer that isn't closing. 2029 w = api.nvim_get_current_win() 2030 command( 2031 'only | autocmd BufHidden * ++once call nvim_open_win(bufnr("#"), 0, #{split: "left", win: ' 2032 .. w 2033 .. '})' 2034 ) 2035 command('new | quit') 2036 2037 -- Apply to opening floats too, as that can similarly create new views into a closing buffer. 2038 -- For example, the following would open a float into an unloaded buffer: 2039 exec([[ 2040 only 2041 new 2042 let g:buf = bufnr() 2043 autocmd BufUnload * ++once call nvim_open_win(g:buf, 0, #{relative: "editor", width: 5, height: 5, row: 1, col: 1}) 2044 setlocal bufhidden=unload 2045 ]]) 2046 matches('E1159: Cannot open a float when closing the buffer$', pcall_err(command, 'quit')) 2047 eq(false, eval('nvim_buf_is_loaded(g:buf)')) 2048 eq(0, eval('win_findbuf(g:buf)->len()')) 2049 2050 -- Only checking b_locked_split for the target buffer is insufficient, as naughty autocommands 2051 -- can cause win_set_buf to remain in a closing curbuf: 2052 exec([[ 2053 only 2054 new 2055 let g:buf = bufnr() 2056 autocmd BufWipeout * ++once ++nested let g:buf2 = nvim_create_buf(1, 0) 2057 \| execute 'autocmd BufLeave * ++once call nvim_buf_delete(g:buf2, #{force: 1})' 2058 \| setlocal bufhidden= 2059 \| call nvim_open_win(g:buf2, 1, #{relative: 'editor', width: 5, height: 5, col: 5, row: 5}) 2060 setlocal bufhidden=wipe 2061 ]]) 2062 matches('E1159: Cannot open a float when closing the buffer$', pcall_err(command, 'quit')) 2063 eq(false, eval('nvim_buf_is_loaded(g:buf)')) 2064 eq(0, eval('win_findbuf(g:buf)->len()')) 2065 -- BufLeave shouldn't run here (buf2 isn't deleted and remains hidden) 2066 eq(true, eval('nvim_buf_is_loaded(g:buf2)')) 2067 eq(0, eval('win_findbuf(g:buf2)->len()')) 2068 end) 2069 2070 it('restores last known cursor position if BufWinEnter did not move it', function() 2071 -- This test mostly exists to ensure BufWinEnter is executed before enter_buffer's epilogue. 2072 local buf = api.nvim_get_current_buf() 2073 insert([[ 2074 foo 2075 bar baz .etc 2076 i love autocommand bugs! 2077 supercalifragilisticexpialidocious 2078 marvim is actually a human 2079 llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch 2080 ]]) 2081 api.nvim_win_set_cursor(0, { 5, 2 }) 2082 command('set nostartofline | enew') 2083 local new_win = api.nvim_open_win(buf, false, { split = 'left' }) 2084 eq({ 5, 2 }, api.nvim_win_get_cursor(new_win)) 2085 2086 exec([[ 2087 only! 2088 autocmd BufWinEnter * ++once normal! j6l 2089 ]]) 2090 new_win = api.nvim_open_win(buf, false, { split = 'left' }) 2091 eq({ 2, 6 }, api.nvim_win_get_cursor(new_win)) 2092 end) 2093 2094 it('does not block all win_set_buf autocommands if !enter and !noautocmd', function() 2095 local new_buf = fn.bufadd('foobarbaz') 2096 exec([[ 2097 let triggered = "" 2098 autocmd BufReadCmd * ++once let triggered = bufname() 2099 ]]) 2100 api.nvim_open_win(new_buf, false, { split = 'left' }) 2101 eq('foobarbaz', eval('triggered')) 2102 end) 2103 2104 it('sets error when no room', function() 2105 matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)')) 2106 matches( 2107 'E36: Not enough room$', 2108 pcall_err(api.nvim_open_win, 0, true, { split = 'above', win = 0 }) 2109 ) 2110 matches( 2111 'E36: Not enough room$', 2112 pcall_err(api.nvim_open_win, 0, true, { split = 'below', win = 0 }) 2113 ) 2114 end) 2115 2116 it("can create split window when 'winborder' is set", function() 2117 local old_win = api.nvim_get_current_win() 2118 api.nvim_set_option_value('winborder', 'single', {}) 2119 local new_win = api.nvim_open_win(0, false, { split = 'right', win = 0 }) 2120 eq({ 'row', { { 'leaf', old_win }, { 'leaf', new_win } } }, fn.winlayout()) 2121 eq('', api.nvim_win_get_config(new_win).relative) 2122 end) 2123 2124 describe("with 'autochdir'", function() 2125 local topdir 2126 local otherbuf 2127 2128 before_each(function() 2129 command('set shellslash') 2130 topdir = fn.getcwd() 2131 t.mkdir(topdir .. '/Xacd') 2132 t.mkdir(topdir .. '/Xacd/foo') 2133 otherbuf = api.nvim_create_buf(false, true) 2134 api.nvim_buf_set_name(otherbuf, topdir .. '/Xacd/baz.txt') 2135 2136 command('set autochdir') 2137 command('edit Xacd/foo/bar.txt') 2138 eq(topdir .. '/Xacd/foo', fn.getcwd()) 2139 end) 2140 2141 after_each(function() 2142 n.rmdir(topdir .. '/Xacd') 2143 end) 2144 2145 it('does not change cwd with enter=false #15280', function() 2146 api.nvim_open_win( 2147 otherbuf, 2148 false, 2149 { relative = 'editor', height = 5, width = 5, row = 5, col = 5 } 2150 ) 2151 eq(topdir .. '/Xacd/foo', fn.getcwd()) 2152 end) 2153 2154 it('changes cwd with enter=true', function() 2155 api.nvim_open_win( 2156 otherbuf, 2157 true, 2158 { relative = 'editor', height = 5, width = 5, row = 5, col = 5 } 2159 ) 2160 eq(topdir .. '/Xacd', fn.getcwd()) 2161 end) 2162 end) 2163 2164 it('no memory leak with valid title and invalid footer', function() 2165 eq( 2166 'title/footer must be string or array', 2167 pcall_err(api.nvim_open_win, 0, false, { 2168 relative = 'editor', 2169 row = 10, 2170 col = 10, 2171 height = 10, 2172 width = 10, 2173 border = 'single', 2174 title = { { 'TITLE' } }, 2175 footer = 0, 2176 }) 2177 ) 2178 end) 2179 2180 it('no memory leak with invalid title and valid footer', function() 2181 eq( 2182 'title/footer must be string or array', 2183 pcall_err(api.nvim_open_win, 0, false, { 2184 relative = 'editor', 2185 row = 10, 2186 col = 10, 2187 height = 10, 2188 width = 10, 2189 border = 'single', 2190 title = 0, 2191 footer = { { 'FOOTER' } }, 2192 }) 2193 ) 2194 end) 2195 2196 it('no crash when closing the only non-float in other tabpage #31236', function() 2197 local tp = api.nvim_get_current_tabpage() 2198 local split_win = api.nvim_get_current_win() 2199 local float_win = api.nvim_open_win( 2200 0, 2201 false, 2202 { relative = 'editor', width = 5, height = 5, row = 1, col = 1 } 2203 ) 2204 command('tabnew') 2205 2206 api.nvim_win_close(split_win, false) 2207 eq(false, api.nvim_win_is_valid(split_win)) 2208 eq(false, api.nvim_win_is_valid(float_win)) 2209 eq(false, api.nvim_tabpage_is_valid(tp)) 2210 2211 tp = api.nvim_get_current_tabpage() 2212 split_win = api.nvim_get_current_win() 2213 local float_buf = api.nvim_create_buf(true, false) 2214 float_win = api.nvim_open_win( 2215 float_buf, 2216 false, 2217 { relative = 'editor', width = 5, height = 5, row = 1, col = 1 } 2218 ) 2219 -- Set these options to prevent the float from being automatically closed. 2220 api.nvim_set_option_value('modified', true, { buf = float_buf }) 2221 api.nvim_set_option_value('bufhidden', 'wipe', { buf = float_buf }) 2222 command('tabnew') 2223 2224 matches( 2225 'E5601: Cannot close window, only floating window would remain$', 2226 pcall_err(api.nvim_win_close, split_win, false) 2227 ) 2228 eq(true, api.nvim_win_is_valid(split_win)) 2229 eq(true, api.nvim_win_is_valid(float_win)) 2230 eq(true, api.nvim_tabpage_is_valid(tp)) 2231 2232 api.nvim_set_current_win(float_win) 2233 api.nvim_win_close(split_win, true) -- Force it this time. 2234 eq(false, api.nvim_win_is_valid(split_win)) 2235 eq(false, api.nvim_win_is_valid(float_win)) 2236 eq(false, api.nvim_tabpage_is_valid(tp)) 2237 2238 -- Ensure opening a float after the initial check (like in WinClosed) doesn't crash... 2239 exec([[ 2240 tabnew 2241 let g:tp = nvim_get_current_tabpage() 2242 let g:win = win_getid() 2243 tabprevious 2244 autocmd! WinClosed * ++once call nvim_open_win(0, 0, #{win: g:win, relative: 'win', width: 5, height: 5, row: 5, col: 5}) 2245 ]]) 2246 matches( 2247 'E5601: Cannot close window, only floating window would remain$', 2248 pcall_err(command, 'call nvim_win_close(g:win, 0)') 2249 ) 2250 eq(true, eval 'nvim_tabpage_is_valid(g:tp)') 2251 2252 exec([[ 2253 tabnew 2254 let g:tp = nvim_get_current_tabpage() 2255 let g:win = win_getid() 2256 let g:buf = bufnr() 2257 tabprevious 2258 let s:buf2 = nvim_create_buf(0, 0) 2259 call setbufvar(s:buf2, '&modified', 1) 2260 call setbufvar(s:buf2, '&bufhidden', 'wipe') 2261 autocmd! WinClosed * ++once call nvim_open_win(s:buf2, 0, #{win: g:win, relative: 'win', width: 5, height: 5, row: 5, col: 5}) 2262 ]]) 2263 matches( 2264 'E5601: Cannot close window, only floating window would remain$', 2265 pcall_err(command, 'call nvim_buf_delete(g:buf, #{force: 1})') 2266 ) 2267 eq(true, eval 'nvim_tabpage_is_valid(g:tp)') 2268 end) 2269 2270 it('respects requested size for large splits', function() 2271 command('vsplit') 2272 local win = api.nvim_open_win(0, false, { win = -1, split = 'right', width = 38 }) 2273 eq(38, api.nvim_win_get_width(win)) 2274 2275 -- No zero-sized windows (e.g: from skipping forced equalization in win_split_ins) if 2276 -- requesting a chonky window; that could lead to crashes! 2277 api.nvim_open_win(0, false, { win = -1, split = 'right', width = 9999 }) 2278 eq({ 1, 1, 1, 74 }, eval("range(1, winnr('$'))->map({_, nr -> winwidth(nr)})")) 2279 2280 command('split') 2281 win = api.nvim_open_win(0, false, { win = 0, split = 'below', height = 10 }) 2282 eq(10, api.nvim_win_get_height(win)) 2283 2284 -- Still defaults to half-sized when no size was specified. 2285 command('only') 2286 eq(80, api.nvim_win_get_width(0)) 2287 api.nvim_open_win(0, true, { split = 'right' }) 2288 eq(40, api.nvim_win_get_width(0)) 2289 2290 eq(22, api.nvim_win_get_height(0)) 2291 api.nvim_open_win(0, true, { split = 'below' }) 2292 eq(11, api.nvim_win_get_height(0)) 2293 end) 2294 2295 it('no leak when win_set_buf fails and window is closed immediately', function() 2296 -- Following used to leak. 2297 command('autocmd BufEnter * ++once quit! | throw 1337') 2298 eq( 2299 'Window was closed immediately', 2300 pcall_err( 2301 api.nvim_open_win, 2302 api.nvim_create_buf(true, true), 2303 true, 2304 { relative = 'editor', width = 5, height = 5, row = 1, col = 1 } 2305 ) 2306 ) 2307 -- If the window wasn't closed, still set errors from win_set_buf. 2308 command('autocmd BufEnter * ++once throw 1337') 2309 eq( 2310 'BufEnter Autocommands for "*": 1337', 2311 pcall_err( 2312 api.nvim_open_win, 2313 api.nvim_create_buf(true, true), 2314 true, 2315 { relative = 'editor', width = 5, height = 5, row = 1, col = 1 } 2316 ) 2317 ) 2318 end) 2319 end) 2320 2321 describe('set_config', function() 2322 it("uses 'winborder' when converting a split to a floating window", function() 2323 api.nvim_set_option_value('winborder', 'single', {}) 2324 command('split') 2325 local winid = api.nvim_get_current_win() 2326 -- Convert split to float without specifying border 2327 api.nvim_win_set_config(winid, { 2328 relative = 'editor', 2329 row = 2, 2330 col = 2, 2331 width = 10, 2332 height = 5, 2333 }) 2334 local config = api.nvim_win_get_config(winid) 2335 eq('┌', config.border[1]) 2336 end) 2337 2338 it('erases border of a floating window when converting to split window', function() 2339 api.nvim_set_option_value('winborder', 'single', {}) 2340 local winid = api.nvim_open_win(api.nvim_create_buf(false, false), false, { 2341 relative = 'editor', 2342 row = 2, 2343 col = 2, 2344 width = 10, 2345 height = 5, 2346 }) 2347 local config = api.nvim_win_get_config(winid) 2348 eq('┌', config.border[1]) 2349 api.nvim_win_set_config(winid, { split = 'right', win = 0 }) 2350 config = api.nvim_win_get_config(winid) 2351 eq(nil, config.border) 2352 end) 2353 2354 it('moves a split into a float', function() 2355 local win = api.nvim_open_win(0, true, { 2356 vertical = false, 2357 }) 2358 eq('', api.nvim_win_get_config(win).relative) 2359 api.nvim_win_set_config(win, { 2360 relative = 'editor', 2361 row = 5, 2362 col = 5, 2363 width = 5, 2364 height = 5, 2365 }) 2366 eq('editor', api.nvim_win_get_config(win).relative) 2367 end) 2368 2369 it('throws error when attempting to move the last non-floating window', function() 2370 local err = pcall_err(api.nvim_win_set_config, 0, { 2371 vertical = false, 2372 }) 2373 eq('Cannot move last non-floating window', err) 2374 2375 local win1 = api.nvim_get_current_win() 2376 command('tabnew') 2377 eq( 2378 'Cannot move last non-floating window', 2379 pcall_err(api.nvim_win_set_config, 0, { win = win1, split = 'left' }) 2380 ) 2381 api.nvim_open_win(0, false, { relative = 'editor', width = 5, height = 5, row = 1, col = 1 }) 2382 eq( 2383 'Cannot move last non-floating window', 2384 pcall_err(api.nvim_win_set_config, 0, { win = win1, split = 'left' }) 2385 ) 2386 2387 -- If it's no longer the last non-float, still an error if autocommands make it the last 2388 -- non-float again before it's moved. 2389 command('vsplit') 2390 exec_lua(function() 2391 vim.api.nvim_create_autocmd('WinEnter', { 2392 once = true, 2393 callback = function() 2394 vim.api.nvim_win_set_config( 2395 0, 2396 { relative = 'editor', width = 5, height = 5, row = 1, col = 1 } 2397 ) 2398 end, 2399 }) 2400 end) 2401 eq( 2402 'Cannot move last non-floating window', 2403 pcall_err(api.nvim_win_set_config, 0, { win = win1, split = 'left' }) 2404 ) 2405 end) 2406 2407 it('passing retval of get_config results in no-op', function() 2408 -- simple split layout 2409 local win = api.nvim_open_win(0, true, { 2410 split = 'left', 2411 }) 2412 local layout = fn.winlayout() 2413 local config = api.nvim_win_get_config(win) 2414 api.nvim_win_set_config(win, config) 2415 eq(layout, fn.winlayout()) 2416 2417 -- nested split layout 2418 local win2 = api.nvim_open_win(0, true, { 2419 vertical = true, 2420 }) 2421 local win3 = api.nvim_open_win(0, true, { 2422 win = win2, 2423 vertical = false, 2424 }) 2425 layout = fn.winlayout() 2426 config = api.nvim_win_get_config(win2) 2427 api.nvim_win_set_config(win2, config) 2428 eq(layout, fn.winlayout()) 2429 2430 config = api.nvim_win_get_config(win3) 2431 api.nvim_win_set_config(win3, config) 2432 eq(layout, fn.winlayout()) 2433 end) 2434 2435 it('moves a float into a split', function() 2436 local layout = fn.winlayout() 2437 eq('leaf', layout[1]) 2438 local win = api.nvim_open_win(0, true, { 2439 relative = 'editor', 2440 row = 5, 2441 col = 5, 2442 width = 5, 2443 height = 5, 2444 }) 2445 api.nvim_win_set_config(win, { 2446 split = 'below', 2447 win = -1, 2448 }) 2449 eq('', api.nvim_win_get_config(win).relative) 2450 layout = fn.winlayout() 2451 eq('col', layout[1]) 2452 eq(2, #layout[2]) 2453 eq(win, layout[2][2][2]) 2454 end) 2455 2456 it('respects the "split" option', function() 2457 local layout = fn.winlayout() 2458 eq('leaf', layout[1]) 2459 local first_win = layout[2] 2460 local win = api.nvim_open_win(0, true, { 2461 relative = 'editor', 2462 row = 5, 2463 col = 5, 2464 width = 5, 2465 height = 5, 2466 }) 2467 api.nvim_win_set_config(win, { 2468 split = 'right', 2469 win = first_win, 2470 }) 2471 layout = fn.winlayout() 2472 eq('row', layout[1]) 2473 eq(2, #layout[2]) 2474 eq(win, layout[2][2][2]) 2475 local config = api.nvim_win_get_config(win) 2476 eq('', config.relative) 2477 eq('right', config.split) 2478 api.nvim_win_set_config(win, { 2479 split = 'below', 2480 win = first_win, 2481 }) 2482 layout = fn.winlayout() 2483 eq('col', layout[1]) 2484 eq(2, #layout[2]) 2485 eq(win, layout[2][2][2]) 2486 config = api.nvim_win_get_config(win) 2487 eq('', config.relative) 2488 eq('below', config.split) 2489 2490 eq( 2491 "non-float with 'win' requires at least 'split' or 'vertical'", 2492 pcall_err(api.nvim_win_set_config, 0, { win = 0 }) 2493 ) 2494 eq( 2495 "non-float with 'win' requires at least 'split' or 'vertical'", 2496 pcall_err(api.nvim_win_set_config, 0, { win = 0, relative = '' }) 2497 ) 2498 end) 2499 2500 it('creates top-level splits', function() 2501 local win = api.nvim_open_win(0, true, { 2502 vertical = false, 2503 }) 2504 local win2 = api.nvim_open_win(0, true, { 2505 vertical = true, 2506 win = -1, 2507 }) 2508 local layout = fn.winlayout() 2509 eq('row', layout[1]) 2510 eq(2, #layout[2]) 2511 eq(win2, layout[2][1][2]) 2512 api.nvim_win_set_config(win, { 2513 split = 'below', 2514 win = -1, 2515 }) 2516 layout = fn.winlayout() 2517 eq('col', layout[1]) 2518 eq(2, #layout[2]) 2519 eq('row', layout[2][1][1]) 2520 eq(win, layout[2][2][2]) 2521 end) 2522 2523 it('moves splits to other tabpages', function() 2524 local curtab = api.nvim_get_current_tabpage() 2525 local win = api.nvim_open_win(0, false, { split = 'left' }) 2526 command('tabnew') 2527 local tabnr = api.nvim_get_current_tabpage() 2528 command('tabprev') -- return to the initial tab 2529 2530 api.nvim_win_set_config(win, { 2531 split = 'right', 2532 win = api.nvim_tabpage_get_win(tabnr), 2533 }) 2534 2535 eq(tabnr, api.nvim_win_get_tabpage(win)) 2536 -- we are changing the config, the current tabpage should not change 2537 eq(curtab, api.nvim_get_current_tabpage()) 2538 2539 command('tabnext') -- switch to the new tabpage so we can get the layout 2540 local layout = fn.winlayout() 2541 2542 eq({ 2543 'row', 2544 { 2545 { 'leaf', api.nvim_tabpage_get_win(tabnr) }, 2546 { 'leaf', win }, 2547 }, 2548 }, layout) 2549 end) 2550 2551 it('correctly moves curwin when moving curwin to a different tabpage', function() 2552 local curtab = api.nvim_get_current_tabpage() 2553 command('tabnew') 2554 local tab2 = api.nvim_get_current_tabpage() 2555 local tab2_win = api.nvim_get_current_win() 2556 2557 command('tabprev') -- return to the initial tab 2558 2559 local neighbor = api.nvim_get_current_win() 2560 2561 -- create and enter a new split 2562 local win = api.nvim_open_win(0, true, { 2563 vertical = false, 2564 }) 2565 2566 eq(curtab, api.nvim_win_get_tabpage(win)) 2567 2568 eq({ win, neighbor }, api.nvim_tabpage_list_wins(curtab)) 2569 2570 -- move the current win to a different tabpage 2571 api.nvim_win_set_config(win, { 2572 split = 'right', 2573 win = api.nvim_tabpage_get_win(tab2), 2574 }) 2575 2576 eq(curtab, api.nvim_get_current_tabpage()) 2577 2578 -- win should have moved to tab2 2579 eq(tab2, api.nvim_win_get_tabpage(win)) 2580 -- tp_curwin of tab2 should not have changed 2581 eq(tab2_win, api.nvim_tabpage_get_win(tab2)) 2582 -- win lists should be correct 2583 eq({ tab2_win, win }, api.nvim_tabpage_list_wins(tab2)) 2584 eq({ neighbor }, api.nvim_tabpage_list_wins(curtab)) 2585 2586 -- current win should have moved to neighboring win 2587 eq(neighbor, api.nvim_tabpage_get_win(curtab)) 2588 end) 2589 2590 it('splits windows in non-current tabpage', function() 2591 local curtab = api.nvim_get_current_tabpage() 2592 command('tabnew') 2593 local tabnr = api.nvim_get_current_tabpage() 2594 command('tabprev') -- return to the initial tab 2595 2596 local win = api.nvim_open_win(0, false, { 2597 vertical = false, 2598 win = api.nvim_tabpage_get_win(tabnr), 2599 }) 2600 2601 eq(tabnr, api.nvim_win_get_tabpage(win)) 2602 -- since enter = false, the current tabpage should not change 2603 eq(curtab, api.nvim_get_current_tabpage()) 2604 end) 2605 2606 it('moves the current split window', function() 2607 local initial_win = api.nvim_get_current_win() 2608 local win = api.nvim_open_win(0, true, { 2609 vertical = true, 2610 }) 2611 local win2 = api.nvim_open_win(0, true, { 2612 vertical = true, 2613 }) 2614 api.nvim_set_current_win(win) 2615 eq({ 2616 'row', 2617 { 2618 { 'leaf', win2 }, 2619 { 'leaf', win }, 2620 { 'leaf', initial_win }, 2621 }, 2622 }, fn.winlayout()) 2623 2624 api.nvim_win_set_config(0, { 2625 vertical = false, 2626 win = 0, 2627 }) 2628 eq(win, api.nvim_get_current_win()) 2629 eq({ 2630 'col', 2631 { 2632 { 'leaf', win }, 2633 { 2634 'row', 2635 { 2636 { 'leaf', win2 }, 2637 { 'leaf', initial_win }, 2638 }, 2639 }, 2640 }, 2641 }, fn.winlayout()) 2642 2643 api.nvim_set_current_win(win2) 2644 local win3 = api.nvim_open_win(0, true, { 2645 vertical = true, 2646 }) 2647 eq(win3, api.nvim_get_current_win()) 2648 2649 eq({ 2650 'col', 2651 { 2652 { 'leaf', win }, 2653 { 2654 'row', 2655 { 2656 { 'leaf', win3 }, 2657 { 'leaf', win2 }, 2658 { 'leaf', initial_win }, 2659 }, 2660 }, 2661 }, 2662 }, fn.winlayout()) 2663 2664 api.nvim_win_set_config(0, { 2665 vertical = false, 2666 win = 0, 2667 }) 2668 2669 eq(win3, api.nvim_get_current_win()) 2670 eq({ 2671 'col', 2672 { 2673 { 'leaf', win }, 2674 { 2675 'row', 2676 { 2677 { 2678 'col', 2679 { 2680 { 'leaf', win3 }, 2681 { 'leaf', win2 }, 2682 }, 2683 }, 2684 { 'leaf', initial_win }, 2685 }, 2686 }, 2687 }, 2688 }, fn.winlayout()) 2689 end) 2690 2691 it('closing new curwin when moving window to other tabpage works', function() 2692 command('split | tabnew') 2693 local t2_win = api.nvim_get_current_win() 2694 command('tabfirst | autocmd WinEnter * ++once quit') 2695 local t1_move_win = api.nvim_get_current_win() 2696 -- win_set_config fails to switch away from "t1_move_win" because the WinEnter autocmd that 2697 -- closed the window we're switched to returns us to "t1_move_win", as it filled the space. 2698 eq( 2699 'Failed to switch away from window ' .. t1_move_win, 2700 pcall_err(api.nvim_win_set_config, t1_move_win, { win = t2_win, split = 'left' }) 2701 ) 2702 eq(t1_move_win, api.nvim_get_current_win()) 2703 2704 command('split | split | autocmd WinEnter * ++once quit') 2705 t1_move_win = api.nvim_get_current_win() 2706 -- In this case, we closed the window that we got switched to, but doing so didn't switch us 2707 -- back to "t1_move_win", which is fine. 2708 api.nvim_win_set_config(t1_move_win, { win = t2_win, split = 'left' }) 2709 neq(t1_move_win, api.nvim_get_current_win()) 2710 end) 2711 2712 it('messing with "win" or "parent" when moving "win" to other tabpage', function() 2713 command('split | tabnew') 2714 local t2 = api.nvim_get_current_tabpage() 2715 local t2_win1 = api.nvim_get_current_win() 2716 command('split') 2717 local t2_win2 = api.nvim_get_current_win() 2718 command('split') 2719 local t2_win3 = api.nvim_get_current_win() 2720 2721 command('tabfirst | autocmd WinEnter * ++once call nvim_win_close(' .. t2_win1 .. ', 1)') 2722 local cur_win = api.nvim_get_current_win() 2723 eq( 2724 'Windows to split were closed', 2725 pcall_err(api.nvim_win_set_config, 0, { win = t2_win1, split = 'left' }) 2726 ) 2727 eq(cur_win, api.nvim_get_current_win()) 2728 2729 command('split | autocmd WinLeave * ++once quit!') 2730 cur_win = api.nvim_get_current_win() 2731 eq( 2732 'Windows to split were closed', 2733 pcall_err(api.nvim_win_set_config, 0, { win = t2_win2, split = 'left' }) 2734 ) 2735 neq(cur_win, api.nvim_get_current_win()) 2736 2737 exec([[ 2738 split 2739 autocmd WinLeave * ++once 2740 \ call nvim_win_set_config(0, #{relative:'editor', row:0, col:0, width:5, height:5}) 2741 ]]) 2742 cur_win = api.nvim_get_current_win() 2743 eq( 2744 'Floating state of windows to split changed', 2745 pcall_err(api.nvim_win_set_config, 0, { win = t2_win3, split = 'left' }) 2746 ) 2747 eq('editor', api.nvim_win_get_config(0).relative) 2748 eq(cur_win, api.nvim_get_current_win()) 2749 2750 command('autocmd WinLeave * ++once wincmd J') 2751 cur_win = api.nvim_get_current_win() 2752 eq( 2753 'Floating state of windows to split changed', 2754 pcall_err(api.nvim_win_set_config, 0, { win = t2_win3, split = 'left' }) 2755 ) 2756 eq('', api.nvim_win_get_config(0).relative) 2757 eq(cur_win, api.nvim_get_current_win()) 2758 2759 -- Try to make "parent" floating. This should give the same error as before, but because 2760 -- changing a split from another tabpage into a float isn't supported yet, check for that 2761 -- error instead for now. 2762 -- Use ":silent!" to avoid the one second delay from printing the error message. 2763 exec(([[ 2764 autocmd WinLeave * ++once silent! 2765 \ call nvim_win_set_config(%d, #{relative:'editor', row:0, col:0, width:5, height:5}) 2766 ]]):format(t2_win3)) 2767 cur_win = api.nvim_get_current_win() 2768 api.nvim_win_set_config(0, { win = t2_win3, split = 'left' }) 2769 matches( 2770 'Cannot change window from different tabpage into float$', 2771 api.nvim_get_vvar('errmsg') 2772 ) 2773 -- The error doesn't abort moving the window (or maybe it should, if that's wanted?) 2774 neq(cur_win, api.nvim_get_current_win()) 2775 eq(t2, api.nvim_win_get_tabpage(cur_win)) 2776 end) 2777 2778 it('expected autocmds when moving window to other tabpage', function() 2779 local new_curwin = api.nvim_get_current_win() 2780 command('split') 2781 local win = api.nvim_get_current_win() 2782 command('tabnew') 2783 local parent = api.nvim_get_current_win() 2784 exec([[ 2785 tabfirst 2786 let result = [] 2787 autocmd WinEnter * let result += ["Enter", win_getid()] 2788 autocmd WinLeave * let result += ["Leave", win_getid()] 2789 autocmd WinNew * let result += ["New", win_getid()] 2790 ]]) 2791 api.nvim_win_set_config(0, { win = parent, split = 'left' }) 2792 -- Shouldn't see WinNew, as we're not creating any new windows, just moving existing ones. 2793 eq({ 'Leave', win, 'Enter', new_curwin }, eval('result')) 2794 end) 2795 2796 it('no autocmds when moving window within same tabpage', function() 2797 local parent = api.nvim_get_current_win() 2798 exec([[ 2799 split 2800 let result = [] 2801 autocmd WinEnter * let result += ["Enter", win_getid()] 2802 autocmd WinLeave * let result += ["Leave", win_getid()] 2803 autocmd WinNew * let result += ["New", win_getid()] 2804 ]]) 2805 api.nvim_win_set_config(0, { win = parent, split = 'left' }) 2806 -- Shouldn't see any of those events, as we remain in the same window. 2807 eq({}, eval('result')) 2808 end) 2809 2810 it('checks if splitting disallowed', function() 2811 command('split | autocmd WinEnter * ++once call nvim_win_set_config(0, #{split: "right"})') 2812 matches("E242: Can't split a window while closing another$", pcall_err(command, 'quit')) 2813 2814 command('autocmd BufHidden * ++once call nvim_win_set_config(0, #{split: "left"})') 2815 matches( 2816 'E1159: Cannot split a window when closing the buffer$', 2817 pcall_err(command, 'new | quit') 2818 ) 2819 2820 -- OK when using window to different buffer. 2821 local w = api.nvim_get_current_win() 2822 command('autocmd BufHidden * ++once call nvim_win_set_config(' .. w .. ', #{split: "left"})') 2823 command('new | quit') 2824 end) 2825 2826 --- Returns a function to get information about the window layout, sizes and positions of a 2827 --- tabpage. 2828 local function define_tp_info_function() 2829 exec_lua([[ 2830 function tp_info(tp) 2831 return { 2832 layout = vim.fn.winlayout(vim.api.nvim_tabpage_get_number(tp)), 2833 pos_sizes = vim.tbl_map( 2834 function(w) 2835 local pos = vim.fn.win_screenpos(w) 2836 return { 2837 row = pos[1], 2838 col = pos[2], 2839 width = vim.fn.winwidth(w), 2840 height = vim.fn.winheight(w) 2841 } 2842 end, 2843 vim.api.nvim_tabpage_list_wins(tp) 2844 ) 2845 } 2846 end 2847 ]]) 2848 2849 return function(tp) 2850 return exec_lua('return tp_info(...)', tp) 2851 end 2852 end 2853 2854 it('attempt to move window with no room', function() 2855 -- Fill the 2nd tabpage full of windows until we run out of room. 2856 -- Use &laststatus=0 to ensure restoring missing statuslines doesn't affect things. 2857 command('set laststatus=0 | tabnew') 2858 matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)')) 2859 command('vsplit | wincmd | | wincmd p') 2860 local t2 = api.nvim_get_current_tabpage() 2861 local t2_cur_win = api.nvim_get_current_win() 2862 local t2_top_split = fn.win_getid(1) 2863 local t2_bot_split = fn.win_getid(fn.winnr('$')) 2864 local t2_float = api.nvim_open_win( 2865 0, 2866 false, 2867 { relative = 'editor', row = 0, col = 0, width = 10, height = 10 } 2868 ) 2869 local t2_float_config = api.nvim_win_get_config(t2_float) 2870 local tp_info = define_tp_info_function() 2871 local t2_info = tp_info(t2) 2872 matches( 2873 'E36: Not enough room$', 2874 pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'above' }) 2875 ) 2876 matches( 2877 'E36: Not enough room$', 2878 pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'below' }) 2879 ) 2880 matches( 2881 'E36: Not enough room$', 2882 pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'above' }) 2883 ) 2884 matches( 2885 'E36: Not enough room$', 2886 pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'below' }) 2887 ) 2888 matches( 2889 'E36: Not enough room$', 2890 pcall_err(api.nvim_win_set_config, t2_float, { win = t2_top_split, split = 'above' }) 2891 ) 2892 matches( 2893 'E36: Not enough room$', 2894 pcall_err(api.nvim_win_set_config, t2_float, { win = t2_top_split, split = 'below' }) 2895 ) 2896 matches( 2897 'E36: Not enough room$', 2898 pcall_err(api.nvim_win_set_config, t2_float, { win = t2_bot_split, split = 'above' }) 2899 ) 2900 matches( 2901 'E36: Not enough room$', 2902 pcall_err(api.nvim_win_set_config, t2_float, { win = t2_bot_split, split = 'below' }) 2903 ) 2904 eq(t2_cur_win, api.nvim_get_current_win()) 2905 eq(t2_info, tp_info(t2)) 2906 eq(t2_float_config, api.nvim_win_get_config(t2_float)) 2907 2908 -- Try to move windows from the 1st tabpage to the 2nd. 2909 command('tabfirst | split | wincmd _') 2910 local t1 = api.nvim_get_current_tabpage() 2911 local t1_cur_win = api.nvim_get_current_win() 2912 local t1_float = api.nvim_open_win( 2913 0, 2914 false, 2915 { relative = 'editor', row = 5, col = 3, width = 7, height = 6 } 2916 ) 2917 local t1_float_config = api.nvim_win_get_config(t1_float) 2918 local t1_info = tp_info(t1) 2919 matches( 2920 'E36: Not enough room$', 2921 pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'above' }) 2922 ) 2923 matches( 2924 'E36: Not enough room$', 2925 pcall_err(api.nvim_win_set_config, 0, { win = t2_top_split, split = 'below' }) 2926 ) 2927 matches( 2928 'E36: Not enough room$', 2929 pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'above' }) 2930 ) 2931 matches( 2932 'E36: Not enough room$', 2933 pcall_err(api.nvim_win_set_config, 0, { win = t2_bot_split, split = 'below' }) 2934 ) 2935 matches( 2936 'E36: Not enough room$', 2937 pcall_err(api.nvim_win_set_config, t1_float, { win = t2_top_split, split = 'above' }) 2938 ) 2939 matches( 2940 'E36: Not enough room$', 2941 pcall_err(api.nvim_win_set_config, t1_float, { win = t2_top_split, split = 'below' }) 2942 ) 2943 matches( 2944 'E36: Not enough room$', 2945 pcall_err(api.nvim_win_set_config, t1_float, { win = t2_bot_split, split = 'above' }) 2946 ) 2947 matches( 2948 'E36: Not enough room$', 2949 pcall_err(api.nvim_win_set_config, t1_float, { win = t2_bot_split, split = 'below' }) 2950 ) 2951 eq(t1_cur_win, api.nvim_get_current_win()) 2952 eq(t1_info, tp_info(t1)) 2953 eq(t1_float_config, api.nvim_win_get_config(t1_float)) 2954 end) 2955 2956 it('attempt to move window from other tabpage with no room', function() 2957 -- Fill up the 1st tabpage with horizontal splits, then create a 2nd with only a few. Go back 2958 -- to the 1st and try to move windows from the 2nd (while it's non-current) to it. Check that 2959 -- window positions and sizes in the 2nd are unchanged. 2960 command('set laststatus=0') 2961 matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)')) 2962 2963 command('tab split') 2964 local t2 = api.nvim_get_current_tabpage() 2965 local t2_top = api.nvim_get_current_win() 2966 command('belowright split') 2967 local t2_mid_left = api.nvim_get_current_win() 2968 command('belowright vsplit') 2969 local t2_mid_right = api.nvim_get_current_win() 2970 command('split | wincmd J') 2971 local t2_bot = api.nvim_get_current_win() 2972 local tp_info = define_tp_info_function() 2973 local t2_info = tp_info(t2) 2974 eq({ 2975 'col', 2976 { 2977 { 'leaf', t2_top }, 2978 { 2979 'row', 2980 { 2981 { 'leaf', t2_mid_left }, 2982 { 'leaf', t2_mid_right }, 2983 }, 2984 }, 2985 { 'leaf', t2_bot }, 2986 }, 2987 }, t2_info.layout) 2988 2989 local function try_move_t2_wins_to_t1() 2990 for _, w in ipairs({ t2_bot, t2_mid_left, t2_mid_right, t2_top }) do 2991 matches( 2992 'E36: Not enough room$', 2993 pcall_err(api.nvim_win_set_config, w, { win = 0, split = 'below' }) 2994 ) 2995 eq(t2_info, tp_info(t2)) 2996 end 2997 end 2998 command('tabfirst') 2999 try_move_t2_wins_to_t1() 3000 -- Go to the 2nd tabpage to ensure nothing changes after win_comp_pos, last_status, .etc. 3001 -- from enter_tabpage. 3002 command('tabnext') 3003 eq(t2_info, tp_info(t2)) 3004 3005 -- Check things are fine with the global statusline too, for good measure. 3006 -- Set it while the 2nd tabpage is current, so last_status runs for it. 3007 command('set laststatus=3') 3008 t2_info = tp_info(t2) 3009 command('tabfirst') 3010 try_move_t2_wins_to_t1() 3011 end) 3012 3013 it('handles cmdwin and textlock restrictions', function() 3014 command('tabnew') 3015 local t2 = api.nvim_get_current_tabpage() 3016 local t2_win = api.nvim_get_current_win() 3017 command('tabfirst') 3018 local t1_move_win = api.nvim_get_current_win() 3019 command('split') 3020 3021 -- Can't move the cmdwin, or its old curwin to a different tabpage. 3022 local old_curwin = api.nvim_get_current_win() 3023 feed('q:') 3024 eq( 3025 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', 3026 pcall_err(api.nvim_win_set_config, 0, { split = 'left', win = t2_win }) 3027 ) 3028 eq( 3029 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', 3030 pcall_err(api.nvim_win_set_config, old_curwin, { split = 'left', win = t2_win }) 3031 ) 3032 -- But we can move other windows. 3033 api.nvim_win_set_config(t1_move_win, { split = 'left', win = t2_win }) 3034 eq(t2, api.nvim_win_get_tabpage(t1_move_win)) 3035 command('quit!') 3036 3037 -- Can't configure windows such that the cmdwin would become the only non-float. 3038 command('only!') 3039 feed('q:') 3040 eq( 3041 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', 3042 pcall_err( 3043 api.nvim_win_set_config, 3044 old_curwin, 3045 { relative = 'editor', row = 0, col = 0, width = 5, height = 5 } 3046 ) 3047 ) 3048 -- old_curwin is now no longer the only other non-float, so we can make it floating now. 3049 local t1_new_win = api.nvim_open_win( 3050 api.nvim_create_buf(true, true), 3051 false, 3052 { split = 'left', win = old_curwin } 3053 ) 3054 api.nvim_win_set_config( 3055 old_curwin, 3056 { relative = 'editor', row = 0, col = 0, width = 5, height = 5 } 3057 ) 3058 eq('editor', api.nvim_win_get_config(old_curwin).relative) 3059 -- ...which means we shouldn't be able to also make the new window floating too! 3060 eq( 3061 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', 3062 pcall_err( 3063 api.nvim_win_set_config, 3064 t1_new_win, 3065 { relative = 'editor', row = 0, col = 0, width = 5, height = 5 } 3066 ) 3067 ) 3068 -- Nothing ought to stop us from making the cmdwin itself floating, though... 3069 api.nvim_win_set_config(0, { relative = 'editor', row = 0, col = 0, width = 5, height = 5 }) 3070 eq('editor', api.nvim_win_get_config(0).relative) 3071 -- We can't make our new window from before floating too, as it's now the only non-float. 3072 eq( 3073 'Cannot change last window into float', 3074 pcall_err( 3075 api.nvim_win_set_config, 3076 t1_new_win, 3077 { relative = 'editor', row = 0, col = 0, width = 5, height = 5 } 3078 ) 3079 ) 3080 command('quit!') 3081 3082 -- Can't switch away from window before moving it to a different tabpage during textlock. 3083 exec(([[ 3084 new 3085 call setline(1, 'foo') 3086 setlocal debug=throw indentexpr=nvim_win_set_config(0,#{split:'left',win:%d}) 3087 ]]):format(t2_win)) 3088 local cur_win = api.nvim_get_current_win() 3089 matches( 3090 'E565: Not allowed to change text or change window$', 3091 pcall_err(command, 'normal! ==') 3092 ) 3093 eq(cur_win, api.nvim_get_current_win()) 3094 end) 3095 3096 it('updates statusline when moving bottom split', function() 3097 local screen = Screen.new(10, 10) 3098 exec([[ 3099 set laststatus=0 3100 belowright split 3101 call nvim_win_set_config(0, #{split: 'above', win: win_getid(winnr('#'))}) 3102 ]]) 3103 screen:expect([[ 3104 ^ | 3105 {1:~ }|*3 3106 {3:[No Name] }| 3107 | 3108 {1:~ }|*3 3109 | 3110 ]]) 3111 end) 3112 3113 it("updates tp_curwin of moved window's original tabpage", function() 3114 local t1 = api.nvim_get_current_tabpage() 3115 command('tab split | split') 3116 local t2 = api.nvim_get_current_tabpage() 3117 local t2_alt_win = api.nvim_get_current_win() 3118 command('vsplit') 3119 local t2_cur_win = api.nvim_get_current_win() 3120 command('tabprevious') 3121 eq(t2_cur_win, api.nvim_tabpage_get_win(t2)) 3122 3123 -- tp_curwin is unchanged when moved within the same tabpage. 3124 api.nvim_win_set_config(t2_cur_win, { split = 'left', win = t2_alt_win }) 3125 eq(t2_cur_win, api.nvim_tabpage_get_win(t2)) 3126 3127 -- Also unchanged if the move failed. 3128 command('let &winwidth = &columns | let &winminwidth = &columns') 3129 matches( 3130 'E36: Not enough room$', 3131 pcall_err(api.nvim_win_set_config, t2_cur_win, { split = 'left', win = 0 }) 3132 ) 3133 eq(t2_cur_win, api.nvim_tabpage_get_win(t2)) 3134 command('set winminwidth& winwidth&') 3135 3136 -- But is changed if successfully moved to a different tabpage. 3137 api.nvim_win_set_config(t2_cur_win, { split = 'left', win = 0 }) 3138 eq(t2_alt_win, api.nvim_tabpage_get_win(t2)) 3139 eq(t1, api.nvim_win_get_tabpage(t2_cur_win)) 3140 3141 -- Now do it for a float, which has different altwin logic. 3142 command('tabnext') 3143 t2_cur_win = 3144 api.nvim_open_win(0, true, { relative = 'editor', row = 5, col = 5, width = 5, height = 5 }) 3145 eq(t2_alt_win, fn.win_getid(fn.winnr('#'))) 3146 command('tabprevious') 3147 eq(t2_cur_win, api.nvim_tabpage_get_win(t2)) 3148 3149 api.nvim_win_set_config(t2_cur_win, { split = 'left', win = 0 }) 3150 eq(t2_alt_win, api.nvim_tabpage_get_win(t2)) 3151 eq(t1, api.nvim_win_get_tabpage(t2_cur_win)) 3152 end) 3153 3154 it('set_config cannot change "noautocmd" #36409', function() 3155 local cfg = { relative = 'editor', row = 1, col = 1, height = 2, width = 2, noautocmd = true } 3156 local win = api.nvim_open_win(0, false, cfg) 3157 cfg.height = 10 3158 eq(true, pcall(api.nvim_win_set_config, win, cfg)) 3159 cfg.noautocmd = false 3160 eq( 3161 "'noautocmd' cannot be changed with existing windows", 3162 pcall_err(api.nvim_win_set_config, win, cfg) 3163 ) 3164 end) 3165 end) 3166 3167 describe('get_config', function() 3168 it('includes border', function() 3169 local b = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' } 3170 local win = api.nvim_open_win(0, true, { 3171 relative = 'win', 3172 row = 3, 3173 col = 3, 3174 width = 12, 3175 height = 3, 3176 border = b, 3177 }) 3178 3179 local cfg = api.nvim_win_get_config(win) 3180 eq(b, cfg.border) 3181 end) 3182 3183 it('includes border with highlight group', function() 3184 local b = { 3185 { 'a', 'Normal' }, 3186 { 'b', 'Special' }, 3187 { 'c', 'String' }, 3188 { 'd', 'Comment' }, 3189 { 'e', 'Visual' }, 3190 { 'f', 'Error' }, 3191 { 'g', 'Constant' }, 3192 { 'h', 'PreProc' }, 3193 } 3194 local win = api.nvim_open_win(0, true, { 3195 relative = 'win', 3196 row = 3, 3197 col = 3, 3198 width = 12, 3199 height = 3, 3200 border = b, 3201 }) 3202 3203 local cfg = api.nvim_win_get_config(win) 3204 eq(b, cfg.border) 3205 end) 3206 3207 it('includes title and footer', function() 3208 local title = { { 'A', { 'StatusLine', 'TabLine' } }, { 'B' }, { 'C', 'WinBar' } } 3209 local footer = { { 'A', 'WinBar' }, { 'B' }, { 'C', { 'StatusLine', 'TabLine' } } } 3210 local win = api.nvim_open_win(0, true, { 3211 relative = 'win', 3212 row = 3, 3213 col = 3, 3214 width = 12, 3215 height = 3, 3216 border = 'single', 3217 title = title, 3218 footer = footer, 3219 }) 3220 3221 local cfg = api.nvim_win_get_config(win) 3222 eq(title, cfg.title) 3223 eq(footer, cfg.footer) 3224 end) 3225 3226 it('includes split for normal windows', function() 3227 local win = api.nvim_open_win(0, true, { 3228 vertical = true, 3229 win = -1, 3230 }) 3231 eq('left', api.nvim_win_get_config(win).split) 3232 api.nvim_win_set_config(win, { 3233 vertical = false, 3234 win = -1, 3235 }) 3236 eq('above', api.nvim_win_get_config(win).split) 3237 api.nvim_win_set_config(win, { 3238 split = 'below', 3239 win = -1, 3240 }) 3241 eq('below', api.nvim_win_get_config(win).split) 3242 end) 3243 3244 it('includes split when splitting with ex commands', function() 3245 local win = api.nvim_get_current_win() 3246 eq('left', api.nvim_win_get_config(win).split) 3247 3248 command('vsplit') 3249 local win2 = api.nvim_get_current_win() 3250 3251 -- initial window now be marked as right split 3252 -- since it was split with a vertical split 3253 -- and 'splitright' is false by default 3254 eq('right', api.nvim_win_get_config(win).split) 3255 eq('left', api.nvim_win_get_config(win2).split) 3256 3257 api.nvim_set_option_value('splitbelow', true, { 3258 scope = 'global', 3259 }) 3260 api.nvim_win_close(win, true) 3261 command('split') 3262 local win3 = api.nvim_get_current_win() 3263 eq('below', api.nvim_win_get_config(win3).split) 3264 end) 3265 3266 it("includes the correct 'split' option in complex layouts", function() 3267 local initial_win = api.nvim_get_current_win() 3268 local win = api.nvim_open_win(0, false, { 3269 split = 'right', 3270 win = -1, 3271 }) 3272 3273 local win2 = api.nvim_open_win(0, false, { 3274 split = 'below', 3275 win = win, 3276 }) 3277 3278 api.nvim_win_set_config(win2, { 3279 width = 50, 3280 }) 3281 3282 api.nvim_win_set_config(win, { 3283 split = 'left', 3284 win = -1, 3285 }) 3286 3287 local win3 = api.nvim_open_win(0, false, { 3288 split = 'above', 3289 win = -1, 3290 }) 3291 local float = api.nvim_open_win(0, false, { 3292 relative = 'editor', 3293 width = 40, 3294 height = 20, 3295 col = 20, 3296 row = 10, 3297 }) 3298 api.nvim_win_set_config(float, { 3299 split = 'right', 3300 win = -1, 3301 }) 3302 3303 local layout = fn.winlayout() 3304 3305 eq({ 3306 'row', 3307 { 3308 { 3309 'col', 3310 { 3311 { 'leaf', win3 }, 3312 { 3313 'row', 3314 { 3315 { 'leaf', win }, 3316 { 'leaf', initial_win }, 3317 { 'leaf', win2 }, 3318 }, 3319 }, 3320 }, 3321 }, 3322 { 3323 'leaf', 3324 float, 3325 }, 3326 }, 3327 }, layout) 3328 3329 eq('above', api.nvim_win_get_config(win3).split) 3330 eq('left', api.nvim_win_get_config(win).split) 3331 eq('left', api.nvim_win_get_config(initial_win).split) 3332 eq('right', api.nvim_win_get_config(win2).split) 3333 eq('right', api.nvim_win_get_config(float).split) 3334 end) 3335 end) 3336 3337 describe('set_config', function() 3338 it('no crash with invalid title', function() 3339 local win = api.nvim_open_win(0, true, { 3340 width = 10, 3341 height = 10, 3342 relative = 'editor', 3343 row = 10, 3344 col = 10, 3345 title = { { 'test' } }, 3346 border = 'single', 3347 }) 3348 eq( 3349 'title/footer must be string or array', 3350 pcall_err(api.nvim_win_set_config, win, { title = 0 }) 3351 ) 3352 command('redraw!') 3353 assert_alive() 3354 eq( 3355 'title/footer cannot be an empty array', 3356 pcall_err(api.nvim_win_set_config, win, { title = {} }) 3357 ) 3358 command('redraw!') 3359 assert_alive() 3360 end) 3361 3362 it('no crash with invalid footer', function() 3363 local win = api.nvim_open_win(0, true, { 3364 width = 10, 3365 height = 10, 3366 relative = 'editor', 3367 row = 10, 3368 col = 10, 3369 footer = { { 'test' } }, 3370 border = 'single', 3371 }) 3372 eq( 3373 'title/footer must be string or array', 3374 pcall_err(api.nvim_win_set_config, win, { footer = 0 }) 3375 ) 3376 command('redraw!') 3377 assert_alive() 3378 eq( 3379 'title/footer cannot be an empty array', 3380 pcall_err(api.nvim_win_set_config, win, { footer = {} }) 3381 ) 3382 command('redraw!') 3383 assert_alive() 3384 end) 3385 3386 describe('no crash or memory leak', function() 3387 local win 3388 3389 before_each(function() 3390 win = api.nvim_open_win(0, false, { 3391 relative = 'editor', 3392 row = 10, 3393 col = 10, 3394 height = 10, 3395 width = 10, 3396 border = 'single', 3397 title = { { 'OLD_TITLE' } }, 3398 footer = { { 'OLD_FOOTER' } }, 3399 }) 3400 end) 3401 3402 it('with valid title and invalid footer', function() 3403 eq( 3404 'title/footer must be string or array', 3405 pcall_err(api.nvim_win_set_config, win, { 3406 title = { { 'NEW_TITLE' } }, 3407 footer = 0, 3408 }) 3409 ) 3410 command('redraw!') 3411 assert_alive() 3412 eq({ { 'OLD_TITLE' } }, api.nvim_win_get_config(win).title) 3413 end) 3414 3415 it('with invalid title and valid footer', function() 3416 eq( 3417 'title/footer must be string or array', 3418 pcall_err(api.nvim_win_set_config, win, { 3419 title = 0, 3420 footer = { { 'NEW_FOOTER' } }, 3421 }) 3422 ) 3423 command('redraw!') 3424 assert_alive() 3425 eq({ { 'OLD_FOOTER' } }, api.nvim_win_get_config(win).footer) 3426 end) 3427 end) 3428 3429 it('cannot split from a float', function() 3430 local win = api.nvim_get_current_win() 3431 local float_win = api.nvim_open_win(0, true, { 3432 relative = 'editor', 3433 width = 10, 3434 height = 10, 3435 row = 10, 3436 col = 10, 3437 }) 3438 eq( 3439 'Cannot split a floating window', 3440 pcall_err(api.nvim_win_set_config, win, { win = float_win, split = 'right' }) 3441 ) 3442 eq( 3443 'Cannot split a floating window', 3444 pcall_err(api.nvim_win_set_config, win, { win = 0, split = 'right' }) 3445 ) 3446 end) 3447 3448 it('cannot move autocmd window between tabpages', function() 3449 local win_type, split_ok, err = exec_lua(function() 3450 local other_tp_win = vim.api.nvim_get_current_win() 3451 vim.cmd.tabnew() 3452 3453 local win_type, split_ok, err 3454 vim.api.nvim_buf_call(vim.api.nvim_create_buf(true, true), function() 3455 win_type = vim.fn.win_gettype() 3456 split_ok, err = 3457 pcall(vim.api.nvim_win_set_config, 0, { win = other_tp_win, split = 'right' }) 3458 end) 3459 return win_type, split_ok, err 3460 end) 3461 eq('autocmd', win_type) 3462 eq({ false, 'Cannot move autocmd window to another tabpage' }, { split_ok, err }) 3463 end) 3464 3465 it('cannot move cmdwin between tabpages', function() 3466 local other_tp_win = api.nvim_get_current_win() 3467 command('tabnew') 3468 local old_curwin = api.nvim_get_current_win() 3469 feed('q:') 3470 eq('command', fn.win_gettype()) 3471 eq( 3472 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', 3473 pcall_err(api.nvim_win_set_config, 0, { win = other_tp_win, split = 'right' }) 3474 ) 3475 -- Shouldn't move the old curwin from before we entered the cmdwin either. 3476 eq( 3477 'E11: Invalid in command-line window; <CR> executes, CTRL-C quits', 3478 pcall_err(api.nvim_win_set_config, old_curwin, { win = other_tp_win, split = 'right' }) 3479 ) 3480 end) 3481 3482 it('minimal style persists through float-to-split and buffer change #37067', function() 3483 -- Set all options globally 3484 command('set number relativenumber cursorline cursorcolumn spell list') 3485 command('set signcolumn=yes colorcolumn=80 statuscolumn=%l foldcolumn=2') 3486 local buf1 = api.nvim_create_buf(false, true) 3487 local win = api.nvim_open_win(buf1, true, { 3488 relative = 'editor', 3489 width = 10, 3490 height = 10, 3491 row = 5, 3492 col = 5, 3493 style = 'minimal', 3494 }) 3495 -- Convert to split then change buffer 3496 api.nvim_win_set_config(win, { split = 'below', win = -1 }) 3497 local buf2 = api.nvim_create_buf(false, true) 3498 api.nvim_win_set_buf(win, buf2) 3499 eq(false, api.nvim_get_option_value('number', { win = win })) 3500 eq(false, api.nvim_get_option_value('relativenumber', { win = win })) 3501 eq(false, api.nvim_get_option_value('cursorline', { win = win })) 3502 eq(false, api.nvim_get_option_value('cursorcolumn', { win = win })) 3503 eq(false, api.nvim_get_option_value('spell', { win = win })) 3504 eq(false, api.nvim_get_option_value('list', { win = win })) 3505 eq('0', api.nvim_get_option_value('foldcolumn', { win = win })) 3506 eq('auto', api.nvim_get_option_value('signcolumn', { win = win })) 3507 eq('', api.nvim_get_option_value('colorcolumn', { win = win })) 3508 eq('', api.nvim_get_option_value('statuscolumn', { win = win })) 3509 end) 3510 end) 3511 end)