buffer_updates_spec.lua (57586B)
1 -- Test suite for testing interactions with API bindings 2 local t = require('test.testutil') 3 local n = require('test.functional.testnvim')() 4 local Screen = require('test.functional.ui.screen') 5 6 local command = n.command 7 local api = n.api 8 local fn = n.fn 9 local clear = n.clear 10 local eq = t.eq 11 local fail = t.fail 12 local exec_lua = n.exec_lua 13 local feed = n.feed 14 local expect_events = t.expect_events 15 local write_file = t.write_file 16 local dedent = t.dedent 17 local matches = t.matches 18 local pcall_err = t.pcall_err 19 20 local origlines = { 21 'original line 1', 22 'original line 2', 23 'original line 3', 24 'original line 4', 25 'original line 5', 26 'original line 6', 27 ' indented line', 28 } 29 30 before_each(function() 31 clear() 32 exec_lua(function() 33 local events = {} 34 35 function _G.test_register(bufnr, evname, id, changedtick, utf_sizes, preview) 36 local function callback(...) 37 table.insert(events, { id, ... }) 38 if _G.test_unreg == id then 39 return true 40 end 41 end 42 local opts = { 43 [evname] = callback, 44 on_detach = callback, 45 on_reload = callback, 46 utf_sizes = utf_sizes, 47 preview = preview, 48 } 49 if changedtick then 50 opts.on_changedtick = callback 51 end 52 vim.api.nvim_buf_attach(bufnr, false, opts) 53 end 54 55 function _G.get_events() 56 local ret_events = events 57 events = {} 58 return ret_events 59 end 60 end) 61 end) 62 63 describe('lua: nvim_buf_attach on_lines', function() 64 local function setup_eventcheck(verify, utf_sizes, lines) 65 local lastsize 66 api.nvim_buf_set_lines(0, 0, -1, true, lines) 67 if verify then 68 lastsize = api.nvim_buf_get_offset(0, api.nvim_buf_line_count(0)) 69 end 70 exec_lua('return test_register(...)', 0, 'on_lines', 'test1', false, utf_sizes) 71 local verify_name = 'test1' 72 73 local function check_events(expected) 74 local events = exec_lua('return get_events(...)') 75 if utf_sizes then 76 -- this test case uses ASCII only, so sizes should be the same. 77 -- Unicode is tested below. 78 for _, event in ipairs(expected) do 79 event[9] = event[9] or event[8] 80 event[10] = event[10] or event[9] 81 end 82 end 83 expect_events(expected, events, 'line updates') 84 if verify then 85 for _, event in ipairs(events) do 86 if event[1] == verify_name and event[2] == 'lines' then 87 local startline, endline = event[5], event[7] 88 local newrange = api.nvim_buf_get_offset(0, endline) 89 - api.nvim_buf_get_offset(0, startline) 90 local newsize = api.nvim_buf_get_offset(0, api.nvim_buf_line_count(0)) 91 local oldrange = newrange + lastsize - newsize 92 eq(oldrange, event[8]) 93 lastsize = newsize 94 end 95 end 96 end 97 end 98 return check_events, function(new) 99 verify_name = new 100 end 101 end 102 103 -- verifying the sizes with nvim_buf_get_offset is nice (checks we cannot 104 -- assert the wrong thing), but masks errors with unflushed lines (as 105 -- nvim_buf_get_offset forces a flush of the memline). To be safe run the 106 -- test both ways. 107 local function check(verify, utf_sizes) 108 local check_events, verify_name = setup_eventcheck(verify, utf_sizes, origlines) 109 110 local tick = api.nvim_buf_get_changedtick(0) 111 command('set autoindent') 112 command('normal! GyyggP') 113 tick = tick + 1 114 check_events { { 'test1', 'lines', 1, tick, 0, 0, 1, 0 } } 115 116 api.nvim_buf_set_lines(0, 3, 5, true, { 'changed line' }) 117 tick = tick + 1 118 check_events { { 'test1', 'lines', 1, tick, 3, 5, 4, 32 } } 119 120 exec_lua('return test_register(...)', 0, 'on_lines', 'test2', true, utf_sizes) 121 tick = tick + 1 122 command('undo') 123 124 -- plugins can opt in to receive changedtick events, or choose 125 -- to only receive actual changes. 126 check_events { 127 { 'test1', 'lines', 1, tick, 3, 4, 5, 13 }, 128 { 'test2', 'lines', 1, tick, 3, 4, 5, 13 }, 129 { 'test2', 'changedtick', 1, tick + 1 }, 130 } 131 tick = tick + 1 132 133 tick = tick + 1 134 command('redo') 135 check_events { 136 { 'test1', 'lines', 1, tick, 3, 5, 4, 32 }, 137 { 'test2', 'lines', 1, tick, 3, 5, 4, 32 }, 138 { 'test2', 'changedtick', 1, tick + 1 }, 139 } 140 tick = tick + 1 141 142 tick = tick + 1 143 command('undo!') 144 check_events { 145 { 'test1', 'lines', 1, tick, 3, 4, 5, 13 }, 146 { 'test2', 'lines', 1, tick, 3, 4, 5, 13 }, 147 { 'test2', 'changedtick', 1, tick + 1 }, 148 } 149 tick = tick + 1 150 151 -- simulate next callback returning true 152 exec_lua("test_unreg = 'test1'") 153 154 api.nvim_buf_set_lines(0, 6, 7, true, { 'x1', 'x2', 'x3' }) 155 tick = tick + 1 156 157 -- plugins can opt in to receive changedtick events, or choose 158 -- to only receive actual changes. 159 check_events { 160 { 'test1', 'lines', 1, tick, 6, 7, 9, 16 }, 161 { 'test2', 'lines', 1, tick, 6, 7, 9, 16 }, 162 } 163 164 verify_name 'test2' 165 166 api.nvim_buf_set_lines(0, 1, 1, true, { 'added' }) 167 tick = tick + 1 168 check_events { { 'test2', 'lines', 1, tick, 1, 1, 2, 0 } } 169 170 feed('wix') 171 tick = tick + 1 172 check_events { { 'test2', 'lines', 1, tick, 4, 5, 5, 16 } } 173 174 -- check hot path for multiple insert 175 feed('yz') 176 tick = tick + 1 177 check_events { { 'test2', 'lines', 1, tick, 4, 5, 5, 17 } } 178 179 feed('<bs>') 180 tick = tick + 1 181 check_events { { 'test2', 'lines', 1, tick, 4, 5, 5, 19 } } 182 183 feed('<esc>Go') 184 tick = tick + 1 185 check_events { { 'test2', 'lines', 1, tick, 11, 11, 12, 0 } } 186 187 feed('x') 188 tick = tick + 1 189 check_events { { 'test2', 'lines', 1, tick, 11, 12, 12, 5 } } 190 191 command('bwipe!') 192 check_events { { 'test2', 'detach', 1 } } 193 end 194 195 it('works', function() 196 check(false) 197 end) 198 199 it('works with verify', function() 200 check(true) 201 end) 202 203 it('works with utf_sizes and ASCII text', function() 204 check(false, true) 205 end) 206 207 local function check_unicode(verify) 208 local unicode_text = { 209 'ascii text', 210 'latin text åäö', 211 'BMP text ɧ αλφά', 212 'BMP text 汉语 ↥↧', 213 'SMP 🤦 🦄🦃', 214 'combining å بِيَّة', 215 } 216 local check_events, verify_name = setup_eventcheck(verify, true, unicode_text) 217 218 local tick = api.nvim_buf_get_changedtick(0) 219 220 feed('ggdd') 221 tick = tick + 1 222 check_events { { 'test1', 'lines', 1, tick, 0, 1, 0, 11, 11, 11 } } 223 224 feed('A<bs>') 225 tick = tick + 1 226 check_events { { 'test1', 'lines', 1, tick, 0, 1, 1, 18, 15, 15 } } 227 228 feed('<esc>jylp') 229 tick = tick + 1 230 check_events { { 'test1', 'lines', 1, tick, 1, 2, 2, 21, 16, 16 } } 231 232 feed('+eea<cr>') 233 tick = tick + 1 234 check_events { { 'test1', 'lines', 1, tick, 2, 3, 4, 23, 15, 15 } } 235 236 feed('<esc>jdw') 237 tick = tick + 1 238 -- non-BMP chars count as 2 UTF-2 codeunits 239 check_events { { 'test1', 'lines', 1, tick, 4, 5, 5, 18, 9, 12 } } 240 241 feed('+rx') 242 tick = tick + 1 243 -- count the individual codepoints of a composed character. 244 check_events { { 'test1', 'lines', 1, tick, 5, 6, 6, 27, 20, 20 } } 245 246 feed('kJ') 247 tick = tick + 1 248 -- verification fails with multiple line updates, sorry about that 249 verify_name '' 250 -- NB: this is inefficient (but not really wrong). 251 check_events { 252 { 'test1', 'lines', 1, tick, 4, 5, 5, 14, 5, 8 }, 253 { 'test1', 'lines', 1, tick + 1, 5, 6, 5, 27, 20, 20 }, 254 } 255 end 256 257 it('works with utf_sizes and unicode text', function() 258 check_unicode(false) 259 end) 260 261 it('works with utf_sizes and unicode text with verify', function() 262 check_unicode(true) 263 end) 264 265 it('has valid cursor position while shifting', function() 266 api.nvim_buf_set_lines(0, 0, -1, true, { 'line1' }) 267 exec_lua(function() 268 vim.api.nvim_buf_attach(0, false, { 269 on_lines = function() 270 vim.api.nvim_set_var('listener_cursor_line', vim.api.nvim_win_get_cursor(0)[1]) 271 end, 272 }) 273 end) 274 feed('>>') 275 eq(1, api.nvim_get_var('listener_cursor_line')) 276 end) 277 278 it('has valid cursor position while deleting lines', function() 279 api.nvim_buf_set_lines(0, 0, -1, true, { 'line_1', 'line_2', 'line_3', 'line_4' }) 280 api.nvim_win_set_cursor(0, { 2, 0 }) 281 eq(2, api.nvim_win_get_cursor(0)[1]) 282 api.nvim_buf_set_lines(0, 0, -1, true, { 'line_1', 'line_2', 'line_3' }) 283 eq(2, api.nvim_win_get_cursor(0)[1]) 284 end) 285 286 it('#12718 lnume', function() 287 api.nvim_buf_set_lines(0, 0, -1, true, { '1', '2', '3' }) 288 exec_lua(function() 289 vim.api.nvim_buf_attach(0, false, { 290 on_lines = function(...) 291 vim.api.nvim_set_var('linesev', { ... }) 292 end, 293 }) 294 end) 295 feed('1G0') 296 feed('y<C-v>2j') 297 feed('G0') 298 feed('p') 299 -- Is the last arg old_byte_size correct? Doesn't matter for this PR 300 eq({ 'lines', 1, 4, 2, 3, 5, 4 }, api.nvim_get_var('linesev')) 301 302 feed('2G0') 303 feed('p') 304 eq({ 'lines', 1, 5, 1, 4, 4, 8 }, api.nvim_get_var('linesev')) 305 306 feed('1G0') 307 feed('P') 308 eq({ 'lines', 1, 6, 0, 3, 3, 9 }, api.nvim_get_var('linesev')) 309 end) 310 311 it('nvim_buf_call() from callback does not cause wrong Normal mode CTRL-A #16729', function() 312 exec_lua(function() 313 vim.api.nvim_buf_attach(0, false, { 314 on_lines = function() 315 vim.api.nvim_buf_call(0, function() end) 316 end, 317 }) 318 end) 319 feed('itest123<Esc><C-A>') 320 eq('test124', api.nvim_get_current_line()) 321 end) 322 323 it('setting extmark in on_lines callback works', function() 324 local screen = Screen.new(40, 6) 325 326 api.nvim_buf_set_lines(0, 0, -1, true, { 'aaa', 'bbb', 'ccc' }) 327 exec_lua(function() 328 local ns = vim.api.nvim_create_namespace('') 329 vim.api.nvim_buf_attach(0, false, { 330 on_lines = function(_, _, _, row, _, end_row) 331 vim.api.nvim_buf_clear_namespace(0, ns, row, end_row) 332 for i = row, end_row - 1 do 333 vim.api.nvim_buf_set_extmark(0, ns, i, 0, { 334 virt_text = { { 'NEW' .. tostring(i), 'WarningMsg' } }, 335 }) 336 end 337 end, 338 }) 339 end) 340 341 feed('o') 342 screen:expect({ 343 grid = [[ 344 aaa | 345 ^ {19:NEW1} | 346 bbb | 347 ccc | 348 {1:~ }| 349 {5:-- INSERT --} | 350 ]], 351 }) 352 feed('<CR>') 353 screen:expect({ 354 grid = [[ 355 aaa | 356 {19:NEW1} | 357 ^ {19:NEW2} | 358 bbb | 359 ccc | 360 {5:-- INSERT --} | 361 ]], 362 }) 363 end) 364 365 it('line lengths are correct when pressing TAB with folding #29119', function() 366 api.nvim_buf_set_lines(0, 0, -1, true, { 'a', 'b' }) 367 368 exec_lua(function() 369 _G.res = {} 370 vim.o.foldmethod = 'indent' 371 vim.o.softtabstop = -1 372 vim.api.nvim_buf_attach(0, false, { 373 on_lines = function(_, bufnr, _, row, _, end_row) 374 local lines = vim.api.nvim_buf_get_lines(bufnr, row, end_row, true) 375 table.insert(_G.res, lines) 376 end, 377 }) 378 end) 379 380 feed('i<Tab>') 381 eq({ '\ta' }, exec_lua('return _G.res[#_G.res]')) 382 end) 383 384 it('quickfix buffer send change', function() 385 command('copen') 386 exec_lua(function() 387 vim.api.nvim_buf_attach(vim.api.nvim_get_current_buf(), false, { 388 on_lines = function(...) 389 vim.g.qf_on_lines = { ... } 390 end, 391 on_bytes = function(...) 392 vim.g.qf_on_bytes = { ... } 393 end, 394 }) 395 end) 396 command('caddexpr "foo"') 397 eq({ 'bytes', 2, 2, 0, 0, 0, 0, 0, 0, 0, 6, 6 }, api.nvim_get_var('qf_on_bytes')) 398 eq({ 'lines', 2, 3, 0, 1, 1, 1 }, api.nvim_get_var('qf_on_lines')) 399 400 command('caddexpr "bar"') 401 eq({ 'bytes', 2, 3, 0, 6, 6, 0, 0, 0, 1, 6, 6 }, api.nvim_get_var('qf_on_bytes')) 402 eq({ 'lines', 2, 4, 1, 1, 2, 0 }, api.nvim_get_var('qf_on_lines')) 403 404 command('caddexpr ["line1", "line2", "line3"]') 405 eq({ 'bytes', 2, 4, 1, 6, 13, 0, 0, 0, 3, 8, 26 }, api.nvim_get_var('qf_on_bytes')) 406 eq({ 'lines', 2, 5, 2, 2, 5, 0 }, api.nvim_get_var('qf_on_lines')) 407 408 command('cexpr "replace"') 409 eq({ 'bytes', 2, 5, 0, 0, 0, 4, 0, 40, 0, 10, 10 }, api.nvim_get_var('qf_on_bytes')) 410 eq({ 'lines', 2, 6, 0, 5, 1, 42 }, api.nvim_get_var('qf_on_lines')) 411 end) 412 413 it('single-line visual block insert should not trigger extra on_lines #22009', function() 414 exec_lua(function() 415 _G.res = {} 416 vim.api.nvim_buf_attach(0, false, { 417 on_lines = function(_, bufnr, _, first, last, last_updated, _, _, _) 418 _G.res = { bufnr, first, last, last_updated } 419 end, 420 }) 421 end) 422 feed('<C-v>I <ESC>') 423 eq({ api.nvim_get_current_buf(), 0, 1, 1 }, exec_lua('return _G.res')) 424 end) 425 426 it('prompt buffer', function() 427 local check_events = setup_eventcheck(false, nil, {}) 428 api.nvim_set_option_value('buftype', 'prompt', {}) 429 feed('i') 430 check_events { 431 { 'test1', 'lines', 1, 4, 0, 1, 1, 1 }, 432 } 433 fn.prompt_setprompt('', 'foo > ') 434 check_events { 435 { 'test1', 'lines', 1, 5, 0, 1, 1, 3 }, 436 } 437 feed('hello') 438 check_events { 439 { 'test1', 'lines', 1, 6, 0, 1, 1, 7 }, 440 } 441 fn.prompt_setprompt('', 'super-foo > ') 442 check_events { 443 { 'test1', 'lines', 1, 7, 0, 1, 1, 12 }, 444 } 445 eq({ 'super-foo > hello' }, api.nvim_buf_get_lines(0, 0, -1, true)) 446 -- Do this in the same event. 447 exec_lua(function() 448 vim.fn.setpos("':", { 0, 1, 999, 0 }) 449 vim.fn.prompt_setprompt('', 'discard > ') 450 end) 451 check_events { 452 { 'test1', 'lines', 1, 8, 0, 1, 1, 18 }, 453 } 454 eq({ 'discard > ' }, api.nvim_buf_get_lines(0, 0, -1, true)) 455 feed('hello<S-CR>there') 456 check_events { 457 { 'test1', 'lines', 1, 9, 0, 1, 1, 11 }, 458 { 'test1', 'lines', 1, 10, 0, 1, 2, 16 }, 459 { 'test1', 'lines', 1, 11, 1, 2, 2, 1 }, 460 } 461 fn.prompt_setprompt('', 'foo > ') 462 check_events { 463 { 'test1', 'lines', 1, 12, 0, 1, 1, 16 }, 464 } 465 eq({ 'foo > hello', 'there' }, api.nvim_buf_get_lines(0, 0, -1, true)) 466 467 -- init_prompt uses appended_lines_mark when appending to fix prompt. 468 api.nvim_buf_set_lines(0, 0, -1, true, { 'hi' }) 469 eq({ 'hi', 'foo > ' }, api.nvim_buf_get_lines(0, 0, -1, true)) 470 check_events { 471 { 'test1', 'lines', 1, 13, 0, 2, 1, 18 }, 472 { 'test1', 'lines', 1, 14, 1, 1, 2, 0 }, 473 } 474 end) 475 end) 476 477 describe('lua: nvim_buf_attach on_bytes', function() 478 -- verifying the sizes with nvim_buf_get_offset is nice (checks we cannot 479 -- assert the wrong thing), but masks errors with unflushed lines (as 480 -- nvim_buf_get_offset forces a flush of the memline). To be safe run the 481 -- test both ways. 482 local function setup_eventcheck(verify, start_txt) 483 if start_txt then 484 api.nvim_buf_set_lines(0, 0, -1, true, start_txt) 485 else 486 start_txt = api.nvim_buf_get_lines(0, 0, -1, true) 487 end 488 local shadowbytes = table.concat(start_txt, '\n') .. '\n' 489 -- TODO: while we are brewing the real strong coffee, 490 -- verify should check buf_get_offset after every check_events 491 if verify then 492 local len = api.nvim_buf_get_offset(0, api.nvim_buf_line_count(0)) 493 eq(len == -1 and 1 or len, string.len(shadowbytes)) 494 end 495 exec_lua('return test_register(...)', 0, 'on_bytes', 'test1', false, false, true) 496 api.nvim_buf_get_changedtick(0) 497 498 local verify_name = 'test1' 499 local function check_events(expected) 500 local events = exec_lua('return get_events(...)') 501 expect_events(expected, events, 'byte updates') 502 503 if not verify then 504 return 505 end 506 507 for _, event in ipairs(events) do 508 for _, elem in ipairs(event) do 509 if type(elem) == 'number' and elem < 0 then 510 fail(string.format('Received event has negative values')) 511 end 512 end 513 514 if event[1] == verify_name and event[2] == 'bytes' then 515 local _, _, _, _, _, _, start_byte, _, _, old_byte, _, _, new_byte = unpack(event) 516 local before = string.sub(shadowbytes, 1, start_byte) 517 -- no text in the tests will contain 0xff bytes (invalid UTF-8) 518 -- so we can use it as marker for unknown bytes 519 local unknown = string.rep('\255', new_byte) 520 local after = string.sub(shadowbytes, start_byte + old_byte + 1) 521 shadowbytes = before .. unknown .. after 522 elseif event[1] == verify_name and event[2] == 'reload' then 523 shadowbytes = table.concat(api.nvim_buf_get_lines(0, 0, -1, true), '\n') .. '\n' 524 end 525 end 526 527 local text = api.nvim_buf_get_lines(0, 0, -1, true) 528 local bytes = table.concat(text, '\n') .. '\n' 529 530 eq( 531 string.len(bytes), 532 string.len(shadowbytes), 533 '\non_bytes: total bytecount of buffer is wrong' 534 ) 535 for i = 1, string.len(shadowbytes) do 536 local shadowbyte = string.sub(shadowbytes, i, i) 537 if shadowbyte ~= '\255' then 538 eq(string.sub(bytes, i, i), shadowbyte, i) 539 end 540 end 541 end 542 543 return check_events 544 end 545 546 -- Yes, we can do both 547 local function do_both(verify) 548 it('single and multiple join', function() 549 local check_events = setup_eventcheck(verify, origlines) 550 feed 'ggJ' 551 check_events { 552 { 'test1', 'bytes', 1, 3, 0, 15, 15, 1, 0, 1, 0, 1, 1 }, 553 } 554 555 feed '3J' 556 check_events { 557 { 'test1', 'bytes', 1, 5, 0, 31, 31, 1, 0, 1, 0, 1, 1 }, 558 { 'test1', 'bytes', 1, 5, 0, 47, 47, 1, 0, 1, 0, 1, 1 }, 559 } 560 end) 561 562 it('opening lines', function() 563 local check_events = setup_eventcheck(verify, origlines) 564 api.nvim_set_option_value('autoindent', false, {}) 565 feed 'Go' 566 check_events { 567 { 'test1', 'bytes', 1, 3, 7, 0, 114, 0, 0, 0, 1, 0, 1 }, 568 } 569 feed '<cr>' 570 check_events { 571 { 'test1', 'bytes', 1, 4, 7, 0, 114, 0, 0, 0, 1, 0, 1 }, 572 } 573 end) 574 575 it('opening lines with autoindent', function() 576 local check_events = setup_eventcheck(verify, origlines) 577 api.nvim_set_option_value('autoindent', true, {}) 578 feed 'Go' 579 check_events { 580 { 'test1', 'bytes', 1, 3, 7, 0, 114, 0, 0, 0, 1, 0, 5 }, 581 } 582 feed '<cr>' 583 check_events { 584 { 'test1', 'bytes', 1, 4, 7, 0, 114, 0, 4, 4, 0, 0, 0 }, 585 { 'test1', 'bytes', 1, 4, 7, 0, 114, 0, 0, 0, 1, 4, 5 }, 586 } 587 end) 588 589 it('setline(num, line)', function() 590 local check_events = setup_eventcheck(verify, origlines) 591 fn.setline(2, 'babla') 592 check_events { 593 { 'test1', 'bytes', 1, 3, 1, 0, 16, 0, 15, 15, 0, 5, 5 }, 594 } 595 596 fn.setline(2, { 'foo', 'bar' }) 597 check_events { 598 { 'test1', 'bytes', 1, 4, 1, 0, 16, 0, 5, 5, 0, 3, 3 }, 599 { 'test1', 'bytes', 1, 5, 2, 0, 20, 0, 15, 15, 0, 3, 3 }, 600 } 601 602 local buf_len = api.nvim_buf_line_count(0) 603 fn.setline(buf_len + 1, 'baz') 604 check_events { 605 { 'test1', 'bytes', 1, 6, 7, 0, 90, 0, 0, 0, 1, 0, 4 }, 606 } 607 end) 608 609 it('continuing comments with fo=or', function() 610 local check_events = setup_eventcheck(verify, { '// Comment' }) 611 api.nvim_set_option_value('formatoptions', 'ro', {}) 612 api.nvim_set_option_value('filetype', 'c', {}) 613 feed 'A<CR>' 614 check_events { 615 { 'test1', 'bytes', 1, 3, 0, 10, 10, 0, 0, 0, 1, 3, 4 }, 616 } 617 618 feed '<ESC>' 619 check_events { 620 { 'test1', 'bytes', 1, 4, 1, 2, 13, 0, 1, 1, 0, 0, 0 }, 621 } 622 623 feed 'ggo' -- goto first line to continue testing 624 check_events { 625 { 'test1', 'bytes', 1, 5, 1, 0, 11, 0, 0, 0, 1, 0, 4 }, 626 } 627 628 feed '<CR>' 629 check_events { 630 { 'test1', 'bytes', 1, 6, 1, 2, 13, 0, 1, 1, 0, 0, 0 }, 631 { 'test1', 'bytes', 1, 6, 1, 2, 13, 0, 0, 0, 1, 3, 4 }, 632 } 633 end) 634 635 it('editing empty buffers', function() 636 local check_events = setup_eventcheck(verify, {}) 637 638 feed 'ia' 639 check_events { 640 { 'test1', 'bytes', 1, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1 }, 641 } 642 end) 643 644 it('deleting lines', function() 645 local check_events = setup_eventcheck(verify, origlines) 646 647 feed('dd') 648 649 check_events { 650 { 'test1', 'bytes', 1, 3, 0, 0, 0, 1, 0, 16, 0, 0, 0 }, 651 } 652 653 feed('d2j') 654 655 check_events { 656 { 'test1', 'bytes', 1, 4, 0, 0, 0, 3, 0, 48, 0, 0, 0 }, 657 } 658 659 feed('ld<c-v>2j') 660 661 check_events { 662 { 'test1', 'bytes', 1, 5, 0, 1, 1, 0, 1, 1, 0, 0, 0 }, 663 { 'test1', 'bytes', 1, 5, 1, 1, 16, 0, 1, 1, 0, 0, 0 }, 664 { 'test1', 'bytes', 1, 5, 2, 1, 31, 0, 1, 1, 0, 0, 0 }, 665 } 666 667 feed('vjwd') 668 669 check_events { 670 { 'test1', 'bytes', 1, 10, 0, 1, 1, 1, 9, 23, 0, 0, 0 }, 671 } 672 end) 673 674 it('changing lines', function() 675 local check_events = setup_eventcheck(verify, origlines) 676 677 feed 'cc' 678 check_events { 679 { 'test1', 'bytes', 1, 3, 0, 0, 0, 0, 15, 15, 0, 0, 0 }, 680 } 681 682 feed '<ESC>' 683 check_events {} 684 685 feed 'c3j' 686 check_events { 687 { 'test1', 'bytes', 1, 4, 1, 0, 1, 3, 0, 48, 0, 0, 0 }, 688 } 689 end) 690 691 it('visual charwise paste', function() 692 local check_events = setup_eventcheck(verify, { '1234567890' }) 693 fn.setreg('a', '___') 694 695 feed '1G1|vll' 696 check_events {} 697 698 feed '"ap' 699 check_events { 700 { 'test1', 'bytes', 1, 3, 0, 0, 0, 0, 3, 3, 0, 0, 0 }, 701 { 'test1', 'bytes', 1, 5, 0, 0, 0, 0, 0, 0, 0, 3, 3 }, 702 } 703 end) 704 705 it('blockwise paste', function() 706 local check_events = setup_eventcheck(verify, { '1', '2', '3' }) 707 feed('1G0') 708 feed('y<C-v>2j') 709 feed('G0') 710 feed('p') 711 check_events { 712 { 'test1', 'bytes', 1, 3, 2, 1, 5, 0, 0, 0, 0, 1, 1 }, 713 { 'test1', 'bytes', 1, 3, 3, 0, 7, 0, 0, 0, 0, 3, 3 }, 714 { 'test1', 'bytes', 1, 3, 4, 0, 10, 0, 0, 0, 0, 3, 3 }, 715 } 716 717 feed('2G0') 718 feed('p') 719 check_events { 720 { 'test1', 'bytes', 1, 4, 1, 1, 3, 0, 0, 0, 0, 1, 1 }, 721 { 'test1', 'bytes', 1, 4, 2, 1, 6, 0, 0, 0, 0, 1, 1 }, 722 { 'test1', 'bytes', 1, 4, 3, 1, 10, 0, 0, 0, 0, 1, 1 }, 723 } 724 725 feed('1G0') 726 feed('P') 727 check_events { 728 { 'test1', 'bytes', 1, 5, 0, 0, 0, 0, 0, 0, 0, 1, 1 }, 729 { 'test1', 'bytes', 1, 5, 1, 0, 3, 0, 0, 0, 0, 1, 1 }, 730 { 'test1', 'bytes', 1, 5, 2, 0, 7, 0, 0, 0, 0, 1, 1 }, 731 } 732 end) 733 734 it('linewise paste', function() 735 local check_events = setup_eventcheck(verify, origlines) 736 737 feed 'yyp' 738 check_events { 739 { 'test1', 'bytes', 1, 3, 1, 0, 16, 0, 0, 0, 1, 0, 16 }, 740 } 741 742 feed 'Gyyp' 743 check_events { 744 { 'test1', 'bytes', 1, 4, 8, 0, 130, 0, 0, 0, 1, 0, 18 }, 745 } 746 end) 747 748 it('inccomand=nosplit and substitute', function() 749 local check_events = setup_eventcheck(verify, { 'abcde', '12345' }) 750 api.nvim_set_option_value('inccommand', 'nosplit', {}) 751 752 -- linewise substitute 753 feed(':%s/bcd/') 754 check_events { 755 { 'test1', 'bytes', 1, 3, 0, 1, 1, 0, 3, 3, 0, 0, 0 }, 756 { 'test1', 'bytes', 1, 5, 0, 1, 1, 0, 0, 0, 0, 3, 3 }, 757 } 758 759 feed('a') 760 check_events { 761 { 'test1', 'bytes', 1, 3, 0, 1, 1, 0, 3, 3, 0, 1, 1 }, 762 { 'test1', 'bytes', 1, 5, 0, 1, 1, 0, 1, 1, 0, 3, 3 }, 763 } 764 765 feed('<esc>') 766 767 -- splitting lines 768 feed([[:%s/abc/\r]]) 769 check_events { 770 { 'test1', 'bytes', 1, 3, 0, 0, 0, 0, 3, 3, 1, 0, 1 }, 771 { 'test1', 'bytes', 1, 6, 0, 0, 0, 1, 0, 1, 0, 3, 3 }, 772 } 773 774 feed('<esc>') 775 -- multi-line regex 776 feed([[:%s/de\n123/a]]) 777 778 check_events { 779 { 'test1', 'bytes', 1, 3, 0, 3, 3, 1, 3, 6, 0, 1, 1 }, 780 { 'test1', 'bytes', 1, 6, 0, 3, 3, 0, 1, 1, 1, 3, 6 }, 781 } 782 783 feed('<esc>') 784 -- replacing with unicode 785 feed(':%s/b/→') 786 787 check_events { 788 { 'test1', 'bytes', 1, 3, 0, 1, 1, 0, 1, 1, 0, 3, 3 }, 789 { 'test1', 'bytes', 1, 5, 0, 1, 1, 0, 3, 3, 0, 1, 1 }, 790 } 791 792 feed('<esc>') 793 -- replacing with expression register 794 feed([[:%s/b/\=5+5]]) 795 check_events { 796 { 'test1', 'bytes', 1, 3, 0, 1, 1, 0, 1, 1, 0, 2, 2 }, 797 { 'test1', 'bytes', 1, 5, 0, 1, 1, 0, 2, 2, 0, 1, 1 }, 798 } 799 800 feed('<esc>') 801 -- replacing with backslash 802 feed([[:%s/b/\\]]) 803 check_events { 804 { 'test1', 'bytes', 1, 3, 0, 1, 1, 0, 1, 1, 0, 1, 1 }, 805 { 'test1', 'bytes', 1, 5, 0, 1, 1, 0, 1, 1, 0, 1, 1 }, 806 } 807 808 feed('<esc>') 809 -- replacing with backslash from expression register 810 feed([[:%s/b/\='\']]) 811 check_events { 812 { 'test1', 'bytes', 1, 3, 0, 1, 1, 0, 1, 1, 0, 1, 1 }, 813 { 'test1', 'bytes', 1, 5, 0, 1, 1, 0, 1, 1, 0, 1, 1 }, 814 } 815 816 feed('<esc>') 817 -- replacing with backslash followed by another character 818 feed([[:%s/b/\\!]]) 819 check_events { 820 { 'test1', 'bytes', 1, 3, 0, 1, 1, 0, 1, 1, 0, 2, 2 }, 821 { 'test1', 'bytes', 1, 5, 0, 1, 1, 0, 2, 2, 0, 1, 1 }, 822 } 823 824 feed('<esc>') 825 -- replacing with backslash followed by another character from expression register 826 feed([[:%s/b/\='\!']]) 827 check_events { 828 { 'test1', 'bytes', 1, 3, 0, 1, 1, 0, 1, 1, 0, 2, 2 }, 829 { 'test1', 'bytes', 1, 5, 0, 1, 1, 0, 2, 2, 0, 1, 1 }, 830 } 831 end) 832 833 it('nvim_buf_set_text insert', function() 834 local check_events = setup_eventcheck(verify, { 'bastext' }) 835 api.nvim_buf_set_text(0, 0, 3, 0, 3, { 'fiol', 'kontra' }) 836 check_events { 837 { 'test1', 'bytes', 1, 3, 0, 3, 3, 0, 0, 0, 1, 6, 11 }, 838 } 839 840 api.nvim_buf_set_text(0, 1, 6, 1, 6, { 'punkt', 'syntgitarr', 'övnings' }) 841 check_events { 842 { 'test1', 'bytes', 1, 4, 1, 6, 14, 0, 0, 0, 2, 8, 25 }, 843 } 844 845 eq( 846 { 'basfiol', 'kontrapunkt', 'syntgitarr', 'övningstext' }, 847 api.nvim_buf_get_lines(0, 0, -1, true) 848 ) 849 end) 850 851 it('nvim_buf_set_text replace', function() 852 local check_events = setup_eventcheck(verify, origlines) 853 854 api.nvim_buf_set_text(0, 2, 3, 2, 8, { 'very text' }) 855 check_events { 856 { 'test1', 'bytes', 1, 3, 2, 3, 35, 0, 5, 5, 0, 9, 9 }, 857 } 858 859 api.nvim_buf_set_text(0, 3, 5, 3, 7, { ' splitty', 'line ' }) 860 check_events { 861 { 'test1', 'bytes', 1, 4, 3, 5, 57, 0, 2, 2, 1, 5, 14 }, 862 } 863 864 api.nvim_buf_set_text(0, 0, 8, 1, 2, { 'JOINY' }) 865 check_events { 866 { 'test1', 'bytes', 1, 5, 0, 8, 8, 1, 2, 10, 0, 5, 5 }, 867 } 868 869 api.nvim_buf_set_text(0, 4, 0, 6, 0, { 'was 5,6', '' }) 870 check_events { 871 { 'test1', 'bytes', 1, 6, 4, 0, 75, 2, 0, 32, 1, 0, 8 }, 872 } 873 874 eq({ 875 'originalJOINYiginal line 2', 876 'orivery text line 3', 877 'origi splitty', 878 'line l line 4', 879 'was 5,6', 880 ' indented line', 881 }, api.nvim_buf_get_lines(0, 0, -1, true)) 882 end) 883 884 it('nvim_buf_set_text delete', function() 885 local check_events = setup_eventcheck(verify, origlines) 886 887 -- really {""} but accepts {} as a shorthand 888 api.nvim_buf_set_text(0, 0, 0, 1, 0, {}) 889 check_events { 890 { 'test1', 'bytes', 1, 3, 0, 0, 0, 1, 0, 16, 0, 0, 0 }, 891 } 892 893 -- TODO(bfredl): this works but is not as convenient as set_lines 894 api.nvim_buf_set_text(0, 4, 15, 5, 17, { '' }) 895 check_events { 896 { 'test1', 'bytes', 1, 4, 4, 15, 79, 1, 17, 18, 0, 0, 0 }, 897 } 898 eq({ 899 'original line 2', 900 'original line 3', 901 'original line 4', 902 'original line 5', 903 'original line 6', 904 }, api.nvim_buf_get_lines(0, 0, -1, true)) 905 end) 906 907 it('checktime autoread', function() 908 write_file( 909 'Xtest-reload', 910 dedent [[ 911 old line 1 912 old line 2]] 913 ) 914 local atime = os.time() - 10 915 vim.uv.fs_utime('Xtest-reload', atime, atime) 916 command 'e Xtest-reload' 917 command 'set autoread' 918 919 local check_events = setup_eventcheck(verify, nil) 920 921 write_file( 922 'Xtest-reload', 923 dedent [[ 924 new line 1 925 new line 2 926 new line 3]] 927 ) 928 929 command 'checktime' 930 check_events { 931 { 'test1', 'reload', 1 }, 932 } 933 934 feed 'ggJ' 935 check_events { 936 { 'test1', 'bytes', 1, 5, 0, 10, 10, 1, 0, 1, 0, 1, 1 }, 937 } 938 939 eq({ 'new line 1 new line 2', 'new line 3' }, api.nvim_buf_get_lines(0, 0, -1, true)) 940 941 -- check we can undo and redo a reload event. 942 feed 'u' 943 check_events { 944 { 'test1', 'bytes', 1, 8, 0, 10, 10, 0, 1, 1, 1, 0, 1 }, 945 } 946 947 feed 'u' 948 check_events { 949 { 'test1', 'reload', 1 }, 950 } 951 952 feed '<c-r>' 953 check_events { 954 { 'test1', 'reload', 1 }, 955 } 956 957 feed '<c-r>' 958 check_events { 959 { 'test1', 'bytes', 1, 14, 0, 10, 10, 1, 0, 1, 0, 1, 1 }, 960 } 961 end) 962 963 it('tab with noexpandtab and softtabstop', function() 964 command('set noet') 965 command('set ts=4') 966 command('set sw=2') 967 command('set sts=4') 968 969 local check_events = setup_eventcheck(verify, { 'asdfasdf' }) 970 971 feed('gg0i<tab>') 972 973 check_events { 974 { 'test1', 'bytes', 1, 3, 0, 0, 0, 0, 0, 0, 0, 1, 1 }, 975 { 'test1', 'bytes', 1, 4, 0, 1, 1, 0, 0, 0, 0, 1, 1 }, 976 } 977 feed('<tab>') 978 979 -- when spaces are merged into a tabstop 980 check_events { 981 { 'test1', 'bytes', 1, 5, 0, 2, 2, 0, 0, 0, 0, 1, 1 }, 982 { 'test1', 'bytes', 1, 6, 0, 3, 3, 0, 0, 0, 0, 1, 1 }, 983 { 'test1', 'bytes', 1, 7, 0, 0, 0, 0, 4, 4, 0, 1, 1 }, 984 } 985 986 feed('<esc>u') 987 check_events { 988 { 'test1', 'bytes', 1, 9, 0, 0, 0, 0, 1, 1, 0, 4, 4 }, 989 { 'test1', 'bytes', 1, 9, 0, 0, 0, 0, 4, 4, 0, 0, 0 }, 990 } 991 992 -- in REPLACE mode 993 feed('R<tab><tab>') 994 check_events { 995 { 'test1', 'bytes', 1, 10, 0, 0, 0, 0, 1, 1, 0, 1, 1 }, 996 { 'test1', 'bytes', 1, 11, 0, 1, 1, 0, 0, 0, 0, 1, 1 }, 997 { 'test1', 'bytes', 1, 12, 0, 2, 2, 0, 1, 1, 0, 1, 1 }, 998 { 'test1', 'bytes', 1, 13, 0, 3, 3, 0, 0, 0, 0, 1, 1 }, 999 { 'test1', 'bytes', 1, 14, 0, 0, 0, 0, 4, 4, 0, 1, 1 }, 1000 } 1001 feed('<esc>u') 1002 check_events { 1003 { 'test1', 'bytes', 1, 16, 0, 0, 0, 0, 1, 1, 0, 4, 4 }, 1004 { 'test1', 'bytes', 1, 16, 0, 2, 2, 0, 2, 2, 0, 1, 1 }, 1005 { 'test1', 'bytes', 1, 16, 0, 0, 0, 0, 2, 2, 0, 1, 1 }, 1006 } 1007 1008 -- in VISUALREPLACE mode 1009 feed('gR<tab><tab>') 1010 check_events { 1011 { 'test1', 'bytes', 1, 17, 0, 0, 0, 0, 1, 1, 0, 1, 1 }, 1012 { 'test1', 'bytes', 1, 18, 0, 1, 1, 0, 1, 1, 0, 1, 1 }, 1013 { 'test1', 'bytes', 1, 19, 0, 2, 2, 0, 1, 1, 0, 1, 1 }, 1014 { 'test1', 'bytes', 1, 20, 0, 3, 3, 0, 1, 1, 0, 1, 1 }, 1015 { 'test1', 'bytes', 1, 21, 0, 3, 3, 0, 1, 1, 0, 0, 0 }, 1016 { 'test1', 'bytes', 1, 22, 0, 3, 3, 0, 0, 0, 0, 1, 1 }, 1017 { 'test1', 'bytes', 1, 24, 0, 2, 2, 0, 1, 1, 0, 0, 0 }, 1018 { 'test1', 'bytes', 1, 25, 0, 2, 2, 0, 0, 0, 0, 1, 1 }, 1019 { 'test1', 'bytes', 1, 27, 0, 1, 1, 0, 1, 1, 0, 0, 0 }, 1020 { 'test1', 'bytes', 1, 28, 0, 1, 1, 0, 0, 0, 0, 1, 1 }, 1021 { 'test1', 'bytes', 1, 30, 0, 0, 0, 0, 1, 1, 0, 0, 0 }, 1022 { 'test1', 'bytes', 1, 31, 0, 0, 0, 0, 0, 0, 0, 1, 1 }, 1023 { 'test1', 'bytes', 1, 33, 0, 0, 0, 0, 4, 4, 0, 1, 1 }, 1024 } 1025 1026 -- inserting tab after other tabs 1027 command('set sw=4') 1028 feed('<esc>0a<tab>') 1029 check_events { 1030 { 'test1', 'bytes', 1, 34, 0, 1, 1, 0, 0, 0, 0, 1, 1 }, 1031 { 'test1', 'bytes', 1, 35, 0, 2, 2, 0, 0, 0, 0, 1, 1 }, 1032 { 'test1', 'bytes', 1, 36, 0, 3, 3, 0, 0, 0, 0, 1, 1 }, 1033 { 'test1', 'bytes', 1, 37, 0, 4, 4, 0, 0, 0, 0, 1, 1 }, 1034 { 'test1', 'bytes', 1, 38, 0, 1, 1, 0, 4, 4, 0, 1, 1 }, 1035 } 1036 end) 1037 1038 it('retab', function() 1039 command('set noet') 1040 command('set ts=4') 1041 1042 local check_events = setup_eventcheck(verify, { ' asdf' }) 1043 command('retab 8') 1044 1045 check_events { 1046 { 'test1', 'bytes', 1, 3, 0, 0, 0, 0, 7, 7, 0, 9, 9 }, 1047 } 1048 end) 1049 1050 it('sends events when undoing with undofile', function() 1051 write_file( 1052 'Xtest-undofile', 1053 dedent([[ 1054 12345 1055 hello world 1056 ]]) 1057 ) 1058 1059 command('e! Xtest-undofile') 1060 command('set undodir=. | set undofile') 1061 1062 local ns = n.request('nvim_create_namespace', 'ns1') 1063 api.nvim_buf_set_extmark(0, ns, 0, 0, {}) 1064 1065 eq({ '12345', 'hello world' }, api.nvim_buf_get_lines(0, 0, -1, true)) 1066 1067 -- splice 1068 feed('gg0d2l') 1069 1070 eq({ '345', 'hello world' }, api.nvim_buf_get_lines(0, 0, -1, true)) 1071 1072 -- move 1073 command('.m+1') 1074 1075 eq({ 'hello world', '345' }, api.nvim_buf_get_lines(0, 0, -1, true)) 1076 1077 -- reload undofile and undo changes 1078 command('w') 1079 command('set noundofile') 1080 command('bw!') 1081 command('e! Xtest-undofile') 1082 1083 command('set undofile') 1084 1085 local check_events = setup_eventcheck(verify, nil) 1086 1087 feed('u') 1088 eq({ '345', 'hello world' }, api.nvim_buf_get_lines(0, 0, -1, true)) 1089 1090 check_events { 1091 { 'test1', 'bytes', 2, 6, 1, 0, 12, 1, 0, 4, 0, 0, 0 }, 1092 { 'test1', 'bytes', 2, 6, 0, 0, 0, 0, 0, 0, 1, 0, 4 }, 1093 } 1094 1095 feed('u') 1096 eq({ '12345', 'hello world' }, api.nvim_buf_get_lines(0, 0, -1, true)) 1097 1098 check_events { 1099 { 'test1', 'bytes', 2, 8, 0, 0, 0, 0, 0, 0, 0, 2, 2 }, 1100 } 1101 command('bw!') 1102 end) 1103 1104 it('blockwise paste with uneven line lengths', function() 1105 local check_events = setup_eventcheck(verify, { 'aaaa', 'aaa', 'aaa' }) 1106 1107 -- eq({}, api.nvim_buf_get_lines(0, 0, -1, true)) 1108 feed('gg0<c-v>jj$d') 1109 1110 check_events { 1111 { 'test1', 'bytes', 1, 3, 0, 0, 0, 0, 4, 4, 0, 0, 0 }, 1112 { 'test1', 'bytes', 1, 3, 1, 0, 1, 0, 3, 3, 0, 0, 0 }, 1113 { 'test1', 'bytes', 1, 3, 2, 0, 2, 0, 3, 3, 0, 0, 0 }, 1114 } 1115 1116 feed('p') 1117 check_events { 1118 { 'test1', 'bytes', 1, 4, 0, 0, 0, 0, 0, 0, 0, 4, 4 }, 1119 { 'test1', 'bytes', 1, 4, 1, 0, 5, 0, 0, 0, 0, 3, 3 }, 1120 { 'test1', 'bytes', 1, 4, 2, 0, 9, 0, 0, 0, 0, 3, 3 }, 1121 } 1122 end) 1123 1124 it(':luado', function() 1125 local check_events = setup_eventcheck(verify, { 'abc', '12345' }) 1126 1127 command(".luado return 'a'") 1128 1129 check_events { 1130 { 'test1', 'bytes', 1, 3, 0, 0, 0, 0, 3, 3, 0, 1, 1 }, 1131 } 1132 1133 command('luado return 10') 1134 1135 check_events { 1136 { 'test1', 'bytes', 1, 4, 0, 0, 0, 0, 1, 1, 0, 2, 2 }, 1137 { 'test1', 'bytes', 1, 5, 1, 0, 3, 0, 5, 5, 0, 2, 2 }, 1138 } 1139 end) 1140 1141 it('flushes deleted bytes on move', function() 1142 local check_events = setup_eventcheck(verify, { 'AAA', 'BBB', 'CCC', 'DDD' }) 1143 1144 feed(':.move+1<cr>') 1145 1146 check_events { 1147 { 'test1', 'bytes', 1, 5, 0, 0, 0, 1, 0, 4, 0, 0, 0 }, 1148 { 'test1', 'bytes', 1, 5, 1, 0, 4, 0, 0, 0, 1, 0, 4 }, 1149 } 1150 1151 feed('jd2j') 1152 1153 check_events { 1154 { 'test1', 'bytes', 1, 6, 2, 0, 8, 2, 0, 8, 0, 0, 0 }, 1155 } 1156 end) 1157 1158 it('virtual edit', function() 1159 local check_events = setup_eventcheck(verify, { '', ' ' }) 1160 1161 api.nvim_set_option_value('virtualedit', 'all', {}) 1162 1163 feed [[<Right><Right>iab<ESC>]] 1164 1165 check_events { 1166 { 'test1', 'bytes', 1, 3, 0, 0, 0, 0, 0, 0, 0, 2, 2 }, 1167 { 'test1', 'bytes', 1, 4, 0, 2, 2, 0, 0, 0, 0, 2, 2 }, 1168 } 1169 1170 feed [[j<Right><Right>iab<ESC>]] 1171 1172 check_events { 1173 { 'test1', 'bytes', 1, 5, 1, 0, 5, 0, 1, 1, 0, 8, 8 }, 1174 { 'test1', 'bytes', 1, 6, 1, 5, 10, 0, 0, 0, 0, 2, 2 }, 1175 } 1176 end) 1177 1178 it('block visual paste', function() 1179 local check_events = setup_eventcheck(verify, { 'AAA', 'BBB', 'CCC', 'DDD', 'EEE', 'FFF' }) 1180 fn.setreg('a', '___') 1181 feed([[gg0l<c-v>3jl"ap]]) 1182 1183 check_events { 1184 { 'test1', 'bytes', 1, 3, 0, 1, 1, 0, 2, 2, 0, 0, 0 }, 1185 { 'test1', 'bytes', 1, 3, 1, 1, 3, 0, 2, 2, 0, 0, 0 }, 1186 { 'test1', 'bytes', 1, 3, 2, 1, 5, 0, 2, 2, 0, 0, 0 }, 1187 { 'test1', 'bytes', 1, 3, 3, 1, 7, 0, 2, 2, 0, 0, 0 }, 1188 { 'test1', 'bytes', 1, 5, 0, 1, 1, 0, 0, 0, 0, 3, 3 }, 1189 { 'test1', 'bytes', 1, 6, 1, 1, 6, 0, 0, 0, 0, 3, 3 }, 1190 { 'test1', 'bytes', 1, 7, 2, 1, 11, 0, 0, 0, 0, 3, 3 }, 1191 { 'test1', 'bytes', 1, 8, 3, 1, 16, 0, 0, 0, 0, 3, 3 }, 1192 } 1193 end) 1194 1195 it('visual paste', function() 1196 local check_events = setup_eventcheck(verify, { 'aaa {', 'b', '}' }) 1197 -- Setting up 1198 feed [[jdd]] 1199 check_events { 1200 { 'test1', 'bytes', 1, 3, 1, 0, 6, 1, 0, 2, 0, 0, 0 }, 1201 } 1202 1203 -- Actually testing 1204 feed [[v%p]] 1205 check_events { 1206 { 'test1', 'bytes', 1, 8, 0, 4, 4, 1, 1, 3, 0, 0, 0 }, 1207 { 'test1', 'bytes', 1, 8, 0, 4, 4, 0, 0, 0, 2, 0, 3 }, 1208 } 1209 end) 1210 1211 it('visual paste 2: splitting an empty line', function() 1212 local check_events = setup_eventcheck(verify, { 'abc', '{', 'def', '}' }) 1213 feed('ggyyjjvi{p') 1214 check_events { 1215 { 'test1', 'bytes', 1, 6, 2, 0, 6, 1, 0, 4, 0, 0, 0 }, 1216 { 'test1', 'bytes', 1, 6, 2, 0, 6, 0, 0, 0, 2, 0, 5 }, 1217 } 1218 end) 1219 1220 it('nvim_buf_set_lines', function() 1221 local check_events = setup_eventcheck(verify, { 'AAA', 'BBB' }) 1222 1223 -- delete 1224 api.nvim_buf_set_lines(0, 0, 1, true, {}) 1225 1226 check_events { 1227 { 'test1', 'bytes', 1, 3, 0, 0, 0, 1, 0, 4, 0, 0, 0 }, 1228 } 1229 1230 -- add 1231 api.nvim_buf_set_lines(0, 0, 0, true, { 'asdf' }) 1232 check_events { 1233 { 'test1', 'bytes', 1, 4, 0, 0, 0, 0, 0, 0, 1, 0, 5 }, 1234 } 1235 1236 -- replace 1237 api.nvim_buf_set_lines(0, 0, 1, true, { 'asdf', 'fdsa' }) 1238 check_events { 1239 { 'test1', 'bytes', 1, 5, 0, 0, 0, 1, 0, 5, 2, 0, 10 }, 1240 } 1241 end) 1242 1243 it('flushes delbytes on substitute', function() 1244 local check_events = setup_eventcheck(verify, { 'AAA', 'BBB', 'CCC' }) 1245 1246 feed('gg0') 1247 command('s/AAA/GGG/') 1248 1249 check_events { 1250 { 'test1', 'bytes', 1, 3, 0, 0, 0, 0, 3, 3, 0, 3, 3 }, 1251 } 1252 1253 -- check that byte updates for :delete (which uses curbuf->deleted_bytes2) 1254 -- are correct 1255 command('delete') 1256 check_events { 1257 { 'test1', 'bytes', 1, 4, 0, 0, 0, 1, 0, 4, 0, 0, 0 }, 1258 } 1259 end) 1260 1261 it('on_bytes sees modified buffer after substitute', function() 1262 api.nvim_buf_set_lines(0, 0, -1, true, { 'Hello' }) 1263 1264 local buffer_lines = exec_lua(function() 1265 local lines 1266 vim.api.nvim_buf_attach(0, false, { 1267 on_bytes = function() 1268 lines = vim.api.nvim_buf_get_lines(0, 0, -1, true) 1269 end, 1270 }) 1271 vim.cmd('s/llo/y/') 1272 return lines 1273 end) 1274 1275 -- Make sure on_bytes is called after the buffer is modified. 1276 eq({ 'Hey' }, buffer_lines) 1277 end) 1278 1279 it('on_bytes called multiple times for multiple substitutions on same line', function() 1280 api.nvim_buf_set_lines(0, 0, -1, true, { 'Hello Hello' }) 1281 1282 local call_count, args = exec_lua(function() 1283 local count = 0 1284 local args = {} 1285 vim.api.nvim_buf_attach(0, false, { 1286 on_bytes = function( 1287 _, 1288 _, 1289 _, 1290 start_row, 1291 start_col, 1292 start_byte, 1293 old_row, 1294 old_col, 1295 old_byte, 1296 new_row, 1297 new_col, 1298 new_byte 1299 ) 1300 count = count + 1 1301 table.insert(args, { 1302 start_row = start_row, 1303 start_col = start_col, 1304 start_byte = start_byte, 1305 old_row = old_row, 1306 old_col = old_col, 1307 old_byte = old_byte, 1308 new_row = new_row, 1309 new_col = new_col, 1310 new_byte = new_byte, 1311 buffer_lines = vim.api.nvim_buf_get_lines(0, 0, -1, true), 1312 }) 1313 end, 1314 }) 1315 vim.cmd('s/llo/y/g') 1316 return count, args 1317 end) 1318 1319 -- Should be called twice, once for each match. 1320 eq(2, call_count) 1321 1322 -- First match: "llo" at column 2 -> "y". 1323 eq({ 1324 start_row = 0, 1325 start_col = 2, 1326 start_byte = 2, 1327 old_row = 0, 1328 old_col = 3, 1329 old_byte = 3, 1330 new_row = 0, 1331 new_col = 1, 1332 new_byte = 1, 1333 buffer_lines = { 'Hey Hey' }, 1334 }, args[1]) 1335 1336 -- Second match: "llo" at column 8 (in original) -> column 6 (after first substitution). 1337 eq({ 1338 start_row = 0, 1339 start_col = 6, -- Adjusted position after first substitution. 1340 start_byte = 6, 1341 old_row = 0, 1342 old_col = 3, 1343 old_byte = 3, 1344 new_row = 0, 1345 new_col = 1, 1346 new_byte = 1, 1347 buffer_lines = { 'Hey Hey' }, 1348 }, args[2]) 1349 end) 1350 1351 it('on_bytes called correctly for multi-line substitutions', function() 1352 api.nvim_buf_set_lines(0, 0, -1, true, { 'foo bar', 'baz qux' }) 1353 1354 local call_count, args = exec_lua(function() 1355 local count = 0 1356 local args = {} 1357 vim.api.nvim_buf_attach(0, false, { 1358 on_bytes = function( 1359 _, 1360 _, 1361 _, 1362 start_row, 1363 start_col, 1364 start_byte, 1365 old_row, 1366 old_col, 1367 old_byte, 1368 new_row, 1369 new_col, 1370 new_byte 1371 ) 1372 count = count + 1 1373 table.insert(args, { 1374 start_row = start_row, 1375 start_col = start_col, 1376 start_byte = start_byte, 1377 old_row = old_row, 1378 old_col = old_col, 1379 old_byte = old_byte, 1380 new_row = new_row, 1381 new_col = new_col, 1382 new_byte = new_byte, 1383 buffer_lines = vim.api.nvim_buf_get_lines(0, 0, -1, true), 1384 }) 1385 end, 1386 }) 1387 vim.cmd('s/bar/X\\rY/') 1388 return count, args 1389 end) 1390 1391 -- Should be called once for the substitution. 1392 eq(1, call_count) 1393 1394 eq({ 1395 start_row = 0, 1396 start_col = 4, 1397 start_byte = 4, 1398 old_row = 0, 1399 old_col = 3, 1400 old_byte = 3, 1401 new_row = 1, 1402 new_col = 1, 1403 new_byte = 3, 1404 buffer_lines = { 'foo X', 'Y', 'baz qux' }, 1405 }, args[1]) 1406 end) 1407 1408 it('on_bytes called multiple times for global substitution creating multiple lines', function() 1409 api.nvim_buf_set_lines(0, 0, -1, true, { 'foo bar baz' }) 1410 1411 local call_count, args = exec_lua(function() 1412 local count = 0 1413 local args = {} 1414 vim.api.nvim_buf_attach(0, false, { 1415 on_bytes = function( 1416 _, 1417 _, 1418 _, 1419 start_row, 1420 start_col, 1421 start_byte, 1422 old_row, 1423 old_col, 1424 old_byte, 1425 new_row, 1426 new_col, 1427 new_byte 1428 ) 1429 count = count + 1 1430 table.insert(args, { 1431 start_row = start_row, 1432 start_col = start_col, 1433 start_byte = start_byte, 1434 old_row = old_row, 1435 old_col = old_col, 1436 old_byte = old_byte, 1437 new_row = new_row, 1438 new_col = new_col, 1439 new_byte = new_byte, 1440 buffer_lines = vim.api.nvim_buf_get_lines(0, 0, -1, true), 1441 }) 1442 end, 1443 }) 1444 -- Global substitution with newlines in replacement. 1445 vim.cmd([[s/ /\r/g]]) 1446 return count, args 1447 end) 1448 1449 -- Should be called once per space replacement. 1450 eq(2, call_count) 1451 1452 eq({ 1453 start_row = 0, 1454 start_col = 3, 1455 start_byte = 3, 1456 old_row = 0, 1457 old_col = 1, 1458 old_byte = 1, 1459 new_row = 1, 1460 new_col = 0, 1461 new_byte = 1, 1462 buffer_lines = { 'foo', 'bar', 'baz' }, 1463 }, args[1]) 1464 1465 eq({ 1466 start_row = 1, 1467 start_col = 3, 1468 start_byte = 7, 1469 old_row = 0, 1470 old_col = 1, 1471 old_byte = 1, 1472 new_row = 1, 1473 new_col = 0, 1474 new_byte = 1, 1475 buffer_lines = { 'foo', 'bar', 'baz' }, 1476 }, args[2]) 1477 end) 1478 1479 it( 1480 'no buffer update event is emitted while editing substitute command, only after confirmation', 1481 function() 1482 api.nvim_buf_set_lines(0, 0, -1, true, { 'Hello world', 'Hello Neovim' }) 1483 1484 exec_lua(function() 1485 _G.num_buffer_updates = 0 1486 vim.api.nvim_buf_attach(0, false, { 1487 on_bytes = function() 1488 _G.num_buffer_updates = _G.num_buffer_updates + 1 1489 end, 1490 }) 1491 end) 1492 1493 -- Start typing the substitute command - no events should be emitted yet. 1494 feed(':%s/Hello/Hi') 1495 eq(0, exec_lua('return _G.num_buffer_updates')) 1496 1497 -- Continue editing the command - still no events. 1498 feed('<BS><BS>Hey') 1499 eq(0, exec_lua('return _G.num_buffer_updates')) 1500 1501 -- After confirming the substitution, two events should be emitted (one per line). 1502 feed('<CR>') 1503 eq(2, exec_lua('return _G.num_buffer_updates')) 1504 1505 -- Verify the buffer was actually modified. 1506 eq({ 'Hey world', 'Hey Neovim' }, api.nvim_buf_get_lines(0, 0, -1, true)) 1507 end 1508 ) 1509 1510 it('flushes delbytes on join', function() 1511 local check_events = setup_eventcheck(verify, { 'AAA', 'BBB', 'CCC' }) 1512 1513 feed('gg0J') 1514 1515 check_events { 1516 { 'test1', 'bytes', 1, 3, 0, 3, 3, 1, 0, 1, 0, 1, 1 }, 1517 } 1518 1519 command('delete') 1520 check_events { 1521 { 'test1', 'bytes', 1, 5, 0, 0, 0, 1, 0, 8, 0, 0, 0 }, 1522 } 1523 end) 1524 1525 it('sends updates on U', function() 1526 feed('ggiAAA<cr>BBB') 1527 feed('<esc>gg$a CCC') 1528 1529 local check_events = setup_eventcheck(verify, nil) 1530 1531 feed('ggU') 1532 1533 check_events { 1534 { 'test1', 'bytes', 1, 6, 0, 7, 7, 0, 0, 0, 0, 3, 3 }, 1535 } 1536 end) 1537 1538 it('delete in completely empty buffer', function() 1539 local check_events = setup_eventcheck(verify, nil) 1540 1541 command 'delete' 1542 check_events {} 1543 end) 1544 1545 it('delete the only line of a buffer', function() 1546 local check_events = setup_eventcheck(verify, { 'AAA' }) 1547 1548 command 'delete' 1549 check_events { 1550 { 'test1', 'bytes', 1, 3, 0, 0, 0, 1, 0, 4, 1, 0, 1 }, 1551 } 1552 end) 1553 1554 it('delete the last line of a buffer with two lines', function() 1555 local check_events = setup_eventcheck(verify, { 'AAA', 'BBB' }) 1556 1557 command '2delete' 1558 check_events { 1559 { 'test1', 'bytes', 1, 3, 1, 0, 4, 1, 0, 4, 0, 0, 0 }, 1560 } 1561 end) 1562 1563 it(':sort lines', function() 1564 local check_events = setup_eventcheck(verify, { 'CCC', 'BBB', 'AAA' }) 1565 1566 command '%sort' 1567 check_events { 1568 { 'test1', 'bytes', 1, 3, 0, 0, 0, 3, 0, 12, 3, 0, 12 }, 1569 } 1570 end) 1571 1572 it('handles already sorted lines', function() 1573 local check_events = setup_eventcheck(verify, { 'AAA', 'BBB', 'CCC' }) 1574 1575 command '%sort' 1576 check_events {} 1577 end) 1578 1579 it('works with accepting spell suggestions', function() 1580 local check_events = setup_eventcheck(verify, { 'hallo world', 'hallo world' }) 1581 1582 feed('gg0z=4<cr><cr>') -- accepts 'Hello' 1583 check_events { 1584 { 'test1', 'bytes', 1, 3, 0, 0, 0, 0, 2, 2, 0, 2, 2 }, 1585 } 1586 1587 command('spellrepall') -- replaces whole words 1588 check_events { 1589 { 'test1', 'bytes', 1, 4, 1, 0, 12, 0, 5, 5, 0, 5, 5 }, 1590 } 1591 end) 1592 1593 it('works with :diffput and :diffget', function() 1594 local check_events = setup_eventcheck(verify, { 'AAA' }) 1595 command('diffthis') 1596 command('new') 1597 command('diffthis') 1598 api.nvim_buf_set_lines(0, 0, -1, true, { 'AAA', 'BBB' }) 1599 feed('G') 1600 command('diffput') 1601 check_events { 1602 { 'test1', 'bytes', 1, 3, 1, 0, 4, 0, 0, 0, 1, 0, 4 }, 1603 } 1604 api.nvim_buf_set_lines(0, 0, -1, true, { 'AAA', 'CCC' }) 1605 feed('<C-w>pG') 1606 command('diffget') 1607 check_events { 1608 { 'test1', 'bytes', 1, 4, 1, 0, 4, 1, 0, 4, 1, 0, 4 }, 1609 } 1610 end) 1611 1612 it('prompt buffer', function() 1613 local check_events = setup_eventcheck(verify, {}) 1614 api.nvim_set_option_value('buftype', 'prompt', {}) 1615 feed('i') 1616 check_events { 1617 { 'test1', 'bytes', 1, 3, 0, 0, 0, 0, 0, 0, 0, 2, 2 }, 1618 } 1619 feed('<CR>') 1620 check_events { 1621 { 'test1', 'bytes', 1, 4, 1, 0, 3, 0, 0, 0, 1, 0, 1 }, 1622 { 'test1', 'bytes', 1, 5, 1, 0, 3, 0, 0, 0, 0, 2, 2 }, 1623 } 1624 feed('<CR>') 1625 check_events { 1626 { 'test1', 'bytes', 1, 6, 2, 0, 6, 0, 0, 0, 1, 0, 1 }, 1627 { 'test1', 'bytes', 1, 7, 2, 0, 6, 0, 0, 0, 0, 2, 2 }, 1628 } 1629 fn.prompt_setprompt('', 'foo > ') 1630 check_events { 1631 { 'test1', 'bytes', 1, 8, 2, 0, 6, 0, 2, 2, 0, 6, 6 }, 1632 } 1633 feed('hello') 1634 check_events { 1635 { 'test1', 'bytes', 1, 9, 2, 6, 12, 0, 0, 0, 0, 5, 5 }, 1636 } 1637 fn.prompt_setprompt('', 'uber-foo > ') 1638 check_events { 1639 { 'test1', 'bytes', 1, 10, 2, 0, 6, 0, 6, 6, 0, 11, 11 }, 1640 } 1641 eq({ '% ', '% ', 'uber-foo > hello' }, api.nvim_buf_get_lines(0, 0, -1, true)) 1642 -- Do this in the same event. 1643 exec_lua(function() 1644 vim.fn.setpos("':", { 0, vim.fn.line('.'), 999, 0 }) 1645 vim.fn.prompt_setprompt('', 'discard > ') 1646 end) 1647 check_events { 1648 { 'test1', 'bytes', 1, 11, 2, 0, 6, 0, 16, 16, 0, 10, 10 }, 1649 } 1650 eq({ '% ', '% ', 'discard > ' }, api.nvim_buf_get_lines(0, 0, -1, true)) 1651 feed('sup<S-CR>dood') 1652 check_events { 1653 { 'test1', 'bytes', 1, 12, 2, 10, 16, 0, 0, 0, 0, 3, 3 }, 1654 { 'test1', 'bytes', 1, 13, 2, 13, 19, 0, 0, 0, 1, 0, 1 }, 1655 { 'test1', 'bytes', 1, 14, 3, 0, 20, 0, 0, 0, 0, 4, 4 }, 1656 } 1657 eq({ '% ', '% ', 'discard > sup', 'dood' }, api.nvim_buf_get_lines(0, 0, -1, true)) 1658 fn.prompt_setprompt('', 'cool > ') 1659 check_events { 1660 { 'test1', 'bytes', 1, 15, 2, 0, 6, 0, 10, 10, 0, 7, 7 }, 1661 } 1662 eq({ '% ', '% ', 'cool > sup', 'dood' }, api.nvim_buf_get_lines(0, 0, -1, true)) 1663 1664 -- init_prompt uses appended_lines_mark when appending to fix prompt. 1665 api.nvim_buf_set_lines(0, 0, -1, true, { 'hi' }) 1666 eq({ 'hi', 'cool > ' }, api.nvim_buf_get_lines(0, 0, -1, true)) 1667 check_events { 1668 { 'test1', 'bytes', 1, 16, 0, 0, 0, 4, 0, 22, 1, 0, 3 }, 1669 { 'test1', 'bytes', 1, 17, 1, 0, 3, 0, 0, 0, 1, 0, 8 }, 1670 } 1671 end) 1672 1673 local function test_lockmarks(mode) 1674 local description = (mode ~= '') and mode or '(baseline)' 1675 it('test_lockmarks ' .. description .. ' %delete _', function() 1676 local check_events = setup_eventcheck(verify, { 'AAA', 'BBB', 'CCC' }) 1677 1678 command(mode .. ' %delete _') 1679 check_events { 1680 { 'test1', 'bytes', 1, 3, 0, 0, 0, 3, 0, 12, 1, 0, 1 }, 1681 } 1682 end) 1683 1684 it('test_lockmarks ' .. description .. ' append()', function() 1685 local check_events = setup_eventcheck(verify) 1686 1687 command(mode .. " call append(0, 'CCC')") 1688 check_events { 1689 { 'test1', 'bytes', 1, 2, 0, 0, 0, 0, 0, 0, 1, 0, 4 }, 1690 } 1691 1692 command(mode .. " call append(1, 'BBBB')") 1693 check_events { 1694 { 'test1', 'bytes', 1, 3, 1, 0, 4, 0, 0, 0, 1, 0, 5 }, 1695 } 1696 1697 command(mode .. " call append(2, '')") 1698 check_events { 1699 { 'test1', 'bytes', 1, 4, 2, 0, 9, 0, 0, 0, 1, 0, 1 }, 1700 } 1701 1702 command(mode .. ' $delete _') 1703 check_events { 1704 { 'test1', 'bytes', 1, 5, 3, 0, 10, 1, 0, 1, 0, 0, 0 }, 1705 } 1706 1707 eq('CCC|BBBB|', table.concat(api.nvim_buf_get_lines(0, 0, -1, true), '|')) 1708 end) 1709 end 1710 1711 -- check that behavior is identical with and without "lockmarks" 1712 test_lockmarks '' 1713 test_lockmarks 'lockmarks' 1714 1715 teardown(function() 1716 os.remove 'Xtest-reload' 1717 os.remove 'Xtest-undofile' 1718 os.remove '.Xtest-undofile.un~' 1719 end) 1720 end 1721 1722 describe('(with verify) handles', function() 1723 do_both(true) 1724 end) 1725 1726 describe('(without verify) handles', function() 1727 do_both(false) 1728 end) 1729 end) 1730 1731 describe('nvim_buf_attach on_detach', function() 1732 it('does not SEGFAULT when accessing window buffer info #14998', function() 1733 local code = function() 1734 local buf = vim.api.nvim_create_buf(false, false) 1735 1736 vim.cmd 'split' 1737 vim.api.nvim_win_set_buf(0, buf) 1738 1739 vim.api.nvim_buf_attach(buf, false, { 1740 on_detach = function(_, buf0) 1741 vim.fn.tabpagebuflist() 1742 vim.fn.win_findbuf(buf0) 1743 end, 1744 }) 1745 end 1746 1747 exec_lua(code) 1748 command('q!') 1749 n.assert_alive() 1750 1751 exec_lua(code) 1752 command('bd!') 1753 n.assert_alive() 1754 end) 1755 1756 it('no invalid lnum error for closed memline #31251', function() 1757 eq(vim.NIL, exec_lua('return _G.did_detach')) 1758 exec_lua(function() 1759 vim.api.nvim_buf_set_lines(0, 0, -1, false, { '' }) 1760 local bufname = 'buf2' 1761 local buf = vim.api.nvim_create_buf(false, true) 1762 vim.api.nvim_buf_set_name(buf, bufname) 1763 vim.bo[buf].bufhidden = 'wipe' 1764 vim.cmd('vertical diffsplit ' .. bufname) 1765 vim.api.nvim_buf_attach(0, false, { 1766 on_detach = function() 1767 vim.cmd('redraw') 1768 _G.did_detach = true 1769 end, 1770 }) 1771 vim.cmd.bdelete() 1772 end) 1773 eq(true, exec_lua('return _G.did_detach')) 1774 end) 1775 1776 it('called before buf_freeall autocommands', function() 1777 exec_lua(function() 1778 vim.api.nvim_create_autocmd({ 'BufUnload', 'BufDelete', 'BufWipeout' }, { 1779 callback = function(args) 1780 table.insert( 1781 _G.events, 1782 ('%s: %d %s'):format( 1783 args.event, 1784 args.buf, 1785 tostring(vim.api.nvim_buf_is_loaded(args.buf)) 1786 ) 1787 ) 1788 end, 1789 }) 1790 function _G.on_detach(_, b) 1791 table.insert( 1792 _G.events, 1793 ('on_detach: %d %s'):format(b, tostring(vim.api.nvim_buf_is_loaded(b))) 1794 ) 1795 end 1796 _G.events = {} 1797 vim.cmd 'new' 1798 vim.bo.bufhidden = 'wipe' 1799 vim.api.nvim_buf_attach(0, false, { on_detach = _G.on_detach }) 1800 vim.cmd 'quit!' 1801 end) 1802 1803 eq( 1804 { 'on_detach: 2 true', 'BufUnload: 2 true', 'BufDelete: 2 true', 'BufWipeout: 2 true' }, 1805 exec_lua('return _G.events') 1806 ) 1807 eq(false, api.nvim_buf_is_valid(2)) 1808 1809 exec_lua(function() 1810 _G.events = {} 1811 local buf = vim.api.nvim_create_buf(false, true) 1812 vim.api.nvim_buf_attach(buf, false, { on_detach = _G.on_detach }) 1813 vim.api.nvim_buf_delete(buf, { force = true }) 1814 end) 1815 1816 -- Was unlisted, so no BufDelete. 1817 eq( 1818 { 'on_detach: 3 true', 'BufUnload: 3 true', 'BufWipeout: 3 true' }, 1819 exec_lua('return _G.events') 1820 ) 1821 eq(false, api.nvim_buf_is_valid(3)) 1822 1823 exec_lua(function() 1824 _G.events = {} 1825 vim.api.nvim_buf_attach(1, false, { on_detach = _G.on_detach }) 1826 vim.api.nvim_create_autocmd('BufUnload', { 1827 buffer = 1, 1828 once = true, 1829 callback = function() 1830 vim.api.nvim_buf_attach(1, false, { 1831 on_detach = function(...) 1832 vim.fn.bufload(1) -- Leaks the memfile it were to run inside free_buffer_stuff. 1833 return _G.on_detach(...) 1834 end, 1835 }) 1836 table.insert(_G.events, 'local BufUnload') 1837 end, 1838 }) 1839 vim.cmd 'edit asdf' -- Reuses buffer 1. 1840 end) 1841 1842 -- on_detach shouldn't run after autocommands when reusing a buffer (in free_buffer_stuff), even 1843 -- if those autocommands registered it, as curbuf may be in a semi-unloaded state at that point. 1844 eq({ 1845 'on_detach: 1 true', 1846 'BufUnload: 1 true', 1847 'local BufUnload', 1848 'BufDelete: 1 true', 1849 'BufWipeout: 1 true', 1850 }, exec_lua('return _G.events')) 1851 1852 exec_lua(function() 1853 _G.events = {} 1854 vim.api.nvim_buf_attach(0, false, { on_detach = _G.on_detach }) 1855 vim.cmd 'edit' 1856 end) 1857 1858 -- Re-edit buffer; on_detach is called. 1859 eq({ 'on_detach: 1 true', 'BufUnload: 1 true' }, exec_lua('return _G.events')) 1860 eq(true, api.nvim_buf_is_valid(1)) 1861 1862 exec_lua(function() 1863 vim.cmd '%bwipeout!' 1864 vim.bo.modified = true 1865 _G.events = {} 1866 vim.api.nvim_buf_attach(0, false, { on_detach = _G.on_detach }) 1867 vim.api.nvim_buf_delete(0, { force = true }) 1868 end) 1869 1870 -- on_detach must still be first when wiping the last buffer if it's listed and non-reusable. 1871 -- Previously: BufUnload → BufDelete → on_detach → BufWipeout. 1872 eq( 1873 { 'on_detach: 4 true', 'BufUnload: 4 true', 'BufDelete: 4 true', 'BufWipeout: 4 false' }, 1874 exec_lua('return _G.events') 1875 ) 1876 end) 1877 1878 it('disallows splitting', function() 1879 command('new | setlocal bufhidden=wipe') 1880 local buf = api.nvim_get_current_buf() 1881 exec_lua(function() 1882 vim.api.nvim_buf_attach(0, false, { 1883 on_detach = function() 1884 -- Used to allow opening more views into a closing buffer, resulting in open windows to an 1885 -- unloaded buffer. 1886 vim.cmd [=[execute "normal! \<C-W>s"]=] 1887 end, 1888 }) 1889 end) 1890 matches('E1159: Cannot split a window when closing the buffer$', pcall_err(command, 'quit!')) 1891 eq({}, fn.win_findbuf(buf)) 1892 eq(false, api.nvim_buf_is_valid(buf)) 1893 end) 1894 end) 1895 1896 it('nvim_buf_attach from buf_freeall autocommands does not leak', function() 1897 exec_lua(function() 1898 local b = vim.api.nvim_create_buf(true, true) 1899 vim.api.nvim_create_autocmd('BufWipeout', { 1900 buffer = b, 1901 once = true, 1902 callback = function() 1903 vim.api.nvim_buf_attach(b, false, {}) 1904 _G.autocmd_fired = true 1905 end, 1906 }) 1907 vim.api.nvim_buf_delete(b, { force = true }) 1908 end) 1909 eq(true, exec_lua('return _G.autocmd_fired')) 1910 end)