Skip to content

Latest commit

 

History

History
614 lines (573 loc) · 24.1 KB

init.org

File metadata and controls

614 lines (573 loc) · 24.1 KB

#+ TITLE minimal emacs startup

Implement init preload (early-init.el)

Emacs 27 introduces early-init.el to go stuff before the graphics - basically package and some gui suppression

Startup

These are speedup and safety changes but note that Aquamacs has probably already taken most of the time.

Elisps and init files have two ways of doing this. One is to put the whole init inside a let block but I want each src block to be runnable from the org file so not good. The other is to set and copy old values at the beginning then use an end hook to put them back. A long term alternative is to make the loader function do that work.

The time is probably not that useful - build on Aquamacs instead and there is an emacs profiler. (the profiler for startup files only really work if there is one init file) I did get the Aquamacs nightly and that is much slower and displaying menus is an issue. I have changed accessibility in System Preferences->Privacy so we will see. Probably wait until emacs 27 is out and use a plain emacs and see about spell checking

The file-name-handler-alist is probably needed as some of the hooks slow things heavily

This is from John Wiegley Also from doom via https://github.com/xenodium/dotsies/blob/main/emacs/early-init.el

;;(defconst emacs-start-time (current-time))

(defvar file-name-handler-alist-old file-name-handler-alist)

(setq file-name-handler-alist nil
	   message-log-max 16384
	   gc-cons-threshold 402653184
	   gc-cons-percentage 0.6
	   auto-window-vscroll nil)

Directories

Some of this needs to be here as package and eln-cache are used early.

We need to sort out paths - ideally after init timers etc but we also need them to load early-init so timer is less accurate but then it is wrong for aquamacs anyway

Set where the init file is

In constant mwb-user-emacs-directory
;; Need the directory from here.
(defconst mwb-user-emacs-directory
  (file-name-directory (or load-file-name buffer-file-name)))

Other emacs files

We want some stuff not under .emacs.d so if on dropbox then this is not. This is the root position.

Local

(defvar mwb-emacs-work-dir (expand-file-name "~/.local/emacs" )
  "Directory on local machine wwhere emacs outside start directory.")

Shared

When I found gnus messed up and moving between emacs inits I realised the non git files need to be outside ~/.emacs.d but coul;d be local but really shared across machines
(defvar mwb-dropbox-root
  (expand-file-name  "~/Library/CloudStorage/Dropbox")
  "Where dropbox is")
(defvar mwb-icloud-root
  (expand-file-name  "~/Library/Mobile Documents/com~apple~CloudDocs")
  "Where icloud is")
(defvar mwb-emacs-share-dir
  (concat mwb-dropbox-root "/data/emacs")
  "Directory on local machine wwhere emacs outside start directory.")

Eln-cache - native compile

This could be moved outside of .emacs.d - When I use multiple machines etc. See https://github.com/jimeh/.emacs.d/blob/master/early-init.el and jerrypnz NOTE the method for setting the eln-cache dir depends on the emacs version.
(defvar mwb-emacs-eln-cache-dir
  (expand-file-name "eln-cache" mwb-emacs-work-dir))
(make-directory mwb-emacs-eln-cache-dir t)

