2017-06-08 11:47:56 +02:00
|
|
|
;;; core-lib.el -*- lexical-binding: t; -*-
|
2017-01-16 23:15:48 -05:00
|
|
|
|
2018-05-20 20:08:03 +02:00
|
|
|
(require 'subr-x)
|
|
|
|
(require 'cl-lib)
|
|
|
|
(require 'map)
|
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
|
|
|
|
(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)))
|
|
|
|
|
2018-05-20 00:07:06 +02:00
|
|
|
(defun doom-file-cookie-p (file)
|
|
|
|
"Returns the value of the ;;;###if predicate form in FILE."
|
|
|
|
(with-temp-buffer
|
|
|
|
(insert-file-contents-literally file nil 0 256)
|
|
|
|
(if (and (re-search-forward "^;;;###if " nil t)
|
|
|
|
(<= (line-number-at-pos) 3))
|
|
|
|
(let ((load-file-name file))
|
|
|
|
(eval (sexp-at-point)))
|
|
|
|
t)))
|
|
|
|
|
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')."
|
|
|
|
(or (stringp str)
|
|
|
|
(signal 'wrong-type-argument (list 'stringp str)))
|
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-05-19 16:32:12 +02:00
|
|
|
(or (keywordp keyword)
|
2018-05-23 19:09:09 +02:00
|
|
|
(signal 'wrong-type-argument (list 'keywordp keyword)))
|
2018-05-19 16:32:12 +02:00
|
|
|
(substring (symbol-name keyword) 1))
|
|
|
|
|
2018-05-23 19:16:33 +02:00
|
|
|
(cl-defun doom-files-in
|
|
|
|
(path-or-paths &rest rest
|
|
|
|
&key
|
|
|
|
filter
|
|
|
|
map
|
|
|
|
full
|
|
|
|
(follow-symlinks t)
|
|
|
|
(type 'files)
|
2018-05-24 13:02:02 +02:00
|
|
|
(relative-to (unless full default-directory))
|
2018-05-23 19:16:33 +02:00
|
|
|
(depth 99999)
|
|
|
|
(match "^[^.]"))
|
|
|
|
"Returns a list of files/directories in PATH-OR-PATHS (one string path or a
|
|
|
|
list of them).
|
|
|
|
|
|
|
|
FILTER is a function or symbol that takes one argument (the path). If it returns
|
|
|
|
non-nil, the entry will be excluded.
|
|
|
|
|
|
|
|
MAP is a function or symbol which will be used to transform each entry in the
|
|
|
|
results.
|
|
|
|
|
|
|
|
TYPE determines what kind of path will be included in the results. This can be t
|
|
|
|
(files and folders), 'files or 'dirs.
|
|
|
|
|
|
|
|
By default, this function returns paths relative to PATH-OR-PATHS if it is a
|
|
|
|
single path. If it a list of paths, this function returns absolute paths.
|
|
|
|
Otherwise, by setting RELATIVE-TO to a path, the results will be transformed to
|
|
|
|
be relative to it.
|
|
|
|
|
|
|
|
The search recurses up to DEPTH and no further. DEPTH is an integer.
|
|
|
|
|
|
|
|
MATCH is a string regexp. Only entries that match it will be included."
|
|
|
|
(cond
|
|
|
|
((listp path-or-paths)
|
|
|
|
(cl-loop for path in path-or-paths
|
|
|
|
if (file-directory-p path)
|
|
|
|
nconc (apply #'doom-files-in path (plist-put rest :relative-to relative-to))))
|
|
|
|
((let ((path path-or-paths)
|
|
|
|
result)
|
|
|
|
(dolist (file (file-name-all-completions "" path))
|
|
|
|
(unless (member file '("./" "../"))
|
|
|
|
(let ((fullpath (expand-file-name file path)))
|
|
|
|
(cond ((directory-name-p fullpath)
|
|
|
|
(when (and (memq type '(t dirs))
|
|
|
|
(string-match-p match file)
|
|
|
|
(not (and filter (funcall filter fullpath)))
|
|
|
|
(not (and (file-symlink-p fullpath)
|
|
|
|
(not follow-symlinks))))
|
|
|
|
(setq result
|
|
|
|
(nconc result
|
|
|
|
(list (cond (map (funcall map fullpath))
|
|
|
|
(relative-to (file-relative-name fullpath relative-to))
|
|
|
|
(fullpath))))))
|
|
|
|
(unless (<= depth 1)
|
|
|
|
(setq result
|
|
|
|
(nconc result (apply #'doom-files-in fullpath
|
|
|
|
(append `(:depth ,(1- depth) :relative-to ,relative-to)
|
|
|
|
rest))))))
|
|
|
|
((and (memq type '(t files))
|
|
|
|
(string-match-p match file)
|
|
|
|
(not (and filter (funcall filter fullpath))))
|
|
|
|
(push (if relative-to
|
|
|
|
(file-relative-name fullpath relative-to)
|
|
|
|
fullpath)
|
|
|
|
result))))))
|
|
|
|
result))))
|
2018-05-19 16:32:12 +02:00
|
|
|
|
2018-05-11 20:23:49 +02:00
|
|
|
(defun doom*shut-up (orig-fn &rest args)
|
|
|
|
"Generic advisor for silencing noisy functions."
|
|
|
|
(quiet! (apply orig-fn args)))
|
|
|
|
|
2017-03-02 18:14:52 -05:00
|
|
|
|
2017-02-04 02:54:22 -05:00
|
|
|
;;
|
2018-05-11 20:23:49 +02:00
|
|
|
;; Macros
|
2017-02-04 02:54:22 -05:00
|
|
|
;;
|
|
|
|
|
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-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
|
|
|
|
compilation."
|
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)
|
|
|
|
(memq targets doom-disabled-packages))
|
|
|
|
(list (if (or (not (bound-and-true-p byte-compile-current-file))
|
|
|
|
(dolist (next (doom-enlist targets))
|
|
|
|
(if (symbolp next)
|
|
|
|
(require next nil :no-error)
|
|
|
|
(load next :no-message :no-error))))
|
|
|
|
#'progn
|
|
|
|
#'with-no-warnings)
|
|
|
|
(cond ((symbolp targets)
|
|
|
|
`(eval-after-load ',targets '(progn ,@body)))
|
|
|
|
((and (consp targets)
|
|
|
|
(memq (car targets) '(:or :any)))
|
|
|
|
`(progn
|
|
|
|
,@(cl-loop for next in (cdr targets)
|
|
|
|
collect `(after! ,next ,@body))))
|
|
|
|
((and (consp targets)
|
|
|
|
(memq (car targets) '(:and :all)))
|
|
|
|
(dolist (next (cdr targets))
|
|
|
|
(setq body `(after! ,next ,@body)))
|
|
|
|
body)
|
|
|
|
((listp targets)
|
|
|
|
`(after! (:all ,@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."
|
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))
|
2018-05-15 13:40:18 +02:00
|
|
|
(let ((append (if (eq (car forms) :after) (pop forms)))
|
|
|
|
(fn (intern (format "doom|transient-hook-%s"
|
|
|
|
(if (not (symbolp (car forms)))
|
|
|
|
(cl-incf doom--transient-counter)
|
|
|
|
(pop forms))))))
|
|
|
|
`(progn
|
2017-06-05 16:41:39 +02:00
|
|
|
(fset ',fn
|
|
|
|
(lambda (&rest _)
|
|
|
|
,@forms
|
|
|
|
(cond ((functionp ,hook) (advice-remove ,hook #',fn))
|
|
|
|
((symbolp ,hook) (remove-hook ,hook #',fn)))
|
2018-05-20 20:08:38 +02:00
|
|
|
(fmakunbound ',fn)))
|
2017-06-05 16:41:39 +02:00
|
|
|
(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!'."
|
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))
|
|
|
|
(signal 'wrong-number-of-arguments (length rest)))
|
|
|
|
`(add-hook! ,hooks
|
|
|
|
,@(let (forms)
|
|
|
|
(while rest
|
|
|
|
(let ((var (pop rest))
|
|
|
|
(val (pop rest)))
|
|
|
|
(push `(setq-local ,var ,val) forms)))
|
|
|
|
(nreverse forms))))
|
|
|
|
|
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 ()
|
2018-05-16 13:14:03 +02:00
|
|
|
(when (and (fboundp ',mode)
|
2017-03-02 18:14:52 -05:00
|
|
|
(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)
|
2018-05-18 01:21:09 +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
|
2018-05-16 13:14:23 +02:00
|
|
|
`(map-put doom-auto-minor-mode-alist ,match ',mode))
|
2017-03-02 18:14:52 -05:00
|
|
|
(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
|
|
|
|
|
|
|
(provide 'core-lib)
|
|
|
|
;;; core-lib.el ends here
|