completion

: で ex コマンドの入力モードに入るが、この時にさまざまな補完機能を作るのを後回しにしていた。それを作る。

補完はいくつかのタイプに分けられる。

  • ex コマンド名の補完
  • file, edit, write, read コマンドの引数としてのファイル名の補完
  • set 時のオプション名の補完

これらの区別は、入力済みの ex コマンド文字列とカーソルの位置がそれぞれの形式にマッチするかということになるのだけど、ex コマンドは | で連結できる上、コマンドによっては | がその効力を失うものもある。その辺の判定もちゃんとやる必要がある。

また、ファイル名の補完というのはたとえば dropbox をバックエンドにするとして、バックグラウンド側に処理を投げ、dropbox の API を呼んで指定のパスのファイル一覧を得て…みたいなことをする必要がある。このへんの動きは非同期になる。

Chromebook

wasaviについての評価とか、要望とか、バグ報告というものは

  • github の issues
  • Chrome Web Store
  • Opera Addons
  • Mozilla Addons
  • ここのサーバのフォーラム
  • メール

辺りの窓口がある。それぞれぽつぽつとコンタクトがある。

その内のバグ報告に y/d/c 辺りのオペレーションが . コマンドの繰り返し対象にならない、というのがあって直したことがある。といってもそのバグ自体は別によくて、興味深いのは、報告してくれた方が Chromebook のユーザーのようであることなのだ。

むう… Chromebook …聞いたことがある……。

聞いたことはあるが、全体像がよくわからない代物ではある。そもそも日本国内では発売されていない。また Chrome OS は基本的にブラウザしか動かせず、すべてのアプリケーションは html と javascript(と flash と NaCL?)で作られるとかなんとか。ベースは Ubuntu らしいけど、ターミナルとかも開けないのかな? ぜんぜんわからないことばかりだ。

わからないが、本当にブラウザしか動かないのだとしたら、テキストエディタを使いたいけどメモ帳的なそれは使いにくいよー! vi とかemacs とか使いたいよー! といった向きには確かに wasavi は向いているかもしれない(そこで vi 云々を発想する人はそもそも Chrome OS の対象外のような気もするが)。

content-editable #2

次に書き込む場合の問題を考える。

wasavi からの書き込みがリクエストされた時点で、対象の要素が input か textarea であれば、value 属性を通してテキストを書き込むだけでいい。しかしそれ以外の要素ならば、DOM のメソッドを使ってツリー構造を構築しないといけない。

input/textarea 以外の要素に対して wasavi が起動したならば、wasavi 側もそれを認識して、出力は改行で区切られた文字列の配列の形式になっている。ツリーがどういう構造であるべきかはサイトに依存するような気もするけど、とりあえずそれを単に取り出して p 要素の内容にしていくことにする。

content-editable

wasavi はアクティブな要素が textarea、または特定の type を持った input 要素であるときに起動させられるわけだが、これを contenteditable 属性が適切な値にセットされ、内容を編集できる状態の要素にも拡大したい。

最近はそういう要素がけっこう使われているのである。たとえば Twitter で書き込む時の textarea っぽいアレは contenteditable=”true” にした div 要素だ。

編集可能要素を wasavi の対象にする場合、読み書きの際にちょっとした問題が出てくる。textarea/input の value はプレーンなテキストだが、編集可能要素はその内容の形式がプレーンテキストとは限らない。というより、ほとんどの場合 Node なのだ。

まず読み込みのことを考える。編集可能要素が Node であるというのはつまり Node のツリー構造であり、子孫要素を持つことができて、それらがブロック要素だったりインライン要素だったりする。それを踏まえた上でプレーンテキストに変換しないといけない。textContent 属性はそれらをまったく考慮しないのだ。

 * * *

というわけで、その辺りを考慮しつつプレーンテキストに変換し、wasavi へ送るようにした。このあたりを参考にした(が、コードはかなり違うものなっている)。
twitter
次は書き込みのほう。

hotkeys

wasavi を起動するためのキーストロークを監視するために、各ページに小さなスクリプトを走らせ、そこであろうことか Node とか HTMLTextAreaElementとか HTMLInputElementとかの prototype が持つ addEventListener() と removeEventListener() をフックしている。これは、wasavi を起動するためのキーストロークを完全にページ上のスクリプトから隠すためだ。なかなかアクロバットである。

しかしこれは、特にキーボード周りのエクステンションを wasavi と同時に動かした場合にそれぞれが正しく動くのか、微妙だ。もしエクステンションシステムがもっと優れたショートカットキーの仕組みを提供してくれていれば、そっちを使ったほうが絶対にいい。

