using :browse old<cr> it's possible to view a list of the 100 most recently visited files.

However, when selecting a file to visit, it's only possible to provide a single number and not a sequence of numbers or a range. Indeed, the prompt visible after pressing :browse<cr>G does not allow the input of symbols other than 0123456789.

Is there a separate command that can be used to open a range of oldfiles?

2 Answers 2



There are plenty of plugins which provide a custom prompt for mru lists and allow you to select several entries:

  • Mru list by ctrlp.vim

    1. Open mru list with <c-p><c-b> or :CtrlPMR[UFiles] or set let g:ctrlp_cmd = 'CtrlPMRU' in your vimrc and then <c-p> (my choice).
    2. select entries either via fuzzy search expr or cycle with <c-j> and <c-k>
    3. Use <c-z> to mark/unmark multiple files
    4. Use <c-o> to open them.
  • :History in fzf.vim (requires go)

    TAB and Shift-TAB to mark multiple items

  • mru.vim

    You can open multiple files from the MRU window by specifying a count before pressing '' or 'v' or 'o' or 't'. You can also visually select multiple filenames and invoke the commands to open the files. Each selected file will be opened in a separate window or tab.

Furthermore, you might consider fzf-mru, Command-T (+ruby) and Denite (+python3) with neomru.vim.

As far as I haved check vim-tinyMRU does not allow to do this.

  • fzf.vim is a good choice
    – Finn
    Feb 20, 2019 at 4:55

Here's one way of doing this with no plugins:

It follows the structure of romainl's Redir.vim script and writes the output of oldfiles (which is not the same thing as browse oldfiles) to a temporary buffer opened with a vertical split.

It also defines a function VisitFilesFromLineRange that allows a user to visit multiple files at once if a line range in visual line mode is currently active.

I also defined a wrapper function around VisitFilesFromLineRange that takes an explicit range argument and rebound gf in visual mode so that it ultimately calls VisitFilesFromLineRange.

The Oldfiles function, by itself, is far more useful than the new gf binding in my opinion since it makes it easy to search through the list of old files using ordinary vim bindings.

function! Oldfiles()
  " kill all the windows called oldfiles_scratch
  " we don't need them around anymore
  for win in range(1, winnr('$'))
    if getwinvar(win, 'oldfiles_scratch')
      execute win . 'windo close'
  " redirect the stuff that normally prints to the console
  " to the variable "output". Note that in "redir => output"
  " output refers to the variable itself not its contents.
  " If VimL were more like C, this would be written
  "     redir => &output
  redir => output
  execute "oldfiles"
  redir END
  " create a new pane thingy but vertically. seems to create it
  " on the left by default which is cool.
  " tag this particular window with out variable outfiles_scratch
  " so we know to kill it later.
  let w:oldfiles_scratch = 1
  " our new window is ephemeral, give it ephemeralish properties.
  " NOTE: explicitly make the buffer modifiable NOW.
  " we set it to nomodifiable after trimming the line numbers.
  setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile modifiable
  " dump the contents of output as lines
  call setline(1, split(output, "\n"))
  " remove the leading numbers. Leading numbers make it harder to ``gf" the lines we want.
  execute "%s/^\\d\\+: //"
  " go to the top line
  execute "1"
  " make it nomodifiable
  setlocal nomodifiable
command! -nargs=0 Oldfiles silent call Oldfiles()

function! VisitFilesFromLineRange(low, hi)
  " save the current buffer index
  " so we can read from it later
  let current_buf = bufnr("%")
  " keep track of whether we spawned a new tab already or not
  " there isn't really a convenient way to spawn a truly-empty
  " container tab that we then fill up so it's easier to spawn
  " the container "on demand" as we process our first line
  let did_tabnew = 0
  " walk the array of lines from the buffer
  for line in getbufline(current_buf, a:low, a:hi)
    " escape the line name before doing stuff to it
    let eline = fnameescape(line)
    " perform a horizontal split or tabedit depending on whether
    " the new tab has been created or not
    if did_tabnew
      execute("split " . eline)
      let did_tabnew = 1
      execute("tabedit " . eline)
command! -nargs=+ VisitFilesFromLineRange silent call VisitFilesFromLineRange(<f-args>)

" define a function that accepts a range
" and forwards it into VisitFilesFromLineRange with the appropriate arguments
function! RangeFuncVisitFilesFromRange() range
  call VisitFilesFromLineRange(a:firstline, a:lastline)
" VisitLines command accepting a range argument for convenience
command! -range VisitLines silent <line1,line2>call RangeFuncVisitFilesFromRange()

" detect visual line mode and call RangeFuncVisitFilesFromRange
" otherwise delegate to the default implementation of "gf" for visual mode
xnoremap <silent> <expr> gf (mode() ==# "V") ? ":call RangeFuncVisitFilesFromRange()\<cr>" : "gf"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.