手元の init.el を淡々と紹介する

Emacs Advent Calendar 2012の記事です。昨日は @syohex さんのEmacsでポモドーロテクニック - Qiitaでした。

さて、今回は手元の init.el のネタを淡々と紹介してみます。
みんな知っているよねというネタばかりだとは思いますが、逆に珍しかったりするものもあるかもしれないので、すこしでもお役に立てれば幸いです。

構造化

まず、設定ファイルの構造化ですが、いろいろ試した結果以下のような感じで分けることで定着しました。

  • init.el 以下のファイルを読み込み
    • base 外部ライブラリに依存しない標準Emacsの設定
    • frame GUIなど見た目周りの設定
    • utils 便利ツールの読み込み、設定
    • modes 各種modeの読み込み、設定
    • keybinds グローバルキーバインドの設定
    • completions, e2wm, howm, org, wl, ... 各種機能ごとの大まかな設定

メインの環境がWindowsLinuxなどを行ったり来たりしていたので、移行の手間がなるべく楽になるようにしています。
base だけはOSやバージョンにあまり依存しないようにしておいて、最低限の動きを確保した上で、他のファイルをあわせていく感じです。
Emacsを動かすのはメインの環境だけなので、環境を見て分岐することはもうなくなりました。(メイン以外はVimが多いです。Windowsだとsakuraとか。)

キーバインドの設定を一箇所で行うようにしています。これで、キーバインドがかぶってしまって、どこで設定していたのか迷わなくなる心配がなくなりました。

この形で完全に納得しているわけではなく、もっといい方法はないかなと模索しています。

便利ツール色々

その場のファイル名やURLを開く ffap

細かいですが、必須です。

自動繰り返し dmacro

どう考えてもキーボードマクロの方が正確で確実だったりするのですが、
ちょっとしたマクロはずっとこっちを多用しています。

やっぱり「モード(状態)」が無い設計がいいのかもしれません。

ウインドウ切り替え elscreen

この手のものであれば、 windows.el か elscreen だと思いますが、elscreenを使っています。

普通だとバッファのヘッダー行にタブが表示されるのですが、ヘッダー行を占領してほしくないので、フレームのタイトルに一覧を表示させています。以下その設定コードです。

