From 0741c8851a33c0a9d4021eaa44c28afcc10d52a3 Mon Sep 17 00:00:00 2001 From: Henrik Lissner Date: Mon, 11 Jun 2018 23:18:15 +0200 Subject: [PATCH] Split core-packages into two (packages & modules) + Move doom-initialize et co into core.el + Lazy load core-packages + load! has been moved into core-lib + Added FILE! and DIR! macros + Fix package! not returning correct value when package is disabled + Remove :disabled support for def-package-hook! officially --- core/autoload/packages.el | 16 + core/core-lib.el | 42 ++- core/core-modules.el | 384 ++++++++++++++++++++++ core/core-packages.el | 652 ++------------------------------------ core/core.el | 241 ++++++++++++-- 5 files changed, 681 insertions(+), 654 deletions(-) create mode 100644 core/core-modules.el diff --git a/core/autoload/packages.el b/core/autoload/packages.el index 0ec4632d1..c8856ab44 100644 --- a/core/autoload/packages.el +++ b/core/autoload/packages.el @@ -550,3 +550,19 @@ calls." (print! (bold (green "Finished!"))) (if success (doom-delete-autoloads-file doom-package-autoload-file)) success))))) + + +;; +;; Make package.el cooperate with Doom +;; + +;; Updates QUELPA after deleting a package +;;;###autoload +(advice-add #'package-delete :after #'doom*package-delete) + +;; Replace with Doom variants +;;;###autoload +(advice-add #'package-autoremove :override #'doom//packages-autoremove) +;;;###autoload +(advice-add #'package-install-selected-packages :override #'doom//packages-install) + diff --git a/core/core-lib.el b/core/core-lib.el index 86aa2e9d4..918c2fc49 100644 --- a/core/core-lib.el +++ b/core/core-lib.el @@ -65,6 +65,16 @@ This is used by `associate!', `file-exists-p!' and `project-file-exists-p!'." collect hook else collect (intern (format "%s-hook" (symbol-name hook))))) +(defun doom--assert-stage-p (stage macro) + (cl-assert (eq stage doom--stage) + nil + "Found %s call in non-%s.el file (%s)" + macro (symbol-name stage) + (let ((path (FILE!))) + (if (file-in-directory-p path doom-emacs-dir) + (file-relative-name path doom-emacs-dir) + (abbreviate-file-name path))))) + ;; ;; Functions @@ -178,6 +188,18 @@ MATCH is a string regexp. Only entries that match it will be included." ;; Macros ;; +(defmacro FILE! () + "TODO" + `(cond ((bound-and-true-p byte-compile-current-file)) + ((stringp (car-safe current-load-list)) (car current-load-list)) + (load-file-name) + (buffer-file-name))) + +(defmacro DIR! () + "TODO" + `(let ((file (FILE!))) + (and file (file-name-directory file)))) + (defmacro λ! (&rest body) "A shortcut for inline interactive lambdas." (declare (doc-string 1)) @@ -190,7 +212,7 @@ MATCH is a string regexp. Only entries that match it will be included." compilation. This will no-op on features that have been disabled by the user." (declare (indent defun) (debug t)) (unless (and (symbolp targets) - (memq targets doom-disabled-packages)) + (memq targets (bound-and-true-p doom-disabled-packages))) (list (if (or (not (bound-and-true-p byte-compile-current-file)) (dolist (next (doom-enlist targets)) (if (symbolp next) @@ -422,5 +444,23 @@ KEY is a key string or vector. It is *not* piped through `kbd'." (push `(define-key map ,key ,def) forms))) (nreverse forms))))) +(defmacro load! (filename &optional path noerror) + "Load a file relative to the current executing file (`load-file-name'). + +FILENAME is either a file path string or a form that should evaluate to such a +string at run time. PATH is where to look for the file (a string representing a +directory path). If omitted, the lookup is relative to either `load-file-name', +`byte-compile-current-file' or `buffer-file-name' (checked in that order). + +If NOERROR is non-nil, don't throw an error if the file doesn't exist." + (unless path + (setq path (or (DIR!) + (error "Could not detect path to look for '%s' in" + filename)))) + `(load ,(if path + `(expand-file-name ,filename ,path) + filename) + ,noerror ,(not doom-debug-mode))) + (provide 'core-lib) ;;; core-lib.el ends here diff --git a/core/core-modules.el b/core/core-modules.el new file mode 100644 index 000000000..0d67b3e23 --- /dev/null +++ b/core/core-modules.el @@ -0,0 +1,384 @@ +;;; core-modules.el --- module & package management system -*- lexical-binding: t; -*- + +(defvar doom-init-modules-p nil + "Non-nil if `doom-initialize-modules' has run.") + +(defvar doom-modules () + "A hash table of enabled modules. Set by `doom-initialize-modules'.") + +(defvar doom-modules-dirs + (list (expand-file-name "modules/" doom-private-dir) doom-modules-dir) + "A list of module root directories. Order determines priority.") + +(defvar doom--current-module nil) + + +;; +;; Bootstrap API +;; + +(defun doom-initialize-modules (&optional force-p) + "Loads the init.el in `doom-private-dir' and sets up hooks for a healthy +session of Dooming. Will noop if used more than once, unless FORCE-P is +non-nil." + (when (or force-p (not doom-init-modules-p)) + ;; Set `doom-init-modules-p' early, so `doom-pre-init-hook' won't infinitely + ;; recurse by accident if any of them need `doom-initialize-modules'. + (setq doom-init-modules-p t) + (when doom-private-dir + (load (expand-file-name "init" doom-private-dir) + 'noerror 'nomessage)))) + +(defun doom-initialize-autoloads (file) + "Tries to load FILE (an autoloads file). Return t on success, nil otherwise." + (condition-case-unless-debug e + (load (file-name-sans-extension file) 'noerror 'nomessage) + ('error + (message "Autoload error: %s -> %s" + (car e) (error-message-string e))))) + + +;; +;; Module API +;; + +(defun doom-module-p (category module) + "Returns t if CATEGORY MODULE is enabled (ie. present in `doom-modules')." + (and (hash-table-p doom-modules) + (gethash (cons category module) doom-modules) + t)) + +(defun doom-module-get (category module &optional property) + "Returns the plist for CATEGORY MODULE. Gets PROPERTY, specifically, if set." + (when-let* ((plist (gethash (cons category module) doom-modules))) + (if property + (plist-get plist property) + plist))) + +(defun doom-module-put (category module property value &rest rest) + "Set a PROPERTY for CATEGORY MODULE to VALUE. PLIST should be additional pairs +of PROPERTY and VALUEs." + (when-let* ((plist (doom-module-get category module))) + (plist-put plist property value) + (when rest + (when (cl-oddp (length rest)) + (signal 'wrong-number-of-arguments (list (length rest)))) + (while rest + (plist-put rest (pop rest) (pop rest)))) + (puthash (cons category module) plist doom-modules))) + +(defun doom-module-set (category module &rest plist) + "Enables a module by adding it to `doom-modules'. + +CATEGORY is a keyword, module is a symbol, PLIST is a plist that accepts the +following properties: + + :flags [SYMBOL LIST] list of enabled category flags + :path [STRING] path to category root directory + +Example: + (doom-module-set :lang 'haskell :flags '(+intero))" + (when plist + (let ((old-plist (doom-module-get category module))) + (unless (plist-member plist :flags) + (plist-put plist :flags (plist-get old-plist :flags))) + (unless (plist-member plist :path) + (plist-put plist :path (or (plist-get old-plist :path) + (doom-module-locate-path category module)))))) + (let ((key (cons category module))) + (puthash key plist doom-modules))) + +(defun doom-module-path (category module &optional file) + "Like `expand-file-name', but expands FILE relative to CATEGORY (keywordp) and +MODULE (symbol). + +If the category isn't enabled this will always return nil. For finding disabled +modules use `doom-module-locate-path'." + (let ((path (doom-module-get category module :path))) + (if file (expand-file-name file path) + path))) + +(defun doom-module-locate-path (category &optional module file) + "Searches `doom-modules-dirs' to find the path to a module. + +CATEGORY is a keyword (e.g. :lang) and MODULE is a symbol (e.g. 'python). FILE +is a string that will be appended to the resulting path. If no path exists, this +returns nil, otherwise an absolute path. + +This doesn't require modules to be enabled. For enabled modules us +`doom-module-path'." + (when (keywordp category) + (setq category (substring (symbol-name category) 1))) + (when (and module (symbolp module)) + (setq module (symbol-name module))) + (cl-loop for default-directory in doom-modules-dirs + for path = (concat category "/" module "/" file) + if (file-exists-p path) + return (expand-file-name path))) + +(defun doom-module-from-path (&optional path) + "Returns a cons cell (CATEGORY . MODULE) derived from PATH (a file path)." + (or doom--current-module + (when path + (save-match-data + (setq path (file-truename path)) + (when (string-match "/modules/\\([^/]+\\)/\\([^/]+\\)/.*$" path) + (when-let* ((module (match-string 1 path)) + (submodule (match-string 2 path))) + (cons (intern (concat ":" module)) + (intern submodule)))))))) + +(defun doom-module-load-path () + "Return a list of absolute file paths to activated modules." + (append (cl-loop for plist being the hash-values of (doom-modules) + collect (plist-get plist :path)) + (list doom-private-dir))) + +(defun doom-modules (&optional refresh-p) + "Minimally initialize `doom-modules' (a hash table) and return it." + (or (unless refresh-p doom-modules) + (let ((noninteractive t) + doom-init-modules-p) + (message "Initializing modules") + (doom-initialize-modules t) + doom-modules))) + + +;; +;; Use-package modifications +;; + +(autoload 'use-package "use-package-core" nil nil t) + +;; Adds the :after-call custom keyword to `use-package' (and consequently, +;; `def-package!'). :after-call takes a symbol or list of symbols. These symbols +;; can be functions to hook variables. +;; +;; (use-package X :after-call find-file-hook) +;; +;; This will load X on the first invokation of `find-file-hook' (then it will +;; remove itself from the hook/function). +(defvar doom--deferred-packages-alist ()) +(after! use-package-core + (add-to-list 'use-package-deferring-keywords :after-call nil #'eq) + + (setq use-package-keywords + (use-package-list-insert :after-call use-package-keywords :after)) + + (defalias 'use-package-normalize/:after-call + 'use-package-normalize-symlist) + + (defun use-package-handler/:after-call (name-symbol _keyword hooks rest state) + (let ((fn (intern (format "doom|transient-hook--load-%s" name-symbol))) + (hooks (delete-dups hooks))) + (if (plist-get state :demand) + (use-package-process-keywords name rest state) + (use-package-concat + `((fset ',fn + (lambda (&rest _) + (require ',name-symbol) + (dolist (hook (cdr (assq ',name-symbol doom--deferred-packages-alist))) + (if (functionp hook) + (advice-remove hook #',fn) + (remove-hook hook #',fn))) + (map-delete doom--deferred-packages-alist ',name-symbol) + (fmakunbound ',fn)))) + (cl-mapcan (lambda (hook) + (if (functionp hook) + `((advice-add #',hook :before #',fn)) + `((add-hook ',hook #',fn)))) + hooks) + `((map-put doom--deferred-packages-alist + ',name-symbol + '(,@hooks ,@(cdr (assq name-symbol doom--deferred-packages-alist))))) + (use-package-process-keywords name rest state)))))) + + +;; +;; Module config macros +;; + +(defmacro doom! (&rest modules) + "Bootstraps DOOM Emacs and its modules. + +The bootstrap process involves making sure the essential directories exist, core +packages are installed, `doom-autoload-file' is loaded, `doom-packages-file' +cache exists (and is loaded) and, finally, loads your private init.el (which +should contain your `doom!' block). + +If the cache exists, much of this function isn't run, which substantially +reduces startup time. + +The overall load order of Doom is as follows: + + ~/.emacs.d/init.el + ~/.emacs.d/core/core.el + `doom-pre-init-hook' + ~/.doom.d/init.el + Module init.el files + `doom-init-hook' + Module config.el files + ~/.doom.d/config.el + `after-init-hook' + `emacs-startup-hook' + `doom-post-init-hook' (at end of `emacs-startup-hook') + +Module load order is determined by your `doom!' block. See `doom-modules-dirs' +for a list of all recognized module trees. Order defines precedence (from most +to least)." + (let ((doom-modules + (make-hash-table :test #'equal + :size (if modules (length modules) 100) + :rehash-threshold 1.0)) + category + init-forms config-forms) + (dolist (m modules) + (cond ((keywordp m) (setq category m)) + ((not category) (error "No module category specified for %s" m)) + ((let* ((module (if (listp m) (car m) m)) + (flags (if (listp m) (cdr m))) + (path (doom-module-locate-path category module))) + (if (not path) + (message "Couldn't find the %s %s module" category module) + (let ((key (cons category module))) + (doom-module-set category module :flags flags :path path) + (push `(let ((doom--current-module ',key)) + (load! "init" ,path t)) + init-forms) + (push `(let ((doom--current-module ',key)) + (load! "config" ,path t)) + config-forms))))))) + `(let (file-name-handler-alist) + (setq doom-modules ',doom-modules) + ,@(nreverse init-forms) + (run-hooks 'doom-init-hook) + (unless noninteractive + (let ((doom--stage 'config)) + ,@(nreverse config-forms) + (when doom-private-dir + (load ,(expand-file-name "config" doom-private-dir) + t (not doom-debug-mode)))))))) + +(defmacro def-package! (name &rest plist) + "A thin wrapper around `use-package'." + ;; Ignore package if NAME is in `doom-disabled-packages' + (when (and (memq name (bound-and-true-p doom-disabled-packages)) + (not (memq :disabled plist))) + (setq plist `(:disabled t ,@plist))) + ;; If byte-compiling, ignore this package if it doesn't meet the condition. + ;; This avoids false-positive load errors. + (unless (and (bound-and-true-p byte-compile-current-file) + (or (and (plist-member plist :if) (not (eval (plist-get plist :if) t))) + (and (plist-member plist :when) (not (eval (plist-get plist :when) t))) + (and (plist-member plist :unless) (eval (plist-get plist :unless) t)))) + `(use-package ,name ,@plist))) + +(defmacro def-package-hook! (package when &rest body) + "Reconfigures a package's `def-package!' block. + +Only use this macro in a module's init.el file. + +Under the hood, this uses use-package's `use-package-inject-hooks'. + +PACKAGE is a symbol; the package's name. +WHEN should be one of the following: + :pre-init :post-init :pre-config :post-config + +WARNING: If :pre-init or :pre-config hooks return nil, the original +`def-package!''s :init/:config block (respectively) is overwritten, so remember +to have them return non-nil (or exploit that to overwrite Doom's config)." + (declare (indent defun)) + (doom--assert-stage-p 'init #'package!) + (unless (memq when '(:pre-init :post-init :pre-config :post-config)) + (error "'%s' isn't a valid hook for def-package-hook!" when)) + `(progn + (setq use-package-inject-hooks t) + (add-hook! + ',(intern (format "use-package--%s--%s-hook" + package + (substring (symbol-name when) 1))) + ,@body))) + +(defmacro require! (category module &rest plist) + "Loads the module specified by CATEGORY (a keyword) and MODULE (a symbol)." + (let ((doom-modules (copy-hash-table doom-modules))) + (apply #'doom-module-set category module + (mapcar #'eval plist)) + (let ((module-path (doom-module-locate-path category module))) + (if (directory-name-p module-path) + `(condition-case-unless-debug ex + (let ((doom--current-module ',(cons category module))) + (load! "init" ,module-path :noerror) + (let ((doom--stage 'config)) + (load! "config" ,module-path :noerror))) + ('error + (lwarn 'doom-modules :error + "%s in '%s %s' -> %s" + (car ex) ,category ',module + (error-message-string ex)))) + (warn 'doom-modules :warning "Couldn't find module '%s %s'" + category module))))) + +(defmacro featurep! (module &optional submodule flag) + "Returns t if MODULE SUBMODULE is enabled. If FLAG is provided, returns t if +MODULE SUBMODULE has FLAG enabled. + + (featurep! :config default) + +Module FLAGs are set in your config's `doom!' block, typically in +~/.emacs.d/init.el. Like so: + + :config (default +flag1 -flag2) + +When this macro is used from inside a module, MODULE and SUBMODULE can be +omitted. eg. (featurep! +flag1)" + (unless submodule + (let* ((path (FILE!)) + (module-pair (doom-module-from-path path))) + (unless module-pair + (error "featurep! couldn't detect what module its in! (in %s)" path)) + (setq flag module + module (car module-pair) + submodule (cdr module-pair)))) + (if flag + (and (memq flag (doom-module-get module submodule :flags)) t) + (doom-module-p module submodule))) + + +;; +;; Cross-module configuration +;; + +;; I needed a way to reliably cross-configure modules without littering my +;; modules with `after!' blocks or testing whether they were enabled, so I wrote +;; `set!'. If a setting doesn't exist at runtime, the `set!' call is ignored and +;; its arguments are left unevaluated (and entirely omitted when byte-compiled). + +(defmacro def-setting! (keyword arglist &optional docstring &rest forms) + "Define a setting. Like `defmacro', this should return a form to be executed +when called with `set!'. FORMS are not evaluated until `set!' calls it. + +See `doom/describe-setting' for a list of available settings. + +Do not use this for configuring Doom core." + (declare (indent defun) (doc-string 3)) + (or (keywordp keyword) + (signal 'wrong-type-argument (list 'keywordp keyword))) + `(fset ',(intern (format "doom--set%s" keyword)) + (lambda ,arglist ,docstring ,@forms))) + +(defmacro set! (keyword &rest values) + "Set an option defined by `def-setting!'. Skip if doesn't exist. See +`doom/describe-setting' for a list of available settings. + +VALUES doesn't get evaluated if the KEYWORD setting doesn't exist." + (declare (indent defun)) + (let ((fn (intern-soft (format "doom--set%s" keyword)))) + (if (and fn (fboundp fn)) + (apply fn values) + (when doom-debug-mode + (message "No setting found for %s" keyword) + nil)))) + +(provide 'core-modules) +;;; core-modules.el ends here diff --git a/core/core-packages.el b/core/core-packages.el index 7cd1d292b..dbb4fb8d0 100644 --- a/core/core-packages.el +++ b/core/core-packages.el @@ -1,4 +1,4 @@ -;;; core-packages.el --- package management system -*- lexical-binding: t; -*- +;;; core/core-packages.el -*- lexical-binding: t; -*- ;; Emacs package management is opinionated, and so am I. I've bound together ;; `use-package', `quelpa' and package.el to create my own, rolling-release, @@ -39,22 +39,6 @@ ;; ;; See core/autoload/packages.el for more functions. -(defvar doom-init-p nil - "Non-nil if `doom-initialize' has run.") - -(defvar doom-init-modules-p nil - "Non-nil if `doom-initialize-modules' has run.") - -(defvar doom-init-time nil - "The time it took, in seconds, for DOOM Emacs to initialize.") - -(defvar doom-modules () - "A hash table of enabled modules. Set by `doom-initialize-modules'.") - -(defvar doom-modules-dirs - (list (expand-file-name "modules/" doom-private-dir) doom-modules-dir) - "A list of module root directories. Order determines priority.") - (defvar doom-packages () "A list of enabled packages. Each element is a sublist, whose CAR is the package's name as a symbol, and whose CDR is the plist supplied to its @@ -67,30 +51,7 @@ missing) and shouldn't be deleted.") (defvar doom-disabled-packages () "A list of packages that should be ignored by `def-package!'.") -(defvar doom-site-load-path load-path - "The starting load-path, before it is altered by `doom-initialize'.") - -(defvar doom-autoload-file (concat doom-local-dir "autoloads.el") - "Where `doom//reload-doom-autoloads' will generate its core autoloads file.") - -(defvar doom-package-autoload-file (concat doom-local-dir "autoloads.pkg.el") - "Where `doom//reload-package-autoloads' will generate its package.el autoloads -file.") - -(defvar doom-reload-hook nil - "A list of hooks to run when `doom//reload-load-path' is called.") - -(defvar doom-emacs-changed-p nil - "If non-nil, the running version of Emacs is different from the first time -Doom was setup, which can cause problems.") - -(defvar doom--current-module nil) -(defvar doom--refreshed-p nil) -(defvar doom--stage 'init) - -;; -(setq autoload-compute-prefixes nil - package--init-file-ensured t +(setq package--init-file-ensured t package-user-dir (expand-file-name "elpa" doom-packages-dir) package-enable-at-startup nil package-archives @@ -100,27 +61,13 @@ Doom was setup, which can cause problems.") ;; I omit Marmalade because its packages are manually submitted rather ;; than pulled, so packages are often out of date with upstream. - ;; security settings - gnutls-verify-error (not (getenv "INSECURE")) ; you shouldn't use this - tls-checktrust gnutls-verify-error - tls-program (list "gnutls-cli --x509cafile %t -p %p %h" - ;; compatibility fallbacks - "gnutls-cli -p %p %h" - "openssl s_client -connect %h:%p -no_ssl2 -no_ssl3 -ign_eof") - - use-package-verbose doom-debug-mode - use-package-minimum-reported-time (if doom-debug-mode 0 0.1) - ;; Don't track MELPA, we'll use package.el for that quelpa-checkout-melpa-p nil quelpa-update-melpa-p nil quelpa-melpa-recipe-stores nil quelpa-self-upgrade-p nil quelpa-verbose doom-debug-mode - quelpa-dir (expand-file-name "quelpa" doom-packages-dir) - - byte-compile-verbose doom-debug-mode - byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local)) + quelpa-dir (expand-file-name "quelpa" doom-packages-dir)) ;; accommodate INSECURE setting (unless gnutls-verify-error @@ -129,199 +76,9 @@ Doom was setup, which can cause problems.") ;; -;; Helpers 'n hooks +;; Bootstrapper ;; -(defun doom--assert-stage-p (stage macro) - (cl-assert (eq stage doom--stage) - nil - "Found %s call in non-%s.el file (%s)" - macro (symbol-name stage) - (if (file-in-directory-p load-file-name doom-emacs-dir) - (file-relative-name load-file-name doom-emacs-dir) - (abbreviate-file-name load-file-name)))) - -(defun doom|display-benchmark (&optional return-p) - "Display a benchmark, showing number of packages and modules, and how quickly -they were loaded at startup. - -If RETURN-P, return the message as a string instead of displaying it." - (funcall (if return-p #'format #'message) - "Doom loaded %s packages across %d modules in %.03fs" - (length package-activated-list) - (if doom-modules (hash-table-count doom-modules) 0) - (or doom-init-time - (setq doom-init-time (float-time (time-subtract (current-time) before-init-time)))))) - -(defun doom|post-init () - "Run `doom-post-init-hook'. That's all." - (run-hooks 'doom-post-init-hook)) - -(defun doom|run-all-startup-hooks () - "Run all startup Emacs hooks. Meant to be executed after starting Emacs with --q or -Q, for example: - - emacs -Q -l init.el -f doom|run-all-startup-hooks" - (run-hooks 'after-init-hook 'delayed-warnings-hook - 'emacs-startup-hook 'term-setup-hook - 'window-setup-hook)) - - -;; -;; Bootstrap helpers -;; - -(defvar doom--last-emacs-file (concat doom-local-dir "emacs-version.el")) -(defvar doom--last-emacs-version nil) - -(defun doom-ensure-same-emacs-version-p () - "Do an Emacs version check and set `doom-emacs-changed-p' if it has changed." - (if (load doom--last-emacs-file 'noerror 'nomessage 'nosuffix) - (setq doom-emacs-changed-p - (not (equal emacs-version doom--last-emacs-version))) - (with-temp-file doom--last-emacs-file - (princ `(setq doom--last-emacs-version ,(prin1-to-string emacs-version)) - (current-buffer)))) - (cond ((not doom-emacs-changed-p)) - ((y-or-n-p - (format - (concat "Your version of Emacs has changed from %s to %s, which may cause incompatibility\n" - "issues. Please run `bin/doom compile :plugins` afterwards to resolve any problems.\n\n" - "Continue?") - doom--last-emacs-version - emacs-version)) - (delete-file doom--last-emacs-file)) - (noninteractive (error "Aborting")) - ((kill-emacs)))) - -(defun doom-ensure-packages-initialized (&optional force-p) - "Make sure package.el is initialized." - (when (or force-p (not (bound-and-true-p package--initialized))) - (require 'package) - (setq package-activated-list nil - package--initialized nil) - (let (byte-compile-warnings) - (condition-case _ - (quiet! (package-initialize)) - ('error (package-refresh-contents) - (setq doom--refreshed-p t) - (package-initialize)))))) - -(defun doom-ensure-core-packages () - "Make sure `doom-core-packages' are installed." - (when-let* ((core-packages (cl-remove-if #'package-installed-p doom-core-packages))) - (message "Installing core packages") - (unless doom--refreshed-p - (package-refresh-contents)) - (dolist (package core-packages) - (let ((inhibit-message t)) - (package-install package)) - (if (package-installed-p package) - (message "✓ Installed %s" package) - (error "✕ Couldn't install %s" package))) - (message "Installing core packages...done"))) - -(defun doom-ensure-core-directories () - "Make sure all Doom's essential local directories (in and including -`doom-local-dir') exist." - (dolist (dir (list doom-local-dir doom-etc-dir doom-cache-dir doom-packages-dir)) - (unless (file-directory-p dir) - (make-directory dir t)))) - -(defun doom-delete-autoloads-file (file) - "Delete FILE (an autoloads file), and delete the accompanying *.elc file, if -it exists." - (cl-check-type file string) - (when (file-exists-p file) - (delete-file file) - (ignore-errors (delete-file (byte-compile-dest-file file))) - (message "Deleted old %s" (file-name-nondirectory file)))) - - -;; -;; Bootstrap API -;; - -(defun doom-initialize (&optional force-p) - "Bootstrap Doom, if it hasn't already (or if FORCE-P is non-nil). - -The bootstrap process involves making sure 1) the essential directories exist, -2) the core packages are installed, 3) `doom-autoload-file' and -`doom-package-autoload-file' exist and have been loaded, and 4) Doom's core -files are loaded. - -If the cache exists, much of this function isn't run, which substantially -reduces startup time. - -The overall load order of Doom is as follows: - - ~/.emacs.d/init.el - ~/.emacs.d/core/core.el - `doom-pre-init-hook' - ~/.doom.d/init.el - Module init.el files - `doom-init-hook' - Module config.el files - ~/.doom.d/config.el - `after-init-hook' - `emacs-startup-hook' - `doom-post-init-hook' (at end of `emacs-startup-hook') - -Module load order is determined by your `doom!' block. See `doom-modules-dirs' -for a list of all recognized module trees. Order defines precedence (from most -to least)." - (when (or force-p (not doom-init-p)) - ;; Set this to prevent infinite recursive calls to `doom-initialize' - (setq doom-init-p t) - ;; `doom-autoload-file' tells Emacs where to load all its autoloaded - ;; functions from. This includes everything in core/autoload/*.el and all - ;; the autoload files in your enabled modules. - (when (or force-p (not (doom-initialize-autoloads doom-autoload-file))) - (doom-ensure-core-directories) - (doom-ensure-same-emacs-version-p) - (doom-ensure-packages-initialized force-p) - (doom-ensure-core-packages) - ;; Regenerate `doom-autoload-file', which tells Doom where to find all its - ;; module autoloaded functions. - (unless (or force-p noninteractive) - (user-error "Your doom autoloads are missing! Run `bin/doom refresh' to regenerate them"))) - ;; Loads `doom-package-autoload-file', which caches `load-path', - ;; `auto-mode-alist', `Info-directory-list', `doom-disabled-packages' and - ;; `package-activated-list'. A big reduction in startup time. - (unless (or force-p - (doom-initialize-autoloads doom-package-autoload-file) - noninteractive) - (user-error "Your package autoloads are missing! Run `bin/doom refresh' to regenerate them"))) - ;; Initialize Doom core - (unless noninteractive - (add-hook! 'emacs-startup-hook - #'(doom|post-init doom|display-benchmark)) - (require 'core-os) - (require 'core-ui) - (require 'core-editor) - (require 'core-projects) - (require 'core-keybinds))) - -(defun doom-initialize-modules (&optional force-p) - "Loads the init.el in `doom-private-dir' and sets up hooks for a healthy -session of Dooming. Will noop if used more than once, unless FORCE-P is -non-nil." - (when (or force-p (not doom-init-modules-p)) - ;; Set `doom-init-modules-p' early, so `doom-pre-init-hook' won't infinitely - ;; recurse by accident if any of them need `doom-initialize-modules'. - (setq doom-init-modules-p t) - (when doom-private-dir - (load (expand-file-name "init" doom-private-dir) - 'noerror 'nomessage)))) - -(defun doom-initialize-autoloads (file) - "Tries to load FILE (an autoloads file). Return t on success, nil otherwise." - (condition-case-unless-debug e - (load (file-name-sans-extension file) 'noerror 'nomessage) - ('error - (message "Autoload error: %s -> %s" - (car e) (error-message-string e))))) - (defun doom-initialize-packages (&optional force-p) "Ensures that Doom's package management system, package.el and quelpa are initialized, and `doom-packages', `packages-alist' and `quelpa-cache' are @@ -384,334 +141,35 @@ them." ;; -;; Module API +;; Package API ;; -(defun doom-module-p (category module) - "Returns t if CATEGORY MODULE is enabled (ie. present in `doom-modules')." - (and (hash-table-p doom-modules) - (gethash (cons category module) doom-modules) - t)) +(defun doom-ensure-packages-initialized (&optional force-p) + "Make sure package.el is initialized." + (when (or force-p (not (bound-and-true-p package--initialized))) + (require 'package) + (setq package-activated-list nil + package--initialized nil) + (let (byte-compile-warnings) + (condition-case _ + (quiet! (package-initialize)) + ('error (package-refresh-contents) + (setq doom--refreshed-p t) + (package-initialize)))))) -(defun doom-module-get (category module &optional property) - "Returns the plist for CATEGORY MODULE. Gets PROPERTY, specifically, if set." - (when-let* ((plist (gethash (cons category module) doom-modules))) - (if property - (plist-get plist property) - plist))) - -(defun doom-module-put (category module property value &rest rest) - "Set a PROPERTY for CATEGORY MODULE to VALUE. PLIST should be additional pairs -of PROPERTY and VALUEs." - (when-let* ((plist (doom-module-get category module))) - (plist-put plist property value) - (when rest - (when (cl-oddp (length rest)) - (signal 'wrong-number-of-arguments (list (length rest)))) - (while rest - (plist-put rest (pop rest) (pop rest)))) - (puthash (cons category module) plist doom-modules))) - -(defun doom-module-set (category module &rest plist) - "Enables a module by adding it to `doom-modules'. - -CATEGORY is a keyword, module is a symbol, PLIST is a plist that accepts the -following properties: - - :flags [SYMBOL LIST] list of enabled category flags - :path [STRING] path to category root directory - -Example: - (doom-module-set :lang 'haskell :flags '(+intero))" - (when plist - (let ((old-plist (doom-module-get category module))) - (unless (plist-member plist :flags) - (plist-put plist :flags (plist-get old-plist :flags))) - (unless (plist-member plist :path) - (plist-put plist :path (or (plist-get old-plist :path) - (doom-module-locate-path category module)))))) - (let ((key (cons category module))) - (puthash key plist doom-modules))) - -(defun doom-module-path (category module &optional file) - "Like `expand-file-name', but expands FILE relative to CATEGORY (keywordp) and -MODULE (symbol). - -If the category isn't enabled this will always return nil. For finding disabled -modules use `doom-module-locate-path'." - (let ((path (doom-module-get category module :path))) - (if file (expand-file-name file path) - path))) - -(defun doom-module-locate-path (category &optional module file) - "Searches `doom-modules-dirs' to find the path to a module. - -CATEGORY is a keyword (e.g. :lang) and MODULE is a symbol (e.g. 'python). FILE -is a string that will be appended to the resulting path. If no path exists, this -returns nil, otherwise an absolute path. - -This doesn't require modules to be enabled. For enabled modules us -`doom-module-path'." - (when (keywordp category) - (setq category (substring (symbol-name category) 1))) - (when (and module (symbolp module)) - (setq module (symbol-name module))) - (cl-loop for default-directory in doom-modules-dirs - for path = (concat category "/" module "/" file) - if (file-exists-p path) - return (expand-file-name path))) - -(defun doom-module-from-path (&optional path) - "Returns a cons cell (CATEGORY . MODULE) derived from PATH (a file path)." - (or doom--current-module - (when path - (save-match-data - (setq path (file-truename path)) - (when (string-match "/modules/\\([^/]+\\)/\\([^/]+\\)/.*$" path) - (when-let* ((module (match-string 1 path)) - (submodule (match-string 2 path))) - (cons (intern (concat ":" module)) - (intern submodule)))))))) - -(defun doom-module-load-path () - "Return a list of absolute file paths to activated modules." - (append (cl-loop for plist being the hash-values of (doom-modules) - collect (plist-get plist :path)) - (list doom-private-dir))) - -(defun doom-modules (&optional refresh-p) - "Minimally initialize `doom-modules' (a hash table) and return it." - (or (unless refresh-p doom-modules) - (let ((noninteractive t) - doom-init-modules-p) - (message "Initializing modules") - (doom-initialize-modules t) - doom-modules))) - - -;; -;; Use-package modifications -;; - -(autoload 'use-package "use-package-core" nil nil t) - -;; Adds the :after-call custom keyword to `use-package' (and consequently, -;; `def-package!'). :after-call takes a symbol ro list of symbols. These symbols -;; can be functions to hook variables. -;; -;; (use-package X :after-call find-file-hook) -;; -;; This will load X on the first invokation of `find-file-hook' (then it will -;; remove itself from the hook). -(defvar doom--deferred-packages-alist ()) -(after! use-package-core - (add-to-list 'use-package-deferring-keywords :after-call nil #'eq) - - (setq use-package-keywords - (use-package-list-insert :after-call use-package-keywords :after)) - - (defalias 'use-package-normalize/:after-call - 'use-package-normalize-symlist) - - (defun use-package-handler/:after-call (name-symbol _keyword hooks rest state) - (let ((fn (intern (format "doom|transient-hook--load-%s" name-symbol))) - (hooks (delete-dups hooks))) - (if (plist-get state :demand) - (use-package-process-keywords name rest state) - (use-package-concat - `((fset ',fn - (lambda (&rest _) - (require ',name-symbol) - (dolist (hook (cdr (assq ',name-symbol doom--deferred-packages-alist))) - (if (functionp hook) - (advice-remove hook #',fn) - (remove-hook hook #',fn))) - (map-delete doom--deferred-packages-alist ',name-symbol) - (fmakunbound ',fn)))) - (cl-mapcan (lambda (hook) - (if (functionp hook) - `((advice-add #',hook :before #',fn)) - `((add-hook ',hook #',fn)))) - hooks) - `((map-put doom--deferred-packages-alist - ',name-symbol - '(,@hooks ,@(cdr (assq name-symbol doom--deferred-packages-alist))))) - (use-package-process-keywords name rest state)))))) - - -;; -;; Module config macros -;; - -(defmacro doom! (&rest modules) - "Bootstraps DOOM Emacs and its modules. - -The bootstrap process involves making sure the essential directories exist, core -packages are installed, `doom-autoload-file' is loaded, `doom-packages-file' -cache exists (and is loaded) and, finally, loads your private init.el (which -should contain your `doom!' block). - -If the cache exists, much of this function isn't run, which substantially -reduces startup time. - -The overall load order of Doom is as follows: - - ~/.emacs.d/init.el - ~/.emacs.d/core/core.el - `doom-pre-init-hook' - ~/.doom.d/init.el - Module init.el files - `doom-init-hook' - Module config.el files - ~/.doom.d/config.el - `after-init-hook' - `emacs-startup-hook' - `doom-post-init-hook' (at end of `emacs-startup-hook') - -Module load order is determined by your `doom!' block. See `doom-modules-dirs' -for a list of all recognized module trees. Order defines precedence (from most -to least)." - (let ((doom-modules - (make-hash-table :test #'equal - :size (if modules (length modules) 100) - :rehash-threshold 1.0)) - category - init-forms config-forms) - (dolist (m modules) - (cond ((keywordp m) (setq category m)) - ((not category) (error "No module category specified for %s" m)) - ((let* ((module (if (listp m) (car m) m)) - (flags (if (listp m) (cdr m))) - (path (doom-module-locate-path category module))) - (if (not path) - (message "Couldn't find the %s %s module" category module) - (let ((key (cons category module))) - (doom-module-set category module :flags flags :path path) - (push `(let ((doom--current-module ',key)) - (load! "init" ,path t)) - init-forms) - (push `(let ((doom--current-module ',key)) - (load! "config" ,path t)) - config-forms))))))) - `(let (file-name-handler-alist) - (setq doom-modules ',doom-modules) - ,@(nreverse init-forms) - (run-hooks 'doom-init-hook) - (unless noninteractive - (let ((doom--stage 'config)) - ,@(nreverse config-forms) - (when doom-private-dir - (load ,(expand-file-name "config" doom-private-dir) - t (not doom-debug-mode)))))))) - -(defmacro def-package! (name &rest plist) - "A thin wrapper around `use-package'." - ;; Ignore package if NAME is in `doom-disabled-packages' - (when (and (memq name doom-disabled-packages) - (not (memq :disabled plist))) - (setq plist `(:disabled t ,@plist))) - ;; If byte-compiling, ignore this package if it doesn't meet the condition. - ;; This avoids false-positive load errors. - (unless (and (bound-and-true-p byte-compile-current-file) - (or (and (plist-member plist :if) (not (eval (plist-get plist :if) t))) - (and (plist-member plist :when) (not (eval (plist-get plist :when) t))) - (and (plist-member plist :unless) (eval (plist-get plist :unless) t)))) - `(use-package ,name ,@plist))) - -(defmacro def-package-hook! (package when &rest body) - "Reconfigures a package's `def-package!' block. - -Only use this macro in a module's init.el file. - -Under the hood, this uses use-package's `use-package-inject-hooks'. - -PACKAGE is a symbol; the package's name. -WHEN should be one of the following: - :pre-init :post-init :pre-config :post-config - -WARNING: If :pre-init or :pre-config hooks return nil, the original -`def-package!''s :init/:config block (respectively) is overwritten, so remember -to have them return non-nil (or exploit that to overwrite Doom's config)." - (declare (indent defun)) - (doom--assert-stage-p 'init #'package!) - (cond ((eq when :disable) - (message "Using :disable with `def-package-hook!' is deprecated. Use :disable in `package!' instead.") - (ignore (push package doom-disabled-packages))) - ((memq when '(:pre-init :post-init :pre-config :post-config)) - `(progn - (setq use-package-inject-hooks t) - (add-hook! - ',(intern (format "use-package--%s--%s-hook" - package - (substring (symbol-name when) 1))) - ,@body))) - ((error "'%s' isn't a valid hook for def-package-hook!" when)))) - -(defmacro load! (filename &optional path noerror) - "Load a file relative to the current executing file (`load-file-name'). - -FILENAME is either a file path string or a form that should evaluate to such a -string at run time. PATH is where to look for the file (a string representing a -directory path). If omitted, the lookup is relative to either `load-file-name', -`byte-compile-current-file' or `buffer-file-name' (checked in that order). - -If NOERROR is non-nil, don't throw an error if the file doesn't exist." - (unless path - (setq path (or (and (bound-and-true-p byte-compile-current-file) - (file-name-directory byte-compile-current-file)) - (and load-file-name (file-name-directory load-file-name)) - (and buffer-file-name (file-name-directory buffer-file-name)) - (error "Could not detect path to look for '%s' in" filename)))) - `(load ,(if path - `(expand-file-name ,filename ,path) - filename) - ,noerror ,(not doom-debug-mode))) - -(defmacro require! (category module &rest plist) - "Loads the module specified by CATEGORY (a keyword) and MODULE (a symbol)." - (let ((doom-modules (copy-hash-table doom-modules))) - (apply #'doom-module-set category module - (mapcar #'eval plist)) - (let ((module-path (doom-module-locate-path category module))) - (if (directory-name-p module-path) - `(condition-case-unless-debug ex - (let ((doom--current-module ',(cons category module))) - (load! "init" ,module-path :noerror) - (let ((doom--stage 'config)) - (load! "config" ,module-path :noerror))) - ('error - (lwarn 'doom-modules :error - "%s in '%s %s' -> %s" - (car ex) ,category ',module - (error-message-string ex)))) - (warn 'doom-modules :warning "Couldn't find module '%s %s'" - category module))))) - -(defmacro featurep! (module &optional submodule flag) - "Returns t if MODULE SUBMODULE is enabled. If FLAG is provided, returns t if -MODULE SUBMODULE has FLAG enabled. - - (featurep! :config default) - -Module FLAGs are set in your config's `doom!' block, typically in -~/.emacs.d/init.el. Like so: - - :config (default +flag1 -flag2) - -When this macro is used from inside a module, MODULE and SUBMODULE can be -omitted. eg. (featurep! +flag1)" - (unless submodule - (let* ((path (or (bound-and-true-p byte-compile-current-file) - load-file-name)) - (module-pair (doom-module-from-path path))) - (unless module-pair - (error "featurep! couldn't detect what module its in! (in %s)" path)) - (setq flag module - module (car module-pair) - submodule (cdr module-pair)))) - (if flag - (and (memq flag (doom-module-get module submodule :flags)) t) - (doom-module-p module submodule))) +(defun doom-ensure-core-packages () + "Make sure `doom-core-packages' are installed." + (when-let* ((core-packages (cl-remove-if #'package-installed-p doom-core-packages))) + (message "Installing core packages") + (unless doom--refreshed-p + (package-refresh-contents)) + (dolist (package core-packages) + (let ((inhibit-message t)) + (package-install package)) + (if (package-installed-p package) + (message "✓ Installed %s" package) + (error "✕ Couldn't install %s" package))) + (message "Installing core packages...done"))) ;; @@ -772,7 +230,7 @@ elsewhere." ,(when (and pkg-pin t) `(map-put package-pinned-packages ',name ,pkg-pin)) (map-put doom-packages ',name ',plist) - (not ,pkg-disable)))) + (not (memq ',name doom-disabled-packages))))) (defmacro packages! (&rest packages) "A convenience macro like `package!', but allows you to declare multiple @@ -804,53 +262,5 @@ loads MODULE SUBMODULE's packages.el file." (doom-module-put ,module ',submodule :flags flags)) (load! "packages" ,(doom-module-locate-path module submodule) t))) - -;; -;; Make package.el cooperate with Doom -;; - -;; Updates QUELPA after deleting a package -(advice-add #'package-delete :after #'doom*package-delete) - -;; Replace with Doom variants -(advice-add #'package-autoremove :override #'doom//packages-autoremove) -(advice-add #'package-install-selected-packages :override #'doom//packages-install) - - -;; -;; Cross-module configuration -;; - -;; I needed a way to reliably cross-configure modules without littering my -;; modules with `after!' blocks or testing whether they were enabled, so I wrote -;; `set!'. If a setting doesn't exist at runtime, the `set!' call is ignored and -;; its arguments are left unevaluated (and entirely omitted when byte-compiled). - -(defmacro def-setting! (keyword arglist &optional docstring &rest forms) - "Define a setting. Like `defmacro', this should return a form to be executed -when called with `set!'. FORMS are not evaluated until `set!' calls it. - -See `doom/describe-setting' for a list of available settings. - -Do not use this for configuring Doom core." - (declare (indent defun) (doc-string 3)) - (or (keywordp keyword) - (signal 'wrong-type-argument (list 'keywordp keyword))) - `(fset ',(intern (format "doom--set%s" keyword)) - (lambda ,arglist ,docstring ,@forms))) - -(defmacro set! (keyword &rest values) - "Set an option defined by `def-setting!'. Skip if doesn't exist. See -`doom/describe-setting' for a list of available settings. - -VALUES doesn't get evaluated if the KEYWORD setting doesn't exist." - (declare (indent defun)) - (let ((fn (intern-soft (format "doom--set%s" keyword)))) - (if (and fn (fboundp fn)) - (apply fn values) - (when doom-debug-mode - (message "No setting found for %s" keyword) - nil)))) - (provide 'core-packages) ;;; core-packages.el ends here diff --git a/core/core.el b/core/core.el index 30011ea33..4da84da45 100644 --- a/core/core.el +++ b/core/core.el @@ -63,7 +63,39 @@ Use this for files that change often, like cache files.") "Where your private customizations are placed. Must end in a slash. Respects XDG directory conventions if ~/.config/doom exists.") +(defconst doom-autoload-file (concat doom-local-dir "autoloads.el") + "Where `doom//reload-doom-autoloads' will generate its core autoloads file.") + +(defconst doom-package-autoload-file (concat doom-local-dir "autoloads.pkg.el") + "Where `doom//reload-package-autoloads' will generate its package.el autoloads +file.") + + +;; +;; State variables +;; + +(defvar doom-init-p nil + "Non-nil if `doom-initialize' has run.") + +(defvar doom-init-time nil + "The time it took, in seconds, for DOOM Emacs to initialize.") + +(defvar doom-emacs-changed-p nil + "If non-nil, the running version of Emacs is different from the first time +Doom was setup, which can cause problems.") + +(defvar doom-site-load-path load-path + "The starting load-path, before it is altered by `doom-initialize'.") + +(defvar doom--refreshed-p nil) +(defvar doom--stage 'init) + + +;; ;; Doom hooks +;; + (defvar doom-init-hook nil "Hooks run after all init.el files are loaded, including your private and all module init.el files, but before their config.el files are loaded.") @@ -73,8 +105,40 @@ module init.el files, but before their config.el files are loaded.") `emacs-startup-hook', as late as possible. Guaranteed to run after everything else (except for `window-setup-hook').") +(defvar doom-reload-hook nil + "A list of hooks to run when `doom//reload-load-path' is called.") + + +;; +;; Optimize startup +;; + +(defvar doom--file-name-handler-alist file-name-handler-alist) +(unless (or after-init-time noninteractive) + ;; A big contributor to long startup times is the garbage collector, so we up + ;; its memory threshold, temporarily and reset it later in `doom|finalize'. + (setq gc-cons-threshold 402653184 + gc-cons-percentage 1.0 + ;; consulted on every `require', `load' and various file reading + ;; functions. You get a minor speed up by nooping this. + file-name-handler-alist nil)) + +(defun doom|finalize () + "Resets garbage collection settings to reasonable defaults (if you don't do +this, you'll get stuttering and random freezes) and resets +`file-name-handler-alist'." + (setq file-name-handler-alist doom--file-name-handler-alist + gc-cons-threshold 16777216 + gc-cons-percentage 0.2)) + +(add-hook 'emacs-startup-hook #'doom|finalize) +(add-hook 'doom-reload-hook #'doom|finalize) + + +;; +;; Emacs core configuration +;; -;;; ;; UTF-8 as the default coding system (when (fboundp 'set-charset-priority) (set-charset-priority 'unicode)) ; pretty @@ -88,11 +152,12 @@ else (except for `window-setup-hook').") (setq-default ad-redefinition-action 'accept ; silence advised function warnings apropos-do-all t ; make `apropos' more useful + auto-mode-case-fold nil + autoload-compute-prefixes nil debug-on-error doom-debug-mode ffap-machine-p-known 'reject ; don't ping things that look like domain names idle-update-delay 2 ; update ui less often - auto-mode-case-fold nil -;; be quiet at startup; don't load or display anything unnecessary + ;; be quiet at startup; don't load or display anything unnecessary inhibit-startup-message t inhibit-startup-echo-area-message user-login-name inhibit-default-init t @@ -105,6 +170,19 @@ else (except for `window-setup-hook').") create-lockfiles nil history-length 500 make-backup-files nil ; don't create backup~ files + ;; `use-package' + use-package-verbose doom-debug-mode + use-package-minimum-reported-time (if doom-debug-mode 0 0.1) + ;; byte compilation + byte-compile-verbose doom-debug-mode + byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local) + ;; security + gnutls-verify-error (not (getenv "INSECURE")) ; you shouldn't use this + tls-checktrust gnutls-verify-error + tls-program (list "gnutls-cli --x509cafile %t -p %p %h" + ;; compatibility fallbacks + "gnutls-cli -p %p %h" + "openssl s_client -connect %h:%p -no_ssl2 -no_ssl3 -ign_eof") ;; files abbrev-file-name (concat doom-local-dir "abbrev.el") auto-save-list-file-name (concat doom-cache-dir "autosave") @@ -121,11 +199,6 @@ else (except for `window-setup-hook').") url-cache-directory (concat doom-cache-dir "url/") url-configuration-directory (concat doom-etc-dir "url/")) - -;; -;; Emacs fixes/hacks -;; - (defvar doom-auto-minor-mode-alist '() "Alist mapping filename patterns to corresponding minor mode functions, like `auto-mode-alist'. All elements of this alist are checked, meaning you can @@ -167,29 +240,129 @@ with functions that require it (like modeline segments)." ;; -;; Optimize startup +;; Bootstrap helpers ;; -(defvar doom--file-name-handler-alist file-name-handler-alist) -(unless (or after-init-time noninteractive) - ;; A big contributor to long startup times is the garbage collector, so we up - ;; its memory threshold, temporarily and reset it later in `doom|finalize'. - (setq gc-cons-threshold 402653184 - gc-cons-percentage 1.0 - ;; consulted on every `require', `load' and various file reading - ;; functions. You get a minor speed up by nooping this. - file-name-handler-alist nil)) +(defvar doom--last-emacs-file (concat doom-local-dir "emacs-version.el")) +(defvar doom--last-emacs-version nil) -(defun doom|finalize () - "Resets garbage collection settings to reasonable defaults (if you don't do -this, you'll get stuttering and random freezes) and resets -`file-name-handler-alist'." - (setq file-name-handler-alist doom--file-name-handler-alist - gc-cons-threshold 16777216 - gc-cons-percentage 0.2)) +(defun doom-ensure-same-emacs-version-p () + "Do an Emacs version check and set `doom-emacs-changed-p' if it has changed." + (if (load doom--last-emacs-file 'noerror 'nomessage 'nosuffix) + (setq doom-emacs-changed-p + (not (equal emacs-version doom--last-emacs-version))) + (with-temp-file doom--last-emacs-file + (princ `(setq doom--last-emacs-version ,(prin1-to-string emacs-version)) + (current-buffer)))) + (cond ((not doom-emacs-changed-p)) + ((y-or-n-p + (format + (concat "Your version of Emacs has changed from %s to %s, which may cause incompatibility\n" + "issues. Please run `bin/doom compile :plugins` afterwards to resolve any problems.\n\n" + "Continue?") + doom--last-emacs-version + emacs-version)) + (delete-file doom--last-emacs-file)) + (noninteractive (error "Aborting")) + ((kill-emacs)))) -(add-hook 'emacs-startup-hook #'doom|finalize) -(add-hook 'doom-reload-hook #'doom|finalize) +(defun doom-ensure-core-directories () + "Make sure all Doom's essential local directories (in and including +`doom-local-dir') exist." + (dolist (dir (list doom-local-dir doom-etc-dir doom-cache-dir doom-packages-dir)) + (unless (file-directory-p dir) + (make-directory dir t)))) + +(defun doom|display-benchmark (&optional return-p) + "Display a benchmark, showing number of packages and modules, and how quickly +they were loaded at startup. + +If RETURN-P, return the message as a string instead of displaying it." + (funcall (if return-p #'format #'message) + "Doom loaded %s packages across %d modules in %.03fs" + (length package-activated-list) + (if doom-modules (hash-table-count doom-modules) 0) + (or doom-init-time + (setq doom-init-time (float-time (time-subtract (current-time) before-init-time)))))) + +(defun doom|post-init () + "Run `doom-post-init-hook'. That's all." + (run-hooks 'doom-post-init-hook)) + +(defun doom|run-all-startup-hooks () + "Run all startup Emacs hooks. Meant to be executed after starting Emacs with +-q or -Q, for example: + + emacs -Q -l init.el -f doom|run-all-startup-hooks" + (run-hooks 'after-init-hook 'delayed-warnings-hook + 'emacs-startup-hook 'term-setup-hook + 'window-setup-hook)) + + +;; +;; Bootstrap functions +;; + +(defun doom-initialize (&optional force-p) + "Bootstrap Doom, if it hasn't already (or if FORCE-P is non-nil). + +The bootstrap process involves making sure 1) the essential directories exist, +2) the core packages are installed, 3) `doom-autoload-file' and +`doom-package-autoload-file' exist and have been loaded, and 4) Doom's core +files are loaded. + +If the cache exists, much of this function isn't run, which substantially +reduces startup time. + +The overall load order of Doom is as follows: + + ~/.emacs.d/init.el + ~/.emacs.d/core/core.el + ~/.doom.d/init.el + Module init.el files + `doom-init-hook' + Module config.el files + ~/.doom.d/config.el + `after-init-hook' + `emacs-startup-hook' + `doom-post-init-hook' (at end of `emacs-startup-hook') + +Module load order is determined by your `doom!' block. See `doom-modules-dirs' +for a list of all recognized module trees. Order defines precedence (from most +to least)." + (when (or force-p (not doom-init-p)) + ;; Set this to prevent infinite recursive calls to `doom-initialize' + (setq doom-init-p t) + ;; `doom-autoload-file' tells Emacs where to load all its autoloaded + ;; functions from. This includes everything in core/autoload/*.el and all + ;; the autoload files in your enabled modules. + (when (or force-p (not (doom-initialize-autoloads doom-autoload-file))) + (doom-ensure-core-directories) + (doom-ensure-same-emacs-version-p) + ;; Ensure packages are set up and initialized + (require 'core-packages) + (doom-ensure-packages-initialized force-p) + (doom-ensure-core-packages) + ;; Regenerate `doom-autoload-file', which tells Doom where to find all its + ;; module autoloaded functions. + (unless (or force-p noninteractive) + (user-error "Your doom autoloads are missing! Run `bin/doom refresh' to regenerate them"))) + ;; Loads `doom-package-autoload-file', which caches `load-path', + ;; `auto-mode-alist', `Info-directory-list', `doom-disabled-packages' and + ;; `package-activated-list'. A big reduction in startup time. + (unless (or force-p + (doom-initialize-autoloads doom-package-autoload-file) + noninteractive) + (user-error "Your package autoloads are missing! Run `bin/doom refresh' to regenerate them"))) + ;; Initialize Doom core + (unless noninteractive + (add-hook! 'emacs-startup-hook + #'(doom|post-init doom|display-benchmark)) + (require 'core-os) + (require 'core-ui) + (require 'core-editor) + (require 'core-projects) + (require 'core-keybinds))) ;; @@ -198,14 +371,18 @@ this, you'll get stuttering and random freezes) and resets (add-to-list 'load-path doom-core-dir) -(require 'core-lib) -(require 'core-packages) - (load custom-file t t t) +(require 'core-lib) +(require 'core-modules) +(when noninteractive + (require 'core-dispatcher)) + (doom-initialize noninteractive) -(if noninteractive - (require 'core-dispatcher) +(unless noninteractive (doom-initialize-modules)) +(after! package + (require 'core-packages)) + (provide 'core) ;;; core.el ends here