]> granicus.if.org Git - vim/commitdiff
patch 8.0.1318: terminal balloon only shows one line v8.0.1318
authorBram Moolenaar <Bram@vim.org>
Sun, 19 Nov 2017 18:56:27 +0000 (19:56 +0100)
committerBram Moolenaar <Bram@vim.org>
Sun, 19 Nov 2017 18:56:27 +0000 (19:56 +0100)
Problem:    Terminal balloon only shows one line.
Solution:   Split into several lines in a clever way.  Add balloon_split().
            Make balloon_show() accept a list in the terminal.

runtime/doc/eval.txt
runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
src/beval.c
src/evalfunc.c
src/popupmnu.c
src/proto/beval.pro
src/proto/popupmnu.pro
src/testdir/test_popup.vim
src/version.c

index c001f6f2b73f71cc993dbe71925be4cd1a8e3726..2ce6c48efcd89e95e30361c5dd0543ea24c0a06a 100644 (file)
@@ -2032,6 +2032,7 @@ asin({expr})                      Float   arc sine of {expr}
 atan({expr})                   Float   arc tangent of {expr}
 atan2({expr1}, {expr2})                Float   arc tangent of {expr1} / {expr2}
 balloon_show({msg})            none    show {msg} inside the balloon
+balloon_split({msg})           List    split {msg} as used for a balloon
 browse({save}, {title}, {initdir}, {default})
                                String  put up a file requester
 browsedir({title}, {initdir})  String  put up a directory requester
@@ -2682,8 +2683,12 @@ atan2({expr1}, {expr2})                                  *atan2()*
 <                      2.356194
                {only available when compiled with the |+float| feature}
 
-balloon_show({msg})                                    *balloon_show()*
-               Show {msg} inside the balloon.
+balloon_show({expr})                                   *balloon_show()*
+               Show {expr} inside the balloon.  For the GUI {expr} is used as
+               a string.  For a terminal {expr} can be a list, which contains
+               the lines of the balloon.  If {expr} is not a list it will be
+               split with |balloon_split()|.
+
                Example: >
                        func GetBalloonContent()
                           " initiate getting the content
@@ -2705,6 +2710,12 @@ balloon_show({msg})                                      *balloon_show()*
                error message.
                {only available when compiled with the +balloon_eval feature}
 
+balloon_split({msg})                                   *balloon_split()*
+               Split {msg} into lines to be displayed in a balloon.  The
+               splits are made for the current window size and optimize to
+               show debugger output.
+               Returns a |List| with the split lines.
+
                                                        *browse()*
 browse({save}, {title}, {initdir}, {default})
                Put up a file requester.  This only works when "has("browse")"
index 1c3c9df9a7a8152b3d0ef067b0d7b90e0b458721..aca56d2a7bd675cef64f04080a738a68288522fe 100644 (file)
@@ -127,9 +127,11 @@ func s:StartDebug(cmd)
   call win_gotoid(s:gdbwin)
 
   " Enable showing a balloon with eval info
-  if has("balloon_eval")
-    set ballooneval
+  if has("balloon_eval") || has("balloon_eval_term")
     set balloonexpr=TermDebugBalloonExpr()
+    if has("balloon_eval")
+      set ballooneval
+    endif
     if has("balloon_eval_term")
       set balloonevalterm
     endif
@@ -158,9 +160,11 @@ func s:EndDebug(job, status)
     let &columns = s:save_columns
   endif
 
-  if has("balloon_eval")
-    set noballooneval
+  if has("balloon_eval") || has("balloon_eval_term")
     set balloonexpr=
+    if has("balloon_eval")
+      set noballooneval
+    endif
     if has("balloon_eval_term")
       set noballoonevalterm
     endif
@@ -366,6 +370,7 @@ func s:HandleError(msg)
   if a:msg =~ 'No symbol .* in current context'
        \ || a:msg =~ 'Cannot access memory at address '
        \ || a:msg =~ 'Attempt to use a type name as an expression'
+       \ || a:msg =~ 'A syntax error in expression,'
     " Result of s:SendEval() failed, ignore.
     return
   endif
index d4705b8b99587661280ab47b0431655d7f833312..f8bb6ba26b223ee9cdad93c11af5f370c356dd2c 100644 (file)
@@ -134,19 +134,20 @@ get_beval_info(
 }
 
 /*
- * Show a balloon with "mesg".
+ * Show a balloon with "mesg" or "list".
  */
     void
