2017-06-08 11:47:56 +02:00
|
|
|
;;; core-lib.el -*- lexical-binding: t; -*-
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2017-02-19 18:02:40 -05:00
|
|
|
;; I don't use use-package for these to save on the `fboundp' lookups it does
|
2017-03-04 20:54:13 -05:00
|
|
|
;; for its :commands property. I use dolists instead of mapc to avoid extra
|
|
|
|
;; stackframes allocated for lambdas. This is _definitely_ premature
|
|
|
|
;; optimization.
|
2017-05-22 22:32:08 +02:00
|
|
|
(dolist (sym '(async-start async-start-process async-byte-recompile-directory
|
|
|
|
async-inject-variables))
|
2017-03-04 20:54:13 -05:00
|
|
|
(autoload sym "async"))
|
2017-03-25 01:44:41 -04:00
|
|
|
|
2017-03-04 20:54:13 -05:00
|
|
|
(dolist (sym '(persistent-soft-exists-p persistent-soft-fetch
|
|
|
|
persistent-soft-flush persistent-soft-store))
|
|
|
|
(autoload sym "persistent-soft"))
|
2017-03-25 01:44:41 -04:00
|
|
|
|
2017-04-17 02:30:35 -04:00
|
|
|
(dolist (sym '(s-center s-pad-left s-pad-right s-truncate s-chop-suffix
|
|
|
|
s-chop-suffixes s-chop-prefix s-chop-prefixes s-join s-replace
|
|
|
|
s-replace-all s-capitalize s-titleize s-split-words
|
|
|
|
s-capitalized-words s-titleized-words))
|
2017-03-04 20:54:13 -05:00
|
|
|
(autoload sym "s"))
|
2017-02-06 00:13:51 -05:00
|
|
|
|
2017-04-17 02:30:35 -04:00
|
|
|
(dolist (sym '(when-let if-let string-trim string-join string-blank-p string-lessp))
|
2017-06-18 23:41:48 +02:00
|
|
|
(autoload sym "subr-x" nil nil 'macro))
|
2017-04-17 02:30:35 -04:00
|
|
|
|
2017-09-02 16:12:53 +02:00
|
|
|
(dolist (sym '(json-read json-read-file json-read-from-string json-encode))
|
|
|
|
(autoload sym "json"))
|
|
|
|
|
2017-02-04 02:54:22 -05:00
|
|
|
|
2017-03-02 18:14:52 -05:00
|
|
|
;;
|
|
|
|
;; Helpers
|
|
|
|
;;
|
|
|
|
|
|
|
|
(defun doom--resolve-paths (paths &optional root)
|
|
|
|
(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
|
|
|
|
collect (doom--resolve-paths i root)))
|
2017-03-02 18:14:52 -05:00
|
|
|
(t paths)))
|
|
|
|
|
|
|
|
(defun doom--resolve-hooks (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))
|
|
|
|
|
2017-02-23 00:06:12 -05:00
|
|
|
(defmacro after! (feature &rest forms)
|
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))
|
2017-06-08 11:47:56 +02:00
|
|
|
`(,(if (or (not (bound-and-true-p byte-compile-current-file))
|
2017-01-16 23:15:48 -05:00
|
|
|
(if (symbolp feature)
|
|
|
|
(require feature nil :no-error)
|
|
|
|
(load feature :no-message :no-error)))
|
2017-04-17 02:17:10 -04:00
|
|
|
#'progn
|
|
|
|
#'with-no-warnings)
|
2017-02-01 00:36:12 -05:00
|
|
|
(with-eval-after-load ',feature ,@forms)))
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2017-02-23 00:06:12 -05:00
|
|
|
(defmacro quiet! (&rest forms)
|
2017-02-09 04:22:08 -05:00
|
|
|
"Run FORMS without making any noise."
|
2017-02-19 19:00:33 -05:00
|
|
|
`(if doom-debug-mode
|
|
|
|
(progn ,@forms)
|
2017-02-09 04:22:08 -05:00
|
|
|
(fset 'doom--old-write-region-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))
|
2017-02-19 18:11:28 -05:00
|
|
|
(doom--old-write-region-fn
|
|
|
|
start end filename append visit lockname mustbenew)))
|
2017-02-09 04:22:08 -05:00
|
|
|
(inhibit-message t)
|
|
|
|
(save-silently t))
|
|
|
|
,@forms)))
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
HOOK can be a quoted hook or a sharp-quoted function (which will be advised).
|
|
|
|
|
|
|
|
These forms will be evaluated once when that function/hook is first invoked,
|
|
|
|
then it detaches itself."
|
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-03-02 18:14:52 -05:00
|
|
|
(let ((hooks (doom--resolve-hooks (pop args)))
|
|
|
|
(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-03-15 22:40:04 -04:00
|
|
|
(push (cond ((eq hook-fn 'remove-hook)
|
|
|
|
`(remove-hook ',hook ,fn ,local-p))
|
|
|
|
(t
|
|
|
|
`(add-hook ',hook ,fn ,append-p ,local-p)))
|
2017-02-28 15:29:23 -05:00
|
|
|
forms)))
|
2017-03-01 19:15:45 -05:00
|
|
|
`(progn ,@(nreverse 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)
|
|
|
|
,(if files (doom--resolve-paths files) t)
|
|
|
|
,(or pred-form t))
|
|
|
|
(,mode 1)))
|
|
|
|
,@(if (and modes (listp modes))
|
2017-06-08 11:47:56 +02:00
|
|
|
(cl-loop for hook in (doom--resolve-hooks modes)
|
|
|
|
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-06-14 21:03:20 +02:00
|
|
|
;; I'm a fan of concise, hassle-free front-facing configuration. Rather than
|
2017-09-26 21:55:01 +02:00
|
|
|
;; littering my config with `after!' blocks, and checking if features and
|
|
|
|
;; modules are loaded before every line of config, I wrote `set!' as a more
|
|
|
|
;; robust alternative. If a setting doesn't exist at run-time, the `set!' call
|
|
|
|
;; is ignored. It also benefits from byte-compilation.
|
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-02-21 16:03:12 -05:00
|
|
|
"Define a setting macro. Like `defmacro', this should return a form to be
|
2017-02-23 00:06:12 -05:00
|
|
|
executed when called with `set!'. FORMS are not evaluated until `set!' calls it."
|
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)
|
|
|
|
"Set an option defined by `def-setting!'. Skip if doesn't exist."
|
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))
|
2017-06-19 00:22:04 +02:00
|
|
|
(let ((fn (cdr (assq keyword doom-settings))))
|
|
|
|
(if fn
|
|
|
|
(apply fn values)
|
2017-02-21 16:03:12 -05:00
|
|
|
(when doom-debug-mode
|
2017-06-24 17:22:10 +02:00
|
|
|
(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
|