読者です 読者をやめる 読者になる 読者になる

gotin blog

Whatever gotin wanna write

GreasemonkeyでEmacsチックなテキストエディタ

Greasemonkeyでテキストエディタに影響され、
同じようなものを作ってみてしまいました。
Emacsチックにして、JavaScriptでコンフィグしたり何やりできたらいいかな〜と思い、
それを実現したつもりです。

こちら↓です。
g_editor.user.js

f:id:gotin:20061203125613p:image

[特徴]
Greasemonkey名前空間内に仮想ファイルシステムを構築し、それを利用
JavaScriptで設定やら何やらする
・その延長で、Greasemonkeyっぽいこともできる
・その延長で、HTTPアクセスしてHTMLとかをテキスト編集エリアに出力



[使い方など]

Greasemonkey名前空間(?)に仮想的なファイルシステム(っていうのかな?)を作ります。
ファイルパスセパレータは"/"で、ルートは"/"です。
インストール後、最初の実行状態では"/"しかありません。

が、最初の初期化処理で/init.jsとして
http://gomaxfire.dnsdojo.com/g_editor/init.js
のファイルをセーブします。

何のためかというと、これが設定用ファイルなんです。
実行直後、/init.jsの内容をevalして実行するしかけになっています。

ちなみにinit.jsにはファイルのセーブ、ロードなどの基本的な処理の定義と、
キーバインドなどが定義されています。

そこで定義されているキーバインドは以下。

[初期状態でのキーバインド一覧]


C-z g_editorの表示/非表示を切り替える。
C-x C-f ファイルを開く。ダイアログが出てくるので、そこでファイルパスを入力する。
ディレクトリを指定するとそのディレクトリのファイル一覧を表示します。
C-x C-s 編集中のテキストをファイルに上書き保存する。
C-x C-w 名前を付けて保存。ダイアログが出てくるので、そこで保存先のファイルパスを入力する。
C-c コマンドの実行。ダイアログが出てくるので、そこでコマンド名を入力する。コマンドについては後述。
C-x C-d ファイル一覧を表示する。ダイアログが出てくるので、そこで表示させたいディレクトリパスを入力する。
C-x i pluginファイルインポート用アイコンの表示

上記のC-zはコントロールキーを押しながらzキーを押す、ことを意味しています。

上記のキーバインドはMac向けです。WindowsだとC-wの時点でウィンドウが閉じてしまったり
C-dでFirefoxのお気に入りに追加だったりといろいろとバッティングしてしまうので、
Windowsな方向けに以下のような定義になるようにしています。


// キーバインド定義
KeyProcessor.addShortCut("M-o", "open");
KeyProcessor.addShortCut("M-s", "save");
KeyProcessor.addShortCut("M-w", "saveas");
KeyProcessor.addShortCut("M-x", "command");
KeyProcessor.addShortCut("M-d", "dir");
KeyProcessor.addShortCut("M-i", Importer.init, false);
KeyProcessor.setToggleShortCut("M-z");

こんなキーバインドになります↓


M-z g_editorの表示/非表示を切り替える。
M-o ファイルを開く。ダイアログが出てくるので、そこでファイルパスを入力する。
M-s 編集中のテキストをファイルに上書き保存する。
M-w 名前を付けて保存。ダイアログが出てくるので、そこで保存先のファイルパスを入力する。
M-x コマンドの実行。ダイアログが出てくるので、そこでコマンド名を入力する。コマンドについては後述。
M-d ファイル一覧を表示する。ダイアログが出てくるので、そこで表示させたいディレクトリパスを入力する。
M i pluginファイルインポート用アイコンの表示

M-zはAltキーを押しながらzキーを押すことを意味します。

ディレクトリを作成するための処理が定義されていませんが(内部ではその処理はありますが)、
ファイルを保存するときに作成されていないディレクトリが指定されているときは
自動的にディレクトリを作成するようにしました。

ファイル一覧は↓こんな感じになります。
f:id:gotin:20061204022311p:image

ディレクトリだと名前の後ろに"/"がついて表示されます。
それをクリックするとそのディレクトリ内のファイルが一覧表示されます。
ファイルをクリックするとそのファイルの編集ができるようになります。

ファイル一覧機能やpluginファイルインポート用アイコンの表示機能、winか否かでのキーバインドの変更処理などは昔のinit.jsには定義されていませんでした。最新のinit.jsに入れ替えるには、


