jump_spec.lua (14821B)
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 = n.clear 6 local command = n.command 7 local dedent = t.dedent 8 local eq = t.eq 9 local fn = n.fn 10 local feed = n.feed 11 local exec_capture = n.exec_capture 12 local write_file = t.write_file 13 local api = n.api 14 15 describe('jumplist', function() 16 local fname1 = 'Xtest-functional-normal-jump' 17 local fname2 = fname1 .. '2' 18 before_each(clear) 19 after_each(function() 20 os.remove(fname1) 21 os.remove(fname2) 22 end) 23 24 it('does not add a new entry on startup', function() 25 eq('\n jump line col file/text\n>', fn.execute('jumps')) 26 end) 27 28 it('does not require two <C-O> strokes to jump back', function() 29 write_file(fname1, 'first file contents') 30 write_file(fname2, 'second file contents') 31 32 command('args ' .. fname1 .. ' ' .. fname2) 33 local buf1 = fn.bufnr(fname1) 34 local buf2 = fn.bufnr(fname2) 35 36 command('next') 37 feed('<C-O>') 38 eq(buf1, fn.bufnr('%')) 39 40 command('first') 41 command('snext') 42 feed('<C-O>') 43 eq(buf1, fn.bufnr('%')) 44 feed('<C-I>') 45 eq(buf2, fn.bufnr('%')) 46 feed('<C-O>') 47 eq(buf1, fn.bufnr('%')) 48 49 command('drop ' .. fname2) 50 feed('<C-O>') 51 eq(buf1, fn.bufnr('%')) 52 end) 53 54 it('<C-O> scrolls cursor halfway when switching buffer #25763', function() 55 write_file(fname1, ('foobar\n'):rep(100)) 56 write_file(fname2, 'baz') 57 58 local screen = Screen.new(5, 25) 59 command('set number') 60 command('edit ' .. fname1) 61 feed('35gg') 62 command('edit ' .. fname2) 63 feed('<C-O>') 64 screen:expect { 65 grid = [[ 66 {1: 24 }foobar | 67 {1: 25 }foobar | 68 {1: 26 }foobar | 69 {1: 27 }foobar | 70 {1: 28 }foobar | 71 {1: 29 }foobar | 72 {1: 30 }foobar | 73 {1: 31 }foobar | 74 {1: 32 }foobar | 75 {1: 33 }foobar | 76 {1: 34 }foobar | 77 {1: 35 }^foobar | 78 {1: 36 }foobar | 79 {1: 37 }foobar | 80 {1: 38 }foobar | 81 {1: 39 }foobar | 82 {1: 40 }foobar | 83 {1: 41 }foobar | 84 {1: 42 }foobar | 85 {1: 43 }foobar | 86 {1: 44 }foobar | 87 {1: 45 }foobar | 88 {1: 46 }foobar | 89 {1: 47 }foobar | 90 | 91 ]], 92 attr_ids = { 93 [1] = { foreground = Screen.colors.Brown }, 94 }, 95 } 96 end) 97 end) 98 99 describe("jumpoptions=stack behaves like 'tagstack'", function() 100 before_each(function() 101 clear() 102 feed(':clearjumps<cr>') 103 104 -- Add lines so that we have locations to jump to. 105 for i = 1, 101, 1 do 106 feed('iLine ' .. i .. '<cr><esc>') 107 end 108 109 -- Jump around to add some locations to the jump list. 110 feed('0gg') 111 feed('10gg') 112 feed('20gg') 113 feed('30gg') 114 feed('40gg') 115 feed('50gg') 116 117 feed(':set jumpoptions=stack<cr>') 118 end) 119 120 after_each(function() 121 feed('set jumpoptions=') 122 end) 123 124 it('discards the tail when navigating from the middle', function() 125 feed('<C-O>') 126 feed('<C-O>') 127 128 eq( 129 '' 130 .. ' jump line col file/text\n' 131 .. ' 4 102 0 \n' 132 .. ' 3 1 0 Line 1\n' 133 .. ' 2 10 0 Line 10\n' 134 .. ' 1 20 0 Line 20\n' 135 .. '> 0 30 0 Line 30\n' 136 .. ' 1 40 0 Line 40\n' 137 .. ' 2 50 0 Line 50', 138 exec_capture('jumps') 139 ) 140 141 feed('90gg') 142 143 eq( 144 '' 145 .. ' jump line col file/text\n' 146 .. ' 5 102 0 \n' 147 .. ' 4 1 0 Line 1\n' 148 .. ' 3 10 0 Line 10\n' 149 .. ' 2 20 0 Line 20\n' 150 .. ' 1 30 0 Line 30\n' 151 .. '>', 152 exec_capture('jumps') 153 ) 154 end) 155 156 it('does not add the same location twice adjacently', function() 157 feed('60gg') 158 feed('60gg') 159 160 eq( 161 '' 162 .. ' jump line col file/text\n' 163 .. ' 7 102 0 \n' 164 .. ' 6 1 0 Line 1\n' 165 .. ' 5 10 0 Line 10\n' 166 .. ' 4 20 0 Line 20\n' 167 .. ' 3 30 0 Line 30\n' 168 .. ' 2 40 0 Line 40\n' 169 .. ' 1 50 0 Line 50\n' 170 .. '>', 171 exec_capture('jumps') 172 ) 173 end) 174 175 it('does add the same location twice nonadjacently', function() 176 feed('10gg') 177 feed('20gg') 178 179 eq( 180 '' 181 .. ' jump line col file/text\n' 182 .. ' 8 102 0 \n' 183 .. ' 7 1 0 Line 1\n' 184 .. ' 6 10 0 Line 10\n' 185 .. ' 5 20 0 Line 20\n' 186 .. ' 4 30 0 Line 30\n' 187 .. ' 3 40 0 Line 40\n' 188 .. ' 2 50 0 Line 50\n' 189 .. ' 1 10 0 Line 10\n' 190 .. '>', 191 exec_capture('jumps') 192 ) 193 end) 194 end) 195 196 describe('buffer deletion with jumpoptions+=clean', function() 197 local base_file = 'Xtest-functional-buffer-deletion' 198 local file1 = base_file .. '1' 199 local file2 = base_file .. '2' 200 local file3 = base_file .. '3' 201 local base_content = 'text' 202 local content1 = base_content .. '1' 203 local content2 = base_content .. '2' 204 local content3 = base_content .. '3' 205 206 local function format_jumplist(input) 207 return dedent(input) 208 :gsub('%{file1%}', file1) 209 :gsub('%{file2%}', file2) 210 :gsub('%{file3%}', file3) 211 :gsub('%{content1%}', content1) 212 :gsub('%{content2%}', content2) 213 :gsub('%{content3%}', content3) 214 end 215 216 before_each(function() 217 clear() 218 command('clearjumps') 219 220 write_file(file1, content1, false, false) 221 write_file(file2, content2, false, false) 222 write_file(file3, content3, false, false) 223 224 command('edit ' .. file1) 225 command('edit ' .. file2) 226 command('edit ' .. file3) 227 end) 228 229 after_each(function() 230 os.remove(file1) 231 os.remove(file2) 232 os.remove(file3) 233 end) 234 235 it('deletes jump list entries when the current buffer is deleted', function() 236 command('edit ' .. file1) 237 238 eq( 239 format_jumplist([[ 240 jump line col file/text 241 3 1 0 {content1} 242 2 1 0 {file2} 243 1 1 0 {file3} 244 >]]), 245 exec_capture('jumps') 246 ) 247 248 command('bwipeout') 249 250 eq( 251 format_jumplist([[ 252 jump line col file/text 253 1 1 0 {file2} 254 > 0 1 0 {content3}]]), 255 exec_capture('jumps') 256 ) 257 end) 258 259 it('deletes jump list entries when another buffer is deleted', function() 260 eq( 261 format_jumplist([[ 262 jump line col file/text 263 2 1 0 {file1} 264 1 1 0 {file2} 265 >]]), 266 exec_capture('jumps') 267 ) 268 269 command('bwipeout ' .. file2) 270 271 eq( 272 format_jumplist([[ 273 jump line col file/text 274 1 1 0 {file1} 275 >]]), 276 exec_capture('jumps') 277 ) 278 end) 279 280 it('sets the correct jump index when the current buffer is deleted', function() 281 feed('<C-O>') 282 283 eq( 284 format_jumplist([[ 285 jump line col file/text 286 1 1 0 {file1} 287 > 0 1 0 {content2} 288 1 1 0 {file3}]]), 289 exec_capture('jumps') 290 ) 291 292 command('bw') 293 294 eq( 295 format_jumplist([[ 296 jump line col file/text 297 1 1 0 {file1} 298 > 0 1 0 {content3}]]), 299 exec_capture('jumps') 300 ) 301 end) 302 303 it('sets the correct jump index when the another buffer is deleted', function() 304 feed('<C-O>') 305 306 eq( 307 format_jumplist([[ 308 jump line col file/text 309 1 1 0 {file1} 310 > 0 1 0 {content2} 311 1 1 0 {file3}]]), 312 exec_capture('jumps') 313 ) 314 315 command('bwipeout ' .. file1) 316 317 eq( 318 format_jumplist([[ 319 jump line col file/text 320 > 0 1 0 {content2} 321 1 1 0 {file3}]]), 322 exec_capture('jumps') 323 ) 324 end) 325 end) 326 327 describe('buffer deletion with jumpoptions-=clean', function() 328 local base_file = 'Xtest-functional-buffer-deletion' 329 local file1 = base_file .. '1' 330 local file2 = base_file .. '2' 331 local base_content = 'text' 332 local content1 = base_content .. '1' 333 local content2 = base_content .. '2' 334 335 before_each(function() 336 clear() 337 command('clearjumps') 338 command('set jumpoptions-=clean') 339 340 write_file(file1, content1, false, false) 341 write_file(file2, content2, false, false) 342 343 command('edit ' .. file1) 344 command('edit ' .. file2) 345 end) 346 347 after_each(function() 348 os.remove(file1) 349 os.remove(file2) 350 end) 351 352 it('Ctrl-O reopens previous buffer with :bunload or :bdelete #28968', function() 353 eq(file2, fn.bufname('')) 354 command('bunload') 355 eq(file1, fn.bufname('')) 356 feed('<C-O>') 357 eq(file2, fn.bufname('')) 358 command('bdelete') 359 eq(file1, fn.bufname('')) 360 feed('<C-O>') 361 eq(file2, fn.bufname('')) 362 end) 363 end) 364 365 describe('jumpoptions=view', function() 366 local file1 = 'Xtestfile-functional-editor-jumps' 367 local file2 = 'Xtestfile-functional-editor-jumps-2' 368 local function content() 369 local c = {} 370 for i = 1, 30 do 371 c[i] = i .. ' line' 372 end 373 return table.concat(c, '\n') 374 end 375 before_each(function() 376 clear() 377 write_file(file1, content(), false, false) 378 write_file(file2, content(), false, false) 379 command('set jumpoptions=view') 380 end) 381 after_each(function() 382 os.remove(file1) 383 os.remove(file2) 384 end) 385 386 it('restores the view', function() 387 local screen = Screen.new(5, 8) 388 command('edit ' .. file1) 389 feed('12Gztj') 390 feed('gg<C-o>') 391 screen:expect([[ 392 12 line | 393 ^13 line | 394 14 line | 395 15 line | 396 16 line | 397 17 line | 398 18 line | 399 | 400 ]]) 401 end) 402 403 it('restores the view across files', function() 404 local screen = Screen.new(5, 5) 405 command('args ' .. file1 .. ' ' .. file2) 406 feed('12Gzt') 407 command('next') 408 feed('G') 409 screen:expect([[ 410 27 line | 411 28 line | 412 29 line | 413 ^30 line | 414 | 415 ]]) 416 feed('<C-o><C-o>') 417 screen:expect([[ 418 ^12 line | 419 13 line | 420 14 line | 421 15 line | 422 | 423 ]]) 424 end) 425 426 it('restores the view across files with <C-^>/:bprevious/:bnext', function() 427 local screen = Screen.new(5, 5) 428 command('args ' .. file1 .. ' ' .. file2) 429 feed('12Gzt') 430 local s1 = [[ 431 ^12 line | 432 13 line | 433 14 line | 434 15 line | 435 | 436 ]] 437 screen:expect(s1) 438 command('next') 439 feed('G') 440 local s2 = [[ 441 27 line | 442 28 line | 443 29 line | 444 ^30 line | 445 | 446 ]] 447 screen:expect(s2) 448 feed('<C-^>') 449 screen:expect(s1) 450 feed('<C-^>') 451 screen:expect(s2) 452 command('bprevious') 453 screen:expect(s1) 454 command('bnext') 455 screen:expect(s2) 456 end) 457 458 it("falls back to standard behavior when view can't be recovered", function() 459 local screen = Screen.new(5, 8) 460 command('edit ' .. file1) 461 feed('7GzbG') 462 api.nvim_buf_set_lines(0, 0, 2, true, {}) 463 -- Move to line 7, and set it as the last line visible on the view with zb, meaning to recover 464 -- the view it needs to put the cursor 7 lines from the top line. Then go to the end of the 465 -- file, delete 2 lines before line 7, meaning the jump/mark is moved 2 lines up to line 5. 466 -- Therefore when trying to jump back to it it's not possible to set a 7 line offset from the 467 -- mark position to the top line, since there's only 5 lines from the mark position to line 0. 468 -- Therefore falls back to standard behavior which is centering the view/line. 469 feed('<C-o>') 470 screen:expect([[ 471 4 line | 472 5 line | 473 6 line | 474 ^7 line | 475 8 line | 476 9 line | 477 10 line | 478 | 479 ]]) 480 end) 481 482 it('falls back to standard behavior for a mark without a view', function() 483 local screen = Screen.new(5, 8) 484 command('edit ' .. file1) 485 feed('10ggzzvwy') 486 screen:expect([[ 487 7 line | 488 8 line | 489 9 line | 490 ^10 line | 491 11 line | 492 12 line | 493 13 line | 494 | 495 ]]) 496 feed('`]') 497 screen:expect([[ 498 7 line | 499 8 line | 500 9 line | 501 10 ^line | 502 11 line | 503 12 line | 504 13 line | 505 | 506 ]]) 507 end) 508 509 describe('tagstack popping', function() 510 local tags_file = 'Xtestfile-functional-editor-jumps-tags' 511 before_each(function() 512 write_file( 513 tags_file, 514 '!_TAG_FILE_ENCODING\tutf-8\t//\n' 515 .. ('10\t%s\t2\n'):format(file1) 516 .. ('30\t%s\t20\n'):format(file2), 517 false, 518 false 519 ) 520 command('set tags=' .. tags_file) 521 end) 522 after_each(function() 523 os.remove(tags_file) 524 end) 525 526 it('restores the view', function() 527 local screen = Screen.new(5, 6) 528 command('set laststatus=2 | set statusline=%f | edit ' .. file1) 529 feed('10Gzb<C-]>30Gzt<C-]>') 530 screen:expect([[ 531 19 line | 532 ^20 line | 533 21 line | 534 22 line | 535 {3:<tor-jumps-2}| 536 | 537 ]]) 538 feed('<C-T>') 539 screen:expect([[ 540 ^30 line | 541 {1:~ }|*3 542 {3:<ditor-jumps}| 543 | 544 ]]) 545 feed('<C-T>') 546 screen:expect([[ 547 7 line | 548 8 line | 549 9 line | 550 ^10 line | 551 {3:<ditor-jumps}| 552 | 553 ]]) 554 555 local tagstack = ' # TO tag FROM line in file/text\n' 556 .. '> 1 1 10 10 \n' 557 .. ' 2 1 30 30 ' 558 eq(tagstack, exec_capture('tags')) 559 -- Un-pop via `:tag`; like `:tag 10` it should go to L2. (restoring cursor/view doesn't apply) 560 -- However, after a `:pop`, it should restore the view from after the single `<C-E>` below. 561 feed('<C-E>') 562 command('tag') 563 screen:expect([[ 564 1 line | 565 ^2 line | 566 3 line | 567 4 line | 568 {3:<ditor-jumps}| 569 | 570 ]]) 571 command('pop') 572 screen:expect([[ 573 8 line | 574 9 line | 575 ^10 line | 576 11 line | 577 {3:<ditor-jumps}| 578 | 579 ]]) 580 eq(tagstack, exec_capture('tags')) 581 582 -- No view information associated with tags set via settagstack(). 583 -- (specifically, replacing tag "10" shouldn't continue to use it's now unrelated view) 584 fn.settagstack(fn.win_getid(), { 585 items = { { from = { fn.bufnr(), 11, 1, 0, 1 }, tagname = 'settagstack!!!' } }, 586 }, 'r') 587 tagstack = ' # TO tag FROM line in file/text\n' 588 .. ' 1 1 settagstack!!! 11 \n' 589 .. '>' 590 eq(tagstack, exec_capture('tags')) 591 feed('G<C-T>') 592 screen:expect([[ 593 10 line | 594 ^11 line | 595 12 line | 596 13 line | 597 {3:<ditor-jumps}| 598 | 599 ]]) 600 end) 601 end) 602 end)