completion #3

set コマンドについて、オプション名を補完できるようにもした。また、オプション名の略語を展開するようにもした。つまり set ai と打って tab を押すと、set autoindent に展開される。また、shift+tab で補完リストを逆方向に辿れるようにした。

これで、補完対象となる 3 種のうち 2 つまでを作り終えた。残るのは dropbox のようなストレージ上のファイル名の補完だ。

この手の補完は、他の 2 つとは決定的に違う点があって、つまり補完リストを得るためにネットワークアクセスが発生するのである。そうすると、アクセスの頻度とか、遅延とか、ワーストケースを考えつつ組まなければいけない。

まずアクセス頻度を抑えるために補完リストは最低数分間はキャッシュするようにしよう。また遅延は、たとえばアクセスして 15 秒経過しても反応がない場合は中断するような監視タイマを設け、中断時は古いキャッシュを使いまわすような仕組みにすればいいんじゃないかな!

completion #2

まずは ex コマンド名の補完に絞って作ってみる。

基本的な考え方は、入力行が ^\s*([^”\S]*).*$ にマッチし、カーソルが \1 の中にある場合、つまり ex コマンド名の入力途中であるとみなされる場合、tab の押下により \1 の範囲内を補完し置き換えるようにすればいい。

しかしながら、これは本当に核の部分の基本的な仕組みであって、いろいろと考えることはある。

  • この正規表現はアドレスの指定を考慮していない。アドレス指定の文法はかなり複雑 なので、補完機能の中にパーサを入れるのはちょっと無駄だ。ex コマンド実行部のパーサを流用できるようにしたい。
  • ex コマンドは | で連結することができる。したがって、補完は行頭の場合のほか、| の直後にも行われないといけない。ただし、/ の引数やアドレス指定内のリテラル内での | 、および ” 以降のコメント内の | は ex コマンドの区切りではないので、補完の開始トリガーとみなしてはならない。これもやはり、ex コマンドパーサの結果を使用しないと正しい動作を行うことができない。

というわけで、ex コマンド実行部にまず拡張を施し、純粋にパースだけを行うことができるようにした。その結果を元に補完を行うかを判断することになる。

また、補完処理は補完の準備が整っていることを確認した後に非同期的に行うようにしないといけない。コマンド名のリストであれば同期的な処理で構わないが、dropbox 上のファイルパスの補完などを行うことを考えると非同期にならざるを得ない。

というわけで作ってみた。

  • 補完対象を置き換える範囲をどうするか? 上記の正規表現の \1 の部分を置き換えるようにすると、
    ver#foo
    みたいなのがあったとき(# はカーソルを示す)、補完後の文字列は例えば version とかになる。つまりカーソルの後ろの領域も置き換えられる。一方、vim では置き換えるのはカーソル位置までなのである。つまり versionfoo とかになる。どちらがいいか。

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
次は書き込みのほう。

question and answer

近頃は技術系の疑問をぐぐったりすると、たいてい stackoverflow がひっかかり、たいていそれを見れば解決してしまう。つまり大変お世話になっている。

それはそれとして、わが国にはそういうものはないのかな? と考えてみると、@IT 内にそんな感じのがあったなあと思い出した。また、ずいぶん前からやっているだけあって、システムは前世紀の掲示板みたいな感じで、たまに見るたび「うーん…使いにくい…」となっていたことも思い出した。

などといろいろと思い出しつつ再び 訪れてみたら奥さん、これがものすごく清々しいほどに stackoverflow をパクって生まれ変わっていたのですよ!

そういうスタンス嫌いじゃないです。

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 に関してはそれぞれ上記のような動きをさせることにした。

ln -s #2

Windows Vista から NTFS にシンボリックリンク機能が追加されているけど、管理者権限でしか使えないから cygwin が対応してない云々というエントリを以前 書いた

実を言えば、ちょっとググれば、ローカルセキュリティポリシーエディタをたちあげて、「シンボリックリンクの作成」ポリシーに任意のアカウントを追加すれば使えるようになりますぞーというソリューションはすぐ出てくる。出てくるので、そのとおりにやったのだが…なぜか、やはり権限が足りねーよと cmd.exe に怒られるままなのである。謎だ。というのが 1 年前のことだ。

で、今何気なく再度ググってみたらこんなことが。つまり、上記ポリシーにアカウントを追加したとしても、そのアカウントが Administrators グループに入っている場合は別の機構が権限をフィルタリングしてしまい、結果、シンボリックの作成はやはりできないのだそうな。なに、それ。意味がわからない。

回避するには、次のいずれかを試す:

  1. UAC を完全に切る。もちろんおすすめできない。
  2. シンボリックリンクの作成権限を与えたいアカウント A を Administrators から外し、Users に含める。つまり管理ユーザから一般ユーザにする。もしローカルマシンにアカウントが A しかない場合は、先に別の管理ユーザを作成しておく必要がある。

というわけで 2. をやってみた。ほー一般ユーザにすると UAC のダイアログで管理ユーザのパスワードを求められるのか。なんか……原始的。sudo をパクればいいのに。

それにしても 1 年前はなんで見つからなかったのかなーあれだけググったのになー。

ちなみにだからといって、cygwin の ln -s が NTFS ネイティブなシンボリックリンクを作ってくれるようには、やはりならない。しかし ln -s として呼ばれたら /cygdrive/c/Windows/system32/cmd.exe /c “mklink <dst> <src>” 辺りを裏で呼び出すラッパを作れば対応できそうである。と思ったら、すでにそれっぽいのを作っている人がいた。あとで試してみる。