GNU Emacs入門

Emacsの使い方についてまとめてみる。

CtrlとCapsLockの位置を入れ替える

EmacsはCtrlキーの同時押しを頻繁に行うため、左Ctrlと左CapsLockの位置を入れ替えておくとよい。 Windowsであれば、Sysinternals Ctrl2capChange Keyが使える。

インストール

ここでは基本的にターミナルでの利用を想定し、GUI版はインストールしないこととする。

UbuntuDebianの場合

$ sudo apt-get install emacs24-nox emacs24-el

CentOSFedoraRHELの場合

$ sudo yum install emacs-nox emacs-el

noxはwithout X supportを意味し、GUI版が必要とするパッケージを含まないものとなっている。

Emacs Clientを使う

Emacsは単体での起動がやや重いため、バックグラウンドで本体をデーモンとして動作させておき、適宜クライアント(emacsclient)から繋ぐようにするとよい。 emacsclientコマンドに次のようなオプションを与えて実行すると、デーモンが存在しないとき自動的に起動させることができる。

$ emacsclient -nw --alternate-editor="" foobar.txt

上のオプションを毎回打つことは現実的ではないため、.bashrcなどで次のようなエイリアスを設定しておくとよい。

alias ec='emacsclient -nw --alternate-editor=""'

上を設定した場合、Emacsは次のようにして起動できる。 初回実行時のみデーモンプロセス(emacs --daemon)が同時に起動し、以降はクライアントが既存のデーモンに繋ぎに行くようになる。

$ ec foobar.txt

キーバインド

