公開日: 2021年8月22日 / 最終更新日: 2023年10月5日

GNU Emacs TIPS

Emacs 自体は packages から簡単にインストールすることができるのですが、快適さを求めて設定にはまりだすと、そこから先はエンドレスです。この記事では、そんな中から得られた戦果の断片をとりとめもなく羅列してゆこうと思います。必要なところだけ摘まんで持ち帰っていただければ幸いです。Emacs Lisp はあまり書きなれていないので、間違えていたらすみません。

なお、この記事で「ウィンドウ」といったら Emacs でいうところのウィンドウを指します。ウィンドウマネージャや Windows などで一般にいうところのウィンドウは、Emacs では「フレーム」と呼びます。

目次

参考書籍

はじめに、Emacs における墨場必携ともいうべき書を挙げておきます。
  • [1] 大竹智也『Emacs 実践入門 (第 2 版)』技術評論社, 2017
  • [2] るびきち『Emacs テクニックバイブル』技術評論社, 2010

Emacs 本体のインストール

# pkg install emacs-27

設定用ディレクトリの構造

Emacs の設定ファイルといえば ~/.emacs …とばかり思っていましたが、現在は ~/.emacs.d/init.el に書くのが一般的なようです。初期設定以外のファイル、例えばネットで入手した Emacs Lisp プログラムだとかオートセーブファイルだとかもこのディレクトリに突っ込むもののようです。私は下記のような構造にしています。
  • ~/emacs.d/
    • init.el ........ Emacs の初期設定ファイル
    • custom.el ........ 外観 (テーマやシンタックスハイライトなど) に関する設定
    • elpa/ ........ ELPA からダウンロードしたパッケージを格納するディレクトリ
    • elisp/ ........ その他の Emacs Lisp プログラムを格納するディレクトリ
    • backups/ ........ バックアップファイルを格納するディレクトリ
