entering code point #2

コードポイントの入力モードに入った場合にそれを抜けるには、最大入力文字数に達するかコードポイントの構成文字以外を入力するか、いずれかを満たす必要がある、というのは前の記事の通り。

しかしこれはちょっと不便ではないか。コードポイントの入力を途中でキャンセルしたいとか、明示的に確定したい+余計な文字は打ちたくないといった要求に応えることができない。前者は esc の押下、後者は enter の押下あたりが自然だと思う。しかし意外なことに vim ではどちらもそういう動作をしない。

ということで、そういう風に動作するようにした。

entering code point

というわけで、wasavi.js を分割した。

  • extension_wrapper.js
  • classes.js
  • classes_ex.js
  • classes_search.js
  • classes_subst.js
  • classes_ui.js
  • classes_undo.js
  • init.js
  • utils.js
  • wasavi.js

の複数のファイルで成り立つようにした。

 * * *

挿入モードでテキストを入力する際、vi には以下の ctrl 併用のショートカットが定義されている。また、vim では [cci]:help ins-special-keys[/cci] で参照できるが、以下のリスト以外にも(それはもう膨大に)ショートカットが用意されている。

  • ^D: shift
  • ^H: カーソル左の 1 文字削除
  • ^J, ^M: 改行
  • ^T: unshift
  • ^U: 入力のやり直し
  • ^V: リテラルの入力
  • ^W: カーソル左の 1 単語削除

  • wasavi ではまだ完全に実装できていない。この中で、^V について考えてみる。

    ^V は後続する文字の特別な意味を打ち消し、単なる文字としてバッファに挿入する。ここまでは、wasavi でも実装済みなのだが vim ではこの機能が更に拡張されている(:help i_CTRL-V_digit)。すなわち、

    • ^V [0-9]{1,3}
      10進でバイトを入力
    • ^V [oO] [0-7]{1,3}
      8進でバイトを入力
    • ^V [xX] [0-9a-f]{1,2}
      16進でバイトを入力
    • ^V u [0-9a-f]{1,4}
      16進で Unicode コードポイントを入力
    • ^V U [0-9a-f]{1,8}
      16進で Unicode コードポイントを入力
    • ^V (上記以外の 1 文字)
      入力した 1 文字そのものを入力

    という感じ。これを wasavi に持って来たい。

    まず javascript アプリケーションなので、取り扱う文字は UTF-16 に固定される。したがってバイトの入力であっても Unicode のコードポイントとして扱う必要がある。つまり x/u/U の違いは最大入力文字数だけになる。

    ^V の次に [0-9oOxXuU] を入力しコードポイント入力モードに入った場合、それを完了させる方法は 2 つある。まずそれぞれのモードの最大入力文字数に達した時点で、自動的に完了する。次にそれぞれのモードが受け付けるコードポイントの構成文字以外の文字 c を入力すると、その時点で蓄えられたコードポイント文字列から文字を生成し、それがバッファに入力される(コードポイント文字列が空の場合は何も入力されない)。入力される場合は、abbreviation の展開処理を迂回する。その直後 c が入力される。こちらは abbreviation の展開処理を経由する。

    なお vim では U プリフィクスを使用した場合、最大 8 桁の 16進数(ただしヘルプでは最大値は 0x7fffffff とのことだ)を入力できるそうだが、でも Unicode って最大 U+10FFFF だよね。クリップしてエラーにしたほうがいいのかな?

    またもちろん、U+10000 以上のコードポイントを入力した場合は、サロゲートペアに分割して 2 文字を入力する必要がある。

    だいたいこんな感じの仕様でいいかな!

ant fix

build.xml を書くのがどうも苦手だ。

たとえば trunk/frontend/ の下の 10 個程度の javascript ソースを minify して、指定のディレクトリに置きたい。そういうタスクはないので、自分で書くか、既存の minifier を exec することになる。

とりあえず簡単なほう、つまり既存の minifier を呼び出す方で行ってみるのだけど、1 つの js ソースだけならともかく複数ある場合はどうしたら? 仮に jsminify みたいなタスクがあって、それが子要素として fileset を受け付けるならば、こんなイメージになるはず:





しかしそんなタスクはない。なので、直接 minifer を呼ぶのではなく、適当な php スクリプトを呼び出すようにして、その中でディレクトリを読み、見つけた js ソースを片っ端から minifier にかけるようにしている。

こんな調子で ant だけではできなさそうな箇所は全部 php スクリプトを挟んでいるのが今の状況なのだった。いやまあ php はいっぱしのグルー言語なのだから、そういう使い方は間違ってないといえば間違ってないと思うけど。なんかとても中途半端だ。

と思ったらこういうのがあるんだね。ant 上で foreach 的なことができるのか。あらやだ素敵!

Tsure-dure

