diff --git a/autoload/ale/floating_preview.vim b/autoload/ale/floating_preview.vim index 3e1fabb859..9ae181b05c 100644 --- a/autoload/ale/floating_preview.vim +++ b/autoload/ale/floating_preview.vim @@ -92,6 +92,7 @@ function! s:VimShow(lines, options) abort endfor call popup_settext(w:preview['id'], a:lines) + call s:VimPinWidth(w:preview['id'], a:lines) if g:ale_close_preview_on_insert augroup ale_floating_preview_window @@ -144,6 +145,56 @@ function! s:NvimCreate(options) abort let w:preview = {'id': l:winid, 'buffer': l:buffer} endfunction +" Before 9.2.0419 Vim sizes popups by the lines in view, so the width changes +" while scrolling. Size the popup by all of the lines instead. +function! s:VimPinWidth(winid, lines) abort + let l:user_opts = s:GetPopupOpts() + + if has('patch-9.2.0419') + \|| has_key(l:user_opts, 'minwidth') + \|| has_key(l:user_opts, 'maxwidth') + return + endif + + call popup_setoptions(a:winid, { + \ 'minwidth': max(map(copy(a:lines), 'strdisplaywidth(v:val)')), + \}) +endfunction + +" Vim's scroll keys scroll the popup while it is open and has more to show. +" Every key runs as CTRL-D or CTRL-U, which stop at the edges of the text, so +" the popup never scrolls into blank space or shrinks. +function! s:VimFilter(winid, key) abort + if a:key is# "\" + call popup_close(a:winid) + + return 1 + endif + + let l:pos = popup_getpos(a:winid) + let l:half = max([1, l:pos.core_height / 2]) + let l:command = get({ + \ "\": "1\", + \ "\": "1\", + \ "\": l:half . "\", + \ "\": l:half . "\", + \ "\": l:pos.core_height . "\", + \ "\": l:pos.core_height . "\", + \}, a:key, '') + + " Let keys through if everything is already visible. + if empty(l:command) + \|| (l:pos.firstline is# 1 && l:pos.lastline >= line('$', a:winid)) + return 0 + endif + + call win_execute(a:winid, 'normal! ' . l:command) + " Vim 8.2 can scroll wrapped text too far if keys arrive faster than redraws. + redraw + + return 1 +endfunction + function! s:VimCreate(options) abort " default options let l:popup_opts = extend({ @@ -164,6 +215,8 @@ function! s:VimCreate(options) abort \ get(g:ale_floating_window_border, 4, '+'), \ get(g:ale_floating_window_border, 5, '+'), \ ], + \ 'filter': function('s:VimFilter'), + \ 'filtermode': 'n', \ 'moved': 'any', \ }, s:GetPopupOpts()) diff --git a/doc/ale.txt b/doc/ale.txt index 638d9b5b5e..475255ccff 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -835,6 +835,11 @@ or |g:ale_floating_preview| is set to `true` or `1`, the hover information will show in a floating window. The borders of the floating preview window can be customized by setting |g:ale_floating_window_border|. +In Vim, closes a floating preview. If it has more text than fits, then +|CTRL-E|, |CTRL-Y|, |CTRL-D|, |CTRL-U|, |CTRL-F|, and |CTRL-B| scroll the +preview instead of the buffer. Set `filter` in +|g:ale_floating_preview_popup_opts| to change this. See |popup-filter|. + For Vim 8.1+ terminals, mouse hovering is disabled by default. Enabling |balloonexpr| commands in terminals can cause scrolling issues in terminals, so ALE will not attempt to show balloons unless |g:ale_set_balloons| is set to diff --git a/test/test_floating_preview_popupwin.vader b/test/test_floating_preview_popupwin.vader new file mode 100644 index 0000000000..6e2a3b1d76 --- /dev/null +++ b/test/test_floating_preview_popupwin.vader @@ -0,0 +1,122 @@ +Before: + Save g:ale_close_preview_on_insert + Save g:ale_floating_preview_popup_opts + + let g:ale_close_preview_on_insert = 0 + let g:ale_floating_preview_popup_opts = {'maxheight': 4} + + runtime autoload/ale/floating_preview.vim + + " Press keys through the popup filter, and return the first line shown + " after each key along with the height of the popup. + function! Press(id, keys) abort + let l:result = [] + + for l:key in a:keys + call call(popup_getoptions(a:id).filter, [a:id, l:key]) + call add(l:result, [ + \ popup_getpos(a:id).firstline, + \ popup_getpos(a:id).core_height, + \]) + endfor + + return l:result + endfunction + +After: + Restore + + if has('popupwin') + call popup_clear(1) + endif + + unlet! w:preview + delfunction Press + +Execute(Scroll keys should scroll Vim popups and stop at the edges): + if has('popupwin') + let id = ale#floating_preview#Show(map(range(1, 10), 'string(v:val)')) + + AssertEqual + \ [[2, 4], [4, 4], [7, 4], [7, 4], [5, 4], [4, 4], [1, 4], [1, 4]], + \ Press(id, [ + \ "\", "\", "\", "\", + \ "\", "\", "\", "\", + \ ]) + endif + +Execute(Scrolling should reach the end of wrapped text): + if has('popupwin') + let g:ale_floating_preview_popup_opts = {'maxheight': 4, 'maxwidth': 20} + let id = ale#floating_preview#Show( + \ ['first', repeat('a', 50), repeat('b', 50), 'last'] + \) + + call Press(id, repeat(["\"], 5)) + + AssertEqual + \ [3, 4, 4], + \ [ + \ popup_getpos(id).firstline, + \ popup_getpos(id).lastline, + \ popup_getpos(id).core_height, + \ ] + endif + +Execute(Keys should pass through when there is nothing to scroll): + if has('popupwin') + let id = ale#floating_preview#Show(['one', 'two']) + let Filter = popup_getoptions(id).filter + " Vim draws the popup before keys are typed. + redraw + + AssertEqual 0, Filter(id, "\") + AssertEqual 0, Filter(id, "\") + endif + +Execute(Other keys should pass through): + if has('popupwin') + let id = ale#floating_preview#Show(map(range(1, 10), 'string(v:val)')) + let Filter = popup_getoptions(id).filter + + AssertEqual 0, Filter(id, 'j') + AssertEqual 0, Filter(id, "\") + AssertEqual 1, popup_getpos(id).firstline + endif + +Execute(Escape should close the popup): + if has('popupwin') + let id = ale#floating_preview#Show(['one']) + + AssertEqual 1, call(popup_getoptions(id).filter, [id, "\"]) + AssertEqual -1, index(popup_list(), id) + endif + +Execute(Typed keys should reach the filter in normal mode): + if has('popupwin') + let id = ale#floating_preview#Show(map(range(1, 10), 'string(v:val)')) + + call feedkeys("\", 'xt') + + AssertEqual 3, popup_getpos(id).firstline + endif + +Execute(The popup width should cover all lines on older Vim versions): + if has('popupwin') + let id = ale#floating_preview#Show( + \ map(range(1, 9), 'string(v:val)') + [repeat('x', 30)] + \) + + AssertEqual + \ has('patch-9.2.0419') ? 0 : 30, + \ popup_getoptions(id).minwidth + endif + +Execute(The popup width should not be changed when users set it): + if has('popupwin') + let g:ale_floating_preview_popup_opts = {'maxheight': 4, 'maxwidth': 10} + let id = ale#floating_preview#Show([repeat('x', 30)]) + + AssertEqual 0, popup_getoptions(id).minwidth + AssertEqual 10, popup_getoptions(id).maxwidth + endif