doomemacs/core/core-defuns.el

357 lines
14 KiB
EmacsLisp
Raw Normal View History

;;; core-defuns.el
;; Bootstrap macro
(defmacro doom (_ theme __ font &rest packages)
"Bootstrap DOOM emacs and initialize PACKAGES"
`(let (file-name-handler-alist)
;; Local settings
;; (load "~/.emacs.local.el" t t)
;; Global constants
(defvar doom-default-theme ,theme)
(defvar doom-default-font
(font-spec :family ,(nth 0 font)
:size ,(nth 1 font)
:antialias ,(not (nth 2 font))))
(defvar doom-current-theme doom-default-theme)
(defvar doom-current-font doom-default-font)
(unless noninteractive
,@(mapcar (lambda (pkg) `(require ',pkg))
packages)
(when window-system
(require 'server)
(unless (server-running-p)
(server-start)))
;; Prevent any auto-displayed text + benchmarking
(advice-add 'display-startup-echo-area-message :override 'ignore)
(message ""))
(setq-default gc-cons-threshold 4388608
gc-cons-percentage 0.4)))
2015-06-15 15:05:52 +08:00
;; Backwards compatible `with-eval-after-load'
(unless (fboundp 'with-eval-after-load)
(defmacro with-eval-after-load (file &rest body)
`(eval-after-load ,file (lambda () ,@body))))
2015-12-12 11:43:31 +08:00
(defmacro λ! (&rest body)
"A shortcut for inline keybind lambdas."
2015-06-15 15:05:52 +08:00
`(lambda () (interactive) ,@body))
(defmacro shut-up! (&rest body)
"Silence message output from code."
(declare (indent defun))
`(let (message-log-max) ,@body (message "")))
(defmacro after! (feature &rest forms)
"A smart wrapper around `with-eval-after-load', that supresses warnings
during compilation."
(declare (indent defun) (debug t))
`(,(if (or (not (boundp 'byte-compile-current-file))
(not byte-compile-current-file)
(if (symbolp feature)
(require feature nil :no-error)
(load feature :no-message :no-error)))
'progn
(message "after: cannot find %s" feature)
'with-no-warnings)
(with-eval-after-load ',feature ,@forms)))
2015-10-23 16:42:34 +08:00
(defmacro in! (dir &rest forms)
(declare (indent defun))
`(let ((default-directory ,dir))
,@forms))
(defmacro noop! (name &optional args)
`(defun ,name ,args (interactive) (error "%s not implemented!" name)))
2015-06-15 15:05:52 +08:00
(defmacro add-hook! (hook &rest func-or-forms)
"A convenience macro for `add-hook'.
2015-06-06 18:40:33 +08:00
HOOK can be one hook or a list of hooks. If the hook(s) are not quoted, -hook is
appended to them automatically. If they are quoted, they are used verbatim.
2016-05-11 17:36:49 +08:00
FUNC-OR-FORMS can be a quoted symbol, a list of quoted symbols, or forms. Forms will be
wrapped in a lambda. A list of symbols will expand into a series of add-hook calls.
2015-06-06 18:40:33 +08:00
Examples:
2015-06-15 15:05:52 +08: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)
2016-05-11 17:36:49 +08:00
(add-hook! (one-mode second-mode) (setq v 5) (setq a 2))"
2015-06-15 15:05:52 +08:00
(declare (indent defun) (debug t))
(unless func-or-forms
(error "add-hook!: FUNC-OR-FORMS is empty"))
(let* ((val (car func-or-forms))
(quoted (eq (car-safe hook) 'quote))
(hook (if quoted (cadr hook) hook))
(funcs (if (eq (car-safe val) 'quote)
(if (cdr-safe (cadr val))
(cadr val)
(list (cadr val)))
(list func-or-forms)))
(forms '()))
(mapc
(lambda (f)
(let ((func (cond ((symbolp f) `(quote ,f))
(t `(lambda (&rest _) ,@func-or-forms)))))
(mapc
(lambda (h)
(push `(add-hook ',(if quoted h (intern (format "%s-hook" h))) ,func) forms))
(-list hook)))) funcs)
2015-06-15 15:05:52 +08:00
`(progn ,@forms)))
2016-05-12 14:36:11 +08:00
(defmacro associate! (mode &rest rest)
2015-06-15 15:05:52 +08:00
"Associate a major or minor mode to certain patterns and project files."
(declare (indent 1))
2016-05-12 14:36:11 +08:00
(let ((minor (plist-get rest :minor))
(in (plist-get rest :in))
2016-05-12 14:36:11 +08:00
(match (plist-get rest :match))
(files (plist-get rest :files))
(pred (plist-get rest :when)))
2016-05-12 14:36:11 +08:00
`(progn
(,@(cond ((or files in pred)
2016-05-12 14:36:11 +08:00
(when (and files (not (or (listp files) (stringp files))))
(user-error "associate! :files expects a string or list of strings"))
2016-05-21 10:37:30 +08:00
(let ((hook-name (intern (format "doom--init-mode-%s" mode))))
2016-05-12 14:36:11 +08:00
`(progn
(defun ,hook-name ()
(when (and ,(if match `(if buffer-file-name (string-match-p ,match buffer-file-name)) t)
(or ,(not files)
(and (boundp ',mode)
(not ,mode)
2016-05-21 10:37:30 +08:00
(doom/project-has-files ,@(-list files))))
(or (not ,pred)
(funcall ,pred buffer-file-name)))
2016-05-12 14:36:11 +08:00
(,mode 1)))
,@(if (and in (listp in))
(mapcar (lambda (h) `(add-hook ',h ',hook-name))
(mapcar (lambda (m) (intern (format "%s-hook" m))) in))
`((add-hook 'find-file-hook ',hook-name))))))
(match
2016-05-21 10:37:30 +08:00
`(add-to-list ',(if minor 'doom-auto-minor-mode-alist 'auto-mode-alist)
2016-05-12 14:36:11 +08:00
(cons ,match ',mode)))
(t (user-error "associate! invalid rules for mode [%s] (in %s) (match %s) (files %s)"
mode in match files)))))))
2016-04-17 09:36:24 +08:00
(defmacro def-project-type! (name lighter &rest body)
"Define a minor mode for a specific framework, library or project type."
2016-04-17 09:36:24 +08:00
(declare (indent 2))
(let* ((mode-name (format "%s-project-mode" name))
(mode (intern mode-name))
(mode-map (intern (format "%s-map" mode-name)))
(mode-hook-sym (intern (format "%s-hook" mode-name)))
2016-05-21 10:37:30 +08:00
(mode-init-sym (intern (format "doom--init-project-%s" mode-name))))
2016-04-17 09:36:24 +08:00
(let ((modes (plist-get body :modes))
(pred (plist-get body :when))
2016-04-17 09:36:24 +08:00
(match (plist-get body :match))
(files (plist-get body :files))
(build (plist-get body :build))
(bind (plist-get body :bind))
elem)
(while (keywordp (car body))
(pop body)
(pop body))
`(progn
(define-minor-mode ,mode
"Auto-generated by `def-project-type!'"
2016-04-17 09:36:24 +08:00
:init-value nil
:lighter ,(concat " " lighter)
:keymap (make-sparse-keymap))
2016-04-17 09:36:24 +08:00
(after! yasnippet
(add-hook ',mode-hook-sym
(lambda ()
(if ,mode
(yas-activate-extra-mode ',mode)
(yas-deactivate-extra-mode ',mode)))))
,(when bind `(map! :map ,mode-map ,bind))
2016-04-17 09:36:24 +08:00
(associate! ,mode
:minor t
:in ,modes
:match ,match
:files ,files
:when ,pred)
2016-04-17 09:36:24 +08:00
(defun ,mode-init-sym ()
(after! company-dict
(push ',mode company-dict-minor-mode-list))
,(when build
2016-05-03 03:58:10 +08:00
(let ((cmd build) file)
(when (and (not (functionp build)) (listp build))
(setq cmd (car-safe (cdr-safe build))
file (cdr-safe (cdr-safe build))))
`(def-builder! ,mode ,cmd ,file)))
,@body
(remove-hook ',mode-hook-sym ',mode-init-sym))
(add-hook ',mode-hook-sym ',mode-init-sym)
2016-04-17 09:36:24 +08:00
',mode))))
2015-06-15 15:05:52 +08:00
(after! evil
2016-05-20 21:04:39 +08:00
(defalias 'ex! 'evil-ex-define-cmd)
;; Register keywords for proper indentation (see `map!')
(put ':prefix 'lisp-indent-function 'defun)
(put ':map 'lisp-indent-function 'defun)
(put ':after 'lisp-indent-function 'defun)
(put ':when 'lisp-indent-function 'defun)
(put ':unless 'lisp-indent-function 'defun)
(put ':leader 'lisp-indent-function 'defun)
(put ':localleader 'lisp-indent-function 'defun)
(defmacro map! (&rest rest)
2015-06-15 15:05:52 +08:00
(let ((i 0)
(keymaps (if (boundp 'keymaps) keymaps))
(default-keymaps '((current-global-map)))
2015-06-15 15:05:52 +08:00
(state-map '(("n" . normal)
("v" . visual)
("i" . insert)
("e" . emacs)
("o" . operator)
("m" . motion)
("r" . replace)))
2015-12-09 15:10:44 +08:00
(prefix (if (boundp 'prefix) prefix))
2016-04-20 10:15:18 +08:00
key def states forms)
2015-06-15 15:05:52 +08:00
(unless keymaps
(setq keymaps default-keymaps))
(while rest
(setq key (pop rest))
2016-04-20 10:15:18 +08:00
(push
(reverse
(cond ((listp key) ; it's a sub exp
`(,(macroexpand `(map! ,@key))))
((keywordp key)
(when (memq key '(:leader :localleader))
(push (cond ((eq key :leader)
2016-05-21 10:37:30 +08:00
doom-leader)
((eq key :localleader)
2016-05-21 10:37:30 +08:00
doom-localleader))
rest)
(setq key :prefix))
(pcase key
(:prefix (setq prefix (concat prefix (kbd (pop rest)))) nil)
(:map (setq keymaps (-list (pop rest))) nil)
(:unset `(,(macroexpand `(map! ,(kbd (pop rest)) nil))))
(:after (prog1 `((after! ,(pop rest) ,(macroexpand `(map! ,@rest)))) (setq rest '())))
(:when (prog1 `((if ,(pop rest) ,(macroexpand `(map! ,@rest)))) (setq rest '())))
(:unless (prog1 `((if (not ,(pop rest)) ,(macroexpand `(map! ,@rest)))) (setq rest '())))
(otherwise ; might be a state prefix
(mapc (lambda (letter)
(if (assoc letter state-map)
(push (cdr (assoc letter state-map)) states)
(user-error "Invalid mode prefix %s in key %s" letter key)))
(split-string (substring (symbol-name key) 1) "" t))
(unless states
(user-error "Unrecognized keyword %s" key)) nil)))
;; It's a key-def pair
((or (stringp key)
(characterp key)
(vectorp key))
(when (stringp key)
(setq key (kbd key)))
(when prefix
(setq key (cond ((vectorp key) (vconcat prefix key))
(t (concat prefix key)))))
(unless (> (length rest) 0)
(user-error "Map has no definition for %s" key))
(setq def (pop rest))
(let (out-forms)
(mapc (lambda (keymap)
(if states
(push `(evil-define-key ',states ,keymap ,key ,def) out-forms)
(push `(define-key ,keymap ,key ,def) out-forms)))
keymaps)
(setq states '())
out-forms))
(t (user-error "Invalid key %s" key))))
2016-04-20 10:15:18 +08:00
forms)
2016-05-12 14:36:11 +08:00
(setq i (1+ i)))
2016-04-20 10:15:18 +08:00
`(progn ,@(apply #'nconc (delete nil (delete (list nil) (reverse forms))))))))
2015-06-06 18:40:33 +08:00
2016-05-12 14:53:31 +08:00
(defmacro def-repeat! (command next-func prev-func)
"Repeat motions with SPC/S-SPC"
`(defadvice ,command
2016-05-21 10:37:30 +08:00
(before ,(intern (format "doom-space--%s" (symbol-name command))) activate)
2016-05-12 14:53:31 +08:00
(define-key evil-motion-state-map (kbd "SPC") ',next-func)
(define-key evil-motion-state-map (kbd "S-SPC") ',prev-func)))
2015-06-06 18:40:33 +08:00
2016-04-19 14:08:48 +08:00
;;
(defun doom|update-scratch-buffer (&optional dir inhibit-doom)
"Make sure scratch buffer is always 'in a project', and looks good."
2016-05-21 10:37:30 +08:00
(let ((dir (or dir (doom/project-root))))
(with-current-buffer doom-buffer
;; Reset scratch buffer if it wasn't visible
(when (and (get-buffer-window-list doom-buffer nil t)
(not doom-buffer-edited)
(not inhibit-doom))
(doom-mode-init t))
(setq default-directory dir)
(setq mode-line-format '(:eval (spaceline-ml-scratch))))))
2015-06-06 18:40:33 +08:00
2016-05-11 17:36:49 +08:00
;;
;; Global Defuns
;;
2016-05-21 10:37:30 +08:00
(defun doom-reload ()
2016-04-05 00:06:47 +08:00
"Reload `load-path', in case you updated cask while emacs was open!"
(interactive)
2016-05-21 10:37:30 +08:00
(setq load-path (append (list doom-private-dir doom-core-dir doom-modules-dir doom-packages-dir)
(f-directories doom-core-dir nil t)
(f-directories doom-modules-dir nil t)
(f-directories doom-packages-dir)
(f-directories (f-expand "../bootstrap" doom-packages-dir))
2016-05-29 10:33:20 +08:00
(f-directories doom-themes-dir nil t)
doom--load-path))
(message "Reloaded!"))
2016-03-26 13:19:31 +08:00
2016-05-21 10:37:30 +08:00
(defun doom-reload-autoloads ()
"Regenerate autoloads for DOOM emacs."
2016-04-09 03:39:36 +08:00
(interactive)
2016-05-21 10:37:30 +08:00
(let ((generated-autoload-file (concat doom-core-dir "/autoloads.el")))
2016-04-09 03:39:36 +08:00
(when (file-exists-p generated-autoload-file)
(delete-file generated-autoload-file)
(message "Deleted old autoloads.el"))
2016-04-09 03:39:36 +08:00
(mapc (lambda (dir)
(update-directory-autoloads (concat dir "/defuns"))
(message "Scanned: %s" dir))
2016-05-21 10:37:30 +08:00
(list doom-core-dir doom-modules-dir))
(when (called-interactively-p 'interactive)
(require 'autoloads))
2016-04-09 03:39:36 +08:00
(message "Done!")))
2016-05-21 10:37:30 +08:00
(defun doom-fix-unicode (font chars &optional size)
2016-05-09 06:28:38 +08:00
"Display certain unicode characters in a specific font.
2016-05-21 10:37:30 +08:00
e.g. (doom-fix-unicode \"DejaVu Sans\" '(?⚠ ?★ ?➊ ?➋ ?➌ ?➍ ?➎ ?❻ ?➐ ?➑ ?➒ ?➓))"
2016-05-09 06:28:38 +08:00
(mapc (lambda (x) (set-fontset-font
2016-06-03 12:59:04 +08:00
t (cons x x)
(font-spec :name font :size size)))
2016-05-09 06:28:38 +08:00
chars))
(defun doom-byte-compile (&optional minimal)
2016-05-22 18:25:04 +08:00
"Byte compile the core and library .el files in ~/.emacs.d"
(interactive)
2016-05-29 12:40:44 +08:00
(mapc (lambda (f) (byte-compile-file (concat doom-emacs-dir "/" f) t))
'("init.el" "core/core.el" "core/core-defuns.el" "core/core-ui.el"
"core/core-os.el" "core/core-os-osx.el" "core/core-os-win32.el"
2016-05-29 12:40:44 +08:00
"core/core-os-linux.el" "private/my-commands.el"
"private/my-bindings.el"))
(unless minimal
(byte-recompile-directory doom-core-dir 0 t)
(byte-recompile-directory doom-modules-dir 0 t))
(when minimal
(byte-recompile-directory (concat doom-core-dir "/defuns") 0 t)
(byte-recompile-directory (concat doom-modules-dir "/defuns") 0 t))
2016-05-22 18:25:04 +08:00
(message "Compiled!"))
2015-06-06 18:40:33 +08:00
(provide 'core-defuns)
;;; core-defuns.el ends here