2017-06-08 11:47:56 +02:00
|
|
|
;;; core-lib.el -*- lexical-binding: t; -*-
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2018-09-07 19:36:16 -04:00
|
|
|
;; Built-in packages we use a lot of
|
2018-05-20 20:08:03 +02:00
|
|
|
(require 'subr-x)
|
|
|
|
(require 'cl-lib)
|
2018-05-19 16:42:31 +02:00
|
|
|
|
2018-03-02 17:42:19 -05:00
|
|
|
(eval-and-compile
|
2018-03-24 04:40:24 -04:00
|
|
|
(unless EMACS26+
|
2018-03-02 17:42:19 -05:00
|
|
|
(with-no-warnings
|
2018-05-24 18:38:50 +02:00
|
|
|
;; if-let and when-let are deprecated in Emacs 26+ in favor of their
|
|
|
|
;; if-let* variants, so we alias them for 25 users.
|
2018-03-02 17:42:19 -05:00
|
|
|
(defalias 'if-let* #'if-let)
|
2018-09-11 21:24:56 +01:00
|
|
|
(defalias 'when-let* #'when-let))))
|
2017-09-02 16:12:53 +02:00
|
|
|
|
2017-03-02 18:14:52 -05:00
|
|
|
;;
|
|
|
|
;; Helpers
|
|
|
|
|
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
|
|
|
|
'(or \"some-file\" (and path-var \"/an/absolute/path\"))
|
|
|
|
\"~\")
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
2018-06-04 13:57:18 +02:00
|
|
|
'(let ((_directory \"~\"))
|
|
|
|
(or (file-exists-p (expand-file-name \"some-file\" _directory))
|
|
|
|
(and (file-exists-p (expand-file-name path-var _directory))
|
|
|
|
(file-exists-p \"/an/absolute/path\"))))
|
2018-05-24 18:35:06 +02:00
|
|
|
|
|
|
|
This is used by `associate!', `file-exists-p!' and `project-file-exists-p!'."
|
2018-06-24 19:54:50 +02:00
|
|
|
(declare (pure t) (side-effect-free t))
|
2018-05-24 18:35:06 +02:00
|
|
|
(cond ((stringp spec)
|
2017-03-02 18:14:52 -05:00
|
|
|
`(file-exists-p
|
2018-05-24 18:35:06 +02:00
|
|
|
,(if (file-name-absolute-p spec)
|
|
|
|
spec
|
|
|
|
`(expand-file-name ,spec ,directory))))
|
|
|
|
((and (listp spec)
|
|
|
|
(memq (car spec) '(or and)))
|
|
|
|
`(,(car spec)
|
|
|
|
,@(cl-loop for i in (cdr spec)
|
|
|
|
collect (doom--resolve-path-forms i directory))))
|
2018-08-03 02:46:48 +02:00
|
|
|
((or (symbolp spec)
|
|
|
|
(listp spec))
|
|
|
|
`(file-exists-p ,(if (and directory
|
|
|
|
(or (not (stringp directory))
|
|
|
|
(file-name-absolute-p directory)))
|
|
|
|
`(expand-file-name ,spec ,directory)
|
|
|
|
spec)))
|
2018-05-24 18:35:06 +02:00
|
|
|
(t spec)))
|
2017-03-02 18:14:52 -05:00
|
|
|
|
2017-09-27 01:21:48 +02:00
|
|
|
(defun doom--resolve-hook-forms (hooks)
|
2018-06-24 19:54:50 +02:00
|
|
|
(declare (pure t) (side-effect-free t))
|
2017-06-24 16:20:22 +02:00
|
|
|
(cl-loop with quoted-p = (eq (car-safe hooks) 'quote)
|
|
|
|
for hook in (doom-enlist (doom-unquote hooks))
|
|
|
|
if (eq (car-safe hook) 'quote)
|
|
|
|
collect (cadr hook)
|
|
|
|
else if quoted-p
|
|
|
|
collect hook
|
|
|
|
else collect (intern (format "%s-hook" (symbol-name hook)))))
|
2017-03-02 18:14:52 -05:00
|
|
|
|
2018-06-11 23:18:15 +02:00
|
|
|
(defun doom--assert-stage-p (stage macro)
|
|
|
|
(cl-assert (eq stage doom--stage)
|
|
|
|
nil
|
|
|
|
"Found %s call in non-%s.el file (%s)"
|
|
|
|
macro (symbol-name stage)
|
|
|
|
(let ((path (FILE!)))
|
|
|
|
(if (file-in-directory-p path doom-emacs-dir)
|
|
|
|
(file-relative-name path doom-emacs-dir)
|
|
|
|
(abbreviate-file-name path)))))
|
|
|
|
|
2018-05-24 18:38:50 +02:00
|
|
|
|
|
|
|
;;
|
2018-09-07 19:36:16 -04: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))
|
|
|
|
|
2018-06-24 19:54:50 +02:00
|
|
|
(defun FILE! ()
|
2018-06-12 00:25:11 +02:00
|
|
|
"Return the emacs lisp file this macro is called from."
|
2018-06-24 19:54:50 +02:00
|
|
|
(cond ((bound-and-true-p byte-compile-current-file))
|
|
|
|
(load-file-name)
|
|
|
|
(buffer-file-name)
|
|
|
|
((stringp (car-safe current-load-list)) (car current-load-list))))
|
2018-06-11 23:18:15 +02:00
|
|
|
|
2018-06-24 19:54:50 +02:00
|
|
|
(defun DIR! ()
|
2018-06-12 00:25:11 +02:00
|
|
|
"Returns the directory of the emacs lisp file this macro is called from."
|
2018-06-24 19:54:50 +02:00
|
|
|
(let ((file (FILE!)))
|
|
|
|
(and file (file-name-directory file))))
|
2018-06-11 23:18:15 +02:00
|
|
|
|
2018-09-07 19:38:16 -04:00
|
|
|
|
|
|
|
;;
|
|
|
|
;; Macros
|
|
|
|
|
2017-02-23 00:06:12 -05:00
|
|
|
(defmacro λ! (&rest body)
|
2017-02-08 02:02:51 -05:00
|
|
|
"A shortcut for inline interactive lambdas."
|
2017-02-06 01:25:48 -05:00
|
|
|
(declare (doc-string 1))
|
2017-01-16 23:15:48 -05:00
|
|
|
`(lambda () (interactive) ,@body))
|
|
|
|
|
2018-02-01 14:46:17 +08:00
|
|
|
(defalias 'lambda! 'λ!)
|
|
|
|
|
2018-06-27 21:29:28 +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))
|
2018-06-28 12:31:37 +02:00
|
|
|
`(if ,condition
|
2018-06-27 21:29:28 +02:00
|
|
|
(progn ,@body)
|
2018-09-09 21:11:45 +01:00
|
|
|
,(let ((fun (make-symbol "doom|delay-form-")))
|
2018-06-27 21:29:28 +02:00
|
|
|
`(progn
|
|
|
|
(fset ',fun (lambda (&rest args)
|
2018-06-28 12:31:37 +02:00
|
|
|
(when ,(or condition t)
|
2018-06-27 21:29:28 +02:00
|
|
|
(remove-hook 'after-load-functions #',fun)
|
|
|
|
(unintern ',fun nil)
|
|
|
|
(ignore args)
|
|
|
|
,@body)))
|
|
|
|
(put ',fun 'permanent-local-hook t)
|
|
|
|
(add-hook 'after-load-functions #',fun)))))
|
|
|
|
|
2018-05-07 18:12:16 +02:00
|
|
|
(defmacro after! (targets &rest body)
|
2017-02-06 01:25:48 -05:00
|
|
|
"A smart wrapper around `with-eval-after-load'. Supresses warnings during
|
2018-05-24 18:38:50 +02:00
|
|
|
compilation. This will no-op on features that have been disabled by the user."
|
2017-01-16 23:15:48 -05:00
|
|
|
(declare (indent defun) (debug t))
|
2018-05-20 15:51:47 +02:00
|
|
|
(unless (and (symbolp targets)
|
2018-06-11 23:18:15 +02:00
|
|
|
(memq targets (bound-and-true-p doom-disabled-packages)))
|
2018-05-20 15:51:47 +02:00
|
|
|
(list (if (or (not (bound-and-true-p byte-compile-current-file))
|
|
|
|
(dolist (next (doom-enlist targets))
|
2018-06-15 23:52:19 +02:00
|
|
|
(unless (keywordp next)
|
|
|
|
(if (symbolp next)
|
|
|
|
(require next nil :no-error)
|
|
|
|
(load next :no-message :no-error)))))
|
2018-05-20 15:51:47 +02:00
|
|
|
#'progn
|
|
|
|
#'with-no-warnings)
|
2018-06-27 22:46:49 +02:00
|
|
|
(if (symbolp targets)
|
|
|
|
`(with-eval-after-load ',targets ,@body)
|
|
|
|
(pcase (car-safe targets)
|
|
|
|
((or :or :any)
|
|
|
|
(macroexp-progn
|
|
|
|
(cl-loop for next in (cdr targets)
|
|
|
|
collect `(after! ,next ,@body))))
|
|
|
|
((or :and :all)
|
|
|
|
(dolist (next (cdr targets))
|
|
|
|
(setq body `((after! ,next ,@body))))
|
|
|
|
(car body))
|
|
|
|
(_ `(after! (:and ,@targets) ,@body)))))))
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2017-02-23 00:06:12 -05:00
|
|
|
(defmacro quiet! (&rest forms)
|
2018-02-28 17:13:40 -05:00
|
|
|
"Run FORMS without making any output."
|
2018-09-27 23:45:32 -04:00
|
|
|
`(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))
|
|
|
|
,@forms
|
|
|
|
(message "")))))
|
2017-02-09 04:22:08 -05:00
|
|
|
|
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
|
|
|
|
2018-07-09 15:33:31 +02:00
|
|
|
FORMS are evaluated once when that function/hook is first invoked, then never
|
|
|
|
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)))
|
2018-06-15 23:54:07 +02:00
|
|
|
(fn (if (symbolp (car forms))
|
|
|
|
(intern (format "doom|transient-hook-%s" (pop forms)))
|
2018-09-09 21:11:45 +01:00
|
|
|
(make-symbol "doom|transient-hook-"))))
|
2018-05-15 13:40:18 +02:00
|
|
|
`(progn
|
2017-06-05 16:41:39 +02:00
|
|
|
(fset ',fn
|
|
|
|
(lambda (&rest _)
|
|
|
|
,@forms
|
2018-07-09 15:33:31 +02:00
|
|
|
(cond ((functionp ,hook-or-function) (advice-remove ,hook-or-function #',fn))
|
|
|
|
((symbolp ,hook-or-function) (remove-hook ,hook-or-function #',fn)))
|
2018-06-15 23:54:07 +02:00
|
|
|
(unintern ',fn nil)))
|
2018-07-09 15:33:31 +02:00
|
|
|
(cond ((functionp ,hook-or-function)
|
|
|
|
(advice-add ,hook-or-function ,(if append :after :before) #',fn))
|
|
|
|
((symbolp ,hook-or-function)
|
2018-05-24 22:35:45 +02:00
|
|
|
(put ',fn 'permanent-local-hook t)
|
2018-07-09 15:33:31 +02:00
|
|
|
(add-hook ,hook-or-function #',fn ,append))))))
|
2017-03-02 01:43:00 -05:00
|
|
|
|
2017-02-28 15:29:23 -05:00
|
|
|
(defmacro add-hook! (&rest args)
|
|
|
|
"A convenience macro for `add-hook'. Takes, 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),
|
|
|
|
2. The hooks: either an unquoted major mode, an unquoted list of major-modes,
|
|
|
|
a quoted hook variable or a quoted list of hook variables. If unquoted, the
|
|
|
|
hooks will be resolved by appending -hook to each symbol.
|
|
|
|
3. A function, list of functions, or body forms to be wrapped in a lambda.
|
2017-01-16 23:15:48 -05:00
|
|
|
|
|
|
|
Examples:
|
2018-05-24 18:38:50 +02:00
|
|
|
(add-hook! 'some-mode-hook 'enable-something) (same as `add-hook')
|
2017-02-23 00:06:12 -05:00
|
|
|
(add-hook! some-mode '(enable-something and-another))
|
|
|
|
(add-hook! '(one-mode-hook second-mode-hook) 'enable-something)
|
|
|
|
(add-hook! (one-mode second-mode) 'enable-something)
|
2017-02-28 15:29:23 -05:00
|
|
|
(add-hook! :append (one-mode second-mode) 'enable-something)
|
|
|
|
(add-hook! :local (one-mode second-mode) 'enable-something)
|
|
|
|
(add-hook! (one-mode second-mode) (setq v 5) (setq a 2))
|
2017-03-02 18:14:52 -05:00
|
|
|
(add-hook! :append :local (one-mode second-mode) (setq v 5) (setq a 2))
|
|
|
|
|
|
|
|
Body forms can access the hook's arguments through the let-bound variable
|
|
|
|
`args'."
|
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))))
|
2017-09-27 01:21:48 +02:00
|
|
|
(let ((hooks (doom--resolve-hook-forms (pop args)))
|
2017-03-02 18:14:52 -05:00
|
|
|
(funcs
|
|
|
|
(let ((val (car args)))
|
2017-04-17 02:17:10 -04:00
|
|
|
(if (memq (car-safe val) '(quote function))
|
2017-03-02 18:14:52 -05:00
|
|
|
(if (cdr-safe (cadr val))
|
|
|
|
(cadr val)
|
|
|
|
(list (cadr val)))
|
|
|
|
(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)))
|
2018-06-06 19:21:27 +02:00
|
|
|
`(progn ,@(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)
|
|
|
|
"Convenience macro for `remove-hook'. Takes the same arguments as
|
|
|
|
`add-hook!'."
|
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
|
|
|
|
2018-05-07 22:35:14 +02:00
|
|
|
(defmacro setq-hook! (hooks &rest rest)
|
|
|
|
"Convenience macro for setting buffer-local variables in a hook.
|
|
|
|
|
|
|
|
(setq-hook! 'markdown-mode-hook
|
|
|
|
line-spacing 2
|
|
|
|
fill-column 80)"
|
|
|
|
(declare (indent 1))
|
|
|
|
(unless (= 0 (% (length rest) 2))
|
2018-09-20 14:14:11 -04:00
|
|
|
(signal 'wrong-number-of-arguments (listp #'evenp (length rest))))
|
|
|
|
`(add-hook! :append ,hooks
|
2018-05-07 22:35:14 +02:00
|
|
|
,@(let (forms)
|
|
|
|
(while rest
|
|
|
|
(let ((var (pop rest))
|
|
|
|
(val (pop rest)))
|
|
|
|
(push `(setq-local ,var ,val) forms)))
|
|
|
|
(nreverse forms))))
|
|
|
|
|
2018-06-16 11:34:42 +02:00
|
|
|
(cl-defmacro associate! (mode &key modes match files when)
|
2018-05-24 18:38:50 +02:00
|
|
|
"Enables a minor mode if certain conditions are met.
|
|
|
|
|
|
|
|
The available conditions are:
|
|
|
|
|
|
|
|
:modes SYMBOL_LIST
|
|
|
|
A list of major/minor modes in which this minor mode may apply.
|
|
|
|
:match REGEXP
|
|
|
|
A regexp to be tested against the current file path.
|
|
|
|
:files SPEC
|
|
|
|
Accepts what `project-file-exists-p!' accepts. Checks if certain files exist
|
|
|
|
relative to the project root.
|
|
|
|
:when FORM
|
|
|
|
Whenever FORM returns non-nil."
|
2017-01-16 23:15:48 -05:00
|
|
|
(declare (indent 1))
|
2017-02-04 21:07:54 -05:00
|
|
|
(unless noninteractive
|
2018-06-16 11:34:42 +02:00
|
|
|
(cond ((or files modes when)
|
|
|
|
(when (and files
|
|
|
|
(not (or (listp files)
|
|
|
|
(stringp files))))
|
|
|
|
(user-error "associate! :files expects a string or list of strings"))
|
|
|
|
(let ((hook-name (intern (format "doom--init-mode-%s" mode))))
|
|
|
|
`(progn
|
|
|
|
(fset ',hook-name
|
|
|
|
(lambda ()
|
|
|
|
(and (fboundp ',mode)
|
|
|
|
(not (bound-and-true-p ,mode))
|
|
|
|
(and buffer-file-name (not (file-remote-p buffer-file-name)))
|
2018-08-10 23:32:21 +02:00
|
|
|
,(or (not match)
|
|
|
|
`(if buffer-file-name (string-match-p ,match buffer-file-name)))
|
2018-06-16 11:34:42 +02:00
|
|
|
,(or (not files)
|
|
|
|
(doom--resolve-path-forms
|
|
|
|
(if (stringp (car files)) (cons 'and files) files)
|
|
|
|
'(doom-project-root)))
|
|
|
|
,(or when t)
|
|
|
|
(,mode 1))))
|
|
|
|
,@(if (and modes (listp modes))
|
|
|
|
(cl-loop for hook in (doom--resolve-hook-forms modes)
|
|
|
|
collect `(add-hook ',hook #',hook-name))
|
|
|
|
`((add-hook 'after-change-major-mode-hook #',hook-name))))))
|
|
|
|
(match
|
2018-06-23 16:48:58 +02:00
|
|
|
`(add-to-list 'doom-auto-minor-mode-alist '(,match . ,mode)))
|
2018-06-16 11:34:42 +02:00
|
|
|
((user-error "Invalid `associate!' rules for mode [%s] (:modes %s :match %s :files %s :when %s)"
|
|
|
|
mode modes match files when)))))
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2018-05-24 18:35:42 +02:00
|
|
|
(defmacro file-exists-p! (spec &optional directory)
|
|
|
|
"Returns t if the files in SPEC all exist.
|
|
|
|
|
|
|
|
SPEC can be a single file or a list of forms/files. It understands nested (and
|
|
|
|
...) and (or ...), as well.
|
|
|
|
|
|
|
|
DIRECTORY is where to look for the files in SPEC if they aren't absolute. This
|
|
|
|
doesn't apply to variables, however.
|
|
|
|
|
|
|
|
For example:
|
|
|
|
|
2018-06-04 13:57:18 +02:00
|
|
|
(file-exists-p! (or doom-core-dir \"~/.config\" \"some-file\") \"~\")"
|
|
|
|
(if directory
|
2018-06-08 13:30:20 +02:00
|
|
|
`(let ((--directory-- ,directory))
|
2018-06-08 13:39:04 +02:00
|
|
|
,(doom--resolve-path-forms spec '--directory--))
|
2018-06-04 13:57:18 +02:00
|
|
|
(doom--resolve-path-forms spec)))
|
2018-05-24 18:35:42 +02:00
|
|
|
|
2018-06-03 23:52:21 +02:00
|
|
|
(defmacro define-key! (keymaps key def &rest rest)
|
|
|
|
"Like `define-key', but accepts a variable number of KEYMAPS and/or KEY+DEFs.
|
|
|
|
|
|
|
|
KEYMAPS can also be (or contain) 'global or 'local, to make this equivalent to
|
|
|
|
using `global-set-key' and `local-set-key'.
|
|
|
|
|
|
|
|
KEY is a key string or vector. It is *not* piped through `kbd'."
|
|
|
|
(declare (indent defun))
|
|
|
|
(or (cl-evenp (length rest))
|
|
|
|
(signal 'wrong-number-of-arguments (list 'evenp (length rest))))
|
|
|
|
(if (and (listp keymaps)
|
|
|
|
(not (eq (car-safe keymaps) 'quote)))
|
|
|
|
`(dolist (map (list ,@keymaps))
|
|
|
|
,(macroexpand `(define-key! map ,key ,def ,@rest)))
|
|
|
|
(when (eq (car-safe keymaps) 'quote)
|
|
|
|
(pcase (cadr keymaps)
|
|
|
|
(`global (setq keymaps '(current-global-map)))
|
|
|
|
(`local (setq keymaps '(current-local-map)))
|
|
|
|
(x (error "%s is not a valid keymap" x))))
|
|
|
|
`(let ((map ,keymaps))
|
|
|
|
(define-key map ,key ,def)
|
|
|
|
,@(let (forms)
|
|
|
|
(while rest
|
|
|
|
(let ((key (pop rest))
|
|
|
|
(def (pop rest)))
|
|
|
|
(push `(define-key map ,key ,def) forms)))
|
|
|
|
(nreverse forms)))))
|
|
|
|
|
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
|
|
|
|
(setq path (or (DIR!)
|
|
|
|
(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
|
|
|
|
2017-01-16 23:15:48 -05:00
|
|
|
(provide 'core-lib)
|
|
|
|
;;; core-lib.el ends here
|