emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[RFC] Added an option to store content of the *scratch* buffer in a file


From: Michal Nazarewicz
Subject: [RFC] Added an option to store content of the *scratch* buffer in a file.
Date: Wed, 5 Jun 2013 23:00:40 +0200

From: Michal Nazarewicz <address@hidden>

* lisp/startup.el (initial-scratch-message): Now accepts additional
'file value.
(scratch-recover-from-auto-save-file): New customize variable
describing behaviour of scratch auto-save file.
(scratch--custom-set): New helper function, used as a :set argument
for the above two variables.
(scratch--initialise, scratch--set-buffer-variables)
(scratch--insert-content, scratch--bury-on-kill-buffer): New functions
which initialise content of the *scratch* buffer.
(command-line-1): Use scratch--initialise function.
---
 Hi guys,

 I've been using this functionality for years now and, in my opinion,
 it's absolutely awesome.  It makes *scratch* to extremely useful
 buffer indeed!

 I haven't tested this patch extensively yet (since I've been using
 a simpler version of the code[1]), so at this point I'm just asking
 for feedback.  If you guys are interested in this, I'll test the code
 properly and send another version at later date.

 PS. I hope you don't mind I'm using git to send the patch.

 [1] https://github.com/mina86/dot-files/blob/master/dot-emacs#L1727

 etc/NEWS        |   4 ++
 lisp/ChangeLog  |  16 +++++++
 lisp/startup.el | 140 +++++++++++++++++++++++++++++++++++++++++++++++++-------
 3 files changed, 144 insertions(+), 16 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index c493e34..ac8ab80 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -87,6 +87,10 @@ simply disabling Transient Mark mode does the same thing.
 ** `initial-buffer-choice' can now specify a function to set up the
 initial buffer.
 
+** `initial-scratch-message' can now be 'file which makes content of
+the *scratch* buffer to be kept in a file.  This makes it possible to
+keep notes which will persist even when Emacs restarts in that buffer.
+
 ** `write-region-inhibit-fsync' now defaults to t in batch mode.
 
 ** ACL support has been added.
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 0d1e65c..949922b 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,19 @@
+2013-06-05  Michal Nazarewicz  <address@hidden>
+
+       Added an option to store content of the *scratch* buffer in a file
+       in user-emacs-directory.  This makes it possible to keep notes
+       which will persist even when Emacs restarts in that buffer.
+       * startup.el (initial-scratch-message): Now accepts additional
+       'file value.
+       (scratch-recover-from-auto-save-file): New customize variable
+       describing behaviour of scratch auto-save file.
+       (scratch--custom-set): New helper function, used as a :set argument
+       for the above two variables.
+       (scratch--initialise, scratch--set-buffer-variables)
+       (scratch--insert-content, scratch--bury-on-kill-buffer): New functions
+       which initialise content of the *scratch* buffer.
+       (command-line-1): Use scratch--initialise function.
+
 2013-06-05  Teodor Zlatanov  <address@hidden>
 
        * net/tls.el (open-tls-stream): Remove unneeded buffer contents up
diff --git a/lisp/startup.el b/lisp/startup.el
index b7b4c15..7105a80 100644
--- a/lisp/startup.el
+++ b/lisp/startup.el
@@ -1288,18 +1288,45 @@ settings will be marked as \"CHANGED outside of 
Customize\"."
           `((changed ((t :background ,color)))))
       (put 'cursor 'face-modified t))))
 
+(defun scratch--custom-set (symbol value)
+  (set-default symbol value)
+  ;; This function is called by defcustom so
+  ;; set-scratch-buffer-variables may be unbound yet.
+  (when (fboundp 'set-scratch-buffer-variables)
+    (set-scratch-buffer-variables)))
+
 (defcustom initial-scratch-message (purecopy "\
 ;; This buffer is for notes you don't want to save, and for Lisp evaluation.
 ;; If you want to create a file, visit that file with C-x C-f,
 ;; then enter the text in that file's own buffer.
 
 ")
-  "Initial message displayed in *scratch* buffer at startup.
-If this is nil, no message will be displayed."
+  "Initial message displayed in `*scratch*' buffer at startup.  If this is nil,
+no message will be displayed.  If this is symbol 'file content of the buffer
+will be read from a \"scratch\" file in `user-emacs-directory' and saved there
+when Emacs exists preserving it across restarts.
+
+As side effect of setting this to 'file, `*scratch*' buffer becomes immortal,
+ie. killing it will merely clear its content and bury it.
+
+When set via customize, various `*scratch*' buffer's local variables are
+modified by calling `set-scratch-buffer-variables' function."
   :type '(choice (text :tag "Message")
-                (const :tag "none" nil))
+                (const :tag "Read from scratch file" file)
+                (const :tag "None" nil))
+  :set 'scratch--custom-set
   :group 'initialization)
 
+(defcustom scratch-recover-from-auto-save-file 'ask
+  "What to do if scratch autosave file is newer than the scratch file.
+
+When set via customize, various `*scratch*' buffer's local variables are
+modified by calling `set-scratch-buffer-variables' function."
+  :type '(choice (const :tag "Always recover auto-save" t)
+                (const :tag "Never recover auto-save" nil)
+                (const :tag "Ask whether to recover auto-save" ask))
+  :set 'scratch--custom-set
+  :group 'initialization)
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;; Fancy splash screen
@@ -2292,19 +2319,7 @@ A fancy display is used on graphic displays, normal 
otherwise."
            ;; abort later.
            (unless (frame-live-p (selected-frame)) (kill-emacs nil))))))
 
