wasavi 0.5.244 released

リリースした。

サマリ

変更点
  • 入力モードのさまざまなバグを修正
  • 入力モードに ^W、^U、^D、^T、^@(ctrl+space) を実装
  • f/F/t/T がカタカナ、ひらがな、および漢字の読みに対応する latin-1 の文字を認識するようにした
  • Firefox 版の Add on SDK を 1.12 に更新
ダウンロード

vi and Japanese #5

やはり fftt(f/F/t/T 系の横方向の検索機能を一言でなんと言い表してよいか思いつかず、そのまま fftt と呼んでいる)の辞書を非同期にしかアクセスできないのはちょっと不便なので、サイズ的に大したこともないのを前提に、wasavi 起動時にバックグラウンドからフロントエンドに転送することにする。

現状では、辞書は ArrayBuffer とか Uint8Array とか、そういう系で保持している。これをバックグラウンドとコンテントスクリプト間でやり取りできるのか? というと、Opera 以外はできない。なぜか Opera はできる。Opera なのに……。

しかしこのへんはおそらく、html5 のメッセージング API でも議論中のトピックだった記憶が。つまり仕様もころころ変わりそうなので、無難にバイナリ文字列か単純な配列に落とし込む方がいいかもしれない。

あるいは、コンテントスクリプト側から明示的にエクステンションのアーカイブ内の辞書を読み込んでもいい。chrome.extension.getURL() や、opera.extension.getFile() が使えると思う。しかし Firefox のコンテントスクリプトはそのへんの自由度がかなり低い上にコンテントスクリプトに対してどういう API が公開されているかすらドキュメント化されていない適当さなので 3 ブラウザとも似た方法で、としにくい。というと、この点から考えてもやはりバイナリ文字列か配列ということになる。

というわけで、そうした。

vi and Japanese #4

fFtT の日本語拡張。wasavi.js の fFtT 処理に組み込んで実際に使用できるようにしてみた。

で、実際に使ってみると。

うーん、自分で作っておいてなんだけど。これはけっこう、いやかなり便利かも。vim に欲しいくらい。vim の偉い人が輸入してくれないかな……。そういうプラグイン的なものは、適当にぐぐった限りではないようだ。

ただいくつか気になる点はあって:

  • fftt 辞書はバックグラウンドが持っている。fFtT コマンドが実行されるタイミングで wasavi からバックグラウンドへ非同期的に問い合わせが行われる。したがって、バックグラウンドから応答を得る処理と fFtT の次の文字が入力され実際にジャンプが行われる処理では、前者が先に行われないと破綻する。ものすごく長い行でものすごく高速にコマンドを打つと逆転が発生する、かもしれない
  • 今回作ったのは日本語の拡張、なわけだけど、漢字部分については中国語のピンインや注音符号、あるいはハングルについてはそのローマ字表記法を基にすれば、つまりどの言語でもローマ字で表記する方法さえ規定されていれば、それに従った展開が可能だ。まあどちらの言語も知らないのですぐに手をつけるわけではないが

unawares

別に頼んだわけでもないのだが、softpedia を初めとする、海外のいくつかのアプリ紹介サイト(日本で言うところの vector とか窓の杜みたいなサイト?)に wasavi が登録されていた。ほー。

しかしそれと関係あるのかないのかわからないのだけど、ここ数日、このブログにくる spam とか、怪しげな勧誘メールの量がめちゃんこ増えてるんですけど。仕事中に大量にメールするのやめてください。

vi and Japanese #3

漢字以外の文字を考える。漢字以外とはつまり、ひらがな・カタカナ、および一部の全角文字……にしようと思っていたが、せっかく漢字の読みについては Unicode のデータを参照するようにしたのだから、こちらもそうしよう。

ひらがな・カタカナについては、UnicodeData.txt を参照する。これらの読みのデータは入っていないが、文字名が “HIRAGANA LETTER KA” とかなのでそこを取り出す。ちょっと強引。

