save-excursionとカーソル周辺のブロック(関数など)の自動インデントを組み合わせてみた

  • Emacs Lisp を書いているときに自動インデントしたいときは C-M-a(beginning-of-defun)で関数の頭に移動して C-M-q(indent-pp-sexp)しているが、2ストロークなので面倒くさくなってきた
  • 1つにまとめよう
  • どうせだから save-excursion で囲んだらいいんじゃね?
  • どうせだから自分が良く使う他の言語でも同じフィーリングで使えるようにしたらいいんじゃね?

というわけで以下のようになりました。

言語によっては beginning-of-defun や indent-pp-sexp に相当するコマンド・関数が使えないこともあるので、その場合は

  • ルールで指定された関数を使ってブロックの先頭に移動した後で
  • ルールで指定された関数を使ってブロックの末尾に移動し、
  • その間をインデント

するようにしました。

なので、移動する関数を適当にいじるとカーソル位置などをもとに好きな範囲をインデントできます。

;; block-indent.el
;; author: sonota
;; license: GPL

;;;;
;; ルールの書式:
;;   ((modes) ; 関連付けるメジャーモードのリスト
;;    ブロック先頭に移動する関数
;;    ブロック末尾に移動する関数
;;    インデントを行う関数
;;    引数に開始・終了位置が必要なら non-nil)

;; .emacs で次のようにルールを追加する

;; (add-to-list
;;  'block-indent:rules
;;  '((ruby-mode)
;;    ruby-beginning-of-defun
;;    nil
;;    ruby-indent-exp nil))

;; (add-to-list
;;  'block-indent:rules
;;  '((js2-mode)
;;    beginning-of-defun
;;    forward-sexp
;;    indent-region t))

(defvar block-indent:default-rule
  '(nil
    backward-paragraph
    forward-paragraph
    indent-region t)
  "デフォルトのルール(ルールが見つからなかったときに使われる)")

(defvar
  block-indent:rules
  '(((emacs-lisp-mode lisp-interaction-mode)
     beginning-of-defun
     nil
     indent-pp-sexp nil))
  "ルールのリスト")

(defun block-indent:select-rule ()
  "メジャーモードをもとにルールを選択"
  (let (rule modes)
    (setq rule
          (car 
           (remove-if-not
            (lambda (x)
              (memq major-mode (nth 0 x)))
            block-indent:rules)))
    (unless rule
      (setq rule block-indent:default-rule))
    rule))

(defun block-indent:sub (rule)
  (let ((beginning-func (nth 1 rule))
        (forward-func   (nth 2 rule))
        (indent-func    (nth 3 rule))
        (region-args-p  (nth 4 rule))
        (beg nil))
    
    (funcall beginning-func)            ; ブロックの先頭に移動
    (setq beg (point))                  ; 先頭位置を保存
    (when forward-func
      (funcall forward-func))           ; ブロック末尾に移動
      
    ;; インデント
    (if region-args-p
        (funcall indent-func beg (point))
      (funcall indent-func))))

(defun block-indent ()
  (interactive)
  (save-excursion
    (block-indent:sub (block-indent:select-rule))))

(provide 'block-indent)