doomemacs/core/autoload/scratch.el
Henrik Lissner 374940b959
Persist scratch buffers more proactively
Prior to this change, scratch buffers were saved only when Emacs is
killed. In cases where Emacs doesn't die properly, you'd lose your
scratch contents. Now, they are persisted as soon as you close them.
2019-08-15 01:07:45 -04:00

174 lines
5.9 KiB
EmacsLisp

;;; core/autoload/scratch.el -*- lexical-binding: t; -*-
(defvar doom-scratch-default-file "__default"
"The default file name for a project-less scratch buffer.
Will be saved in `doom-scratch-dir'.")
(defvar doom-scratch-dir (concat doom-etc-dir "scratch")
"Where to save persistent scratch buffers.")
(defvar doom-scratch-buffer-major-mode nil
"What major mode to use in scratch buffers. This can be one of the
following:
t Inherits the major mode of the last buffer you had selected.
nil Uses `fundamental-mode'
MAJOR-MODE Any major mode symbol")
(defvar doom-scratch-buffers nil
"A list of active scratch buffers.")
(defvar doom-scratch-current-project nil
"The name of the project associated with the current scratch buffer.")
(defvar doom-scratch-buffer-hook ()
"The hooks to run after a scratch buffer is created.")
(defun doom--load-persistent-scratch-buffer (name)
(setq-local doom-scratch-current-project
(or name
doom-scratch-default-file))
(let ((scratch-file
(expand-file-name doom-scratch-current-project
doom-scratch-dir)))
(make-directory doom-scratch-dir t)
(when (file-readable-p scratch-file)
(erase-buffer)
(insert-file-contents scratch-file)
(set-auto-mode)
t)))
;;;###autoload
(defun doom-scratch-buffer (&optional mode directory project-name)
"Return a scratchpad buffer in major MODE."
(with-current-buffer
(get-buffer-create (if project-name
(format "*doom:scratch (%s)*" project-name)
"*doom:scratch*"))
(setq default-directory directory)
(unless doom-scratch-current-project
(doom--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-hook 'kill-buffer-hook #'doom-persist-scratch-buffer-h nil 'local)
(add-hook 'doom-switch-buffer-hook #'doom-persist-scratch-buffers-after-switch-h)
(run-hooks 'doom-scratch-buffer-created-hook)
(current-buffer)))
;;
;;; Persistent scratch buffer
;;;###autoload
(defun doom-persist-scratch-buffer-h ()
"Save the current buffer to `doom-scratch-dir'."
(write-region
(point-min) (point-max)
(expand-file-name (or doom-scratch-current-project
doom-scratch-default-file)
doom-scratch-dir)))
;;;###autoload
(defun doom-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
(doom-persist-scratch-buffer-h))))
;;;###autoload
(defun doom-persist-scratch-buffers-after-switch-h ()
"Kill scratch buffers when they are no longer visible, saving them to disk."
(unless (cl-some #'get-buffer-window doom-scratch-buffers)
(mapc #'kill-buffer doom-scratch-buffers)
(remove-hook 'doom-switch-buffer-hook #'doom-persist-scratch-buffers-after-switch-h)))
;;;###autoload
(unless noninteractive
(add-hook 'kill-emacs-hook #'doom-persist-scratch-buffers-h))
;;
;;; Commands
;;;###autoload
(defun doom/open-scratch-buffer (&optional arg project-p)
"Opens the (persistent) scratch buffer in a popup.
If passed the prefix ARG, switch to it in the current window.
If PROJECT-P is non-nil, open a persistent scratch buffer associated with the
current project."
(interactive "P")
(let (projectile-enable-caching)
(funcall
(if arg
#'switch-to-buffer
#'pop-to-buffer)
(doom-scratch-buffer
(cond ((eq doom-scratch-buffer-major-mode t)
(unless (or buffer-read-only
(derived-mode-p 'special-mode)
(string-match-p "^ ?\\*" (buffer-name)))
major-mode))
((null doom-scratch-buffer-major-mode)
nil)
((symbolp doom-scratch-buffer-major-mode)
doom-scratch-buffer-major-mode))
default-directory
(when project-p
(doom-project-name))))))
;;;###autoload
(defun doom/switch-to-scratch-buffer (&optional project-p)
"Like `doom/open-scratch-buffer', but switches to it in the current window.
If passed the prefix arg, open project scratch buffer."
(interactive "P")
(doom/open-scratch-buffer t project-p))
;;;###autoload
(defun doom/open-project-scratch-buffer (&optional current-window)
"Opens the (persistent) project scratch buffer in a popup.
If passed the prefix arg, switch to it in the current window."
(interactive "P")
(doom/open-scratch-buffer current-window 'project))
;;;###autoload
(defun doom/switch-to-project-scratch-buffer ()
"Like `doom/open-project-scratch-buffer', but switches to it in the current
window."
(interactive)
(doom/open-project-scratch-buffer t))
;;;###autoload
(defun doom/revert-scratch-buffer ()
"Revert scratch buffer to last persistent state."
(interactive)
(unless (string-match-p "^\\*doom:scratch" (buffer-name))
(user-error "Not in a scratch buffer"))
(when (doom--load-persistent-scratch-buffer doom-scratch-current-project)
(message "Reloaded scratch buffer")))
;;;###autoload
(defun doom/delete-persistent-scratch-file (&optional arg)
"Deletes a scratch buffer file in `doom-scratch-dir'.
If prefix ARG, delete all persistent scratches."
(interactive)
(if arg
(progn
(delete-directory doom-scratch-dir t)
(message "Cleared %S" (abbreviate-file-name doom-scratch-dir)))
(make-directory doom-scratch-dir t)
(let ((file (read-file-name "Delete scratch file > " doom-scratch-dir "scratch")))
(if (not (file-exists-p file))
(message "%S does not exist" (abbreviate-file-name file))
(delete-file file)
(message "Successfully deleted %S" (abbreviate-file-name file))))))