1

I'm trying to write a wrapper command around :edit or :find specifically to open either my vimrc or a subpath of ~/.vim/ (e.g. ~.vim/autoloads/foo.vim).

If I run the command with no arguments, it should open ~/.vim/vimrc. It should accept an argument to specify a subpath of ~/.vim/, and provide tab completion of those subpaths.

I have something that works, but it feels overly complicated. I'm having to jump through hoops to tweak 'path' before Vim's command completion kicks in.

let s:vimrc = resolve($MYVIMRC)
let s:vimrc_dir = fnamemodify(s:vimrc, ':h')

nnoremap <leader>f<CR> :call PreparePath()<CR>:EditDotVim 

command! -complete=file_in_path -nargs=? EditDotVim :
      \ call EditDotVim(<f-args>) |
      \ call RestorePath()

func! PreparePath()
  let s:orig_path = &l:path
  let s:orig_buf  = bufnr('')
  exec 'setl path='.s:vimrc_dir
endf

func! EditDotVim(...)
  execute 'edit' a:0 ? a:1 : s:vimrc
endf

func! RestorePath()
  call setbufvar(s:orig_buf, '&path', s:orig_path)
  unlet s:orig_path
  unlet s:orig_buf
endf

I was originally using a custom completion function and using the find shell builtin to provide completions, but given Vim's 'path' variable and :find command, it seemed like I was reinventing the wheel.

Is there a more straightforward way to do this?

UPDATE

I'm still curious if there's a better way, but for now I've reverted to my original strategy, using custom completion and a little shell magic. I wouldn't call it slow, but it's not quite as snappy as I'd like, and I don't know how portable the find regex is. But it works for now. I moved this into my autoload directory:

let s:vimrc = resolve($MYVIMRC)
let s:vimrc_dir = fnamemodify(s:vimrc, ':h')

let s:sub_paths = [
      \ 'after',
      \ 'autoload',
      \ 'colors',
      \ 'compiler',
      \ 'ftdetect',
      \ 'ftplugin',
      \ 'indent',
      \ 'init',
      \ 'plugged',
      \ 'syntax',
      \ 'tmp',
      \ 'todo.org',
      \ 'view',
      \ 'vimrc'
      \]

let s:find_paths = '{'.join(s:sub_paths, ',').'}'

func! dotvim#edit(...)
  execute 'edit' (a:0 ? dotvim#path(a:1) : s:vimrc)
endf

func! dotvim#list(ArgLead, CmdLine, CursorPos)
  let findcmd = "find " . s:find_paths .  " -regex '".a:ArgLead."[^/]*' -exec ls -pd {} +"
  return system("cd " . s:vimrc_dir . " && " . findcmd)
endf

func! dotvim#path(subpath)
  return s:vimrc_dir . '/' . a:subpath
endf

I went back and forth on the hard-coded paths whitelist vs calling out to ls to generate it, vs using find * (if I do find . I get pesky leading ./ in my results), but ultimately the explicit whitelist makes it easy for me to tune which paths I actually care about.

Oh, and please ignore the todo.org file in there. I know it's a cardinal sin having even trace amounts of emacs in my .vim/ ...

UPDATE II

Incorporating the globpath suggestion, this got a lot cleaner:

func! dotvim#list(ArgLead, CmdLine, CursorPos)
  return map(globpath(s:vimrc_dir, a:ArgLead.'*', 0, 1), s:subpath_map)
endf

let s:subpath_map = { k,v -> fnamemodify(v,':p')[(s:leading_len):] }
let s:leading_len = len(s:vimrc_dir) + 1

command! -complete=customlist,dotvim#list -nargs=? DotVim call dotvim#edit(<f-args>)

2 Answers 2

1

Custom completion is indeed the way to go.

But why don't you use globpath instead of system(). Exploring directory hierarchies should much faster. Then you can filter results with filter(), and transform them with map().

If you don't want to use exact paths, but recurse automatically, you can use some/path/**

NB: I resolve .vim/ directory with the following.

2
  • Great suggestion! I'll add an update with what I came up with. Also, I did wonder how best to resolve my .vim/ directory. It's actually a git repo that I symlink from ~/.vim and from ~/.config/nvim
    – ivan
    Commented Jul 17, 2017 at 12:55
  • The idea behind my lh#path#vimfiles() function is to see what kind of pathnames we can find in &rtp. -> .vim/ (every where, but...), vimfiles/ (...windows), or .config/nvim (...neovim). Also, I'm able to work in shared accounts (which is a very bad thing to do!) where I reserve myself a ~/luc/.vim/ directory to store my vim settings. Commented Jul 17, 2017 at 15:46
3
nnoremap <leader>f<CR> :EditVim ~/.vim/**/*
command! -complete=file_in_path -nargs=? EditVim : call EditVim (<f-args>)

func! EditVim(...)
    if a:0 == 0 || a:1 ==? '~/.vim/**/*'
        execute 'edit ~/.vimrc'
    else
        execute 'edit' a:1
    endif
endf

demo

Here the ~/.vim/**/*foo pattern will match anything that has foo and is located in ~/.vim/ or any of its subfolders recursively. Given that you want the completion, I assume that you only need this wrapper command for quick navigation to your config files. If so, then you could just use these mappings instead:

nnoremap <leader>F :edit ~/.vimrc<CR>
nnoremap <leader>f :edit ~/.vim/**/* 
1
  • This looks promising! I just digested the other answer and incorporated its ideas into my current solution. I'll take another look at this approach too. Thanks
    – ivan
    Commented Jul 17, 2017 at 12:56

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.