To: vim_dev@googlegroups.com Subject: Patch 8.0.1318 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.0.1318 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. Files: src/popupmnu.c, src/proto/popupmnu.pro, src/evalfunc.c, src/beval.c, src/proto/beval.pro, src/testdir/test_popup.vim, runtime/doc/eval.txt, runtime/pack/dist/opt/termdebug/plugin/termdebug.vim *** ../vim-8.0.1317/src/popupmnu.c 2017-11-18 22:13:04.753908641 +0100 --- src/popupmnu.c 2017-11-19 19:50:08.635685174 +0100 *************** *** 766,774 **** static int balloon_mouse_row = 0; static int balloon_mouse_col = 0; ! #define BALLOON_MIN_WIDTH 40 #define BALLOON_MIN_HEIGHT 10 void ui_remove_balloon(void) { --- 766,912 ---- static int balloon_mouse_row = 0; static int balloon_mouse_col = 0; ! #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,813 **** * Terminal version of a balloon, uses the popup menu code. */ void ! ui_post_balloon(char_u *mesg) { 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) { ! /* 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 *)""); 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) { /* Enough space below the mouse row. */ pum_row = mouse_row + 1; --- 924,965 ---- * Terminal version of a balloon, uses the popup menu code. */ void ! ui_post_balloon(char_u *mesg, list_T *list) { ui_remove_balloon(); ! if (mesg == NULL && list == NULL) ! return; ! if (list != NULL) { ! 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 > pum_size) { /* Enough space below the mouse row. */ pum_row = mouse_row + 1; *************** *** 817,823 **** else { /* Show above the mouse row, reduce height if it does not fit. */ ! pum_row = mouse_row - 1 - pum_size; if (pum_row < 0) { pum_height += pum_row; --- 969,975 ---- else { /* Show above the mouse row, reduce height if it does not fit. */ ! pum_row = mouse_row - pum_size; if (pum_row < 0) { pum_height += pum_row; *** ../vim-8.0.1317/src/proto/popupmnu.pro 2017-11-18 18:51:08.129770641 +0100 --- src/proto/popupmnu.pro 2017-11-19 19:14:04.474443454 +0100 *************** *** 5,11 **** void pum_clear(void); int pum_visible(void); int pum_get_height(void); void ui_remove_balloon(void); ! void ui_post_balloon(char_u *mesg); void ui_may_remove_balloon(void); /* vim: set ft=c : */ --- 5,12 ---- 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, list_T *list); void ui_may_remove_balloon(void); /* vim: set ft=c : */ *** ../vim-8.0.1317/src/evalfunc.c 2017-11-18 22:13:04.737908886 +0100 --- src/evalfunc.c 2017-11-19 19:21:26.387973807 +0100 *************** *** 61,66 **** --- 61,67 ---- #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,499 **** --- 495,501 ---- #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,1416 **** f_balloon_show(typval_T *argvars, typval_T *rettv UNUSED) { if (balloonEval != NULL) ! post_balloon(balloonEval, get_tv_string_chk(&argvars[0])); } #endif --- 1412,1448 ---- f_balloon_show(typval_T *argvars, typval_T *rettv UNUSED) { if (balloonEval != NULL) ! { ! 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 *** ../vim-8.0.1317/src/beval.c 2017-11-18 22:13:04.741908825 +0100 --- src/beval.c 2017-11-19 19:04:38.238731418 +0100 *************** *** 134,152 **** } /* ! * Show a balloon with "mesg". */ void ! post_balloon(BalloonEval *beval UNUSED, char_u *mesg) { # ifdef FEAT_BEVAL_TERM # ifdef FEAT_GUI if (!gui.in_use) # endif ! ui_post_balloon(mesg); # endif # ifdef FEAT_BEVAL_GUI if (gui.in_use) gui_mch_post_balloon(beval, mesg); # endif } --- 134,153 ---- } /* ! * Show a balloon with "mesg" or "list". */ void ! 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, list); # endif # ifdef FEAT_BEVAL_GUI if (gui.in_use) + /* GUI can't handle a list */ gui_mch_post_balloon(beval, mesg); # endif } *************** *** 257,263 **** set_vim_var_string(VV_BEVAL_TEXT, NULL, -1); if (result != NULL && result[0] != NUL) { ! post_balloon(beval, result); recursive = FALSE; return; } --- 258,264 ---- set_vim_var_string(VV_BEVAL_TEXT, NULL, -1); if (result != NULL && result[0] != NUL) { ! post_balloon(beval, result, NULL); recursive = FALSE; return; } *** ../vim-8.0.1317/src/proto/beval.pro 2017-11-18 22:13:04.741908825 +0100 --- src/proto/beval.pro 2017-11-19 19:04:53.690505299 +0100 *************** *** 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); int can_use_beval(void); void general_beval_cb(BalloonEval *beval, int state); /* vim: set ft=c : */ --- 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, list_T *list); int can_use_beval(void); void general_beval_cb(BalloonEval *beval, int state); /* vim: set ft=c : */ *** ../vim-8.0.1317/src/testdir/test_popup.vim 2017-11-04 19:24:24.750197152 +0100 --- src/testdir/test_popup.vim 2017-11-19 18:29:33.957291218 +0100 *************** *** 703,706 **** --- 703,739 ---- 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 *** ../vim-8.0.1317/runtime/doc/eval.txt 2017-11-16 23:03:43.244816117 +0100 --- runtime/doc/eval.txt 2017-11-19 19:20:18.060974160 +0100 *************** *** 2032,2037 **** --- 2032,2038 ---- 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,2689 **** < 2.356194 {only available when compiled with the |+float| feature} ! balloon_show({msg}) *balloon_show()* ! Show {msg} inside the balloon. Example: > func GetBalloonContent() " initiate getting the content --- 2683,2694 ---- < 2.356194 {only available when compiled with the |+float| feature} ! 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,2710 **** --- 2710,2721 ---- 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")" *** ../vim-8.0.1317/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim 2017-11-18 18:51:08.133770582 +0100 --- runtime/pack/dist/opt/termdebug/plugin/termdebug.vim 2017-11-19 19:50:36.415251892 +0100 *************** *** 127,135 **** call win_gotoid(s:gdbwin) " Enable showing a balloon with eval info ! if has("balloon_eval") ! set ballooneval set balloonexpr=TermDebugBalloonExpr() if has("balloon_eval_term") set balloonevalterm endif --- 127,137 ---- call win_gotoid(s:gdbwin) " Enable showing a balloon with eval info ! 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,166 **** let &columns = s:save_columns endif ! if has("balloon_eval") ! set noballooneval set balloonexpr= if has("balloon_eval_term") set noballoonevalterm endif --- 160,170 ---- let &columns = s:save_columns endif ! 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,371 **** --- 370,376 ---- 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 *** ../vim-8.0.1317/src/version.c 2017-11-19 15:05:40.146159574 +0100 --- src/version.c 2017-11-19 19:51:01.434861998 +0100 *************** *** 773,774 **** --- 773,776 ---- { /* Add new patch number below this line */ + /**/ + 1318, /**/ -- "How is your new girlfriend?" "90-60-90 man!" "What, pale purple?" /// Bram Moolenaar -- Bram@Moolenaar.net -- 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 ///