aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/eval.txt3
-rw-r--r--runtime/doc/options.txt2
-rw-r--r--runtime/doc/windows.txt3
-rw-r--r--src/nvim/buffer.c97
-rw-r--r--src/nvim/buffer_defs.h2
-rw-r--r--src/nvim/eval.c2
-rw-r--r--src/nvim/ex_cmds.c2
-rw-r--r--src/nvim/ex_getln.c28
-rw-r--r--src/nvim/ex_getln.h1
-rw-r--r--src/nvim/misc1.c23
-rw-r--r--src/nvim/normal.c2
-rw-r--r--src/nvim/option.c2
-rw-r--r--src/nvim/option_defs.h1
-rw-r--r--src/nvim/terminal.c5
-rw-r--r--src/nvim/testdir/test_bufwintabinfo.vim7
-rw-r--r--src/nvim/testdir/test_cmdline.vim46
-rw-r--r--src/nvim/testdir/test_excmd.vim32
-rw-r--r--src/nvim/undo.c35
-rw-r--r--test/functional/ex_cmds/ls_spec.lua12
19 files changed, 257 insertions, 48 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 29c5c37bcd..4ff6269004 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -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
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 5fb80d75ce..009695c13c 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -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: >
diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt
index 977e0daef7..b5623f4ea4 100644
--- a/runtime/doc/windows.txt
+++ b/runtime/doc/windows.txt
@@ -1021,6 +1021,9 @@ list of buffers. |unlisted-buffer|
x buffers with a read error
% current 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
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 24ba43676a..7c8f93163a 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -1618,6 +1618,7 @@ void enter_buffer(buf_T *buf)
if (!curbuf->b_help && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) {
(void)did_set_spelllang(curwin);
}
+ curbuf->b_last_used = time(NULL);
redraw_later(NOT_VALID);
}
@@ -2250,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.
@@ -2263,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;
@@ -2313,7 +2332,13 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
} else {
p = vim_strsave(p);
}
- (*file)[count++] = p;
+ if (matches != NULL) {
+ matches[count].buf = buf;
+ matches[count].match = p;
+ count++;
+ } else {
+ (*file)[count++] = p;
+ }
}
}
}
@@ -2322,6 +2347,10 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
}
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);
@@ -2334,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;
}
@@ -2594,11 +2642,35 @@ 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);
+
// skip unspecified buffers
if ((!buf->b_p_bl && !eap->forceit && !strchr((char *)eap->arg, 'u'))
|| (strchr((char *)eap->arg, 'u') && buf->b_p_bl)
@@ -2608,6 +2680,8 @@ void buflist_list(exarg_T *eap)
&& (buf->b_ml.ml_mfp == NULL || buf->b_nwindows == 0))
|| (strchr((char *)eap->arg, 'h')
&& (buf->b_ml.ml_mfp == NULL || buf->b_nwindows != 0))
+ || (strchr((char *)eap->arg, 'R') && (!is_terminal || !job_running))
+ || (strchr((char *)eap->arg, 'F') && (!is_terminal || job_running))
|| (strchr((char *)eap->arg, '-') && buf->b_p_ma)
|| (strchr((char *)eap->arg, '=') && !buf->b_p_ro)
|| (strchr((char *)eap->arg, 'x') && !(buf->b_flags & BF_READERR))
@@ -2654,13 +2728,22 @@ void buflist_list(exarg_T *eap)
do {
IObuff[len++] = ' ';
} while (--i > 0 && len < IOSIZE - 18);
- vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len),
- _("line %" PRId64),
- buf == curbuf ? (int64_t)curwin->w_cursor.lnum
- : (int64_t)buflist_findlnum(buf));
+ 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);
+ }
}
/*
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 2460159fc0..3dbd3ea121 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -543,6 +543,8 @@ struct file_buffer {
long b_mtime_read; // last change time when reading
uint64_t b_orig_size; // size of original file in bytes
int b_orig_mode; // mode of original file
+ time_t b_last_used; // time when the buffer was last used; used
+ // for viminfo
fmark_T b_namedm[NMARKS]; // current named marks (mark.c)
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 12b13a1f08..bcdb4a18f1 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -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;
}
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index d26d3387f8..8a0f2e634a 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -2670,6 +2670,8 @@ int do_ecmd(
msg_scrolled_ign = FALSE;
}
+ curbuf->b_last_used = time(NULL);
+
if (command != NULL)
do_cmdline(command, NULL, NULL, DOCMD_VERBOSE);
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index ecaab76612..d9a63ff6da 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -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,11 +964,11 @@ 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->firstc != '@');
+ 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->firstc != '@');
+ s->res = nextwild(&s->xpc, WILD_NEXT, options,
+ s->firstc != '@');
} else {
s->res = OK; // don't insert 'wildchar' now
}
@@ -975,11 +979,11 @@ 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->firstc != '@');
+ s->res = nextwild(&s->xpc, WILD_LONGEST, options,
+ s->firstc != '@');
} else {
- s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, WILD_NO_BEEP,
- s->firstc != '@');
+ s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options,
+ s->firstc != '@');
}
// if interrupted while completing, behave like it failed
@@ -1016,11 +1020,11 @@ 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,
- s->firstc != '@');
+ 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,
- s->firstc != '@');
+ nextwild(&s->xpc, WILD_NEXT, options,
+ s->firstc != '@');
}
} else {
vim_beep(BO_WILD);
diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h
index 84b2b41f30..dc4395e081 100644
--- a/src/nvim/ex_getln.h
+++ b/src/nvim/ex_getln.h
@@ -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 {
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index e7fb38e801..e10770b6bd 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -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);
+ }
+}
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 58993426dd..76de60cf4f 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1260,6 +1260,8 @@ static void normal_redraw(NormalState *s)
maketitle();
}
+ curbuf->b_last_used = time(NULL);
+
// Display message after redraw. If an external message is still visible,
// it contains the kept message already.
if (keep_msg != NULL && !msg_ext_is_visible()) {
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 25a8d432ee..eae52ff260 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -6988,6 +6988,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;
}
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 192e57a642..ecaa941082 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -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"
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index a37cc60928..560a345333 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -650,6 +650,11 @@ Buffer terminal_buf(const Terminal *term)
return term->buf_handle;
}
+bool terminal_running(const Terminal *term)
+{
+ return !term->closed;
+}
+
// }}}
// libvterm callbacks {{{
diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim
index 176d49d28e..cb7ab44798 100644
--- a/src/nvim/testdir/test_bufwintabinfo.vim
+++ b/src/nvim/testdir/test_bufwintabinfo.vim
@@ -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
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index 9c3c33a943..7f1e1f4456 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -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
diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim
index f5ce979208..4a027c3864 100644
--- a/src/nvim/testdir/test_excmd.vim
+++ b/src/nvim/testdir/test_excmd.vim
@@ -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
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index df92b2c036..97018f6c02 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -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,8 +2510,8 @@ 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),
- uhp->uh_time);
+ add_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff),
+ uhp->uh_time);
if (uhp->uh_save_nr > 0) {
while (STRLEN(IObuff) < 33)
STRCAT(IObuff, " ");
@@ -2575,30 +2576,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
*/
void ex_undojoin(exarg_T *eap)
diff --git a/test/functional/ex_cmds/ls_spec.lua b/test/functional/ex_cmds/ls_spec.lua
index f7bacd7386..9853084c47 100644
--- a/test/functional/ex_cmds/ls_spec.lua
+++ b/test/functional/ex_cmds/ls_spec.lua
@@ -31,6 +31,18 @@ describe(':ls', function()
-- Terminal buffer [F]inished.
eq('\n 3 %aF', string.match(ls_output, '\n *3....'))
end)
+
+ retry(nil, 5000, function()
+ local ls_output = eval('execute("ls R")')
+ -- Just the [R]unning terminal buffer.
+ eq('\n 2 #aR ', string.match(ls_output, '^\n *2 ... '))
+ end)
+
+ retry(nil, 5000, function()
+ local ls_output = eval('execute("ls F")')
+ -- Just the [F]inished terminal buffer.
+ eq('\n 3 %aF ', string.match(ls_output, '^\n *3 ... '))
+ end)
end)
end)