2017-06-08 11:47:56 +02:00
|
|
|
;;; core-lib.el -*- lexical-binding: t; -*-
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2017-03-02 18:14:52 -05:00
|
|
|
;;
|
2019-03-08 18:06:35 -05:00
|
|
|
;;; Helpers
|
2017-03-02 18:14:52 -05:00
|
|
|
|
2017-09-27 01:21:48 +02:00
|
|
|
(defun doom--resolve-hook-forms (hooks)
|
2019-05-01 19:12:52 -04:00
|
|
|
"Converts a list of modes into a list of hook symbols.
|
|
|
|
|
2019-05-13 14:37:00 -04:00
|
|
|
If a mode is quoted, it is left as is. If the entire HOOKS list is quoted, the
|
|
|
|
list is returned as-is."
|
2018-06-24 19:54:50 +02:00
|
|
|
(declare (pure t) (side-effect-free t))
|
2019-05-13 14:37:00 -04:00
|
|
|
(let ((hook-list (doom-enlist (doom-unquote hooks))))
|
|
|
|
(if (eq (car-safe hooks) 'quote)
|
|
|
|
hook-list
|
|
|
|
(cl-loop for hook in hook-list
|
|
|
|
if (eq (car-safe hook) 'quote)
|
|
|
|
collect (cadr hook)
|
|
|
|
else collect (intern (format "%s-hook" (symbol-name hook)))))))
|
2017-03-02 18:14:52 -05:00
|
|
|
|
2019-07-21 14:35:45 +02:00
|
|
|
(defun doom--setq-hook-fns (hooks rest &optional singles)
|
|
|
|
(unless (or singles (= 0 (% (length rest) 2)))
|
|
|
|
(signal 'wrong-number-of-arguments (list #'evenp (length rest))))
|
|
|
|
(cl-loop with vars = (let ((args rest)
|
|
|
|
vars)
|
|
|
|
(while args
|
|
|
|
(push (if singles
|
|
|
|
(list (pop args))
|
|
|
|
(cons (pop args) (pop args)))
|
|
|
|
vars))
|
|
|
|
(nreverse vars))
|
|
|
|
for hook in (doom--resolve-hook-forms hooks)
|
|
|
|
for mode = (string-remove-suffix "-hook" (symbol-name hook))
|
|
|
|
append
|
|
|
|
(cl-loop for (var . val) in vars
|
|
|
|
collect
|
|
|
|
(list var val hook
|
|
|
|
(intern (format "doom--setq-%s-for-%s-h"
|
|
|
|
var mode))))))
|
|
|
|
|
2018-05-24 18:38:50 +02:00
|
|
|
|
|
|
|
;;
|
2019-03-08 18:06:35 -05:00
|
|
|
;;; Public library
|
2018-05-24 18:38:50 +02:00
|
|
|
|
2017-06-12 02:48:26 +02:00
|
|
|
(defun doom-unquote (exp)
|
|
|
|
"Return EXP unquoted."
|
2018-06-24 19:54:50 +02:00
|
|
|
(declare (pure t) (side-effect-free t))
|
2017-06-12 02:48:26 +02:00
|
|
|
(while (memq (car-safe exp) '(quote function))
|
|
|
|
(setq exp (cadr exp)))
|
|
|
|
exp)
|
|
|
|
|
|
|
|
(defun doom-enlist (exp)
|
|
|
|
"Return EXP wrapped in a list, or as-is if already a list."
|
2018-06-24 19:54:50 +02:00
|
|
|
(declare (pure t) (side-effect-free t))
|
2017-06-12 02:48:26 +02:00
|
|
|
(if (listp exp) exp (list exp)))
|
|
|
|
|
2018-05-19 16:32:12 +02:00
|
|
|
(defun doom-keyword-intern (str)
|
2018-05-23 19:09:09 +02:00
|
|
|
"Converts STR (a string) into a keyword (`keywordp')."
|
2018-06-24 19:54:50 +02:00
|
|
|
(declare (pure t) (side-effect-free t))
|
|
|
|
(cl-check-type str string)
|
2018-05-19 16:32:12 +02:00
|
|
|
(intern (concat ":" str)))
|
|
|
|
|
|
|
|
(defun doom-keyword-name (keyword)
|
2018-05-23 19:09:09 +02:00
|
|
|
"Returns the string name of KEYWORD (`keywordp') minus the leading colon."
|
2018-06-24 19:54:50 +02:00
|
|
|
(declare (pure t) (side-effect-free t))
|
2019-07-24 15:26:43 +02:00
|
|
|
(cl-check-type keyword keyword)
|
2018-05-19 16:32:12 +02:00
|
|
|
(substring (symbol-name keyword) 1))
|
|
|
|
|
2019-03-04 18:38:25 -05:00
|
|
|
(defmacro doom-log (format-string &rest args)
|
|
|
|
"Log to *Messages* if `doom-debug-mode' is on.
|
|
|
|
Does not interrupt the minibuffer if it is in use, but still logs to *Messages*.
|
|
|
|
Accepts the same arguments as `message'."
|
2019-03-05 00:19:51 -05:00
|
|
|
`(when doom-debug-mode
|
|
|
|
(let ((inhibit-message (active-minibuffer-window)))
|
2019-03-04 18:38:25 -05:00
|
|
|
(message
|
|
|
|
,(concat (propertize "DOOM " 'face 'font-lock-comment-face)
|
2019-06-16 23:01:17 +02:00
|
|
|
(when (bound-and-true-p doom--current-module)
|
2019-03-07 23:21:58 -05:00
|
|
|
(propertize
|
2019-05-15 15:16:15 -04:00
|
|
|
(format "[%s/%s] "
|
2019-03-07 23:21:58 -05:00
|
|
|
(doom-keyword-name (car doom--current-module))
|
|
|
|
(cdr doom--current-module))
|
2019-05-15 15:16:15 -04:00
|
|
|
'face 'warning))
|
|
|
|
format-string)
|
2019-03-04 18:38:25 -05:00
|
|
|
,@args))))
|
|
|
|
|
2020-04-29 20:19:34 -04:00
|
|
|
(defun doom-try-run-hook (hook)
|
|
|
|
"Run HOOK (a hook function) with better error handling.
|
|
|
|
Meant to be used with `run-hook-wrapped'."
|
|
|
|
(doom-log "Running doom hook: %s" hook)
|
|
|
|
(condition-case e
|
|
|
|
(funcall hook)
|
|
|
|
((debug error)
|
|
|
|
(signal 'doom-hook-error (list hook e))))
|
|
|
|
;; return nil so `run-hook-wrapped' won't short circuit
|
|
|
|
nil)
|
|
|
|
|
|
|
|
(defun doom-load-autoloads-file (file &optional noerror)
|
|
|
|
"Tries to load FILE (an autoloads file).
|
|
|
|
Return t on success, nil otherwise (but logs a warning)."
|
|
|
|
(condition-case e
|
|
|
|
;; Avoid `file-name-sans-extension' for premature optimization reasons.
|
|
|
|
;; `string-remove-suffix' is much cheaper (because it does no file sanity
|
|
|
|
;; checks during or after; just plain ol' string manipulation).
|
|
|
|
(load (string-remove-suffix ".el" file) noerror 'nomessage)
|
2020-05-03 16:00:34 -04:00
|
|
|
(doom-error
|
|
|
|
(signal (car e) (cdr e)))
|
2020-04-29 20:19:34 -04:00
|
|
|
((debug error)
|
|
|
|
(message "Autoload file error: %s -> %s" (file-name-nondirectory file) e)
|
|
|
|
nil)))
|
|
|
|
|
|
|
|
(defun doom-load-envvars-file (file &optional noerror)
|
|
|
|
"Read and set envvars from FILE.
|
|
|
|
If NOERROR is non-nil, don't throw an error if the file doesn't exist or is
|
|
|
|
unreadable. Returns the names of envvars that were changed."
|
|
|
|
(if (null (file-exists-p file))
|
|
|
|
(unless noerror
|
|
|
|
(signal 'file-error (list "No envvar file exists" file)))
|
|
|
|
(when-let
|
|
|
|
(env
|
|
|
|
(with-temp-buffer
|
|
|
|
(save-excursion
|
2020-05-14 04:13:05 -04:00
|
|
|
(setq-local coding-system-for-read 'utf-8)
|
2020-04-29 20:19:34 -04:00
|
|
|
(insert "\0\n") ; to prevent off-by-one
|
2020-05-05 20:14:02 -04:00
|
|
|
(insert-file-contents file))
|
2020-04-29 20:19:34 -04:00
|
|
|
(save-match-data
|
|
|
|
(when (re-search-forward "\0\n *\\([^#= \n]*\\)=" nil t)
|
|
|
|
(setq
|
|
|
|
env (split-string (buffer-substring (match-beginning 1) (point-max))
|
|
|
|
"\0\n"
|
|
|
|
'omit-nulls))))))
|
|
|
|
(setq process-environment (append (nreverse env) process-environment)
|
|
|
|
exec-path (append (split-string (getenv "PATH") path-separator t)
|
|
|
|
(list exec-directory))
|
|
|
|
shell-file-name (or (getenv "SHELL") shell-file-name))
|
|
|
|
env)))
|
|
|
|
|
|
|
|
|
|
|
|
;;
|
|
|
|
;;; Functional library
|
|
|
|
|
2019-07-21 19:13:21 +02:00
|
|
|
(defalias 'doom-partial #'apply-partially)
|
|
|
|
|
|
|
|
(defun doom-rpartial (fn &rest args)
|
|
|
|
"Return a function that is a partial application of FUN to right-hand ARGS.
|
|
|
|
|
|
|
|
ARGS is a list of the last N arguments to pass to FUN. The result is a new
|
|
|
|
function which does the same as FUN, except that the last N arguments are fixed
|
|
|
|
at the values with which this function was called."
|
2020-04-08 15:29:29 -04:00
|
|
|
(declare (side-effect-free t))
|
2019-07-21 19:13:21 +02:00
|
|
|
(lambda (&rest pre-args)
|
|
|
|
(apply fn (append pre-args args))))
|
|
|
|
|
2018-09-07 19:38:16 -04:00
|
|
|
|
|
|
|
;;
|
2019-07-21 14:40:38 +02:00
|
|
|
;;; Sugars
|
2018-09-07 19:38:16 -04:00
|
|
|
|
2017-02-23 00:06:12 -05:00
|
|
|
(defmacro λ! (&rest body)
|
2019-12-13 14:35:28 -05:00
|
|
|
"Expands to (lambda () (interactive) ,@body).
|
|
|
|
A factory for quickly producing interaction commands, particularly for keybinds
|
|
|
|
or aliases."
|
2019-12-19 14:49:17 -05:00
|
|
|
(declare (doc-string 1) (pure t) (side-effect-free t))
|
2020-05-11 19:43:52 -04:00
|
|
|
`(lambda (&rest _) (interactive) ,@body))
|
2019-07-18 15:25:01 +02:00
|
|
|
(defalias 'lambda! 'λ!)
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2019-07-23 19:09:29 +02:00
|
|
|
(defun λ!! (command &optional arg)
|
2019-12-13 14:35:28 -05:00
|
|
|
"Expands to a command that interactively calls COMMAND with prefix ARG.
|
|
|
|
A factory for quickly producing interactive, prefixed commands for keybinds or
|
|
|
|
aliases."
|
2019-12-19 14:49:17 -05:00
|
|
|
(declare (doc-string 1) (pure t) (side-effect-free t))
|
2020-05-11 19:43:52 -04:00
|
|
|
(lambda (&rest _) (interactive)
|
2019-07-25 20:37:44 +03:00
|
|
|
(let ((current-prefix-arg arg))
|
|
|
|
(call-interactively command))))
|
2019-07-09 02:33:31 +02:00
|
|
|
(defalias 'lambda!! 'λ!!)
|
2018-02-01 14:46:17 +08:00
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
(defun dir! ()
|
|
|
|
"Returns the directory of the emacs lisp file this macro is called from."
|
|
|
|
(when-let (path (file!))
|
|
|
|
(directory-file-name (file-name-directory path))))
|
|
|
|
|
2019-07-21 14:44:04 +02:00
|
|
|
(defun file! ()
|
|
|
|
"Return the emacs lisp file this macro is called from."
|
|
|
|
(cond ((bound-and-true-p byte-compile-current-file))
|
2019-07-27 17:00:12 +02:00
|
|
|
(load-file-name)
|
2019-07-21 14:44:04 +02:00
|
|
|
((stringp (car-safe current-load-list))
|
|
|
|
(car current-load-list))
|
2019-07-27 17:00:12 +02:00
|
|
|
(buffer-file-name)
|
|
|
|
((error "Cannot get this file-path"))))
|
2019-07-21 14:44:04 +02:00
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
(defmacro letenv! (envvars &rest body)
|
|
|
|
"Lexically bind ENVVARS in BODY, like `let' but for `process-environment'."
|
|
|
|
(declare (indent 1))
|
|
|
|
`(let ((process-environment (copy-sequence process-environment)))
|
|
|
|
(dolist (var (list ,@(cl-loop for (var val) in envvars
|
|
|
|
collect `(cons ,var ,val))))
|
|
|
|
(setenv (car var) (cdr var)))
|
|
|
|
,@body))
|
|
|
|
|
2020-04-29 21:08:17 -04:00
|
|
|
(defmacro letf! (bindings &rest body)
|
|
|
|
"Temporarily rebind function and macros in BODY.
|
|
|
|
|
|
|
|
BINDINGS is either a) a list of, or a single, `defun' or `defmacro'-ish form, or
|
|
|
|
b) a list of (PLACE VALUE) bindings as `cl-letf*' would accept.
|
|
|
|
|
|
|
|
TYPE is either `defun' or `defmacro'. NAME is the name of the function. If an
|
|
|
|
original definition for NAME exists, it can be accessed as a lexical variable by
|
|
|
|
the same name, for use with `funcall' or `apply'. ARGLIST and BODY are as in
|
|
|
|
`defun'.
|
|
|
|
|
|
|
|
\(fn ((TYPE NAME ARGLIST &rest BODY) ...) BODY...)"
|
|
|
|
(declare (indent defun))
|
|
|
|
(setq body (macroexp-progn body))
|
|
|
|
(when (memq (car bindings) '(defun defmacro))
|
|
|
|
(setq bindings (list bindings)))
|
2020-05-14 22:32:03 -04:00
|
|
|
(dolist (binding (reverse bindings) (macroexpand body))
|
2020-04-29 21:08:17 -04:00
|
|
|
(let ((type (car binding))
|
|
|
|
(rest (cdr binding)))
|
|
|
|
(setq
|
|
|
|
body (pcase type
|
|
|
|
(`defmacro `(cl-macrolet ((,(car rest) ,(cadr rest) ,@(cddr rest))) ,body))
|
|
|
|
(`defun `(cl-letf* ((,(car rest) (symbol-function #',(car rest)))
|
2020-05-14 22:32:03 -04:00
|
|
|
((symbol-function #',(car rest))
|
|
|
|
(lambda ,(cadr rest) ,@(cddr rest))))
|
|
|
|
(ignore ,(car rest))
|
|
|
|
,body))
|
2020-04-29 21:08:17 -04:00
|
|
|
(_
|
|
|
|
(when (eq (car-safe type) 'function)
|
2020-05-14 22:32:03 -04:00
|
|
|
(setq type (list 'symbol-function type)))
|
|
|
|
(list 'cl-letf (list (cons type rest)) body)))))))
|
2020-04-29 21:08:17 -04:00
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
(defmacro quiet! (&rest forms)
|
|
|
|
"Run FORMS without generating any output.
|
|
|
|
|
2020-05-14 22:37:39 -04:00
|
|
|
This silences calls to `message', `load', `write-region' and anything that
|
2020-04-29 20:45:29 -04:00
|
|
|
writes to `standard-output'."
|
2020-05-14 22:36:43 -04:00
|
|
|
`(if doom-debug-mode
|
|
|
|
(progn ,@forms)
|
|
|
|
,(if doom-interactive-mode
|
|
|
|
`(let ((inhibit-message t)
|
|
|
|
(save-silently t))
|
|
|
|
(prog1 ,@forms (message "")))
|
|
|
|
`(letf! ((standard-output (lambda (&rest _)))
|
|
|
|
(defun message (&rest _))
|
2020-05-14 22:37:39 -04:00
|
|
|
(defun load (file &optional noerror nomessage nosuffix must-suffix)
|
|
|
|
(funcall load file noerror t nosuffix must-suffix))
|
2020-05-14 22:36:43 -04:00
|
|
|
(defun write-region (start end filename &optional append visit lockname mustbenew)
|
|
|
|
(unless visit (setq visit 'no-message))
|
|
|
|
(funcall write-region start end filename append visit lockname mustbenew)))
|
|
|
|
,@forms))))
|
2020-04-29 20:45:29 -04:00
|
|
|
|
2020-04-29 21:09:10 -04:00
|
|
|
(defmacro if! (cond then &rest body)
|
|
|
|
"Expands to THEN if COND is non-nil, to BODY otherwise.
|
|
|
|
COND is checked at compile/expansion time, allowing BODY to be omitted
|
|
|
|
entirely when the elisp is byte-compiled. Use this for forms that contain
|
|
|
|
expensive macros that could safely be removed at compile time."
|
|
|
|
(declare (indent 2))
|
|
|
|
(if (eval cond)
|
|
|
|
then
|
|
|
|
(macroexp-progn body)))
|
|
|
|
|
|
|
|
(defmacro when! (cond &rest body)
|
|
|
|
"Expands to BODY if CONDITION is non-nil at compile/expansion time.
|
|
|
|
See `if!' for details on this macro's purpose."
|
|
|
|
(declare (indent 1))
|
|
|
|
(when (eval cond)
|
|
|
|
(macroexp-progn body)))
|
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
|
|
|
|
;;; Mutation
|
|
|
|
(defmacro appendq! (sym &rest lists)
|
|
|
|
"Append LISTS to SYM in place."
|
|
|
|
`(setq ,sym (append ,sym ,@lists)))
|
|
|
|
|
|
|
|
(defmacro setq! (&rest settings)
|
|
|
|
"A stripped-down `customize-set-variable' with the syntax of `setq'.
|
|
|
|
|
2020-05-20 01:05:27 -04:00
|
|
|
This can be used as a drop-in replacement for `setq'. Particularly when you know
|
|
|
|
a variable has a custom setter (a :set property in its `defcustom' declaration).
|
|
|
|
This triggers setters. `setq' does not."
|
2020-04-29 20:45:29 -04:00
|
|
|
(macroexp-progn
|
|
|
|
(cl-loop for (var val) on settings by 'cddr
|
2020-05-20 01:05:27 -04:00
|
|
|
collect `(funcall (or (get ',var 'custom-set) #'set)
|
|
|
|
',var ,val))))
|
2020-04-29 20:45:29 -04:00
|
|
|
|
|
|
|
(defmacro delq! (elt list &optional fetcher)
|
|
|
|
"`delq' ELT from LIST in-place.
|
|
|
|
|
|
|
|
If FETCHER is a function, ELT is used as the key in LIST (an alist)."
|
|
|
|
`(setq ,list
|
|
|
|
(delq ,(if fetcher
|
|
|
|
`(funcall ,fetcher ,elt ,list)
|
|
|
|
elt)
|
|
|
|
,list)))
|
|
|
|
|
|
|
|
(defmacro pushnew! (place &rest values)
|
|
|
|
"Push VALUES sequentially into PLACE, if they aren't already present.
|
|
|
|
This is a variadic `cl-pushnew'."
|
|
|
|
(let ((var (make-symbol "result")))
|
|
|
|
`(dolist (,var (list ,@values) (with-no-warnings ,place))
|
|
|
|
(cl-pushnew ,var ,place :test #'equal))))
|
|
|
|
|
|
|
|
(defmacro prependq! (sym &rest lists)
|
|
|
|
"Prepend LISTS to SYM in place."
|
|
|
|
`(setq ,sym (append ,@lists ,sym)))
|
|
|
|
|
|
|
|
|
|
|
|
;;; Loading
|
|
|
|
(defmacro add-load-path! (&rest dirs)
|
|
|
|
"Add DIRS to `load-path', relative to the current file.
|
|
|
|
The current file is the file from which `add-to-load-path!' is used."
|
|
|
|
`(let ((default-directory ,(dir!))
|
|
|
|
file-name-handler-alist)
|
|
|
|
(dolist (dir (list ,@dirs))
|
|
|
|
(cl-pushnew (expand-file-name dir) load-path))))
|
2019-07-21 14:44:04 +02:00
|
|
|
|
2020-01-30 16:57:49 -05:00
|
|
|
(defmacro after! (package &rest body)
|
|
|
|
"Evaluate BODY after PACKAGE have loaded.
|
|
|
|
|
|
|
|
PACKAGE is a symbol or list of them. These are package names, not modes,
|
|
|
|
functions or variables. It can be:
|
|
|
|
|
|
|
|
- An unquoted package symbol (the name of a package)
|
|
|
|
(after! helm BODY...)
|
|
|
|
- An unquoted list of package symbols (i.e. BODY is evaluated once both magit
|
|
|
|
and git-gutter have loaded)
|
|
|
|
(after! (magit git-gutter) BODY...)
|
|
|
|
- An unquoted, nested list of compound package lists, using any combination of
|
|
|
|
:or/:any and :and/:all
|
|
|
|
(after! (:or package-a package-b ...) BODY...)
|
|
|
|
(after! (:and package-a package-b ...) BODY...)
|
|
|
|
(after! (:and package-a (:or package-b package-c) ...) BODY...)
|
|
|
|
Without :or/:any/:and/:all, :and/:all are implied.
|
|
|
|
|
|
|
|
This is a wrapper around `eval-after-load' that:
|
|
|
|
|
|
|
|
1. Suppresses warnings for disabled packages at compile-time
|
|
|
|
2. No-ops for package that are disabled by the user (via `package!')
|
|
|
|
3. Supports compound package statements (see below)
|
|
|
|
4. Prevents eager expansion pulling in autoloaded macros all at once"
|
|
|
|
(declare (indent defun) (debug t))
|
|
|
|
(if (symbolp package)
|
|
|
|
(unless (memq package (bound-and-true-p doom-disabled-packages))
|
|
|
|
(list (if (or (not (bound-and-true-p byte-compile-current-file))
|
|
|
|
(require package nil 'noerror))
|
|
|
|
#'progn
|
|
|
|
#'with-no-warnings)
|
|
|
|
(let ((body (macroexp-progn body)))
|
|
|
|
`(if (featurep ',package)
|
|
|
|
,body
|
|
|
|
;; We intentionally avoid `with-eval-after-load' to prevent
|
|
|
|
;; eager macro expansion from pulling (or failing to pull) in
|
|
|
|
;; autoloaded macros/packages.
|
|
|
|
(eval-after-load ',package ',body)))))
|
|
|
|
(let ((p (car package)))
|
|
|
|
(cond ((not (keywordp p))
|
|
|
|
`(after! (:and ,@package) ,@body))
|
|
|
|
((memq p '(:or :any))
|
|
|
|
(macroexp-progn
|
|
|
|
(cl-loop for next in (cdr package)
|
|
|
|
collect `(after! ,next ,@body))))
|
|
|
|
((memq p '(:and :all))
|
|
|
|
(dolist (next (cdr package))
|
|
|
|
(setq body `((after! ,next ,@body))))
|
|
|
|
(car body))))))
|
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
(defun doom--handle-load-error (e target path)
|
|
|
|
(let* ((source (file-name-sans-extension target))
|
|
|
|
(err (cond ((not (featurep 'core))
|
|
|
|
(cons 'error (file-name-directory path)))
|
|
|
|
((file-in-directory-p source doom-core-dir)
|
|
|
|
(cons 'doom-error doom-core-dir))
|
|
|
|
((file-in-directory-p source doom-private-dir)
|
|
|
|
(cons 'doom-private-error doom-private-dir))
|
|
|
|
((cons 'doom-module-error doom-emacs-dir)))))
|
|
|
|
(signal (car err)
|
|
|
|
(list (file-relative-name
|
|
|
|
(concat source ".el")
|
|
|
|
(cdr err))
|
|
|
|
e))))
|
2020-01-24 16:38:44 -05:00
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
(defmacro load! (filename &optional path noerror)
|
|
|
|
"Load a file relative to the current executing file (`load-file-name').
|
2019-07-21 19:11:43 +02:00
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
FILENAME is either a file path string or a form that should evaluate to such a
|
|
|
|
string at run time. PATH is where to look for the file (a string representing a
|
|
|
|
directory path). If omitted, the lookup is relative to either `load-file-name',
|
|
|
|
`byte-compile-current-file' or `buffer-file-name' (checked in that order).
|
2019-07-21 14:39:16 +02:00
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
If NOERROR is non-nil, don't throw an error if the file doesn't exist."
|
|
|
|
(let* ((path (or path
|
|
|
|
(dir!)
|
|
|
|
(error "Could not detect path to look for '%s' in"
|
|
|
|
filename)))
|
|
|
|
(file (if path
|
|
|
|
`(expand-file-name ,filename ,path)
|
|
|
|
filename)))
|
|
|
|
`(condition-case-unless-debug e
|
|
|
|
(let (file-name-handler-alist)
|
|
|
|
(load ,file ,noerror 'nomessage))
|
|
|
|
(doom-error (signal (car e) (cdr e)))
|
|
|
|
(error (doom--handle-load-error e ,file ,path)))))
|
2019-07-21 14:39:16 +02:00
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
(defmacro defer-until! (condition &rest body)
|
|
|
|
"Run BODY when CONDITION is true (checks on `after-load-functions'). Meant to
|
|
|
|
serve as a predicated alternative to `after!'."
|
|
|
|
(declare (indent defun) (debug t))
|
|
|
|
`(if ,condition
|
|
|
|
(progn ,@body)
|
|
|
|
,(let ((fn (intern (format "doom--delay-form-%s-h" (sxhash (cons condition body))))))
|
|
|
|
`(progn
|
|
|
|
(fset ',fn (lambda (&rest args)
|
|
|
|
(when ,(or condition t)
|
|
|
|
(remove-hook 'after-load-functions #',fn)
|
|
|
|
(unintern ',fn nil)
|
|
|
|
(ignore args)
|
|
|
|
,@body)))
|
|
|
|
(put ',fn 'permanent-local-hook t)
|
|
|
|
(add-hook 'after-load-functions #',fn)))))
|
2019-08-10 11:07:25 -04:00
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
(defmacro defer-feature! (feature &optional fn)
|
|
|
|
"Pretend FEATURE hasn't been loaded yet, until FEATURE-hook or FN runs.
|
2019-04-08 23:01:30 -04:00
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
Some packages (like `elisp-mode' and `lisp-mode') are loaded immediately at
|
|
|
|
startup, which will prematurely trigger `after!' (and `with-eval-after-load')
|
|
|
|
blocks. To get around this we make Emacs believe FEATURE hasn't been loaded yet,
|
|
|
|
then wait until FEATURE-hook (or MODE-hook, if FN is provided) is triggered to
|
|
|
|
reverse this and trigger `after!' blocks at a more reasonable time."
|
|
|
|
(let ((advice-fn (intern (format "doom--defer-feature-%s-a" feature)))
|
|
|
|
(fn (or fn feature)))
|
|
|
|
`(progn
|
|
|
|
(setq features (delq ',feature features))
|
|
|
|
(advice-add #',fn :before #',advice-fn)
|
|
|
|
(defun ,advice-fn (&rest _)
|
|
|
|
;; Some plugins (like yasnippet) will invoke a fn early to parse
|
|
|
|
;; code, which would prematurely trigger this. In those cases, well
|
|
|
|
;; behaved plugins will use `delay-mode-hooks', which we can check for:
|
|
|
|
(when (and ,(intern (format "%s-hook" fn))
|
|
|
|
(not delay-mode-hooks))
|
|
|
|
;; ...Otherwise, announce to the world this package has been loaded,
|
|
|
|
;; so `after!' handlers can react.
|
|
|
|
(provide ',feature)
|
|
|
|
(advice-remove #',fn #',advice-fn))))))
|
2019-12-08 16:11:29 -05:00
|
|
|
|
2019-10-23 04:24:06 -04:00
|
|
|
|
2020-03-03 18:58:45 -05:00
|
|
|
;;; Hooks
|
|
|
|
(defvar doom--transient-counter 0)
|
2018-07-09 15:33:31 +02:00
|
|
|
(defmacro add-transient-hook! (hook-or-function &rest forms)
|
|
|
|
"Attaches a self-removing function to HOOK-OR-FUNCTION.
|
2017-06-05 16:41:39 +02:00
|
|
|
|
2019-05-01 19:12:52 -04:00
|
|
|
FORMS are evaluated once, when that function/hook is first invoked, then never
|
2018-07-09 15:33:31 +02:00
|
|
|
again.
|
2017-06-05 16:41:39 +02:00
|
|
|
|
2018-07-09 15:33:31 +02:00
|
|
|
HOOK-OR-FUNCTION can be a quoted hook or a sharp-quoted function (which will be
|
|
|
|
advised)."
|
2017-03-02 18:22:37 -05:00
|
|
|
(declare (indent 1))
|
2018-05-15 13:40:18 +02:00
|
|
|
(let ((append (if (eq (car forms) :after) (pop forms)))
|
2020-03-03 18:58:45 -05:00
|
|
|
;; Avoid `make-symbol' and `gensym' here because an interned symbol is
|
|
|
|
;; easier to debug in backtraces (and is visible to `describe-function')
|
|
|
|
(fn (intern (format "doom--transient-%d-h" (cl-incf doom--transient-counter)))))
|
2019-05-04 18:55:24 -04:00
|
|
|
`(let ((sym ,hook-or-function))
|
2019-07-21 14:40:38 +02:00
|
|
|
(defun ,fn (&rest _)
|
2020-03-03 18:58:45 -05:00
|
|
|
,(format "Transient hook for %S" (doom-unquote hook-or-function))
|
2019-07-21 14:40:38 +02:00
|
|
|
,@forms
|
|
|
|
(let ((sym ,hook-or-function))
|
|
|
|
(cond ((functionp sym) (advice-remove sym #',fn))
|
|
|
|
((symbolp sym) (remove-hook sym #',fn))))
|
|
|
|
(unintern ',fn nil))
|
2019-05-04 18:55:24 -04:00
|
|
|
(cond ((functionp sym)
|
2018-07-09 15:33:31 +02:00
|
|
|
(advice-add ,hook-or-function ,(if append :after :before) #',fn))
|
2019-05-04 18:55:24 -04:00
|
|
|
((symbolp sym)
|
2018-05-24 22:35:45 +02:00
|
|
|
(put ',fn 'permanent-local-hook t)
|
2019-05-04 18:55:24 -04:00
|
|
|
(add-hook sym #',fn ,append))))))
|
2017-03-02 01:43:00 -05:00
|
|
|
|
Backport bits of CLI rewrite
The rewrite for Doom's CLI is taking a while, so I've backported a few
important changes in order to ease the transition and fix a couple bugs
sooner.
Fixes #2802, #2737, #2386
The big highlights are:
- Fix #2802: We now update recipe repos *before* updating/installing any
new packages. No more "Could not find package X in recipe repositories".
- Fix #2737: An edge case where straight couldn't reach a pinned
commit (particularly with agda).
- Doom is now smarter about what option it recommends when straight
prompts you to make a choice.
- Introduces a new init path for Doom. The old way:
- Launch in "minimal" CLI mode in non-interactive sessions
- Launch a "full" interactive mode otherwise.
The new way
- Launch in "minimal" CLI mode *only* for bin/doom
- Launch is a simple mode for non-interactive sessions that still need
access to your interactive config (like async org export/babel).
- Launch a "full" interactive mode otherwise.
This should fix compatibility issues with plugins that use the
async.el library or spawn child Emacs processes to fake
parallelization (like org's async export and babel functionality).
- Your private init.el is now loaded more reliably when running any
bin/doom command. This gives you an opportunity to configure its
settings.
- Added doom-first-{input,buffer,file}-hook hooks, which we use to queue
deferred activation of a number of packages. Users can remove these
modes from these hooks; altogether preventing them from loading,
rather than waiting for them to load to then disable them,
e.g. (after! smartparens (smartparens-global-mode -1)) -> (remove-hook
'doom-first-buffer #'smartparens-global-mode)
Hooks added to doom-first-*-hook variables will be removed once they
run.
This should also indirectly fix #2386, by preventing interactive modes
from running in non-interactive session.
- Added `doom/bump-*` commands to make bumping modules and packages
easier, and `doom/bumpify-*` commands for converting package!
statements into user/repo@sha1hash format for bump commits.
- straight.el is now commit-pinned, like all other packages. We also
more reliably install straight.el by cloning it ourselves, rather than
relying on its bootstrap.el.
This should prevent infinite "straight has diverged from master"
prompts whenever we change branches (though, you might have to put up
with it one more after this update -- see #2937 for workaround).
All the other minor changes:
- Moved core/autoload/cli.el to core/autoload/process.el
- The package manager will log attempts to check out pinned commits
- If package state is incomplete while rebuilding packages, emit a
simpler error message instead of an obscure one!
- Added -u switch to 'doom sync' to make it run 'doom update' afterwards
- Added -p switch to 'doom sync' to make it run 'doom purge' afterwards
- Replace doom-modules function with doom-modules-list
- The `with-plist!` macro was removed, since `cl-destructuring-bind`
already serves that purpose well enough.
- core/autoload/packages.el was moved into core-packages.el
- bin/doom will no longer die if DOOMDIR or DOOMLOCALDIR don't have a
trailing slash
- Introduces doom-debug-variables; a list of variables to toggle on
doom/toggle-debug-mode.
- The sandbox has been updated to reflect the above changes, also:
1. Child instances will no longer inherit the process environment of
the host instance,
2. It will no longer produce an auto-save-list directory in ~/.emacs.d
2020-05-14 15:00:23 -04:00
|
|
|
(defmacro add-hook-trigger! (hook-var &rest targets)
|
|
|
|
"TODO"
|
|
|
|
`(let ((fn (intern (format "%s-h" ,hook-var))))
|
|
|
|
(fset fn (lambda (&rest _) (run-hooks ,hook-var) (set ,hook-var nil)))
|
|
|
|
(put ,hook-var 'permanent-local t)
|
|
|
|
(dolist (on (list ,@targets))
|
|
|
|
(if (functionp on)
|
|
|
|
(advice-add on :before fn)
|
|
|
|
(add-hook on fn)))))
|
|
|
|
|
2019-07-26 19:57:13 +02:00
|
|
|
(defmacro add-hook! (hooks &rest rest)
|
2019-05-01 19:12:52 -04:00
|
|
|
"A convenience macro for adding N functions to M hooks.
|
|
|
|
|
|
|
|
This macro accepts, in order:
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2020-01-20 05:41:42 -05:00
|
|
|
1. The mode(s) or hook(s) to add to. This is either an unquoted mode, an
|
|
|
|
unquoted list of modes, a quoted hook variable or a quoted list of hook
|
|
|
|
variables.
|
|
|
|
2. Optional properties :local and/or :append, which will make the hook
|
2017-02-28 15:29:23 -05:00
|
|
|
buffer-local or append to the list of hooks (respectively),
|
2020-01-20 05:41:42 -05:00
|
|
|
3. The function(s) to be added: this can be one function, a quoted list
|
|
|
|
thereof, a list of `defun's, or body forms (implicitly wrapped in a
|
|
|
|
lambda).
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2019-09-16 12:07:32 -04:00
|
|
|
\(fn HOOKS [:append :local] FUNCTIONS)"
|
2019-07-26 19:57:13 +02:00
|
|
|
(declare (indent (lambda (indent-point state)
|
|
|
|
(goto-char indent-point)
|
|
|
|
(when (looking-at-p "\\s-*(")
|
|
|
|
(lisp-indent-defform state indent-point))))
|
|
|
|
(debug t))
|
|
|
|
(let* ((hook-forms (doom--resolve-hook-forms hooks))
|
|
|
|
(func-forms ())
|
|
|
|
(defn-forms ())
|
|
|
|
append-p
|
|
|
|
local-p
|
|
|
|
remove-p
|
|
|
|
forms)
|
|
|
|
(while (keywordp (car rest))
|
|
|
|
(pcase (pop rest)
|
2017-02-28 15:29:23 -05:00
|
|
|
(:append (setq append-p t))
|
2017-06-08 11:47:56 +02:00
|
|
|
(:local (setq local-p t))
|
2019-07-26 19:57:13 +02:00
|
|
|
(:remove (setq remove-p t))))
|
|
|
|
(let ((first (car-safe (car rest))))
|
|
|
|
(cond ((null first)
|
|
|
|
(setq func-forms rest))
|
|
|
|
|
|
|
|
((eq first 'defun)
|
|
|
|
(setq func-forms (mapcar #'cadr rest)
|
|
|
|
defn-forms rest))
|
|
|
|
|
|
|
|
((memq first '(quote function))
|
|
|
|
(setq func-forms
|
|
|
|
(if (cdr rest)
|
|
|
|
(mapcar #'doom-unquote rest)
|
|
|
|
(doom-enlist (doom-unquote (car rest))))))
|
|
|
|
|
2019-10-28 11:54:24 -04:00
|
|
|
((setq func-forms (list `(lambda (&rest _) ,@rest)))))
|
2019-07-26 19:57:13 +02:00
|
|
|
(dolist (hook hook-forms)
|
|
|
|
(dolist (func func-forms)
|
|
|
|
(push (if remove-p
|
|
|
|
`(remove-hook ',hook #',func ,local-p)
|
|
|
|
`(add-hook ',hook #',func ,append-p ,local-p))
|
2017-02-28 15:29:23 -05:00
|
|
|
forms)))
|
2019-07-18 15:27:20 +02:00
|
|
|
(macroexp-progn
|
2019-07-26 19:57:13 +02:00
|
|
|
(append defn-forms
|
|
|
|
(if append-p
|
|
|
|
(nreverse forms)
|
|
|
|
forms))))))
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2019-07-26 19:57:13 +02:00
|
|
|
(defmacro remove-hook! (hooks &rest rest)
|
2019-05-01 19:12:52 -04:00
|
|
|
"A convenience macro for removing N functions from M hooks.
|
|
|
|
|
|
|
|
Takes the same arguments as `add-hook!'.
|
|
|
|
|
|
|
|
If N and M = 1, there's no benefit to using this macro over `remove-hook'.
|
2019-03-10 08:15:46 -04:00
|
|
|
|
2019-09-16 12:07:32 -04:00
|
|
|
\(fn HOOKS [:append :local] FUNCTIONS)"
|
2018-05-07 22:35:14 +02:00
|
|
|
(declare (indent defun) (debug t))
|
2019-07-26 19:57:13 +02:00
|
|
|
`(add-hook! ,hooks :remove ,@rest))
|
2017-02-28 15:38:47 -05:00
|
|
|
|
2019-07-21 14:35:45 +02:00
|
|
|
(defmacro setq-hook! (hooks &rest var-vals)
|
2019-05-01 19:12:52 -04:00
|
|
|
"Sets buffer-local variables on HOOKS.
|
2018-05-07 22:35:14 +02:00
|
|
|
|
2019-07-21 14:35:45 +02:00
|
|
|
\(fn HOOKS &rest [SYM VAL]...)"
|
2018-05-07 22:35:14 +02:00
|
|
|
(declare (indent 1))
|
2019-07-21 14:35:45 +02:00
|
|
|
(macroexp-progn
|
|
|
|
(cl-loop for (var val hook fn) in (doom--setq-hook-fns hooks var-vals)
|
|
|
|
collect `(defun ,fn (&rest _)
|
|
|
|
,(format "%s = %s" var (pp-to-string val))
|
|
|
|
(setq-local ,var ,val))
|
|
|
|
collect `(remove-hook ',hook #',fn) ; ensure set order
|
2019-07-29 21:04:04 +02:00
|
|
|
collect `(add-hook ',hook #',fn))))
|
2019-07-21 14:35:45 +02:00
|
|
|
|
|
|
|
(defmacro unsetq-hook! (hooks &rest vars)
|
|
|
|
"Unbind setq hooks on HOOKS for VARS.
|
|
|
|
|
|
|
|
\(fn HOOKS &rest [SYM VAL]...)"
|
|
|
|
(declare (indent 1))
|
|
|
|
(macroexp-progn
|
2019-10-07 16:10:33 -04:00
|
|
|
(cl-loop for (_var _val hook fn)
|
|
|
|
in (doom--setq-hook-fns hooks vars 'singles)
|
2019-07-21 14:35:45 +02:00
|
|
|
collect `(remove-hook ',hook #',fn))))
|
2018-05-07 22:35:14 +02:00
|
|
|
|
2019-07-18 15:25:01 +02:00
|
|
|
|
2019-07-23 17:24:56 +02:00
|
|
|
;;; Definers
|
|
|
|
(defmacro defadvice! (symbol arglist &optional docstring &rest body)
|
2019-08-05 18:27:56 -05:00
|
|
|
"Define an advice called SYMBOL and add it to PLACES.
|
2019-07-23 17:24:56 +02:00
|
|
|
|
|
|
|
ARGLIST is as in `defun'. WHERE is a keyword as passed to `advice-add', and
|
|
|
|
PLACE is the function to which to add the advice, like in `advice-add'.
|
|
|
|
DOCSTRING and BODY are as in `defun'.
|
|
|
|
|
|
|
|
\(fn SYMBOL ARGLIST &optional DOCSTRING &rest [WHERE PLACES...] BODY\)"
|
|
|
|
(declare (doc-string 3) (indent defun))
|
|
|
|
(unless (stringp docstring)
|
|
|
|
(push docstring body)
|
|
|
|
(setq docstring nil))
|
|
|
|
(let (where-alist)
|
|
|
|
(while (keywordp (car body))
|
|
|
|
(push `(cons ,(pop body) (doom-enlist ,(pop body)))
|
|
|
|
where-alist))
|
|
|
|
`(progn
|
|
|
|
(defun ,symbol ,arglist ,docstring ,@body)
|
2019-12-19 14:49:17 -05:00
|
|
|
(dolist (targets (list ,@(nreverse where-alist)))
|
|
|
|
(dolist (target (cdr targets))
|
|
|
|
(advice-add target (car targets) #',symbol))))))
|
2019-07-23 17:24:56 +02:00
|
|
|
|
2020-01-01 14:31:49 -05:00
|
|
|
(defmacro undefadvice! (symbol _arglist &optional docstring &rest body)
|
|
|
|
"Undefine an advice called SYMBOL.
|
|
|
|
|
|
|
|
This has the same signature as `defadvice!' an exists as an easy undefiner when
|
|
|
|
testing advice (when combined with `rotate-text').
|
|
|
|
|
|
|
|
\(fn SYMBOL ARGLIST &optional DOCSTRING &rest [WHERE PLACES...] BODY\)"
|
|
|
|
(declare (doc-string 3) (indent defun))
|
|
|
|
(let (where-alist)
|
|
|
|
(unless (stringp docstring)
|
|
|
|
(push docstring body))
|
|
|
|
(while (keywordp (car body))
|
|
|
|
(push `(cons ,(pop body) (doom-enlist ,(pop body)))
|
|
|
|
where-alist))
|
|
|
|
`(dolist (targets (list ,@(nreverse where-alist)))
|
|
|
|
(dolist (target (cdr targets))
|
|
|
|
(advice-remove target #',symbol)))))
|
|
|
|
|
2017-01-16 23:15:48 -05:00
|
|
|
(provide 'core-lib)
|
|
|
|
;;; core-lib.el ends here
|