reborn: akahukuplus

この一ヶ月特に何も作っていなかったというのはつまり、引っ越ししたのでそもそもネットから遮断されていたということなのだが、その直前までは何をしていたのかといえば、wasavi に関してはほとんどコードをいじっていない。

いじっていたのは赤福プラスとよばれるブラウザ用拡張なのである。

かつてふたば☆ちゃんねる向けに IE のコンテキストメニューを拡張する赤福というツールを作った。赤福プラスはその Opera 版というわけだ。Opera 版は当時の Opera が搭載したばかりのユーザースクリプトの仕組に乗っかって動作し、コンテキストメニューにとどまらない様々な機能を持っていた。

その後、Chrome でも動くようにしたり、その過程でブラウザ拡張として構成しなおしたりしたのだが、さすがに建て増し続きだったし、また IE6 あたりでも動作するようにしていたので、例えば Selector API とか XPath とか続々とブラウザに搭載されていく新機能を使うわけにも行かず、中味はなかなかぐぬぬなコードになっていた。

今回、そんなわけで一から作り直してみようということである。残念ながら IE はサポート外となる。最近の IE 自身は、新しい赤福プラスを動作させるポテンシャルは十分あるのだけど、スクリプトベースの拡張の仕組みがないので。一方で、Presto Opera はサポートする。サポートするも何も、Opera 12 で開発してるので、未だにファーストクラスのブラウザだったりする。この他には Chrome と Blink Opera でテストする。気が向けば Firefox 版も作るかもしれない(気が向かないのは、Firefox には別の人が作った赤福がすでにあるからである)。

技術的な面に目を向けてみると、フロントエンドとバックエンドで全く違ういくつかのトピックがある。

まずフロントエンドからだが、赤福プラスというのはつまりふたばの画像掲示板の html を動的にちょっと書き換えるツールだ。ここではふたばの画像掲示板に DOMContentLoaded でスクリプトを引っ掛けて、ページの DOM をごにょごにょするのがフロントエンドの役割だ。

しかし、ふたばの画像掲示板が吐く html というのは、まああんまりそういう再利用はしやすくないのである。そこで、おそらく他のふたば用ツールはほとんどやっていないであろう手法を取る。ページを読み込んだらそれをスクレイピングし、一旦中間的な xml を構築する。さらにそれを XSLT プロセッサに与えて html に変換するのである。続きのレスを読んだりダイアログを出したりするときも必要なら XSL による変換が入るので、けっこう XSLT が大回転することになる。

これによってふたばの元々の html に振り回されるのはスクレイピング処理部だけになり、その他の部分は自分で完全に制御できる xml だけを見ればよくなる。また、ページ全体を制御できるのでよりダイナミックにページを構成することが可能になる。たとえばスティッキーなサムネイルとかバナー、固定ヘッダなどをなんでも導入できるようになる。

次にバックエンド。バックエンドとはつまり拡張の表に出てこない裏方の部分だが、実はこれは wasavi とちょっと関連する。wasavi もバックエンドを持っている。wasavi もまたいくつかのブラウザで動くため、各ブラウザの拡張の仕組みをある程度抽象化させた部分をもっているのだが、残念ながら完全ではなかった。また、wasavi というアプリケーションからも独立してるわけでもなかった。

これがずっと気にかかっていたので、今回を機にブラウザ拡張の抽象化層をきっちり作り、またアプリケーションからも完全に独立させたい。その抽象化層をベースにして赤福プラスのバックエンドを構築し、さらに wasavi にもバックポートする。なかなか遠大・壮大な計画なのである。

バックエンドに関してはもうひとつやることがある。オンラインストレージをアプリケーションから利用する際の承認において、dropbox はなんかなかなか OAuth 2.0 をサポートしなかった。ので、wasavi も jsOAuth というちょっと癖のあるライブラリを使って OAuth 1.0 で繋ぐしかなかったのである。しかし最近なのか結構前なのか知らないけど、いつの間にか OAuth 2.0 がサポートされていたのでそのへんの仕組みをごっそり書き直したい。当然この辺りも wasavi にバックポートされることになる。

