neovim

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

win_scrolled_resized_spec.lua (13734B)


      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 eq = t.eq
      7 local eval = n.eval
      8 local exec = n.exec
      9 local command = n.command
     10 local feed = n.feed
     11 local api = n.api
     12 local assert_alive = n.assert_alive
     13 
     14 before_each(clear)
     15 
     16 describe('WinResized', function()
     17  -- oldtest: Test_WinResized()
     18  it('works', function()
     19    exec([[
     20      set scrolloff=0
     21      call setline(1, ['111', '222'])
     22      vnew
     23      call setline(1, ['aaa', 'bbb'])
     24      new
     25      call setline(1, ['foo', 'bar'])
     26 
     27      let g:resized = 0
     28      au WinResized * let g:resized += 1
     29      au WinResized * let g:v_event = deepcopy(v:event)
     30    ]])
     31    eq(0, eval('g:resized'))
     32 
     33    -- increase window height, two windows will be reported
     34    feed('<C-W>+')
     35    eq(1, eval('g:resized'))
     36    eq({ windows = { 1002, 1001 } }, eval('g:v_event'))
     37 
     38    -- increase window width, three windows will be reported
     39    feed('<C-W>>')
     40    eq(2, eval('g:resized'))
     41    eq({ windows = { 1002, 1001, 1000 } }, eval('g:v_event'))
     42  end)
     43 
     44  it('is triggered in terminal mode #21197 #27207', function()
     45    exec([[
     46      autocmd TermOpen * startinsert
     47      let g:resized = 0
     48      autocmd WinResized * let g:resized += 1
     49    ]])
     50    eq(0, eval('g:resized'))
     51 
     52    command('vsplit term://')
     53    feed('<Ignore>') -- Add input to separate two RPC requests
     54    eq({ mode = 't', blocking = false }, api.nvim_get_mode())
     55    eq(1, eval('g:resized'))
     56 
     57    command('split')
     58    feed('<Ignore>') -- Add input to separate two RPC requests
     59    eq({ mode = 't', blocking = false }, api.nvim_get_mode())
     60    eq(2, eval('g:resized'))
     61  end)
     62 end)
     63 
     64 describe('WinScrolled', function()
     65  local win_id
     66 
     67  before_each(function()
     68    win_id = api.nvim_get_current_win()
     69    command(string.format('autocmd WinScrolled %d let g:matched = v:true', win_id))
     70    exec([[
     71      let g:scrolled = 0
     72      au WinScrolled * let g:scrolled += 1
     73      au WinScrolled * let g:amatch = str2nr(expand('<amatch>'))
     74      au WinScrolled * let g:afile = str2nr(expand('<afile>'))
     75      au WinScrolled * let g:v_event = deepcopy(v:event)
     76    ]])
     77  end)
     78 
     79  after_each(function()
     80    eq(true, eval('g:matched'))
     81    eq(win_id, eval('g:amatch'))
     82    eq(win_id, eval('g:afile'))
     83  end)
     84 
     85  it('is triggered by scrolling vertically', function()
     86    local lines = { '123', '123' }
     87    api.nvim_buf_set_lines(0, 0, -1, true, lines)
     88    eq(0, eval('g:scrolled'))
     89 
     90    feed('<C-E>')
     91    eq(1, eval('g:scrolled'))
     92    eq({
     93      all = { leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0 },
     94      ['1000'] = { leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0 },
     95    }, eval('g:v_event'))
     96 
     97    feed('<C-Y>')
     98    eq(2, eval('g:scrolled'))
     99    eq({
    100      all = { leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0 },
    101      ['1000'] = { leftcol = 0, topline = -1, topfill = 0, width = 0, height = 0, skipcol = 0 },
    102    }, eval('g:v_event'))
    103  end)
    104 
    105  it('is triggered by scrolling horizontally', function()
    106    command('set nowrap')
    107    local width = api.nvim_win_get_width(0)
    108    local line = '123' .. ('*'):rep(width * 2)
    109    local lines = { line, line }
    110    api.nvim_buf_set_lines(0, 0, -1, true, lines)
    111    eq(0, eval('g:scrolled'))
    112 
    113    feed('zl')
    114    eq(1, eval('g:scrolled'))
    115    eq({
    116      all = { leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0 },
    117      ['1000'] = { leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0 },
    118    }, eval('g:v_event'))
    119 
    120    feed('zh')
    121    eq(2, eval('g:scrolled'))
    122    eq({
    123      all = { leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0 },
    124      ['1000'] = { leftcol = -1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0 },
    125    }, eval('g:v_event'))
    126  end)
    127 
    128  it('is triggered by horizontal scrolling from cursor move', function()
    129    command('set nowrap')
    130    local lines = { '', '', 'Foo' }
    131    api.nvim_buf_set_lines(0, 0, -1, true, lines)
    132    api.nvim_win_set_cursor(0, { 3, 0 })
    133    eq(0, eval('g:scrolled'))
    134 
    135    feed('zl')
    136    eq(1, eval('g:scrolled'))
    137    eq({
    138      all = { leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0 },
    139      ['1000'] = { leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0 },
    140    }, eval('g:v_event'))
    141 
    142    feed('zl')
    143    eq(2, eval('g:scrolled'))
    144    eq({
    145      all = { leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0 },
    146      ['1000'] = { leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0 },
    147    }, eval('g:v_event'))
    148 
    149    feed('h')
    150    eq(3, eval('g:scrolled'))
    151    eq({
    152      all = { leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0 },
    153      ['1000'] = { leftcol = -1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0 },
    154    }, eval('g:v_event'))
    155 
    156    feed('zh')
    157    eq(4, eval('g:scrolled'))
    158    eq({
    159      all = { leftcol = 1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0 },
    160      ['1000'] = { leftcol = -1, topline = 0, topfill = 0, width = 0, height = 0, skipcol = 0 },
    161    }, eval('g:v_event'))
    162  end)
    163 
    164  -- oldtest: Test_WinScrolled_long_wrapped()
    165  it('is triggered by scrolling on a long wrapped line #19968', function()
    166    local height = api.nvim_win_get_height(0)
    167    local width = api.nvim_win_get_width(0)
    168    api.nvim_buf_set_lines(0, 0, -1, true, { ('foo'):rep(height * width) })
    169    api.nvim_win_set_cursor(0, { 1, height * width - 1 })
    170    eq(0, eval('g:scrolled'))
    171 
    172    feed('gj')
    173    eq(1, eval('g:scrolled'))
    174    eq({
    175      all = { leftcol = 0, topline = 0, topfill = 0, width = 0, height = 0, skipcol = width },
    176      ['1000'] = { leftcol = 0, topline = 0, topfill = 0, width = 0, height = 0, skipcol = width },
    177    }, eval('g:v_event'))
    178 
    179    feed('0')
    180    eq(2, eval('g:scrolled'))
    181    eq({
    182      all = { leftcol = 0, topline = 0, topfill = 0, width = 0, height = 0, skipcol = width },
    183      ['1000'] = { leftcol = 0, topline = 0, topfill = 0, width = 0, height = 0, skipcol = -width },
    184    }, eval('g:v_event'))
    185 
    186    feed('$')
    187    eq(3, eval('g:scrolled'))
    188  end)
    189 
    190  it('is triggered when the window scrolls in Insert mode', function()
    191    local height = api.nvim_win_get_height(0)
    192    local lines = {}
    193    for i = 1, height * 2 do
    194      lines[i] = tostring(i)
    195    end
    196    api.nvim_buf_set_lines(0, 0, -1, true, lines)
    197 
    198    feed('M')
    199    eq(0, eval('g:scrolled'))
    200 
    201    feed('i<C-X><C-E><Esc>')
    202    eq(1, eval('g:scrolled'))
    203    eq({
    204      all = { leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0 },
    205      ['1000'] = { leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0 },
    206    }, eval('g:v_event'))
    207 
    208    feed('i<C-X><C-Y><Esc>')
    209    eq(2, eval('g:scrolled'))
    210    eq({
    211      all = { leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0 },
    212      ['1000'] = { leftcol = 0, topline = -1, topfill = 0, width = 0, height = 0, skipcol = 0 },
    213    }, eval('g:v_event'))
    214 
    215    feed('L')
    216    eq(2, eval('g:scrolled'))
    217 
    218    feed('A<CR><Esc>')
    219    eq(3, eval('g:scrolled'))
    220    eq({
    221      all = { leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0 },
    222      ['1000'] = { leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0 },
    223    }, eval('g:v_event'))
    224  end)
    225 end)
    226 
    227 describe('WinScrolled', function()
    228  -- oldtest: Test_WinScrolled_mouse()
    229  it('is triggered by mouse scrolling in another window', function()
    230    local _ = Screen.new(75, 10)
    231    exec([[
    232      set nowrap scrolloff=0
    233      set mouse=a
    234      call setline(1, ['foo']->repeat(32))
    235      split
    236      let g:scrolled = 0
    237      au WinScrolled * let g:scrolled += 1
    238    ]])
    239    eq(0, eval('g:scrolled'))
    240 
    241    -- With the upper split focused, send a scroll-down event to the unfocused one.
    242    api.nvim_input_mouse('wheel', 'down', '', 0, 6, 0)
    243    eq(1, eval('g:scrolled'))
    244 
    245    -- Again, but this time while we're in insert mode.
    246    feed('i')
    247    api.nvim_input_mouse('wheel', 'down', '', 0, 6, 0)
    248    feed('<Esc>')
    249    eq(2, eval('g:scrolled'))
    250  end)
    251 
    252  -- oldtest: Test_WinScrolled_close_curwin()
    253  it('closing window does not cause use-after-free #13265', function()
    254    exec([[
    255      set nowrap scrolloff=0
    256      call setline(1, ['aaa', 'bbb'])
    257      vsplit
    258      au WinScrolled * close
    259    ]])
    260 
    261    -- This was using freed memory
    262    feed('<C-E>')
    263    assert_alive()
    264  end)
    265 
    266  -- oldtest: Test_WinScrolled_diff()
    267  it('is triggered for both windows when scrolling in diff mode', function()
    268    exec([[
    269      set diffopt+=foldcolumn:0
    270      call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'])
    271      vnew
    272      call setline(1, ['d', 'e', 'f', 'g', 'h', 'i'])
    273      windo diffthis
    274      au WinScrolled * let g:v_event = deepcopy(v:event)
    275    ]])
    276 
    277    feed('<C-E>')
    278    eq({
    279      all = { leftcol = 0, topline = 1, topfill = 1, width = 0, height = 0, skipcol = 0 },
    280      ['1000'] = { leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0 },
    281      ['1001'] = { leftcol = 0, topline = 0, topfill = -1, width = 0, height = 0, skipcol = 0 },
    282    }, eval('g:v_event'))
    283 
    284    feed('2<C-E>')
    285    eq({
    286      all = { leftcol = 0, topline = 2, topfill = 2, width = 0, height = 0, skipcol = 0 },
    287      ['1000'] = { leftcol = 0, topline = 2, topfill = 0, width = 0, height = 0, skipcol = 0 },
    288      ['1001'] = { leftcol = 0, topline = 0, topfill = -2, width = 0, height = 0, skipcol = 0 },
    289    }, eval('g:v_event'))
    290 
    291    feed('<C-E>')
    292    eq({
    293      all = { leftcol = 0, topline = 2, topfill = 0, width = 0, height = 0, skipcol = 0 },
    294      ['1000'] = { leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0 },
    295      ['1001'] = { leftcol = 0, topline = 1, topfill = 0, width = 0, height = 0, skipcol = 0 },
    296    }, eval('g:v_event'))
    297 
    298    feed('2<C-Y>')
    299    eq({
    300      all = { leftcol = 0, topline = 3, topfill = 1, width = 0, height = 0, skipcol = 0 },
    301      ['1000'] = { leftcol = 0, topline = -2, topfill = 0, width = 0, height = 0, skipcol = 0 },
    302      ['1001'] = { leftcol = 0, topline = -1, topfill = 1, width = 0, height = 0, skipcol = 0 },
    303    }, eval('g:v_event'))
    304  end)
    305 
    306  it('is triggered by mouse scrolling in unfocused floating window #18222', function()
    307    local screen = Screen.new(80, 24)
    308 
    309    exec([[
    310      let g:scrolled = 0
    311      autocmd WinScrolled * let g:scrolled += 1
    312      autocmd WinScrolled * let g:amatch = expand('<amatch>')
    313      autocmd WinScrolled * let g:v_event = deepcopy(v:event)
    314    ]])
    315    eq(0, eval('g:scrolled'))
    316 
    317    local buf = api.nvim_create_buf(true, true)
    318    api.nvim_buf_set_lines(
    319      buf,
    320      0,
    321      -1,
    322      false,
    323      { '@', 'b', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n' }
    324    )
    325    local win = api.nvim_open_win(buf, false, {
    326      height = 5,
    327      width = 10,
    328      col = 0,
    329      row = 1,
    330      relative = 'editor',
    331      style = 'minimal',
    332    })
    333    screen:expect({ any = '@' })
    334    local winid_str = tostring(win)
    335    -- WinScrolled should not be triggered when creating a new floating window
    336    eq(0, eval('g:scrolled'))
    337 
    338    api.nvim_input_mouse('wheel', 'down', '', 0, 3, 3)
    339    eq(1, eval('g:scrolled'))
    340    eq(winid_str, eval('g:amatch'))
    341    eq({
    342      all = { leftcol = 0, topline = 3, topfill = 0, width = 0, height = 0, skipcol = 0 },
    343      [winid_str] = { leftcol = 0, topline = 3, topfill = 0, width = 0, height = 0, skipcol = 0 },
    344    }, eval('g:v_event'))
    345 
    346    api.nvim_input_mouse('wheel', 'up', '', 0, 3, 3)
    347    eq(2, eval('g:scrolled'))
    348    eq(tostring(win), eval('g:amatch'))
    349    eq({
    350      all = { leftcol = 0, topline = 3, topfill = 0, width = 0, height = 0, skipcol = 0 },
    351      [winid_str] = { leftcol = 0, topline = -3, topfill = 0, width = 0, height = 0, skipcol = 0 },
    352    }, eval('g:v_event'))
    353  end)
    354 
    355  it('does not crash when WinResized closes popup before WinScrolled #35803', function()
    356    exec([[
    357      set scrolloff=0
    358      call setline(1, range(1, 100))
    359 
    360      " Create first popup window (will be resized and closed)
    361      let buf1 = nvim_create_buf(v:false, v:true)
    362      call nvim_buf_set_lines(buf1, 0, -1, v:false, map(range(1, 50), 'string(v:val)'))
    363      let popup1 = nvim_open_win(buf1, v:false, {
    364        \ 'relative': 'editor',
    365        \ 'width': 20,
    366        \ 'height': 5,
    367        \ 'col': 10,
    368        \ 'row': 5
    369        \ })
    370 
    371      " Create second popup window (will be scrolled)
    372      let buf2 = nvim_create_buf(v:false, v:true)
    373      call nvim_buf_set_lines(buf2, 0, -1, v:false, map(range(1, 50), 'string(v:val)'))
    374      let popup2 = nvim_open_win(buf2, v:false, {
    375        \ 'relative': 'editor',
    376        \ 'width': 20,
    377        \ 'height': 5,
    378        \ 'col': 35,
    379        \ 'row': 5
    380        \ })
    381 
    382      let g:resized = 0
    383      let g:scrolled = 0
    384 
    385      " WinResized autocmd resizes and closes the first popup
    386      autocmd WinResized * let g:resized += 1 | call nvim_win_set_height(popup1, 10) | call nvim_win_close(popup1, v:true)
    387      " WinScrolled autocmd scrolls the second popup
    388      autocmd WinScrolled * let g:scrolled += 1 | call nvim_win_call(popup2, {-> execute('normal! \<C-E>')})
    389    ]])
    390    eq(0, eval('g:resized'))
    391    eq(0, eval('g:scrolled'))
    392 
    393    -- Trigger a resize on popup1, which will close it
    394    -- This should trigger WinResized (which closes popup1) and WinScrolled (which scrolls popup2)
    395    -- Before the fix, WinScrolled would use the freed pointer causing a crash
    396    api.nvim_win_set_height(eval('popup1'), 8)
    397 
    398    -- The key is that it should not crash when WinResized closes a window
    399    -- that WinScrolled might have referenced via a stale buf_T pointer
    400    assert_alive()
    401    -- Verify autocmds were actually triggered
    402    eq(1, eval('g:resized > 0'))
    403  end)
    404 end)