# HG changeset patch # User Christian Brabandt # Date 1511118006 -3600 # Node ID 058e93aee6211145865a4718aed0f38ec48d127f # Parent 53c7865ec6aad9372ee855d30dbbda021cde76bc patch 8.0.1318: terminal balloon only shows one line commit https://github.com/vim/vim/commit/246fe03d154c09070d5b7365b7f61716c4e0ddd4 Author: Bram Moolenaar Date: Sun Nov 19 19:56:27 2017 +0100 patch 8.0.1318: terminal balloon only shows one line 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. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -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")" diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim --- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim +++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim @@ -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 diff --git a/src/beval.c b/src/beval.c --- a/src/beval.c +++ b/src/beval.c @@ -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 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; } diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -61,6 +61,7 @@ static void f_atan2(typval_T *argvars, t #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 *ret 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 diff --git a/src/popupmnu.c b/src/popupmnu.c --- a/src/popupmnu.c +++ b/src/popupmnu.c @@ -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; diff --git a/src/proto/beval.pro b/src/proto/beval.pro --- a/src/proto/beval.pro +++ b/src/proto/beval.pro @@ -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 : */ diff --git a/src/proto/popupmnu.pro b/src/proto/popupmnu.pro --- a/src/proto/popupmnu.pro +++ b/src/proto/popupmnu.pro @@ -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 : */ diff --git a/src/testdir/test_popup.vim b/src/testdir/test_popup.vim --- a/src/testdir/test_popup.vim +++ b/src/testdir/test_popup.vim @@ -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 diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -772,6 +772,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1318, +/**/ 1317, /**/ 1316,