赤福プラスにおいては、オンラインストレージは画像の保存・スレッドの保存時に利用することになる。画像のサムネイルから保存ボタンを押すとオンラインストレージの API を通して保存を行うのである。オンラインストレージ側の機能でローカルや複数デバイス間で同期することができる。

最近の状況はこんな感じです。

hang on search?

issue #20 で、検索するとパターンによって無限ループになってタブを閉じるしかなくなるというのがある。特に [cci]|[/cci] とかが危険らしい。なるほどなかなかそんな雰囲気がしないでもない。

これは由々しき問題だ……ということですぐ直そうとしてみたのだけど、再現しない。ということでどういうパターンだと発生するのか聞いているところ。

home updated

wasavi のホームを更新してみたりした。といっても開発版のエクステンションは github に置くようにしたし、フォーラムのようなものは全然利用されないので除去した、つまり単に readme.md を表示するだけの特に存在理由のないページになっている。

まあきっといろいろと、そのうちに拡充していくんじゃないかな!(希望的観測)

visual in vi #8

vim 同様、V を押すことで行単位の選択もできるようにした。モード名は bound_line。また、bound モード中に bound_line モードに入ると(あるいは逆に bound_line から bound でも)、現在の選択範囲を活かしたまま文字単位・行単位が切り替わるようにした。これも vim と同様。

ところで vi では、空行以外の行においてカーソルを改行の上に置くことはできない。vim もこれは同じだが、ただし visual 系モードに入るとその制限が取り払われる。これはこれでまあそういうものなのかな、ということで一応付けてみたのだが……これ、もしかしてなくてもよくなくなくない? とりあえずオミットしておこう。クレームが付いたらつける。

それにしてもかなり wasavi.js に手が入った。とりあえずの機能は実装し終えたのでモードを切り替えてテスト三昧の毎日になるのである。

visual in vi #7

vim の visual モード中に実行できる編集コマンドのうち、大文字のものはほぼ、操作対象が行単位になる。行単位ということは、選択範囲の端点の桁位置は使用されないということだ。行位置だけが意味を持つ。

行単位の操作においては、特に削除処理について文字単位でのそれとの違いを意識する必要がある。具体的には行にマークが含まれる場合である。マークを含んだ行を文字単位で全削除(つまり [cci]^.*$[/cci] を削除)したとしても、マークは空行の先頭位置に収斂され、削除されない。一方、行単位での削除(つまり [cci]^.*\n[/cci])の場合はマークごと削除される。これは決定的な違いだ。ちなみに削除に対応する undo 情報にはマークも含まれるので、undo すれば行全体とともにマークも復活する。

ところで、最後に使用した選択範囲はマーク [cci]<[/cci] と [cci]>[/cci] で参照することができる。では、visual モードを経由して行単位での削除を行った場合、それらのマークはどうなるのか? というと vim では削除後もそれらのマークは生き残っているのである。もちろん本来の選択範囲は削除されているので、全然関係ない別の行を指していることになる。これはなんというかバグなのか仕様なのか判別しづらい。

wasavi では行単位で削除を行ったあとは [cci]<[/cci] と [cci]>[/cci] マークは無効になるようにした。

visual in vi #6

gq
command モードでの [cci]gq[/cci] はオペレータとして扱われ、後続するモーションによって移動したカーソルとの間の領域に「textwidth の幅に収まるよう適切に文字を充填したり適切に改行を挿入する」という処理を行う。ここで、カウントを指定し、かつモーションが [cci]_[/cci] またはそのエイリアス(つまり [cci]gqgq[/cci] または [cci]gqq[/cci])だった場合、カウントは [cci]gq[/cci] 自体に作用し、相当する行数が再フォーマットの対象になる。

