;;; lib/modules.el -*- lexical-binding: t; -*- ;;; Commentary: ;;; Code: (defvar doom-modules nil "A table of enabled modules and metadata. See `doom-modules-initialize'.") (define-obsolete-variable-alias 'doom-modules-dirs 'doom-module-load-path "3.0.0") (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: Remove in v3, as it will be handled in the CLI (make-obsolete-variable 'doom-obsolete-modules nil "3.0.0") (defconst doom-obsolete-modules '((:feature (version-control (:emacs vc) (:ui vc-gutter)) (spellcheck (:checkers spell)) (syntax-checker (:checkers syntax)) (evil (:editor evil)) (snippets (:editor snippets)) (file-templates (:editor file-templates)) (workspaces (:ui workspaces)) (eval (:tools eval)) (lookup (:tools lookup)) (debugger (:tools debugger))) (:tools (rotate-text (:editor rotate-text)) (vterm (:term vterm)) (password-store (:tools pass)) (flycheck (:checkers syntax)) (flyspell (:checkers spell)) (macos (:os macos))) (:emacs (electric-indent (:emacs electric)) (hideshow (:editor fold)) (eshell (:term eshell)) (term (:term term))) (:ui (doom-modeline (:ui modeline)) (fci (:ui fill-column)) (evil-goggles (:ui ophints)) (tabbar (:ui tabs)) (pretty-code (:ui ligatures))) (:app (email (:email mu4e)) (notmuch (:email notmuch))) (:lang (perl (:lang raku)))) "A tree alist that maps deprecated modules to their replacement(s). Each entry is a three-level tree. For example: (:feature (version-control (:emacs vc) (:ui vc-gutter)) (spellcheck (:checkers spell)) (syntax-checker (:tools flycheck))) This marks :feature version-control, :feature spellcheck and :feature syntax-checker modules obsolete. e.g. If :feature version-control is found in your `doom!' block, a warning is emitted before replacing it with :emacs vc and :ui vc-gutter.") (make-obsolete-variable 'doom-inhibit-module-warnings nil "3.0.0") (defvar doom-inhibit-module-warnings (not noninteractive) "If non-nil, don't emit deprecated or missing module warnings at startup.") ;;; 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).") ;; ;;; API ;;;###autoload (defun doom-modules-initialize (&optional force?) "Initializes module metadata." (when (or (null doom-modules) force?) (setq doom-modules (make-hash-table :test 'equal)) ;; Register Doom's two virtual module categories, representing Doom's core ;; and the user's config; which are always enabled. (doom-module--put '(:doom . nil) :path doom-core-dir :depth -110) (doom-module--put '(:user . nil) :path doom-user-dir :depth '(-105 . 105)) ;; DEPRECATED: I intend to phase out our internal usage of `use-package' and ;; move it to a :config use-package module. The macro is far too complex ;; and magical for our needs, but until this move is done, ':config ;; use-package' will remain a hardcoded module for backwards ;; compatibility. (doom-module--put '(:config . use-package) :path (doom-module-locate-path '(:config . use-package)) :depth -111) ;; Load $DOOMDIR/init.el, where the user's `doom!' lives, which will inform ;; us of all desired modules. (doom-load (file-name-concat doom-user-dir doom-module-init-file) 'noerror))) (cl-defun doom-module--put ((group . name) &rest plist) "Enable GROUP NAME and associate PLIST with it. This enables the target module, where GROUP is a keyword, NAME is a symbol, and PLIST is a property list accepting none, any, or all of the following properties: :group KEYWORD Indicating the group this module is in. This doesn't have to match GROUP, as it could indicate a module alias. :name SYMBOL Indicating the name of this module. This doesn't have to match NAME, as it could indicate a module alias. :path STRING Path to the directory where this module lives. :depth INT|(INITDEPTH . CONFIGDEPTH) Determines module load order. If a cons cell, INITDEPTH determines the load order of the module's init.el, while CONFIGDEPTH determines the same for all other config files (config.el, packages.el, doctor.el, etc). :flags (SYMBOL...) A list of activated flags for this module. Will be collapsed into pre-existing flags for the module. :features (SYMBOL...) A list of active features, determined from the module's metadata. Will be collapsed into any pre-existing features for the module. NOT IMPLEMENTED YET. \(fn (GROUP . NAME) &key GROUP NAME PATH DEPTH FLAGS FEATURES)" (let ((module (make-doom-module :index (hash-table-count doom-modules) :group (or (plist-get plist :group) group) :name (or (plist-get plist :name) name) :path (plist-get plist :path) :flags (plist-get plist :flags) :features () ; TODO :depth (if (not (plist-member plist :depth)) '(0 . 0) (let ((depth (plist-get plist :depth))) (cl-check-type depth (or integer cons)) (cond ((integerp depth) (cons depth depth)) ((consp depth) (cons (or (car depth) 0) (or (cdr depth) 0))) ((error "Invalid DEPTH value: %S" depth)))))))) (doom-log 2 "module-put: %s" module) (prog1 (puthash (cons group name) module doom-modules) ;; PERF: Doom caches module index, flags, and features in symbol plists ;; for fast lookups in `modulep!' and elsewhere. plists are lighter and ;; faster than hash tables for datasets this size, and this information ;; is looked up *very* often. (put group name (doom-module->context module))))) (defun doom-module-mplist-map (fn mplist) "Apply FN to each module in MPLIST." (let ((mplist (copy-sequence mplist)) (inhibit-message doom-inhibit-module-warnings) obsolete results group m) (while mplist (setq m (pop mplist)) (cond ((keywordp m) (setq group m obsolete (assq m doom-obsolete-modules))) ((null group) (error "No module group specified for %s" m)) ((and (listp m) (keywordp (car m))) (pcase (car m) (:cond (cl-loop for (cond . mods) in (cdr m) if (eval cond t) return (prependq! mplist mods))) (:if (if (eval (cadr m) t) (push (caddr m) mplist) (prependq! mplist (cdddr m)))) (test (if (xor (eval (cadr m) t) (eq test :unless)) (prependq! mplist (cddr m)))))) ((catch 'doom-modules (let* ((module (if (listp m) (car m) m)) (flags (if (listp m) (cdr m)))) (when-let (new (assq module obsolete)) (let ((newkeys (cdr new))) (if (null newkeys) (print! (warn "%s module was removed")) (if (cdr newkeys) (print! (warn "%s module was removed and split into the %s modules") (list group module) (mapconcat #'prin1-to-string newkeys ", ")) (print! (warn "%s module was moved to %s") (list group module) (car newkeys))) (push group mplist) (dolist (key newkeys) (push (if flags (nconc (cdr key) flags) (cdr key)) mplist) (push (car key) mplist)) (throw 'doom-modules t)))) (doom-log "module: %s %s %s -> %s" group module (or flags "") (doom-module-locate-path (cons group module))) (push (funcall fn (cons group module) :flags (if (listp m) (cdr m)) :path (doom-module-locate-path (cons group module))) results)))))) (when noninteractive (setq doom-inhibit-module-warnings t)) (nreverse results))) (provide 'doom-lib '(modules)) ;;; modules.el ends here