vimrcアンチパターン

この記事はVim Advent Calendar 2014 - Qiita1日目の記事です。

今回は、もう130回も続いているvimrc読書会でよく見られるvimrcのアンチパターン、 まぁ「これは気を付けたほうがいいんじゃない」的なことを私なりにまとめてみようと思う。

vimrcの文字コード

Vim scriptにはscriptencodingという現在のVim scriptファイルの文字コードを指定するコマンドが存在します。 一般的にscriptencodingはマルチバイト文字を使う前に宣言します。マルチバイト文字を一切使っていない場合、特に宣言する必要はないでしょう。 なので、マルチバイト文字をvimrc内で使用する場合(コメント内でマルチバイト文字を使用する場合も含みます)、vimrcの先頭で宣言するのがいいでしょう。

悪いパターン

" ミュートにする。
set t_vb=
set visualbell
set noerrorbells

良いパターン

scriptencoding utf-8

" ミュートにする。
set t_vb=
set visualbell
set noerrorbells

scriptencodingset encoding

上項で「vimrcの先頭で宣言するのがいいでしょう。」と言いましたが、 scriptencodingset encodingの順番には気を付けましょう。 Vim文字コードの内部的な仕組み上、set encodingscriptencodingより あとに記述すると文字コードを上手く扱うことができません。 よって、set encodingを書く場合にはscriptencodingより前に記述するべきです。

悪いパターン

scriptencoding utf-8

set encoding=utf-8

良いパターン

set encoding=utf-8

scriptencoding utf-8

省略記法のオプション

たまに省略しているオプションと省略してない同じオプションをそれぞれ宣言しているのを見かけるけど、 これは同じ処理を2回も行っていてもったいない処理です。 省略しているオプションと省略してないオプションを混ぜてvimrcに記述するのは精神衛生上可読性的にも非常に悪いです。 省略しないでオプションを設定するようにするといいでしょう。

悪いパターン

" ↓modelineの省略記法
set ml

良いパターン

set modeline

もし、省略しているオプションの正式な名前がわからない場合には:h mlのようにヘルプを引きましょう。 以下のような感じで省略名と正式な名前の両方が記載されているはずです。

   *'modeline'* *'ml'* *'nomodeline'* *'noml'*

スコープのない変数(1)

vimrcに書かれるよくあるパターンして<leader>の文字を決める変数g:mapleaderがあります。 たまにg:mapleadermapleaderをそれぞれ宣言しているのを見かけるけど、これは全く同じ変数を参照します。

悪いパターン

let mapleader = ' '

良いパターン

let g:mapleader = ' '

関数外でスコープなし変数を宣言した場合、暗黙的にグローバル変数扱いになります。 関数外で変数を宣言するときはスコープを付けるくせをつけるといいでしょう。

スコープのない変数(2)

関数外でfor文を使う場合、一時変数(下記でいうi)もlet文で宣言したのと同等な扱いとなります。 ということはスコープのなしでfor文を使えば当然グローバル変数扱いとなりとても邪悪です。

悪いパターン

for i in range(0,2)
  execute printf('source .local_%d.vim', i)
endfor

良いパターン

for s:i in range(0,2)
  execute printf('source .local_%d.vim', s:i)
endfor

let文と同じく、関数外でfor文を使うときはスコープを付けるくせをつけるといいでしょう。 基本的にvimrc内で関数外の変数を宣言する時にはスクリプト変数であるs:をつけるといいでしょう。

グループに属していないautocmd

autocmdコマンドは以下のような構文で、[group]を省略することができるのですが、 vimrc内で[group]を省略すると少々厄介です。

:au[tocmd] [group] {event} {pat} [nested] {cmd}

この[group]を指定していないautocmdをvimrc内に記述していると、vimrcを再読み込みするたびにそのautocmdが登録されてしまいます。 ということはvimrcを読み込んだ回数だけautocmdが実行されてしまいます。 要するに余計な処理が増え、段々Vimが重くなっていきます。

悪いパターン

autocmd FileType cpp setlocal expandtab
autocmd FileType make setlocal noexpandtab

良いパターン

augroup vimrc
  autocmd!
