feat(ui): 'pumborder' (popup menu border) #25541

Problem:
Popup menu cannot have a border.

Solution:
Support 'pumborder' option.
Generalize `win_redr_border` to `grid_redr_border`,
which redraws border for window grid and pum grid.
This commit is contained in:
glepnir
2025-10-10 22:14:50 +08:00
committed by GitHub
parent 9c89212de1
commit fafc329bbd
17 changed files with 649 additions and 133 deletions

View File

@@ -4908,6 +4908,12 @@ A jump table for the options with a short description can be found at |Q_op|.
<
UI-dependent. Works best with RGB colors. 'termguicolors'
*'pumborder'*
'pumborder' string (default "")
global
Defines the default border style of popupmenu windows. Same as
'winborder'.
*'pumheight'* *'ph'*
'pumheight' 'ph' number (default 0)
global

View File

@@ -5129,6 +5129,13 @@ vim.o.pb = vim.o.pumblend
vim.go.pumblend = vim.o.pumblend
vim.go.pb = vim.go.pumblend
--- Defines the default border style of popupmenu windows. Same as
--- 'winborder'.
---
--- @type string
vim.o.pumborder = ""
vim.go.pumborder = vim.o.pumborder
--- Maximum number of items to show in the popup menu
--- (`ins-completion-menu`). Zero means "use available screen space".
---

View File

