feat(ui): config popupmenu by use completepopup

This commit is contained in:
glepnir 2023-10-08 18:32:29 +08:00
parent f3677c71f0
commit 7e3b565ca8
16 changed files with 655 additions and 130 deletions

View File

@ -143,6 +143,8 @@ OPTIONS
• 'completeopt' flag "fuzzy" enables |fuzzy-matching| during |ins-completion|.
• 'tabclose' controls which tab page to focus when closing a tab page.
• Added new option 'completepopup' for config completion window and info
preview floating window.
PERFORMANCE

View File

@ -1571,6 +1571,19 @@ A jump table for the options with a short description can be found at |Q_op|.
list of alternatives, but not how the candidates are
collected (using different completion types).
*'completepopup'* *'cpp'*
'completepopup' 'cpp' string (default "")
global
This option is used to set the completion window and info floating
preview window. The option is a comma-separated list of values:
`align`, `border`, `title`, `titlepos`, `footer`, `footerpos`.
see |nvim_open_win()|.
When the "align" value is "item" then the info floating preview window
is positioned close to the selected item. Changing the selection will
also move the popup. When "align" is "menu" then the floating preview
window is aligned with the top of the menu if the menu is below the text,
and the bottom of the menu otherwise.
*'completeslash'* *'csl'*
'completeslash' 'csl' string (default "")
local to buffer

View File

@ -607,7 +607,6 @@ These legacy Vim features are not yet implemented:
- *:gui*
- *:gvim*
- *'completepopup'*
- *'previewpopup'*
==============================================================================

View File

@ -1103,6 +1103,22 @@ vim.bo.cot = vim.bo.completeopt
vim.go.completeopt = vim.o.completeopt
vim.go.cot = vim.go.completeopt
--- This option is used to set the completion window and info floating
--- preview window. The option is a comma-separated list of values:
--- `align`, `border`, `title`, `titlepos`, `footer`, `footerpos`.
--- see `nvim_open_win()`.
--- When the "align" value is "item" then the info floating preview window
--- is positioned close to the selected item. Changing the selection will
--- also move the popup. When "align" is "menu" then the floating preview
--- window is aligned with the top of the menu if the menu is below the text,
--- and the bottom of the menu otherwise.
---
--- @type string
vim.o.completepopup = ""
vim.o.cpp = vim.o.completepopup
vim.go.completepopup = vim.o.completepopup
vim.go.cpp = vim.go.completepopup
--- only for MS-Windows
--- When this option is set it overrules 'shellslash' for completion:
--- - When this option is set to "slash", a forward slash is used for path

View File

