I would like to implement a vim command to select buffers with the following behaviour:
:History
command provided by fzf.vim
in that we list only the recently used buffers from the current directory, fzf.vim lists all the recent buffers). The user can search for the file name they would like to loadThis is what I have so far (This assumes that junegunn/fzf.vim
is already installed):
nnoremap <silent> <space><space> :call <SID>recent_files()<CR>
function! s:recent_files_sink(items)
if len(a:items) == 2
execute "edit" a:items[1]
return
endif
call fzf#vim#files("", {'options': ['--query', a:items[0]]})
endfunction
" deduped is a list of items without duplicates, this
" function inserts elements from items into deduped
function! s:add_unique(deduped, items)
let dict = {}
for item in a:deduped
let dict[item] = ''
endfor
for f in a:items
if has_key(dict, f) | continue | endif
let dict[f] = ''
call add(a:deduped, f)
endfor
return a:deduped
endfunction
function! s:recent_files()
let regex = '^' . fnamemodify(getcwd(), ":p")
let buffers = filter(map(
\ getbufinfo({'buflisted':1}), {_, b -> fnamemodify(b.name, ":p")}),
\ {_, f -> filereadable(f)}
\ )
let recent = filter(
\ map(copy(v:oldfiles), {_, f -> fnamemodify(f, ":p")}),
\ {_, f -> filereadable(f) && f =~# regex})
let combined = s:add_unique(buffers, recent)
call fzf#run(fzf#wrap({
\ 'source': map(combined, {_, f -> fnamemodify(f, ":~:.")}),
\ 'sink*': function('s:recent_files_sink'),
\ 'options': '--print-query --exit-0 --prompt "Recent> "'
\ }))
endfunction
SpaceSpace invokes s:recent_files()
which lists loaded buffers and recently used files from viminfo. The interesting bit here is the sink*
option in the call to fzf#run
(4th line from the bottom). The sink is another function. If a filename was selected, the sink function loads it for editing, otherwise, it calls fzf#vim#files
, which lists the contents of the directory.
This is pretty close to what I want but there are a couple of problems:
I'm looking for suggestions on how to solve these problems, particularly 2 and 3. I'm also open to solutions that don't meet the specifications exactly but provide a good user experience.
EDIT: I came up with another approach to achieve this using this as the source for fzf
cat recent_files.txt fifo.txt
where recent_files.txt
is a list of recent files and fifo.txt
is an empty fifo created using mkfifo
. A mapping can be added to buffer which triggers a command to write to the fifo. This way, the user can use that mapping to include a list of all files in case they don't find a match in recent files. This approach becomes problematic in cases where user finds the file in recents and presses enter. Since fzf is till waiting to read from fifo, it does not exit https://github.com/junegunn/fzf/issues/2288
I was finally able to come to a solution that is pretty close using fzf's reload
functionality. (Thanks to junegunn)
This is how it goes:
nnoremap <silent> <space><space> :call <SID>recent_files()<CR>
" Initialize fzf with a list of loaded buffers and recent files from
" the current directory. If <space> is pressed, we load a list of all
" the files in the current directory
function! s:recent_files()
let regex = '^' . fnamemodify(getcwd(), ":p")
let buffers = filter(map(
\ getbufinfo({'buflisted':1}), {_, b -> fnamemodify(b.name, ":p")}),
\ {_, f -> filereadable(f)}
\ )
let recent = filter(
\ map(copy(v:oldfiles), {_, f -> fnamemodify(f, ":p")}),
\ {_, f -> filereadable(f) && f =~# regex})
let combined = <SID>add_unique(buffers, recent)
"-------------------------------------------
" This is the key piece that makes it work
"-------------------------------------------
let options = [
\ '--bind', 'space:reload:git ls-files',
\ ]
call fzf#run(fzf#wrap({
\ 'source': map(combined, {_, f -> fnamemodify(f, ":~:.")}),
\ 'options': options
\ }))
endfunction
" deduped is a list of items without duplicates, this
" function inserts elements from items into deduped
function! s:add_unique(deduped, items)
let dict = {}
for item in a:deduped
let dict[item] = ''
endfor
for f in a:items
if has_key(dict, f) | continue | endif
let dict[f] = ''
call add(a:deduped, f)
endfor
return a:deduped
endfunction
I start FZF by using <space><space>
. This FZF window contains only the most recent files from the current directory. If I then press <space>
, the FZF window is updated with a new list of files obtained from git ls-files
.
Update: Apr 2023
FZF now supports a zero
event. It is triggered when no match is found. This can be used to trigger a reload
like the example above does with space