Write core initfiles + defuns

This commit is contained in:
Henrik Lissner 2015-06-15 09:05:52 +02:00
parent c0661f5293
commit b998f4ab08
52 changed files with 2444 additions and 706 deletions

View file

@ -1,41 +1,35 @@
(! (defalias '@--concat-forms 'use-package-concat)
(defalias '@--normalize-symbols 'use-package-normalize-symlist)
(defalias '@--normalize-paths 'use-package-normalize-paths)
(eval-when-compile (require 'cl-lib))
;; 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))))
;; 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 λ (&rest body)
"A shortcut for: `(lambda () (interactive) ,@body)"
`(lambda () (interactive) ,@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 @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)))
(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'.
(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.
@ -45,242 +39,160 @@ 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-hook 'enable-something)
(@add-hook some-mode '(enable-something and-another))
(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 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 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 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)))
(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 () ,@func-or-forms)))))
(mapc
(lambda (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)))))))
(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."
(declare (indent 1))
(let* ((minor-p (assoc mode minor-mode-alist)))
`(progn
(,@(when match
`(add-to-list ',(if minor-p 'narf-auto-minor-mode-alist 'auto-mode-alist)
(cons ,match ',mode))))
(,@(when files
(unless (or (listp files) (stringp files))
(user-error "associate! :files expects a string or list of strings"))
(let ((hook-name (intern (format "narf--init-mode-%s" mode))))
`(progn
(defun ,hook-name ()
(when (and (not ,mode)
(narf/project-has-files ,@(-list files)))
(,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))))))))))
(@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)))
(after! evil
;; Register keywords for proper indentation (see `bind!')
(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)
(macroexpand `(@map (:map my-map "C-k" 'hello :n "C-p" 'goodbye)))
(defmacro bind! (&rest rest)
(let ((i 0)
key def
first-set
prefix
internal
(default-keymaps '(narf-mode-map))
(keymaps (if (boundp 'keymaps) keymaps))
(states (if (boundp 'states) states '()))
(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))
(add-to-list
'forms
(cond ((eq key '-) nil) ; skip this
(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
`((bind! ,@key)))
((listp key) ; it's a sub exp
(macroexpand `(@map ,@key)))
((keywordp key)
(pcase key
;; TODO: Data checks
(: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 `((bind! ,(kbd (pop rest)) nil)))
(:after (prog1 `((after! ,(pop rest) (bind! ,@rest))) (setq rest '())))
(:when (prog1 `((if ,(pop rest) (bind! ,@rest))) (setq rest '())))
(:unless (prog1 `((if (not ,(pop rest)) (bind! ,@rest))) (setq rest '())))
(otherwise ; might be a state prefix
(mapc (lambda (letter)
(if (assoc letter state-map)
(add-to-list 'states (cdr (assoc letter state-map)))
(user-error "Invalid mode prefix %s in key %s" letter key)))
(s-split "" (substring (symbol-name key) 1) t))
(unless states
(user-error "Unrecognized keyword %s" key)) nil)))
((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
(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 ((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)
(add-to-list 'out-forms `(evil-define-key nil ,keymap ,key ,def) t)
(dolist (state states)
(add-to-list 'out-forms `(evil-define-key ',state ,keymap ,key ,def) t))))
;; (add-to-list 'out-forms `(define-key (evil-get-auxiliary-keymap ,keymap ',state) ,key ,def) t))))
;; It's a key-def pair
((or (stringp key)
(characterp key)
(vectorp key))
(setq prefix (if (eq first-key :prefix) first-value))
(setq keymaps (if (eq first-key :map) first-value default-keymaps))
(setq states '())
out-forms))
(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)))))))
(t (user-error "Invalid key %s" key)))
t)
(cl-incf i))
`(progn ,@(apply #'nconc (delete nil (delete (list nil) forms)))))))
;; Hooks ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -311,7 +223,7 @@ Examples:
(defun narf|update-scratch-buffer-cwd () ; see core-editor.el
"Make sure scratch buffer is always 'in a project.'"
(let ((dir (narf-project-root)))
(let ((dir (narf/project-root)))
(with-current-buffer (get-buffer-create "*scratch*")
(cd dir))))
@ -329,43 +241,26 @@ to abort the minibuffer."
(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))))
(after! evil
(evil-define-command narf:exit-mode-maybe ()
"Exits insert/replace mode using jk without the momentary pause caused by
key-chord-define."
:repeat change
(interactive)
(let ((modified (buffer-modified-p)))
(call-interactively 'self-insert-command)
(let ((evt (read-event nil nil 0.4)))
(cond
((null evt) (message ""))
((and (integerp evt) (or (char-equal evt ?k)
(char-equal evt ?K)))
(if (evil-replace-state-p)
(evil-replace-backspace)
(delete-char -1))
(set-buffer-modified-p modified)
(push 'escape unread-command-events))
(t
(setq unread-command-events (append unread-command-events (list evt)))))))))
(provide 'core-defuns)
;;; core-defuns.el ends here