3

I tend to use often non-file buffers kinda like post-it-notes and it is way too easy to kill them inadvertently, as there is no confirmation to kill. Even if you enforce the habit of writing only in file buffers (subject to confirmation), you might still inadvertently write in a non-file buffer.

Therefore, I created a kill-hook to ask kill confirmation, but only for buffers that I created (setting global buffer-offer-save would make Emacs unusable). To identify user buffers, I exploit the fact that the name of buffers created automatically starts with a star.

Now, given its prominence, I would like to use the *scratch* buffer as a note taking buffer too. In this case, to fit the Emacs naming convention, I need to rename it scratch (or anyway something without the star).

I see two possibilities:

  1. I prevent its creation: in the kill hook I check if I am going to kill the last buffer and create my scratch, to prevent the creation of Emacs' *scratch*.

  2. I look for a create create-scratch-buffer hook and rename the scratch here.

As for point 1, (length (list-buffers)) includes also so called non-interesting (hidden) buffers and I would happily avoid to parse names with regexp on every hook call (to detect interesting buffers).

As for point 2, how can I detect the scratch buffer creation? Perhaps with a mode hook, e.g. something like:

(add-hook 'after-change-major-mode-hook 'rename-scratch)

My actual kill hook follows.

(add-to-list 'kill-buffer-query-functions   'ask-before-killing-user-buffer)
(defun ask-before-killing-user-buffer ()
  "If the current buffer is a user buffer, ask for saving before killing.
Here a user buffer is one not: associated to a file, belonging to a process or auto-created by Emacs. 
The latter are considered such when starting with the usual `*'.
The proposed filename will not be accepted if any buffer is already visiting the file"
  (interactive)
  (let* (save-name visited-by exit-while-p
           (ask "Save file as: ")
           (buffer (current-buffer))    
           (buffer-name (buffer-name))
           (user-buff-p (if (or (string-match  "^ \\|\\*" buffer-name)
                    (get-buffer-process buffer)
                    (buffer-file-name)) nil t)))

    ;; Ask to save modified buffer
    (if (and user-buff-p (buffer-modified-p))
    (if (y-or-n-p (format "`%s' was modified; save it? " buffer-name))
        (progn
          ;; accept only non visited file names
          (setq default-directory DEFAULT-SAVE-DIR)
          (while (not exit-while-p)
        (setq save-name (file-truename (read-file-name ask nil nil nil buffer-name)))
        (setq visited-by (find-buffer-visiting save-name))
        (if visited-by
            (setq ask (format "`%s' is being visited by buffer `%s'.\nPlease choose another name: "
                      save-name (buffer-name visited-by)))                 
          (setq exit-while-p t)))
          (write-file save-name t))))

    t))

UPDATE

Try to answer to your comments, clarifiying what I am willing to achieve.

I have a shortcut creating buffers named scratch1, scratch2, etc to readily copy snippets from web pages or from ebooks, to type a nearby task, the draft of an email, etc. Normally these are ephemeral buffers never translating into files, they live until the text snippets are copied in actual documents (but occasionally the buffer is edited and saved). Important original material gets immediately the status of file-buffer (with the related backup facilities); it is anyway unpleasant to accidentally kill my scratches, despite I can recover them going on the source web page, ebook etc. Just like it is not crucial to lose your remember-the-milk notes, but ...

Currently using a mode hook seems to me a solution without to much overhead:

(defun rename-scratch-buffer ()
  (if (and (get-buffer "*scratch*") (not (get-buffer "scratch")))      
      (with-current-buffer "*scratch*"  (rename-buffer "scratch"))))
