swap.vim を書きました
思い返してみると、実際たいしたことはないうえに頻繁にあるわけでもないんですけど、関数の引数だったり配列の要素だったりの順番を入れ替えたくなることがあります。
call func(arg1, arg2, arg3)
これが例えば arg2
と arg3
を入れ替えるとかだったら問題ないんです。 , arg2
を delete して、
call func(arg1, arg3)
arg3
の後ろへ paste 、
call func(arg1, arg3, arg2)
できましたね。世の中には vim-textobj-parameter あるいは fork版 という便利なものがあるので、これらを使えばいっそう心の平穏が保たれます。ところが arg1
と arg3
を入れ替えるとなると、うってかわって心穏やかではいられません。 arg1
を切り取ったり、コンマを移動させたり面倒なわけです。この辺 Vim がうまいことやってくれるといいなー、とは思っていました。
ところが、上の例くらい簡単ならいいんですけど、関数が引数に関数を取ったり、
call func(arg1, func(arg1, arg2, arg3), arg3)
任意の文字列を含んだり、
call func(arg1, 'foo, bar')
してくると、なかなか複雑です。しかも、言語によって違う部分もあるので難しい。そんなわけでしばらく放置していたんですけど、まあそこそこいい感じに動く、ぐらいなら可能かもしれないと思いたって swap.vim を書きました。
このプラグインは三つのキーマッピング g<
, g>
, gs
を定義します。g<
はカーソル下のアイテムを直前のアイテムと入れ替えます。 g>
はカーソル下のアイテムを直後のアイテムと入れ替えます。最初の例
call func(arg1, arg2, arg3)
でいえば、カーソルを arg2
に置いて g<
を入力すると arg2
と arg1
が入れ替わり、
call func(arg2, arg1, arg3)
一方、 g>
を入力すると arg2
と arg3
が入れ替わります、
call func(arg1, arg3, arg2)
gs
はちょっと変わり種で、 swap mode と呼んでいるモードのようなものを開始して、インタラクティブに要素入れ替えを行います。 swap mode では h
, l
によって隣り合う要素の入れ替え、 j
, k
によるカーソルの移動、 u
, <C-r>
による簡単なアンドゥ・リドゥ、数字 1
から 9
による入れ替えアイテムの直接指定ができます。納得のいく順番になったらご存知のように <Esc>
でノーマルモードに戻ります。
これらはドットリピートで繰り返すことができますが、注意する点が一つあります。 g<
, g>
は常にカーソル下のアイテムとの相対位置で働く一方で、 gs
が繰り返される場合は何番目のアイテムか、という絶対位置で働きます。この違いを覚えておかないと予期しない結果を得るでしょう。
一応、非明示的な行継続がある場合でもいい感じにできるようにインデントをスキップするようにしています。また、設定次第では行継続文字を無視もできます。例えば Vim script であればデフォルトで設定されているので行頭の \
をスキップします。
大きなブロック (移動しているもの) があるのは {}
によるグループ化を考慮しているためです。
julialang v0.4
先週 julia の最新安定バージョンの 0.4 がリリースされたみたいですね。早速ビルドしてみました。Windows を使っているので msys2 を使ってここにあるとおりにビルドしてます。ちょっと前まで master をビルドしようとすると gmp のビルドでこけてたんですけど なんかいつの間にか治ってました。 issue 閉じなくていいのかな。
ビルドはなぜか openblas が cpu の自動認識に失敗したので OPENBLAS_TARGET_ARCH
を直接指定してもう一度 make 走らせたらいけました。
make -j 4 OPENBLAS_TARGET_ARCH=HASWELL
こんな感じ。OPENBLAS_TARGET_ARCH
に指定できる物は deps/openblas/TargetList.txt
に列挙されてます。不思議なのですが openblas 単体でビルドしようとすると問題なく cpu 認識するようです。何が違うんだろうか。
もう一つ、なぜか usr/lib/sys.ji
が生成されませんでした。多分これが原因だと思うんですけど、うちの環境だと起動に 5s 前後かかってしまう…。 0.3.11 のときはビルド時に自動的に生成されていたと思うんですけど。地味につらいのでいろいろ考えた結果、よく見ると usr/lib/sys.dll
は生成されているようなので、起動時に --precompiled=yes
つけるようにすると <1s で起動するようになりました。とりあえず常に --precompiled=yes
つけるバッチファイル書いてパス通ったところにおいて満足しています。
@echo off path\to\julia --precompiled=yes %*
実行オプションに --output-ji
とか system image 生成するためのものがあるっぽいんですけどうまくいかない…。
ERROR: could not open file boot.jl
とか言われる。
まだちょっと触っただけですけど、 Incremental code caching とかいう新機能のおかげで package の読み込みがかなり早くなっているのに驚きました。初回使用時にコンパイルのために待つことになるので、いっそのこと Pkg.add()
のときにやってくれてもいいかなー、と思いました。コンパイルされたパッケージのイメージファイル (多分、~/.julia/lib/
以下のファイル) が生成されてしまえば以降は速いですね。多分パッケージをアップデートしたらまた、コンパイルしなおす感じでしょう。一番最初に julia をさわったときは起動の遅さとパッケージ読み込みの遅さ(Gadflyとか)が引っかかっていたので、今回ので解消されてしまいました。いくつかコンパイルできなかったパッケージもあったみたいなので、もしかしたら将来もう少し早くなるのかも。
もともと数値計算を主眼に置いているので、息をするように複素数を扱えて、瞬きをするように行列計算できるうえ、多重ループ書いても速いとか夢みたいな言語なのでひそかに応援してます。
Vim のドットリピートとテスト
2018/8/29 追記
以下に書いている問題は v8.0.0548
以降解消されています。
たまに Vim の拡張としてオペレータを書くのですが、テストを書こうとすると困ることがあります。例として、オペレータを書いてみました。指定された領域の先端に a
を、末端に b
を入力するオペレータです。
function! OperatorInputAB(motionwise) abort let head = getpos("'[") let tail = getpos("']") call setpos('.', tail) normal! ab call setpos('.', head) normal! ia endfunction nnoremap <silent> \a :set operatorfunc=OperatorInputAB<CR>g@
今回は、ビジュアルモードは考えません。またカウント、レジスター、 operatorfunc
に渡される引数も使いません。詳しくは、 :help opfunc
及び :help map-operator
を参照願います。
さて、簡単で、なんら役に立たないものではありますが、ひとまずオペレータができました。しかし、このオペレータのドットコマンド時の挙動をテストしようと思うとなかなかうまくいきません。 vim-themis を例にとりますが、次のようなテストを想定しています。
let g:assert = themis#helper('assert') let s:suite = themis#suite('test: ') function! s:suite.before() abort function! OperatorInputAB(motionwise) abort let head = getpos("'[") let tail = getpos("']") call setpos('.', tail) normal! ab call setpos('.', head) normal! ia endfunction nnoremap <silent> \a :set operatorfunc=OperatorInputAB<CR>g@ endfunction function! s:suite.dotrepeat() abort call setline('.', 'foo') " test 1 normal 0\aiw call g:assert.equals(getline('.'), 'afoob', 'failed after an operator command') " test 2 normal . call g:assert.equals(getline('.'), 'aafoobb', 'failed after a dot-repeating') endfunction
実行した結果を見ると、
not ok 1 - test: dotrepeat # The equivalent values were expected, but it was not the case. # # expected: 'aafoobb' # got: 'aafoob' # # failed after a dot-repeating # tests 1 # passes 0 # fails 1
test 2
のところでこけていますね。なぜか、 b
が最後に入力されていません。詳しくはわからないのですが、どうも要約するとドットリピートで繰り返される単位の更新のタイミングの問題のようです。次の二つのスクリプトを :source
してみると、一見同じことをしているように見えて別の結果が得られます。
- a.vim
function! OperatorInputAB(motionwise) abort let head = getpos("'[") let tail = getpos("']") call setpos('.', tail) normal! ab call setpos('.', head) normal! ia endfunction nnoremap <silent> \a :set operatorfunc=OperatorInputAB<CR>g@ call setline('.', 'foo') normal \aiw normal .
- b.vim
function! OperatorInputAB(motionwise) abort let head = getpos("'[") let tail = getpos("']") call setpos('.', tail) normal! ab call setpos('.', head) normal! ia endfunction nnoremap <silent> \a :set operatorfunc=OperatorInputAB<CR>g@ function! s:operation() normal \aiw normal . endfunction call setline('.', 'foo') call s:operation()
ほぼ同じことをしているように見えますが、 a.vim
の結果は意図通りの aafoobb
、しかし b.vim
の結果は最後の b
が欠けて aafoob
です。違いは a.vim
が関数の外でドットリピートしているのに対し、 b.vim
は関数の中でドットリピートをしている点です。どうやら、ドットリピートの単位はすべての関数を抜ける時にまとめて更新されるらしく、何らかの関数の中でドットリピートした場合は OperatorInputAB()
の中の最後の編集 normal! ia
のみがドットリピートの対象になっているみたいです。
と、いうことは逆に考えると a.vim
をもう少し工夫するとドットリピート用のテストが書けて、あとはバッチなりシェルスクリプトなりを用意すれば大勝利、となるわけですけど未だ肝心のテストはかけてないです。書かないと、はー。
Vimのpatch-7.4.849について
Vim の patch-7.4.849 欲しさに野良ビルドに手を出してしまいました。これは例えば括弧を自動的に閉じるような設定をしている場合に便利なのです。
inoremap ( ()<Left>
この設定自体には賛否両論あるらしいのですがその点はスルーします。ただ、賛だとしても Vim の操作として考えた時大きな短所があるのは認めざるを得なくて、今まではその点に目をつぶりながら似たような設定を使ってきました。
その短所がどういうものかというと、例えば上記のような設定をせずに、空のバッファで a(foo)<Esc>.
と打ち込むと当然バッファの内容は次の通りになります。
(foo)(foo)
.
は直近の入力を繰り返しているのでこうなりますね。期待通りの結果だと思います。しかし、上記のような設定をしていた場合(その場合は入力は a(foo<Esc>l.
)はこうはならず、次のようになります。
(foo)foo
これは設定に含まれている <Left>
キーが少し特殊な扱いになるからです。一部のキーは、挿入モードで押下されると、あたかもそこでいったん挿入モードを抜け、もう一度挿入モードに入ったかのように解釈されます。 <Left>
や <Right>
のような文字入力を伴わずにカーソルを移動するものや、 <C-o>
のように挿入モードでノーマルモードコマンドを使うためのキーなどを含めいろいろあり :help ins-special-special
に列挙されています。さて、この関係で上記のような設定をしていた場合、キー入力は a(foo<Esc>l.
ですが、キーマップを展開すると a()<Left>foo<Esc>l.
となります。 .
が繰り返す直近の入力は <Left>
より後の foo
のみ、とみなされたというわけです。<Left>
でインサートモードを抜けて、カーソルを移動、インサートモードに入りなおしている、と考えるとここでドットリピートの単位が区切られてしまっているのです。
小さいといえば小さいのですけど、これがなかなか入力を繰り返す時にネックになることが多いのです。私は smartinput を使って似たような設定を使っていたのですけど、この問題のために一時的にすべての設定を停止するキーマップをvimrcに書いてなんとか誤魔化していました。とはいえ、このキーマップも意図的に操作しないといけないので、先に繰り返すと決めていた場合にしか効果がないので気休めにしかなりません。件の挙動はヘルプにもしっかり書いてあるし、仕様だと諦めていたところに Patch-7.4.849 がきました。これはこのように使います。
inoremap ( ()<C-g>U<Left>
<Left>
や <Right>
の前に <C-g>U
を挟むことでドットリピートで繰り返される単位を区切らないようにすることができるようになりました。()<Left>foo
の入力を繰り返すということですね。すばらしい。ただし、制限がないわけではなくて、どうも同一の行内で編集が完結する場合のみしか有効ではないみたいです。
" | はカーソル {|}
これを <Enter>
一発で
{ | }
こうするような設定とかだとダメなんでしょう。使ってないからいいかな。 <C-g>U
はまだ使い始めたところだけどいろいろ便利そうです。単純に括弧の件だけでも十分すぎるほどに朗報で興奮しています。
完全に余談ではあるけどビルドにあたって、ずっと欲しいと思っていた このパッチ をあててみました。もやもやしていたのが解決されて、とても幸せです。これも早く本体に取り込まれてほしいです。
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 にお詳しい方なら目次を眺めるだけでも何ができるかの見当がある程度つくかもしれません。