何を言いたいのかというと command モードでは [cci]3gqq[/cci] とか指定した場合、カーソル以下の 3 行を再フォーマットしろ、という意味になる。カウント := 縦方向の行数。

vim では、これが visual モードになると、カウントは textwidth オプションを一時的に上書きする。カウントの意味合いが垂直方向から水平方向に完全に切り替わる。カウント := 横方向の文字数。

これ、良いインターフェースか? と問われればもちろん全然良くないんだけどどうしたものかなー…。

gv
vim では、command モードでの [cci]gv[/cci] は、最後に使用した選択領域を再びアクティブにしつつ visual モードへ遷移する。ということで wasavi にも実装したのだけど、では visual モード中に [cci]gv[/cci] したらどうなるんでしたっけ? と思いつつやってみたら、やはり command モードでのそれと同じ動作をした。つまり最後の選択領域の端点と、アクティブな端点は個別に管理しているようだ。

それに倣い、wasavi でも bound モードでのアクティブな選択領域の端点はプライベートなマークを使用するようにした。

visual in vi #5

選択範囲の指定方法を最適化したい。

今の実装では、wasavi のバッファは DOM そのものであり、バッファ内の行は div 要素であり、選択範囲は span 要素でくくられた領域となる。その状況下で選択範囲が拡張・縮小された場合、当然 span の境界も移動しなければならないのだが、とりあえず一番簡単なのは、再設定に先駆けていったん span 要素を脱皮させ、それから新しい選択範囲をくくりだすことだ。

これはもちろん賢いやり方ではない。選択範囲が複数行で構成されている場合、行ごとに span のくくりだしというループを経なければならないのだ。もっと最適化して、拡張・縮小された領域だけを操作するようにしないといけない。

ということで、そうした。この最適化により、bound モード中のモーション全体が恩恵を受けるのだけど、特に [cci]/[/cci] や [cci]?[/cci] でインクリメンタルサーチを行った際に顕著かもしれない。

visual in vi #4

スクロール系のコマンドを実装した。実装したというか、command モードのマッピングに移譲するだけ。

次にオペレーションから独立している編集コマンドを実装する。vim では、これに該当するのは [cci]: r s C S R x D X Y p P J U u ^] I A[/cci] であるので、wasavi でもだいたいそれに準拠する形になる。

ただし、とりあえずとして、矩形選択はめんどくさそーなので実装しない。またタグ検索は全然 wasavi には実装していない。つまり [cci]^] I A[/cci] は除外する。

とりあえず簡単なところから埋めていきたい。まず [cci]:[/cci]。これは他の編集コマンドとはちょっと異なり、bound だからといって直接的な影響は受けない。数日前、command モードにおいてカウントを前置した状態で [cci]:[/cci] を押すと自動的に [cci].,.+10[/cci] みたいなアドレスが ex コマンドラインに入力されるようなナイスな機能を付けたのだが(といっても例によって vim のパクリである)、 それと同様、bound モードから line-input モードに遷移した場合は自動的に初期値が [cci]’<,'>[/cci] となるようにした。つまり選択中の範囲が対象になる。

ただし基本的に、ex コマンドは操作する要素の単位は「行」である。文字単位で選択し、そのマークを指定したからといって文字単位で編集できるわけではない。[cci]foobarbazxyz[/cci] の bar を選択した状態で [cci]’<,'>ya[/cci] すると、無名レジスタに保存されるのは [cci]foobarbazxyz^J[/cci] と行全体である。

visual in vi #3

bound モードで range symbol(vim でいうところの text object)を指定できるようにした。

その辺を実装してみて初めて気づいたのだけど、vim では visual モードで連続して aw aw aw と押していくとそれに従って選択範囲が拡張していくのだね。aw ごとに選択範囲が再設定されるわけではない。というわけでそういうふうに動くようにした。

次は operation、つまり y c d < > の眷属を実装する。

* * *

実装した。