aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJan Edmund Lazo <jan.lazo@mail.utoronto.ca>2021-06-22 22:02:59 -0400
committerJan Edmund Lazo <jan.lazo@mail.utoronto.ca>2021-06-23 23:16:20 -0400
commit4cb0bf09421e88adb812bb716b56af22bd51353e (patch)
treeed9c686270a620545f39a39cae5e8a00f1fe4ff2 /src
parentd5329c0331a4e899ea88277b745df8d1bf3a99fa (diff)
downloadrneovim-4cb0bf09421e88adb812bb716b56af22bd51353e.tar.gz
rneovim-4cb0bf09421e88adb812bb716b56af22bd51353e.tar.bz2
rneovim-4cb0bf09421e88adb812bb716b56af22bd51353e.zip
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) https://github.com/vim/vim/commit/d43906d2e5969288f239df851f5ad7b1dc2c7251
Diffstat (limited to 'src')
-rw-r--r--src/nvim/eval.c13
-rw-r--r--src/nvim/eval/typval.c44
-rw-r--r--src/nvim/eval/typval.h2
-rw-r--r--src/nvim/option.c5
-rw-r--r--src/nvim/quickfix.c141
-rw-r--r--src/nvim/testdir/test_quickfix.vim67
6 files changed, 224 insertions, 48 deletions
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 7e462e568b..1b78147ec2 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -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;
}
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 4118ec407c..4275ff7c61 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -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) {
- tv->v_type = VAR_PARTIAL;
- tv->vval.v_partial = cb->data.partial;
- cb->data.partial->pt_refcount++;
- } else if (cb->type == kCallbackFuncref) {
- tv->v_type = VAR_FUNC;
- tv->vval.v_string = vim_strsave(cb->data.funcref);
- func_ref(cb->data.funcref);
+ switch (cb->type) {
+ case kCallbackPartial:
+ tv->v_type = VAR_PARTIAL;
+ tv->vval.v_partial = cb->data.partial;
+ cb->data.partial->pt_refcount++;
+ 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;
}
}
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index 2b4612016b..050b84efec 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -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;
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 57b8fe1a2e..f57abe89cc 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -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;
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 1a9bbe26f0..508257d5b1 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -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,10 +4134,15 @@ 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
- qftf_str = (char_u *)tv_get_string_chk(TV_LIST_ITEM_TV(qftf_li));
+ // 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,
prev_bufnr != qfp->qf_fnum) == FAIL) {
@@ -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,19 +6214,25 @@ 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);
- }
- return OK;
+ Callback cb;
+
+ callback_free(&qfl->qftf_cb);
+ if (callback_from_typval(&cb, &di->di_tv)) {
+ qfl->qftf_cb = cb;
+ }
+ return OK;
}
/// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the
@@ -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) {
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 14240f0d5f..c63613ab1b 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -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()