2017-06-08 11:47:56 +02:00
|
|
|
;;; core-lib.el -*- lexical-binding: t; -*-
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2018-02-16 02:02:58 -05:00
|
|
|
(let ((load-path doom-site-load-path))
|
2018-01-06 17:02:57 -05:00
|
|
|
(require 'subr-x)
|
|
|
|
(require 'cl-lib)
|
2018-01-11 22:18:31 -05:00
|
|
|
(require 'map))
|
2018-01-06 17:02:57 -05:00
|
|
|
|
2018-03-02 17:42:19 -05:00
|
|
|
(eval-and-compile
|
|
|
|
(when (version< emacs-version "26")
|
|
|
|
(with-no-warnings
|
|
|
|
(defalias 'if-let* #'if-let)
|
|
|
|
(defalias 'when-let* #'when-let))))
|
2017-12-10 14:49:52 -05:00
|
|
|
|
2017-09-02 16:12:53 +02:00
|
|
|
|
2017-03-02 18:14:52 -05:00
|
|
|
;;
|
|
|
|
;; Helpers
|
|
|
|
;;
|
|
|
|
|
2017-09-27 01:21:48 +02:00
|
|
|
(defun doom--resolve-path-forms (paths &optional root)
|
2017-03-02 18:14:52 -05:00
|
|
|
(cond ((stringp paths)
|
|
|
|
`(file-exists-p
|
|
|
|
(expand-file-name
|
|
|
|
,paths ,(if (or (string-prefix-p "./" paths)
|
|
|
|
(string-prefix-p "../" paths))
|
|
|
|
'default-directory
|
2017-03-25 03:46:27 -04:00
|
|
|
(or root `(doom-project-root))))))
|
2017-03-02 18:14:52 -05:00
|
|
|
((listp paths)
|
2017-06-08 11:47:56 +02:00
|
|
|
(cl-loop for i in paths
|
2017-09-27 01:21:48 +02:00
|
|
|
collect (doom--resolve-path-forms i root)))
|
2017-03-02 18:14:52 -05:00
|
|
|
(t paths)))
|
|
|
|
|
2017-09-27 01:21:48 +02:00
|
|
|
(defun doom--resolve-hook-forms (hooks)
|
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
|
|
|
|
2017-06-12 02:48:26 +02:00
|
|
|
(defun doom-unquote (exp)
|
|
|
|
"Return EXP unquoted."
|
|
|
|
(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."
|
|
|
|
(if (listp exp) exp (list exp)))
|
|
|
|
|
2017-03-02 18:14:52 -05:00
|
|
|
|
2017-02-04 02:54:22 -05:00
|
|
|
;;
|
|
|
|
;; Library
|
|
|
|
;;
|
|
|
|
|
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-02-28 17:04:48 -05:00
|
|
|
(defmacro after! (features &rest body)
|
2017-02-06 01:25:48 -05:00
|
|
|
"A smart wrapper around `with-eval-after-load'. Supresses warnings during
|
|
|
|
compilation."
|
2017-01-16 23:15:48 -05:00
|
|
|
(declare (indent defun) (debug t))
|
2018-02-28 17:04:48 -05:00
|
|
|
(list (if (or (not (bound-and-true-p byte-compile-current-file))
|
|
|
|
(dolist (next (doom-enlist features))
|
|
|
|
(if (symbolp next)
|
|
|
|
(require next nil :no-error)
|
|
|
|
(load next :no-message :no-error))))
|
|
|
|
#'progn
|
|
|
|
#'with-no-warnings)
|
|
|
|
(cond ((symbolp features)
|
|
|
|
`(eval-after-load ',features '(progn ,@body)))
|
|
|
|
((and (consp features)
|
|
|
|
(memq (car features) '(:or :any)))
|
|
|
|
`(progn
|
|
|
|
,@(cl-loop for next in (cdr features)
|
|
|
|
collect `(after! ,next ,@body))))
|
|
|
|
((and (consp features)
|
|
|
|
(memq (car features) '(:and :all)))
|
|
|
|
(dolist (next (cdr features))
|
|
|
|
(setq body `(after! ,next ,@body)))
|
|
|
|
body)
|
|
|
|
((listp features)
|
|
|
|
`(after! (:all ,@features) ,@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."
|
2017-02-19 19:00:33 -05:00
|
|
|
`(if doom-debug-mode
|
|
|
|
(progn ,@forms)
|
2017-11-16 16:36:00 +01:00
|
|
|
(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)))
|
|
|
|
(inhibit-message t)
|
|
|
|
(save-silently t))
|
|
|
|
,@forms))))
|
2017-02-09 04:22:08 -05:00
|
|
|
|
2017-06-05 16:41:39 +02:00
|
|
|
(defvar doom--transient-counter 0)
|
2017-03-02 01:43:00 -05:00
|
|
|
(defmacro add-transient-hook! (hook &rest forms)
|
2017-06-05 16:41:39 +02:00
|
|
|
"Attaches transient forms to a HOOK.
|
|
|
|
|
2018-02-28 17:13:40 -05:00
|
|
|
This means FORMS will be evaluated once when that function/hook is first
|
|
|
|
invoked, then never again.
|
2017-06-05 16:41:39 +02:00
|
|
|
|
2018-02-28 17:13:40 -05:00
|
|
|
HOOK 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))
|
2017-06-05 16:41:39 +02:00
|
|
|
(let ((append (eq (car forms) :after))
|
|
|
|
(fn (intern (format "doom-transient-hook-%s" (cl-incf doom--transient-counter)))))
|
|
|
|
`(when ,hook
|
|
|
|
(fset ',fn
|
|
|
|
(lambda (&rest _)
|
|
|
|
,@forms
|
|
|
|
(cond ((functionp ,hook) (advice-remove ,hook #',fn))
|
|
|
|
((symbolp ,hook) (remove-hook ,hook #',fn)))
|
|
|
|
(unintern ',fn nil)))
|
|
|
|
(cond ((functionp ,hook)
|
|
|
|
(advice-add ,hook ,(if append :after :before) #',fn))
|
|
|
|
((symbolp ,hook)
|
|
|
|
(add-hook ,hook #',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:
|
2017-02-23 00:06:12 -05:00
|
|
|
(add-hook! 'some-mode-hook 'enable-something)
|
|
|
|
(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-01-08 14:55:54 -05:00
|
|
|
`(progn ,@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!'."
|
2017-06-08 11:47:56 +02:00
|
|
|
`(add-hook! :remove ,@args))
|
2017-02-28 15:38:47 -05:00
|
|
|
|
2017-02-23 00:06:12 -05:00
|
|
|
(defmacro associate! (mode &rest plist)
|
2017-03-02 18:14:52 -05:00
|
|
|
"Associate a minor mode to certain patterns and project files."
|
2017-01-16 23:15:48 -05:00
|
|
|
(declare (indent 1))
|
2017-02-04 21:07:54 -05:00
|
|
|
(unless noninteractive
|
2017-06-08 11:47:56 +02:00
|
|
|
(let ((modes (plist-get plist :modes))
|
|
|
|
(match (plist-get plist :match))
|
|
|
|
(files (plist-get plist :files))
|
|
|
|
(pred-form (plist-get plist :when)))
|
2017-03-02 18:14:52 -05:00
|
|
|
(cond ((or files modes pred-form)
|
2017-02-19 18:11:28 -05:00
|
|
|
(when (and files
|
|
|
|
(not (or (listp files)
|
|
|
|
(stringp files))))
|
2017-02-23 00:06:12 -05:00
|
|
|
(user-error "associate! :files expects a string or list of strings"))
|
2017-02-04 21:07:54 -05:00
|
|
|
(let ((hook-name (intern (format "doom--init-mode-%s" mode))))
|
2017-03-02 18:14:52 -05:00
|
|
|
`(progn
|
|
|
|
(defun ,hook-name ()
|
|
|
|
(when (and (boundp ',mode)
|
|
|
|
(not ,mode)
|
2017-04-27 18:05:21 -04:00
|
|
|
(and buffer-file-name (not (file-remote-p buffer-file-name)))
|
2017-03-02 18:14:52 -05:00
|
|
|
,(if match `(if buffer-file-name (string-match-p ,match buffer-file-name)) t)
|
2017-09-27 01:21:48 +02:00
|
|
|
,(if files (doom--resolve-path-forms files) t)
|
2017-03-02 18:14:52 -05:00
|
|
|
,(or pred-form t))
|
|
|
|
(,mode 1)))
|
|
|
|
,@(if (and modes (listp modes))
|
2017-09-27 01:21:48 +02:00
|
|
|
(cl-loop for hook in (doom--resolve-hook-forms modes)
|
2017-06-08 11:47:56 +02:00
|
|
|
collect `(add-hook ',hook ',hook-name))
|
2017-03-02 18:14:52 -05:00
|
|
|
`((add-hook 'after-change-major-mode-hook ',hook-name))))))
|
2017-02-04 21:07:54 -05:00
|
|
|
(match
|
2017-03-02 18:14:52 -05:00
|
|
|
`(push (cons ,match ',mode) doom-auto-minor-mode-alist))
|
|
|
|
(t (user-error "associate! invalid rules for mode [%s] (modes %s) (match %s) (files %s)"
|
|
|
|
mode modes match files))))))
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2017-02-21 16:03:12 -05:00
|
|
|
|
2017-12-09 16:23:19 -05:00
|
|
|
;; I needed a way to reliably cross-configure modules without worrying about
|
|
|
|
;; whether they were enabled or not, so I wrote `set!'. If a setting doesn't
|
|
|
|
;; exist at runtime, the `set!' call is ignored (and omitted when
|
|
|
|
;; byte-compiled).
|
2017-05-27 14:31:08 +02:00
|
|
|
(defvar doom-settings nil)
|
|
|
|
|
2017-02-23 00:06:12 -05:00
|
|
|
(defmacro def-setting! (keyword arglist &optional docstring &rest forms)
|
2017-12-09 16:23:19 -05:00
|
|
|
"Define a setting. Like `defmacro', this should return a form to be executed
|
|
|
|
when called with `set!'. FORMS are not evaluated until `set!' calls it.
|
|
|
|
|
|
|
|
See `doom/describe-setting' for a list of available settings.
|
|
|
|
|
|
|
|
Do not use this for configuring Doom core."
|
2017-02-21 16:03:12 -05:00
|
|
|
(declare (indent defun) (doc-string 3))
|
|
|
|
(unless (keywordp keyword)
|
|
|
|
(error "Not a valid property name: %s" keyword))
|
2017-06-24 16:21:04 +02:00
|
|
|
(let ((fn (intern (format "doom--set%s" keyword))))
|
2017-06-19 00:22:04 +02:00
|
|
|
`(progn
|
|
|
|
(defun ,fn ,arglist
|
|
|
|
,docstring
|
|
|
|
,@forms)
|
|
|
|
(cl-pushnew ',(cons keyword fn) doom-settings :test #'eq :key #'car))))
|
2017-02-21 16:03:12 -05:00
|
|
|
|
2017-02-23 00:06:12 -05:00
|
|
|
(defmacro set! (keyword &rest values)
|
2017-12-09 16:23:19 -05:00
|
|
|
"Set an option defined by `def-setting!'. Skip if doesn't exist. See
|
|
|
|
`doom/describe-setting' for a list of available settings."
|
2017-02-21 16:03:12 -05:00
|
|
|
(declare (indent defun))
|
|
|
|
(unless values
|
2017-02-23 00:06:12 -05:00
|
|
|
(error "Empty set! for %s" keyword))
|
2018-01-06 03:03:02 -05:00
|
|
|
(if-let* ((fn (cdr (assq keyword doom-settings))))
|
|
|
|
(apply fn values)
|
|
|
|
(when doom-debug-mode
|
|
|
|
(message "No setting found for %s" keyword)
|
|
|
|
nil)))
|
2017-02-21 16:03:12 -05:00
|
|
|
|
2017-01-16 23:15:48 -05:00
|
|
|
(provide 'core-lib)
|
|
|
|
;;; core-lib.el ends here
|