また Vim のカラースキームかいた

前の に飽きてきたのでまたカラースキームを書いてしまった。もうちょっと褪せた感じというか、彩度の低い感じを考えてたはずが結構カラフルにも見える気がする。User1 ... User9 ってカラースキームファイルで定義した方がいいのかな、とか考えたけど使ったことないから使うまでやめとくことにした。

vim-colorscheme-reki


set background=light

light

set background=dark

dark


sandwich.vim がすこし便利になりました

先日 sandwich.vim にすこし手を入れまして、ちょっとだけ便利になりました。いくつかの新機能をデフォルトで有効にしたのですが、おそらく一番便利なのが関数囲みの追加・削除ができるようになったことだと思います。以前から設定を書けばできていたのですが、やはり面倒ですし設定なしで使えた方がいいですよね。

さて、それがどういったものかというと、例えば saiwf でカーソル下の単語を関数で囲むことができます。 f の入力の後、関数名の入力を求められますので入力して <Enter> を押してください。

arg      -- saiwffunc<Enter> -->     func(arg)

関数名の入力中には <Tab> キーで現在のバッファからの簡単な補完が使えます。また、 <C-f> でコマンドラインウィンドウへ移行すれば通常のインサートモード補完が使え、履歴を使うこともできます。

逆に関数囲みを削除するのは sdf です。

func(arg)       -- sdf -->             arg

sdF も使えます。関数がネストしているとき sdf はカーソル下の関数を削除するのに対し sdF はカーソル位置を囲んでいる関数を消します。

カーソルが 'func2' の上にある場合
func1(func2(arg))  -- sdf -->   func1(arg)
                   -- sdF -->   func2(arg)

というわけでデモです。

Function surroundings demo

他の機能については Wiki をどうぞ。

vim-highlightedyank を書きました

vim-operator-flashy というプラグインがあって、これ非常に便利で生活を彩る素晴らしいアイデアなんですよ。ただ、ユーザー定義オペレータってどうやってもドットリピートの単位を更新してしまうわけです。Vim の cpoptionsy 抜きでもドットリピートされてしまうのがなんとも気持ち悪かったので、オペレータによる解決をあきらめたものを書きました。

vim-highlightedyank

あんまり綺麗でないハックを使っているのでテキストオブジェクトやモーションがコマンドラインに出力したプロンプト等を握りつぶしたり、間違ったキー入力を入れても(偽装)オペレータ待機モードを抜けなかったりするけど、ひとまず実用上問題なさそうな感じがしています。正直、ちゃんと動くかわからないんですけど、しばらく試してみようかと思います。

たまには sandwich.vim の話をしたい

すこし前の話になりますが sandwich.vim というプラグインを作りました。有名なプラグイン surround.vim のようなもので、文字列を囲んだり、囲まれた文字列から囲みを消したり、置換したりします。 surround.vim に比べて、よりユーザーによる拡張性に重点を置いていて次のような点が特長です。

  • 囲み文字列のペアを式で指定できる
  • 削除/置換される囲み文字列のペアもユーザーが定義できる (surround.vim は追加するペアのみ)
    • 正規表現を使うこともできる
    • 他のテキストオブジェクトの選択範囲差分でも指定できる
  • ユーザーの定義した文字列に囲まれた領域を選択するテキストオブジェクトが使える

ユーザーが設定を追加する場合は、 g:sandwich#recipes というリストに追加していきます。ゼロから作ることもできますが、ひとまずデフォルトの設定をコピーするとよいでしょう。

let g:sandwich#recipes = deepcopy(g:sandwich#default_recipes)

私自身そこまでいろいろ設定しているわけではないのですが、いくつか紹介します。


\(, \) のペアを使う

Vim の正規表現におけるグループ化には \(, \) および、 \%(, \) を使うので Vim script を書いているときにこれらで囲めたり、囲みを消せたりできるといいな、と思うことがあります。

let g:sandwich#recipes += [
      \   {
      \     'buns': ['\(', '\)'],
      \     'filetype': ['vim'],
      \     'nesting': 1,
      \   },
      \
      \   {
      \     'buns': ['\%(', '\)'],
      \     'filetype': ['vim'],
      \     'nesting': 1,
      \   },
      \ ]