1. /init.jsを開いて、(C-x C-f [コマンドopen])
2. 内容を全部削除し、
3. コマンドwgetで 
http://gomaxfire.dnsdojo.com/g_editor/init.js
  の内容を出力、
4. 保存する(C-x C-s[コマンドsave])

でできるはずです。
2と3は前後させてもよいでしょう。


C-c(winだとM-x)で実行できるコマンドですが、以下のようなコマンドがinit.jsでは定義されています。
[初期状態でのコマンド一覧]


open ファイルを開く。(C-x C-f)
save ファイルに上書き保存する。(C-x C-s)
saveas 名前を付けて保存する。(C-x C-w)
rm ファイルやディレクトリを削除する。ダイアログが出てくるのでそこに削除対象のファイルパスを入力する。
wget httpアクセスした結果を編集エリアに出力する。ダイアログが出てくるのでそこにアクセス先URLを入力する。
exec ファイルを指定して中身を実行する(evalする)
dir ファイル一覧を表示する。(C-x C-d)

wgetはよそ様のhtmlやjavascriptを頂戴するときに便利かもしれません。
execはGreasemonkeyスクリプトの作成に便利かも。
書いて、実行して試す、書いて、実行して試す、というときに楽チンな気がします。

Greasemonkeyっぽい使い方もできて、
保存したJavaScriptファイルや関数を今開いているページのURLに従って実行させる機能があります。
init.jsの最後のほうに書いてある↓がそれです。


Executer.execute(function(){alert(["Greasemonkeyっぽいこともできます!",
"このalertうざったいだろうから、",
"init.jsの最後の方をコメントアウトしてくださいね。"].
join(""));},
{include:["http://d.hatena.ne.jp/gotin*"]});

Executer.executeの第1引数に実行させたい関数やJavaScriptを保存したファイルのパス、もしくはコマンド名を指定します。
関数ならその関数を実行しますし、
ファイルパスなら(文字列ならファイルパスだと思って)ファイルの内容をevalします。
/init.jsを実行させる処理もこの機能を使っていて、
g_editor.user.js自体の最後の方に、


Executer.execute("/init.js");

と書いています。
第2引数はオプションで、includeやexcludeを属性として持つオブジェクトを指定します。
どちらの属性の値とも文字列配列を指定してください。
その配列の要素にURLを書いて、実行させたい・させたくないを指定します。
実行させたい方はincludeで、させたくない方はexcludeです。
Greasemonkeyと同じように、*を使った表現もできます。

ところでEmacsチック、といってもテキスト編集のキーバインドEmacsキーバインドにはなっていません。
Firefoxのテキスト入力をEmacsキーバインドにする技はどこかに載っていたと思うので、
探してそれをつかってください^^;;

ちなみに随時更新すると思いますので、
GreasemonkeyでGreasemonkeyを使っていただけると、
イチイチ更新されていないかチェックして再インストール処理などせずに済んで便利だと思います。


TODO

  • Windows環境でのテスト(C-xとか、カットの動作になるような気がしてならない)

→カットの動作にはならないものの、逆にカットの動作をさせたいときに困るという
当たり前のことに気づいた。
他にもC-x C-dでdirしたいときになにやらfirefoxのブックマーク追加の機能にキーバインドされているようで
バッティングしてしまう。
どうすんべ。んーカットの処理とかを作るのは面倒な気がするので、
  キーバインドを変更することで対応してもらおうかな。
キーバインドの変更の仕方を詳しくドキュメント化することでOK?^^;;;
もしくはマックつかってくださいってことで^^;;;;;; 

  • ファイルの削除機能

→rmコマンドとして実装。
 JavaScriptでオブジェクトの属性を削除する、という機能はないもんだろうか?
 あるのかもしれないけど、知らなかったのでとりあえず削除対象のものの値をnullにして、
 JSON文字列化する際に値がnullのものは属性として出力しないことにし、
 そのJSON文字列を再びオブジェクトに戻す、という面倒な感じの実装にした。

→delete演算子でできることが判明した。

  • テキストをペーストされたときのdirty処理(setIntervalとかでチェックするしかないかなぁ。。)
  • Emacsにおけるdirっぽいもの。ファイルシステム(?)があっても何があるのか分からんと管理しづらいので。

→それっぽいものはできた。

  • ほんとのファイルとして保存する機能

