-
Notifications
You must be signed in to change notification settings - Fork 9
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
上項で「vimrcの先頭で宣言するのがいいでしょう。」と言いましたが、
scriptencoding
とset encoding
の順番には気を付けましょう。
Vimは文字コードの内部的な仕組み上、set encoding
をscriptencoding
より
あとに記述すると文字コードを上手く扱うことができません。
よって、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'*
vimrcに書かれるよくあるパターンして<leader>
の文字を決める変数g:mapleader
があります。
たまにg:mapleader
とmapleader
をそれぞれ宣言しているのを見かけるけど、これは全く同じ変数を参照します。
悪いパターン
let mapleader = ' '
良いパターン
let g:mapleader = ' '
関数外でスコープなし変数を宣言した場合、暗黙的にグローバル変数扱いになります。 関数外で変数を宣言するときはスコープを付けるくせをつけるといいでしょう。
関数外で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コマンドは以下のような構文で、[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に良く書かれるマップにはcmap
、imap
、nmap
がある。でもこれは展開されるマップです。
これらにはそれぞれcnoremap
、inoremap
、nnnoremap
と展開されないマップも存在します。
これを上手く使いこなせていないvimrcをちらほら見かけます。
悪いパターン
nmap <leader>c :<C-u>copen<cr>
良いパターン
nnoremap <leader>c :<C-u>copen<cr>
上記の 悪いパターン はnmap : ;
などとコロンの挙動を変えてしまうと途端に意図しない挙動になるでしょう。
一般的に展開されるマップを使うのはプラグインが提供している<Plug>(...)
系のマッピングです。
例
smap <Plug>(neosnippet_expand_or_jump)
その他のマッピングでは展開されないマップを使う方がよいでしょう。 当然、意図的に展開されるマップを使うなら全くもって問題ないです。
なお、Vim 8.2.4498 及び Neovim nightly(#16969 より) で <Plug>
マッピングに限り noremap
で定義しても展開されるようになりました。
今までは以下のような定義をする際(例はコミットより)、nmapの方の定義しかできず <Plug>
マッピングに続いて何かをしたい際には remap されているか気を遣う必要がありました。(<script>
を使って頑張れば分離を実現できないことはない)
これからは noremap
の方の定義が期待通りに動くようになります。
例
nnoremap <Plug>(Increase_x) <Cmd>let g:foo += 1<CR>
nnoremap x <Nop>
nmap <F4> x<Plug>(Increase_x)x
nnoremap <F5> x<Plug>(Increase_x)x
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 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 2012の164日目の記事ですでに説明されていますけど、まぁ一応。 Vimは以下のキーを同一視してしまいます。
-
<C-i>
==<Tab>
-
<C-m>
==<Enter>
-
<C-[>
==<ESC>
なので、<Tab>
キーのマッピングしたのに<C-i>
キーのマッピングで上書きしてしまったってことが起こりえます。
inoremap <expr> <Tab> MySuperTab()
" ↓これは <Tab> のキーマッピングを上書きしてしまう!
inoremap <C-i> <C-o><C-i>
(上記の記事のコードをそのまま引用)
この挙動の理由について書かれた記事もありますので参考までにどうぞ [端末アプリで Ctrl- が Esc になる理由
アンチパターンとかではないけどあまり知られていないので、g:vim_indent_cont
について説明します。
g:vim_indent_cont
は以下のように\
を入力した時のインデント量をつかさどっている変数です。
この変数はVim script(vimrc)を編集しているときにしか使われないので、まぁVim scriptをいじらない方はどうでもいいですね。
let s:hoge = [
_______\
" ↑この`\`を入力した時のインデント量をつかさどっている変数
以下の 悪いパターン ではcolorscheme
をした後にhighlight
を定義しているが、colorscheme
コマンドでカラースキーマを変えるとこれらのhighlight
はすべて削除されてしまいます。
なぜかというとcolorscheme
コマンドを実行すると内部的にはhighlight clear
が呼ばれるため、これによって全てのhighlight
が削除されてしまうためです。
なので、カラースキーマが読み込んだ後に発行されるColorSchemeイベントを利用するのがベストでしょう。
悪いパターン
colorscheme hybrid
highlight Pmenu ctermfg=Black
highlight Pmenu ctermbg=Gray
highlight PmenuSel ctermfg=Black
highlight PmenuSel ctermbg=Cyan
highlight PmenuSbar ctermfg=White
highlight PmenuSbar ctermbg=DarkGray
良いパターン
function! DefineMyHighlights()
if g:colors_name is "hybrid"
highlight Pmenu ctermfg=Black
highlight Pmenu ctermbg=Gray
highlight PmenuSel ctermfg=Black
highlight PmenuSel ctermbg=Cyan
highlight PmenuSbar ctermfg=White
highlight PmenuSbar ctermbg=DarkGray
endif
endfunction
augroup cs
autocmd!
autocmd ColorScheme * :call DefineMyHighlights()
augroup END
コマンドをマッピングする際の、<C-u>
についてです。
以下の 悪いパターン では、<C-l>
の前に数字キーの入力がある場合にエラーが発生してしまいます。
Vim によって挿入される範囲指定を <C-u>
で削除し、誤って数字キーを入力してしまった場合でも正常に動作するようにします。
:help c_CTRL-U
を参照してください。
悪いパターン
nnoremap <C-l> :nohlsearch<CR><C-l>
良いパターン
nnoremap <C-l> :<C-u>nohlsearch<CR><C-l>
Vim 8.2.1978 及び Neovim 0.3.0 以降は <Cmd>
という特別な表記が使えます。
この表記を使えば、モードによらず同じ書き方ができる他(通常の :
とは違う方法で実行されるため、もちろん上記の <C-u>
の制約もない)、どのモードにいてもコマンドを実行できます。(コマンドをその場で実行するのが困難なコマンドラインモードなどで特に有用)
例
" <Cmd>と<CR>の間がExコマンドとして扱われる
noremap! <C-l> <Cmd>redraw!<CR>
マップコマンドの中には map(noremap)
や vmap(vnoremap)
がありますが、これらのコマンドは複数のモードに一気にマッピングを定義するため、不用意に使うと望んでいないモードのマッピングを破壊します。
少し面倒ではありますが、nmap(nnoremap)
や xmap(xnoremap)
や omap(onoremap)
や smap(snoremap)
を使用して個別に定義をした方がいいでしょう。詳しくは :help map-overview
もしくは :help map-table
を参照してください。
悪いパターン
" オペレータ待機モードや選択モードで予期しない動作をする
noremap ; :
良いパターン
nnoremap ; :
xnoremap ; :
map
コマンドが受け付ける <nowait> は <buffer> とともに利用して、バッファローカルマッピングしたときに意味がある。
グローバルの複数マッピング間では機能しない。
悪いパターン
" a だけだと待ちになる
nnoremap <nowait> a <cmd>echo 'short'<cr>
nnoremap aa <cmd>echo 'long'<cr>
良いパターン
" a だけで即座に発動する
nnoremap <nowait><buffer> a <cmd>echo 'short'<cr>
nnoremap aa <cmd>echo 'long'<cr>