.emacs.d を公開しました

  • 84
    いいね
  • 0
    コメント

.emacs.d を大幅に整理したので,GitHub に .emacs を公開しました.

https://github.com/yynozk/dotfiles.old/tree/master/.emacs.d

もし参考になるとしたら .emacs.d/init.el と .emacs.d/inits 内のファイル群でしょうか.

細かくコメントを書いているつもりなので,どんな設定をしてるかについてはそちらを参考にしてください.

以下に,ファイル構成や設定内容の説明をしますが,設定内容だけでなくファイル分割の考え方や Elisp の書き方も説明します.Elisp がある程度わかる人向けです.

ディレクトリ構成

ざっとこんな感じです.

~/.emacs.d
├── .gitignore
├── ddskk/
│   ├── dic/
│   ├── emacs-id
│   ├── init
│   ├── jisyo
│   ├── record
│   └── study
├── elisp/
│   ├── auto-install/
│   └── work/
├── elpa/
├── init.el
├── inits/
│   ├── 000_init.el
│   ├── 00_command-keymap.el
│   ├── 00_misc.el
│   ├── ...
│   ├── 99_overriding-minor-mode-keymap.el
│   └── lin-conf.el
└── snippets/

ポイント

  • ddskk の学習ファイル,辞書ファイル等は ~/.emacs.d/ddskk に入れています
  • auto-install コマンドでダウンロードした elisp は ~/.emacs.d/elisp/auto-install に入ります
  • ~/.emacs.d/elisp/work には自作の elisp を入れています
  • ~/.emacs.d/inits 内のファイル群が,いわゆる .emacs と呼ばれる Emacs の設定です
  • ~/.emacs.d/init.el をシンボリックリンクとしてホームディレクトリに置くことで,inits のファイル群を読み込みます
  • ~/.emacs.d/snippets は yasnippet.el で使用する自作スニペットファイルを置くところです

init.el

~/.emacs.d/init.el 内のコード部分は以下の 2 行です.