-    (when (eq initial-buffer-choice t)
-      ;; When initial-buffer-choice equals t make sure that *scratch*
-      ;; exists.
-      (get-buffer-create "*scratch*"))
-
-    ;; If *scratch* exists and is empty, insert initial-scratch-message.
-    ;; Do this before switching to *scratch* below to handle bug#9605.
-    (and initial-scratch-message
-        (get-buffer "*scratch*")
-        (with-current-buffer "*scratch*"
-          (when (zerop (buffer-size))
-            (insert initial-scratch-message)
-            (set-buffer-modified-p nil))))
+    (scratch--initialise)
 
     (when initial-buffer-choice
       (let ((buf
@@ -2383,4 +2398,97 @@ A fancy display is used on graphic displays, normal 
otherwise."
        (setq file (replace-match "/" t t file))))
     file))
 
+(defun scratch--initialise ()
+  "Initialises the *scratch* buffer.
+
+If `initial-buffer-choice' variable equals t *scratch* buffer will be created
+if it does not exist.  If it is empty its content will be populated depending
+on the `initial-scratch-message' variable.  (So if it's nil, the buffer will
+be left empty).
+
+Finally, hooks will be added which are affect the *scratch* buffer if
+`initial-scratch-message' variable equals 'file.  One will make the buffer
+immortal by burying it on kill, the other will save its content when Emacs
+exits."
+    (let ((buf (if (eq initial-buffer-choice t)
+                  (get-buffer-create "*scratch*")
+                (get-buffer "*scratch*"))))
+      (and buf
+          initial-scratch-message
+          (with-current-buffer buf
+            (when (zerop (buffer-size))
+              (scratch--insert-content)
+              (set-buffer-modified-p nil))))))
+
+(defconst scratch--initial-file-message (purecopy "\
+;; This buffer is for notes and for Lisp evaluation.
+;; If you want to create a file, visit that file with C-x C-f,
+;; then enter the text in that file's own buffer.
+;; Contents of this buffer will be saved across restarts.
+
+"))
+
+(defun scratch--set-buffer-variables ()
+  (if (eq initial-scratch-message 'file)
+      (progn
+       (setq buffer-file-name (concat user-emacs-directory "scratch")
+             buffer-save-without-query t)
+       (auto-save-mode (if scratch-recover-from-auto-save-file 1 -1))
+       (add-hook 'kill-buffer-query-functions 'scratch--bury-on-kill-buffer
+                 nil t))
+    (auto-save-mode -1)
+    (kill-local-variable 'buffer-file-name)
+    (kill-local-variable 'buffer-save-without-query)
+    (remove-hook 'kill-buffer-query-functions 'scratch--bury-on-kill-buffer)))
+
+(defun set-scratch-buffer-variables ()
+  "Sets `*sctrach*' local buffer variables based on customize options.
+
+If `*scratch*' buffer does not exist, this function does nothing.  Otherwise
+the following is affected:
+- `buffer-file-name' and `buffer-save-without-query' local variables,
+- `auto-save-mode', and
+- `kill-buffer-query-functions'."
+  (let ((buf (get-buffer "*scratch*")))
+    (when buf
+      (with-current-buffer buf
+       (stratch--set-buffer-variables)))))
+
+(defun scratch--insert-content ()
+  (if (not (eq initial-scratch-message 'file))
+      (insert initial-scratch-message)
+    (scratch--set-buffer-variables)
+    (let* ((have-file (file-readable-p buffer-file-name))
+          (have-auto-save (and buffer-auto-save-file-name
+                               (file-readable-p buffer-auto-save-file-name))))
+      ;; If autosave is older, pretend it does not exist.
+      (and have-file
+          have-auto-save
+          (not (file-newer-than-file-p buffer-auto-save-file-name
+                                       buffer-file-name))
+          (setq have-auto-save nil))
+      ;; If user wants us to always recover, pretend there's no base file.
+      (and have-auto-save
+          (eq t scratch-recover-from-auto-save-file)
+          (setq have-file nil))
+      ;; Ask user what to do.
+      (and have-file
+          have-auto-save
+          (if (y-or-n-p "Recover scratch file? ")
+              (setq have-file nil)
+            (setq have-auto-save nil)))
+      (let ((file (cond (have-file buffer-file-name)
+                       (have-auto-save buffer-auto-save-file-name))))
+       (if file
+           (insert-file-contents file nil nil nil t)
+         (insert scratch--initial-file-message))))))
+
+(defun scratch--bury-on-kill-buffer ()
+  (not (when (and (eq 'file initial-scratch-message)
+                 (string-equal (buffer-name (current-buffer)) "*scratch*"))
+        (erase-buffer)
+        (set-buffer-modified-p nil)
+        (bury-buffer)
+        t)))
+
 ;;; startup.el ends here
-- 
1.8.3




reply via email to

[Prev in Thread] Current Thread [Next in Thread]