-post_balloon(BalloonEval *beval UNUSED, char_u *mesg)
+post_balloon(BalloonEval *beval UNUSED, char_u *mesg, list_T *list)
 {
 # ifdef FEAT_BEVAL_TERM
 #  ifdef FEAT_GUI
     if (!gui.in_use)
 #  endif
-       ui_post_balloon(mesg);
+       ui_post_balloon(mesg, list);
 # endif
 # ifdef FEAT_BEVAL_GUI
     if (gui.in_use)
+       /* GUI can't handle a list */
        gui_mch_post_balloon(beval, mesg);
 # endif
 }
@@ -257,7 +258,7 @@ general_beval_cb(BalloonEval *beval, int state UNUSED)
            set_vim_var_string(VV_BEVAL_TEXT, NULL, -1);
            if (result != NULL && result[0] != NUL)
            {
-               post_balloon(beval, result);
+               post_balloon(beval, result, NULL);
                recursive = FALSE;
                return;
            }
index c03e214165aabf9ecc10daa9eebb5c4c5b8cd81a..77a4fc2c05e82ae0bd3e142cbbf8ddf410866f21 100644 (file)
@@ -61,6 +61,7 @@ static void f_atan2(typval_T *argvars, typval_T *rettv);
 #endif
 #ifdef FEAT_BEVAL
 static void f_balloon_show(typval_T *argvars, typval_T *rettv);
+static void f_balloon_split(typval_T *argvars, typval_T *rettv);
 #endif
 static void f_browse(typval_T *argvars, typval_T *rettv);
 static void f_browsedir(typval_T *argvars, typval_T *rettv);
@@ -494,6 +495,7 @@ static struct fst
 #endif
 #ifdef FEAT_BEVAL
     {"balloon_show",   1, 1, f_balloon_show},
+    {"balloon_split",  1, 1, f_balloon_split},
 #endif
     {"browse",         4, 4, f_browse},
     {"browsedir",      2, 2, f_browsedir},
@@ -1410,7 +1412,37 @@ f_atan2(typval_T *argvars, typval_T *rettv)
 f_balloon_show(typval_T *argvars, typval_T *rettv UNUSED)
 {
     if (balloonEval != NULL)
-       post_balloon(balloonEval, get_tv_string_chk(&argvars[0]));
+    {
+       if (argvars[0].v_type == VAR_LIST
+# ifdef FEAT_GUI
+               && !gui.in_use
+# endif
+          )
+           post_balloon(balloonEval, NULL, argvars[0].vval.v_list);
+       else
+           post_balloon(balloonEval, get_tv_string_chk(&argvars[0]), NULL);
+    }
+}
+
+    static void
+f_balloon_split(typval_T *argvars, typval_T *rettv UNUSED)
+{
+    if (rettv_list_alloc(rettv) == OK)
+    {
+       char_u *msg = get_tv_string_chk(&argvars[0]);
+
+       if (msg != NULL)
+       {
+           pumitem_T   *array;
+           int         size = split_message(msg, &array);
+           int         i;
+
+           /* Skip the first and last item, they are always empty. */
+           for (i = 1; i < size - 1; ++i)
+               list_append_string(rettv->vval.v_list, array[i].pum_text, -1);
+           vim_free(array);
+       }
+    }
 }
 #endif
 
index 82e3ef705e41880b92f7003b3319edab9d6e5bff..77460a1a6c4a22ba9060993fd1eea75609d936f0 100644 (file)
@@ -766,9 +766,147 @@ static int balloon_arraysize;
 static int balloon_mouse_row = 0;
 static int balloon_mouse_col = 0;
 
-#define BALLOON_MIN_WIDTH 40
+#define BALLOON_MIN_WIDTH 50
 #define BALLOON_MIN_HEIGHT 10
 
