diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index cf064e17fd..37ebfff481 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -4583,7 +4583,8 @@ A jump table for the options with a short description can be found at |Q_op|. A:DiffAdd,C:DiffChange,D:DiffDelete, T:DiffText,E:DiffTextAdd,>:SignColumn, -:Conceal,B:SpellBad,P:SpellCap, - R:SpellRare, L:SpellLocal,+:Pmenu, + R:SpellRare, L:SpellLocal, + +:Pmenu,I:PmenuBorder, =:PmenuSel, k:PmenuMatch,<:PmenuMatchSel, [:PmenuKind,]:PmenuKindSel, {:PmenuExtra,}:PmenuExtraSel, @@ -6674,6 +6675,29 @@ A jump table for the options with a short description can be found at |Q_op|. global When on a ":" prompt is used in Ex mode. + *'pumborder'* *'pbr'* +'pumborder' 'pbr' string (default "") + global + When non-empty, specifies the border style used for the + |ins-completion-menu| during completion. This affects how the border + around the popup menu is drawn. + Possible values are: + "single" a single-line border + "double" a double-line border + "rounded" a rounded border using box-drawing characters + "solid" a solid block-style border + "shadow" a shadow-like border style + + Alternatively, a custom border style can be defined by specifying + a comma-separated list of eight border characters. The expected + order is as follows: + Top-left corner, Top border, Top-right corner, Right border + Bot-right corner, Bot border, Bot-left corner, Left border + + Example: > + :set pumborder=rounded + :set pumborder=+,-,+,\|,+,-,+,\| +< *'pumheight'* *'ph'* 'pumheight' 'ph' number (default 0) global diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 44fed277ff..abdeb2af1e 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -5915,6 +5915,8 @@ NonText '@' at the end of the window, "<<<" at the start of the window Normal Normal text. *hl-Pmenu* Pmenu Popup menu: Normal item. + *hl-PmenuBorder* +PmenuBorder Popup menu: Border character. *hl-PmenuSel* PmenuSel Popup menu: Selected item. *hl-PmenuKind* diff --git a/runtime/doc/tags b/runtime/doc/tags index 6b363dbaa9..fe36d05420 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -841,6 +841,7 @@ $quote eval.txt /*$quote* 'patchexpr' options.txt /*'patchexpr'* 'patchmode' options.txt /*'patchmode'* 'path' options.txt /*'path'* +'pbr' options.txt /*'pbr'* 'pdev' options.txt /*'pdev'* 'penc' options.txt /*'penc'* 'perldll' options.txt /*'perldll'* @@ -870,6 +871,7 @@ $quote eval.txt /*$quote* 'printoptions' options.txt /*'printoptions'* 'prompt' options.txt /*'prompt'* 'pt' options.txt /*'pt'* +'pumborder' options.txt /*'pumborder'* 'pumheight' options.txt /*'pumheight'* 'pummaxwidth' options.txt /*'pummaxwidth'* 'pumwidth' options.txt /*'pumwidth'* @@ -8280,6 +8282,7 @@ hl-MsgArea syntax.txt /*hl-MsgArea* hl-NonText syntax.txt /*hl-NonText* hl-Normal syntax.txt /*hl-Normal* hl-Pmenu syntax.txt /*hl-Pmenu* +hl-PmenuBorder syntax.txt /*hl-PmenuBorder* hl-PmenuExtra syntax.txt /*hl-PmenuExtra* hl-PmenuExtraSel syntax.txt /*hl-PmenuExtraSel* hl-PmenuKind syntax.txt /*hl-PmenuKind* diff --git a/src/highlight.c b/src/highlight.c index ec3d11568c..2121d12a30 100644 --- a/src/highlight.c +++ b/src/highlight.c @@ -260,6 +260,7 @@ static char *(highlight_init_both[]) = { "default link CursorLineSign SignColumn", "default link CursorLineFold FoldColumn", "default link CurSearch Search", + "default link PmenuBorder Pmenu", "default link PmenuKind Pmenu", "default link PmenuKindSel PmenuSel", "default link PmenuMatch Pmenu", diff --git a/src/option.h b/src/option.h index 5590e5635b..295400afde 100644 --- a/src/option.h +++ b/src/option.h @@ -521,6 +521,7 @@ EXTERN unsigned cfc_flags; // flags from "completefuzzycollect" EXTERN char_u *p_cia; // 'completeitemalign' EXTERN unsigned cia_flags; // order flags of 'completeitemalign' EXTERN char_u *p_cot; // 'completeopt' +EXTERN char_u *p_pbr; // 'pumborder' EXTERN unsigned cot_flags; // flags from 'completeopt' // Keep in sync with p_cot_values in optionstr.c #define COT_MENU 0x001 diff --git a/src/optiondefs.h b/src/optiondefs.h index 3074181831..7dde2f312a 100644 --- a/src/optiondefs.h +++ b/src/optiondefs.h @@ -2058,6 +2058,9 @@ static struct vimoption options[] = {"prompt", NULL, P_BOOL|P_VI_DEF, (char_u *)&p_prompt, PV_NONE, NULL, NULL, {(char_u *)TRUE, (char_u *)0L} SCTX_INIT}, + {"pumborder", "pbr", P_STRING|P_VI_DEF, + (char_u *)&p_pbr, PV_NONE, did_set_pumborder, NULL, + {(char_u *)"", (char_u *)0L} SCTX_INIT}, {"pumheight", "ph", P_NUM|P_VI_DEF, (char_u *)&p_ph, PV_NONE, NULL, NULL, {(char_u *)0L, (char_u *)0L} SCTX_INIT}, diff --git a/src/optionstr.c b/src/optionstr.c index dcbb30b837..c475abad60 100644 --- a/src/optionstr.c +++ b/src/optionstr.c @@ -3338,6 +3338,17 @@ did_set_optexpr(optset_T *args) } #endif +/* + * The 'pumborder' option is changed + */ + char * +did_set_pumborder(optset_T *args UNUSED) +{ + if (!pum_parse_border()) + return e_invalid_argument; + return NULL; +} + /* * The 'pastetoggle' option is changed. */ diff --git a/src/popupmenu.c b/src/popupmenu.c index eecddc5be6..12aebb03e9 100644 --- a/src/popupmenu.c +++ b/src/popupmenu.c @@ -39,6 +39,8 @@ static int pum_win_height; static int pum_win_col; static int pum_win_wcol; static int pum_win_width; +static int pum_border_chars[8]; +static int pum_has_border = FALSE; // Some parts are not updated when a popup menu is visible. Setting this flag // makes pum_visible() return FALSE even when there is a popup menu. @@ -104,6 +106,7 @@ pum_display( int content_width; int right_edge_col; int redo_count = 0; + int border_cells = pum_has_border ? 2 : 0; #if defined(FEAT_QUICKFIX) win_T *pvwin; #endif @@ -161,7 +164,7 @@ pum_display( // Put the pum below "pum_win_row" if possible. If there are few lines // decide on where there is more room. - if (pum_win_row + 2 >= below_row - pum_height + if (pum_win_row + 2 + border_cells >= below_row - pum_height && pum_win_row - above_row > (below_row - above_row) / 2) { // pum above "pum_win_row" @@ -187,6 +190,14 @@ pum_display( pum_row += pum_height - p_ph; pum_height = p_ph; } + + if (pum_has_border && border_cells + pum_row + pum_height > pum_win_row) + { + if (pum_row < 2) + pum_height -= border_cells; + else + pum_row -= border_cells; + } } else { @@ -207,6 +218,10 @@ pum_display( pum_height = MIN(below_row - pum_row, size); if (p_ph > 0 && pum_height > p_ph) pum_height = p_ph; + + if ((State == MODE_CMDLINE) + && pum_row + pum_height + border_cells >= cmdline_row) + pum_height -= border_cells; } // don't display when we only have room for one line @@ -375,6 +390,9 @@ pum_display( pum_width = max_width - pum_scrollbar; } + if (pum_col + border_cells + pum_width > Columns) + pum_col -= border_cells; + // Set selected item and redraw. If the window size changed need to // redo the positioning. Limit this to two times, when there is not // much room the window size will keep changing. @@ -872,6 +890,148 @@ pum_draw_scrollbar( screen_putchar(' ', row, pum_col + pum_width, attr); } + int +pum_parse_border(void) +{ + int i; + char_u *p = p_pbr; + char_u *next = NULL; + int this_char; + int comb[MAX_MCO]; + char_u buf[10]; + int len; + int tmp[8]; + + struct { + char_u *name; + int c[8]; + } defaults[] = { + { (char_u *)"double", { 0x2554, 0x2550, 0x2557, 0x2551, + 0x255D, 0x2550, 0x255A, 0x2551 } }, + { (char_u *)"single", { 0x250C, 0x2500, 0x2510, 0x2502, + 0x2518, 0x2500, 0x2514, 0x2502 } }, + { (char_u *)"rounded", { 0x256D, 0x2500, 0x256E, 0x2502, + 0x256F, 0x2500, 0x2570, 0x2502 } }, + { (char_u *)"bold", { 0x250F, 0x2501, 0x2513, 0x2503, + 0x251B, 0x2501, 0x2517, 0x2503 } }, + { (char_u *)"solid", { 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20 } }, + }; + + if (*p_pbr == NUL) + { + pum_has_border = FALSE; + return TRUE; + } + + for (i = 0; i < 5; i++) + { + if (STRCMP(p_pbr, defaults[i].name) == 0) + { + memcpy(pum_border_chars, defaults[i].c, 8 * sizeof(int)); + pum_has_border = TRUE; + return TRUE; + } + } + + for (i = 0; i < 8; i++) + { + next = vim_strchr(p, ','); + if (next) + { + len = (int)(next - p); + if (len > 9) + len = 9; + vim_strncpy(buf, p, len); + buf[len] = NUL; + p = next + 1; + } + else + { + vim_strncpy(buf, p, 9); + buf[9] = NUL; + p += STRLEN(buf); + } + this_char = utfc_ptr2char(buf, comb); + if (!vim_isprintc(this_char)) + { + pum_has_border = FALSE; + return FALSE; + } + tmp[i] = this_char; + } + + if (*p != NUL) + { + pum_has_border = FALSE; + return FALSE; + } + + memcpy(pum_border_chars, tmp, 8 * sizeof(int)); + pum_has_border = TRUE; + return TRUE; +} + + static void +pum_draw_border(int *border_char, int thumb_pos, int thumb_height) +{ + int i; + int row = pum_row; + int col = pum_col; + int attr = syn_name2attr((char_u *)"PmenuBorder"); + int width = pum_width - (State & MODE_CMDLINE ? 1 : 0); + int height = pum_height; + +#ifdef FEAT_RIGHTLEFT + if (pum_rl) + { + // topright leftright botright botleft + screen_putchar(border_char[2], row, col + 1, attr); + screen_putchar(border_char[0], row, col - width, attr); + screen_putchar(border_char[4], row + height + 1, col + 1, attr); + screen_putchar(border_char[6], row + height + 1, col - width, attr); + + // top bot + for (i = 0; i < width; i++) + { + screen_putchar(border_char[1], row, col - i, attr); + screen_putchar(border_char[5], row + height + 1, col - i, attr); + } + + // left right + for (i = 1; i < height + 1; i++) + { + screen_putchar(border_char[3], row + i, col + 1, attr); + if (!pum_scrollbar || i < thumb_pos || i >= thumb_pos + thumb_height) + screen_putchar(border_char[7], row + i, col - width, attr); + } + } + else +#endif + { + // topleft topright botleft botright + screen_putchar(border_char[0], row, col, attr); + screen_putchar(border_char[2], row, col + width, attr); + screen_putchar(border_char[6], row + height + 1, col, attr); + screen_putchar(border_char[4], row + height + 1, col + width, attr); + + // top bot + for (i = 1; i < width; i++) + { + screen_putchar(border_char[1], row, col + i, attr); + screen_putchar(border_char[5], row + height + 1, col + i, attr); + } + + // left right + for (i = 1; i < height + 1; i++) + { + screen_putchar(border_char[7], row + i, col, attr); + if (!pum_scrollbar || i - 1 < thumb_pos || i - 1 >= thumb_pos + thumb_height) + screen_putchar(border_char[3], row + i, col + width, attr); + } + } +} + /* * Redraw the popup menu, using "pum_first" and "pum_selected". */ @@ -898,7 +1058,9 @@ pum_redraw(void) int basic_width; // first item width int last_isabbr = FALSE; int orig_attr = -1; - int scroll_range = pum_size - pum_height; + int border_cells = pum_has_border ? 2 : 0; + int scroll_range = pum_size - pum_height + border_cells; + int col_off = pum_has_border > 0 ? 1 : 0; hlf_T hlfsNorm[3]; hlf_T hlfsSel[3]; @@ -940,6 +1102,9 @@ pum_redraw(void) screen_zindex = POPUPMENU_ZINDEX; #endif + if (pum_has_border > 0) + ++row; + for (i = 0; i < pum_height; ++i) { idx = i + pum_first; @@ -947,21 +1112,31 @@ pum_redraw(void) hlf = hlfs[0]; // start with "word" highlight attr = highlight_attr[hlf]; - // prepend a space if there is room -#ifdef FEAT_RIGHTLEFT - if (pum_rl) + if (!pum_has_border) { - if (pum_col < curwin->w_wincol + curwin->w_width - 1) - screen_putchar(' ', row, pum_col + 1, attr); - } - else + // prepend a space if there is room +#ifdef FEAT_RIGHTLEFT + if (pum_rl) + { + if (pum_col < curwin->w_wincol + curwin->w_width - 1) + screen_putchar(' ', row, pum_col + 1, attr); + } + else #endif - if (pum_col > 0) - screen_putchar(' ', row, pum_col - 1, attr); + if (pum_col > 0) + screen_putchar(' ', row, pum_col - 1, attr); + } // Display each entry, use two spaces for a Tab. // Do this 3 times and order from p_cia col = pum_col; + if (pum_has_border > 0) + { +#ifdef FEAT_RIGHTLEFT + if (!pum_rl) +#endif + ++col; + } totwidth = 0; pum_align_order(order); basic_width = items_width_array[order[0]]; @@ -1005,9 +1180,9 @@ pum_redraw(void) else #endif { - screen_fill(row, row + 1, col, pum_col + basic_width + n, + screen_fill(row, row + 1, col, pum_col + basic_width + n + col_off, ' ', ' ', orig_attr); - col = pum_col + basic_width + n; + col = pum_col + basic_width + n + col_off; } totwidth = basic_width + n; } @@ -1021,10 +1196,12 @@ pum_redraw(void) screen_fill(row, row + 1, col, pum_col + pum_width, ' ', ' ', orig_attr); pum_draw_scrollbar(row, i, thumb_pos, thumb_height); - ++row; } + if (pum_has_border && pum_window != NULL) + pum_draw_border(pum_border_chars, thumb_pos, thumb_height); + #ifdef FEAT_PROP_POPUP screen_zindex = 0; #endif diff --git a/src/proto/optionstr.pro b/src/proto/optionstr.pro index 2d5e17a5c5..da949384f6 100644 --- a/src/proto/optionstr.pro +++ b/src/proto/optionstr.pro @@ -208,4 +208,5 @@ int check_ff_value(char_u *p); void save_clear_shm_value(void); void restore_shm_value(void); void export_myvimdir(void); +char *did_set_pumborder(optset_T *args); /* vim: set ft=c : */ diff --git a/src/proto/popupmenu.pro b/src/proto/popupmenu.pro index 53ef843c18..767b43dfa8 100644 --- a/src/proto/popupmenu.pro +++ b/src/proto/popupmenu.pro @@ -17,4 +17,5 @@ void ui_post_balloon(char_u *mesg, list_T *list); void ui_may_remove_balloon(void); void pum_show_popupmenu(vimmenu_T *menu); void pum_make_popup(char_u *path_name, int use_mouse_pos); +int pum_parse_border(void); /* vim: set ft=c : */ diff --git a/src/testdir/gen_opt_test.vim b/src/testdir/gen_opt_test.vim index 78a783e508..c8ec7dd5e1 100644 --- a/src/testdir/gen_opt_test.vim +++ b/src/testdir/gen_opt_test.vim @@ -275,6 +275,8 @@ let test_values = { \ ['xxx', 'xxx,c:yes', 'xxx:', 'xxx:,c:yes']], \ 'printoptions': [['', 'header:0', 'left:10pc,top:5pc'], \ ['xxx', 'header:-1']], + \ 'pumborder': [['', 'single', 'rounded', '+,-,+,\|,+,-,+,\|'], + \ ['xxx', '+,-,+,\|,+,-,+,']], \ 'scrollopt': [['', 'ver', 'hor', 'jump', 'ver,hor'], ['xxx']], \ 'renderoptions': [[''], ['xxx']], \ 'rightleftcmd': [['search'], ['xxx']],