残りの文字も UnicodeData.txt から取り出すのだが、bmp 全体を対象にし、データベースの 5 番目のフィールド、Decomposition_Type と Decomposition_Mapping を参照する。Decomposition_Mapping の先頭の文字が Latin-1 の U+0020 から U+007F であれば、fFtT の対象にする。ちなみに先頭の文字をただ取り出すのではなく、Decomposition できなくなるまで再帰的に変換する必要がある。

生成するデータの構造は、bmp のコードポイント 2 バイトと、変換先文字のコードポイント 0x20 から 0x7f の 1 バイトで計 3 バイト。

というわけで生成させてみたところ、1574 文字分、4722Bytes のデータができた。あら小さいのね!

これによって生成したデータにより、例えば㍇とか①とかʣといった特殊な文字でも、それぞれ Latin-1 の文字 m、1、d を対応させることが可能になる。これはつまり、例えば一般的な日本語の文章でも、下記のように Latin-1 の文字を使って各文字に自由に fFtT できるようになるということだ。

a a m ! f w m m ! !
ああ㍇! ふわ㍉ ㍇!!


j a
k d g
m o h k k
j s s i m n m
y t m m d t w m t n i z k t h m o r w s i t
柚純㎟まだ終わ㍇てないぞ 今度は㎟後ろを向いて

たださらに微調整の余地はある。助詞としての「は」は w で飛べたほうが自然かも。濁点や半濁点付きの半角カナに対応していない。悪名高い円記号とバックスラッシュの問題。また、上の例の「後」のように、「うしろ」と読めるはずなのに u が定義されていない、Unicode の仕様自体の不備も気になる。もっとも、Unihan_Readings.txt 内の kJapaneseOn と kJapaneseKun は status:Provisional だそうなので文句を言うわけにもいかないのだが。

ちなみに変換表は

となっている。漢字のほうはそれなりにでかいので注意。

vi and Japanese #2

fFtT を日本語対応にするために辞書を作る。まず漢字。必要なデータは、コードポイントとそのコードポイントのローマ字読みの先頭のアルファベット。

もしかすると、コードポイントは不要かもしれない。Unihan_Readings.txt がサポートする U+3400 から U+FA2D(とりあえず BMP 以降は考えないことにする)の配列という形にすればインデックスでアクセスするだけでいい。50733 文字もあるけど……。しかし調べてみると、その中で音読み・訓読みが定義されているのは 13369 文字だけだった。となるとやはりコードポイントは必要か。

一方、読み先頭字はというと、アルファベット 26 文字分のビットフィールドということになる。つまり 4 バイトを消費するのだが、こちらもまた調べてみると実際に使われる文字はもう少し少ない。ローマ字なので例えば L とか Q とかはまあないのだ。具体的には

7647 K
6664 S
4033 T
3514 H
2188 M
1594 G
1545 Y
1538 N
1494 R
1328 A
1319 O
1294 B
1057 I
943 J
855 C
808 U
772 D
625 E
499 F
407 W
6 P

こんな感じで 21 字で済む。そうすると 3 バイト。うーんなんとか 2 バイトに詰められないかな……。まあ、とりあえず 3 バイトにするかな。そうすると 1 エントリあたり 5 バイトの、13369 字分ということで 65KBytes になる。けっこう小さくなるなー。

参照する際は、コードポイント順に並べてある前提で 2 分探索することになる。

vi and Japanese

vi コマンドに f、F、t、T がある。これらのキーを押すとさらに 1 文字入力を求められ、カーソル位置から最も近いその文字のところへカーソルがジャンプする。非常に良くできた機能。

なのだが、日本語との相性は非常に悪い。vim では 1 文字入力待ちの状態で IME をオンにして……ということ自体はできるのだが、それでもまだるっこしい。

これ、ひらがな・カタカナ・漢字についてローマ字の読み、Latin-1 に基本形を持つ全角文字のその基本形、などを内部的に持っておいて、それを利用できないか。つまり「日本語の文章」の先頭にカーソルがあるとき、[cci]fb[/cci] と押せば「文」のところにカーソルが飛んでくというイメージ。若干 migemo と考え方は似ている。

