doomemacs/core/core-defuns.el
2015-06-06 06:40:46 -04:00

372 lines
15 KiB
EmacsLisp

(! (defalias '@--concat-forms 'use-package-concat)
(defalias '@--normalize-symbols 'use-package-normalize-symlist)
(defalias '@--normalize-paths 'use-package-normalize-paths)
;; Backwards compatible `with-eval-after-load'
(unless (fboundp 'with-eval-after-load)
(defmacro with-eval-after-load (file &rest body)
`(eval-after-load ,file
`(funcall (function ,(lambda () ,@body))))))
(defmacro @after (feature &rest forms)
(declare (indent 1))
`(,(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)))
(defmacro @shut-up (&rest body)
"Silence message output from code."
(declare (indent defun))
`(let (message-log-max) ,@body (message "")))
(defmacro @ (args &rest body)
"A shortcut for: `(lambda ,args ,@body)"
`(lambda ,args ,@body))
(defmacro λ (&rest body)
"A shortcut for: `(lambda () (interactive) ,@body)"
`(lambda () (interactive) ,@body))
(defmacro @add-hook (hook &rest func-or-forms)
"A convenience macro for `add-hook'.
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.
FUNC-OR-FORMS can be one quoted symbol, a list of quoted symbols, or a series of
forms. Forms will be wrapped in one lambda. A list of symbols will expand into a
series of add-hook calls.
Examples:
(@add-hook 'some-mode-hook 'enable-something)
=> (add-hook 'some-mode-hook 'enable-something)
(@add-hook some-mode '(enable-something and-another))
=> (add-hook 'some-mode-hook 'enable-something)
(add-hook 'some-mode-hook 'and-another)
(@add-hook '(one-mode-hook second-mode-hook) 'enable-something)
=> (add-hook 'one-mode-hook 'enable-something)
(add-hook 'second-mode-hook 'enable-something)
(@add-hook (one-mode second-mode) 'enable-something)
=> (add-hook 'one-mode-hook 'enable-something)
(add-hook 'second-mode-hook 'enable-something)
(@add-hook (one-mode second-mode) (setq v 5) (setq a 2))
=> (add-hook 'one-mode-hook (lambda () (setq v 5) (setq a 2)))
(add-hook 'second-mode-hook (lambda () (setq v 5) (setq a 2)))"
(declare (indent 1))
(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 (@ (f)
(let ((func (cond ((symbolp f) `(quote ,f))
(t `(lambda () ,@func-or-forms)))))
(mapc (@ (h)
(push `(add-hook ',(if quoted h (intern (format "%s-hook" h))) ,func) forms))
(if (listp hook) hook (list hook))))) funcs)
`(progn ,@forms)))
(cl-defmacro @associate (mode &key in
&key match
&key files
&allow-other-keys)
"Associate a major or minor mode to certain patterns and project files."
(let* ((minor-p (memq mode minor-mode-alist))
(modes (@--normalize-symbols ":in" in)))
(@--concat-forms
(when match
`(add-to-list ,(if minor-p 'narf-auto-minor-mode-alist 'auto-mode-alist)
(cons ,match ,mode)))
(when files
`(defun ,(intern (format "narf|init-mode-%s" 'lb6-mode)) ()
(when (and (assq major-mode '(,@(@--normalize-paths ":in" in)))
(narf-project-has-files ,@(@--normalize-paths ":files" files)))
(,mode 1)))))))
(@after evil
;; Placeholders to correct binding indentation. Don't use these.
(defmacro :leader (key &rest rest) (declare (indent 1)))
(defmacro :localleader (key &rest rest) (declare (indent 1)))
(defmacro :map (key &rest rest) (declare (indent 1)))
(defmacro :after (key &rest rest) (declare (indent 1)))
(defmacro :when (key &rest rest) (declare (indent 1)))
(macroexpand `(@map (:map my-map "C-k" 'hello :n "C-p" 'goodbye)))
(defmacro @map (&rest rest)
(declare (indent defun))
(let ((i 0)
key def
first-set
prefix
(default-keymaps '(narf-mode-map))
(keymaps (if (boundp 'keymaps) keymaps))
(states (if (boundp 'states) states '()))
(forms (if (boundp 'forms) forms))
(state-map '(("n" . normal)
("v" . visual)
("i" . insert)
("e" . emacs)
("o" . operator)
("m" . motion)
("r" . replace)
("I" . iedit))))
(unless keymaps
(setq keymaps default-keymaps))
(while rest
(setq key (pop rest))
(message ">>> KEY: %s" key)
(add-to-list
'forms
(cond ((eq key '-)) ; skip this
((listp key) ; it's a sub exp
(macroexpand `(@map ,@key)))
((keywordp key)
(pcase key
;; TODO: Data checks
(:leader (setq prefix (kbd (pop rest))) nil)
(:localleader (setq prefix (kbd (pop rest))) nil)
(:prefix (setq prefix (kbd (pop rest)))
(if (= i 0) (setq first-set `(:prefix . ,prefix)))
nil)
(:map (setq keymaps (-list (pop rest)))
(if (= i 0) (setq first-set `(:map . ,keymaps)))
nil)
(:unset (prog1 `(@map ,(kbd (pop rest)) nil)))
(:after (prog1 `(@after ,(pop rest) ,(macroexp-progn `(@map ,@rest))) (setq rest '())))
(:when (prog1 `(when ,(pop rest) ,(macroexp-progn `(@map ,@rest))) (setq rest '())))
(:unless (prog1 `(unless ,(pop rest) ,(macroexp-progn `(@map ,@rest))) (setq rest '())))
(otherwise ; might be a state prefix
(mapc (lambda (letter)
(when (assoc letter state-map)
(add-to-list 'states (cdr (assoc letter state-map)))))
(s-split "" (substring (symbol-name key) 1) t)) nil)))
;; It's a key-def pair
((or (stringp key)
(characterp key)
(vectorp key))
(when (stringp key)
(setq key (kbd key)))
(when prefix
(cond ((vectorp key)
(setq key (vconcat prefix key)))
(t
(setq key (concat prefix key)))))
(unless (car rest)
(user-error "Map has no definition for %s" key))
(setq def (pop rest))
(let ((first-key (car first-set))
(first-value (cdr first-set))
out-forms)
(dolist (keymap keymaps)
(if (not states)
(add-to-list 'out-forms `(define-key ,keymap ,key ,def) t)
(dolist (state states)
(add-to-list 'out-forms `(define-key (evil-get-auxiliary-keymap ,keymap ,state t) ,key ,def) t))))
(setq prefix (if (eq first-key :prefix) first-value))
(setq keymaps (if (eq first-key :map) first-value default-keymaps))
(setq states '())
out-forms))
(t (user-error "" key)))
t)
(cl-incf i))
`(progn ,@(apply #'nconc (delete nil (delete (list nil) forms))))))
;; (defmacro @map (&rest keys)
;; "A minimalistic and evil-centric way of binding keys. KEYS is
;;made up of either:
;;
;;1. Any of the following keywords:
;;
;;:when CONDITION
;;:unless CONDITION
;;:prefix PREFIX Key(s) to prefix keymappings with
;;:map KEYMAP Keymaps to bind keys to. Can be a list.
;;:global Tags these keymaps for the global keymap
;;:local Ditto, but for local keymap
;;
;;
;;2. A key (as a vector e.g. [escape], a string \"<escape>\", or
;;character ?\^?).
;;
;;3. A key definition: a symbol or a lambda function. "
;; (declare (indent defun))
;; (let* ((keymaps (-list map))
;; (states (-list in))
;; (forms '())
;; item def)
;; (while keys
;; (setq item (pop keys))
;; (cond ((keywordp item)
;; (let ((val (pop keys)))
;; (pcase item
;; (:after)
;; (:when)
;; (:unless)
;; (:keymap)
;; (:in)
;; (otherwise)
;; )
;; ))
;;
;; ((or (and (symbolp item)
;; (evil-state-p item))
;; (and (listp item)
;; (--all? (evil-state-p it) item)))
;; (setq states (-list item)))
;;
;; ;; item-definition pairs
;; ((consp item)
;; (let ((def (cdr item))
;; (item (car item)))
;; (message "k %s : d %s" item def)
;;
;; ;;(or (stringp item)
;; ;; (vectorp item)
;; ;; (characterp item))
;; ;;(unless items (signal 'bind-no-definition item))
;; ;;(setq def (pop items))
;; (when condition
;; ;; Process the item
;; (cond ((stringp item) (setq item (kbd item)))
;; ((characterp item) (setq item (string item))))
;; (when prefix
;; (setq item (if (vectorp item)
;; (vconcat prefix item)
;; (concat (kbd prefix) item))))
;; ;; Do the binding
;; `(,@(if (null states)
;; (push (mapcar
;; (lambda (keymap) `(define-key ,keymap ,item ,def)) keymaps)
;; forms)
;; (push (mapcar (lambda (state)
;; (mapcar (lambda (keymap)
;; `(define-key (evil-get-auxiliary-keymap ,keymap ',state t) ,item ,def))
;; keymaps))
;; states) forms))))))
;;
;; ;; fallback
;; (t (signal 'bind-invalid-key key)))
;; `(progn ,@forms))))
(defmacro @exmap (command func)
(evil-ex-define-cmd
,command
,(cond ((autoloadp (symbol-function `,func))
`(lambda () (interactive) (call-interactively ,func)))
((symbolp `,func) func)
(t (user-error "Command for %s is invalid" command)))))))
;; Hooks ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun narf|enable-comment-hard-wrap ()
(set (make-local-variable 'comment-auto-fill-only-comments) t)
(turn-on-auto-fill))
(defun narf|enable-hard-wrap ()
(turn-on-auto-fill))
(defun narf|enable-tab-width-2 ()
(setq tab-width 2 evil-shift-width 2))
(defun narf|enable-tab-width-4 ()
(setq tab-width 4 evil-shift-width 4))
(defun narf|disable-final-newline ()
(set (make-local-variable 'require-final-newline) nil))
(defun narf|enable-tabs ()
(setq indent-tabs-mode t))
(defun narf|disable-tabs ()
(setq indent-tabs-mode nil))
(defun narf|disable-delete-trailing-whitespace ()
(remove-hook 'before-save-hook 'delete-trailing-whitespace))
(defun narf|update-scratch-buffer-cwd () ; see core-editor.el
"Make sure scratch buffer is always 'in a project.'"
(let ((dir (narf-project-root)))
(with-current-buffer (get-buffer-create "*scratch*")
(cd dir))))
;;;; Global Defuns ;;;;;;;;;;;;;;;;;;;;;
(defun narf-minibuffer-quit ()
"Abort recursive edit. In Delete Selection mode, if the mark is
active, just deactivate it; then it takes a second \\[keyboard-quit]
to abort the minibuffer."
(interactive)
(let (message-log-max)
(if (and delete-selection-mode transient-mark-mode mark-active)
(setq deactivate-mark t)
(when (get-buffer "*Completions*")
(delete-windows-on "*Completions*"))
(abort-recursive-edit))))
;;;; Project defuns ;;;;;;;;;;;;;;;;;;;;
(defun narf-project-root (&optional strict-p)
"Get the path to the root of your project. Uses `narf-project-root-files' to
determine if a directory is a project."
(let ((home (file-truename "~")))
(catch 'found
(f-traverse-upwards
(lambda (path)
(let ((path (file-truename path)))
(if (file-equal-p home path)
(throw 'found (if strict-p nil default-directory))
(dolist (file narf-project-root-files)
(when (file-exists-p (expand-file-name file path))
(throw 'found path)))))) default-directory)
default-directory)))
(defun narf-project-has-files (files &optional root)
"Return non-nil if `file' exists in the project root."
(let ((root (or root (narf-project-root)))
(files (if (listp files) files (list files)))
found-p file)
(while (and files (not found-p))
(setq file (pop files))
(setq found-p (file-exists-p (narf-project-path-to file root))))
found-p))
(defun narf-project-path-to (file &optional root)
(let ((root (or root (narf-project-root))))
(expand-file-name file root)))
(defun narf-project-name (&optional root)
(file-name-nondirectory (directory-file-name (or root (narf-project-root)))))
(defun narf-project-p ()
(not (null (narf-project-root t))))
(provide 'core-defuns)
;;; core-defuns.el ends here