diff --git a/core/autoload/packages.el b/core/autoload/packages.el index 229bc4153..82b97cb0c 100644 --- a/core/autoload/packages.el +++ b/core/autoload/packages.el @@ -6,25 +6,24 @@ ;;;###autoload (defun doom-refresh-packages () "Refresh ELPA packages." + (doom-initialize) (when (or (not doom-packages-last-refresh) (> (nth 1 (time-since doom-packages-last-refresh)) 3600)) - (doom-initialize) (package-refresh-contents) (setq doom-packages-last-refresh (current-time)))) ;;;###autoload -(defun doom-package-elpa-p (name) - "Returns non-nil if NAME was a package installed with elpa." +(defun doom-package-backend (name) + "Get which backend the package NAME was installed with. Can either be elpa, +quelpa or nil (if not installed)." (doom-initialize) - (and (assq name package-alist) - (not (doom-package-quelpa-p name)))) - -;;;###autoload -(defun doom-package-quelpa-p (name) - "Returns non-nil if NAME was a package installed with quelpa." (unless (quelpa-setup-p) (error "Could not initialize quelpa")) - (assq name quelpa-cache)) + (cond ((or (assq name quelpa-cache) + (plist-get (cdr (assq name doom-packages)) :recipe)) + 'quelpa) + ((assq name package-alist) + 'elpa))) ;;;###autoload (defun doom-package-outdated-p (name) @@ -32,23 +31,24 @@ list, whose car is NAME, and cdr the current version list and latest version list of the package." (doom-refresh-packages) - (package-read-all-archive-contents) - (when (assq name package-alist) - (let* ((old-version - (package-desc-version (cadr (or (assq name package-alist) - (assq name package--builtins))))) - (new-version - (cond ((doom-package-quelpa-p name) - (let ((recipe (assq name quelpa-cache)) - (dir (f-expand (symbol-name name) quelpa-build-dir)) - (inhibit-message t)) - (or (quelpa-checkout recipe dir) - old-version))) - - ((doom-package-elpa-p name) - (package-desc-version (cadr (assq name package-archive-contents))))))) - (unless (version-list-<= new-version old-version) - (cons name old-version new-version))))) + (let ((pkg (or (assq name package-alist) + (assq name package--builtins)))) + (when pkg + (let* ((old-version (package-desc-version (cadr pkg))) + (new-version + (pcase (doom-package-backend name) + ('quelpa + (let ((recipe (assq name quelpa-cache)) + (dir (f-expand (symbol-name name) quelpa-build-dir)) + (inhibit-message t)) + (or (and (quelpa-setup-p) (quelpa-checkout recipe dir)) + old-version))) + ('elpa + (package-desc-version (cadr (assq name package-archive-contents))))))) + (when (stringp new-version) + (setq new-version (version-to-list new-version))) + (when (version-list-< old-version new-version) + (list name old-version new-version)))))) ;;;###autoload (defun doom-get-packages (&optional backend) @@ -58,12 +58,14 @@ the quelpa recipe (if any). BACKEND can be 'quelpa or 'elpa, and will instruct this function to return only the packages relevant to that backend." - (doom-reload) + (doom-read-packages t) (unless (quelpa-setup-p) (error "Could not initialize quelpa")) - (--map (cons it (assq it quelpa-cache)) - (-intersection (package--find-non-dependencies) - (append (mapcar 'car doom-packages) doom-protected-packages)))) + (-non-nil + (--map (or (assq it doom-packages) + (list (car (assq it package-alist)))) + (append (mapcar 'car doom-packages) + doom-protected-packages)))) ;;;###autoload (defun doom-get-outdated-packages () @@ -76,16 +78,15 @@ be fed to `doom/packages-update'." (defun doom-get-orphaned-packages () "Return a list of packages that are no longer needed or depended on. Can be fed to `doom/packages-delete'." - (doom-reload) - (let ((package-selected-packages - (append (mapcar 'car doom-packages) doom-protected-packages))) + (doom-read-packages t) + (let ((package-selected-packages (append (mapcar 'car doom-packages) doom-protected-packages))) (package--removable-packages))) ;;;###autoload (defun doom-get-packages-to-install () "Return a list of packages that aren't installed, but need to be. Used by `doom/packages-install'." - (doom-reload) + (doom-read-packages t) (--remove (assq (car it) package-alist) (append doom-packages (-map 'list doom-protected-packages)))) @@ -106,15 +107,29 @@ fed to `doom/packages-delete'." ;; Main functions ;; -(defun doom-install-package (name &optional recipe) +(defun doom-install-package (name &optional plist) "Installs package NAME with optional quelpa RECIPE (see `quelpa-recipe' for an example; the package name can be omitted)." (doom-refresh-packages) (when (package-installed-p name) - (error "%s is already installed" name)) - (cond (recipe (quelpa (plist-get plist :recipe))) + (error "%s is already installed, skipping" name)) + (when (plist-get plist :disabled) + (error "%s is disabled, skipping" name)) + (when (plist-get plist :load-path) + (error "%s has a local load-path, skipping" name)) + (cond ((plist-get plist :recipe) + (let ((recipe (plist-get plist :recipe))) + (when (and recipe (= 0 (mod (length recipe) 2))) + (setq recipe (cons name recipe))) + (quelpa recipe))) (t (package-install name))) - (add-to-list 'doom-packages (cons name recipe)) + (cl-pushnew (cons name plist) doom-packages :key 'car) + (when (plist-member plist :setup) + (let ((setup (plist-get plist :setup))) + (when (listp setup) + (setq setup (assq (doom-os) setup))) + (when setup + (async-shell-command setup)))) (package-installed-p name)) (defun doom-update-package (name) @@ -125,18 +140,18 @@ appropriate." (error "%s isn't installed" name)) (when (doom-package-outdated-p name) (let (quelpa-modified-p) - (cond ((doom-package-quelpa-p name) - (let ((quelpa-upgrade-p t)) - (quelpa it) - (setq quelpa-modified-p t))) - (t - (let ((desc (cadr (assq name package-alist))) - (archive (cadr (assq name package-archive-contents)))) - (package-install-from-archive archive) - (delete-directory (package-desc-dir desc) t)) - (package-install name)))) - (when quelpa-modified-p - (quelpa-save-cache)) + (pcase (doom-package-backend name) + ('quelpa + (let ((quelpa-upgrade-p t)) + (quelpa it) + (setq quelpa-modified-p t))) + ('elpa + (let ((desc (cadr (assq name package-alist))) + (archive (cadr (assq name package-archive-contents)))) + (package-install-from-archive archive) + (delete-directory (package-desc-dir desc) t)))) + (when quelpa-modified-p + (quelpa-save-cache))) (version-list-= (package-desc-version (cadr (assq name package-alist))) (package-desc-version (cadr (assq name package-archive-contents)))))) @@ -165,13 +180,17 @@ appropriate." (message "No packages to install!")) ((not (y-or-n-p - (format "%s packages will be installed:\n%s\n\nProceed?" + (format "%s packages will be installed:\n\n%s\n\nProceed?" (length packages) - (mapconcat (lambda (pkg) (format "+ %s (%s)" - (symbol-name (car pkg)) - (cond ((cdr pkg) "QUELPA") - (t "ELPA")))) - packages "\n")))) + (mapconcat (lambda (pkg) + (format "+ %s (%s)" + (car pkg) + (cond ((plist-get (cdr pkg) :recipe) "QUELPA") + (t "ELPA")))) + (--sort (string-lessp (symbol-name (car it)) + (symbol-name (car other))) + packages) + "\n")))) (message "Aborted!")) (t @@ -180,10 +199,9 @@ appropriate." (dolist (pkg packages) (condition-case ex (doom-message "%s %s (%s)" - (let ((plist (cdr pkg))) - (if (doom-install-package (car pkg) (cdr pkg)) - "Installed" - "Failed to install")) + (if (doom-install-package (car pkg) (cdr pkg)) + "Installed" + "Failed to install") pkg (cond ((cdr pkg) "QUELPA") (t "ELPA"))) @@ -201,27 +219,29 @@ appropriate." (message "Everything is up-to-date")) ((not (y-or-n-p - (format "%s packages will be updated:\n%s\n\nProceed?" + (format "%s packages will be updated:\n\n%s\n\nProceed?" (length packages) - (mapconcat (lambda (pkg) (format "%s: %s -> %s" - (car pkg) - (car (cdr pkg)) - (cdr (cdr pkg)))) - (--sort (string-lessp (symbol-name (car it)) - (symbol-name (car other))) - outdated-packages) ", ")))) + (mapconcat + (lambda (pkg) (format "+ %s %s -> %s\t%s" + (s-pad-right 20 " " (symbol-name (car pkg))) + (car (cdr pkg)) + (car (cdr (cdr pkg))))) + (--sort (string-lessp (symbol-name (car it)) + (symbol-name (car other))) + packages) + "\n")))) (message "Aborted!")) (t (dolist (pkg packages) (condition-case ex (doom-message "%s %s" - (if (doom-update-package pkg) + (if (doom-update-package (car pkg)) "Updated" "Failed to update") - pkg) + (car pkg)) (error - (doom-message "Error installing %s: %s" pkg ex)))) + (doom-message "Error installing %s: %s" (car pkg) ex)))) (doom-message "Finished!"))))) @@ -234,7 +254,7 @@ appropriate." (message "No unused packages to remove")) ((not (y-or-n-p - (format "%s packages will be deleted:\n%s\n\nProceed?" + (format "%s packages will be deleted:\n\n%s\n\nProceed?" (length packages) (mapconcat 'symbol-name (-sort 'string-lessp packages) ", ")))) (message "Aborted!")) @@ -253,10 +273,10 @@ appropriate." (doom-message "Finished!"))))) ;;;###autoload -(defalias 'doom/package-install 'package-install) +(defalias 'doom/install-package 'package-install) ;;;###autoload -(defun doom/package-delete (&optional package) +(defun doom/delete-package (&optional package) (interactive (list (completing-read "Delete package: " (doom-get-packages)))) (if (package-installed-p package) @@ -268,7 +288,7 @@ appropriate." (message "%s isn't installed" package))) ;;;###autoload -(defun doom/package-update (&optional package) +(defun doom/update-package (&optional package) (interactive (list (completing-read "Update package: " (doom-get-packages)))) (if (doom-package-outdated-p package) diff --git a/core/core-editor.el b/core/core-editor.el index eaa967aad..cc5d31fa3 100644 --- a/core/core-editor.el +++ b/core/core-editor.el @@ -184,6 +184,8 @@ (package! smart-forward :commands (smart-up smart-down smart-backward smart-forward)) +(package! smex :commands smex) + (package! swiper :commands (swiper swiper-all)) (package! wgrep diff --git a/core/core-lib.el b/core/core-lib.el index 4d148954e..dc9c11181 100644 --- a/core/core-lib.el +++ b/core/core-lib.el @@ -7,6 +7,21 @@ (require 'f) (require 's) +(package! anaphora + :commands (awhen aif acond awhile)) + +(package! async + :commands (async-start + async-start-process + async-byte-recompile-directory)) + +(package! persistent-soft + :preface (defvar pcache-directory (concat doom-cache-dir "pcache/")) + :commands (persistent-soft-exists-p + persistent-soft-fetch + persistent-soft-flush + persistent-soft-store)) + ;; ;; Library @@ -68,35 +83,36 @@ Examples: (defmacro associate! (mode &rest plist) "Associate a major or minor mode to certain patterns and project files." (declare (indent 1)) - (let* ((minor (plist-get plist :minor)) - (in (plist-get plist :in)) - (match (plist-get plist :match)) - (files (plist-get plist :files)) - (pred (plist-get plist :when))) - (cond ((or files in pred) - (when (and files (not (or (listp files) (stringp files)))) - (user-error "associate! :files expects a string or list of strings")) - (let ((hook-name (intern (format "doom--init-mode-%s" mode)))) - (macroexp-progn - (list `(defun ,hook-name () - (when (and ,(if match `(if buffer-file-name (string-match-p ,match buffer-file-name)) t) - (or ,(not files) - (and (boundp ',mode) - (not ,mode) - (doom-project-has-files ,@(-list files)))) - (or (not ,pred) - (funcall ,pred buffer-file-name))) - (,mode 1))) - (if (and in (listp in)) - (macroexp-progn - (mapcar (lambda (h) `(add-hook ',h ',hook-name)) - (mapcar (lambda (m) (intern (format "%s-hook" m))) in))) - `(add-hook 'find-file-hook ',hook-name)))))) - (match - `(add-to-list ',(if minor 'doom-auto-minor-mode-alist 'auto-mode-alist) - (cons ,match ',mode))) - (t (user-error "associate! invalid rules for mode [%s] (in %s) (match %s) (files %s)" - mode in match files))))) + (unless noninteractive + (let* ((minor (plist-get plist :minor)) + (in (plist-get plist :in)) + (match (plist-get plist :match)) + (files (plist-get plist :files)) + (pred (plist-get plist :when))) + (cond ((or files in pred) + (when (and files (not (or (listp files) (stringp files)))) + (user-error "associate! :files expects a string or list of strings")) + (let ((hook-name (intern (format "doom--init-mode-%s" mode)))) + (macroexp-progn + (list `(defun ,hook-name () + (when (and ,(if match `(if buffer-file-name (string-match-p ,match buffer-file-name)) t) + (or ,(not files) + (and (boundp ',mode) + (not ,mode) + (doom-project-has-files ,@(-list files)))) + (or (not ,pred) + (funcall ,pred buffer-file-name))) + (,mode 1))) + (if (and in (listp in)) + (macroexp-progn + (mapcar (lambda (h) `(add-hook ',h ',hook-name)) + (mapcar (lambda (m) (intern (format "%s-hook" m))) in))) + `(add-hook 'find-file-hook ',hook-name)))))) + (match + `(add-to-list ',(if minor 'doom-auto-minor-mode-alist 'auto-mode-alist) + (cons ,match ',mode))) + (t (user-error "associate! invalid rules for mode [%s] (in %s) (match %s) (files %s)" + mode in match files)))))) ;; Register keywords for proper indentation (see `map!') (put ':prefix 'lisp-indent-function 'defun) @@ -159,97 +175,108 @@ Example (:when IS-MAC :n \"M-s\" 'some-fn :i \"M-o\" (lambda (interactive) (message \"Hi\"))))" - (let ((keymaps (if (boundp 'keymaps) keymaps)) - (prefix (if (boundp 'prefix) prefix)) - (state-map '(("n" . normal) - ("v" . visual) - ("i" . insert) - ("e" . emacs) - ("o" . operator) - ("m" . motion) - ("r" . replace))) - local key def states forms) - (while rest - (setq key (pop rest)) - (cond - ;; it's a sub expr - ((listp key) - (push (macroexpand `(map! ,@key)) forms)) + (unless noninteractive + (let ((keymaps (if (boundp 'keymaps) keymaps)) + (prefix (if (boundp 'prefix) prefix)) + (state-map '(("n" . normal) + ("v" . visual) + ("i" . insert) + ("e" . emacs) + ("o" . operator) + ("m" . motion) + ("r" . replace))) + local key def states forms) + (while rest + (setq key (pop rest)) + (cond + ;; it's a sub expr + ((listp key) + (push (macroexpand `(map! ,@key)) forms)) - ;; it's a flag - ((keywordp key) - (when (cond ((eq key :leader) - (push (or +evil-leader ",") rest)) - ((eq key :localleader) - (push (or +evil-localleader "\\") rest))) - (setq key :prefix)) - (pcase key - (:prefix (setq prefix (concat prefix (kbd (pop rest))))) - (:map (setq keymaps (-list (pop rest)))) - (:unset `(,(macroexpand `(map! ,(kbd (pop rest)))))) - (:after (prog1 `((after! ,(pop rest) ,(macroexpand `(map! ,@rest)))) (setq rest '()))) - (:when (prog1 `((if ,(pop rest) ,(macroexpand `(map! ,@rest)))) (setq rest '()))) - (:unless (prog1 `((if (not ,(pop rest)) ,(macroexpand `(map! ,@rest)))) (setq rest '()))) - (otherwise ; might be a state prefix - (mapc (lambda (letter) - (cond ((assoc letter state-map) - (push (cdr (assoc letter state-map)) states)) - ((string= letter "L") - (setq local t)) - (t (user-error "Invalid mode prefix %s in key %s" letter key)))) - (split-string (substring (symbol-name key) 1) "" t)) - (unless states - (user-error "Unrecognized keyword %s" key)) - (when (assoc "L" states) - (cond ((= (length states) 1) - (user-error "local keybinding for %s must accompany another state" key)) - ((> (length keymaps) 0) - (user-error "local keybinding for %s cannot accompany a keymap" key))))))) + ;; it's a flag + ((keywordp key) + (when (cond ((eq key :leader) + (push (or +evil-leader ",") rest)) + ((eq key :localleader) + (push (or +evil-localleader "\\") rest))) + (setq key :prefix)) + (pcase key + (:prefix (setq prefix (concat prefix (kbd (pop rest))))) + (:map (setq keymaps (-list (pop rest)))) + (:unset `(,(macroexpand `(map! ,(kbd (pop rest)))))) + (:after (prog1 `((after! ,(pop rest) ,(macroexpand `(map! ,@rest)))) (setq rest '()))) + (:when (prog1 `((if ,(pop rest) ,(macroexpand `(map! ,@rest)))) (setq rest '()))) + (:unless (prog1 `((if (not ,(pop rest)) ,(macroexpand `(map! ,@rest)))) (setq rest '()))) + (otherwise ; might be a state prefix + (mapc (lambda (letter) + (cond ((assoc letter state-map) + (push (cdr (assoc letter state-map)) states)) + ((string= letter "L") + (setq local t)) + (t (user-error "Invalid mode prefix %s in key %s" letter key)))) + (split-string (substring (symbol-name key) 1) "" t)) + (unless states + (user-error "Unrecognized keyword %s" key)) + (when (assoc "L" states) + (cond ((= (length states) 1) + (user-error "local keybinding for %s must accompany another state" key)) + ((> (length keymaps) 0) + (user-error "local keybinding for %s cannot accompany a keymap" key))))))) - ;; It's a key-def pair - ((or (stringp key) - (characterp key) - (vectorp key)) - (unwind-protect - (catch 'skip - (when (stringp key) - (setq key (kbd key))) - (when prefix - (setq key (if (vectorp key) (vconcat prefix key) (concat prefix key)))) - (unless (> (length rest) 0) - (user-error "Map has no definition for %s" key)) - (setq def (pop rest)) - (push - (cond ((and keymaps states) - (throw 'skip 'evil) - (macroexp-progn - (mapcar (lambda (keymap) `(evil-define-key* ',states ,keymap ,key ,def)) - keymaps))) - (keymaps - (macroexp-progn - (mapcar (lambda (keymap) `(define-key ,keymap ,key ,def)) - keymaps))) - (states - (throw 'skip 'evil) - (macroexp-progn - (mapcar (lambda (state) - `(define-key - (evil-state-property ',state ,(if local :local-keymap :keymap) t) - ,key ,def)) - states))) - (t `(,(if local 'local-set-key 'global-set-key) - ,key ,def))) - forms)) - (setq states '() - local nil))) + ;; It's a key-def pair + ((or (stringp key) + (characterp key) + (vectorp key)) + (unwind-protect + (catch 'skip + (when (stringp key) + (setq key (kbd key))) + (when prefix + (setq key (if (vectorp key) (vconcat prefix key) (concat prefix key)))) + (unless (> (length rest) 0) + (user-error "Map has no definition for %s" key)) + (setq def (pop rest)) + (push + (cond ((and keymaps states) + (throw 'skip 'evil) + (macroexp-progn + (mapcar (lambda (keymap) `(evil-define-key* ',states ,keymap ,key ,def)) + keymaps))) + (keymaps + (macroexp-progn + (mapcar (lambda (keymap) `(define-key ,keymap ,key ,def)) + keymaps))) + (states + (throw 'skip 'evil) + (macroexp-progn + (mapcar (lambda (state) + `(define-key + (evil-state-property ',state ,(if local :local-keymap :keymap) t) + ,key ,def)) + states))) + (t `(,(if local 'local-set-key 'global-set-key) + ,key ,def))) + forms)) + (setq states '() + local nil))) - (t (user-error "Invalid key %s" key)))) - (macroexp-progn (reverse forms)))) + (t (user-error "Invalid key %s" key)))) + (macroexp-progn (reverse forms))))) -(when (or noninteractive doom-dont-load-p) - (defmacro add-hook! (&rest _)) - (defmacro associate! (&rest _)) - (defmacro map! (&rest _))) +(defun doom-os () + "Returns the OS: arch, debian, macos, general linux, cygwin or windows." + (let ((gnu-linux-p (eq system-type 'gnu/linux))) + (cond ((and gnu-linux-p (f-exists-p "/etc/arch-release")) + 'arch) + ((and gnu-linux-p (f-exists-p "/etc/debian_version")) + 'debian) + (gnu-linux-p + 'linux) + ((eq system-type 'darwin) + 'macos) + ((memq system-type '(windows-nt cygwin)) + 'windows) + (t (error "Unknown OS: %s" system-type))))) (provide 'core-lib) ;;; core-lib.el ends here diff --git a/core/core-packages.el b/core/core-packages.el index 26873952d..9e6d46150 100644 --- a/core/core-packages.el +++ b/core/core-packages.el @@ -32,18 +32,6 @@ submodule symbol, e.g. 'evil.") "Non-nil if doom's package system has been initialized or not. It may not be if you have byte-compiled your configuration (as intended).") -(defvar doom-dont-load-p nil - "If non-nil, run DOOM emacs in declarative mode, meaning: don't actually load -anything, just track what should be loaded. Useful for scanning packages and -loaded modules.") - -(defvar doom-reloading-p nil - "If non-nil, DOOM is reloading itself. Use this to determine to prevent -infinite recursion.") - -(defvar doom-prefer-el-p noninteractive - "If non-nil, load uncompiled .el config files.") - (defvar doom--base-load-path (append (list doom-core-dir doom-modules-dir) load-path) @@ -83,27 +71,26 @@ infinite recursion.") byte-compilation." (let (mode) (dolist (p packages) - (cond ((string-prefix-p ":" (symbol-name p)) + (cond ((keywordp p) (setq mode p)) ((not mode) (error "No namespace specified on `doom!' for %s" p)) (t (setq doom-enabled-modules (append doom-enabled-modules (list (cons mode p)))))))) - `(unless doom-dont-load-p - (let (file-name-handler-alist) - ,@(mapcar (lambda (pkg) `(load! ,(car pkg) ,(cdr pkg))) - doom-enabled-modules) + `(let (file-name-handler-alist) + ,@(mapcar (lambda (pkg) `(load! ,(car pkg) ,(cdr pkg))) + doom-enabled-modules) - (when (display-graphic-p) - (require 'server) - (unless (server-running-p) - (server-start))) + (when (display-graphic-p) + (require 'server) + (unless (server-running-p) + (server-start))) - ;; Prevent any auto-displayed text + benchmarking - (advice-add 'display-startup-echo-area-message :override 'ignore) - (message "Loaded %s packages in %s" - (- (length load-path) (length doom--base-load-path)) - (emacs-init-time))))) + ;; Prevent any auto-displayed text + benchmarking + (advice-add 'display-startup-echo-area-message :override 'ignore) + (message "Loaded %s packages in %s" + (- (length load-path) (length doom--base-load-path)) + (emacs-init-time)))) (defun doom-initialize (&optional force-p) "Initialize installed packages (using package.el) and ensure the core packages @@ -142,25 +129,6 @@ avoided to speed up startup." '(:ensure :pin)) (setq doom-init-p t))) -(defun doom-reload () - "Rereads the Emacs config, reloading `doom-packages' and -`doom-enabled-modules'." - (unless doom-reloading-p - (doom-initialize) - (let ((doom-prefer-el-p t) - (doom-dont-load-p t) - (doom-reloading-p t) - noninteractive) - (setq doom-enabled-modules nil - doom-packages nil) - (let ((load-fn (lambda (file) (load file :noerror (not doom-debug-mode) :nosuffix)))) - (mapc load-fn - (list (f-expand "init.el" doom-emacs-dir) - (f-expand "core.el" doom-core-dir))) - (mapc load-fn - (--map (doom-module-path (car it) (cdr it) "packages.el") - doom-enabled-modules)))))) - ;; ;; Macros @@ -170,6 +138,19 @@ avoided to speed up startup." (defvar __FILE__ nil "The full path of the currently loaded file (set by `load!')") (defvar __PACKAGE__ nil "The name of the current package.") +(defun __DIR__ () + (or __DIR__ + (and load-file-name (f-dirname load-file-name)) + (and buffer-file-name (f-dirname buffer-file-name)) + default-directory + (error "__DIR__ is unset"))) + +(defun __FILE__ () + (or __FILE__ + load-file-name + buffer-file-name + (error "__FILE__ is unset"))) + (defmacro use-package! (name &rest plist) "A `use-package' wrapper. It exists so configs can adhere to the naming conventions of DOOM emacs, as well as let-bind `__PACKAGE__' for the containing @@ -180,46 +161,31 @@ packages are deferred by default." (use-package ,name ,@plist))) (defmacro package! (name &rest plist) - "Wraps around `use-package' to declare a deferred package (unless otherwise -indicated), takes the same arguments, but adds a few custom properties: + "Declares a package. This does not actually load nor install them explicitly. + +If used in `doom-core-dir', this is a wrapper for `use-package!' (all packages +are deferred by default), and takes the same arguments as `use-package'. + +If used outside of `doom-core-dir', this macro is purely declarative and doesn't +call `use-package!'. These calls are parsed by package management functions, +such as `doom-read-packages'. + +Adds a few custom properties in either case: :recipe RECIPE Takes a MELPA-style recipe (see `quelpa-recipe' for an example); for packages to be installed from external sources. :pin ARCHIVE-NAME Instructs ELPA to only look for this package in ARCHIVE-NAME. e.g. \"org\". - :needs FEATURE Don't install this package if FEATURE isn't available. - -Also binds `__PACKAGE__` for PLIST forms to optionally use." + :needs FEATURE Don't install this package if FEATURE isn't available. Can be a + (:module . submodule) cons pair. + :setup CMD-OR-PCASE A command to run after install. Can be a pcase list, whose + car's are symbols of OSes that `doom-os' returns, and whose + cdr's are string shell commands." (declare (indent defun)) - (let ((recipe (cadr (memq :recipe plist))) - (pin (cadr (memq :pin plist))) - (lpath (cadr (memq :load-path plist))) - (dep (cadr (memq :needs plist)))) - (when (or (not dep) - (or (featurep dep) - (package-installed-p dep))) - (when (and recipe (= 0 (mod (length recipe) 2))) - (push name recipe)) - (if (not lpath) - (cl-pushnew (cons name recipe) doom-packages :key 'car) - (cl-pushnew lpath doom--base-load-path) - (setq recipe nil - pin nil)) - (when pin - (cl-pushnew (cons package (plist-get plist :pin)) package-pinned-packages :key 'car)) - (setq plist (use-package-plist-delete plist :recipe)) - (setq plist (use-package-plist-delete plist :pin)) - (unless doom-dont-load-p - `(use-package! ,name ,@plist))))) - -(defmacro require! (feature) - "Like `require', but will prefer uncompiled files if `doom-prefer-el-p' is -non-nil or this is a noninteractive session." - (let ((prefer-el-p (or doom-prefer-el-p noninteractive))) - `(require ',feature - ,(locate-file (concat (symbol-name feature) (if prefer-el-p ".el")) - load-path)))) + (mapc (lambda (key) (setq plist (use-package-plist-delete plist key))) + '(:recipe :pin :setup :needs)) + `(use-package! ,name ,@plist)) (defmacro load! (module &optional submodule file) "Load a module from `doom-modules-dir' when both MODULE and SUBMODULE is @@ -227,9 +193,6 @@ provided (both symbols). If FILE is non-nil, append it to the resulting path. If SUBMODULE is nil, MODULE is loaded relative to the current file (see `__DIR__'). When SUBMODULE is nil, FILE isn't used. -Will prefer uncompiled elisp sources if `doom-prefer-el-p' is non-nil or this is -an noninteractive session. - Examples: (load! :lang emacs-lisp) @@ -238,8 +201,7 @@ Examples: (load! +local-module) Loads +local-module.el relative to `__DIR__' or `doom-core-dir'." - (let ((prefer-el-p (or doom-prefer-el-p noninteractive)) - path file) + (let (path file) (cond ((null submodule) (setq path __DIR__ file (concat (symbol-name module) ".el"))) @@ -254,8 +216,7 @@ Examples: file (concat path file)) `(let ((__FILE__ ,file) (__DIR__ ,path)) - (load ,(if doom-prefer-el-p file (f-no-ext file)) - nil (not doom-debug-mode) ,doom-prefer-el-p)))) + (load ,(f-no-ext file) nil (not doom-debug-mode))))) (defun doom-module-path (module submodule &optional file) "Get the full path to a module: e.g. :lang emacs-lisp maps to @@ -276,8 +237,8 @@ Examples: ;; (defun doom/reload () - "Reload `load-path' by scanning all packages. Run this if you ran make update -or make clean outside of Emacs." + "Reload `load-path' by reinitializing package.el. Run this if you ran update +or delete packages from outside of Emacs." (interactive) (doom-initialize t) (message "Reloaded %s packages" (length package-alist))) @@ -291,29 +252,29 @@ In modules, checks for modules/*/autoload.el and modules/*/autoload/*.el. Rerun this whenever you modify your init.el (or use `make autoloads` from the command line)." (interactive) - (unless doom-reloading-p - (doom-reload) - (let ((generated-autoload-file (concat doom-local-dir "autoloads.el")) - (autoload-files - (append (-flatten (mapcar (lambda (dir) - (let ((auto-dir (f-expand "autoload" dir)) - (auto-file (f-expand "autoload.el" dir))) - (cond ((f-directory-p auto-dir) - (f-glob "*.el" auto-dir)) - ((f-exists-p auto-file) - auto-file)))) - (--map (doom-module-path (car it) (cdr it)) doom-enabled-modules))) - (f-glob "autoload/*.el" doom-core-dir)))) - (when (f-exists-p generated-autoload-file) - (f-delete generated-autoload-file) - (message "Deleted old autoloads.el")) - (dolist (file autoload-files) - (update-file-autoloads file) - (message "Scanned %s" (f-relative file doom-emacs-dir))) - (with-current-buffer (get-file-buffer generated-autoload-file) - (save-buffer) - (eval-buffer)) - (message "Done!")))) + (doom-read-packages nil t) + (let ((generated-autoload-file (concat doom-local-dir "autoloads.el")) + (autoload-files + (append (-flatten (mapcar (lambda (dir) + (let ((auto-dir (f-expand "autoload" dir)) + (auto-file (f-expand "autoload.el" dir))) + (cond ((f-directory-p auto-dir) + (f-glob "*.el" auto-dir)) + ((f-exists-p auto-file) + auto-file)))) + (--map (doom-module-path (car it) (cdr it)) + doom-enabled-modules))) + (f-glob "autoload/*.el" doom-core-dir)))) + (when (f-exists-p generated-autoload-file) + (f-delete generated-autoload-file) + (message "Deleted old autoloads.el")) + (dolist (file autoload-files) + (update-file-autoloads file) + (message "Scanned %s" (f-relative file doom-emacs-dir))) + (with-current-buffer (get-file-buffer generated-autoload-file) + (save-buffer) + (eval-buffer)) + (message "Done!"))) (defun doom/byte-compile (&optional comprehensive-p) "Byte (re)compile the important files in your emacs configuration (i.e. @@ -323,7 +284,7 @@ a lot from this. If COMPREHENSIVE-P is non-nil, then compile modules/*/*/*.el (except for packages.el files) -- this will likely take a long time." (interactive) - (doom-reload) + (doom-read-packages) (let ((targets (append (list (f-expand "init.el" doom-emacs-dir) (f-expand "core.el" doom-core-dir)) @@ -338,18 +299,98 @@ packages.el files) -- this will likely take a long time." doom-enabled-modules)))) (n 0) results) - (mapc (lambda (file) - (push (cons (f-relative file doom-emacs-dir) - (when (byte-recompile-file file nil 0) - (setq n (1+ n)) - t)) - results)) - targets) + (dolist (file targets) + (push (cons (f-relative file doom-emacs-dir) + (when (byte-recompile-file file nil 0) + (setq n (1+ n)) + t)) + results)) (when noninteractive (when targets (message "\n")) - (message "Compiling %s files:\n%s" n + (message "Compiled %s files:\n%s" n (mapconcat (lambda (file) (concat "+ " (if (cdr file) "SUCCESS" "FAIL") ": " (car file))) (reverse results) "\n"))))) + +;; +;; Package parsing +;; + +(defun doom--parse-forms (sym forms) + (let ((result-forms (and (boundp 'result-forms) result-forms))) + (dolist (form forms) + (cond ((eq (car-safe form) sym) + (push (cdr-safe form) result-forms)) + + ((and (listp form) + (not (-cons-pair? form))) + (setq result-forms (doom--parse-forms sym form))))) + result-forms)) + +(defun doom--parse-file-forms (sym file) + (declare (indent defun)) + (unless (f-exists-p file) + (error "%s does not exist" file)) + (unless (symbolp sym) + (error "%s is not a valid symbol" sym)) + (let (forms) + (with-temp-buffer + (insert "(setq forms '(\n") + (insert-file-contents file) + (goto-char (point-max)) + (insert "\n))") + (eval-buffer)) + (doom--parse-forms sym forms))) + +(defun doom--strip-property (plist property) + (let (forms) + (while (and plist (not (eq (car plist) property))) + (setq forms (append forms (list (pop plist))))) + (pop plist) + (while (and plist (not (keywordp (car plist)))) + (pop plist)) + (when plist + (setq forms (append forms plist))) + forms)) + +;;;###autoload +(defun doom-read-packages (&optional force-p nopackages) + "Parses your Emacs config to keep track of packages declared with `package!' +in `doom-packages' and enabled modules in `doom-enabled-modules'." + (doom-initialize) + (when (or force-p (not doom-enabled-modules) (not doom-packages)) + (setq doom-enabled-modules + (let (paths mode enabled-modules) + (--each (doom--parse-file-forms 'doom! (f-expand "init.el" doom-emacs-dir)) + (dolist (module it) + (cond ((keywordp module) + (setq mode module)) + ((not mode) + (error "Malformed doom! call: no namespace for %s" module)) + (t + (push (cons mode module) enabled-modules))))) + enabled-modules)) + + (unless nopackages + (setq package-pinned-packages nil + doom-packages nil) + (mapc (lambda (pkg) (cl-pushnew pkg doom-packages :key 'car)) + (mapcar (lambda (args) + (mapc (lambda (keyword) (setq args (doom--strip-property args keyword))) + '(:preface :ensure :requires :no-require :bind :bind* :bind-keymap + :bind-keymap* :interpreter :mode :commands :defines :functions + :defer :init :after :demand :config :diminish :delight)) + args) + (--sort (string-greaterp (symbol-name (car it)) + (symbol-name (car other))) + (-flatten-n + 1 (mapcar (lambda (file) + (when (f-exists-p file) + (doom--parse-file-forms 'package! file))) + (append (f-glob "core*.el" doom-core-dir) + (--map (doom-module-path (car it) (cdr it) "packages.el") + doom-enabled-modules))))))) + t))) + (provide 'core-packages) ;;; core-packages.el ends here diff --git a/core/core.el b/core/core.el index f7f0eb8b9..e36c75245 100644 --- a/core/core.el +++ b/core/core.el @@ -130,30 +130,12 @@ enable multiple minor modes for the same regexp.") (require 'core-lib) (require 'autoloads (concat doom-local-dir "autoloads.el") t) (unless noninteractive - (package! anaphora - :commands (awhen aif acond awhile)) - - (package! async - :commands (async-start - async-start-process - async-byte-recompile-directory)) - - (package! persistent-soft - :preface (defvar pcache-directory (concat doom-cache-dir "pcache/")) - :commands (persistent-soft-exists-p - persistent-soft-fetch - persistent-soft-flush - persistent-soft-store)) - - (package! smex :commands smex) - - ;; - (require! core-set) ; a centralized config system; provides `set!' - (require! core-states) ; TODO - (require! core-ui) ; draw me like one of your French editors - (require! core-popups) ; taming sudden yet inevitable windows - (require! core-editor) ; baseline configuration for text editing - (require! core-projects) ; making Emacs project-aware + (require 'core-set) ; a centralized config system; provides `set!' + (require 'core-states) ; TODO + (require 'core-ui) ; draw me like one of your French editors + (require 'core-popups) ; taming sudden yet inevitable windows + (require 'core-editor) ; baseline configuration for text editing + (require 'core-projects) ; making Emacs project-aware ;; We check last as a promise that the core files won't use autoloaded ;; functions. If they do, it shouldn't be autoloaded!