Emacs Evil Tips

最終更新日 2005/05/11

邪悪な設定編

ここでは、邪悪な Emacs の設定を紹介します。

設定ファイルを自動的にバイトコンパイル

問題

.emacs や .gnus.el などの設定ファイルは、Emacs を長年使っている間に、 少しずつ肥大化してしまうものです。 このような状況では次のような問題が生じます。

原因

起動が遅いのは、 肥大化した設定ファイルを読み込んでパースするのに時間がかかっているためです。 また、 実行速度が遅いのは、 設定ファイルで定義したバイトコンパイルしてない elisp のコードを実行しているためです。

解決方法

以下のような解決方法が考えられます。 多少遅くても我慢できるのであれば、我慢したほうがいいでしょう。 バイトコンパイルしても飛躍的に速くなるわけではありませんので。

一方、少しでも速い方がいいのであれば、 バイトコンパイルを試してみる価値はあります。

以下に、設定ファイルを自動的にバイトコンパイルするための設定を示します。

次の設定は、バッファセーブ時にバイトコンパイルするためのものです。 バッファセーブ時に、カレントバッファがバイトコンパイル対象であるか調べて、 対象であればバイトコンパイルします。 私は、この設定のほうが、 後述する Emacs 終了時にバイトコンパイル設定よりも、使いやすいと思います。

