vim-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 vim/vim#6499)
d43906d2e5
This commit is contained in:
Jan Edmund Lazo 2021-06-22 22:02:59 -04:00
parent d5329c0331
commit 4cb0bf0942
No known key found for this signature in database
GPG Key ID: 64915E6E9F735B15
9 changed files with 232 additions and 51 deletions

View File

@ -7879,7 +7879,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

@ -4607,7 +4607,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

@ -1919,7 +1919,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

@ -7200,12 +7200,15 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)
r = FAIL;
} else if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) {
char_u *name = arg->vval.v_string;
if (name != NULL) {
if (name == NULL) {
r = FAIL;
} else if (*name == NUL) {
callback->type = kCallbackNone;
callback->data.funcref = NULL;
} else {
func_ref(name);
callback->data.funcref = vim_strsave(name);
callback->type = kCallbackFuncref;
} else {
r = FAIL;
}
} else if (nlua_is_table_from_lua(arg)) {
char_u *name = nlua_register_table_as_callable(arg);
@ -7216,8 +7219,10 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)
} else {
r = FAIL;
}
} else if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) {
} else if (arg->v_type == VAR_SPECIAL
|| (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0)) {
callback->type = kCallbackNone;
callback->data.funcref = NULL;
} else {
r = FAIL;
}

View File

