doomemacs/lisp/doom-start.el

348 lines
15 KiB
EmacsLisp
Raw Normal View History

;;; lisp/doom-start.el --- bootstrapper for interactive sessions -*- lexical-binding: t; -*-
;;; Commentary:
;;; Code:
(require 'doom-modules)
;;
;;; doom-first-*-hook
(defvar doom-first-input-hook nil
"Transient hooks run before the first user input.")
(put 'doom-first-input-hook 'permanent-local t)
(defvar doom-first-file-hook nil
"Transient hooks run before the first interactively opened file.")
(put 'doom-first-file-hook 'permanent-local t)
(defvar doom-first-buffer-hook nil
"Transient hooks run before the first interactively opened buffer.")
(put 'doom-first-buffer-hook 'permanent-local t)
;;
;;; Runtime/startup optimizations
;; A second, case-insensitive pass over `auto-mode-alist' is time wasted.
(setq auto-mode-case-fold nil)
;; Disable bidirectional text scanning for a modest performance boost. I've set
;; this to `nil' in the past, but the `bidi-display-reordering's docs say that
;; is an undefined state and suggest this to be just as good:
(setq-default bidi-display-reordering 'left-to-right
bidi-paragraph-direction 'left-to-right)
;; Disabling the BPA makes redisplay faster, but might produce incorrect display
;; reordering of bidirectional text with embedded parentheses and other bracket
;; characters whose 'paired-bracket' Unicode property is non-nil.
(setq bidi-inhibit-bpa t) ; Emacs 27+ only
;; Reduce rendering/line scan work for Emacs by not rendering cursors or regions
;; in non-focused windows.
(setq-default cursor-in-non-selected-windows nil)
(setq highlight-nonselected-windows nil)
;; More performant rapid scrolling over unfontified regions. May cause brief
;; spells of inaccurate syntax highlighting right after scrolling, which should
;; quickly self-correct.
(setq fast-but-imprecise-scrolling t)
;; Don't ping things that look like domain names.
(setq ffap-machine-p-known 'reject)
;; Resizing the Emacs frame can be a terribly expensive part of changing the
;; font. By inhibiting this, we halve startup times, particularly when we use
;; fonts that are larger than the system default (which would resize the frame).
(setq frame-inhibit-implied-resize t)
;; Emacs "updates" its ui more often than it needs to, so slow it down slightly
(setq idle-update-delay 1.0) ; default is 0.5
;; Font compacting can be terribly expensive, especially for rendering icon
;; fonts on Windows. Whether disabling it has a notable affect on Linux and Mac
;; hasn't been determined, but do it anyway, just in case. This increases memory
;; usage, however!
(setq inhibit-compacting-font-caches t)
;; PGTK builds only: this timeout adds latency to frame operations, like
;; `make-frame-invisible', which are frequently called without a guard because
;; it's inexpensive in non-PGTK builds. Lowering the timeout from the default
;; 0.1 should make childframes and packages that manipulate them (like `lsp-ui',
;; `company-box', and `posframe') feel much snappier. See emacs-lsp/lsp-ui#613.
(eval-when! (boundp 'pgtk-wait-for-event-timeout)
(setq pgtk-wait-for-event-timeout 0.001))
;; Increase how much is read from processes in a single chunk (default is 4kb).
;; This is further increased elsewhere, where needed (like our LSP module).
(setq read-process-output-max (* 64 1024)) ; 64kb
;; Introduced in Emacs HEAD (b2f8c9f), this inhibits fontification while
;; receiving input, which should help a little with scrolling performance.
(setq redisplay-skip-fontification-on-input t)
;; Performance on Windows is considerably worse than elsewhere. We'll need
;; everything we can get.
(eval-when! (boundp 'w32-get-true-file-attributes)
(setq w32-get-true-file-attributes nil ; decrease file IO workload
w32-pipe-read-delay 0 ; faster IPC
w32-pipe-buffer-size (* 64 1024))) ; read more at a time (was 4K)
;; Remove command line options that aren't relevant to our current OS; means
;; slightly less to process at startup.
(eval-when! (not IS-MAC) (setq command-line-ns-option-alist nil))
(eval-when! (not IS-LINUX) (setq command-line-x-option-alist nil))
;; HACK: `tty-run-terminal-initialization' is *tremendously* slow for some
;; reason; inexplicably doubling startup time for terminal Emacs. Keeping it
;; disabled will have nasty side-effects, so we simply delay it instead, and
;; invoke it later, at which point it runs quickly; how mysterious!
(unless (or (daemonp) init-file-debug)
(advice-add #'tty-run-terminal-initialization :override #'ignore)
(defun doom-init-tty-h ()
(advice-remove #'tty-run-terminal-initialization #'ignore)
(tty-run-terminal-initialization (selected-frame) nil t))
(add-hook 'window-setup-hook #'doom-init-tty-h))
;; Reduce *Message* noise at startup. An empty scratch buffer (or the dashboard)
;; is more than enough.
(setq inhibit-startup-screen t
inhibit-startup-echo-area-message user-login-name
inhibit-default-init t)
;; Shave seconds off startup time by starting the scratch buffer in
;; `fundamental-mode', rather than, say, `org-mode' or `text-mode', which pull
;; in a ton of packages. `doom/open-scratch-buffer' provides a better scratch
;; buffer anyway.
(setq initial-major-mode 'fundamental-mode
initial-scratch-message nil)
;; The GC introduces annoying pauses and stuttering into our Emacs experience,
;; so we use `gcmh' to stave off the GC while we're using Emacs, and provoke it
;; when it's idle. However, if the idle delay is too long, we run the risk of
;; runaway memory usage in busy sessions. If it's too low, then we may as well
;; not be using gcmh at all.
(setq gcmh-idle-delay 'auto ; default is 15s
gcmh-auto-idle-delay-factor 10
gcmh-high-cons-threshold (* 16 1024 1024)) ; 16mb
(add-hook 'doom-first-buffer-hook #'gcmh-mode)
;;
;;; Reasonable defaults for interactive sessions
;; Contrary to what many Emacs users have in their configs, you don't need more
;; than this to make UTF-8 the default coding system:
(set-language-environment "UTF-8")
;; ...but `set-language-environment' also sets `default-input-method', which is
;; a step too opinionated.
(setq default-input-method nil)
;; ...And the clipboard on Windows could be in a wider encoding (UTF-16), so
;; leave Emacs to its own devices.
(eval-when! IS-WINDOWS
(setq selection-coding-system 'utf-8))
;; Get rid of "For information about GNU Emacs..." message at startup. It's
;; redundant with our dashboard. However, in daemon sessions it says "Starting
;; Emacs daemon." instead, which is fine.
(unless (daemonp)
(advice-add #'display-startup-echo-area-message :override #'ignore))
;; GUIs are inconsistent across systems, will rarely match our active Emacs
;; theme, and impose their shortcut key paradigms suddenly. Let's just avoid
;; them altogether and have Emacs handle the prompting.
(setq use-dialog-box nil)
(when (bound-and-true-p tooltip-mode)
(tooltip-mode -1))
(eval-when! IS-LINUX
(setq x-gtk-use-system-tooltips nil))
;; Favor vertical splits over horizontal ones, since monitors are trending
;; toward wide rather than tall.
(setq split-width-threshold 160
split-height-threshold nil)
;;
;;; MODE-local-vars-hook
;; File+dir local variables are initialized after the major mode and its hooks
;; have run. If you want hook functions to be aware of these customizations, add
;; them to MODE-local-vars-hook instead.
(defvar doom-inhibit-local-var-hooks nil)
(defun doom-run-local-var-hooks-h ()
"Run MODE-local-vars-hook after local variables are initialized."
(unless (or doom-inhibit-local-var-hooks delay-mode-hooks)
(setq-local doom-inhibit-local-var-hooks t)
(doom-run-hooks (intern (format "%s-local-vars-hook" major-mode)))))
;; If the user has disabled `enable-local-variables', then
;; `hack-local-variables-hook' is never triggered, so we trigger it at the end
;; of `after-change-major-mode-hook':
(defun doom-run-local-var-hooks-maybe-h ()
"Run `doom-run-local-var-hooks-h' if `enable-local-variables' is disabled."
(unless enable-local-variables
(doom-run-local-var-hooks-h)))
;;
;;; Incremental lazy-loading
(defvar doom-incremental-packages '(t)
"A list of packages to load incrementally after startup. Any large packages
here may cause noticeable pauses, so it's recommended you break them up into
sub-packages. For example, `org' is comprised of many packages, and can be
broken up into:
(doom-load-packages-incrementally
'(calendar find-func format-spec org-macs org-compat
org-faces org-entities org-list org-pcomplete org-src
org-footnote org-macro ob org org-clock org-agenda
org-capture))
This is already done by the lang/org module, however.
If you want to disable incremental loading altogether, either remove
`doom-load-packages-incrementally-h' from `emacs-startup-hook' or set
`doom-incremental-first-idle-timer' to nil. Incremental loading does not occur
in daemon sessions (they are loaded immediately at startup).")
(defvar doom-incremental-first-idle-timer (if (daemonp) 0 2.0)
"How long (in idle seconds) until incremental loading starts.
Set this to nil to disable incremental loading.
Set this to 0 to load all incrementally deferred packages immediately at
`emacs-startup-hook'.")
(defvar doom-incremental-idle-timer 0.75
"How long (in idle seconds) in between incrementally loading packages.")
(defun doom-load-packages-incrementally (packages &optional now)
"Registers PACKAGES to be loaded incrementally.
If NOW is non-nil, load PACKAGES incrementally, in `doom-incremental-idle-timer'
intervals."
fix: memory leak & freezes on native-comp+pgtk builds b7f84bd introduced a nasty regression that caused an infinite loop and runaway memory usage on some pgtk+native-comp builds of Emacs when it attempted to perform deferred native compilation of your packages. This would make Emacs unusable and, if left alone, could even crash your system. The only Emacs builds I'm certain are affected are derived from flatwhatson/emacs (like emacs-pgtk-native-comp on Guix and Arch Linux in particular). 28.1 stable and master (on emacs-mirror/emacs@e13509468b7c) are unaffected. It appears that some, earlier pgtk builds stack idle timers differently. I'm not entirely sure how, because it doesn't manifest in more recent builds of Emacs, and I'm already burnt out on debugging this, but here's how Doom encountered it: Doom has an incremental package loader; it loads packages, piecemeal, after Emacs has been idle for 2s, then again every 0.75s until it finishes or the user sends input (then it waits another 2s before starting again). However, if at any time the iloader detected that native-compilation is in progress, it waits 2s before trying again (repeat, until native-comp is done). But here's the catch, given the following: (run-with-idle-timer 2 nil (lambda () (run-with-idle-timer 1 nil (lambda () (message "hi"))))) I had assumed "hi" would be emitted after 3 seconds (once idle), but instead it is emitted after 2. Like this, Doom's iloader would elapse one idle timer directly into another, ad infinitum, until Emacs was forcibly killed. By switching to run-at-time and employing my own rudimentary idle timer, I avoid this issue. Also, the iloader no longer needs to be considerate of native-comp, because the latter does its own rate-limiting controlled by native-comp-async-jobs-number. Amend: b7f84bdd0105
2022-09-10 01:01:28 +02:00
(let ((gc-cons-threshold most-positive-fixnum))
(if (not now)
(cl-callf append doom-incremental-packages packages)
(while packages
fix: memory leak & freezes on native-comp+pgtk builds b7f84bd introduced a nasty regression that caused an infinite loop and runaway memory usage on some pgtk+native-comp builds of Emacs when it attempted to perform deferred native compilation of your packages. This would make Emacs unusable and, if left alone, could even crash your system. The only Emacs builds I'm certain are affected are derived from flatwhatson/emacs (like emacs-pgtk-native-comp on Guix and Arch Linux in particular). 28.1 stable and master (on emacs-mirror/emacs@e13509468b7c) are unaffected. It appears that some, earlier pgtk builds stack idle timers differently. I'm not entirely sure how, because it doesn't manifest in more recent builds of Emacs, and I'm already burnt out on debugging this, but here's how Doom encountered it: Doom has an incremental package loader; it loads packages, piecemeal, after Emacs has been idle for 2s, then again every 0.75s until it finishes or the user sends input (then it waits another 2s before starting again). However, if at any time the iloader detected that native-compilation is in progress, it waits 2s before trying again (repeat, until native-comp is done). But here's the catch, given the following: (run-with-idle-timer 2 nil (lambda () (run-with-idle-timer 1 nil (lambda () (message "hi"))))) I had assumed "hi" would be emitted after 3 seconds (once idle), but instead it is emitted after 2. Like this, Doom's iloader would elapse one idle timer directly into another, ad infinitum, until Emacs was forcibly killed. By switching to run-at-time and employing my own rudimentary idle timer, I avoid this issue. Also, the iloader no longer needs to be considerate of native-comp, because the latter does its own rate-limiting controlled by native-comp-async-jobs-number. Amend: b7f84bdd0105
2022-09-10 01:01:28 +02:00
(let ((req (pop packages))
idle-time)
(if (featurep req)
(doom-log "start:iloader: Already loaded %s (%d left)" req (length packages))
(condition-case-unless-debug e
fix: memory leak & freezes on native-comp+pgtk builds b7f84bd introduced a nasty regression that caused an infinite loop and runaway memory usage on some pgtk+native-comp builds of Emacs when it attempted to perform deferred native compilation of your packages. This would make Emacs unusable and, if left alone, could even crash your system. The only Emacs builds I'm certain are affected are derived from flatwhatson/emacs (like emacs-pgtk-native-comp on Guix and Arch Linux in particular). 28.1 stable and master (on emacs-mirror/emacs@e13509468b7c) are unaffected. It appears that some, earlier pgtk builds stack idle timers differently. I'm not entirely sure how, because it doesn't manifest in more recent builds of Emacs, and I'm already burnt out on debugging this, but here's how Doom encountered it: Doom has an incremental package loader; it loads packages, piecemeal, after Emacs has been idle for 2s, then again every 0.75s until it finishes or the user sends input (then it waits another 2s before starting again). However, if at any time the iloader detected that native-compilation is in progress, it waits 2s before trying again (repeat, until native-comp is done). But here's the catch, given the following: (run-with-idle-timer 2 nil (lambda () (run-with-idle-timer 1 nil (lambda () (message "hi"))))) I had assumed "hi" would be emitted after 3 seconds (once idle), but instead it is emitted after 2. Like this, Doom's iloader would elapse one idle timer directly into another, ad infinitum, until Emacs was forcibly killed. By switching to run-at-time and employing my own rudimentary idle timer, I avoid this issue. Also, the iloader no longer needs to be considerate of native-comp, because the latter does its own rate-limiting controlled by native-comp-async-jobs-number. Amend: b7f84bdd0105
2022-09-10 01:01:28 +02:00
(and
(or (null (setq idle-time (current-idle-time)))
(< (float-time idle-time) doom-incremental-first-idle-timer)
(not
(while-no-input
(doom-log "start:iloader: Loading %s (%d left)" req (length packages))
fix: memory leak & freezes on native-comp+pgtk builds b7f84bd introduced a nasty regression that caused an infinite loop and runaway memory usage on some pgtk+native-comp builds of Emacs when it attempted to perform deferred native compilation of your packages. This would make Emacs unusable and, if left alone, could even crash your system. The only Emacs builds I'm certain are affected are derived from flatwhatson/emacs (like emacs-pgtk-native-comp on Guix and Arch Linux in particular). 28.1 stable and master (on emacs-mirror/emacs@e13509468b7c) are unaffected. It appears that some, earlier pgtk builds stack idle timers differently. I'm not entirely sure how, because it doesn't manifest in more recent builds of Emacs, and I'm already burnt out on debugging this, but here's how Doom encountered it: Doom has an incremental package loader; it loads packages, piecemeal, after Emacs has been idle for 2s, then again every 0.75s until it finishes or the user sends input (then it waits another 2s before starting again). However, if at any time the iloader detected that native-compilation is in progress, it waits 2s before trying again (repeat, until native-comp is done). But here's the catch, given the following: (run-with-idle-timer 2 nil (lambda () (run-with-idle-timer 1 nil (lambda () (message "hi"))))) I had assumed "hi" would be emitted after 3 seconds (once idle), but instead it is emitted after 2. Like this, Doom's iloader would elapse one idle timer directly into another, ad infinitum, until Emacs was forcibly killed. By switching to run-at-time and employing my own rudimentary idle timer, I avoid this issue. Also, the iloader no longer needs to be considerate of native-comp, because the latter does its own rate-limiting controlled by native-comp-async-jobs-number. Amend: b7f84bdd0105
2022-09-10 01:01:28 +02:00
;; If `default-directory' doesn't exist or is
;; unreadable, Emacs throws file errors.
(let ((default-directory doom-emacs-dir)
(inhibit-message t)
(file-name-handler-alist
(list (rassq 'jka-compr-handler file-name-handler-alist))))
(require req nil t)
t))))
(push req packages))
(error
fix: memory leak & freezes on native-comp+pgtk builds b7f84bd introduced a nasty regression that caused an infinite loop and runaway memory usage on some pgtk+native-comp builds of Emacs when it attempted to perform deferred native compilation of your packages. This would make Emacs unusable and, if left alone, could even crash your system. The only Emacs builds I'm certain are affected are derived from flatwhatson/emacs (like emacs-pgtk-native-comp on Guix and Arch Linux in particular). 28.1 stable and master (on emacs-mirror/emacs@e13509468b7c) are unaffected. It appears that some, earlier pgtk builds stack idle timers differently. I'm not entirely sure how, because it doesn't manifest in more recent builds of Emacs, and I'm already burnt out on debugging this, but here's how Doom encountered it: Doom has an incremental package loader; it loads packages, piecemeal, after Emacs has been idle for 2s, then again every 0.75s until it finishes or the user sends input (then it waits another 2s before starting again). However, if at any time the iloader detected that native-compilation is in progress, it waits 2s before trying again (repeat, until native-comp is done). But here's the catch, given the following: (run-with-idle-timer 2 nil (lambda () (run-with-idle-timer 1 nil (lambda () (message "hi"))))) I had assumed "hi" would be emitted after 3 seconds (once idle), but instead it is emitted after 2. Like this, Doom's iloader would elapse one idle timer directly into another, ad infinitum, until Emacs was forcibly killed. By switching to run-at-time and employing my own rudimentary idle timer, I avoid this issue. Also, the iloader no longer needs to be considerate of native-comp, because the latter does its own rate-limiting controlled by native-comp-async-jobs-number. Amend: b7f84bdd0105
2022-09-10 01:01:28 +02:00
(message "Error: failed to incrementally load %S because: %s" req e)
(setq packages nil)))
(if (null packages)
(doom-log "start:iloader: Finished!")
fix: memory leak & freezes on native-comp+pgtk builds b7f84bd introduced a nasty regression that caused an infinite loop and runaway memory usage on some pgtk+native-comp builds of Emacs when it attempted to perform deferred native compilation of your packages. This would make Emacs unusable and, if left alone, could even crash your system. The only Emacs builds I'm certain are affected are derived from flatwhatson/emacs (like emacs-pgtk-native-comp on Guix and Arch Linux in particular). 28.1 stable and master (on emacs-mirror/emacs@e13509468b7c) are unaffected. It appears that some, earlier pgtk builds stack idle timers differently. I'm not entirely sure how, because it doesn't manifest in more recent builds of Emacs, and I'm already burnt out on debugging this, but here's how Doom encountered it: Doom has an incremental package loader; it loads packages, piecemeal, after Emacs has been idle for 2s, then again every 0.75s until it finishes or the user sends input (then it waits another 2s before starting again). However, if at any time the iloader detected that native-compilation is in progress, it waits 2s before trying again (repeat, until native-comp is done). But here's the catch, given the following: (run-with-idle-timer 2 nil (lambda () (run-with-idle-timer 1 nil (lambda () (message "hi"))))) I had assumed "hi" would be emitted after 3 seconds (once idle), but instead it is emitted after 2. Like this, Doom's iloader would elapse one idle timer directly into another, ad infinitum, until Emacs was forcibly killed. By switching to run-at-time and employing my own rudimentary idle timer, I avoid this issue. Also, the iloader no longer needs to be considerate of native-comp, because the latter does its own rate-limiting controlled by native-comp-async-jobs-number. Amend: b7f84bdd0105
2022-09-10 01:01:28 +02:00
(run-at-time (if idle-time
doom-incremental-idle-timer
doom-incremental-first-idle-timer)
nil #'doom-load-packages-incrementally
packages t)
(setq packages nil))))))))
(defun doom-load-packages-incrementally-h ()
"Begin incrementally loading packages in `doom-incremental-packages'.
If this is a daemon session, load them all immediately instead."
(when (numberp doom-incremental-first-idle-timer)
(if (zerop doom-incremental-first-idle-timer)
(mapc #'require (cdr doom-incremental-packages))
(run-with-idle-timer doom-incremental-first-idle-timer
nil #'doom-load-packages-incrementally
(cdr doom-incremental-packages) t))))
;;
;;; Let 'er rip
(defvar doom-init-time nil
"The time it took, in seconds, for Doom Emacs to initialize.")
(defun doom-display-benchmark-h (&optional return-p)
"Display a benchmark including number of packages and modules loaded.
If RETURN-P, return the message as a string instead of displaying it."
(funcall (if return-p #'format #'message)
"Doom loaded %d packages across %d modules in %.03fs"
(- (length load-path) (length (get 'load-path 'initial-value)))
(hash-table-count doom-modules)
(or doom-init-time
(setq doom-init-time
(float-time (time-subtract (current-time) before-init-time))))))
;; Add support for additional file extensions.
(dolist (entry '(("/\\.doom\\(?:rc\\|project\\|module\\|profile\\)\\'" . emacs-lisp-mode)
("/LICENSE\\'" . text-mode)
("\\.log\\'" . text-mode)
("rc\\'" . conf-mode)
("\\.\\(?:hex\\|nes\\)\\'" . hexl-mode)))
(push entry auto-mode-alist))
;; Doom caches a lot of information in `doom-autoloads-file'. Module and package
;; autoloads, autodefs like `set-company-backend!', and variables like
;; `doom-modules', `doom-disabled-packages', `load-path', `auto-mode-alist', and
;; `Info-directory-list'. etc. Compiling them into one place is a big reduction
;; in startup time.
(condition-case-unless-debug e
;; Avoid `file-name-sans-extension' for premature optimization reasons.
;; `string-remove-suffix' is cheaper because it performs no file sanity
;; checks; just plain ol' string manipulation.
(load (string-remove-suffix ".el" doom-autoloads-file) nil 'nomessage)
(file-missing
;; If the autoloads file fails to load then the user forgot to sync, or
;; aborted a doom command midway!
(if (locate-file doom-autoloads-file load-path)
;; Something inside the autoloads file is triggering this error;
;; forward it to the caller!
(signal 'doom-autoload-error e)
(signal 'doom-error
(list "Doom is in an incomplete state"
"run 'doom sync' on the command line to repair it")))))
(when (and (or (display-graphic-p)
(daemonp))
doom-env-file)
(setq-default process-environment (get 'process-environment 'initial-value))
(doom-load-envvars-file doom-env-file 'noerror))
;; Bootstrap the interactive session
(add-hook 'after-change-major-mode-hook #'doom-run-local-var-hooks-h 100)
(add-hook 'hack-local-variables-hook #'doom-run-local-var-hooks-h)
(add-hook 'emacs-startup-hook #'doom-load-packages-incrementally-h)
(add-hook 'window-setup-hook #'doom-display-benchmark-h 105)
(doom-run-hook-on 'doom-first-buffer-hook '(find-file-hook doom-switch-buffer-hook))
(doom-run-hook-on 'doom-first-file-hook '(find-file-hook dired-initial-position-hook))
(doom-run-hook-on 'doom-first-input-hook '(pre-command-hook))
;; There's a chance the user will later use package.el or straight in this
;; interactive session. If they do, make sure they're properly initialized
;; when they do.
(autoload 'doom-initialize-packages "doom-packages")
(eval-after-load 'package '(require 'doom-packages))
(eval-after-load 'straight '(doom-initialize-packages))
;; Load all things.
(doom-initialize-modules)
(provide 'doom-start)
;;; doom-start.el ends here