augroup END

autocmd vimrc FileType cpp setlocal expandtab
autocmd vimrc FileType make setlocal noexpandtab
" ...

vimrcの先頭の方で

augroup vimrc
  autocmd!
augroup END

を宣言することでグループvimrcに属するautocmdを初期化できます。 もうちょっと詳しくいうとautocmd!が現在のグループに属しているautocmdをすべて登録解除を行います。 その後にグループvimrcに属したautocmdを使えばOKです。 これでvimrcを再読み込みしてもVimが重くなることもありません。

上記の方法以外にもいくつか書き方があります。

" 別解
augroup vimrc
  autocmd!
  autocmd FileType cpp setlocal expandtab
  autocmd FileType make setlocal noexpandtab
  " ...
augroup END

これは初期化と登録を同時にしてしまう方法です。 毎回、autocmdの後にグループ名を書かなくていいので若干コーディングが楽になります。

※ グループ名は好きな名前が使えます。vimrcである必要はありません。

展開されるマップと展開されないマップ

vimrcに良く書かれるマップにはcmapimapnmapがある。でもこれは展開されるマップです。 これらにはそれぞれcnoremapinoremapnnnoremapと展開されないマップも存在します。 これを上手く使いこなせていないvimrcをちらほら見かけます。

悪いパターン

nmap  <leader>c          :<C-u>copen<cr>

良いパターン

nnoremap  <leader>c          :<C-u>copen<cr>

上記の悪いパターンnmap : ;などとコロンの挙動を変えてしまうと途端に意図しない挙動になるでしょう。 一般的に展開されるマップを使うのはプラグインが提供している<Plug>(...)系のマッピングです。

smap <C-z> <Plug>(neosnippet_expand_or_jump)

その他のマッピングでは展開されないマップを使う方がよいでしょう。 当然、意図的に展開されるマップを使うなら全くもって問題ないです。

実は必要のないset nocompatible

Vimはvimrcもしくはgvimrcを発見すると自動的にset nocompatibleになります。 なので、vimrcにset nocompatibleを書く必要はありません。 また、set nocompatibleはちょっと厄介な副作用を持っていて、以下のオプションが初期化されてしまいます。 (vim-jp/vimdoc-jaから引用) vimrc読書会でよく話題に上がる問題としてset nocompatibleするとオプションhistoryが初期化されていまうため、 履歴が削除されてしまうというものです。

 オプション + Viの既定値    効果  ~

    'allowrevins'     オフ        コマンド CTRL-_ なし
    'backupcopy'      Unix: "yes"   バックアップファイルがコピーになる
              他: "auto"    バップアップはコピーまたはリネーム
    'backspace'   ""        普通のバックスペース
    'backup'      オフ        バックアップファイルなし
    'cindent'     オフ        C言語ファイルにインデントなし
    'cedit'     + ""        |cmdwin| を開くキーなし
    'cpoptions' + (全フラグ)    Vi互換のフラグ
    'cscopetag'   オフ        ":tag" に cscope を使わない
    'cscopetagorder'  0     |cscopetagorder| を参照
    'cscopeverbose'   オフ        |cscopeverbose| を参照
    'digraph'     オフ        ダイグラフなし
    'esckeys'   + オフ        挿入モードで <Esc> で始まるキーなし
    'expandtab'   オフ        タブはスペースに展開されない
    'fileformats'   + ""        自動ファイルタイプ決定なし
              "dos,unix"    (ただし DOS, Windows と OS/2 以外で)
    'formatoptions' + "vt"      Vi互換の文書整形
    'gdefault'    オフ        ":s" でフラグの既定値に 'g' なし
    'history'   + 0     コマンドラインの履歴なし
    'hkmap'       オフ        ヘブライ語用キーボードマップなし
    'hkmapp'      オフ        phoneticヘブライ語用キーボードマップなし
    'hlsearch'    オフ        検索でマッチした文字列に強調なし
    'incsearch'   オフ        インクリメンタル・サーチなし
    'indentexpr'      ""        expression によるインデントなし
    'insertmode'      オフ        挿入モードでの開始なし
    'iskeyword' + "@,48-57,_"   キーワードはアルファベットと数字と '_'
    'joinspaces'      オン        ピリオドの後ろには空白を2個挿入
    'modeline'  + オフ        モードラインなし
    'more'      + オフ        リスト表示が止まらない
    'revins'      オフ        右から左の挿入なし
    'ruler'       オフ        ルーラなし
    'scrolljump'      1     ジャンプスクロールなし
    'scrolloff'   0     スクロールにオフセットなし
    'shiftround'      オフ        インデントは shiftwidth の整数倍でない
    'shortmess' + ""        メッセージの短縮なし
    'showcmd'   + オフ        コマンドの文字は表示されない
    'showmode'  + オフ        現在のモードは表示されない
    'smartcase'   オフ        大文字小文字の無視は自動にならない
    'smartindent'     オフ        高度なインデントなし
    'smarttab'    オフ        高度なタブ挿入なし
    'softtabstop'     0     タブは常に 'tabstop' を基準
    'startofline'     オン        いくつかのコマンドで行頭に移動する
    'tagrelative'   + オフ        タグファイル名は相対的でない
    'textauto'  + オフ        自動改行コード決定なし
    'textwidth'   0     自動行分割なし
    'tildeop'     オフ        チルダはオペレータではない
    'ttimeout'    オフ        ターミナルの時間切れなし
    'whichwrap' + ""        左から右への移動は行を超えない
    'wildchar'  + CTRL-E    現在の値が <Tab> のときのみ、コマンド
                    ライン補完に CTRL-E を使う
    'writebackup'     オンかオフ   |+writebackup| 機能に依る

