join to the world #5

ひとまず input モードでの自動改行が動くようにした。改行できない場合は、無理に改行せずに長いままにするようにした。

あとは overwrite モードでの整合性をとるのと、Unicode.org 謹製のテストパターンがあるのでこれを通すようにしよう。6311 パターンくらいある。それから、gq コマンドを実装する。

とりあえずこれで、一通り pure vi の機能を実装し終えたということになるのかな。だいたい 1 年かかった。週末に一気に書き上げたという伝説はともかく、Bill Joy さんはせいぜい数ヶ月で(しかも BSD 本体やら Pascal コンパイラと並行して)vi を書いたというのだからおそろしい(参考: Bill Joy’s greatest gift to man – the vi editor および vi – Wikipedia, the free encyclopedia)。

 * * *

とりあえずテストを通してみたところ、87.59% 通った(テストデータは基本的な実装に加えて UAX #14 の 8.2 Examples of Customization の Example 7、つまり数値文字列に対する拡張を施した上での状態を前提にしている。したがって基本的な実装のままでは 100% にはならない)。

しかしなんかしょうもないバグが残っているような気がしないでもないのでもう少し追ってみることにしよう。……うわーん、案の定。修正したら 95.5%。

残りは上記の Example 7 の件だろう。たぶん。そういうことにしてこの状態で進める。

 * * *

といいつつまだやっている。CR や LF などの処理を付け足したりして 99.5% まできた。残りの半分は、Contingent Break Opportunity (CB)、具体的には U+FFFC Replacement Character にまつわるテストケースなのだが……これが UAX #14 を見てもよくわからない。CB は LB1 で処理することになってたり、文脈に応じて適当に扱うことになっていたり、UAX #14 を通してどうするか明示されてるわけではない。うーん。U+FFFC を含んだテストケースでは、FFFC については LB1 のルールに従う、みたいな事が書いてあるのだが LB1 の定義にも U+FFFC の取り扱いは明示されていない。うーん。

まあいいか?

join to the world #4

順を追って考えてみる。

Lorem ipsum dolor sit amet,
consectetur adipisicing elit,
sed do eiusmod tempor
incididunt ut labore et
dolore magna aliqua.

というような、長い 1 行がある。次に [cci]:set tw=30[/cci] として、[cci]Aa^[[/cci] する。すると、”a” が打ち込まれる前に、行の先頭からカーソル位置までの折り返されている行全体が処理され、適切な位置に改行が挿入される。……ということは、今まで、自動的な改行挿入はカーソル位置から逆向きに走査すればいいと考えていたが、そうではなさそうだ。ループ処理が必要になりそうだし、行の先頭から正順に走査することになる。

  1. まず行の先頭から、”L” の幅を算出し、line break 情報を得、textwidth を超えてないかを判定する。超えてなければ、次は “Lo” で……というループを繰り返す。
  2. いずれ “Lorem ipsum doloer sit amet, con” あたりで textwidth を超える。取得した line break 情報のうち、最後の改行挿入可能ポイントで改行する(このとき、ポイントの左側の空白文字列を削除する処理が必要かもしれない)。今、ループ中で着目している位置は 2 行目の con の次の文字だ。これは挿入が行われる位置(5 行目の最終桁)と同じかを判定する。同じではなければ、1. へ戻る。ちなみに挿入が行われる位置の絶対的な行、桁は改行を挿入するごとに変化するので、それを考慮する必要がある

ということになる、と思う。

2. で、「最後の改行挿入可能ポイント」が存在しない場合、つまりその行に改行できるポイントがない場合はどうするか。

  • textwidth を満たす位置で強制的に改行する。改行禁止であっても
  • 改行しない

さて、どちらがいいのか。vim では formatoption で選択できる(たぶん)ようだが。

join to the world #3

UAX #14 を実装した。

UAX #14 が提示するサンプル実装は、基本的にはさまざまなスクリプトに対して最大公約数的な動作をする仕様だと思う(実際、最後の方にさまざまなカスタマイズを行う例とかが載っている)。が、一方で UAX #14 は Unicode のアップデートに従ってけっこう拡張されているようなので、とりあえずサンプル実装のまま組んだ。また、Unicode 関連の関数やクラスが増えてきたので、くくりだして独立したソースにした。

これを利用して、textwidth に応じて自動的に改行する処理を組み込む。

[cci]:set tw=10[/cci] とした上で

abc def ghi

と打つと、i を打った時点で自動的に改行され

abc def
ghi

となるわけだ。内部的には、i を打ってバッファが更新される直前に line break の情報を取得する。すると 4 文字目、および 8 文字目のスペースが INDIRECT_BRK とマークされる。これはつまりその文字の直後で改行してもよいということだ。したがって、line break 情報を後ろからサーチし、最初に見つけた改行可能マークで実際に改行すればよい。

ここで

  • 改行できない場合。たとえば

    abcdefghijk

    などと打った場合はどうなるのか?
  • Undo 情報はどのような構造になるべきなのか?

を明確にしておく必要がある。これは vim のソース(edit.c)を見るしかないかな。

join to the world #2

textwidth に値を設定すると、input モード時、ある文字 C を入力しようとしたときにその行(の先頭からカーソル位置までの部分文字列の表示上の幅に C の表示上の幅を足した値)が textwidth を超えるであろう場合、先んじてリフォーマット処理が行われ、その後に実際に C が入力される。リフォーマット処理は、カーソル位置から後方に改行挿入可能位置を走査し、最初に見つかった箇所で行を分割する。

このときとても面倒なのはつまり、改行挿入可能位置の走査というやつだ。Latin-1 だけで考えるならば、アルファベットの列の中では改行してはいけないとか、せいぜい数種の特定の約物の前では改行してはいけないとか、その程度のことに気をつけるだけでよかった。が、javascript で動く web ページ上のプログラムとして見るとどうしても Unicode のことを考えなければならない。

めんどくさいねぇ Unicode。ノムラススムばりに時空を超えて何度も我々の前に立ちはだかってくる。

で、Unicode の文脈で、文の中の改行挿入可能位置を算出する処理というのは UAX #14 にまとめられている。その仕様に沿って書けばいい。くらくらする長さの仕様だが。

join to the world

vim の textwidth オプション、および gq コマンドまわりに対応するものを実装したい(vi の wrapmargin はたぶん作らない)。まずは、とりあえずその前フリとして、J コマンド(と :join)と Unicode の関係を考える。おそらく gq の処理を行う過程で join を何度も呼び出すことになるので。

J コマンドは、カーソル行と次の行を連結する。このとき、以下のプロセスで連結が行われる:

  1. 連結される行の先行する空白文字を削除する
  2. 連結される行の現在の状態が空行ならば、以下の 3. ~ 5. の処理を飛ばす
  3. カーソル行の末尾が空白文字であるか、または、連結される行の最初の文字が ‘)’ であるならば、何の加工もせずに 2 行を連結する
  4. そうではなく、カーソル行の末尾が ‘.’ であるならば、2 行の間に 2 つの空白文字を挟んで連結する
  5. そうではない場合は、2 行の間に 1 つの空白文字を挟んで連結する

wasavi では、3. が ‘)}]’ のいずれか、4. が ‘.!?’ のいずれか……に拡張してあるほかは、この通りの動作をする。これ、Unicode のさまざまな文字について考えるとどうなんですかね。Unicode な文字で考えると 3. はむしろ一般カテゴリ Pe (a closing punctuation mark) に属する文字、というのがふさわしいだろう。Pe に属する文字は Unicode 6.2.0 で 71 字。

