Magit でコメントの履歴を便利に使う (後編)

Magit の小ネタ、後編です。前編はこちら

前回はコメントの履歴を session.el で恒久化しました。今回は、もう少し履歴を使い易くしたいと思います。

vc.el や Magit のコメント入力ウィンドウでの M-p / M-n による履歴呼び出しは確かに便利ですが、入力中のコメント全体が履歴で上書きされてしまう点が、ちょっと残念だと思います。特に Magit の場合は、コメントの中に Magit 用のヘッダ ("Commit-All: yes" など) も含まれているため、その不便さが特に気になります。大抵、そこは再利用したくないですよね。

そこで、過去のコメントを上書きするのではなくポイントに挿入するようにして、かつ Magit のヘッダは無視するようにしてみましょう。インターフェイスとしては anything.el を利用します。(anything.el の設定は割愛します。)

;;;
;;; 過去に入力したコメントを anything で選択できるようにする
;;;

(defvar anything-c-source-log-edit-comment
  '((name . "Log-edit Comment")
    (candidates . anything-c-log-edit-comment-candidates)
    (action . (("Insert" . (lambda (str) (insert str)))))
    (migemo)
    (multiline))
  "Source for browse and insert Log-edit comment.")

(defun anything-c-log-edit-comment-candidates ()
  (loop for comment in (ring-elements log-edit-comment-ring)
        ;; コメントから Magit のヘッダ部分を除去
        collect (if (string-match ".*-- End of Magit header --\n" comment)
                    (setq comment (substring comment (match-end 0)))
                  comment)))

(defun anything-show-log-edit-comment ()
  "`anything' for Log-edit comment."
  (interactive)
  (anything-other-buffer 'anything-c-source-log-edit-comment
                         "*anything log-edit comment*"))

;; Magit のコメント入力時に C-M-p で anything によるコメント選択起動
(define-key magit-log-edit-mode-map (kbd "C-M-p") 'anything-show-log-edit-comment)

上のプログラムでは、 Magit のコメント入力画面でのみ C-M-p でコメント選択用の anything インターフェイスを起動していますが、キーバインドは自分の好みに合わせてください。 vc.el で使いたい場合は、そちらのキーマップに割り当てるか、グローバルなバインディングとしてしまえばいいと思います。

動作確認環境は以下の通りです。

その他…

ここまでやって、大体満足の行くものになりました。Tortoise 某の便利さに近いです。最近の Tortoise 某では、実際にコミットを行わなかった時の履歴も残しますよね。確かに、コメントまで書いてからコミット前に不備に気づいて一旦中断するということも結構あるので (特にやり直しのきかない Subversion の時)、あったら便利そうです。 Magit でそうすることも割とすぐにできるのですが、今回はやっていません。やっぱり欲しいとなったら追記しようかと思います。

Magit でコメントの履歴を便利に使う (前編)

Magit についての小ネタを 2 つ紹介。今回が前編です。後編はこちら

WindowsVCS のフロントエンドとして Tortoise 某を使っていると、過去のコミット時に入力したコメントを再利用できるのが非常に便利だと感じるようになります。何度か続けて似たようなコメントを書くことが結構あるからです。Emacs の標準 VCS フロントエンドである vc.el や Magit でも、コメント入力ウィンドウで M-p / M-n を押すと、過去に入力したコメントを取り出すことができます。
ですが、その履歴は log-edit-comment-ring という変数に記録されているだけなので、 Emacs を終了すれば失われてしまいます。そこで、 session.el を使って履歴をずっと残すようにしてみましょう。

;;;
;;; log-edit.el のコメント履歴を session.el で保存する
;;;

;; session.el で保存するコメント履歴
(defvar my-log-edit-comment-ring nil)

;; コメント履歴が追加された時に `log-edit-comment-ring' を session.el で保存できる形式に変換
(defadvice magit-log-edit-push-to-comment-ring (after my-store-log-edit-commment-ring activate)
  (setq my-log-edit-comment-ring (my-ring-to-list log-edit-comment-ring)))

;; リングをリストに変換 (car にリングサイズ、cdr に要素)
(defun my-ring-to-list (ring)
  (when (ring-p ring)
    (append (list (ring-size ring)) (ring-elements ring))))

;; `my-ring-to-list' で作成したリストをリングに変換
(defun my-list-to-ring (l &optional newsize)
  (let* ((size (if newsize newsize (car l)))
         (orgelem (append (cdr l) nil)) ; 後でリストを破壊するので append を使ってリストをコピー
         (elem (reverse (if (>= size (length orgelem))
                            orgelem
                          (setcdr (nthcdr (1- size) orgelem) nil)
                          orgelem)))
         (ring (make-ring size)))
    (while elem
      (ring-insert ring (car elem))
      (setq elem (cdr elem)))
    ring))

;; session.el に保存したコメント履歴を log-edit.el に復元
(add-hook 'session-after-load-save-file-hook
          (lambda ()
            (when (boundp 'my-log-edit-comment-ring)
              (setq log-edit-comment-ring
                    (my-list-to-ring my-log-edit-comment-ring
                                     (when (boundp 'log-edit-comment-ring)
                                       (ring-size log-edit-comment-ring)))))))

勿論、別途 session.el を読み込んでいる必要があります。

動作確認環境は以下の通りです。

実装など細かい話

vc.el も Magit も、コメント履歴の管理を log-edit.el に委譲しています。変数 log-edit-comment-ring は、 ring.el によるリングバッファです。

元々 session.el は、 "-ring" で終わる名前の変数を自動的に記録してくれるのですが、 ring.el のリングバッファはリストの中にベクトルを含んでいるため、 session.el の管理対象から除外されてしまいます。そもそも session.el の管理から明示的に除外する変数を列挙する session-globals-exclude に vc-comment-ring (log-edit-comment-ring の昔の名前) がデフォルトで含まれていたりしますし、session.el と ring.el の相性は良くないのかもしれません。