'nesting' キーは囲みがネスト構造をつくる場合は 1 、それ以外は 0 にしてください。例えば (, ) のような括弧はネストをつくるので 1、 ', ' のようなクオーテーションはネストをつくらないので 0 になります。

saiw\( と単語の上で入力するとその単語を \(, \) のペアで囲みます。また、 sd\%( と入力すると \%(, \) の囲みを消します。あるいは sdb という入力は常に一番近い囲みを消すのでこれを使うと便利でしょう。 \) という部分は二つの設定で被ってしまっていますが、 sandwich.vim はリスト後方の設定を優先するので saiw\) と入力すると単語を \%(, \) で囲むでしょう。別の入力を使いたければ 'input' キーを設定してください。

let g:sandwich#recipes += [
      \   {
      \     'buns': ['\%(', '\)'],
      \     'filetype': ['vim'],
      \     'nesting': 1,
      \     'input': ['%'],
      \   },
      \ ]

これで saiw% と入力すると単語を \%(, \) で囲むでしょう。

連続するシングルクオーテーション

Vim script だけではなかったと思うのですが、一部の言語にはシングルクオートに囲まれたリテラル文字列中の連続する二つのシングルクオートを一つのシングルクオートとみなす、というルールがあります。

echo 'Why don''t you try sandwich.vim?'     "--> Why don't you try sandwich.vim?

これは Vim の 'quoteescape' (:help 'quoteescape') オプションでは対応できないので、ちょっと不便ですね。 sandwich.vim で何とかしましょう。

let g:sandwich#recipes += [
      \   {
      \     'buns': ["'", "'"],
      \     'filetype': ['vim'],
      \     'skip_regex_head': ['\%(\%#\zs''\|''\%#\zs\)''\%(''''\)*[^'']'],
      \     'skip_regex_tail': ['[^'']\%(''''\)*\%(\%#\zs''\|''\%#\zs\)'''],
      \     'nesting': 0,
      \     'linewise': 0,
      \   },
      \ ]

これで例えば sd' と入力すると連続するクオーテーションをスキップして端のクオーテーションを消します。また、 sandwich.vim は囲まれた文字列を選択するテキストオブジェクトを提供するので、例えば vis' と入力すると連続するクオーテーションをスキップして文字列リテラルを選択します。

'skip_regex' は特定の正規表現にマッチする位置をスキップする機能で、 'skip_regex_head' および 'skip_regex_tail' はそれぞれ先頭・末尾の囲みを検索するときにのみ使われます。

実は文字列リテラルの端に連続するシングルクオートがあり、カーソルがその上にある場合は上手くいきません…。下の文字列リテラル中の # に示される位置がそれに当たりますが、間の文字列にカーソルがあれば大丈夫です。

"    ###              ###
echo '''string literal'''

関数で囲む

たまに関数名を指定して関数で囲みたいと思うことがあります。つまりこういうことです。

foo  --->  func(foo)

これは sandwich.vim の囲みを式で指定する機能を使うと実現できます。

let g:sandwich#recipes += [
      \   {
      \     'buns': ['FuncName()', '")"'],
      \     'expr': 1,
      \     'cursor': 'inner_tail',
      \     'kind': ['add', 'replace'],
      \     'action': ['add'],
      \     'input': ['f']
      \   },
      \ ]

function! FuncName() abort
  let funcname = input('funcname: ', '')
  if funcname ==# ''
    throw 'OperatorSandwichCancel'
  endif
  return funcname . '('
endfunction