@@ -961,7 +961,7 @@ static bool parse_bordertext_pos(win_T *wp, String bordertext_pos, BorderTextTyp
return true;
}
static void parse_border_style(Object style, WinConfig *fconfig, Error *err)
void parse_border_style(Object style, WinConfig *fconfig, Error *err)
{
struct {
const char *name;
@@ -1079,14 +1079,14 @@ static void generate_api_error(win_T *wp, const char *attribute, Error *err)
}
/// Parses a border style name or custom (comma-separated) style.
bool parse_winborder(WinConfig *fconfig, Error *err)
bool parse_winborder(WinConfig *fconfig, const char *border_opt, Error *err)
{
if (!fconfig) {
return false;
}
Object style = OBJECT_INIT;
if (strchr(p_winborder, ',')) {
if (strchr(border_opt, ',')) {
Array border_chars = ARRAY_DICT_INIT;
char *p = p_winborder;
char part[MAX_SCHAR_SIZE] = { 0 };
@@ -1364,7 +1364,7 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
}
}
} else if (*p_winborder != NUL && (wp == NULL || !wp->w_floating)
&& !parse_winborder(fconfig, err)) {
&& !parse_winborder(fconfig, p_winborder, err)) {
goto fail;
}

View File

@@ -111,6 +111,7 @@
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/syntax_defs.h"
#include "nvim/terminal.h"
#include "nvim/types_defs.h"
#include "nvim/ui.h"
@@ -657,7 +658,8 @@ int update_screen(void)
win_grid_alloc(wp);
if (wp->w_redr_border || wp->w_redr_type >= UPD_NOT_VALID) {
win_redr_border(wp);
grid_draw_border(&wp->w_grid_alloc, wp->w_config, wp->w_border_adj, (int)wp->w_p_winbl,
wp->w_ns_hl_attr);
}
if (wp->w_redr_type != 0) {
@@ -743,112 +745,6 @@ void end_search_hl(void)
screen_search_hl.rm.regprog = NULL;
}
static void win_redr_bordertext(win_T *wp, VirtText vt, int col, BorderTextType bt)
{
for (size_t i = 0; i < kv_size(vt);) {
int attr = -1;
char *text = next_virt_text_chunk(vt, &i, &attr);
if (text == NULL) {
break;
}
if (attr == -1) { // No highlight specified.
attr = wp->w_ns_hl_attr[bt == kBorderTextTitle ? HLF_BTITLE : HLF_BFOOTER];
}
attr = hl_apply_winblend(wp, attr);
col += grid_line_puts(col, text, -1, attr);
}
}
int win_get_bordertext_col(int total_col, int text_width, AlignTextPos align)
{
switch (align) {
case kAlignLeft:
return 1;
case kAlignCenter:
return MAX((total_col - text_width) / 2 + 1, 1);
case kAlignRight:
return MAX(total_col - text_width + 1, 1);
}
UNREACHABLE;
}
static void win_redr_border(win_T *wp)
{
wp->w_redr_border = false;
if (!(wp->w_floating && wp->w_config.border)) {
return;
}
ScreenGrid *grid = &wp->w_grid_alloc;
schar_T chars[8];
for (int i = 0; i < 8; i++) {
chars[i] = schar_from_str(wp->w_config.border_chars[i]);
}
int *attrs = wp->w_config.border_attr;
int *adj = wp->w_border_adj;
int irow = wp->w_view_height + wp->w_winbar_height;
int icol = wp->w_view_width;
if (adj[0]) {
screengrid_line_start(grid, 0, 0);
if (adj[3]) {
grid_line_put_schar(0, chars[0], attrs[0]);
}
for (int i = 0; i < icol; i++) {
grid_line_put_schar(i + adj[3], chars[1], attrs[1]);
}
if (wp->w_config.title) {
int title_col = win_get_bordertext_col(icol, wp->w_config.title_width,
wp->w_config.title_pos);
win_redr_bordertext(wp, wp->w_config.title_chunks, title_col, kBorderTextTitle);
}
if (adj[1]) {
grid_line_put_schar(icol + adj[3], chars[2], attrs[2]);
}
grid_line_flush();
}
for (int i = 0; i < irow; i++) {
if (adj[3]) {
screengrid_line_start(grid, i + adj[0], 0);
grid_line_put_schar(0, chars[7], attrs[7]);
grid_line_flush();
}
if (adj[1]) {
int ic = (i == 0 && !adj[0] && chars[2]) ? 2 : 3;
screengrid_line_start(grid, i + adj[0], 0);
grid_line_put_schar(icol + adj[3], chars[ic], attrs[ic]);
grid_line_flush();
}
}
if (adj[2]) {
screengrid_line_start(grid, irow + adj[0], 0);
if (adj[3]) {
grid_line_put_schar(0, chars[6], attrs[6]);
}
for (int i = 0; i < icol; i++) {
int ic = (i == 0 && !adj[3] && chars[6]) ? 6 : 5;
grid_line_put_schar(i + adj[3], chars[ic], attrs[ic]);
}
if (wp->w_config.footer) {
int footer_col = win_get_bordertext_col(icol, wp->w_config.footer_width,
wp->w_config.footer_pos);
win_redr_bordertext(wp, wp->w_config.footer_chunks, footer_col, kBorderTextFooter);
}
if (adj[1]) {
grid_line_put_schar(icol + adj[3], chars[4], attrs[4]);
}
grid_line_flush();
}
}
/// Set cursor to its position in the current window.
void setcursor(void)
{

View File

@@ -1084,6 +1084,112 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
}
}
static void grid_draw_bordertext(VirtText vt, int col, int winbl, const int *hl_attr,
BorderTextType bt)
{
for (size_t i = 0; i < kv_size(vt);) {
int attr = -1;
char *text = next_virt_text_chunk(vt, &i, &attr);
if (text == NULL) {
break;
}
if (attr == -1) { // No highlight specified.
attr = hl_attr[bt == kBorderTextTitle ? HLF_BTITLE : HLF_BFOOTER];
}
attr = hl_apply_winblend(winbl, attr);
col += grid_line_puts(col, text, -1, attr);
}
}
static int get_bordertext_col(int total_col, int text_width, AlignTextPos align)
{
switch (align) {
case kAlignLeft:
return 1;
case kAlignCenter:
return MAX((total_col - text_width) / 2 + 1, 1);
case kAlignRight:
return MAX(total_col - text_width + 1, 1);
}
UNREACHABLE;
}
/// draw border on floating window grid
void grid_draw_border(ScreenGrid *grid, WinConfig config, int *adj, int winbl, int *hl_attr)
{
int *attrs = config.border_attr;
int default_adj[4] = { 1, 1, 1, 1 };
if (adj == NULL) {
adj = default_adj;
}
schar_T chars[8];
if (!hl_attr) {
hl_attr = hl_attr_active;
}
for (int i = 0; i < 8; i++) {
chars[i] = schar_from_str(config.border_chars[i]);
}
int irow = grid->rows - adj[0] - adj[2];
int icol = grid->cols - adj[1] - adj[3];
if (adj[0]) {
screengrid_line_start(grid, 0, 0);
if (adj[3]) {
grid_line_put_schar(0, chars[0], attrs[0]);
}
for (int i = 0; i < icol; i++) {
grid_line_put_schar(i + adj[3], chars[1], attrs[1]);
}
if (config.title) {
int title_col = get_bordertext_col(icol, config.title_width, config.title_pos);
grid_draw_bordertext(config.title_chunks, title_col, winbl, hl_attr, kBorderTextTitle);
}
if (adj[1]) {
grid_line_put_schar(icol + adj[3], chars[2], attrs[2]);
}
grid_line_flush();
}
for (int i = 0; i < irow; i++) {
if (adj[3]) {
screengrid_line_start(grid, i + adj[0], 0);
grid_line_put_schar(0, chars[7], attrs[7]);
grid_line_flush();
}
if (adj[1]) {
int ic = (i == 0 && !adj[0] && chars[2]) ? 2 : 3;
screengrid_line_start(grid, i + adj[0], 0);
grid_line_put_schar(icol + adj[3], chars[ic], attrs[ic]);
grid_line_flush();
}
}
if (adj[2]) {
screengrid_line_start(grid, irow + adj[0], 0);
if (adj[3]) {
grid_line_put_schar(0, chars[6], attrs[6]);
}
for (int i = 0; i < icol; i++) {
int ic = (i == 0 && !adj[3] && chars[6]) ? 6 : 5;
grid_line_put_schar(i + adj[3], chars[ic], attrs[ic]);
}
if (config.footer) {
int footer_col = get_bordertext_col(icol, config.footer_width, config.footer_pos);
grid_draw_bordertext(config.footer_chunks, footer_col, winbl, hl_attr, kBorderTextFooter);
}
if (adj[1]) {
grid_line_put_schar(icol + adj[3], chars[4], attrs[4]);
}
grid_line_flush();
}
}
static void linecopy(ScreenGrid *grid, int to, int from, int col, int width)
{
unsigned off_to = (unsigned)(grid->line_offset[to] + (size_t)col);

View File

@@ -3,6 +3,7 @@
#include <stdbool.h>
#include <stddef.h> // IWYU pragma: keep
#include "nvim/buffer_defs.h" // IWYU pragma: keep
#include "nvim/grid_defs.h" // IWYU pragma: keep
#include "nvim/macros_defs.h"
#include "nvim/pos_defs.h"

View File

@@ -325,16 +325,16 @@ int hl_get_ui_attr(int ns_id, int idx, int final_id, bool optional)
/// Apply 'winblend' to highlight attributes.
///
/// @param wp The window to get 'winblend' value from.
/// @param winbl The 'winblend' value.
/// @param attr The original attribute code.
///
/// @return The attribute code with 'winblend' applied.
int hl_apply_winblend(win_T *wp, int attr)
int hl_apply_winblend(int winbl, int attr)
{
HlEntry entry = attr_entry(attr);
// if blend= attribute is not set, 'winblend' value overrides it.
if (entry.attr.hl_blend == -1 && wp->w_p_winbl > 0) {
entry.attr.hl_blend = (int)wp->w_p_winbl;
if (entry.attr.hl_blend == -1 && winbl > 0) {
entry.attr.hl_blend = winbl;
attr = get_attr_entry(entry);
}
return attr;
@@ -379,7 +379,7 @@ void update_window_hl(win_T *wp, bool invalid)
}
if (wp->w_floating) {
wp->w_hl_attr_normal = hl_apply_winblend(wp, wp->w_hl_attr_normal);
wp->w_hl_attr_normal = hl_apply_winblend((int)wp->w_p_winbl, wp->w_hl_attr_normal);
}
wp->w_config.shadow = false;
@@ -390,7 +390,7 @@ void update_window_hl(win_T *wp, bool invalid)
attr = hl_get_ui_attr(ns_id, HLF_BORDER,
wp->w_config.border_hl_ids[i], false);
}
attr = hl_apply_winblend(wp, attr);
attr = hl_apply_winblend((int)wp->w_p_winbl, attr);
if (syn_attr2entry(attr).hl_blend > 0) {
wp->w_config.shadow = true;
}
@@ -411,7 +411,7 @@ void update_window_hl(win_T *wp, bool invalid)
}
if (wp->w_floating) {
wp->w_hl_attr_normalnc = hl_apply_winblend(wp, wp->w_hl_attr_normalnc);
wp->w_hl_attr_normalnc = hl_apply_winblend((int)wp->w_p_winbl, wp->w_hl_attr_normalnc);
}
}

