]> granicus.if.org Git - vim/commitdiff
patch 8.1.1928: popup windows don't move with the text when making changes v8.1.1928
authorBram Moolenaar <Bram@vim.org>
Sun, 25 Aug 2019 20:25:02 +0000 (22:25 +0200)
committerBram Moolenaar <Bram@vim.org>
Sun, 25 Aug 2019 20:25:02 +0000 (22:25 +0200)
Problem:    Popup windows don't move with the text when making changes.
Solution:   Add the 'textprop" property to the popup window options, position
            the popup relative to a text property. (closes #4560)
            No tests yet.

runtime/doc/popup.txt
src/move.c
src/popupwin.c
src/proto/move.pro
src/proto/popupwin.pro
src/proto/textprop.pro
src/structs.h
src/textprop.c
src/version.c
src/window.c

index e766deb4a7b7ff7c9154ea3a2c62789bffdea280..6f29c5efd7923835a9e9ed977533d82d62b36501 100644 (file)
@@ -16,6 +16,7 @@ Displaying text in a floating window.                 *popup* *popup-window*
 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|
@@ -325,6 +326,9 @@ popup_getoptions({id})                                      *popup_getoptions()*
                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.
 
 
@@ -507,17 +511,29 @@ The second argument of |popup_create()| is a dictionary with options:
                        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
@@ -670,6 +686,72 @@ So we get:
                        |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
index b3475a8022e507b4f03f7e68bf07761aeb9c0c66..223ed5417e4ef21545df0d81a212e774109e02b3 100644 (file)
@@ -1179,12 +1179,12 @@ curs_columns(
     curwin->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL;
 }
 
-#if defined(FEAT_EVAL) || defined(PROTO)
+#if (defined(FEAT_EVAL) || defined(FEAT_TEXT_PROP)) || defined(PROTO)
 /*
  * Compute the screen position of text character at "pos" in window "wp"
  * The resulting values are one-based, zero when character is not visible.
  */
-    static void
+    void
 textpos2screenpos(
        win_T   *wp,
        pos_T   *pos,
@@ -1213,12 +1213,12 @@ textpos2screenpos(
        col += off;
        width = wp->w_width - off + win_col_off2(wp);
 
-       /* long line wrapping, adjust row */
+       // long line wrapping, adjust row
        if (wp->w_p_wrap
                && col >= (colnr_T)wp->w_width
                && width > 0)
        {
-           /* use same formula as what is used in curs_columns() */
+           // use same formula as what is used in curs_columns()
            rowoff = ((col - wp->w_width) / width + 1);
            col -= rowoff * width;
        }
@@ -1236,7 +1236,9 @@ textpos2screenpos(
     *ccolp = ccol + coloff;
     *ecolp = ecol + coloff;
 }
+#endif
 
+#if defined(FEAT_EVAL) || defined(PROTO)
 /*
  * "screenpos({winid}, {lnum}, {col})" function
  */
index 7aef2735842499c77be12e7abc0d352735a454d0..bf1205517fee2cad87100709549b3c4ef311b058 100644 (file)
@@ -75,41 +75,6 @@ popup_options_one(dict_T *dict, char_u *key)
     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)
 {
@@ -256,7 +221,6 @@ popup_start_drag(win_T *wp, int row, int col)
 {
     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
@@ -430,7 +394,9 @@ popup_add_timeout(win_T *wp, int time)
     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;
@@ -440,7 +406,64 @@ apply_move_options(win_T *wp, dict_T *d)
        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
@@ -1015,12 +1038,74 @@ popup_adjust_position(win_T *wp)
     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
@@ -1029,22 +1114,20 @@ popup_adjust_position(win_T *wp)
     }
     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;
        }
@@ -1161,7 +1244,7 @@ popup_adjust_position(win_T *wp)
     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.
@@ -1216,15 +1299,25 @@ popup_adjust_position(win_T *wp)
     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.
@@ -1837,17 +1930,23 @@ popup_close_and_callback(win_T *wp, typval_T *arg)
     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
@@ -1864,12 +1963,8 @@ check_mouse_moved(win_T *wp, win_T *mouse_wp)
                || 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);
     }
 }
 