問題は、どうやってローマ字の読みを得るかだ。ネイティブアプリなら、IME が提供する再変換 API で読みを得られるだろう。または MeCab のようなものを呼び出してもいい。しかし wasavi では無理だ。そういう web サービスはいくつかないこともないが:

しかしコマンドを実行するごとにネットワークアクセスが発生するのはなかなかに富豪的すぎる。何とかローカルのデータでもてないか。しかもできるだけコンパクトに。

Unicode の仕様の中に、unihan というものがある。これは CJK Ideograph、いわゆる漢字についてのさまざまな情報を集めたものだ。その中に、Unicode に収められている漢字について訓読みと音読みを定義しているデータベース Unihan_Readings.txt がある。例えばこんな感じ:

㞮 kJapaneseKun DERU DASU
㞮 kJapaneseOn SHUTSU SUI
㡡 kJapaneseKun TOBARI KAYA
㡡 kJapaneseOn CHU JIU
一 kJapaneseKun HITOTSU HITOTABI HAJIME
一 kJapaneseOn ICHI ITSU

これを元にぎゅっとつめ、せいぜい数十~数百 KB 程度のデータにならないかな。

unifying similar routines

vi のコマンドには、x/X、p/P、のようにペアになっているものが少なくない。内部的にも同じような処理でほんの一部のパラメータだけが違う、といった感じになっている。ちょっと無駄なので、できるだけまとめるようにした。

 * * *

めっきり寒くなってきたので、コタツと Thinkpad X121e が再びフル回転することになる。Thinkpad に Thinkpad USB keyboard with trackpoint を付け、キーボードはコタツの中に入れるのである。

まったく死角がない!


この体勢は非常に快適なのだ。

 * * *

ところで X121e、購入から 1 年経過して保障が切れたあたりから、AC 電源に接続しているのに勝手に放電状態になったりすぐ戻ったりなんだか怪しい。なんなんだ、レノボタイマーか。

 * * *

普段 vim を使うときは gvim なのだが、cygwin の vim もけっこう使う。で、cygwin を全画面表示 + 背景透過などにするとなんだかなつかしの PC-98 的 DOS 環境な感じで嫌いじゃないです。うぉー今コードを書いてる! 書いてるぜー! って感じになる。まあ 98 持ってなかったけど。

うぉー今コードを書いてる! 書いてるぜー!

brushing up, input mode #5

input モードのショートカット ^T、^D をなんとかしよう。

これらの機能、実はあまり利用したことがない。とりあえず ^D の posix の仕様は以下の通り:

^D
^D は、行指向コマンド(/, ?, :, !)によって開始されたテキスト入力モードでは特別な意味を持たない。またこのコマンドは、ブロックモードの端末ではサポートされてなくてもよい。

カーソルが字下げ文字に続いていない、または字下げ文字、’0′ または ‘^’ に続いていない場合:

  1. カーソルが 1 桁目にある場合、^D は無視され何も起こらない
  2. そうではない場合、^D は何の意味も持たない<(^D そのものが挿入される)

最後に入力した文字が ‘0’ であった場合、カーソルは 1 桁目に移動しなければならない。
そうではなく最後に入力した文字が ‘^’ であった場合、カーソルは 1 桁目に移動し、加えて、次の入力行の自動字下げレベルは現在行の(もともとの)字下げ量をもたらした行から同様にもたらされなければならない。

そうではない場合、カーソルは shiftwidth 単位で前に戻される。

現在行: 変更されない。
現在桁: ^D の前に ‘^’ または ‘0’ があった場合は 1。そうでない場合は (column -1) -((column -2) % shiftwidth)。

という感じ。結局のところはインデントを制御するショートカットということだ。なお vi では ^D はカーソル行が /^\s+[^0]?/ な状態のときのみ効力を持つのだが、vim では拡張されいつでも実行可能になっている。

