bufferの一括削除 (kill-buffers-with)

現在の環境で Emacs のバッファを開きすぎてメモリが足りなくなることはそんれほどありませんが、switch-buffer するときや、 Buffer List から選択するときなどは、バッファの数が多すぎると手間取ります。また、なにか調査するのために以下のようなコードから大量にバッファを作ってしまうこともあります。

(loop for v in list
      do (pp (something-with v) (generate-new-buffer "*my-temp*")))

そこで、ag-kill-bufferstramp-cleanup-all-buffers のように、一括で削除するコマンドを作りました。削除のルールは、buffer-namemode-name (major-mode ではない)、もしくは buffer-file-namelist-buffers-directory に対して、シェルスクリプトのファイルパターン (*, ?, [], {}) でマッチしたバッファを削除します。まずはパターンの正規表現化、

(defun pattern2regexp (pat)
  (cl-labels
      ((rec (pat-chars result)
            (pcase pat-chars
              ((pred null) (reverse result))
              (`(?* . ,tail) (rec tail (cons '(* not-newline)
                                             result)))
              (`(?? . ,tail) (rec tail (cons 'not-newline
                                             result)))
              ((and `(?\\ . ,tail)
                    (guard (not (null tail))))
               (rec (cdr tail) (cons (char-to-string (car tail))
                                     result)))
              ((and `(?\[ . ,tail)
                    (app (member ?\]) rest)
                    (guard (not (null rest))))
               (rec (cdr rest)
                    (cons (list 'in (concat
                                     (cl-loop for c in tail
                                              until (= c ?\])
                                              collect c)))
                          result)))
              ((and `(?\{ . ,tail)
                    (app (member ?\}) rest)
                    (guard (not (null rest))))
               (rec (cdr rest)
                    (cons (cons 'or
                                (split-string (concat
                                               (cl-loop for c in tail
                                                        until (= c ?\})
                                                        if (not (= c ? )) collect c)) ","))
                          result)))
              (`(,c . ,tail)
               (rec tail (cons (char-to-string c)
                               result)))))
       (exec-rx (pseq)
                (eval `(rx (seq line-start
                                ,@pseq
                                line-end)))))
    (exec-rx
     (rec (string-to-list pat) nil))))

正規表現には rx マクロを使っています。S式によって構造化できるので、複雑な表現をわかりやすく書けます。Emacs 拡張でテストを書くことはこれまでなかったのですが、今回は書いています。(test.el) あとは buffer-list をループ処理します。

(defun kill-buffers-with--type (name)
  (cond
   ((string= "buffer" name)
    #'(lambda (regexp buffer)
        (string-match regexp (buffer-name buffer))))
   ((string= "mode" name)
    #'(lambda (regexp buffer)
        (let ((mode-name (buffer-local-value 'mode-name buffer)))
          (unless (stringp mode-name) (setq mode-name ""))
          (string-match regexp mode-name))))
   ((string= "file" name)
    #'(lambda (regexp buffer)
        (string-match regexp
                      (abbreviate-file-name
                       (or (buffer-file-name buffer)
                           (buffer-local-value 'list-buffers-directory buffer)
                           "")))))
   (t (error ""))))

Ag モードや Dired の様にファイルリストを表示するバッファは buffer-file-name を持っていませんが、list-buffers-directory にディレクトリ名を持っていることがあります。(Ag と Dired は持っています)

(defun kill-buffers-with (pat type)
  (interactive (list (read-string "Pattern: ")
                     (completing-read "Select name type: "
                                      '("buffer" "file" "mode")
                                      nil t)))
  (let ((regexp (pattern2regexp pat))
        (match (kill-buffers-with--type type)))
    (loop for b in (buffer-list)
          for bn = (buffer-name b)
          with targets = nil
          when (and (not (string= " " (substring bn 0 1)))
                    (funcall match regexp b))
          collect bn into targets
          and do (kill-buffer b)
          finally (when targets
                    (message "killed buffers %S" targets)))))

(Project Repository)