'expr' キーが 0 でない値を持つとき 'buns' の要素はそれぞれ式として評価され、評価値で囲みます。 FuncName() 関数はユーザーに関数名を問い合わせ、空でなければ ( をつけて返します。

sandwich.vim は他のオペレータに倣って、デフォルトでは作用した文字列の先頭にカーソルを置きますが、 'cursor' キーを使うことで終了時のカーソル位置を指定できます。この場合は続けて引数を追加したい場合が多いので作用した文字列の末尾にカーソルを移動させます。

この設定は囲む動作でのみ使うので、 'kind', 'action' キーを使って必要になるシチュエーションを制限しています。

f:id:machakann:20160519180358g:plain

あまりキータイプ数の上では得をしていないようにも思われるかもしれませんが、矩形選択範囲に対して使えたり、ドットリピート可能だったりするので、対象が何か所もある場合に便利です。

関数囲みを消す

関数で囲めるのなら、これを消せてもいいと思うのが人情というものだと思います。関数を表す範囲を認識するのはなかなか複雑な仕事になるので既存のテキストオブジェクトを使いましょう。 sandwich.vim には既存のテキストオブジェクトの範囲差分から削除する文字列を指定する機能があるのでこれを使います。

例えば、HTMLなどのマークアップ言語のタグは Vim 組み込みのテキストオブジェクト it, at の差分で指定できます。 at の範囲から it の範囲を除いた部分がタグですね。 (:help it, :help at, :help tag-blocks)

      <--- it --->
<body>hello world!</body>
<--------- at ---------->

textobj-functioncall は関数囲み全体を、 textobj-parameter はカーソル下の引数を選択するので、カーソル直下の引数を残して関数囲みを消すことができます。

     <->    textobj-parameter
func(arg)
<------->   textobj-functioncall

既存のテキストオブジェクトを使う場合は 'buns' キーの代わりに 'external' キーを使います。 sdf と入力することで関数囲みを消します。

let g:sandwich#recipes += [
      \   {
      \     'external': ["\<Plug>(textobj-parameter-i)", "\<Plug>(textobj-functioncall-a)"],
      \     'noremap': 0,
      \     'kind': ['delete', 'replace', 'query'],
      \     'input': ['f']
      \   },
      \ ]

f:id:machakann:20160519180407g:plain

textobj-parameter の選択範囲はカーソル位置に依存するので、残念ながらビジュアルモードではうまく動きませんが、 itat はカーソル位置に寛容なので意図通りに動きます。また、矩形選択は必ずしも矩形である必要はありません。空行は無視されます。

let g:sandwich#recipes += [
      \   {
      \     'external': ['it', 'at'],
      \     'noremap': 1,
      \     'kind': ['delete', 'replace', 'query'],
      \     'input': ['t']
      \   },
      \ ]

f:id:machakann:20160519180411g:plain


複雑そうに見えますが、ひとまず 'buns' キーを設定するだけでも大体いい感じに動くと思います。他にも issue #1 #2 は sandwich.vim にどんなことができるかを端的に説明していて、どうやら Tex など書く場合に便利らしいです。よろしければ sandwich.vim を試してみてください。


おまけ: ハイライト機能

私は画面がぴかぴか光ればキャッキャッと喜ぶおさるさん並みの感性の持ち主なので、 sandwich.vim はこれでもか、というくらい画面がぴかぴかします。囲みを追加した場合には追加した文字列をハイライトするのですが、以前はユーザーが何かキーを押し次第ハイライトを消していたのをしばらく残るように最近書き換えました。 call operator#sandwich#set('all', 'all', 'hi_duration', 1000) と、極端に持続時間を長くしてみるとちょっと楽しいです。ハイライトが遅れて消えます。

f:id:machakann:20160519180416g:plain

なお、ユーザーが何らかの編集を開始した場合はハイライトを直ちに消します。ハイライトを消すのに timer 機能を使っているので新しめの Vim でのみこのように動きます、 timer 機能がない場合は以前と同じように動作します。正直なところ、新しい機能を使ってみたかっただけです。ハイライトなどいらぬ、という人間様は vimrc に次の先進的一行を堂々と刻んでください。

call operator#sandwich#set('all', 'all', 'highlight', 0)

columnmove.vim を書き直した

Vim 組み込みの f t F T ; , w b e ge W B E gE コマンドを縦 (列) 方向に模倣したモーションを提供するプラグインです。長いことバグとか放置していたのを何とかしました。

columnmove.vim

columnmove-f

f t F T コマンドは特定の文字が出てくるところまでカーソルを移動するコマンドです。そのままだととんでもねぇ眼力を要求されるので、移動先をハイライトするようになっています。

columnmove-f

