├── .github ├── dependabot.yml └── workflows │ └── ci.yaml ├── .gitignore ├── README.md ├── flake.lock ├── flake.nix └── kill-or-bury-alive.el /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | types: 8 | - opened 9 | - synchronize 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: cachix/install-nix-action@v31 16 | - run: nix build 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *-autoloads.el 2 | *.elc 3 | *~ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kill or Bury Alive 2 | 3 | [![License GPL 3](https://img.shields.io/badge/license-GPL_3-green.svg)](http://www.gnu.org/licenses/gpl-3.0.txt) 4 | [![MELPA](https://melpa.org/packages/kill-or-bury-alive-badge.svg)](https://melpa.org/#/kill-or-bury-alive) 5 | [![CI](https://github.com/mrkkrp/kill-or-bury-alive/actions/workflows/ci.yaml/badge.svg)](https://github.com/mrkkrp/kill-or-bury-alive/actions/workflows/ci.yaml) 6 | 7 | Have you ever killed a buffer that you might want to leave alive? Motivation 8 | for killing is usually “get out of my way,” and killing may be not the best 9 | choice in some cases. This package allows us to teach Emacs which buffers to 10 | kill and which to bury alive. 11 | 12 | When we want to kill a buffer, it turns out that not all buffers would like 13 | to die in the same way. To help with that, the package allows us to specify 14 | *how* to kill different kinds of buffers. This may be especially useful when 15 | a buffer has an associated process. 16 | 17 | ## Installation 18 | 19 | The package is available via MELPA, so you can just type `M-x 20 | package-install RET kill-or-bury-alive RET`. 21 | 22 | If you would like to install the package manually, download or clone it and 23 | put on Emacs' `load-path`. Then you can require it in your init file like 24 | this: 25 | 26 | ```emacs-lisp 27 | (require 'kill-or-bury-alive) 28 | ``` 29 | 30 | ## Usage 31 | 32 | I have noticed that people usually want to kill the current buffer. They 33 | even rebind `C-x k` to `kill-this-buffer`. This makes sense, it's natural 34 | for us to care about buffers that are visible and active. It's also 35 | intuitive to switch to some buffer before killing it. If you use this sort 36 | of workflow, you can add something like this to your initialization file: 37 | 38 | ```emacs-lisp 39 | (global-set-key (kbd "C-x k") #'kill-or-bury-alive) 40 | (global-set-key (kbd "C-c p") #'kill-or-bury-alive-purge-buffers) 41 | ``` 42 | 43 | The second function purges all buffers (see its description below). `C-c p` 44 | is just a key binding that I use myself, you can choose whatever you like, 45 | of course. 46 | 47 | ## API 48 | 49 | *Buffer designator* is something that can be used to match a particular kind 50 | of buffer. In `kill-or-bury-alive`, a buffer designator is either: 51 | 52 | * a string—a regular expression to match the name of a buffer; 53 | 54 | * a symbol—the major mode of a buffer. 55 | 56 | ---- 57 | 58 | ``` 59 | kill-or-bury-alive-kill-with buffer-designator killing-function &optional simple 60 | ``` 61 | 62 | Kill buffers selected by the `buffer-designator` with `killing-function`. 63 | 64 | Normally, `killing-function` should be able to take one argument: a buffer 65 | object. However, you can use a function that operates on the current buffer 66 | and doesn't take any arguments. Just pass non-`nil` `simple` argument and 67 | `killing-function` will be wrapped as needed automatically. 68 | 69 | *This function should be used to configure the package, it cannot be called 70 | interactively.* 71 | 72 | ---- 73 | 74 | ``` 75 | kill-or-bury-alive &optional arg 76 | ``` 77 | 78 | Kill or bury the current buffer. 79 | 80 | This is a universal killing mechanism. When the argument `arg` is given and 81 | it's not `nil`, kill the current buffer. Otherwise the behavior of this 82 | command varies. If the current buffer matches a buffer designator listed in 83 | `kill-or-bury-alive-must-die-list`, kill it immediately, otherwise just bury 84 | it. 85 | 86 | You can specify how to kill various kinds of buffers, see 87 | `kill-or-bury-alive-killing-function-alist` for more information. Buffers 88 | are killed with `kill-or-bury-alive-killing-function` by default. 89 | 90 | ---- 91 | 92 | ``` 93 | kill-or-bury-alive-purge-buffers &optional arg 94 | ``` 95 | 96 | Kill all buffers except for the long lasting ones. 97 | 98 | The long lasting buffers are specified in 99 | `kill-or-bury-alive-long-lasting-list`. 100 | 101 | If `kill-or-bury-alive-base-buffer` is not `nil`, switch to the buffer with 102 | that name after purging and delete all other windows. 103 | 104 | When `arg` is given and it's not `nil`, ask to confirm killing of every 105 | buffer. 106 | 107 | ## Customization 108 | 109 | This package can be customized via the customization system. Type `M-x 110 | customize-group RET kill-or-bury-alive RET` to try it out. 111 | 112 | There are quite a few variables that you can modify to control behavior the 113 | package. Let's list them (we give their default values too). 114 | 115 | ---- 116 | 117 | ``` 118 | kill-or-bury-alive-must-die-list => nil 119 | ``` 120 | 121 | A list of buffer designators for buffers that always should be killed. 122 | 123 | This variable is used by the `kill-or-bury-alive` function. 124 | 125 | ---- 126 | 127 | ``` 128 | kill-or-bury-alive-killing-function-alist => nil 129 | ``` 130 | 131 | AList that maps buffer designators to functions that should kill them. 132 | 133 | This variable is used by `kill-or-bury-alive` and 134 | `kill-or-bury-alive-purge-buffers`. 135 | 136 | You can use `kill-or-bury-alive-kill-with` to add elements to this alist. 137 | 138 | ---- 139 | 140 | ``` 141 | kill-or-bury-alive-long-lasting-list => 142 | ("^\\*scratch\\*$" 143 | "^\\*Messages\\*$" 144 | "^\\*git-credential-cache--daemon\\*$" 145 | erc-mode) 146 | ``` 147 | 148 | A list of buffer designators for buffers that should not be purged. 149 | 150 | This variable is used by `kill-or-bury-alive-purge-buffers`. 151 | 152 | ---- 153 | 154 | ``` 155 | kill-or-bury-alive-killing-function => nil 156 | ``` 157 | 158 | The default function for buffer killing. 159 | 160 | This function is used when nothing is found in 161 | `kill-or-bury-alive-killing-function-alist`. 162 | 163 | The function should be able to take one argument: buffer object to kill or 164 | its name. 165 | 166 | If the value of the variable is `nil`, `kill-buffer` is used. 167 | 168 | ---- 169 | 170 | ``` 171 | kill-or-bury-alive-burying-function ⇒ nil 172 | ``` 173 | 174 | The function used by `kill-or-bury-alive` to bury a buffer. 175 | 176 | The function should be able to take one argument: buffer object to bury or 177 | its name. 178 | 179 | If value of the variable is `nil`, `kill-or-bury-alive--bury-buffer*` is 180 | used. 181 | 182 | ---- 183 | 184 | ``` 185 | kill-or-bury-alive-base-buffer ⇒ "*scratch*" 186 | ``` 187 | 188 | Name of the buffer to switch to after `kill-or-bury-alive-purge-buffers`. 189 | 190 | ## License 191 | 192 | Copyright © 2015–present Mark Karpov 193 | 194 | Distributed under GNU GPL, version 3. 195 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "emacs-package-flake": { 4 | "inputs": { 5 | "flake-utils": "flake-utils", 6 | "nixpkgs": "nixpkgs" 7 | }, 8 | "locked": { 9 | "lastModified": 1686061605, 10 | "narHash": "sha256-P9zhW//ov8RsCpp4YWgsPN/jXfJbNJkYci5ohCCtRi0=", 11 | "owner": "mrkkrp", 12 | "repo": "emacs-package-flake", 13 | "rev": "16560243f855e9e918a1026768d53a78d047ef43", 14 | "type": "github" 15 | }, 16 | "original": { 17 | "owner": "mrkkrp", 18 | "repo": "emacs-package-flake", 19 | "type": "github" 20 | } 21 | }, 22 | "flake-utils": { 23 | "inputs": { 24 | "systems": "systems" 25 | }, 26 | "locked": { 27 | "lastModified": 1685518550, 28 | "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", 29 | "owner": "numtide", 30 | "repo": "flake-utils", 31 | "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "numtide", 36 | "repo": "flake-utils", 37 | "type": "github" 38 | } 39 | }, 40 | "nixpkgs": { 41 | "locked": { 42 | "lastModified": 1685931219, 43 | "narHash": "sha256-8EWeOZ6LKQfgAjB/USffUSELPRjw88A+xTcXnOUvO5M=", 44 | "owner": "NixOS", 45 | "repo": "nixpkgs", 46 | "rev": "7409480d5c8584a1a83c422530419efe4afb0d19", 47 | "type": "github" 48 | }, 49 | "original": { 50 | "id": "nixpkgs", 51 | "ref": "nixos-unstable", 52 | "type": "indirect" 53 | } 54 | }, 55 | "root": { 56 | "inputs": { 57 | "emacs-package-flake": "emacs-package-flake" 58 | } 59 | }, 60 | "systems": { 61 | "locked": { 62 | "lastModified": 1681028828, 63 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 64 | "owner": "nix-systems", 65 | "repo": "default", 66 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 67 | "type": "github" 68 | }, 69 | "original": { 70 | "owner": "nix-systems", 71 | "repo": "default", 72 | "type": "github" 73 | } 74 | } 75 | }, 76 | "root": "root", 77 | "version": 7 78 | } 79 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | emacs-package-flake.url = "github:mrkkrp/emacs-package-flake"; 4 | }; 5 | outputs = { self, emacs-package-flake }: 6 | emacs-package-flake.lib.mkOutputs { 7 | name = "kill-or-bury-alive"; 8 | srcDir = ./.; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /kill-or-bury-alive.el: -------------------------------------------------------------------------------- 1 | ;;; kill-or-bury-alive.el --- Precise control over buffer killing -*- lexical-binding: t; -*- 2 | ;; 3 | ;; Copyright © 2015–present Mark Karpov 4 | ;; 5 | ;; Author: Mark Karpov 6 | ;; URL: https://github.com/mrkkrp/kill-or-bury-alive 7 | ;; Version: 0.1.3 8 | ;; Package-Requires: ((emacs "24.4")) 9 | ;; Keywords: convenience 10 | ;; 11 | ;; This file is not part of GNU Emacs. 12 | ;; 13 | ;; This program is free software: you can redistribute it and/or modify it 14 | ;; under the terms of the GNU General Public License as published by the 15 | ;; Free Software Foundation, either version 3 of the License, or (at your 16 | ;; option) any later version. 17 | ;; 18 | ;; This program is distributed in the hope that it will be useful, but 19 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 21 | ;; Public License for more details. 22 | ;; 23 | ;; You should have received a copy of the GNU General Public License along 24 | ;; with this program. If not, see . 25 | 26 | ;;; Commentary: 27 | 28 | ;; Have you ever killed a buffer that you might want to leave alive? 29 | ;; Motivation for killing is usually “get out of my way,” and killing may be 30 | ;; not the best choice in some cases. This package allows us to teach Emacs 31 | ;; which buffers to kill and which to bury alive. 32 | ;; 33 | ;; When we want to kill a buffer, it turns out that not all buffers would 34 | ;; like to die in the same way. To help with that, the package allows us to 35 | ;; specify *how* to kill different kinds of buffers. This may be especially 36 | ;; useful when a buffer has an associated process. 37 | 38 | ;;; Code: 39 | 40 | (require 'cl-lib) 41 | 42 | (defgroup kill-or-bury-alive nil 43 | "Precise control over buffer killing in Emacs." 44 | :group 'convenience 45 | :tag "Kill or bury alive" 46 | :prefix "kill-or-bury-alive-" 47 | :link '(url-link :tag "GitHub" 48 | "https://github.com/mrkkrp/kill-or-bury-alive")) 49 | 50 | ;;;###autoload 51 | (defcustom kill-or-bury-alive-must-die-list nil 52 | "A list of buffer designators for buffers that always should be killed. 53 | 54 | See the description of `kill-or-bury-alive--buffer-match' for 55 | information about the concept of buffer designators. 56 | 57 | This variable is used by the `kill-or-bury-alive' function." 58 | :tag "Must die list" 59 | :type '(repeat :tag "Buffer Designators" 60 | (choice (regexp :tag "Buffer Name") 61 | (symbol :tag "Major Mode")))) 62 | 63 | ;;;###autoload 64 | (defcustom kill-or-bury-alive-killing-function-alist nil 65 | "AList that maps buffer designators to functions that should kill them. 66 | 67 | See the description of `kill-or-bury-alive--buffer-match' for 68 | information about the concept of buffer designators. 69 | 70 | This variable is used by `kill-or-bury-alive' and 71 | `kill-or-bury-alive-purge-buffers'. 72 | 73 | You can use `kill-or-bury-alive-kill-with' to add elements to 74 | this alist." 75 | :tag "Killing function alist" 76 | :type '(alist :key-type (choice :tag "Buffer Designator" 77 | (regexp :tag "Buffer Name") 78 | (symbol :tag "Major Mode")) 79 | :value-type (function :tag "Killing Function"))) 80 | 81 | ;;;###autoload 82 | (defcustom kill-or-bury-alive-long-lasting-list 83 | '("^\\*scratch\\*$" 84 | "^\\*Messages\\*$" 85 | "^ ?\\*git-credential-cache--daemon\\*$" 86 | erc-mode) 87 | "A list of buffer designators for buffers that should not be purged. 88 | 89 | See the description of `kill-or-bury-alive--buffer-match' for 90 | information about the concept of buffer designators. 91 | 92 | This variable is used by `kill-or-bury-alive-purge-buffers'." 93 | :tag "Long lasting list" 94 | :type '(repeat :tag "Buffer Designators" 95 | (choice (regexp :tag "Buffer Name") 96 | (symbol :tag "Major Mode")))) 97 | 98 | (defcustom kill-or-bury-alive-killing-function nil 99 | "The default function for buffer killing. 100 | 101 | This is used when nothing is found in 102 | `kill-or-bury-alive-killing-function-alist'. 103 | 104 | The function should be able to take one argument: buffer object 105 | to kill or its name. 106 | 107 | If value of the variable is NIL, `kill-buffer' is used." 108 | :tag "Killing function" 109 | :type '(choice function 110 | (const :tag "Use Default" nil))) 111 | 112 | (defcustom kill-or-bury-alive-burying-function nil 113 | "The function used by `kill-or-bury-alive' to bury a buffer. 114 | 115 | The function should be able to take one argument: buffer object 116 | to bury or its name. 117 | 118 | If value of the variable is NIL, 119 | `kill-or-bury-alive--bury-buffer*' is used." 120 | :tag "Burying function" 121 | :type '(choice function 122 | (const :tag "Use Default" nil))) 123 | 124 | (defcustom kill-or-bury-alive-base-buffer "*scratch*" 125 | "Name of the buffer to switch to after `kill-or-bury-alive-purge-buffers'." 126 | :tag "Base buffer" 127 | :type 'string) 128 | 129 | ;;;###autoload 130 | (defun kill-or-bury-alive-kill-with 131 | (buffer-designator killing-function &optional simple) 132 | "Kill buffers selected by the BUFFER-DESIGNATOR with KILLING-FUNCTION. 133 | 134 | See the description of `kill-or-bury-alive--buffer-match' for 135 | information about the concept of buffer designators. 136 | 137 | Normally, KILLING-FUNCTION should be able to take one argument: 138 | buffer object. However, you can use a function that operates on 139 | the current buffer and doesn't take any arguments. Just pass 140 | non-NIL SIMPLE argument and KILLING-FUNCTION will be wrapped as 141 | needed automatically." 142 | (push (cons buffer-designator 143 | (if simple 144 | (lambda (buffer) 145 | (with-current-buffer buffer 146 | (funcall killing-function))) 147 | killing-function)) 148 | kill-or-bury-alive-killing-function-alist)) 149 | 150 | (defun kill-or-bury-alive--buffer-match (buffer buffer-designator) 151 | "Return a non-NIL value if BUFFER matches BUFFER-DESIGNATOR. 152 | 153 | BUFFER should be a buffer object. BUFFER-DESIGNATOR can be a 154 | string (regexp to match the name of a buffer) or a symbol (the 155 | major mode of a buffer)." 156 | (when (get-buffer buffer) 157 | (if (stringp buffer-designator) 158 | (string-match-p buffer-designator 159 | (buffer-name buffer)) 160 | (with-current-buffer buffer 161 | (or (eq major-mode buffer-designator) 162 | (derived-mode-p buffer-designator)))))) 163 | 164 | (defun kill-or-bury-alive--must-die-p (buffer) 165 | "Return a non-NIL value when BUFFER must be killed no matter what." 166 | (cl-some (apply-partially #'kill-or-bury-alive--buffer-match buffer) 167 | kill-or-bury-alive-must-die-list)) 168 | 169 | (defun kill-or-bury-alive--long-lasting-p (buffer) 170 | "Return a non-NIL value when BUFFER is a long lasting one." 171 | (cl-some (apply-partially #'kill-or-bury-alive--buffer-match buffer) 172 | kill-or-bury-alive-long-lasting-list)) 173 | 174 | (defun kill-or-bury-alive--kill-buffer (buffer) 175 | "Kill the buffer BUFFER according to the killing preferences. 176 | 177 | The variable `kill-or-bury-alive-killing-function-alist' is used 178 | to determine how to kill the BUFFER. If nothing is found, the 179 | `kill-or-bury-alive-killing-function' is used." 180 | (funcall 181 | (or (cdr 182 | (cl-find-if 183 | (apply-partially #'kill-or-bury-alive--buffer-match buffer) 184 | kill-or-bury-alive-killing-function-alist 185 | :key #'car)) 186 | kill-or-bury-alive-killing-function 187 | #'kill-buffer) 188 | buffer)) 189 | 190 | (defun kill-or-bury-alive--bury-buffer* (buffer-or-name) 191 | "This is a rewrite of `bury-buffer' that works for any BUFFER-OR-NAME." 192 | (let ((buffer (window-normalize-buffer buffer-or-name))) 193 | (bury-buffer-internal buffer) 194 | (when (eq buffer (window-buffer)) 195 | (unless (window--delete nil t) 196 | (set-window-dedicated-p nil nil) 197 | (switch-to-prev-buffer nil 'bury))) 198 | nil)) 199 | 200 | (defun kill-or-bury-alive--bury-buffer (buffer) 201 | "Bury the BUFFER according to the burying preferences. 202 | 203 | `kill-or-bury-alive-burying-function' is used to bury the buffer, 204 | see its description for more information." 205 | (funcall (or kill-or-bury-alive-burying-function 206 | #'kill-or-bury-alive--bury-buffer*) 207 | buffer)) 208 | 209 | ;;;###autoload 210 | (defun kill-or-bury-alive (&optional arg) 211 | "Kill or bury the current buffer. 212 | 213 | This is a universal killing mechanism. When argument ARG is 214 | given and it's not NIL, kill the current buffer. Otherwise the 215 | behavior of this command varies. If the current buffer matches a 216 | buffer designator listed in `kill-or-bury-alive-must-die-list', 217 | kill it immediately, otherwise just bury it. 218 | 219 | You can specify how to kill various kinds of buffers, see 220 | `kill-or-bury-alive-killing-function-alist' for more information. 221 | Buffers are killed with `kill-or-bury-alive-killing-function' by 222 | default." 223 | (interactive "P") 224 | (let ((buffer (current-buffer))) 225 | (if (or arg (kill-or-bury-alive--must-die-p buffer)) 226 | (when (or (not (kill-or-bury-alive--long-lasting-p buffer)) 227 | (yes-or-no-p 228 | (format "Buffer ‘%s’ is a long lasting one, kill?" 229 | (buffer-name buffer)))) 230 | (kill-or-bury-alive--kill-buffer buffer)) 231 | (kill-or-bury-alive--bury-buffer buffer)))) 232 | 233 | ;;;###autoload 234 | (defun kill-or-bury-alive-purge-buffers (&optional arg) 235 | "Kill all buffers except for the long lasting ones. 236 | 237 | The long lasting buffers are specified in 238 | `kill-or-bury-alive-long-lasting-list'. 239 | 240 | If `kill-or-bury-alive-base-buffer' is not NIL, switch to the 241 | buffer with that name after purging and delete all other windows. 242 | 243 | When ARG is given and it's not NIL, ask to confirm killing of 244 | every buffer." 245 | (interactive "P") 246 | (dolist (buffer (buffer-list)) 247 | (let ((buffer-name (buffer-name buffer))) 248 | (when (and buffer-name 249 | (not (kill-or-bury-alive--long-lasting-p buffer)) 250 | (or (not arg) 251 | (yes-or-no-p 252 | (format "Kill ‘%s’?" buffer-name)))) 253 | (kill-or-bury-alive--kill-buffer buffer)))) 254 | (when kill-or-bury-alive-base-buffer 255 | (switch-to-buffer kill-or-bury-alive-base-buffer) 256 | (delete-other-windows))) 257 | 258 | (provide 'kill-or-bury-alive) 259 | 260 | ;;; kill-or-bury-alive.el ends here 261 | --------------------------------------------------------------------------------