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 <cb@256bit.org>
Co-authored-by: Tom Praschan <13141438+tom-anders@users.noreply.github.com>
diff --git a/src/quickfix.c b/src/quickfix.c
index 4501d11..6aa828b 100644
--- a/src/quickfix.c
+++ b/src/quickfix.c
@@ -43,6 +43,7 @@
     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 @@
     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 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 @@
     char_u	*pattern;
     int		enr;
     int		type;
+    typval_T	*user_data;
     int		valid;
 } qffields_T;
 
@@ -1720,6 +1723,7 @@
 		fields->pattern,
 		fields->enr,
 		fields->type,
+		fields->user_data,
 		fields->valid);
 }
 
@@ -1965,6 +1969,7 @@
     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 @@
     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 @@
     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 @@
 		    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 @@
     // 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 @@
 	    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 @@
 			    NULL,	// search pattern
 			    0,		// nr
 			    0,		// type
+			    NULL,	// user_data
 			    TRUE	// valid
 			    ) == QF_FAIL)
 		{
@@ -6131,6 +6148,7 @@
 			    NULL,	// search pattern
 			    0,		// nr
 			    0,		// type
+			    NULL,	// user_data
 			    TRUE	// valid
 			    ) == QF_FAIL)
 		{
@@ -6771,6 +6789,8 @@
 	    || 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 @@
     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 @@
 			pattern,	// search pattern
 			nr,
 			type == NULL ? NUL : *type,
+			&user_data,
 			valid);
 
     vim_free(filename);
@@ -7348,6 +7372,7 @@
     vim_free(pattern);
     vim_free(text);
     vim_free(type);
+    clear_tv(&user_data);
 
     if (valid)
 	*valid_entry = TRUE;
@@ -7805,6 +7830,27 @@
     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 @@
     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 @@
 	    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 @@
 			NULL,	// search pattern
 			0,	// nr
 			1,	// type
+			NULL,	// user_data
 			TRUE	// valid
 			) == QF_FAIL)
 	    {