そんな軟弱なものはいらん、という硬派な方、眼力MAXな方、 set cursorcolumn 運用の方は次の行を vimrc に追加してください。

let g:columnmove_highlight = 0

また、移動先ハイライト中 (ユーザーの入力待ち中) には以下のキーでスクロールできます。

上へスクロール 下へスクロール
一行ずつ <C-y> <C-e>
半ページずつ('scroll' オプションに依存) <C-u> <C-d>
一ページずつ <C-b> <C-f>

columnmove-w

w b e ge コマンドは単語の境界まで移動するコマンドです。列方向なので「単語」という言い方は適切ではないかもしれませんが。書いてから、欲しかったやつとは微妙に違うことに気が付いてやっつけで機能を追加した経緯があります。

let g:columnmove_strict_wbege = 0

このように vimrc に書いておくと文字があるかないかの境界に沿ってカーソルを移動します。つまり iskeyword オプションを考慮しませんし、スペースだろうがタブ文字だろうが文字があればカーソルを止める点が通常の w 等のコマンドとは違ってきます。例えば次のような列を考えてみましょう、ハイフンはスペースだと思ってください。

a
b

"
c
d

-
e

一行目の a から、 通常であれば w コマンドが止まるのは ", c, e です。しかし上記の設定をしていた場合、止まるのは ", - になります。ちなみに W コマンドであれば ", e ですね。

キーマッピングについて

私は gvim を使うことが多いので、デフォルトだと <M-f> <M-w> などのメタキー (Altキー) を使ったキーシークエンスへマッピングされます。ターミナルをお使いの方の場合これらのキーシークエンスは有効でないかもしれないので、お手数ですがマッピングしなおしてお使いください。 columnmove#utility#map() 関数が便利です。

" デフォルトのキーマッピングはいらない
let g:columnmove_no_default_key_mappings = 1

" columnmove#utility#map({モード}, {コマンドの種類}, {キーシークエンス})
" モード : n -> ノーマルモード
"         x -> ビジュアルモード
"         o -> オペレータ待機モード
"         i -> インサートモード
" 種類 : `f` `t` `F` `T` `;` `,` `w` `b` `e` `ge` `W` `B` `E` `gE` のどれか
" キーシークエンス : `:map` コマンドの `{lhs}` と同じように解釈される。
call columnmove#utility#map('nxo', 'f', '\f')
call columnmove#utility#map('nxo', 'w', '\w')

必要なものだけで十分だと思いますが、次のようにすると、まとめてマップできて便利です。

let g:columnmove_no_default_key_mappings = 1
for s:x in split('ftFT;,wbeWBE', '\zs') + ['ge', 'gE']
    call columnmove#utility#map('nxo', s:x, '\' . s:x)
endfor
unlet s:x

ユーザーさんに教えてもらいました。\ 始まりのキーシークエンスへマップされます。

オペレータと組み合わせて使う場合

もともと、 j k コマンドの延長のように考えていたので、デフォルトだとオペレータ待機モードで行指向に働きます。つまりカーソルの通る行全体が処理の対象です。

call columnmove#utility#map('nxo', 'e', '\e')

" Press \e to delete all.
abc
def     ->
ghi

ただし、columnmove#utility#map() 関数の第四引数に 'block' を与えていると矩形指向で働きます。

call columnmove#utility#map('nxo', 'w', '\w', 'block')

" Press \e to delete block-wise.
abc            bc
def     ->     ef
ghi            hi

こっちの方が便利な場合も多いかもしれませんね。どちらの場合でも o_v o_V o_CTRL-V は有効 (参考 :help o_v :help o_V :help o_CTRL-V) なのでどちらにしてもさほど困らないと思います。 o_v とか自体あまり使う人いない気もしますが。ビジュアルモード使えばいいですし。


もともと、 Vim script の練習のつもりで書き始めた初めてのプラグインなのですが、思いのほか長いこと使ってます。実際のところ、すでに世の中にはもっと万能で汎用的なモーションコマンドがたくさん発明されているので、それらに比べると、とりわけ便利というものでもないです。ただ、単純なのでマクロ (:help complex-repeat) のなかでよく使ったりしてます。地味に便利。

緑っぽい 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 を開きます。

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