Hi Bram - this is outstanding, thank you.

One small thing I noticed is that the popup window "balloon" is shown
botleft (see problem_1.png, attached). Is this intentional?

I was expecting the "balloon" to appear below and to the right of the
mouse, and hence that popup create option to be topleft?


Paul

On Sun, 7 Jul 2019 at 17:29, Bram Moolenaar <[email protected]> wrote:
>
>
> Patch 8.1.1645
> Problem:    Cannot use a popup window for a balloon.
> Solution:   Add popup_beval().  Add the "mousemoved" property.  Add the
>             screenpos() function.
> Files:      src/popupwin.c, src/proto/popupwin.pro, src/move.c,
>             src/proto/move.pro, src/beval.c, src/proto/beval.pro,
>             src/evalfunc.c, src/popupmnu.c, src/normal.c,
>             src/testdir/test_popupwin.vim, src/testdir/test_cursor_func.vim,
>             runtime/doc/popup.txt, runtime/doc/eval.txt,
>             runtime/doc/usr_41.txt,
>             src/testdir/dumps/Test_popupwin_beval_1.dump,
>             src/testdir/dumps/Test_popupwin_beval_2.dump,
>             src/testdir/dumps/Test_popupwin_beval_3.dump
>
>
> *** ../vim-8.1.1644/src/popupwin.c      2019-07-05 20:17:18.928510171 +0200
> --- src/popupwin.c      2019-07-07 16:59:37.333487332 +0200
> ***************
> *** 168,173 ****
> --- 168,202 ----
>   }
>
>   /*
> +  * Used when popup options contain "mousemoved": set default moved values.
> +  */
> +     static void
> + set_mousemoved_values(win_T *wp)
> + {
> +     wp->w_popup_mouse_row = mouse_row;
> +     wp->w_popup_mouse_mincol = mouse_col;
> +     wp->w_popup_mouse_maxcol = mouse_col;
> + }
> +
> + /*
> +  * Used when popup options contain "moved" with "word" or "WORD".
> +  */
> +     static void
> + set_mousemoved_columns(win_T *wp, int flags)
> + {
> +     char_u    *text;
> +     int               col;
> +
> +     if (find_word_under_cursor(mouse_row, mouse_col, TRUE, flags,
> +                                               NULL, NULL, &text, &col) == 
> OK)
> +     {
> +       wp->w_popup_mouse_mincol = col;
> +       wp->w_popup_mouse_maxcol = col + STRLEN(text) - 1;
> +       vim_free(text);
> +     }
> + }
> +
> + /*
>    * Return TRUE if "row"/"col" is on the border of the popup.
>    * The values are relative to the top-left corner.
>    */
> ***************
> *** 336,341 ****
> --- 365,417 ----
>   }
>
>       static void
> + handle_moved_argument(win_T *wp, dictitem_T *di, int mousemoved)
> + {
> +     if (di->di_tv.v_type == VAR_STRING && di->di_tv.vval.v_string != NULL)
> +     {
> +       char_u  *s = di->di_tv.vval.v_string;
> +       int     flags = 0;
> +
> +       if (STRCMP(s, "word") == 0)
> +           flags = FIND_IDENT | FIND_STRING;
> +       else if (STRCMP(s, "WORD") == 0)
> +           flags = FIND_STRING;
> +       else if (STRCMP(s, "expr") == 0)
> +           flags = FIND_IDENT | FIND_STRING | FIND_EVAL;
> +       else if (STRCMP(s, "any") != 0)
> +           semsg(_(e_invarg2), s);
> +       if (flags != 0)
> +       {
> +           if (mousemoved)
> +               set_mousemoved_columns(wp, flags);
> +           else
> +               set_moved_columns(wp, flags);
> +       }
> +     }
> +     else if (di->di_tv.v_type == VAR_LIST
> +           && di->di_tv.vval.v_list != NULL
> +           && di->di_tv.vval.v_list->lv_len == 2)
> +     {
> +       list_T  *l = di->di_tv.vval.v_list;
> +       int     mincol = tv_get_number(&l->lv_first->li_tv);
> +       int     maxcol = tv_get_number(&l->lv_first->li_next->li_tv);
> +
> +       if (mousemoved)
> +       {
> +           wp->w_popup_mouse_mincol = mincol;
> +           wp->w_popup_mouse_maxcol = maxcol;
> +       }
> +       else
> +       {
> +           wp->w_popup_mincol = mincol;
> +           wp->w_popup_maxcol = maxcol;
> +       }
> +     }
> +     else
> +       semsg(_(e_invarg2), tv_get_string(&di->di_tv));
> + }
> +
> +     static void
>   check_highlight(dict_T *dict, char *name, char_u **pval)
>   {
>       dictitem_T  *di;
> ***************
> *** 541,571 ****
>       if (di != NULL)
>       {
>         set_moved_values(wp);
> !       if (di->di_tv.v_type == VAR_STRING && di->di_tv.vval.v_string != NULL)
> !       {
> !           char_u  *s = di->di_tv.vval.v_string;
> !           int     flags = 0;
> !
> !           if (STRCMP(s, "word") == 0)
> !               flags = FIND_IDENT | FIND_STRING;
> !           else if (STRCMP(s, "WORD") == 0)
> !               flags = FIND_STRING;
> !           else if (STRCMP(s, "any") != 0)
> !               semsg(_(e_invarg2), s);
> !           if (flags != 0)
> !               set_moved_columns(wp, flags);
> !       }
> !       else if (di->di_tv.v_type == VAR_LIST
> !               && di->di_tv.vval.v_list != NULL
> !               && di->di_tv.vval.v_list->lv_len == 2)
> !       {
> !           list_T *l = di->di_tv.vval.v_list;
>
> !           wp->w_popup_mincol = tv_get_number(&l->lv_first->li_tv);
> !           wp->w_popup_maxcol = tv_get_number(&l->lv_first->li_next->li_tv);
> !       }
> !       else
> !           semsg(_(e_invarg2), tv_get_string(&di->di_tv));
>       }
>
>       di = dict_find(dict, (char_u *)"filter", -1);
> --- 617,630 ----
>       if (di != NULL)
>       {
>         set_moved_values(wp);
> !       handle_moved_argument(wp, di, FALSE);
> !     }
>
> !     di = dict_find(dict, (char_u *)"mousemoved", -1);
> !     if (di != NULL)
> !     {
> !       set_mousemoved_values(wp);
> !       handle_moved_argument(wp, di, TRUE);
>       }
>
>       di = dict_find(dict, (char_u *)"filter", -1);
> ***************
> *** 956,961 ****
> --- 1015,1021 ----
>   {
>       TYPE_NORMAL,
>       TYPE_ATCURSOR,
> +     TYPE_BEVAL,
>       TYPE_NOTIFICATION,
>       TYPE_DIALOG,
>       TYPE_MENU
> ***************
> *** 1137,1153 ****
>       {
>         wp->w_popup_pos = POPPOS_BOTLEFT;
>         setcursor_mayforce(TRUE);
> !       wp->w_wantline = screen_screenrow();
>         if (wp->w_wantline == 0)  // cursor in first line
>         {
>             wp->w_wantline = 2;
>             wp->w_popup_pos = POPPOS_TOPLEFT;
>         }
> !       wp->w_wantcol = screen_screencol() + 1;
>         set_moved_values(wp);
>         set_moved_columns(wp, FIND_STRING);
>       }
>
>       // set default values
>       wp->w_zindex = POPUPWIN_DEFAULT_ZINDEX;
>       wp->w_popup_close = POPCLOSE_NONE;
> --- 1197,1229 ----
>       {
>         wp->w_popup_pos = POPPOS_BOTLEFT;
>         setcursor_mayforce(TRUE);
> !       wp->w_wantline = curwin->w_winrow + curwin->w_wrow;
>         if (wp->w_wantline == 0)  // cursor in first line
>         {
>             wp->w_wantline = 2;
>             wp->w_popup_pos = POPPOS_TOPLEFT;
>         }
> !       wp->w_wantcol = curwin->w_wincol + curwin->w_wcol + 1;
>         set_moved_values(wp);
>         set_moved_columns(wp, FIND_STRING);
>       }
>
> +     if (type == TYPE_BEVAL)
> +     {
> +       wp->w_popup_pos = POPPOS_BOTLEFT;
> +
> +       // by default use the mouse position
> +       wp->w_wantline = mouse_row;
> +       if (wp->w_wantline <= 0)  // mouse on first line
> +       {
> +           wp->w_wantline = 2;
> +           wp->w_popup_pos = POPPOS_TOPLEFT;
> +       }
> +       wp->w_wantcol = mouse_col + 1;
> +       set_mousemoved_values(wp);
> +       set_mousemoved_columns(wp, FIND_IDENT + FIND_STRING + FIND_EVAL);
> +     }
> +
>       // set default values
>       wp->w_zindex = POPUPWIN_DEFAULT_ZINDEX;
>       wp->w_popup_close = POPCLOSE_NONE;
> ***************
> *** 1276,1281 ****
> --- 1352,1366 ----
>   }
>
>   /*
> +  * popup_beval({text}, {options})
> +  */
> +     void
> + f_popup_beval(typval_T *argvars, typval_T *rettv)
> + {
> +     popup_create(argvars, rettv, TYPE_BEVAL);
> + }
> +
> + /*
>    * Invoke the close callback for window "wp" with value "result".
>    * Careful: The callback may make "wp" invalid!
>    */
> ***************
> *** 1334,1339 ****
> --- 1419,1466 ----
>       popup_close_and_callback(wp, &res);
>   }
>
> +     static void
> + check_mouse_moved(win_T *wp, win_T *mouse_wp)
> + {
> +     // Close the popup when all if these are true:
> +     // - the mouse is not on this popup
> +     // - "mousemoved" was used
> +     // - the mouse is no longer on the same screen row or the mouse column 
> is
> +     //   outside of the relevant text
> +     if (wp != mouse_wp
> +           && wp->w_popup_mouse_row != 0
> +           && (wp->w_popup_mouse_row != mouse_row
> +               || 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;
> +       popup_close_and_callback(wp, &res);
> +     }
> + }
> +
> + /*
> +  * Called when the mouse moved: may close a popup with "mousemoved".
> +  */
> +     void
> + popup_handle_mouse_moved(void)
> + {
> +     win_T   *wp;
> +     win_T   *mouse_wp;
> +     int           row = mouse_row;
> +     int           col = mouse_col;
> +
> +     // find the window where the mouse is in
> +     mouse_wp = mouse_find_win(&row, &col, FIND_POPUP);
> +
> +     for (wp = first_popupwin; wp != NULL; wp = wp->w_next)
> +       check_mouse_moved(wp, mouse_wp);
> +     for (wp = curtab->tp_first_popupwin; wp != NULL; wp = wp->w_next)
> +       check_mouse_moved(wp, mouse_wp);
> + }
> +
>   /*
>    * In a filter: check if the typed key is a mouse event that is used for
>    * dragging the popup.
> ***************
> *** 1821,1827 ****
>   }
>
>   /*
> !  * For popup_getoptions(): add a "moved" entry to "dict".
>    */
>       static void
>   get_moved_list(dict_T *dict, win_T *wp)
> --- 1948,1954 ----
>   }
>
>   /*
> !  * For popup_getoptions(): add a "moved" and "mousemoved" entry to "dict".
>    */
>       static void
>   get_moved_list(dict_T *dict, win_T *wp)
> ***************
> *** 1832,1840 ****
> --- 1959,1976 ----
>       if (list != NULL)
>       {
>         dict_add_list(dict, "moved", list);
> +       list_append_number(list, wp->w_popup_lnum);
>         list_append_number(list, wp->w_popup_mincol);
>         list_append_number(list, wp->w_popup_maxcol);
>       }
> +     list = list_alloc();
> +     if (list != NULL)
> +     {
> +       dict_add_list(dict, "mousemoved", list);
> +       list_append_number(list, wp->w_popup_mouse_row);
> +       list_append_number(list, wp->w_popup_mouse_mincol);
> +       list_append_number(list, wp->w_popup_mouse_maxcol);
> +     }
>   }
>
>   /*
> *** ../vim-8.1.1644/src/proto/popupwin.pro      2019-06-30 18:04:53.793559360 
> +0200
> --- src/proto/popupwin.pro      2019-07-06 17:06:06.122751209 +0200
> ***************
> *** 11,17 ****
> --- 11,19 ----
>   void f_popup_clear(typval_T *argvars, typval_T *rettv);
>   void f_popup_create(typval_T *argvars, typval_T *rettv);
>   void f_popup_atcursor(typval_T *argvars, typval_T *rettv);
> + void f_popup_beval(typval_T *argvars, typval_T *rettv);
>   void popup_close_for_mouse_click(win_T *wp);
> + void popup_handle_mouse_moved(void);
>   void f_popup_filter_menu(typval_T *argvars, typval_T *rettv);
>   void f_popup_filter_yesno(typval_T *argvars, typval_T *rettv);
>   void f_popup_dialog(typval_T *argvars, typval_T *rettv);
> *** ../vim-8.1.1644/src/move.c  2019-03-30 18:46:57.356077354 +0100
> --- src/move.c  2019-07-07 17:02:20.284693207 +0200
> ***************
> *** 1189,1194 ****
> --- 1189,1284 ----
>       curwin->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL;
>   }
>
> + #if defined(FEAT_EVAL) || 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
> + textpos2screenpos(
> +       win_T   *wp,
> +       pos_T   *pos,
> +       int     *rowp,  // screen row
> +       int     *scolp, // start screen column
> +       int     *ccolp, // cursor screen column
> +       int     *ecolp) // end screen column
> + {
> +     colnr_T   scol = 0, ccol = 0, ecol = 0;
> +     int               row = 0;
> +     int               rowoff = 0;
> +     colnr_T   coloff = 0;
> +
> +     if (pos->lnum >= wp->w_topline && pos->lnum < wp->w_botline)
> +     {
> +       colnr_T off;
> +       colnr_T col;
> +       int     width;
> +
> +       row = plines_m_win(wp, wp->w_topline, pos->lnum - 1) + 1;
> +       getvcol(wp, pos, &scol, &ccol, &ecol);
> +
> +       // similar to what is done in validate_cursor_col()
> +       col = scol;
> +       off = win_col_off(wp);
> +       col += off;
> +       width = wp->w_width - off + win_col_off2(wp);
> +
> +       /* 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() */
> +           rowoff = ((col - wp->w_width) / width + 1);
> +           col -= rowoff * width;
> +       }
> +       col -= wp->w_leftcol;
> +       if (col >= width)
> +           col = -1;
> +       if (col >= 0)
> +           coloff = col - scol + wp->w_wincol + 1;
> +       else
> +           // character is left or right of the window
> +           row = scol = ccol = ecol = 0;
> +     }
> +     *rowp = wp->w_winrow + row + rowoff;
> +     *scolp = scol + coloff;
> +     *ccolp = ccol + coloff;
> +     *ecolp = ecol + coloff;
> + }
> +
> + /*
> +  * "screenpos({winid}, {lnum}, {col})" function
> +  */
> +     void
> + f_screenpos(typval_T *argvars UNUSED, typval_T *rettv)
> + {
> +     dict_T    *dict;
> +     win_T     *wp;
> +     pos_T     pos;
> +     int               row = 0;
> +     int               scol = 0, ccol = 0, ecol = 0;
> +
> +     if (rettv_dict_alloc(rettv) != OK)
> +       return;
> +     dict = rettv->vval.v_dict;
> +
> +     wp = find_win_by_nr_or_id(&argvars[0]);
> +     if (wp == NULL)
> +       return;
> +
> +     pos.lnum = tv_get_number(&argvars[1]);
> +     pos.col = tv_get_number(&argvars[2]) - 1;
> +     pos.coladd = 0;
> +     textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol);
> +
> +     dict_add_number(dict, "row", row);
> +     dict_add_number(dict, "col", scol);
> +     dict_add_number(dict, "curscol", ccol);
> +     dict_add_number(dict, "endcol", ecol);
> + }
> + #endif
> +
>   /*
>    * Scroll the current window down by "line_count" logical lines.  "CTRL-Y"
>    */
> *** ../vim-8.1.1644/src/proto/move.pro  2019-01-31 13:22:28.068543628 +0100
> --- src/proto/move.pro  2019-07-07 17:02:26.788661232 +0200
> ***************
> *** 27,32 ****
> --- 27,33 ----
>   int win_col_off2(win_T *wp);
>   int curwin_col_off2(void);
>   void curs_columns(int may_scroll);
> + void f_screenpos(typval_T *argvars, typval_T *rettv);
>   void scrolldown(long line_count, int byfold);
>   void scrollup(long line_count, int byfold);
>   void check_topfill(win_T *wp, int down);
> *** ../vim-8.1.1644/src/beval.c 2019-06-12 20:21:57.737817533 +0200
> --- src/beval.c 2019-07-07 16:31:17.941627410 +0200
> ***************
> *** 14,20 ****
>
>   /*
>    * Get the text and position to be evaluated for "beval".
> !  * If "getword" is true the returned text is not the whole line but the
>    * relevant word in allocated memory.
>    * Returns OK or FAIL.
>    */
> --- 14,20 ----
>
>   /*
>    * Get the text and position to be evaluated for "beval".
> !  * If "getword" is TRUE the returned text is not the whole line but the
>    * relevant word in allocated memory.
>    * Returns OK or FAIL.
>    */
> ***************
> *** 27,38 ****
>       char_u    **textp,
>       int               *colp)
>   {
> -     win_T     *wp;
>       int               row, col;
> -     char_u    *lbuf;
> -     linenr_T  lnum;
>
> -     *textp = NULL;
>   # ifdef FEAT_BEVAL_TERM
>   #  ifdef FEAT_GUI
>       if (!gui.in_use)
> --- 27,34 ----
> ***************
> *** 49,70 ****
>         col = X_2_COL(beval->x);
>       }
>   #endif
>       wp = mouse_find_win(&row, &col, FAIL_POPUP);
>       if (wp != NULL && row >= 0 && row < wp->w_height && col < wp->w_width)
>       {
> !       /* Found a window and the cursor is in the text.  Now find the line
> !        * number. */
>         if (!mouse_comp_pos(wp, &row, &col, &lnum))
>         {
> !           /* Not past end of the file. */
>             lbuf = ml_get_buf(wp->w_buffer, lnum, FALSE);
>             if (col <= win_linetabsize(wp, lbuf, (colnr_T)MAXCOL))
>             {
> !               /* Not past end of line. */
>                 if (getword)
>                 {
> !                   /* For Netbeans we get the relevant part of the line
> !                    * instead of the whole line. */
>                     int         len;
>                     pos_T       *spos = NULL, *epos = NULL;
>
> --- 45,112 ----
>         col = X_2_COL(beval->x);
>       }
>   #endif
> +     if (find_word_under_cursor(row, col, getword,
> +               FIND_IDENT + FIND_STRING + FIND_EVAL,
> +               winp, lnump, textp, colp) == OK)
> +     {
> + #ifdef FEAT_VARTABS
> +       vim_free(beval->vts);
> +       beval->vts = tabstop_copy((*winp)->w_buffer->b_p_vts_array);
> +       if ((*winp)->w_buffer->b_p_vts_array != NULL && beval->vts == NULL)
> +       {
> +           if (getword)
> +               vim_free(*textp);
> +           return FAIL;
> +       }
> + #endif
> +       beval->ts = (*winp)->w_buffer->b_p_ts;
> +       return OK;
> +     }
> +
> +     return FAIL;
> + }
> +
> + /*
> +  * Find text under the mouse position "row" / "col".
> +  * If "getword" is TRUE the returned text in "*textp" is not the whole line 
> but
> +  * the relevant word in allocated memory.
> +  * Return OK if found.
> +  * Return FAIL if not found, no text at the mouse position.
> +  */
> +     int
> + find_word_under_cursor(
> +       int         mouserow,
> +       int         mousecol,
> +       int         getword,
> +       int         flags,      // flags for find_ident_at_pos()
> +       win_T       **winp,     // can be NULL
> +       linenr_T    *lnump,     // can be NULL
> +       char_u      **textp,
> +       int         *colp)
> + {
> +     int               row = mouserow;
> +     int               col = mousecol;
> +     win_T     *wp;
> +     char_u    *lbuf;
> +     linenr_T  lnum;
> +
> +     *textp = NULL;
>       wp = mouse_find_win(&row, &col, FAIL_POPUP);
>       if (wp != NULL && row >= 0 && row < wp->w_height && col < wp->w_width)
>       {
> !       // Found a window and the cursor is in the text.  Now find the line
> !       // number.
>         if (!mouse_comp_pos(wp, &row, &col, &lnum))
>         {
> !           // Not past end of the file.
>             lbuf = ml_get_buf(wp->w_buffer, lnum, FALSE);
>             if (col <= win_linetabsize(wp, lbuf, (colnr_T)MAXCOL))
>             {
> !               // Not past end of line.
>                 if (getword)
>                 {
> !                   // For Netbeans we get the relevant part of the line
> !                   // instead of the whole line.
>                     int         len;
>                     pos_T       *spos = NULL, *epos = NULL;
>
> ***************
> *** 93,101 ****
>                                 ? col <= (int)epos->col
>                                 : lnum < epos->lnum))
>                     {
> !                       /* Visual mode and pointing to the line with the
> !                        * Visual selection: return selected text, with a
> !                        * maximum of one line. */
>                         if (spos->lnum != epos->lnum || spos->col == 
> epos->col)
>                             return FAIL;
>
> --- 135,143 ----
>                                 ? col <= (int)epos->col
>                                 : lnum < epos->lnum))
>                     {
> !                       // Visual mode and pointing to the line with the
> !                       // Visual selection: return selected text, with a
> !                       // maximum of one line.
>                         if (spos->lnum != epos->lnum || spos->col == 
> epos->col)
>                             return FAIL;
>
> ***************
> *** 109,118 ****
>                     }
>                     else
>                     {
> !                       /* Find the word under the cursor. */
>                         ++emsg_off;
>                         len = find_ident_at_pos(wp, lnum, (colnr_T)col, &lbuf,
> !                                       FIND_IDENT + FIND_STRING + FIND_EVAL);
>                         --emsg_off;
>                         if (len == 0)
>                             return FAIL;
> --- 151,160 ----
>                     }
>                     else
>                     {
> !                       // Find the word under the cursor.
>                         ++emsg_off;
>                         len = find_ident_at_pos(wp, lnum, (colnr_T)col, &lbuf,
> !                                                                       
> flags);
>                         --emsg_off;
>                         if (len == 0)
>                             return FAIL;
> ***************
> *** 120,141 ****
>                     }
>                 }
>
> !               *winp = wp;
> !               *lnump = lnum;
>                 *textp = lbuf;
>                 *colp = col;
> - #ifdef FEAT_VARTABS
> -               vim_free(beval->vts);
> -               beval->vts = tabstop_copy(wp->w_buffer->b_p_vts_array);
> -               if (wp->w_buffer->b_p_vts_array != NULL && beval->vts == NULL)
> -                   return FAIL;
> - #endif
> -               beval->ts = wp->w_buffer->b_p_ts;
>                 return OK;
>             }
>         }
>       }
> -
>       return FAIL;
>   }
>
> --- 162,177 ----
>                     }
>                 }
>
> !               if (winp != NULL)
> !                   *winp = wp;
> !               if (lnump != NULL)
> !                   *lnump = lnum;
>                 *textp = lbuf;
>                 *colp = col;
>                 return OK;
>             }
>         }
>       }
>       return FAIL;
>   }
>
> *** ../vim-8.1.1644/src/proto/beval.pro 2018-05-17 13:52:56.000000000 +0200
> --- src/proto/beval.pro 2019-07-07 16:04:03.689341355 +0200
> ***************
> *** 1,5 ****
> --- 1,6 ----
>   /* beval.c */
>   int get_beval_info(BalloonEval *beval, int getword, win_T **winp, linenr_T 
> *lnump, char_u **textp, int *colp);
> + int find_word_under_cursor(int mouserow, int mousecol, int getword, int 
> flags, win_T **winp, linenr_T *lnump, char_u **textp, int *colp);
>   void post_balloon(BalloonEval *beval, char_u *mesg, list_T *list);
>   int can_use_beval(void);
>   void general_beval_cb(BalloonEval *beval, int state);
> *** ../vim-8.1.1644/src/evalfunc.c      2019-07-04 15:39:23.823385977 +0200
> --- src/evalfunc.c      2019-07-06 15:58:24.308858997 +0200
> ***************
> *** 771,776 ****
> --- 771,777 ----
>   #endif
>   #ifdef FEAT_TEXT_PROP
>       {"popup_atcursor",        2, 2, f_popup_atcursor},
> +     {"popup_beval",   2, 2, f_popup_beval},
>       {"popup_clear",   0, 0, f_popup_clear},
>       {"popup_close",   1, 2, f_popup_close},
>       {"popup_create",  2, 2, f_popup_create},
> ***************
> *** 849,854 ****
> --- 850,856 ----
>       {"screenchar",    2, 2, f_screenchar},
>       {"screenchars",   2, 2, f_screenchars},
>       {"screencol",     0, 0, f_screencol},
> +     {"screenpos",     3, 3, f_screenpos},
>       {"screenrow",     0, 0, f_screenrow},
>       {"screenstring",  2, 2, f_screenstring},
>       {"search",                1, 4, f_search},
> *** ../vim-8.1.1644/src/popupmnu.c      2019-06-15 19:37:11.680608505 +0200
> --- src/popupmnu.c      2019-07-06 16:41:49.828506031 +0200
> ***************
> *** 992,999 ****
>   # if defined(FEAT_BEVAL_TERM) || defined(PROTO)
>   static pumitem_T *balloon_array = NULL;
>   static int balloon_arraysize;
> - static int balloon_mouse_row = 0;
> - static int balloon_mouse_col = 0;
>
>   #define BALLOON_MIN_WIDTH 50
>   #define BALLOON_MIN_HEIGHT 10
> --- 992,997 ----
> ***************
> *** 1209,1216 ****
>       void
>   ui_may_remove_balloon(void)
>   {
> !     if (mouse_row != balloon_mouse_row || mouse_col != balloon_mouse_col)
> !       ui_remove_balloon();
>   }
>   # endif
>
> --- 1207,1215 ----
>       void
>   ui_may_remove_balloon(void)
>   {
> !     // For now: remove the balloon whenever the mouse moves to another 
> screen
> !     // cell.
> !     ui_remove_balloon();
>   }
>   # endif
>
> *** ../vim-8.1.1644/src/normal.c        2019-06-30 22:16:06.931821750 +0200
> --- src/normal.c        2019-07-06 16:43:04.132100090 +0200
> ***************
> *** 2329,2334 ****
> --- 2329,2337 ----
>             bevalexpr_due_set = TRUE;
>         }
>   #endif
> + #ifdef FEAT_TEXT_PROP
> +       popup_handle_mouse_moved();
> + #endif
>         return FALSE;
>       }
>
> *** ../vim-8.1.1644/src/testdir/test_popupwin.vim       2019-07-05 
> 21:53:14.939268472 +0200
> --- src/testdir/test_popupwin.vim       2019-07-07 18:18:14.652578345 +0200
> ***************
> *** 1005,1010 ****
> --- 1005,1057 ----
>     bwipe!
>   endfunc
>
> + func Test_popup_beval()
> +   if !CanRunVimInTerminal()
> +     throw 'Skipped: cannot make screendumps'
> +   endif
> +
> +   let lines =<< trim END
> +       call setline(1, range(1, 20))
> +       call setline(5, 'here is some text to hover over')
> +       set balloonevalterm
> +       set balloonexpr=BalloonExpr()
> +       set balloondelay=100
> +       func BalloonExpr()
> +         let s:winid = popup_beval([v:beval_text], {})
> +         return ''
> +       endfunc
> +       func Hover()
> +         call test_setmouse(5, 15)
> +         call feedkeys("\<MouseMove>\<Ignore>", "xt")
> +         sleep 100m
> +       endfunc
> +       func MoveOntoPopup()
> +         call test_setmouse(4, 17)
> +         call feedkeys("\<F4>\<MouseMove>\<Ignore>", "xt")
> +       endfunc
> +       func MoveAway()
> +         call test_setmouse(5, 13)
> +         call feedkeys("\<F5>\<MouseMove>\<Ignore>", "xt")
> +       endfunc
> +   END
> +   call writefile(lines, 'XtestPopupBeval')
> +   let buf = RunVimInTerminal('-S XtestPopupBeval', {'rows': 10})
> +   call term_wait(buf, 100)
> +   call term_sendkeys(buf, 'j')
> +   call term_sendkeys(buf, ":call Hover()\<CR>")
> +   call VerifyScreenDump(buf, 'Test_popupwin_beval_1', {})
> +
> +   call term_sendkeys(buf, ":call MoveOntoPopup()\<CR>")
> +   call VerifyScreenDump(buf, 'Test_popupwin_beval_2', {})
> +
> +   call term_sendkeys(buf, ":call MoveAway()\<CR>")
> +   call VerifyScreenDump(buf, 'Test_popupwin_beval_3', {})
> +
> +   " clean up
> +   call StopVimInTerminal(buf)
> +   call delete('XtestPopupBeval')
> + endfunc
> +
>   func Test_popup_filter()
>     new
>     call setline(1, 'some text')
> ***************
> *** 1413,1419 ****
>     let winid = popup_atcursor('text', {'moved': 'any'})
>     redraw
>     call assert_equal(1, popup_getpos(winid).visible)
> !   call assert_equal([4, 4], popup_getoptions(winid).moved)
>     " trigger the check for last_cursormoved by going into insert mode
>     call feedkeys("li\<Esc>", 'xt')
>     call assert_equal({}, popup_getpos(winid))
> --- 1460,1466 ----
>     let winid = popup_atcursor('text', {'moved': 'any'})
>     redraw
>     call assert_equal(1, popup_getpos(winid).visible)
> !   call assert_equal([1, 4, 4], popup_getoptions(winid).moved)
>     " trigger the check for last_cursormoved by going into insert mode
>     call feedkeys("li\<Esc>", 'xt')
>     call assert_equal({}, popup_getpos(winid))
> ***************
> *** 1423,1429 ****
>     let winid = popup_atcursor('text', {'moved': 'word'})
>     redraw
>     call assert_equal(1, popup_getpos(winid).visible)
> !   call assert_equal([4, 7], popup_getoptions(winid).moved)
>     call feedkeys("hi\<Esc>", 'xt')
>     call assert_equal({}, popup_getpos(winid))
>     call popup_clear()
> --- 1470,1476 ----
>     let winid = popup_atcursor('text', {'moved': 'word'})
>     redraw
>     call assert_equal(1, popup_getpos(winid).visible)
> !   call assert_equal([1, 4, 7], popup_getoptions(winid).moved)
>     call feedkeys("hi\<Esc>", 'xt')
>     call assert_equal({}, popup_getpos(winid))
>     call popup_clear()
> ***************
> *** 1432,1438 ****
>     let winid = popup_atcursor('text', {'moved': 'word'})
>     redraw
>     call assert_equal(1, popup_getpos(winid).visible)
> !   call assert_equal([4, 7], popup_getoptions(winid).moved)
>     call feedkeys("li\<Esc>", 'xt')
>     call assert_equal(1, popup_getpos(winid).visible)
>     call feedkeys("ei\<Esc>", 'xt')
> --- 1479,1485 ----
>     let winid = popup_atcursor('text', {'moved': 'word'})
>     redraw
>     call assert_equal(1, popup_getpos(winid).visible)
> !   call assert_equal([1, 4, 7], popup_getoptions(winid).moved)
>     call feedkeys("li\<Esc>", 'xt')
>     call assert_equal(1, popup_getpos(winid).visible)
>     call feedkeys("ei\<Esc>", 'xt')
> ***************
> *** 1446,1452 ****
>     let winid = popup_atcursor('text', {})
>     redraw
>     call assert_equal(1, popup_getpos(winid).visible)
> !   call assert_equal([2, 15], popup_getoptions(winid).moved)
>     call feedkeys("eli\<Esc>", 'xt')
>     call assert_equal(1, popup_getpos(winid).visible)
>     call feedkeys("wi\<Esc>", 'xt')
> --- 1493,1499 ----
>     let winid = popup_atcursor('text', {})
>     redraw
>     call assert_equal(1, popup_getpos(winid).visible)
> !   call assert_equal([2, 2, 15], popup_getoptions(winid).moved)
>     call feedkeys("eli\<Esc>", 'xt')
>     call assert_equal(1, popup_getpos(winid).visible)
>     call feedkeys("wi\<Esc>", 'xt')
> *** ../vim-8.1.1644/src/testdir/test_cursor_func.vim    2019-05-16 
> 22:24:52.403017783 +0200
> --- src/testdir/test_cursor_func.vim    2019-07-07 14:42:25.107175433 +0200
> ***************
> *** 72,74 ****
> --- 72,102 ----
>     call assert_equal(6, winsaveview().curswant)
>     quit!
>   endfunc
> +
> + func Test_screenpos()
> +   rightbelow new
> +   rightbelow 20vsplit
> +   call setline(1, ["\tsome text", "long wrapping line here", "next line"])
> +   redraw
> +   let winid = win_getid()
> +   let [winrow, wincol] = win_screenpos(winid)
> +   call assert_equal({'row': winrow,
> +       \ 'col': wincol + 0,
> +       \ 'curscol': wincol + 7,
> +       \ 'endcol': wincol + 7}, screenpos(winid, 1, 1))
> +   call assert_equal({'row': winrow,
> +       \ 'col': wincol + 13,
> +       \ 'curscol': wincol + 13,
> +       \ 'endcol': wincol + 13}, screenpos(winid, 1, 7))
> +   call assert_equal({'row': winrow + 2,
> +       \ 'col': wincol + 1,
> +       \ 'curscol': wincol + 1,
> +       \ 'endcol': wincol + 1}, screenpos(winid, 2, 22))
> +   setlocal number
> +   call assert_equal({'row': winrow + 3,
> +       \ 'col': wincol + 9,
> +       \ 'curscol': wincol + 9,
> +       \ 'endcol': wincol + 9}, screenpos(winid, 2, 22))
> +   close
> +   bwipe!
> + endfunc
> *** ../vim-8.1.1644/runtime/doc/popup.txt       2019-07-04 16:53:21.369654166 
> +0200
> --- runtime/doc/popup.txt       2019-07-07 17:12:34.957616704 +0200
> ***************
> *** 146,151 ****
> --- 146,153 ----
>         |popup_create()|        centered in the screen
>         |popup_atcursor()|      just above the cursor position, closes when
>                                 the cursor moves away
> +       |popup_beval()|         at the position indicated by v:beval_
> +                               variables, closes when the mouse moves away
>         |popup_notification()|  show a notification for three seconds
>         |popup_dialog()|        centered with padding and border
>         |popup_menu()|          prompt for selecting an item from a list
> ***************
> *** 184,189 ****
> --- 186,205 ----
>   <             Use {options} to change the properties.
>
>
> + popup_beval({what}, {options})                        *popup_beval()*
> +               Show the {what} above the position from 'ballooneval' and
> +               close it when the mouse moves.  This works like: >
> +                 let pos = screenpos(v:beval_winnr, v:beval_lnum, 
> v:beval_col)
> +                 call popup_create({what}, {
> +                       \ 'pos': 'botleft',
> +                       \ 'line': pos.lnum - 1,
> +                       \ 'col': pos.col,
> +                       \ 'mousemoved': 'WORD',
> +                       \ })
> + <             Use {options} to change the properties.
> +               See |popup_beval_example| for an example use.
> +
> +
>                                                         *popup_clear()*
>   popup_clear() Emergency solution to a misbehaving plugin: close all popup
>                 windows for the current tab and global popups.
> ***************
> *** 276,283 ****
>                 A zero value means the option was not set.  For "zindex" the
>                 default value is returned, not zero.
>
> !               The "moved" entry is a list with minimum and maximum column,
> !               [0, 0] when not set.
>
>                 "border" and "padding" are not included when all values are
>                 zero.  When all values are one then an empty list is included.
> --- 292,302 ----
>                 A zero value means the option was not set.  For "zindex" the
>                 default value is returned, not zero.
>
> !               The "moved" entry is a list with line number, minimum and
> !               maximum column, [0, 0, 0] when not set.
> !
> !               The "mousemoved" entry is a list with screen row, minimum and
> !               maximum screen column, [0, 0, 0] when not set.
>
>                 "border" and "padding" are not included when all values are
>                 zero.  When all values are one then an empty list is included.
> ***************
> *** 566,571 ****
> --- 585,591 ----
>                         - "any": if the cursor moved at all
>                         - "word": if the cursor moved outside |<cword>|
>                         - "WORD": if the cursor moved outside |<cWORD>|
> +                       - "expr": if the cursor moved outside |<cexpr>|
>                         - [{start}, {end}]: if the cursor moved before column
>                           {start} or after {end}
>                         The popup also closes if the cursor moves to another
> ***************
> *** 736,740 ****
> --- 756,800 ----
>           return popup_filter_menu(a:id, a:key)
>         endfunc
>   <
> +                                       *popup_beval_example*
> + Example for using a popup window for 'ballooneval': >
> +
> +       set ballooneval balloonevalterm
> +       set balloonexpr=BalloonExpr()
> +       let s:winid = 0
> +
> +       func BalloonExpr()
> +         if s:winid
> +           call popup_close(s:winid)
> +           let s:winid = 0
> +         endif
> +         let s:winid = popup_beval([bufname(v:beval_bufnr), v:beval_text], 
> {})
> +         return ''
> +       endfunc
> + <
> + If the text has to be obtained asynchronously return an empty string from 
> the
> + expression function and call popup_beval() once the text is available.  In
> + this example similated with a timer callback: >
> +
> +       set ballooneval balloonevalterm
> +       set balloonexpr=BalloonExpr()
> +       let s:winid = 0
> +
> +       func BalloonExpr()
> +         if s:winid
> +           call popup_close(s:winid)
> +           let s:winid = 0
> +         endif
> +         " simulate an asynchronous loopup for the text to display
> +         let s:balloonFile = bufname(v:beval_bufnr)
> +         let s:balloonWord = v:beval_text
> +         call timer_start(100, 'ShowPopup')
> +         return ''
> +       endfunc
> +
> +       func ShowPopup(id)
> +         let s:winid = popup_beval([s:balloonFile, s:balloonWord], {})
> +       endfunc
> + <
>
>    vim:tw=78:ts=8:noet:ft=help:norl:
> *** ../vim-8.1.1644/runtime/doc/eval.txt        2019-07-04 17:11:16.799440884 
> +0200
> --- runtime/doc/eval.txt        2019-07-07 14:41:25.071768307 +0200
> ***************
> *** 2534,2539 ****
> --- 2535,2541 ----
>   pathshorten({expr})           String  shorten directory names in a path
>   perleval({expr})              any     evaluate |Perl| expression
>   popup_atcursor({what}, {options}) Number create popup window near the cursor
> + popup_beval({what}, {options})        Number  create popup window for 
> 'ballooneval'
>   popup_clear()                 none    close all popup windows
>   popup_close({id} [, {result}])        none    close popup window {id}
>   popup_create({what}, {options}) Number        create a popup window
> ***************
> *** 2612,2617 ****
> --- 2614,2620 ----
>   screenchar({row}, {col})      Number  character at screen position
>   screenchars({row}, {col})     List    List of characters at screen position
>   screencol()                   Number  current cursor column
> + screenpos({winid}, {lnum}, {col}) Dict        screen row and col of a text 
> character
>   screenrow()                   Number  current cursor row
>   screenstring({row}, {col})    String  characters at screen position
>   search({pattern} [, {flags} [, {stopline} [, {timeout}]]])
> ***************
> *** 7906,7911 ****
> --- 7909,7931 ----
>                         nnoremap <expr> GG ":echom ".screencol()."\n"
>                         nnoremap <silent> GG :echom screencol()<CR>
>   <
> + screenpos({winid}, {lnum}, {col})                             *screenpos()*
> +               The result is a Dict with the screen position of the text
> +               character in window {winid} at buffer line {lnum} and column
> +               {col}.  {col} is a one-based byte index.
> +               The Dict has these members:
> +                       row     screen row
> +                       col     first screen column
> +                       endcol  last screen column
> +                       curscol cursor screen column
> +               If the specified position is not visible, all values are zero.
> +               The "endcol" value differs from "col" when the character
> +               occupies more than one screen cell.  E.g. for a Tab "col" can
> +               be 1 and "endcol" can be 8.
> +               The "curscol" value is where the cursor would be placed.  For
> +               a Tab it would be the same as "endcol", while for a double
> +               width character it would be the same as "col".
> +
>   screenrow()                                                   *screenrow()*
>                 The result is a Number, which is the current screen row of the
>                 cursor.  The top line has number one.
> *** ../vim-8.1.1644/runtime/doc/usr_41.txt      2019-07-04 16:53:21.373654143 
> +0200
> --- runtime/doc/usr_41.txt      2019-07-06 15:16:12.426341319 +0200
> ***************
> *** 720,725 ****
> --- 720,726 ----
>         cursor()                position the cursor at a line/column
>         screencol()             get screen column of the cursor
>         screenrow()             get screen row of the cursor
> +       screenpos()             screen row and col of a text character
>         getcurpos()             get position of the cursor
>         getpos()                get position of cursor, mark, etc.
>         setpos()                set position of cursor, mark, etc.
> ***************
> *** 1046,1051 ****
> --- 1047,1054 ----
>         popup_create()          create popup centered in the screen
>         popup_atcursor()        create popup just above the cursor position,
>                                 closes when the cursor moves away
> +       popup_beval()           at the position indicated by v:beval_
> +                               variables, closes when the mouse moves away
>         popup_notification()    show a notification for three seconds
>         popup_dialog()          create popup centered with padding and border
>         popup_menu()            prompt for selecting an item from a list
> *** ../vim-8.1.1644/src/testdir/dumps/Test_popupwin_beval_1.dump        
> 2019-07-07 18:22:04.315270748 +0200
> --- src/testdir/dumps/Test_popupwin_beval_1.dump        2019-07-07 
> 18:15:51.053411015 +0200
> ***************
> *** 0 ****
> --- 1,10 ----
> + |1+0&#ffffff0| @73
> + >2| @73
> + |3| @73
> + |4| @12|t+0#0000001#ffd7ff255|e|x|t| +0#0000000#ffffff0@56
> + |h|e|r|e| |i|s| |s|o|m|e| |t|e|x|t| |t|o| |h|o|v|e|r| |o|v|e|r| @43
> + |6| @73
> + |7| @73
> + |8| @73
> + |9| @73
> + |:|c|a|l@1| |H|o|v|e|r|(|)| @43|2|,|1| @10|T|o|p|
> *** ../vim-8.1.1644/src/testdir/dumps/Test_popupwin_beval_2.dump        
> 2019-07-07 18:22:04.319270725 +0200
> --- src/testdir/dumps/Test_popupwin_beval_2.dump        2019-07-07 
> 18:18:55.720342601 +0200
> ***************
> *** 0 ****
> --- 1,10 ----
> + |1+0&#ffffff0| @73
> + >2| @73
> + |3| @73
> + |4| @12|t+0#0000001#ffd7ff255|e|x|t| +0#0000000#ffffff0@56
> + |h|e|r|e| |i|s| |s|o|m|e| |t|e|x|t| |t|o| |h|o|v|e|r| |o|v|e|r| @43
> + |6| @73
> + |7| @73
> + |8| @73
> + |9| @73
> + |:|c|a|l@1| |M|o|v|e|O|n|t|o|P|o|p|u|p|(|)| @35|2|,|1| @10|T|o|p|
> *** ../vim-8.1.1644/src/testdir/dumps/Test_popupwin_beval_3.dump        
> 2019-07-07 18:22:04.323270704 +0200
> --- src/testdir/dumps/Test_popupwin_beval_3.dump        2019-07-07 
> 18:16:43.881103039 +0200
> ***************
> *** 0 ****
> --- 1,10 ----
> + |1+0&#ffffff0| @73
> + >2| @73
> + |3| @73
> + |4| @73
> + |h|e|r|e| |i|s| |s|o|m|e| |t|e|x|t| |t|o| |h|o|v|e|r| |o|v|e|r| @43
> + |6| @73
> + |7| @73
> + |8| @73
> + |9| @73
> + |:|c|a|l@1| |M|o|v|e|A|w|a|y|(|)| @40|2|,|1| @10|T|o|p|
> *** ../vim-8.1.1644/src/version.c       2019-07-07 15:12:08.765146251 +0200
> --- src/version.c       2019-07-07 18:19:47.056049202 +0200
> ***************
> *** 779,780 ****
> --- 779,782 ----
>   {   /* Add new patch number below this line */
> + /**/
> +     1645,
>   /**/
>
> --
> ARTHUR:  Well, I can't just call you `Man'.
> DENNIS:  Well, you could say `Dennis'.
> ARTHUR:  Well, I didn't know you were called `Dennis.'
> DENNIS:  Well, you didn't bother to find out, did you?
>                                   The Quest for the Holy Grail (Monty Python)
>
>  /// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net   \\\
> ///        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
> \\\  an exciting new programming language -- http://www.Zimbu.org        ///
>  \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///
>
> --
> --
> You received this message from the "vim_dev" maillist.
> Do not top-post! Type your reply below the text you are replying to.
> For more information, visit http://www.vim.org/maillist.php
>
> ---
> You received this message because you are subscribed to the Google Groups 
> "vim_dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to [email protected].
> To view this discussion on the web visit 
> https://groups.google.com/d/msgid/vim_dev/201907071629.x67GT85H010977%40masaka.moolenaar.net.
> For more options, visit https://groups.google.com/d/optout.

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/CACoUkn6oR1k%2BV%2B5XnUo_%2BNsNus4WP%3DAr9vX_X0j8w-ZE4UsZow%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Raspunde prin e-mail lui