neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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)