Vim の自動インデント機能について
必要になっていろいろ調べてみると、思いのほか分かっていなかったのでまとめました。上から調べて行って該当するものが使われるようです。
'equalprg'
オプションが空でなければ指定された外部プログラムが使われる。'lisp'
オプションがオンなら組み込みで定義された lisp 標準(の一種)のインデントがなされる。'autoindent'
をオンにする必要がある。
'indentexpr'
が空でなければ'indentexpr'
の評価によってインデント深さを計算する。- ユーザーが Vim script によってインデントを定義することができる。
:filetype indent on
によって有効になるインデントの多くは$VIMRUNTIME/indent/{ファイルタイプ名}.vim
で定義されるインデント関数を'indentexpr'
に設定することによって実現される。'indentkeys'
オプションによってトリガーとなるキーを設定できる。
'cindent'
がオンなら組み込みのC言語向けインデントを行う。'cinkeys'
オプションによってトリガーとなるキーを設定できる。'cinoptions'
オプションによって細かい調整ができる。'cinwords'
に登録されたキーワードに動作が依存する。
'smartindent'
がオンなら組み込みのC言語風のインデントを行う。'cindent'
のほうが厳密。'autoindent'
をオンにする必要がある。'cinwords'
に登録されたキーワードに動作が依存する。
'autoindent'
がオンなら自動インデントが行われる。- 新しい行は常に元の行と同じインデント、という簡単なもの。
自動インデントをしない。
'lisp'
と 'smartindent'
が 'autoindent'
を要求するのは知らなかったのですが 'smartindent'
は 'autoindent'
なしでも動いているような? あと 'equalprg'
って具体的にどういうプログラムを指定するんでしょう?
たまに見る
vimrc に
set noautoindent
書いたのに自動インデントがオフにならない。
のようなのは、ファイル読み込みのときにバッファローカルな 'indentexpr'
が設定されているような気がします。
:verbose setlocal indentexpr
を実行して 'indentexpr'
が空でなければ vimrc に
augroup vimrc autocmd FileType {ファイルタイプ名} setlocal indentexpr= augroup END
と書くか、あるいは ~/.vim/after/indent/{ファイルタイプ名}.vim
あたりに
setlocal indentexpr=
と書くのがよいでしょう。 vimrc に書く場合 :filetype indent on
より後に書かなければ有効になりません。
さらにそれでもまだ自動でインデントされるなら、
verbose setlocal equalprg? verbose setlocal lisp? verbose setlocal cindent? verbose setlocal smartindent? verbose setlocal autoindent?
を順に実行して犯人探しというのが筋ですが、 ~/.vim/after/indent/{ファイルタイプ名}.vim
あたりに
setlocal equalprg= setlocal nolisp setlocal indentexpr= setlocal nocindent setlocal nosmartindent setlocal noautoindent
と書くのが手っ取り早いですね。'equalprg'
が空でなかったり 'lisp'
がオンの場合はなかなかないので上二行はいらないかもしれません。
参考
- :help =
- :help 'equalprg'
- :help 'lisp'
- :help 'indentexpr'
- :help 'cindent'
- :help 'smartindent'
- :help 'autoindent'
Vimの組み込み関数について
Vim の組み込み関数も結構変更を受けたりしていて、古い Vim だと動かない機能があったりする。これは仕方がない。どちらかというと今なお活発に開発が行われているということで、いいことだと思う。ただ、困るのはそれがいつからだったか調べるのが大変面倒な作業だという点だ。ひとまず組み込み関数の追加・変更履歴を調べた。大体、7.4以降の話でそれより前は大変なのでカット。どこかにこういうのがまとまってるとこがあると助かるんだけど。コマンド、オプション、オートコマンドイベントなどもまとまってるとありがたい。
追加・変更リスト
arglistid()
- patch-7.4.312: 追加。
and()
- patch-7.3.377: 追加。
byteidxcomp()
- patch-7.4.057: 追加。
char2nr()
cursor()
- patch-7.4.310: 第一引数
{list}
に第四要素curswant
を指定できるように。
eval()
- patch-7.4.574:
eval('$')
がエラーを返すように修正。 #687
executable()
- patch-7.4.428: MS-Windowsで間違った値を返すのを修正。 #449
exepath()
- patch-7.4.235: 追加。
exists()
- patch-7.4.268: スクリプトローカル関数の存在を確認できないのを修正。
expand()
- patch-7.3.065:
<slnum>
の追加。現在の行番号へと展開される特殊引数。 - patch-7.3.465: 第三引数
{list}
の追加。返り値をリストとして受け取れるように。 - patch-7.4.423: シェル変数や環境変数を展開できていなかったのを修正。
- patch-7.4.465: 長い文字列でクラッシュするのを修正。 #649
feedkeys()
- patch-7.4.601: 第二引数
{mode}
に'i'
を指定できるように。
getbufvar()
- patch-7.3.831: 第三引数
{def}
の追加。タブ、ウィンドウ、バッファあるいは変数が存在しない場合は{def}
に指定された値を返す。 #245
getchar()
- patch-7.4.306: 端末で使用時に、第一引数
{expr}
に0
か1
を与えた場合<Esc>
キーがすぐに取得できないのを修正。 - patch07.4.457:
<expr>
マッピングの中でgetchar()
を呼ぶと、:autocmd
イベント用の特殊なキーシークエンスをキャッチしてしまう。これを回避するために<Cursorhold>
キーが定義された。 #607
getcmdwintype()
- patch-7.4.392: 追加。
getcurpos()
- patch-7.4.313: 追加。
- patch-7.4.578:
$
コマンド使用後に返り値のcurswant
が負数になっているのを修正。(パッチコメントでは空行でおこる、とあるが空でない行でも同様) #604
getpos()
- patch-7.4.310:
第一引数{expr}に'.'を指定した場合、返り値のリストに第五要素curswantを追加するように変更。 - patch-7.4.313: patch-7.4.310の変更を取消。代わりに
getcurpos()
関数を追加。
getreg()
- patch-7.4.242: 第三引数
{list}
の追加。返り値をリストとして受け取れるように。 - patch-7.4.513: クラッシュを引き起こす恐れのあるバグを修正。詳細はよくわからず。リストを返す場合に起こる?
gettabvar()
- patch-7.3.831: 第三引数
{def}
の追加。タブ、ウィンドウ、バッファあるいは変数が存在しない場合は{def}
に指定された値を返す。 #245 - patch-7.4.434: 第二引数
{varname}
に空文字を与えた場合スコープ変数を取得できるように。 #622 - patch-7.4.442: (patch-7.4.434の修正?) #622
getwinvar()
- patch-7.3.831: 第三引数
{def}
の追加。タブ、ウィンドウ、バッファあるいは変数が存在しない場合は{def}
に指定された値を返す。 #245
glob()
- patch-7.3.465: 第三引数
{list}
の追加。返り値をリストとして受け取れるように。 - patch-7.4.654: 第四引数
{alllinks}
の追加。返り値にすべてのシンボリックリンクを含められるように。
globpath()
- patch-7.4.279: 第四引数
{list}
の追加。返り値をリストとして受け取れるように。 - patch-7.4.654: 第五引数
{alllinks}
の追加。返り値にすべてのシンボリックリンクを含められるように。
glob2regpat()
- patch-7.4.668: 追加。
has()
- patch-7.4.236: パッチがあてられているかを
has('patch-7.4.236')
の形式で確認できるように。
input()
- patch-7.4.047: マッピングから呼ばれた関数の中で動作しない。#469
invert()
- patch-7.3.377: 追加。
luaeval()
- patch-7.3.490: 追加。
map()
- patch-7.4.525:
{expr}
が不正な場合にメモリリークするのを修正。 - patch-7.4.708: 高速化。 #724
match()
matchadd()
- patch-7.4.528:
'regexpengine'
が0の時クラッシュする恐れのあるバグを修正。 #668
matchaddpos()
- patch-7.4.330: 追加。
- patch-7.4.343: (関連する?) #580
- patch-7.4.362: 指定した長さよりも行が短い場合は行の最後までハイライトする(パッチコメントが逆のような?)。同時にマルチバイト文字に関連したバグも修正されている。 #586
matchdelete()
- patch-7.4.343: (matchaddpos()によるハイライトを消す際に限り?)正しくハイライトを消せないのを修正。 #580
matchend()
matchstr()
- patch-7.4.526: 長い文字列で失敗するのを修正。
mkdir()
- patch-7.4.006:
mkdir('foo/bar/', 'p')
が末尾のパスセパレーターでエラーを吐くのを修正。 - patch-7.4.010: patch-7.4.006 以降無効な引数でクラッシュするのを修正。
or()
- patch-7.3.377: 追加。
screenattr()
- patch-7.3.1164: 追加。
screenchar()
- patch-7.3.1164: 追加。
screencol()
- patch-7.3.748: 追加。
screenrow()
- patch-7.3.748: 追加。
search()
- patch-7.4.771: 第二引数
{flags}
に'bceW'、あるいは第二引数{flags}
に'bce'と第三引数{stopline}
を指定したときにカーソル直下のマルチバイト文字の検索に失敗するのを修正。 #747
searchpos()
- patch-7.4.771: 第二引数
{flags}
に'bceW'、あるいは第二引数{flags}
に'bce'と第三引数{stopline}
を指定したときにカーソル直下のマルチバイト文字の検索に失敗するのを修正。 #747
setmatches()
- patch-7.4.745:
getmatches()
の返り値をsetmatches()
で復元できないのを修正。
setpos()
- patch-7.4.310: 第二引数
{list}
が第五要素curswant
を許容するように。
setreg()
- patch-7.4.243: 第二引数
{value}
がリストを許容するように。 - patch-7.4.249: 第二引数
{value}
に数値のリストを渡した場合に働かないのを修正。 - patch-7.4.725:
:call setreg('"', [])
で内部エラーを出すのを修正。 #676
sha256()
- patch-7.3.816: 追加。 #86
sort()
- patch-7.3.224: 第三引数
{dict}
の追加。 "dict" 属性の関数が self を参照できる。 - patch-7.4.341: 第二引数
{func}
に'n'
を指定できるように。要素を数値としてソートする(通常は文字列として扱われる)。 - patch-7.4.351:
安定ソートに。 - patch-7.4.358: 本当に安定ソートに。
- patch-7.4.411: 長さの違う文字列のソートに問題があるのを修正。'foo bar'が'foo'よりも先にソートされていた。
strchars()
- patch-7.4.755: 第二引数
{skipcc}
を追加。合成文字のカウント方法を指定できるように。
submatch()
- patch-7.4.241: 第二引数
{list}
の追加。返り値をリストで受け取れるように。
substitute()
- patch-7.4.045:
\ze
から始まるパターンで正しく動かないのを修正。 - patch-7.4.158: (patch-7.4.045以降?)
\zs
を含むパターンが正しく動作しないのを修正。 #503 - patch-7.4.323: ゼロ幅マッチがマルチバイト文字を破壊するのを修正。 #572
- patch-7.4.499: 高速化。 #648
system()
- patch-7.4.247: 第二引数
{input}
がリストを許容するように。 - patch-7.4.427:
:autocmd
イベントInsertCharPre
にて実行された場合に表示が崩れるのを修正。 - patch-7.4.451: 空文字を引数に渡すとエラーを吐くのを修正。
systemlist()
- patch-7.4.248: 追加。
- patch-7.4.256: 正しく動いてなかったのを修正。 #548
- patch-7.4.597: 返り値に変更を加えられないのを修正。
uniq()
- patch-7.4.218: 追加。
wildmenumode()
- patch-7.3.828: 追加。
winrestview()
- patch-7.4.311: 第一引数の
{dict}
に全ての要素を指定する必要はなくするように変更。curswant
のみ、topline
のみの指定などができる。 - patch-7.4.491:
topline
に負数を指定すると表示が崩れるのを修正。 #637
xor()
- patch-7.3.377: 追加。
sandwich.vim を書きました
vim-sandwich という vim plugin を書きました。これは vim-surround や vim-operator-surround などのような、括弧やクオーテーションなどで囲まれた文字列を編集するためのプラグインと同種のものです。改行の扱いや矩形選択に対する挙動など、細かい点が自分好みなものが欲しくなったので書きました。
実のところ先駆者達と大きな違いありません。あえて挙げるとすれば、ドットリピートに外部ライブラリ(vim-repeat)を必要としない点、改行を含むパターンを扱えることなどが vim-surround と異なります。 vim-operator-surround との違いは補助用のテキストオブジェクトを同梱している点です。抱き合わせですね。ただ、補助用とは書きましたが単体でも使用できます。 vim-textobj-multiblock 、 vim-textobj-anyblock 、 vim-textobj-between (こちらは少し違う機能ですが)を想像していただければおよそそのままかと思います。このようなオペレータとテキストオブジェクトの必要とする情報/設定が似通ってきたので、まとめて管理したいと思ったのも書くきっかけでした。
オペレータは三種あります。それぞれ、囲みの追加/削除/置換を行います。削除あるいは置換される文字列は両端が同じ文字か、事前に設定された文字列のペア(例えば、 (
と )
)に一致した場合に編集されます。これは vim-operator-surround の定めているルールと同じです。
テキストオブジェクトは二種あります。一つはユーザーに入力を促し、入力に一致する設定があれば事前に設定されたペアに囲まれた領域を、一致するものがなければ入力された一文字に囲まれた領域を選択します。もう一つは事前に設定されたペアの中からカーソルに最も近いものを自動的に検索します。
囲みの削除/置換を行うオペレータはその性質上、補助用のテキストオブジェクトと常に一緒に使うのが便利なのでこれらの複合マッピングにバインドされています。まとめると操作は次のようになります。
- 囲みを追加
sa
にバインドされています。例えば saiw(
と押下すると foo が (foo) となります。
- 囲みを削除
sd
にバインドされています。例えば sd(
と押下すると (foo) が foo となります。また、 sdb
と押下すると自動で検索するテキストオブジェクトが使われ、同じ結果が得られるでしょう。
- 囲みを置換
sr
にバインドされています。例えば sr("
と押下すると (foo) が "foo" となります。また、上記と同様に srb"
でも同じ結果を得られるでしょう。
- テキストオブジェクト
ユーザーの入力を促すテキストオブジェクトは is
及び as
に、自動で検索するテキストオブジェクトは ib
及び ab
にバインドされています。 is
及び ib
は囲みの内側のテキストを、 as
及び ab
は囲みを含めた領域を選択します。
|<----ib,is---->| {surrounding}{surrounded text}{surrounding} |<------------------ab,as---------------->|
いくつか特徴的な点を紹介します。
ハイライトについて
まず目に付くのがハイライト機能でしょう。囲みの追加/削除/置換において、挿入位置/削除される文字列/置換される文字列をハイライトします。囲みの削除においては削除の寸前に、ごく短い時間だけハイライトします。この時間は g:operator#sandwich#highlight_duration
変数で調節可能です。ハイライトの間もユーザーの入力をブロックせず、入力があれば即座に編集を終了します。しかし、それでもうっとうしいという方は次の行を vimrc に加えましょう。
call operator#sandwich#set('delete', 'all', 'highlight', 0)
カウントについて
次にノーマルモードにおけるカウントの扱いが独特となっています。一般的にオペレータとモーションあるいはテキストオブジェクトの組み合わせは次のような順番で入力されます。
[count1]{operator}[count2]{textobject}
通常のオペレータは [count1]
と [count2]
を区別しません。すなわち 3diw
と d3iw
は全く等価です。両方を使った場合、そのカウントは二つのカウントの積になります。すなわち 2d3iw
と 3d2iw
は等しく、 6diw
または d6iw
にもまた等しくなります。これに対し、 sandwich.vim のオペレータは二者を区別します。 [count1]
をオペレータに渡し、 [count2]
をテキストオブジェクトへ渡します。すなわち 2sa3iw''
は二つの単語と間の空白を二度シングルクオーテーションで囲みます。2以上のカウントをオペレータに与えた場合デフォルトではカウントの回数だけユーザーに入力を促します。この挙動はオプションで制御可能です。
call operator#sandwich#set('all', 'all', 'query_once', 0)
.
コマンドで繰り返す際にカウントを与えた際には、カウントをテキストオブジェクトに渡し、オペレータは最後に与えられたカウントを使い続けます。
カスタマイズについて
さらに、 sandwich.vim
には豊富なオプションがあります。カスタマイズをしようと思った時には大きな助けとなるでしょう。しかし、反面ドキュメントがそのために長くなっています。このためファイルを三つに分けています。 doc/sandwich.jax
には共通部分について書かれており、これを眺めるだけでも使用には困らないでしょう。カスタマイズをしたくなったら doc/operator-sandwich.jax
及び doc/textobj-sandwich.jax
をご覧ください。それぞれ分割してなお分量がありますが、 Vim にお詳しい方なら目次を眺めるだけでも何ができるかの見当がある程度つくかもしれません。