diff options
| author | Craig Jennings <c@cjennings.net> | 2026-07-31 12:39:27 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-07-31 12:39:27 -0500 |
| commit | 0b93863689038a385fd320440ac0a413ce81c4e1 (patch) | |
| tree | 30dbabae88dfe704236544e5ebb6f6ebd18f4814 /modules/system-lib.el | |
| parent | 445a0be315f3d6fff585f1558262fb5dacd10d12 (diff) | |
| download | dotemacs-0b93863689038a385fd320440ac0a413ce81c4e1.tar.gz dotemacs-0b93863689038a385fd320440ac0a413ce81c4e1.zip | |
I had to kill an Emacs session today over a prompt I could not answer. A
second agent session held the selected window while "Overwrite hiroshi.m3u?
(yes or no)" waited in another frame, so my keystrokes went to a terminal
and the minibuffer stayed empty. Losing unsaved buffers is a far worse
outcome than a mis-keyed confirm.
So cj/confirm-strong becomes cj/confirm-destructive, and instead of binding
use-short-answers to nil for a typed "yes" it reads a single y or n.
Two things I kept:
- No default. Only y and n answer, so a stray RET or space re-prompts
rather than confirming a shutdown.
- Pending input is discarded before the read. Without that the change would
have been a bad trade: read-char-choice reads the input queue, so a key
typed before the prompt painted would confirm instantly, where the old
typed-"yes" absorbed it harmlessly.
Worth being honest about what this does not fix. One keystroke does not
make a prompt reachable when focus is elsewhere, and C-g is still the
escape. It lowers the cost of the situation rather than preventing it.
Six call sites: shutdown and reboot, shred, playlist overwrite and delete,
and the two file overwrites.
Diffstat (limited to 'modules/system-lib.el')
| -rw-r--r-- | modules/system-lib.el | 37 |
1 files changed, 27 insertions, 10 deletions
diff --git a/modules/system-lib.el b/modules/system-lib.el index 54e20b74..bde53d82 100644 --- a/modules/system-lib.el +++ b/modules/system-lib.el @@ -130,16 +130,33 @@ Callers that must have a secret layer their own error on top." (secret (plist-get (car (apply #'auth-source-search spec)) :secret))) (if (functionp secret) (funcall secret) secret))) -;; ---------------------------- Strong Confirmation ---------------------------- - -(defun cj/confirm-strong (prompt) - "Ask PROMPT, requiring a full typed \"yes\" or \"no\" answer. -For irreversible actions -- file destruction, overwrites, power-off. The -global default makes `yes-or-no-p' a single keystroke (`use-short-answers' -is t); this binds it to nil for the one call so the prompt demands the -long-form answer, keeping a stray RET or space from confirming." - (let ((use-short-answers nil)) - (yes-or-no-p prompt))) +;; -------------------------- Destructive Confirmation ------------------------- + +(defun cj/confirm-destructive (prompt) + "Ask PROMPT for an irreversible action. Return non-nil for yes. + +One keystroke, y or n. Nothing else answers: a stray RET or space +re-prompts rather than confirming, so the accidental-confirm protection +survives without the answer costing four keystrokes. + +Pending input is discarded first, and that line is load-bearing. +`read-char-choice' reads from the input queue, so without it a keystroke +typed before the prompt appeared would confirm a shutdown or a file +deletion instantly. The typed-\"yes\" form this replaced absorbed such a +key harmlessly, and dropping the guard without replacing it would have +traded a rare annoyance for a rare catastrophe. + +This used to demand a typed \"yes\", and that was a worse trade than it +looked. On 2026-07-31 one such prompt went unanswered -- a second agent +session held the selected window while the prompt waited in another frame, +so keystrokes went to a terminal instead of the minibuffer, and the session +was killed with buffers unsaved. A single keystroke does not make a prompt +reachable when focus is elsewhere; C-g is still the escape either way. What +it changes is the cost of the situation, and losing unsaved buffers is a far +bigger hazard than a mis-keyed confirm." + (discard-input) + (eq ?y (downcase (read-char-choice (concat prompt "(y or n) ") + '(?y ?Y ?n ?N))))) (defun cj/--font-lock-global-modes-excluding (current mode) "Return CURRENT `font-lock-global-modes' with MODE added to the exclusion. |