一方 4. は、つまり文の終わりを示す約物ということになる。これは Unicode でいうと LineBreak.txt で STerm (Sentence Terminator) と定義されている文字に対応する。こちらは 83文字。

それから、空白を挟んで連結するかどうかは、言語(というより書記法的なもの?)によると思う。例えば漢字とその亜種(ひらがな、カタカナ、あるいはボポモフォみたいなの)どうしが隣り合う場合、空白は不要だ。おそらく隣り合う両方ではなく、片方だけが漢字とその亜種だったとしても不要だ。ところで「漢字とその亜種」っていう種別は Unicode にはない気がする。East Asian Scripts としてまとめられてるもの(漢字、ひらがな、カタカナ、注音符号、および全角・半角形あたり)を全部突っ込めばいいのかな。

 * * *

上記の修正を行った。漢字部分は、

'\u3100-\u312F', // Bopomofo
'\u31A0-\u31BF', // Bopomofo Extended
'\u4E00-\u9FCF', // CJK Unified Ideographs (Han)
'\u3400-\u4DBF', // CJK Extension-A
//'\u20000-\u2A6DF', // CJK Extension-B
//'\u2A700-\u2B73F', // CJK Extension-C
//'\u2B740-\u2B81F', // CJK Extension-D
'\uF900-\uFAFF', // CJK Compatibility Ideographs
//'\u2F800-\u2FA1F', // CJK Compatibility Ideographs Supplement
'\u2F00-\u2FDF', // CJK Radicals / KangXi Radicals
'\u2E80-\u2EFF', // CJK Radicals Supplement
'\u31C0-\u31EF', // CJK Strokes
'\u2FF0-\u2FFF', // Ideographic Description Characters

/* Korean */
'\u1100-\u11FF', // Hangul Jamo
'\uA960-\uA97F', // Hangul Jamo Extended-A
'\uD7B0-\uD7FF', // Hangul Jamo Extended-B
'\u3130-\u318F', // Hangul Compatibility Jamo
'\uFFA0-\uFFDC', // Halfwidth Jamo
'\uAC00-\uD7AF', // Hangul Syllables

/* Japanese */
'\u3040-\u309F', // Hiragana
'\u30A0-\u30FF', // Katakana
'\u31F0-\u31FF', // Katakana Phonetic Extensions
//'\u1B000-\u1B0FF', // Kana Supplement
'\uFF65-\uFF9F', // Halfwidth Katakana
'\u3190-\u319F', // Kanbun

/* Lisu */
//'\uA4D0-\uA4FF', // Lisu

/* Miao */
//'\u16F00-\u16F9F', // Miao

/* Yi */
'\uA000-\uA48F', // Yi Syllables
'\uA490-\uA4CF', // Yi Radicals

という感じ。LisuMiao は、Latin 文字ベースの中国語? という不思議なソレなのでよくわからないがとりあえず対象外。ところでハングルはどうなんだろう。単語間にスペース入れて記述するような気がするけど。

次は textwidth。

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 だそうなので文句を言うわけにもいかないのだが。

ちなみに変換表は

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