徒然と何点か。

^L の処理を書き直した。vi や vim では、これは画面全体の再描画を行う。特に vim なんかでシンタックスハイライトさせている場合に、ときどき間違った色付けが間違って表示されることがある(たぶん、解析を見えている範囲の近辺部分でのみ行うからだと思う)。そんなときに ^L を押すとだいたい直る。直らないときもある。

一方 html ページに構築される wasavi において、再描画って? という話になる。そもそもそんな機能、不要なのだ。しかしそれはそれでもったいないので、^L を押すと 0.5 秒の間、wasavi を隠すことにした。つまり wasavi の下にある textarea/input をちょっとだけ確認できる。それの何が便利なのかは、よく分からない。

 * * *

入門 vi のテストをちょっとだけ(2 テストだけ)書いてみた。2 章のものなので、基本的なモーションというレベルだ。この辺は既存のテストと丸被りしているのであまりする意味はないかもしれない。

 * * *

:edit コマンドはスタンドアロンモードでのみ有効にしていたのだが、textarea を拡張した場合でも、引数なしで実行する(つまり、それまでの編集を全て捨て、textarea の元の内容で編集をやり直す)ことはできるようにした。

 * * *

ところで iframe 内で実行される wasavi の本体は wasavi.js に全部入っている(正確には agent.js でも使用する部分は extension_wrapper.js に分離してある)。そうすると当然ながら、wasavi.js が超巨大になっている。現在 12751 行ある。これはよくない。github でソースを見るときもブラウザがほとんど固まってしまうし、pull request してくれる人(がいるのか知らないが)には弄りにくいだろうし、全部込みの弊害で割と変数をフリーダムに参照しているのもよくない傾向だ。とあるところで

an insanely complicated 11K line JS file

などと呆れられているが、「そのとおりでございます」と言う他ない。

これを上手く分割したい。

Lerning the vi Editor, 6th edition

O’Reilly に表題のようなタイトルがある。日本語版は「入門 vi」だ。一丁前のブログぽく広告なんかを出してみると、

こんな感じ。ちなみにこの本は持っていて決して損はない、とても分かりやすいいい本だ。

なんでいきなりステマかというと、この本の中に記述されている vi/ex コマンドの実例をテストケースとして起こして、だいたい全てのテストに通ることを保証したい。つまり巷の vi 本が、そのまま wasavi のチュートリアルとしても通用するようにしたい。とそういうわけです。

pulling strongly

wasavi.js、agent.js、extension_wrapper_js、background.js を strict mode にした。

と言ってもそんなに弄る必要もなかったのだけど、arguments.callee を追い出す必要があった。そもそも strict モードは、コードを堅牢にしたりセキュアにしたりするためのものだ。しかし arguments.callee の参照禁止という点では、副作用的に jit コンパイルがより深いレベルで行われるとかで、速くなる(可能性がある)、そうな。へー。

 * * *

wasavi 0.4.207 をそれぞれのブラウザのエクステンション向けに公開した。Chrome が即時、Opera がだいたい 1 日なのは今までの通りとして、Firefox がとりあえずまず validation が滞りなく進み(やっと)、Full Review 待ちになった。ここからだいたい 10 日かかると見ていたら、3 日くらいでレビューが通った。なんだー早いなー。

それはそれとして、レビュー結果で、ソース中のとあるコメントに対して

This is not even remotely true.

つまり「ぜんぜん間違ってるんですけど?」と一言あったのだけど、そのコメントは複数の文からなるものなのだ。えーとどの部分が間違ってるのか書いてほしい……。レビュー結果のメールに返答して返事が返ってくるものなのかな? なんか機械的に送られてきてる気がするけど。

record key strokes #3

いろいろと修正。keyManager や mapManager を弄るのは、wasavi の動作が根本的にぶっ壊れる可能性があるので怖い。ちなみに「なんとか Manager」と命名されたクラスは悪い設計の兆候だという意見もあるらしい。うむむ。

前の記事の通り、@ コマンドや :@ コマンドで実行する文字列内で特殊なキーを示すためには私用領域の文字 U+e000 をヘッダにする。矢印キーなら “\ue000<down>” などといった感じだ。q コマンドで記録したストロークに特殊なキーが含まれていたとして、それを “ap とかすると普通に U+e000 の文字も貼り付けられる。どういう文字がレンダリングされるかは環境に依存する。

q コマンドに頼らない場合、適当な行にキーストロークを打ち込んでそれを yy する必要があるが、挿入モードでの ^V で任意のコードポイントを入力する方法は wasavi にはまだない。将来的にはできるようにするつもりだが、とりあえずあとまわし。

