# HG changeset patch # User Bram Moolenaar # Date 1572149706 -3600 # Node ID 18d7337b6837b58a65172591f579e00f15744ed9 # Parent 3953494798001110f6c32c11e421e3533b43d53b 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 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) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- 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. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt --- 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: > diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt --- 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 diff --git a/src/buffer.c b/src/buffer.c --- 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 } /* diff --git a/src/evalbuffer.c b/src/evalbuffer.c --- 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; } diff --git a/src/ex_getln.c b/src/ex_getln.c --- 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 diff --git a/src/misc1.c b/src/misc1.c --- 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); + } +} diff --git a/src/option.c b/src/option.c --- 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; diff --git a/src/option.h b/src/option.h --- 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 --- 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 : */ diff --git a/src/proto/viminfo.pro b/src/proto/viminfo.pro --- 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 : */ diff --git a/src/testdir/test_bufwintabinfo.vim b/src/testdir/test_bufwintabinfo.vim --- 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 diff --git a/src/testdir/test_cmdline.vim b/src/testdir/test_cmdline.vim --- 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 = "\=execute('let X=getcmdline()')\" + call feedkeys(":b \" .. cap .. "\", 'xt') + call assert_equal('b bufb', X) + call feedkeys(":b \\" .. cap .. "\", 'xt') + call assert_equal('b bufa', X) + call feedkeys(":b \\\" .. cap .. "\", 'xt') + call assert_equal('b bufc', X) + enew + + edit other + call feedkeys(":b \" .. cap .. "\", 'xt') + call assert_equal('b bufb', X) + call feedkeys(":b \\" .. cap .. "\", 'xt') + call assert_equal('b bufa', X) + call feedkeys(":b \\\" .. cap .. "\", '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 --- 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 diff --git a/src/undo.c b/src/undo.c --- 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 diff --git a/src/version.c b/src/version.c --- 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, diff --git a/src/vim.h b/src/vim.h --- 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 diff --git a/src/viminfo.c b/src/viminfo.c --- 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;