赤心慶福

Subscribe to RSS feed

vi in javascript #2

,

昨日の記事で、javascript による vi を作ると書いた。

実はこれは嘘である。作ることにしたのではなく、すでにだいたい出来ている。ちょっと前に vi についてどうのこうの書いている記事が連続しているのはその過程のものだ。


こういう textarea 要素があったとして、それにフォーカスを移すと、


こんな感じで vi 化する。あとはだいたいそのまんま vi だ。ブラウザで動かすという前提において無意味な仕様(:rewind とか、:next とか、shell とか ! とか)以外は、だいたいのところを実装した。esc の代わりに ctrl+[ も認識するし、abbrev もあるし、パラグラフやセクションは troff マクロも認識するし、:s も :g も あるし、:map もあるし、INSERT モードでは IME 経由の入力もインラインで可能。


:set all したところ。オプションは poxix で定義されているものはとりあえずすべて認識する(すべてが意味を持つわけではない)。


/ を押して正規表現検索しているところ。素の vi にはインクリメンタルサーチはないが、vim にはあるので実装した。vim から輸入したものはほかに複数のアンドゥ・リドゥもある。

もちろん完成しているわけではなくバグもけっこう残ってるし、現在は単なる javascript ファイルなので、これをエクステンションにパッケージしてとりあえずα版という感じ。

土日にがんばろー。

vi in javascript

,

html には、ユーザーからのキーボード入力を受け付ける要素がいくつかある。input 要素(の、特定の type)と textarea 要素だ。これらの要素は、ユーザーからのキーボード入力や編集操作を受け付け、入力された値を保持し、それを表示する。つまり、ちょっとしたプレーンテキストエディタとして振る舞う。

特に、複数行の入力を受け付ける textarea 要素はまさしくテキストエディタそのものである。

さて、この textarea 要素。テキストエディタとして振る舞うための独自のキーバインドを備えているのだが、大体どのブラウザにおいても、そのキーバインドは「メモ帳」的である。

「メモ帳的キーバインド」(といいつつ、起源は Macintosh の TeachText あたりなのかもしれない)とはつまり、
  • 文字を表すキーが押されたら、その文字が入力される
  • 機能を表すキーが押されたら、その機能が実行される
  • ctrl+矢印キーやマウスのドラッグによって、選択範囲を生成できる
  • 選択範囲が存在する状態での文字の入力は、選択範囲との置き換えとして振る舞う

だいたいこんな感じのシンプルな操作体系。

メモ帳的キーバインドは非常にわかりやすく習熟が容易なのだが、さて使いやすいか? あるいは大量の文書を入力・編集するのに効率的か? と考えると、必ずしもそうではない。特にカーソルを任意の場所に動かすために、Page Up/Page Down/Home/End、マウスのクリック、あるいは矢印キーを使うしかなく、いずれもキーボードのホームポジションを離れなくてはならないので、そのためどうしても入力と編集作業の間に 1 クッションが入る。

そこで、textarea 要素のキーバインドが、たとえば vi 互換だったらいいのに! と思うわけである。

しかし組み込みの機能としてそんなことをさせてくれるブラウザはない。で、次にそういうエクステンションはないのかと探してみると、意外にもない(あればどなたか教えてください)。強いて言えば、vimperator が TEXTAREA モードを備えているが、vi の機能のほんのさわりしか実装されていないので実用にはならない(これはたぶん中の人にとって TEXTAREA モードは <C-S-I> で外部エディタを起動できない場合のフォールバックであって、それほど重要視していないからなのだと思う)。

ここまでくると、例によって自分で作るしかないのか……となるのだが、その前に、エクステンションに限らず javascript で実装された vi、あるいは vi に特化していなくても、vi キーバインドを備えたオンラインエディタというものを探してみると、これはいくつかある:

特に jsvi なんかは、リリース直後に割といろいろな blog で紹介されたので有名ですね。しかしこれらもまた、実用に近いか? と言われると否である。なにしろ INSERT モードで IME を経由した入力がまずできなかったり、その他いろいろやっつけな出来のものが多い。そして、ものすごく重要なことであるが…… vi を実装するというのは、本質的には「ex を実装して、さらにその visual モードを実装する」ということに他ならないのであるが、なんかどの実装例も「カーソルを h/j/k/l で動かせるようにしたよ! だから vi!」的な考えで作られている気がしてならないのだ。

そういうわけで、「textarea 要素を実用に耐えうるレベルで vi 化したい」という要件に応えてくれるエクステンションなり、スクリプトはどうもこの地球上の誰も作っていないようである。

なら、自分で作るしかないじゃないか。

C's family

C とは直接の関係はないけど、C の文法を ++(あるいは --)したプログラム言語っていっぱいあるじゃないですか。

でも個人的に C の文法で一番イけてると思う「隣接する文字列リテラルが、コンパイル時にまとめられる」っていうルールを継承している言語が多くないのはなぜなの?

char *foo (void) {
  return "abc"
         "def"
         "ghi";
}

とってもイけてる例

function foo () {
  return 'abc' +
         'def' +
         'ghi';
}

全然イけてない例

Tsumami released

,

そんなわけで長らく単にフィードリーダーと呼んでいたアレを Tsumami と名づけ、公開した。
Download

Tsumami というのはつまみ、いわゆる knob のことだが、例によって特に意味はない。

どばーっと readme も書いたが、改めて読み直してみると、そもそもの要件(フィードの提供先を見に行くのがめんどいので読み上げて欲しい)が非常に個人的なものである上に、インストールが恐ろしく面倒なので公開してもこれ誰も使わねーだろと思った。

talk about rss feed #19

しゃべるフィードリーダー。これを動かしているとフィードを読み込んで読み上げる。そのままですね。

さて PC を使っていると、いろいろと音が出る場面がある。音楽が流れるほかに、動画などで音声が流れる場合もある。そうすると、フィードリーダーの音声とかぶってなかなかなカオスになる。

そこで、実行中のフィードリーダーに対して動作を指示できるように、TCP コネクションを 1 つ保持しておき、メインループ内でその面倒も見ることにした。

localhost:8859 に対して、
  • pause: 発声中のフィードを中断する
  • continue: 中断したフィードの発声を再開する。再開はフィードの先頭から行われる
  • skip: 発声中のフィードを中断し、次のフィードを(あれば)発声する
  • exit: アプリケーションを終了する
  • status: 現在の状態を返す


とこれくらいで十分ではないか。簡便のためにこれらのコマンドを発行するアプリケーションも同梱する。つまり
C:\> tsumamictl.php pause
paused http:// ... /foo.xml

C:\> tsumamictl.php continue
continuing http:// ... /foo.xml

C:\> tsumamictl.php status
the current feed is http:// ... /foo.xml, and the 'softalk' hook is running now.

C:\> tsumamictl.php exit
bye.

といった調子だ。

ところでソケット周りを書いてみたら wincon が便利すぎる。

talk about rss feed #18

昨日の記事の続き。

昨日妄想したようなエクステンション php_wincon.dll を妄想したような通りの仕様で書いた。

ただしいくつか注意点がある。

  • pcntl ではシグナルの発生した回数分、php のコールバック関数も呼ばれるが、wincon エクステンションでは ctrl-c/ctrl-break/ウィンドウのクローズボタンのいずれかが押されたかどうかだけしか保持していない。したがって wincon_dispatch_signal() が呼ばれる前にシグナルが複数回発生していたとしても、wincon_dispatch_signal() を呼び出すことで起動されるコールバックの起動回数は、高々 1 回だけである。
  • php 側の状態として、シグナルを発生させると定数 STDIN であらわされるストリームが eof 状態になってしまう。定数なので、再オープンできない。
    もしインタラクティブなシェル的なプログラムと組み合わせる場合は、直接 STDIN を使わずに、
    $stdin = STDIN;
    while (true) {
      wincon_dispatch_signal();
      $line = fgets($stdin);
      if ($line === false) {
        $stdin = fopen("php://stdin", "r");
        continue;
      }
      do_something();
    }
    

    ……というような、若干回りくどい書き方にする必要がある。また、fgets のように入力に対してブロックされる関数については、シグナルが発生するタイミングは不定である(入力待ちの状態で ctrl-c を押下して即シグナルのハンドラが起動することもあるし、enter を押下して fgets の処理を抜けた後にハンドラが起動することもある)。
  • ticks には対応していない(register_tick_function() で登録するイベントハンドラ内で wincon_dispatch_signal() を呼ぶことは可能)。


ctrl-c に対応した graceful な終了処理の実現、という要件にはとりあえず十分なので、このままにする。

talk about rss feed #17

前の記事で、Windows の php(cli)では ctrl-c がキャッチできないと書いた。

しかしできないなら、そういうエクステンションを書けばいいのではないか。

つまり
  • モジュールの初期化時に sapi が cli だったら SetConsoleCtrlHandler() を呼んで、適当な関数でキャッチするようにしておく
  • シグナル受信時のハンドラとなる php 側コールバック関数のリストを保持しておく
  • ハンドラを登録する php の関数を公開する
  • ハンドラをディスパッチする php の関数を公開する。ハンドラをディスパッチする、とはシグナル受信済みだったら、登録された php 側の関数を順繰りに実行すること
  • tick 関数でもハンドラをディスパッチする(tick まわりは deprecated なのかな?)

というような感じ。

これにより、たとえば
wincon_register_signal(function () {
    release_resources();
    exit(0);
});

while (true) {
    wincon_dispatch_signal();
    do_something_fantastic();
}

というようなコードで ctrl-c をキャッチして終了時の後始末を行えるようになるはず。

pcntl のソースを見てみたが大体同じようなことをやっていた。

誰か作ってくれないかなー(チラッ

talk about rss feed #16

ぼちぼち配布のことについて考える。

しかし、配布するかどうかは定かではない。なにしろ、満足に動かすまでがとても面倒なのだ。

最低限必要なもの:

  • PHP 5.3+
  • SofTalk

あるといいもの:

  • 英単語をそれなりに読ませる場合: php_sqlite3.dll
  • サービスとして動かす場合: php_win32service.dll
  • 漢字の読みやアクセントをマシにする場合:
    • php_chasen.dll
    • php_xsl.dll
    • ChaSen 2.3.3+
    • UniDic (UTF-8) 1.3.12+
    • ChaOne 1.3.3+
    • NumTrans 0.51+

そもそも Windows に PHP を入れてあるような人はだいたい開発畑の人だろうから、単にリストしてあるのを入れてね☆ で済むのかもしれないが……。

そうでない人には無理だこれ!

talk about rss feed #15

形態素解析に ChaSen を使うようにした。

前の記事のとおりアクセントの確定に ChaOne を使うわけだが、さらに数値文字列+接尾辞を自然な読み上げに変換するために NumTrans も使うようにした。

これらのツールに対して、いくつか不満がないわけではない。

  • やはり再配布禁止 + ダウンロードするにはユーザ登録が必要、という規定が厳しすぎる
  • ChaOne、NumTrans は xslt で書かれているわけだが、とても重い
  • NumTrans のアーカイブにマニュアルが同梱されていないので、どういう入力をすればいいのか、他のプログラムのソースを覗かないとわからない
  • 茶筅、茶碗と来てなんで NumTrans は茶柱とかじゃないの! ばかなの! しぬの!


とまあどうでもいいものばかりだ。一方で性能はとてもよいと思う。ChaOne については名詞が連続した際のアクセント位置の移動も行ってくれるようだ。たぶん。なので、呼び出し側で行う事前処理はモーラが連続する際の読点の自動挿入だけになった。これは助詞と助詞以外(ただし非自立な動詞は除く)の境界で、直前の読点から 12 モーラより離れている場合に読点を挿入するというものだ。こうしないと読点少な目の長めの文章を一気に読まれて聞き取りづらくなる。12 という値は経験則だ(5、7、12、17 あたりの数値から選択した)。

NumTrans について。ChaOne と同様に xsl ファイルで構成される。したがって、入力は xml のはずなのだけど、なぜかマニュアルが同梱されていない。

とりあえずいじってみた感じ、変換対象になるプレーンテキストを xml の形式にでっち上げて入力すればいいようだ。当然、でっち上げたとしても整形式である必要はある。

変換前のテキストが
ニュージーランドのゴールデン湾で、100頭近くのゴンドウクジラが浜辺に乗り上げているのが見つかった。

であれば
<?xml version="1.0" encoding="UTF-8"?>
<cha:D xmlns="http://www.unidic.org/chasen/ns/structure/1.0">
ニュージーランドのゴールデン湾で、100頭近くのゴンドウクジラが浜辺に乗り上げているのが見つかった。
</cha:D>

な感じにして NumTrans に与える(つまり、この xml を入力として、numtrans.xsl を読み込んだ XSL プロセッサに与えて、変換を行う)。名前空間とタグ名は重要ではない。たぶん名前空間はなくてもいいし、オレオレ名前空間でもいいし、タグ名もなんでもいい。

ただし、変換対象になる文字列の末尾には改行を入れておかないと変換されない。

変換するとこんな具合になる。
<?xml version="1.0" encoding="UTF-8"?>
<cha:D xmlns:cha="http://www.unidic.org/chasen/ns/structure/1.0">ニュージーランドのゴールデン湾で、<num:A xmlns:num="http://www.unidic.org/numtrans/ns/structure/1.0" type="decimal" origText="
100">百</num:A>頭近くのゴンドウクジラが浜辺に乗り上げているのが見つかった。
</cha:D>


num:A 要素の属性は今のところ特に必要ではないので、cha:D 要素の textContent 属性を取り出して、一番最初のプレーンテキストに上書きすればいい。

talk about rss feed #14

さて ChaSen にも php エクステンションがある(なぜか pecl のサイトからは辿れない)。

php_mecab のビルドには失敗したが、こちらは成功した: VC9、non thread safe(参考: PHP の Chasen モジュールを作成するための Patch)。

それにしても pecl エクステンションを Windows でビルドするのは面倒だー。

Windows も、最適化甘ちょろバージョンでいいから、Windows のコンポーネントに cl.exe 等一式の build_essentials を含めてくれれば、こういうソース配布するから後は自分でビルドしてね! という流儀に十分対応できるのに…