I would like to implement a vim command to select buffers with the following behaviour:
- When called, it present the user with a list of loaded buffers and other recently used buffers from the current directory (This is different from the
:History
command provided byfzf.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 load - If none of the options matches user's search term, expand the scope of the search by listing all the files in the current directory and letting the user fuzzy search through them.
This 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:
- When no matches are found in recent files, the user must press Return to trigger the fall-back. (One can easily argue that this is the correct behaviour)
- When the fall-back fzf window is loaded, it starts in the normal mode and not the insert mode
The user must enter the search query again in the new fzf window(solved)
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:
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 fromgit 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 areload
like the example above does withspace