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 #6499)
This commit is contained in:
Bram Moolenaar
2020-07-20 21:31:32 +02:00
parent 470adb827f
commit d43906d2e5
11 changed files with 224 additions and 57 deletions

View File

@ -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}]]) *setqflist()*
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|

View File

@ -5913,7 +5913,8 @@ A jump table for the options with a short description can be found at |Q_op|.
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.

View File

@ -1964,7 +1964,10 @@ The function should return a single line of text to display in the quickfix
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

View File

@ -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)
{

View File

@ -3848,6 +3848,27 @@ set_callback(callback_T *dest, callback_T *src)
dest->cb_partial = src->cb_partial;
}
/*
* 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().
*/

View File

@ -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
{

View File

@ -88,5 +88,6 @@ void f_setbufvar(typval_T *argvars, typval_T *rettv);
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 : */

View File

@ -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);

View File

@ -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_list_T *to_qfl)
}
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;
}
@ -4348,6 +4351,49 @@ qf_find_buf(qf_info_T *qi)
return NULL;
}
/*
* 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
*/
@ -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_winid, long start_idx, long end_idx)
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 *buf, qfline_T *old_last, int qf_winid)
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 *buf, qfline_T *old_last, int qf_winid)
{
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 loclist)
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 flags, int locstack, dict_T *retdict)
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;
}
@ -6836,6 +6911,28 @@ qf_getprop_idx(qf_list_T *qfl, int eidx, dict_T *retdict)
return dict_add_number(retdict, "idx", 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,
@ -6899,6 +6996,8 @@ qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
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_T *qfl, dictitem_T *di)
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;
}

View File

@ -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('<lambda>\\d\\+')", string(g:Xgetlist({'quickfixtextfunc' : 1}).quickfixtextfunc))
call g:Xsetlist([], 'f')
endfunc
func Test_qftextfunc()

View File

@ -754,6 +754,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
1255,
/**/
1254,
/**/