2017-06-08 11:47:56 +02:00
|
|
|
;;; core-lib.el -*- lexical-binding: t; -*-
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2019-07-21 04:02:09 +02:00
|
|
|
(let ((load-path doom--initial-load-path))
|
2019-06-16 23:01:17 +02:00
|
|
|
(require 'subr-x)
|
|
|
|
(require 'cl-lib))
|
|
|
|
|
|
|
|
;; Polyfills
|
|
|
|
(unless EMACS26+
|
|
|
|
(with-no-warnings
|
|
|
|
;; `kill-current-buffer' was introduced in Emacs 26
|
|
|
|
(defalias 'kill-current-buffer #'kill-this-buffer)
|
|
|
|
;; if-let and when-let were moved to (if|when)-let* in Emacs 26+ so we alias
|
|
|
|
;; them for 25 users.
|
|
|
|
(defalias 'if-let* #'if-let)
|
2019-07-22 22:31:09 +02:00
|
|
|
(defalias 'when-let* #'when-let)
|
|
|
|
|
|
|
|
(defun alist-get (key alist &optional default remove testfn)
|
|
|
|
"Return the value associated with KEY in ALIST.
|
|
|
|
If KEY is not found in ALIST, return DEFAULT.
|
|
|
|
Use TESTFN to lookup in the alist if non-nil. Otherwise, use `assq'.
|
|
|
|
|
|
|
|
This is a generalized variable suitable for use with `setf'.
|
|
|
|
When using it to set a value, optional argument REMOVE non-nil
|
|
|
|
means to remove KEY from ALIST if the new value is `eql' to DEFAULT."
|
|
|
|
(ignore remove) ;;Silence byte-compiler.
|
|
|
|
(let ((x (if (not testfn)
|
|
|
|
(assq key alist)
|
|
|
|
;; In Emacs<26, `assoc' has no testfn arg, so we have to
|
|
|
|
;; implement it ourselves
|
|
|
|
(if testfn
|
|
|
|
(cl-loop for entry in alist
|
|
|
|
if (funcall testfn key entry)
|
|
|
|
return entry)
|
|
|
|
(assoc key alist)))))
|
|
|
|
(if x (cdr x) default)))))
|
2019-06-16 23:01:17 +02: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
|
|
|
|
2018-05-24 18:35:06 +02:00
|
|
|
(defun doom--resolve-path-forms (spec &optional directory)
|
|
|
|
"Converts a simple nested series of or/and forms into a series of
|
|
|
|
`file-exists-p' checks.
|
|
|
|
|
|
|
|
For example
|
|
|
|
|
|
|
|
(doom--resolve-path-forms
|
2019-03-06 17:39:46 -05:00
|
|
|
'(or A (and B C))
|
2018-05-24 18:35:06 +02:00
|
|
|
\"~\")
|
|
|
|
|
2019-03-06 17:39:46 -05:00
|
|
|
Returns (approximately):
|
2018-05-24 18:35:06 +02:00
|
|
|
|
2019-03-06 17:39:46 -05:00
|
|
|
'(let* ((_directory \"~\")
|
|
|
|
(A (expand-file-name A _directory))
|
|
|
|
(B (expand-file-name B _directory))
|
|
|
|
(C (expand-file-name C _directory)))
|
|
|
|
(or (and (file-exists-p A) A)
|
|
|
|
(and (if (file-exists-p B) B)
|
|
|
|
(if (file-exists-p C) C))))
|
2018-05-24 18:35:06 +02:00
|
|
|
|
2019-07-21 14:38:12 +02:00
|
|
|
This is used by `file-exists-p!' and `project-file-exists-p!'."
|
2018-06-24 19:54:50 +02:00
|
|
|
(declare (pure t) (side-effect-free t))
|
2019-07-21 14:38:12 +02:00
|
|
|
(let ((exists-fn (if (fboundp 'projectile-file-exists-p)
|
|
|
|
#'projectile-file-exists-p
|
|
|
|
#'file-exists-p)))
|
2019-07-23 20:32:40 +02:00
|
|
|
(if (and (listp spec)
|
|
|
|
(memq (car spec) '(or and)))
|
|
|
|
(cons (car spec)
|
|
|
|
(mapcar (doom-rpartial #'doom--resolve-path-forms directory)
|
|
|
|
(cdr spec)))
|
|
|
|
(let ((filevar (make-symbol "file")))
|
|
|
|
`(let* ((file-name-handler-alist nil)
|
|
|
|
(,filevar ,spec))
|
|
|
|
(and ,(if directory
|
|
|
|
`(let ((default-directory ,directory))
|
|
|
|
(,exists-fn ,filevar))
|
|
|
|
(list exists-fn filevar))
|
|
|
|
,filevar))))))
|
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))
|
|
|
|
(cl-check-type :test 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))))
|
|
|
|
|
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."
|
|
|
|
(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-05-01 19:12:52 -04:00
|
|
|
"Expands to (lambda () (interactive) ,@body)."
|
2017-02-06 01:25:48 -05:00
|
|
|
(declare (doc-string 1))
|
2017-01-16 23:15:48 -05:00
|
|
|
`(lambda () (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-07-09 02:33:31 +02:00
|
|
|
"Expands to a command that interactively calls COMMAND with prefix ARG."
|
|
|
|
(declare (doc-string 1))
|
|
|
|
`(lambda () (interactive)
|
|
|
|
(let ((current-prefix-arg ,arg))
|
|
|
|
(call-interactively ,command))))
|
|
|
|
(defalias 'lambda!! 'λ!!)
|
2018-02-01 14:46:17 +08:00
|
|
|
|
2019-07-23 12:44:03 +02:00
|
|
|
(define-obsolete-function-alias 'FILE! 'file!) ; DEPRECATED
|
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))
|
|
|
|
(load-in-progress load-file-name)
|
|
|
|
((stringp (car-safe current-load-list))
|
|
|
|
(car current-load-list))
|
|
|
|
(buffer-file-name)))
|
|
|
|
|
2019-07-23 12:44:03 +02:00
|
|
|
(define-obsolete-function-alias 'DIR! 'dir!) ; DEPRECATED
|
2019-07-21 14:44:04 +02: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-04-08 23:01:30 -04:00
|
|
|
(defmacro pushnew! (place &rest values)
|
2019-07-21 19:11:43 +02:00
|
|
|
"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))
|
|
|
|
(cl-pushnew ,var ,place))))
|
|
|
|
|
|
|
|
(defmacro pushmany! (place &rest values)
|
|
|
|
"Push VALUES sequentually into PLACE.
|
|
|
|
This is a variadic `push'."
|
|
|
|
(let ((var (make-symbol "result")))
|
|
|
|
`(dolist (,var ,values)
|
|
|
|
(push ,var ,place))))
|
2019-04-08 23:01:30 -04:00
|
|
|
|
2019-07-21 14:39:16 +02:00
|
|
|
(defmacro prependq! (sym &rest lists)
|
|
|
|
"Prepend LISTS to SYM in place."
|
2019-07-23 18:10:40 +02:00
|
|
|
`(setq ,sym (append ,@lists ,sym)))
|
2019-07-21 14:39:16 +02:00
|
|
|
|
|
|
|
(defmacro appendq! (sym &rest lists)
|
|
|
|
"Append LISTS to SYM in place."
|
|
|
|
`(setq ,sym (append ,sym ,@lists)))
|
|
|
|
|
2019-07-21 19:12:11 +02:00
|
|
|
(defmacro nconcq! (sym &rest lists)
|
|
|
|
"Append LISTS to SYM by altering them in place."
|
|
|
|
`(setq ,sym (nconc ,sym ,@lists)))
|
|
|
|
|
2019-04-08 23:01:30 -04:00
|
|
|
(defmacro delq! (elt list &optional fetcher)
|
|
|
|
"Delete ELT from LIST in-place."
|
|
|
|
`(setq ,list
|
|
|
|
(delq ,(if fetcher
|
|
|
|
`(funcall ,fetcher ,elt ,list)
|
|
|
|
elt)
|
|
|
|
,list)))
|
|
|
|
|
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)))
|
2019-07-21 14:40:38 +02:00
|
|
|
(fn (intern (format "doom--transient-%s-h" (sxhash hook-or-function)))))
|
2019-05-04 18:55:24 -04:00
|
|
|
`(let ((sym ,hook-or-function))
|
2019-07-21 14:40:38 +02:00
|
|
|
(defun ,fn (&rest _)
|
|
|
|
,@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
|
|
|
|
2017-02-28 15:29:23 -05:00
|
|
|
(defmacro add-hook! (&rest args)
|
2019-05-01 19:12:52 -04:00
|
|
|
"A convenience macro for adding N functions to M hooks.
|
|
|
|
|
|
|
|
If N and M = 1, there's no benefit to using this macro over `add-hook'.
|
|
|
|
|
|
|
|
This macro accepts, in order:
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2017-02-28 15:29:23 -05:00
|
|
|
1. Optional properties :local and/or :append, which will make the hook
|
|
|
|
buffer-local or append to the list of hooks (respectively),
|
2019-05-01 19:12:52 -04:00
|
|
|
2. The hook(s) to be added to: either an unquoted mode, an unquoted list of
|
|
|
|
modes, a quoted hook variable or a quoted list of hook variables. If
|
|
|
|
unquoted, '-hook' will be appended to each symbol.
|
2019-07-18 15:27:20 +02:00
|
|
|
3. The function(s) to be added: this can be one function, a list thereof, a
|
|
|
|
list of `defun's, or body forms (implicitly wrapped in a closure).
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2019-03-10 08:15:46 -04:00
|
|
|
\(fn [:append :local] HOOKS FUNCTIONS)"
|
2017-01-16 23:15:48 -05:00
|
|
|
(declare (indent defun) (debug t))
|
2017-06-08 11:47:56 +02:00
|
|
|
(let ((hook-fn 'add-hook)
|
|
|
|
append-p local-p)
|
2017-02-28 15:29:23 -05:00
|
|
|
(while (keywordp (car args))
|
2017-03-02 18:14:52 -05:00
|
|
|
(pcase (pop args)
|
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))
|
|
|
|
(:remove (setq hook-fn 'remove-hook))))
|
2019-07-18 15:27:20 +02:00
|
|
|
(let* ((defun-forms nil)
|
|
|
|
(hooks (doom--resolve-hook-forms (pop args)))
|
|
|
|
(funcs
|
|
|
|
(let ((val (car args)))
|
|
|
|
(if (memq (car-safe val) '(quote function))
|
|
|
|
(if (cdr-safe (cadr val))
|
|
|
|
(cadr val)
|
|
|
|
(list (cadr val)))
|
|
|
|
(or (and (eq (car-safe val) 'defun)
|
|
|
|
(cl-loop for arg in args
|
|
|
|
if (not (eq (car-safe arg) 'defun))
|
|
|
|
return nil
|
|
|
|
else
|
|
|
|
collect (cadr arg)
|
|
|
|
and do (push arg defun-forms)))
|
|
|
|
(list args)))))
|
|
|
|
forms)
|
2017-02-28 15:29:23 -05:00
|
|
|
(dolist (fn funcs)
|
2017-03-02 18:14:52 -05:00
|
|
|
(setq fn (if (symbolp fn)
|
2017-04-17 02:17:10 -04:00
|
|
|
`(function ,fn)
|
2017-06-08 11:47:56 +02:00
|
|
|
`(lambda (&rest _) ,@args)))
|
2017-03-02 18:14:52 -05:00
|
|
|
(dolist (hook hooks)
|
2017-12-09 16:23:19 -05:00
|
|
|
(push (if (eq hook-fn 'remove-hook)
|
|
|
|
`(remove-hook ',hook ,fn ,local-p)
|
|
|
|
`(add-hook ',hook ,fn ,append-p ,local-p))
|
2017-02-28 15:29:23 -05:00
|
|
|
forms)))
|
2019-07-18 15:27:20 +02:00
|
|
|
(macroexp-progn
|
|
|
|
(append (nreverse defun-forms)
|
|
|
|
(if append-p (nreverse forms) forms))))))
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2017-02-28 15:38:47 -05:00
|
|
|
(defmacro remove-hook! (&rest args)
|
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
|
|
|
|
|
|
|
\(fn [:append :local] HOOKS FUNCTIONS)"
|
2018-05-07 22:35:14 +02:00
|
|
|
(declare (indent defun) (debug t))
|
2017-06-08 11:47:56 +02:00
|
|
|
`(add-hook! :remove ,@args))
|
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
|
|
|
|
|
|
|
(setq-hook! 'markdown-mode-hook
|
|
|
|
line-spacing 2
|
2019-05-01 19:12:52 -04:00
|
|
|
fill-column 80)
|
|
|
|
|
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
|
|
|
|
collect `(add-hook ',hook #',fn 'append))))
|
|
|
|
|
|
|
|
(defmacro unsetq-hook! (hooks &rest vars)
|
|
|
|
"Unbind setq hooks on HOOKS for VARS.
|
|
|
|
|
|
|
|
\(fn HOOKS &rest [SYM VAL]...)"
|
|
|
|
(declare (indent 1))
|
|
|
|
(macroexp-progn
|
|
|
|
(cl-loop for (_var _val hook fn) in (doom--setq-hook-fns hooks vars 'singles)
|
|
|
|
collect `(remove-hook ',hook #',fn))))
|
2018-05-07 22:35:14 +02:00
|
|
|
|
2019-07-23 20:32:40 +02:00
|
|
|
(defmacro file-exists-p! (files &optional directory)
|
|
|
|
"Returns non-nil if the FILES in DIRECTORY all exist.
|
2018-05-24 18:35:42 +02:00
|
|
|
|
2019-07-23 20:32:40 +02:00
|
|
|
DIRECTORY is a path; defaults to `default-directory'.
|
2018-05-24 18:35:42 +02:00
|
|
|
|
2019-07-23 20:32:40 +02:00
|
|
|
Returns the last file found to meet the rules set by FILES, which can be a
|
|
|
|
single file or nested compound statement of `and' and `or' statements."
|
|
|
|
`(let ((p ,(doom--resolve-path-forms files directory)))
|
|
|
|
(and p (expand-file-name p ,directory))))
|
2018-05-24 18:35:42 +02:00
|
|
|
|
2018-06-11 23:18:15 +02:00
|
|
|
(defmacro load! (filename &optional path noerror)
|
|
|
|
"Load a file relative to the current executing file (`load-file-name').
|
|
|
|
|
|
|
|
FILENAME is either a file path string or a form that should evaluate to such a
|
|
|
|
string at run time. PATH is where to look for the file (a string representing a
|
|
|
|
directory path). If omitted, the lookup is relative to either `load-file-name',
|
|
|
|
`byte-compile-current-file' or `buffer-file-name' (checked in that order).
|
|
|
|
|
|
|
|
If NOERROR is non-nil, don't throw an error if the file doesn't exist."
|
|
|
|
(unless path
|
2019-07-21 14:44:04 +02:00
|
|
|
(setq path (or (dir!)
|
2018-06-11 23:18:15 +02:00
|
|
|
(error "Could not detect path to look for '%s' in"
|
|
|
|
filename))))
|
2018-06-20 02:07:12 +02:00
|
|
|
(let ((file (if path `(expand-file-name ,filename ,path) filename)))
|
|
|
|
`(condition-case e
|
|
|
|
(load ,file ,noerror ,(not doom-debug-mode))
|
|
|
|
((debug doom-error) (signal (car e) (cdr e)))
|
|
|
|
((debug error)
|
|
|
|
(let* ((source (file-name-sans-extension ,file))
|
|
|
|
(err (cond ((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)))))))
|
2018-06-11 23:18:15 +02:00
|
|
|
|
2019-07-18 15:25:01 +02: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)
|
2019-07-21 14:40:38 +02:00
|
|
|
,(let ((fn (intern (format "doom--delay-form-%s-h" (sxhash (cons condition body))))))
|
2019-07-18 15:25:01 +02:00
|
|
|
`(progn
|
2019-07-21 14:40:38 +02:00
|
|
|
(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-07-18 15:25:01 +02:00
|
|
|
|
|
|
|
(defmacro defer-feature! (feature &optional mode)
|
|
|
|
"Pretend FEATURE hasn't been loaded yet, until FEATURE-hook is triggered.
|
|
|
|
|
|
|
|
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 MODE 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)))
|
|
|
|
(mode (or mode feature)))
|
|
|
|
`(progn
|
|
|
|
(setq features (delq ',feature features))
|
|
|
|
(advice-add #',mode :before #',advice-fn)
|
|
|
|
(defun ,advice-fn (&rest _)
|
|
|
|
;; Some plugins (like yasnippet) will invoke a mode 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" mode))
|
|
|
|
(not delay-mode-hooks))
|
|
|
|
;; ...Otherwise, announce to the world this package has been loaded,
|
|
|
|
;; so `after!' handlers can react.
|
|
|
|
(provide ',feature)
|
|
|
|
(advice-remove #',mode #',advice-fn))))))
|
|
|
|
|
|
|
|
(defmacro quiet! (&rest forms)
|
|
|
|
"Run FORMS without generating any output.
|
|
|
|
|
|
|
|
This silences calls to `message', `load-file', `write-region' and anything that
|
|
|
|
writes to `standard-output'."
|
|
|
|
`(cond (noninteractive
|
|
|
|
(let ((old-fn (symbol-function 'write-region)))
|
|
|
|
(cl-letf ((standard-output (lambda (&rest _)))
|
|
|
|
((symbol-function 'load-file) (lambda (file) (load file nil t)))
|
|
|
|
((symbol-function 'message) (lambda (&rest _)))
|
|
|
|
((symbol-function 'write-region)
|
|
|
|
(lambda (start end filename &optional append visit lockname mustbenew)
|
|
|
|
(unless visit (setq visit 'no-message))
|
|
|
|
(funcall old-fn start end filename append visit lockname mustbenew))))
|
|
|
|
,@forms)))
|
|
|
|
((or doom-debug-mode debug-on-error debug-on-quit)
|
|
|
|
,@forms)
|
|
|
|
((let ((inhibit-message t)
|
|
|
|
(save-silently t))
|
|
|
|
(prog1 ,@forms (message ""))))))
|
|
|
|
|
2019-07-23 17:24:56 +02:00
|
|
|
|
|
|
|
;;
|
|
|
|
;;; Definers
|
|
|
|
|
|
|
|
(define-obsolete-function-alias 'def-advice! 'defadvice!)
|
|
|
|
(defmacro defadvice! (symbol arglist &optional docstring &rest body)
|
|
|
|
"Define an advice called NAME and add it to PLACES.
|
|
|
|
|
|
|
|
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)
|
|
|
|
,(when where-alist
|
|
|
|
`(dolist (targets (list ,@(nreverse where-alist)))
|
|
|
|
(dolist (target (cdr targets))
|
|
|
|
(advice-add target (car targets) #',symbol)))))))
|
|
|
|
|
2017-01-16 23:15:48 -05:00
|
|
|
(provide 'core-lib)
|
|
|
|
;;; core-lib.el ends here
|