changeset 18463:18d7337b6837 v8.1.2225

patch 8.1.2225: the "last used" info of a buffer is under used Commit: https://github.com/vim/vim/commit/52410575be50d5c40bbe6380159df48cfc382ceb Author: Bram Moolenaar <Bram@vim.org> Date: Sun Oct 27 05:12:45 2019 +0100 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)
author Bram Moolenaar <Bram@vim.org>
date Sun, 27 Oct 2019 05:15:06 +0100
parents 395349479800
children 44a72ca3b693
files runtime/doc/eval.txt runtime/doc/options.txt runtime/doc/windows.txt src/buffer.c src/evalbuffer.c src/ex_getln.c src/misc1.c src/option.c src/option.h src/proto/misc1.pro src/proto/viminfo.pro src/testdir/test_bufwintabinfo.vim src/testdir/test_cmdline.vim src/testdir/test_excmd.vim src/undo.c src/version.c src/vim.h src/viminfo.c
diffstat 18 files changed, 228 insertions(+), 45 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -4778,6 +4778,10 @@ getbufinfo([{dict}])
 			changed		TRUE if the buffer is modified.
 			changedtick	number of changes made to the buffer.
 			hidden		TRUE if the buffer is hidden.
+			lastused	timestamp in seconds, like
+					|localtime()|, when the buffer was
+					last used.
+					{only with the |+viminfo| feature}
 			listed		TRUE if the buffer is listed.
 			lnum		current line number in buffer.
 			loaded		TRUE if the buffer is loaded.
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -8689,6 +8689,8 @@ A jump table for the options with a shor
 			complete first match.
 	"list:longest"	When more than one match, list all matches and
 			complete till longest common string.
+	"list:lastused" When more than one buffer matches, sort buffers
+			by time last used (other than the current buffer).
 	When there is only a single match, it is fully completed in all cases.
 
 	Examples: >
--- a/runtime/doc/windows.txt
+++ b/runtime/doc/windows.txt
@@ -1090,6 +1090,7 @@ list of buffers. |unlisted-buffer|
 		     R	 terminal buffers with a running job
 		     F	 terminal buffers with a finished job
 		     ?   terminal buffers without a job: `:terminal NONE`
+		     t   show time last used and sort buffers
 		Combining flags means they are "and"ed together, e.g.:
 		     h+   hidden buffers which are modified
 		     a+   active buffers which are modified
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -2601,6 +2601,13 @@ buflist_findpat(
     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 @@ ExpandBufnames(
     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 @@ ExpandBufnames(
 			    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 @@ ExpandBufnames(
 			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 @@ ExpandBufnames(
     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 @@ buflist_findlnum(buf_T *buf)
     void
 buflist_list(exarg_T *eap)
 {
-    buf_T	*buf;
+    buf_T	*buf = firstbuf;
     int		len;
     int		i;
     int		ro_char;
@@ -3026,7 +3071,32 @@ buflist_list(exarg_T *eap)
     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 @@ buflist_list(exarg_T *eap)
 	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
 }
 
 /*
--- a/src/evalbuffer.c
+++ b/src/evalbuffer.c
@@ -595,6 +595,10 @@ get_buffer_info(buf_T *buf)
     }
 #endif
 
+#ifdef FEAT_VIMINFO
+    dict_add_number(dict, "lastused", buf->b_last_used);
+#endif
+
     return dict;
 }
 
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -1407,6 +1407,9 @@ getcmdline_int(
 	 */
 	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 @@ getcmdline_int(
 		    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 @@ getcmdline_int(
 		/* 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 @@ getcmdline_int(
 			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
--- a/src/misc1.c
+++ b/src/misc1.c
@@ -2576,3 +2576,34 @@ path_with_url(char_u *fname)
 	;
     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);
+    }
+}
--- a/src/option.c
+++ b/src/option.c
@@ -6987,6 +6987,8 @@ check_opt_wim(void)
 	    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;
--- 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"
--- a/src/proto/misc1.pro
+++ b/src/proto/misc1.pro
@@ -46,4 +46,5 @@ int goto_im(void);
 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 : */
--- a/src/proto/viminfo.pro
+++ b/src/proto/viminfo.pro
@@ -3,5 +3,6 @@ int get_viminfo_parameter(int type);
 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 : */
--- a/src/testdir/test_bufwintabinfo.vim
+++ b/src/testdir/test_bufwintabinfo.vim
@@ -139,3 +139,15 @@ function Test_get_win_options()
     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
--- a/src/testdir/test_cmdline.vim
+++ b/src/testdir/test_cmdline.vim
@@ -767,3 +767,48 @@ func Test_cmdwin_bug()
   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
--- a/src/testdir/test_excmd.vim
+++ b/src/testdir/test_excmd.vim
@@ -19,3 +19,28 @@ func Test_range_error()
   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
--- a/src/undo.c
+++ b/src/undo.c
@@ -106,7 +106,6 @@ static void u_getbot(void);
 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 @@ u_undo_end(
     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 @@ ex_undolist(exarg_T *eap UNUSED)
 		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 @@ ex_undolist(exarg_T *eap UNUSED)
 }
 
 /*
- * 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
--- a/src/version.c
+++ b/src/version.c
@@ -742,6 +742,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2225,
+/**/
     2224,
 /**/
     2223,
--- a/src/vim.h
+++ b/src/vim.h
@@ -811,6 +811,7 @@ extern int (*dyn_libintl_wputenv)(const 
 #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
--- a/src/viminfo.c
+++ b/src/viminfo.c
@@ -2152,7 +2152,7 @@ write_viminfo_filemarks(FILE *fp)
 /*
  * 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;