Acme エディタはPlan 9 のためにRob Pikeによってデザインされたエディタで、現在はUnix にも移植されています。慣れると非常に快適なんですが、既存のエディタとは操作感が全然違うので、初見では何をすればいいのか分からないとよく聞きます。なので、この記事ではGoのコードを実際に書きながら、Acme をどうやって使っていくのかを紹介していきます。ただし、Acme は色々と便利な使い方があり、ひとつの記事で書き切るのは大変なので、複数の記事に分けて公開していこうと思っています。今のところ以下の内容を予定していますが、順番や内容は変更するかもしれません。
Acme の基本的な使い方(この記事)
コードリーディングするときの使い方
win
で生活する方法、拡張機能 の紹介など
ところでYouTube では、Russ CoxがAcme を使っている様子を動画にしているので、見てみると学びがあるかもしれません。
VIDEO www.youtube.com
次もYouTube の動画で、RussがAcme でプレゼンしている様子です。Acme の拡張*1 でコマンドをフックすることにより実現しています。
VIDEO www.youtube.com
一時期、9fansで話題になっていましたが、この動画でタイトル文字のところに使っているフォントの書体が違うのはフォントをハックしているからで、Acme には一部の文字だけを装飾する機能はありません。
基本的な使い方
まずはacme を実行してみましょう。Acme の実行にはplan9portが必要ですが、インストール自体はPlan 9 from User Space(plan9port)を使う に書いたのでここでは省略します。
Acme はターミナルで
% acme
とするだけで起動します。ここでエラーになった場合はPLAN9 環境変数 が設定されていないことが原因かもしれないので、変数の値を確認してみてください。
Acme の起動自体はこれだけなのですが、素の状態ではフォントが汚いとか、必要な9Pサービスが実行されていなくて使いづらい面があるので、macOS の人は以下のアプリケーション*2 から使ってもらうと便利かもしれません。
github.com
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 にはユーザーによる設定ファイルはありません*5 。フォントや一部の挙動はコマンドライン オプションで指定します。
$ acme -a -f /mnt/font/GoRegular/14a/font -F /mnt/font/GoMono/14a/font
上の例で挙げたオプションは、それぞれ以下の意味を持ちます。
起動直後の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 といったコマンドも、やっていることは同じですね。
blog.lufia.org
ほかにも、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 の使い方を書いた記事があるので、こちらはチュートリアル ではありませんが、興味があれば読んでみてください。
blog.lufia.org