# HG changeset patch # User Bram Moolenaar # Date 1595274304 -7200 # Node ID 2c40e60017a8d8299ffa4d82a6a97bdbf1ac5b88 # Parent 233bc1a5e828a768e9b0d465cd90adc84d9b8837 patch 8.2.1255: cannot use a lambda with quickfix functions Commit: https://github.com/vim/vim/commit/d43906d2e5969288f239df851f5ad7b1dc2c7251 Author: Bram Moolenaar Date: Mon Jul 20 21:31:32 2020 +0200 patch 8.2.1255: cannot use a lambda with quickfix functions Problem: Cannot use a lambda with quickfix functions. Solution: Add support for lambda. (Yegappan Lakshmanan, closes https://github.com/vim/vim/issues/6499) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1,4 +1,4 @@ -*eval.txt* For Vim version 8.2. Last change: 2020 Jul 09 +*eval.txt* For Vim version 8.2. Last change: 2020 Jul 19 VIM REFERENCE MANUAL by Bram Moolenaar @@ -94,8 +94,9 @@ the Number. Examples: Number 0 --> String "0" ~ Number -1 --> String "-1" ~ *octal* -Conversion from a String to a Number is done by converting the first digits to -a number. Hexadecimal "0xf9", Octal "017" or "0o17", and Binary "0b10" +Conversion from a String to a Number only happens in legacy Vim script, not in +Vim9 script. It is done by converting the first digits to a number. +Hexadecimal "0xf9", Octal "017" or "0o17", and Binary "0b10" numbers are recognized (NOTE: when using |scriptversion-4| octal with a leading "0" is not recognized). If the String doesn't start with digits, the result is zero. @@ -2831,7 +2832,7 @@ stridx({haystack}, {needle} [, {start}]) string({expr}) String String representation of {expr} value strlen({expr}) Number length of the String {expr} strpart({str}, {start} [, {len}]) - String {len} characters of {str} at {start} + String {len} bytes of {str} at byte {start} strptime({format}, {timestring}) Number Convert {timestring} to unix timestamp strridx({haystack}, {needle} [, {start}]) @@ -9183,7 +9184,8 @@ setqflist({list} [, {action} [, {what}]] the last quickfix list. quickfixtextfunc function to get the text to display in the - quickfix window. Refer to + quickfix window. The value can be the name of + a function or a funcref or a lambda. Refer to |quickfix-window-function| for an explanation of how to write the function and an example. title quickfix list title text. See |quickfix-title| diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -5913,7 +5913,8 @@ A jump table for the options with a shor customize the information displayed in the quickfix or location window for each entry in the corresponding quickfix or location list. See |quickfix-window-function| for an explanation of how to write the - function and an example. + function and an example. The value can be the name of a function or a + lambda. This option cannot be set from a |modeline| or in the |sandbox|, for security reasons. diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -1964,7 +1964,10 @@ The function should return a single line window for each entry from start_idx to end_idx. The function can obtain information about the entries using the |getqflist()| function and specifying the quickfix list identifier "id". For a location list, getloclist() function -can be used with the 'winid' argument. +can be used with the 'winid' argument. If an empty list is returned, then the +default format is used to display all the entries. If an item in the returned +list is an empty string, then the default format is used to display the +corresponding entry. If a quickfix or location list specific customization is needed, then the 'quickfixtextfunc' attribute of the list can be set using the |setqflist()| or diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -1101,27 +1101,6 @@ channel_open( return channel; } -/* - * Copy callback from "src" to "dest", incrementing the refcounts. - */ - static void -copy_callback(callback_T *dest, callback_T *src) -{ - dest->cb_partial = src->cb_partial; - if (dest->cb_partial != NULL) - { - dest->cb_name = src->cb_name; - dest->cb_free_name = FALSE; - ++dest->cb_partial->pt_refcount; - } - else - { - dest->cb_name = vim_strsave(src->cb_name); - dest->cb_free_name = TRUE; - func_ref(src->cb_name); - } -} - static void free_set_callback(callback_T *cbp, callback_T *callback) { diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -3849,6 +3849,27 @@ set_callback(callback_T *dest, callback_ } /* + * Copy callback from "src" to "dest", incrementing the refcounts. + */ + void +copy_callback(callback_T *dest, callback_T *src) +{ + dest->cb_partial = src->cb_partial; + if (dest->cb_partial != NULL) + { + dest->cb_name = src->cb_name; + dest->cb_free_name = FALSE; + ++dest->cb_partial->pt_refcount; + } + else + { + dest->cb_name = vim_strsave(src->cb_name); + dest->cb_free_name = TRUE; + func_ref(src->cb_name); + } +} + +/* * Unref/free "callback" returned by get_callback() or set_callback(). */ void diff --git a/src/optionstr.c b/src/optionstr.c --- a/src/optionstr.c +++ b/src/optionstr.c @@ -2255,6 +2255,14 @@ did_set_string_option( # endif #endif +#ifdef FEAT_QUICKFIX + else if (varp == &p_qftf) + { + if (qf_process_qftf_option() == FALSE) + errmsg = e_invarg; + } +#endif + // Options that are a list of flags. else { diff --git a/src/proto/evalvars.pro b/src/proto/evalvars.pro --- a/src/proto/evalvars.pro +++ b/src/proto/evalvars.pro @@ -88,5 +88,6 @@ void f_setbufvar(typval_T *argvars, typv callback_T get_callback(typval_T *arg); void put_callback(callback_T *cb, typval_T *tv); void set_callback(callback_T *dest, callback_T *src); +void copy_callback(callback_T *dest, callback_T *src); void free_callback(callback_T *callback); /* vim: set ft=c : */ diff --git a/src/proto/quickfix.pro b/src/proto/quickfix.pro --- a/src/proto/quickfix.pro +++ b/src/proto/quickfix.pro @@ -15,6 +15,7 @@ void ex_cclose(exarg_T *eap); void ex_copen(exarg_T *eap); void ex_cbottom(exarg_T *eap); linenr_T qf_current_entry(win_T *wp); +int qf_process_qftf_option(void); int grep_internal(cmdidx_T cmdidx); void ex_make(exarg_T *eap); int qf_get_size(exarg_T *eap); diff --git a/src/quickfix.c b/src/quickfix.c --- a/src/quickfix.c +++ b/src/quickfix.c @@ -82,7 +82,7 @@ typedef struct qf_list_S char_u *qf_title; // title derived from the command that created // the error list or set by setqflist typval_T *qf_ctx; // context set by setqflist/setloclist - char_u *qf_qftf; // 'quickfixtextfunc' setting for this list + callback_T qftf_cb; // 'quickfixtextfunc' callback function struct dir_stack_T *qf_dir_stack; char_u *qf_directory; @@ -161,6 +161,9 @@ static int quickfix_busy = 0; static efm_T *fmt_start = NULL; // cached across qf_parse_line() calls +// callback function for 'quickfixtextfunc' +static callback_T qftf_cb; + static void qf_new_list(qf_info_T *qi, char_u *qf_title); static int qf_add_entry(qf_list_T *qfl, char_u *dir, char_u *fname, char_u *module, int bufnum, char_u *mesg, long lnum, int col, int vis_col, char_u *pattern, int nr, int type, int valid); static void qf_free(qf_list_T *qfl); @@ -2279,10 +2282,10 @@ copy_loclist(qf_list_T *from_qfl, qf_lis } else to_qfl->qf_ctx = NULL; - if (from_qfl->qf_qftf != NULL) - to_qfl->qf_qftf = vim_strsave(from_qfl->qf_qftf); + if (from_qfl->qftf_cb.cb_name != NULL) + copy_callback(&to_qfl->qftf_cb, &from_qfl->qftf_cb); else - to_qfl->qf_qftf = NULL; + to_qfl->qftf_cb.cb_name = NULL; if (from_qfl->qf_count) if (copy_loclist_entries(from_qfl, to_qfl) == FAIL) @@ -3818,7 +3821,7 @@ qf_free(qf_list_T *qfl) VIM_CLEAR(qfl->qf_title); free_tv(qfl->qf_ctx); qfl->qf_ctx = NULL; - VIM_CLEAR(qfl->qf_qftf); + free_callback(&qfl->qftf_cb); qfl->qf_id = 0; qfl->qf_changedtick = 0L; } @@ -4349,6 +4352,49 @@ qf_find_buf(qf_info_T *qi) } /* + * Process the 'quickfixtextfunc' option value. + */ + int +qf_process_qftf_option(void) +{ + typval_T *tv; + callback_T cb; + + if (p_qftf == NULL || *p_qftf == NUL) + { + free_callback(&qftf_cb); + return TRUE; + } + + if (*p_qftf == '{') + { + // Lambda expression + tv = eval_expr(p_qftf, NULL); + if (tv == NULL) + return FALSE; + } + else + { + // treat everything else as a function name string + tv = alloc_string_tv(vim_strsave(p_qftf)); + if (tv == NULL) + return FALSE; + } + + cb = get_callback(tv); + if (cb.cb_name == NULL) + { + free_tv(tv); + return FALSE; + } + + free_callback(&qftf_cb); + set_callback(&qftf_cb, &cb); + free_tv(tv); + return TRUE; +} + +/* * Update the w:quickfix_title variable in the quickfix/location list window */ static void @@ -4424,7 +4470,9 @@ qf_buf_add_line( int len; buf_T *errbuf; - if (qftf_str != NULL) + // If the 'quickfixtextfunc' function returned an non-empty custom string + // for this entry, then use it. + if (qftf_str != NULL && *qftf_str != NUL) vim_strncpy(IObuff, qftf_str, IOSIZE - 1); else { @@ -4501,21 +4549,26 @@ qf_buf_add_line( return OK; } +/* + * Call the 'quickfixtextfunc' function to get the list of lines to display in + * the quickfix window for the entries 'start_idx' to 'end_idx'. + */ static list_T * call_qftf_func(qf_list_T *qfl, int qf_winid, long start_idx, long end_idx) { - char_u *qftf = p_qftf; + callback_T *cb = &qftf_cb; list_T *qftf_list = NULL; // If 'quickfixtextfunc' is set, then use the user-supplied function to get // the text to display. Use the local value of 'quickfixtextfunc' if it is // set. - if (qfl->qf_qftf != NULL) - qftf = qfl->qf_qftf; - if (qftf != NULL && *qftf != NUL) + if (qfl->qftf_cb.cb_name != NULL) + cb = &qfl->qftf_cb; + if (cb != NULL && cb->cb_name != NULL) { typval_T args[1]; dict_T *d; + typval_T rettv; // create the dict argument if ((d = dict_alloc_lock(VAR_FIXED)) == NULL) @@ -4529,8 +4582,17 @@ call_qftf_func(qf_list_T *qfl, int qf_wi args[0].v_type = VAR_DICT; args[0].vval.v_dict = d; - qftf_list = call_func_retlist(qftf, 1, args); - --d->dv_refcount; + qftf_list = NULL; + if (call_callback(cb, 0, &rettv, 1, args) != FAIL) + { + if (rettv.v_type == VAR_LIST) + { + qftf_list = rettv.vval.v_list; + qftf_list->lv_refcount++; + } + clear_tv(&rettv); + } + dict_unref(d); } return qftf_list; @@ -4569,6 +4631,7 @@ qf_fill_buffer(qf_list_T *qfl, buf_T *bu if (qfl != NULL) { char_u dirname[MAXPATHL]; + int invalid_val = FALSE; *dirname = NUL; @@ -4593,9 +4656,15 @@ qf_fill_buffer(qf_list_T *qfl, buf_T *bu { char_u *qftf_str = NULL; - if (qftf_li != NULL) - // Use the text supplied by the user defined function + // Use the text supplied by the user defined function (if any). + // If the returned value is not string, then ignore the rest + // of the returned values and use the default. + if (qftf_li != NULL && !invalid_val) + { qftf_str = tv_get_string_chk(&qftf_li->li_tv); + if (qftf_str == NULL) + invalid_val = TRUE; + } if (qf_buf_add_line(buf, lnum, qfp, dirname, qftf_str) == FAIL) break; @@ -6515,7 +6584,8 @@ enum { QF_GETLIST_TICK = 0x100, QF_GETLIST_FILEWINID = 0x200, QF_GETLIST_QFBUFNR = 0x400, - QF_GETLIST_ALL = 0x7FF, + QF_GETLIST_QFTF = 0x800, + QF_GETLIST_ALL = 0xFFF, }; /* @@ -6644,6 +6714,9 @@ qf_getprop_keys2flags(dict_T *what, int if (dict_find(what, (char_u *)"qfbufnr", -1) != NULL) flags |= QF_GETLIST_QFBUFNR; + if (dict_find(what, (char_u *)"quickfixtextfunc", -1) != NULL) + flags |= QF_GETLIST_QFTF; + return flags; } @@ -6738,6 +6811,8 @@ qf_getprop_defaults(qf_info_T *qi, int f status = dict_add_number(retdict, "filewinid", 0); if ((status == OK) && (flags & QF_GETLIST_QFBUFNR)) status = qf_getprop_qfbufnr(qi, retdict); + if ((status == OK) && (flags & QF_GETLIST_QFTF)) + status = dict_add_string(retdict, "quickfixtextfunc", (char_u *)""); return status; } @@ -6837,6 +6912,28 @@ qf_getprop_idx(qf_list_T *qfl, int eidx, } /* + * Return the 'quickfixtextfunc' function of a quickfix/location list + */ + static int +qf_getprop_qftf(qf_list_T *qfl, dict_T *retdict) +{ + int status; + + if (qfl->qftf_cb.cb_name != NULL) + { + typval_T tv; + + put_callback(&qfl->qftf_cb, &tv); + status = dict_add_tv(retdict, "quickfixtextfunc", &tv); + clear_tv(&tv); + } + else + status = dict_add_string(retdict, "quickfixtextfunc", (char_u *)""); + + return status; +} + +/* * Return quickfix/location list details (title) as a * dictionary. 'what' contains the details to return. If 'list_idx' is -1, * then current list is used. Otherwise the specified list is used. @@ -6899,6 +6996,8 @@ qf_get_properties(win_T *wp, dict_T *wha status = qf_getprop_filewinid(wp, qi, retdict); if ((status == OK) && (flags & QF_GETLIST_QFBUFNR)) status = qf_getprop_qfbufnr(qi, retdict); + if ((status == OK) && (flags & QF_GETLIST_QFTF)) + status = qf_getprop_qftf(qfl, retdict); return status; } @@ -7260,10 +7359,12 @@ qf_setprop_curidx(qf_info_T *qi, qf_list static int qf_setprop_qftf(qf_info_T *qi UNUSED, qf_list_T *qfl, dictitem_T *di) { - VIM_CLEAR(qfl->qf_qftf); - if (di->di_tv.v_type == VAR_STRING - && di->di_tv.vval.v_string != NULL) - qfl->qf_qftf = vim_strsave(di->di_tv.vval.v_string); + callback_T cb; + + free_callback(&qfl->qftf_cb); + cb = get_callback(&di->di_tv); + if (cb.cb_name != NULL && *cb.cb_name != NUL) + set_callback(&qfl->qftf_cb, &cb); return OK; } diff --git a/src/testdir/test_quickfix.vim b/src/testdir/test_quickfix.vim --- a/src/testdir/test_quickfix.vim +++ b/src/testdir/test_quickfix.vim @@ -3490,13 +3490,13 @@ func Xgetlist_empty_tests(cchar) if a:cchar == 'c' call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, \ 'items' : [], 'nr' : 0, 'size' : 0, 'qfbufnr' : 0, - \ 'title' : '', 'winid' : 0, 'changedtick': 0}, - \ g:Xgetlist({'all' : 0})) + \ 'title' : '', 'winid' : 0, 'changedtick': 0, + \ 'quickfixtextfunc' : ''}, g:Xgetlist({'all' : 0})) else call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, \ 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', \ 'winid' : 0, 'changedtick': 0, 'filewinid' : 0, - \ 'qfbufnr' : 0}, + \ 'qfbufnr' : 0, 'quickfixtextfunc' : ''}, \ g:Xgetlist({'all' : 0})) endif @@ -3535,12 +3535,13 @@ func Xgetlist_empty_tests(cchar) if a:cchar == 'c' call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, - \ 'qfbufnr' : qfbufnr, + \ 'qfbufnr' : qfbufnr, 'quickfixtextfunc' : '', \ 'changedtick' : 0}, g:Xgetlist({'id' : qfid, 'all' : 0})) else call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, - \ 'changedtick' : 0, 'filewinid' : 0, 'qfbufnr' : 0}, + \ 'changedtick' : 0, 'filewinid' : 0, 'qfbufnr' : 0, + \ 'quickfixtextfunc' : ''}, \ g:Xgetlist({'id' : qfid, 'all' : 0})) endif @@ -3557,13 +3558,13 @@ func Xgetlist_empty_tests(cchar) if a:cchar == 'c' call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, - \ 'changedtick' : 0, 'qfbufnr' : qfbufnr}, - \ g:Xgetlist({'nr' : 5, 'all' : 0})) + \ 'changedtick' : 0, 'qfbufnr' : qfbufnr, + \ 'quickfixtextfunc' : ''}, g:Xgetlist({'nr' : 5, 'all' : 0})) else call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, - \ 'changedtick' : 0, 'filewinid' : 0, 'qfbufnr' : 0}, - \ g:Xgetlist({'nr' : 5, 'all' : 0})) + \ 'changedtick' : 0, 'filewinid' : 0, 'qfbufnr' : 0, + \ 'quickfixtextfunc' : ''}, g:Xgetlist({'nr' : 5, 'all' : 0})) endif endfunc @@ -4865,6 +4866,9 @@ func Xtest_qftextfunc(cchar) set efm=%f:%l:%c:%m set quickfixtextfunc=Tqfexpr + call assert_equal('Tqfexpr', &quickfixtextfunc) + call assert_equal('', + \ g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc) Xexpr ['F1:10:2:green', 'F1:20:4:blue'] Xwindow call assert_equal('F1-L10C2-green', getline(1)) @@ -4901,12 +4905,15 @@ func Xtest_qftextfunc(cchar) call assert_equal('Line 10, Col 2', getline(1)) call assert_equal('Line 20, Col 4', getline(2)) Xclose + call assert_equal(function('PerQfText'), + \ g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc) " Add entries to the list when the quickfix buffer is hidden Xaddexpr ['F1:30:6:red'] Xwindow call assert_equal('Line 30, Col 6', getline(3)) Xclose call g:Xsetlist([], 'r', {'quickfixtextfunc' : ''}) + call assert_equal('', g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc) set quickfixtextfunc& delfunc PerQfText @@ -4941,12 +4948,53 @@ func Xtest_qftextfunc(cchar) call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue', 'F1:30:6:red']", \ 'E730:') call assert_fails('Xwindow', 'E730:') - call assert_equal(['one', 'F1|20 col 4| blue', 'two'], getline(1, '$')) + call assert_equal(['one', 'F1|20 col 4| blue', 'F1|30 col 6| red'], + \ getline(1, '$')) Xclose set quickfixtextfunc& delfunc Xqftext delfunc Xqftext2 + + " set the global option to a lambda function + set quickfixtextfunc={d\ ->\ map(g:Xgetlist({'id'\ :\ d.id,\ 'items'\ :\ 1}).items[d.start_idx-1:d.end_idx-1],\ 'v:val.text')} + Xexpr ['F1:10:2:green', 'F1:20:4:blue'] + Xwindow + call assert_equal(['green', 'blue'], getline(1, '$')) + Xclose + call assert_equal("{d -> map(g:Xgetlist({'id' : d.id, 'items' : 1}).items[d.start_idx-1:d.end_idx-1], 'v:val.text')}", &quickfixtextfunc) + set quickfixtextfunc& + + " use a lambda function that returns an empty list + set quickfixtextfunc={d\ ->\ []} + Xexpr ['F1:10:2:green', 'F1:20:4:blue'] + Xwindow + call assert_equal(['F1|10 col 2| green', 'F1|20 col 4| blue'], + \ getline(1, '$')) + Xclose + set quickfixtextfunc& + + " use a lambda function that returns a list with empty strings + set quickfixtextfunc={d\ ->\ ['',\ '']} + Xexpr ['F1:10:2:green', 'F1:20:4:blue'] + Xwindow + call assert_equal(['F1|10 col 2| green', 'F1|20 col 4| blue'], + \ getline(1, '$')) + Xclose + set quickfixtextfunc& + + " set the per-quickfix list text function to a lambda function + call g:Xsetlist([], ' ', + \ {'quickfixtextfunc' : + \ {d -> map(g:Xgetlist({'id' : d.id, 'items' : 1}).items[d.start_idx-1:d.end_idx-1], + \ "'Line ' .. v:val.lnum .. ', Col ' .. v:val.col")}}) + Xaddexpr ['F1:10:2:green', 'F1:20:4:blue'] + Xwindow + call assert_equal('Line 10, Col 2', getline(1)) + call assert_equal('Line 20, Col 4', getline(2)) + Xclose + call assert_match("function('\\d\\+')", string(g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc)) + call g:Xsetlist([], 'f') endfunc func Test_qftextfunc() diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -755,6 +755,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1255, +/**/ 1254, /**/ 1253,