🐶

[Vim] filetype毎の設定を関数単位で分ける

2020/12/05に公開

この記事は Vim2 advent calendar の5日目の記事です。

vimrcを書いていると以下のようなことがありませんか?

  • filetype毎にマップを定義したい
  • filetype毎にインデントを変えたい

こんな感じです。

augroup vimrc
  autocmd! 
  autocmd Filetype javascript setlocal expandtab softtabstop=4 shiftwidth=4 tabstop=4
  autocmd Filetype javascript inoremap <buffer> <C-CR> <End>;<CR>
  autocmd Filetype javascript inoremap <buffer> jk <End>;
augroup END

この方法でいろんなファイルタイプに対応するとなると、横にも縦にも伸びます。
文字数が多く、ぱっと見たときにわかりづらいです。

augroup vimrc
  autocmd! 
  autocmd Filetype javascript setlocal expandtab   softtabstop=4 shiftwidth=4 tabstop=4
  autocmd Filetype typescript setlocal expandtab   softtabstop=4 shiftwidth=4 tabstop=4
  autocmd Filetype ruby       setlocal expandtab   softtabstop=2 shiftwidth=2 tabstop=2
  autocmd Filetype gitconfig  setlocal noexpandtab softtabstop=4 shiftwidth=4 tabstop=4
  autocmd Filetype gitcommit  setlocal expandtab   softtabstop=2 shiftwidth=2 tabstop=2
  autocmd Filetype gitcommit  setlocal spell
  autocmd Filetype javascript,typescript inoremap <buffer> <C-CR> <End>;<CR>
  autocmd Filetype javascript,typescript inoremap <buffer> jk <End>;
  autocmd Filetype help                  nnoremap <buffer> q ZZ
  autocmd Filetype vimfiler              nmap <buffer> l <Plug>(vimfiler_expand_tree)

  :
  :
  :
  :
augroup END

filetype毎の設定については filetype plugin indent on で読み込まれるようになる
ftplugin/ , indent/ ディレクトリ配下のファイルで設定するのがVimおすすめの方法のようなのですが、その場合filetype毎にファイルを作成しなければいけません(javascript.vim, ruby.vim, help.vim.....)。
ファイルの量が増えてしまい、(私としては)管理しづらくなってしまします。

そこで、filetype毎の設定を関数単位で管理する方法を紹介します。

やり方

以下をvimrcに記載します。

augroup vimrc
  autocmd!
  autocmd Filetype * call s:filetype(expand('<amatch>'))
augroup END

function! s:filetype(ftype) abort
  if !empty(a:ftype) && exists('*' . 's:filetype_' . a:ftype)
    execute 'call s:filetype_' . a:ftype . '()'
  endif
endfunction

function! s:filetype_javascript() abort
  call s:set_indent(4, 0)
  inoremap <buffer> <C-CR> <End>;<CR>
  inoremap <buffer> jk <End>;
endfunction

function! s:filetype_typescript() abort
  call s:filetype_javascript()
endfunction

function! s:filetype_ruby() abort
  call s:set_indent(2, 0)
endfunction

function! s:filetype_gitconfig() abort
  call s:set_indent(4, 1)
endfunction

function! s:filetype_gitcommit() abort
  call s:set_indent(2, 0)
  setlocal spell
endfunction

function! s:set_indent(tab_length, is_hard_tab) abort
  if a:is_hard_tab
    setlocal noexpandtab
  else
    setlocal expandtab
  endif
  let &l:shiftwidth  = a:tab_length
  let &l:softtabstop = a:tab_length
  let &l:tabstop     = a:tab_length
endfunction

最初のaugroupでは、Filetypeイベントが発生した際に s:filetype 関数を呼び出しています。
Filetypeイベントの場合 expand('<amatch>') でfiletype("javascript", "ruby", "help", etc...)
が取れるのでs:filetype 関数の引数にfiletypeを渡すことができます。

次に s:filetype 関数では引数で受け取った文字列より s:filetype_<引数で受け取った文字列> という関数があるかをチェックしています。存在する場合はその関数を呼び出します。

s:filetype_xxx という関数ではfiletype毎の設定を行っています。

これで関数単位で管理することができます。
管理したいfiletypeが増えた場合は s:filetype_xxxx 関数を増やすだけです。

好みの問題だとは思いますが、私はこちらの関数単位での管理の方が好きです。別のfiletypeと設定内容が一致する場合は別のfiletype用の関数をコールするだけで済みます。あと、なんかかっこよくも見えます(ここが一番大事)

最後の s:set_indent という関数ですがインデントの設定を行っています

  1. expandtab or noexpandtabの設定
  2. shiftwidthの指定
  3. softtabstopの指定
  4. tabstopの指定

こちらもあると便利ですので、ぜひ使ってみてください。

注意

提案した管理方法ですが、無効化または2重読み込み防止用のb:did_ftplugin , b:did_indent 相当の処理については何も考慮していません。また別のFiletypeが割り当たった場合に初期化するための設定を指定しておくb:undo_ftplugin , b:undo_indent についても考慮していません。

現状私が使っている中で問題も出ていないのでおそらく大丈夫だとは思いますが、b:did... やb:undo...について知っておいていただけるといいかなと思います。

その他

filetype毎の設定をvimrcから抜き出して1つのファイルにまとめるのも手です(私の設定ではそうしています)

最後に

説明した管理方法については私が考えたわけではなく、どなたかのvimrcを参考に作りました。
おそらくvimrc読書会の過去ログ探っているときに見つけたのかなーという記憶。どなたかに感謝です。

説明内容に関連するヘルプやファイルを以下に記載しておきます (vimrc読書会の過去ログも参考になります。)

" :h FileType
" :h :filetype
" :h ftplugin
" :h undo_ftplugin
" :h undo_indent
" :h 30.3

" :e $VIMRUNTIME\ftplugin.vim (filetype plugin on時に何が行われているか分かります)
" :e $VIMRUNTIME\indent.vim (filetype indent on時に何が行われているか分かります)

ではでは、 Happy Vim Life!!!!

Discussion