(require 'init-loader-x "~/.emacs.d/elisp/auto-install/init-loader-x")
(init-loader-load "~/.emacs.d/inits")

init.el に記述しているのは,init-loader で inits ファイル群を読み込む設定のみです (init-loader-x は init-loader の改造版).

init-loader は init-loader-load で指定したディレクトリの elisp を読み込む際,どれかのファイルにエラーがあったとしても残りのファイルを読んでくれます.

そのため,inits 内でファイルを細かく分けておくことにより,パッケージの更新や .el ファイルの記述ミスによってエラーが発生しても,いつもとほぼ変わらない環境でエラーの発生したファイルを修正できるというメリットがあります.

~/.emacs に全設定を書いていたら,上の方でエラーが発生したとき,まっさらな Emacs が起動して使いにくいという Emacs あるあるを防ぐためにも,init-loader の導入をおすすめします.

詳しい説明は以下を参照してください.

http://tech.kayac.com/archive/divide-dot-emacs.html

inits ファイル群の作成方針

ファイルの分割

基本的には 1 つの require に対して 1 ファイルを作成しています.

これにより,パッケージをアップデートしたときに旧設定が使えなくなっても,他の設定まで被害が及びません.

ファイル数が多すぎて (実際,私の inits ファイル群は現在 75 あります) 管理が面倒と思うかもしれませんが,Helm や dired,dmoccur などで必要なファイルを簡単に探せます.

ファイル名

そうは言っても大量のファイルを管理するためにはファイル名が重要になります.

ファイル名として基本的には require している Elisp の名前や,メジャーモードの名前を付けています.

ただし,Emacs の設定では,複数の機能の連携に対する設定が必要になる場合もあります.その際は,ファイル名をタグとして使用しています.

例えば SKK と Org mode の連携設定であれば,skk-org.el とします.こうすることで,org で検索しても skk で検索しても引っかかるようになります.

また,Helm 情報源など,ある機能に対する拡張の Elisp もあります.

その場合は helm-0.el のように,元となる設定のファイル名の後ろに -0 を付けます.そうすることで,他の helm- ファイルよりも先に読み込むことができます.

ファイル群の先頭の数字

init-loader を導入した当初はかなり厳密に数字を決めていましたが,今はそこまで深く考えていません.

ざっとこんな感じです.

外部 Elisp をインストールするためのコメント

(install-elisp "http://www.pitecan.com/DynamicMacro/dmacro.el")

とか

(package-install 'flycheck-color-mode-line)

みたいなやつです.Github に他の人が作った Elisp を置けないので, ) の後ろで C-x C-e を入力すれば必要なパッケージがインストールできるようになっています.

全部書いてるつもりですが,たまに書き忘れてるところがあるのはご愛嬌.

mode-hook の書き方

mode-hook は基本的に,-mode-hooks 関数を作成し,(add-hook '***-mode-hook '***-mode-hooks) で登録します.これは モダン hook 入門。 を参考にした書き方です.

例えば ruby-mode-hook では以下のような書き方になります.

(defun ruby-mode-hooks ()
  ;; ruby-mode に Hook したい関数群
  ...
)

(add-hook 'ruby-mode-hook 'ruby-mode-hooks)

この書き方をしておけば,mode-hook を変更したい場合に,関数の再評価で挙動を変更できます.

lambda で書いてしまうと,再評価したときに新しく記述した関数が追加されるだけで,以前の mode-hook の内容も残ってしまいます.

設定ファイルの数々

000_init.el

;;; dwim 用の入力関数
;;; - リージョンが指定されていないときはミニバッファから入力を求める
;;; - 指定されているときはリージョンの文字列を得る
;;; - ミニバッファの入力のデフォルト値は現在位置のシンボル(単語)
(defun region-or-prompt (prompt)
  (if (region-active-p)
      (prog1
          (buffer-substring (region-beginning) (region-end))
        (deactivate-mark)
        (message ""))
    (read-string (format "%s (default:%s): " prompt (thing-at-point 'symbol))
                 nil nil (thing-at-point 'symbol))))

;;; 前置引数で動作を変える関数作成用
;;; (auto-install-from-url "https://github.com/tm8st/emacs-prefix-arg-commands/raw/master/prefix-arg-commands.el")
(require 'prefix-arg-commands)

region-or-prompt は dwim 用の入力関数です.

Emacs では末尾に dwim が付く関数がいくつかあります.DWIM は "Do What I Mean" の略で,その状況に応じて異なる処理を行うような関数に dwim が付けられます.

例えば,Emacs でプログラミングを書く人であれば M-; でコメント化する機能はよく使用すると思います.このコマンドは,普段はその行にコメントを書くための記号を入力しますが,色付きのリージョンがあるときは色を付けた部分をコメント化するコマンドとして働きます.このコマンドの名前は comment-dwim です.

region-or-prompt は,そんな dwim 関数を作るための補助関数です.色付きのリージョンがあるときはリージョン内の文字列を返し,そうでなければミニバッファにカーソル位置の単語が入った状態で入力を求められます.カーソル位置の単語をちょっと書き換えて関数に渡したいときなどに使えます.

また,prefix-arg-commands も設定を書く際に非常に便利です.

人は大量のコマンドにキーを割り当てても覚えられないものです.

似たような処理は C-q, C-u C-q, C-u C-u C-q のように,同じキーに対して C-u を前置する形で割り当てることで,「○○関係の機能を使うならとりあえず最後は C-q だ」というように少しでも覚える負担を減らすようにしましょう.

00_command-keymap.el

;;; C-h にバックスペース
(keyboard-translate ?\C-h ?\C-?)

;;; 自前プレフィックス
(global-unset-key (kbd "C-l"))
(global-set-key (kbd "C-l C-l") 'recenter-top-bottom)

;;; C-l r(R) で置換
(global-set-key (kbd "C-l r") 'query-replace)
(global-set-key (kbd "C-l R") 'query-replace-regexp)

C-h にバックスペースを割り当てるのは Emacs を使う人にとっては当然ですよね?

C-l は押しやすいキーの中では重要度が低いキーが割り当てられているので,割り当てられるキーを増やすためにプレフィックスキーとして使っています.

押しにくい置換のコマンドを C-l r に変更したり,新しい機能にキーを割り当てたい場合はこのプレフィックスキーを使った割り当てを考えるようにしてます (既にワンストロークキーの大半が埋まってしまっているので).

頻繁に使うにも関わらず押しにくいキーをどの押しやすいキーに割り当てるか.押しやすいキーは何に割り当てるか.使えるキーは限られているためとても重要な課題です.

04_dmacro.el, 04_popwin.el, 04_sequential-command.el, 05_smartrep-0.el など

04_, 05_ にでは自分が便利だと思う機能を追加しているのですが,その中でも

辺りは特に便利なので興味があれば是非調べてみてください.

06_flycheck-0.el

Emacs でのリアルタイムシンタックスチェックといえばもちろん,みんな大好き flymake ですね.ただ設定したことがある人はわかると思うのですが, flymake って設定がけっこう面倒なんですよ.

そんな flymake の面倒な設定をまとめたのが flycheck になります.広く使われている言語であれば,メジャーモードに hook するだけで使えます.

拡張も簡単にできるので,flymake を使っている人は乗り換えも検討してはいかがでしょうか.

06_foreign-regexp.el

;;; (package-install 'foreign-regexp)
(require 'foreign-regexp)
(custom-set-variables
 '(foreign-regexp/regexp-type 'ruby)
 '(reb-re-syntax 'foreign-regexp))

Emacs の正規表現って正直わけわかんないですよね.私は嫌いです.

同じような人がいれば是非 foreign-regexp.el を使ってみてください.Perl, Ruby, JavaScript の中から好きな正規表現を選んで,それを検索などに利用できます.

私は Ruby が大好きなので Ruby の正規表現を使ってます.

07_migemo.el

;;; (package-install 'migemo)
(require 'migemo)
(setq migemo-command "cmigemo")
(setq migemo-options '("-q" "--emacs"))

;;; migemo-dict のパスを指定
(cond ((file-exists-p "/usr/share/migemo/utf-8/migemo-dict")
       (setq migemo-dictionary "/usr/share/migemo/utf-8/migemo-dict"))
      ((file-exists-p "/usr/share/cmigemo/utf-8/migemo-dict")
       (setq migemo-dictionary "/usr/share/cmigemo/utf-8/migemo-dict")))

(setq migemo-user-dictionary nil)
(setq migemo-regex-dictionary nil)

;;; 辞書の文字コードを指定.
(setq migemo-coding-system 'utf-8-unix)

;;; ライブラリーのロードと初期化
(load-library "migemo")
(migemo-init)

migemo もお気に入りの機能の一つです.日本語入力を ON にしなくても日本語が検索できます.

導入には migemo 自体のインストールも必要ですが,わずらわしい作業をしてでも入れる価値は十分あります.慣れると日本語で日本語を検索するのが煩わしくなるという弊害もあります.

詳しい内容は以下を参考にしてください.

migemoを使ってEmacsライフを快適に

10_color-moccur.el

Emacs には標準でバッファ内検索を行う occur という機能があるのですが,少しばかり貧弱なため,拡張がいろいろあります.color-moccur はその一つです.

全バッファやディレクトリ内の全ファイルに occur を行うことができます.設定しだいでは Word や Excel, PDF のファイルも検索できるらしいです (私は設定していませんが).

occur 族については以下を参考にしてください (color-moccur の説明もあります).

バッファの検索 ― occur

30_dired-0.el

;; 2画面なら他方にコピー
(setq dired-dwim-target t)

(defun dired-dwim-find-alternate-file ()
  "画面分割に適した `dired-find-alternate-file'.
通常は `dired-find-alternate-file' を行うが,画面分割されていて
他方のウィンドウに同じバッファが表示されていれば `dired-find-file'."
  (interactive)
  (cond
   ;; 同じバッファが他のwindowにある場合
   ((delq (selected-window) (get-buffer-window-list))
    (dired-find-file))
   ;; 同じバッファが他のwindowにない場合
   (t
    (dired-find-alternate-file))))


(defun dired-up-alternate-directory ()
  "バッファを増やさず上のディレクトリに移動."
  (interactive)
  (let* ((dir (dired-current-directory))
         (up (file-name-directory (directory-file-name dir))))
    (or (dired-goto-file (directory-file-name dir))
        ;; Only try dired-goto-subdir if buffer has more than one dir.
        (and (cdr dired-subdir-alist)
             (dired-goto-subdir up))
        (progn
          (find-alternate-file up)
          (dired-goto-file dir)))))


(defun dired-dwim-up-alternate-directory ()
  "画面分割に適した `dired-up-alternate-directory'."
  (interactive)
  (cond
   ;; 同じバッファが他のwindowにある場合
   ((delq (selected-window) (get-buffer-window-list))
    (dired-up-directory))
   ;; 同じバッファが他のwindowにない場合
   (t
    (dired-up-alternate-directory))))


(defun dired-dwim-quit-window ()
  "画面分割に適した `quit-window'."
  (interactive)
  (quit-window (not (delq (selected-window) (get-buffer-window-list)))))

dired は 2 画面ファイラのように使いたくて色々試したのですが,使い勝手と設定の簡単さのバランスで今のシンプルな設定におちついています.

基本的には,dired を 2 画面ファイラに近付けるために (setq dired-dwim-target t) 以上のことは行わないという方針です.もっと 2 画面ファイラっぽくするなら e2wm.el を使う方法などがあるんじゃないでしょうか.あと,私は触ったことないのですが Sunrise Commander というものもあるらしいです.

さて,dired を使用する際どうしても気に入らなかったのが,ディレクトリを移動する度にバッファが増え続けることです.Helm のバッファ一覧が dired で埋まるのは見るに堪えません.

a には dired-find-alternate-file という,バッファを増やさずにファイル/ディレクトリを開くコマンドが割り当てられているのですが,左右で画面を分割して同じディレクトリ(バッファ)を開いているときにそれを使うと,バッファが消えてしまうためもう片方の画面も変わってしまう残念な仕様です.

2 画面ファイラとして使用したい私には納得のいかない挙動なため,dired-find-alternate-file に代わる関数などを独自に定義しています.それが上記の Elisp の内容になります.これだけでとりあえず最低限 2 画面ファイラとして耐えうる挙動となりました.私自身はそこそこ満足しています.

あと,dired を使うのであれば wdired は是非使ってみてください.複数のファイルをリネームする抵抗が大きく下がります.矩形編集と相性が非常にいいので使用する際は CUA モードなどと一緒にどうぞ.

40_yasnippet.el

(require 'yasnippet)

(yas-global-mode 1)
(setq yas-prompt-functions '(yas-no-prompt))
(define-key yas-minor-mode-map (kbd "C-i") nil)


;;; フィールドの編集で smartchr が効かなくなる問題の修正
(remove-hook 'c-mode-common-hook
             '(lambda ()
                (dolist (k '(":" ">" ";" "<" "{" "}"))
                  (define-key (symbol-value (make-local-variable 'yas-keymap))
                    k 'self-insert-command))))

;;; キーが重複したときに yas-snippet-dirs のリストで先頭に近い
;;; ディレクトリのスニペットが挿入されるように修正
(defun yas--prompt-for-template (templates &optional prompt)
  (when templates
    (some #'(lambda (fn)
              (funcall fn (or prompt "Choose a snippet: ")
                       templates
                       #'yas--template-name))
          yas-prompt-functions)))

おなじみ yasnippet です.個人的にトラブルの多い Elisp トップ 3 に入ります.他の Elisp とよく干渉します.コード中央の remove-hook はまさに干渉対策です.

文句言ってますが,yasnippet は非常に便利なスニペット(コード展開)を提供してくれる Elisp なので手放せません.Emacs でプログラム書くのに使ったことない人は一度は試してみることをオススメします.私はポップアップ補完機能を提供する auto-complete と連携して使っています.

また文句になってしまうのですが,デフォルトのスニペット読み込みディレクトリに ~/.emacs.d/snippets を用意しているのに,その中に作ったスニペットの名前が標準提供されているスニペットと被ると自作の方が無視される挙動は理解できません.その挙動を回避するために標準のスニペットが格納されている elpa/yasnippet-***/snippets に自作のスニペットを作ると,パッケージ更新できれいに消えます.

この問題を回避するのが上記コードの一番下の関数になります.これで安心して ~/.emacs.d/snippets に自作スニペットを作ることができますね.

yasnippet については以下のリンクが参考になると思います.

emacs 最強スニペット展開プラグイン yasnippet.elのインストール!
yasnippet 8.0の導入からスニペットの書き方、anything/helm/auto-completeとの連携

50_helm-0.el

(require 'helm)
(require 'helm-config)
(require 'helm-migemo)
(require 'helm-mode)

(require 'helm-descbinds)

;;; キー設定
(global-set-key (kbd "C-;") 'helm-for-files)
(global-set-key (kbd "M-x") 'helm-M-x)
(global-set-key (kbd "M-y") 'helm-show-kill-ring)
(define-key helm-map (kbd "C-j") 'helm-maybe-exit-minibuffer)
(define-key helm-map (kbd "M-j") 'helm-select-3rd-action)
(define-key helm-map (kbd "C-;") 'anything-keyboard-quit)

;; ;;; 既存のコマンドを Helm インターフェイスに置き換える
(helm-mode 1)
;;; 自動補完を無効
(custom-set-variables '(helm-ff-auto-update-initial-value nil))
;;; helm-mode で無効にしたいコマンド
(add-to-list 'helm-completing-read-handlers-alist '(find-file . nil))
(add-to-list 'helm-completing-read-handlers-alist '(find-file-at-point . nil))
(add-to-list 'helm-completing-read-handlers-alist '(write-file . nil))
(add-to-list 'helm-completing-read-handlers-alist '(helm-c-yas-complete . nil))
(add-to-list 'helm-completing-read-handlers-alist '(dired-do-copy . nil))
(add-to-list 'helm-completing-read-handlers-alist '(dired-do-rename . nil))
(add-to-list 'helm-completing-read-handlers-alist '(dired-create-directory . nil))

;;; 一度に表示する最大候補数を増やす
(setq helm-candidate-number-limit 99999)

Emacs のヘビーユーザであれば知らない人はいないであろう Helm (Anything のフォーク) です.私は Helm が誕生する前から,その前身である Anything を使っており,今ではこれがないと Emacs がまともに使えません.

これを読んでいる人で Helm を使っていない人はそういないのではないかと思いますが,もし知らなかった人がいればすぐにでも試してみてください.helm-for-filesC-; に割り当てるだけで世界が変わります.

宣伝になってしまうのですが,私が作成した helm-dired-recent-dirs もよろしくお願いします.MELPA からもインストール可能です.

zsh の chpwd_recent_dirs を Emacs の dired と連携する

50_skk-henkan-muhenkan.el

;;; ひらがなとカタカナのトグル
(defun skk-j-mode-and-toggle-kana ()
  (interactive)
  (cond (skk-j-mode
         (skk-toggle-kana nil))
        (t
         (skk-j-mode-on))))


;;; 変換/無変換キーで対応したキー設定削除
(defun skk-load-hooks ()
  ;; キーでモードを変更しない
  (defvar skk-my-unnecessary-base-rule-list
    '("l" "q" "L"))
  (dolist (str skk-my-unnecessary-base-rule-list)
    (setq skk-rom-kana-base-rule-list
          (skk-del-alist str skk-rom-kana-base-rule-list))))

(add-hook 'skk-load-hook 'skk-load-hooks)


;;; 変換/無変換キーで対応したazik専用キー設定削除
(defun skk-azik-load-hooks ()
  ;; 1. l, q が使えないことによる変更を戻す
  ;; 2. tU で「っ」になるのを防ぐ
  (defvar skk-my-azik-unnecessary-rule-list
    '("xxa" "xxe" "xxi" "xxo" "xxu" "@" "tU"))
  (dolist (str skk-my-azik-unnecessary-rule-list)
    (setq skk-rom-kana-rule-list
          (skk-del-alist str skk-rom-kana-rule-list)))
  ;; l を使った小文字入力など
  (defvar skk-my-azik-additional-rom-kana-rule-list
    '(("la" nil ("ァ" . "ぁ"))
      ("le" nil ("ェ" . "ぇ"))
      ("li" nil ("ィ" . "ぃ"))
      ("lo" nil ("ォ" . "ぉ"))
      ("lu" nil ("ゥ" . "ぅ"))
      ("lya" nil ("ャ" . "ゃ"))
      ("lyu" nil ("ュ" . "ゅ"))
      ("lyo" nil ("ョ" . "ょ"))
      ("@" nil skk-today)))
  (dolist (rule skk-my-azik-additional-rom-kana-rule-list)
    (add-to-list 'skk-rom-kana-rule-list rule)))

(add-hook 'skk-azik-load-hook 'skk-azik-load-hooks)


;;; キー割り当て
(global-set-key (kbd "<zenkaku-hankaku>") 'skk-mode)
(global-set-key (kbd "<muhenkan>") 'skk-latin-mode)
(global-set-key (kbd "<henkan>") 'skk-j-mode-and-toggle-kana)
(global-set-key (kbd "S-<muhenkan>") 'skk-jisx0208-latin-mode)
(global-set-key (kbd "S-<henkan>") 'skk-toggle-katakana)

私は DDSKK ユーザなのですが,日本語入力に関しては Emacs を使う前から愛用していた「IME の on/off の切り替えに変換/無変換キーを使用する」という設定は手放すことができませんでした.

そのため,上のコードで ddskk の挙動を自分好みに改造しています.詳しい内容は以下のリンクで説明しています.

DDSKK の入力モード切り替えを変換/無変換キーで行う

72_smartchr.el

(require 'smartchr)


(defun my-smartchr-braces ()
  "Insert a pair of braces."
  (lexical-let (beg end)
    (smartchr-make-struct
     :insert-fn (lambda ()
                  (setq beg (point))
                  (insert "{\n\n}")
                  (indent-region beg (point))
                  (forward-line -1)
                  (indent-according-to-mode)
                  (goto-char (point-at-eol))
                  (setq end (save-excursion
                              (re-search-forward "[[:space:][:cntrl:]]+}" nil t))))
     :cleanup-fn (lambda ()
                   (delete-region beg end)))))


(defun my-smartchr-semicolon ()
  "Insert ';' and newline-and-indent"
  (lexical-let (beg end)
    (smartchr-make-struct
     :insert-fn (lambda ()
                  (indent-according-to-mode)
                  (setq beg (point))
                  (insert ";")
                  (newline-and-indent)
                  (setq end (point)))
     :cleanup-fn (lambda ()
                   (delete-region beg end)))))


(defun smartchr-keybindings-ruby ()
  (local-set-key (kbd ",")  (smartchr '(", " ",")))
  (local-set-key (kbd "=")  (smartchr '(" = " " == " " === " "=")))
  (local-set-key (kbd "~")  (smartchr '(" =~ " "~")))
  (local-set-key (kbd "+")  (smartchr '(" + " " += " "+")))
  (local-set-key (kbd "-")  (smartchr '(" - " " -= " "-")))
  (local-set-key (kbd ">")  (smartchr '(" > " " => " " >= " ">")))
  (local-set-key (kbd "%")  (smartchr '(" % " " %= " "%")))
  (local-set-key (kbd "!")  (smartchr '(" != " " !~ " "!")))
  (local-set-key (kbd "&")  (smartchr '(" & " " && " "&")))
  (local-set-key (kbd "*")  (smartchr '(" * " "**" "*")))
  (local-set-key (kbd "<")  (smartchr '(" < " " << " " <= " "<")))
  (local-set-key (kbd "|")  (smartchr '("|`!!'|" " ||= " " || " "|")))
  (local-set-key (kbd "/")  (smartchr '("/" "/`!!'/" " / " "// ")))
  (local-set-key (kbd "#")  (smartchr '("#{`!!'}" "#")))
  (local-set-key (kbd "(")  (smartchr '("(`!!')" "(")))
  (local-set-key (kbd "[")  (smartchr '("[`!!']" "[")))
  (local-set-key (kbd "{")  (smartchr '("{`!!'}" "{|`!!'|  }" "{")))
  (local-set-key (kbd "'")  (smartchr '("'`!!''" "'")))
  (local-set-key (kbd "\"") (smartchr '("\"`!!'\"" "\""))))

(defun smartchr-keybindings-c/c++ ()
  (local-set-key (kbd ";")  (smartchr '(my-smartchr-semicolon ";")))
  (local-set-key (kbd ",")  (smartchr '(", " ",")))
  (local-set-key (kbd "=")  (smartchr '(" = " " == " "=")))
  (local-set-key (kbd "+")  (smartchr '(" + " "++" " += " "+")))
  (local-set-key (kbd "-")  (smartchr '(" - " "--" " -= " "-")))
  (local-set-key (kbd ">")  (smartchr '(" > " " >> " " >= " "->" ">")))
  (local-set-key (kbd "%")  (smartchr '(" % " " %= " "%")))
  (local-set-key (kbd "!")  (smartchr '(" != " "!")))
  (local-set-key (kbd "&")  (smartchr '(" && " " & " "&")))
  (local-set-key (kbd "*")  (smartchr '("*" " * " " *= ")))
  (local-set-key (kbd "<")  (smartchr '(" < " " << " " <= " "<`!!'>" "<")))
  (local-set-key (kbd "|")  (smartchr '(" || " " |= " "|")))
  (local-set-key (kbd "/")  (smartchr '("/" " / " " /= ")))
  (local-set-key (kbd "(")  (smartchr '("(`!!')" "(")))
  (local-set-key (kbd "[")  (smartchr '("[`!!']" "[")))
  (local-set-key (kbd "{")  (smartchr '(my-smartchr-braces "{`!!'}" "{")))
  (local-set-key (kbd "'")  (smartchr '("'`!!''" "'")))
  (local-set-key (kbd "\"") (smartchr '("\"`!!'\"" "\""))))

(defun smartchr-keybindings-awk ()
  (local-set-key (kbd ",")  (smartchr '(", " ",")))
  (local-set-key (kbd "=")  (smartchr '(" = " " == " "=")))
  (local-set-key (kbd "!")  (smartchr '(" != " "!")))
  (local-set-key (kbd "~")  (smartchr '(" ~ " " !~ " "~")))
  (local-set-key (kbd ">")  (smartchr '(" > " " >= " ">")))
  (local-set-key (kbd "<")  (smartchr '(" < " " <= " "<")))
  (local-set-key (kbd "+")  (smartchr '(" + " "++" " += " "+")))
  (local-set-key (kbd "-")  (smartchr '(" - " "--" " -= " "-")))
  (local-set-key (kbd "|")  (smartchr '(" || " "|")))
  (local-set-key (kbd "&")  (smartchr '(" && " "&")))
  (local-set-key (kbd "/")  (smartchr '("/`!!'/" " / " "/")))
  (local-set-key (kbd "{")  (smartchr '("{`!!'}" my-smartchr-braces "{")))
  (local-set-key (kbd "\"") (smartchr '("\"`!!'\"" "\""))))


(add-hook 'ruby-mode-hook 'smartchr-keybindings-ruby)
(add-hook 'c-mode-hook 'smartchr-keybindings-c/c++)
(add-hook 'c++-mode-hook 'smartchr-keybindings-c/c++)
(add-hook 'awk-mode-hook 'smartchr-keybindings-awk)

smartchr は,

  • プログラムを書いているときにイコールを入力するときは " = " のように前後にスペースを入れることが普通
  • でも " == " や " === " を入力することもあるのでイコールを " = " に割り当てることはできない

とか,

  • " += " なんて入力するの面倒

とか,

  • "!~" と "~!" のどっちだったか,いつも忘れる (私です)

という人にオススメです.イコールを連打すると,

" = " → "= " → " ==" → "=" → " = " → ...

のように入力候補を変更してくれます.smartchr は以下のページでわかりやすく解説しています.

smartchr.el を使って生産性を上げるAdd Star

ちなみに,これに似た Elisp に key-combo.el というものもあります.

ソースコードを書いているときに "=" の前後にスペースを自動で挿入してくれる key-combo.el

この記事を書いたのも私なのですが,key-combo.el はかつて Anything と干渉することが判明してからは使用してなかったりします.

80_org-0.el

(add-to-list 'auto-mode-alist '("\\.org\\'" . org-mode))
(setq org-startup-truncated nil)
(setq org-return-follows-link t)
(setq org-hide-leading-stars t)


;;; 前置引数を付けて新しい見出しの階層を区別する設定
(defun org-insert-upheading (arg)
  "1レベル上の見出しを入力する。"
  (interactive "P")
  (org-insert-heading arg)
  (cond ((org-at-heading-p) (org-do-promote))
        ((org-at-item-p) (org-outdent-item))))

(prefix-arg-commands-create prefix-arg-commands-org-insert-heading
                            '((lambda () (org-insert-heading nil))
                              (lambda () (org-insert-subheading nil))
                              (lambda () (org-insert-upheading nil))))

(define-key org-mode-map (kbd "C-:") 'prefix-arg-commands-org-insert-heading)

私が大好きな org-mode の紹介です.

org-mode は Emacs 用のマークアップ言語モードになります.Wiki 記法やはてな記法,この Qiita の投稿でも使用する Markdown 記法などと同じ部類です.

私は基本的に文書を書くときは org-mode しか使いません.メモを取るときも org-mode を使用しますし,LaTeX やブログの文章を書くときも,まず org-mode で記述して,それを変換して LaTeX などに出力します.この Qiita の投稿も org-mode で書いてます.

全て org-mode を経由する理由として,記法自体の書きやすさ・覚えやすさや,Emacs Lisp による拡張・設定のしやすさ,Emacs に標準で付いている手軽さが挙げられますが,最も大きな理由は,環境を変えるたびに新しい記法を覚えたくないからです.

複数の記法を覚えてスラスラ使い分けられる人はすごいなぁと思います.でも,記憶力の悪い私には到底無理な話です.なので私は org-mode の記法だけを覚え,どんな文章を書く場合も org-mode を使用しています.

(ちなみに,見出しや箇条書きなど単純なメモやレポートを書く程度の記法は簡単ですが,表・リンクといった複雑な文書を作成したり,大量の機能を割り当てた大量のコマンドを使いこなすのはかなり難しいです.便利な機能である分,学習コスト非常に高いので,まずは基本的な機能を使って普段のメモを org-mode で取ることなどから始めてみるのをオススメします.Emacs 同様,使い慣れれば一生ものだと思います.)

さて,上記のコードの話ですが,私は C-: に新しい見出しの作成コマンドを割り当てており,そのコマンドの前に C-u を付けると一つ下の階層の見出しを,C-u C-u を付けると一つ上の階層の見出しが作成できます.C-: に割り当てたのは,単純に入力しやすいキーで空いているのがそこしかなかったというだけです (Emacs を使用している以上,M-RET というキーバインドはありえません).

興味と暇があれば以下の記事もどうぞ.

table.el の使い方と org-mode 連携
org-mode による論文作成入門
org-mode の publishing で特定のディレクトリ上のサイトマップを作成する
org 文書をはてな記法に変換する ox-hatena.el (旧 org-export-hatena) を作った

91_whitespace.el

(require 'whitespace)
(global-whitespace-mode 1)

;;; 強調したい要素を指定
(setq whitespace-style '(space-mark tab-mark face spaces tabs trailing))

;;; whitespace-space を全角スペースと定義
(setq whitespace-space-regexp "\\(\u3000+\\)")

;;; 全角スペース,タブに使用する記号
(setq whitespace-display-mappings
      '((space-mark ?\u3000 [?] [?_ ?_])
        (tab-mark     ?\t    [?^ ?\t] [?\\ ?\t])))

;;; 各要素の face 設定
(set-face-attribute 'whitespace-space nil
                    :foreground "green"
                    :background 'unspecified)
(set-face-attribute 'whitespace-tab nil
                    :foreground "purple"
                    :background 'unspecified
                    :underline t)
(set-face-attribute 'whitespace-trailing nil
                    :foreground "purple"
                    :background 'unspecified
                    :underline t)

;;; ファイル保存時に行末のスペースを除去
(add-hook 'before-save-hook 'delete-trailing-whitespace)

プログラム中に全角スペースやタブ文字なんてありえないですよね.そんなあなたに whitespace.el.プログラムで使っちゃいけない文字たちを強調してくれます.

Git や diff で反応しないように,コードの一番下の行末スペース自動除去もオススメです.

99_overriding-minor-mode-keymap.el

;;; 画面の分割と移動 (分割されていたら移動)
(defun other-window-or-split ()
  (interactive)
  (when (one-window-p) (split-window-sensibly (get-buffer-window)))
  (other-window 1))


;;; 上に加えてC-uが付いていたら画面を閉じる
(defun other-window-or-split-or-close (arg)
  "画面が1つなら分割、2つ以上なら移動。
C-uをつけるとウィンドウを閉じる。"
  (interactive "p")
  (case arg
    (4  (delete-other-windows))
    (16 (delete-window))
    (t  (other-window-or-split))))


;;; キーマップ変更は下のコメントを評価してから define-minor-mode を再評価
;;; (makunbound 'overriding-minor-mode-map)
(define-minor-mode overriding-minor-mode
  "全てのマイナーモードマップを無視してキーを割り当てる"
  t                                     ; デフォルトで有効
  nil                                   ; モードライン
  `((, (kbd "C-q") . other-window-or-split-or-close)
    (, (kbd "C-S-q") . rotate-window)
    (, (kbd "M-h") . backward-kill-word)))


;;; 特定のモードで強制グローバルキーを設定しなおす
(add-hook 'minibuffer-setup-hook
          (lambda ()
            (overriding-minor-mode -1)
            (local-set-key (kbd "M-h") 'backward-kill-word)))

(add-hook 'org-mode-hook
          (lambda ()
            (overriding-minor-mode -1)
            (local-set-key (kbd "C-q") 'other-window-or-split-or-close)
            (local-set-key (kbd "M-h") 'backward-kill-word)))

どんなモードであっても同じキーに割り当てていたい機能は overriding-minor-mode-map で割り当てることができます.

画面の分割と移動を状況に応じて使いわけてくれるキーをこの map に割り当てるというアイデアはるびきちさんのブログを参考にしました.

詳しくは以下をどうぞ.

そろそろEmacsのウィンドウについて一言いっとくか
特定のキーに強制的にコマンドを割り当てる方法

おわり

非常に長くなりましたが,以上が現在の私の .emacs.d です.

Emacs 歴 5 年ちょっとの私ですが,最近は .emacs.d も安定してきたようで,大幅な変更をしたり新しい Elisp を触ったりすることが少なくなってきました.

1 年ごとくらいに .emacs.d を整理して,メモ書きを残していきたいですね.そのためにも,時間があるときに積極的に新しい Elisp を触ってみたいと思います.