:map コマンドでマップ前とマップ後のストロークをそれぞれ指定する際は、U+e000 を明示的に打ち込む必要はない。<~> の文字列には自動的に U+e000 が前置される。これを避け、純粋に文字列としての <~> を指定するには、”<” の前に ^V そのものを前置する。つまり ^V^V と入力してから <~> を打つ。ストロークに空白文字を含める場合にも ^V を前置する必要がある。この辺は素の vi、あるいは vim でもだいたい同じだ。

 * * *

Chrome Web Store でのバグレポート、および Opera でのフィードバックで指摘されたのだが、”array[i]” とか打つと wasavi 内のとあるループ処理が終わらなくなってハングする。閉じ括弧に対応する開き括弧を点滅させる処理と挿入モードの下処理がかち合ってるために起こるバグだ。これは手元のソースではすでに修正してある。公開版では、とりあえず [cci]:set noshowmatch[/cci] することで回避できる。

Chrome Web Store でのバグレポは(たぶん)ニュージーランドの方からだったが、Opera 版のそれは日本の方からのようだ。で、その方のブログのエントリを見るとバグレポートを出すのに少なくない心の中の逡巡があるようなのだな。

これは日本人らしい奥ゆかしさの発露なのかもしれないが、バグレポートはまずそれが行われないことにはこちらに届かないわけなので、特に難しいことを考えず気軽にしてほしいと思った。「このエクステンションはゴミだな! 作った奴は死んだほうがいい! てか死ね!」とか書かれても特段なんとも思わないくらいの鈍感力は備えているので心配ない。

ちなみに各エクステンションのリポジトリにあるフィードバック機構はそんなにちょくちょく見てるわけじゃないので、もしアカウントを持っているなら github の issue を作ってもらえるか、ここのフォーラムに書き込んでもらうのがいちばん手っ取り早いです。

record key strokes #2

キーストロークの記録は、:map と間接的な関係がある。両者とも、キーストロークを文字列化したものを扱う。さてキーの中には、一般的なコードポイントに対応しないものがある。例えば Page Up とか F1 とかね。そういうキーをどう文字列化するか? というところが現在の wasavi の実装ではまだ不完全なのだ。

vim では、特殊なキーは CSI (Control Sequence Introducer, 0x9B) と呼ばれる特別なバイトを前置して区別している、ようだ。wasavi でも同じような形式にしよう。私用領域の U+e000 を 使用する。たとえば delete キーに対応するキーストロークは、文字列化すると “\ue000<delete>” となる。

:map では同じような、しかし逆の変換を実装することになる。:map の rhs で “<delete>” と書いたら、内部的には “\ue000<delete>” として扱わないといけないのだ。

record key strokes

@ コマンド、:@ コマンドはすでに作ってある。これらは、指定のレジスタの内容をそれぞれ vi コマンド、ex コマンドとして実行する。
ではコマンド列をどうやってレジスタに突っ込むかというと、素の vi では適当な行にコマンド列を打ち込んで yy することになる。

一方 vim では、q{register} コマンドが新設されている。これはエディタの状態を「キーストローク記録中」にする。この状態のキー入力は記録される。この状態を抜けるには、単に q を押す。すると記録されたキーストロークが {register} に保存される。この機能はつまり、インタラクティブにキーストロークを記録するものだ。

というわけでこれを実装する。

Opera and DOM3 Composition Events

こんなことを書いてそろそろ 1 年経つのだけど:

DOM3 Composition events、いつのまにやら Chrome(と、おそらく Safari)、IE9、Fx9 と実装済みのブラウザが揃っている 2011 年末。

さて Opera は何年後くらいに実装するの?

# そしてまさかの実装されない、というオチ

まさか 1 年たってもまったく状況が変わってないとは思わなかった。強いて変わった点といえば、Opera は 12.50 で DOM3 Events に準拠するようになるらしいということ。ただし開発版 Opera である Opera Next の最新ビルドでも、Composition Events はまったく実装されていない。さすがに最終的に「実装されない、というオチ」はないと思うけど。ない……よね?

ちなみに、Composition Events をサポートしていないとしても、たとえば google でやってるように、思い切りタイマーをぶんまわして textarea#value をポーリングすれば確かに同じようなことはできる。できるのだけど、ただ 1 つだけ問題がある。IME を通した入力サイクルを考えてみると、

  • 仮入力
  • 変換して候補を選択
  • enter を押して確定

の繰り返しだ。しかし実際は、確定として enter を押すことはそれほど多くない。(少なくとも MS-IME は)変換後の候補選択状態で、選択のための特定のキー以外のキーを押せば、暗黙的に確定が行われて仮入力状態に移行する。この暗黙的な確定が発生した正確なタイミングをタイマー方式では認識できない。

そういうわけで、wasavi には Composition Events がないブラウザのためのフォールバック機構は入れていない。つまり今のところ Opera で IME を通した入力は正しく動作しない。どうしたものかなあ……。