3. Usage |popup-usage|
popup_create() arguments |popup_create-arguments|
Popup text properties |popup-props|
+ Position popup with textprop |popup-textprop-pos|
Popup filter |popup-filter|
Popup callback |popup-callback|
Popup scrollbar |popup-scrollbar|
the current tabpage and a positive number for a popup on
another tabpage.
+ "textprop", "textpropid" and "textpropwin" are only present
+ when "textprop" was set.
+
If popup window {id} is not found an empty Dict is returned.
the line of the cursor and add or subtract a number of
lines. If omitted the popup is vertically centered.
The first line is 1.
+ When using "textprop" the number is relative to the
+ text property and can be negative.
col Screen column where to position the popup. Can use a
number or "cursor" to use the column of the cursor,
"cursor+9" or "cursor-9" to add or subtract a number
of columns. If omitted the popup is horizontally
centered. The first column is 1.
+ When using "textprop" the number is relative to the
+ text property and can be negative.
pos "topleft", "topright", "botleft" or "botright":
defines what corner of the popup "line" and "col" are
used for. When not set "topleft" is used.
Alternatively "center" can be used to position the
popup in the center of the Vim window, in which case
"line" and "col" are ignored.
+ textprop When present the popup is positioned next to a text
+ property with this name and will move when the text
+ property moves. Use an empty string to remove. See
+ |popup-textprop-pos|.
+ textpropwin What window to search for the text property. When
+ omitted or invalid the current window is used.
+ textpropid Used to identify the text property when "textprop" is
+ present. Use zero to reset.
fixed When FALSE (the default), and:
- "pos" is "botleft" or "topleft", and
- "wrap" is off, and
|prop_type_add()|
+POSITION POPUP WITH TEXTPROP *popup-textprop-pos*
+
+Positioning a popup next to a text property causes the popup to move when text
+is inserted or deleted. The popup functions like a tooltip.
+
+These steps are needed to make this work:
+
+- Define a text property type, it defines the name. >
+ call prop_type_add('popupMarker', {})
+
+- Place a text property at the desired text: >
+ let lnum = {line of the text}
+ let col = {start column of the text}
+ let len = {length of the text}
+ let propId = {arbitrary but unique number}
+ call prop_add(lnum, col, #{
+ \ length: len,
+ \ type: 'popupMarker',
+ \ id: propId,
+ \ })
+
+- Create a popup: >
+ let winid = popup_create('the text', #{
+ \ pos: 'botleft',
+ \ textprop: 'popupMarker',
+ \ textpropid: propId,
+ \ border: [],
+ \ padding: [0,1,0,1],
+ \ close: 'click',
+ \ })
+
+By default the popup is positioned at the corner of the text, opposite of the
+"pos" specified for the popup. Thus when the popup uses "botleft", the
+bottom-left corner of the popup is positioned next to the top-right corner of
+the text property:
+ +----------+
+ | the text |
+ +----------+
+ just some PROPERTY as an example
+
+Here the text property is on "PROPERTY". Move the popup to the left by
+passing a negative "col" value to popup_create(). With "col: -5" you get:
+
+ +----------+
+ | the text |
+ +----------+
+ just some PROPERTY as an example
+
+If the text property moves out of view then the popup will be hidden.
+If the window for which the popup was defined is closed, the popup is closed.
+
+If the popup cannot fit in the desired position, it may show at a nearby
+position.
+
+Some hints:
+- To avoid collision with other plugins the text property type name has to be
+ unique. You can also use the "bufnr" item to make it local to a buffer.
+- You can leave out the text property ID if there is only ever one text
+ property visible.
+- The popup may be in the way of what the user is doing, making it close with
+ a click, as in the example above, helps for that.
+- If the text property is removed the popup is closed. Use something like
+ this: >
+ call prop_remove(#{type: 'popupMarker', id: propId})
+
+
POPUP FILTER *popup-filter*
A callback that gets any typed keys while a popup is displayed. The filter is
return n;
}
- static void
-get_pos_options(win_T *wp, dict_T *dict)
-{
- char_u *str;
- int nr;
- dictitem_T *di;
-
- nr = popup_options_one(dict, (char_u *)"line");
- if (nr > 0)
- wp->w_wantline = nr;
- nr = popup_options_one(dict, (char_u *)"col");
- if (nr > 0)
- wp->w_wantcol = nr;
-
- di = dict_find(dict, (char_u *)"fixed", -1);
- if (di != NULL)
- wp->w_popup_fixed = dict_get_number(dict, (char_u *)"fixed") != 0;
-
- str = dict_get_string(dict, (char_u *)"pos", FALSE);
- if (str != NULL)
- {
- for (nr = 0;
- nr < (int)(sizeof(poppos_entries) / sizeof(poppos_entry_T));
- ++nr)
- if (STRCMP(str, poppos_entries[nr].pp_name) == 0)
- {
- wp->w_popup_pos = poppos_entries[nr].pp_val;
- nr = -1;
- break;
- }
- if (nr != -1)
- semsg(_(e_invarg2), str);
- }
-}
-
static void
set_padding_border(dict_T *dict, int *array, char *name, int max_val)
{
{
drag_start_row = mouse_row;
drag_start_col = mouse_col;
- // TODO: handle using different corner
if (wp->w_wantline == 0)
drag_start_wantline = wp->w_winrow + 1;
else
static void
apply_move_options(win_T *wp, dict_T *d)
{
- int nr;
+ int nr;
+ char_u *str;
+ dictitem_T *di;
if ((nr = dict_get_number(d, (char_u *)"minwidth")) > 0)
wp->w_minwidth = nr;
wp->w_maxwidth = nr;
if ((nr = dict_get_number(d, (char_u *)"maxheight")) > 0)
wp->w_maxheight = nr;
- get_pos_options(wp, d);
+
+ nr = popup_options_one(d, (char_u *)"line");
+ if (nr > 0)
+ wp->w_wantline = nr;
+ nr = popup_options_one(d, (char_u *)"col");
+ if (nr > 0)
+ wp->w_wantcol = nr;
+
+ di = dict_find(d, (char_u *)"fixed", -1);
+ if (di != NULL)
+ wp->w_popup_fixed = dict_get_number(d, (char_u *)"fixed") != 0;
+
+ str = dict_get_string(d, (char_u *)"pos", FALSE);
+ if (str != NULL)
+ {
+ for (nr = 0;
+ nr < (int)(sizeof(poppos_entries) / sizeof(poppos_entry_T));
+ ++nr)
+ if (STRCMP(str, poppos_entries[nr].pp_name) == 0)
+ {
+ wp->w_popup_pos = poppos_entries[nr].pp_val;
+ nr = -1;
+ break;
+ }
+ if (nr != -1)
+ semsg(_(e_invarg2), str);
+ }
+
+ str = dict_get_string(d, (char_u *)"textprop", FALSE);
+ if (str != NULL)
+ {
+ wp->w_popup_prop_type = 0;
+ if (*str != NUL)
+ {
+ nr = find_prop_type_id(str, wp->w_buffer);
+ if (nr <= 0)
+ nr = find_prop_type_id(str, NULL);
+ if (nr <= 0)
+ semsg(_(e_invarg2), str);
+ else
+ {
+ wp->w_popup_prop_type = nr;
+ wp->w_popup_prop_win = curwin;
+
+ di = dict_find(d, (char_u *)"textpropwin", -1);
+ if (di != NULL)
+ {
+ wp->w_popup_prop_win = find_win_by_nr_or_id(&di->di_tv);
+ if (win_valid(wp->w_popup_prop_win))
+ wp->w_popup_prop_win = curwin;
+ }
+ }
+ }
+ }
+
+ di = dict_find(d, (char_u *)"textpropid", -1);
+ if (di != NULL)
+ wp->w_popup_prop_id = dict_get_number(d, (char_u *)"textpropid");
}
static void
int org_leftcol = wp->w_leftcol;
int org_leftoff = wp->w_popup_leftoff;
int minwidth;
+ int wantline = wp->w_wantline; // adjusted for textprop
+ int wantcol = wp->w_wantcol; // adjusted for textprop
wp->w_winrow = 0;
wp->w_wincol = 0;
wp->w_leftcol = 0;
wp->w_popup_leftoff = 0;
wp->w_popup_rightoff = 0;
+
+ // If no line was specified default to vertical centering.
+ if (wantline == 0)
+ center_vert = TRUE;
+
+ if (wp->w_popup_prop_type > 0 && win_valid(wp->w_popup_prop_win))
+ {
+ win_T *prop_win = wp->w_popup_prop_win;
+ textprop_T prop;
+ linenr_T prop_lnum;
+ pos_T pos;
+ int screen_row;
+ int screen_scol;
+ int screen_ccol;
+ int screen_ecol;
+
+ // Popup window is positioned relative to a text property.
+ if (find_visible_prop(prop_win,
+ wp->w_popup_prop_type, wp->w_popup_prop_id,
+ &prop, &prop_lnum) == FAIL)
+ {
+ // Text property is no longer visible, hide the popup.
+ // Unhiding the popup is done in check_popup_unhidden().
+ if ((wp->w_popup_flags & POPF_HIDDEN) == 0)
+ {
+ wp->w_popup_flags |= POPF_HIDDEN;
+ --wp->w_buffer->b_nwindows;
+ if (win_valid(wp->w_popup_prop_win))
+ redraw_win_later(wp->w_popup_prop_win, SOME_VALID);
+ }
+ return;
+ }
+
+ // Compute the desired position from the position of the text
+ // property. Use "wantline" and "wantcol" as offsets.
+ pos.lnum = prop_lnum;
+ pos.col = prop.tp_col;
+ if (wp->w_popup_pos == POPPOS_TOPLEFT
+ || wp->w_popup_pos == POPPOS_BOTLEFT)
+ pos.col += prop.tp_len - 1;
+ textpos2screenpos(prop_win, &pos, &screen_row,
+ &screen_scol, &screen_ccol, &screen_ecol);
+
+ if (wp->w_popup_pos == POPPOS_TOPLEFT
+ || wp->w_popup_pos == POPPOS_TOPRIGHT)
+ // below the text
+ wantline = screen_row + wantline + 1;
+ else
+ // above the text
+ wantline = screen_row + wantline - 1;
+ center_vert = FALSE;
+ if (wp->w_popup_pos == POPPOS_TOPLEFT
+ || wp->w_popup_pos == POPPOS_BOTLEFT)
+ // right of the text
+ wantcol = screen_ecol + wantcol;
+ else
+ // left of the text
+ wantcol = screen_scol + wantcol - 1;
+ }
+
if (wp->w_popup_pos == POPPOS_CENTER)
{
// center after computing the size
}
else
{
- if (wp->w_wantline == 0)
- center_vert = TRUE;
- else if (wp->w_popup_pos == POPPOS_TOPLEFT
- || wp->w_popup_pos == POPPOS_TOPRIGHT)
+ if (wantline != 0 && (wp->w_popup_pos == POPPOS_TOPLEFT
+ || wp->w_popup_pos == POPPOS_TOPRIGHT))
{
- wp->w_winrow = wp->w_wantline - 1;
+ wp->w_winrow = wantline - 1;
if (wp->w_winrow >= Rows)
wp->w_winrow = Rows - 1;
}
- if (wp->w_wantcol == 0)
+ if (wantcol == 0)
center_hor = TRUE;
else if (wp->w_popup_pos == POPPOS_TOPLEFT
|| wp->w_popup_pos == POPPOS_BOTLEFT)
{
- wp->w_wincol = wp->w_wantcol - 1;
+ wp->w_wincol = wantcol - 1;
if (wp->w_wincol >= Columns - 3)
wp->w_wincol = Columns - 3;
}
else if (wp->w_popup_pos == POPPOS_BOTRIGHT
|| wp->w_popup_pos == POPPOS_TOPRIGHT)
{
- int leftoff = wp->w_wantcol - (wp->w_width + extra_width);
+ int leftoff = wantcol - (wp->w_width + extra_width);
// Right aligned: move to the right if needed.
// No truncation, because that would change the height.
else if (wp->w_popup_pos == POPPOS_BOTRIGHT
|| wp->w_popup_pos == POPPOS_BOTLEFT)
{
- if ((wp->w_height + extra_height) <= wp->w_wantline)
+ if ((wp->w_height + extra_height) <= wantline)
// bottom aligned: may move down
- wp->w_winrow = wp->w_wantline - (wp->w_height + extra_height);
+ wp->w_winrow = wantline - (wp->w_height + extra_height);
else
// not enough space, make top aligned
- wp->w_winrow = wp->w_wantline + 1;
+ wp->w_winrow = wantline + 1;
}
+ if (wp->w_winrow >= Rows)
+ wp->w_winrow = Rows - 1;
+ else if (wp->w_winrow < 0)
+ wp->w_winrow = 0;
wp->w_popup_last_changedtick = CHANGEDTICK(wp->w_buffer);
+ if (win_valid(wp->w_popup_prop_win))
+ {
+ wp->w_popup_prop_changedtick =
+ CHANGEDTICK(wp->w_popup_prop_win->w_buffer);
+ wp->w_popup_prop_topline = wp->w_popup_prop_win->w_topline;
+ }
// Need to update popup_mask if the position or size changed.
// And redraw windows and statuslines that were behind the popup.
popup_close(id);
}
+ static void
+popup_close_with_retval(win_T *wp, int retval)
+{
+ typval_T res;
+
+ res.v_type = VAR_NUMBER;
+ res.vval.v_number = retval;
+ popup_close_and_callback(wp, &res);
+}
+
/*
* Close popup "wp" because of a mouse click.
*/
void
popup_close_for_mouse_click(win_T *wp)
{
- typval_T res;
-
- res.v_type = VAR_NUMBER;
- res.vval.v_number = -2;
- popup_close_and_callback(wp, &res);
+ popup_close_with_retval(wp, -2);
}
static void
|| mouse_col < wp->w_popup_mouse_mincol
|| mouse_col > wp->w_popup_mouse_maxcol))
{
- typval_T res;
-
- res.v_type = VAR_NUMBER;
- res.vval.v_number = -2;
// Careful: this makes "wp" invalid.
- popup_close_and_callback(wp, &res);
+ popup_close_with_retval(wp, -2);
}
}
dict_add_number(dict, "scrollbar", wp->w_want_scrollbar);
dict_add_number(dict, "zindex", wp->w_zindex);
dict_add_number(dict, "fixed", wp->w_popup_fixed);
+ if (wp->w_popup_prop_type && win_valid(wp->w_popup_prop_win))
+ {
+ proptype_T *pt = text_prop_type_by_id(
+ wp->w_popup_prop_win->w_buffer,
+ wp->w_popup_prop_type);
+
+ if (pt != NULL)
+ dict_add_string(dict, "textprop", pt->pt_name);
+ dict_add_number(dict, "textpropwin", wp->w_popup_prop_win->w_id);
+ dict_add_number(dict, "textpropid", wp->w_popup_prop_id);
+ }
dict_add_string(dict, "title", wp->w_popup_title);
dict_add_number(dict, "wrap", wp->w_p_wrap);
dict_add_number(dict, "drag", (wp->w_popup_flags & POPF_DRAG) != 0);
- dict_add_number(dict, "mapping", (wp->w_popup_flags & POPF_MAPPING) != 0);
+ dict_add_number(dict, "mapping",
+ (wp->w_popup_flags & POPF_MAPPING) != 0);
dict_add_number(dict, "resize", (wp->w_popup_flags & POPF_RESIZE) != 0);
dict_add_number(dict, "cursorline",
(wp->w_popup_flags & POPF_CURSORLINE) != 0);
// Emergency exit: CTRL-C closes the popup.
if (c == Ctrl_C)
{
- rettv.v_type = VAR_NUMBER;
- rettv.vval.v_number = -1;
- popup_close_and_callback(wp, &rettv);
+ popup_close_with_retval(wp, -1);
return 1;
}
popup_check_cursor_pos()
{
win_T *wp;
- typval_T tv;
popup_reset_handled();
while ((wp = find_next_popup(TRUE)) != NULL)
|| curwin->w_cursor.lnum != wp->w_popup_lnum
|| curwin->w_cursor.col < wp->w_popup_mincol
|| curwin->w_cursor.col > wp->w_popup_maxcol))
- {
- tv.v_type = VAR_NUMBER;
- tv.vval.v_number = -1;
- popup_close_and_callback(wp, &tv);
- }
+ popup_close_with_retval(wp, -1);
}
/*
}
}
+/*
+ * Only called when popup window "wp" is hidden: If the window is positioned
+ * next to a text property, and it is now visible, then unhide the popup.
+ * We don't check if visible popups become hidden, that is done in
+ * popup_adjust_position().
+ * Return TRUE if the popup became unhidden.
+ */
+ static int
+check_popup_unhidden(win_T *wp)
+{
+ if (wp->w_popup_prop_type > 0 && win_valid(wp->w_popup_prop_win))
+ {
+ textprop_T prop;
+ linenr_T lnum;
+
+ if (find_visible_prop(wp->w_popup_prop_win,
+ wp->w_popup_prop_type, wp->w_popup_prop_id,
+ &prop, &lnum) == OK)
+ {
+ wp->w_popup_flags &= ~POPF_HIDDEN;
+ ++wp->w_buffer->b_nwindows;
+ wp->w_popup_prop_topline = 0; // force repositioning
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/*
+ * Return TRUE if popup_adjust_position() needs to be called for "wp".
+ * That is when the buffer in the popup was changed, or the popup is following
+ * a textprop and the referenced buffer was changed.
+ */
+ static int
+popup_need_position_adjust(win_T *wp)
+{
+ if (wp->w_popup_last_changedtick != CHANGEDTICK(wp->w_buffer))
+ return TRUE;
+ if (win_valid(wp->w_popup_prop_win))
+ return wp->w_popup_prop_changedtick
+ != CHANGEDTICK(wp->w_popup_prop_win->w_buffer)
+ || wp->w_popup_prop_topline != wp->w_popup_prop_win->w_topline;
+ return FALSE;
+}
+
/*
* Update "popup_mask" if needed.
* Also recomputes the popup size and positions.
popup_mask_refresh = TRUE;
redraw_all_popups = TRUE;
}
+
+ // Check if any popup window buffer has changed and if any popup connected
+ // to a text property has become visible.
+ for (wp = first_popupwin; wp != NULL; wp = wp->w_next)
+ if (wp->w_popup_flags & POPF_HIDDEN)
+ popup_mask_refresh |= check_popup_unhidden(wp);
+ else if (popup_need_position_adjust(wp))
+ popup_mask_refresh = TRUE;
+ for (wp = curtab->tp_first_popupwin; wp != NULL; wp = wp->w_next)
+ if (wp->w_popup_flags & POPF_HIDDEN)
+ popup_mask_refresh |= check_popup_unhidden(wp);
+ else if (popup_need_position_adjust(wp))
+ popup_mask_refresh = TRUE;
+
if (!popup_mask_refresh)
- {
- // Check if any popup window buffer has changed.
- for (wp = first_popupwin; wp != NULL; wp = wp->w_next)
- if (wp->w_popup_last_changedtick != CHANGEDTICK(wp->w_buffer))
- popup_mask_refresh = TRUE;
- for (wp = curtab->tp_first_popupwin; wp != NULL; wp = wp->w_next)
- if (wp->w_popup_last_changedtick != CHANGEDTICK(wp->w_buffer))
- popup_mask_refresh = TRUE;
- if (!popup_mask_refresh)
- return;
- }
+ return;
// Need to update the mask, something has changed.
popup_mask_refresh = FALSE;
popup_visible = TRUE;
- // Recompute the position if the text changed.
- if (redraw_all_popups
- || wp->w_popup_last_changedtick != CHANGEDTICK(wp->w_buffer))
+ // Recompute the position if the text changed. It may make the popup
+ // hidden if it's attach to a text property that is no longer visible.
+ if (redraw_all_popups || popup_need_position_adjust(wp))
+ {
popup_adjust_position(wp);
+ if (wp->w_popup_flags & POPF_HIDDEN)
+ continue;
+ }
width = popup_width(wp);
height = popup_height(wp);
win_T *wp = popup_find_preview_window();
if (wp != NULL)
- {
- typval_T res;
-
- res.v_type = VAR_NUMBER;
- res.vval.v_number = -1;
- popup_close_and_callback(wp, &res);
- }
+ popup_close_with_retval(wp, -1);
}
/*
}
#endif
+/*
+ * Close any popup for a text property associated with "win".
+ * Return TRUE if a popup was closed.
+ */
+ int
+popup_win_closed(win_T *win)
+{
+ win_T *wp;
+
+ for (wp = first_popupwin; wp != NULL; wp = wp->w_next)
+ if (wp->w_popup_prop_win == win)
+ {
+ popup_close_with_retval(wp, -1);
+ return TRUE;
+ }
+ for (wp = curtab->tp_first_popupwin; wp != NULL; wp = wp->w_next)
+ if (wp->w_popup_prop_win == win)
+ {
+ popup_close_with_retval(wp, -1);
+ return TRUE;
+ }
+ return FALSE;
+}
+
/*
* Set the title of the popup window to the file name.
*/