@@ -2458,10 +2553,22 @@ f_popup_getoptions(typval_T *argvars, typval_T *rettv)
        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);
@@ -2603,9 +2710,7 @@ invoke_popup_filter(win_T *wp, int c)
     // 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;
     }
 
@@ -2687,7 +2792,6 @@ popup_no_mapping(void)
 popup_check_cursor_pos()
 {
     win_T *wp;
-    typval_T tv;
 
     popup_reset_handled();
     while ((wp = find_next_popup(TRUE)) != NULL)
@@ -2696,11 +2800,7 @@ popup_check_cursor_pos()
                    || 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);
 }
 
 /*
@@ -2821,6 +2921,51 @@ update_popup_transparent(win_T *wp, int val)
     }
 }
 
+/*
+ * 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.
@@ -2844,18 +2989,22 @@ may_update_popup_mask(int type)
        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;
@@ -2886,10 +3035,14 @@ may_update_popup_mask(int type)
 
        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);
@@ -3411,13 +3564,7 @@ popup_close_preview(void)
     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);
 }
 
 /*
@@ -3433,6 +3580,30 @@ popup_hide_info(void)
 }
 #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.
  */
index c2ec8d5953e37db6127b2673688ff9b1da3f08d7..d05eb17537292de874d5550740c2d49eb70c5f96 100644 (file)
@@ -27,6 +27,7 @@ int curwin_col_off(void);
 int win_col_off2(win_T *wp);
 int curwin_col_off2(void);
 void curs_columns(int may_scroll);
+void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, int *ecolp);
 void f_screenpos(typval_T *argvars, typval_T *rettv);
 void scrolldown(long line_count, int byfold);
 void scrollup(long line_count, int byfold);
index dab117e744feee9652ab86e655bfc12695df33aa..d6ba3e7f8b7a33e02e261ed31934c00369074be8 100644 (file)
@@ -55,6 +55,7 @@ void f_popup_findpreview(typval_T *argvars, typval_T *rettv);
 int popup_create_preview_window(int info);
 void popup_close_preview(void);
 void popup_hide_info(void);
+int popup_win_closed(win_T *win);
 void popup_set_title(win_T *wp);
 void popup_update_preview_title(void);
 /* vim: set ft=c : */
index f33c7a4befc446bddade1bfebc46f1a336532f07..028e3d4b4fd29b729e043728f37b5c063b87ec07 100644 (file)
@@ -1,7 +1,9 @@
 /* textprop.c */
+int find_prop_type_id(char_u *name, buf_T *buf);
 void f_prop_add(typval_T *argvars, typval_T *rettv);
 void prop_add_common(linenr_T start_lnum, colnr_T start_col, dict_T *dict, buf_T *default_buf, typval_T *dict_arg);
 int get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change);
+int find_visible_prop(win_T *wp, int type_id, int id, textprop_T *prop, linenr_T *found_lnum);
 proptype_T *text_prop_type_by_id(buf_T *buf, int id);
 void f_prop_clear(typval_T *argvars, typval_T *rettv);
 void f_prop_list(typval_T *argvars, typval_T *rettv);
index 15deda7afb3f388d78d349fad4d6205cdf0272b4..35a22b10218cefaca047a9e91494d0283af67402 100644 (file)
@@ -3022,6 +3022,9 @@ struct window_S
     char_u     *w_popup_title;
     poppos_T   w_popup_pos;
     int                w_popup_fixed;      // do not shift popup to fit on screen
+    int                w_popup_prop_type;  // when not zero: textprop type ID
+    win_T      *w_popup_prop_win;  // window to search for textprop
+    int                w_popup_prop_id;    // when not zero: textprop ID
     int                w_zindex;
     int                w_minheight;        // "minheight" for popup window
     int                w_minwidth;         // "minwidth" for popup window