(add-hook 'after-change-major-mode-hook 'rename-scratch-buffer)
8
  • Wouldn't it be simpler just to put one more clause in for user-buff-p along the lines of (not (string= "*scratch*" (buffer-name)))?
    – Dan
    Commented Jan 2, 2015 at 1:30
  • 2
    Why do you "use non-file buffers kinda like post-it-notes" and then complain that "it is way too easy to kill them inadvertently"? What is your use case? If you just use file buffers, instead of non-file buffers, for your "post-it" use case, then you automatically get the confirmation request that you are asking for. IOW, if you use C-x 4 f foo.txt instead of C-x 4 b foo then you get what you want, no? And you can of course set auto-mode-alist to give you whatever mode you want for such buffers. The buffers are not written to disk unless you explicitly do so. So what's the problem?
    – Drew
    Commented Jan 2, 2015 at 1:40
  • If you really want to rename *scratch* to scratch, you can do so via after-init-hook, such as: (add-hook 'after-init-hook (lambda () (rename-buffer "scratch"))).
    – Dan
    Commented Jan 2, 2015 at 1:42
  • 2
    Your update does not answer my question, FWIW: Why not use file buffers for your notes? You don't need to save the buffers to files. And you get exactly the confirmation requests you desire when you try to kill them. When you visit a file buffer (even one that that has never been written to disk, so there is no such file), the buffer is automatically put in whatever major mode you want, and killing the buffer asks you for confirmation. That's the point of such buffers.
    – Drew
    Commented Jan 2, 2015 at 2:53
  • 3
    You don't need to worry about their name or location - ahead of time or at all. That's the point. You are not going to save them, so you do not care about such things. All you care about is not killing them accidentally, and that's what a so-called file buffer is for. Other buffers do not have that behavior, precisely because you do not want to confirm their deletion. It is a fundamental misunderstanding to think that confirmation is missing from buffers that are designed not to require it. By default, *scratch* is one of those. What you want is a file buffer (which you never save).
    – Drew
    Commented Jan 2, 2015 at 3:40

3 Answers 3

2

I guess much of this is a matter of taste and usage patterns. I tend to use something inbetween your solution and @Drew's usage (according to the comments).

Here is the gist of it:

  • I want "throwaway" buffers to be quickly available, without having to think ahead of time where to save them;
  • I want them to be file buffers in order for Emacs to warn me if I accidentally kill them;
  • I don't want to accidentally save them due to my C-xC-s OCD.

And here is the way I implement it:

  • M-xfind-scratch-bufferRET visits a new scratch buffer, associated to a file in /tmp/scratch-buffer/ and named scratch<N> (or you can provide an explicit name using the universal prefix argument)

  • the newly created buffer is put in scratch-mode, which remaps C-xC-s to C-xC-w, meaning that if you accidentally want to save the file, you will be prompted for a location.

And the code:

(defvar scratch-mode-directory "/tmp/scratch-buffer/"
  "Prefix path for scratch buffers")

(defvar scratch-mode--index 0
  "Index of the newest anonymous scratch buffer.")

(defun scratch-mode-write-file ()
  "Run whatever is bound to \"C-x C-w\".
This should be something like `write-file'."
  (interactive)
  (call-interactively (key-binding (kbd "C-x C-w"))))

(define-minor-mode scratch-mode
  "Minor mode for temporary buffers"
  :global nil
  :init-value nil
  :lighter " scratch"
  :keymap (let ((map (make-sparse-keymap)))
            (define-key map (kbd "C-x C-s") #'scratch-mode-write-file)
            map))

(defun scratch-mode--enable-maybe ()
  (when (string= scratch-mode-directory
                 (file-name-directory
                  (or (buffer-file-name) "")))
    (scratch-mode 1)))

(add-hook 'after-change-major-mode-hook #'scratch-mode--enable-maybe)

(defun find-scratch-buffer (&optional name)
  "Visit a scratch buffer.
When NAME is not provided, it defaults to \"scratch<N>\".  The
newly created buffer inherits the current value of
`default-directory', and is put in `scratch-mode'."
  (interactive `(,(if current-prefix-arg
                      (read-from-minibuffer "Scratch name: ")
                    nil)))
  (unless (file-exists-p scratch-mode-directory)
    (make-directory scratch-mode-directory t))
  (let ((dd default-directory))
    (find-file (format "%s%s"
                       scratch-mode-directory
                       (or name
                           (format "scratch<%d>" (incf scratch-mode--index)))))
    (setq default-directory dd)))
2

I made it slightly different (but the previous answers helped me a lot, thanks guys), to avoid string matching.

I modified the "new-empty-buffer" function suggested by Xah Lee from ErgoEmacs, making it set a local-variable 'custom-scratch-buffer when the buffer is created.

Therefore, we know when the buffer is a scratch buffer created by this function and we can add a query to the kill-buffer-query-functions list identifying this kind of buffer.

Here goes the code:

(defun custom-scratch-buffer-create ()
  "Open a new empty buffer.
URL `http://ergoemacs.org/emacs/emacs_new_empty_buffer.html'
Version 2016-08-11"
  (interactive)
  (let ((-buf (generate-new-buffer "untitled")))
    (switch-to-buffer -buf)
    (funcall initial-major-mode)
    (setq buffer-offer-save t)
    (set (make-local-variable 'custom-scratch-buffer) t))) ;; I added this line

(defun custom-scratch-buffer-kill-query-function ()
  (if (and (not buffer-file-name)           ;; buffer is not visiting a file
           (buffer-modified-p)              ;; buffer has been modified
           (boundp 'custom-scratch-buffer))
      (if 'custom-scratch-buffer            ;; buffer is a custom scratch created buffer
          (yes-or-no-p "Scratch buffer modified. Kill it anyway? "))
    t))

;; run query before killing if buffer is custom-scratch-buffer
(add-to-list 'kill-buffer-query-functions 'custom-scratch-buffer-kill-query-function)

Now you can bind the function custom-scratch-buffer-create to a key or use it with M-x.

1

After some testing, I saw the solution, based on the hook after-change-major-mode-hook, proposed in the question, works most of the time to modify the scratch buffer name. Anyway, when killing scratch (with the modified name) and when it is the only Emacs buffer, *scratch* might be recreated without triggering (the change-mode hook and therefore) the name modification.

To avoid this, it is sufficient not to allow to kill scratch when it is the last buffer. Therefore we need to add a proper hook to kill-buffer-query-functions which returns nil in this instance (nil causes Emacs not execute the kill).

(add-to-list 'kill-buffer-query-functions 'check-scratch)
(defun check-scratch ()
  (let ((bufs (remq nil
            (mapcar
             #'(lambda (x) (string-match "^[^ ]" x)) 
             (mapcar 'buffer-name (buffer-list))))))
    (if (and (not (buffer-modified-p))
         (string= (buffer-name) "scratch") 
         (eq (length bufs) 1))
    nil t)))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.