AcmeエディタはPlan 9のためにRob Pikeによってデザインされたエディタで、現在はUnixにも移植されています。慣れると非常に快適なんですが、既存のエディタとは操作感が全然違うので、初見では何をすればいいのか分からないとよく聞きます。なので、この記事ではGoのコードを実際に書きながら、Acmeをどうやって使っていくのかを紹介していきます。ただし、Acmeは色々と便利な使い方があり、ひとつの記事で書き切るのは大変なので、複数の記事に分けて公開していこうと思っています。今のところ以下の内容を予定していますが、順番や内容は変更するかもしれません。
ところでYouTubeでは、Russ CoxがAcmeを使っている様子を動画にしているので、見てみると学びがあるかもしれません。
次もYouTubeの動画で、RussがAcmeでプレゼンしている様子です。Acmeの拡張*1でコマンドをフックすることにより実現しています。
一時期、9fansで話題になっていましたが、この動画でタイトル文字のところに使っているフォントの書体が違うのはフォントをハックしているからで、Acmeには一部の文字だけを装飾する機能はありません。
基本的な使い方
まずはacmeを実行してみましょう。Acmeの実行にはplan9portが必要ですが、インストール自体はPlan 9 from User Space(plan9port)を使うに書いたのでここでは省略します。
Acmeはターミナルで
% acme
とするだけで起動します。ここでエラーになった場合はPLAN9環境変数が設定されていないことが原因かもしれないので、変数の値を確認してみてください。
Acmeの起動自体はこれだけなのですが、素の状態ではフォントが汚いとか、必要な9Pサービスが実行されていなくて使いづらい面があるので、macOSの人は以下のアプリケーション*2から使ってもらうと便利かもしれません。
Acmeを実行すると、カレントディレクトリにあるファイルが右のウィンドウで表示されていて、右は空白の画面が表示されます。
黄色いウィンドウごとに青い背景のヘッダが付いています。また、列ごとにも同様のヘッダがあり、いちばん外側にも同じものがあります。Acmeではコマンドを実行してファイルの保存などを行いますが、一部のコマンドは実行できる場所が決まっています。たとえばファイルを保存するPut
は、ファイルを編集するとウィンドウごとのヘッダに出現するし、列を削除するDelcol
は列ごとに用意されているヘッダにはじめから用意されています。この部分はタグライン(tag line)と呼ぶらしいけれど、分かりやすさのためこの記事ではヘッダと表記します。
最初から多くのコマンドがヘッダに並んでいますが、以下のチュートリアルで必要になったら説明するので、今はまだ気にしなくてもかまいません。
Goのコードを書く
このチュートリアルではGoのコードを書いていくので、ディレクトリを作りましょう。右のウィンドウ一番下に
mkdir src/acmetut
と入力して、mkdir src/acmetut
全体を選択した状態でマウスの中ボタン(ホイールマウスの場合はホイールクリック)を押すと、テキストで選択したものをコマンドとして実行します。中ボタンは一般的に「実行(Exec)」を意味します。トラックパッドしかない場合は、キーボードと組み合わせることで代用可能です。
- 中クリック: Altキーを押しながらクリック
- 右クリック: Commandキーを押しながらクリック
ただ、トラックパッドだけではボタンの同時押しができないなど非常に使いづらいので、マウスを買ったほうがいいと思います。
次に、上記で作ったディレクトリを開きます。mkdir src/acmetut
と書いたテキストのうちsrc/acmetutのテキストをどこでもいいので右クリックすると、Acmeは新しいウィンドウでそれを開きます。右ボタンは一般的に「検索(Look)」を意味します。ひとつ上の例ではmkdir src/acmetut
を事前に選択してから実行しましたが、スペースや記号を含まない文字列なら自動的に境界までを対象とするので、ここでは事前にsrc/acmetutを選択しておく必要はありません。
開いた直後の画面はこのようになるはず。
これ以降、ホームディレクトリは使わないので閉じましょう。ホームディレクトリが表示されたウィンドウのヘッダにDel
と書かれたテキストがあるので、これにポインタをあわせて中クリックで閉じます。
(ヒント)コマンドを選択するのが面倒
上の説明でも少し言及していますが、スペースや特殊な記号を含まないテキストの場合は、選択せずにクリックするとAcmeが前後の区切りまでをコマンドの対象として扱います。src/acmetut/やDel
などは単にテキスト上のどこかを中クリックするだけで実行できます。
または、テキストを入力してEscキーを押すと、直前の入力内容だけを選択した状態にするので、スペースや記号などを含む場合はEscキーで選択してから実行すると便利です。1行すべてがコマンドの場合は、行頭または行末でダブルクリックすると1行まるごと選択状態になるので、そのまま実行する方法もあります。
(ヒント)中ボタンでコマンドを実行するシェルを変更する
AcmeはもともとPlan 9のエディタなので、コマンドを実行するときのシェルはrcシェルが使われます。rcはとても書きやすいシェルなので慣れてしまえば困らないと思いますが、POSIXシェルの記法とは全く異なるので最初は混乱するかもしれません。
ここで使われるシェルを変更したい場合、acmeshell環境変数にシェルをセットしておくと、それが使われます。
% export acmeedit=bash % acme
余談ですが、Plan 9の環境変数は基本的に小文字なので、上のコマンドは間違いではありません。homeやpathなども全部小文字です。
コードを書いてテストする
ではまず簡単なコマンドを書いてみましょう。ホームディレクトリは閉じたので、src/acmetut/ディレクトリを表現したウィンドウだけ残っているはずです。src/acmetut/のウィンドウに
go mod init example.com/acmetut
と入力して、全てを選択した状態で中ボタンを押すと、新しいウィンドウでコマンドの実行結果が表示されます。このときウィンドウのタイトルは/path/to/dir+Errors
のように+Errorsが付いていますが、コマンドが正常に終了した場合でも+Errorsなので気にする必要はありません。src/acmetut+Errors
のウィンドウはもういらないのでDel
で閉じておきましょう。
ところで、go mod init
が正常に終了したのでgo.modファイルが作られているはずですが、src/acmetut/ディレクトリはまだ何も表示されていません。Acmeは標準では自動リロードの機能を持たないので、src/acmetut/ディレクトリのヘッダにあるGet
コマンドでリロードします(コマンドがなければ自分で入力します)。そうすると、現在のディレクトリにあるファイルリストでウィンドウが更新されてgo.modだけ表示されるはずです。ファイルの内容を見る場合は、ファイル名をポイントして右クリックです。以下のようになっているはず。
module example.com/acmetut go 1.18
では次に、main.goを作成して、以下の内容で保存してみましょう。
package main import "fmt" func main() { fmt.Println("hello world") }
もう分かると思いますが、ファイルの作成は
touch main.go
と書いて、すべて選択してから中クリックです*3。main.goを開いて編集するとヘッダにPut
というコマンドが増えます。main.goのコードを書き終わったら、Put
を実行してファイルに保存しましょう。
(ヒント)コピペしたい
macOS版では、意外とcmd+c
やcmd+v
に対応していた記憶がありますが、Acme本来の方法はマウスを使ったものになります。左ボタンを押しながら範囲を選択し、左ボタンを押したまま同時に中ボタンを押すと選択していたテキストをカットしてSnarfバッファ*4に格納します。同様に、左ボタンを押したまま選択して同時に右ボタンを押すと、Snarfバッファに入っているテキストをペーストします。
ではコピーはどうするのかというと、左ボタンを押したまま、同時に中→右ボタンと順番に押せばコピーです。途中で一瞬だけテキストが消えますが、このときの動作はCut
→Undo
のようになります。
(ヒント)Acmeの設定
Acmeにはユーザーによる設定ファイルはありません*5。フォントや一部の挙動はコマンドラインオプションで指定します。
$ acme -a -f /mnt/font/GoRegular/14a/font -F /mnt/font/GoMono/14a/font
上の例で挙げたオプションは、それぞれ以下の意味を持ちます。
-a
改行したときに自動でインデントする(Go Playgroundの挙動と同じ)-f
プロポーショナルフォントを設定-F
等幅フォントを設定
起動直後のAcmeはプロポーショナルフォントで文字を描画しています。ウィンドウのヘッダにFont
と入力して実行すると、プロポーショナルフォントと等幅フォントを切り替えます。上のコマンドラインでは/mnt/fontというUnixでは見慣れないディレクトリが使われていますが、Acmeはフォントとして/mnt/fontで始まるファイルが渡されるとOSが管理するフォントを探しに行くので、このディレクトリを事前に作っておく必要はありません。
fontsrvコマンドで、具体的にどのようなフォントが利用可能か確認できます。
# フォントの名前を確認する % fontsrv -p . | grep Go Go-Bold/ Go-BoldItalic/ Go-Italic/ GoMedium/ GoMedium-Italic/ GoMono/ GoMono-Bold/ GoMono-BoldItalic/ GoMono-Italic/ ... # サイズを確認する(aがあるものはアンチエイリアス) % fontsrv -p GoMono 4/ 4a/ 5/ 5a/ 6/ 6a/ ...
ウィンドウの移動
せっかくなのでここからのスクリーンショットはGoフォントにしました
縦に並んでいると狭いので、main.goのウィンドウを左に移動させましょう。ファイル名の左にある四角をドラッグするとウィンドウを移動できます。そうすると、右の下が空くので、
go run main.go
と書いて中クリックで実行してみましょう。正しく進めていれば以下の画面になるはず。
テストを書く
次にテストを書いてみます。main_test.goファイルを作成して、以下のコードを書いてください。
package main import ( "testing" ) func TestAdd(t *testing.T) { n := Add(1, 3) if n != 4 { t.Errorf("Add(1, 3) = %d; want 4", n) } }
今度はmain_test.goを開いているウィンドウのヘッダに
go test
と入力して実行します。Add
関数はまだ実装していないので、当然ですが以下の画面になるはず。
このとき、エラーメッセージにファイル名と行番号が書かれたテキストがあるので、マウスでmain_test.go:8:7を右クリックしてみましょう。そうするとエラーが発生したソースコードの位置にジャンプして行が選択状態になるので、エラーになった行の前後をみて、原因を調べられますね。移動先のファイルが閉じている場合は、新しいウィンドウで開かれます。
今回のエラーはAdd
が見つからないことが原因なので、main.goにAdd
を実装しましょう。Acmeの動作をみるため、ここでは意図的に、常に4を返すようにします。
func Add(i, j int) int { return 4 }
実装した後にPut
で保存したら、main_test.goのヘッダに書いていたgo test
がまだ残っていると思いますので、もういちどクリックするとテストが通るはずです。
テーブルテスト
ところで、上記のAdd
は常に4を返すバグがあります。テストを以下のように変更して
func TestAdd(t *testing.T) { tests := []struct{ i, j int n int }{ {1, 3, 4}, {-1, -3, -4}, {0, 0, 0}, } for _, tt := range tests { n := Add(tt.i, tt.j) if n != tt.n { t.Errorf("Add(%d, %d) = %d; want %d", tt.i, tt.j, n, tt.n) } } }
これを保存してからgo test
すると以下のような出力になるはずです。
これまでと同じように、main_test.go:19を右クリックしてエラーの箇所(この場合はErrorfの行)にジャンプします。Goでは基本的に<filename>:<line>[:<offset>]
のルールで行を出力するので、そのままAcmeでジャンプできるようになっています。この程度の行数だとあまり便利さを感じませんが、もっとテストケースがあるファイルの場合は嬉しいはず。
テストが失敗する理由は見れば分かりますね。常に4を返すところが問題なので、return i + j
に変更してテストをすれば通ります。
(ヒント)スクロールの方法
Acmeはキーボードの上下キー、またはスクロールバーの上でマウスの左右ボタンをクリックするとページ単位のスクロールを行います。また、スクロールバーの上でマウスの中ボタンをクリックすると、クリックした位置まで移動しますし、中ボタンを押しながらマウスを上下に移動させると、マウスの移動にあわせて画面がスクロールします。個人的には、ファイルを頭から読むときはキーボードの上下で移動して、それ以外はマウスの中ボタンを使うことが多いですね。
キーボードなどでカーソルを上下に移動する方法は(たぶん)ありません。最初は常にキーボードから手を離さないようにしたかったですが、慣れてしまえば全然困りませんし、広い範囲を選択するときはマウスの方が早いと思います。
gofmt
ところでAcme自体は汎用のテキストエディタなので、保存時にgofmtで整形するなどは行いません。なので少し面倒ですが手動でgofmtしておきましょう。gofmtする方法は複数あります。最も簡単なのは、どこか適当な場所に
go fmt
と書いて実行した後に、差分のあったファイルをGet
で再読み込みする方法です。それでもいいのですが、より応用がきく方法として、テキスト全体を選択した状態でヘッダ部分に
| gofmt
と書いて実行すると(先頭の|
が重要です)、選択した部分にgofmtを適用します。この方法なら、一部の範囲をソートしたければ選択してから| sort
とすればいいし、ほかにも色々と便利に使えます。
ファイル全体を選択するのが面倒な場合は、
Edit , | gofmt
として実行するだけでもいいです。Edit
はAcme独自のコマンドで、Edit 1,$
とすると1行目から最終行までを選択状態にしますが、1
と$
は省略できるので,
だけでファイル全体を選択する動作になります。
(ヒント)別のターミナルからファイルを渡したい
plumberが動作していればBコマンドで開けます。
最初のほうで紹介したacmeeditリポジトリのパッケージから起動すると、plumberが動作していなければ自動で実行するようになっているのでお手軽です。Bコマンドはファイル名だけではなく行番号なども一緒に渡せるので、以下のような開き方ができます。
% B main.go % B main.go:20 % B 'main.go:/^func main/'
エディタの終了
では最後にエディタの終了方法です。ここまで進めたならもう分かると思いますが、Acmeのいちばん外側にあるヘッダからExit
を実行すれば終了します。ただし、変更されたあと保存していないファイルがが残っている場合はExit
が中断するので、Put
で保存するかDelete
で保存せずに閉じてからExit
しましょう。
おすすめする使いかた
Acmeでは拡張を書かなくても面白い使い方ができますが、特におすすめするのは、独自のスクリプトで<filename>:<line>[:<offset>]
形式のテキストを出力することです。簡単なものだと
grep -n '<pattern>' file /dev/null (git grepでも同じ)
とすると<pattern>
にマッチした行ごとに<filename>:<line>
形式の行番号も出力するので、修正する場所をあらかじめ絞り込むときに使うと便利です。grepの結果が出力された+Errors ウィンドウもただのテキストなので、不要な行は削除しておくと、より見通しがよくなります。Plan 9にはgという、カレントディレクトリのソースコードをgrepする便利なラッパーコマンドがありますし、Rob Pike氏が紹介していたfやcfといったコマンドも、やっていることは同じですね。
ほかにも、grepした結果をブックマークのように残しておくと便利な場合があります。例えばfunc Add
をgrepすると
/home/lufia/src/acmetut/+Errors
というタイトルになるのですが、このウィンドウを開いたままfunc main
をgrepすると、結果はfunc Add
のウィンドウに追記されます。これはこれで便利なときもあるものの、量が多くなると見通しが悪くなるので、区切りのいいところで+
の前に任意の文字列を入れておきましょう。たとえばヘッダを編集して
/home/lufia/src/acmetut/Add+Errors
としておくと、次回以降にgrepしたときは新しく/home/lufia/src/acmetut/+Errors
ウィンドウが作られるので、Add
のウィンドウをそのまま残すことができます。これは比較的規模の大きいコードを読み進めるときに便利ではないかなと思います。
Acme上でターミナルを開く
Acme上でそのままbash
などを実行しても、すぐに終了してしまって使えません。winという特別なコマンドが用意されていて、winを実行すると新しいウィンドウでシェルのプロンプトが表示されて、そのままシェルとして利用できます。
シェルを実行しているウィンドウもただのテキストなので、コマンドの出力結果などを自由に編集できますが、Unixのターミナルと異なりヒストリや色などには対応していません。ヒストリについては "
(quote1) と ""
(quote2) が用意されていて、winの履歴からプロンプトのような行をgrepするコマンドがあるので、これを使うことになるでしょう。
色や装飾については、コマンドごとに無効化していく必要があってやや面倒です。次回以降のどこかで紹介するかもしれません。
Acmeのターミナルでgit commitしたい
Russ Cox氏によりeditinacmeコマンドが公開されているので、これをEDITOR環境変数またはGIT_EDITOR環境変数にセットします。
% go install 9fans.net/go/acme/editinacme@latest % export GIT_EDITOR=editinacme
次の予定
基本的な操作のチュートリアルはこれで終わりです。次回はAcmeでコードリーディングすると便利だという話を書こうと思っていますが、先にGitを使えたほうが便利かもしれないので順番は前後するかもしれません。
過去にもAcmeの使い方を書いた記事があるので、こちらはチュートリアルではありませんが、興味があれば読んでみてください。