+typedef struct {
+    char_u     *start;
+    int                bytelen;
+    int                cells;
+    int                indent;
+} balpart_T;
+
+/*
+ * Split a string into parts to display in the balloon.
+ * Aimed at output from gdb.  Attempts to split at white space, preserve quoted
+ * strings and make a struct look good.
+ * Resulting array is stored in "array" and returns the size of the array.
+ */
+    int
+split_message(char_u *mesg, pumitem_T **array)
+{
+    garray_T   ga;
+    char_u     *p;
+    balpart_T  *item;
+    int                quoted = FALSE;
+    int                height;
+    int                line;
+    int                item_idx;
+    int                indent = 0;
+    int                max_cells = 0;
+    int                max_height = Rows / 2 - 2;
+    int                long_item_count = 0;
+    int                split_long_items = FALSE;
+
+    ga_init2(&ga, sizeof(balpart_T), 20);
+    p = mesg;
+
+    while (*p != NUL)
+    {
+       if (ga_grow(&ga, 1) == FAIL)
+           goto failed;
+       item = ((balpart_T *)ga.ga_data) + ga.ga_len;
+       item->start = p;
+       item->indent = indent;
+       item->cells = indent * 2;
+       ++ga.ga_len;
+       while (*p != NUL)
+       {
+           if (*p == '"')
+               quoted = !quoted;
+           else if (*p == '\\' && p[1] != NUL)
+               ++p;
+           else if (!quoted)
+           {
+               if ((*p == ',' && p[1] == ' ') || *p == '{' || *p == '}')
+               {
+                   /* Looks like a good point to break. */
+                   if (*p == '{')
+                       ++indent;
+                   else if (*p == '}' && indent > 0)
+                       --indent;
+                   ++item->cells;
+                   p = skipwhite(p + 1);
+                   break;
+               }
+           }
+           item->cells += ptr2cells(p);
+           p += MB_PTR2LEN(p);
+       }
+       item->bytelen = p - item->start;
+       if (item->cells > max_cells)
+           max_cells = item->cells;
+       long_item_count += item->cells / BALLOON_MIN_WIDTH;
+    }
+
+    height = 2 + ga.ga_len;
+
+    /* If there are long items and the height is below the limit: split lines */
+    if (long_item_count > 0 && height + long_item_count <= max_height)
+    {
+       split_long_items = TRUE;
+       height += long_item_count;
+    }
+
+    /* Limit to half the window height, it has to fit above or below the mouse
+     * position. */
+    if (height > max_height)
+       height = max_height;
+    *array = (pumitem_T *)alloc_clear((unsigned)sizeof(pumitem_T) * height);
+    if (*array == NULL)
+       goto failed;
+
+    /* Add an empty line above and below, looks better. */
+    (*array)->pum_text = vim_strsave((char_u *)"");
+    (*array + height - 1)->pum_text = vim_strsave((char_u *)"");
+
+    for (line = 1, item_idx = 0; line < height - 1; ++item_idx)
+    {
+       int     skip;
+       int     thislen;
+       int     copylen;
+       int     ind;
+       int     cells;
+
+       item = ((balpart_T *)ga.ga_data) + item_idx;
+       for (skip = 0; skip < item->bytelen; skip += thislen)
+       {
+           if (split_long_items && item->cells >= BALLOON_MIN_WIDTH)
+           {
+               cells = item->indent * 2;
+               for (p = item->start + skip; p < item->start + item->bytelen;
+                                                           p += MB_PTR2LEN(p))
+                   if ((cells += ptr2cells(p)) > BALLOON_MIN_WIDTH)
+                       break;
+               thislen = p - (item->start + skip);
+           }
+           else
+               thislen = item->bytelen;
+
+           /* put indent at the start */
+           p = alloc(thislen + item->indent * 2 + 1);
+           for (ind = 0; ind < item->indent * 2; ++ind)
+               p[ind] = ' ';
+
+           /* exclude spaces at the end of the string */
+           for (copylen = thislen; copylen > 0; --copylen)
+               if (item->start[skip + copylen - 1] != ' ')
+                   break;
+
+           vim_strncpy(p + ind, item->start + skip, copylen);
+           (*array)[line].pum_text = p;
+           item->indent = 0;  /* wrapped line has no indent */
+           ++line;
+       }
+    }
+    ga_clear(&ga);
+    return height;
+
+failed:
+    ga_clear(&ga);
+    return 0;
+}
+
     void
 ui_remove_balloon(void)
 {
@@ -786,28 +924,42 @@ ui_remove_balloon(void)
  * Terminal version of a balloon, uses the popup menu code.
  */
     void
-ui_post_balloon(char_u *mesg)
+ui_post_balloon(char_u *mesg, list_T *list)
 {
     ui_remove_balloon();
 
-    /* TODO: split the text in multiple lines. */
-    balloon_arraysize = 3;
-    balloon_array = (pumitem_T *)alloc_clear(
-                             (unsigned)sizeof(pumitem_T) * balloon_arraysize);
-    if (balloon_array != NULL)
+    if (mesg == NULL && list == NULL)
+       return;
+    if (list != NULL)
     {
-       /* Add an empty line above and below, looks better. */
-       balloon_array[0].pum_text = vim_strsave((char_u *)"");
-       balloon_array[1].pum_text = vim_strsave(mesg);
-       balloon_array[2].pum_text = vim_strsave((char_u *)"");
+       listitem_T  *li;
+       int         idx;
+
+       balloon_arraysize = list->lv_len;
+       balloon_array = (pumitem_T *)alloc_clear(
+                                  (unsigned)sizeof(pumitem_T) * list->lv_len);
+       if (balloon_array == NULL)
+           return;
+       for (idx = 0, li = list->lv_first; li != NULL; li = li->li_next, ++idx)
+       {
+           char_u *text = get_tv_string_chk(&li->li_tv);
 
+           balloon_array[idx].pum_text = vim_strsave(
+                                          text == NULL ? (char_u *)"" : text);
+       }
+    }
+    else
+       balloon_arraysize = split_message(mesg, &balloon_array);
+
+    if (balloon_arraysize > 0)
+    {
        pum_array = balloon_array;
        pum_size = balloon_arraysize;
        pum_compute_size();
        pum_scrollbar = 0;
        pum_height = balloon_arraysize;
 
-       if (Rows - mouse_row > BALLOON_MIN_HEIGHT)
+       if (Rows - mouse_row > pum_size)
        {
            /* Enough space below the mouse row. */
            pum_row = mouse_row + 1;
@@ -817,7 +969,7 @@ ui_post_balloon(char_u *mesg)
        else
        {
            /* Show above the mouse row, reduce height if it does not fit. */
-           pum_row = mouse_row - 1 - pum_size;
+           pum_row = mouse_row - pum_size;
            if (pum_row < 0)
            {
                pum_height += pum_row;
index 716fbbe306579253b5fe98679dc0978a3c5251b5..2be64a0da195d64f3c06365068ba36214d62dcdb 100644 (file)
@@ -1,6 +1,6 @@
 /* beval.c */
 int get_beval_info(BalloonEval *beval, int getword, win_T **winp, linenr_T *lnump, char_u **textp, int *colp);
-void post_balloon(BalloonEval *beval, char_u *mesg);
+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: set ft=c : */
index 57795338b0c5adf568a0299952e0df41230791fc..272730433ff78a07f53690ef645cc6ad5b66067a 100644 (file)
@@ -5,7 +5,8 @@ void pum_undisplay(void);
 void pum_clear(void);
 int pum_visible(void);
 int pum_get_height(void);
+int split_message(char_u *mesg, pumitem_T **array);
 void ui_remove_balloon(void);
-void ui_post_balloon(char_u *mesg);
+void ui_post_balloon(char_u *mesg, list_T *list);
 void ui_may_remove_balloon(void);
 /* vim: set ft=c : */
index 54d641fdeec61960d9879a372793fec79851a71b..bfccb7410f68f1fadf794190d4efa19706990246 100644 (file)
@@ -703,4 +703,37 @@ func Test_popup_and_preview_autocommand()
   bw!
 endfunc
 
+func Test_balloon_split()
+  call assert_equal([
+        \ 'one two three four one two three four one two thre',
+        \ 'e four',
+        \ ], balloon_split(
+        \ 'one two three four one two three four one two three four'))
+
+  call assert_equal([
+        \ 'struct = {',
+        \ '  one = 1,',
+        \ '  two = 2,',
+        \ '  three = 3}',
+        \ ], balloon_split(
+        \ 'struct = {one = 1, two = 2, three = 3}'))
+
+  call assert_equal([
+        \ 'struct = {',
+        \ '  one = 1,',
+        \ '  nested = {',
+        \ '    n1 = "yes",',
+        \ '    n2 = "no"}',
+        \ '  two = 2}',
+        \ ], balloon_split(
+        \ 'struct = {one = 1, nested = {n1 = "yes", n2 = "no"} two = 2}'))
+  call assert_equal([
+        \ 'struct = 0x234 {',
+        \ '  long = 2343 "\\"some long string that will be wr',
+        \ 'apped in two\\"",',
+        \ '  next = 123}',
+        \ ], balloon_split(
+        \ 'struct = 0x234 {long = 2343 "\\"some long string that will be wrapped in two\\"", next = 123}'))
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
index 7e03c4b5060c61d264f96c3c10bfaa9e9f8ee25f..9896a7715908d244319caf75a96b44139b0bf1e5 100644 (file)
@@ -771,6 +771,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1318,
 /**/
     1317,
 /**/