(defvar elscreen-my-title-maps '()) ; タイトルの変換をする

(defun my-elscreen-truncate-screen-name (screen-name truncate-length &optional padding)
  (let ((truncate-length (max truncate-length 4)))
    (cond
     ((> (string-width screen-name) truncate-length)
      (concat (truncate-string-to-width screen-name truncate-length nil) "~"))
     (padding
      (truncate-string-to-width screen-name truncate-length nil ?\ ))
     (t
      screen-name))))

(defun elscreen-frame-title-update ()
  (when (elscreen-screen-modified-p 'elscreen-frame-title-update)
    (let* 
        ((screen-list (sort (elscreen-get-screen-list) '<))
         (screen-to-name-alist (elscreen-get-screen-to-name-alist))
         (tab-width (elscreen-e21-tab-width))
         (title (mapconcat
		   (lambda (screen)
		     (format 
              "%s"
              (let ((label 
                     (elscreen-e21-tab-escape-%
                      (my-elscreen-truncate-screen-name
                       (reduce (lambda (x f) (funcall f x)) elscreen-my-title-maps
                               :initial-value (get-alist screen screen-to-name-alist))
                       tab-width t))))
                (if (eq screen (elscreen-get-current-screen))
                    (concat "【" label "】") label))))
		   screen-list " ")))
      (if (fboundp 'set-frame-name)
	  (set-frame-name title)
	(setq frame-title-format title)))))

(eval-after-load "elscreen"
  '(add-hook 'elscreen-screen-update-hook 'elscreen-frame-title-update))

;; elscreen-my-title-maps の使い方例

(defun skype--elscreen-title-name-map (x)
 (if (string-match "Skype\\(Chat\\|Message\\):\\[\\(.*\\)\\]$" x)
     (let* ((title (match-string 2 x))
            (buf (get-buffer x))
            (missed (if buf (skype--chat-missed-p 
                             (buffer-local-value 'skype-chat-handle buf))
                      nil)))
       (concat (if missed "★" "☆") title))
   x))
(add-to-list 'elscreen-my-title-maps 'skype--elscreen-title-name-map)

あんまり見ることが無いので、たまに確認するぐらいならこれで十分かなという感じです。

C-w で単語削除、

シェルと同様の操作はかなり快適です。また、もともとの機能も無駄になっていません。

(defun kill-region-or-backward-kill-word ()
  (interactive)
  (if (region-active-p)
      (kill-region (point) (mark))
    (backward-kill-word 1)))

; (global-set-key (kbd "C-w") 'kill-region-or-backward-kill-word)
クオートの取り換え

", ', ` をトグルします。RubyやJSでクオート変えることがよくあるので。

transpose-chars-or-swap-quotes を C-t に割り当てています。

(defvar swap-quotes-list
  '((?\" ?\') (?\' ?\`) (?\` ?\")))

(defun transpose-chars-or-swap-quotes (arg)
  (interactive "p")
  (or (swap-quotes) (transpose-chars arg)))

(defun swap-quotes ()
  (interactive)
  (catch 'break
    (dolist (i swap-quotes-list)
      (let ((target-char (car i))
            (replaced-char (cadr i))
            (prev-pos (point)))
        (if (= (char-after (point)) target-char)
            (save-excursion
              (forward-char 1)
              (let ((next-pos (re-search-forward (char-to-string target-char))))
                (if next-pos
                    (subst-char-in-region prev-pos next-pos target-char replaced-char)
                  (message "The corresponding quote is not found.")))
              (throw 'break t)))))
    nil))
スペースを一気に削除

コードをコピペしたり行をつなげたりすると、スペースを大量に何とかする必要があって、そんな時によく使ってます。カーソールの場所がスペース文字かそうでないかで動きが変わります。

kill-word-or-delete-horizontal-space を M-d に割り当てています。

(defun kill-word-or-delete-horizontal-space (arg)
  (interactive "p")
  (let ((pos (point)))
    (if (and (not (eobp))
             (= (char-syntax (char-after pos)) 32)
             (= (char-syntax (char-after (1+ pos))) 32))
        (prog1 (delete-horizontal-space) 
          (unless (memq (char-after pos) '(?( ?) ?{ ?} ?[ ?]))
            (insert " ")))
      (kill-word arg))))
let にアスターを後で付ける

アスターなるべく付けないほうがいいのかなと思っていて、そうすると後で必要になった時にカーソールを移動するのが面倒なので、よく使ってます。


日本語逆変換

間違えて確定して逆変換したいことがたまにあるのですが、IMによってはサポートしてなかったりするのでEmacs内で逆変換を実現させてみました。形態素解析などを使って強引に漢字からローマ字に変換して、キー入力エミュレートして変換させます。

mozcとatokanthyとかの連文節変換を仮定しています。MeCabChaSenが必要です。

下はIIIMECFでいろいろ実験してた時の動画ですが、逆変換の動きもあります。



js2-mode

moozさんのところのjs2を使っています。最高です。

JSには決まったクラスの書き方があまりなくて、各エディタや支援ツールが解析に苦労しているところです。
js2でもそこをうまく考えて設計してあり、予めメジャーなクラスシステムのパターンが登録してあるのですが、ASTのパターンを使って独自のクラスシステムも登録できるようになっています。

例えば、以下のようなプロトタイプ指向なクラスシステムがあった時、

// Class1をクローンして上書き
var Class2 = clone(Class1, {
    method1: function() { ... },
    field1: 123
});

以下のようなコードでこのパターンを登録できます。

(eval-after-load 'js2-mode
  '(progn
     (require 'js2-imenu-extras)
     (push 
      `(:framework object-clone
                   :call-re   ,(concat "\\_<clone(" js2-mode-identifier-re "\\s-*,\\s-*")
                   :recorder  js2-imenu-record-object-clone-extend)
      js2-imenu-extension-styles)
     (js2-imenu-extras-setup)))

(defun js2-imenu-record-object-clone-extend ()
  (let* ((node (js2-node-at-point (1- (point)))))
  (when (js2-call-node-p node)
    (let* ((args (js2-call-node-args node))
           (methods (second args))
           (super-class (first args))
           (parent (js2-node-parent node)))
      (when (js2-object-node-p methods)
        (let ((subject (cond ((js2-var-init-node-p parent)
                              (js2-var-init-node-target parent))
                             ((js2-assign-node-p parent)
                              (js2-assign-node-left parent)))))
          (when subject
            (js2-record-object-literal methods
                                       (js2-compute-nested-prop-get subject)
                                       (js2-node-abs-pos methods)))))))))

多少認識の甘いところがあるのですが、だいたいうまくいっています。

MochikitとかEXT.jsなどのクラスシステムとかでも、うまく認識させることができそうです。

smartchr でPHPの頻出文字を入力

こんなのを多用するのもどうかとは思いますが、そういう仕事も多いので、以下のような感じで使っています。

(eval-after-load "php-mode"
  '(progn
     (define-key php-mode-map (kbd "F") (smartchr '("F" "$" "$this->")))
     (define-key php-mode-map (kbd ">") (smartchr '(">" " => ")))
     (define-key php-mode-map (kbd "?") (smartchr '("?" "<?php `!!' ?>" "<?php echo `!!' ?>")))
     (define-key php-mode-map (kbd "a") (smartchr '("a" "array(`!!')")))
     (define-key php-mode-map (kbd "[") (smartchr '("[" "[`!!']" "[\'`!!'\']")))
     ))

(eval-after-load "sgml-mode"
  '(progn
     (define-key sgml-mode-map (kbd "\"") (smartchr '("\"" "\"`!!'\"" "&quot;`!!'")))
     (define-key sgml-mode-map (kbd ">") (smartchr '(">" "&gt;")))
     (define-key html-mode-map (kbd "=") (smartchr '("=" "<%= `!!' %>")))
     (define-key html-mode-map (kbd "%") (smartchr '("%" "<% `!!' %>")))
     ))

smartchr にするか yasnippet にするかは、悩みどころです。

キー入力数と言うよりは、リズムとか気持ちよさの問題なのかなと思っています。



以上です。

明日(すいません。今日ですが。。。)は id:uhiaha888 さんです。よろしくお願いいたします。