緑っぽい Vim のカラースキームを書いた

青っぽい色が好きなので大体青い感じのカラースキームを好んで使っていたんですけど、 Vim を使っているんだし緑もいいかな、って思って書きました。

vim-colorscheme-tatami

set background=light はかなり明るく、set background=dark もそんなに暗くないです。書き始めた時に畳っぽく見えたので名付けたんですけど、以来ずっと「でも畳の緑色って結構すぐ抜けるよな…」と思い続けています。まあ、いいでしょう。

六畳間

六畳間

なんか四畳半はできないらしいです。

tyru.hatenablog.com

tyru.hatenablog.com

tyru.hatenablog.com

vim-vimhelplint を書きました

ドキュメントは大事です。自分で書いたプログラムですら、しばらくすると使い方を忘れてしまいます。おおよそ、自分で書いてきた vim プラグインにはドキュメント書いてきたのですが、あとあとになってミスを見つけたりしてつまらないコミットを増やすこともしばしばです。と、いうわけで vim のヘルプ用にタグなどをチェックしてくれるツールを書きました。

vim-vimhelplint

使い方は簡単で、 vim のヘルプファイルの編集を開始し、

:VimhelpLint

するだけです。疑わしいところがあれば quickfix へ登録されます。 :copen するなどしましょう。

:VimhelpLint!

すると自動で quickfix window を開きます。

精度はまだまだだと思いますので、必ず自分の目と手で確認してください。なお、このツールにヘルプファイルはありません。いろいろあって追加されました!

swap.vim についてあれこれ

先日、 swap.vim というものを書きました。コンマ区切りの要素の順番を簡単に入れ替えるためのプラグインです。

今のところ、デフォルトではコンマで区切られたテキストだけに反応します。しかし、ユーザーが設定することでコンマ区切り以外のテキストも扱えます。詳しくは help に書いてありますが、よくわからなければ issues で聞いてください。区切り文字にはゼロ幅マッチも使えるので、例えばキャメルケース(あるいはパスカルケース)の文字列の部分を入れ替えたりできます。

let g:swap#rules  = deepcopy(g:swap#default_rules)
let g:swap#rules += [
      \   {'mode': 'n', 'body': '\<\h\w*\>', 'delimiter': ['\C\ze[A-Z]']},
      \ ]

demo1

この設定自体はそんなに便利でもないですね。他にも、行選択あるいは矩形選択と組み合わせて使うと行単位で入れ替えたりできます。これには特に設定は必要ありません。 demo2

以前は編集範囲が広いと動作が遅くなっていたのが、最近のコミットでずいぶん改善しました。また、何か思いつけばよくしていこうと思います。

swap.vim を書きました

思い返してみると、実際たいしたことはないうえに頻繁にあるわけでもないんですけど、関数の引数だったり配列の要素だったりの順番を入れ替えたくなることがあります。

call func(arg1, arg2, arg3)

これが例えば arg2arg3 を入れ替えるとかだったら問題ないんです。 , arg2 を delete して、

call func(arg1, arg3)

arg3 の後ろへ paste 、

call func(arg1, arg3, arg2)

できましたね。世の中には vim-textobj-parameter あるいは fork版 という便利なものがあるので、これらを使えばいっそう心の平穏が保たれます。ところが arg1arg3 を入れ替えるとなると、うってかわって心穏やかではいられません。 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< を入力すると arg2arg1 が入れ替わり、

call func(arg2, arg1, arg3)

一方、 g> を入力すると arg2arg3 が入れ替わります、

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 であればデフォルトで設定されているので行頭の \ をスキップします。

demo

大きなブロック (移動しているもの) があるのは {} によるグループ化を考慮しているためです。

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 はまだ使い始めたところだけどいろいろ便利そうです。単純に括弧の件だけでも十分すぎるほどに朗報で興奮しています。

完全に余談ではあるけどビルドにあたって、ずっと欲しいと思っていた このパッチ をあててみました。もやもやしていたのが解決されて、とても幸せです。これも早く本体に取り込まれてほしいです。