patch 8.1.2225: the "last used" info of a buffer is under used

Problem:    The "last used" info of a buffer is under used.
Solution:   Add "lastused" to getbufinfo(). List buffers sorted by last-used
            field. (Andi Massimino, closes #4722)
diff --git a/src/buffer.c b/src/buffer.c
index 86f6ffc..15fbc17 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -2601,6 +2601,13 @@
     return match;
 }
 
+#ifdef FEAT_VIMINFO
+typedef struct {
+    buf_T   *buf;
+    char_u  *match;
+} bufmatch_T;
+#endif
+
 /*
  * Find all buffer names that match.
  * For command line expansion of ":buf" and ":sbuf".
@@ -2619,6 +2626,9 @@
     char_u	*p;
     int		attempt;
     char_u	*patc;
+#ifdef FEAT_VIMINFO
+    bufmatch_T	*matches = NULL;
+#endif
 
     *num_file = 0;		    /* return values in case of FAIL */
     *file = NULL;
@@ -2675,7 +2685,16 @@
 			    p = home_replace_save(buf, p);
 			else
 			    p = vim_strsave(p);
-			(*file)[count++] = p;
+#ifdef FEAT_VIMINFO
+			if (matches != NULL)
+			{
+			    matches[count].buf = buf;
+			    matches[count].match = p;
+			    count++;
+			}
+			else
+#endif
+			    (*file)[count++] = p;
 		    }
 		}
 	    }
@@ -2691,6 +2710,10 @@
 			vim_free(patc);
 		    return FAIL;
 		}
+#ifdef FEAT_VIMINFO
+		if (options & WILD_BUFLASTUSED)
+		    matches = ALLOC_MULT(bufmatch_T, count);
+#endif
 	    }
 	}
 	vim_regfree(regmatch.regprog);
@@ -2701,6 +2724,28 @@
     if (patc != pat)
 	vim_free(patc);
 
+#ifdef FEAT_VIMINFO
+    if (matches != NULL)
+    {
+	int i;
+	if (count > 1)
+	    qsort(matches, count, sizeof(bufmatch_T), buf_compare);
+	// if the current buffer is first in the list, place it at the end
+	if (matches[0].buf == curbuf)
+	{
+	    for (i = 1; i < count; i++)
+		(*file)[i-1] = matches[i].match;
+	    (*file)[count-1] = matches[0].match;
+	}
+	else
+	{
+	    for (i = 0; i < count; i++)
+		(*file)[i] = matches[i].match;
+	}
+	vim_free(matches);
+    }
+#endif
+
     *num_file = count;
     return (count == 0 ? FAIL : OK);
 }
@@ -3016,7 +3061,7 @@
     void
 buflist_list(exarg_T *eap)
 {
-    buf_T	*buf;
+    buf_T	*buf = firstbuf;
     int		len;
     int		i;
     int		ro_char;
@@ -3026,7 +3071,32 @@
     int		job_none_open;
 #endif
 
+#ifdef FEAT_VIMINFO
+    garray_T	buflist;
+    buf_T	**buflist_data = NULL, **p;
+
+    if (vim_strchr(eap->arg, 't'))
+    {
+	ga_init2(&buflist, sizeof(buf_T *), 50);
+	for (buf = firstbuf; buf != NULL; buf = buf->b_next)
+	{
+	    if (ga_grow(&buflist, 1) == OK)
+		((buf_T **)buflist.ga_data)[buflist.ga_len++] = buf;
+	}
+
+	qsort(buflist.ga_data, (size_t)buflist.ga_len,
+		sizeof(buf_T *), buf_compare);
+
+	p = buflist_data = (buf_T **)buflist.ga_data;
+	buf = *p;
+    }
+
+    for (; buf != NULL && !got_int; buf = buflist_data
+	    ? (++p < buflist_data + buflist.ga_len ? *p : NULL)
+	    : buf->b_next)
+#else
     for (buf = firstbuf; buf != NULL && !got_int; buf = buf->b_next)
+#endif
     {
 #ifdef FEAT_TERMINAL
 	job_running = term_job_running(buf->b_term);
@@ -3100,13 +3170,23 @@
 	do
 	    IObuff[len++] = ' ';
 	while (--i > 0 && len < IOSIZE - 18);
-	vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len),
-		_("line %ld"), buf == curbuf ? curwin->w_cursor.lnum
+#ifdef FEAT_VIMINFO
+	if (vim_strchr(eap->arg, 't') && buf->b_last_used)
+	    add_time(IObuff + len, (size_t)(IOSIZE - len), buf->b_last_used);
+	else
+#endif
+	    vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len),
+		    _("line %ld"), buf == curbuf ? curwin->w_cursor.lnum
 					       : (long)buflist_findlnum(buf));
 	msg_outtrans(IObuff);
 	out_flush();	    /* output one line at a time */
 	ui_breakcheck();
     }
