2017-06-08 11:47:56 +02:00
|
|
|
|
;;; core-lib.el -*- lexical-binding: t; -*-
|
2017-01-16 23:15:48 -05: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
|
|
|
|
|
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))
|
2019-07-24 15:26:43 +02:00
|
|
|
|
(cl-check-type keyword 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)
|
2020-05-25 02:58:07 -04:00
|
|
|
|
"Log to *Messages* if `doom-debug-p' is on.
|
2020-12-11 17:38:59 -05:00
|
|
|
|
Does not display text in echo area, but still logs to *Messages*. Accepts the
|
|
|
|
|
same arguments as `message'."
|
2020-05-25 02:58:07 -04:00
|
|
|
|
`(when doom-debug-p
|
2019-03-05 00:19:51 -05:00
|
|
|
|
(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)
|
2020-12-11 17:38:59 -05:00
|
|
|
|
"Return a partial application of FUN to right-hand ARGS.
|
2019-07-21 19:13:21 +02:00
|
|
|
|
|
|
|
|
|
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."
|
2020-04-08 15:29:29 -04:00
|
|
|
|
(declare (side-effect-free t))
|
2019-07-21 19:13:21 +02:00
|
|
|
|
(lambda (&rest pre-args)
|
|
|
|
|
(apply fn (append pre-args args))))
|
|
|
|
|
|
2021-02-06 06:54:18 -05:00
|
|
|
|
(defun doom-lookup-key (keys &rest keymaps)
|
2021-01-03 22:40:06 -05:00
|
|
|
|
"Like `lookup-key', but search active keymaps if KEYMAP is omitted."
|
2021-02-06 06:54:18 -05:00
|
|
|
|
(if keymaps
|
|
|
|
|
(cl-some (doom-rpartial #'lookup-key keys) keymaps)
|
2021-01-03 22:40:06 -05:00
|
|
|
|
(cl-loop for keymap
|
2021-01-05 01:50:48 -05:00
|
|
|
|
in (append (cl-loop for alist in emulation-mode-map-alists
|
2021-01-18 17:43:57 -05:00
|
|
|
|
append (mapcar #'cdr
|
|
|
|
|
(if (symbolp alist)
|
|
|
|
|
(if (boundp alist) (symbol-value alist))
|
|
|
|
|
alist)))
|
2021-01-03 22:40:06 -05:00
|
|
|
|
(list (current-local-map))
|
2021-01-05 01:50:48 -05:00
|
|
|
|
(mapcar #'cdr minor-mode-overriding-map-alist)
|
2021-01-18 17:43:57 -05:00
|
|
|
|
(mapcar #'cdr minor-mode-map-alist)
|
2021-01-03 22:40:06 -05:00
|
|
|
|
(list (current-global-map)))
|
2021-01-05 01:50:48 -05:00
|
|
|
|
if (keymapp keymap)
|
2021-01-03 22:40:06 -05:00
|
|
|
|
if (lookup-key keymap keys)
|
|
|
|
|
return it)))
|
|
|
|
|
|
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
|
|
|
|
|
2020-04-29 20:45:29 -04: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-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))
|
2019-07-27 17:00:12 +02:00
|
|
|
|
(load-file-name)
|
2019-07-21 14:44:04 +02:00
|
|
|
|
((stringp (car-safe current-load-list))
|
|
|
|
|
(car current-load-list))
|
2019-07-27 17:00:12 +02:00
|
|
|
|
(buffer-file-name)
|
|
|
|
|
((error "Cannot get this file-path"))))
|
2019-07-21 14:44:04 +02:00
|
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
|
(defmacro letenv! (envvars &rest body)
|
|
|
|
|
"Lexically bind ENVVARS in BODY, like `let' but for `process-environment'."
|
|
|
|
|
(declare (indent 1))
|
|
|
|
|
`(let ((process-environment (copy-sequence process-environment)))
|
|
|
|
|
(dolist (var (list ,@(cl-loop for (var val) in envvars
|
|
|
|
|
collect `(cons ,var ,val))))
|
|
|
|
|
(setenv (car var) (cdr var)))
|
|
|
|
|
,@body))
|
|
|
|
|
|
2020-04-29 21:08:17 -04:00
|
|
|
|
(defmacro letf! (bindings &rest body)
|
|
|
|
|
"Temporarily rebind function and macros in BODY.
|
2020-12-11 17:38:59 -05:00
|
|
|
|
Intended as a simpler version of `cl-letf' and `cl-macrolet'.
|
2020-04-29 21:08:17 -04:00
|
|
|
|
|
|
|
|
|
BINDINGS is either a) a list of, or a single, `defun' or `defmacro'-ish form, or
|
|
|
|
|
b) a list of (PLACE VALUE) bindings as `cl-letf*' would accept.
|
|
|
|
|
|
|
|
|
|
TYPE is either `defun' or `defmacro'. NAME is the name of the function. If an
|
|
|
|
|
original definition for NAME exists, it can be accessed as a lexical variable by
|
|
|
|
|
the same name, for use with `funcall' or `apply'. ARGLIST and BODY are as in
|
|
|
|
|
`defun'.
|
|
|
|
|
|
|
|
|
|
\(fn ((TYPE NAME ARGLIST &rest BODY) ...) BODY...)"
|
|
|
|
|
(declare (indent defun))
|
|
|
|
|
(setq body (macroexp-progn body))
|
|
|
|
|
(when (memq (car bindings) '(defun defmacro))
|
|
|
|
|
(setq bindings (list bindings)))
|
2020-05-14 22:32:03 -04:00
|
|
|
|
(dolist (binding (reverse bindings) (macroexpand body))
|
2020-04-29 21:08:17 -04:00
|
|
|
|
(let ((type (car binding))
|
|
|
|
|
(rest (cdr binding)))
|
|
|
|
|
(setq
|
|
|
|
|
body (pcase type
|
2020-06-04 20:02:46 -04:00
|
|
|
|
(`defmacro `(cl-macrolet ((,@rest)) ,body))
|
2020-04-29 21:08:17 -04:00
|
|
|
|
(`defun `(cl-letf* ((,(car rest) (symbol-function #',(car rest)))
|
2020-05-14 22:32:03 -04:00
|
|
|
|
((symbol-function #',(car rest))
|
|
|
|
|
(lambda ,(cadr rest) ,@(cddr rest))))
|
|
|
|
|
(ignore ,(car rest))
|
|
|
|
|
,body))
|
2020-04-29 21:08:17 -04:00
|
|
|
|
(_
|
|
|
|
|
(when (eq (car-safe type) 'function)
|
2020-05-14 22:32:03 -04:00
|
|
|
|
(setq type (list 'symbol-function type)))
|
|
|
|
|
(list 'cl-letf (list (cons type rest)) body)))))))
|
2020-04-29 21:08:17 -04:00
|
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
|
(defmacro quiet! (&rest forms)
|
|
|
|
|
"Run FORMS without generating any output.
|
|
|
|
|
|
2020-05-14 22:37:39 -04:00
|
|
|
|
This silences calls to `message', `load', `write-region' and anything that
|
2020-12-11 17:38:59 -05:00
|
|
|
|
writes to `standard-output'. In interactive sessions this won't suppress writing
|
|
|
|
|
to *Messages*, only inhibit output in the echo area."
|
2020-05-25 02:58:07 -04:00
|
|
|
|
`(if doom-debug-p
|
2020-05-14 22:36:43 -04:00
|
|
|
|
(progn ,@forms)
|
2020-05-25 02:58:07 -04:00
|
|
|
|
,(if doom-interactive-p
|
2020-05-14 22:36:43 -04:00
|
|
|
|
`(let ((inhibit-message t)
|
|
|
|
|
(save-silently t))
|
|
|
|
|
(prog1 ,@forms (message "")))
|
|
|
|
|
`(letf! ((standard-output (lambda (&rest _)))
|
|
|
|
|
(defun message (&rest _))
|
2020-05-14 22:37:39 -04:00
|
|
|
|
(defun load (file &optional noerror nomessage nosuffix must-suffix)
|
|
|
|
|
(funcall load file noerror t nosuffix must-suffix))
|
2020-05-14 22:36:43 -04:00
|
|
|
|
(defun write-region (start end filename &optional append visit lockname mustbenew)
|
|
|
|
|
(unless visit (setq visit 'no-message))
|
|
|
|
|
(funcall write-region start end filename append visit lockname mustbenew)))
|
|
|
|
|
,@forms))))
|
2020-04-29 20:45:29 -04:00
|
|
|
|
|
2020-08-26 21:40:53 -04:00
|
|
|
|
(defmacro eval-if! (cond then &rest body)
|
2020-04-29 21:09:10 -04:00
|
|
|
|
"Expands to THEN if COND is non-nil, to BODY otherwise.
|
2020-08-26 21:40:53 -04:00
|
|
|
|
COND is checked at compile/expansion time, allowing BODY to be omitted entirely
|
|
|
|
|
when the elisp is byte-compiled. Use this for forms that contain expensive
|
|
|
|
|
macros that could safely be removed at compile time."
|
2020-04-29 21:09:10 -04:00
|
|
|
|
(declare (indent 2))
|
|
|
|
|
(if (eval cond)
|
|
|
|
|
then
|
|
|
|
|
(macroexp-progn body)))
|
|
|
|
|
|
2020-08-26 21:40:53 -04:00
|
|
|
|
(defmacro eval-when! (cond &rest body)
|
2020-04-29 21:09:10 -04:00
|
|
|
|
"Expands to BODY if CONDITION is non-nil at compile/expansion time.
|
2020-08-26 21:40:53 -04:00
|
|
|
|
See `eval-if!' for details on this macro's purpose."
|
2020-04-29 21:09:10 -04:00
|
|
|
|
(declare (indent 1))
|
|
|
|
|
(when (eval cond)
|
|
|
|
|
(macroexp-progn body)))
|
|
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
|
|
2020-05-20 15:32:34 -04:00
|
|
|
|
;;; Closure factories
|
|
|
|
|
(defmacro fn! (arglist &rest body)
|
2020-12-11 17:38:59 -05:00
|
|
|
|
"Returns (cl-function (lambda ARGLIST BODY...))
|
|
|
|
|
The closure is wrapped in `cl-function', meaning ARGLIST will accept anything
|
|
|
|
|
`cl-defun' will. "
|
2020-05-20 15:32:34 -04:00
|
|
|
|
(declare (indent defun) (doc-string 1) (pure t) (side-effect-free t))
|
|
|
|
|
`(cl-function (lambda ,arglist ,@body)))
|
|
|
|
|
|
2020-05-27 16:12:45 -04:00
|
|
|
|
(defmacro cmd! (&rest body)
|
2020-12-11 17:38:59 -05:00
|
|
|
|
"Returns (lambda () (interactive) ,@body)
|
2020-05-27 16:12:45 -04:00
|
|
|
|
A factory for quickly producing interaction commands, particularly for keybinds
|
|
|
|
|
or aliases."
|
|
|
|
|
(declare (doc-string 1) (pure t) (side-effect-free t))
|
|
|
|
|
`(lambda (&rest _) (interactive) ,@body))
|
|
|
|
|
|
2020-07-22 17:09:38 -04:00
|
|
|
|
(defmacro cmd!! (command &optional prefix-arg &rest args)
|
2020-12-11 17:38:59 -05:00
|
|
|
|
"Returns a closure that interactively calls COMMAND with ARGS and PREFIX-ARG.
|
|
|
|
|
Like `cmd!', but allows you to change `current-prefix-arg' or pass arguments to
|
|
|
|
|
COMMAND. This macro is meant to be used as a target for keybinds (e.g. with
|
|
|
|
|
`define-key' or `map!')."
|
2020-05-27 16:12:45 -04:00
|
|
|
|
(declare (doc-string 1) (pure t) (side-effect-free t))
|
2020-07-22 17:09:38 -04:00
|
|
|
|
`(lambda (arg &rest _) (interactive "P")
|
|
|
|
|
(let ((current-prefix-arg (or ,prefix-arg arg)))
|
2020-07-24 18:17:33 -04:00
|
|
|
|
(,(if args
|
|
|
|
|
'funcall-interactively
|
|
|
|
|
'call-interactively)
|
|
|
|
|
,command ,@args))))
|
2020-05-27 16:12:45 -04:00
|
|
|
|
|
|
|
|
|
(defmacro cmds! (&rest branches)
|
2020-12-11 17:38:59 -05:00
|
|
|
|
"Returns a dispatcher that runs the a command in BRANCHES.
|
|
|
|
|
Meant to be used as a target for keybinds (e.g. with `define-key' or `map!').
|
|
|
|
|
|
|
|
|
|
BRANCHES is a flat list of CONDITION COMMAND pairs. CONDITION is a lisp form
|
|
|
|
|
that is evaluated when (and each time) the dispatcher is invoked. If it returns
|
|
|
|
|
non-nil, COMMAND is invoked, otherwise it falls through to the next pair.
|
|
|
|
|
|
|
|
|
|
The last element of BRANCHES can be a COMMANd with no CONDITION. This acts as
|
|
|
|
|
the fallback if all other conditions fail.
|
|
|
|
|
|
|
|
|
|
Otherwise, Emacs will fall through the keybind and search the next keymap for a
|
|
|
|
|
keybind (as if this keybind never existed).
|
|
|
|
|
|
|
|
|
|
See `general-key-dispatch' for what other arguments it accepts in BRANCHES."
|
2020-05-27 16:12:45 -04:00
|
|
|
|
(declare (doc-string 1))
|
|
|
|
|
(let ((docstring (if (stringp (car branches)) (pop branches) ""))
|
|
|
|
|
fallback)
|
|
|
|
|
(when (cl-oddp (length branches))
|
|
|
|
|
(setq fallback (car (last branches))
|
|
|
|
|
branches (butlast branches)))
|
2021-02-11 15:51:43 -05:00
|
|
|
|
(let ((defs (cl-loop for (key value) on branches by 'cddr
|
|
|
|
|
unless (keywordp key)
|
|
|
|
|
collect (list key value))))
|
|
|
|
|
`'(menu-item
|
|
|
|
|
,(or docstring "") nil
|
|
|
|
|
:filter (lambda (&optional _)
|
|
|
|
|
(let (it)
|
|
|
|
|
(cond ,@(mapcar (lambda (pred-def)
|
|
|
|
|
`((setq it ,(car pred-def))
|
|
|
|
|
,(cadr pred-def)))
|
|
|
|
|
defs)
|
|
|
|
|
(t ,fallback))))))))
|
2020-05-27 16:12:45 -04:00
|
|
|
|
|
2020-12-11 16:59:47 -05:00
|
|
|
|
(defalias 'kbd! 'general-simulate-key)
|
|
|
|
|
|
2020-05-27 16:12:45 -04:00
|
|
|
|
;; For backwards compatibility
|
|
|
|
|
(defalias 'λ! 'cmd!)
|
|
|
|
|
(defalias 'λ!! 'cmd!!)
|
|
|
|
|
;; DEPRECATED These have been superseded by `cmd!' and `cmd!!'
|
|
|
|
|
(define-obsolete-function-alias 'lambda! 'cmd! "3.0.0")
|
|
|
|
|
(define-obsolete-function-alias 'lambda!! 'cmd!! "3.0.0")
|
|
|
|
|
|
2020-05-20 15:32:34 -04:00
|
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
|
;;; Mutation
|
|
|
|
|
(defmacro appendq! (sym &rest lists)
|
|
|
|
|
"Append LISTS to SYM in place."
|
|
|
|
|
`(setq ,sym (append ,sym ,@lists)))
|
|
|
|
|
|
|
|
|
|
(defmacro setq! (&rest settings)
|
|
|
|
|
"A stripped-down `customize-set-variable' with the syntax of `setq'.
|
|
|
|
|
|
2020-05-20 01:05:27 -04:00
|
|
|
|
This can be used as a drop-in replacement for `setq'. Particularly when you know
|
|
|
|
|
a variable has a custom setter (a :set property in its `defcustom' declaration).
|
|
|
|
|
This triggers setters. `setq' does not."
|
2020-04-29 20:45:29 -04:00
|
|
|
|
(macroexp-progn
|
|
|
|
|
(cl-loop for (var val) on settings by 'cddr
|
2020-05-20 01:05:27 -04:00
|
|
|
|
collect `(funcall (or (get ',var 'custom-set) #'set)
|
|
|
|
|
',var ,val))))
|
2020-04-29 20:45:29 -04:00
|
|
|
|
|
|
|
|
|
(defmacro delq! (elt list &optional fetcher)
|
|
|
|
|
"`delq' ELT from LIST in-place.
|
|
|
|
|
|
|
|
|
|
If FETCHER is a function, ELT is used as the key in LIST (an alist)."
|
|
|
|
|
`(setq ,list
|
|
|
|
|
(delq ,(if fetcher
|
|
|
|
|
`(funcall ,fetcher ,elt ,list)
|
|
|
|
|
elt)
|
|
|
|
|
,list)))
|
|
|
|
|
|
|
|
|
|
(defmacro pushnew! (place &rest values)
|
|
|
|
|
"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) (with-no-warnings ,place))
|
|
|
|
|
(cl-pushnew ,var ,place :test #'equal))))
|
|
|
|
|
|
|
|
|
|
(defmacro prependq! (sym &rest lists)
|
|
|
|
|
"Prepend LISTS to SYM in place."
|
|
|
|
|
`(setq ,sym (append ,@lists ,sym)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; Loading
|
|
|
|
|
(defmacro add-load-path! (&rest dirs)
|
|
|
|
|
"Add DIRS to `load-path', relative to the current file.
|
|
|
|
|
The current file is the file from which `add-to-load-path!' is used."
|
|
|
|
|
`(let ((default-directory ,(dir!))
|
|
|
|
|
file-name-handler-alist)
|
|
|
|
|
(dolist (dir (list ,@dirs))
|
2020-08-07 18:28:36 +02:00
|
|
|
|
(cl-pushnew (expand-file-name dir) load-path :test #'string=))))
|
2019-07-21 14:44:04 +02:00
|
|
|
|
|
2020-01-30 16:57:49 -05:00
|
|
|
|
(defmacro after! (package &rest body)
|
|
|
|
|
"Evaluate BODY after PACKAGE have loaded.
|
|
|
|
|
|
|
|
|
|
PACKAGE is a symbol or list of them. These are package names, not modes,
|
|
|
|
|
functions or variables. It can be:
|
|
|
|
|
|
|
|
|
|
- An unquoted package symbol (the name of a package)
|
|
|
|
|
(after! helm BODY...)
|
|
|
|
|
- An unquoted list of package symbols (i.e. BODY is evaluated once both magit
|
|
|
|
|
and git-gutter have loaded)
|
|
|
|
|
(after! (magit git-gutter) BODY...)
|
|
|
|
|
- An unquoted, nested list of compound package lists, using any combination of
|
|
|
|
|
:or/:any and :and/:all
|
|
|
|
|
(after! (:or package-a package-b ...) BODY...)
|
|
|
|
|
(after! (:and package-a package-b ...) BODY...)
|
|
|
|
|
(after! (:and package-a (:or package-b package-c) ...) BODY...)
|
|
|
|
|
Without :or/:any/:and/:all, :and/:all are implied.
|
|
|
|
|
|
|
|
|
|
This is a wrapper around `eval-after-load' that:
|
|
|
|
|
|
|
|
|
|
1. Suppresses warnings for disabled packages at compile-time
|
|
|
|
|
2. No-ops for package that are disabled by the user (via `package!')
|
|
|
|
|
3. Supports compound package statements (see below)
|
|
|
|
|
4. Prevents eager expansion pulling in autoloaded macros all at once"
|
|
|
|
|
(declare (indent defun) (debug t))
|
|
|
|
|
(if (symbolp package)
|
|
|
|
|
(unless (memq package (bound-and-true-p doom-disabled-packages))
|
|
|
|
|
(list (if (or (not (bound-and-true-p byte-compile-current-file))
|
|
|
|
|
(require package nil 'noerror))
|
|
|
|
|
#'progn
|
|
|
|
|
#'with-no-warnings)
|
2021-02-24 18:28:23 -05:00
|
|
|
|
;; We intentionally avoid `with-eval-after-load' to prevent eager
|
|
|
|
|
;; macro expansion from pulling (or failing to pull) in autoloaded
|
|
|
|
|
;; macros/packages.
|
|
|
|
|
`(eval-after-load ',package ',(macroexp-progn body))))
|
2020-01-30 16:57:49 -05:00
|
|
|
|
(let ((p (car package)))
|
|
|
|
|
(cond ((not (keywordp p))
|
|
|
|
|
`(after! (:and ,@package) ,@body))
|
|
|
|
|
((memq p '(:or :any))
|
|
|
|
|
(macroexp-progn
|
|
|
|
|
(cl-loop for next in (cdr package)
|
|
|
|
|
collect `(after! ,next ,@body))))
|
|
|
|
|
((memq p '(:and :all))
|
|
|
|
|
(dolist (next (cdr package))
|
|
|
|
|
(setq body `((after! ,next ,@body))))
|
|
|
|
|
(car body))))))
|
|
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
|
(defun doom--handle-load-error (e target path)
|
|
|
|
|
(let* ((source (file-name-sans-extension target))
|
|
|
|
|
(err (cond ((not (featurep 'core))
|
|
|
|
|
(cons 'error (file-name-directory path)))
|
|
|
|
|
((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))))
|
2020-01-24 16:38:44 -05:00
|
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
|
(defmacro load! (filename &optional path noerror)
|
|
|
|
|
"Load a file relative to the current executing file (`load-file-name').
|
2019-07-21 19:11:43 +02:00
|
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
|
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).
|
2019-07-21 14:39:16 +02:00
|
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
|
If NOERROR is non-nil, don't throw an error if the file doesn't exist."
|
|
|
|
|
(let* ((path (or path
|
|
|
|
|
(dir!)
|
|
|
|
|
(error "Could not detect path to look for '%s' in"
|
|
|
|
|
filename)))
|
|
|
|
|
(file (if path
|
|
|
|
|
`(expand-file-name ,filename ,path)
|
|
|
|
|
filename)))
|
|
|
|
|
`(condition-case-unless-debug e
|
|
|
|
|
(let (file-name-handler-alist)
|
|
|
|
|
(load ,file ,noerror 'nomessage))
|
|
|
|
|
(doom-error (signal (car e) (cdr e)))
|
|
|
|
|
(error (doom--handle-load-error e ,file ,path)))))
|
2019-07-21 14:39:16 +02:00
|
|
|
|
|
2020-04-29 20:45:29 -04: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)
|
|
|
|
|
,(let ((fn (intern (format "doom--delay-form-%s-h" (sxhash (cons condition body))))))
|
|
|
|
|
`(progn
|
|
|
|
|
(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-08-10 11:07:25 -04:00
|
|
|
|
|
2020-06-05 01:41:49 -04:00
|
|
|
|
(defmacro defer-feature! (feature &rest fns)
|
2020-04-29 20:45:29 -04:00
|
|
|
|
"Pretend FEATURE hasn't been loaded yet, until FEATURE-hook or FN runs.
|
2019-04-08 23:01:30 -04:00
|
|
|
|
|
2020-04-29 20:45:29 -04:00
|
|
|
|
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 FN is provided) is triggered to
|
|
|
|
|
reverse this and trigger `after!' blocks at a more reasonable time."
|
2020-06-05 01:41:49 -04:00
|
|
|
|
(let ((advice-fn (intern (format "doom--defer-feature-%s-a" feature))))
|
2020-04-29 20:45:29 -04:00
|
|
|
|
`(progn
|
2020-06-05 01:41:49 -04:00
|
|
|
|
(delq! ',feature features)
|
|
|
|
|
(defadvice! ,advice-fn (&rest _)
|
|
|
|
|
:before ',fns
|
2020-04-29 20:45:29 -04:00
|
|
|
|
;; Some plugins (like yasnippet) will invoke a fn 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:
|
2020-06-05 01:41:49 -04:00
|
|
|
|
(unless delay-mode-hooks
|
2020-04-29 20:45:29 -04:00
|
|
|
|
;; ...Otherwise, announce to the world this package has been loaded,
|
|
|
|
|
;; so `after!' handlers can react.
|
|
|
|
|
(provide ',feature)
|
2020-06-05 01:41:49 -04:00
|
|
|
|
(dolist (fn ',fns)
|
|
|
|
|
(advice-remove fn #',advice-fn)))))))
|
2019-12-08 16:11:29 -05:00
|
|
|
|
|
2019-10-23 04:24:06 -04:00
|
|
|
|
|
2020-03-03 18:58:45 -05:00
|
|
|
|
;;; Hooks
|
|
|
|
|
(defvar doom--transient-counter 0)
|
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)))
|
2020-03-03 18:58:45 -05:00
|
|
|
|
;; Avoid `make-symbol' and `gensym' here because an interned symbol is
|
|
|
|
|
;; easier to debug in backtraces (and is visible to `describe-function')
|
|
|
|
|
(fn (intern (format "doom--transient-%d-h" (cl-incf doom--transient-counter)))))
|
2019-05-04 18:55:24 -04:00
|
|
|
|
`(let ((sym ,hook-or-function))
|
2019-07-21 14:40:38 +02:00
|
|
|
|
(defun ,fn (&rest _)
|
2020-03-03 18:58:45 -05:00
|
|
|
|
,(format "Transient hook for %S" (doom-unquote hook-or-function))
|
2019-07-21 14:40:38 +02:00
|
|
|
|
,@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
|
|
|
|
|
2019-07-26 19:57:13 +02:00
|
|
|
|
(defmacro add-hook! (hooks &rest rest)
|
2019-05-01 19:12:52 -04:00
|
|
|
|
"A convenience macro for adding N functions to M hooks.
|
|
|
|
|
|
|
|
|
|
This macro accepts, in order:
|
2017-01-16 23:15:48 -05:00
|
|
|
|
|
2020-01-20 05:41:42 -05:00
|
|
|
|
1. The mode(s) or hook(s) to add to. This is either an unquoted mode, an
|
|
|
|
|
unquoted list of modes, a quoted hook variable or a quoted list of hook
|
|
|
|
|
variables.
|
2021-05-16 21:19:30 -04:00
|
|
|
|
2. Optional properties :local, :append, and/or :depth [N], which will make the
|
|
|
|
|
hook buffer-local or append to the list of hooks (respectively),
|
2020-01-20 05:41:42 -05:00
|
|
|
|
3. The function(s) to be added: this can be one function, a quoted list
|
|
|
|
|
thereof, a list of `defun's, or body forms (implicitly wrapped in a
|
|
|
|
|
lambda).
|
2017-01-16 23:15:48 -05:00
|
|
|
|
|
2019-09-16 12:07:32 -04:00
|
|
|
|
\(fn HOOKS [:append :local] FUNCTIONS)"
|
2019-07-26 19:57:13 +02:00
|
|
|
|
(declare (indent (lambda (indent-point state)
|
|
|
|
|
(goto-char indent-point)
|
|
|
|
|
(when (looking-at-p "\\s-*(")
|
|
|
|
|
(lisp-indent-defform state indent-point))))
|
|
|
|
|
(debug t))
|
|
|
|
|
(let* ((hook-forms (doom--resolve-hook-forms hooks))
|
|
|
|
|
(func-forms ())
|
|
|
|
|
(defn-forms ())
|
|
|
|
|
append-p
|
|
|
|
|
local-p
|
|
|
|
|
remove-p
|
2021-05-09 13:06:24 -04:00
|
|
|
|
depth
|
2019-07-26 19:57:13 +02:00
|
|
|
|
forms)
|
|
|
|
|
(while (keywordp (car rest))
|
|
|
|
|
(pcase (pop rest)
|
2017-02-28 15:29:23 -05:00
|
|
|
|
(:append (setq append-p t))
|
2021-05-09 13:06:24 -04:00
|
|
|
|
(:depth (setq depth (pop rest)))
|
2017-06-08 11:47:56 +02:00
|
|
|
|
(:local (setq local-p t))
|
2019-07-26 19:57:13 +02:00
|
|
|
|
(:remove (setq remove-p t))))
|
|
|
|
|
(let ((first (car-safe (car rest))))
|
|
|
|
|
(cond ((null first)
|
|
|
|
|
(setq func-forms rest))
|
|
|
|
|
|
|
|
|
|
((eq first 'defun)
|
|
|
|
|
(setq func-forms (mapcar #'cadr rest)
|
|
|
|
|
defn-forms rest))
|
|
|
|
|
|
|
|
|
|
((memq first '(quote function))
|
|
|
|
|
(setq func-forms
|
|
|
|
|
(if (cdr rest)
|
|
|
|
|
(mapcar #'doom-unquote rest)
|
|
|
|
|
(doom-enlist (doom-unquote (car rest))))))
|
|
|
|
|
|
2019-10-28 11:54:24 -04:00
|
|
|
|
((setq func-forms (list `(lambda (&rest _) ,@rest)))))
|
2019-07-26 19:57:13 +02:00
|
|
|
|
(dolist (hook hook-forms)
|
|
|
|
|
(dolist (func func-forms)
|
|
|
|
|
(push (if remove-p
|
|
|
|
|
`(remove-hook ',hook #',func ,local-p)
|
2021-05-09 13:06:24 -04:00
|
|
|
|
`(add-hook ',hook #',func ,(or depth append-p) ,local-p))
|
2017-02-28 15:29:23 -05:00
|
|
|
|
forms)))
|
2019-07-18 15:27:20 +02:00
|
|
|
|
(macroexp-progn
|
2019-07-26 19:57:13 +02:00
|
|
|
|
(append defn-forms
|
|
|
|
|
(if append-p
|
|
|
|
|
(nreverse forms)
|
|
|
|
|
forms))))))
|
2017-01-16 23:15:48 -05:00
|
|
|
|
|
2019-07-26 19:57:13 +02:00
|
|
|
|
(defmacro remove-hook! (hooks &rest rest)
|
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
|
|
|
|
|
2019-09-16 12:07:32 -04:00
|
|
|
|
\(fn HOOKS [:append :local] FUNCTIONS)"
|
2018-05-07 22:35:14 +02:00
|
|
|
|
(declare (indent defun) (debug t))
|
2019-07-26 19:57:13 +02:00
|
|
|
|
`(add-hook! ,hooks :remove ,@rest))
|
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
|
|
|
|
|
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
|
2019-07-29 21:04:04 +02:00
|
|
|
|
collect `(add-hook ',hook #',fn))))
|
2019-07-21 14:35:45 +02:00
|
|
|
|
|
|
|
|
|
(defmacro unsetq-hook! (hooks &rest vars)
|
|
|
|
|
"Unbind setq hooks on HOOKS for VARS.
|
|
|
|
|
|
|
|
|
|
\(fn HOOKS &rest [SYM VAL]...)"
|
|
|
|
|
(declare (indent 1))
|
|
|
|
|
(macroexp-progn
|
2019-10-07 16:10:33 -04:00
|
|
|
|
(cl-loop for (_var _val hook fn)
|
|
|
|
|
in (doom--setq-hook-fns hooks vars 'singles)
|
2019-07-21 14:35:45 +02:00
|
|
|
|
collect `(remove-hook ',hook #',fn))))
|
2018-05-07 22:35:14 +02:00
|
|
|
|
|
2019-07-18 15:25:01 +02:00
|
|
|
|
|
2019-07-23 17:24:56 +02:00
|
|
|
|
;;; Definers
|
|
|
|
|
(defmacro defadvice! (symbol arglist &optional docstring &rest body)
|
2019-08-05 18:27:56 -05:00
|
|
|
|
"Define an advice called SYMBOL and add it to PLACES.
|
2019-07-23 17:24:56 +02:00
|
|
|
|
|
|
|
|
|
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)
|
2019-12-19 14:49:17 -05:00
|
|
|
|
(dolist (targets (list ,@(nreverse where-alist)))
|
|
|
|
|
(dolist (target (cdr targets))
|
|
|
|
|
(advice-add target (car targets) #',symbol))))))
|
2019-07-23 17:24:56 +02:00
|
|
|
|
|
2020-01-01 14:31:49 -05:00
|
|
|
|
(defmacro undefadvice! (symbol _arglist &optional docstring &rest body)
|
|
|
|
|
"Undefine an advice called SYMBOL.
|
|
|
|
|
|
|
|
|
|
This has the same signature as `defadvice!' an exists as an easy undefiner when
|
|
|
|
|
testing advice (when combined with `rotate-text').
|
|
|
|
|
|
|
|
|
|
\(fn SYMBOL ARGLIST &optional DOCSTRING &rest [WHERE PLACES...] BODY\)"
|
|
|
|
|
(declare (doc-string 3) (indent defun))
|
|
|
|
|
(let (where-alist)
|
|
|
|
|
(unless (stringp docstring)
|
|
|
|
|
(push docstring body))
|
|
|
|
|
(while (keywordp (car body))
|
|
|
|
|
(push `(cons ,(pop body) (doom-enlist ,(pop body)))
|
|
|
|
|
where-alist))
|
|
|
|
|
`(dolist (targets (list ,@(nreverse where-alist)))
|
|
|
|
|
(dolist (target (cdr targets))
|
|
|
|
|
(advice-remove target #',symbol)))))
|
|
|
|
|
|
2020-05-24 16:45:55 -04:00
|
|
|
|
|
|
|
|
|
;;
|
|
|
|
|
;;; Backports
|
|
|
|
|
|
2020-08-26 21:40:53 -04:00
|
|
|
|
(eval-when! (version< emacs-version "27.0.90")
|
2020-05-24 16:45:55 -04:00
|
|
|
|
;; DEPRECATED Backported from Emacs 27
|
|
|
|
|
(defmacro setq-local (&rest pairs)
|
|
|
|
|
"Make variables in PAIRS buffer-local and assign them the corresponding values.
|
|
|
|
|
|
|
|
|
|
PAIRS is a list of variable/value pairs. For each variable, make
|
|
|
|
|
it buffer-local and assign it the corresponding value. The
|
|
|
|
|
variables are literal symbols and should not be quoted.
|
|
|
|
|
|
|
|
|
|
The second VALUE is not computed until after the first VARIABLE
|
|
|
|
|
is set, and so on; each VALUE can use the new value of variables
|
|
|
|
|
set earlier in the ‘setq-local’. The return value of the
|
|
|
|
|
‘setq-local’ form is the value of the last VALUE.
|
|
|
|
|
|
|
|
|
|
\(fn [VARIABLE VALUE]...)"
|
|
|
|
|
(declare (debug setq))
|
|
|
|
|
(unless (zerop (mod (length pairs) 2))
|
|
|
|
|
(error "PAIRS must have an even number of variable/value members"))
|
|
|
|
|
(let ((expr nil))
|
|
|
|
|
(while pairs
|
|
|
|
|
(unless (symbolp (car pairs))
|
|
|
|
|
(error "Attempting to set a non-symbol: %s" (car pairs)))
|
|
|
|
|
;; Can't use backquote here, it's too early in the bootstrap.
|
|
|
|
|
(setq expr
|
|
|
|
|
(cons
|
|
|
|
|
(list 'set
|
|
|
|
|
(list 'make-local-variable (list 'quote (car pairs)))
|
|
|
|
|
(car (cdr pairs)))
|
|
|
|
|
expr))
|
|
|
|
|
(setq pairs (cdr (cdr pairs))))
|
|
|
|
|
(macroexp-progn (nreverse expr)))))
|
|
|
|
|
|
2020-11-01 18:48:45 -05:00
|
|
|
|
(eval-when! (version< emacs-version "27.1")
|
2021-01-31 03:47:16 -05:00
|
|
|
|
;; DEPRECATED Backported from Emacs 27. Remove when 26.x support is dropped.
|
2020-11-01 18:48:45 -05:00
|
|
|
|
(defun executable-find (command &optional remote)
|
|
|
|
|
"Search for COMMAND in `exec-path' and return the absolute file name.
|
|
|
|
|
Return nil if COMMAND is not found anywhere in `exec-path'. If
|
|
|
|
|
REMOTE is non-nil, search on the remote host indicated by
|
|
|
|
|
`default-directory' instead."
|
|
|
|
|
(if (and remote (file-remote-p default-directory))
|
|
|
|
|
(let ((res (locate-file
|
|
|
|
|
command
|
|
|
|
|
(mapcar
|
|
|
|
|
(lambda (x) (concat (file-remote-p default-directory) x))
|
|
|
|
|
(exec-path))
|
|
|
|
|
exec-suffixes 'file-executable-p)))
|
|
|
|
|
(when (stringp res) (file-local-name res)))
|
|
|
|
|
;; Use 1 rather than file-executable-p to better match the
|
|
|
|
|
;; behavior of call-process.
|
2020-11-02 17:00:13 -05:00
|
|
|
|
(let ((default-directory
|
|
|
|
|
(let (file-name-handler-alist)
|
|
|
|
|
(file-name-quote default-directory))))
|
2020-11-01 18:48:45 -05:00
|
|
|
|
(locate-file command exec-path exec-suffixes 1)))))
|
|
|
|
|
|
2021-01-31 03:44:56 -05:00
|
|
|
|
(eval-when! (not (fboundp 'exec-path))
|
2021-01-31 03:47:16 -05:00
|
|
|
|
;; DEPRECATED Backported from Emacs 27.1. Remove when 26.x support is dropped.
|
2021-01-03 17:40:33 -05:00
|
|
|
|
(defun exec-path ()
|
|
|
|
|
"Return list of directories to search programs to run in remote subprocesses.
|
|
|
|
|
The remote host is identified by `default-directory'. For remote
|
|
|
|
|
hosts that do not support subprocesses, this returns `nil'.
|
|
|
|
|
If `default-directory' is a local directory, this function returns
|
|
|
|
|
the value of the variable `exec-path'."
|
|
|
|
|
(let ((handler (find-file-name-handler default-directory 'exec-path)))
|
|
|
|
|
(if handler
|
|
|
|
|
(funcall handler 'exec-path)
|
|
|
|
|
exec-path))))
|
|
|
|
|
|
2017-01-16 23:15:48 -05:00
|
|
|
|
(provide 'core-lib)
|
|
|
|
|
;;; core-lib.el ends here
|