3

I would like emacs to check for a ~/emacs.d/scratch.txt and use it's contents instead of the default message when emacs starts.

How can this be done?

ideasman42
  • 8,375
  • 1
  • 28
  • 105

4 Answers4

3

initial-buffer-choice allows to specify a path to a file or directory:

(let ((filename "~/.emacs.d/startup.txt"))
  (when (file-exists-p filename)
    (setq initial-buffer-choice filename)))
wasamasa
  • 21,803
  • 1
  • 65
  • 97
  • 1
    This has a fairly big disadvantage that the file gets loaded, so running `emacs somefile.txt` will not show the file given in the command line, but the startup file instead. It also doesn't use the scratch buffer name which is ignored for buffer switching in some cases. – ideasman42 Feb 10 '18 at 08:15
  • Well, you're changing what the initial buffer is, so it's hardly surprising it's not named `*scratch*` anymore. As for the other thing, if I run `emacs somefile.txt`, it opens that file in a split, additionally to the buffer I've specified. This is hardcoded in `startup.el`. – wasamasa Feb 10 '18 at 09:39
  • 1
    My aim was simply to have text from a file to replace the default text in `*scratch*`, not to open a file or split windows. Basically, if I open emacs with no files: show a buffer with useful content, otherwise behave as normal. – ideasman42 Feb 10 '18 at 09:59
3

This will do the job.

(let ((filename (concat user-emacs-directory "scratch.txt")))
  (when (file-exists-p filename)
    (let ((scratch-buf (get-buffer "*scratch*")))
      (when scratch-buf
        (with-current-buffer scratch-buf
          (erase-buffer)
          (insert-file-contents filename))))))
ideasman42
  • 8,375
  • 1
  • 28
  • 105
SamanGh
  • 51
  • 3
2

This can be done using initial-buffer-choice,

While initial-buffer-choice can be set to a filename, this will load the file as well as any files passed via the command line (splitting the window or not even showing the buffer depending on your setup).
So reading file data into *scratch* buffer has the advantage that exact behavior is preserved, just replacing the text.

This example:

  • Only runs when the user starts without loading a file.

  • Loads in a text file into the startup buffer.

  • Users a default startup.txt, optionally taking a user defined startup file.

  • Sets the mode based on the filename, so users can pass in startup.org for an org-mode buffer.

  • Adds a short commented line at the top of the file, eg:

    # Emacs: 28.0, time: 0.93, packages: 58

  • When no startup file is found, the name of the file to create is referenced.

;; Load startup text when available.
;;
;; Example usage:
;;
;;   (my-scratch-buffer-from-file)
;;
;; Or if you like to use an org-mode scratch buffer,
;; an option file path can be passed in.
;; The file extension is used to set the mode:
;;
;;   (my-scratch-buffer-from-file (concat user-emacs-directory "scratch.org"))
;;

(defvar my-scratch-buffer-from-file--value nil)
(defun my-scratch-buffer-from-file (&optional scratch-file)
  (setq inhibit-startup-screen t)
  (setq initial-scratch-message nil)
  (when scratch-file
    (setq my-scratch-buffer-from-file--value scratch-file))

  (setq
   initial-buffer-choice
   (lambda ()
     (if (buffer-file-name)
         (current-buffer) ;; leave as-is
       (let ((original-buffer (current-buffer))
             (filename
              (or my-scratch-buffer-from-file--value
                  (concat user-emacs-directory "scratch.txt")))
             ;; Not essential, just gives some handy startup info.
             (startup-info
              (format
               "Emacs: %d.%d, time: %.2f, packages: %d"
               emacs-major-version
               emacs-minor-version
               (float-time (time-subtract after-init-time before-init-time))
               (length package-activated-list))))
         (with-current-buffer (get-buffer-create "*scratch*")
           ;; Don't track undo.
           (buffer-disable-undo)

           ;; Set the mode based on the filename, users may use filenames that infer modes.
           (condition-case err
               (let ((buffer-file-name filename))
                 (set-auto-mode t))
             (error (message "Unable to activate mode: %s" err)))

           ;; Use the comment character set by the mode where possible.
           (let ((comment-start-or-empty
                  (if comment-start
                      ;; Ensure one trailing space (some comments include space).
                      (concat
                       (replace-regexp-in-string "[[:blank:]]*$" "" comment-start)
                       " ")
                    "")))
             (if (file-exists-p filename)
                 (insert-file-contents filename)
               (insert
                comment-start-or-empty
                (format "Scratch buffer, create '%s' to replace this text on startup."
                        filename))
               (goto-char (point-min)))
             ;; Add some startup info above the static text.
             (insert comment-start-or-empty startup-info "\n\n"))
           (buffer-enable-undo)
           (set-buffer original-buffer)))))))


Others might suggest how this can be done better, it seems to work well enough though.

ideasman42
  • 8,375
  • 1
  • 28
  • 105
  • 1
    With proper indentation and the closing parentheses not spread apart. – wasamasa Feb 09 '18 at 09:01
  • @wasamasa done. – ideasman42 Feb 22 '20 at 00:07
  • Your formatter does somewhat better, but it's still far from idiomatic. Even this thing does better than that: https://www.t3x.org/lisp64k/grinding.html – wasamasa Feb 22 '20 at 11:44
  • It looks like this doesn't wrap long lines, so I don't think it's usable in it's current form. – ideasman42 Feb 22 '20 at 23:42
  • Thats a lot of code to replace initial-scratch-message. Also see the package https://melpa.org/#/elisp-format . Not ideal but is any elisp formatter? Everyone seems to have their own way.... – RichieHH Feb 24 '20 at 06:28
  • Re: melpa.org/#/elisp-format `By default, when you format 'huge' file, it will hang emacs.` -- this isn't practical to run on save, which I do for my auto-formatter. Tested this and it has right shift problems - pushing my code over 120 columns. – ideasman42 Feb 24 '20 at 12:00
  • Re: "Thats a lot of code to replace initial-scratch-message", it does seem that way, but found these details useful in practice. – ideasman42 Feb 24 '20 at 12:02
2

I would use initial-scratch-message.

(let ((file "~/.emacs.d/scratch.txt"))
  (when (file-exists-p file)
    (setq initial-scratch-message
      (with-temp-buffer
        (insert-file-contents file)
        (buffer-string)))))
phils
  • 48,657
  • 3
  • 76
  • 115