-*insert.txt* For Vim version 9.1. Last change: 2025 Oct 12
+*insert.txt* For Vim version 9.1. Last change: 2025 Oct 14
VIM REFERENCE MANUAL by Bram Moolenaar
three lines, but 'previewheight' is used when it has a value of 1 or 2.
*complete-popup*
-When "popup" is in 'completeopt' a popup window is used to display the "info".
-Then the 'completepopup' option specifies the properties of the popup. This
-is used when the info popup is created. The option is a comma-separated list
-of values:
- height maximum height of the popup
- width maximum width of the popup
- highlight highlight group of the popup (default is PmenuSel)
- align "item" (default) or "menu"
- border "on" (default) or "off"
-Example: >
- :set completepopup=height:10,width:60,highlight:InfoPopup
-
-When `"align"` is set to `"item"`, the popup is positioned near the selected
-item, and moves as the selection changes.
-When set to `"menu"`, the popup aligns with the top of the menu (if the menu
-appears below the text), or with the bottom (if the menu appears above).
-
-If the 'mouse' is enabled, a close button and resize handle will appear on the
-popup border.
-
+When "popup" is included in 'completeopt', a popup window is used to display
+the completion "info" text. The appearance and behavior of this popup are
+controlled by the 'completepopup' option when the popup is created.
After creation, the info popup can be located with |popup_findinfo()| and
modified using |popup_setoptions()|.
-*options.txt* For Vim version 9.1. Last change: 2025 Oct 12
+*options.txt* For Vim version 9.1. Last change: 2025 Oct 14
VIM REFERENCE MANUAL by Bram Moolenaar
global
{not available when compiled without the |+textprop|
or |+quickfix| feature}
- When 'completeopt' contains "popup" then this option is used for the
+ When 'completeopt' contains "popup", this option specifies the
properties of the info popup when it is created. If an info popup
window already exists it is closed, so that the option value is
applied when it is created again.
- You can also use |popup_findinfo()| and then set properties for an
- existing info popup with |popup_setoptions()|. See |complete-popup|.
+
+ The option is a comma-separated list of values:
+ align "item" (default) or "menu"
+ border border style:
+ "single" thin box-drawing characters
+ "double" double-line
+ "round" rounded corners
+ "ascii" ASCII characters (-, |, +)
+ "custom:X;X;X;X;X;X;X;X"
+ eight characters separated by
+ semicolons, in the order: top,
+ right, bottom, left, topleft,
+ topright, botright, botleft
+ "on" same as "double" when
+ 'encoding' is "utf-8" and
+ 'ambiwidth' is "single", else
+ "ascii" (default)
+ "off" no border
+ borderhighlight highlight group for the popup border characters
+ close show close button: "on" (default) or "off"
+ height maximum height of the popup
+ highlight popup highlight group (default: PmenuSel)
+ resize show resize handle: "on" (default) or "off"
+ shadow "off" (default) or "on" using |hl-PmenuShadow|
+ width maximum width of the popup
+
+ Example: >
+ :set completepopup=height:10,border:single,highlight:InfoPopup
+ :set completepopup=width:60,border:custom:─;│;─;│;┌;┐;┘;└
+<
+ When "align" is set to "item", the popup is positioned near the
+ selected item and moves as the selection changes.
+ When set to "menu", the popup aligns with the top of the menu (if the
+ menu appears below the text) or with the bottom (if it appears above).
+
+ The close button and resize handle are shown on the popup border only
+ when 'mouse' is enabled.
+
+ After creation, the info popup can be located with |popup_findinfo()|
+ and modified using |popup_setoptions()|. See also: |complete-popup|.
*'completeslash'* *'csl'*
'completeslash' 'csl' string (default: "")
to ">" by default, indicating text that extends beyond the window width.
- 'guioptions': New value |'go-C'| to style the title/caption bar on Windows 11
(see also the below platform specific change).
+- 'completepopup': Add more values to style popup windows.
Ex commands: ~
- allow to specify a priority when defining a new sign |:sign-define|
SCTX_INIT},
{"completepopup", "cpp", P_STRING|P_VI_DEF|P_COMMA|P_NODUP|P_COLON,
#if defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX)
- (char_u *)&p_cpp, PV_NONE, did_set_completepopup, expand_set_popupoption,
+ (char_u *)&p_cpp, PV_NONE, did_set_completepopup, expand_set_completepopup,
{(char_u *)"", (char_u *)0L}
#else
(char_u *)NULL, PV_NONE, NULL, NULL,
{(char_u *)12L, (char_u *)0L} SCTX_INIT},
{"previewpopup", "pvp", P_STRING|P_VI_DEF|P_COMMA|P_NODUP|P_COLON,
#ifdef FEAT_PROP_POPUP
- (char_u *)&p_pvp, PV_NONE, did_set_previewpopup, expand_set_popupoption,
+ (char_u *)&p_pvp, PV_NONE, did_set_previewpopup, expand_set_previewpopup,
{(char_u *)"", (char_u *)0L}
#else
(char_u *)NULL, PV_NONE, NULL, NULL,
static char *(p_kpc_protocol_values[]) = {"none", "mok2", "kitty", NULL};
#ifdef FEAT_PROP_POPUP
// Note: Keep this in sync with parse_popup_option()
-static char *(p_popup_option_values[]) = { "align:", "border:", "height:",
- "highlight:", "shadow:", "width:", NULL};
-static char *(p_popup_option_border_values[]) = {"on", "off", NULL};
+static char *(p_popup_cpp_option_values[]) = {"align:", "border:",
+ "borderhighlight:", "close:", "height:", "highlight:", "resize:",
+ "shadow:", "width:", NULL};
+static char *(p_popup_pvp_option_values[]) = {"height:", "highlight:",
+ "width:", NULL};
+static char *(p_popup_option_on_off_values[]) = {"on", "off", NULL};
+static char *(p_popup_cpp_border_values[]) = {"single", "double", "round",
+ "ascii", "on", "off", "custom:", NULL};
static char *(p_popup_option_align_values[]) = {"item", "menu", NULL};
#endif
#if defined(FEAT_SPELL)
return NULL;
}
- int
-expand_set_popupoption(optexpand_T *args, int *numMatches, char_u ***matches)
+ static int
+expand_set_popupoption(optexpand_T *args, int *numMatches, char_u ***matches,
+ int previewpopup)
{
expand_T *xp = args->oe_xp;
{
// Within "highlight:"/"border:"/"align:", we have a subgroup of possible options.
int border_len = (int)STRLEN("border:");
- if (xp->xp_pattern - args->oe_set_arg >= border_len &&
- STRNCMP(xp->xp_pattern - border_len, "border:", border_len) == 0)
+ int close_len = (int)STRLEN("close:");
+ int resize_len = (int)STRLEN("resize:");
+ int shadow_len = (int)STRLEN("shadow:");
+ int is_border = xp->xp_pattern - args->oe_set_arg >= border_len &&
+ STRNCMP(xp->xp_pattern - border_len, "border:", border_len) == 0;
+ int is_close = xp->xp_pattern - args->oe_set_arg >= close_len &&
+ STRNCMP(xp->xp_pattern - close_len, "close:", close) == 0;
+ int is_resize = xp->xp_pattern - args->oe_set_arg >= resize_len &&
+ STRNCMP(xp->xp_pattern - resize_len, "resize:", resize_len) == 0;
+ int is_shadow = xp->xp_pattern - args->oe_set_arg >= shadow_len &&
+ STRNCMP(xp->xp_pattern - shadow_len, "shadow:", shadow_len) == 0;
+ if (is_close || is_resize || is_shadow)
+ {
+ return expand_set_opt_string(
+ args,
+ p_popup_option_on_off_values,
+ ARRAY_LENGTH(p_popup_option_on_off_values) - 1,
+ numMatches,
+ matches);
+ }
+ if (is_border)
{
return expand_set_opt_string(
args,
- p_popup_option_border_values,
- ARRAY_LENGTH(p_popup_option_border_values) - 1,
+ previewpopup ? p_popup_option_on_off_values
+ : p_popup_cpp_border_values,
+ (previewpopup ? ARRAY_LENGTH(p_popup_option_on_off_values)
+ : ARRAY_LENGTH(p_popup_cpp_border_values)) - 1,
numMatches,
matches);
}
matches);
}
int highlight_len = (int)STRLEN("highlight:");
- if (xp->xp_pattern - args->oe_set_arg >= highlight_len &&
- STRNCMP(xp->xp_pattern - highlight_len, "highlight:", highlight_len) == 0)
+ int borderhighlight_len = (int)STRLEN("borderhighlight:");
+ int is_highlight = xp->xp_pattern - args->oe_set_arg >= highlight_len
+ && STRNCMP(xp->xp_pattern - highlight_len, "highlight:",
+ highlight_len) == 0;
+ int is_borderhighlight
+ = xp->xp_pattern - args->oe_set_arg >= borderhighlight_len
+ && STRNCMP(xp->xp_pattern - borderhighlight_len, "highlight:",
+ borderhighlight_len) == 0;
+ if (is_highlight || is_borderhighlight)
{
// Return the list of all highlight names
return expand_set_opt_generic(
return expand_set_opt_string(
args,
- p_popup_option_values,
- ARRAY_LENGTH(p_popup_option_values) - 1,
+ previewpopup ? p_popup_pvp_option_values
+ : p_popup_cpp_option_values,
+ previewpopup ? ARRAY_LENGTH(p_popup_pvp_option_values) - 1
+ : ARRAY_LENGTH(p_popup_cpp_option_values) - 1,
numMatches,
matches);
}
+
+ int
+expand_set_previewpopup(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+ return expand_set_popupoption(args, numMatches, matches, TRUE);
+}
+
+ int
+expand_set_completepopup(optexpand_T *args, int *numMatches, char_u ***matches)
+{
+ return expand_set_popupoption(args, numMatches, matches, FALSE);
+}
#endif
#if defined(FEAT_POSTSCRIPT)
int center_hor = FALSE;
int allow_adjust_left = !wp->w_popup_fixed;
int top_extra = popup_top_extra(wp);
- int right_extra = wp->w_popup_border[1] + wp->w_popup_padding[1];
- int bot_extra = wp->w_popup_border[2] + wp->w_popup_padding[2];
+ int right_extra = wp->w_popup_border[1] + wp->w_popup_padding[1]
+ + (wp->w_popup_shadow ? 2 : 0);
+ int bot_extra = wp->w_popup_border[2] + wp->w_popup_padding[2]
+ + wp->w_popup_shadow;
int left_extra = wp->w_popup_border[3] + wp->w_popup_padding[3];
int extra_height = top_extra + bot_extra;
int extra_width = left_extra + right_extra;
curbuf = curwin->w_buffer;
}
+#define SET_BORDER_CHARS(a0, a1, a2, a3, a4, a5, a6, a7) \
+ do { \
+ if (wp != NULL) \
+ { \
+ wp->w_border_char[0] = (a0); \
+ wp->w_border_char[1] = (a1); \
+ wp->w_border_char[2] = (a2); \
+ wp->w_border_char[3] = (a3); \
+ wp->w_border_char[4] = (a4); \
+ wp->w_border_char[5] = (a5); \
+ wp->w_border_char[6] = (a6); \
+ wp->w_border_char[7] = (a7); \
+ } \
+ } while (0)
+
/*
* Parse the 'previewpopup' or 'completepopup' option and apply the values to
* window "wp" if it is not NULL.
!is_preview ? p_cpp :
#endif
p_pvp;
+ int border_enabled = FALSE;
if (wp != NULL)
wp->w_popup_flags &= ~POPF_INFO_MENU;
*p = NUL;
set_string_option_direct_in_win(wp, (char_u *)"wincolor", -1,
- s + 10, OPT_FREE|OPT_LOCAL, 0);
+ s + 10, OPT_FREE|OPT_LOCAL, 0);
*p = c;
}
}
+ else if (STRNCMP(s, "borderhighlight:", 16) == 0)
+ {
+ char_u *arg = s + 16;
+
+ if (*arg == NUL || *arg == ',')
+ return FAIL;
+ if (wp != NULL)
+ {
+ for (int i = 0; i < 4; ++i)
+ {
+ VIM_CLEAR(wp->w_border_highlight[i]);
+ wp->w_border_highlight[i] = vim_strnsave(arg, p - arg);
+ }
+ }
+ }
else if (STRNCMP(s, "border:", 7) == 0)
{
- // Note: Keep this in sync with p_popup_option_border_values.
char_u *arg = s + 7;
- int on = STRNCMP(arg, "on", 2) == 0 && arg + 2 == p;
- int off = STRNCMP(arg, "off", 3) == 0 && arg + 3 == p;
int i;
+ int token_len = p - arg;
+ char_u *token;
+ // Use box-drawing characters only when 'encoding' is "utf-8" and
+ // 'ambiwidth' is "single".
+ int can_use_box_chars = (enc_utf8 && *p_ambw == 's');
+
+ if (token_len == 0
+ || (STRNCMP(arg, "off", 3) == 0 && arg + 3 == p))
+ {
+ if (wp != NULL)
+ {
+ for (i = 0; i < 4; ++i)
+ wp->w_popup_border[i] = 0;
+ SET_BORDER_CHARS(0, 0, 0, 0, 0, 0, 0, 0);
+ // only show the X for close when there is a border
+ wp->w_popup_close = POPCLOSE_NONE;
+ }
+ continue;
+ }
+
+ token = vim_strnsave(arg, token_len);
+ if (token == NULL)
+ return FAIL;
+
+ if ((can_use_box_chars && (STRCMP(token, "single") == 0
+ || STRCMP(token, "double") == 0
+ || STRCMP(token, "on") == 0
+ || STRCMP(token, "round") == 0))
+ || STRCMP(token, "ascii") == 0
+ || (STRNCMP(token, "custom:", 7) == 0))
+ {
+ if (STRCMP(token, "single") == 0)
+ SET_BORDER_CHARS(0x2500, 0x2502, 0x2500, 0x2502, // ─ │ ─ │
+ 0x250c, 0x2510, 0x2518, 0x2514); // ┌ ┐ ┘ └
+ else if (STRCMP(token, "double") == 0)
+ SET_BORDER_CHARS(0x2550, 0x2551, 0x2550, 0x2551, // ═ ║ ═ ║
+ 0x2554, 0x2557, 0x255D, 0x255A); // ╔ ╗ ╝ ╚
+ else if (STRCMP(token, "round") == 0)
+ SET_BORDER_CHARS(0x2500, 0x2502, 0x2500, 0x2502, // ─ │ ─ │
+ 0x256d, 0x256e, 0x256f, 0x2570); // ╭ ╮ ╯ ╰
+ else if (STRCMP(token, "on") == 0)
+ SET_BORDER_CHARS(0, 0, 0, 0, 0, 0, 0, 0);
+ else if (STRCMP(token, "ascii") == 0)
+ SET_BORDER_CHARS('-', '|', '-', '|', '+', '+', '+', '+');
+ else if (STRNCMP(token, "custom:", 7) == 0)
+ {
+ char_u *q = token + 7;
+ int out[8];
+ int failed = FALSE;
+
+ SET_BORDER_CHARS(0, 0, 0, 0, 0, 0, 0, 0);
- if (!on && !off)
+ for (i = 0; i < 8 && !failed; i++)
+ {
+ if (*q == NUL)
+ failed = TRUE;
+ else
+ {
+ out[i] = mb_ptr2char(q);
+ mb_ptr2char_adv(&q);
+ if (i < 7)
+ {
+ if (*q != ';')
+ failed = TRUE; // must be semicolon
+ q++;
+ }
+ }
+ }
+ if (failed || *q != NUL) // must end exactly after the 8th char
+ {
+ vim_free(token);
+ return FAIL;
+ }
+ SET_BORDER_CHARS(out[0], out[1], out[2], out[3], out[4],
+ out[5], out[6], out[7]);
+ }
+ }
+ else
+ {
+ vim_free(token);
return FAIL;
+ }
+
if (wp != NULL)
{
for (i = 0; i < 4; ++i)
- wp->w_popup_border[i] = on ? 1 : 0;
- if (off)
- // only show the X for close when there is a border
- wp->w_popup_close = POPCLOSE_NONE;
+ wp->w_popup_border[i] = 1;
+ }
+ border_enabled = TRUE;
+
+ vim_free(token);
+ }
+ else if (STRNCMP(s, "close:", 6) == 0)
+ {
+ char_u *arg = s + 6;
+ int on = STRNCMP(arg, "on", 2) == 0 && arg + 2 == p;
+ int off = STRNCMP(arg, "off", 3) == 0 && arg + 3 == p;
+
+ if ((!on && !off) || is_preview)
+ return FAIL;
+ on = on && mouse_has(MOUSE_INSERT) && border_enabled;
+ if (wp != NULL)
+ wp->w_popup_close = on ? POPCLOSE_BUTTON : POPCLOSE_NONE;
+ }
+ else if (STRNCMP(s, "resize:", 7) == 0)
+ {
+ char_u *arg = s + 7;
+ int on = STRNCMP(arg, "on", 2) == 0 && arg + 2 == p;
+ int off = STRNCMP(arg, "off", 3) == 0 && arg + 3 == p;
+
+ if ((!on && !off) || is_preview)
+ return FAIL;
+ if (wp != NULL)
+ {
+ if (on && mouse_has(MOUSE_INSERT))
+ wp->w_popup_flags |= POPF_RESIZE;
+ else
+ wp->w_popup_flags &= ~POPF_RESIZE;
}
}
+ else if (STRNCMP(s, "shadow:", 7) == 0)
+ {
+ char_u *arg = s + 7;
+ int on = STRNCMP(arg, "on", 2) == 0 && arg + 2 == p;
+ int off = STRNCMP(arg, "off", 3) == 0 && arg + 3 == p;
+
+ if ((!on && !off) || is_preview)
+ return FAIL;
+ if (wp != NULL)
+ wp->w_popup_shadow = on ? 1 : 0;
+ }
else if (STRNCMP(s, "align:", 6) == 0)
{
// Note: Keep this in sync with p_popup_option_align_values.
parse_previewpopup(wp);
popup_set_wantpos_cursor(wp, wp->w_minwidth, d);
}
+
+ for (i = 0; i < 4; ++i)
+ VIM_CLEAR(wp->w_border_highlight[i]);
+ for (i = 0; i < 8; ++i)
+ wp->w_border_char[i] = 0;
+
# ifdef FEAT_QUICKFIX
if (type == TYPE_INFO)
{
}
# endif
- for (i = 0; i < 4; ++i)
- VIM_CLEAR(wp->w_border_highlight[i]);
- for (i = 0; i < 8; ++i)
- wp->w_border_char[i] = 0;
wp->w_want_scrollbar = 1;
wp->w_popup_fixed = 0;
wp->w_filter_mode = MODE_ALL;
else
wp->w_popup_flags &= ~POPF_ON_CMDLINE;
-
// We can only use these line drawing characters when 'encoding' is
// "utf-8" and 'ambiwidth' is "single".
if (enc_utf8 && *p_ambw == 's')
popup_attr);
}
+ // right shadow
+ if (wp->w_popup_shadow)
+ {
+ int col = wincol + total_width;
+ for (i = 0; i < total_height; ++i)
+ {
+ row = wp->w_winrow + i + 1;
+ put_shadow_char(row, col);
+ put_shadow_char(row, col + 1);
+ }
+ }
+
if (wp->w_popup_padding[2] > 0)
{
// bottom padding
{
// bottom border
row = wp->w_winrow + total_height - 1;
- screen_fill(row , row + 1,
+ screen_fill(row, row + 1,
wincol < 0 ? 0 : wincol,
wincol + total_width,
wp->w_popup_border[3] != 0 && wp->w_popup_leftoff == 0
}
}
+ if (wp->w_popup_shadow)
+ {
+ // bottom shadow
+ row = wp->w_winrow + total_height;
+ for (int col = 2 + (wincol < 0 ? 0 : wincol);
+ col < wincol + total_width; col++)
+ put_shadow_char(row, col);
+ }
+
if (wp->w_popup_close == POPCLOSE_BUTTON)
{
// close button goes on top of anything at the top-right corner
char *did_set_optexpr(optset_T *args);
char *did_set_pastetoggle(optset_T *args);
char *did_set_previewpopup(optset_T *args);
-int expand_set_popupoption(optexpand_T *args, int *numMatches, char_u ***matches);
+int expand_set_previewpopup(optexpand_T *args, int *numMatches, char_u ***matches);
+int expand_set_completepopup(optexpand_T *args, int *numMatches, char_u ***matches);
char *did_set_printencoding(optset_T *args);
int expand_set_printoptions(optexpand_T *args, int *numMatches, char_u ***matches);
char *did_set_renderoptions(optset_T *args);
int w_popup_border[4]; // popup border top/right/bot/left
char_u *w_border_highlight[4]; // popup border highlight
int w_border_char[8]; // popup border characters
+ int w_popup_shadow; // popup shadow (right and bottom edges)
int w_popup_leftoff; // columns left of the screen
int w_popup_rightoff; // columns right of the screen
--- /dev/null
+|a+0&#ffffff0|w|o|r|d| @69
+|a|w|o|r|d> @69
+|w+0#0000001#e0e0e08|r|d| @4|W| |e|x|t|r|a| |t|e|x|t| | +0#0000000#0000001| +0#0000001#e0e0e08|w|o|r|d|s| |a|r|e| |c|o@1|l| | +0#4040ff13#ffffff0@36
+|a+0#0000001#ffd7ff255|n|o|t|w|r|d| |W| |e|x|t|r|a| |t|e|x|t| | +0#0000000#0000001| +0#4040ff13#ffffff0@1| +0#6c6c6c255#0000001@15| +0#4040ff13#ffffff0@34
+|n+0#0000001#ffd7ff255|o|a|w|r|d| @1|W| |e|x|t|r|a| |t|e|x|t| | +0#0000000#a8a8a8255| +0#4040ff13#ffffff0@52
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|~| @73
+|-+2#0000000&@1| |U|s|e|r| |d|e|f|i|n|e|d| |c|o|m|p|l|e|t|i|o|n| |(|^|U|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |4| +0#0000000&@26
set keyprotocol&
" previewpopup / completepopup
- call assert_equal('align:', getcompletion('set previewpopup=', 'cmdline')[0])
+ call assert_equal('height:', getcompletion('set previewpopup=', 'cmdline')[0])
call assert_equal('EndOfBuffer', getcompletion('set previewpopup=highlight:End*Buffer', 'cmdline')[0])
call feedkeys(":set previewpopup+=border:\<Tab>\<C-B>\"\<CR>", 'xt')
call assert_equal('"set previewpopup+=border:on', @:)
call term_sendkeys(buf, "a\<C-X>\<C-U>")
call VerifyScreenDump(buf, 'Test_popupwin_infopopup_8', {})
+ " Test shadow
+ call term_sendkeys(buf, "\<Esc>")
+ call term_sendkeys(buf, ":set completepopup=border:off,shadow:on\<CR>")
+ call term_sendkeys(buf, "Sa\<C-X>\<C-U>")
+ call VerifyScreenDump(buf, 'Test_popupwin_infopopup_9', {})
+
call term_sendkeys(buf, "\<Esc>")
call StopVimInTerminal(buf)
endfunc
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 1856,
/**/
1855,
/**/