patch 8.0.0657: cannot get and set quickfix list items

Problem:    Cannot get and set quickfix list items.
Solution:   Add the "items" argument to getqflist() and setqflist(). (Yegappan
            Lakshmanan)
diff --git a/src/quickfix.c b/src/quickfix.c
index f4bc597..40b6fe9 100644
--- a/src/quickfix.c
+++ b/src/quickfix.c
@@ -47,19 +47,32 @@
  */
 #define LISTCOUNT   10
 
+/*
+ * Quickfix/Location list definition
+ * Contains a list of entries (qfline_T). qf_start points to the first entry
+ * and qf_last points to the last entry. qf_count contains the list size.
+ *
+ * Usually the list contains one or more entries. But an empty list can be
+ * created using setqflist()/setloclist() with a title and/or user context
+ * information and entries can be added later using setqflist()/setloclist().
+ */
 typedef struct qf_list_S
 {
     qfline_T	*qf_start;	/* pointer to the first error */
     qfline_T	*qf_last;	/* pointer to the last error */
     qfline_T	*qf_ptr;	/* pointer to the current error */
-    int		qf_count;	/* number of errors (0 means no error list) */
+    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 */
     char_u	*qf_title;	/* title derived from the command that created
-				 * the error list */
+				 * the error list or set by setqflist */
     typval_T	*qf_ctx;	/* context set by setqflist/setloclist */
 } qf_list_T;
 
+/*
+ * Quickfix/Location list stack definition
+ * Contains a list of quickfix/location lists (qf_list_T)
+ */
 struct qf_info_S
 {
     /*
@@ -1347,6 +1360,9 @@
     static void
 qf_store_title(qf_info_T *qi, int qf_idx, char_u *title)
 {
+    vim_free(qi->qf_lists[qf_idx].qf_title);
+    qi->qf_lists[qf_idx].qf_title = NULL;
+
     if (title != NULL)
     {
 	char_u *p = alloc((int)STRLEN(title) + 2);
@@ -2735,10 +2751,11 @@
 }
 
 /*
- * Free all the entries in the error list "idx".
+ * Free all the entries in the error list "idx". Note that other information
+ * associated with the list like context and title are not freed.
  */
     static void
-qf_free(qf_info_T *qi, int idx)
+qf_free_items(qf_info_T *qi, int idx)
 {
     qfline_T	*qfp;
     qfline_T	*qfpnext;
@@ -2763,10 +2780,7 @@
 	qi->qf_lists[idx].qf_start = qfpnext;
 	--qi->qf_lists[idx].qf_count;
     }
-    vim_free(qi->qf_lists[idx].qf_title);
-    qi->qf_lists[idx].qf_title = NULL;
-    free_tv(qi->qf_lists[idx].qf_ctx);
-    qi->qf_lists[idx].qf_ctx = NULL;
+
     qi->qf_lists[idx].qf_index = 0;
     qi->qf_lists[idx].qf_start = NULL;
     qi->qf_lists[idx].qf_last = NULL;
@@ -2783,6 +2797,21 @@
 }
 
 /*
+ * Free error list "idx". Frees all the entries in the quickfix list,
+ * associated context information and the title.
+ */
+    static void
+qf_free(qf_info_T *qi, int idx)
+{
+    qf_free_items(qi, idx);
+
+    vim_free(qi->qf_lists[idx].qf_title);
+    qi->qf_lists[idx].qf_title = NULL;
+    free_tv(qi->qf_lists[idx].qf_ctx);
+    qi->qf_lists[idx].qf_ctx = NULL;
+}
+
+/*
  * qf_mark_adjust: adjust marks
  */
    void
