Plan 9とGo言語のブログ

主にPlan 9やGo言語の日々気づいたことを書きます。

Acmeエディタの使い方

この記事は本来Plan 9 Advent Calendar 2019の11日目でしたが、今は27日ですね、はい...すいません...

Acmeエディタ、使ってみたけど何もわからないという声をよく聞くので、基本的な使い方を説明します。この記事ではplan9portのacmeを使うことを想定していますが、Plan 9acmeでも基本的には同じです。

使い方

ターミナル等から起動すると、カレントディレクトリのリストを含んだwindowが表示されます。

$ acme

f:id:lufiabb:20191226133203p:plain
起動直後の画面

Acmeはタイル状にファイルやディレクトリを扱います。この画面では、右側にホームディレクトリを表示していて、1つのファイルごとにタグという青い領域(tag)とファイル内容が書かれた黄色い領域(body)を持ちます。また、カラム毎やウィンドウ全体にもタグ領域が存在します。

操作方法

Acmeではマウスを多用することになります。マウスは3つボタンが必要です。ホイールでもボタンとして反応するなら支障ありませんが、Magic Trackpadなどボタンの同時押しができないものは、Acmeを使うには向かないのでお勧めしませんが、macOSの場合はキーボードと合わせてボタンを押すことで他のボタンとして扱えます。

  • 2ボタン: Altキーを押しながら1ボタン
  • 3ボタン: Cmdキーを押しながら1ボタン

ボタンは左から1, 2, 3ボタンと呼びます。1ボタン(左)はクリックした場所にカーソルを移動させたり範囲を選択したりなど、よくある操作を行います。ファイル内容の領域でキーボードから文字を入力すると、ファイルを開いている場合はウインドウが編集済み(dirty)に変わります*1。タグ領域のファイル名より左側にあるが青くなっている場合、そのファイルは編集した後に保存していない状態を表します。

マウスの2ボタン(中)は選択したテキストまたは前後スペースまでのテキストを実行します。例えばExitの上で2ボタンをクリックすると、(何も編集中でなければ)Acmeが終了します。Acmeは画面に表示しているテキスト(上の画面ではNewcol, Xerox, Snarfなど)を全て編集できるので、Goのコードを書いているウィンドウのタグ領域に

/path/to/file ... Del Snarf | Look go test

のように追加して、go testを選択した状態で2ボタンを押す(または2ボタンを押しながら選択して離す)とコマンドを実行できます。こういったよく使うコマンドを、タグ領域にボタンとして置いておくと、2ボタンを押すだけで各種コマンドが実行できるので便利です*2。2ボタンで実行するコマンドは、開いているファイルまたはディレクトリと同じ階層をカレントディレクトリとします。

Acmeには、以下のような組み込みコマンドがあります。

コマンド 説明
Get ファイルを再読み込みする
Put ファイルを保存する
Del ウィンドウを閉じる(編集中ならエラー)
Delete 編集中でもウィンドウを閉じる
Font プロポーショナルフォント等幅フォントを切り替える
Edit ed(1)コマンドのようなものを実行
Snarf 選択したテキストをコピー(Snarf bufferに残る)
Cut 選択したテキストをカット(Snarf bufferに残る)
Paste Snarf bufferの内容をペースト
Undo ファイルの変更を元に戻す
Redo ファイルの変更をやり直す
Look Look xxxとするとxxxをファイルから検索する
New 新しいウィンドウを開く
Zerox 同じウィンドウを複製する(編集中の状態も共有)
Newcol カラムを作成する
Delcol カラムを閉じる
Dump ウィンドウの状態を$HOME/acme.dumpに書き出す(acme -lオプションで読み込む)
Exit Acmeを終了する

この他にも色々な組み込みコマンドがあるので、知りたい人はacme(1)を読んでください。これらのうち一部しかタグ領域に表示されていませんが、タグ領域はただのテキストなので、自分で入力すればコマンドとして使うことができるようになります。以前はタグ領域を複数行にできませんでしたが、今は何行でも大丈夫です。また、これらの組み込みコマンド以外にも、Acmeファイルサーバを扱うコードを書けば、Language Serverクライアントなど任意の操作を行える拡張を作ることもできます。

3ボタン(右)は、ファイルを開く・テキストを検索する、など色々な操作に使います。

  1. 選択したテキストと同名のファイルやディレクトリがあればそのファイルを開く
  2. URLのようであれば(実際はplumber(4)により)ブラウザでURLを開く
  3. どれでもなければファイルの内容からテキストを検索する

のように動作します。この3ボタンの操作も、Acmeファイルサーバを扱うコードを書けば好きなようにカスタマイズできます。通常はこの挙動で困りませんが、ファイルが開かれてしまうと困る場合はLookコマンドを使いましょう。

Mouse Chording

3ボタンマウスを使っている場合、Snarf, Cut, Pasteはマウス操作で行えます。

  • 1ボタンを押しながら2ボタン: 選択したテキストをCut
  • 1ボタンを押しながら3ボタン: コピーしたテキストをカーソル位置にPaste
  • 1ボタンを押しながら2ボタンでCutした後、1ボタンを押したまま3ボタン: Snarf

