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.
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.
listed TRUE if the buffer is listed.
lnum current line number in buffer.
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.
"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: >

View File

@@ -1023,6 +1023,7 @@ list of buffers. |unlisted-buffer|
# alternate buffer
R terminal buffers with a running 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.:
h+ hidden buffers which are modified
a+ active buffers which are modified

View File

@@ -2251,6 +2251,23 @@ int buflist_findpat(
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.
@@ -2264,6 +2281,7 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
char_u *p;
int attempt;
char_u *patc;
bufmatch_T *matches = NULL;
*num_file = 0; // return values in case of FAIL
*file = NULL;
@@ -2314,15 +2332,25 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
} else {
p = vim_strsave(p);
}
if (matches != NULL) {
matches[count].buf = buf;
matches[count].match = p;
count++;
} else {
(*file)[count++] = p;
}
}
}
}
if (count == 0) { // no match found, break here
break;
}
if (round == 1) {
*file = xmalloc((size_t)count * sizeof(**file));
if (options & WILD_BUFLASTUSED) {
matches = xmalloc((size_t)count * sizeof(*matches));
}
}
}
vim_regfree(regmatch.regprog);
@@ -2335,6 +2363,25 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
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;
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).
void buflist_list(exarg_T *eap)
{
buf_T *buf;
buf_T *buf = firstbuf;
int len;
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 job_running = buf->terminal && terminal_running(buf->terminal);
@@ -2660,13 +2728,22 @@ void buflist_list(exarg_T *eap)
do {
IObuff[len++] = ' ';
} 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),
_("line %" PRId64),
buf == curbuf ? (int64_t)curwin->w_cursor.lnum
: (int64_t)buflist_findlnum(buf));
}
msg_outtrans(IObuff);
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_nr(dict, S_LEN("lastused"), buf->b_last_used);
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
// typed, not when it comes from a macro
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 'wildmode' contains "list" may still need to list
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) {
s->res = nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP,
s->res = nextwild(&s->xpc, WILD_LONGEST, options,
s->firstc != '@');
} 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 != '@');
} else {
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
// common part
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 != '@');
} else {
s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, WILD_NO_BEEP,
s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options,
s->firstc != '@');
}
@@ -1016,10 +1020,10 @@ static int command_line_execute(VimState *state, int key)
s->did_wild_list = true;
if (wim_flags[s->wim_index] & WIM_LONGEST) {
nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP,
nextwild(&s->xpc, WILD_LONGEST, options,
s->firstc != '@');
} 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 != '@');
}
} else {

View File

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

View File

@@ -1162,3 +1162,26 @@ int goto_im(void)
{
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;
} 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;
}

View File

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

View File

@@ -149,3 +149,10 @@ func Test_getbufinfo_lines()
edit Xfoo
bw!
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
call feedkeys("q:\<CR>", 'x')
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
call assert_equal(['a', 'c'], getline(1, 2))
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;
}
if (uhp == NULL)
if (uhp == NULL) {
*msgbuf = NUL;
else
u_add_time(msgbuf, sizeof(msgbuf), uhp->uh_time);
} else {
add_time(msgbuf, sizeof(msgbuf), uhp->uh_time);
}
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
@@ -2509,7 +2510,7 @@ void ex_undolist(exarg_T *eap)
&& uhp->uh_walk != mark) {
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) {
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
*/