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)