この操作は慣れてくると非常によく使うことになりますが、Magic Trackpadなどでは同時押しができないため使うことができません。また、ホイールだと誤ってスクロールしてしまうことがあり、慣れるまでは誤動作にイライラします。なので、なるべく3ボタンマウスを用意した方が良いと思います。

現在、業務では、前職のこの記事に書いたように、左手にトラックパッド、右手に3ボタンマウスを使っています。3ボタンマウスだけではブラウザなどでスクロールする際に不満がありましたが、その辺りも解決するのでおすすめです。

ファイル操作

Acmeは、ファイル名:行番号の書式をうまく扱います。この書式で書かれたテキストの上で3ボタンをクリックすると、行を選択した状態でファイルを開きます。これはコードリーディングにとても便利です。Goはコンパイルエラーやスタックトレースなどファイルを参照する場面でファイル名:行番号を使っているので、エラーメッセージに含まれるファイル名を3ボタンクリックすると問題の行に移動できます。または、git grep -nも同じ書式なので、適当な場所(通常はタグ領域)にgit grep -n xxを書いて2ボタンで実行させて、その検索結果からそのまま3ボタンでファイルを開けます。もちろん検索結果もただのテキストなので、不要だと思った行は実行結果から削除すれば邪魔になりません。

これ以外にもファイル名:開始行,終了行で範囲を選択できるし、ファイル名:/正規表現とするとファイルの先頭から正規表現にマッチしたテキストを探したり、ファイル名:-/正規表現でファイルの末尾から探したりできます。ファイル名を省略して:行番号とすると、そのウィンドウが開いているファイル内容から該当する行へ移動します。

ターミナル

Acme上でシェル実行を行うwinというコマンドがあるので、タグ領域などにwinと書いて2ボタンをクリックすると、ターミナルを起動することができます。このターミナルはいわゆるダム端末というもので、ANSIエスケープシーケンスなどは扱えませんが、代わりに全てテキストなのでコマンドの出力結果も編集することができます。

ただ、この辺り、Gitなど最近のツールとすこぶる相性が悪いので、個人的に使っている設定を近いうちに紹介します。

Editコマンド

Editコマンドはテキストをコマンドで編集するときに使います。Editはスペースに続けてed(1)に似たコマンドが必要です。よく使うコマンドをここでは紹介しますが、全部は無理なので、興味があればsam(1)を読んでください。

Editの書式は

Edit [address] [command]

となっていて、[address]を省略するとファイル内容の現在選択している範囲がEditコマンドの編集対象として使われます。いくつか例を挙げます。

// ファイル全てのgetをGetに置き換え
Edit 1,$ s/get/Get/g

// これでも同じ(カンマは省略できない)
Edit , s/get/Get/g

// 10〜20行目を選択するだけで何も変更しない
Edit 10,20

// 行末にスペースがあれば削除
Edit , s/ +$//g

これである程度の編集は可能ですが、特定範囲内の一部だけ編集したいなど、もっと複雑なコマンドのためにループと条件分岐も用意されています。

ループと条件分岐

ループはx//またはy//で書きます。

x/regexp/command
y/regexp/command

例えば<...>を全て取り除きたい場合は以下のように書きます。

Edit , x/<[^>]+>/d

x//s///gと似ていますが、ループはマッチした部分ごとに任意のEditコマンドを記述できる点が異なります。実際に上の例では、テキスト置換(s)ではなくテキスト削除(d)を使っています。また、編集対象となる範囲をマッチしたテキストの範囲に再設定するので、後続のコマンドはその範囲を対象として動作するようになります。ややこしいですがx//もコマンドなので、x//でマッチした範囲をさらにx//で絞り込むこともできます。

y//正規表現で分割した文字列ごとにコマンドを実行できます。スペース区切りのテキスト末尾にカンマを入れたい場合はこのように書きます。a/text/はテキストを範囲の末尾に追加するコマンドです。

Edit y/[ \n]+/a/,/

条件分岐はg//またはv//を使います。

g/regexp/command
v/regexp/command

g//は編集対象範囲に、指定した正規表現にマッチするテキストがあればコマンドを実行します。v//は逆にマッチするテキストがなければコマンドを実行します。

最後に複雑な例をひとつ。

// Goのimportにあるghe.example.com/pkgをgithub.com/pkgに置き換え
Edit /^import \($/+1;/^\)/-1y/\n/g/ghe\.example\.com\/pkg/s/ghe\.example\.com/github.com/

こういった複雑なコマンドを組み立てる場合は、g/regexp/=#のように=#コマンドを使うと、どの部分がマッチしていくのか確認しながら進めることができて便利なのでぜひ使ってみてください。

*1:ディレクトリの場合、またはファイル名の末尾が+Errorsの場合は変化しない

*2:コマンドはrc(1)経由で起動するので=はクオートが必要