diff --git a/core/autoload/scratch.el b/core/autoload/scratch.el index 97a2ed206..95c731784 100644 --- a/core/autoload/scratch.el +++ b/core/autoload/scratch.el @@ -1,12 +1,12 @@ ;;; core/autoload/scratch.el -*- lexical-binding: t; -*- -(defvar doom-scratch-files-dir (concat doom-etc-dir "scratch/") - "Where to store project scratch files, created by -`doom/open-project-scratch-buffer'.") +(defvar doom-scratch-default-file "__default" + "The default file name for a project-less scratch buffer. -(defvar doom-scratch-buffer-display-fn #'display-buffer - "The function to use to display the scratch buffer. Must accept one argument: -the buffer to display.") +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 @@ -16,77 +16,129 @@ following: nil Uses `fundamental-mode' MAJOR-MODE Any major mode symbol") +(defvar doom-scratch-buffers nil + "A list of active scratch buffers.") + +(defvar-local 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 made.") + "The hooks to run after a scratch buffer is created.") + +(defun doom--load-persistent-scratch-buffer (name) + (let ((scratch-file (expand-file-name (or name doom-scratch-default-file) + doom-scratch-dir))) + (make-directory doom-scratch-dir t) + (if (not (file-readable-p scratch-file)) + nil + (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." + (let* ((buffer-name (if project-name + (format "*doom:scratch (%s)*" project-name) + "*doom:scratch*")) + (buffer (get-buffer buffer-name))) + (with-current-buffer (get-buffer-create buffer-name) + (unless buffer + (setq buffer (current-buffer) + default-directory directory + doom-scratch-current-project project-name) + (setq doom-scratch-buffers (cl-delete-if-not #'buffer-live-p doom-scratch-buffers)) + (cl-pushnew buffer doom-scratch-buffers) + (doom--load-persistent-scratch-buffer project-name) + (when (and (eq major-mode 'fundamental-mode) + (functionp mode)) + (funcall mode)) + (add-hook 'kill-buffer-hook #'doom|persist-scratch-buffer nil 'local) + (run-hooks 'doom-scratch-buffer-created-hook)) + buffer))) ;; -;; Library +;;; Persistent scratch buffer ;;;###autoload -(defun doom-scratch-buffer (&optional file mode text) - "Return a scratchpad buffer in major MODE with TEXT in it. +(defun doom|persist-scratch-buffer () + "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))) -If FILE is a valid path, open it as if it were a persistent scratchpad." - (if file (setq file (file-truename file))) - (let ((buffer - (if file - (with-current-buffer (find-file-noselect file) - (rename-buffer (format "*doom:scratch (%s)*" (file-name-nondirectory file))) - (current-buffer)) - (get-buffer-create "*doom:scratch*")))) +;;;###autoload +(defun doom|persist-scratch-buffers () + "Save all scratch buffers to `doom-scratch-dir'." + (dolist (buffer (cl-delete-if-not #'buffer-live-p doom-scratch-buffers)) (with-current-buffer buffer - (when (and (functionp mode) - (not (eq major-mode mode))) - (funcall mode)) - (when text - (insert text)) - (run-hooks 'doom-scratch-buffer-hook) - (current-buffer)))) + (doom|persist-scratch-buffer)))) ;;;###autoload -(defun doom/open-scratch-buffer (&optional arg) - "Opens a scratch pad window in the same major-mode. +(add-hook 'kill-emacs-hook #'doom|persist-scratch-buffers) -If ARG (universal argument), then open a persistent scratch pad buffer. You'll -be prompted for its name, or to open a previously created. These are stored in -`doom-scratch-files-dir'. -If a region is active, copy its contents to the scratch pad." +;; +;;; Commands + +;;;###autoload +(defun doom/open-scratch-buffer (&optional arg project-p) + "Opens the (persistent) scratch buffer in a popup. + +If ARG, switch to it in the current window." (interactive "P") (let (projectile-enable-caching) (funcall - doom-scratch-buffer-display-fn + (if arg + #'switch-to-buffer + #'pop-to-buffer) (doom-scratch-buffer - (when arg - (if-let* ((file (read-file-name "Open scratch file > " doom-scratch-files-dir "scratch"))) - file - (user-error "Aborting"))) (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) + ((null doom-scratch-buffer-major-mode) + nil) ((symbolp doom-scratch-buffer-major-mode) doom-scratch-buffer-major-mode)) - (and (region-active-p) - (buffer-substring-no-properties - (region-beginning) (region-end))))))) + default-directory + (when project-p + (doom-project-name)))))) ;;;###autoload -(defun doom/switch-to-scratch-buffer (&optional arg) - "Switches to a scratch pad buffer in the current window. +(defun doom/open-project-scratch-buffer (&optional arg) + "Opens the (persistent) project scratch buffer in a popup. -Otherwise, does exactly what `doom/open-scratch-buffer' does." +If ARG, switch to it in the current window." (interactive "P") - (let ((doom-scratch-buffer-display-fn #'switch-to-buffer)) - (doom/open-scratch-buffer arg))) + (doom/open-scratch-buffer arg 'project)) ;;;###autoload -(defun doom/delete-scratch-files () - "Deletes all scratch buffers in `doom-scratch-files-dir'." +(defun doom/revert-scratch-buffer () + "Revert scratch buffer to last persistent state." (interactive) - (dolist (file (directory-files doom-scratch-files-dir t "^[^.]" t)) - (delete-file file) - (message "Deleted '%s'" (file-name-nondirectory file)))) + (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)))))) diff --git a/modules/config/default/+evil-bindings.el b/modules/config/default/+evil-bindings.el index e0a4e2ae1..ba9dd1d59 100644 --- a/modules/config/default/+evil-bindings.el +++ b/modules/config/default/+evil-bindings.el @@ -735,6 +735,7 @@ :desc "Find other file" "o" #'projectile-find-other-file :desc "Switch project" "p" #'projectile-switch-project :desc "Find recent project files" "r" #'projectile-recentf + :desc "Scratch buffer" "s" #'doom/open-project-scratch-buffer :desc "List project tasks" "t" #'+default/project-tasks (:prefix ("x" . "terminal") :desc "Open eshell in project" "e" #'projectile-run-eshell diff --git a/modules/ui/popup/config.el b/modules/ui/popup/config.el index 7caa21719..8861be67f 100644 --- a/modules/ui/popup/config.el +++ b/modules/ui/popup/config.el @@ -136,7 +136,7 @@ prevent the popup(s) from messing up the UI (or vice versa)." :slot -1 :vslot -2 :ttl 0) ("^\\*Compil\\(?:ation\\|e-Log\\)" :vslot -2 :size 0.3 :ttl nil :quit t) - ("^\\*\\(?:\\(?:doom:\\)?scratch\\|Messages\\)" + ("^\\*\\(?:scratch\\|Messages\\)" :autosave t :ttl nil) ("^\\*Man " :size 0.45 :vslot -3 :ttl 0 :quit t :select t)