+
+#ifdef FEAT_VIMINFO
+    if (buflist_data)
+	ga_clear(&buflist);
+#endif
 }
 
 /*
diff --git a/src/evalbuffer.c b/src/evalbuffer.c
index 35c9ed2..a82b897 100644
--- a/src/evalbuffer.c
+++ b/src/evalbuffer.c
@@ -595,6 +595,10 @@
     }
 #endif
 
+#ifdef FEAT_VIMINFO
+    dict_add_number(dict, "lastused", buf->b_last_used);
+#endif
+
     return dict;
 }
 
diff --git a/src/ex_getln.c b/src/ex_getln.c
index 9c98f99..5cc1c34 100644
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -1407,6 +1407,9 @@
 	 */
 	if ((c == p_wc && !gotesc && KeyTyped) || c == p_wcm)
 	{
+	    int options = WILD_NO_BEEP;
+	    if (wim_flags[wim_index] & WIM_BUFLASTUSED)
+		options |= WILD_BUFLASTUSED;
 	    if (xpc.xp_numfiles > 0)   /* typed p_wc at least twice */
 	    {
 		/* if 'wildmode' contains "list" may still need to list */
@@ -1419,10 +1422,10 @@
 		    did_wild_list = TRUE;
 		}
 		if (wim_flags[wim_index] & WIM_LONGEST)
-		    res = nextwild(&xpc, WILD_LONGEST, WILD_NO_BEEP,
+		    res = nextwild(&xpc, WILD_LONGEST, options,
 							       firstc != '@');
 		else if (wim_flags[wim_index] & WIM_FULL)
-		    res = nextwild(&xpc, WILD_NEXT, WILD_NO_BEEP,
+		    res = nextwild(&xpc, WILD_NEXT, options,
 							       firstc != '@');
 		else
 		    res = OK;	    /* don't insert 'wildchar' now */
@@ -1434,10 +1437,10 @@
 		/* if 'wildmode' first contains "longest", get longest
 		 * common part */
 		if (wim_flags[0] & WIM_LONGEST)
-		    res = nextwild(&xpc, WILD_LONGEST, WILD_NO_BEEP,
+		    res = nextwild(&xpc, WILD_LONGEST, options,
 							       firstc != '@');
 		else
-		    res = nextwild(&xpc, WILD_EXPAND_KEEP, WILD_NO_BEEP,
+		    res = nextwild(&xpc, WILD_EXPAND_KEEP, options,
 							       firstc != '@');
 
 		/* if interrupted while completing, behave like it failed */
@@ -1488,10 +1491,10 @@
 			redrawcmd();
 			did_wild_list = TRUE;
 			if (wim_flags[wim_index] & WIM_LONGEST)
-			    nextwild(&xpc, WILD_LONGEST, WILD_NO_BEEP,
+			    nextwild(&xpc, WILD_LONGEST, options,
 							       firstc != '@');
 			else if (wim_flags[wim_index] & WIM_FULL)
-			    nextwild(&xpc, WILD_NEXT, WILD_NO_BEEP,
+			    nextwild(&xpc, WILD_NEXT, options,
 							       firstc != '@');
 		    }
 		    else
diff --git a/src/misc1.c b/src/misc1.c
index 0e18255..74c8eaa 100644
--- a/src/misc1.c
+++ b/src/misc1.c
@@ -2576,3 +2576,34 @@
 	;
     return path_is_url(p);
 }
+
+/*
+ * Put timestamp "tt" in "buf[buflen]" in a nice format.
+ */
+    void
+add_time(char_u *buf, size_t buflen, time_t tt)
+{
+#ifdef HAVE_STRFTIME
+    struct tm	tmval;
+    struct tm	*curtime;
+
+    if (vim_time() - tt >= 100)
+    {
+	curtime = vim_localtime(&tt, &tmval);
+	if (vim_time() - tt < (60L * 60L * 12L))
+	    /* within 12 hours */
+	    (void)strftime((char *)buf, buflen, "%H:%M:%S", curtime);
+	else
+	    /* longer ago */
+	    (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", curtime);
+    }
+    else
+#endif
+    {
+	long seconds = (long)(vim_time() - tt);
+
+	vim_snprintf((char *)buf, buflen,
+		NGETTEXT("%ld second ago", "%ld seconds ago", seconds),
+		seconds);
+    }
+}
diff --git a/src/option.c b/src/option.c
index 3314a3c..1140a01 100644
--- a/src/option.c
+++ b/src/option.c
@@ -6987,6 +6987,8 @@
 	    new_wim_flags[idx] |= WIM_FULL;
 	else if (i == 4 && STRNCMP(p, "list", 4) == 0)
 	    new_wim_flags[idx] |= WIM_LIST;
+	else if (i == 8 && STRNCMP(p, "lastused", 8) == 0)
+	    new_wim_flags[idx] |= WIM_BUFLASTUSED;
 	else
 	    return FAIL;
 	p += i;
diff --git a/src/option.h b/src/option.h
index 254ca6a..3ee9979 100644
--- a/src/option.h
+++ b/src/option.h
@@ -338,6 +338,7 @@
 #define WIM_FULL	0x01
 #define WIM_LONGEST	0x02
 #define WIM_LIST	0x04
+#define WIM_BUFLASTUSED	0x08
 
 // arguments for can_bs()
 #define BS_INDENT	'i'	// "Indent"
diff --git a/src/proto/misc1.pro b/src/proto/misc1.pro
index 1a06d7e..2f7bd03 100644
--- a/src/proto/misc1.pro
+++ b/src/proto/misc1.pro
@@ -46,4 +46,5 @@
 char_u *get_isolated_shell_name(void);
 int path_is_url(char_u *p);
 int path_with_url(char_u *fname);
+void add_time(char_u *buf, size_t buflen, time_t tt);
 /* vim: set ft=c : */
diff --git a/src/proto/viminfo.pro b/src/proto/viminfo.pro
index a1f0337..b1f97f6 100644
--- a/src/proto/viminfo.pro
+++ b/src/proto/viminfo.pro
@@ -3,5 +3,6 @@
 void check_marks_read(void);
 int read_viminfo(char_u *file, int flags);
 void write_viminfo(char_u *file, int forceit);
+int buf_compare(const void *s1, const void *s2);
 void ex_viminfo(exarg_T *eap);
 /* vim: set ft=c : */
diff --git a/src/testdir/test_bufwintabinfo.vim b/src/testdir/test_bufwintabinfo.vim
index ee22ebd..b4b8a10 100644
--- a/src/testdir/test_bufwintabinfo.vim
+++ b/src/testdir/test_bufwintabinfo.vim
@@ -139,3 +139,15 @@
     set foldlevel=0
   endif
 endfunc
+
+function Test_getbufinfo_lastused()
+  call test_settime(1234567)
+  edit Xtestfile1
+  enew
+  call test_settime(7654321)
+  edit Xtestfile2
+  enew
+  call assert_equal(getbufinfo('Xtestfile1')[0].lastused, 1234567)
+  call assert_equal(getbufinfo('Xtestfile2')[0].lastused, 7654321)
+  call test_settime(0)
+endfunc
diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim
index 55f35e7..57dff41 100644
--- a/src/testdir/test_cmdline.vim
+++ b/src/testdir/test_cmdline.vim
@@ -767,3 +767,48 @@
   endtry
   bw!
 endfunc
+
+func Test_buffers_lastused()
+  " check that buffers are sorted by time when wildmode has lastused
+  call test_settime(1550020000)	  " middle
+  edit bufa
+  enew
+  call test_settime(1550030000)	  " newest
+  edit bufb
+  enew
+  call test_settime(1550010000)	  " oldest
+  edit bufc
+  enew
+  call test_settime(0)
+  enew
+
+  call assert_equal(['bufa', 'bufb', 'bufc'],
+	\ getcompletion('', 'buffer'))
+
+  let save_wildmode = &wildmode
+  set wildmode=full:lastused
+
+  let cap = "\<c-r>=execute('let X=getcmdline()')\<cr>"
+  call feedkeys(":b \<tab>" .. cap .. "\<esc>", 'xt')
+  call assert_equal('b bufb', X)
+  call feedkeys(":b \<tab>\<tab>" .. cap .. "\<esc>", 'xt')
+  call assert_equal('b bufa', X)
+  call feedkeys(":b \<tab>\<tab>\<tab>" .. cap .. "\<esc>", 'xt')
+  call assert_equal('b bufc', X)
+  enew
+
+  edit other
+  call feedkeys(":b \<tab>" .. cap .. "\<esc>", 'xt')
+  call assert_equal('b bufb', X)
+  call feedkeys(":b \<tab>\<tab>" .. cap .. "\<esc>", 'xt')
+  call assert_equal('b bufa', X)
+  call feedkeys(":b \<tab>\<tab>\<tab>" .. cap .. "\<esc>", 'xt')
+  call assert_equal('b bufc', X)
+  enew
+
+  let &wildmode = save_wildmode
+
+  bwipeout bufa
+  bwipeout bufb
+  bwipeout bufc
+endfunc
diff --git a/src/testdir/test_excmd.vim b/src/testdir/test_excmd.vim
index 509d78d..992fc3d 100644
--- a/src/testdir/test_excmd.vim
+++ b/src/testdir/test_excmd.vim
@@ -19,3 +19,28 @@
   normal vv
   call assert_fails(":'<,'>echo 1", 'E481:')
 endfunc
+
+func Test_buffers_lastused()
+  call test_settime(localtime() - 2000) " middle
+  edit bufa
+  enew
+  call test_settime(localtime() - 10)   " newest
+  edit bufb
+  enew
+  call test_settime(1550010000)	        " oldest
+  edit bufc
+  enew
+  call test_settime(0)
+  enew
+
+  let ls = split(execute('buffers t', 'silent!'), '\n')
+  let bufs = ls->map({i,v->split(v, '"\s*')[1:2]})
+  call assert_equal(['bufb', 'bufa', 'bufc'], bufs[1:]->map({i,v->v[0]}))
+  call assert_match('1[0-3] seconds ago', bufs[1][1])
+  call assert_match('\d\d:\d\d:\d\d', bufs[2][1])
+  call assert_match('2019/02/1\d \d\d:\d\d:00', bufs[3][1])
+
+  bwipeout bufa
+  bwipeout bufb
+  bwipeout bufc
+endfunc
diff --git a/src/undo.c b/src/undo.c
index 2736f2a..64fb205 100644
--- a/src/undo.c
+++ b/src/undo.c
@@ -106,7 +106,6 @@
 static void u_doit(int count);
 static void u_undoredo(int undo);
 static void u_undo_end(int did_undo, int absolute);
-static void u_add_time(char_u *buf, size_t buflen, time_t tt);
 static void u_freeheader(buf_T *buf, u_header_T *uhp, u_header_T **uhpp);
 static void u_freebranch(buf_T *buf, u_header_T *uhp, u_header_T **uhpp);
 static void u_freeentries(buf_T *buf, u_header_T *uhp, u_header_T **uhpp);
@@ -2973,7 +2972,7 @@
     if (uhp == NULL)
 	*msgbuf = NUL;
     else
-	u_add_time(msgbuf, sizeof(msgbuf), uhp->uh_time);
+	add_time(msgbuf, sizeof(msgbuf), uhp->uh_time);
 
 #ifdef FEAT_CONCEAL
     {
@@ -3050,7 +3049,7 @@
 		break;
 	    vim_snprintf((char *)IObuff, IOSIZE, "%6ld %7d  ",
 							uhp->uh_seq, changes);
-	    u_add_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff),
+	    add_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff),
 								uhp->uh_time);
 	    if (uhp->uh_save_nr > 0)
 	    {
@@ -3125,37 +3124,6 @@
 }
 
 /*
- * Put the timestamp of an undo header in "buf[buflen]" in a nice format.
- */
-    static void
-u_add_time(char_u *buf, size_t buflen, time_t tt)
-{
-#ifdef HAVE_STRFTIME
-    struct tm	tmval;
-    struct tm	*curtime;
-
-    if (vim_time() - tt >= 100)
-    {
-	curtime = vim_localtime(&tt, &tmval);
-	if (vim_time() - tt < (60L * 60L * 12L))
-	    /* within 12 hours */
-	    (void)strftime((char *)buf, buflen, "%H:%M:%S", curtime);
-	else
-	    /* longer ago */
-	    (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", curtime);
-    }
-    else
-#endif
-    {
-	long seconds = (long)(vim_time() - tt);
-
-	vim_snprintf((char *)buf, buflen,
-		NGETTEXT("%ld second ago", "%ld seconds ago", seconds),
-		seconds);
-    }
-}
-
-/*
  * ":undojoin": continue adding to the last entry list
  */
     void
diff --git a/src/version.c b/src/version.c
index 62a4888..3f36958 100644
--- a/src/version.c
+++ b/src/version.c
@@ -742,6 +742,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2225,
+/**/
     2224,
 /**/
     2223,
diff --git a/src/vim.h b/src/vim.h
index 917dbfa..230f5b3 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -811,6 +811,7 @@
 #define WILD_ALLLINKS		    0x200
 #define WILD_IGNORE_COMPLETESLASH   0x400
 #define WILD_NOERROR		    0x800  // sets EW_NOERROR
+#define WILD_BUFLASTUSED	    0x1000
 
 // Flags for expand_wildcards()
 #define EW_DIR		0x01	// include directory names
diff --git a/src/viminfo.c b/src/viminfo.c
index b162828..a49bb01 100644
--- a/src/viminfo.c
+++ b/src/viminfo.c
@@ -2152,7 +2152,7 @@
 /*
  * Compare functions for qsort() below, that compares b_last_used.
  */
-    static int
+    int
 buf_compare(const void *s1, const void *s2)
 {
     buf_T *buf1 = *(buf_T **)s1;