0
0
mirror of https://github.com/vim/vim.git synced 2025-09-26 04:04:07 -04:00

patch 8.1.1449: popup text truncated at end of screen

Problem:    Popup text truncated at end of screen.
Solution:   Move popup left if needed.  Add the "fixed" property to disable
            that. (Ben Jackson , closes #4466)
This commit is contained in:
Bram Moolenaar
2019-06-02 14:49:56 +02:00
parent 988c43310a
commit 042fb4b449
5 changed files with 306 additions and 71 deletions

View File

@@ -65,8 +65,10 @@ The width of the window is normally equal to the longest line in the buffer.
It can be limited with the "maxwidth" property. You can use spaces to It can be limited with the "maxwidth" property. You can use spaces to
increase the width or the "minwidth" property. increase the width or the "minwidth" property.
By default the 'wrap' option is set, so that no text disappears. However, if By default the 'wrap' option is set, so that no text disappears. Otherwise,
there is not enough space, some text may be invisible. if there is not enough space then the window is shifted left in order to
display more text. This can be disabled with the "fixed" property. Also
disabled when right-aligned.
Vim tries to show the popup in the location you specify. In some cases, e.g. Vim tries to show the popup in the location you specify. In some cases, e.g.
when the popup would go outside of the Vim window, it will show it somewhere when the popup would go outside of the Vim window, it will show it somewhere
@@ -78,8 +80,6 @@ window it will be placed below the cursor position.
TODO: TODO:
Example how to use syntax highlighting of a code snippet.
Scrolling: When the screen scrolls up for output of an Ex command, what Scrolling: When the screen scrolls up for output of an Ex command, what
happens with popups? happens with popups?
1. Stay where they are. Problem: listed text may go behind and can't be read. 1. Stay where they are. Problem: listed text may go behind and can't be read.
@@ -93,12 +93,12 @@ IMPLEMENTATION:
- Fix positioning with border and padding. - Fix positioning with border and padding.
- Why does 'nrformats' leak from the popup window buffer??? - Why does 'nrformats' leak from the popup window buffer???
- Make redrawing more efficient and avoid flicker. - Make redrawing more efficient and avoid flicker.
Store popup info in a mask, use the mask in screen_line() First draw popups, creating a mask, use the mask in screen_line() when
Keep mask until next update_screen(), find differences and redraw affected drawing other windows and stuff. Mask contains zindex of popups.
windows/lines Keep mask until next update_screen(), use when drawing status lines.
Remove update_popup() calls after draw_tabline()/updating statusline
Fix redrawing problem with completion. Fix redrawing problem with completion.
Fix redrawing problem when scrolling non-current window Fix redrawing problem when scrolling non-current window
Fix redrawing the statusline on top of a popup
- Disable commands, feedkeys(), CTRL-W, etc. in a popup window. - Disable commands, feedkeys(), CTRL-W, etc. in a popup window.
Use NOT_IN_POPUP_WINDOW for more commands. Use NOT_IN_POPUP_WINDOW for more commands.
- Invoke filter with character before mapping? - Invoke filter with character before mapping?
@@ -327,81 +327,100 @@ optionally text properties. It is in one of three forms:
|popup-props|. |popup-props|.
The second argument of |popup_create()| is a dictionary with options: The second argument of |popup_create()| is a dictionary with options:
line screen line where to position the popup; can use a line Screen line where to position the popup. Can use a
number or "cursor", "cursor+1" or "cursor-1" to use number or "cursor", "cursor+1" or "cursor-1" to use
the line of the cursor and add or subtract a number of the line of the cursor and add or subtract a number of
lines; if omitted the popup is vertically centered, lines. If omitted the popup is vertically centered.
otherwise "pos" is used. The first line is 1.
col screen column where to position the popup; can use a col Screen column where to position the popup. Can use a
number or "cursor" to use the column of the cursor, number or "cursor" to use the column of the cursor,
"cursor+99" and "cursor-99" to add or subtract a "cursor+9" or "cursor-9" to add or subtract a number
number of columns; if omitted the popup is of columns. If omitted the popup is horizontally
horizontally centered, otherwise "pos" is used centered. The first column is 1.
pos "topleft", "topright", "botleft" or "botright": pos "topleft", "topright", "botleft" or "botright":
defines what corner of the popup "line" and "col" are defines what corner of the popup "line" and "col" are
used for. When not set "topleft" is used. used for. When not set "topleft" is used.
Alternatively "center" can be used to position the Alternatively "center" can be used to position the
popup in the center of the Vim window, in which case popup in the center of the Vim window, in which case
"line" and "col" are ignored. "line" and "col" are ignored.
flip when TRUE (the default) and the position is relative fixed When FALSE (the default), and:
- "pos" is "botleft" or "topleft", and
- "wrap" is off, and
- the popup would be truncated at the right edge of
the screen, then
the popup is moved to the left so as to fit the
contents on the screen. Set to TRUE to disable this.
flip When TRUE (the default) and the position is relative
to the cursor, flip to below or above the cursor to to the cursor, flip to below or above the cursor to
avoid overlap with the |popupmenu-completion| or avoid overlap with the |popupmenu-completion| or
another popup with a higher "zindex" another popup with a higher "zindex".
{not implemented yet} {not implemented yet}
maxheight maximum height maxheight Maximum height of the contents, excluding border and
minheight minimum height padding.
maxwidth maximum width minheight Minimum height of the contents, excluding border and
minwidth minimum width padding.
hidden when TRUE the popup exists but is not displayed; use maxwidth Maximum width of the contents, excluding border and
padding.
minwidth Minimum width of the contents, excluding border and
padding.
hidden When TRUE the popup exists but is not displayed; use
`popup_show()` to unhide it. `popup_show()` to unhide it.
{not implemented yet} {not implemented yet}
tab when -1: display the popup on all tabs; when 0 (the tab When -1: display the popup on all tabs.
default): display the popup on the current tab; When 0 (the default): display the popup on the current
otherwise the number of the tab page the popup is tab.
displayed on; when invalid the current tab is used Otherwise the number of the tab page the popup is
displayed on; when invalid the current tab is used.
{only -1 and 0 are implemented} {only -1 and 0 are implemented}
title text to be displayed above the first item in the title Text to be displayed above the first item in the
popup, on top of any border popup, on top of any border. If there is no top
border on line of padding is added to put the title on.
{not implemented yet} {not implemented yet}
wrap TRUE to make the lines wrap (default TRUE) wrap TRUE to make the lines wrap (default TRUE).
highlight highlight group name to use for the text, stored in highlight Highlight group name to use for the text, stored in
the 'wincolor' option the 'wincolor' option.
padding list with numbers, defining the padding padding List with numbers, defining the padding
above/right/below/left of the popup (similar to CSS); above/right/below/left of the popup (similar to CSS).
an empty list uses a padding of 1 all around; the An empty list uses a padding of 1 all around. The
padding goes around the text, inside any border; padding goes around the text, inside any border.
padding uses the 'wincolor' highlight; Example: [1, 2, Padding uses the 'wincolor' highlight.
1, 3] has 1 line of padding above, 2 columns on the Example: [1, 2, 1, 3] has 1 line of padding above, 2
right, 1 line below and 3 columns on the left columns on the right, 1 line below and 3 columns on
border list with numbers, defining the border thickness the left.
above/right/below/left of the popup (similar to CSS); border List with numbers, defining the border thickness
only values of zero and non-zero are recognized; above/right/below/left of the popup (similar to CSS).
an empty list uses a border all around Only values of zero and non-zero are recognized.
borderhighlight list of highlight group names to use for the border; An empty list uses a border all around.
when one entry it is used for all borders, otherwise borderhighlight List of highlight group names to use for the border.
the highlight for the top/right/bottom/left border When one entry it is used for all borders, otherwise
borderchars list with characters, defining the character to use the highlight for the top/right/bottom/left border.
for the top/right/bottom/left border; optionally Example: ['TopColor', 'RightColor', 'BottomColor,
'LeftColor']
borderchars List with characters, defining the character to use
for the top/right/bottom/left border. Optionally
followed by the character to use for the followed by the character to use for the
topleft/topright/botright/botleft corner; when the topleft/topright/botright/botleft corner.
list has one character it is used for all; when Example: ['-', '|', '-', '|', '┌', '┐', '┘', '└']
the list has two characters the first is used for the When the list has one character it is used for all.
border lines, the second for the corners; by default When the list has two characters the first is used for
a double line is used all around when 'encoding' is the border lines, the second for the corners.
"utf-8", otherwise ASCII characters are used. By default a double line is used all around when
zindex priority for the popup, default 50 'encoding' is "utf-8", otherwise ASCII characters are
time time in milliseconds after which the popup will close; used.
when omitted |popup_close()| must be used. zindex Priority for the popup, default 50.
time Time in milliseconds after which the popup will close.
When omitted |popup_close()| must be used.
moved "cell": close the popup if the cursor moved at least moved "cell": close the popup if the cursor moved at least
one screen cell; "word" allows for moving within one screen cell.
|<cword>|, "WORD" allows for moving within |<cWORD>|, "word" allows for moving the cursor within |<cword>|
"WORD" allows for moving the cursor within |<cWORD>|
a list with two numbers specifies the start and end a list with two numbers specifies the start and end
column column outside of which the popup will close
{not implemented yet} {not implemented yet}
filter a callback that can filter typed characters, see filter A callback that can filter typed characters, see
|popup-filter| |popup-filter|.
callback a callback to be used when the popup closes, e.g. when callback A callback that is called when the popup closes, e.g.
using |popup_filter_menu()|, see |popup-callback|. when using |popup_filter_menu()|, see |popup-callback|.
Depending on the "zindex" the popup goes under or above other popups. The Depending on the "zindex" the popup goes under or above other popups. The
completion menu (|popup-menu|) has zindex 100. For messages that occur for a completion menu (|popup-menu|) has zindex 100. For messages that occur for a

View File

@@ -84,6 +84,8 @@ get_pos_options(win_T *wp, dict_T *dict)
if (nr > 0) if (nr > 0)
wp->w_wantcol = nr; wp->w_wantcol = nr;
wp->w_popup_fixed = dict_get_number(dict, (char_u *)"fixed") != 0;
str = dict_get_string(dict, (char_u *)"pos", FALSE); str = dict_get_string(dict, (char_u *)"pos", FALSE);
if (str != NULL) if (str != NULL)
{ {
@@ -379,6 +381,7 @@ popup_adjust_position(win_T *wp)
int maxwidth; int maxwidth;
int center_vert = FALSE; int center_vert = FALSE;
int center_hor = FALSE; int center_hor = FALSE;
int allow_adjust_left = !wp->w_popup_fixed;
wp->w_winrow = 0; wp->w_winrow = 0;
wp->w_wincol = 0; wp->w_wincol = 0;
@@ -412,10 +415,14 @@ popup_adjust_position(win_T *wp)
} }
// When centering or right aligned, use maximum width. // When centering or right aligned, use maximum width.
// When left aligned use the space available. // When left aligned use the space available, but shift to the left when we
// hit the right of the screen.
maxwidth = Columns - wp->w_wincol; maxwidth = Columns - wp->w_wincol;
if (wp->w_maxwidth > 0 && maxwidth > wp->w_maxwidth) if (wp->w_maxwidth > 0 && maxwidth > wp->w_maxwidth)
{
allow_adjust_left = FALSE;
maxwidth = wp->w_maxwidth; maxwidth = wp->w_maxwidth;
}
// Compute width based on longest text line and the 'wrap' option. // Compute width based on longest text line and the 'wrap' option.
// TODO: more accurate wrapping // TODO: more accurate wrapping
@@ -424,12 +431,34 @@ popup_adjust_position(win_T *wp)
{ {
int len = vim_strsize(ml_get_buf(wp->w_buffer, lnum, FALSE)); int len = vim_strsize(ml_get_buf(wp->w_buffer, lnum, FALSE));
while (wp->w_p_wrap && len > maxwidth) if (wp->w_p_wrap)
{
while (len > maxwidth)
{ {
++wrapped; ++wrapped;
len -= maxwidth; len -= maxwidth;
wp->w_width = maxwidth; wp->w_width = maxwidth;
} }
}
else if (len > maxwidth
&& allow_adjust_left
&& (wp->w_popup_pos == POPPOS_TOPLEFT
|| wp->w_popup_pos == POPPOS_BOTLEFT))
{
// adjust leftwise to fit text on screen
int shift_by = ( len - maxwidth );
if ( shift_by > wp->w_wincol )
{
int truncate_shift = shift_by - wp->w_wincol;
len -= truncate_shift;
shift_by -= truncate_shift;
}
wp->w_wincol -= shift_by;
maxwidth += shift_by;
wp->w_width = maxwidth;
}
if (wp->w_width < len) if (wp->w_width < len)
wp->w_width = len; wp->w_width = len;
} }
@@ -895,6 +924,7 @@ f_popup_getoptions(typval_T *argvars, typval_T *rettv)
dict_add_number(dict, "maxheight", wp->w_maxheight); dict_add_number(dict, "maxheight", wp->w_maxheight);
dict_add_number(dict, "maxwidth", wp->w_maxwidth); dict_add_number(dict, "maxwidth", wp->w_maxwidth);
dict_add_number(dict, "zindex", wp->w_zindex); dict_add_number(dict, "zindex", wp->w_zindex);
dict_add_number(dict, "fixed", wp->w_popup_fixed);
for (i = 0; i < (int)(sizeof(poppos_entries) / sizeof(poppos_entry_T)); for (i = 0; i < (int)(sizeof(poppos_entries) / sizeof(poppos_entry_T));
++i) ++i)

View File

@@ -2881,6 +2881,7 @@ struct window_S
#ifdef FEAT_TEXT_PROP #ifdef FEAT_TEXT_PROP
int w_popup_flags; // POPF_ values int w_popup_flags; // POPF_ values
poppos_T w_popup_pos; poppos_T w_popup_pos;
int w_popup_fixed; // do not shift popup to fit on screen
int w_zindex; int w_zindex;
int w_minheight; // "minheight" for popup window int w_minheight; // "minheight" for popup window
int w_minwidth; // "minwidth" for popup window int w_minwidth; // "minwidth" for popup window

View File

@@ -422,6 +422,7 @@ func Test_popup_getoptions()
\ 'maxheight': 21, \ 'maxheight': 21,
\ 'zindex': 100, \ 'zindex': 100,
\ 'time': 5000, \ 'time': 5000,
\ 'fixed': 1
\}) \})
redraw redraw
let res = popup_getoptions(winid) let res = popup_getoptions(winid)
@@ -432,6 +433,7 @@ func Test_popup_getoptions()
call assert_equal(20, res.maxwidth) call assert_equal(20, res.maxwidth)
call assert_equal(21, res.maxheight) call assert_equal(21, res.maxheight)
call assert_equal(100, res.zindex) call assert_equal(100, res.zindex)
call assert_equal(1, res.fixed)
if has('timers') if has('timers')
call assert_equal(5000, res.time) call assert_equal(5000, res.time)
endif endif
@@ -447,6 +449,7 @@ func Test_popup_getoptions()
call assert_equal(0, res.maxwidth) call assert_equal(0, res.maxwidth)
call assert_equal(0, res.maxheight) call assert_equal(0, res.maxheight)
call assert_equal(50, res.zindex) call assert_equal(50, res.zindex)
call assert_equal(0, res.fixed)
if has('timers') if has('timers')
call assert_equal(0, res.time) call assert_equal(0, res.time)
endif endif
@@ -647,3 +650,183 @@ func Test_popup_never_behind()
call StopVimInTerminal(buf) call StopVimInTerminal(buf)
call delete('XtestPopupBehind') call delete('XtestPopupBehind')
endfunc endfunc
func s:VerifyPosition( p, msg, line, col, width, height )
call assert_equal( a:line, popup_getpos( a:p ).line, a:msg . ' (l)' )
call assert_equal( a:col, popup_getpos( a:p ).col, a:msg . ' (c)' )
call assert_equal( a:width, popup_getpos( a:p ).width, a:msg . ' (w)' )
call assert_equal( a:height, popup_getpos( a:p ).height, a:msg . ' (h)' )
endfunc
func Test_popup_position_adjust()
" Anything placed past 2 cells from of the right of the screen is moved to the
" left.
"
" When wrapping is disabled, we also shift to the left to display on the
" screen, unless fixed is set.
" Entries for cases which don't vary based on wrapping.
" Format is per tests described below
let both_wrap_tests = [
\ [ 'a', 5, &columns, 5, &columns - 2, 1, 1 ],
\ [ 'b', 5, &columns + 1, 5, &columns - 2, 1, 1 ],
\ [ 'c', 5, &columns - 1, 5, &columns - 2, 1, 1 ],
\ [ 'd', 5, &columns - 2, 5, &columns - 2, 1, 1 ],
\ [ 'e', 5, &columns - 3, 5, &columns - 3, 1, 1 ],
\
\ [ 'aa', 5, &columns, 5, &columns - 2, 2, 1 ],
\ [ 'bb', 5, &columns + 1, 5, &columns - 2, 2, 1 ],
\ [ 'cc', 5, &columns - 1, 5, &columns - 2, 2, 1 ],
\ [ 'dd', 5, &columns - 2, 5, &columns - 2, 2, 1 ],
\ [ 'ee', 5, &columns - 3, 5, &columns - 3, 2, 1 ],
\
\ [ 'aaa', 5, &columns, 5, &columns - 2, 3, 1 ],
\ [ 'bbb', 5, &columns + 1, 5, &columns - 2, 3, 1 ],
\ [ 'ccc', 5, &columns - 1, 5, &columns - 2, 3, 1 ],
\ [ 'ddd', 5, &columns - 2, 5, &columns - 2, 3, 1 ],
\ [ 'eee', 5, &columns - 3, 5, &columns - 3, 3, 1 ],
\ ]
" these test groups are dicts with:
" - comment: something to identify the group of tests by
" - options: dict of options to merge with the row/col in tests
" - tests: list of cases. Each one is a list with elements:
" - text
" - row
" - col
" - expected row
" - expected col
" - expected width
" - expected height
let tests = [
\ {
\ 'comment': 'left-aligned with wrapping',
\ 'options': {
\ 'wrap': 1,
\ 'pos': 'botleft',
\ },
\ 'tests': both_wrap_tests + [
\ [ 'aaaa', 5, &columns, 4, &columns - 2, 3, 2 ],
\ [ 'bbbb', 5, &columns + 1, 4, &columns - 2, 3, 2 ],
\ [ 'cccc', 5, &columns - 1, 4, &columns - 2, 3, 2 ],
\ [ 'dddd', 5, &columns - 2, 4, &columns - 2, 3, 2 ],
\ [ 'eeee', 5, &columns - 3, 5, &columns - 3, 4, 1 ],
\ ],
\ },
\ {
\ 'comment': 'left aligned without wrapping',
\ 'options': {
\ 'wrap': 0,
\ 'pos': 'botleft',
\ },
\ 'tests': both_wrap_tests + [
\ [ 'aaaa', 5, &columns, 5, &columns - 3, 4, 1 ],
\ [ 'bbbb', 5, &columns + 1, 5, &columns - 3, 4, 1 ],
\ [ 'cccc', 5, &columns - 1, 5, &columns - 3, 4, 1 ],
\ [ 'dddd', 5, &columns - 2, 5, &columns - 3, 4, 1 ],
\ [ 'eeee', 5, &columns - 3, 5, &columns - 3, 4, 1 ],
\ ],
\ },
\ {
\ 'comment': 'left aligned with fixed position',
\ 'options': {
\ 'wrap': 0,
\ 'fixed': 1,
\ 'pos': 'botleft',
\ },
\ 'tests': both_wrap_tests + [
\ [ 'aaaa', 5, &columns, 5, &columns - 2, 3, 1 ],
\ [ 'bbbb', 5, &columns + 1, 5, &columns - 2, 3, 1 ],
\ [ 'cccc', 5, &columns - 1, 5, &columns - 2, 3, 1 ],
\ [ 'dddd', 5, &columns - 2, 5, &columns - 2, 3, 1 ],
\ [ 'eeee', 5, &columns - 3, 5, &columns - 3, 4, 1 ],
\ ],
\ },
\ ]
for test_group in tests
for test in test_group.tests
let [ text, line, col, e_line, e_col, e_width, e_height ] = test
let options = {
\ 'line': line,
\ 'col': col,
\ }
call extend( options, test_group.options )
let p = popup_create( text, options )
let msg = string( extend( options, { 'text': text } ) )
call s:VerifyPosition( p, msg, e_line, e_col, e_width, e_height )
call popup_close( p )
endfor
endfor
popupclear
%bwipe!
endfunc
function Test_adjust_left_past_screen_width()
" width of screen
let X = join(map(range(&columns), {->'X'}), '')
let p = popup_create( X, { 'line': 1, 'col': 1, 'wrap': 0 } )
call s:VerifyPosition( p, 'full width topleft', 1, 1, &columns, 1 )
redraw
let line = join(map(range(1, &columns + 1), 'screenstring(1, v:val)'), '')
call assert_equal(X, line)
call popup_close( p )
redraw
" Same if placed on the right hand side
let p = popup_create( X, { 'line': 1, 'col': &columns, 'wrap': 0 } )
call s:VerifyPosition( p, 'full width topright', 1, 1, &columns, 1 )
redraw
let line = join(map(range(1, &columns + 1), 'screenstring(1, v:val)'), '')
call assert_equal(X, line)
call popup_close( p )
redraw
" Extend so > window width
let X .= 'x'
let p = popup_create( X, { 'line': 1, 'col': 1, 'wrap': 0 } )
call s:VerifyPosition( p, 'full width + 1 topleft', 1, 1, &columns, 1 )
redraw
let line = join(map(range(1, &columns + 1), 'screenstring(1, v:val)'), '')
call assert_equal(X[ : -2 ], line)
call popup_close( p )
redraw
" Shifted then truncated (the x is not visible)
let p = popup_create( X, { 'line': 1, 'col': &columns - 3, 'wrap': 0 } )
call s:VerifyPosition( p, 'full width + 1 topright', 1, 1, &columns, 1 )
redraw
let line = join(map(range(1, &columns + 1), 'screenstring(1, v:val)'), '')
call assert_equal(X[ : -2 ], line)
call popup_close( p )
redraw
" Not shifted, just truncated
let p = popup_create( X,
\ { 'line': 1, 'col': 2, 'wrap': 0, 'fixed': 1 } )
call s:VerifyPosition( p, 'full width + 1 fixed', 1, 2, &columns - 1, 1)
redraw
let line = join(map(range(1, &columns + 1), 'screenstring(1, v:val)'), '')
let e_line = ' ' . X[ 1 : -2 ]
call assert_equal(e_line, line)
call popup_close( p )
redraw
popupclear
%bwipe!
endfunction

View File

@@ -767,6 +767,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1449,
/**/ /**/
1448, 1448,
/**/ /**/