;; バッファセーブ時に、カレントバッファがバイトコンパイル対象ならバイ
;; トコンパイルする
(add-hook 'after-save-hook
          (lambda ()
            (mapcar
             (lambda (file)
               (setq file (expand-file-name file))
               (when (string= file (buffer-file-name))
                 (save-excursion (byte-compile-file file))))
             '("~/.emacs" "~/.gnus.el" "~/.wl"))))
次の設定は、Emacs 終了時にバイトコンパイルするためのものです。 Emacs 終了時に、バイトコンパイル対象のファイルが更新されていかどうか調べて、 更新されているときだけバイトコンパイルします。
;; Emacs 終了時に、バイトコンパイル対象が更新されているときだけバイト
;; コンパイルする
(add-hook 'kill-emacs-hook
          (lambda ()
              (mapcar
               (lambda (file)
                (setq file (expand-file-name file))
                (when (file-newer-than-file-p file
                                              (byte-compile-dest-file file))
                  (save-excursion (byte-compile-file file))))
               '("~/.emacs" "~/.gnus.el" "~/.wl"))))
この設定には、設定ファイルにエラーがあった場合に、 そのエラーを発見しにくいという問題があります。 バイトコンパイル時に出されるエラーメッセージを確認する前に、 Emacs が終了してしまうからです。

備考

ここに示した設定だと、 バイトコンパイル時に多くの警告メッセージが出てうっとうしいかと思います。 eval-when-compile () を使う警告メッセージ抑止のテクニックも、 この場合は使えませんので、困ったものです。 何か、いい方法があったら教えてください。

Wanderlust でバナー広告を消す

問題

特定のメーリングリストサービス(eGroup 等)やメールサービス(yahoo.co.jp 等) を利用したメールには、 メール中にバナー広告が挿入されることがあります。 この広告は、メールを読む側にすると大変うっとうしいものであって、 できれば見たくないものです。

このような考えに基づき、Gnus/gnus では、 グループパラメタを使ってバナー広告を消す機能が提供されています。

しかしながら、Wanderlust では、このような機能は提供されておらず、 簡単に広告を消すことができません。

原因

Wanderlust では、バナー広告を消す機能が提供されていないことが原因です。

解決方法

Wanderlust として提供されていないのであれば、 自分で実装すればいいのです。 elisp で書いてあるので、簡単に機能追加できます。

処理の手順は次のようになります。

以下に設定を示します。この設定を ~/.wl に追加することで、 バナー広告を消すことができます。 my-wl-banner-list を各自でカスタマイズしてください。

なお、この設定は、あくまでも、広告を非表示にするだけであり、 メールの元データから削除するわけではありません。

;;;
;;; バナー広告を消すための設定
;;;

;; 見たくないバナー広告を正規表現で指定
;; リストで複数指定可能
(setq my-wl-banner-list 
      '(
        ;; eGroups のバナー
        "^-+ eGroups Sponsor -+~-->\n\\(.\\|\n\\)+-+~->\n\\(.\\|\n\\)+"
        ;; Yahoo! BB のバナー
        "^_+\nDo You Yahoo!\\?\\(.\\|\n\\)+http://bb\\.yahoo\\.co\\.jp/\n"))

;; メール表示時にバナー広告を削除
(add-hook 'wl-message-display-internal-hook
          (lambda ()
            (let (buffer-read-only)
              (goto-char (point-min))
              (while (re-search-forward 
                      (concat "\\(" (mapconcat 'identity
                                               my-wl-banner-list "\\|") "\\)")
                      (point-max) t)
                (replace-match ""))
              (set-buffer-modified-p nil))))

備考

正規表現の指定が少々難しいのでコツを少し述べます。

例えば、次のようなバナー広告を消す場合を考えます。

----- banner start ------
毎回変わる複数行の広告
...
...
----- banner end ------
この場合、広告の開始と終了のキーワードが決まってますので、わりと簡単です。 正規表現は次のようになります。 複数行の広告にマッチする部分を `\\(.\\|\n\\)+ ' と表現するのがポイントです。
"^----- banner start ------\n\\(.\\|\n\\)+----- banner end ------\n"
`-' の数に曖昧さを持たせても問題ないのであれば、 次のように簡略化できます。
"^-+ banner start -+\n\\(.\\|\n\\)+-+ banner end -+\n"

Wanderlust で快適に POP 経由でメールを取得する

問題

新着メールのチェックやメールの取得は、頻繁に行うものであり、 快適に実行できることが望ましいものです。

しかしながら、私の環境では、 Wanderlust で POP + パイプフォルダを使った場合、 非常に遅いと言う問題がありました。 新着メールのチェックに20秒程度、 小さなメール1 通を取ってくるのに1 分近くもかかっていました。

特に、 スピード狂で知られる Gnus/gnus から Wanderlustに移行してきた私にとっては、 耐えられるものではありませんでした。 Gnus/gnus では、 上記のような新着メールのチェックや取得は瞬時(コンマ数秒程度) に終わっていたものでした。

原因

調査の結果、次のことが分かりました。 したがって、新着メールのチェックやメールの取得が遅くなるのは当然と言えます。

さらに、現状の Wanderlust の実装では、Gnus/gnus ほど性能を重視してチューニングされていないことも一因だと考えられます。

解決方法

以下のような解決方法が考えられます。 これらの中では、fetchmail を使うのが正道だとは思います。 この場合は、次のような処理手順となるのが普通です。
  1. Wanderlust から fetchmail を起動してメールを取得する
  2. procmail でメールを振り分ける
  3. prom-wl で新着メールをチェックする
しかしながら、fetchmail を使っても、Gnus を使う場合に比べると、 まだまだ遅いです。 そこで、ここでは、邪道な例として、 Gnus の POP アクセスの実装をを使う方法を紹介します。

処理のおおまかな流れは次のようになります。

  1. Gnus を利用してメールを取得する
  2. elmo-split でメールを振り分ける
  3. prom-wl で新着メールをチェックする
ここで、Gnus としては、Emacs に含まれているものが利用できます。 ただし、特定の版(例えば、Meadow-1.99に含まれるもの)には、 APOP 認証に問題あります。 うまく認証できない場合には、最近の版の Gnus/gnus を試してください。

elmo-split は Wanderlust に含まれています。

prom-wl は Prom-WL -- Procmail reader for Wanderlust -- から取得してください。

以下に設定を示します。この設定を ~/.wl に追加することで、Gnus/gnus と 同じ感覚で、メールを取得できるようになります。メールを取得するには、 Wanderlust のフォルダーバッファにて `g' を入力します。

;;;
;;; Gnus を使って POP 経由でメールを取得
;;;

;; 取得したメールの保存先の指定
(setq my-mail-spool-directory "spool")

(require 'nnml)
(require 'nnmail)

(defun my-wl-fetch-mail (&optional arg)
  (interactive "P")
  (when wl-plugged                      ; off-line の場合は何もしない
    (let (
          ;; メールディレクトリの設定
          (mail-source-directory elmo-localdir-folder-path)
          (nnml-directory elmo-localdir-folder-path)
          ;; 取得したメールの保存先の設定
          (nnmail-split-methods `((,my-mail-spool-directory)))
          ;; メール取得用の一時ファイルを削除
          (mail-source-delete-incoming t) 
          ;; ???
          (nnmail-fetched-sources '(t))
          ;; POP サーバの指定
          (mail-sources '((pop :server "your.pop.server"
                               ;; APOP の場合は下記を指定する
                               ;; :authentication apop
                               ;; 詳しくは`(gnus)Mail Source
                               ;; Specifiers' を参照
                               )))
          ;; Message-ID cache を残さない
          nnmail-treat-duplicates)
      ;; メールの取得
      (nnmail-get-new-mail 'nnml nil nnml-directory))
    ;; メール振り分けのために prom-wl を起動する
    (when (fboundp 'prom-wl)
      (prom-wl))))
 
(add-hook 'wl-folder-mode-hook
          (lambda ()
            (define-key wl-folder-mode-map "g" 'my-wl-fetch-mail)))

;;;
;;; elmo-split の設定
;;;
(autoload 'elmo-split "elmo-split" "Split messages on the folder." t)

;; 振り分けの対象となるフォルダを指定
(setq elmo-split-folder (list (concat "+" my-mail-spool-directory)))

;; ログファイルの指定
(setq elmo-split-log-file "~/.elmo/split-log")

;; 振り分けルールの指定
(setq elmo-split-rule
      '(
        ;; Wanderlust の info を見て、自分流の振り分け規則を書くべし
        ;; ...
        ;; X-ML-Name: があれば、それを名前とするフォルダに移動
        ((match x-ml-name "\\(.*\\)") "+\\1")
        ;; ...
        ;; どれにもマッチしなかったものを `+inbox' へ
        (t "+inbox")))

;;;
;;; prom-wl の設定
;;;
(cond
 ((locate-library "prom-wl")
  (autoload 'prom-wl "prom-wl" "Prom for Wanderlust" t)

  ;; elmo-split のログファイルの指定
  (setq proc-log-list (list elmo-split-log-file))

  ;; elmo-split のログからフォルダ名を抽出するパターンの指定
  (setq proc-folder-regexp "^  Folder: \\+\\([^ \t\n]+\\)")

  ;; prom-wl のログファイル指定
  (setq proc-keep-log
        (expand-file-name "listlog" elmo-localdir-folder-path))

  ;; Windows の場合は、シンボリックリンクがうまく動かないので、ファイ
  ;; ルのロックを自前で処理
  (when (eq system-type 'windows-nt)
    ;; lockfile を使わない
    (setq prom-use-lockfile nil)

    ;; ファイルのロックは自前で処理
    (eval-after-load "prom-wl"
      '(defun prom-make-lock (lockfile)
         (if (file-exists-p lockfile)
             (progn
               (message "lock file exists!!")
               nil)
           (write-region "" nil lockfile nil 0)
           t))))

  ;; prom-wl から elmo-split を実行
  (add-hook 'prom-wl-get-new-mail-pre-hook 'elmo-split)))

備考

上記の mail-sources 設定を変えることで、 複数の POP サーバを指定することもできますし、 APOP 認証を指定することもできます。 詳しくは Info の `(gnus)Mail Source Specifiers' の節を参照してください。

ちょっとした設定編

ここでは、ちょっとした Emacs の設定を紹介します。

フレームのサイズをディスプレイに合わせる

フレームのサイズをディスプレイの解像度に合わせて変更する例を示します。

この設定を .emacs に記述することで、 フレームの高さをディスプレイの高さの92%とすることができます。 つまり、ディスプレイの解像度が低い場合は小さなフレームが、 解像度が高い場合は大きなフレームが開くようになります。

フレームで使用するフォントの設定が完了しているのであれば、 行の高さを知るのに frame-char-height() を利用できます。 ところが、下記のような設定では、この前提が必ずしも成立しません。 したがって、利用するにはそれなりの工夫が必要でしょう。

(let* ((line-height 16)                 ; 行の高さ
       (scale 0.92)                     ; ディスプレイの高さを1としたときの
                                        ; フレームの高さの割合
       ;; ディスプレイの高さを行の高さで割り行数に変換して、scale 倍する
       (height (round (* (/ (x-display-pixel-height) line-height) scale))))
  ;; フレームパラメタに設定
  (setq default-frame-alist
        (cons (cons 'height height) default-frame-alist)))
もちろん、ディスプレイの高さ方向の解像度と、 フレーム行数との関係を表す alist を使うことも可能です。 この場合の設定例を示します。
(setq resolution-alist
      '((480 . 29)                      ; 480ピクセル -> 29行
        (600 . 33)                      ; 600ピクセル -> 33行
        (768 . 43)                      ; 768ピクセル -> 43行
        (1024 . 59)))                   ; 9999ピクセル -> 59行

(setq default-frame-alist
      (cons (cons 'height 
                  (cdr (assoc (x-display-pixel-height) resolution-alist)))
            default-frame-alist)))
さまざまな解像度のディスプレイが存在するため、 すべてを列挙するのが困難な場合には、次のような設定も有効でしょう。 この設定では、ディスプレイの解像度が480ピクセル以下の場合には、 フレームの行数を29行とするといったことが可能です。
(setq resolution-alist
      '((480 . 29)                      ; 480ピクセル以下 -> 29行
        (600 . 33)                      ; 600ピクセル以下 -> 33行
        (768 . 43)                      ; 768ピクセル以下 -> 43行
        (9999 . 59)))                   ; 9999ピクセル以下 -> 59行

(setq default-frame-alist
      (cons (cons 'height 
                  (assoc-default (x-display-pixel-height)
                                 resolution-alist '>=))
            default-frame-alist)))