Firefox の Add-on SDK には hotkeys モジュールがある。エクステンションのバックグラウンドでストロークとそのハンドラを登録する。ハンドラ内で各タブ上のエージェントにメッセージを投げてやればいい。

Chrome には Commands API がある。これが、ちょっとまだよくわからない。コマンドは manifest に登録するのである。ふーん、え? じゃスクリプトで動的に再定義はできないの? しかしドキュメントは何も答えてくれない……。こちらも基本的な仕組みはバックグラウンドでハンドラを登録するという形になる。

Opera にはない。また、Opera 15 も Commands API はまだサポートしていない。

というわけでとりあえず Firefox の場合はエージェント上でキーストロークを監視するのではなく、hotkeys モジュールを使用するようにした。その副作用として、今まではモディファイアとして shift と ctrl だけを認識するようにしていたのが、alt/meta/accel キーも使用できるようになっている。それぞれ wasavi の設定ページでは、a/m/x と記述する。たとえば alt+meta+p なら <a-m-p> と書く。

* * *

ははあ、わかった。Commands API では、スクリプトから動的にキーストロークを再定義することはできない。その代わり、Chrome の拡張機能ページの最下部に「キーボード ショートカット」というリンクがあり、対話的にストロークを変更できる。

なるほどねー!

しかし Commands API を通したキーストロークはかなり制限されているようだ。どうも

  • モディファイアが最低 1 種必須。ここで言うモディファイアとは Ctrl または Alt。もしかしたらキーボードによっては Meta や Command も有効かもしれない。
  • モディファイアではないキーは、0 ~ 9 および A ~ Z のみ。

というような感じらしい。

つまり wasavi を起動させるための Ctrl+Enter とかは定義できない。……だめじゃん。

referencing contents inside extension

wasavi.js は以前、大きくなりすぎたので分割したことがあった。一方でバックグラウンドで動いていろいろな縁の下の力持ち役を担う background.js はそのままだった。それもやはり分割した。

* * *

wasavi が起動すると、その実体は iframe なのだが、その src はこのサーバを指している。つまり wasavi を起動したことが、期せずして Apache のログからわかってしまう。これははっきり言って不要な情報だ。別にそんな情報で統計をとったりしたくはない。

なんでそんなことをしているのかというと、Opera が悪い。もし iframe が、エクステンション内部のファイルを指すことができれば、この外部へのアクセスは不要である。ただし何でもかんでも参照できるとセキュリティ上の問題になりうるわけで、今日びのブラウザは基本的にはそういうアクセスは禁止している。

ただし、Chrome はホワイトリストに特定のファイルを登録することで、その制限を回避できる。Firefox では、Chrome のようなシステマチックな解決法は(まだ)ないけど、iframe の src を data スキームの文字列にした上で PageMod の判定部を注意深くそれに対応させることでなんとかできる。

一方、Opera は無理。いやエラーページを乗っ取ったりオレオレスキームの URL を開かせたりすればできないことはないんだけど、なんか、特定のバージョンの特定の動作に依存するようなすごく嫌な感じのハックになる。

というわけで、仕方なく、wasavi 起動時はここのサーバの html ファイルを参照するという動作を強いられていたのだった。

しかし Opera 12 はもう歴史上の、過去のブラウザである。Opera を気にすることなく、Chrome と Firefox、そしてもちろん Opera 15 に関してはそれぞれ上記のような動きをさせることにした。

Mysterious Color #2

とりあえず ace のソースなんかを見てみる。バッファへの編集は edit_session.js でやっていて、色分けのトークンの切り出しは background_tokenizer.js でやっている。切り出しの処理は setTimeout で分割される。

ソースを見た感じ、再解析は編集の行われた行以降? それでいいのかな。

 * * *

ところでスクリプティングやシンタックスハイライティングについて下調べしているわけだが、すぐ実装するわけではない。0.5 での目標は、まず安定して編集できるようにすること。textarea の拡張として常用できるようにすること。いろんな機能があるけど不安定なエディタなんて誰も使わないのだ。

その後でまず、モデルとビューを分離させたい。今はバッファはすなわち DOM の要素群であり、したがってビューを兼ねている。これはよくない。バッファは単に 1 行を要素にとる配列にし、ビューは見えている部分だけを描画させるようにしないと、数万行のテキストを編集する際にメモリ消費がひどいことになる(はず)。

 * * *

Webkit Opera のデスクトップ版っていつお披露目されるのかな。Opera が Webkit ベースになれば、つまり Composition Events ももれなくついてくるということなので、今やってるような気持ちの悪いエミュレーションはまったく要らなくなる。そうすればいろいろとコードをスッキリさせられる。かなり楽しみだ。