もし、なにかの理由set nocompatibleしたい場合には以下のようにすると良いでしょう。

if &compatible
  set nocompatible
endif

syntax onの宣言位置

ハイライトを有効にしてくれるsyntax on(もしくはsyntax enable)があります。 実はファイルタイプ系ハイライトプラグインを導入している場合、syntax onの宣言位置を気を付ける必要があります。 syntax onは現在のruntimepathに含まれている設定をもとにシンタックスを生成しようとします。 なので、runtimepathを初期化するような処理をした後にsyntax onしてもあまり意味はなく、 runtimepathをすべて設定し終えた後にsyntax onをするべきです。 まぁ、runtimepathを初期化するような処理をvimrcに入れている人は結構まれなので心の片隅に入れとくとよいかと思います。

悪いパターン

" runtimepathを初期化するような処理
set runtimepath=$VIMRUNTIME

syntax on

" ファイルタイプ系ハイライトプラグイン
neoBundle 'kongo2002/fsharp-vim'

良いパターン

" runtimepathを初期化するような処理
set runtimepath=$VIMRUNTIME

" ファイルタイプ系ハイライトプラグイン
neoBundle 'kongo2002/fsharp-vim'

syntax on

同一視されるキー

Vim Advent Calendar 2012164日目の記事ですでに説明されていますけど、まぁ一応。 Vimは以下のキーを同一視してしまいます。

  • <C-i> == <Tab>
  • <C-m> == <Enter>
  • <C-[> == <ESC>

なので、<Tab>キーのマッピングしたのに<C-i>キーのマッピングで上書きしてしまったってことが起こりえます。

inoremap <expr> <Tab> MySuperTab()
" ↓これは <Tab> のキーマッピングを上書きしてしまう!
inoremap <C-i> <C-o><C-i>

(上記の記事のコードをそのまま引用)

g:vim_indent_cont

アンチパターンとかではないけどあまり知られていないので、g:vim_indent_contについて説明します。 g:vim_indent_contは以下のように\を入力した時のインデント量をつかさどっている変数です。 この変数はVim script(vimrc)を編集しているときにしか使われないので、まぁVim scriptをいじらない方はどうでもいいですね。

let s:hoge = [
_______\
" ↑この`\`を入力した時のインデント量をつかさどっている変数

以上、Vim Advent Calendar 2014 - Qiita1日目の記事でした。 今回、プラグイン関連のノウハウは一切載せないようにしました。 vimrc読書会ではプラグイン関連のノウハウがいろいろと発言されているのですが...まとめるとキリがない。