Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
gfixler opened this issue Jul 15, 2017 · 5 comments
Open

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

gfixler opened this issue Jul 15, 2017 · 5 comments

Comments

@gfixler
Copy link

gfixler commented Jul 15, 2017

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.

@odnoletkov
Copy link

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.

@galanonym
Copy link

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?

@gfixler
Copy link
Author

gfixler commented Oct 10, 2019

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.

@sienic
Copy link

sienic commented Feb 13, 2021

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>

@tpope
Copy link
Owner

tpope commented Feb 13, 2021

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants