Posted at

Unite.vimでプロジェクト内のファイル一覧を出したり、検索したりする

More than 3 years have passed since last update.

:UniteWithProjectDirを使うとプロジェクトディレクトリにターゲットを絞れるけれど、カレントディレクトリ(特に変更していない場合はVimを起動したディレクトリ)を起点としてプロジェクトディレクトリを探すので、これをカレントバッファで開いているファイルのディレクトリを起点とするようにしたい。

以下のことができるよう設定する。


  • カレントバッファのファイルのディレクトリから、プロジェクトディレクトリを決定する

  • VimFilerにいるときは、VimFilerの現在のディレクトリから、プロジェクトディレクトリを決定する

  • 自分でプロジェクトディレクトリにマッチするパターンを設定できるようにする

  • .gitignoreで設定されたファイルは無視する

まず、カレントバッファのディレクトリは

let buffer_dir = expand('%:p:h')

で、絶対パスで取得できる。一方、VimFilerの現在のパスはVimFilerのコードを見てみるとb:vimfiler.current_dirというバッファのローカル変数に入っているようなので、これらを使って、現在のバッファのディレクトリを決定する。

if exists('b:vimfiler.current_dir')

let buffer_dir = b:vimfiler.current_dir
else
let buffer_dir = expand('%:p:h')
endif

次にプロジェクトディレクトリだが、.gitなどを検知してプロジェクトディレクトリをいい感じで返してくれる関数がvital.vimにあるので、vital.vimをインストールしてこれを使う。こんな感じ。

let project_dir = vital#of('vital').import('Prelude').path2project_directory(buffer_dir, 1)

第2引数に0以外の数字を与えると、プロジェクトディレクトリが発見できなかった時は空文字を返す。与えないとフルパスを返す(:h Vital.Prelude.path2project_directory())。

プロジェクトディレクトリが発見できなかった場合はg:project_dir_patternで設定した正規表現にマッチしたディレクトリをプロジェクトディレクトリにする。例えば、

let g:project_dir_pattern = '^/home/tmsanrinsha/project/[^/]\+'

とした場合に、/home/tmsanrinsha/project/hogeがプロジェクトディレクトリとして認識されるようにする。

最終的に、プロジェクトディレクトリが決定できたらプロジェクトディレクトリを、できなかったらバッファのディレクトリを返すような関数を作る。

function! GetProjectDir() abort " {{{

if exists('b:vimfiler.current_dir')
let l:buffer_dir = b:vimfiler.current_dir
else
let l:buffer_dir = expand('%:p:h')
endif

let l:project_dir = vital#of('vital').import('Prelude').path2project_directory(l:buffer_dir, 1)
if empty(l:project_dir) && exists('g:project_dir_pattern')
let l:project_dir = matchstr(l:buffer_dir, g:project_dir_pattern)
endif

if empty(l:project_dir)
return l:buffer_dir
else
return l:project_dir
endif
endfunction " }}}

変数にはl:をつけて、関数ローカルな変数であること明示した。

次にUnite.vimの設定。以下の設定をすると,fpでプロジェクト内のファイル一覧でuniteバッファで表示されるようになる1

nnoremap ,fp :<C-u>call <SID>unite_file_project('-start-insert')<CR>

function! s:unite_file_project(...)
let l:opts = (a:0 ? join(a:000, ' ') : '')
let l:project_dir = GetProjectDir()

execute 'Unite '.opts.' file_rec/async:'.l:project_dir
endfunction

uniteバッファをinsert modeで始めたくない場合は'-start-insert'を消す

ただし、このままだと、.gitignoreに設定されているファイルも出てきてしまうので、git ls-filesコマンドを利用したUnite file_rec/gitをgitリポジトリでは使うようにする。:h unite-source-file_rec/gitに書いてあるfile_rec/gitのオプションを参考にして

nnoremap [unite]fp :<C-u>call <SID>unite_file_project('-start-insert')<CR>

function! s:unite_file_project(...)
let l:opts = (a:0 ? join(a:000, ' ') : '')
let l:project_dir = GetProjectDir()

if isdirectory(l:project_dir.'/.git')
execute 'lcd '.l:project_dir
execute 'Unite '.opts.' file_rec/git:--cached:--others:--exclude-standard'
else
execute 'Unite '.opts.' file_rec/async:'.l:project_dir
endif
endfunction

とする。ここでfile_rec/gitはカレントディレクトリがgitのリポジトリでないと使えないので、lcdで移動する。file_rec/gitのオプションの--cachedはインデックスにキャッシュされたファイルを出力するオプション、--othersは追跡されてないファイルを出力するオプション、--exlude-standardは.gitignoreで無視されるファイルは除いて出力するオプション。要するに、.gitignoreで無視されるファイル以外のすべてを出力する。

同様な感じでgrepの方も設定する。

nnoremap ,gp :<C-u>call <SID>unite_grep_project('-start-insert')<CR>

function! s:unite_grep_project(...)
let opts = (a:0 ? join(a:000, ' ') : '')
let l:project_dir = GetProjectDir()
if !executable('ag') && isdirectory(l:project_dir.'/.git')
execute 'Unite '.opts.' grep/git:/:--untracked'
else
execute 'Unite '.opts.' grep:'.l:project_dir
endif
endfunction

grepの方はagがあれば、agが.gitignoreにあるファイルを無視してくれるのと、smart-caseを使いたいのでagを使う。

ない場合はgit grepを利用するgrep/gitを使う。/でリポジトリのルートディレクトリをディレクトリ対象に指定し、--untrackedで追跡していないファイルも含める。

まとめると最終的に以下のような設定になる。ちょっとuniteの設定を追加した。

" Note: Skip initialization for vim-tiny or vim-small.

if 0 | endif

if has('vim_starting')
if &compatible
set nocompatible " Be iMproved
endif

" Required:
set runtimepath+=~/.vim/bundle/neobundle.vim/
endif

" Required:
call neobundle#begin(expand('~/.vim/bundle/'))

" Let NeoBundle manage NeoBundle
" Required:
NeoBundleFetch 'Shougo/neobundle.vim'

" My Bundles here:
" Refer to |:NeoBundle-examples|.
" Note: You don't set neobundle setting in .gvimrc!
NeoBundle 'Shougo/vimproc.vim', {
\ 'build' : {
\ 'windows' : 'tools\\update-dll-mingw',
\ 'cygwin' : 'make -f make_cygwin.mak',
\ 'mac' : 'make -f make_mac.mak',
\ 'linux' : 'make',
\ 'unix' : 'gmake',
\ },
\ }
NeoBundle 'Shougo/unite.vim'
NeoBundle 'Shougo/vimfiler.vim'
NeoBundle 'vim-jp/vital.vim'

call neobundle#end()

" Required:
filetype plugin indent on

" If there are uninstalled bundles found on startup,
" this will conveniently prompt you to install them.
NeoBundleCheck

" 設定例
" let g:project_dir_pattern = '^/home/tmsanrinsha/project/[^/]\+'

function! GetProjectDir() abort " {{{
if exists('b:vimfiler.current_dir')
let l:buffer_dir = b:vimfiler.current_dir
else
let l:buffer_dir = expand('%:p:h')
endif

let l:project_dir = vital#of('vital').import('Prelude').path2project_directory(l:buffer_dir, 1)
if empty(l:project_dir) && exists('g:project_dir_pattern')
let l:project_dir = matchstr(l:buffer_dir, g:project_dir_pattern)
endif

if empty(l:project_dir)
return l:buffer_dir
else
return l:project_dir
endif
endfunction " }}}

nnoremap [unite] <Nop>
nmap , [unite]

call unite#custom#profile('default', 'context', {
\ 'start_insert': 1,
\ 'winheight': 10,
\ 'auto_resize': 1,
\ })

nnoremap [unite]fp :<C-u>call <SID>unite_file_project('-start-insert')<CR>
function! s:unite_file_project(...)
let l:opts = (a:0 ? join(a:000, ' ') : '')
let l:project_dir = GetProjectDir()

if isdirectory(l:project_dir.'/.git')
execute 'lcd '.l:project_dir
execute 'Unite '.opts.' file_rec/git:--cached:--others:--exclude-standard'
else
execute 'Unite '.opts.' file_rec/async:'.l:project_dir
endif
endfunction

if executable('ag')
" Use ag in unite grep source.
let g:unite_source_grep_command = 'ag'
let g:unite_source_grep_default_opts =
\ '-f --vimgrep --hidden --ignore ' .
\ '''.hg'' --ignore ''.svn'' --ignore ''.git'' --ignore ''.bzr'''
let g:unite_source_grep_recursive_opt = ''
endif

nnoremap [unite]gp :<C-u>call <SID>unite_grep_project('-start-insert')<CR>
function! s:unite_grep_project(...)
let opts = (a:0 ? join(a:000, ' ') : '')
let l:project_dir = GetProjectDir()
if !executable('ag') && isdirectory(l:project_dir.'/.git')
execute 'Unite '.opts.' grep/git:/:--untracked'
else
execute 'Unite '.opts.' grep:'.l:project_dir
endif
endfunction