@ -836,8 +836,8 @@ static bool parse_float_bufpos(Array bufpos, lpos_T *out)
return true;
}
static void parse_bordertext(Object bordertext, BorderTextType bordertext_type, WinConfig *fconfig,
Error *err)
void parse_bordertext(Object bordertext, BorderTextType bordertext_type, WinConfig *fconfig,
Error *err)
{
if (bordertext.type != kObjectTypeString && bordertext.type != kObjectTypeArray) {
api_set_error(err, kErrorTypeValidation, "title/footer must be string or array");
@ -891,8 +891,8 @@ static void parse_bordertext(Object bordertext, BorderTextType bordertext_type,
*is_present = true;
}
static bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertext_type,
WinConfig *fconfig, Error *err)
bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertext_type, WinConfig *fconfig,
Error *err)
{
AlignTextPos *align;
switch (bordertext_type) {
@ -931,7 +931,7 @@ static bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertex
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;

View File

@ -4,6 +4,7 @@
#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
#include "nvim/api/private/defs.h" // IWYU pragma: keep
#include "nvim/buffer_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/win_config.h.generated.h"

View File

@ -112,6 +112,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"
@ -628,7 +629,9 @@ int update_screen(void)
win_grid_alloc(wp);
if (wp->w_redr_border || wp->w_redr_type >= UPD_NOT_VALID) {
win_redr_border(wp);
// 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) {
@ -707,112 +710,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_height_inner + wp->w_winbar_height;
int icol = wp->w_width_inner;
if (adj[0]) {
grid_line_start(grid, 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]) {
grid_line_start(grid, i + adj[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;
grid_line_start(grid, i + adj[0]);
grid_line_put_schar(icol + adj[3], chars[ic], attrs[ic]);
grid_line_flush();
}
}
if (adj[2]) {
grid_line_start(grid, irow + adj[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

@ -1067,6 +1067,108 @@ 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;
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]) {
grid_line_start(grid, 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]) {
grid_line_start(grid, i + adj[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;
grid_line_start(grid, i + adj[0]);
grid_line_put_schar(icol + adj[3], chars[ic], attrs[ic]);
grid_line_flush();
}
}
if (adj[2]) {
grid_line_start(grid, irow + adj[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

@ -332,12 +332,12 @@ int hl_get_ui_attr(int ns_id, int idx, int final_id, bool optional)
/// @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

@ -443,6 +443,7 @@ EXTERN unsigned cot_flags; ///< flags from 'completeopt'
#define COT_NOINSERT 0x020 // false: select & insert, true: noinsert
#define COT_NOSELECT 0x040 // false: select & insert, true: noselect
#define COT_FUZZY 0x080 // true: fuzzy match enabled
EXTERN char *p_cpp; ///< 'completepopup'
#ifdef BACKSLASH_IN_FILENAME
EXTERN char *p_csl; ///< 'completeslash'
#endif

View File

@ -1480,6 +1480,28 @@ return {
type = 'string',
varname = 'p_cot',
},
{
abbreviation = 'cpp',
cb = 'did_set_completepopup',
defaults = { if_true = '' },
desc = [=[
This option is used to set the completion window and info floating
preview window. The option is a comma-separated list of values:
`align`, `border`, `title`, `titlepos`, `footer`, `footerpos`.
see |nvim_open_win()|.
When the "align" value is "item" then the info floating preview window
is positioned close to the selected item. Changing the selection will
also move the popup. When "align" is "menu" then the floating preview
window is aligned with the top of the menu if the menu is below the text,
and the bottom of the menu otherwise.
]=],
full_name = 'completepopup',
list = 'onecomma',
scope = { 'global' },
short_desc = N_('options for completion window'),
type = 'string',
varname = 'p_cpp',
},
{
abbreviation = 'csl',
cb = 'did_set_completeslash',

View File

@ -11,6 +11,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"
@ -38,6 +39,7 @@
#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/os.h"
#include "nvim/popupmenu.h"
#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/regexp_defs.h"
@ -1027,6 +1029,23 @@ int expand_set_completeopt(optexpand_T *args, int *numMatches, char ***matches)
matches);
}
const char *did_set_completepopup(optset_T *args FUNC_ATTR_UNUSED)
{
WinConfig config = WIN_CONFIG_INIT;
int adj[4];
bool result = parse_completepopup(&config, adj);
if (config.title) {
clear_virttext(&config.title_chunks);
}
if (config.footer) {
clear_virttext(&config.footer_chunks);
}
if (!result) {
return e_invarg;
}
return NULL;
}
#ifdef BACKSLASH_IN_FILENAME
/// The 'completeslash' option is changed.
const char *did_set_completeslash(optset_T *args)

View File

@ -10,6 +10,7 @@
#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"
@ -18,6 +19,7 @@
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/errors.h"
@ -33,6 +35,7 @@
#include "nvim/grid.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"
@ -80,6 +83,9 @@ static int pum_col; // left column of pum, right column if 'righ
static int pum_left_col; // left column of pum, before padding or scrollbar
static bool pum_above; // pum is drawn above cursor line
static PumInfoAlign pum_align = kInfoAlignMenu; // float preview align
static bool pum_has_border = false; // pum grid has border
static bool pum_is_visible = false;
static bool pum_is_drawn = false;
static bool pum_external = false;
@ -234,11 +240,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
}
// Figure out the size and position of the pum.
if (size < PUM_DEF_HEIGHT) {
pum_height = size;
} else {
pum_height = PUM_DEF_HEIGHT;
}
pum_height = size < PUM_DEF_HEIGHT ? size : PUM_DEF_HEIGHT;
if (p_ph > 0 && pum_height > p_ph) {
pum_height = (int)p_ph;
@ -566,17 +568,30 @@ void pum_redraw(void)
}
}
WinConfig fconfig = WIN_CONFIG_INIT;
int border_adj[4];
parse_completepopup(&fconfig, border_adj);
pum_has_border = fconfig.border;
// has border
int pum_extra_border = fconfig.border ? 2 : 0;
// avoid border out of screen
if (pum_extra_border && pum_col + pum_extra_border + pum_width > Columns) {
pum_col -= 2;
}
grid_assign_handle(&pum_grid);
pum_left_col = pum_col - col_off;
bool moved = ui_comp_put_grid(&pum_grid, pum_row, pum_left_col,
pum_height, grid_width, false, true);
pum_height + pum_extra_border, grid_width + pum_extra_border, 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_extra_border, grid_width + pum_extra_border,
!invalid_grid, false);
ui_call_grid_resize(pum_grid.handle, pum_grid.cols, pum_grid.rows);
} else if (invalid_grid) {
grid_invalidate(&pum_grid);
@ -588,6 +603,19 @@ void pum_redraw(void)
pum_row - row_off, pum_left_col, false, pum_grid.zindex);
}
if (fconfig.border) {
grid_draw_border(&pum_grid, fconfig, border_adj, 0, NULL);
row++;
col_off++;
if (fconfig.title) {
clear_virttext(&fconfig.title_chunks);
}
if (fconfig.footer) {
clear_virttext(&fconfig.footer_chunks);
}
}
// Never display more than we have
if (pum_first > pum_size - pum_height) {
pum_first = pum_size - pum_height;
@ -816,9 +844,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 height, int width)
{
int col = pum_col + pum_width + pum_scrollbar + 1;
// TODO(glepnir): support config align border by using completepopup
// align menu
int extra_width = pum_has_border ? 2 : 0;
int col = pum_col + pum_width + pum_scrollbar + 1 + extra_width;
int right_extra = Columns - col;
int left_extra = pum_col - 2;
@ -835,7 +862,12 @@ static void pum_adjust_info_position(win_T *wp, int height, int width)
}
// when pum_above is SW otherwise is NW
wp->w_config.anchor = pum_above ? kFloatAnchorSouth : 0;
wp->w_config.row = pum_above ? pum_row + height : pum_row;
if (pum_align == kInfoAlignMenu) {
wp->w_config.row = (pum_above ? pum_row + height : pum_row) + (extra_width > 0 ? 1 : 0);
} else {
wp->w_config.row = pum_row + pum_selected - pum_first + 1 + (pum_above
&& pum_has_border ? 1 : 0);
}
wp->w_config.height = MIN(Rows, height);
wp->w_config.hide = false;
win_config_float(wp, wp->w_config);
@ -1491,3 +1523,106 @@ void pum_make_popup(const char *path_name, int use_mouse_pos)
pum_show_popupmenu(menu);
}
}
static bool pum_parse_title(WinConfig *fconfig, BorderTextType bt, const char *s, size_t len,
bool pos)
{
Error err = ERROR_INIT;
char *data = xmemdupz(s, len);
if (!data) {
return false;
}
bool result = true;
if (pos) {
result = parse_bordertext_pos(cstr_as_string(data), bt, fconfig, &err);
} else {
parse_bordertext(CSTR_AS_OBJ(data), bt, fconfig, &err);
}
xfree(data);
if (ERROR_SET(&err)) {
emsg(err.msg);
api_clear_error(&err);
result = false;
}
return result;
}
bool parse_completepopup(WinConfig *fconfig, int *border_adj)
{
const char *p = p_cpp;
Error err = ERROR_INIT;
while (*p != NUL) {
const char *s = p;
const char *e = vim_strchr(p, ':');
if (e == NULL || e[1] == NUL) {
return FAIL;
}
p = vim_strchr(e, ',');
if (p == NULL) {
p = e + strlen(e);
}
size_t len = (size_t)(p - e - 1);
if (strncmp(s, "border:", 7) == 0) {
char *style = xmemdupz(e + 1, len);
if (!style) {
return false;
}
parse_border_style(CSTR_AS_OBJ(style), fconfig, &err);
xfree(style);
if (ERROR_SET(&err)) {
emsg(err.msg);
api_clear_error(&err);
return false;
}
int border_attr = win_hl_attr(curwin, HLF_BORDER);
for (int i = 0; i < 8; i++) {
fconfig->border_attr[i] = fconfig->border_hl_ids[i]
? hl_get_ui_attr(0, HLF_BORDER, fconfig->border_hl_ids[i], false)
: border_attr;
if (i < 4 && fconfig->border_chars[2 * i + 1][0]) {
border_adj[i] = 1;
}
}
} else if (strncmp(s, "title:", 6) == 0) {
if (!pum_parse_title(fconfig, kBorderTextTitle, e + 1, len, false)) {
return false;
}
fconfig->title = true;
} else if (strncmp(s, "titlepos:", 9) == 0) {
if (!pum_parse_title(fconfig, kBorderTextTitle, e + 1, len, true)) {
return false;
}
} else if (strncmp(s, "footer:", 7) == 0) {
if (!pum_parse_title(fconfig, kBorderTextFooter, e + 1, len, false)) {
return false;
}
fconfig->footer = true;
} else if (strncmp(s, "footerpos:", 10) == 0) {
if (!pum_parse_title(fconfig, kBorderTextFooter, e + 1, len, true)) {
return false;
}
} else if (strncmp(s, "align:", 6) == 0) {
const char *arg = e + 1;
if (strncmp(arg, "item", 4) == 0) {
pum_align = kInfoAlignItem;
} else if (strncmp(arg, "menu", 4) == 0) {
pum_align = kInfoAlignMenu;
} else {
return false;
}
} else {
return false;
}
if (*p == ',') {
p++;
}
}
return true;
}

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"
@ -29,6 +30,11 @@ EXTERN struct {
bool finish;
} pum_want;
typedef enum {
kInfoAlignMenu = 0,
kInfoAlignItem,
} PumInfoAlign;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "popupmenu.h.generated.h"
#endif

View File

@ -1173,6 +1173,7 @@ describe('builtin popupmenu', function()
[6] = { foreground = Screen.colors.White, background = Screen.colors.Red },
[7] = { background = Screen.colors.Yellow }, -- Search
[8] = { foreground = Screen.colors.Red },
[9] = { foreground = Screen.colors.Fuchsia, bold = true },
ks = { foreground = Screen.colors.Red, background = Screen.colors.Grey },
kn = { foreground = Screen.colors.Red, background = Screen.colors.Plum1 },
xs = { foreground = Screen.colors.Black, background = Screen.colors.Grey },
@ -5012,6 +5013,316 @@ describe('builtin popupmenu', function()
feed('<C-E><Esc>')
end)
end
describe('#completepopup', 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
set omnifunc=Omni_test
set completeopt-=preview
set completepopup=border:rounded
]])
end)
it('can set border', function()
feed('Gi<C-x><C-o>')
if multigrid then
screen:expect {
grid = [[
## grid 1
[2:------------------------------]|
[2:------------------------------]|
[2:------------------------------]|
[2:------------------------------]|
[2:------------------------------]|
[2:------------------------------]|
[2:------------------------------]|
[2:------------------------------]|
[2:------------------------------]|
[2:------------------------------]|
[3:------------------------------]|
## grid 2
one^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
## grid 3
{2:-- }{5:match 1 of 3} |
## grid 4
|
{s:one }|
{n:two }|
{n:three }|
|
]],
float_pos = {
[4] = { -1, 'NW', 2, 1, 0, false, 100 },
},
win_viewport = {
[2] = {
win = 1000,
topline = 0,
botline = 2,
curline = 0,
curcol = 3,
linecount = 1,
sum_scroll_delta = 0,
},
},
}
else
screen:expect {
grid = [[
one^ |
{1: }|
{s:one }{1: }|
{n:two }{1: }|
{n:three }{1: }|
{1: }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- }{5: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:------------------------------]|
[2:------------------------------]|
[2:------------------------------]|
[2:------------------------------]|
[2:------------------------------]|
[2:------------------------------]|
[2:------------------------------]|
[2:------------------------------]|
[2:------------------------------]|
[2:------------------------------]|
[3:------------------------------]|
## grid 2
oneaaaaaaaaaaaaaaaaaaaaaaaaaon|
e^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
## grid 3
{2:-- }{5:match 1 of 3} |
## grid 4
|
{s: one }|
{n: two }|
{n: three}|
|
]],
float_pos = {
[4] = { -1, 'NW', 2, 2, 22, false, 100 },
},
win_viewport = {
[2] = {
win = 1000,
topline = 0,
botline = 2,
curline = 0,
curcol = 31,
linecount = 1,
sum_scroll_delta = 0,
},
},
}
else
screen:expect {
grid = [[
oneaaaaaaaaaaaaaaaaaaaaaaaaaon|
e^ |
{1:~ }|
{1:~ }{s: one }|
{1:~ }{n: two }|
{1:~ }{n: three}|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:-- }{5:match 1 of 3} |
]],
}
end
end)
it('can set title', function()
command('set completepopup=border:rounded,title:Omni,titlepos:right')
feed('Gi<C-x><C-o>')
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:------------------------------]|*10
[3:------------------------------]|
## grid 2
one^ |
{1:~ }|*9
## grid 3
{2:-- }{5:match 1 of 3} |
## grid 4
{9:Omni}|
{s:one }|
{n:two }|
{n:three }|
|
]],
float_pos = {
[4] = { -1, 'NW', 2, 1, 0, false, 100 },
},
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({
grid = [[
one^ |
{9:Omni}{1: }|
{s:one }{1: }|
{n:two }{1: }|
{n:three }{1: }|
{1: }|
{1:~ }|*4
{2:-- }{5:match 1 of 3} |
]],
})
end
end)
it('align', function()
command('set completepopup=align:item,border:rounded,title:Omni, completeopt+=popup')
feed('<ESC>S<C-X><C-O><C-N>')
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:------------------------------]|*10
[3:------------------------------]|
## grid 2
two^ |
{1:~ }|*9
## grid 3
{2:-- }{5:match 2 of 3} |
## grid 4
{n:2info}|
{n: }|
## grid 5
{9:Omni}|
{n:one }|
{s:two }|
{n:three }|
|
]],
float_pos = {
[5] = { -1, 'NW', 2, 1, 0, false, 100 },
[4] = { 1001, 'NW', 1, 3, 17, false, 50 },
},
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 = 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,
},
[4] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1001,
},
},
})
else
screen:expect({
grid = [[
two^ |
{9:Omni}{1: }|
{n:one }{1: }|
{s:two }{n:2info}{1: }|
{n:three }{n: }{1: }|
{1: }|
{1:~ }|*4
{2:-- }{5:match 2 of 3} |
]],
})
end
end)
it('wrong value in option', function()
eq(
'Vim(set):invalid border style "foo"',
pcall_err(command, 'set completepopup=border:foo')
)
eq(
'Vim(set):E474: Invalid argument: completepopup=align:foo',
pcall_err(command, 'set completepopup=align:foo')
)
end)
end)
end
describe('with ext_multigrid', function()