# HG changeset patch # User Christian Brabandt # Date 1691789405 -7200 # Node ID 05188ca69e43db37577a1bb363766585612d0396 # Parent 6c57126dc2d1fc59236168e838006d5e3023dee7 patch 9.0.1688: cannot store custom data in quickfix list Commit: https://github.com/vim/vim/commit/ca6ac99077d2e6d020a34267aa5e0fbc4d23dc38 Author: Tom Praschan <13141438+tom-anders@users.noreply.github.com> Date: Fri Aug 11 23:26:12 2023 +0200 patch 9.0.1688: cannot store custom data in quickfix list Problem: cannot store custom data in quickfix list Solution: add `user_data` field for the quickfix list closes: #11818 Signed-off-by: Christian Brabandt Co-authored-by: Tom Praschan <13141438+tom-anders@users.noreply.github.com> diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -4002,6 +4002,9 @@ getqflist([{what}]) *getqflist()* text description of the error type type of the error, 'E', '1', etc. valid |TRUE|: recognized error message + user_data + custom data associated with the item, can be + any type. When there is no error list or it's empty, an empty list is returned. Quickfix list entries with a non-existing buffer @@ -8298,6 +8301,8 @@ setqflist({list} [, {action} [, {what}]] text description of the error type single-character error type, 'E', 'W', etc. valid recognized error message + user_data custom data associated with the item, can be + any type. The "col", "vcol", "nr", "type" and "text" entries are optional. Either "lnum" or "pattern" entry can be used to diff --git a/src/quickfix.c b/src/quickfix.c --- a/src/quickfix.c +++ b/src/quickfix.c @@ -43,6 +43,7 @@ struct qfline_S char_u qf_cleared; // set to TRUE if line has been deleted char_u qf_type; // type of the error (mostly 'E'); 1 for // :helpgrep + typval_T qf_user_data; // custom user data associated with this item char_u qf_valid; // valid error message detected }; @@ -82,6 +83,7 @@ typedef struct qf_list_S int qf_count; // number of errors (0 means empty list) int qf_index; // current index in the error list int qf_nonevalid; // TRUE if not a single valid entry found + int qf_has_user_data; // TRUE if at least one item has user_data attached 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 @@ -168,7 +170,7 @@ static efm_T *fmt_start = NULL; // cache 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, long end_lnum, int col, int end_col, int vis_col, char_u *pattern, int nr, int type, int valid); +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, long end_lnum, int col, int end_col, int vis_col, char_u *pattern, int nr, int type, typval_T *user_data, int valid); static void qf_free(qf_list_T *qfl); static char_u *qf_types(int, int); static int qf_get_fnum(qf_list_T *qfl, char_u *, char_u *); @@ -951,6 +953,7 @@ typedef struct { char_u *pattern; int enr; int type; + typval_T *user_data; int valid; } qffields_T; @@ -1720,6 +1723,7 @@ qf_init_process_nextline( fields->pattern, fields->enr, fields->type, + fields->user_data, fields->valid); } @@ -1965,6 +1969,7 @@ qf_new_list(qf_info_T *qi, char_u *qf_ti qf_store_title(qfl, qf_title); qfl->qfl_type = qi->qfl_type; qfl->qf_id = ++last_qf_id; + qfl->qf_has_user_data = FALSE; } /* @@ -2145,6 +2150,7 @@ qf_add_entry( char_u *pattern, // search pattern int nr, // error number int type, // type character + typval_T *user_data, // custom user data or NULL int valid) // valid entry { qfline_T *qfp; @@ -2173,6 +2179,13 @@ qf_add_entry( qfp->qf_col = col; qfp->qf_end_col = end_col; qfp->qf_viscol = vis_col; + if (user_data == NULL || user_data->v_type == VAR_UNKNOWN) + qfp->qf_user_data.v_type = VAR_UNKNOWN; + else + { + copy_tv(user_data, &qfp->qf_user_data); + qfl->qf_has_user_data = TRUE; + } if (pattern == NULL || *pattern == NUL) qfp->qf_pattern = NULL; else if ((qfp->qf_pattern = vim_strsave(pattern)) == NULL) @@ -2335,6 +2348,7 @@ copy_loclist_entries(qf_list_T *from_qfl from_qfp->qf_pattern, from_qfp->qf_nr, 0, + &from_qfp->qf_user_data, from_qfp->qf_valid) == QF_FAIL) return FAIL; @@ -2360,6 +2374,7 @@ copy_loclist(qf_list_T *from_qfl, qf_lis // Some of the fields are populated by qf_add_entry() to_qfl->qfl_type = from_qfl->qfl_type; to_qfl->qf_nonevalid = from_qfl->qf_nonevalid; + to_qfl->qf_has_user_data = from_qfl->qf_has_user_data; to_qfl->qf_count = 0; to_qfl->qf_index = 0; to_qfl->qf_start = NULL; @@ -3957,6 +3972,7 @@ qf_free_items(qf_list_T *qfl) vim_free(qfp->qf_module); vim_free(qfp->qf_text); vim_free(qfp->qf_pattern); + clear_tv(&qfp->qf_user_data); stop = (qfp == qfpnext); vim_free(qfp); if (stop) @@ -6085,6 +6101,7 @@ vgr_match_buflines( NULL, // search pattern 0, // nr 0, // type + NULL, // user_data TRUE // valid ) == QF_FAIL) { @@ -6131,6 +6148,7 @@ vgr_match_buflines( NULL, // search pattern 0, // nr 0, // type + NULL, // user_data TRUE // valid ) == QF_FAIL) { @@ -6771,6 +6789,8 @@ get_qfline_items(qfline_T *qfp, list_T * || dict_add_string(dict, "pattern", qfp->qf_pattern) == FAIL || dict_add_string(dict, "text", qfp->qf_text) == FAIL || dict_add_string(dict, "type", buf) == FAIL + || (qfp->qf_user_data.v_type != VAR_UNKNOWN + && dict_add_tv(dict, "user_data", &qfp->qf_user_data) == FAIL ) || dict_add_number(dict, "valid", (long)qfp->qf_valid) == FAIL) return FAIL; @@ -7305,6 +7325,9 @@ qf_add_entry_from_dict( text = dict_get_string(d, "text", TRUE); if (text == NULL) text = vim_strsave((char_u *)""); + typval_T user_data; + user_data.v_type = VAR_UNKNOWN; + dict_get_tv(d, "user_data", &user_data); valid = TRUE; if ((filename == NULL && bufnum == 0) || (lnum == 0 && pattern == NULL)) @@ -7341,6 +7364,7 @@ qf_add_entry_from_dict( pattern, // search pattern nr, type == NULL ? NUL : *type, + &user_data, valid); vim_free(filename); @@ -7348,6 +7372,7 @@ qf_add_entry_from_dict( vim_free(pattern); vim_free(text); vim_free(type); + clear_tv(&user_data); if (valid) *valid_entry = TRUE; @@ -7805,6 +7830,27 @@ set_errorlist( return retval; } +static int mark_quickfix_user_data(qf_info_T *qi, int copyID) +{ + int abort = FALSE; + for (int i = 0; i < LISTCOUNT && !abort; ++i) + { + qf_list_T *qfl = &qi->qf_lists[i]; + if (!qfl->qf_has_user_data) + continue; + qfline_T *qfp; + int j; + FOR_ALL_QFL_ITEMS(qfl, qfp, j) + { + typval_T* user_data = &qfp->qf_user_data; + if (user_data != NULL && user_data->v_type != VAR_NUMBER + && user_data->v_type != VAR_STRING && user_data->v_type != VAR_FLOAT) + abort = abort || set_ref_in_item(user_data, copyID, NULL, NULL); + } + } + return abort; +} + /* * Mark the quickfix context and callback function as in use for all the lists * in a quickfix stack. @@ -7846,6 +7892,10 @@ set_ref_in_quickfix(int copyID) if (abort) return abort; + abort = mark_quickfix_user_data(&ql_info, copyID); + if (abort) + return abort; + abort = set_ref_in_callback(&qftf_cb, copyID); if (abort) return abort; @@ -7857,6 +7907,10 @@ set_ref_in_quickfix(int copyID) abort = mark_quickfix_ctx(win->w_llist, copyID); if (abort) return abort; + + abort = mark_quickfix_user_data(win->w_llist, copyID); + if (abort) + return abort; } if (IS_LL_WINDOW(win) && (win->w_llist_ref->qf_refcount == 1)) { @@ -8225,6 +8279,7 @@ hgr_search_file( NULL, // search pattern 0, // nr 1, // type + NULL, // user_data TRUE // valid ) == QF_FAIL) { 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 @@ -1808,13 +1808,23 @@ func SetXlistTests(cchar, bnum) call s:setup_commands(a:cchar) call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 1}, - \ {'bufnr': a:bnum, 'lnum': 2, 'end_lnum': 3, 'col': 4, 'end_col': 5}]) + \ {'bufnr': a:bnum, 'lnum': 2, 'end_lnum': 3, 'col': 4, 'end_col': 5, 'user_data': {'6': [7, 8]}}]) let l = g:Xgetlist() call assert_equal(2, len(l)) call assert_equal(2, l[1].lnum) call assert_equal(3, l[1].end_lnum) call assert_equal(4, l[1].col) call assert_equal(5, l[1].end_col) + call assert_equal({'6': [7, 8]}, l[1].user_data) + + " Test that user_data is garbage collected + call g:Xsetlist([{'user_data': ['high', 5]}, + \ {'user_data': {'this': [7, 'eight'], 'is': ['a', 'dictionary']}}]) + call test_garbagecollect_now() + let l = g:Xgetlist() + call assert_equal(2, len(l)) + call assert_equal(['high', 5], l[0].user_data) + call assert_equal({'this': [7, 'eight'], 'is': ['a', 'dictionary']}, l[1].user_data) Xnext call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 3}], 'a') diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -696,6 +696,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1688, +/**/ 1687, /**/ 1686,