Implementing X where X = "Custom Doom Emacs's Scratch Buffer"
By Anak Wannaphaschaiyong
I was trying to implement my own scratch buffer.I learned alot about Emacs’s way of programming. I gained a new mental debugging tool.
The main function I started to modify is  doom/open-scratch-buffer. The functions deals with opening scratch buffer when current-buffer is inside and outside of a projectile’s project.
doom/open-scratch-buffer does the following
- check if scratch will open in the same buffer or as pop up buffer
 - Then, it passes arguments to 
(doom-scratch-buffer &optional DONT-RESTORE-P MODE DIRECTORY PROJECT-NAME). 
doom-scratch-buffer does the following
- create buffer if the name doesn’t already exist
 - restore existing buffer if buffer already opened by running 
(doom--load-persistent-scratch-buffer project-name) - add the scratch buffer to 
doom-scratch-buffers - add related hooks. then open the buffer.
 
I ended up copied code from scratch.el and modified it. Modification was pretty straight forward. First, I simplified arguments to pass as a way to poke the system to match my prediction of its behavior to its actual behavior.
I learned about how scratch page persist content. Content of doom scratch buffer is saved to ~/.emacs.d/.local/etc/scratch/__default.el whenever its closed. To persist my scratch buffer content, I created ~/.emacs.d/.local/etc/scratch/__now-n-next.el to use inplace of ~/.emacs.d/.local/etc/scratch/__default.el.
After this step, I get the following code.
(defun anak/open-scratch-buffer (&optional arg)
  "Pop up a persistent scratch buffer.
If passed the prefix ARG, do not restore the last scratch buffer.
If PROJECT-P is non-nil, open a persistent scratch buffer associated with the
  current project."
  (interactive "P")
  (let (projectile-enable-caching)
    (funcall
     #'pop-to-buffer
     ;; #'switch-to-buffer
     (anak/scratch-buffer
      arg
      'org-mode
      default-directory
      nil))))
(defun anak/scratch-buffer (&optional dont-restore-p mode directory project-name)
  "Return a scratchpad buffer in major MODE."
  (let* ((buffer-name "*anak:now-n-next*")
         (buffer (get-buffer buffer-name)))
    (with-current-buffer
        (or buffer (get-buffer-create buffer-name))
      (setq default-directory directory)
      (setq-local so-long--inhibited t)
      (if dont-restore-p
          (erase-buffer)
        (unless buffer
          (anak/load-persistent-scratch-buffer project-name)
          (when (and (eq major-mode 'fundamental-mode)
                     (functionp mode))
            (funcall mode))))
      (cl-pushnew (current-buffer) doom-scratch-buffers)
      (add-transient-hook! 'doom-switch-buffer-hook (anak/persist-scratch-buffers-h))
      (add-transient-hook! 'doom-switch-window-hook (anak/persist-scratch-buffers-h))
      (add-hook 'kill-buffer-hook #'anak/persist-scratch-buffer-h nil 'local)
      (run-hooks 'doom-scratch-buffer-created-hook)
      (current-buffer))))
(defvar anak/scratch-default-file "__now-n-next"
  "The default file name for a project-less scratch buffer.
Will be saved in `doom-scratch-dir'.")
(defun anak/persist-scratch-buffer-h ()
  "Save the current buffer to `doom-scratch-dir'."
  (let ((content (buffer-substring-no-properties (point-min) (point-max)))
        (point (point))
        (mode major-mode))
    (with-temp-file
        (expand-file-name (concat anak/scratch-default-file
                                  ".el")
                          doom-scratch-dir)
      (prin1 (list content
                   point
                   mode)
             (current-buffer)))))
(defun anak/load-persistent-scratch-buffer (project-name)
  (setq-local doom-scratch-current-project
              (or project-name
                  anak/scratch-default-file))
  (let ((smart-scratch-file
         (expand-file-name (concat doom-scratch-current-project ".el")
                           doom-scratch-dir)))
    (make-directory doom-scratch-dir t)
    (when (file-readable-p smart-scratch-file)
      (message "Reading %s" smart-scratch-file)
      (cl-destructuring-bind (content point mode)
          (with-temp-buffer
            (save-excursion (insert-file-contents smart-scratch-file))
            (read (current-buffer)))
        (erase-buffer)
        (funcall mode)
        (insert content)
        (goto-char point)
        t))))
(defun anak/persist-scratch-buffers-h ()
  "Save all scratch buffers to `doom-scratch-dir'."
  (setq doom-scratch-buffers
        (cl-delete-if-not #'buffer-live-p doom-scratch-buffers))
  (dolist (buffer doom-scratch-buffers)
    (with-current-buffer buffer
      (anak/persist-scratch-buffer-h))))
The code wasn’t totally correct. When I opened my scratch buffer (*anak:now-n-next*), the buffer replaced the current buffer (where my cursor is in). I found that pop-to-buffer caused this behavior, shown below.
...
(funcall
     #'pop-to-buffer
     ;; #'switch-to-buffer
     (anak/scratch-buffer
      arg
      'org-mode
      default-directory
      nil))
...
I dig into it further by carefully inspect *Messages* output using edebug. I found that display-buffer applies functions based on file’s name. That’s it. I changed (buffer-name "*anak:now-n-next*") to (buffer-name "*doom:now-n-next*"). Now, *doom:now-n-next* and *doom:scratch* opens buffer the same way.
A Note on display-buffer, display-buffer collects list of functions to be applied based on class of display-buffer-*-action and *-action where (cdr functions) is buffer name.
...
(while (and functions (not window))
	  (setq window (funcall (car functions) buffer alist)
	  	functions (cdr functions)))
...
Below is a message output by display-buffer's code section, shown below.
messages are, in order,
- function name to be applied
 - buffer name
 - list of actions to apply based.
 
Below is output when doom/open-scratch-buffer is evalulated.
Result: +popup-buffer
Result: #<buffer *doom:scratch*>
Result: ((actions) (side . bottom) (size . 0.35) (window-width . 40) (window-height . 0.35) (slot) (vslot . -4) (window-parameters (ttl . t) (quit) (select . t) (modeline . t) (autosave . t) (transient . t) (no-other-window . t)))
Result: #<window 115 on *doom:scratch*>
Below is output when anak/open-scratch-buffer is evalulated.
Result: display-buffer-use-some-window
Result: #<buffer *anak:now-n-next*>
Result: nil
Result: #<window 67 on *anak:now-n-next*>
That’s it.
Peace.
~milfex-lostex