Emacsでは、C-xは「CtrlとXの同時押し」、M-xは「AltとXの同時押し」または「Escを押した後X」、C-M-xは「CtrlとAltとXの同時押し」または「Escを押した後CtrlとXの同時押し」を意味する。 Escの入力はC-[で代用できる。 したがって、M-xは「C-[を押した後X」としてもよい。

M-xを押すことでコマンド名によるコマンド実行ができる。 たとえば、M-xを押した後describe-bindingsと打つと、キーバインドと対応するコマンド名の一覧を表示させることができる。

ファイルの保存はC-x C-sEmacsの終了はC-x C-cである。 なお、起動しているデーモン自体を終了させるにはM-x kill-emacsを実行する。 基本的なカーソル操作については、C-h t(help-with-tutorial)で表示されるチュートリアルを読むとよい。

各コマンドの実行前にC-u 数字を押すことで、数引数を与えることができる。 たとえば、C-u 4 M-dと押すと一度に4単語を削除(正確にはkill)することができる。 しかし、Emacsではこのような引数指定による操作より、後述するリージョン操作やキーボードマクロが使われることが多い。

リージョン操作

リージョンとは、ある位置から現在のカーソル位置までの範囲を表す概念である。 起点となる位置はC-@で指定することができ(これをmarkするという)、そこから移動したカーソル位置までが選択中のリージョンとなる。 リージョンを選択した状態で、たとえばC-wを押すとリージョンの内容がkillされる。

Emacsでは切り取り(cut)、貼り付け(paste)のことをkill、yankという。 killした文字列はkill ringと呼ばれるリスト構造によって管理されており、C-y M-y M-y ……と押すことで順に選択しながらyankすることができる。 なお、削除せずkill ringへの追加のみを行う(いわゆるコピー)場合はM-wを使えばよい。

例: 2回killした後、yankの候補を選択する

f:id:inaz2:20151225214528g:plain

なお、undoはC-/ C-/ ……と押していくことで行うことができる。 ただし、Emacsのundoはやや特殊で、undo自体のundoがredoを表す。 undoの連鎖は適当なコマンドかC-gで止めることができ、そこからのundoはそれまでのundoを取り消す動作となる。

リージョンは矩形として操作することもできる。 たとえば、リージョンを選択した状態でC-x r dと押すと、markした位置から現在のカーソル位置からなる矩形領域をkillすることができる。 この他、markした位置から縦に移動してC-x r tを押すと、それぞれの行に同一の文字列を挿入することができる。

例: 複数行への文字列挿入

f:id:inaz2:20151225214600g:plain

バッファ全体をリージョンとして選択するにはC-x hを押す。 続けてC-wを押せば全削除となる。

括弧やクオートで囲まれた領域の操作

Emacsでは、括弧(あるいはクオート)で囲まれた領域をLispにならいS式(sexp)として扱う。 カーソルを括弧の位置に置きC-M-fC-M-bを押すと、対応する括弧の位置に前後移動することができる。 また、C-M-kを押すと括弧を含めた領域をkillすることができる。

検索・置換

検索はC-sで行うことができる。 文字を入力した後繰り返しC-sを押すことで、次のマッチ箇所に移動する。 また、最初にC-s C-sと押すと、直近の検索文字列での検索を再開することができる。 検索中にC-%を押すと、検索中の文字列を指定した文字列で置換することができる。 さらに、検索中にM-s oと押すと、マッチ箇所の一覧を別のバッファで表示させることができる(occur)。

例: 文字列検索の後occurでマッチ箇所を表示し、適当なマッチ箇所に移動する

f:id:inaz2:20151225214622g:plain

Emacs中で複数ファイルにまたがるgrepを行うことも可能である。 具体的には、M-x grepを行った後、grep -nH -e foobar *grep -nH -e foobar -rのように入力することで、対象のファイル群からfoobarを検索した結果を別のバッファで表示させることができる。 また、表示されたバッファに切り替え適当な行の上でEnterを押すと、実際にそのファイルの対応箇所を開くことができる。

キーボードマクロ

C-x z z ……と連続して押すことで、直前の操作を繰り返すことができる(repeat)。

また、一連の操作を記憶させて繰り返すこともでき、これはキーボードマクロと呼ばれる。 具体的には、C-x (を押して記録を開始し、適当な操作を行った後、C-x )で終了する。 この状態でC-x e e ……と連続して押すことで、記録した操作を繰り返すことができる。

キーボードマクロは慣れれば非常に強力な機能である。

例: 行末移動(C-e)と行末削除(C-k)のセットを記憶させ、繰り返す

f:id:inaz2:20151225214650g:plain

ウィンドウ分割

C-x 3と押すと、画面を二つのウィンドウに縦分割することができる。 分割されたウィンドウの間はC-x oで移動でき、選択中のウィンドウはモードラインの色で判別できる。 現在選択しているウィンドウはC-x 0で閉じることができる。 また、C-x 2で横分割することもでき、複数回行うことで三つ以上のウィンドウに分けることもできる。

バッファ、ウィンドウ、フレーム

Emacsにはバッファとウィンドウとフレームと呼ばれる概念がある。 バッファはファイルの内容や種々の出力結果を保持するものであり、ウィンドウは分割された一つ一つの画面を指す。 バッファとウィンドウは独立に管理されているため、二つのウィンドウに同じバッファの異なる箇所を表示させるといったことができるようになっている。 フレームはGUI上でのひとつのウィンドウに対応する概念であるが、凝ったことをしない限り意識しなくてもよい。

C-x C-fを押すことで、ファイルを新しいバッファとして開くことができる。 また、C-x bを押すことで、現在のウィンドウで表示するバッファを別のものに切り替えることができる。 C-x C-bを押せば、バッファの一覧を別のバッファに表示することもできる。

動的補完

単語を途中まで入力した状態でC-/を押すと、先頭が一致する単語を自動的に探索し補完することができる。 さらに続けて繰り返しC-/を押せば、他の候補を順に切り換えることができる。 この機能はdynamic abbreviation(動的補完)、略してdabbrevとも呼ばれる。

プログラムコードで直前にある変数名を繰り返し入力する場面などにおいて、この機能は非常に便利である。

例: 前後にある単語を動的補完する

f:id:inaz2:20151225214716g:plain

シェルコマンド実行

Emacsでは、シェルコマンドの実行結果もバッファとして扱うことができる。 たとえば、M-&を押した後ls -alと打つと、コマンドの実行結果が別のバッファに表示され、通常のファイルと同じように扱うことができる。 さらに、bashpythonirbを実行した場合は、インタラクティブシェルとして操作することができる。 つまり、シェルを操作しつつその出力をコピーし、別のファイルにyankするといったことが可能である。

例: シェルを起動し、実行結果を編集中のファイルにコピーする

f:id:inaz2:20151225214742g:plain

M-|は選択中のリージョンを標準入力としてコマンドを実行する。 たとえば、適当な文字列をリージョンとして選択した状態でM-|を押した後base64と打てば、選択した文字列をBase64エンコードした結果を別のバッファに表示させることができる。

また、C-u M-&C-u M-|と押せば、現在のカーソル位置に実行結果を挿入、あるいは、選択中のリージョンを実行結果で置き換えることもできる。

コメントアウト、自動インデント

リージョンを選択した状態でM-;と押せば、各行をコメントアウトすることができる。 また、すべての行がコメントアウトされている場合はコメント記号を取り除くことができる。

リージョンを選択した状態でM-x indent-regionを実行すると、リージョン内のインデントを自動で整形することができる。

Emacsではファイルの拡張子に合わせて適当な動作モードが選択されるようになっており、コメント記号や自動インデント、シンタックスハイライトはこの動作モードに応じて適当なものが使われる。

便利機能とカスタマイズ

EmacsEmacs Lispと呼ばれる言語を使い、独自にカスタマイズすることができる。 設定ファイルは~/.emacs~/.emacs.el~/.emacs.d/init.elのうち好きなものを使えばよい。

以下に、いくつかの便利機能とカスタマイズ例を紹介する。

whitespace-mode

whitespace-modeを有効にすると、空白文字などを強調表示することができる。 次の例は、行末スペース、タブ文字、先頭・末尾の空行をハイライト表示する。

(global-whitespace-mode t)
(setq whitespace-style '(face trailing tabs empty))

buffer-menu

標準でC-x C-bバインドされているlist-buffersはバッファ一覧をウィンドウ分割して表示するため、やや操作が煩雑となる。 一方、buffer-menuは同一ウィンドウでバッファ一覧を表示する。 そこで、これを代わりに使うようにすると便利である。

;; let "C-x C-b" open in the current window
(global-set-key (kbd "\C-x\C-b") 'buffer-menu)

dired

C-x dと押すと、Emacsバッファでディレクトリの一覧を表示し、ファイルを開いたりリネームしたりすることができる(dired)。 しかし、デフォルトでは毎回ディレクトリを指定する必要があり、手間である。 また、C-x C-dは打ちやすいキーバインドであるにも関わらず、単純にファイル一覧を表示する機能となっており使い道に乏しい。 そこで、C-x C-dを押したとき、カレントディレクトリに対してdiredを行うようにしておくと便利である。

;; let "C-x C-d" dired current directory
(defun dired-here ()
  (interactive)
  (dired "."))
(global-set-key (kbd "\C-x\C-d") 'dired-here)

ffap-bindings

FFAP(Find File At Point)は、C-x C-fでファイルを開くとき自動でカーソルの下にあるファイルパスを補完する機能である。 地味に便利なので、自動で有効にしておくとよい。

(ffap-bindings)

vc-git-grep

M-x vc-git-grepと打つと、git grepを使ってgitリポジトリを検索することができる。 このコマンドは毎回対象ファイルやディレクトリを確認してきてうるさいため、一発でリポジトリ全体を検索できるようにしておくと便利である。 具体的には、次のようなコマンドを作り、M-x git-grepで呼び出す。

;; global vc-git-grep
(defun git-grep (regexp)
  (interactive "sSearch for: ")
  (vc-git-grep regexp "*" (vc-git-root default-directory)))

etags

巨大なソースコードツリーを読むとき、毎回grepで目的の関数が定義された箇所を探すのは不便である。 etagsを使うと、あらかじめ関数の定義箇所のデータベースを作り、これをもとに定義箇所へジャンプすることができる。 これはタグジャンプと呼ばれる。

ソースコードのルートディレクトリで次のコマンドを実行することで、定義箇所のデータベースであるTAGSファイルが作成できる。

$ find * | etags -

あとは、関数名の上にカーソルを移動させた状態でM-.を押すと、その関数の定義箇所にジャンプできる。 初回のみTAGSファイルのパスを聞かれるので、作成したファイルを指定する。 なお、M-*を押せばジャンプ前の位置に戻ることができる。

また、次のようなキーバインドを定義し、定義箇所を別ウィンドウで表示するようにしておくと便利である。

;; let "M-." open other window
(global-set-key (kbd "M-.") 'find-tag-other-window)

etagsは参照元から定義箇所へのジャンプのみが可能であり、定義箇所から参照元をリストアップすることはできない。 参照元のリストアップも行いたい場合には、GNU GLOBAL(gtags)RTagsを使うとよい。

flymake

flymakeはソースコードに対してリアルタイムでシンタックスチェッカを走らせ、エラー箇所の強調表示を行う機能である。 有効にするには、対応している拡張子のファイルを開いた状態でM-x flymake-modeを実行すればよい。

ただし、デフォルトではGUIでの利用が想定されており、CUIではカスタマイズを行わないと使いづらい。 下の設定は、次のようなカスタマイズを行うものである。

  • M-n/M-pで前後のエラー箇所に移動する
  • エラー箇所の上に0.9秒カーソルを置いたとき、エラー内容をミニバッファに表示する
  • C/C++について、Makefileがなくても動くようにする
;; flymake
(require 'flymake)

(defvar flymake-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd "M-n") 'flymake-goto-next-error)
    (define-key map (kbd "M-p") 'flymake-goto-prev-error)
    map))
(add-to-list 'minor-mode-map-alist (cons 'flymake-mode flymake-mode-map))

(custom-set-variables
 '(help-at-pt-timer-delay 0.9)
 '(help-at-pt-display-when-idle '(flymake-overlay)))

(defun flymake-simple-generic-init (cmd &optional opts)
  (let* ((temp-file  (flymake-init-create-temp-buffer-copy
                      'flymake-create-temp-inplace))
         (local-file (file-relative-name
                      temp-file
                      (file-name-directory buffer-file-name))))
    (list cmd (append opts (list local-file)))))

(defun flymake-simple-make-or-generic-init (cmd &optional opts)
  (if (file-exists-p "Makefile")
      (flymake-simple-make-init)
    (flymake-simple-generic-init cmd opts)))

(defun flymake-c-init ()
  (flymake-simple-make-or-generic-init
   "gcc" '("-Wall" "-Wextra" "-fsyntax-only")))
(defun flymake-cc-init ()
  (flymake-simple-make-or-generic-init
   "g++" '("-Wall" "-Wextra" "-fsyntax-only")))

(add-to-list 'flymake-allowed-file-name-masks '("\\.c\\'" flymake-c-init))
(add-to-list 'flymake-allowed-file-name-masks '("\\.\\(?:cc\\|cpp\\)\\'" flymake-cc-init))

行末C-fで動的補完する

動的補完は非常に便利なため使用頻度も高くなるが、デフォルトではM-/バインドされておりやや押しにくい。 そこで、行末でC-fした場合に動的補完するようにしておくと便利である。 詳細は下記を参照。

まとめ

CtrlとCapsLockの入れ替えやemacsclientのエイリアスなど多少の準備が必要ではあるが、これらさえクリアできればEmacsは高機能で便利なエディタである。 リージョン操作と検索・置換、動的補完に慣れることができれば、快適にテキスト編集を行うことができるだろう。

関連リンク