(when (>= emacs-major-version 28)
  (if (fboundp 'startup-redirect-eln-cache)
	  (startup-redirect-eln-cache mwb-emacs-eln-cache-dir)
	(add-to-list 'native-comp-eln-load-path mwb-emacs-eln-cache-dir)))

Ignore warnings as we can’t do much about them as most code is not written by me.

(setq native-comp-async-report-warnings-errors 'silent)

Where my init code is

See Xah Lee get directory name for file for possible work around for user-emacs-directory. Except in some cases I do want the directory so break it up
(defun mwb-user-emacs-file (name)
	"Return an absolute per-user Emacs-specific file name around where the init file is.
  It is basically locate-user-emacs-file but I have followed Aquamacs is setting that not where my init.el file is.
  Main reason to use is so that I can put init under version control and the rest go elsewhere."
	(expand-file-name name mwb-user-emacs-directory))
  

Package

Package initialize occurs automatically, before `user-init-file’ is loaded, but after `early-init-file’. emacs handles package initialization, so we must prevent Emacs from doing it early! But if using package we do want it done - early-init turn off is for straight.el etc

Package manager

Set paths

Set the path to packages
(setq package-user-dir
      (expand-file-name
       (format "elpa/%s" emacs-major-version) mwb-user-emacs-directory))

Set package quickload by version

(setq package-quickstart-file
	  (expand-file-name
	   (format "package-quickstart.%d.el" emacs-major-version)
	   mwb-user-emacs-directory))

Not package.el

(setq package-enable-at-startup nil)

Graphical suppression

Fiddle with suppressing graphics. I do want some of these
;;(menu-bar-mode -1)
(unless (and (display-graphic-p) (eq system-type 'darwin))
  (push '(menu-bar-lines . 0) default-frame-alist))
(push '(tool-bar-lines . 0) default-frame-alist)
;;(push '(vertical-scroll-bars) default-frame-alist)
(setq tool-bar-mode nil)
(setq mode-line-format nil)

Frame size change on font

From doomemacs Resizing the Emacs frame can be a terribly expensive part of changing the font. By inhibiting this, we easily halve startup times with fonts that are larger than the system default.
(setq orig-frame-inhibit-implied-resize frame-inhibit-implied-resize)
(setq frame-inhibit-implied-resize t)

Emacs lisp

Control a bit of loading

Use source where newer

This variable tells Emacs to prefer the .el file if itq’s newer, even if there is a corresponding .elc
(setq load-prefer-newer t)

use-package

From centaur. `use-package’ is builtin since 29. It must be set before loading `use-package’.
(setq use-package-enable-imenu-support t)

(setq use-package-enable-imenu-support t)

Implement init environment (init.el)

All this is tangled into init.el which is also under git.

Early init

For Emacs < 27 we need early -init. All my fancy directory stuff is in early-init so we don’t know where to find this so hard code :(
(when (version< emacs-version "27")
  (load (expand-file-name "~/.emacs.d/early-init")))

package

As this is now ~/.emacs.d/init.el and not in ~/Library/Preferences Emacs sees this as startup and adds the package-initialise. So need to add here to stop init.el changing and being see in github
;; Added by Package.el.  This must come before configurations of
;; installed packages.  Don't delete this line.  If you don't want it,
;; just comment it out by adding a semicolon to the start of the line.
;; You may delete these explanatory comments.
;(package-initialize)

Debugging

This slows things down so for debugging outside init. but I now don’t like Backtrace mainly as trying to use emacs not edit it. However for debugging init we don’t want the backtrace

Elisp

Set for init - can chang in init-mwb-hook-after-init. Later toggles in main hydra.
;;  setting to nil turns them off
(setq debug-on-error t)
;; (setq debug-on-error nil)
(setq debug-on-quit nil)

use-package

True adds staistics - only set to nil when I actually use emacs and not just fiddle with init.
(setq init-file-debug 'nil)
;; (setq init-file-debug t)

Variable watcher helpers

From kasual modi
(defvar modi/variables-to-be-watched ()
  "List of variables to be watched.
Used by `modi/set-variable-watchers' and
`modi/unset-variable-watchers'")

(defun modi/variable-watcher-fn (symbol newval operation where)
  "Print message when the value of variable SYMBOL changes.
The message shows the NEWVAL it changed to, the OPERATION that
caused that, and the buffer WHERE that happened if the value
change was buffer-local."
  (message (format "[Watcher: %s] Now set to %S, by `%S'%s"
                   (symbol-name symbol)
                   newval
                   operation
                   (if where
                       (format " in %S" where)
                     ""))))

(defun modi/set-variable-watchers ()
  "Enable printing messages when any watched variable changes.
The variables to be watched should be added to
`modi/variables-to-be-watched'."
  (interactive)
  (dolist (var modi/variables-to-be-watched)
    (add-variable-watcher var #'modi/variable-watcher-fn)))

(defun modi/unset-variable-watchers ()
  "Disable variable watchers.
Variable watching will be disabled for the list of variables set
in `modi/variables-to-be-watched'."
  (interactive)
  (dolist (var modi/variables-to-be-watched)
    (remove-variable-watcher var #'modi/variable-watcher-fn)))

Actual watchers

(add-to-list 'modi/variables-to-be-watched 'debug-on-quit)
(modi/set-variable-watchers)

Trace

;; Trace some function
;; (require 'trace)
;; (untrace-all)
;; (trace-function-foreground 'iso-transl-define-keys)

Profiler

;; (require 'profiler)
;; (profiler-start 'cpu+mem)
;; (add-hook-lambda after-init-hook (profiler-stop))

Message

Show message with timestamp - commented out
(defvar mwb-message-offset (float-time) "Time of last message")

(defun my-message-with-timestamp (old-func fmt-string &rest args)
  "Prepend current timestamp (with microsecond precision) to a message"
  (apply old-func
         (concat (format-time-string "[%F %T] ")
                 fmt-string)
         args))

(defun my-message-with-timediff (old-func fmt-string &rest args)
  "Prepend difference in time to a message"

  (let* ((now (float-time))
         (diff (- now mwb-message-offset)))
    (setq mwb-message-offset now)
    (apply old-func
           (if (> diff 0.1)
               (concat (format "[%g] " diff)
                       fmt-string)
             fmt-string)
           args)))

(advice-add 'message :around #'my-message-with-timediff)

(defun mwb-message-remove-timediff ()
  (interactive)
  (advice-remove 'message #'my-message-with-timediff)
  (message "remove timestamp"))

(defvar mwb-message-timestamp 'nil "true iff message should show timestamp")

(defun toggle-mwb-message-timestamp ()
  (interactive)
  (if mwb-message-timestamp
      (progn
        (advice-remove 'message #'my-message-with-timestamp)
        (setq mwb-message-timestamp 'nil)
        (message "remove timestamp"))
    (advice-add 'message :around #'my-message-with-timestamp)
	(setq mwb-message-timestamp t)
    (message "add timestamp")))

(add-hook 'after-init-hook 'mwb-message-remove-timediff)

Code to do loading

Need to get the correct directory

Function to load the code for this part of the init. Currently it just loads the .el of that name so could just be (load “mwb-init-load”). I now tangle all org-mode buffers on save. Eventually it will get the data from mwb-init-load.org and tangle it and use that.

Helper functions

Thse are required elisp for initialisation

Non org mode expander

This is from nullman’s init files]] withn a rename to show it is not part of org.
(defun nullman/org-babel-generate-elisp-file (file &optional byte-compile force)
  "Generate an emacs-lisp file from an org-babel FILE.

Additionally, byte compile the file if BYTE-COMPILE is
non-nil.

Process file even if timestamp is not newer than target if FORCE
is non-nil."
  (let* ((case-fold-search t)
         (file-base (expand-file-name (file-name-sans-extension file)))
         (file-org (concat file-base ".org"))
         (file-elisp (concat file-base ".el"))
         (file-comp (concat file-base ".elc"))
         (heading-regexp "^\*+ ")
         (heading-comment-regexp "^\*+ COMMENT ")
         (begin-regexp "^[ \t]*#\\+BEGIN_SRC emacs-lisp")
         (begin-tangle-regexp "^[ \t]*#\\+BEGIN_SRC .*:tangle ")
         (end-regexp "^[ \t]*#\\+END_SRC")
         (indent-regexp "^  "))
    ;; generate elisp file if needed
    (when (or force
              (not (file-exists-p file-elisp))
              (file-newer-than-file-p file-org file-elisp))
      (message "Nullman Writing %s..." file-elisp)
      (with-temp-file file-elisp
        (insert-file-contents file)
        (goto-char (point-min))
        (let (code
              headings-counts
              (level 1)
              (comment-level 0)
              (end-comment ""))
          (while (not (eobp))
            (cond
             ;; comment heading
             ((let ((case-fold-search nil))
                (looking-at heading-comment-regexp))
              (setq level (/ (- (match-end 0) (line-beginning-position) 8) 2))
              (when (or (zerop comment-level)
                        (< level comment-level))
                (setq comment-level level))
              (delete-region (line-beginning-position) (progn (forward-line) (point))))
             ;; normal heading
             ((looking-at heading-regexp)
              (setq level (/ (- (match-end 0) (line-beginning-position)) 2))
              (when (or (zerop comment-level)
                        (<= level comment-level))
                (setq comment-level 0)
                (if (assoc level headings-counts)
                    (setf (cdr (assoc level headings-counts))
                          (cons (buffer-substring-no-properties (match-end 0) (line-end-position)) 1))
                  (setq headings-counts (append headings-counts (list (cons level (cons "No heading" 1)))))))
              (delete-region (line-beginning-position) (progn (forward-line) (point))))
             ;; start of tangled source block
             ((and (looking-at begin-regexp)
                   (zerop comment-level)
                   (not (looking-at begin-tangle-regexp))) ; skip blocks with their own tangle directive
              (let* ((heading-count (cdr (assoc level headings-counts)))
                     (heading (car heading-count))
                     (count (cdr heading-count)))
                (delete-region (line-beginning-position) (progn (forward-line) (point)))
                (unless (bobp)
                  (newline))
                ;; (when (fboundp 'org-link-escape)
                ;;   (insert (format ";; [[file:%s::*%s][%s:%s]]\n" file-org (org-link-escape heading) heading count))
                ;;   (setq end-comment (format ";; %s:%s ends here\n" heading count))
                ;;   (cl-incf (cddr (assoc level headings-counts))))
                (setq code t)))
             ;; end of tangled source block
             ((and code
                   (looking-at end-regexp))
              (delete-region (line-beginning-position) (progn (forward-line) (point)))
              (insert end-comment)
              (setq code nil
                    end-comment ""))
             ;; inside tangled source block
             (code
              (when (looking-at indent-regexp)
                (delete-char (if (boundp 'org-edit-src-content-indentation)
                                 org-edit-src-content-indentation
                               2)))
              (forward-line))
             ;; outside tangled source block
             (t
              (delete-region (line-beginning-position) (progn (forward-line) (point))))))
          (time-stamp))
        (message "Nullman Wrote %s..." file-elisp)))

    ))

The loader

Actually load the init files, protect is aquamacs macro to carch errors also see Stack Exchange answer
Internal loader function
Does the actual work
(setq mwb-esup-depth 1)                 ; Some attempt at benchmarking

(defun mwb-init--load (file-root-abs &optional no-org)
  "Load the relevant code.
<file-root> is an absolute file root
Look for <file-root>.org and <file-root>.el files.
If org and no el or org file is newer then retangle the org file if noorg is not nil then use nullmans expand then load <file-root>.el "
  (let* ((org-file
          (concat file-root-abs ".org"))
         (el-file
          (concat file-root-abs ".el")))
    (setq esup-depth mwb-esup-depth)
    ;; (setq esup-child-max-depth mwb-esup-depth )
    (setq esup-child-current-depth 0)
    (when (file-newer-than-file-p org-file el-file)
	  ;; (let ((org-att (file-attributes org-file) )
	  ;; 		(el-time (file-attribute-access-time el-file))
	  ;; 		(org-time (file-attribute-access-time org-att)))
	  ;; 	(message "Generating .el from org for <%s> times %s %s"
	  ;; 			 file-root-abs
	  ;; 			 org-time
	  ;; 			 el-time
	  ;; 			 ))
      (cond (no-org
             (message "tangle <%s> to <%s> using regex replacement not org mode"
                      org-file el-file)
             (nullman/org-babel-generate-elisp-file org-file el-file))
            (t
             (require 'org)
             (message "This loaded an org mode but from the system - best to restart")
             (message "tangle <%s> to <%s> using org version %s"
                      org-file el-file org-version)
             (org-babel-tangle-file org-file el-file))))

    (condition-case err
        (load el-file)
      (error (let ((msg (format-message "Error loading %s: \"%s\""
                                        el-file
                                        (error-message-string err))))
               (warn msg)
               (message msg))))))
Load one file
Use in the main init
(defun mwb-init-load (file-root &optional no-org)
  (mwb-init--load (expand-file-name file-root mwb-user-emacs-directory) no-org))
Load all the files from a directory.
Perhaps might need to sort by length as - is before . so lisp.org loads after lisp-emacs.org. Currently use _ as separator.
(defun mwb-init-load-directory (rel-dir-name)
  "Load up all the files using the init loaded from a directory"
  (let* ((directory-name (expand-file-name rel-dir-name mwb-user-emacs-directory))
         (files (directory-files
                 directory-name
                 nil
                 (rx-to-string '(seq any ".org" eol)))))

    (dolist (f files)
      (mwb-init--load (expand-file-name (file-name-sans-extension f) directory-name)))))

The Load

Also switch between an alternate setup - ideally should be driven from command line but.....
(when (>= emacs-major-version 27)
  ;; (load (concat (expand-file-name "alt/alt_init" mwb-user-emacs-directory) ".el"))
  ;;(load (concat (expand-file-name "alt/straight" mwb-user-emacs-directory) ".el"))
  (mwb-init-load "config" "no-org"))