Google Chrome のテキストエリアを外部エディタで編集する Edit with Emacs

おことわり: Windows での事情は分かりません。以下は MacLinux のみで確認しています。

Firefox では昔は mozex、最近は It's All Text を使ってテキストエリアを emacs で編集していたのですが、Google Chrome では extension が見つからないので渋々普通にテキストエリアを使っていました。
が、昨日 Extensions Gallery をみていたら、Edit with Emacs というそれらしいものを発見。

Chrome はセキュリティ上の理由で外部プロセスを起動することができないため、localhost:9292 に httpd を立ててそこに XMLHttpRequest でテキストエリアの内容を POST、レスポンスとして返ってきた内容を書き戻す、という動作をするものだそうです。

emacs 用にedit-server.el という elisp が付いてきます。これを load-path に入れて .emacs

(require 'edit-server)
(edit-server-start)

とすると、emacs 自体が localhost:9292 を Listen します。


テキストエリアにはこのように [edit] ボタンが付くので、それをクリックすることで emacs で編集できます。

これで万事 OK かと思ったのですが、いくつか問題が。

  • Mac だと書き戻した内容から日本語(マルチバイト)文字が消える(?)
  • 編集中に C-x C-s すると終了してブラウザに戻ってしまうのが不満
    • 編集中に時々保存する手癖があるもので

Edit with Emacs のオプション画面には以下のようにありました。

If for some reason you don't want to use Emacs you have a couple of other options available.

Emacs を使いたくない場合はほかのサーバ実装もあるよ、ってことですね。

挙動をみると結構単純そうなので、自分で PSGI で実装してみました。http://gist.github.com/289476

  • HTTP body を一旦テンポラリファイルに書き込む
  • 外部エディタ (ここでは emacsclient) 起動
  • 編集されたファイル内容を body にしてレスポンスを返す

という挙動ができればいいようです。

# -*- mode:perl -*-
use strict;
use warnings;
use feature qw/ :5.10 /;
use File::Temp qw/ tempfile /;
use IO::File::WithPath;

my ($client) = grep { -e $_ } qw(
    /usr/bin/emacsclient.emacs-snapshot
    /Applications/Emacs.app/Contents/MacOS/bin/emacsclient
);
die "client not found" unless $client;

sub {
    my $env = shift;
    my ($status, $headers, $body)
        = ( 200, [ "Conetnt-Type" => "text/plain" ], undef );
    given ($env->{PATH_INFO}) {
        when (qr{^/status}) {
            $body = [ "OK" ];
        }
        when (qr{^/edit}) {
            # HTTP body をテンポラリファイルに書き込む
            my ($tmpfh, $tmpfile) = tempfile();
            my $buf;
            print $tmpfh $buf while read $env->{"psgi.input"}, $buf, 4096;
            close $tmpfh;

            # emacsclient 起動
            system($client, $tmpfile) != 0
                and warn $!;

            # テンポラリファイルの内容を送信
            push @$headers, ( "Content-Length" => -s $tmpfile );
            $body = IO::File::WithPath->new($tmpfile);
            unlink $tmpfile;
        }
        default {
            $status = 404;
            $body   = [ "NotFound" ];
        }
    }
    return [ $status, $headers, $body ];
}

あとはこれを plackup --port 9292 edit-server.psgi などとして起動しておけば OK です。
仕組み上一つのテキストエリアの編集時にはブロックしますが、複数のテキストエリアを同時に編集したければ Standalone::Prefork を使えば大丈夫。

ということで、Chrome でも快適にテキストエリアを編集できるようになりました。