この中で、異質なのはもちろん ‘0’ や ‘^’ を前置した上で ^D すると特別扱いされるという点だ。これらの文字を打った直後に ^D を押すと、カレント行のインデントが削除される。0 と ^ の違いは、インデントの削除が一時的かどうかだ。[cci]:set ai[/cci] な状態で、前者はインデントを削除した行で enter を押した場合、次の行もインデントなしになる。つまり、カレント行が次の行のインデント量を算出する基準になる。一方後者の場合、カレント行は次の行のインデント量を算出する基準から外される。これは例えば、C ソースでラベルを書く場合とかに有用:


foo();
loop:
bar();

なんてコードだと、1 行目で改行した時点で自動的に 1 レベルのインデントが挿入されるが、そこで ^^D し、1 桁目から loop: と打つ。で、さらに改行すると、新しい行のインデントは 2 行目ではなく 1 行目から派生し、1 レベルインデントが生成される。ちなみにインデント量が補正された場合、’0′ や ‘^’ は自動的に削除される。

というわけで、そうなるように実装した。

さて、vi の input モードで定義されている特別な機能はこれで一通り実装したのだけど、実はもうひとつだけ残っている。input モードに入った直後に NUL、つまりコードポイント 0 の文字を入力すると、最後に input モードに入力した文字が自動的に再度入力され、かつ input モードも自動的に抜ける。

この機能はどうなんだ。まずキーボードから NUL って入力できていいのか? 一般的にコントロールコードは Ctrl + A とかで打つ。そうすると、A のコードポイントを 0x1f で論理積を取った値をコードポイントとした文字が打たれたことになる。そう考えるとコードポイント 0 は Ctrl + @ になる。しかし US 配列のキーボードの場合 @ は Shift + 2 で打つので、実際には Shift + Ctrl + 2 ということになるが、これだと制御キーのためのショートカットだとキーイベントハンドラで判別できない。javascript ではキーボードレイアウトを意識するのは非常に難しいのだ。

とりあえず、ctrl+space を特別扱いし、それが押されたら wasavi の中では NUL が押されたことにする。

というわけで、その機能も作った。

brushing up, input mode #4

いろいろ逡巡したが、vim と「だいたい」同じ動作をするようにする。たとえば入力開始位置は ^W の動作に影響を与える。

それから、入力開始位置を保持するのにテンポラリ的なマークを設定しているのだが(wasavi のマークは vim と違い、桁方向についてもテキストの編集による増減に追従するので流用できる)、それにより副作用的に gi コマンド、つまり最後の入力終了位置へジャンプする機能も実現可能になるので、付けた。これは割りと便利ですよね。

入力開始位置は、基本的には input モードの 1 セッション中は変化することはないが、backspace や ^W でどんどん前方へ削除していった場合、カーソル位置が入力開始位置より前に来てしまう。この overrun な状況は、以下の動作に影響を与える。

  1. R コマンドで overwrite モードに入っている場合。入力開始位置より前方にカーソルがある場合、
    • 元のテキストの上で backspace や ^W を押しても、カーソルは移動するものの削除は行われない
    • 元のテキストを上書きしたテキストの上で backspace や ^W を押した場合は、元のテキストが復活する

    wasavi では元のテキストの復活処理はまだ組み込んでいない。

  2. ^U の処理。^U は入力開始位置からカーソル位置までを削除する。入力開始位置より前方にカーソルがある場合、次の候補
    • カーソル行の行頭
    • [cci]:set ai[/cci] してある場合は、カーソル行の最初の非空白文字のある位置

    が選択され、いずれかのうち、カーソルより前方にありかつカーソルに最も近い位置が最終的に残る。この位置からカーソル位置までが削除される。

  3. 文字を入力した場合。入力開始位置はそこに更新される

overwrite モードでの overrun はそもそもそれが起こらないようにクリップするだけにとどめるかもしれない。ちゃんと作ろうとするとかなり大変だ(しかも大変な割にめったに使わない)。

 * * *

overwrite モードでの overrun 状態。入力開始位置より前方へのカーソルの移動は可能だが、編集しようとするとエラーになるようにした。