1" Tests for popup windows
2
3source check.vim
4CheckFeature popupwin
5
6source screendump.vim
7
8func Test_simple_popup()
9  CheckScreendump
10
11  let lines =<< trim END
12	call setline(1, range(1, 100))
13	hi PopupColor1 ctermbg=lightblue
14	hi PopupColor2 ctermbg=lightcyan
15	hi EndOfBuffer ctermbg=lightgrey
16	hi Comment ctermfg=red
17	call prop_type_add('comment', #{highlight: 'Comment'})
18	let winid = popup_create('hello there', #{line: 3, col: 11, minwidth: 20, highlight: 'PopupColor1'})
19	let winid2 = popup_create(['another one', 'another two', 'another three'], #{line: 3, col: 25, minwidth: 20})
20	call setwinvar(winid2, '&wincolor', 'PopupColor2')
21  END
22  call writefile(lines, 'XtestPopup')
23  let buf = RunVimInTerminal('-S XtestPopup', #{rows: 10})
24  call VerifyScreenDump(buf, 'Test_popupwin_01', {})
25
26  " Add a tabpage
27  call term_sendkeys(buf, ":tabnew\<CR>")
28  call term_sendkeys(buf, ":let popupwin = popup_create(["
29	\ .. "#{text: 'other tab'},"
30	\ .. "#{text: 'a comment line', props: [#{"
31	\ .. "col: 3, length: 7, minwidth: 20, type: 'comment'"
32	\ .. "}]},"
33	\ .. "], #{line: 4, col: 9, minwidth: 20})\<CR>")
34  call VerifyScreenDump(buf, 'Test_popupwin_02', {})
35
36  " switch back to first tabpage
37  call term_sendkeys(buf, "gt")
38  call VerifyScreenDump(buf, 'Test_popupwin_03', {})
39
40  " close that tabpage
41  call term_sendkeys(buf, ":quit!\<CR>")
42  call VerifyScreenDump(buf, 'Test_popupwin_04', {})
43
44  " set 'columns' to a small value, size must be recomputed
45  call term_sendkeys(buf, ":let cols = &columns\<CR>")
46  call term_sendkeys(buf, ":set columns=12\<CR>")
47  call VerifyScreenDump(buf, 'Test_popupwin_04a', {})
48  call term_sendkeys(buf, ":let &columns = cols\<CR>")
49
50  " resize popup, show empty line at bottom
51  call term_sendkeys(buf, ":call popup_move(popupwin, #{minwidth: 15, maxwidth: 25, minheight: 3, maxheight: 5})\<CR>")
52  call term_sendkeys(buf, ":redraw\<CR>")
53  call VerifyScreenDump(buf, 'Test_popupwin_05', {})
54
55  " show not fitting line at bottom
56  call term_sendkeys(buf, ":call setbufline(winbufnr(popupwin), 3, 'this line will not fit here')\<CR>")
57  call term_sendkeys(buf, ":redraw\<CR>")
58  call VerifyScreenDump(buf, 'Test_popupwin_06', {})
59
60  " move popup over ruler
61  call term_sendkeys(buf, ":set cmdheight=2\<CR>")
62  call term_sendkeys(buf, ":call popup_move(popupwin, #{line: 7, col: 55})\<CR>")
63  call term_sendkeys(buf, ":\<CR>")
64  call VerifyScreenDump(buf, 'Test_popupwin_07', {})
65
66  " clear all popups after moving the cursor a bit, so that ruler is updated
67  call term_sendkeys(buf, "axxx\<Esc>")
68  call TermWait(buf)
69  call term_sendkeys(buf, "0")
70  call TermWait(buf)
71  call term_sendkeys(buf, ":call popup_clear()\<CR>")
72  call VerifyScreenDump(buf, 'Test_popupwin_08', {})
73
74  " clean up
75  call StopVimInTerminal(buf)
76  call delete('XtestPopup')
77endfunc
78
79func Test_popup_with_border_and_padding()
80  CheckScreendump
81
82  for iter in range(0, 1)
83    let lines =<< trim END
84	  call setline(1, range(1, 100))
85	  call popup_create('hello border', #{line: 2, col: 3, border: []})
86	  call popup_create('hello padding', #{line: 2, col: 23, padding: []})
87	  call popup_create('hello both', #{line: 2, col: 43, border: [], padding: [], highlight: 'Normal'})
88	  call popup_create('border TL', #{line: 6, col: 3, border: [1, 0, 0, 4]})
89	  call popup_create('paddings', #{line: 6, col: 23, padding: range(1, 4)})
90	  call popup_create('wrapped longer text', #{line: 8, col: 55, padding: [0, 3, 0, 3], border: [0, 1, 0, 1]})
91	  call popup_create('right aligned text', #{line: 11, col: 56, wrap: 0, padding: [0, 3, 0, 3], border: [0, 1, 0, 1]})
92	  call popup_create('X', #{line: 2, col: 73})
93	  call popup_create('X', #{line: 3, col: 74})
94	  call popup_create('X', #{line: 4, col: 75})
95	  call popup_create('X', #{line: 5, col: 76})
96    END
97    call insert(lines, iter == 1 ? '' : 'set enc=latin1')
98    call writefile(lines, 'XtestPopupBorder')
99    let buf = RunVimInTerminal('-S XtestPopupBorder', #{rows: 15})
100    call VerifyScreenDump(buf, 'Test_popupwin_2' .. iter, {})
101
102    call StopVimInTerminal(buf)
103    call delete('XtestPopupBorder')
104  endfor
105
106  let lines =<< trim END
107	call setline(1, range(1, 100))
108	hi BlueColor ctermbg=lightblue
109	hi TopColor ctermbg=253
110	hi RightColor ctermbg=245
111	hi BottomColor ctermbg=240
112	hi LeftColor ctermbg=248
113	call popup_create('hello border', #{line: 2, col: 3, border: [], borderhighlight: ['BlueColor']})
114	call popup_create(['hello border', 'and more'], #{line: 2, col: 23, border: [], borderhighlight: ['TopColor', 'RightColor', 'BottomColor', 'LeftColor']})
115	call popup_create(['hello border', 'lines only'], #{line: 2, col: 43, border: [], borderhighlight: ['BlueColor'], borderchars: ['x']})
116	call popup_create(['hello border', 'with corners'], #{line: 2, col: 60, border: [], borderhighlight: ['BlueColor'], borderchars: ['x', '#']})
117	let winid = popup_create(['hello border', 'with numbers'], #{line: 6, col: 3, border: [], borderhighlight: ['BlueColor'], borderchars: ['0', '1', '2', '3', '4', '5', '6', '7']})
118	call popup_create(['hello border', 'just blanks'], #{line: 7, col: 23, border: [], borderhighlight: ['BlueColor'], borderchars: [' ']})
119	func MultiByte()
120	  call popup_create(['hello'], #{line: 8, col: 43, border: [], borderchars: ['─', '│', '─', '│', '┌', '┐', '┘', '└']})
121	endfunc
122  END
123  call writefile(lines, 'XtestPopupBorder')
124  let buf = RunVimInTerminal('-S XtestPopupBorder', #{rows: 12})
125  call VerifyScreenDump(buf, 'Test_popupwin_22', {})
126
127  " check that changing borderchars triggers a redraw
128  call term_sendkeys(buf, ":call popup_setoptions(winid, #{borderchars: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']})\<CR>")
129  call VerifyScreenDump(buf, 'Test_popupwin_23', {})
130
131  " check multi-byte border only with 'ambiwidth' single
132  if &ambiwidth == 'single'
133    call term_sendkeys(buf, ":call MultiByte()\<CR>")
134    call VerifyScreenDump(buf, 'Test_popupwin_24', {})
135  endif
136
137  call StopVimInTerminal(buf)
138  call delete('XtestPopupBorder')
139
140  let with_border_or_padding = #{
141	\ line: 2,
142	\ core_line: 3,
143	\ col: 3,
144	\ core_col: 4,
145	\ width: 14,
146	\ core_width: 12,
147	\ height: 3,
148	\ core_height: 1,
149	\ firstline: 1,
150	\ lastline: 1,
151	\ scrollbar: 0,
152	\ visible: 1}
153  let winid = popup_create('hello border', #{line: 2, col: 3, border: []})",
154  call assert_equal(with_border_or_padding, winid->popup_getpos())
155  let options = popup_getoptions(winid)
156  call assert_equal([], options.border)
157  call assert_false(has_key(options, "padding"))
158
159  let winid = popup_create('hello padding', #{line: 2, col: 3, padding: []})
160  let with_border_or_padding.width = 15
161  let with_border_or_padding.core_width = 13
162  call assert_equal(with_border_or_padding, popup_getpos(winid))
163  let options = popup_getoptions(winid)
164  call assert_false(has_key(options, "border"))
165  call assert_equal([], options.padding)
166
167  call popup_setoptions(winid, #{
168	\ padding: [1, 2, 3, 4],
169	\ border: [4, 0, 7, 8],
170	\ borderhighlight: ['Top', 'Right', 'Bottom', 'Left'],
171	\ borderchars: ['1', '^', '2', '>', '3', 'v', '4', '<'],
172	\ })
173  let options = popup_getoptions(winid)
174  call assert_equal([1, 0, 1, 1], options.border)
175  call assert_equal([1, 2, 3, 4], options.padding)
176  call assert_equal(['Top', 'Right', 'Bottom', 'Left'], options.borderhighlight)
177  call assert_equal(['1', '^', '2', '>', '3', 'v', '4', '<'], options.borderchars)
178
179  " Check that popup_setoptions() takes the output of popup_getoptions()
180  call popup_setoptions(winid, options)
181  call assert_equal(options, popup_getoptions(winid))
182
183  " Check that range() doesn't crash
184  call popup_setoptions(winid, #{
185	\ padding: range(1, 4),
186	\ border: range(5, 8),
187	\ borderhighlight: range(4),
188	\ borderchars: range(8),
189	\ })
190
191  let winid = popup_create('hello both', #{line: 3, col: 8, border: [], padding: []})
192  call assert_equal(#{
193	\ line: 3,
194	\ core_line: 5,
195	\ col: 8,
196	\ core_col: 10,
197	\ width: 14,
198	\ core_width: 10,
199	\ height: 5,
200	\ scrollbar: 0,
201	\ core_height: 1,
202	\ firstline: 1,
203	\ lastline: 1,
204	\ visible: 1}, popup_getpos(winid))
205
206  call popup_clear()
207endfunc
208
209func Test_popup_with_syntax_win_execute()
210  CheckScreendump
211
212  let lines =<< trim END
213	call setline(1, range(1, 100))
214	hi PopupColor ctermbg=lightblue
215	let winid = popup_create([
216	    \ '#include <stdio.h>',
217	    \ 'int main(void)',
218	    \ '{',
219	    \ '    printf(123);',
220	    \ '}',
221	    \], #{line: 3, col: 25, highlight: 'PopupColor'})
222	call win_execute(winid, 'set syntax=cpp')
223  END
224  call writefile(lines, 'XtestPopup')
225  let buf = RunVimInTerminal('-S XtestPopup', #{rows: 10})
226  call VerifyScreenDump(buf, 'Test_popupwin_10', {})
227
228  " clean up
229  call StopVimInTerminal(buf)
230  call delete('XtestPopup')
231endfunc
232
233func Test_popup_with_syntax_setbufvar()
234  CheckScreendump
235
236  let lines =<< trim END
237	call setline(1, range(1, 100))
238	hi PopupColor ctermbg=lightgrey
239	let winid = popup_create([
240	    \ '#include <stdio.h>',
241	    \ 'int main(void)',
242	    \ '{',
243	    \ "\tprintf(567);",
244	    \ '}',
245	    \], #{line: 3, col: 21, highlight: 'PopupColor'})
246	call setbufvar(winbufnr(winid), '&syntax', 'cpp')
247  END
248  call writefile(lines, 'XtestPopup')
249  let buf = RunVimInTerminal('-S XtestPopup', #{rows: 10})
250  call VerifyScreenDump(buf, 'Test_popupwin_11', {})
251
252  " clean up
253  call StopVimInTerminal(buf)
254  call delete('XtestPopup')
255endfunc
256
257func Test_popup_with_matches()
258  CheckScreendump
259
260  let lines =<< trim END
261	call setline(1, ['111 222 333', '444 555 666'])
262	let winid = popup_create([
263	    \ '111 222 333',
264	    \ '444 555 666',
265	    \], #{line: 3, col: 10, border: []})
266	set hlsearch
267	hi VeryBlue ctermfg=blue guifg=blue
268	/666
269	call matchadd('ErrorMsg', '111')
270	call matchadd('VeryBlue', '444')
271	call win_execute(winid, "call matchadd('ErrorMsg', '111')")
272	call win_execute(winid, "call matchadd('VeryBlue', '555')")
273  END
274  call writefile(lines, 'XtestPopupMatches')
275  let buf = RunVimInTerminal('-S XtestPopupMatches', #{rows: 10})
276  call VerifyScreenDump(buf, 'Test_popupwin_matches', {})
277
278  " clean up
279  call StopVimInTerminal(buf)
280  call delete('XtestPopupMatches')
281endfunc
282
283func Test_popup_all_corners()
284  CheckScreendump
285
286  let lines =<< trim END
287	call setline(1, repeat([repeat('-', 60)], 15))
288	set so=0
289	normal 2G3|r#
290	let winid1 = popup_create(['first', 'second'], #{
291	      \ line: 'cursor+1',
292	      \ col: 'cursor',
293	      \ pos: 'topleft',
294	      \ border: [],
295	      \ padding: [],
296	      \ })
297	normal 24|r@
298	let winid1 = popup_create(['First', 'SeconD'], #{
299	      \ line: 'cursor+1',
300	      \ col: 'cursor',
301	      \ pos: 'topright',
302	      \ border: [],
303	      \ padding: [],
304	      \ })
305	normal 9G27|r%
306	let winid1 = popup_create(['fiRSt', 'seCOnd'], #{
307	      \ line: 'cursor-1',
308	      \ col: 'cursor',
309	      \ pos: 'botleft',
310	      \ border: [],
311	      \ padding: [],
312	      \ })
313	normal 48|r&
314	let winid1 = popup_create(['FIrsT', 'SEcoND'], #{
315	      \ line: 'cursor-1',
316	      \ col: 'cursor',
317	      \ pos: 'botright',
318	      \ border: [],
319	      \ padding: [],
320	      \ })
321	normal 1G51|r*
322	let winid1 = popup_create(['one', 'two'], #{
323	      \ line: 'cursor-1',
324	      \ col: 'cursor',
325	      \ pos: 'botleft',
326	      \ border: [],
327	      \ padding: [],
328	      \ })
329  END
330  call writefile(lines, 'XtestPopupCorners')
331  let buf = RunVimInTerminal('-S XtestPopupCorners', #{rows: 12})
332  call VerifyScreenDump(buf, 'Test_popupwin_corners', {})
333
334  " clean up
335  call StopVimInTerminal(buf)
336  call delete('XtestPopupCorners')
337endfunc
338
339func Test_popup_nospace()
340  CheckScreendump
341
342  let lines =<< trim END
343	call setline(1, repeat([repeat('-', 60)], 15))
344	set so=0
345
346	" cursor in a line in top half, using "botleft" with popup that
347	" does fit
348	normal 5G2|r@
349	let winid1 = popup_create(['one', 'two'], #{
350	      \ line: 'cursor-1',
351	      \ col: 'cursor',
352	      \ pos: 'botleft',
353	      \ border: [],
354	      \ })
355	" cursor in a line in top half, using "botleft" with popup that
356	" doesn't fit: gets truncated
357	normal 5G9|r#
358	let winid1 = popup_create(['one', 'two', 'tee'], #{
359	      \ line: 'cursor-1',
360	      \ col: 'cursor',
361	      \ pos: 'botleft',
362	      \ posinvert: 0,
363	      \ border: [],
364	      \ })
365	" cursor in a line in top half, using "botleft" with popup that
366	" doesn't fit and 'posinvert' set: flips to below.
367	normal 5G16|r%
368	let winid1 = popup_create(['one', 'two', 'tee'], #{
369	      \ line: 'cursor-1',
370	      \ col: 'cursor',
371	      \ pos: 'botleft',
372	      \ border: [],
373	      \ })
374	" cursor in a line in bottom half, using "botleft" with popup that
375	" doesn't fit: does not flip.
376	normal 8G23|r*
377	let winid1 = popup_create(['aaa', 'bbb', 'ccc', 'ddd', 'eee', 'fff'], #{
378	      \ line: 'cursor-1',
379	      \ col: 'cursor',
380	      \ pos: 'botleft',
381	      \ border: [],
382	      \ })
383
384	" cursor in a line in bottom half, using "topleft" with popup that
385	" does fit
386	normal 8G30|r@
387	let winid1 = popup_create(['one', 'two'], #{
388	      \ line: 'cursor+1',
389	      \ col: 'cursor',
390	      \ pos: 'topleft',
391	      \ border: [],
392	      \ })
393	" cursor in a line in top half, using "topleft" with popup that
394	" doesn't fit: truncated
395	normal 8G37|r#
396	let winid1 = popup_create(['one', 'two', 'tee'], #{
397	      \ line: 'cursor+1',
398	      \ col: 'cursor',
399	      \ pos: 'topleft',
400	      \ posinvert: 0,
401	      \ border: [],
402	      \ })
403	" cursor in a line in top half, using "topleft" with popup that
404	" doesn't fit and "posinvert" set: flips to above.
405	normal 8G44|r%
406	let winid1 = popup_create(['one', 'two', 'tee', 'fou', 'fiv'], #{
407	      \ line: 'cursor+1',
408	      \ col: 'cursor',
409	      \ pos: 'topleft',
410	      \ border: [],
411	      \ })
412	" cursor in a line in top half, using "topleft" with popup that
413	" doesn't fit: does not flip.
414	normal 5G51|r*
415	let winid1 = popup_create(['aaa', 'bbb', 'ccc', 'ddd', 'eee', 'fff'], #{
416	      \ line: 'cursor+1',
417	      \ col: 'cursor',
418	      \ pos: 'topleft',
419	      \ border: [],
420	      \ })
421  END
422  call writefile(lines, 'XtestPopupNospace')
423  let buf = RunVimInTerminal('-S XtestPopupNospace', #{rows: 12})
424  call VerifyScreenDump(buf, 'Test_popupwin_nospace', {})
425
426  " clean up
427  call StopVimInTerminal(buf)
428  call delete('XtestPopupNospace')
429endfunc
430
431func Test_popup_firstline_dump()
432  CheckScreendump
433
434  let lines =<< trim END
435	call setline(1, range(1, 20))
436	let winid = popup_create(['1111', '222222', '33333', '44', '5', '666666', '77777', '888', '9999999999999999'], #{
437	      \ maxheight: 4,
438	      \ firstline: 3,
439	      \ })
440  END
441  call writefile(lines, 'XtestPopupFirstline')
442  let buf = RunVimInTerminal('-S XtestPopupFirstline', #{rows: 10})
443  call VerifyScreenDump(buf, 'Test_popupwin_firstline_1', {})
444
445  call term_sendkeys(buf, ":call popup_setoptions(winid, #{firstline: -1})\<CR>")
446  call term_sendkeys(buf, ":\<CR>")
447  call VerifyScreenDump(buf, 'Test_popupwin_firstline_2', {})
448
449  " clean up
450  call StopVimInTerminal(buf)
451  call delete('XtestPopupFirstline')
452endfunc
453
454func Test_popup_firstline()
455  let winid = popup_create(['1111', '222222', '33333', '44444'], #{
456	\ maxheight: 2,
457	\ firstline: 3,
458	\ })
459  call assert_equal(3, popup_getoptions(winid).firstline)
460  call popup_setoptions(winid, #{firstline: 1})
461  call assert_equal(1, popup_getoptions(winid).firstline)
462  eval winid->popup_close()
463
464  let winid = popup_create(['xxx']->repeat(50), #{
465	\ maxheight: 3,
466	\ firstline: 11,
467	\ })
468  redraw
469  call assert_equal(11, popup_getoptions(winid).firstline)
470  call assert_equal(11, popup_getpos(winid).firstline)
471  " check line() works with popup window
472  call assert_equal(11, line('.', winid))
473  call assert_equal(50, line('$', winid))
474  call assert_equal(0, line('$', 123456))
475
476  " Normal command changes what is displayed but not "firstline"
477  call win_execute(winid, "normal! \<c-y>")
478  call assert_equal(11, popup_getoptions(winid).firstline)
479  call assert_equal(10, popup_getpos(winid).firstline)
480
481  " Making some property change applies "firstline" again
482  call popup_setoptions(winid, #{line: 4})
483  call assert_equal(11, popup_getoptions(winid).firstline)
484  call assert_equal(11, popup_getpos(winid).firstline)
485
486  " Remove "firstline" property and scroll
487  call popup_setoptions(winid, #{firstline: 0})
488  call win_execute(winid, "normal! \<c-y>")
489  call assert_equal(0, popup_getoptions(winid).firstline)
490  call assert_equal(10, popup_getpos(winid).firstline)
491
492  " Making some property change has no side effect
493  call popup_setoptions(winid, #{line: 3})
494  call assert_equal(0, popup_getoptions(winid).firstline)
495  call assert_equal(10, popup_getpos(winid).firstline)
496  call popup_close(winid)
497
498  " CTRL-D scrolls down half a page
499  let winid = popup_create(['xxx']->repeat(50), #{
500	\ maxheight: 8,
501	\ })
502  redraw
503  call assert_equal(1, popup_getpos(winid).firstline)
504  call win_execute(winid, "normal! \<C-D>")
505  call assert_equal(5, popup_getpos(winid).firstline)
506  call win_execute(winid, "normal! \<C-D>")
507  call assert_equal(9, popup_getpos(winid).firstline)
508  call win_execute(winid, "normal! \<C-U>")
509  call assert_equal(5, popup_getpos(winid).firstline)
510
511  call win_execute(winid, "normal! \<C-F>")
512  call assert_equal(11, popup_getpos(winid).firstline)
513  call win_execute(winid, "normal! \<C-B>")
514  call assert_equal(5, popup_getpos(winid).firstline)
515
516  call popup_close(winid)
517endfunc
518
519func Test_popup_noscrolloff()
520  set scrolloff=5
521  let winid = popup_create(['xxx']->repeat(50), #{
522	\ maxheight: 5,
523	\ firstline: 11,
524	\ })
525  redraw
526  call assert_equal(11, popup_getoptions(winid).firstline)
527  call assert_equal(11, popup_getpos(winid).firstline)
528
529  call popup_setoptions(winid, #{firstline: 0})
530  call win_execute(winid, "normal! \<c-y>")
531  call assert_equal(0, popup_getoptions(winid).firstline)
532  call assert_equal(10, popup_getpos(winid).firstline)
533
534  call popup_close(winid)
535endfunc
536
537func Test_popup_drag()
538  CheckScreendump
539
540  " create a popup that covers the command line
541  let lines =<< trim END
542	call setline(1, range(1, 20))
543	split
544	vsplit
545	$wincmd w
546	vsplit
547	1wincmd w
548	let winid = popup_create(['1111', '222222', '33333'], #{
549	      \ drag: 1,
550	      \ resize: 1,
551	      \ border: [],
552	      \ line: &lines - 4,
553	      \ })
554	func Dragit()
555	  call feedkeys("\<F3>\<LeftMouse>\<F4>\<LeftDrag>\<LeftRelease>", "xt")
556	endfunc
557	map <silent> <F3> :call test_setmouse(&lines - 4, &columns / 2)<CR>
558	map <silent> <F4> :call test_setmouse(&lines - 8, &columns / 2 - 20)<CR>
559	func Resize()
560	  call feedkeys("\<F5>\<LeftMouse>\<F6>\<LeftDrag>\<LeftRelease>", "xt")
561	endfunc
562	map <silent> <F5> :call test_setmouse(6, 21)<CR>
563	map <silent> <F6> :call test_setmouse(7, 25)<CR>
564  END
565  call writefile(lines, 'XtestPopupDrag')
566  let buf = RunVimInTerminal('-S XtestPopupDrag', #{rows: 10})
567  call VerifyScreenDump(buf, 'Test_popupwin_drag_01', {})
568
569  call term_sendkeys(buf, ":call Dragit()\<CR>")
570  call VerifyScreenDump(buf, 'Test_popupwin_drag_02', {})
571
572  call term_sendkeys(buf, ":call Resize()\<CR>")
573  call VerifyScreenDump(buf, 'Test_popupwin_drag_03', {})
574
575  " clean up
576  call StopVimInTerminal(buf)
577  call delete('XtestPopupDrag')
578endfunc
579
580func Test_popup_drag_termwin()
581  CheckUnix
582  CheckScreendump
583  CheckFeature terminal
584
585  " create a popup that covers the terminal window
586  let lines =<< trim END
587	set foldmethod=marker
588	call setline(1, range(100))
589	for nr in range(7)
590	  call setline(nr * 12 + 1, "fold {{{")
591	  call setline(nr * 12 + 11 , "end }}}")
592	endfor
593	%foldclose
594	set shell=/bin/sh noruler
595	let $PS1 = 'vim> '
596        terminal ++rows=4
597	$wincmd w
598	let winid = popup_create(['1111', '2222'], #{
599	      \ drag: 1,
600	      \ resize: 1,
601	      \ border: [],
602	      \ line: 3,
603	      \ })
604	func DragitLeft()
605	  call feedkeys("\<F3>\<LeftMouse>\<F4>\<LeftDrag>\<LeftRelease>", "xt")
606	endfunc
607	func DragitDown()
608	  call feedkeys("\<F4>\<LeftMouse>\<F5>\<LeftDrag>\<LeftRelease>", "xt")
609	endfunc
610	func DragitDownLeft()
611	  call feedkeys("\<F5>\<LeftMouse>\<F6>\<LeftDrag>\<LeftRelease>", "xt")
612	endfunc
613	map <silent> <F3> :call test_setmouse(3, &columns / 2)<CR>
614	map <silent> <F4> :call test_setmouse(3, &columns / 2 - 20)<CR>
615	map <silent> <F5> :call test_setmouse(12, &columns / 2)<CR>
616	map <silent> <F6> :call test_setmouse(12, &columns / 2 - 20)<CR>
617  END
618  call writefile(lines, 'XtestPopupTerm')
619  let buf = RunVimInTerminal('-S XtestPopupTerm', #{rows: 16})
620  call VerifyScreenDump(buf, 'Test_popupwin_term_01', {})
621
622  call term_sendkeys(buf, ":call DragitLeft()\<CR>")
623  call VerifyScreenDump(buf, 'Test_popupwin_term_02', {})
624
625  call term_sendkeys(buf, ":call DragitDown()\<CR>")
626  call VerifyScreenDump(buf, 'Test_popupwin_term_03', {})
627
628  call term_sendkeys(buf, ":call DragitDownLeft()\<CR>")
629  call VerifyScreenDump(buf, 'Test_popupwin_term_04', {})
630
631  " clean up
632  call StopVimInTerminal(buf)
633  call delete('XtestPopupTerm')
634endfunc
635
636func Test_popup_close_with_mouse()
637  CheckScreendump
638
639  let lines =<< trim END
640	call setline(1, range(1, 20))
641	" With border, can click on X
642	let winid = popup_create('foobar', #{
643	      \ close: 'button',
644	      \ border: [],
645	      \ line: 1,
646	      \ col: 1,
647	      \ })
648	func CloseMsg(id, result)
649	  echomsg 'Popup closed with ' .. a:result
650	endfunc
651	let winid = popup_create('notification', #{
652	      \ close: 'click',
653	      \ line: 3,
654	      \ col: 15,
655	      \ callback: 'CloseMsg',
656	      \ })
657	let winid = popup_create('no border here', #{
658	      \ close: 'button',
659	      \ line: 5,
660	      \ col: 3,
661	      \ })
662	let winid = popup_create('only padding', #{
663	      \ close: 'button',
664	      \ padding: [],
665	      \ line: 5,
666	      \ col: 23,
667	      \ })
668	func CloseWithX()
669	  call feedkeys("\<F3>\<LeftMouse>\<LeftRelease>", "xt")
670	endfunc
671	map <silent> <F3> :call test_setmouse(1, len('foobar') + 2)<CR>
672	func CloseWithClick()
673	  call feedkeys("\<F4>\<LeftMouse>\<LeftRelease>", "xt")
674	endfunc
675	map <silent> <F4> :call test_setmouse(3, 17)<CR>
676	func CreateWithMenuFilter()
677	  let winid = popup_create('barfoo', #{
678		\ close: 'button',
679		\ filter: 'popup_filter_menu',
680		\ border: [],
681		\ line: 1,
682		\ col: 40,
683		\ })
684	endfunc
685  END
686  call writefile(lines, 'XtestPopupClose')
687  let buf = RunVimInTerminal('-S XtestPopupClose', #{rows: 10})
688  call VerifyScreenDump(buf, 'Test_popupwin_close_01', {})
689
690  call term_sendkeys(buf, ":call CloseWithX()\<CR>")
691  call VerifyScreenDump(buf, 'Test_popupwin_close_02', {})
692
693  call term_sendkeys(buf, ":call CloseWithClick()\<CR>")
694  call VerifyScreenDump(buf, 'Test_popupwin_close_03', {})
695
696  call term_sendkeys(buf, ":call CreateWithMenuFilter()\<CR>")
697  call VerifyScreenDump(buf, 'Test_popupwin_close_04', {})
698
699  " We have to send the actual mouse code, feedkeys() would be caught the
700  " filter.
701  call term_sendkeys(buf, "\<Esc>[<0;47;1M")
702  call VerifyScreenDump(buf, 'Test_popupwin_close_05', {})
703
704  " clean up
705  call StopVimInTerminal(buf)
706  call delete('XtestPopupClose')
707endfunction
708
709func Test_popup_menu_wrap()
710  CheckScreendump
711
712  let lines =<< trim END
713	call setline(1, range(1, 20))
714	call popup_create([
715	      \ 'one',
716	      \ 'asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfas',
717	      \ 'three',
718	      \ 'four',
719	      \ ], #{
720	      \ pos: "botleft",
721	      \ border: [],
722	      \ padding: [0,1,0,1],
723	      \ maxheight: 3,
724	      \ cursorline: 1,
725	      \ filter: 'popup_filter_menu',
726	      \ })
727  END
728  call writefile(lines, 'XtestPopupWrap')
729  let buf = RunVimInTerminal('-S XtestPopupWrap', #{rows: 10})
730  call VerifyScreenDump(buf, 'Test_popupwin_wrap_1', {})
731
732  call term_sendkeys(buf, "jj")
733  call VerifyScreenDump(buf, 'Test_popupwin_wrap_2', {})
734
735  " clean up
736  call term_sendkeys(buf, "\<Esc>")
737  call StopVimInTerminal(buf)
738  call delete('XtestPopupWrap')
739endfunction
740
741func Test_popup_with_mask()
742  CheckScreendump
743
744  let lines =<< trim END
745	call setline(1, repeat([join(range(1, 42), '')], 13))
746	hi PopupColor ctermbg=lightgrey
747	let winid = popup_create([
748	    \ 'some text',
749	    \ 'another line',
750	    \], #{
751	    \ line: 1,
752	    \ col: 10,
753	    \ posinvert: 0,
754	    \ wrap: 0,
755	    \ fixed: 1,
756	    \ zindex: 90,
757	    \ padding: [],
758	    \ highlight: 'PopupColor',
759	    \ mask: [[1,1,1,1], [-5,-1,4,4], [7,9,2,3], [2,4,3,3]]})
760	call popup_create([
761	    \ 'xxxxxxxxx',
762	    \ 'yyyyyyyyy',
763	    \], #{
764	    \ line: 3,
765	    \ col: 18,
766	    \ zindex: 20})
767	let winidb = popup_create([
768	    \ 'just one line',
769	    \], #{
770	    \ line: 7,
771	    \ col: 10,
772	    \ posinvert: 0,
773	    \ wrap: 0,
774	    \ fixed: 1,
775	    \ close: 'button',
776	    \ zindex: 90,
777	    \ padding: [],
778	    \ border: [],
779	    \ mask: [[1,2,1,1], [-5,-1,4,4], [7,9,2,3], [3,5,5,5],[-7,-4,5,5]]})
780  END
781  call writefile(lines, 'XtestPopupMask')
782  let buf = RunVimInTerminal('-S XtestPopupMask', #{rows: 13})
783  call VerifyScreenDump(buf, 'Test_popupwin_mask_1', {})
784
785  call term_sendkeys(buf, ":call popup_move(winid, #{col: 11, line: 2})\<CR>")
786  call term_sendkeys(buf, ":call popup_move(winidb, #{col: 12})\<CR>")
787  call term_sendkeys(buf, ":\<CR>")
788  call VerifyScreenDump(buf, 'Test_popupwin_mask_2', {})
789
790  call term_sendkeys(buf, ":call popup_move(winid, #{col: 65, line: 2})\<CR>")
791  call term_sendkeys(buf, ":call popup_move(winidb, #{col: 63})\<CR>")
792  call term_sendkeys(buf, ":\<CR>")
793  call VerifyScreenDump(buf, 'Test_popupwin_mask_3', {})
794
795  call term_sendkeys(buf, ":call popup_move(winid, #{pos: 'topright', col: 12, line: 2})\<CR>")
796  call term_sendkeys(buf, ":call popup_move(winidb, #{pos: 'topright', col: 12})\<CR>")
797  call term_sendkeys(buf, ":\<CR>")
798  call VerifyScreenDump(buf, 'Test_popupwin_mask_4', {})
799
800  call term_sendkeys(buf, ":call popup_move(winid, #{pos: 'topright', col: 12, line: 11})\<CR>")
801  call term_sendkeys(buf, ":call popup_move(winidb, #{pos: 'topleft', col: 42, line: 11})\<CR>")
802  call term_sendkeys(buf, ":\<CR>")
803  call VerifyScreenDump(buf, 'Test_popupwin_mask_5', {})
804
805  " clean up
806  call StopVimInTerminal(buf)
807  call delete('XtestPopupMask')
808endfunc
809
810func Test_popup_select()
811  CheckScreendump
812  CheckFeature clipboard_working
813
814  " create a popup with some text to be selected
815  let lines =<< trim END
816    set clipboard=autoselect
817    call setline(1, range(1, 20))
818    let winid = popup_create(['the word', 'some more', 'several words here', 'invisible', '5', '6', '7'], #{
819	  \ drag: 1,
820	  \ border: [],
821	  \ line: 3,
822	  \ col: 10,
823	  \ maxheight: 3,
824	  \ })
825    func Select1()
826      call feedkeys("\<F3>\<LeftMouse>\<F4>\<LeftDrag>\<LeftRelease>", "xt")
827    endfunc
828    map <silent> <F3> :call test_setmouse(4, 15)<CR>
829    map <silent> <F4> :call test_setmouse(6, 23)<CR>
830  END
831  call writefile(lines, 'XtestPopupSelect')
832  let buf = RunVimInTerminal('-S XtestPopupSelect', #{rows: 10})
833  call term_sendkeys(buf, ":call Select1()\<CR>")
834  call VerifyScreenDump(buf, 'Test_popupwin_select_01', {})
835
836  call term_sendkeys(buf, ":call popup_close(winid)\<CR>")
837  call term_sendkeys(buf, "\"*p")
838  " clean the command line, sometimes it still shows a command
839  call term_sendkeys(buf, ":\<esc>")
840
841  call VerifyScreenDump(buf, 'Test_popupwin_select_02', {})
842
843  " clean up
844  call StopVimInTerminal(buf)
845  call delete('XtestPopupSelect')
846endfunc
847
848func Test_popup_in_tab()
849  " default popup is local to tab, not visible when in other tab
850  let winid = popup_create("text", {})
851  let bufnr = winbufnr(winid)
852  call assert_equal(1, popup_getpos(winid).visible)
853  call assert_equal(0, popup_getoptions(winid).tabpage)
854  tabnew
855  call assert_equal(0, popup_getpos(winid).visible)
856  call assert_equal(1, popup_getoptions(winid).tabpage)
857  quit
858  call assert_equal(1, popup_getpos(winid).visible)
859
860  call assert_equal(1, bufexists(bufnr))
861  call popup_clear()
862  " buffer is gone now
863  call assert_equal(0, bufexists(bufnr))
864
865  " global popup is visible in any tab
866  let winid = popup_create("text", #{tabpage: -1})
867  call assert_equal(1, popup_getpos(winid).visible)
868  call assert_equal(-1, popup_getoptions(winid).tabpage)
869  tabnew
870  call assert_equal(1, popup_getpos(winid).visible)
871  call assert_equal(-1, popup_getoptions(winid).tabpage)
872  quit
873  call assert_equal(1, popup_getpos(winid).visible)
874  call popup_clear()
875
876  " create popup in other tab
877  tabnew
878  let winid = popup_create("text", #{tabpage: 1})
879  call assert_equal(0, popup_getpos(winid).visible)
880  call assert_equal(1, popup_getoptions(winid).tabpage)
881  quit
882  call assert_equal(1, popup_getpos(winid).visible)
883  call assert_equal(0, popup_getoptions(winid).tabpage)
884  call popup_clear()
885endfunc
886
887func Test_popup_valid_arguments()
888  call assert_equal(0, len(popup_list()))
889
890  " Zero value is like the property wasn't there
891  let winid = popup_create("text", #{col: 0})
892  let pos = popup_getpos(winid)
893  call assert_inrange(&columns / 2 - 1, &columns / 2 + 1, pos.col)
894  call assert_equal([winid], popup_list())
895  call popup_clear()
896
897  " using cursor column has minimum value of 1
898  let winid = popup_create("text", #{col: 'cursor-100'})
899  let pos = popup_getpos(winid)
900  call assert_equal(1, pos.col)
901  call popup_clear()
902
903  " center
904  let winid = popup_create("text", #{pos: 'center'})
905  let pos = popup_getpos(winid)
906  let around = (&columns - pos.width) / 2
907  call assert_inrange(around - 1, around + 1, pos.col)
908  let around = (&lines - pos.height) / 2
909  call assert_inrange(around - 1, around + 1, pos.line)
910  call popup_clear()
911endfunc
912
913func Test_popup_invalid_arguments()
914  call assert_fails('call popup_create(666, {})', 'E86:')
915  call popup_clear()
916  call assert_fails('call popup_create("text", "none")', 'E715:')
917  call popup_clear()
918  call assert_fails('call popup_create(test_null_string(), {})', 'E450:')
919  call assert_fails('call popup_create(test_null_list(), {})', 'E450:')
920  call popup_clear()
921
922  call assert_fails('call popup_create("text", #{col: "xxx"})', 'E475:')
923  call popup_clear()
924  call assert_fails('call popup_create("text", #{col: "cursor8"})', 'E15:')
925  call popup_clear()
926  call assert_fails('call popup_create("text", #{col: "cursor+x"})', 'E15:')
927  call popup_clear()
928  call assert_fails('call popup_create("text", #{col: "cursor+8x"})', 'E15:')
929  call popup_clear()
930
931  call assert_fails('call popup_create("text", #{line: "xxx"})', 'E475:')
932  call popup_clear()
933  call assert_fails('call popup_create("text", #{line: "cursor8"})', 'E15:')
934  call popup_clear()
935  call assert_fails('call popup_create("text", #{line: "cursor+x"})', 'E15:')
936  call popup_clear()
937  call assert_fails('call popup_create("text", #{line: "cursor+8x"})', 'E15:')
938  call popup_clear()
939
940  call assert_fails('call popup_create("text", #{pos: "there"})', 'E475:')
941  call popup_clear()
942  call assert_fails('call popup_create("text", #{padding: "none"})', 'E714:')
943  call popup_clear()
944  call assert_fails('call popup_create("text", #{border: "none"})', 'E714:')
945  call popup_clear()
946  call assert_fails('call popup_create("text", #{borderhighlight: "none"})', 'E714:')
947  call popup_clear()
948  call assert_fails('call popup_create("text", #{borderhighlight: test_null_list()})', 'E714:')
949  call popup_clear()
950  call assert_fails('call popup_create("text", #{borderchars: "none"})', 'E714:')
951  call popup_clear()
952
953  call assert_fails('call popup_create([#{text: "text"}, 666], {})', 'E715:')
954  call popup_clear()
955  call assert_fails('call popup_create([#{text: "text", props: "none"}], {})', 'E714:')
956  call popup_clear()
957  call assert_fails('call popup_create([#{text: "text", props: ["none"]}], {})', 'E715:')
958  call popup_clear()
959  call assert_fails('call popup_create([#{text: "text", props: range(3)}], {})', 'E715:')
960  call popup_clear()
961  call assert_fails('call popup_create("text", #{mask: ["asdf"]})', 'E475:')
962  call popup_clear()
963  call assert_fails('call popup_create("text", #{mask: range(5)})', 'E475:')
964  call popup_clear()
965  call popup_create("text", #{mask: [range(4)]})
966  call popup_clear()
967  call assert_fails('call popup_create("text", #{mask: test_null_list()})', 'E475:')
968  call assert_fails('call popup_create("text", #{mapping: []})', 'E745:')
969  call popup_clear()
970  call assert_fails('call popup_create("text", #{tabpage : 4})', 'E997:')
971  call popup_clear()
972endfunc
973
974func Test_win_execute_closing_curwin()
975  split
976  let winid = popup_create('some text', {})
977  call assert_fails('call win_execute(winid, winnr() .. "close")', 'E994')
978  call popup_clear()
979
980  let winid = popup_create('some text', {})
981  call assert_fails('call win_execute(winid, printf("normal! :\<C-u>call popup_close(%d)\<CR>", winid))', 'E994')
982  call popup_clear()
983endfunc
984
985func Test_win_execute_not_allowed()
986  let winid = popup_create('some text', {})
987  call assert_fails('call win_execute(winid, "split")', 'E994:')
988  call assert_fails('call win_execute(winid, "vsplit")', 'E994:')
989  call assert_fails('call win_execute(winid, "close")', 'E994:')
990  call assert_fails('call win_execute(winid, "bdelete")', 'E994:')
991  call assert_fails('call win_execute(winid, "bwipe!")', 'E994:')
992  call assert_fails('call win_execute(winid, "tabnew")', 'E994:')
993  call assert_fails('call win_execute(winid, "tabnext")', 'E994:')
994  call assert_fails('call win_execute(winid, "next")', 'E994:')
995  call assert_fails('call win_execute(winid, "rewind")', 'E994:')
996  call assert_fails('call win_execute(winid, "pedit filename")', 'E994:')
997  call assert_fails('call win_execute(winid, "buf")', 'E994:')
998  call assert_fails('call win_execute(winid, "bnext")', 'E994:')
999  call assert_fails('call win_execute(winid, "bprev")', 'E994:')
1000  call assert_fails('call win_execute(winid, "bfirst")', 'E994:')
1001  call assert_fails('call win_execute(winid, "blast")', 'E994:')
1002  call assert_fails('call win_execute(winid, "edit")', 'E994:')
1003  call assert_fails('call win_execute(winid, "enew")', 'E994:')
1004  call assert_fails('call win_execute(winid, "help")', 'E994:')
1005  call assert_fails('call win_execute(winid, "1only")', 'E994:')
1006  call assert_fails('call win_execute(winid, "wincmd x")', 'E994:')
1007  call assert_fails('call win_execute(winid, "wincmd w")', 'E994:')
1008  call assert_fails('call win_execute(winid, "wincmd t")', 'E994:')
1009  call assert_fails('call win_execute(winid, "wincmd b")', 'E994:')
1010  call popup_clear()
1011endfunc
1012
1013func Test_popup_with_wrap()
1014  CheckScreendump
1015
1016  let lines =<< trim END
1017	 call setline(1, range(1, 100))
1018	 let winid = popup_create(
1019	   \ 'a long line that wont fit',
1020	   \ #{line: 3, col: 20, maxwidth: 10, wrap: 1})
1021  END
1022  call writefile(lines, 'XtestPopup')
1023  let buf = RunVimInTerminal('-S XtestPopup', #{rows: 10})
1024  call VerifyScreenDump(buf, 'Test_popupwin_wrap', {})
1025
1026  " clean up
1027  call StopVimInTerminal(buf)
1028  call delete('XtestPopup')
1029endfunc
1030
1031func Test_popup_without_wrap()
1032  CheckScreendump
1033
1034  let lines =<< trim END
1035	 call setline(1, range(1, 100))
1036	 let winid = popup_create(
1037	   \ 'a long line that wont fit',
1038	   \ #{line: 3, col: 20, maxwidth: 10, wrap: 0})
1039  END
1040  call writefile(lines, 'XtestPopup')
1041  let buf = RunVimInTerminal('-S XtestPopup', #{rows: 10})
1042  call VerifyScreenDump(buf, 'Test_popupwin_nowrap', {})
1043
1044  " clean up
1045  call StopVimInTerminal(buf)
1046  call delete('XtestPopup')
1047endfunc
1048
1049func Test_popup_with_showbreak()
1050  CheckScreendump
1051
1052  let lines =<< trim END
1053	 set showbreak=>>\
1054	 call setline(1, range(1, 20))
1055	 let winid = popup_dialog(
1056	   \ 'a long line here that wraps',
1057	   \ #{filter: 'popup_filter_yesno',
1058	   \   maxwidth: 12})
1059  END
1060  call writefile(lines, 'XtestPopupShowbreak')
1061  let buf = RunVimInTerminal('-S XtestPopupShowbreak', #{rows: 10})
1062  call VerifyScreenDump(buf, 'Test_popupwin_showbreak', {})
1063
1064  " clean up
1065  call term_sendkeys(buf, "y")
1066  call StopVimInTerminal(buf)
1067  call delete('XtestPopupShowbreak')
1068endfunc
1069
1070func Test_popup_time()
1071  CheckFeature timers
1072
1073  topleft vnew
1074  call setline(1, 'hello')
1075
1076  let winid = popup_create('world', #{
1077	\ line: 1,
1078	\ col: 1,
1079	\ minwidth: 20,
1080	\ time: 500,
1081	\})
1082  redraw
1083  let line = join(map(range(1, 5), 'screenstring(1, v:val)'), '')
1084  call assert_equal('world', line)
1085
1086  call assert_equal(winid, popup_locate(1, 1))
1087  call assert_equal(winid, popup_locate(1, 20))
1088  call assert_equal(0, popup_locate(1, 21))
1089  call assert_equal(0, popup_locate(2, 1))
1090
1091  sleep 700m
1092  redraw
1093  let line = join(map(range(1, 5), '1->screenstring(v:val)'), '')
1094  call assert_equal('hello', line)
1095
1096  call popup_create('on the command line', #{
1097	\ line: &lines,
1098	\ col: 10,
1099	\ minwidth: 20,
1100	\ time: 500,
1101	\})
1102  redraw
1103  let line = join(map(range(1, 30), 'screenstring(&lines, v:val)'), '')
1104  call assert_match('.*on the command line.*', line)
1105
1106  sleep 700m
1107  redraw
1108  let line = join(map(range(1, 30), 'screenstring(&lines, v:val)'), '')
1109  call assert_notmatch('.*on the command line.*', line)
1110
1111  bwipe!
1112endfunc
1113
1114func Test_popup_hide()
1115  topleft vnew
1116  call setline(1, 'hello')
1117
1118  let winid = popup_create('world', #{
1119	\ line: 1,
1120	\ col: 1,
1121	\ minwidth: 20,
1122	\})
1123  redraw
1124  let line = join(map(range(1, 5), 'screenstring(1, v:val)'), '')
1125  call assert_equal('world', line)
1126  call assert_equal(1, popup_getpos(winid).visible)
1127  " buffer is still listed and active
1128  call assert_match(winbufnr(winid) .. 'u a.*\[Popup\]', execute('ls u'))
1129
1130  call popup_hide(winid)
1131  redraw
1132  let line = join(map(range(1, 5), 'screenstring(1, v:val)'), '')
1133  call assert_equal('hello', line)
1134  call assert_equal(0, popup_getpos(winid).visible)
1135  " buffer is still listed but hidden
1136  call assert_match(winbufnr(winid) .. 'u a.*\[Popup\]', execute('ls u'))
1137
1138  eval winid->popup_show()
1139  redraw
1140  let line = join(map(range(1, 5), 'screenstring(1, v:val)'), '')
1141  call assert_equal('world', line)
1142  call assert_equal(1, popup_getpos(winid).visible)
1143
1144
1145  call popup_close(winid)
1146  redraw
1147  let line = join(map(range(1, 5), 'screenstring(1, v:val)'), '')
1148  call assert_equal('hello', line)
1149
1150  " error is given for existing non-popup window
1151  call assert_fails('call popup_hide(win_getid())', 'E993:')
1152
1153  " no error non-existing window
1154  eval 1234234->popup_hide()
1155  call popup_show(41234234)
1156
1157  bwipe!
1158endfunc
1159
1160func Test_popup_move()
1161  topleft vnew
1162  call setline(1, 'hello')
1163
1164  let winid = popup_create('world', #{
1165	\ line: 1,
1166	\ col: 1,
1167	\ minwidth: 20,
1168	\})
1169  redraw
1170  let line = join(map(range(1, 6), 'screenstring(1, v:val)'), '')
1171  call assert_equal('world ', line)
1172
1173  call popup_move(winid, #{line: 2, col: 2})
1174  redraw
1175  let line = join(map(range(1, 6), 'screenstring(1, v:val)'), '')
1176  call assert_equal('hello ', line)
1177  let line = join(map(range(1, 6), 'screenstring(2, v:val)'), '')
1178  call assert_equal('~world', line)
1179
1180  eval winid->popup_move(#{line: 1})
1181  redraw
1182  let line = join(map(range(1, 6), 'screenstring(1, v:val)'), '')
1183  call assert_equal('hworld', line)
1184
1185  call assert_fails('call popup_move(winid, [])', 'E715:')
1186  call assert_fails('call popup_move(winid, test_null_dict())', 'E715:')
1187
1188  call popup_close(winid)
1189
1190  call assert_equal(0, popup_move(-1, {}))
1191
1192  bwipe!
1193endfunc
1194
1195func Test_popup_getpos()
1196  let winid = popup_create('hello', #{
1197    \ line: 2,
1198    \ col: 3,
1199    \ minwidth: 10,
1200    \ minheight: 11,
1201    \})
1202  redraw
1203  let res = popup_getpos(winid)
1204  call assert_equal(2, res.line)
1205  call assert_equal(3, res.col)
1206  call assert_equal(10, res.width)
1207  call assert_equal(11, res.height)
1208  call assert_equal(1, res.visible)
1209
1210  call popup_close(winid)
1211endfunc
1212
1213func Test_popup_width_longest()
1214  let tests = [
1215	\ [['hello', 'this', 'window', 'displays', 'all of its text'], 15],
1216	\ [['hello', 'this', 'window', 'all of its text', 'displays'], 15],
1217	\ [['hello', 'this', 'all of its text', 'window', 'displays'], 15],
1218	\ [['hello', 'all of its text', 'this', 'window', 'displays'], 15],
1219	\ [['all of its text', 'hello', 'this', 'window', 'displays'], 15],
1220	\ ]
1221
1222  for test in tests
1223    let winid = popup_create(test[0], #{line: 2, col: 3})
1224    redraw
1225    let position = popup_getpos(winid)
1226    call assert_equal(test[1], position.width)
1227    call popup_close(winid)
1228  endfor
1229endfunc
1230
1231func Test_popup_wraps()
1232  let tests = [
1233	\ ['nowrap', 6, 1],
1234	\ ['a line that wraps once', 12, 2],
1235	\ ['a line that wraps two times', 12, 3],
1236	\ ]
1237  for test in tests
1238    let winid = popup_create(test[0],
1239	  \ #{line: 2, col: 3, maxwidth: 12})
1240    redraw
1241    let position = popup_getpos(winid)
1242    call assert_equal(test[1], position.width)
1243    call assert_equal(test[2], position.height)
1244
1245    call popup_close(winid)
1246    call assert_equal({}, popup_getpos(winid))
1247  endfor
1248endfunc
1249
1250func Test_popup_getoptions()
1251  let winid = popup_create('hello', #{
1252    \ line: 2,
1253    \ col: 3,
1254    \ minwidth: 10,
1255    \ minheight: 11,
1256    \ maxwidth: 20,
1257    \ maxheight: 21,
1258    \ zindex: 100,
1259    \ time: 5000,
1260    \ fixed: 1
1261    \})
1262  redraw
1263  let res = popup_getoptions(winid)
1264  call assert_equal(2, res.line)
1265  call assert_equal(3, res.col)
1266  call assert_equal(10, res.minwidth)
1267  call assert_equal(11, res.minheight)
1268  call assert_equal(20, res.maxwidth)
1269  call assert_equal(21, res.maxheight)
1270  call assert_equal(100, res.zindex)
1271  call assert_equal(1, res.fixed)
1272  call assert_equal(1, res.mapping)
1273  if has('timers')
1274    call assert_equal(5000, res.time)
1275  endif
1276  call popup_close(winid)
1277
1278  let winid = popup_create('hello', {})
1279  redraw
1280  let res = popup_getoptions(winid)
1281  call assert_equal(0, res.line)
1282  call assert_equal(0, res.col)
1283  call assert_equal(0, res.minwidth)
1284  call assert_equal(0, res.minheight)
1285  call assert_equal(0, res.maxwidth)
1286  call assert_equal(0, res.maxheight)
1287  call assert_equal(50, res.zindex)
1288  call assert_equal(0, res.fixed)
1289  if has('timers')
1290    call assert_equal(0, res.time)
1291  endif
1292  call popup_close(winid)
1293  call assert_equal({}, popup_getoptions(winid))
1294endfunc
1295
1296func Test_popup_option_values()
1297  new
1298  " window-local
1299  setlocal number
1300  setlocal nowrap
1301  " buffer-local
1302  setlocal omnifunc=Something
1303  " global/buffer-local
1304  setlocal path=/there
1305  " global/window-local
1306  setlocal statusline=2
1307
1308  let winid = popup_create('hello', {})
1309  call assert_equal(0, getwinvar(winid, '&number'))
1310  call assert_equal(1, getwinvar(winid, '&wrap'))
1311  call assert_equal('', getwinvar(winid, '&omnifunc'))
1312  call assert_equal(&g:path, getwinvar(winid, '&path'))
1313  call assert_equal(&g:statusline, getwinvar(winid, '&statusline'))
1314
1315  " 'scrolloff' is reset to zero
1316  call assert_equal(5, &scrolloff)
1317  call assert_equal(0, getwinvar(winid, '&scrolloff'))
1318
1319  call popup_close(winid)
1320  bwipe
1321endfunc
1322
1323func Test_popup_atcursor()
1324  topleft vnew
1325  call setline(1, [
1326  \  'xxxxxxxxxxxxxxxxx',
1327  \  'xxxxxxxxxxxxxxxxx',
1328  \  'xxxxxxxxxxxxxxxxx',
1329  \])
1330
1331  call cursor(2, 2)
1332  redraw
1333  let winid = popup_atcursor('vim', {})
1334  redraw
1335  let line = join(map(range(1, 17), 'screenstring(1, v:val)'), '')
1336  call assert_equal('xvimxxxxxxxxxxxxx', line)
1337  call popup_close(winid)
1338
1339  call cursor(3, 4)
1340  redraw
1341  let winid = 'vim'->popup_atcursor({})
1342  redraw
1343  let line = join(map(range(1, 17), 'screenstring(2, v:val)'), '')
1344  call assert_equal('xxxvimxxxxxxxxxxx', line)
1345  call popup_close(winid)
1346
1347  call cursor(1, 1)
1348  redraw
1349  let winid = popup_create('vim', #{
1350	\ line: 'cursor+2',
1351	\ col: 'cursor+1',
1352	\})
1353  redraw
1354  let line = join(map(range(1, 17), 'screenstring(3, v:val)'), '')
1355  call assert_equal('xvimxxxxxxxxxxxxx', line)
1356  call popup_close(winid)
1357
1358  call cursor(3, 3)
1359  redraw
1360  let winid = popup_create('vim', #{
1361	\ line: 'cursor-2',
1362	\ col: 'cursor-1',
1363	\})
1364  redraw
1365  let line = join(map(range(1, 17), 'screenstring(1, v:val)'), '')
1366  call assert_equal('xvimxxxxxxxxxxxxx', line)
1367  call popup_close(winid)
1368
1369  " just enough room above
1370  call cursor(3, 3)
1371  redraw
1372  let winid = popup_atcursor(['vim', 'is great'], {})
1373  redraw
1374  let pos = popup_getpos(winid)
1375  call assert_equal(1, pos.line)
1376  call popup_close(winid)
1377
1378  " not enough room above, popup goes below the cursor
1379  call cursor(3, 3)
1380  redraw
1381  let winid = popup_atcursor(['vim', 'is', 'great'], {})
1382  redraw
1383  let pos = popup_getpos(winid)
1384  call assert_equal(4, pos.line)
1385  call popup_close(winid)
1386
1387  " cursor in first line, popup in line 2
1388  call cursor(1, 1)
1389  redraw
1390  let winid = popup_atcursor(['vim', 'is', 'great'], {})
1391  redraw
1392  let pos = popup_getpos(winid)
1393  call assert_equal(2, pos.line)
1394  call popup_close(winid)
1395
1396  bwipe!
1397endfunc
1398
1399func Test_popup_atcursor_pos()
1400  CheckScreendump
1401
1402  let lines =<< trim END
1403	call setline(1, repeat([repeat('-', 60)], 15))
1404	set so=0
1405
1406	normal 9G3|r#
1407	let winid1 = popup_atcursor(['first', 'second'], #{
1408	      \ moved: [0, 0, 0],
1409	      \ })
1410	normal 9G21|r&
1411	let winid1 = popup_atcursor(['FIrsT', 'SEcoND'], #{
1412	      \ pos: 'botright',
1413	      \ moved: [0, 0, 0],
1414	      \ })
1415	normal 3G27|r%
1416	let winid1 = popup_atcursor(['fiRSt', 'seCOnd'], #{
1417	      \ pos: 'topleft',
1418	      \ moved: [0, 0, 0],
1419	      \ })
1420	normal 3G45|r@
1421	let winid1 = popup_atcursor(['First', 'SeconD'], #{
1422	      \ pos: 'topright',
1423	      \ moved: range(3),
1424	      \ mousemoved: range(3),
1425	      \ })
1426  END
1427  call writefile(lines, 'XtestPopupAtcursorPos')
1428  let buf = RunVimInTerminal('-S XtestPopupAtcursorPos', #{rows: 12})
1429  call VerifyScreenDump(buf, 'Test_popupwin_atcursor_pos', {})
1430
1431  " clean up
1432  call StopVimInTerminal(buf)
1433  call delete('XtestPopupAtcursorPos')
1434endfunc
1435
1436func Test_popup_beval()
1437  CheckScreendump
1438  CheckFeature balloon_eval_term
1439
1440  let lines =<< trim END
1441	call setline(1, range(1, 20))
1442	call setline(5, 'here is some text to hover over')
1443	set balloonevalterm
1444	set balloonexpr=BalloonExpr()
1445	set balloondelay=100
1446	func BalloonExpr()
1447	  let s:winid = [v:beval_text]->popup_beval({})
1448	  return ''
1449	endfunc
1450	func Hover()
1451	  call test_setmouse(5, 15)
1452	  call feedkeys("\<MouseMove>\<Ignore>", "xt")
1453	  sleep 100m
1454	endfunc
1455	func MoveOntoPopup()
1456	  call test_setmouse(4, 17)
1457	  call feedkeys("\<F4>\<MouseMove>\<Ignore>", "xt")
1458	endfunc
1459	func MoveAway()
1460	  call test_setmouse(5, 13)
1461	  call feedkeys("\<F5>\<MouseMove>\<Ignore>", "xt")
1462	endfunc
1463  END
1464  call writefile(lines, 'XtestPopupBeval')
1465  let buf = RunVimInTerminal('-S XtestPopupBeval', #{rows: 10})
1466  call TermWait(buf, 50)
1467  call term_sendkeys(buf, 'j')
1468  call term_sendkeys(buf, ":call Hover()\<CR>")
1469  call VerifyScreenDump(buf, 'Test_popupwin_beval_1', {})
1470
1471  call term_sendkeys(buf, ":call MoveOntoPopup()\<CR>")
1472  call VerifyScreenDump(buf, 'Test_popupwin_beval_2', {})
1473
1474  call term_sendkeys(buf, ":call MoveAway()\<CR>")
1475  call VerifyScreenDump(buf, 'Test_popupwin_beval_3', {})
1476
1477  " clean up
1478  call StopVimInTerminal(buf)
1479  call delete('XtestPopupBeval')
1480endfunc
1481
1482func Test_popup_filter()
1483  new
1484  call setline(1, 'some text')
1485
1486  func MyPopupFilter(winid, c)
1487    if a:c == 'e' || a:c == "\<F9>"
1488      let g:eaten = a:c
1489      return 1
1490    endif
1491    if a:c == '0'
1492      let g:ignored = '0'
1493      return 0
1494    endif
1495    if a:c == 'x'
1496      call popup_close(a:winid)
1497      return 1
1498    endif
1499    return 0
1500  endfunc
1501
1502  let winid = 'something'->popup_create(#{filter: 'MyPopupFilter'})
1503  redraw
1504
1505  " e is consumed by the filter
1506  call feedkeys('e', 'xt')
1507  call assert_equal('e', g:eaten)
1508  call feedkeys("\<F9>", 'xt')
1509  call assert_equal("\<F9>", g:eaten)
1510
1511  " 0 is ignored by the filter
1512  normal $
1513  call assert_equal(9, getcurpos()[2])
1514  call feedkeys('0', 'xt')
1515  call assert_equal('0', g:ignored)
1516  call assert_equal(1, getcurpos()[2])
1517
1518  " x closes the popup
1519  call feedkeys('x', 'xt')
1520  call assert_equal("\<F9>", g:eaten)
1521  call assert_equal(-1, winbufnr(winid))
1522
1523  delfunc MyPopupFilter
1524  call popup_clear()
1525endfunc
1526
1527func ShowDialog(key, result)
1528  let s:cb_res = 999
1529  let winid = popup_dialog('do you want to quit (Yes/no)?', #{
1530	  \ filter: 'popup_filter_yesno',
1531	  \ callback: 'QuitCallback',
1532	  \ })
1533  redraw
1534  call feedkeys(a:key, "xt")
1535  call assert_equal(winid, s:cb_winid)
1536  call assert_equal(a:result, s:cb_res)
1537endfunc
1538
1539func Test_popup_dialog()
1540  func QuitCallback(id, res)
1541    let s:cb_winid = a:id
1542    let s:cb_res = a:res
1543  endfunc
1544
1545  let winid = ShowDialog("y", 1)
1546  let winid = ShowDialog("Y", 1)
1547  let winid = ShowDialog("n", 0)
1548  let winid = ShowDialog("N", 0)
1549  let winid = ShowDialog("x", 0)
1550  let winid = ShowDialog("X", 0)
1551  let winid = ShowDialog("\<Esc>", 0)
1552  let winid = ShowDialog("\<C-C>", -1)
1553
1554  delfunc QuitCallback
1555endfunc
1556
1557func ShowMenu(key, result)
1558  let s:cb_res = 999
1559  let winid = popup_menu(['one', 'two', 'something else'], #{
1560	  \ callback: 'QuitCallback',
1561	  \ })
1562  redraw
1563  call feedkeys(a:key, "xt")
1564  call assert_equal(winid, s:cb_winid)
1565  call assert_equal(a:result, s:cb_res)
1566endfunc
1567
1568func Test_popup_menu()
1569  func QuitCallback(id, res)
1570    let s:cb_winid = a:id
1571    let s:cb_res = a:res
1572  endfunc
1573  " mapping won't be used in popup
1574  map j k
1575
1576  let winid = ShowMenu(" ", 1)
1577  let winid = ShowMenu("j \<CR>", 2)
1578  let winid = ShowMenu("JjK \<CR>", 2)
1579  let winid = ShowMenu("jjjjjj ", 3)
1580  let winid = ShowMenu("kkk ", 1)
1581  let winid = ShowMenu("x", -1)
1582  let winid = ShowMenu("X", -1)
1583  let winid = ShowMenu("\<Esc>", -1)
1584  let winid = ShowMenu("\<C-C>", -1)
1585
1586  delfunc QuitCallback
1587  unmap j
1588endfunc
1589
1590func Test_popup_menu_screenshot()
1591  CheckScreendump
1592
1593  let lines =<< trim END
1594	call setline(1, range(1, 20))
1595	hi PopupSelected ctermbg=lightblue
1596	call popup_menu(['one', 'two', 'another'], #{callback: 'MenuDone', title: ' make a choice from the list '})
1597	func MenuDone(id, res)
1598	  echomsg "selected " .. a:res
1599	endfunc
1600  END
1601  call writefile(lines, 'XtestPopupMenu')
1602  let buf = RunVimInTerminal('-S XtestPopupMenu', #{rows: 10})
1603  call VerifyScreenDump(buf, 'Test_popupwin_menu_01', {})
1604
1605  call term_sendkeys(buf, "jj")
1606  call VerifyScreenDump(buf, 'Test_popupwin_menu_02', {})
1607
1608  call term_sendkeys(buf, " ")
1609  call VerifyScreenDump(buf, 'Test_popupwin_menu_03', {})
1610
1611  " clean up
1612  call StopVimInTerminal(buf)
1613  call delete('XtestPopupMenu')
1614endfunc
1615
1616func Test_popup_menu_narrow()
1617  CheckScreendump
1618
1619  let lines =<< trim END
1620	call setline(1, range(1, 20))
1621	hi PopupSelected ctermbg=green
1622	call popup_menu(['one', 'two', 'three'], #{callback: 'MenuDone'})
1623	func MenuDone(id, res)
1624	  echomsg "selected " .. a:res
1625	endfunc
1626  END
1627  call writefile(lines, 'XtestPopupNarrowMenu')
1628  let buf = RunVimInTerminal('-S XtestPopupNarrowMenu', #{rows: 10})
1629  call VerifyScreenDump(buf, 'Test_popupwin_menu_04', {})
1630
1631  " clean up
1632  call term_sendkeys(buf, "x")
1633  call StopVimInTerminal(buf)
1634  call delete('XtestPopupNarrowMenu')
1635endfunc
1636
1637func Test_popup_title()
1638  CheckScreendump
1639
1640  " Create a popup without title or border, a line of padding will be added to
1641  " put the title on.
1642  let lines =<< trim END
1643	call setline(1, range(1, 20))
1644	let winid = popup_create(['one', 'two', 'another'], #{title: 'Title String'})
1645  END
1646  call writefile(lines, 'XtestPopupTitle')
1647  let buf = RunVimInTerminal('-S XtestPopupTitle', #{rows: 10})
1648  call VerifyScreenDump(buf, 'Test_popupwin_title', {})
1649
1650  call term_sendkeys(buf, ":call popup_setoptions(winid, #{maxwidth: 20, title: 'a very long title that is not going to fit'})\<CR>")
1651  call term_sendkeys(buf, ":\<CR>")
1652  call VerifyScreenDump(buf, 'Test_popupwin_longtitle_1', {})
1653
1654  call term_sendkeys(buf, ":call popup_setoptions(winid, #{border: []})\<CR>")
1655  call term_sendkeys(buf, ":\<CR>")
1656  call VerifyScreenDump(buf, 'Test_popupwin_longtitle_2', {})
1657
1658  " clean up
1659  call StopVimInTerminal(buf)
1660  call delete('XtestPopupTitle')
1661
1662  let winid = popup_create('something', #{title: 'Some Title'})
1663  call assert_equal('Some Title', popup_getoptions(winid).title)
1664  call popup_setoptions(winid, #{title: 'Another Title'})
1665  call assert_equal('Another Title', popup_getoptions(winid).title)
1666
1667  call popup_clear()
1668endfunc
1669
1670func Test_popup_close_callback()
1671  func PopupDone(id, result)
1672    let g:result = a:result
1673  endfunc
1674  let winid = popup_create('something', #{callback: 'PopupDone'})
1675  redraw
1676  call popup_close(winid, 'done')
1677  call assert_equal('done', g:result)
1678endfunc
1679
1680func Test_popup_empty()
1681  let winid = popup_create('', #{padding: [2,2,2,2]})
1682  redraw
1683  let pos = popup_getpos(winid)
1684  call assert_equal(5, pos.width)
1685  call assert_equal(5, pos.height)
1686  call popup_close(winid)
1687
1688  let winid = popup_create([], #{border: []})
1689  redraw
1690  let pos = popup_getpos(winid)
1691  call assert_equal(3, pos.width)
1692  call assert_equal(3, pos.height)
1693  call popup_close(winid)
1694endfunc
1695
1696func Test_popup_never_behind()
1697  CheckScreendump
1698
1699  " +-----------------------------+
1700  " |             |               |
1701  " |             |               |
1702  " |             |               |
1703  " |            line1            |
1704  " |------------line2------------|
1705  " |            line3            |
1706  " |            line4            |
1707  " |                             |
1708  " |                             |
1709  " +-----------------------------+
1710  let lines =<< trim END
1711    split
1712    vsplit
1713    let info_window1 = getwininfo()[0]
1714    let line = info_window1['height']
1715    let col = info_window1['width']
1716    call popup_create(['line1', 'line2', 'line3', 'line4'], #{
1717	      \   line : line,
1718	      \   col : col,
1719	      \ })
1720  END
1721  call writefile(lines, 'XtestPopupBehind')
1722  let buf = RunVimInTerminal('-S XtestPopupBehind', #{rows: 10})
1723  call term_sendkeys(buf, "\<C-W>w")
1724  call VerifyScreenDump(buf, 'Test_popupwin_behind', {})
1725
1726  " clean up
1727  call StopVimInTerminal(buf)
1728  call delete('XtestPopupBehind')
1729endfunc
1730
1731func s:VerifyPosition(p, msg, line, col, width, height)
1732  call assert_equal(a:line,   popup_getpos(a:p).line,   a:msg . ' (l)')
1733  call assert_equal(a:col,    popup_getpos(a:p).col,    a:msg . ' (c)')
1734  call assert_equal(a:width,  popup_getpos(a:p).width,  a:msg . ' (w)')
1735  call assert_equal(a:height, popup_getpos(a:p).height, a:msg . ' (h)')
1736endfunc
1737
1738func Test_popup_position_adjust()
1739  " Anything placed past the last cell on the right of the screen is moved to
1740  " the left.
1741  "
1742  " When wrapping is disabled, we also shift to the left to display on the
1743  " screen, unless fixed is set.
1744
1745  " Entries for cases which don't vary based on wrapping.
1746  " Format is per tests described below
1747  let both_wrap_tests = [
1748	\       ['a', 5, &columns,        5, &columns, 1, 1],
1749	\       ['b', 5, &columns + 1,    5, &columns, 1, 1],
1750	\       ['c', 5, &columns - 1,    5, &columns - 1, 1, 1],
1751	\       ['d', 5, &columns - 2,    5, &columns - 2, 1, 1],
1752	\       ['e', 5, &columns - 3,    5, &columns - 3, 1, 1]]
1753
1754  " these test groups are dicts with:
1755  "  - comment: something to identify the group of tests by
1756  "  - options: dict of options to merge with the row/col in tests
1757  "  - tests: list of cases. Each one is a list with elements:
1758  "     - text
1759  "     - row
1760  "     - col
1761  "     - expected row
1762  "     - expected col
1763  "     - expected width
1764  "     - expected height
1765  let tests = [
1766	\ #{
1767	\   comment: 'left-aligned with wrapping',
1768	\   options: #{
1769	\     wrap: 1,
1770	\     pos: 'botleft',
1771	\   },
1772	\   tests: both_wrap_tests + [
1773	\       ['aa', 5, &columns,        4, &columns, 1, 2],
1774	\       ['bb', 5, &columns + 1,    4, &columns, 1, 2],
1775	\       ['cc', 5, &columns - 1,    5, &columns - 1, 2, 1],
1776	\       ['dd', 5, &columns - 2,    5, &columns - 2, 2, 1],
1777	\       ['ee', 5, &columns - 3,    5, &columns - 3, 2, 1],
1778	\
1779	\       ['aaa', 5, &columns,        3, &columns, 1, 3],
1780	\       ['bbb', 5, &columns + 1,    3, &columns, 1, 3],
1781	\       ['ccc', 5, &columns - 1,    4, &columns - 1, 2, 2],
1782	\       ['ddd', 5, &columns - 2,    5, &columns - 2, 3, 1],
1783	\       ['eee', 5, &columns - 3,    5, &columns - 3, 3, 1],
1784	\
1785	\       ['aaaa', 5, &columns,        2, &columns, 1, 4],
1786	\       ['bbbb', 5, &columns + 1,    2, &columns, 1, 4],
1787	\       ['cccc', 5, &columns - 1,    4, &columns - 1, 2, 2],
1788	\       ['dddd', 5, &columns - 2,    4, &columns - 2, 3, 2],
1789	\       ['eeee', 5, &columns - 3,    5, &columns - 3, 4, 1],
1790	\       ['eeee', 5, &columns - 4,    5, &columns - 4, 4, 1],
1791	\   ],
1792	\ },
1793	\ #{
1794	\   comment: 'left aligned without wrapping',
1795	\   options: #{
1796	\     wrap: 0,
1797	\     pos: 'botleft',
1798	\   },
1799	\   tests: both_wrap_tests + [
1800	\       ['aa', 5, &columns,        5, &columns - 1, 2, 1],
1801	\       ['bb', 5, &columns + 1,    5, &columns - 1, 2, 1],
1802	\       ['cc', 5, &columns - 1,    5, &columns - 1, 2, 1],
1803	\       ['dd', 5, &columns - 2,    5, &columns - 2, 2, 1],
1804	\       ['ee', 5, &columns - 3,    5, &columns - 3, 2, 1],
1805	\
1806	\       ['aaa', 5, &columns,        5, &columns - 2, 3, 1],
1807	\       ['bbb', 5, &columns + 1,    5, &columns - 2, 3, 1],
1808	\       ['ccc', 5, &columns - 1,    5, &columns - 2, 3, 1],
1809	\       ['ddd', 5, &columns - 2,    5, &columns - 2, 3, 1],
1810	\       ['eee', 5, &columns - 3,    5, &columns - 3, 3, 1],
1811	\
1812	\       ['aaaa', 5, &columns,        5, &columns - 3, 4, 1],
1813	\       ['bbbb', 5, &columns + 1,    5, &columns - 3, 4, 1],
1814	\       ['cccc', 5, &columns - 1,    5, &columns - 3, 4, 1],
1815	\       ['dddd', 5, &columns - 2,    5, &columns - 3, 4, 1],
1816	\       ['eeee', 5, &columns - 3,    5, &columns - 3, 4, 1],
1817	\   ],
1818	\ },
1819	\ #{
1820	\   comment: 'left aligned with fixed position',
1821	\   options: #{
1822	\     wrap: 0,
1823	\     fixed: 1,
1824	\     pos: 'botleft',
1825	\   },
1826	\   tests: both_wrap_tests + [
1827	\       ['aa', 5, &columns,        5, &columns, 1, 1],
1828	\       ['bb', 5, &columns + 1,    5, &columns, 1, 1],
1829	\       ['cc', 5, &columns - 1,    5, &columns - 1, 2, 1],
1830	\       ['dd', 5, &columns - 2,    5, &columns - 2, 2, 1],
1831	\       ['ee', 5, &columns - 3,    5, &columns - 3, 2, 1],
1832	\
1833	\       ['aaa', 5, &columns,        5, &columns, 1, 1],
1834	\       ['bbb', 5, &columns + 1,    5, &columns, 1, 1],
1835	\       ['ccc', 5, &columns - 1,    5, &columns - 1, 2, 1],
1836	\       ['ddd', 5, &columns - 2,    5, &columns - 2, 3, 1],
1837	\       ['eee', 5, &columns - 3,    5, &columns - 3, 3, 1],
1838	\
1839	\       ['aaaa', 5, &columns,        5, &columns, 1, 1],
1840	\       ['bbbb', 5, &columns + 1,    5, &columns, 1, 1],
1841	\       ['cccc', 5, &columns - 1,    5, &columns - 1, 2, 1],
1842	\       ['dddd', 5, &columns - 2,    5, &columns - 2, 3, 1],
1843	\       ['eeee', 5, &columns - 3,    5, &columns - 3, 4, 1],
1844	\   ],
1845	\ },
1846	\ ]
1847
1848  for test_group in tests
1849    for test in test_group.tests
1850      let [ text, line, col, e_line, e_col, e_width, e_height ] = test
1851      let options = #{
1852	    \ line: line,
1853	    \ col: col,
1854	    \ }
1855      call extend(options, test_group.options)
1856
1857      let p = popup_create(text, options)
1858
1859      let msg = string(extend(options, #{text: text}))
1860      call s:VerifyPosition(p, msg, e_line, e_col, e_width, e_height)
1861      call popup_close(p)
1862    endfor
1863  endfor
1864
1865  call popup_clear()
1866  %bwipe!
1867endfunc
1868
1869func Test_adjust_left_past_screen_width()
1870  " width of screen
1871  let X = join(map(range(&columns), {->'X'}), '')
1872
1873  let p = popup_create(X, #{line: 1, col: 1, wrap: 0})
1874  call s:VerifyPosition(p, 'full width topleft', 1, 1, &columns, 1)
1875
1876  redraw
1877  let line = join(map(range(1, &columns + 1), 'screenstring(1, v:val)'), '')
1878  call assert_equal(X, line)
1879
1880  call popup_close(p)
1881  redraw
1882
1883  " Same if placed on the right hand side
1884  let p = popup_create(X, #{line: 1, col: &columns, wrap: 0})
1885  call s:VerifyPosition(p, 'full width topright', 1, 1, &columns, 1)
1886
1887  redraw
1888  let line = join(map(range(1, &columns + 1), 'screenstring(1, v:val)'), '')
1889  call assert_equal(X, line)
1890
1891  call popup_close(p)
1892  redraw
1893
1894  " Extend so > window width
1895  let X .= 'x'
1896
1897  let p = popup_create(X, #{line: 1, col: 1, wrap: 0})
1898  call s:VerifyPosition(p, 'full width +  1 topleft', 1, 1, &columns, 1)
1899
1900  redraw
1901  let line = join(map(range(1, &columns + 1), 'screenstring(1, v:val)'), '')
1902  call assert_equal(X[ : -2 ], line)
1903
1904  call popup_close(p)
1905  redraw
1906
1907  " Shifted then truncated (the x is not visible)
1908  let p = popup_create(X, #{line: 1, col: &columns - 3, wrap: 0})
1909  call s:VerifyPosition(p, 'full width + 1 topright', 1, 1, &columns, 1)
1910
1911  redraw
1912  let line = join(map(range(1, &columns + 1), 'screenstring(1, v:val)'), '')
1913  call assert_equal(X[ : -2 ], line)
1914
1915  call popup_close(p)
1916  redraw
1917
1918  " Not shifted, just truncated
1919  let p = popup_create(X,
1920	\ #{line: 1, col: 2, wrap: 0, fixed: 1})
1921  call s:VerifyPosition(p, 'full width + 1 fixed', 1, 2, &columns - 1, 1)
1922
1923  redraw
1924  let line = join(map(range(1, &columns + 1), 'screenstring(1, v:val)'), '')
1925  let e_line = ' ' . X[ 1 : -2 ]
1926  call assert_equal(e_line, line)
1927
1928  call popup_close(p)
1929  redraw
1930
1931  call popup_clear()
1932  %bwipe!
1933endfunc
1934
1935func Test_popup_moved()
1936  new
1937  call test_override('char_avail', 1)
1938  call setline(1, ['one word to move around', 'a WORD.and->some thing'])
1939
1940  exe "normal gg0/word\<CR>"
1941  let winid = popup_atcursor('text', #{moved: 'any'})
1942  redraw
1943  call assert_equal(1, popup_getpos(winid).visible)
1944  call assert_equal([1, 4, 4], popup_getoptions(winid).moved)
1945  " trigger the check for last_cursormoved by going into insert mode
1946  call feedkeys("li\<Esc>", 'xt')
1947  call assert_equal({}, popup_getpos(winid))
1948  call popup_clear()
1949
1950  exe "normal gg0/word\<CR>"
1951  let winid = popup_atcursor('text', #{moved: 'word'})
1952  redraw
1953  call assert_equal(1, popup_getpos(winid).visible)
1954  call assert_equal([1, 4, 7], popup_getoptions(winid).moved)
1955  call feedkeys("hi\<Esc>", 'xt')
1956  call assert_equal({}, popup_getpos(winid))
1957  call popup_clear()
1958
1959  exe "normal gg0/word\<CR>"
1960  let winid = popup_atcursor('text', #{moved: 'word'})
1961  redraw
1962  call assert_equal(1, popup_getpos(winid).visible)
1963  call assert_equal([1, 4, 7], popup_getoptions(winid).moved)
1964  call feedkeys("li\<Esc>", 'xt')
1965  call assert_equal(1, popup_getpos(winid).visible)
1966  call feedkeys("ei\<Esc>", 'xt')
1967  call assert_equal(1, popup_getpos(winid).visible)
1968  call feedkeys("eli\<Esc>", 'xt')
1969  call assert_equal({}, popup_getpos(winid))
1970  call popup_clear()
1971
1972  " WORD is the default
1973  exe "normal gg0/WORD\<CR>"
1974  let winid = popup_atcursor('text', {})
1975  redraw
1976  call assert_equal(1, popup_getpos(winid).visible)
1977  call assert_equal([2, 2, 15], popup_getoptions(winid).moved)
1978  call feedkeys("eli\<Esc>", 'xt')
1979  call assert_equal(1, popup_getpos(winid).visible)
1980  call feedkeys("wi\<Esc>", 'xt')
1981  call assert_equal(1, popup_getpos(winid).visible)
1982  call feedkeys("Eli\<Esc>", 'xt')
1983  call assert_equal({}, popup_getpos(winid))
1984  call popup_clear()
1985
1986  exe "normal gg0/word\<CR>"
1987  let winid = popup_atcursor('text', #{moved: [5, 10]})
1988  redraw
1989  call assert_equal(1, popup_getpos(winid).visible)
1990  call feedkeys("eli\<Esc>", 'xt')
1991  call feedkeys("ei\<Esc>", 'xt')
1992  call assert_equal(1, popup_getpos(winid).visible)
1993  call feedkeys("eli\<Esc>", 'xt')
1994  call assert_equal({}, popup_getpos(winid))
1995  call popup_clear()
1996
1997  bwipe!
1998  call test_override('ALL', 0)
1999endfunc
2000
2001func Test_notifications()
2002  CheckFeature timers
2003  CheckScreendump
2004
2005  let lines =<< trim END
2006	call setline(1, range(1, 20))
2007	hi Notification ctermbg=lightblue
2008	call popup_notification('first notification', {})
2009  END
2010  call writefile(lines, 'XtestNotifications')
2011  let buf = RunVimInTerminal('-S XtestNotifications', #{rows: 10})
2012  call VerifyScreenDump(buf, 'Test_popupwin_notify_01', {})
2013
2014  " second one goes below the first one
2015  call term_sendkeys(buf, ":hi link PopupNotification Notification\<CR>")
2016  call term_sendkeys(buf, ":call popup_notification('another important notification', {})\<CR>")
2017  call VerifyScreenDump(buf, 'Test_popupwin_notify_02', {})
2018
2019  " clean up
2020  call StopVimInTerminal(buf)
2021  call delete('XtestNotifications')
2022endfunc
2023
2024func Test_popup_scrollbar()
2025  CheckScreendump
2026
2027  let lines =<< trim END
2028    call setline(1, range(1, 20))
2029    hi ScrollThumb ctermbg=blue
2030    hi ScrollBar ctermbg=red
2031    let winid = popup_create(['one', 'two', 'three', 'four', 'five',
2032	  \ 'six', 'seven', 'eight', 'nine'], #{
2033	  \ minwidth: 8,
2034	  \ maxheight: 4,
2035	  \ })
2036    func ScrollUp()
2037      call feedkeys("\<F3>\<ScrollWheelUp>", "xt")
2038    endfunc
2039    func ScrollDown()
2040      call feedkeys("\<F3>\<ScrollWheelDown>", "xt")
2041    endfunc
2042    func ClickTop()
2043      call feedkeys("\<F4>\<LeftMouse>", "xt")
2044    endfunc
2045    func ClickBot()
2046      call popup_setoptions(g:winid, #{border: [], close: 'button'})
2047      call feedkeys("\<F5>\<LeftMouse>", "xt")
2048    endfunc
2049    func Popup_filter(winid, key)
2050      if a:key == 'j'
2051	let line = popup_getoptions(a:winid).firstline
2052	let nlines = line('$', a:winid)
2053	let newline = line < nlines ? (line + 1) : nlines
2054	call popup_setoptions(a:winid, #{firstline: newline})
2055	return v:true
2056      elseif a:key == 'x'
2057	call popup_close(a:winid)
2058	return v:true
2059      endif
2060    endfunc
2061
2062    func PopupScroll()
2063      call popup_clear()
2064      let text =<< trim END
2065	  1
2066	  2
2067	  3
2068	  4
2069	  long line long line long line long line long line long line
2070	  long line long line long line long line long line long line
2071	  long line long line long line long line long line long line
2072      END
2073      call popup_create(text, #{
2074	    \ minwidth: 30,
2075	    \ maxwidth: 30,
2076	    \ minheight: 4,
2077	    \ maxheight: 4,
2078	    \ firstline: 1,
2079	    \ lastline: 4,
2080	    \ wrap: v:true,
2081	    \ scrollbar: v:true,
2082	    \ mapping: v:false,
2083	    \ filter: funcref('Popup_filter')
2084	    \ })
2085    endfunc
2086    map <silent> <F3> :call test_setmouse(5, 36)<CR>
2087    map <silent> <F4> :call test_setmouse(4, 42)<CR>
2088    map <silent> <F5> :call test_setmouse(7, 42)<CR>
2089  END
2090  call writefile(lines, 'XtestPopupScroll')
2091  let buf = RunVimInTerminal('-S XtestPopupScroll', #{rows: 10})
2092  call VerifyScreenDump(buf, 'Test_popupwin_scroll_1', {})
2093
2094  call term_sendkeys(buf, ":call popup_setoptions(winid, #{firstline: 2})\<CR>")
2095  call term_sendkeys(buf, ":\<CR>")
2096  call VerifyScreenDump(buf, 'Test_popupwin_scroll_2', {})
2097
2098  call term_sendkeys(buf, ":call popup_setoptions(winid, #{firstline: 6})\<CR>")
2099  call term_sendkeys(buf, ":\<CR>")
2100  call VerifyScreenDump(buf, 'Test_popupwin_scroll_3', {})
2101
2102  call term_sendkeys(buf, ":call popup_setoptions(winid, #{firstline: 9})\<CR>")
2103  call term_sendkeys(buf, ":\<CR>")
2104  call VerifyScreenDump(buf, 'Test_popupwin_scroll_4', {})
2105
2106  call term_sendkeys(buf, ":call popup_setoptions(winid, #{scrollbarhighlight: 'ScrollBar', thumbhighlight: 'ScrollThumb', firstline: 5})\<CR>")
2107  " this scrolls two lines (half the window height)
2108  call term_sendkeys(buf, ":call ScrollUp()\<CR>")
2109  call VerifyScreenDump(buf, 'Test_popupwin_scroll_5', {})
2110
2111  call term_sendkeys(buf, ":call ScrollDown()\<CR>")
2112  call VerifyScreenDump(buf, 'Test_popupwin_scroll_6', {})
2113
2114  call term_sendkeys(buf, ":call ScrollDown()\<CR>")
2115  " wait a bit, otherwise it fails sometimes (double click recognized?)
2116  sleep 100m
2117  call term_sendkeys(buf, ":call ScrollDown()\<CR>")
2118  call VerifyScreenDump(buf, 'Test_popupwin_scroll_7', {})
2119
2120  call term_sendkeys(buf, ":call ClickTop()\<CR>")
2121  sleep 100m
2122  call term_sendkeys(buf, ":call ClickTop()\<CR>")
2123  call VerifyScreenDump(buf, 'Test_popupwin_scroll_8', {})
2124
2125  call term_sendkeys(buf, ":call ClickBot()\<CR>")
2126  call VerifyScreenDump(buf, 'Test_popupwin_scroll_9', {})
2127
2128  " remove the minwidth and maxheight
2129  call term_sendkeys(buf, ":call popup_setoptions(winid, #{maxheight: 0, minwidth: 0})\<CR>")
2130  call term_sendkeys(buf, ":\<CR>")
2131  call VerifyScreenDump(buf, 'Test_popupwin_scroll_10', {})
2132
2133  " check size with non-wrapping lines
2134  call term_sendkeys(buf, ":call PopupScroll()\<CR>")
2135  call VerifyScreenDump(buf, 'Test_popupwin_scroll_11', {})
2136
2137  " check size with wrapping lines
2138  call term_sendkeys(buf, "j")
2139  call VerifyScreenDump(buf, 'Test_popupwin_scroll_12', {})
2140
2141  " clean up
2142  call term_sendkeys(buf, "x")
2143  call StopVimInTerminal(buf)
2144  call delete('XtestPopupScroll')
2145endfunc
2146
2147func Test_popup_fitting_scrollbar()
2148  " this was causing a crash, divide by zero
2149  let winid = popup_create([
2150	\ 'one', 'two', 'longer line that wraps', 'four', 'five'], #{
2151	\ scrollbar: 1,
2152	\ maxwidth: 10,
2153	\ maxheight: 5,
2154	\ firstline: 2})
2155  redraw
2156  call popup_clear()
2157endfunc
2158
2159func Test_popup_settext()
2160  CheckScreendump
2161
2162  let lines =<< trim END
2163    let opts = #{wrap: 0}
2164    let p = popup_create('test', opts)
2165    eval p->popup_settext('this is a text')
2166  END
2167
2168  call writefile(lines, 'XtestPopupSetText')
2169  let buf = RunVimInTerminal('-S XtestPopupSetText', #{rows: 10})
2170  call VerifyScreenDump(buf, 'Test_popup_settext_01', {})
2171
2172  " Setting to empty string clears it
2173  call term_sendkeys(buf, ":call popup_settext(p, '')\<CR>")
2174  call VerifyScreenDump(buf, 'Test_popup_settext_02', {})
2175
2176  " Setting a list
2177  call term_sendkeys(buf, ":call popup_settext(p, ['a','b','c'])\<CR>")
2178  call VerifyScreenDump(buf, 'Test_popup_settext_03', {})
2179
2180  " Shrinking with a list
2181  call term_sendkeys(buf, ":call popup_settext(p, ['a'])\<CR>")
2182  call VerifyScreenDump(buf, 'Test_popup_settext_04', {})
2183
2184  " Growing with a list
2185  call term_sendkeys(buf, ":call popup_settext(p, ['a','b','c'])\<CR>")
2186  call VerifyScreenDump(buf, 'Test_popup_settext_03', {})
2187
2188  " Empty list clears
2189  call term_sendkeys(buf, ":call popup_settext(p, [])\<CR>")
2190  call VerifyScreenDump(buf, 'Test_popup_settext_05', {})
2191
2192  " Dicts
2193  call term_sendkeys(buf, ":call popup_settext(p, [#{text: 'aaaa'}, #{text: 'bbbb'}, #{text: 'cccc'}])\<CR>")
2194  call VerifyScreenDump(buf, 'Test_popup_settext_06', {})
2195
2196  " range() (doesn't work)
2197  call term_sendkeys(buf, ":call popup_settext(p, range(4, 8))\<CR>")
2198  call VerifyScreenDump(buf, 'Test_popup_settext_07', {})
2199
2200  " clean up
2201  call StopVimInTerminal(buf)
2202  call delete('XtestPopupSetText')
2203endfunc
2204
2205func Test_popup_hidden()
2206  new
2207
2208  let winid = popup_atcursor('text', #{hidden: 1})
2209  redraw
2210  call assert_equal(0, popup_getpos(winid).visible)
2211  call popup_close(winid)
2212
2213  let winid = popup_create('text', #{hidden: 1})
2214  redraw
2215  call assert_equal(0, popup_getpos(winid).visible)
2216  call popup_close(winid)
2217
2218  func QuitCallback(id, res)
2219    let s:cb_winid = a:id
2220    let s:cb_res = a:res
2221  endfunc
2222  let winid = 'make a choice'->popup_dialog(#{hidden: 1,
2223	  \ filter: 'popup_filter_yesno',
2224	  \ callback: 'QuitCallback',
2225	  \ })
2226  redraw
2227  call assert_equal(0, popup_getpos(winid).visible)
2228  call assert_equal(function('popup_filter_yesno'), popup_getoptions(winid).filter)
2229  call assert_equal(function('QuitCallback'), popup_getoptions(winid).callback)
2230  exe "normal anot used by filter\<Esc>"
2231  call assert_equal('not used by filter', getline(1))
2232
2233  call popup_show(winid)
2234  call feedkeys('y', "xt")
2235  call assert_equal(1, s:cb_res)
2236
2237  bwipe!
2238  delfunc QuitCallback
2239endfunc
2240
2241" Test options not checked elsewhere
2242func Test_set_get_options()
2243  let winid = popup_create('some text', #{highlight: 'Beautiful'})
2244  let options = popup_getoptions(winid)
2245  call assert_equal(1, options.wrap)
2246  call assert_equal(0, options.drag)
2247  call assert_equal('Beautiful', options.highlight)
2248
2249  call popup_setoptions(winid, #{wrap: 0, drag: 1, highlight: 'Another'})
2250  let options = popup_getoptions(winid)
2251  call assert_equal(0, options.wrap)
2252  call assert_equal(1, options.drag)
2253  call assert_equal('Another', options.highlight)
2254
2255  call assert_fails('call popup_setoptions(winid, [])', 'E715:')
2256  call assert_fails('call popup_setoptions(winid, test_null_dict())', 'E715:')
2257
2258  call popup_close(winid)
2259  call assert_equal(0, popup_setoptions(winid, options.wrap))
2260endfunc
2261
2262func Test_popupwin_garbage_collect()
2263  func MyPopupFilter(x, winid, c)
2264    " NOP
2265  endfunc
2266
2267  let winid = popup_create('something', #{filter: function('MyPopupFilter', [{}])})
2268  call test_garbagecollect_now()
2269  redraw
2270  " Must not crash caused by invalid memory access
2271  call feedkeys('j', 'xt')
2272  call assert_true(v:true)
2273
2274  call popup_close(winid)
2275  delfunc MyPopupFilter
2276endfunc
2277
2278func Test_popupwin_filter_mode()
2279  func MyPopupFilter(winid, c)
2280    let s:typed = a:c
2281    if a:c == ':' || a:c == "\r" || a:c == 'v'
2282      " can start cmdline mode, get out, and start/stop Visual mode
2283      return 0
2284    endif
2285    return 1
2286  endfunc
2287
2288  " Normal, Visual and Insert mode
2289  let winid = popup_create('something', #{filter: 'MyPopupFilter', filtermode: 'nvi'})
2290  redraw
2291  call feedkeys('x', 'xt')
2292  call assert_equal('x', s:typed)
2293
2294  call feedkeys(":let g:foo = 'foo'\<CR>", 'xt')
2295  call assert_equal(':', s:typed)
2296  call assert_equal('foo', g:foo)
2297
2298  let @x = 'something'
2299  call feedkeys('v$"xy', 'xt')
2300  call assert_equal('y', s:typed)
2301  call assert_equal('something', @x)  " yank command is filtered out
2302  call feedkeys('v', 'xt')  " end Visual mode
2303
2304  call popup_close(winid)
2305
2306  " only Normal mode
2307  let winid = popup_create('something', #{filter: 'MyPopupFilter', filtermode: 'n'})
2308  redraw
2309  call feedkeys('x', 'xt')
2310  call assert_equal('x', s:typed)
2311
2312  call feedkeys(":let g:foo = 'foo'\<CR>", 'xt')
2313  call assert_equal(':', s:typed)
2314  call assert_equal('foo', g:foo)
2315
2316  let @x = 'something'
2317  call feedkeys('v$"xy', 'xt')
2318  call assert_equal('v', s:typed)
2319  call assert_notequal('something', @x)
2320
2321  call popup_close(winid)
2322
2323  " default: all modes
2324  let winid = popup_create('something', #{filter: 'MyPopupFilter'})
2325  redraw
2326  call feedkeys('x', 'xt')
2327  call assert_equal('x', s:typed)
2328
2329  let g:foo = 'bar'
2330  call feedkeys(":let g:foo = 'foo'\<CR>", 'xt')
2331  call assert_equal("\r", s:typed)
2332  call assert_equal('bar', g:foo)
2333
2334  let @x = 'something'
2335  call feedkeys('v$"xy', 'xt')
2336  call assert_equal('y', s:typed)
2337  call assert_equal('something', @x)  " yank command is filtered out
2338  call feedkeys('v', 'xt')  " end Visual mode
2339
2340  call popup_close(winid)
2341  delfunc MyPopupFilter
2342endfunc
2343
2344func Test_popupwin_filter_mouse()
2345  func MyPopupFilter(winid, c)
2346    let g:got_mousepos = getmousepos()
2347    return 0
2348  endfunc
2349
2350  call setline(1, ['.'->repeat(25)]->repeat(10))
2351  let winid = popup_create(['short', 'long line that will wrap', 'other'], #{
2352	\ line: 2,
2353	\ col: 4,
2354	\ maxwidth: 12,
2355	\ padding: [],
2356	\ border: [],
2357	\ filter: 'MyPopupFilter',
2358	\ })
2359  redraw
2360  "    123456789012345678901
2361  "  1 .....................
2362  "  2 ...+--------------+..
2363  "  3 ...|              |..
2364  "  4 ...| short        |..
2365  "  5 ...| long line th |..
2366  "  6 ...| at will wrap |..
2367  "  7 ...| other        |..
2368  "  8 ...|              |..
2369  "  9 ...+--------------+..
2370  " 10 .....................
2371  let tests = []
2372
2373  func AddItemOutsidePopup(tests, row, col)
2374    eval a:tests->add(#{clickrow: a:row, clickcol: a:col, result: #{
2375	  \ screenrow: a:row, screencol: a:col,
2376	  \ winid: win_getid(), winrow: a:row, wincol: a:col,
2377	  \ line: a:row, column: a:col,
2378	  \ }})
2379  endfunc
2380  func AddItemInPopupBorder(tests, winid, row, col)
2381    eval a:tests->add(#{clickrow: a:row, clickcol: a:col, result: #{
2382	  \ screenrow: a:row, screencol: a:col,
2383	  \ winid: a:winid, winrow: a:row - 1, wincol: a:col - 3,
2384	  \ line: 0, column: 0,
2385	  \ }})
2386  endfunc
2387  func AddItemInPopupText(tests, winid, row, col, textline, textcol)
2388    eval a:tests->add(#{clickrow: a:row, clickcol: a:col, result: #{
2389	  \ screenrow: a:row, screencol: a:col,
2390	  \ winid: a:winid, winrow: a:row - 1, wincol: a:col - 3,
2391	  \ line: a:textline, column: a:textcol,
2392	  \ }})
2393  endfunc
2394
2395  " above and below popup
2396  for c in range(1, 21)
2397    call AddItemOutsidePopup(tests, 1, c)
2398    call AddItemOutsidePopup(tests, 10, c)
2399  endfor
2400  " left and right of popup
2401  for r in range(1, 10)
2402    call AddItemOutsidePopup(tests, r, 3)
2403    call AddItemOutsidePopup(tests, r, 20)
2404  endfor
2405  " top and bottom in popup
2406  for c in range(4, 19)
2407    call AddItemInPopupBorder(tests, winid, 2, c)
2408    call AddItemInPopupBorder(tests, winid, 3, c)
2409    call AddItemInPopupBorder(tests, winid, 8, c)
2410    call AddItemInPopupBorder(tests, winid, 9, c)
2411  endfor
2412  " left and right margin in popup
2413  for r in range(2, 9)
2414    call AddItemInPopupBorder(tests, winid, r, 4)
2415    call AddItemInPopupBorder(tests, winid, r, 5)
2416    call AddItemInPopupBorder(tests, winid, r, 18)
2417    call AddItemInPopupBorder(tests, winid, r, 19)
2418  endfor
2419  " text "short"
2420  call AddItemInPopupText(tests, winid, 4, 6, 1, 1)
2421  call AddItemInPopupText(tests, winid, 4, 10, 1, 5)
2422  call AddItemInPopupText(tests, winid, 4, 11, 1, 6)
2423  call AddItemInPopupText(tests, winid, 4, 17, 1, 6)
2424  " text "long line th"
2425  call AddItemInPopupText(tests, winid, 5, 6, 2, 1)
2426  call AddItemInPopupText(tests, winid, 5, 10, 2, 5)
2427  call AddItemInPopupText(tests, winid, 5, 17, 2, 12)
2428  " text "at will wrap"
2429  call AddItemInPopupText(tests, winid, 6, 6, 2, 13)
2430  call AddItemInPopupText(tests, winid, 6, 10, 2, 17)
2431  call AddItemInPopupText(tests, winid, 6, 17, 2, 24)
2432  " text "other"
2433  call AddItemInPopupText(tests, winid, 7, 6, 3, 1)
2434  call AddItemInPopupText(tests, winid, 7, 10, 3, 5)
2435  call AddItemInPopupText(tests, winid, 7, 11, 3, 6)
2436  call AddItemInPopupText(tests, winid, 7, 17, 3, 6)
2437
2438  for item in tests
2439    call test_setmouse(item.clickrow, item.clickcol)
2440    call feedkeys("\<LeftMouse>", 'xt')
2441    call assert_equal(item.result, g:got_mousepos)
2442  endfor
2443
2444  call popup_close(winid)
2445  enew!
2446  delfunc MyPopupFilter
2447endfunc
2448
2449func Test_popupwin_with_buffer()
2450  call writefile(['some text', 'in a buffer'], 'XsomeFile')
2451  let buf = bufadd('XsomeFile')
2452  call assert_equal(0, bufloaded(buf))
2453
2454  setlocal number
2455  call setbufvar(buf, "&wrapmargin", 13)
2456
2457  let winid = popup_create(buf, {})
2458  call assert_notequal(0, winid)
2459  let pos = popup_getpos(winid)
2460  call assert_equal(2, pos.height)
2461  call assert_equal(1, bufloaded(buf))
2462
2463  " window-local option is set to default, buffer-local is not
2464  call assert_equal(0, getwinvar(winid, '&number'))
2465  call assert_equal(13, getbufvar(buf, '&wrapmargin'))
2466
2467  call popup_close(winid)
2468  call assert_equal({}, popup_getpos(winid))
2469  call assert_equal(1, bufloaded(buf))
2470  exe 'bwipe! ' .. buf
2471  setlocal nonumber
2472
2473  edit test_popupwin.vim
2474  let winid = popup_create(bufnr(''), {})
2475  redraw
2476  call popup_close(winid)
2477  call delete('XsomeFile')
2478endfunc
2479
2480func Test_popupwin_terminal_buffer()
2481  CheckFeature terminal
2482  CheckUnix
2483  " Starting a terminal to run a shell in is considered flaky.
2484  let g:test_is_flaky = 1
2485
2486  let origwin = win_getid()
2487  let termbuf = term_start(&shell, #{hidden: 1})
2488  let winid = popup_create(termbuf, #{minwidth: 40, minheight: 10})
2489  " Wait for shell to start
2490  call WaitForAssert({-> assert_equal("run", job_status(term_getjob(termbuf)))})
2491  sleep 100m
2492  " Check this doesn't crash
2493  call assert_equal(winnr(), winnr('j'))
2494  call assert_equal(winnr(), winnr('k'))
2495  call assert_equal(winnr(), winnr('h'))
2496  call assert_equal(winnr(), winnr('l'))
2497
2498  " Cannot quit while job is running
2499  call assert_fails('call feedkeys("\<C-W>:quit\<CR>", "xt")', 'E948:')
2500
2501  " Cannot enter Terminal-Normal mode. (TODO: but it works...)
2502  call feedkeys("xxx\<C-W>N", 'xt')
2503  call assert_fails('call feedkeys("gf", "xt")', 'E863:')
2504  call feedkeys("a\<C-U>", 'xt')
2505
2506  " Cannot escape from terminal window
2507  call assert_fails('tab drop xxx', 'E863:')
2508
2509  " Cannot open a second one.
2510  let termbuf2 = term_start(&shell, #{hidden: 1})
2511  call assert_fails('call popup_create(termbuf2, #{})', 'E861:')
2512  call term_sendkeys(termbuf2, "exit\<CR>")
2513
2514  " Exiting shell closes popup window
2515  call feedkeys("exit\<CR>", 'xt')
2516  " Wait for shell to exit
2517  call WaitForAssert({-> assert_equal("dead", job_status(term_getjob(termbuf)))})
2518
2519  call feedkeys(":quit\<CR>", 'xt')
2520  call assert_equal(origwin, win_getid())
2521endfunc
2522
2523func Test_popupwin_close_prevwin()
2524  CheckFeature terminal
2525
2526  call assert_equal(1, winnr('$'))
2527  split
2528  wincmd b
2529  call assert_equal(2, winnr())
2530  let buf = term_start(&shell, #{hidden: 1})
2531  call popup_create(buf, {})
2532  call term_wait(buf, 100)
2533  call popup_clear(1)
2534  call assert_equal(2, winnr())
2535
2536  quit
2537  exe 'bwipe! ' .. buf
2538endfunc
2539
2540func Test_popupwin_with_buffer_and_filter()
2541  new Xwithfilter
2542  call setline(1, range(100))
2543  let bufnr = bufnr()
2544  hide
2545
2546  func BufferFilter(win, key)
2547    if a:key == 'G'
2548      " recursive use of "G" does not cause problems.
2549      call win_execute(a:win, 'normal! G')
2550      return 1
2551    endif
2552    return 0
2553  endfunc
2554
2555  let winid = popup_create(bufnr, #{maxheight: 5, filter: 'BufferFilter'})
2556  call assert_equal(1, popup_getpos(winid).firstline)
2557  redraw
2558  call feedkeys("G", 'xt')
2559  call assert_equal(99, popup_getpos(winid).firstline)
2560
2561  call popup_close(winid)
2562  exe 'bwipe! ' .. bufnr
2563endfunc
2564
2565func Test_popupwin_width()
2566  let winid = popup_create(repeat(['short', 'long long long line', 'medium width'], 50), #{
2567	\ maxwidth: 40,
2568	\ maxheight: 10,
2569	\ })
2570  for top in range(1, 20)
2571    eval winid->popup_setoptions(#{firstline: top})
2572    redraw
2573    call assert_equal(19, popup_getpos(winid).width)
2574  endfor
2575  call popup_clear()
2576endfunc
2577
2578func Test_popupwin_buf_close()
2579  let buf = bufadd('Xtestbuf')
2580  call bufload(buf)
2581  call setbufline(buf, 1, ['just', 'some', 'lines'])
2582  let winid = popup_create(buf, {})
2583  redraw
2584  call assert_equal(3, popup_getpos(winid).height)
2585  let bufinfo = getbufinfo(buf)[0]
2586  call assert_equal(1, bufinfo.changed)
2587  call assert_equal(0, bufinfo.hidden)
2588  call assert_equal(0, bufinfo.listed)
2589  call assert_equal(1, bufinfo.loaded)
2590  call assert_equal([], bufinfo.windows)
2591  call assert_equal([winid], bufinfo.popups)
2592
2593  call popup_close(winid)
2594  call assert_equal({}, popup_getpos(winid))
2595  let bufinfo = getbufinfo(buf)[0]
2596  call assert_equal(1, bufinfo.changed)
2597  call assert_equal(1, bufinfo.hidden)
2598  call assert_equal(0, bufinfo.listed)
2599  call assert_equal(1, bufinfo.loaded)
2600  call assert_equal([], bufinfo.windows)
2601  call assert_equal([], bufinfo.popups)
2602  exe 'bwipe! ' .. buf
2603endfunc
2604
2605func Test_popup_menu_with_maxwidth()
2606  CheckScreendump
2607
2608  let lines =<< trim END
2609	call setline(1, range(1, 10))
2610	hi ScrollThumb ctermbg=blue
2611	hi ScrollBar ctermbg=red
2612	func PopupMenu(lines, line, col, scrollbar = 0)
2613		return popup_menu(a:lines, #{
2614			\ maxwidth: 10,
2615			\ maxheight: 3,
2616			\ pos : 'topleft',
2617			\ col : a:col,
2618			\ line : a:line,
2619			\ scrollbar : a:scrollbar,
2620			\ })
2621	endfunc
2622	call PopupMenu(['x'], 1, 1)
2623	call PopupMenu(['123456789|'], 1, 16)
2624	call PopupMenu(['123456789|' .. ' '], 7, 1)
2625	call PopupMenu([repeat('123456789|', 100)], 7, 16)
2626	call PopupMenu(repeat(['123456789|' .. ' '], 5), 1, 33, 1)
2627  END
2628  call writefile(lines, 'XtestPopupMenuMaxWidth')
2629  let buf = RunVimInTerminal('-S XtestPopupMenuMaxWidth', #{rows: 13})
2630  call VerifyScreenDump(buf, 'Test_popupwin_menu_maxwidth_1', {})
2631
2632  " close the menu popupwin.
2633  call term_sendkeys(buf, " ")
2634  call term_sendkeys(buf, " ")
2635  call term_sendkeys(buf, " ")
2636  call term_sendkeys(buf, " ")
2637  call term_sendkeys(buf, " ")
2638
2639  " clean up
2640  call StopVimInTerminal(buf)
2641  call delete('XtestPopupMenuMaxWidth')
2642endfunc
2643
2644func Test_popup_menu_with_scrollbar()
2645  CheckScreendump
2646
2647  let lines =<< trim END
2648    call setline(1, range(1, 20))
2649    hi ScrollThumb ctermbg=blue
2650    hi ScrollBar ctermbg=red
2651    eval ['one', 'two', 'three', 'four', 'five',
2652	  \ 'six', 'seven', 'eight', 'nine']
2653	  \ ->popup_menu(#{
2654	  \ minwidth: 8,
2655	  \ maxheight: 3,
2656	  \ })
2657  END
2658  call writefile(lines, 'XtestPopupMenuScroll')
2659  let buf = RunVimInTerminal('-S XtestPopupMenuScroll', #{rows: 10})
2660
2661  call term_sendkeys(buf, "j")
2662  call VerifyScreenDump(buf, 'Test_popupwin_menu_scroll_1', {})
2663
2664  call term_sendkeys(buf, "jjj")
2665  call VerifyScreenDump(buf, 'Test_popupwin_menu_scroll_2', {})
2666
2667  " if the cursor is the bottom line, it stays at the bottom line.
2668  call term_sendkeys(buf, repeat("j", 20))
2669  call VerifyScreenDump(buf, 'Test_popupwin_menu_scroll_3', {})
2670
2671  call term_sendkeys(buf, "kk")
2672  call VerifyScreenDump(buf, 'Test_popupwin_menu_scroll_4', {})
2673
2674  call term_sendkeys(buf, "k")
2675  call VerifyScreenDump(buf, 'Test_popupwin_menu_scroll_5', {})
2676
2677  " if the cursor is in the top line, it stays in the top line.
2678  call term_sendkeys(buf, repeat("k", 20))
2679  call VerifyScreenDump(buf, 'Test_popupwin_menu_scroll_6', {})
2680
2681  " close the menu popupwin.
2682  call term_sendkeys(buf, " ")
2683
2684  " clean up
2685  call StopVimInTerminal(buf)
2686  call delete('XtestPopupMenuScroll')
2687endfunc
2688
2689func Test_popup_menu_filter()
2690  CheckScreendump
2691
2692  let lines =<< trim END
2693	function! MyFilter(winid, key) abort
2694	  if a:key == "0"
2695		call win_execute(a:winid, "call setpos('.', [0, 1, 1, 0])")
2696		return 1
2697	  endif
2698	  if a:key == "G"
2699		call win_execute(a:winid, "call setpos('.', [0, line('$'), 1, 0])")
2700		return 1
2701	  endif
2702	  if a:key == "j"
2703		call win_execute(a:winid, "call setpos('.', [0, line('.') + 1, 1, 0])")
2704		return 1
2705	  endif
2706	  if a:key == "k"
2707		call win_execute(a:winid, "call setpos('.', [0, line('.') - 1, 1, 0])")
2708		return 1
2709	  endif
2710	  if a:key == ':'
2711		call popup_close(a:winid)
2712		return 0
2713	  endif
2714	  return 0
2715	endfunction
2716	call popup_menu(['111', '222', '333', '444', '555', '666', '777', '888', '999'], #{
2717	  \ maxheight : 3,
2718	  \ filter : 'MyFilter'
2719	  \ })
2720  END
2721  call writefile(lines, 'XtestPopupMenuFilter')
2722  let buf = RunVimInTerminal('-S XtestPopupMenuFilter', #{rows: 10})
2723
2724  call term_sendkeys(buf, "j")
2725  call VerifyScreenDump(buf, 'Test_popupwin_menu_filter_1', {})
2726
2727  call term_sendkeys(buf, "k")
2728  call VerifyScreenDump(buf, 'Test_popupwin_menu_filter_2', {})
2729
2730  call term_sendkeys(buf, "G")
2731  call VerifyScreenDump(buf, 'Test_popupwin_menu_filter_3', {})
2732
2733  call term_sendkeys(buf, "0")
2734  call VerifyScreenDump(buf, 'Test_popupwin_menu_filter_4', {})
2735
2736  " check that when the popup is closed in the filter the screen is redrawn
2737  call term_sendkeys(buf, ":")
2738  call VerifyScreenDump(buf, 'Test_popupwin_menu_filter_5', {})
2739  call term_sendkeys(buf, "\<CR>")
2740
2741  " clean up
2742  call StopVimInTerminal(buf)
2743  call delete('XtestPopupMenuFilter')
2744endfunc
2745
2746func Test_popup_cursorline()
2747  CheckScreendump
2748
2749  let winid = popup_create('some text', {})
2750  call assert_equal(0, popup_getoptions(winid).cursorline)
2751  call popup_close(winid)
2752
2753  let winid = popup_create('some text', #{ cursorline: 1, })
2754  call assert_equal(1, popup_getoptions(winid).cursorline)
2755  call popup_close(winid)
2756
2757  let winid = popup_create('some text', #{ cursorline: 0, })
2758  call assert_equal(0, popup_getoptions(winid).cursorline)
2759  call popup_close(winid)
2760
2761  let winid = popup_menu('some text', {})
2762  call assert_equal(1, popup_getoptions(winid).cursorline)
2763  call popup_close(winid)
2764
2765  let winid = popup_menu('some text', #{ cursorline: 1, })
2766  call assert_equal(1, popup_getoptions(winid).cursorline)
2767  call popup_close(winid)
2768
2769  let winid = popup_menu('some text', #{ cursorline: 0, })
2770  call assert_equal(0, popup_getoptions(winid).cursorline)
2771  call popup_close(winid)
2772
2773  " ---------
2774  " Pattern 1
2775  " ---------
2776  let lines =<< trim END
2777	call popup_create(['111', '222', '333'], #{ cursorline : 0 })
2778  END
2779  call writefile(lines, 'XtestPopupCursorLine')
2780  let buf = RunVimInTerminal('-S XtestPopupCursorLine', #{rows: 10})
2781  call VerifyScreenDump(buf, 'Test_popupwin_cursorline_1', {})
2782  call term_sendkeys(buf, ":call popup_clear()\<cr>")
2783  call StopVimInTerminal(buf)
2784
2785  " ---------
2786  " Pattern 2
2787  " ---------
2788  let lines =<< trim END
2789	call popup_create(['111', '222', '333'], #{ cursorline : 1 })
2790  END
2791  call writefile(lines, 'XtestPopupCursorLine')
2792  let buf = RunVimInTerminal('-S XtestPopupCursorLine', #{rows: 10})
2793  call VerifyScreenDump(buf, 'Test_popupwin_cursorline_2', {})
2794  call term_sendkeys(buf, ":call popup_clear()\<cr>")
2795  call StopVimInTerminal(buf)
2796
2797  " ---------
2798  " Pattern 3
2799  " ---------
2800  let lines =<< trim END
2801	function! MyFilter(winid, key) abort
2802	  if a:key == "j"
2803		call win_execute(a:winid, "call setpos('.', [0, line('.') + 1, 1, 0]) | redraw")
2804		return 1
2805	  endif
2806	  if a:key == 'x'
2807		call popup_close(a:winid)
2808		return 1
2809	  endif
2810	  return 0
2811	endfunction
2812	call popup_menu(['111', '222', '333'], #{
2813	  \ cursorline : 0,
2814	  \ maxheight : 2,
2815	  \ filter : 'MyFilter',
2816	  \ })
2817  END
2818  call writefile(lines, 'XtestPopupCursorLine')
2819  let buf = RunVimInTerminal('-S XtestPopupCursorLine', #{rows: 10})
2820  call VerifyScreenDump(buf, 'Test_popupwin_cursorline_3', {})
2821  call term_sendkeys(buf, "j")
2822  call term_sendkeys(buf, "j")
2823  call VerifyScreenDump(buf, 'Test_popupwin_cursorline_4', {})
2824  call term_sendkeys(buf, "x")
2825  call StopVimInTerminal(buf)
2826
2827  " ---------
2828  " Pattern 4
2829  " ---------
2830  let lines =<< trim END
2831	function! MyFilter(winid, key) abort
2832	  if a:key == "j"
2833		call win_execute(a:winid, "call setpos('.', [0, line('.') + 1, 1, 0]) | redraw")
2834		return 1
2835	  endif
2836	  if a:key == 'x'
2837		call popup_close(a:winid)
2838		return 1
2839	  endif
2840	  return 0
2841	endfunction
2842	call popup_menu(['111', '222', '333'], #{
2843	  \ cursorline : 1,
2844	  \ maxheight : 2,
2845	  \ filter : 'MyFilter',
2846	  \ })
2847  END
2848  call writefile(lines, 'XtestPopupCursorLine')
2849  let buf = RunVimInTerminal('-S XtestPopupCursorLine', #{rows: 10})
2850  call VerifyScreenDump(buf, 'Test_popupwin_cursorline_5', {})
2851  call term_sendkeys(buf, "j")
2852  call term_sendkeys(buf, "j")
2853  call VerifyScreenDump(buf, 'Test_popupwin_cursorline_6', {})
2854  call term_sendkeys(buf, "x")
2855  call StopVimInTerminal(buf)
2856
2857  " ---------
2858  " Cursor in second line when creating the popup
2859  " ---------
2860  let lines =<< trim END
2861    let winid = popup_create(['111', '222', '333'], #{
2862	  \ cursorline : 1,
2863	  \ })
2864    call win_execute(winid, "2")
2865  END
2866  call writefile(lines, 'XtestPopupCursorLine')
2867  let buf = RunVimInTerminal('-S XtestPopupCursorLine', #{rows: 10})
2868  call VerifyScreenDump(buf, 'Test_popupwin_cursorline_7', {})
2869  call StopVimInTerminal(buf)
2870
2871  call delete('XtestPopupCursorLine')
2872
2873  " ---------
2874  " Use current buffer for popupmenu
2875  " ---------
2876  let lines =<< trim END
2877    call setline(1, ['one', 'two', 'three'])
2878    let winid = popup_create(bufnr('%'), #{
2879	  \ cursorline : 1,
2880	  \ })
2881    call win_execute(winid, "2")
2882  END
2883  call writefile(lines, 'XtestPopupCursorLine')
2884  let buf = RunVimInTerminal('-S XtestPopupCursorLine', #{rows: 10})
2885  call VerifyScreenDump(buf, 'Test_popupwin_cursorline_8', {})
2886  call StopVimInTerminal(buf)
2887
2888  call delete('XtestPopupCursorLine')
2889endfunc
2890
2891func Test_previewpopup()
2892  CheckScreendump
2893  CheckFeature quickfix
2894
2895  call writefile([
2896        \ "!_TAG_FILE_ENCODING\tutf-8\t//",
2897        \ "another\tXtagfile\t/^this is another",
2898        \ "theword\tXtagfile\t/^theword"],
2899        \ 'Xtags')
2900  call writefile(range(1,20)
2901        \ + ['theword is here']
2902        \ + range(22, 27)
2903        \ + ['this is another place']
2904        \ + range(29, 40),
2905        \ "Xtagfile")
2906  call writefile(range(1,10)
2907        \ + ['searched word is here']
2908        \ + range(12, 20),
2909        \ "Xheader.h")
2910  let lines =<< trim END
2911        set tags=Xtags
2912	call setline(1, [
2913	      \ 'one',
2914	      \ '#include "Xheader.h"',
2915	      \ 'three',
2916	      \ 'four',
2917	      \ 'five',
2918	      \ 'six',
2919	      \ 'seven',
2920	      \ 'find theword somewhere',
2921	      \ 'nine',
2922	      \ 'this is another word',
2923	      \ 'very long line where the word is also another'])
2924        set previewpopup=height:4,width:40
2925	set path=.
2926  END
2927  call writefile(lines, 'XtestPreviewPopup')
2928  let buf = RunVimInTerminal('-S XtestPreviewPopup', #{rows: 14})
2929
2930  call term_sendkeys(buf, "/theword\<CR>\<C-W>}")
2931  call term_sendkeys(buf, ":\<CR>")
2932  call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_1', {})
2933
2934  call term_sendkeys(buf, "/another\<CR>\<C-W>}")
2935  call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_2', {})
2936
2937  call term_sendkeys(buf, ":call popup_move(popup_findpreview(), #{col: 15})\<CR>")
2938  call term_sendkeys(buf, ":\<CR>")
2939  call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_3', {})
2940
2941  call term_sendkeys(buf, "/another\<CR>\<C-W>}")
2942  call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_4', {})
2943
2944  call term_sendkeys(buf, ":silent cd ..\<CR>:\<CR>")
2945  call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_5', {})
2946  call term_sendkeys(buf, ":silent cd testdir\<CR>")
2947
2948  call term_sendkeys(buf, ":pclose\<CR>")
2949  call term_sendkeys(buf, ":\<BS>")
2950  call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_6', {})
2951
2952  call term_sendkeys(buf, ":pedit +/theword Xtagfile\<CR>")
2953  call term_sendkeys(buf, ":\<CR>")
2954  call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_7', {})
2955
2956  call term_sendkeys(buf, ":pclose\<CR>")
2957  call term_sendkeys(buf, ":psearch searched\<CR>")
2958  call term_sendkeys(buf, ":\<CR>")
2959  call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_8', {})
2960
2961  call term_sendkeys(buf, "\<C-W>p")
2962  call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_9', {})
2963
2964  call term_sendkeys(buf, ":call win_execute(popup_findpreview(), 'call popup_clear()')\<CR>")
2965  call VerifyScreenDump(buf, 'Test_popupwin_previewpopup_10', {})
2966
2967  call StopVimInTerminal(buf)
2968  call delete('Xtags')
2969  call delete('Xtagfile')
2970  call delete('XtestPreviewPopup')
2971  call delete('Xheader.h')
2972endfunc
2973
2974func Get_popupmenu_lines()
2975  let lines =<< trim END
2976      set completeopt+=preview,popup
2977      set completefunc=CompleteFuncDict
2978      hi InfoPopup ctermbg=yellow
2979
2980      func CompleteFuncDict(findstart, base)
2981	if a:findstart
2982	  if col('.') > 10
2983	    return col('.') - 10
2984	  endif
2985	  return 0
2986	endif
2987
2988	return {
2989		\ 'words': [
2990		  \ {
2991		    \ 'word': 'aword',
2992		    \ 'abbr': 'wrd',
2993		    \ 'menu': 'extra text',
2994		    \ 'info': 'words are cool',
2995		    \ 'kind': 'W',
2996		    \ 'user_data': 'test'
2997		  \ },
2998		  \ {
2999		    \ 'word': 'anotherword',
3000		    \ 'abbr': 'anotwrd',
3001		    \ 'menu': 'extra text',
3002		    \ 'info': "other words are\ncooler than this and some more text\nto make wrap",
3003		    \ 'kind': 'W',
3004		    \ 'user_data': 'notest'
3005		  \ },
3006		  \ {
3007		    \ 'word': 'noinfo',
3008		    \ 'abbr': 'noawrd',
3009		    \ 'menu': 'extra text',
3010		    \ 'info': "lets\nshow\na\nscrollbar\nhere",
3011		    \ 'kind': 'W',
3012		    \ 'user_data': 'notest'
3013		  \ },
3014		  \ {
3015		    \ 'word': 'thatword',
3016		    \ 'abbr': 'thatwrd',
3017		    \ 'menu': 'extra text',
3018		    \ 'info': 'that word is cool',
3019		    \ 'kind': 'W',
3020		    \ 'user_data': 'notest'
3021		  \ },
3022		\ ]
3023	      \ }
3024      endfunc
3025      call setline(1, 'text text text text text text text ')
3026      func ChangeColor()
3027	let id = popup_findinfo()
3028	eval id->popup_setoptions(#{highlight: 'InfoPopup'})
3029      endfunc
3030
3031      func InfoHidden()
3032	set completepopup=height:4,border:off,align:menu
3033	set completeopt-=popup completeopt+=popuphidden
3034	au CompleteChanged * call HandleChange()
3035      endfunc
3036
3037      let s:counter = 0
3038      func HandleChange()
3039	let s:counter += 1
3040	let selected = complete_info(['selected']).selected
3041	if selected <= 0
3042	  " First time: do nothing, info remains hidden
3043	  return
3044	endif
3045	if selected == 1
3046	  " Second time: show info right away
3047	  let id = popup_findinfo()
3048	  if id
3049	    call popup_settext(id, 'immediate info ' .. s:counter)
3050	    call popup_show(id)
3051	  endif
3052	else
3053	  " Third time: show info after a short delay
3054	  call timer_start(100, 'ShowInfo')
3055	endif
3056      endfunc
3057
3058      func ShowInfo(...)
3059	let id = popup_findinfo()
3060	if id
3061	  call popup_settext(id, 'async info ' .. s:counter)
3062	  call popup_show(id)
3063	endif
3064      endfunc
3065  END
3066  return lines
3067endfunc
3068
3069func Test_popupmenu_info_border()
3070  CheckScreendump
3071  CheckFeature quickfix
3072
3073  let lines = Get_popupmenu_lines()
3074  call add(lines, 'set completepopup=height:4,highlight:InfoPopup')
3075  call writefile(lines, 'XtestInfoPopup')
3076
3077  let buf = RunVimInTerminal('-S XtestInfoPopup', #{rows: 14})
3078  call TermWait(buf, 25)
3079
3080  call term_sendkeys(buf, "A\<C-X>\<C-U>")
3081  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_1', {})
3082
3083  call term_sendkeys(buf, "\<C-N>")
3084  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_2', {})
3085
3086  call term_sendkeys(buf, "\<C-N>")
3087  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_3', {})
3088
3089  call term_sendkeys(buf, "\<C-N>\<C-N>")
3090  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_4', {})
3091
3092  " info on the left with scrollbar
3093  call term_sendkeys(buf, "test text test text\<C-X>\<C-U>")
3094  call term_sendkeys(buf, "\<C-N>\<C-N>")
3095  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_5', {})
3096
3097  " Test that the popupmenu's scrollbar and infopopup do not overlap
3098  call term_sendkeys(buf, "\<Esc>")
3099  call term_sendkeys(buf, ":set pumheight=3\<CR>")
3100  call term_sendkeys(buf, "cc\<C-X>\<C-U>")
3101  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_6', {})
3102
3103  " Hide the info popup, cycle trough buffers, make sure it didn't get
3104  " deleted.
3105  call term_sendkeys(buf, "\<Esc>")
3106  call term_sendkeys(buf, ":set hidden\<CR>")
3107  call term_sendkeys(buf, ":bn\<CR>")
3108  call term_sendkeys(buf, ":bn\<CR>")
3109  call term_sendkeys(buf, "otest text test text\<C-X>\<C-U>")
3110  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_7', {})
3111
3112  " Test that when the option is changed the popup changes.
3113  call term_sendkeys(buf, "\<Esc>")
3114  call term_sendkeys(buf, ":set completepopup=border:off\<CR>")
3115  call term_sendkeys(buf, "a\<C-X>\<C-U>")
3116  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_8', {})
3117
3118  call term_sendkeys(buf, " \<Esc>")
3119  call term_sendkeys(buf, ":set completepopup+=width:10\<CR>")
3120  call term_sendkeys(buf, "a\<C-X>\<C-U>")
3121  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_9', {})
3122
3123  call term_sendkeys(buf, "\<Esc>")
3124  call StopVimInTerminal(buf)
3125  call delete('XtestInfoPopup')
3126endfunc
3127
3128func Test_popupmenu_info_noborder()
3129  CheckScreendump
3130  CheckFeature quickfix
3131
3132  let lines = Get_popupmenu_lines()
3133  call add(lines, 'set completepopup=height:4,border:off')
3134  call writefile(lines, 'XtestInfoPopupNb')
3135
3136  let buf = RunVimInTerminal('-S XtestInfoPopupNb', #{rows: 14})
3137  call TermWait(buf, 25)
3138
3139  call term_sendkeys(buf, "A\<C-X>\<C-U>")
3140  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_nb_1', {})
3141
3142  call StopVimInTerminal(buf)
3143  call delete('XtestInfoPopupNb')
3144endfunc
3145
3146func Test_popupmenu_info_align_menu()
3147  CheckScreendump
3148  CheckFeature quickfix
3149
3150  let lines = Get_popupmenu_lines()
3151  call add(lines, 'set completepopup=height:4,border:off,align:menu')
3152  call writefile(lines, 'XtestInfoPopupNb')
3153
3154  let buf = RunVimInTerminal('-S XtestInfoPopupNb', #{rows: 14})
3155  call TermWait(buf, 25)
3156
3157  call term_sendkeys(buf, "A\<C-X>\<C-U>")
3158  call term_sendkeys(buf, "\<C-N>")
3159  call term_sendkeys(buf, "\<C-N>")
3160  call term_sendkeys(buf, "\<C-N>")
3161  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_align_1', {})
3162
3163  call term_sendkeys(buf, "test text test text test\<C-X>\<C-U>")
3164  call term_sendkeys(buf, "\<C-N>")
3165  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_align_2', {})
3166
3167  call term_sendkeys(buf, "\<Esc>")
3168  call term_sendkeys(buf, ":call ChangeColor()\<CR>")
3169  call term_sendkeys(buf, ":call setline(2, ['x']->repeat(10))\<CR>")
3170  call term_sendkeys(buf, "Gotest text test text\<C-X>\<C-U>")
3171  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_align_3', {})
3172
3173  call StopVimInTerminal(buf)
3174  call delete('XtestInfoPopupNb')
3175endfunc
3176
3177func Test_popupmenu_info_hidden()
3178  CheckScreendump
3179  CheckFeature quickfix
3180
3181  let lines = Get_popupmenu_lines()
3182  call add(lines, 'call InfoHidden()')
3183  call writefile(lines, 'XtestInfoPopupHidden')
3184
3185  let buf = RunVimInTerminal('-S XtestInfoPopupHidden', #{rows: 14})
3186  call TermWait(buf, 25)
3187
3188  call term_sendkeys(buf, "A\<C-X>\<C-U>")
3189  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_hidden_1', {})
3190
3191  call term_sendkeys(buf, "\<C-N>")
3192  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_hidden_2', {})
3193
3194  call term_sendkeys(buf, "\<C-N>")
3195  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_hidden_3', {})
3196
3197  call term_sendkeys(buf, "\<Esc>")
3198  call StopVimInTerminal(buf)
3199  call delete('XtestInfoPopupHidden')
3200endfunc
3201
3202func Test_popupmenu_info_too_wide()
3203  CheckScreendump
3204  CheckFeature quickfix
3205
3206  let lines =<< trim END
3207    call setline(1, range(10))
3208
3209    set completeopt+=preview,popup
3210    set completepopup=align:menu
3211    set omnifunc=OmniFunc
3212    hi InfoPopup ctermbg=lightgrey
3213
3214    func OmniFunc(findstart, base)
3215      if a:findstart
3216        return 0
3217      endif
3218
3219      let menuText = 'some long text to make sure the menu takes up all of the width of the window'
3220      return #{
3221    	\ words: [
3222    	  \ #{
3223    	    \ word: 'scrap',
3224    	    \ menu: menuText,
3225    	    \ info: "other words are\ncooler than this and some more text\nto make wrap",
3226    	  \ },
3227    	  \ #{
3228    	    \ word: 'scappier',
3229    	    \ menu: menuText,
3230    	    \ info: 'words are cool',
3231    	  \ },
3232    	  \ #{
3233    	    \ word: 'scrappier2',
3234    	    \ menu: menuText,
3235    	    \ info: 'words are cool',
3236    	  \ },
3237    	\ ]
3238     \ }
3239    endfunc
3240  END
3241
3242  call writefile(lines, 'XtestInfoPopupWide')
3243  let buf = RunVimInTerminal('-S XtestInfoPopupWide', #{rows: 8})
3244  call TermWait(buf, 25)
3245
3246  call term_sendkeys(buf, "Ascr\<C-X>\<C-O>")
3247  call VerifyScreenDump(buf, 'Test_popupwin_infopopup_wide_1', {})
3248
3249  call term_sendkeys(buf, "\<Esc>")
3250  call StopVimInTerminal(buf)
3251  call delete('XtestInfoPopupWide')
3252endfunc
3253
3254func Test_popupwin_recycle_bnr()
3255  let bufnr = popup_notification('nothing wrong', {})->winbufnr()
3256  call popup_clear()
3257  let winid = 'nothing wrong'->popup_notification({})
3258  call assert_equal(bufnr, winbufnr(winid))
3259  call popup_clear()
3260endfunc
3261
3262func Test_popupwin_getoptions_tablocal()
3263  topleft split
3264  let win1 = popup_create('nothing', #{maxheight: 8})
3265  let win2 = popup_create('something', #{maxheight: 10})
3266  let win3 = popup_create('something', #{maxheight: 15})
3267  call assert_equal(8, popup_getoptions(win1).maxheight)
3268  call assert_equal(10, popup_getoptions(win2).maxheight)
3269  call assert_equal(15, popup_getoptions(win3).maxheight)
3270  call popup_clear()
3271  quit
3272endfunc
3273
3274func Test_popupwin_cancel()
3275  let win1 = popup_create('one', #{line: 5, filter: {... -> 0}})
3276  let win2 = popup_create('two', #{line: 10, filter: {... -> 0}})
3277  let win3 = popup_create('three', #{line: 15, filter: {... -> 0}})
3278  call assert_equal(5, popup_getpos(win1).line)
3279  call assert_equal(10, popup_getpos(win2).line)
3280  call assert_equal(15, popup_getpos(win3).line)
3281  " TODO: this also works without patch 8.1.2110
3282  call feedkeys("\<C-C>", 'xt')
3283  call assert_equal(5, popup_getpos(win1).line)
3284  call assert_equal(10, popup_getpos(win2).line)
3285  call assert_equal({}, popup_getpos(win3))
3286  call feedkeys("\<C-C>", 'xt')
3287  call assert_equal(5, popup_getpos(win1).line)
3288  call assert_equal({}, popup_getpos(win2))
3289  call assert_equal({}, popup_getpos(win3))
3290  call feedkeys("\<C-C>", 'xt')
3291  call assert_equal({}, popup_getpos(win1))
3292  call assert_equal({}, popup_getpos(win2))
3293  call assert_equal({}, popup_getpos(win3))
3294endfunc
3295
3296func Test_popupwin_filter_redraw()
3297  " Create two popups with a filter that closes the popup when typing "0".
3298  " Both popups should close, even though the redraw also calls
3299  " popup_reset_handled()
3300
3301  func CloseFilter(winid, key)
3302    if a:key == '0'
3303      call popup_close(a:winid)
3304      redraw
3305    endif
3306    return 0  " pass the key
3307  endfunc
3308
3309  let id1 = popup_create('first one', #{
3310	\ line: 1,
3311	\ col: 1,
3312	\ filter: 'CloseFilter',
3313	\ })
3314  let id2 = popup_create('second one', #{
3315	\ line: 9,
3316	\ col: 1,
3317	\ filter: 'CloseFilter',
3318	\ })
3319  call assert_equal(1, popup_getpos(id1).line)
3320  call assert_equal(9, popup_getpos(id2).line)
3321
3322  call feedkeys('0', 'xt')
3323  call assert_equal({}, popup_getpos(id1))
3324  call assert_equal({}, popup_getpos(id2))
3325
3326  call popup_clear()
3327  delfunc CloseFilter
3328endfunc
3329
3330func Test_popupwin_double_width()
3331  CheckScreendump
3332
3333  let lines =<< trim END
3334    call setline(1, 'x你好世界你好世你好世界你好')
3335    call setline(2, '你好世界你好世你好世界你好')
3336    call setline(3, 'x你好世界你好世你好世界你好')
3337    call popup_create('你好,世界 - 你好,世界xxxxx', #{line: 1, col: 3, maxwidth: 14})
3338  END
3339  call writefile(lines, 'XtestPopupWide')
3340
3341  let buf = RunVimInTerminal('-S XtestPopupWide', #{rows: 10})
3342  call VerifyScreenDump(buf, 'Test_popupwin_doublewidth_1', {})
3343
3344  call StopVimInTerminal(buf)
3345  call delete('XtestPopupWide')
3346endfunc
3347
3348func Test_popupwin_sign()
3349  CheckScreendump
3350
3351  let lines =<< trim END
3352    call setline(1, range(10))
3353    call sign_define('Current', {
3354	    \ 'text': '>>',
3355	    \ 'texthl': 'WarningMsg',
3356	    \ 'linehl': 'Error',
3357	    \ })
3358    call sign_define('Other', {
3359	    \ 'text': '#!',
3360	    \ 'texthl': 'Error',
3361	    \ 'linehl': 'Search',
3362	    \ })
3363    let winid = popup_create(['hello', 'bright', 'world'], {
3364	    \ 'minwidth': 20,
3365	    \ })
3366    call setwinvar(winid, "&signcolumn", "yes")
3367    let winbufnr = winbufnr(winid)
3368
3369    " add sign to current buffer, shows
3370    call sign_place(1, 'Selected', 'Current', bufnr('%'), {'lnum': 1})
3371    " add sign to current buffer, does not show
3372    call sign_place(2, 'PopUpSelected', 'Other', bufnr('%'), {'lnum': 2})
3373
3374    " add sign to popup buffer, shows
3375    call sign_place(3, 'PopUpSelected', 'Other', winbufnr, {'lnum': 1})
3376    " add sign to popup buffer, does not show
3377    call sign_place(4, 'Selected', 'Current', winbufnr, {'lnum': 2})
3378
3379    func SetOptions()
3380      call setwinvar(g:winid, '&number', 1)
3381      call setwinvar(g:winid, '&foldcolumn', 2)
3382      call popup_settext(g:winid, 'a longer line to check the width')
3383    endfunc
3384  END
3385  call writefile(lines, 'XtestPopupSign')
3386
3387  let buf = RunVimInTerminal('-S XtestPopupSign', #{rows: 10})
3388  call VerifyScreenDump(buf, 'Test_popupwin_sign_1', {})
3389
3390  " set more options to check the width is adjusted
3391  call term_sendkeys(buf, ":call SetOptions()\<CR>")
3392  call VerifyScreenDump(buf, 'Test_popupwin_sign_2', {})
3393
3394  call StopVimInTerminal(buf)
3395  call delete('XtestPopupSign')
3396endfunc
3397
3398func Test_popupwin_bufnr()
3399  let popwin = popup_create(['blah'], #{})
3400  let popbuf = winbufnr(popwin)
3401  split asdfasdf
3402  let newbuf = bufnr()
3403  call assert_true(newbuf > popbuf, 'New buffer number is higher')
3404  call assert_equal(newbuf, bufnr('$'))
3405  call popup_clear()
3406  let popwin = popup_create(['blah'], #{})
3407  " reuses previous buffer number
3408  call assert_equal(popbuf, winbufnr(popwin))
3409  call assert_equal(newbuf, bufnr('$'))
3410
3411  call popup_clear()
3412  bwipe!
3413endfunc
3414
3415func Test_popupwin_filter_input_multibyte()
3416  func MyPopupFilter(winid, c)
3417    let g:bytes = range(a:c->strlen())->map({i -> char2nr(a:c[i])})
3418    return 0
3419  endfunc
3420  let winid = popup_create('', #{mapping: 0, filter: 'MyPopupFilter'})
3421
3422  " UTF-8: E3 80 80, including K_SPECIAL(0x80)
3423  call feedkeys("\u3000", 'xt')
3424  call assert_equal([0xe3, 0x80, 0x80], g:bytes)
3425
3426  " UTF-8: E3 80 9B, including CSI(0x9B)
3427  call feedkeys("\u301b", 'xt')
3428  call assert_equal([0xe3, 0x80, 0x9b], g:bytes)
3429
3430  if has('unix')
3431    " with modifyOtherKeys <M-S-a> does not include a modifier sequence
3432    if has('gui_running')
3433      call feedkeys("\x9b\xfc\x08A", 'Lx!')
3434    else
3435      call feedkeys("\<Esc>[27;4;65~", 'Lx!')
3436    endif
3437    call assert_equal([0xc3, 0x81], g:bytes)
3438  endif
3439
3440  call popup_clear()
3441  delfunc MyPopupFilter
3442  unlet g:bytes
3443endfunc
3444
3445func Test_popupwin_atcursor_far_right()
3446  new
3447
3448  " this was getting stuck
3449  set signcolumn=yes
3450  call setline(1, repeat('=', &columns))
3451  normal! ggg$
3452  let winid = popup_atcursor(repeat('x', 500), #{moved: 'any', border: []})
3453
3454  " 'signcolumn' was getting reset
3455  call setwinvar(winid, '&signcolumn', 'yes')
3456  call popup_setoptions(winid, {'zindex': 1000})
3457  call assert_equal('yes', getwinvar(winid, '&signcolumn'))
3458
3459  call popup_close(winid)
3460  bwipe!
3461  set signcolumn&
3462endfunc
3463
3464func Test_popupwin_splitmove()
3465  vsplit
3466  let win2 = win_getid()
3467  let popup_winid = popup_dialog('hello', {})
3468  call assert_fails('call win_splitmove(popup_winid, win2)', 'E957:')
3469  call assert_fails('call win_splitmove(win2, popup_winid)', 'E957:')
3470
3471  call popup_clear()
3472  bwipe
3473endfunc
3474
3475
3476" vim: shiftwidth=2 sts=2
3477