View File

@@ -86,6 +86,7 @@ EXTERN const char *hlf_names[] INIT( = {
[HLF_TS] = "StatusLineTerm",
[HLF_TSNC] = "StatusLineTermNC",
[HLF_PRE] = "PreInsert",
[HLF_PBR] = "PmenuBorder",
});
EXTERN int highlight_attr[HLF_COUNT]; // Highl. attr for each context.

View File

@@ -110,6 +110,7 @@ typedef enum {
HLF_PSX, ///< popup menu selected item "menu" (extra text)
HLF_PSB, ///< popup menu scrollbar
HLF_PST, ///< popup menu scrollbar thumb
HLF_PBR, ///< popup menu border
HLF_TP, ///< tabpage line
HLF_TPS, ///< tabpage line selected
HLF_TPF, ///< tabpage line filler

View File

@@ -174,6 +174,9 @@ static const char *highlight_init_both[] = {
"default link PmenuKind Pmenu",
"default link PmenuKindSel PmenuSel",
"default link PmenuSbar Pmenu",
"default link PmenuBorder Pmenu",
"default link PmenuShadow FloatShadow",
"default link PmenuShadowThrough FloatShadowThrough",
"default link PreInsert Added",
"default link ComplMatchIns NONE",
"default link ComplHint NonText",

View File

@@ -308,6 +308,7 @@ EXTERN OptInt p_acl; ///< 'autocompletedelay'
#ifdef BACKSLASH_IN_FILENAME
EXTERN char *p_csl; ///< 'completeslash'
#endif
EXTERN char *p_pumborder; ///< 'pumborder'
EXTERN OptInt p_pb; ///< 'pumblend'
EXTERN OptInt p_ph; ///< 'pumheight'
EXTERN OptInt p_pw; ///< 'pumwidth'

View File

@@ -6719,6 +6719,21 @@ local options = {
type = 'number',
varname = 'p_pb',
},
{
full_name = 'pumborder',
scope = { 'global' },
cb = 'did_set_pumborder',
defaults = { if_true = '' },
values = { '', 'double', 'single', 'shadow', 'rounded', 'solid', 'bold', 'none' },
desc = [=[
Defines the default border style of popupmenu windows. Same as
'winborder'.
]=],
short_desc = N_('border of popupmenu'),
type = 'string',
list = 'onecomma',
varname = 'p_pumborder',
},
{
abbreviation = 'ph',
defaults = 0,

View File

@@ -13,6 +13,7 @@
#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
#include "nvim/cursor_shape.h"
#include "nvim/decoration.h"
#include "nvim/diff.h"
#include "nvim/digraph.h"
#include "nvim/drawscreen.h"
@@ -2124,16 +2125,32 @@ const char *did_set_winbar(optset_T *args)
return did_set_statustabline_rulerformat(args, false, false);
}
/// The 'winborder' option is changed.
const char *did_set_winborder(optset_T *args)
static bool parse_border_opt(const char *border_opt)
{
WinConfig fconfig = WIN_CONFIG_INIT;
Error err = ERROR_INIT;
if (!parse_winborder(&fconfig, &err)) {
api_clear_error(&err);
return e_invarg;
bool result = true;
if (!parse_winborder(&fconfig, border_opt, &err)) {
result = false;
}
api_clear_error(&err);
return result;
}
/// The 'winborder' option is changed.
const char *did_set_winborder(optset_T *args)
{
if (!parse_border_opt(p_winborder)) {
return e_invarg;
}
return NULL;
}
const char *did_set_pumborder(optset_T *args)
{
if (!parse_border_opt(p_pumborder)) {
return e_invarg;
}
return NULL;
}

View File

@@ -10,12 +10,15 @@
#include "nvim/api/buffer.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
#include "nvim/api/win_config.h"
#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
#include "nvim/errors.h"
#include "nvim/eval/typval.h"
@@ -28,8 +31,10 @@
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/grid_defs.h"
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
#include "nvim/mbyte.h"
@@ -47,6 +52,7 @@
#include "nvim/pos_defs.h"
#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
@@ -116,7 +122,7 @@ static void pum_compute_size(void)
/// Calculate vertical placement for popup menu.
/// Sets pum_row and pum_height based on available space.
static void pum_compute_vertical_placement(int size, win_T *target_win, int pum_win_row,
int above_row, int below_row)
int above_row, int below_row, int pum_border_size)
{
int context_lines;
@@ -128,7 +134,7 @@ static void pum_compute_vertical_placement(int size, win_T *target_win, int pum_
// 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 + pum_border_size >= below_row - pum_height
&& pum_win_row - above_row > (below_row - above_row) / 2) {
// pum above "pum_win_row"
pum_above = true;
@@ -152,6 +158,14 @@ static void pum_compute_vertical_placement(int size, win_T *target_win, int pum_
pum_row += pum_height - (int)p_ph;
pum_height = (int)p_ph;
}
if (pum_border_size > 0 && pum_border_size + pum_row + pum_height >= pum_win_row) {
if (pum_row < 2) {
pum_height -= pum_border_size;
} else {
pum_row -= pum_border_size;
}
}
} else {
// pum below "pum_win_row"
pum_above = false;
@@ -172,6 +186,10 @@ static void pum_compute_vertical_placement(int size, win_T *target_win, int pum_
if (p_ph > 0 && pum_height > p_ph) {
pum_height = (int)p_ph;
}
if (pum_row + pum_height + pum_border_size >= cmdline_row) {
pum_height -= pum_border_size;
}
}
// If there is a preview window above avoid drawing over it.
@@ -279,6 +297,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
pum_rl = (State & MODE_CMDLINE) == 0 && curwin->w_p_rl;
int pum_border_size = *p_pumborder != NUL ? 2 : 0;
do {
// Mark the pum as visible already here,
// to avoid that must_redraw is set when 'cursorcolumn' is on.
@@ -366,10 +386,11 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
}
// Figure out the vertical size and position of the pum.
pum_compute_vertical_placement(size, target_win, pum_win_row, above_row, below_row);
pum_compute_vertical_placement(size, target_win, pum_win_row, above_row, below_row,
pum_border_size);
// don't display when we only have room for one line
if (pum_height < 1 || (pum_height == 1 && size > 1)) {
if (pum_border_size == 0 && (pum_height < 1 || (pum_height == 1 && size > 1))) {
return;
}
@@ -389,6 +410,10 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
// Figure out the horizontal size and position of the pum.
pum_compute_horizontal_placement(target_win, cursor_col);
if (pum_col + pum_border_size + pum_width > Columns) {
pum_col -= pum_border_size;
}
// 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.
@@ -573,18 +598,43 @@ void pum_redraw(void)
}
}
WinConfig fconfig = WIN_CONFIG_INIT;
int pum_border_width = 0;
// setup popup menu border if 'pumborder' option is set
if (*p_pumborder != NUL) {
pum_border_width = 2;
fconfig.border = true;
Error err = ERROR_INIT;
parse_border_style(CSTR_AS_OBJ(p_pumborder), &fconfig, &err);
// shadow style uses different highlights for different positions
if (strcmp(p_pumborder, opt_winborder_values[3]) == 0) {
int blend = SYN_GROUP_STATIC("PmenuShadow");
int through = SYN_GROUP_STATIC("PmenuShadowThrough");
int attrs[] = { 0, 0, through, blend, blend, blend, through, 0 };
memcpy(fconfig.border_attr, attrs, sizeof(attrs));
} else {
// Non-shadow styles use PumBorder highlight for all border chars
int attr = hl_attr_active[HLF_PBR];
for (int i = 0; i < 8; i++) {
fconfig.border_attr[i] = attr;
}
}
api_clear_error(&err);
}
grid_assign_handle(&pum_grid);
pum_left_col = pum_col - col_off;
pum_right_col = pum_left_col + grid_width;
bool moved = ui_comp_put_grid(&pum_grid, pum_row, pum_left_col,
pum_height, grid_width, false, true);
pum_height + pum_border_width, grid_width + pum_border_width, false,
true);
bool invalid_grid = moved || pum_invalid;
pum_invalid = false;
must_redraw_pum = false;
if (!pum_grid.chars || pum_grid.rows != pum_height || pum_grid.cols != grid_width) {
grid_alloc(&pum_grid, pum_height, grid_width, !invalid_grid, false);
grid_alloc(&pum_grid, pum_height + pum_border_width, grid_width + pum_border_width,
!invalid_grid, false);
ui_call_grid_resize(pum_grid.handle, pum_grid.cols, pum_grid.rows);
} else if (invalid_grid) {
grid_invalidate(&pum_grid);
@@ -599,6 +649,15 @@ void pum_redraw(void)
}
int scroll_range = pum_size - pum_height;
// avoid set border for mouse menu
int mouse_menu = State != MODE_CMDLINE && pum_grid.zindex == kZIndexCmdlinePopupMenu;
if (!mouse_menu && fconfig.border) {
grid_draw_border(&pum_grid, fconfig, NULL, 0, NULL);
row++;
col_off++;
}
// Never display more than we have
pum_first = MIN(pum_first, scroll_range);
@@ -871,7 +930,8 @@ static void pum_preview_set_text(buf_T *buf, char *info, linenr_T *lnum, int *ma
/// adjust floating info preview window position
static void pum_adjust_info_position(win_T *wp, int width)
{
int col = pum_col + pum_width + pum_scrollbar + 1;
int extra_width = *p_pumborder != NUL ? 2 : 0;
int col = pum_col + pum_width + pum_scrollbar + 1 + extra_width;
// TODO(glepnir): support config align border by using completepopup
// align menu
int right_extra = Columns - col;

View File

@@ -2,6 +2,7 @@
#include <stdbool.h>
#include "nvim/buffer_defs.h"
#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
#include "nvim/grid_defs.h"
#include "nvim/macros_defs.h"

View File

@@ -258,11 +258,11 @@ describe('ui/cursor', function()
end
end
if m.hl_id then
m.hl_id = 66
m.hl_id = 67
m.attr = { background = Screen.colors.DarkGray }
end
if m.id_lm then
m.id_lm = 77
m.id_lm = 78
m.attr_lm = {}
end
end

View File

@@ -2151,7 +2151,7 @@ describe('builtin popupmenu', function()
feed('<C-E><ESC>')
end)
it('avoid modified original info text #test', function()
it('avoid modified original info text', function()
command('call Append_multipe()')
feed('S<C-x><C-o><C-P><C-P>')
if multigrid then
@@ -8730,6 +8730,407 @@ describe('builtin popupmenu', function()
]])
end)
end
describe("'pumborder'", function()
before_each(function()
screen:try_resize(30, 11)
exec([[
funct Omni_test(findstart, base)
if a:findstart
return col(".") - 1
endif
return [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "three", info: "3info"}]
endfunc
hi link PmenuBorder FloatBorder
set omnifunc=Omni_test
set completeopt-=preview
set pumborder=rounded
]])
end)
it('can set border', function()
feed('Gi<C-x><C-o>')
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:------------------------------]|*10
[3:------------------------------]|
## grid 2
one^ |
{1:~ }|*9
## grid 3
{5:-- }{6:match 1 of 3} |
## grid 4
{n:1info}|
## grid 5
╭───────────────╮|
│{12:one }│|
│{n:two }│|
│{n:three }│|
╰───────────────╯|
]],
win_pos = {
[2] = {
height = 10,
startcol = 0,
startrow = 0,
width = 30,
win = 1000,
},
},
float_pos = {
[5] = { -1, 'NW', 2, 1, 0, false, 100, 2, 1, 0 },
[4] = { 1001, 'NW', 1, 1, 17, false, 50, 1, 1, 17 },
},
win_viewport = {
[2] = {
win = 1000,
topline = 0,
botline = 2,
curline = 0,
curcol = 3,
linecount = 1,
sum_scroll_delta = 0,
},
[4] = {
win = 1001,
topline = 0,
botline = 1,
curline = 0,
curcol = 0,
linecount = 1,
sum_scroll_delta = 0,
},
},
win_viewport_margins = {
[2] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1000,
},
[4] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1001,
},
},
})
else
screen:expect([[
one^ |
╭───────────────╮{n:1info}{1: }|
│{12:one }│{1: }|
│{n:two }│{1: }|
│{n:three }│{1: }|
╰───────────────╯{1: }|
{1:~ }|*4
{5:-- }{6:match 1 of 3} |
]])
end
-- avoid out of screen
feed(('a'):rep(25) .. '<C-x><C-o>')
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:------------------------------]|*10
[3:------------------------------]|
## grid 2
oneaaaaaaaaaaaaaaaaaaaaaaaaaon|
e^ |
{1:~ }|*8
## grid 3
{5:-- }{6:match 1 of 3} |
## grid 4
{n:1info}|
## grid 5
╭─────────────────╮|
│{12: one }│|
│{n: two }│|
│{n: three }│|
╰─────────────────╯|
]],
win_pos = {
[2] = {
height = 10,
startcol = 0,
startrow = 0,
width = 30,
win = 1000,
},
},
float_pos = {
[5] = { -1, 'NW', 2, 2, 11, false, 100, 2, 2, 11 },
[4] = { 1001, 'NW', 1, 2, 6, false, 50, 1, 2, 6 },
},
win_viewport = {
[2] = {
win = 1000,
topline = 0,
botline = 2,
curline = 0,
curcol = 31,
linecount = 1,
sum_scroll_delta = 0,
},
[4] = {
win = 1001,
topline = 0,
botline = 1,
curline = 0,
curcol = 0,
linecount = 1,
sum_scroll_delta = 0,
},
},
win_viewport_margins = {
[2] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1000,
},
[4] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1001,
},
},
})
else
screen:expect([[
oneaaaaaaaaaaaaaaaaaaaaaaaaaon|
e^ |
{1:~ }{n:1info}╭─────────────────╮|
{1:~ }│{12: one }│|
{1:~ }│{n: two }│|
{1:~ }│{n: three }│|
{1:~ }╰─────────────────╯|
{1:~ }|*3
{5:-- }{6:match 1 of 3} |
]])
end
end)
it('adjust to above when the below row + border out of win height', function()
command('set completeopt+=menuone,noselect')
feed('<ESC>Stwo' .. ('<CR>'):rep(6) .. 'tw<C-N>')
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:------------------------------]|*10
[3:------------------------------]|
## grid 2
two |
|*5
tw^ |
{1:~ }|*3
## grid 3
{5:-- }{19:Back at original} |
## grid 4
╭───────────────╮|
│{n:two }│|
╰───────────────╯|
]],
win_pos = {
[2] = {
height = 10,
startcol = 0,
startrow = 0,
width = 30,
win = 1000,
},
},
float_pos = {
[4] = { -1, 'SW', 2, 4, 0, false, 100, 1, 3, 0 },
},
win_viewport = {
[2] = {
win = 1000,
topline = 0,
botline = 8,
curline = 6,
curcol = 2,
linecount = 7,
sum_scroll_delta = 0,
},
},
win_viewport_margins = {
[2] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1000,
},
},
})
else
screen:expect([[
two |
|*2
╭───────────────╮ |
│{n:two }│ |
╰───────────────╯ |
tw^ |
{1:~ }|*3
{5:-- }{19:Back at original} |
]])
end
end)
it('pum border on cmdline', function()
command('set wildoptions=pum')
feed(':<TAB>')
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:------------------------------]|*10
[3:------------------------------]|
## grid 2
|
{1:~ }|*9
## grid 3
:!^ |
## grid 4
╭─────────────────╮|
│{12: ! }{c: }│|
│{n: # }{12: }│|
│{n: & }{12: }│|
│{n: < }{12: }│|
│{n: = }{12: }│|
│{n: > }{12: }│|
│{n: @ }{12: }│|
│{n: Next }{12: }│|
╰─────────────────╯|
]],
win_pos = {
[2] = {
height = 10,
startcol = 0,
startrow = 0,
width = 30,
win = 1000,
},
},
float_pos = {
[4] = { -1, 'SW', 1, 8, 0, false, 250, 2, 0, 0 },
},
win_viewport = {
[2] = {
win = 1000,
topline = 0,
botline = 2,
curline = 0,
curcol = 0,
linecount = 1,
sum_scroll_delta = 0,
},
},
win_viewport_margins = {
[2] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1000,
},
},
})
else
screen:expect([[
╭─────────────────╮ |
│{12: ! }{c: }│{1: }|
│{n: # }{12: }│{1: }|
│{n: & }{12: }│{1: }|
│{n: < }{12: }│{1: }|
│{n: = }{12: }│{1: }|
│{n: > }{12: }│{1: }|
│{n: @ }{12: }│{1: }|
│{n: Next }{12: }│{1: }|
╰─────────────────╯{1: }|
:!^ |
]])
end
end)
it('reduce pum height when height is not enough', function()
command('set lines=7 laststatus=2')
feed('S<C-x><C-o>')
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:------------------------------]|*5
{3:[No Name] [+] }|
[3:------------------------------]|
## grid 2
one^ |
{1:~ }|*4
## grid 3
{5:-- }{6:match 1 of 3} |
## grid 4
╭────────────────╮|
│{12:one }{c: }│|
╰────────────────╯|
]],
win_pos = {
[2] = {
height = 5,
startcol = 0,
startrow = 0,
width = 30,
win = 1000,
},
},
float_pos = {
[4] = { -1, 'NW', 2, 1, 0, false, 100, 1, 1, 0 },
},
win_viewport = {
[2] = {
win = 1000,
topline = 0,
botline = 2,
curline = 0,
curcol = 3,
linecount = 1,
sum_scroll_delta = 0,
},
},
win_viewport_margins = {
[2] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1000,
},
},
})
else
screen:expect([[
one^ |
╭────────────────╮{1: }|
│{12:one }{c: }│{1: }|
╰────────────────╯{1: }|
{1:~ }|
{3:[No Name] [+] }|
{5:-- }{6:match 1 of 3} |
]])
end
end)
end)
end
describe('with ext_multigrid and actual mouse grid', function()