@@ -4698,13 +4727,11 @@
 	} else if ((di->di_tv.v_type == VAR_STRING) &&
 		(STRCMP(di->di_tv.vval.v_string, "$") == 0))
 	{
-	    {
-		/* Get the last quickfix list number */
-		if (qi->qf_listcount > 0)
-		    qf_idx = qi->qf_listcount - 1;
-		else
-		    qf_idx = -1;	/* Quickfix stack is empty */
-	    }
+	    /* Get the last quickfix list number */
+	    if (qi->qf_listcount > 0)
+		qf_idx = qi->qf_listcount - 1;
+	    else
+		qf_idx = -1;	/* Quickfix stack is empty */
 	    flags |= QF_GETLIST_NR;
 	}
 	else
@@ -4724,6 +4751,9 @@
 
 	if (dict_find(what, (char_u *)"context", -1) != NULL)
 	    flags |= QF_GETLIST_CONTEXT;
+
+	if (dict_find(what, (char_u *)"items", -1) != NULL)
+	    flags |= QF_GETLIST_ITEMS;
     }
 
     if (flags & QF_GETLIST_TITLE)
@@ -4743,6 +4773,15 @@
 	if (win != NULL)
 	    status = dict_add_nr_str(retdict, "winid", win->w_id, NULL);
     }
+    if ((status == OK) && (flags & QF_GETLIST_ITEMS))
+    {
+	list_T	*l = list_alloc();
+	if (l != NULL)
+	{
+	    (void)get_errorlist(wp, qf_idx, l);
+	    dict_add_list(retdict, "items", l);
+	}
+    }
 
     if ((status == OK) && (flags & QF_GETLIST_CONTEXT))
     {
@@ -4802,7 +4841,7 @@
 #endif
     else if (action == 'r')
     {
-	qf_free(qi, qf_idx);
+	qf_free_items(qi, qf_idx);
 	qf_store_title(qi, qf_idx, title);
     }
 
@@ -4915,15 +4954,27 @@
 	    /* for zero use the current list */
 	    if (di->di_tv.vval.v_number != 0)
 		qf_idx = di->di_tv.vval.v_number - 1;
-	    if (qf_idx < 0 || qf_idx >= qi->qf_listcount)
+
+	    if ((action == ' ' || action == 'a') &&
+		    qf_idx == qi->qf_listcount)
+		/*
+		 * When creating a new list, accept qf_idx pointing to the next
+		 * non-available list
+		 */
+		newlist = TRUE;
+	    else if (qf_idx < 0 || qf_idx >= qi->qf_listcount)
 		return FAIL;
+	    else
+		newlist = FALSE;	/* use the specified list */
 	} else if (di->di_tv.v_type == VAR_STRING &&
 		STRCMP(di->di_tv.vval.v_string, "$") == 0 &&
 		qi->qf_listcount > 0)
+	{
 	    qf_idx = qi->qf_listcount - 1;
+	    newlist = FALSE;
+	}
 	else
 	    return FAIL;
-	newlist = FALSE;	/* use the specified list */
     }
 
     if (newlist)
@@ -4944,6 +4995,17 @@
 	    retval = OK;
 	}
     }
