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-buffers
や tramp-cleanup-all-buffers
のように、一括で削除するコマンドを作りました。削除のルールは、buffer-name
、mode-name
(major-mode
ではない)、もしくは buffer-file-name
と list-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)))))