mirror of
https://github.com/doomemacs/doomemacs
synced 2025-08-01 12:17:25 -05:00
916 lines
42 KiB
EmacsLisp
916 lines
42 KiB
EmacsLisp
;;; doom.el --- the heart of the beast -*- lexical-binding: t; -*-
|
|
;;
|
|
;; Author: Henrik Lissner <contact@henrik.io>
|
|
;; URL: https://github.com/doomemacs/doomemacs
|
|
;;
|
|
;; ================= =============== =============== ======== ========
|
|
;; \\ . . . . . . .\\ //. . . . . . .\\ //. . . . . . .\\ \\. . .\\// . . //
|
|
;; ||. . ._____. . .|| ||. . ._____. . .|| ||. . ._____. . .|| || . . .\/ . . .||
|
|
;; || . .|| ||. . || || . .|| ||. . || || . .|| ||. . || ||. . . . . . . ||
|
|
;; ||. . || || . .|| ||. . || || . .|| ||. . || || . .|| || . | . . . . .||
|
|
;; || . .|| ||. _-|| ||-_ .|| ||. . || || . .|| ||. _-|| ||-_.|\ . . . . ||
|
|
;; ||. . || ||-' || || `-|| || . .|| ||. . || ||-' || || `|\_ . .|. .||
|
|
;; || . _|| || || || || ||_ . || || . _|| || || || |\ `-_/| . ||
|
|
;; ||_-' || .|/ || || \|. || `-_|| ||_-' || .|/ || || | \ / |-_.||
|
|
;; || ||_-' || || `-_|| || || ||_-' || || | \ / | `||
|
|
;; || `' || || `' || || `' || || | \ / | ||
|
|
;; || .===' `===. .==='.`===. .===' /==. | \/ | ||
|
|
;; || .==' \_|-_ `===. .===' _|_ `===. .===' _-|/ `== \/ | ||
|
|
;; || .==' _-' `-_ `=' _-' `-_ `=' _-' `-_ /| \/ | ||
|
|
;; || .==' _-' '-__\._-' '-_./__-' `' |. /| | ||
|
|
;; ||.==' _-' `' | /==.||
|
|
;; ==' _-' \/ `==
|
|
;; \ _-' `-_ /
|
|
;; `'' ``'
|
|
;;
|
|
;; These demons are not part of GNU Emacs.
|
|
;;
|
|
;;; Commentary:
|
|
;;
|
|
;; This is Doom's heart, where I define all its major constants and variables,
|
|
;; set its saner global defaults, then prepare Emacs to bootstrap Doom.
|
|
;;
|
|
;; The overall load order of Doom is as follows:
|
|
;;
|
|
;; > $EMACSDIR/early-init.el
|
|
;; > $EMACSDIR/lisp/doom.el
|
|
;; - $EMACSDIR/lisp/doom-lib.el
|
|
;; > $EMACSDIR/lisp/doom-start.el
|
|
;; - hook: `doom-before-init-hook'
|
|
;; - $DOOMDIR/init.el
|
|
;; - hook: `before-init-hook'
|
|
;; > $XDG_DATA_HOME/doom/$PROFILE/@/$VERSION/init.el (replaces $EMACSDIR/init.el)
|
|
;; - $EMACSDIR/doom-{keybinds,ui,projects,editor}.el
|
|
;; - hook: `doom-before-modules-init-hook'
|
|
;; - {$DOOMDIR,$EMACSDIR}/modules/*/*/init.el
|
|
;; - hook: `doom-after-modules-init-hook'
|
|
;; - hook: `doom-before-modules-config-hook'
|
|
;; - {$DOOMDIR,$EMACSDIR}/modules/*/*/config.el
|
|
;; - hook: `doom-after-modules-config-hook'
|
|
;; - $DOOMDIR/config.el
|
|
;; - `custom-file' or $DOOMDIR/custom.el
|
|
;; - hook: `after-init-hook'
|
|
;; - hook: `emacs-startup-hook'
|
|
;; - hook: `window-setup-hook'
|
|
;; - hook: `doom-init-ui-hook'
|
|
;; - hook: `doom-after-init-hook'
|
|
;; > After startup is complete (if file(s) have been opened from the command
|
|
;; line, these will trigger much earlier):
|
|
;; - On first input: `doom-first-input-hook'
|
|
;; - On first switched-to buffer: `doom-first-buffer-hook'
|
|
;; - On first opened file: `doom-first-file-hook'
|
|
;;
|
|
;; This file is Doom's heart, where I define all its major constants and
|
|
;; variables, set only its sanest global defaults, employ its hackiest (and
|
|
;; least offensive) optimizations, and load the minimum needed for all Doom
|
|
;; sessions, interactive or otherwise.
|
|
;;
|
|
;; See doom-start.el for initialization intended solely for interactive
|
|
;; sessions, and doom-cli.el for non-interactive sessions.
|
|
;;
|
|
;;; Code:
|
|
|
|
;; For the `when-let*' and `if-let*' macros on older versions of Emacs, before
|
|
;; they were autoloaded.
|
|
(eval-when-compile (require 'subr-x))
|
|
|
|
(eval-and-compile
|
|
;; Doom core supports Emacs 27.1 and newer. However, keep in mind that modules
|
|
;; may have different requirements (e.g. the official module library requires
|
|
;; 29.x or newer).
|
|
(when (< emacs-major-version 27)
|
|
(user-error
|
|
(concat
|
|
"Detected Emacs " emacs-version ", but Doom requires 27.1 or newer (30.1 is\n\n"
|
|
"recommended). The current Emacs executable in use is:\n\n " (car command-line-args)
|
|
"\n\nA guide for installing a newer version of Emacs can be found at:\n\n "
|
|
(format "https://docs.doomemacs.org/-/install/%s"
|
|
(cond ((eq system-type 'darwin) "on-macos")
|
|
((memq system-type '(cygwin windows-nt ms-dos)) "on-windows")
|
|
("on-linux")))
|
|
"\n\n"
|
|
(if noninteractive
|
|
(concat "Alternatively, either update your $PATH environment variable to include the\n"
|
|
"path of the desired Emacs executable OR alter the $EMACS environment variable\n"
|
|
"to specify the exact path or command needed to invoke Emacs."
|
|
(when-let ((script (cadr (member "--load" command-line-args)))
|
|
(command (file-name-nondirectory script)))
|
|
(concat " For example:\n\n"
|
|
" $ EMACS=/path/to/valid/emacs " command " ...\n"
|
|
" $ EMACS=\"/Applications/Emacs.app/Contents/MacOS/Emacs\" " command " ...\n"
|
|
" $ EMACS=\"snap run emacs\" " command " ..."))
|
|
"\n\nAborting...")
|
|
(concat "If you believe this error is a mistake, run 'doom doctor' on the command line\n"
|
|
"to diagnose common issues with your config and system.")))))
|
|
nil)
|
|
|
|
;; Doom needs to be synced/rebuilt if either Doom or Emacs has been
|
|
;; up/downgraded. This is because byte-code isn't backwards compatible, and many
|
|
;; packages (including Doom), bake in absolute paths into their caches that need
|
|
;; to be refreshed.
|
|
(let ((old-version (eval-when-compile emacs-major-version)))
|
|
(unless (= emacs-major-version old-version)
|
|
(user-error (concat "Doom was compiled with Emacs %s, but was loaded with %s. Run 'doom sync' to"
|
|
"recompile it.")
|
|
emacs-major-version old-version)))
|
|
|
|
;;; Custom features & global constants
|
|
;; Doom has its own features that its modules, CLI, and user extensions can
|
|
;; announce, and don't belong in `features', so they are stored here, which can
|
|
;; include information about the external system environment.
|
|
(defconst doom-system
|
|
(pcase system-type
|
|
('darwin '(macos bsd))
|
|
((or 'cygwin 'windows-nt 'ms-dos) '(windows))
|
|
((or 'gnu 'gnu/linux) '(linux))
|
|
((or 'gnu/kfreebsd 'berkeley-unix) '(linux bsd))
|
|
('android '(android)))
|
|
"A list of symbols denoting available features in the active Doom profile.")
|
|
|
|
;; Convenience aliases for internal use only (may be removed later).
|
|
(defconst doom--system-windows-p (eq 'windows (car doom-system)))
|
|
(defconst doom--system-macos-p (eq 'macos (car doom-system)))
|
|
(defconst doom--system-linux-p (eq 'linux (car doom-system)))
|
|
|
|
;; Announce WSL if it is detected.
|
|
(when (and doom--system-linux-p
|
|
(if (boundp 'operating-system-release) ; is deprecated since 28.x
|
|
(string-match-p "-[Mm]icrosoft" operating-system-release)
|
|
(getenv-internal "WSLENV")))
|
|
(add-to-list 'doom-system 'wsl 'append))
|
|
|
|
;; `system-type' is esoteric, so I create a pseudo feature as a stable and
|
|
;; consistent alternative, for use with `featurep'.
|
|
(push :system features)
|
|
(put :system 'subfeatures doom-system)
|
|
|
|
;; Emacs needs a more consistent way to detect build features, and the docs
|
|
;; claim `system-configuration-features' is not da way. Some features (that
|
|
;; don't represent packages) can be found in `features' (which `featurep'
|
|
;; consults), but aren't consistent, so I'll impose some consistency:
|
|
(if (bound-and-true-p module-file-suffix)
|
|
(push 'dynamic-modules features))
|
|
(if (fboundp #'json-parse-string)
|
|
(push 'jansson features))
|
|
(if (string-match-p "HARFBUZZ" system-configuration-features) ; no alternative
|
|
(push 'harfbuzz features))
|
|
|
|
;; The `native-compile' feature exists whether or not it is functional (e.g.
|
|
;; libgcc is available or not). This seems silly, as some packages will blindly
|
|
;; use the native-comp API if it's present but non-functional, so let's pretend
|
|
;; it doesn't exist if that's the case.
|
|
(if (featurep 'native-compile)
|
|
(if (not (native-comp-available-p))
|
|
(delq 'native-compile features)))
|
|
|
|
;; DEPRECATED: Remove in v3
|
|
(with-no-warnings
|
|
(defconst IS-MAC doom--system-macos-p)
|
|
(defconst IS-LINUX doom--system-linux-p)
|
|
(defconst IS-WINDOWS doom--system-windows-p)
|
|
(defconst IS-BSD (memq 'bsd doom-system))
|
|
(defconst EMACS28+ (> emacs-major-version 27))
|
|
(defconst EMACS29+ (> emacs-major-version 28))
|
|
(defconst MODULES (featurep 'dynamic-modules))
|
|
(defconst NATIVECOMP (featurep 'native-compile))
|
|
|
|
(make-obsolete-variable 'IS-MAC "Use (featurep :system 'macos) instead" "3.0.0")
|
|
(make-obsolete-variable 'IS-LINUX "Use (featurep :system 'linux) instead" "3.0.0")
|
|
(make-obsolete-variable 'IS-WINDOWS "Use (featurep :system 'windows) instead" "3.0.0")
|
|
(make-obsolete-variable 'IS-BSD "Use (featurep :system 'bsd) instead" "3.0.0")
|
|
(make-obsolete-variable 'EMACS28+ "Use (>= emacs-major-version 28) instead" "3.0.0")
|
|
(make-obsolete-variable 'EMACS29+ "Use (>= emacs-major-version 29) instead" "3.0.0")
|
|
(make-obsolete-variable 'MODULES "Use (featurep 'dynamic-modules) instead" "3.0.0")
|
|
(make-obsolete-variable 'NATIVECOMP "Use (featurep 'native-compile) instead" "3.0.0")
|
|
|
|
(define-obsolete-variable-alias 'doom-private-dir 'doom-user-dir "3.0.0")
|
|
(define-obsolete-variable-alias 'doom-etc-dir 'doom-data-dir "3.0.0"))
|
|
|
|
;; HACK: Silence obnoxious obsoletion warnings about (if|when)-let in >=31.
|
|
;; These warnings are unhelpful to end-users, and many packages use these
|
|
;; macros, so I silence these warnings to spare users the unactionable spam.
|
|
;; Not to mention, Emacs doesn't respect `warning-suppress-types' when it
|
|
;; comes to obsoletion warnings.
|
|
(put 'if-let 'byte-obsolete-info nil)
|
|
(put 'when-let 'byte-obsolete-info nil)
|
|
|
|
|
|
;;; Fix $HOME on Windows
|
|
;; $HOME isn't normally defined on Windows, but many unix tools expect it.
|
|
(when doom--system-windows-p
|
|
(when-let* ((realhome
|
|
(and (null (getenv-internal "HOME"))
|
|
(getenv "USERPROFILE"))))
|
|
(setenv "HOME" realhome)
|
|
(setq abbreviated-home-dir nil)))
|
|
|
|
|
|
;;; Load Doom's stdlib
|
|
(add-to-list 'load-path (file-name-directory load-file-name))
|
|
(require 'doom-compat) ; backport niceties from later versions of Emacs
|
|
(require 'doom-lib)
|
|
|
|
|
|
;;
|
|
;;; Core globals
|
|
|
|
(defgroup doom nil
|
|
"A development framework for Emacs configurations and Emacs Lisp projects."
|
|
:link '(url-link "https://doomemacs.org")
|
|
:group 'emacs)
|
|
|
|
(defconst doom-version "3.0.0-pre"
|
|
"Current version of Doom Emacs core.")
|
|
|
|
;; DEPRECATED: Remove these when the modules are moved out of core.
|
|
(defconst doom-modules-version "25.08.0-pre"
|
|
"Current version of Doom Emacs.")
|
|
|
|
(defvar doom-init-time nil
|
|
"The time it took, in seconds (as a float), for Doom Emacs to start up.")
|
|
|
|
(defconst doom-profile
|
|
(if-let* ((profile (getenv-internal "DOOMPROFILE")))
|
|
(save-match-data
|
|
(if (string-match "^\\([^@]+\\)@\\(.+\\)$" profile)
|
|
(cons (match-string 1 profile)
|
|
(match-string 2 profile))
|
|
(cons profile "0")))
|
|
;; TODO Restore this in 3.0
|
|
;; (cons "_" "0")
|
|
)
|
|
"The active profile as a cons cell (NAME . VERSION).")
|
|
|
|
;;; Data directory variables
|
|
(defvar doom-emacs-dir user-emacs-directory
|
|
"The path to the currently loaded .emacs.d directory. Must end with a slash.")
|
|
|
|
(defconst doom-core-dir (file-name-directory load-file-name)
|
|
"The root directory of Doom's core files. Must end with a slash.")
|
|
|
|
(defvar doom-modules-dir (expand-file-name "modules/" doom-emacs-dir)
|
|
"The root directory for Doom's modules. Must end with a slash.")
|
|
|
|
(defvar doom-user-dir
|
|
(expand-file-name
|
|
(if-let* ((doomdir (getenv-internal "DOOMDIR")))
|
|
(file-name-as-directory doomdir)
|
|
(or (let ((xdgdir
|
|
(file-name-concat
|
|
(or (getenv-internal "XDG_CONFIG_HOME")
|
|
"~/.config")
|
|
"doom/")))
|
|
(if (file-directory-p xdgdir) xdgdir))
|
|
"~/.doom.d/")))
|
|
"Where your private configuration is placed.
|
|
|
|
Defaults to ~/.config/doom, ~/.doom.d or the value of the DOOMDIR envvar;
|
|
whichever is found first. Must end in a slash.")
|
|
|
|
;; DEPRECATED: Will be replaced in v3
|
|
(defvar doom-module-load-path
|
|
(list (file-name-concat doom-user-dir "modules")
|
|
(file-name-concat doom-emacs-dir "modules"))
|
|
"A list of paths where Doom should search for modules.
|
|
|
|
Order determines priority (from highest to lowest).
|
|
|
|
Each entry is a string; an absolute path to the root directory of a module tree.
|
|
In other words, they should contain a two-level nested directory structure,
|
|
where the module's group and name was deduced from the first and second level of
|
|
directories. For example: if $DOOMDIR/modules/ is an entry, a
|
|
$DOOMDIR/modules/lang/ruby/ directory represents a ':lang ruby' module.")
|
|
|
|
;; DEPRECATED: .local will be removed entirely in 3.0
|
|
(defvar doom-local-dir
|
|
(if-let* ((localdir (getenv-internal "DOOMLOCALDIR")))
|
|
(expand-file-name (file-name-as-directory localdir))
|
|
(expand-file-name ".local/" doom-emacs-dir))
|
|
"Root directory for local storage.
|
|
|
|
Use this as a storage location for this system's installation of Doom Emacs.
|
|
|
|
These files should not be shared across systems. By default, it is used by
|
|
`doom-data-dir' and `doom-cache-dir'. Must end with a slash.")
|
|
|
|
(defvar doom-data-dir
|
|
(if doom-profile
|
|
(if doom--system-windows-p
|
|
(expand-file-name "doomemacs/data/" (getenv-internal "LOCALAPPDATA"))
|
|
(expand-file-name "doom/" (or (getenv-internal "XDG_DATA_HOME") "~/.local/share")))
|
|
;; DEPRECATED: .local will be removed entirely in 3.0
|
|
(file-name-concat doom-local-dir "etc/"))
|
|
"Where Doom stores its global data files.
|
|
|
|
Data files contain shared and long-lived data that Doom, Emacs, and their
|
|
packages require to function correctly or at all. Deleting them by hand will
|
|
cause breakage, and require user intervention (e.g. a `doom sync` or `doom env`)
|
|
to restore.
|
|
|
|
Use this for: server binaries, package source, pulled module libraries,
|
|
generated files for profiles, profiles themselves, autoloads/loaddefs, etc.
|
|
|
|
For profile-local data files, use `doom-profile-data-dir' instead.")
|
|
|
|
(defvar doom-cache-dir
|
|
(if doom-profile
|
|
(if doom--system-windows-p
|
|
(expand-file-name "doomemacs/cache/" (getenv-internal "LOCALAPPDATA"))
|
|
(expand-file-name "doom/" (or (getenv-internal "XDG_CACHE_HOME") "~/.cache")))
|
|
;; DEPRECATED: .local will be removed entirely in 3.0
|
|
(file-name-concat doom-local-dir "cache/"))
|
|
"Where Doom stores its global cache files.
|
|
|
|
Cache files represent unessential data that shouldn't be problematic when
|
|
deleted (besides, perhaps, a one-time performance hit), lack portability (and so
|
|
shouldn't be copied to other systems/configs), and are regenerated when needed,
|
|
without user input (e.g. a `doom sync`).
|
|
|
|
Some examples: images/data caches, elisp bytecode, natively compiled elisp,
|
|
session files, ELPA archives, authinfo files, org-persist, etc.
|
|
|
|
For profile-local cache files, use `doom-profile-cache-dir' instead.")
|
|
|
|
(defvar doom-state-dir
|
|
(if doom-profile
|
|
(if doom--system-windows-p
|
|
(expand-file-name "doomemacs/state/" (getenv-internal "LOCALAPPDATA"))
|
|
(expand-file-name "doom/" (or (getenv-internal "XDG_STATE_HOME") "~/.local/state")))
|
|
;; DEPRECATED: .local will be removed entirely in 3.0
|
|
(file-name-concat doom-local-dir "state/"))
|
|
"Where Doom stores its global state files.
|
|
|
|
State files contain unessential, non-portable, but persistent data which, if
|
|
lost won't cause breakage, but may be inconvenient as they cannot be
|
|
automatically regenerated or restored. For example, a recently-opened file list
|
|
is not essential, but losing it means losing this record, and restoring it
|
|
requires revisiting all those files.
|
|
|
|
Use this for: history, logs, user-saved data, autosaves/backup files, known
|
|
projects, recent files, bookmarks.
|
|
|
|
For profile-local state files, use `doom-profile-state-dir' instead.")
|
|
|
|
;;; Profile file/directory variables
|
|
(defvar doom-profile-cache-dir
|
|
(file-name-concat doom-cache-dir (car doom-profile))
|
|
"For profile-local cache files under `doom-cache-dir'.")
|
|
|
|
(defvar doom-profile-data-dir
|
|
(file-name-concat doom-data-dir (car doom-profile))
|
|
"For profile-local data files under `doom-data-dir'.")
|
|
|
|
(defvar doom-profile-state-dir
|
|
(file-name-concat doom-state-dir (car doom-profile))
|
|
"For profile-local state files under `doom-state-dir'.")
|
|
|
|
(defconst doom-profile-dir
|
|
(file-name-concat doom-profile-data-dir "@" (cdr doom-profile))
|
|
"Where generated files for the active profile (for Doom's core) are kept.")
|
|
|
|
;; DEPRECATED: Will be moved to cli/env
|
|
(defconst doom-env-file
|
|
(file-name-concat (if doom-profile
|
|
doom-profile-dir
|
|
doom-local-dir)
|
|
"env")
|
|
"The location of your envvar file, generated by `doom env`.
|
|
|
|
This file contains environment variables scraped from your shell environment,
|
|
which is loaded at startup (if it exists). This is helpful if Emacs can't
|
|
\(easily) be launched from the correct shell session (particularly for MacOS
|
|
users).")
|
|
|
|
;;; Module file variables
|
|
(defvar doom-module-init-file "init.el"
|
|
"The filename for module early initialization config files.
|
|
|
|
Init files are loaded early, just after Doom core, and before modules' config
|
|
files. They are always loaded, even in non-interactive sessions, and before
|
|
`doom-before-modules-init-hook'. Related to `doom-module-config-file'.")
|
|
|
|
(defvar doom-module-config-file "config.el"
|
|
"The filename for module configuration files.
|
|
|
|
Config files are loaded later, and almost always in interactive sessions. These
|
|
run before `doom-after-modules-config-hook' and after `doom-module-init-file'.")
|
|
|
|
(defvar doom-module-packages-file "packages.el"
|
|
"The filename for the package configuration file.
|
|
|
|
Package files are read whenever Doom's package manager wants a manifest of all
|
|
desired packages. They are rarely read in interactive sessions (unless the user
|
|
uses a straight or package.el command directly).")
|
|
|
|
|
|
;;
|
|
;;; Startup optimizations
|
|
|
|
;; Here are Doom's hackiest (and least offensive) startup optimizations. They
|
|
;; exploit implementation details and unintended side-effects in Emacs' startup
|
|
;; process, and will change often between major Emacs releases. However, I
|
|
;; disable them if this is a daemon session (where startup time matters less).
|
|
;;
|
|
;; Most of these have been tested on Linux and on fairly fast machines (with
|
|
;; SSDs), so your mileage may vary depending on hardware and `window-system'.
|
|
(unless (daemonp)
|
|
;; PERF: `file-name-handler-alist' is consulted on each call to `require',
|
|
;; `load', or various file/io functions (like `expand-file-name' or
|
|
;; `file-remote-p'). You get a noteable boost to startup time by unsetting
|
|
;; or simplifying its value.
|
|
(let ((old-value (default-toplevel-value 'file-name-handler-alist)))
|
|
(set-default-toplevel-value
|
|
'file-name-handler-alist
|
|
;; HACK: The elisp libraries bundled with Emacs are either compressed or
|
|
;; not, never both. So if calc-loaddefs.el.gz exists, calc-loaddefs.el
|
|
;; won't, and vice versa. This heuristic is used to guess the state of
|
|
;; all other built-in (or site); if they're compressed, we must leave the
|
|
;; gzip file handler in `file-name-handler-alist' so Emacs knows how to
|
|
;; load them. Otherwise, we can omit it (at least during startup) for a
|
|
;; boost in package load time.
|
|
(if (eval-when-compile
|
|
(locate-file-internal "calc-loaddefs.el" load-path))
|
|
nil
|
|
(list (rassq 'jka-compr-handler old-value))))
|
|
;; Remember it so it can be reset where needed.
|
|
(put 'file-name-handler-alist 'initial-value old-value)
|
|
;; COMPAT: Eventually, Emacs will process any files passed to it via the
|
|
;; command line, and will do so *really* early in the startup process.
|
|
;; These might contain special file paths like TRAMP paths, so restore
|
|
;; `file-name-handler-alist' just for this portion of startup.
|
|
(define-advice command-line-1 (:around (fn args-left) respect-file-handlers)
|
|
(let ((file-name-handler-alist (if args-left old-value file-name-handler-alist)))
|
|
(funcall fn args-left)))
|
|
;; COMPAT: ...but restore `file-name-handler-alist' later, because it is
|
|
;; needed for handling encrypted or compressed files, among other things.
|
|
(add-hook! 'emacs-startup-hook :depth 101
|
|
(defun doom--reset-file-handler-alist-h ()
|
|
(set-default-toplevel-value
|
|
'file-name-handler-alist
|
|
;; Merge instead of overwrite because there may have been changes to
|
|
;; `file-name-handler-alist' since startup we want to preserve.
|
|
(delete-dups (append file-name-handler-alist old-value))))))
|
|
|
|
(unless noninteractive
|
|
;; PERF: Resizing the Emacs frame (to accommodate fonts that are smaller or
|
|
;; larger than the default system font) can impact startup time
|
|
;; dramatically. The larger the delta, the greater the delay. Even trivial
|
|
;; deltas can yield up to a ~1000ms loss, depending also on
|
|
;; `window-system' (PGTK builds seem least affected and NS/MAC the most).
|
|
(setq frame-inhibit-implied-resize t)
|
|
|
|
;; PERF: A fair bit of startup time goes into initializing the splash and
|
|
;; scratch buffers in the typical Emacs session (b/c they activate a
|
|
;; non-trivial major mode, generate the splash buffer, and trigger
|
|
;; premature frame redraws by writing to *Messages*). These hacks prevent
|
|
;; most of this work from happening for some decent savings in startup
|
|
;; time. Our dashboard and `doom/open-scratch-buffer' provide a faster
|
|
;; (and more useful) alternative anyway.
|
|
(setq inhibit-startup-screen t
|
|
inhibit-startup-echo-area-message user-login-name
|
|
initial-major-mode 'fundamental-mode
|
|
initial-scratch-message nil)
|
|
;; PERF,UX: Prevent "For information about GNU Emacs..." line in *Messages*.
|
|
(advice-add #'display-startup-echo-area-message :override #'ignore)
|
|
;; PERF: Suppress the vanilla startup screen completely. We've disabled it
|
|
;; with `inhibit-startup-screen', but it would still initialize anyway.
|
|
;; This involves file IO and/or bitmap work (depending on the frame type)
|
|
;; that we can no-op for a free 50-100ms saving in startup time.
|
|
(advice-add #'display-startup-screen :override #'ignore)
|
|
|
|
(unless initial-window-system
|
|
;; PERF: `tty-run-terminal-initialization' can take 2-3s when starting up
|
|
;; TTY Emacs (non-daemon sessions), depending on your TERM, TERMINFO,
|
|
;; and TERMCAP, but this work isn't very useful on modern systems (the
|
|
;; type I expect Doom's users to be using). The function seems less
|
|
;; expensive if run later in the startup process, so I defer it.
|
|
;; REVIEW: This may no longer be needed in 29+. Needs testing!
|
|
(define-advice tty-run-terminal-initialization (:override (&rest _) defer)
|
|
(advice-remove #'tty-run-terminal-initialization #'tty-run-terminal-initialization@defer)
|
|
(add-hook 'window-setup-hook
|
|
(doom-partial #'tty-run-terminal-initialization
|
|
(selected-frame) nil t))))
|
|
|
|
;; These optimizations are brittle, difficult to debug, and obscure other
|
|
;; issues, so bow out when debug mode is on.
|
|
(unless init-file-debug
|
|
;; PERF: The mode-line procs a couple dozen times during startup, before
|
|
;; the user even sees the first mode-line. This is normally fast, but we
|
|
;; can't predict what the user (or packages) will put into the
|
|
;; mode-line. Also, mode-line packages have a bad habit of throwing
|
|
;; performance to the wind, so best we just disable the mode-line until
|
|
;; we can see one.
|
|
(put 'mode-line-format 'initial-value (default-toplevel-value 'mode-line-format))
|
|
(setq-default mode-line-format nil)
|
|
(dolist (buf (buffer-list))
|
|
(with-current-buffer buf (setq mode-line-format nil)))
|
|
;; PERF,UX: Premature redisplays/redraws can substantially affect startup
|
|
;; times and/or flash a white/unstyled Emacs frame during startup, so I
|
|
;; try real hard to suppress them until we're sure the session is ready.
|
|
(setq-default inhibit-redisplay t
|
|
inhibit-message t)
|
|
;; COMPAT: If the above vars aren't reset, Emacs could appear frozen or
|
|
;; garbled after startup (or in case of an startup error).
|
|
(defun doom--reset-inhibited-vars-h ()
|
|
(setq-default inhibit-redisplay nil
|
|
inhibit-message nil)
|
|
(remove-hook 'post-command-hook #'doom--reset-inhibited-vars-h))
|
|
(add-hook 'post-command-hook #'doom--reset-inhibited-vars-h -100))
|
|
|
|
;; PERF: Doom disables the UI elements by default, so that there's less for
|
|
;; the frame to initialize. However, `tool-bar-setup' is still called and
|
|
;; it does some non-trivial work to set up the toolbar before we can
|
|
;; disable it. To side-step this work, I disable the function and call it
|
|
;; later (see `startup--load-user-init-file@undo-hacks').
|
|
(advice-add #'tool-bar-setup :override #'ignore)
|
|
|
|
;; PERF,UX: site-lisp files are often obnoxiously noisy (emitting output
|
|
;; that isn't useful to end-users, like load messages, deprecation
|
|
;; notices, and linter warnings). Displaying these in the minibuffer
|
|
;; causes unnecessary redraws at startup which can impact startup time
|
|
;; drastically and cause flashes of white. It also pollutes the logs. I
|
|
;; suppress it here and load it myself, later, in a more controlled way
|
|
;; (see `doom-initialize').
|
|
(put 'site-run-file 'initial-value site-run-file)
|
|
(setq site-run-file nil)
|
|
|
|
(define-advice startup--load-user-init-file (:around (fn &rest args) undo-hacks 95)
|
|
"Undo Doom's startup optimizations to prep for the user's session."
|
|
(unwind-protect (apply fn args)
|
|
;; Now it's safe to be verbose.
|
|
(setq-default inhibit-message nil)
|
|
;; COMPAT: Once startup is sufficiently complete, undo our earlier
|
|
;; optimizations to reduce the scope of potential edge cases.
|
|
(advice-remove #'tool-bar-setup #'ignore)
|
|
(add-transient-hook! 'tool-bar-mode (tool-bar-setup))
|
|
(unless (default-toplevel-value 'mode-line-format)
|
|
(setq-default mode-line-format (get 'mode-line-format 'initial-value)))))
|
|
|
|
;; PERF: Unset a non-trivial list of command line options that aren't
|
|
;; relevant to this session, but `command-line-1' still processes.
|
|
(unless doom--system-macos-p
|
|
(setq command-line-ns-option-alist nil))
|
|
(unless (memq initial-window-system '(x pgtk))
|
|
(setq command-line-x-option-alist nil))))
|
|
|
|
|
|
;;
|
|
;;; Reasonable, global defaults
|
|
|
|
;;; CLI settings
|
|
(when noninteractive
|
|
;; Don't generate superfluous files when writing temp buffers.
|
|
(setq make-backup-files nil)
|
|
;; Stop user config from interfering with elisp shell scripts.
|
|
(setq enable-dir-local-variables nil)
|
|
;; Reduce ambiguity, embrace specificity, enjoy predictability.
|
|
(setq case-fold-search nil)
|
|
;; Don't clog the user's trash with our CLI refuse.
|
|
(setq delete-by-moving-to-trash nil))
|
|
|
|
;;; Don't litter `doom-emacs-dir'/$HOME
|
|
;; HACK: I change `user-emacs-directory' because many packages (even built-in
|
|
;; ones) abuse it to build paths for storage/cache files (instead of correctly
|
|
;; using `locate-user-emacs-file'). This change ensures that said data files
|
|
;; are never saved to the root of your emacs directory *and* saves us the
|
|
;; trouble of setting a million directory/file variables.
|
|
(setq user-emacs-directory doom-profile-cache-dir)
|
|
|
|
;; ...However, this may surprise packages (and users) that read
|
|
;; `user-emacs-directory' expecting to find the location of your Emacs config,
|
|
;; such as server.el!
|
|
(setq server-auth-dir (file-name-concat doom-emacs-dir "server/"))
|
|
|
|
;; Packages with file/dir settings that don't use `user-emacs-directory' or
|
|
;; `locate-user-emacs-file' to initialize will need to set explicitly, to stop
|
|
;; them from littering in ~/.emacs.d/.
|
|
(setq desktop-dirname (file-name-concat doom-profile-state-dir "desktop")
|
|
pcache-directory (file-name-concat doom-profile-cache-dir "pcache/"))
|
|
|
|
;; Allow the user to store custom.el-saved settings and themes in their Doom
|
|
;; config (e.g. ~/.doom.d/).
|
|
(setq custom-file (file-name-concat doom-user-dir "custom.el"))
|
|
|
|
(define-advice en/disable-command (:around (fn &rest args) write-to-data-dir)
|
|
"Save safe-local-variables to `custom-file' instead of `user-init-file'.
|
|
|
|
Otherwise, `en/disable-command' (in novice.el.gz) is hardcoded to write them to
|
|
`user-init-file')."
|
|
(let ((user-init-file custom-file))
|
|
(apply fn args)))
|
|
|
|
;;; Native compilation support (see http://akrl.sdf.org/gccemacs.html)
|
|
(when (boundp 'native-comp-eln-load-path)
|
|
;; Don't store eln files in ~/.emacs.d/eln-cache (where they can easily be
|
|
;; deleted by 'doom upgrade').
|
|
;; REVIEW: Advise `startup-redirect-eln-cache' when 28 support is dropped.
|
|
(add-to-list 'native-comp-eln-load-path (expand-file-name "eln/" doom-profile-cache-dir))
|
|
|
|
;; UX: Suppress compiler warnings and don't inundate users with their popups.
|
|
;; They are rarely more than warnings, so are safe to ignore.
|
|
(setq native-comp-async-report-warnings-errors init-file-debug
|
|
native-comp-warning-on-missing-source init-file-debug)
|
|
|
|
;; HACK: `native-comp-deferred-compilation-deny-list' is replaced in later
|
|
;; versions of Emacs 29, and with no deprecation warning. I alias them to
|
|
;; ensure backwards compatibility for packages downstream that may have not
|
|
;; caught up yet. I avoid marking it obsolete because obsolete warnings are
|
|
;; unimportant to end-users. It's the package devs that should be informed.
|
|
(unless (boundp 'native-comp-deferred-compilation-deny-list)
|
|
(defvaralias 'native-comp-deferred-compilation-deny-list 'native-comp-jit-compilation-deny-list))
|
|
|
|
;; UX: By default, native-comp uses 100% of half your cores. If you're
|
|
;; expecting this this should be no issue, but the sudden (and silent) spike
|
|
;; of CPU and memory utilization can alarm folks, overheat laptops, or
|
|
;; overwhelm less performant systems.
|
|
(define-advice comp-effective-async-max-jobs (:before (&rest _) set-default-cpus)
|
|
"Default to 1/4 of cores in interactive sessions and all of them otherwise."
|
|
(and (null comp-num-cpus)
|
|
(zerop native-comp-async-jobs-number)
|
|
(setq comp-num-cpus
|
|
(max 1 (/ (num-processors) (if noninteractive 1 4))))))
|
|
|
|
(define-advice comp-run-async-workers (:around (fn &rest args) dont-litter-tmpdir)
|
|
"Normally, native-comp writes a ton to /tmp. This advice forces it to write
|
|
to `doom-profile-cache-dir' instead, so it can be safely cleaned up as part of
|
|
'doom sync' or 'doom gc'."
|
|
(let ((temporary-file-directory (expand-file-name "comp/" doom-profile-cache-dir)))
|
|
(make-directory temporary-file-directory t)
|
|
(apply fn args)))
|
|
|
|
(with-eval-after-load 'comp
|
|
;; HACK: On Emacs 30.0.92, `native-comp-jit-compilation-deny-list' was moved
|
|
;; to comp-run. See emacsmirror/emacs@e6a955d24268. Doom forces straight
|
|
;; to consult this variable when building packages.
|
|
(require 'comp-run nil t)
|
|
;; HACK: Disable native-compilation for some troublesome packages
|
|
(mapc (doom-partial #'add-to-list 'native-comp-deferred-compilation-deny-list)
|
|
(list "/seq-tests\\.el\\'"
|
|
"/emacs-jupyter.*\\.el\\'"
|
|
"/evil-collection-vterm\\.el\\'"
|
|
"/vterm\\.el\\'"
|
|
"/with-editor\\.el\\'"))))
|
|
|
|
|
|
;;; Reduce unnecessary/unactionable warnings/logs
|
|
;; Disable warnings from the legacy advice API. They aren't actionable or
|
|
;; useful, and often come from third party packages.
|
|
(setq ad-redefinition-action 'accept)
|
|
|
|
;; Ignore warnings about "existing variables being aliased". Otherwise the user
|
|
;; gets very intrusive popup warnings about our (intentional) uses of
|
|
;; defvaralias, which are done because ensuring aliases are created before
|
|
;; packages are loaded is an unneeded and unhelpful maintenance burden. Emacs
|
|
;; still aliases them fine regardless.
|
|
(setq warning-suppress-types '((defvaralias) (lexical-binding)))
|
|
|
|
;; As some point in 31+, Emacs began spamming the user with warnings about
|
|
;; missing `lexical-binding' cookies in elisp files that you are unlikely to
|
|
;; have any direct control over (e.g. package files, data lisp files, and elisp
|
|
;; shell scripts). This shuts it up.
|
|
(setq warning-inhibit-types '((files missing-lexbind-cookie)))
|
|
|
|
;; Reduce debug output unless we've asked for it.
|
|
(setq debug-on-error init-file-debug
|
|
jka-compr-verbose init-file-debug)
|
|
|
|
;;; Stricter security defaults
|
|
;; Emacs is essentially one huge security vulnerability, what with all the
|
|
;; dependencies it pulls in from all corners of the globe. Let's try to be a
|
|
;; *little* more discerning.
|
|
(setq gnutls-verify-error noninteractive
|
|
gnutls-algorithm-priority
|
|
(when (boundp 'libgnutls-version)
|
|
(concat "SECURE128:+SECURE192:-VERS-ALL"
|
|
(if (and (not doom--system-windows-p)
|
|
(>= libgnutls-version 30605))
|
|
":+VERS-TLS1.3")
|
|
":+VERS-TLS1.2"))
|
|
;; `gnutls-min-prime-bits' is set based on recommendations from
|
|
;; https://www.keylength.com/en/4/
|
|
gnutls-min-prime-bits 3072
|
|
tls-checktrust gnutls-verify-error
|
|
;; Emacs is built with gnutls.el by default, so `tls-program' won't
|
|
;; typically be used, but in the odd case that it does, we ensure a more
|
|
;; secure default for it (falling back to `openssl' if absolutely
|
|
;; necessary). See https://redd.it/8sykl1 for details.
|
|
tls-program '("openssl s_client -connect %h:%p -CAfile %t -nbio -no_ssl3 -no_tls1 -no_tls1_1 -ign_eof"
|
|
"gnutls-cli -p %p --dh-bits=3072 --ocsp --x509cafile=%t \
|
|
--strict-tofu --priority='SECURE192:+SECURE128:-VERS-ALL:+VERS-TLS1.2:+VERS-TLS1.3' %h"
|
|
;; compatibility fallbacks
|
|
"gnutls-cli -p %p %h"))
|
|
|
|
|
|
;;; Package managers
|
|
;; Since Emacs 27, package initialization occurs before `user-init-file' is
|
|
;; loaded, but after `early-init-file'. Doom handles package initialization, so
|
|
;; we must prevent Emacs from doing it again.
|
|
(setq package-enable-at-startup nil)
|
|
|
|
;; Ensure that, if the user does want package.el, it is configured correctly.
|
|
;; You really shouldn't be using it, though...
|
|
(with-eval-after-load 'package
|
|
(setq package-user-dir (file-name-concat doom-local-dir "elpa/")
|
|
package-gnupghome-dir (expand-file-name "gpg" package-user-dir))
|
|
(let ((s (if gnutls-verify-error "s" "")))
|
|
(cl-callf2 append
|
|
;; I omit Marmalade because its packages are manually submitted rather
|
|
;; than pulled, and so often out of date.
|
|
`(("melpa" . ,(format "http%s://melpa.org/packages/" s))
|
|
("org" . ,(format "http%s://orgmode.org/elpa/" s)))
|
|
package-archives))
|
|
|
|
;; Refresh package.el the first time you call `package-install', so it's still
|
|
;; trivially usable. Remember to run 'doom sync' to purge them; they can
|
|
;; conflict with packages installed via straight!
|
|
(add-transient-hook! 'package-install (package-refresh-contents)))
|
|
|
|
;; DEPRECATED: Interactive sessions won't be able to interact with Straight (or
|
|
;; Elpaca) in the future, so this is temporary.
|
|
(with-eval-after-load 'straight
|
|
(require 'doom-straight)
|
|
(doom-initialize-packages))
|
|
|
|
|
|
;;
|
|
;;; Custom hooks
|
|
|
|
(defcustom doom-before-init-hook ()
|
|
"A hook run after Doom's core has initialized; before user configuration.
|
|
|
|
This is triggered right before $DOOMDIR/init.el is loaded, in the context of
|
|
early-init.el. Use this for configuration at the latest opportunity before the
|
|
session becomes unpredictably complicated by user config, packages, etc. This
|
|
runs in both interactive and non-interactive contexts, so guard hooks
|
|
appropriately against `noninteractive' or the `cli' context (see
|
|
`doom-context').
|
|
|
|
In contrast, `before-init-hook' is run just after $DOOMDIR/init.el is loaded,
|
|
but long before your modules and $DOOMDIR/config.el are loaded."
|
|
:group 'doom
|
|
:type 'hook)
|
|
|
|
(defcustom doom-after-init-hook ()
|
|
"A hook run once Doom's core and modules, and the user's config are loaded.
|
|
|
|
This triggers at the absolute latest point in the eager startup process, and
|
|
runs in both interactive and non-interactive sessions, so guard hooks
|
|
appropriately against `noninteractive' or the `cli' context."
|
|
:group 'doom
|
|
:type 'hook)
|
|
|
|
(defcustom doom-before-modules-init-hook nil
|
|
"Hooks run before module init.el files are loaded."
|
|
:group 'doom
|
|
:type 'hook)
|
|
|
|
(defcustom doom-after-modules-init-hook nil
|
|
"Hooks run after module init.el files are loaded."
|
|
:group 'doom
|
|
:type 'hook)
|
|
|
|
(defcustom doom-before-modules-config-hook nil
|
|
"Hooks run before module config.el files are loaded."
|
|
:group 'doom
|
|
:type 'hook)
|
|
|
|
(defcustom doom-after-modules-config-hook nil
|
|
"Hooks run after module config.el files are loaded (but before the user's)."
|
|
:group 'doom
|
|
:type 'hook)
|
|
|
|
|
|
;;
|
|
;;; Initializers
|
|
|
|
(defun doom-initialize (&optional interactive?)
|
|
"Bootstrap the Doom session ahead."
|
|
(when (doom-context-push 'startup)
|
|
(when (daemonp)
|
|
(message "Starting Doom Emacs in daemon mode...")
|
|
(unless doom-inhibit-log
|
|
(add-hook! 'doom-after-init-hook :depth 106
|
|
(unless doom-inhibit-log
|
|
(setq doom-inhibit-log (not (or noninteractive init-file-debug))))
|
|
(message "Disabling verbose mode. Have fun!"))
|
|
(add-hook! 'kill-emacs-hook :depth 110
|
|
(message "Killing Emacs. Sayonara!"))))
|
|
|
|
(if interactive?
|
|
(when (doom-context-push 'emacs)
|
|
(add-hook 'doom-after-init-hook #'doom-load-packages-incrementally-h 100)
|
|
(add-hook 'doom-after-init-hook #'doom-display-benchmark-h 110)
|
|
(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))
|
|
|
|
;; If the user's already opened something (e.g. with command-line
|
|
;; arguments), then we should assume nothing about the user's
|
|
;; intentions and simply treat this session as fully initialized.
|
|
(add-hook! 'doom-after-init-hook :depth 100
|
|
(defun doom-run-first-hooks-if-files-open-h ()
|
|
(when file-name-history
|
|
(doom-run-hooks 'doom-first-file-hook 'doom-first-buffer-hook))))
|
|
|
|
;; These fire `MAJOR-MODE-local-vars-hook' hooks, which is a Doomism.
|
|
;; See the `MODE-local-vars-hook' section above.
|
|
(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)
|
|
|
|
;; This is the absolute latest a hook can run in Emacs' startup
|
|
;; process.
|
|
(advice-add #'command-line-1 :after #'doom-finalize)
|
|
|
|
(require 'doom-start)
|
|
(let ((init-file (doom-profile-init-file doom-profile)))
|
|
(or (doom-load init-file t)
|
|
(signal 'doom-nosync-error (list init-file)))))
|
|
|
|
(when (doom-context-push 'cli)
|
|
;; REVIEW: Remove later. The endpoints should be responsibile for
|
|
;; ensuring they exist. For now, they exist to quell file errors.
|
|
(with-file-modes #o700
|
|
(mapc (doom-rpartial #'make-directory 'parents)
|
|
(list doom-local-dir
|
|
doom-data-dir
|
|
doom-cache-dir
|
|
doom-state-dir)))
|
|
|
|
(doom-require 'doom-lib 'debug)
|
|
(if init-file-debug (doom-debug-mode +1))
|
|
|
|
;; Then load the rest of Doom's libs eagerly, since autoloads may not
|
|
;; be generated/loaded yet.
|
|
(require 'seq)
|
|
(require 'map)
|
|
(mapc (doom-partial #'doom-require 'doom-lib)
|
|
'(process
|
|
system
|
|
git
|
|
plist
|
|
files
|
|
print
|
|
autoloads
|
|
profiles
|
|
modules
|
|
packages))
|
|
|
|
;; Ensure the CLI framework is ready.
|
|
(require 'doom-cli)
|
|
(add-hook 'doom-cli-initialize-hook #'doom-finalize)))
|
|
|
|
;; HACK: I suppress loading of site files here to load them manually later.
|
|
;; Why? To suppress the otherwise unavoidable output they commonly produce
|
|
;; (like deprecation notices, file-loaded messages, and linter warnings).
|
|
;; This output pollutes Emacs' log and the output of doom's CLI (or
|
|
;; scripts derived from it) with potentially confusing or alarming -- but
|
|
;; always unimportant and rarely actionable -- information to the user. To
|
|
;; see that output, turn on debug mode!
|
|
(let ((site-loader
|
|
(lambda ()
|
|
(quiet!!
|
|
(unless interactive?
|
|
(require 'cl nil t)) ; "Package cl is deprecated"
|
|
(unless site-run-file
|
|
(when-let* ((site-file (get 'site-run-file 'initial-value)))
|
|
(let ((inhibit-startup-screen inhibit-startup-screen))
|
|
(setq site-run-file site-file)
|
|
(load site-run-file t))))))))
|
|
(if interactive?
|
|
(define-advice startup--load-user-init-file (:before (&rest _) load-site-files 100)
|
|
(funcall site-loader))
|
|
(funcall site-loader)))
|
|
|
|
;; A last ditch opportunity to undo hacks or do extra configuration before
|
|
;; the session is complicated by user config and packages.
|
|
(doom-run-hooks 'doom-before-init-hook)
|
|
|
|
;; HACK: Ensure OS checks are as fast as possible (given their ubiquity).
|
|
(setq features (cons :system (delq :system features)))
|
|
|
|
;; Remember these variables' initial values, so they can be safely reset
|
|
;; later (e.g. by `doom/reload'), or compared against for change heuristics.
|
|
(dolist (var '(exec-path load-path process-environment))
|
|
(put var 'initial-value (default-toplevel-value var)))
|
|
|
|
t))
|
|
|
|
(defun doom-finalize (&rest _)
|
|
"Finalize the current Doom session, marking the end of its startup process.
|
|
|
|
Triggers `doom-after-init-hook' and sets `doom-init-time.'"
|
|
(when (doom-context-pop 'startup)
|
|
(setq doom-init-time (float-time (time-subtract (current-time) before-init-time)))
|
|
(doom-run-hooks 'doom-after-init-hook)
|
|
|
|
;; If `gc-cons-threshold' hasn't been reset at this point, we reset it by
|
|
;; force (without overwriting `gcmh' or the user's config). If this isn't
|
|
;; done, this session will be prone to freezing and crashes. This also
|
|
;; handles the case where the user has `gcmh' disabled.
|
|
(when (eq (default-value 'gc-cons-threshold) most-positive-fixnum)
|
|
(setq-default gc-cons-threshold (* 16 1024 1024)))
|
|
t))
|
|
|
|
(provide 'doom)
|
|
;;; doom.el ends here
|