+    if ((di = dict_find(what, (char_u *)"items", -1)) != NULL)
+    {
+	if (di->di_tv.v_type == VAR_LIST)
+	{
+	    char_u *title_save = vim_strsave(qi->qf_lists[qf_idx].qf_title);
+
+	    retval = qf_add_entries(qi, qf_idx, di->di_tv.vval.v_list,
+		    title_save, action == ' ' ? 'a' : action);
+	    vim_free(title_save);
+	}
+    }
 
     if ((di = dict_find(what, (char_u *)"context", -1)) != NULL)
     {
diff --git a/src/testdir/test_quickfix.vim b/src/testdir/test_quickfix.vim
index 16187be..c23e597 100644
--- a/src/testdir/test_quickfix.vim
+++ b/src/testdir/test_quickfix.vim
@@ -1835,6 +1835,73 @@
     call test_garbagecollect_now()
     let m = g:Xgetlist({'context' : 1})
     call assert_equal(["red", "blue", "green"], m.context)
+
+    " Test for setting/getting items
+    Xexpr ""
+    let qfprev = g:Xgetlist({'nr':0})
+    call g:Xsetlist([], ' ', {'title':'Green',
+		\ 'items' : [{'filename':'F1', 'lnum':10}]})
+    let qfcur = g:Xgetlist({'nr':0})
+    call assert_true(qfcur.nr == qfprev.nr + 1)
+    let l = g:Xgetlist({'items':1})
+    call assert_equal('F1', bufname(l.items[0].bufnr))
+    call assert_equal(10, l.items[0].lnum)
+    call g:Xsetlist([], 'a', {'items' : [{'filename':'F2', 'lnum':20},
+		\  {'filename':'F2', 'lnum':30}]})
+    let l = g:Xgetlist({'items':1})
+    call assert_equal('F2', bufname(l.items[2].bufnr))
+    call assert_equal(30, l.items[2].lnum)
+    call g:Xsetlist([], 'r', {'items' : [{'filename':'F3', 'lnum':40}]})
+    let l = g:Xgetlist({'items':1})
+    call assert_equal('F3', bufname(l.items[0].bufnr))
+    call assert_equal(40, l.items[0].lnum)
+    call g:Xsetlist([], 'r', {'items' : []})
+    let l = g:Xgetlist({'items':1})
+    call assert_equal(0, len(l.items))
+
+    " Save and restore the quickfix stack
+    call g:Xsetlist([], 'f')
+    call assert_equal(0, g:Xgetlist({'nr':'$'}).nr)
+    Xexpr "File1:10:Line1"
+    Xexpr "File2:20:Line2"
+    Xexpr "File3:30:Line3"
+    let last_qf = g:Xgetlist({'nr':'$'}).nr
+    call assert_equal(3, last_qf)
+    let qstack = []
+    for i in range(1, last_qf)
+	let qstack = add(qstack, g:Xgetlist({'nr':i, 'all':1}))
+    endfor
+    call g:Xsetlist([], 'f')
+    for i in range(len(qstack))
+	call g:Xsetlist([], ' ', qstack[i])
+    endfor
+    call assert_equal(3, g:Xgetlist({'nr':'$'}).nr)
+    call assert_equal(10, g:Xgetlist({'nr':1, 'items':1}).items[0].lnum)
+    call assert_equal(20, g:Xgetlist({'nr':2, 'items':1}).items[0].lnum)
+    call assert_equal(30, g:Xgetlist({'nr':3, 'items':1}).items[0].lnum)
+    call g:Xsetlist([], 'f')
+
+    " Swap two quickfix lists
+    Xexpr "File1:10:Line10"
+    Xexpr "File2:20:Line20"
+    Xexpr "File3:30:Line30"
+    call g:Xsetlist([], 'r', {'nr':1,'title':'Colors','context':['Colors']})
+    call g:Xsetlist([], 'r', {'nr':2,'title':'Fruits','context':['Fruits']})
+    let l1=g:Xgetlist({'nr':1,'all':1})
+    let l2=g:Xgetlist({'nr':2,'all':1})
+    let l1.nr=2
+    let l2.nr=1
+    call g:Xsetlist([], 'r', l1)
+    call g:Xsetlist([], 'r', l2)
+    let newl1=g:Xgetlist({'nr':1,'all':1})
+    let newl2=g:Xgetlist({'nr':2,'all':1})
+    call assert_equal(':Fruits', newl1.title)
+    call assert_equal(['Fruits'], newl1.context)
+    call assert_equal('Line20', newl1.items[0].text)
+    call assert_equal(':Colors', newl2.title)
+    call assert_equal(['Colors'], newl2.context)
+    call assert_equal('Line10', newl2.items[0].text)
+    call g:Xsetlist([], 'f')
 endfunc
 
 func Test_qf_property()
diff --git a/src/version.c b/src/version.c
index fd57e48..58ae3ae 100644
--- a/src/version.c
+++ b/src/version.c
@@ -765,6 +765,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    657,
+/**/
     656,
 /**/
     655,