Acmeは、Plan 9のために書かれたテキストエディタです。現在はPlan 9 from User SpaceでUnix環境にも移植されています。
このエディタはプログラムから操作するための機能がいくつか用意されていて、それを使うとキーボードやマウスなどの入力イベントを外部のプログラムが受け取り、加工してエディタに反映するなどといったことができます。標準で用意されているのはメーラーですが、最近Russ CoxがTodoを公開していました。
また、Goから扱うためのパッケージもあります。いろいろ複雑なeventファイルを適切に扱ってくれるので便利です。
基本
Acmeは水色の領域(tag)と黄色の領域(body)を合わせてwindowとして扱います。Acmeはwindow毎に1つディレクトリを用意していて(以下の実行例では1という名前のディレクトリ)、windowを開くたびに2,3...と増えていきます。プログラムからAcmeを操作する場合は、これらのディレクトリを使って行います。
$ 9p ls acme | mc 1 cons draw index log acme consctl editout label new $ 9p ls acme/1 | mc addr ctl editout event tag xdata body data errors rdsel wrsel
開いているファイルの取得
acme/indexファイルにも、現在開いているファイルやディレクトリのリストが記録されています。acmeディレクトリを直接読み込んでも同じことはできますが、ほとんどの場合はこちらを使ったほうが簡単でしょう。
$ 9p read acme/index 1 35 318 1 0 /Users/lufia/ Del Snarf Get | Look 2 39 20 0 1 /Users/lufia/.inputrc Del Snarf | Look 4 39 26 1 0 /Users/lufia/lib/ Del Snarf Get | Look 5 40 101 0 0 /Users/lufia/lib/npmrc Del Snarf | Look
1行が1つのwindowを表し、左から順番に固定幅で次のような情報が書かれています。
- windowのID
- タグ行の文字数(rune)
- ファイルの文字数(rune)
- 開いているファイルがディレクトリかどうか(ディレクトリなら1)
- 開いているファイルが編集中(未保存)かどうか(編集中なら1)
- タグ行の内容
テキストの編集
windowディレクトリ(1/など)配下のaddrとdataは、編集しているテキストを読み書きするために使います。
addrにAcmeが扱うアドレスを書くとテキストが選択された状態になります。不要な改行を加えるとエラーになるので気をつけましょう。Acmeは以下のような書式をアドレスとして扱います。
2
- 2行目を選択2,4
- 2〜4行目を選択/func.*\n/
- funcから改行までを選択#0,#2
- ファイルの先頭から2文字選択
範囲選択が成功すると、dataとxdataの内容が範囲で選択された部分に置きかわります。dataをreadすると、範囲選択部分が終わってもテキストが続けば最後まで読んでしまいますが、xdataは範囲の終わりでEOFとなります。同様にwriteすると選択した範囲を新しいテキストで置き換えます。
$ 9p read acme/2/body 1行目 2行目 3行目 $ echo -n '2' | 9p write acme/2/addr $ 9p read acme/2/data 2行目 3行目 $ echo -n '2' | 9p write acme/2/addr $ 9p read acme/2/xdata 2行目
ただし、addrを更新しても内部的に選択された状態になるだけで、画面とはリンクしていません。画面を更新する場合はctlを使います。
$ echo -n '#30,#45' | 9p write acme/1/addr $ echo -n 'dot=addr' | 9p write acme/1/ctl
dot=addr
は変わったコマンドですが、dot
は画面で選択されている範囲、addr
はaddrファイルの内容を表すので、addrファイルの内容で画面の選択範囲を更新する意味になります。画面で選択した範囲をaddrに設定するaddr=dot
もあります。
イベントの取得
Acmeのイベントは大きく2種類あります。ひとつはエディタ全体でのイベント、もうひとつはwindow単位で発生するイベントです。
acme/log
logはwindowのID、操作、ファイル名の3つが連続したテキストです。ファイル名は省略される場合もあります。実行例を示します。
$ 9p read acme/log 1 focus /Users/lufia/ 2 new # Newコマンドを2ボタンで実行した場合 2 focus 3 new /Users/lufia/src/ # 3ボタンでファイルを開いた場合 3 focus /Users/lufia/src/ 2 del 3 focus /Users/lufia/src/ 3 del /Users/lufia/src/ 1 focus /Users/lufia/
スペースで区切った3つのうち、最初のカラムはAcmeのwindowを表す数字です。2つ目は操作を表すコマンドで、これはいくつかあります。
op | 意味 |
---|---|
new | 新しいwindowを作成した |
focus | windowにマウスポインタが移動した |
del | windowを削除した |
get | ファイルを再読み込みした |
put | ファイルを保存した |
zerox | windowを複製した |
最後のカラムは開いているファイル名またはディレクトリ名です。
acme/<id>/event
eventはwindow単位のイベントが流れてくるファイルです。このファイルをopenしている間は、イベントが流れてくる代わりにAcmeデフォルトの動作は止まります(書き戻すことでデフォルトの動作を起こせる)。
ファイルの内容は1つのイベントが1行になっていて、エディタを操作するたびに1行追加されていきます。eventファイルを開いてからのイベントは読み込みできますが、過去のイベントを読むことはできません。
[origin][type][addr0] [addr1] [flag] [n] [text?]\n
originは何からイベントが発生したかを表す1文字です。
origin | どこからイベントが発生したか |
---|---|
E | bodyまたはtagファイル |
F | eventファイルへの書き込みなど |
K | キーボード入力によるイベント |
M | マウス操作によるイベント |
typeは大きく4つですが、tagまたはbodyのどちらで発生したものかを区別します。また、typeによって続きのaddr0とaddr1が意味するものが変わります。
type | 意味 |
---|---|
D | bodyからaddr0〜addr1の範囲が削除された |
d | tagからaddr0〜addr1の範囲が削除された |
I | bodyのaddr0〜addr1にtextを挿入した |
i | tagのaddr0〜addr1にtextを挿入した |
L | bodyでtextを検索(マウスボタン3) |
l | tagでtextを検索(マウスボタン3) |
X | bodyでtext実行(マウスボタン2) |
x | tagでtextを実行(マウスボタン2) |
Dまたはdイベントの場合、addr0とaddr1の値は削除前の範囲を指します。そのため、イベント発生後に範囲の内容を読み込んでも、元のテキストは消えてしまっているため取り出せません。また、Iまたはiイベントの場合は、addr0とaddr1はテキストを挿入し終えた後の範囲(新しく増えたテキスト部分)を表します。では2文字以上範囲選択した状態でテキスト入力するとどうなるか、ですが、この場合はDとIのイベントに分割されます。従って、addrとdataファイルを使って以下の操作を行なった場合、
$ echo -n 2 | 9p write acme/12/addr $ echo hello | 9p write acme/12/data
eventファイルには分割された次のイベントが届きます。
FD6 12 0 0 FI6 12 0 6 hello
イベントの残り要素、flagとtextは複雑なのでacmeのマニュアルを読んでください。textは色々な要因により省略されたりします。
利用例
# 4行目から9行目の範囲をaddrに設定 echo -n '4,6' | 9p write acme/<id>/addr # 選択した範囲(4行目から9行目)を読む 9p read acme/<id>/xdata # 先頭からのオフセットで範囲選択 echo -n '#34,#54' | 9p write acme/5/addr # 選択した範囲を更新 echo test | 9p write acme/<id>/data # addrの内容をwindowに反映(dot) echo -n 'dot=addr' | 9p write acme/<id>/ctl # windowで選択した範囲をaddrに設定 echo -n 'addr=dot' | 9p write acme/<id>/ctl # 変更ありの状態にする echo -n dirty | 9p write acme/<id>/ctl # 変更ありフラグを落とす echo -n clean | 9p write acme/<id>/ctl