vim-unimpaired icon indicating copy to clipboard operation
vim-unimpaired copied to clipboard

]F and [F for [alphabetically] first/last file in current directory

Open gfixler opened this issue 7 years ago • 5 comments

I predict case issues, but this would be useful. I very often have a small handful of known files in a directory and realize I want to jump to the first or last one.

gfixler avatar Jul 15 '17 21:07 gfixler

I find myself mostly using [f/]f to quickly browse/review files in a single directory. So for me the most annoying thing is that it actually moves through the whole tree (across directory boundaries and descends into children). I would rather have [F/]F (or more appropriate name) move strictly through the list of adjacent siblings of the file.

odnoletkov avatar Sep 08 '17 17:09 odnoletkov

Why can't the default action of [f/]f be to jump between files only in a single directory? Is moving through directory tree really useful or needed?

galanonym avatar Oct 02 '19 09:10 galanonym

The fact that it moves out of the directory has always been a point of consternation for me, and has kept me from using that feature, tbh. I only occasionally try it out for immediately adjacent files, like log1, log2, log3, but never to walk my way through, say, all of my python scripts in a folder, because suddenly I'm who knows where.

I just tried it in a folder with 1 file, and 1 directory; I started in a /home/GFixler/work/charExport. I pressed ]f, and I was in /home/GFixler/work/code/py/.gitignore. Whoa. I pressed [f to go back, but found myself in /home/GFixler/work/code/py/' (a file called "'", a kind of backup file I occasionally create by hamfisting who knows what in Vim). It doesn't seem to navigate exactly the same path in reverse. I go back to the .gitignore with another ]f. One more ]f, and I'm in /home/GFixler/winapps/mayapy. This is all rather disorienting.

That said, several times I've tried ]F or [F, because I'm in a folder with sequential files, and I thought "Let's go to the first/last one." More recently, I know it won't work, but I still feel like I want to do it, and feel sad that I can't.

gfixler avatar Oct 10 '19 04:10 gfixler

Hi people,

It seems that our expectations from what is described in the documentation differ from the current behaviour of [f and ]f which recursively visits files in subfolders. Documentation:

                                                *[f*
[f                      Go to the file preceding the current one
                        alphabetically in the current file's directory.

                                                *]f*
]f                      Go to the file succeeding the current one
                        alphabetically in the current file's directory.

Since there's a reason behind the current behaviour (which I'd love to understand; perhaps my expectations proof short) I think we can implement our own solution by using the existing code as inspiration.

I have the following code in my vimrc. It is a quick fix and good enough for me. It also adds the [F, ]F described by the author of this issue. Hope this helps.

function! SNentries(path) abort
  let path = substitute(a:path,'[\\/]$','','')
  let files = split(glob(path."/.*"),"\n")
  let files += split(glob(path."/*"),"\n")
  call map(files,'substitute(v:val,"[\\/]$","","")')
  call filter(files,'v:val !~# "[\\\\/]\\.\\.\\=$"')
  call filter(files,'!isdirectory(v:val)') " remove directories

  let filter_suffixes = substitute(escape(&suffixes, '~.*$^'), ',', '$\\|', 'g') .'$'
  call filter(files, 'v:val !~# filter_suffixes')

  return files
endfunction

function! SNFileByOffset(num) abort
  let file = expand('%:p')
  if empty(file)
    let file = getcwd() . '/'
  endif
  let num = a:num
  while num
    let files = SNentries(fnamemodify(file,':h'))
    if a:num < 0
      call reverse(sort(filter(files,'v:val <# file')))
    else
      call sort(filter(files,'v:val ># file'))
    endif
    if !empty(get(files,0,''))
      let file = get(files,0,'')
    end
    let num += (num > 0 ? -1 : 1)
  endwhile
  return file
endfunction

" I could not think of a better name
function! SNFirstFileByDirection(dir) abort
  let file = expand('%:p')
  if empty(file)
    let file = getcwd() . '/'
  endif
  let files = SNentries(fnamemodify(file,':h'))
  if a:dir > 0
    call reverse(sort(files))
  else
    call sort(files)
  endif
  
  return get(files,0,'')
endfunction

function! SNfnameescape(file) abort
  if exists('*fnameescape')
    return fnameescape(a:file)
  else
    return escape(a:file," \t\n*?[{`$\\%#'\"|!<")
  endif
endfunction

nnoremap <silent> ]f :<C-U>edit <C-R>=SNfnameescape(fnamemodify(SNFileByOffset(v:count1), ':.'))<CR><CR>
nnoremap <silent> [f :<C-U>edit <C-R>=SNfnameescape(fnamemodify(SNFileByOffset(-v:count1), ':.'))<CR><CR>
nnoremap <silent> [F :<C-U>edit <C-R>=SNfnameescape(fnamemodify(SNFirstFileByDirection(-1), ':.'))<CR><CR>
nnoremap <silent> ]F :<C-U>edit <C-R>=SNfnameescape(fnamemodify(SNFirstFileByDirection(1), ':.'))<CR><CR>

sienic avatar Feb 13 '21 16:02 sienic

The documentation is ambiguous, but try reading it with an emphasis on the file. If it lands on a directory, it descends to the first file inside of it, which means that when it reaches the end of the directory it must ascend to the next file outside of it to pick up where it left off. All of this is the behavior I wanted, except for the very annoying fact it continues to ascend after reaching the end of the directory you started in, because each ]f is an independent action, there is no high level knowledge of the starting point.

As a rule, I do not break backwards compatibility on a whim, so this will have to wait until Unimpaired becomes a priority for me again. (Expect a long wait.) And I will ot be adding [F and ]F in the meantime as they don't really fit with the existing behavior. Do try out @sienic's proposed maps in the meantime if they interest you, and offer feedback, as they might possibly one day inspire me.

P.S. fnameescape() was added in Vim patch 7.0.299 in 2008. That was recent history when Unimpaired came out but in 2021 I wouldn't bother backporting it.

tpope avatar Feb 13 '21 19:02 tpope