vim-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 vim/vim#4722)
52410575be
This commit is contained in:
Rob Pilling
2020-03-22 21:40:12 +00:00
parent 9d59f066cb
commit 978a6bcaf2
14 changed files with 226 additions and 48 deletions

View File

@@ -4164,6 +4164,9 @@ getbufinfo([{dict}])
changed TRUE if the buffer is modified. changed TRUE if the buffer is modified.
changedtick number of changes made to the buffer. changedtick number of changes made to the buffer.
hidden TRUE if the buffer is hidden. hidden TRUE if the buffer is hidden.
lastused timestamp in seconds, like
|localtime()|, when the buffer was
last used.
listed TRUE if the buffer is listed. listed TRUE if the buffer is listed.
lnum current line number in buffer. lnum current line number in buffer.
linecount number of lines in the buffer (only linecount number of lines in the buffer (only

View File

@@ -6742,6 +6742,8 @@ A jump table for the options with a short description can be found at |Q_op|.
complete first match. complete first match.
"list:longest" When more than one match, list all matches and "list:longest" When more than one match, list all matches and
complete till longest common string. 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. When there is only a single match, it is fully completed in all cases.
Examples: > Examples: >

View File

@@ -1023,6 +1023,7 @@ list of buffers. |unlisted-buffer|
# alternate buffer # alternate buffer
R terminal buffers with a running job R terminal buffers with a running job
F terminal buffers with a finished job F terminal buffers with a finished job
t show time last used and sort buffers
Combining flags means they are "and"ed together, e.g.: Combining flags means they are "and"ed together, e.g.:
h+ hidden buffers which are modified h+ hidden buffers which are modified
a+ active buffers which are modified a+ active buffers which are modified

View File

@@ -2251,6 +2251,23 @@ int buflist_findpat(
return match; return match;
} }
typedef struct {
buf_T *buf;
char_u *match;
} bufmatch_T;
/// Compare functions for qsort() below, that compares b_last_used.
static int
buf_time_compare(const void *s1, const void *s2)
{
buf_T *buf1 = *(buf_T **)s1;
buf_T *buf2 = *(buf_T **)s2;
if (buf1->b_last_used == buf2->b_last_used) {
return 0;
}
return buf1->b_last_used > buf2->b_last_used ? -1 : 1;
}
/* /*
* Find all buffer names that match. * Find all buffer names that match.
@@ -2264,6 +2281,7 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
char_u *p; char_u *p;
int attempt; int attempt;
char_u *patc; char_u *patc;
bufmatch_T *matches = NULL;
*num_file = 0; // return values in case of FAIL *num_file = 0; // return values in case of FAIL
*file = NULL; *file = NULL;
@@ -2314,15 +2332,25 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
} else { } else {
p = vim_strsave(p); p = vim_strsave(p);
} }
if (matches != NULL) {
matches[count].buf = buf;
matches[count].match = p;
count++;
} else {
(*file)[count++] = p; (*file)[count++] = p;
} }
} }
} }
}
if (count == 0) { // no match found, break here if (count == 0) { // no match found, break here
break; break;
} }
if (round == 1) { if (round == 1) {
*file = xmalloc((size_t)count * sizeof(**file)); *file = xmalloc((size_t)count * sizeof(**file));
if (options & WILD_BUFLASTUSED) {
matches = xmalloc((size_t)count * sizeof(*matches));
}
} }
} }
vim_regfree(regmatch.regprog); vim_regfree(regmatch.regprog);
@@ -2335,6 +2363,25 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
xfree(patc); xfree(patc);
} }
if (matches != NULL) {
if (count > 1) {
qsort(matches, (size_t)count, sizeof(bufmatch_T), buf_time_compare);
}
// if the current buffer is first in the list, place it at the end
if (matches[0].buf == curbuf) {
for (int i = 1; i < count; i++) {
(*file)[i-1] = matches[i].match;
}
(*file)[count-1] = matches[0].match;
} else {
for (int i = 0; i < count; i++) {
(*file)[i] = matches[i].match;
}
}
xfree(matches);
}
*num_file = count; *num_file = count;
return count == 0 ? FAIL : OK; return count == 0 ? FAIL : OK;
} }
@@ -2595,11 +2642,32 @@ linenr_T buflist_findlnum(buf_T *buf)
// List all known file names (for :files and :buffers command). // List all known file names (for :files and :buffers command).
void buflist_list(exarg_T *eap) void buflist_list(exarg_T *eap)
{ {
buf_T *buf; buf_T *buf = firstbuf;
int len; int len;
int i; int i;
for (buf = firstbuf; buf != NULL && !got_int; buf = buf->b_next) { garray_T buflist;
buf_T **buflist_data = NULL, **p;
if (vim_strchr(eap->arg, 't')) {
ga_init(&buflist, sizeof(buf_T *), 50);
for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
ga_grow(&buflist, 1);
((buf_T **)buflist.ga_data)[buflist.ga_len++] = buf;
}
qsort(buflist.ga_data, (size_t)buflist.ga_len,
sizeof(buf_T *), buf_time_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) {
const bool is_terminal = buf->terminal; const bool is_terminal = buf->terminal;
const bool job_running = buf->terminal && terminal_running(buf->terminal); const bool job_running = buf->terminal && terminal_running(buf->terminal);
@@ -2660,13 +2728,22 @@ void buflist_list(exarg_T *eap)
do { do {
IObuff[len++] = ' '; IObuff[len++] = ' ';
} while (--i > 0 && len < IOSIZE - 18); } while (--i > 0 && len < IOSIZE - 18);
if (vim_strchr(eap->arg, 't') && buf->b_last_used) {
add_time(IObuff + len, (size_t)(IOSIZE - len), buf->b_last_used);
} else {
vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len),
_("line %" PRId64), _("line %" PRId64),
buf == curbuf ? (int64_t)curwin->w_cursor.lnum buf == curbuf ? (int64_t)curwin->w_cursor.lnum
: (int64_t)buflist_findlnum(buf)); : (int64_t)buflist_findlnum(buf));
}
msg_outtrans(IObuff); msg_outtrans(IObuff);
line_breakcheck(); line_breakcheck();
} }
if (buflist_data) {
ga_clear(&buflist);
}
} }
/* /*

View File

@@ -7241,6 +7241,8 @@ dict_T *get_buffer_info(buf_T *buf)
tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf));
} }
tv_dict_add_nr(dict, S_LEN("lastused"), buf->b_last_used);
return dict; return dict;
} }

View File

@@ -947,6 +947,10 @@ static int command_line_execute(VimState *state, int key)
// - wildcard expansion is only done when the 'wildchar' key is really // - wildcard expansion is only done when the 'wildchar' key is really
// typed, not when it comes from a macro // typed, not when it comes from a macro
if ((s->c == p_wc && !s->gotesc && KeyTyped) || s->c == p_wcm) { if ((s->c == p_wc && !s->gotesc && KeyTyped) || s->c == p_wcm) {
int options = WILD_NO_BEEP;
if (wim_flags[s->wim_index] & WIM_BUFLASTUSED) {
options |= WILD_BUFLASTUSED;
}
if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice
// if 'wildmode' contains "list" may still need to list // if 'wildmode' contains "list" may still need to list
if (s->xpc.xp_numfiles > 1 if (s->xpc.xp_numfiles > 1
@@ -960,10 +964,10 @@ static int command_line_execute(VimState *state, int key)
} }
if (wim_flags[s->wim_index] & WIM_LONGEST) { if (wim_flags[s->wim_index] & WIM_LONGEST) {
s->res = nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP, s->res = nextwild(&s->xpc, WILD_LONGEST, options,
s->firstc != '@'); s->firstc != '@');
} else if (wim_flags[s->wim_index] & WIM_FULL) { } else if (wim_flags[s->wim_index] & WIM_FULL) {
s->res = nextwild(&s->xpc, WILD_NEXT, WILD_NO_BEEP, s->res = nextwild(&s->xpc, WILD_NEXT, options,
s->firstc != '@'); s->firstc != '@');
} else { } else {
s->res = OK; // don't insert 'wildchar' now s->res = OK; // don't insert 'wildchar' now
@@ -975,10 +979,10 @@ static int command_line_execute(VimState *state, int key)
// if 'wildmode' first contains "longest", get longest // if 'wildmode' first contains "longest", get longest
// common part // common part
if (wim_flags[0] & WIM_LONGEST) { if (wim_flags[0] & WIM_LONGEST) {
s->res = nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP, s->res = nextwild(&s->xpc, WILD_LONGEST, options,
s->firstc != '@'); s->firstc != '@');
} else { } else {
s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, WILD_NO_BEEP, s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options,
s->firstc != '@'); s->firstc != '@');
} }
@@ -1016,10 +1020,10 @@ static int command_line_execute(VimState *state, int key)
s->did_wild_list = true; s->did_wild_list = true;
if (wim_flags[s->wim_index] & WIM_LONGEST) { if (wim_flags[s->wim_index] & WIM_LONGEST) {
nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP, nextwild(&s->xpc, WILD_LONGEST, options,
s->firstc != '@'); s->firstc != '@');
} else if (wim_flags[s->wim_index] & WIM_FULL) { } else if (wim_flags[s->wim_index] & WIM_FULL) {
nextwild(&s->xpc, WILD_NEXT, WILD_NO_BEEP, nextwild(&s->xpc, WILD_NEXT, options,
s->firstc != '@'); s->firstc != '@');
} }
} else { } else {

View File

@@ -31,6 +31,7 @@
#define WILD_ALLLINKS 0x200 #define WILD_ALLLINKS 0x200
#define WILD_IGNORE_COMPLETESLASH 0x400 #define WILD_IGNORE_COMPLETESLASH 0x400
#define WILD_NOERROR 0x800 // sets EW_NOERROR #define WILD_NOERROR 0x800 // sets EW_NOERROR
#define WILD_BUFLASTUSED 0x1000
/// Present history tables /// Present history tables
typedef enum { typedef enum {

View File

@@ -1162,3 +1162,26 @@ int goto_im(void)
{ {
return p_im && stuff_empty() && typebuf_typed(); return p_im && stuff_empty() && typebuf_typed();
} }
/// Put the timestamp of an undo header in "buf[buflen]" in a nice format.
void add_time(char_u *buf, size_t buflen, time_t tt)
{
struct tm curtime;
if (time(NULL) - tt >= 100) {
os_localtime_r(&tt, &curtime);
if (time(NULL) - 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 {
int64_t seconds = time(NULL) - tt;
vim_snprintf((char *)buf, buflen,
NGETTEXT("%" PRId64 " second ago",
"%" PRId64 " seconds ago", (uint32_t)seconds),
seconds);
}
}

View File

@@ -7083,6 +7083,8 @@ static int check_opt_wim(void)
new_wim_flags[idx] |= WIM_FULL; new_wim_flags[idx] |= WIM_FULL;
} else if (i == 4 && STRNCMP(p, "list", 4) == 0) { } else if (i == 4 && STRNCMP(p, "list", 4) == 0) {
new_wim_flags[idx] |= WIM_LIST; new_wim_flags[idx] |= WIM_LIST;
} else if (i == 8 && STRNCMP(p, "lastused", 8) == 0) {
new_wim_flags[idx] |= WIM_BUFLASTUSED;
} else { } else {
return FAIL; return FAIL;
} }

View File

@@ -277,6 +277,7 @@ enum {
#define WIM_FULL 1 #define WIM_FULL 1
#define WIM_LONGEST 2 #define WIM_LONGEST 2
#define WIM_LIST 4 #define WIM_LIST 4
#define WIM_BUFLASTUSED 8
// arguments for can_bs() // arguments for can_bs()
#define BS_INDENT 'i' // "Indent" #define BS_INDENT 'i' // "Indent"

View File

@@ -149,3 +149,10 @@ func Test_getbufinfo_lines()
edit Xfoo edit Xfoo
bw! bw!
endfunc endfunc
function Test_getbufinfo_lastused()
new Xfoo
let info = getbufinfo('Xfoo')[0]
call assert_equal(has_key(info, 'lastused'), 1)
call assert_equal(type(info.lastused), type(0))
endfunc

View File

@@ -755,3 +755,49 @@ func Test_cmdwin_feedkeys()
" This should not generate E488 " This should not generate E488
call feedkeys("q:\<CR>", 'x') call feedkeys("q:\<CR>", 'x')
endfunc endfunc
func Test_buffers_lastused()
" check that buffers are sorted by time when wildmode has lastused
edit bufc " oldest
sleep 1200m
enew
edit bufa " middle
sleep 1200m
enew
edit bufb " newest
enew
call assert_equal(['bufc', 'bufa', 'bufb'],
\ 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
sleep 1200m
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

View File

@@ -8,3 +8,35 @@ func Test_ex_delete()
.dl .dl
call assert_equal(['a', 'c'], getline(1, 2)) call assert_equal(['a', 'c'], getline(1, 2))
endfunc endfunc
func Test_buffers_lastused()
edit bufc " oldest
sleep 1200m
edit bufa " middle
sleep 1200m
edit bufb " newest
enew
let ls = split(execute('buffers t', 'silent!'), '\n')
let bufs = []
for line in ls
let bufs += [split(line, '"\s*')[1:2]]
endfor
let names = []
for buf in bufs
if buf[0] !=# '[No Name]'
let names += [buf[0]]
endif
endfor
call assert_equal(['bufb', 'bufa', 'bufc'], names)
call assert_match('[0-2] seconds ago', bufs[1][1])
bwipeout bufa
bwipeout bufb
bwipeout bufc
endfunc

View File

@@ -2441,10 +2441,11 @@ static void u_undo_end(
uhp = curbuf->b_u_newhead; uhp = curbuf->b_u_newhead;
} }
if (uhp == NULL) if (uhp == NULL) {
*msgbuf = NUL; *msgbuf = NUL;
else } else {
u_add_time(msgbuf, sizeof(msgbuf), uhp->uh_time); add_time(msgbuf, sizeof(msgbuf), uhp->uh_time);
}
{ {
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
@@ -2509,7 +2510,7 @@ void ex_undolist(exarg_T *eap)
&& uhp->uh_walk != mark) { && uhp->uh_walk != mark) {
vim_snprintf((char *)IObuff, IOSIZE, "%6ld %7d ", vim_snprintf((char *)IObuff, IOSIZE, "%6ld %7d ",
uhp->uh_seq, changes); uhp->uh_seq, changes);
u_add_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff), add_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff),
uhp->uh_time); uhp->uh_time);
if (uhp->uh_save_nr > 0) { if (uhp->uh_save_nr > 0) {
while (STRLEN(IObuff) < 33) while (STRLEN(IObuff) < 33)
@@ -2574,30 +2575,6 @@ void ex_undolist(exarg_T *eap)
} }
} }
/*
* 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)
{
struct tm curtime;
if (time(NULL) - tt >= 100) {
os_localtime_r(&tt, &curtime);
if (time(NULL) - 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 {
int64_t seconds = time(NULL) - tt;
vim_snprintf((char *)buf, buflen,
NGETTEXT("%" PRId64 " second ago",
"%" PRId64 " seconds ago", (uint32_t)seconds),
seconds);
}
}
/* /*
* ":undojoin": continue adding to the last entry list * ":undojoin": continue adding to the last entry list
*/ */