こうするには init.el に次のように書いておきます。
(setq custom-file "~/.emacs.d/custom.el")
(setq package-user-dir "~/.emacs.d/elpa/")
(add-to-list 'load-path "~/.emacs.d/elisp")
(add-to-list 'backup-directory-alist
  (cons "." "~/.emacs.d/backups/"))
(setq auto-save-file-name-transforms
  `((".*", (expand-file-name "~/.emacs.d/backups/") t)))

init.el のバイトコンパイル (罠もあるよ)

init.el はバイトコンパイルしておくと Emacs の起動時間の短縮が望めます。バイトコンパイルするには、Emacs を起動して M-x byte-compile-file と実行します。ファイル名を訊かれるので ~/.emacs.d/init.el を指定してください。またはコマンドラインから次のように実行してもコンパイルできます。

% emacs -batch -f batch-byte-compile ~/.emacs.d/init.el

コンパイルに成功すると、拡張子が .elc のファイルが生成されます。次回 Emacs を起動するときにはこの ~/.emacs.d/init.elc が読み込まれます。

要注意なのは同じディレクトリに .el と .elc があると .elc の読み込みが優先される点です。バイトコンパイルを忘れると、init.el をいくら更新しても設定が反映されず、いつまでも悩み続けるということになります (実体験)。これを回避するには、いくつかやり方があるみたいですが、手っ取り早いのは ~/.emacs.d/early-init.el に次のように書いておくことです。

~/.emacs.d/early-init.el
(setq load-prefer-newer t)

こうしておくと .elc よりも .el のほうが新しければ .el を読み込んでくれます。

package.el によるパッケージ管理

init.el に次のようにパッケージの配布元を書いておき、M-x package-list-packages を実行します。
(setq package-archives
  '(("gnu" . "http://elpa.gnu.org/packages/")
    ("melpa" . "http://melpa.org/packages/")
    ("org" . "http://orgmode.org/elpa/")))
(package-initialize)
するとパッケージの一覧が表示されるので、インストールしたいパッケージを選んでインストールを実行します。このバッファでの操作方法は次のとおり。
  • i: インストールするパッケージをマークする
  • U: アップグレードが必要なパッケージを自動的かつ一括でマークする (※)
  • x: マークしたパッケージのインストール・アップグレードを実行する
  • u: マークを解除する
  • d: インストール済みのパッケージ (Status が installed になっている) を削除する

※「U」マークかと思いきや「D」マークが付きます。何の頭文字ですかね?

この記事で紹介するパッケージの一覧

  • package.el

    ag, bind-key, company, company-statistics, helm, helm-ag, helm-gtags, helm-migemo, helm-swoop, imenu-list, migemo, ripgrep, sequential-command, smartrep, sr-speedbar
  • ports/packages

    ja-mozc-el-emacs27, ja-cmigemo, global, ripgrep, font-ricty
  • その他

    company-anywhere, company-dwim, company-same-mode-buffers, point-undo, redo+

言語環境とコーディングシステムの設定

言語環境は Japanese、コーディングシステムは utf-8 を指定しています。
(set-language-environment "Japanese")
(prefer-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-buffer-file-coding-system 'utf-8)
コーディングシステムの一覧は M-x list-coding-systems で確認できます。文字コード判定の際、prefer-coding-system にセットしたコーディングシステムが優先的に試行されます。優先順位は *scratch* バッファで coding-system-priority-list を評価することで確認できます。
(coding-system-priority-list) [C-j]
  (utf-8 japanese-shift-jis iso-2022-jp iso-2022-jp-2 iso-2022-7bit iso-latin-1 iso-2022-8bit-ss2 emacs-mule raw-text in-is13194-devanagari utf-8-auto ...)
ただし、文字コードを判定するのに十分な文字数がないと誤判定することがあるため、ファイルの先頭にコメント文として「-*- coding: utf-8 -*-」と書いておく方法があります。これをマジックコメントといい、例えば C 言語の場合は次のように記述します。
/* -*- coding: utf-8 -*- */
なお、コーディングシステムの設定は init.el の最終行に書いておかなければならない、と以前にどこかのサイトで読んだ気がしますが、先ほどググったかぎりでは、特にそのようなことはなさそうです。

フォント (Ricty)

フォントは Ricty というプログラミングに適したフォントを使用します。ports からインストールしてフォントキャッシュを更新し (fc-cache)、init.el に 1 行追加します。下記の例ではフォントサイズを 12pt としています。
# cd /usr/ports/japanese/font-ricty
# make BATCH=yes install
% fc-cache -v
(add-to-list 'default-frame-alist '(font . "Ricty 12"))
ちなみに、Emacs で利用可能なフォントは *scratch* バッファで下記を実行することで確認できます。
(dolist (x (font-family-list)) (print x)) [C-j]

日本語入力の設定 (Mozc)

かな漢字変換エンジンとして Mozc を使用します。Emacs での日本語入力については「Fcitx と Mozc で日本語入力」も参照してください。また、日本語入力時とそうでないときでカーソルの色を切り替えるようにしています。
(setq default-input-method "japanese-mozc")
(add-hook 'input-method-activate-hook
  (lambda() (set-cursor-color "SteelBlue4")))
(add-hook 'input-method-deactivate-hook
  (lambda() (set-cursor-color "gray40")))

2023年10月2日 追記: 4 行目の "input-method-deactivate-hook" はもともと "input-method-inactivate-hook" でしたが、改称された?ようなので deactivate に修正しました。inactivate では期待する動作になりません。

起動メッセージとツールバーを非表示にする

(setq inhibit-startup-message t)
(tool-bar-mode 0)

警告音を無効にする

(setq ring-bell-function 'ignore)

モードラインに行番号と列番号を表示する

(line-number-mode 1)
(column-number-mode 1)

モードラインからモードの表示を消す

モードラインからモードの表示を消したらモードラインではなくなってしまう訳ですが…私は非表示にしています、すみません。
(delete 'mode-line-modes mode-line-format)

タイトルバーにファイルのフルパスを表示する

(setq frame-title-format "%f")

バッファを再読み込みする

F5 キー押しでバッファを再読み込みするようにしています。
(defun revert-buffer-no-confirm ()
    "Revert buffer without confirmation."
    (interactive) (revert-buffer t t))
(global-set-key (kbd "<f5>") 'revert-buffer-no-confirm)

バックアップファイル (hoge~) とオートセーブファイル (#hoge#) を作らない

(setq make-backup-files nil)
(setq auto-save-default nil)
(setq auto-save-list-file-prefix nil)

何だか動作がもっさりしてるな…を解消する (かも)

(setq vc-handled-backends nil)
一体何のためにこの設定を入れたのかまったく思い出せなかったので、ググってみました。バージョンコントロールに関する設定らしいですが、有効になっていると Emacs の動作がもっさりになるようです。

「yes」「no」の代わりに「y」「n」を使用する

(fset 'yes-or-no-p 'y-or-n-p)

外部プロセス実行中に Emacs を終了させても確認メッセージを表示しない

ripgrep で検索中に Emacs を終了させるような場合が該当します。
(setq confirm-kill-processes nil)

拡張子をメジャーモードと関連付ける

(setq auto-mode-alist
  (append '(("\\.scr$" . text-mode)
            ("\\.txt$" . text-mode)
            ("\\.asm$" . asm-mode))
          auto-mode-alist))

行を折り返し表示する/しない

変数 truncate-lines と truncate-partial-width-windows に t をセットすると行の折り返し表示を抑制します。truncate-partial-width-windows はウィンドウが左右に分割されている場合の変数です。折り返し表示するかしないかは toggle-truncate-lines コマンドで切り替えることができます。これを適当なキーに割り当てておきます。
(setq-default truncate-lines t)
(setq-default truncate-partial-width-windows t)
(global-set-key (kbd "<f10>") 'toggle-truncate-lines)

ファイルの末尾で C-n を押したら新しい行を生成する/しない

変数 next-line-add-newlines に非 nil をセットすると、ファイルの末尾で C-n を押したときに新しい行が生成されます。私は nil にしています。
(setq next-line-add-newlines nil)

タブ文字の幅を設定する

(setq-default tab-width 4)

C/C++ モードと ASM モードのインデントを設定する

(defun my-c-c++-mode-init ()
  (setq c-basic-offset 4)
  (setq c-tab-always-indent t)
  (setq indent-tabs-mode nil))
(add-hook 'c-mode-hook 'my-c-c++-mode-init)

(defun my-asm-mode-init ()
  (setq indent-tabs-mode nil)
  (setq tab-width 4))
(add-hook 'asm-mode-hook 'my-asm-mode-init)

ローマ字入力で日本語をインクリメンタルサーチする (Migemo)

Migemo というソフトウェアを使うと、日本語をローマ字でインクリメンタルサーチすることができます。例えば「かぶって」という語をサーチするには C-s と打ってから「kabutte」と入力します。さらに、カタカナ英語を本場のスペルでサーチすることもできます。例えば「フルフェイスのヘルメットをかぶってギターを弾きたい」という文をサーチするには C-s と打ってから「fullface」と入力するとヒットします。

導入するには package.el から migemo を、ports/packages から ja-cmigemo をインストールし、init.el に次のように書きます。

(when (require 'migemo nil t)
  (setq migemo-command "cmigemo")
  (setq migemo-options '("-q" "--emacs"))
  (setq migemo-dictionary "/usr/local/share/cmigemo/utf-8/migemo-dict")
  (setq migemo-user-dictionary nil)
  (setq migemo-regex-dictionary nil)
  (setq migemo-coding-system 'utf-8-unix)
  (setq helm-migemo-mode 1)
  (migemo-init))

矩形リージョンを操る (rectangle-mark-mode)

いつものとおり C-Space でマークセットしてリージョンを選択し、そこから C-x Space を押すと、図 1 のように矩形のリージョンに切り替えることができます。これを rectangle-mark-mode といい、このモードにあっても通常のリージョンと同じように C-w、M-w、C-y によるカット、コピー、ペーストが可能です。
図 1
図 1
ところで、こうなっていたら激アツだと思いませんか?
図 2
図 2
何にしろ何も考えなくても C-Space をポチポチやれば、勝手に通常選択と矩形選択が切り替わります。かなり前のことですが (おそらくまだ rectangle-mark-mode が実装されていなかったころ)、ネット上で配布されていた sense-region という Emacs Lisp プログラムがこのような操作系になっていて、あまりにもシンプルかつ便利なので感動した記憶があります。という訳でこの動作を模倣すべくコードを書いてみました。
(if (require 'rect nil t)
  (defun my-sense-region ()
    (interactive)
    (if (use-region-p)
      (if (equal rectangle-mark-mode nil)
        (rectangle-mark-mode 1)
        (rectangle-mark-mode -1))
      (set-mark-command nil)))
  (defun my-sense-region ()
    (interactive)
    (set-mark-command nil)))
(global-set-key (kbd "C-SPC") 'my-sense-region)

選択範囲をまとめてインデントする

図 3 のように、選択したリージョンをまとめてインデントする設定です。
図 3
図 3
init.el に下記のように書いておき、リージョンを選択した状態で C-' を押すとミニバッファに「Indent region with H, L, h, or l.」と表示されるので H、L、h、l のいずれかのキーを押します。各キーは次のように動作します。
  • H: 1文字分だけ左方向へインデント
  • L: 1文字分だけ右方向へインデント
  • h: 左方向のタブストップまでインデント
  • l: 右方向のタブストップまでインデント
リージョンを解除しないかぎりは、好きな方向に何回でもインデントすることができます。
(global-set-key (kbd "C-'") 'indent-rigidly)
(when (boundp 'indent-rigidly-map)
  (define-key indent-rigidly-map (kbd "l") 'indent-rigidly-right-to-tab-stop)
  (define-key indent-rigidly-map (kbd "h") 'indent-rigidly-left-to-tab-stop)
  (define-key indent-rigidly-map (kbd "L") 'indent-rigidly-right)
  (define-key indent-rigidly-map (kbd "H") 'indent-rigidly-left)
  (define-key indent-rigidly-map (kbd "C-'") 'indent-rigidly-right-to-tab-stop))

インデントをタブで行うか空白文字で行うかを切り替える

インデントをタブで行うのか空白文字で行うのかを決めかねている、私のような優柔不断な人間にはありがたいテクニックです。F8 キーを押すたびに両者が切り替わります。
(setq-default indent-tabs-mode nil)
(defun toggle-indent-tabs-mode ()
  (interactive)
  (setq indent-tabs-mode (not indent-tabs-mode))
  (if (equal indent-tabs-mode nil)
    (message "indent tabs mode disabled.")
    (message "indent tabs mode enabled.")))
  (global-set-key (kbd "<f8>") 'toggle-indent-tabs-mode)

行末の空白文字を可視化し、ファイル保存時に削除する

行末に要らない空白文字がくっついているんじゃないかというのは気になるものです。可視化するとともに、ファイル保存時に自動的に削除するようにします。ただし Markdown のように行末の半角スペースに意味があるファイルもあるので、削除するかどうかはトグルできるようにしておき、適当なキーにバインドしています。
図 4
図 4
(setq-default show-trailing-whitespace t)
(add-hook 'before-save-hook 'delete-trailing-whitespace)
(defun toggle-del-trailsp ()
  (interactive)
  (if (memq 'delete-trailing-whitespace (symbol-value 'before-save-hook))
    (progn
      (message "delete-trailing-whitespace disabled.")
      (remove-hook 'before-save-hook 'delete-trailing-whitespace))
    (progn
      (message "delete-trailing-whitespace enabled.")
      (add-hook 'before-save-hook 'delete-trailing-whitespace))))
(global-set-key (kbd "<f7>") 'toggle-del-trailsp)

トグルのしくみは、まず最初に delete-trailing-whitespace を before-save-hook に登録しておき、あとはトグルされるたびにフックのリストをチェックして、登録されていれば remove-hook を、されていなければ add-hook を呼び出します。

タブ文字と全角スペースを可視化する (whitespace-mode)

タブ、全角スペース、半角スペースが混在しているんじゃないかというのは気になるものです。whitespace-mode では、図 5 のようにタブと全角スペースを可視化することができます。ついでに表示と非表示の切り替えを適当なキーに割り当てておきましょう。
図 5
図 5
(when (require 'whitespace nil t)
  (setq whitespace-style '(face tabs tab-mark spaces space-mark))
  (setq whitespace-space-regexp "\\( +\\|\u3000+\\)")
  (setq whitespace-display-mappings
    '((space-mark ?\u3000 [?\u25a1])
      (tab-mark ?\t [?\xBB ?\t] [?\\ ?\t])))
  (set-face-foreground 'whitespace-tab "gray40")
  (set-face-background 'whitespace-tab 'nil)
  (set-face-underline  'whitespace-tab t)
  (set-face-foreground 'whitespace-space "orange")
  (set-face-background 'whitespace-space 'nil)
  (set-face-bold       'whitespace-space t)
  (global-whitespace-mode 1))
(global-set-key (kbd "<f9>") 'global-whitespace-mode)

C-o でウィンドウを分割・移動する

参考書籍 [2] p.74 に書かれているテクニックです。ウィンドウを分割してそのウィンドウに移動したいとき、ふつうは C-x 2 C-x o と打ちます。続けてウィンドウを移動したいときはまた C-x o と打ちます。この煩わしい操作を C-t だけでやってしまおうというものです。ちなみに私はこれを C-t ではなく C-o に割り当てています。C-o は標準ではカーソル位置に新しい行を挿入する open-line コマンドが割り当てられていますが、これをほとんど使わないからです (これもどなたかのアイデアだった気がしますが情報源を失念しました)。
(defun other-window-or-split ()
  (interactive)
  (when (one-window-p) (split-window-vertically))
  (other-window 1))
(global-set-key (kbd "C-o") 'other-window-or-split)

前方に向かって大文字化/小文字化/キャピタライズする (sequential-command)

例えば「the presidents of the united states of america」と入力してから M-u と打てば「the presidents of the united states of AMERICA」となり、もう 1 回打てば「the presidents of the united states OF AMERICA」となり、さらにもう 6 回打てば「THE PRESIDENTS OF THE UNITED STATES OF AMERICA」となります。そこから 8 回 M-d と打てば元に戻ります。同じことを M-c でやると「The Presidents Of The United States Of America」となります。これを行うには package.el で sequential-command をインストールし、init.el に次のように書いておきます。
(when (require 'sequential-command-config nil t)
  (sequential-command-setup-keys)
  (global-set-key (kbd "C-a") 'beginning-of-line)
  (global-set-key (kbd "C-e") 'end-of-line)
  (global-set-key (kbd "M-u") 'seq-upcase-backward-word)
  (global-set-key (kbd "M-d") 'seq-downcase-backward-word)
  (global-set-key (kbd "M-c") 'seq-capitalize-backward-word))
3,4 行目について補足を。sequential-command には「C-a を 2 回打つとファイルの先頭に飛ぶ」「C-e を 2 回打つとファイルの末尾に飛ぶ」という機能があり、たぶんとても便利だと思うのですが、私の場合、確実に行頭行末に飛ぶために C-a や C-e をばしばしと何回も叩いてしまう癖が仇となってなじめませんでした。意図せずファイルの位置がころころ変わるので、頭が混乱します。よって標準の機能である beginning-of-line と end-of-line に戻しています。

C-a と C-e を拡張してファイルの先頭や末尾に飛ぶ (sequential-command)

すぐ上で紹介した sequential-command を使います。3 行目の C-a と 4 行目の C-e をコメントアウトしてください。そして適当な位置にカーソルを移動させ C-a や C-e をばしばし打ち込んでみてください。

IntelliSense のように自動補完する (company)

ここで紹介する設定の 95 パーセントくらいは下記からの受け売りであって、私自身は訳も分からず結果オーライで利用させていただいています。まず package.el で company と company-statistics をインストールします。次いで GitHub より下記のプラグインをダウンロードして変数 load-path にセットしたパスに置き、必要ならバイトコンパイル (M-x byte-compile-file) しておきます。init.el は次のとおり。
(when (require 'company nil t)
  (global-set-key (kbd "C-t") 'company-complete)
  (define-key company-active-map (kbd "C-n") 'company-select-next)
  (define-key company-active-map (kbd "C-p") 'company-select-previous)
  (define-key company-active-map (kbd "TAB") 'company-complete-selection)
  (define-key company-active-map (kbd "C-e") 'company-complete-selection)
  (define-key company-active-map (kbd "C-h") nil)
  (setq company-idle-delay 0
        company-minimum-prefix-length 2
        company-selection-wrap-around t
        company-dabbrev-downcase nil
        company-tooltip-align-annotations t
        company-require-match 'never)
  (setq company-backends '(company-dabbrev
                           company-capf
                           company-semantic
                           company-files
                           (company-dabbrev-code company-gtags company-keywords)))
  (when (require 'company-statistics nil t)
    (company-statistics-mode))
  (when (require 'company-dwim nil t)
    (define-key company-active-map (kbd "TAB") 'company-dwim)
    (setq company-frontends
          '(company-pseudo-tooltip-unless-just-one-frontend
            company-dwim-frontend
            company-echo-metadata-frontend)))
  (when (require 'company-anywhere nil t))
  (when (require 'company-same-mode-buffers nil t)
    (company-same-mode-buffers-initialize)
    (push 'company-same-mode-buffers company-backends))
  (global-company-mode t))
このように設定した場合の挙動は、私が試したかぎり、
  • C-t を押すか、company-minimum-prefix-length で指定した文字数だけ入力すると補完が始まる。
  • その状態で TAB を 1 回押すと第 1 候補で確定する。
  • もう 1 回 TAB を押すと第 2 候補が選択・確定される。C-n と C-p も使える。
  • 以降 TAB を押すたびに次の候補が選択・確定される。
となります。company-dwim の Readme に書かれている挙動と少し違う気がしますが、私がよく理解せずいろんな設定を詰め込んだせいか、私の英語力が足りないせいかのどちらかだと思います。不満はないので結果オーライです。

C-e に company-complete-selection を割り当てているのは気分的な問題であり、私以外の人には不要だと思います。補完候補を選択中に C-e を押したら確定したように見えてほしいと思ったので。実際は選択した時点ですでに確定しているので意味がありません。

それから、これまた私だけかもしれませんが、face 設定で company-preview の文字色と背景色が設定されていると、そのせいで確定動作が分かりづらくなる気がします。実際は候補を確定したのに確定してないように見えるというか。よって未設定 (標準色) にしています。ついでなので company 関連の色設定をまとめて掲載しておきましょう。あとで紹介する M-x list-faces-display で設定し、~/.emacs.d/custom.el に自動生成されたコードの一部です。

(custom-set-variables
  ...
  '(company-preview ((t nil)))
  '(company-preview-common ((t (:inherit company-preview :background "nil" :foreground "lightgray" :underline nil))))
  '(company-scrollbar-bg ((t (:background "gray40"))))
  '(company-scrollbar-fg ((t (:background "white"))))
  '(company-tooltip ((t (:background "lightgray" :foreground "black"))))
  '(company-tooltip-common ((t (:background "lightgray" :foreground "black"))))
  '(company-tooltip-common-selection ((t (:background "steelblue" :foreground "white"))))
  '(company-tooltip-selection ((t (:background "steelblue" :foreground "black"))))
  ...)

リドゥする (redo+)

下記のサイトから redo+.el をダウンロードして変数 load-path にセットしたパスに置き、必要ならバイトコンパイル (M-x byte-compile-file) しておきます。
(when (require 'redo+ nil t)
  (global-set-key (kbd "M-/") 'redo))

ジャンプする前の位置にカーソルを戻す (point-undo)

下記のサイトから point-undo.el をダウンロードして変数 load-path にセットしたパスに置き、必要ならバイトコンパイル (M-x byte-compile-file) しておきます。
(when (require 'point-undo nil t)
  (global-set-key (kbd "M-SPC") 'point-undo))
point-undo があれば point-redo もある訳ですが、私はあまり使わないのでキーは割り当てていません。

分割したウィンドウの幅・高さをキーボードだけで変える (smartrep)

上下に分割したウィンドウの高さや、左右に分割したウィンドウの幅を、マウスを使わずにキーボードだけで変更しようというテクニックです。smartrep というパッケージを利用するので package.el からインストールしてください。設定例は次のとおり。
(defun my-shrink-window ()
  (interactive)
  (if (window-full-height-p)
    (shrink-window-horizontally 4)
    (shrink-window 1)))
(defun my-enlarge-window ()
  (interactive)
  (if (window-full-height-p)
    (enlarge-window-horizontally 4)
    (enlarge-window 1)))
(when (require 'smartrep nil t)
  (smartrep-define-key global-map "C-c"
    '(("h" . my-shrink-window)
      ("j" . my-shrink-window)
      ("k" . my-enlarge-window)
      ("l" . my-enlarge-window))))

vi 風のキーバインドにしてみました (こういうところはなぜか vi がしっくりくる)。C-c と打ってから hhh... または jjj... と打つと今いるウィンドウの幅または高さが縮小し、kkk... または lll... と打つと伸長します。ウィンドウが上下と左右のどちらに分割されているのかによって、幅と高さのどちらを調整するのかを自動的に切り替えるようにしてみました (といってもやっていることはチープで、今いるウィンドウの高さがフレームの高さに一致するときは幅を、それ以外のときは高さを変更するというだけのことです)。

ここで注意点。ウィンドウが左右に分割されているとき、ウィンドウの境界を h で左に、l で右に動かせそうに思えますが、あくまで今いるウィンドウが基準となる点に注意してください。右側のウィンドウにいる場合は感覚と逆になります。まあ sHrink の「H」、enLarge の「L」で覚えれば、それほど不都合はないと思います。または、ちょっと押しづらいですが shrinkを「-」キー、enlarge を「+」キーにしておけば感覚には合致します。

自作関数 my-shrink-window と my-enlarge-window の中に 1 とか 4 とか書いてある数字が打鍵 1 回分の移動量ですので、適当に調整してください。

Helm

このあと紹介するいくつかのパッケージのために Helm を package.el でインストールし、読み込んでおきます。変数 helm-move-to-line-cycle-in-source に t をセットしておくと、候補リストの末尾まで達したときに先頭に戻る動作となります。ついでにキーバインドの設定を。C-x C-f を標準の find-file から helm-find-files に置き換え、helm-find-files の最中に TAB で補完が効くようにしています。
(when (require 'helm-config nil t)
  (require 'helm-command)
  (setq helm-move-to-line-cycle-in-source t)
  (global-set-key (kbd "C-x C-f") 'helm-find-files)
  (define-key helm-find-files-map (kbd "TAB") 'helm-execute-persistent-action))

キルリング (クリップボード) から選んでペーストする

(global-set-key (kbd "M-y") 'helm-show-kill-ring)

Emacs 上で grep する (ripgrep)

ripgrep という外部プログラムを利用して Emacs 上で grep できるようにします。package.el から helm-ag と ripgrep を、parts/packages から ripgrep をインストールしてください。キーは M-G に割り当てています。
(setq helm-ag-base-command "rg --vimgrep --no-heading")
(setq helm-ag-insert-at-point 'symbol)
(setq ripgrep-arguments '("-S"))
(setq ripgrep-executable "/usr/local/bin/rg")
(global-set-key (kbd "M-G") 'helm-ag)
なお、検索対象のディレクトリは、デフォルトではカレントディレクトリになります。C-u プレフィックス (universal-argument) を付けて C-u M-G とすると 任意のディレクトリを指定することができるようになります。

タグジャンプを利用してソースコードを読む (Global & helm-gtags)

GNU Global (以下 Global) というソフトウェアを利用して、ソースコード閲覧の際にタグジャンプできるようにします。Global は標準で C、Yacc 、Java、PHP4、アセンブリの 5 言語に対応しており、さらに Pygments と Universal Ctags というプラグインを使うことで C++ など 25 の言語をサポートするそうです。

GNU Global source code tagging system
https://www.gnu.org/software/global/

package.el から helm と helm-gtags をインストールし、Global 本体は ports/packages でインストールします。init.el には次のように書いておきます。

(when (require 'helm-gtags nil t)
  (add-hook 'c-mode-hook (lambda () (helm-gtags-mode)))
  (add-hook 'c++-mode-hook (lambda () (helm-gtags-mode)))
  (add-hook 'asm-mode-hook (lambda () (helm-gtags-mode)))
  (setq helm-gtags-path-style 'root)
  (setq helm-gtags-ignore-case t)
  (setq helm-gtags-auto-update t)
  (setq helm-gtags-use-input-at-cursor t)
  (defalias 'ct 'helm-gtags-create-tags)
  (defalias 'ut 'helm-gtags-update-tags)
  (define-key helm-gtags-mode-map (kbd "C-.") 'helm-gtags-dwim)
  (define-key helm-gtags-mode-map (kbd "M-.") 'helm-gtags-pop-stack))

Global を利用するには、最初に「タグファイル」を作らなければなりません。適当なソースファイルを Emacs で開き、M-x helm-gtags-create-tags (9 行目のようにエイリアスを設定した場合は M-x ct) と打ちます。その際に Root Directory と GTAGSLABEL を訊かれますが、Root Directory にはソースブラウズの起点となるディレクトリを入力し (ここで指定した場所にタグファイルが作られます)、GTAGSLABEL のほうは取りあえずデフォルトのまま Enter を押して進めます。

これでタグジャンプが可能になります (図 6)。適当な関数や変数の上でC-. を押してみましょう。C 言語の人は include 文の上でも押してみましょう。ジャンプ元に戻りたければ M-. を押しましょう。

ソースファイルを変更したら M-x helm-tags-update-tags コマンドでタグファイルも更新します。7 行目のように変数 helm-gtags-auto-update に t をセットしておけば、バッファをセーブした時点で自動的に更新してくれます。

図 6
図 6

ソースファイルの関数一覧を表示する (helm-imenu & sr-speedbar)

helm-imenu や sr-speedbar を使うとソースファイルの関数一覧を表示させることができます。それぞれ package.el からインストールし、下記のように設定しておきます。
(setq sr-speedbar-right-side t)
(setq speedbar-use-images nil)
(global-set-key (kbd "M-I") 'sr-speedbar-toggle)
(global-set-key (kbd "M-i") 'helm-imenu)

1 行目は speedbar のディレクトリツリーを画面の右側に表示するための設定です。また、2 行目のように speedbar-use-images を nil にしておくと、ツリーの表示がテキストベースになります (私はこちらが好み)。

helm-imenu は図 7、speedbar は図 8 のようになります。helm-imenu の操作について特筆すべきことはありません。speedbar のツリーウィンドウは C-n、C-p、Space、Enter だけ覚えておけば、なんとなく操作できると思います。

図 7
図 7
図 8
図 8

ソースコードの差分比較とマージを行う (ediff)

Emacs 上でファイルどうし・バッファどうしの差分を比較したりマージしたりすることができます。
図 9
図 9
M-x ediff-files でファイルを、M-x ediff-buffers でバッファを比較します。適当なキーに割り当てておきましょう。例えばバッファを比較する場合は M-x ediff-buffers を実行します。比較したいバッファを A、B として指定するよう促されるので入力すると、図 9 のように差分が表示されます。バッファ A が左側、B が右側です。ここでの操作方法は次のとおり。
  • n: 次の差分へ
  • p: 前の差分へ
  • a/b: カーソル位置にあるバッファ A/B の差分をバッファ B/A へマージする
  • ra/rb: バッファ A/B に対して行ったマージをアンドゥする
  • wa/wb: バッファ A/B をセーブする
  • q: 差分表示を終了する
  • ?: ヘルプを表示する
そして init.el は次のとおり。
(setq ediff-window-setup-function 'ediff-setup-windows-plain)
(setq ediff-split-window-function 'split-window-horizontally)
(global-set-key (kbd "M-F") 'ediff-files)
(global-set-key (kbd "M-B") 'ediff-buffers)
1 行目はコントロール用のウィンドウ (図 9 で「Type ? for help」と表示されているウィンドウ) を同一フレーム内に表示する設定です。コメントアウトすると、フレーム外に小窓として表示されます。2 行目は比較対象を左右に並べる設定です。

C-h は BS でなければならない。

(global-set-key                 (kbd "C-h") 'delete-backward-char)
(define-key isearch-mode-map    (kbd "C-h") 'isearch-delete-char)
(define-key helm-map            (kbd "C-h") 'delete-backward-char)
(define-key helm-find-files-map (kbd "C-h") 'delete-backward-char)

マウスホイールの調整

(setq mouse-wheel-scroll-amount '(3 ((shift) . 10) ((control))))
(setq mouse-wheel-progressive-speed nil)
(setq mouse-wheel-follow-mouse 't)
(setq scroll-preserve-screen-position 'always)
  • ホイールで 3 行分、Shift+ホイールで 10 行分、Ctrl+ホイールで 1 画面分スクロールします。
  • ホイールの速度に関わらずスクロール量を一定にします。
  • 3 行目はなんだっけ?
  • スクロール中のカーソル位置を固定します。

テーマを選ぶ、そしてシンタックスハイライトを設定する (Faces)

Emacs では Faces というしくみを利用して見た目を変えることができます。まずは M-x customize-themes でテーマを選びましょう (図 10)。

次いでシンタックスハイライトです。M-x list-faces-display で設定可能な項目の一覧が表示されます (図 11)。例えば上のほうで、company-preview という Face の文字色と背景色をあえて未設定にしましたが、「やっぱり何か色がないとな」と思ったら、「company-preview」と表示されている行を Enter かマウスクリックで選択します。すると company-preview で設定可能な属性一覧が表示されるので、Background なり Foreground なりにチェックを入れて色を選びます (図 12)。もし属性が何も表示されない場合は、折りたたまれて非表示になっているので、「Show All Attributes」と書かれている部分をクリックして展開します。設定を終えたら、「Apply and Save」ボタンをクリックすることで保存・適用されます。

ちなみにここで行った設定はどこに保存されるのかというと、変数 custom-file にセットしたファイルになります。

図 10
図 10
図 11
図 11
図 12
図 12

その他の豆知識

  • 現在のキーバインドを知りたい: M-x describe-bindings
  • ESC キーが遠い: C-[ でもいける
  • 直前のコマンドをもう 1 回呼び出す: M-x M-p

おまけ A: 操作早見表 (自家用)

ここまで書いてきたことをまとめると、下の表のようになります。本文では紹介していないものも少しあります。ちなみに私は「自分用プレフィクスキーを探せ」とのるびきち様の教えにしたがい C-, を自分用のプレフィクスキーとして予約してあります。
キーコマンド機能
C-h

本文参照

バックスペース

C-j

newline-and-indent

改行 & インデント

C-o

本文参照

ウィンドウ分割・移動

C-t

company-complete

自動補完開始

C-8

scroll-down-command

上方へスクロール (M-v と同じ)

C-\

toggle-input-method

日本語入力 ON / OFF

C-SPC

本文参照

マークセット / 矩形リージョン選択

C-'(クォート)

indent-rigidly

選択範囲をまとめてインデント

C-/

undo

アンドゥ

M-/

redo

リドゥ

C-;

bs-show

バッファ一覧を表示

F1

help-command

ヘルプを表示

F5

revert-buffer-no-confirm

バッファの再読み込み

F7

toggle-del-trailsp (自作)

セーブ時に行末のスペースを削除するモード ON / OFF

F8

toggle-indent-tabs-mode (自作)

タブインデントモード ON / OFF

F9

global-whitespace-mode

タブと全角スペースの可視化 ON / OFF

F10

toggle-truncate-lines

行の折り返し表示 ON / OFF

C-c hhh...

本文参照

ウィンドウの幅または高さを縮小

C-c jjj...

本文参照

ウィンドウの幅または高さを縮小

C-c kkk...

本文参照

ウィンドウの幅または高さを伸長

C-c lll...

本文参照

ウィンドウの幅または高さを伸長

C-x C-f

helm-find-files

ファイルを開く

M-SPC

point-undo

カーソルをジャンプ元に戻す

M-B

ediff-buffers

バッファどうしを比較

M-F

ediff-files

ファイルどうしを比較

M-G

helm-ag

カレントディレクトリ内の文字列検索

C-u M-G

helm-ag

指定したディレクトリ内の文字列検索

M-g

list-matching-lines

ファイル内の文字列検索

M-I(アイ)

sr-speedbar-toggle

関数一覧を表示

M-i

helm-imenu

関数一覧を表示

M-l(エル)

goto-line

指定された行に移動

M-q

query-replace

文字列置換 (確認あり)

M-r

replace-string

文字列置換 (確認なし)

M-y

helm-show-kill-ring

キルリングから選んでペースト

M-u

seq-upcase-backward-word

単語を大文字化

M-d

seq-downcase-backward-word

単語を小文字化

M-c

seq-capitalize-backward-word

単語をキャピタライズ

C-.(ドット)

helm-gtags-dwim

タグジャンプ

M-.(ドット)

helm-gtags-pop-stack

タグジャンプから戻る

M-x ct

helm-gtags-create-tabs

Global タグファイル作成

M-x ut

helm-gtags-update-tabs

Global タグファイル更新

C-,(カンマ)

予約

おまけ B: Faces 設定早見表 (自家用)

私以外の人にはあまり益のない Faces 設定の早見表です。 これを適用したらどんな具合になるのかは本文のスクリーンショットを参考にしてください。
Facebackgroundforegroundその他の属性(*)
company-preview

company-preview-common

nil

lightgray

I:company-preview, U:Off

company-scrollbar-bg

gray40

company-scrollbar-fg

white

company-tooltip

lightgray

black

company-tooltip-common

lightgray

black

company-tooltip-common-selection

steelblue

white

company-tooltip-selection

steelblue

black

cursor

gray60

ediff-even-diff-A

gray30

ediff-even-diff-B

gray30

ediff-even-diff-C

gray30

ediff-odd-diff-A

gray30

ediff-odd-diff-B

gray30

ediff-odd-diff-C

gray30

font-lock-builtin-face

MediumOrchid1

font-lock-comment-face

DarkSeaGreen4

font-lock-function-name-face

default

font-lock-keyword-face

cornflower blue

W:normal

font-lock-string-face

PaleGreen3

font-lock-type-face

cornflower blue

W:normal

font-lock-variable-name-face

default

helm-buffer-directory

cornflower blue

helm-buffer-file

default

helm-candidate-number

orange

gray12

helm-ff-directory

cornflower blue

helm-ff-dotted-directory

cornflower blue

helm-ff-dotted-symlink-directory

I:helm-ff-dotted-directory

helm-ff-executable

default

helm-ff-file

default

helm-ff-file-extension

E:t

helm-ff-prefix

orange

gray12

helm-header-line-left-margin

orange

gray12

helm-selection

ForestGreen

gray12

isearch-fail

orange

gray12

minibuffer-prompt

default

mode-line-buffer-id

W:normal

mozc-cand-echo-area-focused-face

orange

gray12

mozc-cand-echo-area-status-face

default

mozc-preedit-selected-face

SteelBlue4

white

speedbar-button-face

default

speedbar-directory-face

cornflower blue

speedbar-file-face

cornflower blue

speedbar-highlight-face

forest green

gray12

speedbar-selected-face

orange

U:On

speedbar-tag-face

default

trailing-whitespace

gray30

(*)E: Extend, I: Inherit,U: Underline, W: Weight