Mysterious Color

Syntax Hilighting のことを考えるのだが、いまいち実装のイメージが湧かない。ところで “Syntax” というけれど、巷のテキストエディタって文法的なものまで見ているものなんだろうか? あるいは、単にキーワードを識別しているだけ? まずそこからが分からない。それから、テキストの編集が行われると当然その行の解析をやり直すことになるのだけど、その行だけではなく、場合によっては編集した行の周辺の影響を受けたり与えたりする。むしろそのケースが殆んどかもしれない。その場合、再解析を行う行はカーソル行ではなく n 行遡った行、ということになるのだが。その n はどーいう理屈で求めるのか? それとも編集が行われるたび先頭行から解析し直すのだろうか。

vim に限って言えば、仕組みは :help :syntax すればだいたい分かる。しかし再解析行を指定できたりするのは自由度が高いといえば聞こえはいいが、実装の都合が見えすぎてる気も……。もしかしたら参考にすべき実装とはちょっと言えないのかもしれない。

もうちょっと色々なエディタのソースを見てみる必要があるかな。うーんめんどいなあ!

Interception

ふたたび github の issue から。

  • Chrome で、オプションページから設定を変更しても既存のタブ上の textarea に反映されない?
  • github の issue がまさにそうなのだが、wasavi を起動するためのショートカットキーが textarea に結び付けれたページ固有のイベントリスナと競合することがある

後者について考えてみる。textarea 上でキー入力があった時、それが wasavi を起動させるためのものかを判断するために、あらゆるページで agent.js という小さなスクリプトを走らせている。この中で [cci]window.addEventListener(‘keydown’, …)[/cci] としてそこで判断している。

一方で、ページに付属するスクリプトが textarea や window や document に addEventListener() する場合もある。複数のイベントリスナは登録順に実行される。ここで [cci]Ctrl+Enter[/cci] を特別なキーバインドとして認識する複数のイベントリスナがあったとき、競合が起こる。github の issue では wasavi の起動とコメントの送信が同時に行われてしまう。

実は開発の初期の段階では、agent.js で window と document の addEventListener() のフックというなかなかの荒業をやっていた。addEventListener() を乗っ取り、wasavi が起動中なら wasavi に関連しないキー入力イベントは無効にしてしまうのだ。しかしたしかに荒業すぎるので、取り外していた。

とはいえ、やはりリスナの競合の回避はそういう方法を使わない限り難しい。ということで復活させた。フックの対象は window/document ではなく window.Node.prototype.addEventListener() にした。つまりあらゆるノードが影響を受ける。荒業どころではなく傍若無人なレベルに達している気がするが……。いや問題なく動くのなら別に傍若無人だろうといいと思うけど、似たようなエクステンションと共存させたとき、うまく動くのかな、とちょっと心配ではある。

Scripting #7

とりあえずの取っ掛かりとしてスクリプトを動かす基本的な部分は作った。が、このアーティクルではスクリプティングについて特に書くことはない。github で要望や pull request が来ていたという話なのだ。

  • スペルチェッカがほしい
  • コンテキストメニューから wasavi を起動できるようにしてほしい

こんなところである。また、pull request の方はメッセージや readme を自然な英語に書き換えてもらった。これは嬉しい。

さてこの中で興味深いのは、スペルチェッカだと思う。ピュアな vi にはそんな機能はない。また、nvi にもない。vim はある。ちなみに elvis にもあるらしい。

といっても、同等の機能を javascript で実装するのはなかなか面倒だ。実装というか、辞書を用意するのが大変だ。そこで、単にブラウザの機能で入力用の textarea の spellcheck 属性を true にすることにする。とりあえず input モード時、間違った綴りの単語を区別することはできるようになる。Opera では間違った綴りの単語に赤線が引かれる。
wasavi_spell_checker
ただ、たとえば Opera の場合、間違った綴りの単語の上でコンテキストメニューを出すと正しい綴りの単語が提示されて、単語を置き換えたりできるのだが、wasavi は個々の要素に対するコンテキストメニューを封じているので、置換はできないのであった。本当にただ、綴りが間違っているかどうかを知ることができるだけである。また、input モードを抜けると赤線は消去される。

spellcheck 属性は、html5 で規定されている。したがって html5 対応と謳うブラウザはだいたいスペルチェック機能を持っていると考えてよい(必須の条件ではない)のだが、どうせブラウザがスペルチェック機能を内包するのなら、javascript の API も規定してくれればいいのになー。