Emacsの初期化ファイル書こう?

まえがき

UEC Advent Calendar くこけ?

adventar.org

くこは安全 🙆
安全確認完了しました

この記事は UEC Advent Calendar 7日目の記事です.

6日目は id:kden さんの

kden.hatenablog.com

でした. 色をいい感じに表現してくれるかどうかは絵を書いてる人には大事そう.


安全と前日の人の記事を確認したところで,本題に入ろうと思います. 最初は思いっきりネタ記事を書こうかと思いましたが,面倒なのでやめました. 代わりに Emacs の話をします. 読者としては電通大の学部生を想定していますが,Emacs について少し書くため一般 Emacs ユーザが間違えて迷い込んでしまう可能性があります.
恐らく電通大生以外に有益な情報はほとんど含んでいない (むしろ有害) と思われるため,電通大生以外の Emacs ユーザはそっ閉じしてください.

電通大生にとっての Emacs

電通大に入ってからプログラミングに触れている人の多くは Emacs を使っていると思います. 恐らく何かしらの講義で Emacs とかいうわけのわからんエディタの基本的な使い方を教わり,講義が終わったときには全てのキーバインドが頭から抜けおちて,Emacs は文字通りよくわからないエディタという印象の人がほとんどでしょう. とは言え,みんながみんな Emacs を使っているわけではなく,中には Vim に頭を汚染された人や EmacsVim から自らを守るために SublimeText や AtomVSCode といったモダンなテキストエディタを使っている人もいるでしょう. nano あたりを使ってる人もいるようですし,gedit を使う異端児もいるようですが,賢明な判断だと思われます.

バニラ Emacs は正直使いやすいとは言えないし,テキストエディタとして Vim に劣るのは明白でしょう. バニラ Emacs という呼称は恐らく一般的に使われているものではありませんが,ここでは初期化ファイルに何も書いていない状態で使用する Emacs のことを指して使おうと思います.

さて,本記事は Emacs について書いていくが Vimmer の皆様も Emacs に乗り換えられるということだけ言っておきたい.
Emacs では vi をエミュレートするためのパッケージが Vim 登場以前から開発されていた. この辺の歴史については以下の記事を参照されたし.

要するに Evil と呼ばれるパッケージを使えば EmacsVim ライクなキーバインドを使うことができる. ヘビーな Vim ユーザにとっては,不満な部分があると思うが,Emacs なのでその辺は自分で Emacs Lisp を書くことで修正できる.

また,そんなの面倒だ.強くてニューゲームがしたいという人には Spacemacs と呼ばれるものがある. これは Emacs + Evil をベースに便利なパッケージやらキーバインドを予め追加してくれているものだ. ただ,Spacemacs を使うのをやめて Evil の設定を自分で書いている同輩曰く,Vim との微妙な挙動の違いが気になるらしい.

以前,自分も Spacemacs を使っていたが今は結局普通の Emacs に戻ってきてしまった. ただ,こいつは色々と便利だし,各プログラミング言語や外部プログラム (git とか) ごとにレイヤーと呼ばれるものが用意されており,それらは自分の Emacs をカスタマイズする上で参考になるため,EmacsVim のバインドが使いたいわけではない Emacs ユーザも目を通すといいと思う.
それに見た目が格好いい.モダンなテキストエディタといった感じがする (小並感).
まぁ,見た目格好よくするだけなら Spacemacs が使ってるパッケージを入れれば簡単に真似できるんだけどね.
その辺は需要があれば,記事を書こうかな.

電通大生の Emacs 事情

さて,自分は修士 1 年であるため今の学部生,特に学部 1 年生がどのような環境で最初に C 言語を学んでいるのか知りません. 自分たちが学部 1 年のときには,Mac を使って講義を受けていましたが,その後 Mac は廃止されたようで今は Windows を使っているのでしょうか. 学部 1 年生については知りませんが,学部 2 年生は CeiED か IED で講義を受けているためちゃんと Emacs に触れていると思います. この記事では CeiED 環境で Emacs を動作させることを前提に書いていきます.

