You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1599 lines
59 KiB
1599 lines
59 KiB
;;; use-package-core.el --- A configuration macro for simplifying your .emacs -*- lexical-binding: t; -*- |
|
|
|
;; Copyright (C) 2012-2017 John Wiegley |
|
|
|
;; Author: John Wiegley <johnw@newartisans.com> |
|
;; Maintainer: John Wiegley <johnw@newartisans.com> |
|
;; Created: 17 Jun 2012 |
|
;; Modified: 29 Nov 2017 |
|
;; Version: 2.4.1 |
|
;; Package-Requires: ((emacs "24.3")) |
|
;; Keywords: dotemacs startup speed config package |
|
;; URL: https://github.com/jwiegley/use-package |
|
|
|
;; This program is free software; you can redistribute it and/or |
|
;; modify it under the terms of the GNU General Public License as |
|
;; published by the Free Software Foundation; either version 3, or (at |
|
;; your option) any later version. |
|
|
|
;; This program is distributed in the hope that it will be useful, but |
|
;; WITHOUT ANY WARRANTY; without even the implied warranty of |
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
;; General Public License for more details. |
|
|
|
;; You should have received a copy of the GNU General Public License |
|
;; along with GNU Emacs; see the file COPYING. If not, write to the |
|
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
|
;; Boston, MA 02111-1307, USA. |
|
|
|
;;; Commentary: |
|
|
|
;; The `use-package' declaration macro allows you to isolate package |
|
;; configuration in your ".emacs" in a way that is performance-oriented and, |
|
;; well, just tidy. I created it because I have over 80 packages that I use |
|
;; in Emacs, and things were getting difficult to manage. Yet with this |
|
;; utility my total load time is just under 1 second, with no loss of |
|
;; functionality! |
|
;; |
|
;; Please see README.md from the same repository for documentation. |
|
|
|
;;; Code: |
|
|
|
(require 'bytecomp) |
|
(require 'cl-lib) |
|
(require 'tabulated-list) |
|
|
|
(if (and (eq emacs-major-version 24) (eq emacs-minor-version 3)) |
|
(defsubst hash-table-keys (hash-table) |
|
"Return a list of keys in HASH-TABLE." |
|
(cl-loop for k being the hash-keys of hash-table collect k)) |
|
(eval-when-compile (require 'subr-x))) |
|
|
|
(eval-when-compile |
|
(require 'regexp-opt)) |
|
|
|
(defgroup use-package nil |
|
"A use-package declaration for simplifying your `.emacs'." |
|
:group 'startup) |
|
|
|
(defconst use-package-version "2.4.1" |
|
"This version of use-package.") |
|
|
|
(defcustom use-package-keywords |
|
'(:disabled |
|
:load-path |
|
:requires |
|
:defines |
|
:functions |
|
:preface |
|
:if :when :unless |
|
:no-require |
|
:catch |
|
:after |
|
:custom |
|
:custom-face |
|
:bind |
|
:bind* |
|
:bind-keymap |
|
:bind-keymap* |
|
:interpreter |
|
:mode |
|
:magic |
|
:magic-fallback |
|
:hook |
|
;; Any other keyword that also declares commands to be autoloaded (such as |
|
;; :bind) must appear before this keyword. |
|
:commands |
|
:init |
|
:defer |
|
:demand |
|
:load |
|
;; This must occur almost last; the only forms which should appear after |
|
;; are those that must happen directly after the config forms. |
|
:config) |
|
"The set of valid keywords, in the order they are processed in. |
|
The order of this list is *very important*, so it is only |
|
advisable to insert new keywords, never to delete or reorder |
|
them. Further, attention should be paid to the NEWS.md if the |
|
default order ever changes, as they may have subtle effects on |
|
the semantics of use-package declarations and may necessitate |
|
changing where you had inserted a new keyword earlier. |
|
|
|
Note that `:disabled' is special in this list, as it causes |
|
nothing at all to happen, even if the rest of the use-package |
|
declaration is incorrect." |
|
:type '(repeat symbol) |
|
:group 'use-package) |
|
|
|
(defcustom use-package-deferring-keywords |
|
'(:bind-keymap |
|
:bind-keymap* |
|
:commands) |
|
"Unless `:demand' is used, keywords in this list imply deferred loading. |
|
The reason keywords like `:hook' are not in this list is that |
|
they only imply deferred loading if they reference actual |
|
function symbols that can be autoloaded from the module; whereas |
|
the default keywords provided here always defer loading unless |
|
otherwise requested." |
|
:type '(repeat symbol) |
|
:group 'use-package) |
|
|
|
(defcustom use-package-ignore-unknown-keywords nil |
|
"If non-nil, issue warning instead of error when unknown |
|
keyword is encountered. The unknown keyword and its associated |
|
arguments will be ignored in the `use-package' expansion." |
|
:type 'boolean |
|
:group 'use-package) |
|
|
|
(defcustom use-package-verbose nil |
|
"Whether to report about loading and configuration details. |
|
If you customize this, then you should require the `use-package' |
|
feature in files that use `use-package', even if these files only |
|
contain compiled expansions of the macros. If you don't do so, |
|
then the expanded macros do their job silently." |
|
:type '(choice (const :tag "Quiet, without catching errors" errors) |
|
(const :tag "Quiet" nil) |
|
(const :tag "Verbose" t) |
|
(const :tag "Debug" debug)) |
|
:group 'use-package) |
|
|
|
(defcustom use-package-check-before-init nil |
|
"If non-nil, check that package exists before executing its `:init' block. |
|
This check is performed by calling `locate-library'." |
|
:type 'boolean |
|
:group 'use-package) |
|
|
|
(defcustom use-package-always-defer nil |
|
"If non-nil, assume `:defer t' unless `:demand' is used. |
|
See also `use-package-defaults', which uses this value." |
|
:type 'boolean |
|
:group 'use-package) |
|
|
|
(defcustom use-package-always-demand nil |
|
"If non-nil, assume `:demand t' unless `:defer' is used. |
|
See also `use-package-defaults', which uses this value." |
|
:type 'boolean |
|
:group 'use-package) |
|
|
|
(defcustom use-package-defaults |
|
'(;; this '(t) has special meaning; see `use-package-handler/:config' |
|
(:config '(t) t) |
|
(:init nil t) |
|
(:catch t (lambda (name args) |
|
(not use-package-expand-minimally))) |
|
(:defer use-package-always-defer |
|
(lambda (name args) |
|
(and use-package-always-defer |
|
(not (plist-member args :defer)) |
|
(not (plist-member args :demand))))) |
|
(:demand use-package-always-demand |
|
(lambda (name args) |
|
(and use-package-always-demand |
|
(not (plist-member args :defer)) |
|
(not (plist-member args :demand)))))) |
|
"Default values for specified `use-package' keywords. |
|
Each entry in the alist is a list of three elements: |
|
The first element is the `use-package' keyword. |
|
|
|
The second is a form that can be evaluated to get the default |
|
value. It can also be a function that will receive the name of |
|
the use-package declaration and the keyword plist given to |
|
`use-package', in normalized form. The value it returns should |
|
also be in normalized form (which is sometimes *not* what one |
|
would normally write in a `use-package' declaration, so use |
|
caution). |
|
|
|
The third element is a form that can be evaluated to determine |
|
whether or not to assign a default value; if it evaluates to nil, |
|
then the default value is not assigned even if the keyword is not |
|
present in the `use-package' form. This third element may also be |
|
a function, in which case it receives the name of the package (as |
|
a symbol) and a list of keywords (in normalized form). It should |
|
return nil or non-nil depending on whether defaulting should be |
|
attempted." |
|
:type `(repeat |
|
(list (choice :tag "Keyword" |
|
,@(mapcar #'(lambda (k) (list 'const k)) |
|
use-package-keywords)) |
|
(choice :tag "Default value" sexp function) |
|
(choice :tag "Enable if non-nil" sexp function))) |
|
:group 'use-package) |
|
|
|
(defcustom use-package-merge-key-alist |
|
'((:if . (lambda (new old) `(and ,new ,old))) |
|
(:after . (lambda (new old) `(:all ,new ,old))) |
|
(:defer . (lambda (new old) old)) |
|
(:bind . (lambda (new old) (append new (list :break) old)))) |
|
"Alist of keys and the functions used to merge multiple values. |
|
For example, if the following form is provided: |
|
|
|
(use-package foo :if pred1 :if pred2) |
|
|
|
Then based on the above defaults, the merged result will be: |
|
|
|
(use-package foo :if (and pred1 pred2)) |
|
|
|
This is done so that, at the stage of invoking handlers, each |
|
handler is called only once." |
|
:type `(repeat |
|
(cons (choice :tag "Keyword" |
|
,@(mapcar #'(lambda (k) (list 'const k)) |
|
use-package-keywords) |
|
(const :tag "Any" t)) |
|
function)) |
|
:group 'use-package) |
|
|
|
(defcustom use-package-hook-name-suffix "-hook" |
|
"Text append to the name of hooks mentioned by :hook. |
|
Set to nil if you don't want this to happen; it's only a |
|
convenience." |
|
:type '(choice string (const :tag "No suffix" nil)) |
|
:group 'use-package) |
|
|
|
(defcustom use-package-minimum-reported-time 0.1 |
|
"Minimal load time that will be reported. |
|
Note that `use-package-verbose' has to be set to a non-nil value |
|
for anything to be reported at all." |
|
:type 'number |
|
:group 'use-package) |
|
|
|
(defcustom use-package-inject-hooks nil |
|
"If non-nil, add hooks to the `:init' and `:config' sections. |
|
In particular, for a given package `foo', the following hooks |
|
become available: |
|
|
|
`use-package--foo--pre-init-hook' |
|
`use-package--foo--post-init-hook' |
|
`use-package--foo--pre-config-hook' |
|
`use-package--foo--post-config-hook' |
|
|
|
This way, you can add to these hooks before evaluation of a |
|
`use-package` declaration, and exercise some control over what |
|
happens. |
|
|
|
NOTE: These hooks are run even if the user does not specify an |
|
`:init' or `:config' block, and they will happen at the regular |
|
time when initialization and configuration would have been |
|
performed. |
|
|
|
NOTE: If the `pre-init' hook return a nil value, that block's |
|
user-supplied configuration is not evaluated, so be certain to |
|
return t if you only wish to add behavior to what the user had |
|
specified." |
|
:type 'boolean |
|
:group 'use-package) |
|
|
|
(defcustom use-package-expand-minimally nil |
|
"If non-nil, make the expanded code as minimal as possible. |
|
This disables: |
|
|
|
- Printing to the *Messages* buffer of slowly-evaluating forms |
|
- Capturing of load errors (normally redisplayed as warnings) |
|
- Conditional loading of packages (load failures become errors) |
|
|
|
The main advantage to this variable is that, if you know your |
|
configuration works, it will make the byte-compiled file as |
|
minimal as possible. It can also help with reading macro-expanded |
|
definitions, to understand the main intent of what's happening." |
|
:type 'boolean |
|
:group 'use-package) |
|
|
|
(defcustom use-package-form-regexp-eval |
|
`(concat ,(eval-when-compile |
|
(concat "^\\s-*(" |
|
(regexp-opt '("use-package" "require") t) |
|
"\\s-+\\(")) |
|
(or (bound-and-true-p lisp-mode-symbol-regexp) |
|
"\\(?:\\sw\\|\\s_\\|\\\\.\\)+") "\\)") |
|
"Sexp providing regexp for finding use-package forms in user files. |
|
This is used by `use-package-jump-to-package-form' and |
|
`use-package-enable-imenu-support'." |
|
:type 'sexp |
|
:group 'use-package) |
|
|
|
(defcustom use-package-enable-imenu-support nil |
|
"If non-nil, cause imenu to see `use-package' declarations. |
|
This is done by adjusting `lisp-imenu-generic-expression' to |
|
include support for finding `use-package' and `require' forms. |
|
|
|
Must be set before loading use-package." |
|
:type 'boolean |
|
:set |
|
#'(lambda (_sym value) |
|
(eval-after-load 'lisp-mode |
|
(if value |
|
`(add-to-list 'lisp-imenu-generic-expression |
|
(list "Packages" ,use-package-form-regexp-eval 2)) |
|
`(setq lisp-imenu-generic-expression |
|
(remove (list "Packages" ,use-package-form-regexp-eval 2) |
|
lisp-imenu-generic-expression))))) |
|
:group 'use-package) |
|
|
|
(defconst use-package-font-lock-keywords |
|
'(("(\\(use-package\\)\\_>[ \t']*\\(\\(?:\\sw\\|\\s_\\)+\\)?" |
|
(1 font-lock-keyword-face) |
|
(2 font-lock-constant-face nil t)))) |
|
|
|
(font-lock-add-keywords 'emacs-lisp-mode use-package-font-lock-keywords) |
|
|
|
(defcustom use-package-compute-statistics nil |
|
"If non-nil, compute statistics concerned use-package declarations. |
|
View the statistical report using `use-package-report'. Note that |
|
if this option is enabled, you must require `use-package' in your |
|
user init file at loadup time, or you will see errors concerning |
|
undefined variables." |
|
:type 'boolean |
|
:group 'use-package) |
|
|
|
(defvar use-package-statistics (make-hash-table)) |
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
;; |
|
;;; Utility functions |
|
;; |
|
|
|
(defsubst use-package-error (msg) |
|
"Report MSG as an error, so the user knows it came from this package." |
|
(error "use-package: %s" msg)) |
|
|
|
(defsubst use-package-concat (&rest elems) |
|
"Delete all empty lists from ELEMS (nil or (list nil)), and append them." |
|
(apply #'append (delete nil (delete (list nil) elems)))) |
|
|
|
(defsubst use-package-non-nil-symbolp (sym) |
|
(and sym (symbolp sym))) |
|
|
|
(defsubst use-package-as-symbol (string-or-symbol) |
|
"If STRING-OR-SYMBOL is already a symbol, return it. Otherwise |
|
convert it to a symbol and return that." |
|
(if (symbolp string-or-symbol) string-or-symbol |
|
(intern string-or-symbol))) |
|
|
|
(defsubst use-package-as-string (string-or-symbol) |
|
"If STRING-OR-SYMBOL is already a string, return it. Otherwise |
|
convert it to a string and return that." |
|
(if (stringp string-or-symbol) string-or-symbol |
|
(symbol-name string-or-symbol))) |
|
|
|
(defsubst use-package-regex-p (re) |
|
"Return t if RE is some regexp-like thing." |
|
(or (and (listp re) (eq (car re) 'rx)) |
|
(stringp re))) |
|
|
|
(defun use-package-normalize-regex (re) |
|
"Given some regexp-like thing in RE, resolve to a regular expression." |
|
(cond |
|
((and (listp re) (eq (car re) 'rx)) (eval re)) |
|
((stringp re) re) |
|
(t (error "Not recognized as regular expression: %s" re)))) |
|
|
|
(defsubst use-package-is-pair (x car-pred cdr-pred) |
|
"Return non-nil if X is a cons satisfying the given predicates. |
|
CAR-PRED and CDR-PRED are applied to X's `car' and `cdr', |
|
respectively." |
|
(and (consp x) |
|
(funcall car-pred (car x)) |
|
(funcall cdr-pred (cdr x)))) |
|
|
|
(defun use-package-as-mode (string-or-symbol) |
|
"If STRING-OR-SYMBOL ends in `-mode' (or its name does), return |
|
it as a symbol. Otherwise, return it as a symbol with `-mode' |
|
appended." |
|
(let ((string (use-package-as-string string-or-symbol))) |
|
(intern (if (string-match "-mode\\'" string) |
|
string |
|
(concat string "-mode"))))) |
|
|
|
(defsubst use-package-load-name (name &optional noerror) |
|
"Return a form which will load or require NAME. |
|
It does the right thing no matter if NAME is a string or symbol. |
|
Argument NOERROR means to indicate load failures as a warning." |
|
(if (stringp name) |
|
`(load ,name ,noerror) |
|
`(require ',name nil ,noerror))) |
|
|
|
(defun use-package-hook-injector (name-string keyword body) |
|
"Wrap pre/post hook injections around the given BODY for KEYWORD. |
|
The BODY is a list of forms, so `((foo))' if only `foo' is being called." |
|
(if (not use-package-inject-hooks) |
|
body |
|
(let ((keyword-name (substring (format "%s" keyword) 1))) |
|
`((when (run-hook-with-args-until-failure |
|
',(intern (concat "use-package--" name-string |
|
"--pre-" keyword-name "-hook"))) |
|
,@body |
|
(run-hooks |
|
',(intern (concat "use-package--" name-string |
|
"--post-" keyword-name "-hook")))))))) |
|
|
|
(defun use-package-with-elapsed-timer (text body) |
|
"BODY is a list of forms, so `((foo))' if only `foo' is being called." |
|
(declare (indent 1)) |
|
(if use-package-expand-minimally |
|
body |
|
(let ((nowvar (make-symbol "now"))) |
|
(if (bound-and-true-p use-package-verbose) |
|
`((let ((,nowvar (current-time))) |
|
(message "%s..." ,text) |
|
(prog1 |
|
,(macroexp-progn body) |
|
(let ((elapsed |
|
(float-time (time-subtract (current-time) ,nowvar)))) |
|
(if (> elapsed ,use-package-minimum-reported-time) |
|
(message "%s...done (%.3fs)" ,text elapsed) |
|
(message "%s...done" ,text)))))) |
|
body)))) |
|
|
|
(put 'use-package-with-elapsed-timer 'lisp-indent-function 1) |
|
|
|
(defun use-package-require (name &optional no-require body) |
|
(if use-package-expand-minimally |
|
(use-package-concat |
|
(unless no-require |
|
(list (use-package-load-name name))) |
|
body) |
|
(if no-require |
|
body |
|
(use-package-with-elapsed-timer |
|
(format "Loading package %s" name) |
|
`((if (not ,(use-package-load-name name t)) |
|
(display-warning 'use-package |
|
(format "Cannot load %s" ',name) |
|
:error) |
|
,@body)))))) |
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
;; |
|
;;; Property lists |
|
;; |
|
|
|
(defun use-package-plist-delete (plist property) |
|
"Delete PROPERTY from PLIST. |
|
This is in contrast to merely setting it to 0." |
|
(let (p) |
|
(while plist |
|
(if (not (eq property (car plist))) |
|
(setq p (plist-put p (car plist) (nth 1 plist)))) |
|
(setq plist (cddr plist))) |
|
p)) |
|
|
|
(defun use-package-plist-delete-first (plist property) |
|
"Delete PROPERTY from PLIST. |
|
This is in contrast to merely setting it to 0." |
|
(let (p) |
|
(while plist |
|
(if (eq property (car plist)) |
|
(setq p (nconc p (cddr plist)) |
|
plist nil) |
|
(setq p (nconc p (list (car plist) (cadr plist))) |
|
plist (cddr plist)))) |
|
p)) |
|
|
|
(defsubst use-package-plist-maybe-put (plist property value) |
|
"Add a VALUE for PROPERTY to PLIST, if it does not already exist." |
|
(if (plist-member plist property) |
|
plist |
|
(plist-put plist property value))) |
|
|
|
(defsubst use-package-plist-cons (plist property value) |
|
"Cons VALUE onto the head of the list at PROPERTY in PLIST." |
|
(plist-put plist property (cons value (plist-get plist property)))) |
|
|
|
(defsubst use-package-plist-append (plist property value) |
|
"Append VALUE onto the front of the list at PROPERTY in PLIST." |
|
(plist-put plist property (append value (plist-get plist property)))) |
|
|
|
(defun use-package-split-list (pred xs) |
|
(let ((ys (list nil)) (zs (list nil)) flip) |
|
(cl-dolist (x xs) |
|
(if flip |
|
(nconc zs (list x)) |
|
(if (funcall pred x) |
|
(progn |
|
(setq flip t) |
|
(nconc zs (list x))) |
|
(nconc ys (list x))))) |
|
(cons (cdr ys) (cdr zs)))) |
|
|
|
(defun use-package-split-list-at-keys (key lst) |
|
(and lst |
|
(let ((xs (use-package-split-list (apply-partially #'eq key) lst))) |
|
(cons (car xs) (use-package-split-list-at-keys key (cddr xs)))))) |
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
;; |
|
;;; Keywords |
|
;; |
|
|
|
(defun use-package-keyword-index (keyword) |
|
(cl-loop named outer |
|
with index = 0 |
|
for k in use-package-keywords do |
|
(if (eq k keyword) |
|
(cl-return-from outer index)) |
|
(cl-incf index))) |
|
|
|
(defun use-package-normalize-plist (name input &optional plist merge-function) |
|
"Given a pseudo-plist, normalize it to a regular plist. |
|
The normalized key/value pairs from input are added to PLIST, |
|
extending any keys already present." |
|
(if (null input) |
|
plist |
|
(let* ((keyword (car input)) |
|
(xs (use-package-split-list #'keywordp (cdr input))) |
|
(args (car xs)) |
|
(tail (cdr xs)) |
|
(normalizer |
|
(intern-soft (concat "use-package-normalize/" |
|
(symbol-name keyword)))) |
|
(arg (and (functionp normalizer) |
|
(funcall normalizer name keyword args))) |
|
(error-string (format "Unrecognized keyword: %s" keyword))) |
|
(if (memq keyword use-package-keywords) |
|
(progn |
|
(setq plist (use-package-normalize-plist |
|
name tail plist merge-function)) |
|
(plist-put plist keyword |
|
(if (plist-member plist keyword) |
|
(funcall merge-function keyword arg |
|
(plist-get plist keyword)) |
|
arg))) |
|
(if use-package-ignore-unknown-keywords |
|
(progn |
|
(display-warning 'use-package error-string) |
|
(use-package-normalize-plist |
|
name tail plist merge-function)) |
|
(use-package-error error-string)))))) |
|
|
|
(defun use-package-unalias-keywords (_name args) |
|
(setq args (cl-nsubstitute :if :when args)) |
|
(let (temp) |
|
(while (setq temp (plist-get args :unless)) |
|
(setq args (use-package-plist-delete-first args :unless) |
|
args (append args `(:if (not ,temp)))))) |
|
args) |
|
|
|
(defun use-package-merge-keys (key new old) |
|
(let ((merger (assq key use-package-merge-key-alist))) |
|
(if merger |
|
(funcall (cdr merger) new old) |
|
(append new old)))) |
|
|
|
(defun use-package-sort-keywords (plist) |
|
(let (plist-grouped) |
|
(while plist |
|
(push (cons (car plist) (cadr plist)) |
|
plist-grouped) |
|
(setq plist (cddr plist))) |
|
(let (result) |
|
(cl-dolist |
|
(x |
|
(nreverse |
|
(sort plist-grouped |
|
#'(lambda (l r) (< (use-package-keyword-index (car l)) |
|
(use-package-keyword-index (car r))))))) |
|
(setq result (cons (car x) (cons (cdr x) result)))) |
|
result))) |
|
|
|
(defun use-package-normalize-keywords (name args) |
|
(let* ((name-symbol (if (stringp name) (intern name) name)) |
|
(name-string (symbol-name name-symbol))) |
|
|
|
;; The function `elisp--local-variables' inserts this unbound variable into |
|
;; macro forms to determine the locally bound variables for |
|
;; `elisp-completion-at-point'. It ends up throwing a lot of errors since it |
|
;; can occupy the position of a keyword (or look like a second argument to a |
|
;; keyword that takes one). Deleting it when it's at the top level should be |
|
;; harmless since there should be no locally bound variables to discover |
|
;; here anyway. |
|
(setq args (delq 'elisp--witness--lisp args)) |
|
|
|
;; Reduce the set of keywords down to its most fundamental expression. |
|
(setq args (use-package-unalias-keywords name-symbol args)) |
|
|
|
;; Normalize keyword values, coalescing multiple occurrences. |
|
(setq args (use-package-normalize-plist name-symbol args nil |
|
#'use-package-merge-keys)) |
|
|
|
;; Add default values for keywords not specified, when applicable. |
|
(cl-dolist (spec use-package-defaults) |
|
(when (let ((func (nth 2 spec))) |
|
(if (and func (functionp func)) |
|
(funcall func name args) |
|
(eval func))) |
|
(setq args (use-package-plist-maybe-put |
|
args (nth 0 spec) |
|
(let ((func (nth 1 spec))) |
|
(if (and func (functionp func)) |
|
(funcall func name args) |
|
(eval func))))))) |
|
|
|
;; Determine any autoloads implied by the keywords used. |
|
(let ((iargs args) |
|
commands) |
|
(while iargs |
|
(when (keywordp (car iargs)) |
|
(let ((autoloads |
|
(intern-soft (concat "use-package-autoloads/" |
|
(symbol-name (car iargs)))))) |
|
(when (functionp autoloads) |
|
(setq commands |
|
;; jww (2017-12-07): Right now we just ignored the type of |
|
;; the autoload being requested, and assume they are all |
|
;; `command'. |
|
(append (mapcar |
|
#'car |
|
(funcall autoloads name-symbol (car iargs) |
|
(cadr iargs))) |
|
commands))))) |
|
(setq iargs (cddr iargs))) |
|
(when commands |
|
(setq args |
|
;; Like `use-package-plist-append', but removing duplicates. |
|
(plist-put args :commands |
|
(delete-dups |
|
(append commands (plist-get args :commands))))))) |
|
|
|
;; If byte-compiling, pre-load the package so all its symbols are in |
|
;; scope. This is done by prepending statements to the :preface. |
|
(when (bound-and-true-p byte-compile-current-file) |
|
(setq args |
|
(use-package-plist-append |
|
args :preface |
|
(use-package-concat |
|
(mapcar #'(lambda (var) `(defvar ,var)) |
|
(plist-get args :defines)) |
|
(mapcar #'(lambda (fn) `(declare-function ,fn ,name-string)) |
|
(plist-get args :functions)) |
|
`((eval-when-compile |
|
(with-demoted-errors |
|
,(format "Cannot load %s: %%S" name-string) |
|
,(when (eq use-package-verbose 'debug) |
|
`(message ,(format "Compiling package %s" name-string))) |
|
,(unless (plist-get args :no-require) |
|
`(unless (featurep ',name-symbol) |
|
(load ,name-string nil t)))))))))) |
|
|
|
;; Certain keywords imply :defer, if :demand was not specified. |
|
(when (and (not (plist-member args :demand)) |
|
(not (plist-member args :defer)) |
|
(not (or (equal '(t) (plist-get args :load)) |
|
(equal (list (use-package-as-string name)) |
|
(mapcar #'use-package-as-string |
|
(plist-get args :load))))) |
|
(cl-some #'identity |
|
(mapcar (apply-partially #'plist-member args) |
|
use-package-deferring-keywords))) |
|
(setq args (append args '(:defer t)))) |
|
|
|
;; The :load keyword overrides :no-require |
|
(when (and (plist-member args :load) |
|
(plist-member args :no-require)) |
|
(setq args (use-package-plist-delete args :no-require))) |
|
|
|
;; If at this point no :load, :defer or :no-require has been seen, then |
|
;; :load the package itself. |
|
(when (and (not (plist-member args :load)) |
|
(not (plist-member args :defer)) |
|
(not (plist-member args :no-require))) |
|
(setq args (append args `(:load (,name))))) |
|
|
|
;; Sort the list of keywords based on the order of `use-package-keywords'. |
|
(use-package-sort-keywords args))) |
|
|
|
(defun use-package-process-keywords (name plist &optional state) |
|
"Process the next keyword in the free-form property list PLIST. |
|
The values in the PLIST have each been normalized by the function |
|
use-package-normalize/KEYWORD (minus the colon). |
|
|
|
STATE is a property list that the function may modify and/or |
|
query. This is useful if a package defines multiple keywords and |
|
wishes them to have some kind of stateful interaction. |
|
|
|
Unless the KEYWORD being processed intends to ignore remaining |
|
keywords, it must call this function recursively, passing in the |
|
plist with its keyword and argument removed, and passing in the |
|
next value for the STATE." |
|
(declare (indent 1)) |
|
(unless (null plist) |
|
(let* ((keyword (car plist)) |
|
(arg (cadr plist)) |
|
(rest (cddr plist))) |
|
(unless (keywordp keyword) |
|
(use-package-error (format "%s is not a keyword" keyword))) |
|
(let* ((handler (concat "use-package-handler/" (symbol-name keyword))) |
|
(handler-sym (intern handler))) |
|
(if (functionp handler-sym) |
|
(funcall handler-sym name keyword arg rest state) |
|
(use-package-error |
|
(format "Keyword handler not defined: %s" handler))))))) |
|
|
|
(put 'use-package-process-keywords 'lisp-indent-function 'defun) |
|
|
|
(defun use-package-list-insert (elem xs &optional anchor after test) |
|
"Insert ELEM into the list XS. |
|
If ANCHOR is also a keyword, place the new KEYWORD before that |
|
one. |
|
If AFTER is non-nil, insert KEYWORD either at the end of the |
|
keywords list, or after the ANCHOR if one has been provided. |
|
If TEST is non-nil, it is the test used to compare ELEM to list |
|
elements. The default is `eq'. |
|
The modified list is returned. The original list is not modified." |
|
(let (result) |
|
(dolist (k xs) |
|
(if (funcall (or test #'eq) k anchor) |
|
(if after |
|
(setq result (cons k result) |
|
result (cons elem result)) |
|
(setq result (cons elem result) |
|
result (cons k result))) |
|
(setq result (cons k result)))) |
|
(if anchor |
|
(nreverse result) |
|
(if after |
|
(nreverse (cons elem result)) |
|
(cons elem (nreverse result)))))) |
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
;; |
|
;;; Argument Processing |
|
;; |
|
|
|
(defun use-package-only-one (label args f) |
|
"Call F on the first member of ARGS if it has exactly one element." |
|
(declare (indent 1)) |
|
(cond |
|
((and (listp args) (listp (cdr args)) |
|
(= (length args) 1)) |
|
(funcall f label (car args))) |
|
(t |
|
(use-package-error |
|
(concat label " wants exactly one argument"))))) |
|
|
|
(put 'use-package-only-one 'lisp-indent-function 'defun) |
|
|
|
(defun use-package-as-one (label args f &optional allow-empty) |
|
"Call F on the first element of ARGS if it has one element, or all of ARGS. |
|
If ALLOW-EMPTY is non-nil, it's OK for ARGS to be an empty list." |
|
(declare (indent 1)) |
|
(if (if args |
|
(and (listp args) (listp (cdr args))) |
|
allow-empty) |
|
(if (= (length args) 1) |
|
(funcall f label (car args)) |
|
(funcall f label args)) |
|
(use-package-error |
|
(concat label " wants a non-empty list")))) |
|
|
|
(put 'use-package-as-one 'lisp-indent-function 'defun) |
|
|
|
(defun use-package-memoize (f arg) |
|
"Ensure the macro-expansion of F applied to ARG evaluates ARG |
|
no more than once." |
|
(let ((loaded (cl-gentemp "use-package--loaded")) |
|
(result (cl-gentemp "use-package--result")) |
|
(next (cl-gentemp "use-package--next"))) |
|
`((defvar ,loaded nil) |
|
(defvar ,result nil) |
|
(defvar ,next #'(lambda () (if ,loaded ,result |
|
(setq ,loaded t ,result ,arg)))) |
|
,@(funcall f `((funcall ,next)))))) |
|
|
|
(defsubst use-package-normalize-value (_label arg) |
|
"Normalize the Lisp value given by ARG. |
|
The argument LABEL is ignored." |
|
(cond ((null arg) nil) |
|
((eq t arg) t) |
|
((use-package-non-nil-symbolp arg) |
|
`(symbol-value ',arg)) |
|
((functionp arg) |
|
`(funcall #',arg)) |
|
(t arg))) |
|
|
|
(defun use-package-normalize-symbols (label arg &optional recursed) |
|
"Normalize a list of symbols." |
|
(cond |
|
((use-package-non-nil-symbolp arg) |
|
(list arg)) |
|
((and (not recursed) (listp arg) (listp (cdr arg))) |
|
(mapcar #'(lambda (x) (car (use-package-normalize-symbols label x t))) arg)) |
|
(t |
|
(use-package-error |
|
(concat label " wants a symbol, or list of symbols"))))) |
|
|
|
(defun use-package-normalize-symlist (_name keyword args) |
|
(use-package-as-one (symbol-name keyword) args |
|
#'use-package-normalize-symbols)) |
|
|
|
(defun use-package-normalize-recursive-symbols (label arg) |
|
"Normalize a list of symbols." |
|
(cond |
|
((use-package-non-nil-symbolp arg) |
|
arg) |
|
((and (listp arg) (listp (cdr arg))) |
|
(mapcar #'(lambda (x) (use-package-normalize-recursive-symbols label x)) |
|
arg)) |
|
(t |
|
(use-package-error |
|
(concat label " wants a symbol, or nested list of symbols"))))) |
|
|
|
(defun use-package-normalize-recursive-symlist (_name keyword args) |
|
(use-package-as-one (symbol-name keyword) args |
|
#'use-package-normalize-recursive-symbols)) |
|
|
|
(defun use-package-normalize-paths (label arg &optional recursed) |
|
"Normalize a list of filesystem paths." |
|
(cond |
|
((and arg (or (use-package-non-nil-symbolp arg) (functionp arg))) |
|
(let ((value (use-package-normalize-value label arg))) |
|
(use-package-normalize-paths label (eval value)))) |
|
((stringp arg) |
|
(let ((path (if (file-name-absolute-p arg) |
|
arg |
|
(expand-file-name arg user-emacs-directory)))) |
|
(list path))) |
|
((and (not recursed) (listp arg) (listp (cdr arg))) |
|
(mapcar #'(lambda (x) |
|
(car (use-package-normalize-paths label x t))) arg)) |
|
(t |
|
(use-package-error |
|
(concat label " wants a directory path, or list of paths"))))) |
|
|
|
(defun use-package-normalize-predicate (_name keyword args) |
|
(if (null args) |
|
t |
|
(use-package-only-one (symbol-name keyword) args |
|
#'use-package-normalize-value))) |
|
|
|
(defun use-package-normalize-form (label args) |
|
"Given a list of forms, return it wrapped in `progn'." |
|
(unless (listp (car args)) |
|
(use-package-error (concat label " wants a sexp or list of sexps"))) |
|
(mapcar #'(lambda (form) |
|
(if (and (consp form) |
|
(memq (car form) |
|
'(use-package bind-key bind-key* |
|
unbind-key bind-keys bind-keys*))) |
|
(macroexpand form) |
|
form)) args)) |
|
|
|
(defun use-package-normalize-forms (_name keyword args) |
|
(use-package-normalize-form (symbol-name keyword) args)) |
|
|
|
(defun use-package-normalize-pairs |
|
(key-pred val-pred name label arg &optional recursed) |
|
"Normalize a list of pairs. |
|
KEY-PRED and VAL-PRED are predicates recognizing valid keys and |
|
values, respectively. |
|
If RECURSED is non-nil, recurse into sublists." |
|
(cond |
|
((funcall key-pred arg) |
|
(list (cons arg (use-package-as-symbol name)))) |
|
((use-package-is-pair arg key-pred val-pred) |
|
(list arg)) |
|
((and (not recursed) (listp arg) (listp (cdr arg))) |
|
(let (last-item) |
|
(mapcar |
|
#'(lambda (x) |
|
(prog1 |
|
(let ((ret (use-package-normalize-pairs |
|
key-pred val-pred name label x t))) |
|
(if (and (listp ret) |
|
(not (keywordp last-item))) |
|
(car ret) |
|
ret)) |
|
(setq last-item x))) arg))) |
|
(t arg))) |
|
|
|
(defun use-package-recognize-function (v &optional binding additional-pred) |
|
"A predicate that recognizes functional constructions: |
|
nil |
|
sym |
|
'sym |
|
(quote sym) |
|
#'sym |
|
(function sym) |
|
(lambda () ...) |
|
'(lambda () ...) |
|
(quote (lambda () ...)) |
|
#'(lambda () ...) |
|
(function (lambda () ...))" |
|
(or (if binding |
|
(symbolp v) |
|
(use-package-non-nil-symbolp v)) |
|
(and (listp v) |
|
(memq (car v) '(quote function)) |
|
(use-package-non-nil-symbolp (cadr v))) |
|
(if binding (commandp v) (functionp v)) |
|
(and additional-pred |
|
(funcall additional-pred v)))) |
|
|
|
(defun use-package-normalize-function (v) |
|
"Reduce functional constructions to one of two normal forms: |
|
sym |
|
#'(lambda () ...)" |
|
(cond ((symbolp v) v) |
|
((and (listp v) |
|
(memq (car v) '(quote function)) |
|
(use-package-non-nil-symbolp (cadr v))) |
|
(cadr v)) |
|
((and (consp v) |
|
(eq 'lambda (car v))) |
|
v) |
|
((and (listp v) |
|
(memq (car v) '(quote function)) |
|
(eq 'lambda (car (cadr v)))) |
|
(cadr v)) |
|
(t v))) |
|
|
|
(defun use-package-normalize-commands (args) |
|
"Map over ARGS of the form ((_ . F) ...), normalizing functional F's." |
|
(mapcar #'(lambda (x) |
|
(if (consp x) |
|
(cons (car x) (use-package-normalize-function (cdr x))) |
|
x)) |
|
args)) |
|
|
|
(defun use-package-normalize-mode (name keyword args) |
|
"Normalize arguments for keywords which add regexp/mode pairs to an alist." |
|
(use-package-as-one (symbol-name keyword) args |
|
(apply-partially #'use-package-normalize-pairs |
|
#'use-package-regex-p |
|
#'use-package-recognize-function |
|
name))) |
|
|
|
(defun use-package-autoloads-mode (_name _keyword args) |
|
(mapcar |
|
#'(lambda (x) (cons (cdr x) 'command)) |
|
(cl-remove-if-not #'(lambda (x) |
|
(and (consp x) |
|
(use-package-non-nil-symbolp (cdr x)))) |
|
args))) |
|
|
|
(defun use-package-handle-mode (name alist args rest state) |
|
"Handle keywords which add regexp/mode pairs to an alist." |
|
(use-package-concat |
|
(use-package-process-keywords name rest state) |
|
(mapcar |
|
#'(lambda (thing) |
|
`(add-to-list |
|
',alist |
|
',(cons (use-package-normalize-regex (car thing)) |
|
(cdr thing)))) |
|
(use-package-normalize-commands args)))) |
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
;; |
|
;;; Statistics |
|
;; |
|
|
|
(defun use-package-reset-statistics () |
|
(interactive) |
|
(setq use-package-statistics (make-hash-table))) |
|
|
|
(defun use-package-statistics-status (package) |
|
"Return loading configuration status of PACKAGE statistics." |
|
(cond ((gethash :config package) "Configured") |
|
((gethash :init package) "Initialized") |
|
((gethash :preface package) "Prefaced") |
|
((gethash :use-package package) "Declared"))) |
|
|
|
(defun use-package-statistics-last-event (package) |
|
"Return the date when PACKAGE's status last changed. |
|
The date is returned as a string." |
|
(format-time-string "%Y-%m-%d %a %H:%M" |
|
(or (gethash :config package) |
|
(gethash :init package) |
|
(gethash :preface package) |
|
(gethash :use-package package)))) |
|
|
|
(defun use-package-statistics-time (package) |
|
"Return the time is took for PACKAGE to load." |
|
(+ (float-time (gethash :config-secs package '(0 0 0 0))) |
|
(float-time (gethash :init-secs package '(0 0 0 0))) |
|
(float-time (gethash :preface-secs package '(0 0 0 0))) |
|
(float-time (gethash :use-package-secs package '(0 0 0 0))))) |
|
|
|
(defun use-package-statistics-convert (package) |
|
"Return information about PACKAGE. |
|
|
|
The information is formatted in a way suitable for |
|
`use-package-statistics-mode'." |
|
(let ((statistics (gethash package use-package-statistics))) |
|
(list |
|
package |
|
(vector |
|
(symbol-name package) |
|
(use-package-statistics-status statistics) |
|
(use-package-statistics-last-event statistics) |
|
(format "%.2f" (use-package-statistics-time statistics)))))) |
|
|
|
(defun use-package-report () |
|
"Show current statistics gathered about use-package declarations. |
|
In the table that's generated, the status field has the following |
|
meaning: |
|
Configured :config has been processed (the package is loaded!) |
|
Initialized :init has been processed (load status unknown) |
|
Prefaced :preface has been processed |
|
Declared the use-package declaration was seen" |
|
(interactive) |
|
(with-current-buffer (get-buffer-create "*use-package statistics*") |
|
(setq tabulated-list-entries |
|
(mapcar #'use-package-statistics-convert |
|
(hash-table-keys use-package-statistics))) |
|
(use-package-statistics-mode) |
|
(tabulated-list-print) |
|
(display-buffer (current-buffer)))) |
|
|
|
(define-derived-mode use-package-statistics-mode tabulated-list-mode |
|
"use-package statistics" |
|
"Show current statistics gathered about use-package declarations." |
|
(setq tabulated-list-format |
|
;; The sum of column width is 80 characters: |
|
[("Package" 25 t) |
|
("Status" 13 t) |
|
("Last Event" 23 t) |
|
("Time" 10 t)]) |
|
(tabulated-list-init-header)) |
|
|
|
(defun use-package-statistics-gather (keyword name after) |
|
(let* ((hash (gethash name use-package-statistics |
|
(make-hash-table))) |
|
(before (and after (gethash keyword hash (current-time))))) |
|
(puthash keyword (current-time) hash) |
|
(when after |
|
(puthash (intern (concat (symbol-name keyword) "-secs")) |
|
(time-subtract (current-time) before) hash)) |
|
(puthash name hash use-package-statistics))) |
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
;; |
|
;;; Handlers |
|
;; |
|
|
|
;;;; :disabled |
|
|
|
;; Don't alias this to `ignore', as that will cause the resulting |
|
;; function to be interactive. |
|
(defun use-package-normalize/:disabled (_name _keyword _arg) |
|
"Do nothing, return nil.") |
|
|
|
(defun use-package-handler/:disabled (name _keyword _arg rest state) |
|
(use-package-process-keywords name rest state)) |
|
|
|
;;;; :if, :when and :unless |
|
|
|
(defun use-package-normalize-test (_name keyword args) |
|
(use-package-only-one (symbol-name keyword) args |
|
#'use-package-normalize-value)) |
|
|
|
(defalias 'use-package-normalize/:if 'use-package-normalize-test) |
|
|
|
(defun use-package-handler/:if (name _keyword pred rest state) |
|
(let ((body (use-package-process-keywords name rest state))) |
|
`((when ,pred ,@body)))) |
|
|
|
(defalias 'use-package-normalize/:when 'use-package-normalize-test) |
|
|
|
(defalias 'use-package-handler/:when 'use-package-handler/:if) |
|
|
|
(defalias 'use-package-normalize/:unless 'use-package-normalize-test) |
|
|
|
(defun use-package-handler/:unless (name _keyword pred rest state) |
|
(let ((body (use-package-process-keywords name rest state))) |
|
`((unless ,pred ,@body)))) |
|
|
|
;;;; :requires |
|
|
|
(defalias 'use-package-normalize/:requires 'use-package-normalize-symlist) |
|
|
|
(defun use-package-handler/:requires (name _keyword requires rest state) |
|
(let ((body (use-package-process-keywords name rest state))) |
|
(if (null requires) |
|
body |
|
`((when ,(if (> (length requires) 1) |
|
`(not (member nil (mapcar #'featurep ',requires))) |
|
`(featurep ',(car requires))) |
|
,@body))))) |
|
|
|
;;;; :load-path |
|
|
|
(defun use-package-normalize/:load-path (_name keyword args) |
|
(use-package-as-one (symbol-name keyword) args |
|
#'use-package-normalize-paths)) |
|
|
|
(defun use-package-handler/:load-path (name _keyword arg rest state) |
|
(let ((body (use-package-process-keywords name rest state))) |
|
(use-package-concat |
|
(mapcar #'(lambda (path) |
|
`(eval-and-compile (add-to-list 'load-path ,path))) |
|
arg) |
|
body))) |
|
|
|
;;;; :no-require |
|
|
|
(defalias 'use-package-normalize/:no-require 'use-package-normalize-predicate) |
|
|
|
(defun use-package-handler/:no-require (name _keyword _arg rest state) |
|
(use-package-process-keywords name rest state)) |
|
|
|
;;;; :defines |
|
|
|
(defalias 'use-package-normalize/:defines 'use-package-normalize-symlist) |
|
|
|
(defun use-package-handler/:defines (name _keyword _arg rest state) |
|
(use-package-process-keywords name rest state)) |
|
|
|
;;;; :functions |
|
|
|
(defalias 'use-package-normalize/:functions 'use-package-normalize-symlist) |
|
|
|
(defun use-package-handler/:functions (name _keyword _arg rest state) |
|
(use-package-process-keywords name rest state)) |
|
|
|
;;;; :preface |
|
|
|
(defalias 'use-package-normalize/:preface 'use-package-normalize-forms) |
|
|
|
(defun use-package-handler/:preface (name _keyword arg rest state) |
|
(let ((body (use-package-process-keywords name rest state))) |
|
(use-package-concat |
|
(when use-package-compute-statistics |
|
`((use-package-statistics-gather :preface ',name nil))) |
|
(when arg |
|
`((eval-and-compile ,@arg))) |
|
body |
|
(when use-package-compute-statistics |
|
`((use-package-statistics-gather :preface ',name t)))))) |
|
|
|
;;;; :catch |
|
|
|
(defvar use-package--form) |
|
(defvar use-package--hush-function #'(lambda (_keyword body) body)) |
|
|
|
(defsubst use-package-hush (context keyword body) |
|
`((condition-case-unless-debug err |
|
,(macroexp-progn body) |
|
(error (funcall ,context ,keyword err))))) |
|
|
|
(defun use-package-normalize/:catch (_name keyword args) |
|
(if (null args) |
|
t |
|
(use-package-only-one (symbol-name keyword) args |
|
use-package--hush-function))) |
|
|
|
(defun use-package-handler/:catch (name keyword arg rest state) |
|
(let* ((context (cl-gentemp "use-package--warning"))) |
|
(cond |
|
((not arg) |
|
(use-package-process-keywords name rest state)) |
|
((eq arg t) |
|
`((defvar ,context |
|
#'(lambda (keyword err) |
|
(let ((msg (format "%s/%s: %s" ',name keyword |
|
(error-message-string err)))) |
|
,@(when (eq use-package-verbose 'debug) |
|
`((with-current-buffer |
|
(get-buffer-create "*use-package*") |
|
(goto-char (point-max)) |
|
(insert "-----\n" msg ,use-package--form) |
|
(emacs-lisp-mode)) |
|
(setq msg |
|
(concat msg |
|
" (see the *use-package* buffer)")))) |
|
(display-warning 'use-package msg :error)))) |
|
,@(let ((use-package--hush-function |
|
(apply-partially #'use-package-hush context))) |
|
(funcall use-package--hush-function keyword |
|
(use-package-process-keywords name rest state))))) |
|
((functionp arg) |
|
`((defvar ,context ,arg) |
|
,@(let ((use-package--hush-function |
|
(apply-partially #'use-package-hush context))) |
|
(funcall use-package--hush-function keyword |
|
(use-package-process-keywords name rest state))))) |
|
(t |
|
(use-package-error "The :catch keyword expects 't' or a function"))))) |
|
|
|
;;;; :interpreter |
|
|
|
(defalias 'use-package-normalize/:interpreter 'use-package-normalize-mode) |
|
(defalias 'use-package-autoloads/:interpreter 'use-package-autoloads-mode) |
|
|
|
(defun use-package-handler/:interpreter (name _keyword arg rest state) |
|
(use-package-handle-mode name 'interpreter-mode-alist arg rest state)) |
|
|
|
;;;; :mode |
|
|
|
(defalias 'use-package-normalize/:mode 'use-package-normalize-mode) |
|
(defalias 'use-package-autoloads/:mode 'use-package-autoloads-mode) |
|
|
|
(defun use-package-handler/:mode (name _keyword arg rest state) |
|
(use-package-handle-mode name 'auto-mode-alist arg rest state)) |
|
|
|
;;;; :magic |
|
|
|
(defalias 'use-package-normalize/:magic 'use-package-normalize-mode) |
|
(defalias 'use-package-autoloads/:magic 'use-package-autoloads-mode) |
|
|
|
(defun use-package-handler/:magic (name _keyword arg rest state) |
|
(use-package-handle-mode name 'magic-mode-alist arg rest state)) |
|
|
|
;;;; :magic-fallback |
|
|
|
(defalias 'use-package-normalize/:magic-fallback 'use-package-normalize-mode) |
|
(defalias 'use-package-autoloads/:magic-fallback 'use-package-autoloads-mode) |
|
|
|
(defun use-package-handler/:magic-fallback (name _keyword arg rest state) |
|
(use-package-handle-mode name 'magic-fallback-mode-alist arg rest state)) |
|
|
|
;;;; :hook |
|
|
|
(defun use-package-normalize/:hook (name keyword args) |
|
(use-package-as-one (symbol-name keyword) args |
|
#'(lambda (label arg) |
|
(unless (or (use-package-non-nil-symbolp arg) (consp arg)) |
|
(use-package-error |
|
(concat label " a <symbol> or (<symbol or list of symbols> . <symbol or function>)" |
|
" or list of these"))) |
|
(use-package-normalize-pairs |
|
#'(lambda (k) |
|
(or (use-package-non-nil-symbolp k) |
|
(and k (let ((every t)) |
|
(while (and every k) |
|
(if (and (consp k) |
|
(use-package-non-nil-symbolp (car k))) |
|
(setq k (cdr k)) |
|
(setq every nil))) |
|
every)))) |
|
#'use-package-recognize-function |
|
name label arg)))) |
|
|
|
(defalias 'use-package-autoloads/:hook 'use-package-autoloads-mode) |
|
|
|
(defun use-package-handler/:hook (name _keyword args rest state) |
|
"Generate use-package custom keyword code." |
|
(use-package-concat |
|
(use-package-process-keywords name rest state) |
|
(cl-mapcan |
|
#'(lambda (def) |
|
(let ((syms (car def)) |
|
(fun (cdr def))) |
|
(when fun |
|
(mapcar |
|
#'(lambda (sym) |
|
`(add-hook |
|
(quote ,(intern |
|
(concat (symbol-name sym) |
|
use-package-hook-name-suffix))) |
|
(function ,fun))) |
|
(if (use-package-non-nil-symbolp syms) (list syms) syms))))) |
|
(use-package-normalize-commands args)))) |
|
|
|
;;;; :commands |
|
|
|
(defalias 'use-package-normalize/:commands 'use-package-normalize-symlist) |
|
|
|
(defun use-package-handler/:commands (name _keyword arg rest state) |
|
(use-package-concat |
|
;; Since we deferring load, establish any necessary autoloads, and also |
|
;; keep the byte-compiler happy. |
|
(let ((name-string (use-package-as-string name))) |
|
(cl-mapcan |
|
#'(lambda (command) |
|
(when (symbolp command) |
|
(append |
|
(unless (plist-get state :demand) |
|
`((unless (fboundp ',command) |
|
(autoload #',command ,name-string nil t)))) |
|
(when (bound-and-true-p byte-compile-current-file) |
|
`((eval-when-compile |
|
(declare-function ,command ,name-string))))))) |
|
(delete-dups arg))) |
|
(use-package-process-keywords name rest state))) |
|
|
|
;;;; :defer |
|
|
|
(defalias 'use-package-normalize/:defer 'use-package-normalize-predicate) |
|
|
|
(defun use-package-handler/:defer (name _keyword arg rest state) |
|
(let ((body (use-package-process-keywords name rest state))) |
|
(use-package-concat |
|
;; Load the package after a set amount of idle time, if the argument to |
|
;; `:defer' was a number. |
|
(when (numberp arg) |
|
`((run-with-idle-timer ,arg nil #'require |
|
',(use-package-as-symbol name) nil t))) |
|
(if (or (not arg) (null body)) |
|
body |
|
`((eval-after-load ',name ',(macroexp-progn body))))))) |
|
|
|
;;;; :after |
|
|
|
(defun use-package-normalize/:after (name keyword args) |
|
(setq args (use-package-normalize-recursive-symlist name keyword args)) |
|
(if (consp args) |
|
args |
|
(list args))) |
|
|
|
(defun use-package-after-count-uses (features*) |
|
"Count the number of time the body would appear in the result." |
|
(cond ((use-package-non-nil-symbolp features*) |
|
1) |
|
((and (consp features*) |
|
(memq (car features*) '(:or :any))) |
|
(let ((num 0)) |
|
(cl-dolist (next (cdr features*)) |
|
(setq num (+ num (use-package-after-count-uses next)))) |
|
num)) |
|
((and (consp features*) |
|
(memq (car features*) '(:and :all))) |
|
(apply #'max (mapcar #'use-package-after-count-uses |
|
(cdr features*)))) |
|
((listp features*) |
|
(use-package-after-count-uses (cons :all features*))))) |
|
|
|
(defun use-package-require-after-load (features* body) |
|
"Generate `eval-after-load' statements to represents FEATURES*. |
|
FEATURES* is a list containing keywords `:and' and `:all', where |
|
no keyword implies `:all'." |
|
(cond |
|
((use-package-non-nil-symbolp features*) |
|
`((eval-after-load ',features* ',(macroexp-progn body)))) |
|
((and (consp features*) |
|
(memq (car features*) '(:or :any))) |
|
(cl-mapcan #'(lambda (x) (use-package-require-after-load x body)) |
|
(cdr features*))) |
|
((and (consp features*) |
|
(memq (car features*) '(:and :all))) |
|
(cl-dolist (next (cdr features*)) |
|
(setq body (use-package-require-after-load next body))) |
|
body) |
|
((listp features*) |
|
(use-package-require-after-load (cons :all features*) body)))) |
|
|
|
(defun use-package-handler/:after (name _keyword arg rest state) |
|
(let ((body (use-package-process-keywords name rest state)) |
|
(uses (use-package-after-count-uses arg))) |
|
(if (or (null uses) (null body)) |
|
body |
|
(if (<= uses 1) |
|
(use-package-require-after-load arg body) |
|
(use-package-memoize |
|
(apply-partially #'use-package-require-after-load arg) |
|
(macroexp-progn body)))))) |
|
|
|
;;;; :demand |
|
|
|
(defalias 'use-package-normalize/:demand 'use-package-normalize-predicate) |
|
|
|
(defun use-package-handler/:demand (name _keyword _arg rest state) |
|
(use-package-process-keywords name rest state)) |
|
|
|
;;;; :custom |
|
|
|
(defun use-package-normalize/:custom (_name keyword args) |
|
"Normalize use-package custom keyword." |
|
(use-package-as-one (symbol-name keyword) args |
|
#'(lambda (label arg) |
|
(unless (listp arg) |
|
(use-package-error |
|
(concat label " a (<symbol> <value> [comment])" |
|
" or list of these"))) |
|
(if (use-package-non-nil-symbolp (car arg)) |
|
(list arg) |
|
arg)))) |
|
|
|
(defun use-package-handler/:custom (name _keyword args rest state) |
|
"Generate use-package custom keyword code." |
|
(use-package-concat |
|
(mapcar |
|
#'(lambda (def) |
|
(let ((variable (nth 0 def)) |
|
(value (nth 1 def)) |
|
(comment (nth 2 def))) |
|
(unless (and comment (stringp comment)) |
|
(setq comment (format "Customized with use-package %s" name))) |
|
`(funcall (or (get (quote ,variable) 'custom-set) #'set-default) |
|
(quote ,variable) |
|
,value))) |
|
args) |
|
(use-package-process-keywords name rest state))) |
|
|
|
;;;; :custom-face |
|
|
|
(defun use-package-normalize/:custom-face (name-symbol _keyword arg) |
|
"Normalize use-package custom-face keyword." |
|
(let ((error-msg |
|
(format "%s wants a (<symbol> <face-spec>) or list of these" |
|
name-symbol))) |
|
(unless (listp arg) |
|
(use-package-error error-msg)) |
|
(cl-dolist (def arg arg) |
|
(unless (listp def) |
|
(use-package-error error-msg)) |
|
(let ((face (nth 0 def)) |
|
(spec (nth 1 def))) |
|
(when (or (not face) |
|
(not spec) |
|
(> (length def) 2)) |
|
(use-package-error error-msg)))))) |
|
|
|
(defun use-package-handler/:custom-face (name _keyword args rest state) |
|
"Generate use-package custom-face keyword code." |
|
(use-package-concat |
|
(mapcar #'(lambda (def) `(custom-set-faces (backquote ,def))) args) |
|
(use-package-process-keywords name rest state))) |
|
|
|
;;;; :init |
|
|
|
(defalias 'use-package-normalize/:init 'use-package-normalize-forms) |
|
|
|
(defun use-package-handler/:init (name _keyword arg rest state) |
|
(use-package-concat |
|
(when use-package-compute-statistics |
|
`((use-package-statistics-gather :init ',name nil))) |
|
(let ((init-body |
|
(use-package-hook-injector (use-package-as-string name) |
|
:init arg))) |
|
(when init-body |
|
(funcall use-package--hush-function :init |
|
(if use-package-check-before-init |
|
`((when (locate-library ,(use-package-as-string name)) |
|
,@init-body)) |
|
init-body)))) |
|
(use-package-process-keywords name rest state) |
|
(when use-package-compute-statistics |
|
`((use-package-statistics-gather :init ',name t))))) |
|
|
|
;;;; :load |
|
|
|
(defun use-package-normalize/:load (name keyword args) |
|
(setq args (use-package-normalize-recursive-symlist name keyword args)) |
|
(if (consp args) |
|
args |
|
(list args))) |
|
|
|
(defun use-package-handler/:load (name _keyword arg rest state) |
|
(let ((body (use-package-process-keywords name rest state))) |
|
(cl-dolist (pkg arg) |
|
(setq body (use-package-require (if (eq t pkg) name pkg) nil body))) |
|
body)) |
|
|
|
;;;; :config |
|
|
|
(defalias 'use-package-normalize/:config 'use-package-normalize-forms) |
|
|
|
(defun use-package-handler/:config (name _keyword arg rest state) |
|
(let* ((body (use-package-process-keywords name rest state)) |
|
(name-symbol (use-package-as-symbol name))) |
|
(use-package-concat |
|
(when use-package-compute-statistics |
|
`((use-package-statistics-gather :config ',name nil))) |
|
(if (and (or (null arg) (equal arg '(t))) (not use-package-inject-hooks)) |
|
body |
|
(use-package-with-elapsed-timer |
|
(format "Configuring package %s" name-symbol) |
|
(funcall use-package--hush-function :config |
|
(use-package-concat |
|
(use-package-hook-injector |
|
(symbol-name name-symbol) :config arg) |
|
body |
|
(list t))))) |
|
(when use-package-compute-statistics |
|
`((use-package-statistics-gather :config ',name t)))))) |
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
|
;; |
|
;;; The main macro |
|
;; |
|
|
|
(defmacro use-package-core (name args) |
|
`(let* ((args* (use-package-normalize-keywords ,name ,args)) |
|
(use-package--form |
|
(if (eq use-package-verbose 'debug) |
|
(concat "\n\n" |
|
(pp-to-string `(use-package ,name ,@,args)) |
|
"\n -->\n\n" |
|
(pp-to-string `(use-package ,name ,@args*)) |
|
"\n ==>\n\n" |
|
(pp-to-string |
|
(macroexp-progn |
|
(let ((use-package-verbose 'errors) |
|
(use-package-expand-minimally t)) |
|
(use-package-process-keywords name args* |
|
(and (plist-get args* :demand) |
|
(list :demand t))))))) |
|
""))) |
|
(use-package-process-keywords name args* |
|
(and (plist-get args* :demand) |
|
(list :demand t))))) |
|
|
|
;;;###autoload |
|
(defmacro use-package (name &rest args) |
|
"Declare an Emacs package by specifying a group of configuration options. |
|
|
|
For full documentation, please see the README file that came with |
|
this file. Usage: |
|
|
|
(use-package package-name |
|
[:keyword [option]]...) |
|
|
|
:init Code to run before PACKAGE-NAME has been loaded. |
|
:config Code to run after PACKAGE-NAME has been loaded. Note that |
|
if loading is deferred for any reason, this code does not |
|
execute until the lazy load has occurred. |
|
:preface Code to be run before everything except `:disabled'; this |
|
can be used to define functions for use in `:if', or that |
|
should be seen by the byte-compiler. |
|
|
|
:mode Form to be added to `auto-mode-alist'. |
|
:magic Form to be added to `magic-mode-alist'. |
|
:magic-fallback Form to be added to `magic-fallback-mode-alist'. |
|
:interpreter Form to be added to `interpreter-mode-alist'. |
|
|
|
:commands Define autoloads for commands that will be defined by the |
|
package. This is useful if the package is being lazily |
|
loaded, and you wish to conditionally call functions in your |
|
`:init' block that are defined in the package. |
|
:hook Specify hook(s) to attach this package to. |
|
|
|
:bind Bind keys, and define autoloads for the bound commands. |
|
:bind* Bind keys, and define autoloads for the bound commands, |
|
*overriding all minor mode bindings*. |
|
:bind-keymap Bind a key prefix to an auto-loaded keymap defined in the |
|
package. This is like `:bind', but for keymaps. |
|
:bind-keymap* Like `:bind-keymap', but overrides all minor mode bindings |
|
|
|
:defer Defer loading of a package -- this is implied when using |
|
`:commands', `:bind', `:bind*', `:mode', `:magic', `:hook', |
|
`:magic-fallback', or `:interpreter'. This can be an integer, |
|
to force loading after N seconds of idle time, if the package |
|
has not already been loaded. |
|
:after Delay the use-package declaration until after the named modules |
|
have loaded. Once load, it will be as though the use-package |
|
declaration (without `:after') had been seen at that moment. |
|
:demand Prevent the automatic deferred loading introduced by constructs |
|
such as `:bind' (see `:defer' for the complete list). |
|
|
|
:if EXPR Initialize and load only if EXPR evaluates to a non-nil value. |
|
:disabled The package is ignored completely if this keyword is present. |
|
:defines Declare certain variables to silence the byte-compiler. |
|
:functions Declare certain functions to silence the byte-compiler. |
|
:load-path Add to the `load-path' before attempting to load the package. |
|
:diminish Support for diminish.el (if installed). |
|
:delight Support for delight.el (if installed). |
|
:custom Call `custom-set' or `set-default' with each variable |
|
definition without modifying the Emacs `custom-file'. |
|
(compare with `custom-set-variables'). |
|
:custom-face Call `customize-set-faces' with each face definition. |
|
:ensure Loads the package using package.el if necessary. |
|
:pin Pin the package to an archive." |
|
(declare (indent 1)) |
|
(unless (memq :disabled args) |
|
(macroexp-progn |
|
(use-package-concat |
|
(when use-package-compute-statistics |
|
`((use-package-statistics-gather :use-package ',name nil))) |
|
(if (eq use-package-verbose 'errors) |
|
(use-package-core name args) |
|
(condition-case-unless-debug err |
|
(use-package-core name args) |
|
(error |
|
(ignore |
|
(display-warning |
|
'use-package |
|
(format "Failed to parse package %s: %s" |
|
name (error-message-string err)) :error))))) |
|
(when use-package-compute-statistics |
|
`((use-package-statistics-gather :use-package ',name t))))))) |
|
|
|
(put 'use-package 'lisp-indent-function 'defun) |
|
|
|
(provide 'use-package-core) |
|
|
|
;; Local Variables: |
|
;; indent-tabs-mode: nil |
|
;; End: |
|
|
|
;;; use-package-core.el ends here
|
|
|