@@ -3041,8 +3044,14 @@ struct window_S
 
     int                w_popup_leftoff;    // columns left of the screen
     int                w_popup_rightoff;   // columns right of the screen
-    varnumber_T        w_popup_last_changedtick; // b:changedtick when position was
-                                         // computed
+    varnumber_T        w_popup_last_changedtick; // b:changedtick of popup buffer
+                                         // when position was computed
+    varnumber_T        w_popup_prop_changedtick; // b:changedtick of buffer with
+                                         // w_popup_prop_type when position
+                                         // was computed
+    int                w_popup_prop_topline; // w_topline of window with
+                                     // w_popup_prop_type when position was
+                                     // computed
     callback_T w_close_cb;         // popup close callback
     callback_T w_filter_cb;        // popup filter callback
 
index 93787f3c46259bd104b444b5306ecb2b0df67426..7947d348f772e5cb78bbd3a07f93fa7c6bbe7812 100644 (file)
@@ -90,6 +90,20 @@ find_prop(char_u *name, buf_T *buf)
     return HI2PT(hi);
 }
 
+/*
+ * Get the prop type ID of "name".
+ * When not found return zero.
+ */
+    int
+find_prop_type_id(char_u *name, buf_T *buf)
+{
+    proptype_T *pt = find_prop(name, buf);
+
+    if (pt == NULL)
+       return 0;
+    return pt->pt_id;
+}
+
 /*
  * Lookup a property type by name.  First in "buf" and when not found in the
  * global types.
@@ -367,6 +381,40 @@ get_text_props(buf_T *buf, linenr_T lnum, char_u **props, int will_change)
     return (int)(proplen / sizeof(textprop_T));
 }
 
+/*
+ * Find text property "type_id" in the visible lines of window "wp".
+ * Match "id" when it is > 0.
+ * Returns FAIL when not found.
+ */
+    int
+find_visible_prop(win_T *wp, int type_id, int id, textprop_T *prop,
+                                                         linenr_T *found_lnum)
+{
+    linenr_T           lnum;
+    char_u             *props;
+    int                        count;
+    int                        i;
+
+    // w_botline may not have been updated yet.
+    if (wp->w_botline > wp->w_buffer->b_ml.ml_line_count)
+       wp->w_botline = wp->w_buffer->b_ml.ml_line_count + 1;
+    for (lnum = wp->w_topline; lnum < wp->w_botline; ++lnum)
+    {
+       count = get_text_props(wp->w_buffer, lnum, &props, FALSE);
+       for (i = 0; i < count; ++i)
+       {
+           mch_memmove(prop, props + i * sizeof(textprop_T),
+                                                          sizeof(textprop_T));
+           if (prop->tp_type == type_id && (id <= 0 || prop->tp_id == id))
+           {
+               *found_lnum = lnum;
+               return OK;
+           }
+       }
+    }
+    return FAIL;
+}
+
 /*
  * Set the text properties for line "lnum" to "props" with length "len".
  * If "len" is zero text properties are removed, "props" is not used.
index 264a0b90e77217fbdf942eceaaf2656774f18bb9..e317c8fd102573eeb591538cc673e7f13b4fdb3c 100644 (file)
@@ -761,6 +761,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1928,
 /**/
     1927,
 /**/
index 8da5ed0c938da09659547ef8542927d9e6970e01..0507cee667b8bbec99c89bc9d5e9181b04cb355b 100644 (file)
@@ -2522,6 +2522,10 @@ win_close(win_T *win, int free_buf)
        out_flush();
 #endif
 
+#ifdef FEAT_TEXT_PROP
+    if (popup_win_closed(win) && !win_valid(win))
+       return FAIL;
+#endif
     win_close_buffer(win, free_buf ? DOBUF_UNLOAD : 0, TRUE);
 
     if (only_one_window() && win_valid(win) && win->w_buffer == NULL