@ -1162,20 +1162,48 @@ void callback_free(Callback *callback)
}
}
callback->type = kCallbackNone;
callback->data.funcref = NULL;
}
/// Copy a callback into a typval_T.
void callback_put(Callback *cb, typval_T *tv)
FUNC_ATTR_NONNULL_ALL
{
if (cb->type == kCallbackPartial) {
switch (cb->type) {
case kCallbackPartial:
tv->v_type = VAR_PARTIAL;
tv->vval.v_partial = cb->data.partial;
cb->data.partial->pt_refcount++;
} else if (cb->type == kCallbackFuncref) {
break;
case kCallbackFuncref:
tv->v_type = VAR_FUNC;
tv->vval.v_string = vim_strsave(cb->data.funcref);
func_ref(cb->data.funcref);
break;
default:
tv->v_type = VAR_SPECIAL;
tv->vval.v_special = kSpecialVarNull;
break;
}
}
// Copy callback from "src" to "dest", incrementing the refcounts.
void callback_copy(Callback *dest, Callback *src)
FUNC_ATTR_NONNULL_ALL
{
dest->type = src->type;
switch (src->type) {
case kCallbackPartial:
dest->data.partial = src->data.partial;
dest->data.partial->pt_refcount++;
break;
case kCallbackFuncref:
dest->data.funcref = vim_strsave(src->data.funcref);
func_ref(src->data.funcref);
break;
default:
dest->data.funcref = NULL;
break;
}
}

View File

@ -120,7 +120,7 @@ typedef enum {
VAR_DICT, ///< Dictionary, .v_dict is used.
VAR_FLOAT, ///< Floating-point value, .v_float is used.
VAR_BOOL, ///< true, false
VAR_SPECIAL, ///< Special value (true, false, null), .v_special
VAR_SPECIAL, ///< Special value (null), .v_special
///< is used.
VAR_PARTIAL, ///< Partial, .v_partial is used.
} VarType;

View File

@ -85,6 +85,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/os/input.h"
#include "nvim/os/lang.h"
#include "nvim/quickfix.h"
/*
* The options that are local to a window or buffer have "indir" set to one of
@ -3182,6 +3183,10 @@ ambw_end:
}
}
}
} else if (varp == &p_qftf) {
if (!qf_process_qftf_option()) {
errmsg = e_invarg;
}
} else {
// Options that are a list of flags.
p = NULL;

View File

@ -100,7 +100,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 qftf_cb; ///< 'quickfixtextfunc' callback function
struct dir_stack_T *qf_dir_stack;
char_u *qf_directory;
@ -541,6 +541,9 @@ static int efm_to_regpat(const char_u *efm, int len, efm_T *fmt_ptr,
static efm_T *fmt_start = NULL; // cached across qf_parse_line() calls
// callback function for 'quickfixtextfunc'
static Callback qftf_cb;
static void free_efm_list(efm_T **efm_first)
{
for (efm_T *efm_ptr = *efm_first; efm_ptr != NULL; efm_ptr = *efm_first) {
@ -1978,7 +1981,7 @@ static int copy_loclist_entries(const qf_list_T *from_qfl, qf_list_T *to_qfl)
}
/// Copy the specified location list 'from_qfl' to 'to_qfl'.
static int copy_loclist(const qf_list_T *from_qfl, qf_list_T *to_qfl)
static int copy_loclist(qf_list_T *from_qfl, qf_list_T *to_qfl)
FUNC_ATTR_NONNULL_ALL
{
// Some of the fields are populated by qf_add_entry()
@ -2000,11 +2003,7 @@ static int copy_loclist(const 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);
} else {
to_qfl->qf_qftf = NULL;
}
callback_copy(&to_qfl->qftf_cb, &from_qfl->qftf_cb);
if (from_qfl->qf_count) {
if (copy_loclist_entries(from_qfl, to_qfl) == FAIL) {
@ -3385,7 +3384,7 @@ static void qf_free(qf_list_T *qfl)
XFREE_CLEAR(qfl->qf_title);
tv_free(qfl->qf_ctx);
qfl->qf_ctx = NULL;
XFREE_CLEAR(qfl->qf_qftf);
callback_free(&qfl->qftf_cb);
qfl->qf_id = 0;
qfl->qf_changedtick = 0L;
}
@ -3860,6 +3859,41 @@ static buf_T *qf_find_buf(qf_info_T *qi)
return NULL;
}
// Process the 'quickfixtextfunc' option value.
bool qf_process_qftf_option(void)
{
typval_T *tv;
Callback cb;
if (p_qftf == NULL || *p_qftf == NUL) {
callback_free(&qftf_cb);
return true;
}
if (*p_qftf == '{') {
// Lambda expression
tv = eval_expr(p_qftf);
if (tv == NULL) {
return false;
}
} else {
// treat everything else as a function name string
tv = xcalloc(1, sizeof(*tv));
tv->v_type = VAR_STRING;
tv->vval.v_string = vim_strsave(p_qftf);
}
if (!callback_from_typval(&cb, tv)) {
tv_free(tv);
return false;
}
callback_free(&qftf_cb);
qftf_cb = cb;
tv_free(tv);
return true;
}
/// Update the w:quickfix_title variable in the quickfix/location list window in
/// all the tab pages.
static void qf_update_win_titlevar(qf_info_T *qi)
@ -3928,7 +3962,9 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum,
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) {
STRLCPY(IObuff, qftf_str, IOSIZE);
} else {
if (qfp->qf_module != NULL) {
@ -3997,22 +4033,25 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum,
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 *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 (qfl->qftf_cb.type != kCallbackNone) {
cb = &qfl->qftf_cb;
}
if (qftf != NULL && *qftf != NUL) {
if (cb != NULL && cb->type != kCallbackNone) {
typval_T args[1];
typval_T rettv;
// create the dict argument
dict_T *const dict = tv_dict_alloc_lock(VAR_FIXED);
@ -4026,8 +4065,16 @@ static list_T *call_qftf_func(qf_list_T *qfl,
args[0].v_type = VAR_DICT;
args[0].vval.v_dict = dict;
qftf_list = call_func_retlist(qftf, 1, args);
dict->dv_refcount--;
qftf_list = NULL;
if (callback_call(cb, 1, args, &rettv)) {
if (rettv.v_type == VAR_LIST) {
qftf_list = rettv.vval.v_list;
tv_list_ref(qftf_list);
}
tv_clear(&rettv);
}
tv_dict_unref(dict);
}
return qftf_list;
@ -4064,6 +4111,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last,
if (qfl != NULL) {
char_u dirname[MAXPATHL];
int prev_bufnr = -1;
bool invalid_val = false;
*dirname = NUL;
@ -4086,9 +4134,14 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last,
while (lnum < qfl->qf_count) {
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 = (char_u *)tv_get_string_chk(TV_LIST_ITEM_TV(qftf_li));
if (qftf_str == NULL) {
invalid_val = true;
}
}
if (qf_buf_add_line(qfl, buf, lnum, qfp, dirname, qftf_str,
@ -5796,7 +5849,9 @@ enum {
QF_GETLIST_SIZE = 0x80,
QF_GETLIST_TICK = 0x100,
QF_GETLIST_FILEWINID = 0x200,
QF_GETLIST_ALL = 0x3FF,
QF_GETLIST_QFBUFNR = 0x400,
QF_GETLIST_QFTF = 0x800,
QF_GETLIST_ALL = 0xFFF,
};
/// Parse text from 'di' and return the quickfix list items.
@ -5894,6 +5949,9 @@ static int qf_getprop_keys2flags(const dict_T *what, bool loclist)
if (loclist && tv_dict_find(what, S_LEN("filewinid")) != NULL) {
flags |= QF_GETLIST_FILEWINID;
}
if (tv_dict_find(what, S_LEN("quickfixtextfunc")) != NULL) {
flags |= QF_GETLIST_QFTF;
}
return flags;
}
@ -5985,6 +6043,9 @@ static int qf_getprop_defaults(qf_info_T *qi,
if ((status == OK) && locstack && (flags & QF_GETLIST_FILEWINID)) {
status = tv_dict_add_nr(retdict, S_LEN("filewinid"), 0);
}
if ((status == OK) && (flags & QF_GETLIST_QFTF)) {
status = tv_dict_add_str(retdict, S_LEN("quickfixtextfunc"), "");
}
return status;
}
@ -6060,6 +6121,26 @@ static int qf_getprop_idx(qf_list_T *qfl, int eidx, dict_T *retdict)
return tv_dict_add_nr(retdict, S_LEN("idx"), eidx);
}
/// Return the 'quickfixtextfunc' function of a quickfix/location list
/// @return OK or FAIL
static int qf_getprop_qftf(qf_list_T *qfl, dict_T *retdict)
FUNC_ATTR_NONNULL_ALL
{
int status;
if (qfl->qftf_cb.type != kCallbackNone) {
typval_T tv;
callback_put(&qfl->qftf_cb, &tv);
status = tv_dict_add_tv(retdict, S_LEN("quickfixtextfunc"), &tv);
tv_clear(&tv);
} else {
status = tv_dict_add_str(retdict, S_LEN("quickfixtextfunc"), "");
}
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.
@ -6133,17 +6214,23 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
if ((status == OK) && (wp != NULL) && (flags & QF_GETLIST_FILEWINID)) {
status = qf_getprop_filewinid(wp, qi, retdict);
}
if ((status == OK) && (flags & QF_GETLIST_QFTF)) {
status = qf_getprop_qftf(qfl, retdict);
}
return status;
}
/// Set the current index in the specified quickfix list
static int qf_setprop_qftf(qf_info_T *qi, qf_list_T *qfl,
dictitem_T *di)
/// @return OK
static int qf_setprop_qftf(qf_list_T *qfl, dictitem_T *di)
FUNC_ATTR_NONNULL_ALL
{
XFREE_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 cb;
callback_free(&qfl->qftf_cb);
if (callback_from_typval(&cb, &di->di_tv)) {
qfl->qftf_cb = cb;
}
return OK;
}
@ -6514,7 +6601,7 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action,
retval = qf_setprop_curidx(qi, qfl, di);
}
if ((di = tv_dict_find(what, S_LEN("quickfixtextfunc"))) != NULL) {
retval = qf_setprop_qftf(qi, qfl, di);
retval = qf_setprop_qftf(qfl, di);
}
if (newlist || retval == OK) {

View File

@ -3483,12 +3483,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},
\ 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},
\ 'winid' : 0, 'changedtick': 0, 'filewinid' : 0,
\ 'quickfixtextfunc' : ''},
\ g:Xgetlist({'all' : 0}))
endif
@ -3526,11 +3527,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,
\ '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},
\ 'changedtick' : 0, 'filewinid' : 0,
\ 'quickfixtextfunc' : ''},
\ g:Xgetlist({'id' : qfid, 'all' : 0}))
endif
@ -3547,12 +3550,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}, g:Xgetlist({'nr' : 5, 'all' : 0}))
\ 'changedtick' : 0,
\ '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},
\ g:Xgetlist({'nr' : 5, 'all' : 0}))
\ 'changedtick' : 0, 'filewinid' : 0,
\ 'quickfixtextfunc' : ''}, g:Xgetlist({'nr' : 5, 'all' : 0}))
endif
endfunc
@ -4994,6 +4998,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))
@ -5030,12 +5037,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
@ -5074,12 +5084,53 @@ func Xtest_qftextfunc(cchar)
" \ 'E730:')
Xexpr ['F1:10:2:green', 'F1:20:4:blue', 'F1:30:6:red']
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()