学部 3 年以下の電通大生のほとんどは CeiED や IED 以外で Emacs に触れないじゃないでしょうか? (偏見) CeiED では Emacs を使っているけど,自分の PC で課題をやるときは別のエディタを使ってる人はある程度いると思われます. それが原因かはわかりませんが,CeiED でしか使わないからと何の設定も書いていない Emacs を使うという苦行をしている M な方たちで溢れているのが現状です. もしくは,Emacs が色々とカスタマイズできるということ知らないだけなのかもしれません. とは言え,大学の課題を解く程度のことは大した作業ではないのだから Emacs の設定を弄ったところで何の改善もないだろうと言われれば確かにその通りですが, 括弧の対応を自分で取ったり,行番号を数えるようなことは人間のすることではないでしょう.

そこでそんな現状を打破するために,自分が CeiED でちゃんと動くような設定ファイルを書いたので,とりあえずいい感じにしたいとう人はコピペして使ってみるといいと思います.

(その前にみんな基本的なキーバインドくらいは覚えて欲しいというのが本音ですが…)

CeiED の Emacs

さて,みなさんは CeiED の Emacs のバージョンがいくつか知ってますか?

$ /usr/bin/emacs -version
GNU Emacs 23.1.1
Copyright (C) 2009 Free Software Foundation, Inc.
GNU Emacs comes with ABSOLUTELY NO WARRANTY.
You may redistribute copies of Emacs
under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING.

今日のタイムラインの会話
あの流行りのエディタかっこいい とか あのIDEほしい とか
ま、それが普通ですわな

かたや俺は CeiED で Emacs 23.1.1 を見て、呟くんすわ
it’a true wolrd.狂ってる?それ、誉め言葉ね。


Emacs 23.1.1 … さすが電気通信大学
俺たちにできないことを平然とやってのけるッ そこにシビれる!あこがれるゥ!

Emacs 23.1.1 を使い続けるなんて正気の沙汰じゃない.
モダンなパッケージが 24.4 以上を要求する時代に生きてるとは思えない.

挙げるまでもなく Emacs 24.4 より古いバージョンは人権喪失なんだ.

話は CeiED に Emacs 25.3 を入れてからだ.
ここでは $HOME/usr 以下に入れることにする.

$ cd
$ mkdir usr
$ cd /tmp
$ wget http://ftp.gnu.org/gnu/emacs/emacs-25.3.tar.gz
$ tar xvf emacs-25.3.tar.gz
$ cd emacs-25.3
$ ./autogen.sh
$ ./configure --prefix=$HOME/usr --without-x
$ make
$ make install

多少時間はかかるがこれで Emacs 25.3 が使えるようになるはず. ただし,GUI ではなく CUI でのみだが… どうも CeiED の環境だと上手く行かない様子.
./configure --prefix=$HOME/usr --with-gif=no で configure 自体は通るが,make に失敗する. 頑張れば make を成功させられるとは思うけど,そこまでして GUI が欲しいかと言われるとそうでもないので妥協する. ここで Emacs 25.3 インストールバトルをして記事にするのも一興だったが,今回の本題は違うのでやめた.

後は .cshrc とかに

setenv PATH $HOME/usr/bin:$PATH

とか書いておけば emacs がちゃんと 25.3 の方になるだろう.
注意としては以前のように GUI が起動するのではなく,ターミナル上に Emacs が開かれるということだ.
まぁ,これでも別に支障はないだろう. C 言語のソースコードコンパイルしたくなったら C-z で一旦 Emacs を止めるか,M-x shell もしくは M-x eshell でシェルを起動して好きにコマンドを叩けばよい.

何はともあれ Emacs 23.1.1 とかいういつの時代の人間が使ってるのかわからないバージョンから脱することができた.

CeiED の Emacs 初期化ファイル

さて,CeiED のクソなところは Emacs のバージョンにとどまりません.
その前に Emacs の初期化ファイルについて話しましょう. Emacs の初期化ファイルは複数存在しています. これについは以下の記事を読むのがいいと思います.

CeiED では ~/.emacs が自動生成されます. 消しても消しても復活してくれます. なのでファイルを消すのは諦めて,書かれている内容を消して以下のようにします.

(load "~/.emacs.d/init.el")