→超簡易ファイルサーバとの同期pluginでほんとのファイルとして保存するのと似た環境を作った。
ファイルサーバ上ではほんとのファイルとして保存されるので、そのファイルサーバをローカルに置いておけばローカルファイルを編集している気分になれるはずです^^;

  • ブログのエントリーとしてアップロードする機能
  • カーソルの管理(できないのかも。できるならC-SPACEでマーク、C-wでカット、とかやりたい)
  • ↑これの延長で、今何行目とか、今何列目、とか表示したい
  • Emacsっぽく、バッファ管理したい(今は一枚しかバッファがない状態)
  • Emacsっぽく、mini-bufferでインタラクティブな動作をさせる(現状はpromptによるダイアログ入力)



[追記]
(1)Emacsのdirっぽい機能を追加しました。
最新のinit.jsの設定だと、コマンド名"dir"で、C-x C-dにキーバインドしています。
パスの入力を促すダイアログが出てくるので、ディレクトリのパスを指定してください。
そのディレクトリ内のファイル一覧が出ます。
ファイルをクリックするとバッファにそのファイルが表示されます。
ディレクトリをクリックするとそのディレクトリ内のファイル一覧が表示されます。
.. は親ディレクトリを表します。

最新のinit.jsに入れ替えるには、


1. /init.jsを開いて、(C-x C-f [コマンドopen])
2. 内容を全部削除し、
3. コマンドwgetで 
http://gomaxfire.dnsdojo.com/g_editor/init.js
  の内容を出力、
4. 保存する(C-x C-s[コマンドsave])

でできるはずです。
2と3は前後させてもよいでしょう。

(2)記号を入力したときにすぐに反映されない問題に対処
キー入力のイベント処理に問題があって、記号は二回続けて入力しないと入力できない問題がありましたが、
これに対応しました。

[ver 0.12]
最新のinit.jsではファイルを開く際にディレクトリを指定すると
dirで開くようにしました。
というか、今までのinit.jsではディレクトリを指定しても
空ファイルを開いたときと同じ動作をしてしまうという問題がありました。


[ver 0.13]
ファイルの保存、ロード、画面を開く・閉じるの動作時にイベントが発生するようにし、
イベントリスナを登録することで各動作のタイミングで何かの処理を動かす、
といったことができるようにしました。
その機能を利用したスクリプトを作ってみました。
スクリプトについてはこちら↓をどうぞ。
g_editor.user.js用textarea連携スクリプト

[ver 0.14]
ファイルの1クリックインポート機能を追加しました。
/plugins/ 以下のファイルを起動時にすべてevalするようにしました。
1クリックインポートのデフォルトの保存先が/plugins/になってるので、
インポートしたものはすべて実行時に最初にevalされるようになります。
これで拡張機能スクリプトの導入も簡単になったと思います。
詳細は↓こちら
GreasemonkeyでEmacsチックなテキストエディタをちょっと更新

導入も簡単に、なんて言っても、
今のところ公開している拡張機能スクリプトはtextarea連携の一つだけですが ^^;;;


それとついでに名前もg_editor→gmacsに変更しました。なんだか呼びづらかったし。

[ver 0.15, 0.16]
内部ファイルの構造を変更してファイルやディレクトリの作成日、修正日を管理するようにしました。


[ver 0.17]
内部ファイルの構造に伴っていろいろと修正したら動かなくなってしまったので
内部ファイル管理についての下位互換性をあきらめました。
(その分プログラムの複雑さから開放されました^^;;)


/plugins/直下にGreasemonkeyスクリプトをインストールした場合に、
ただevalするのではなく、includeやexclodeの設定を加味してevalするか否かを判定した上でevalするようにしました。
詳細は↓こちら
gmacsでGreasemonkeyスクリプトの実行

[ver 0.191]
超簡易ファイルサーバとの同期処理pluginを作成しました。
それに伴うステータスバー表示処理を本体に追加しました。

  • 今までwinとそれ以外でキーバインド定義を分けるために別のinit.jsを読み込むようにしていたのを、ひとつのinit.jsでまとめて書くようにした
  • 超簡易ファイルサーバとの同期処理のため、ファイルパスがおかしなものだとエラーを返すようにした



「仮想ファイルシステムを構築」なんて大げさなことを書いてしまいましたが、
ホントはたいしたことはしていません。
説明(というか言い訳?)を
gotinの日記 - g_editor.user.js における仮想ファイルシステムについて
に書いておきました。