これは好みの問題ですが,自分は Emacs に関するファイルを全て ~/.emacs.d 以下で管理したいためこのようにします. 後で載せますが,今回紹介する Emacs の設定は一つのファイルに収めているため,~/.emacs の内容を全て消して,設定をそのまま書いてもいいかもしれません.

ちなみに,初期の ~/.emacs には以下のような設定が書かれていました.

;;; uncomment this line to disable loading of "default.el" at startup
;; (setq inhibit-default-init t)

;; enable visual feedback on selections
;(setq transient-mark-mode t)

;; default to better frame titles
(setq frame-title-format
      (concat  "%b - emacs@" (system-name)))

;; default to unified diffs
(setq diff-switches "-u")

;; always end a file with a newline
;(setq require-final-newline 'query)

;;; uncomment for CJK utf-8 support for non-Asian users
;; (require 'un-define)


;;初期メッセージ消去
(setq inhibit-startup-message t)
;;バッファーメッセージの非表示
(setq initial-scratch-message "")






(autoload 'mew "mew" nil t)
(autoload 'mew-send "mew" nil t)

(if (boundp 'read-mail-command)
    (setq read-mail-command 'mew))
(autoload 'mew-user-agent-compose "mew" nil t)
(if (boundp 'mail-user-agent)
    (setq mail-user-agent 'mew-user-agent))
(if (fboundp 'define-mail-user-agent)
    (define-mail-user-agent
      'mew-user-agent
      'mew-user-agent-compose
      'mew-draft-send-message
      'mew-draft-kill
      'mew-send-hook))

(setq load-path (cons "/usr/local/share/emacs/site-lisp/mew/" load-path))

(load "/usr/local/tuareg-2.0.7/tuareg-site-file")

(load "/usr/local/ProofGeneral/generic/proof-site.el")

(add-to-list 'load-path "/usr/local/tuareg-2.0.7/")

(global-set-key [zenkaku-hankaku] 'toggle-input-method)

残しておいた方がいい設定は特になさそう (適当)
不安な人は消さないで残しておいて,末尾に ~/.emacs.d/init.el を読み込むように書いてもいいと思います. すべてを理解して,消しても問題ないと思った人は消してやりましょう.

初期化ファイルの例

ここでは,自分が普段使っている .emacs.d から一部設定とパッケージを取り出して,CeiED でも動くように修正したものを載せておこうと思います.

自分は,普段 init-loader + el-get + use-package で初期化ファイルを書いています. 昔は req-package なんかを使ってかいたりもしてましたが,少しオーバーパワー感があったのでやめました. また,init-loader は使わずに 1 つのファイルにすべてまとめて書くのもいいと思います.

余談はこの辺で終わりにして,ここに載せるのは package.el + use-package を使ったものになってます. 本当は el-get を使おうと思ったのですが,どうもプロキシ周りが上手くいかなったため,package.el で妥協しました. また,Emacs を起動するたびにパッケージの更新情報を取りにいくようになっているため,起動に時間がかかってしまうと思います. その辺が気になる人は自分で書き換えてください. 親の顔より S 式 を見ている電通大生なら余裕だと思います.

使ってるパッケージは特定の言語に依存していないものだけにしています.
各言語ごとに色々と便利なパッケージが存在していますが,それは各個人が必要に応じて入れるのが望ましいと思われるため除きました.

以下のコードを ~/.emacs.d/init.el に貼り付けた後,ターミナルから Emacs を起動してください. 途中で y or n で答えろ的なのがミニバッファに表示されると思いますが,n と答えればいいと思います.

;;; 基本的な設定

(menu-bar-mode 0)

;; 行番号の表示
(global-linum-mode t)

;; 起動時にメッセージを表示しない
(setq inhibit-startup-message t)

;; 言語を日本語にする
(set-language-environment 'Japanese)

;; UTF-8
(prefer-coding-system 'utf-8)

;; backup の保存先を変更

(setq backup-directory-alist
      (cons (cons ".*" (expand-file-name "~/.emacs.d/backups"))
            backup-directory-alist))

(setq auto-save-file-name-trasforms
      `((".*", (expand-file-name "~/.emacs.d/backups/") t)))

;; yes/no を y/n に変更
(defalias 'yes-or-no-p 'y-or-n-p)

;; ビープ音,フラッシュを消す
(setq ring-bell-function 'ignore)

;; バッファの自動再読み込み
(global-auto-revert-mode 1)

;; 行の折り返し無効
(set-default 'truncate-lines t)
(setq truncate-partial-width-windows t)

;; 1 行単位でスクロール
(setq scroll-conservatively 1)

;; スペースでインデント
(setq-default indent-tabs-mode nil)

;; 括弧を強調
(show-paren-mode t) ; smartparens を使うのでいらないかも

;; 時計やカーソル位置を表示
(setq display-time-string-forms
      '((format
         "%s/%s(%s) %s:%s" month day dayname 24-hours minutes))
      line-number-mode t
      column-number-mode t)
(display-time-mode 1)

;;; パッケージ

;; MELPAを追加
(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)

;; MELPA-stableを追加
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t)

;; Marmaladeを追加
;; (add-to-list 'package-archives  '("marmalade" . "http://marmalade-repo.org/packages/") t)

(package-initialize)

(package-refresh-contents)


;; use-package
(package-install 'use-package)
(require 'use-package)

;; theme

;; (use-package zenburn-theme
;;   :ensure
;;   :init
;;  (load-theme 'zenburn t))


;; ddskk

(package-install 'ddskk)

(use-package skk-autoloads
  :bind (("C-x j" . skk-mode))
  :config
  (setq skk-byte-compile-init-file t)
  ;; 句読点を全角 (,.) にする
  (setq skk-kutouten-type 'en))

;; path

(use-package exec-path-from-shell
  :ensure
  :init
  (exec-path-from-shell-initialize))

;; parens

(use-package smartparens
  :ensure
  :diminish smartparens-mode
  :init
  (smartparens-global-mode t)
  (ad-disable-advice 'delete-backward-char 'before 'sp-delete-pair-advice)
  (ad-activate 'delete-backward-char)
  (sp-with-modes '(lisp-interaction-mode emacs-lisp-mode)
    (sp-local-pair "'" nil :actions nil)
    (sp-local-pair "`" nil :actions nil)))

;; anzu

(use-package anzu
  :ensure
  :diminish anzu-mode
  :init
  (global-anzu-mode +1)
  :config
  (global-set-key [remap query-replace] 'anzu-query-replace)
  (global-set-key [remap query-replace-regexp] 'anzu-query-replace-regexp)
  (setq anzu-replace-threshold 1000)
  (setq anzu-search-threshold 1000)
  (copy-face 'mode-line 'anzu-mode-line))

;; helm

(package-install 'helm)
(require 'helm)

(use-package helm-config
  :diminish helm-mode
  :bind (("M-x"     . helm-M-x)
         ("C-x C-f" . helm-find-files)
         ("M-y"     . helm-show-kill-ring)
         ("C-x b"   . helm-mini)
         ("C-c h o" . helm-occur)
         ("C-h SPC" . helm-all-mark-rings)
         :map helm-map
         ("<tab>"   . helm-execute-persistent-action)
         ("C-i"     . helm-execute-persistent-action)
         ("C-z"     . helm-select-action))
  :init
  (helm-mode 1)
  :config
  (bind-key "C-c h" helm-command-prefix)
  (unbind-key "C-x c")  
  (helm-descbinds-install)
  (setq helm-M-x-fuzzy-match t))

(use-package helm-swoop
  :ensure
  :bind (("M-i"     . helm-swoop)
         ("M-I"     . helm-swoop-back-to-last-point)
         ("C-c M-i" . helm-multi-swoop)
         ("C-x M-i" . helm-multi-swoop-all)
         :map isearch-mode-map
         ("M-i" . helm-swoop-from-isearch)
         :map helm-swoop-map
         ("M-i" . helm-multi-swoop-all-from-helm-swoop)
         ("C-r" . helm-previous-line)
         ("C-s" . helm-next-line)
         :map helm-multi-swoop-map
         ("C-r" . helm-previous-line)
         ("C-s" . helm-next-line))
  :init
  (setq helm-multi-swoop-edit-save t)
  (setq helm-swoop-speed-or-color nil)
  (setq helm-swoop-move-to-line-cycle t))

;; highlight

(use-package highlight-symbol
  :ensure
  :diminish highlight-symbol-mode
  :init
  (add-hook 'prog-mode-hook 'highlight-symbol-mode)
  (add-hook 'prog-mode-hook 'highlight-symbol-nav-mode)
  :config
  (setq highlight-symbol-idle-delay 0.5))

;; undo

(use-package undohist
  :ensure
  :config
  (undohist-initialize))

(use-package undo-tree
  :ensure
  :config
  (global-undo-tree-mode t))

(use-package whitespace
  :ensure
  :defer t
  :diminish (global-whitespace-mode whitespace-mode)
  :init
  (global-whitespace-mode 1)
  :config
  (setq whitespace-style '(face
                           trailing
                           tabs
                           space-mark
                           tab-mark))
  (setq whitespace-display-mappings
        '(
          (space-mark ?\u3000 [?\u2423])
          (tab-mark ?\t [?\u00BB ?\t] [?\\ ?\t])
          ))
  (setq whitespace-trailing-regexp  "\\([ \u00A0]+\\)$")
  (setq whitespace-space-regexp "\\(\u3000+\\)")
  (set-face-attribute 'whitespace-trailing nil
                      :foreground "RoyalBlue4"
                      :background "RoyalBlue4"
                      :underline nil)
  (set-face-attribute 'whitespace-tab nil
                      :foreground "yellow4"
                      :background "yellow4"
                      :underline nil)
  (set-face-attribute 'whitespace-space nil
                      :foreground "gray40"
                      :background "gray20"
                      :underline nil))

(use-package company
  :ensure
  :diminish company-mode
  :bind (("C-M-i" . company-complete)
         :map company-mode-map
         ("TAB" . indent-for-tab-command)
         :map company-active-map
         ("C-n" . company-select-next)
         ("C-p" . company-select-previous)
         ("C-s" . company-filter-candidates)
         :map company-search-map
         ("C-n" . company-select-next)
         ("C-p" . company-select-previous))
  :init
  (global-company-mode +1)
  :config
  (setq company-idle-delay 0
        company-selection-wrap-around t
        company-dabbrev-around t))

雑に書いたので本来なら :init に書くべきことを :config に,:config に書くべきことを :init に書いているかもしれません.

このまま投げっぱなしでもいいと思ったのですが,使ってるパッケージの簡単な説明をしようと思います.
各パッケージについて軽く触れますが,パッケージ名で調べた方がより詳細な情報が得られると思います. そのため,ここでは基本的により詳しく書かれている記事へのリンクを貼る程度に留めようと思います (面倒なだけ). また,各パッケージのドキュメントや info を読むのが最も多くの情報を得る手段だと思われます.

zenburn

テーマです. 人気の (?) zenburn にしておきました. 個人的には dracula が好きです.

ddskk

日本語入力システムです. 日本語を快適に入力することができます. 副作用として SKK に慣れると,それ以外の入力システムを使うとき慣れるまで身体に拒絶反応が現れることです. 気になった人は試してみることをおすすめします.

上記の設定では,バッファを開いたら C-x jskk-mode を有効にできます. それ以降は C-jl 等でモードを切り替えることができます.

smartparens

いい感じに組になっている括弧を自動で挿入してくれます. 実際にはもう少し高度なこともできるようですが…

anzu

デフォルトの Emacs では C-s で検索をした時にいくつヒットしたのか表示されません. このパッケージは検索にヒットした件数と何番目を見ているか表示してくれます. また,ここではデフォルトの置換を anzu のもの置き換えています. 詳しくはこの辺を見るといいと思います.

helm, helm-swoop

helm のない EmacsEmacs と呼べるだろうか? (過激派)
Emacs には helm と呼ばれる統一的なインターフェイスを提供してくれるパッケージがあります. 詳しくは,るびきちさんの記事などを読むのがいいでしょう.

より詳しい個々の機能についてはこの辺を.

ここでは,少しだけキーバインドを変えているのでその点に注意してください. 以下の記事でも言われてますが,TAB で補完できないのはどうも違和感があるので,TAB で補完するようにしています.

また,従来の TAB の機能は C-z に割り当てて,C-z に割り当ててあった関数は C-i に割り当て直しています.

helm-swoop は現在開いているバッファ内や全てのバッファに対して絞り込み検索を行うことができます. やれることは helm-occur と少し被っていますが,とても便利なので使ってみましょう. これについては,以下の記事が参考になると思います.

helm 以外にも同様のことができるパッケージはいくつか存在し,以下の記事が参考になると思います.

highlight-symbol

カーソル下にあるシンボルと同じものをハイライトしてくれます. 個人的に好きなのでここでも入れました. ここでは設定していませんが,ハイライトされてるシンボルの置換やシンボル間のカーソル移動もできます.

undo-tree, undohist

これは Emacs の Undo 機能を強化するためのパッケージです. Emacs の Undo は C-x u で行えますが少しばかり貧弱です. undo-tree では変更履歴を木で表示してくれます. 詳しくはこの辺の記事を見るとわかると思います.

undohist は変更履歴をバッファ (Emacs) をキルした後も保持し続けてくれるようにするものです. これによって Emacs で開いてたファイルを閉じてもある程度までファイルを昔の状態に戻したりすることができるようになります.

whitespace

タブや行末の空白を可視化してくれます.

company

補完をするためのパッケージです. company と双璧をなすものとして auto-complete が有名ですが,個人的に company の方を使っているため,ここでは company を入れました.

Emacs デーモンを利用する

.cshrc に以下を追加しましょう.

alias ekill "emacsclient -e '(kill-emacs)'"
alias e 'emacsclient -t -a ""'

Emacs には Emacs Daemonと呼ばれる機能があります. これは Emacs を起動しっぱなしにして二回目以降は初期化ファイルの読み込みをせず,初回に起動した Emacs でバッファを開くようにできます.
ここでは e にそのエイリアスを貼っているので,e hoge みたいに実行すると初回起動時は初期化ファイルが読み込まれてから hoge という名前のバッファを開いた状態で Emacs が起動すると思います. 二回目以降は初期化ファイルが読み込まれず,すばやく Emacs が立ち上がると思います.
またデーモンのキルは ekill で行えるようにエイリアスを貼っています.

この設定は,個人の PC で利用する分には特に問題ないですが,CeiED とかでプロセスを kill し忘れるとずっと残ってしまうので注意してください 1

あとがき

くぅ~疲れましたw これにて完結です!
実は、ネタレスしたら代行の話を持ちかけられたのが始まりでした
本当は話のネタなかったのですが←
ご厚意を無駄にするわけには行かないので流行りのネタで挑んでみた所存ですw

これで CeiED の Emacs が少しはマシになったのではないでしょうか.
パッケージ入れたりする前に基本的なキーバインドを覚えた方がいいと思いますが,色々なパッケージを試すのは楽しいので気になるパッケージがあったら試してみましょう.

Emacs のことや各パッケージについてより知りたい場合は C-h i でマニュアルを見ることができます.
このとき,info-mode と呼ばれるモードに Emacs がなりますが,キーバインドについては ? を押すと一覧が出ます. いい暇つぶしになると思うので,暇なときには読んでみましょう.

Emacs の初期化ファイルやパッケージについて書きましたが,vi やバニラ Emacs を使わざるを得ない場面というのは多々あるため,キーバインドをカスタマイズするのもいいですが,デフォルトのバインドもしっかり覚えましょう.

自分の記事はこれで終わりです.
ここで書いた他に,あまり日本語の記事が書かれていない Emacs の便利パッケージの紹介も書こうかと思いましたが,それはまたの機会ということで.

最後にモナコインが高騰してるらしいのでウォレットのアドレスを貼っておきます (乞食)

MWQWYbTGhaQVXKtZsT966RuzmMKe3NVi7f

この記事が役立ったと思った人は投げて貰えるとありがたいです.


明日は id:wktk037 さんの「UEC99歳を祝う系の記事の予定」だそうです.

追記

CeiED で確認したところテーマが zenburn だと少し見難いことがわかったので,該当部分をコメントアウトしました.


  1. ssh でログインして確かめただけなので,CeiED のマシンでログインしたときも同様かは確かめてません.