Refactor map! & tests

This resolves issues with :leader/:localleader keys not working when
evil states are specified. Evil states are now ignored. Also, some of
map!'s internals have been optimized to yield a ~10% improvement in
macro expansion time.
This commit is contained in:
Henrik Lissner 2018-12-23 23:12:10 -05:00
parent 29f119ad4d
commit 5ad0b749a1
No known key found for this signature in database
GPG key ID: 5F6C0EA160557395
2 changed files with 86 additions and 80 deletions

View file

@ -53,17 +53,23 @@ If any hook returns non-nil, all hooks after it are ignored.")
(defalias 'define-key! #'general-def) (defalias 'define-key! #'general-def)
(defalias 'unmap! #'general-unbind) (defalias 'unmap! #'general-unbind)
;; <leader> ;; leader/localleader keys
(define-prefix-command 'doom-leader 'doom-leader-map) (define-prefix-command 'doom-leader 'doom-leader-map)
(define-key doom-leader-map [override-state] 'all) (define-key doom-leader-map [override-state] 'all)
(general-define-key :states '(normal visual motion replace) doom-leader-key 'doom-leader)
(global-set-key (kbd doom-leader-alt-key) 'doom-leader)
(general-define-key :states '(emacs insert) doom-leader-alt-key 'doom-leader) (general-define-key :states '(emacs insert) doom-leader-alt-key 'doom-leader)
(general-define-key :states '(normal visual motion replace) doom-leader-key 'doom-leader)
(general-create-definer define-leader-key! ;; We avoid `general-create-definer' to ensure that :states, :wk-full-keys and
:wk-full-keys nil ;; :keymaps cannot be overwritten.
:keymaps 'doom-leader-map) (defmacro define-leader-key! (&rest args)
`(general-define-key
:states nil
:wk-full-keys nil
:keymaps 'doom-leader-map
,@args))
;; <localleader>
(general-create-definer define-localleader-key! (general-create-definer define-localleader-key!
:major-modes t :major-modes t
:keymaps 'local :keymaps 'local
@ -215,47 +221,48 @@ For example, :nvi will map to (list 'normal 'visual 'insert). See
(let ((a (plist-get doom--map-parent-state prop)) (let ((a (plist-get doom--map-parent-state prop))
(b (plist-get doom--map-state prop))) (b (plist-get doom--map-state prop)))
(if (and a b) (if (and a b)
`(general--concat t ,a ,b) `(general--concat nil ,a ,b)
(or a b)))) (or a b))))
(defun doom--map-nested (wrapper rest) (defun doom--map-nested (wrapper rest)
(doom--map-commit) (doom--map-commit)
(let ((doom--map-parent-state (copy-seq (append doom--map-state doom--map-parent-state nil)))) (let ((doom--map-parent-state (append doom--map-state doom--map-parent-state nil)))
(push (if wrapper (push (if wrapper
(append wrapper (list (doom--map-process rest))) (append wrapper (list (doom--map-process rest)))
(doom--map-process rest)) (doom--map-process rest))
doom--map-forms))) doom--map-forms)))
(defun doom--map-set (prop &optional value inhibit-commit) (defun doom--map-set (prop &optional value)
(unless (or inhibit-commit (unless (equal (plist-get doom--map-state prop) value)
(equal (plist-get doom--map-state prop) value))
(doom--map-commit)) (doom--map-commit))
(setq doom--map-state (plist-put doom--map-state prop value))) (setq doom--map-state (plist-put doom--map-state prop value)))
(defun doom--map-def (key def &optional states desc) (defun doom--map-def (key def &optional states desc)
(when (or (memq 'global states) (null states)) (when (or (memq 'global states)
(setq states (delq 'global states)) (null states))
(push 'nil states)) (setq states (cons 'nil (delq 'global states))))
(if (and (listp def) (when desc
(memq (car-safe (doom-unquote def)) '(:def :ignore :keymap))) (let (unquoted)
(setq def `(quote ,(plist-put (general--normalize-extended-def (doom-unquote def)) (cond ((and (listp def)
:which-key desc))) (keywordp (car-safe (setq unquoted (doom-unquote def)))))
(when desc (setq def (list 'quote (plist-put unquoted :which-key desc))))
(setq def `(list ,@(cond ((and (equal key "") ((setq def (cons 'list
(null def)) (if (and (equal key "")
`(nil :which-key ,desc)) (null def))
((plist-put (general--normalize-extended-def def) `(nil :which-key ,desc)
:which-key desc))))))) (plist-put (general--normalize-extended-def def)
:which-key desc))))))))
(dolist (state states) (dolist (state states)
(push key (alist-get state doom--map-batch-forms)) (push (list key def)
(push def (alist-get state doom--map-batch-forms)))) (alist-get state doom--map-batch-forms))))
(defun doom--map-commit () (defun doom--map-commit ()
(when doom--map-batch-forms (when doom--map-batch-forms
(cl-loop with attrs = (doom--map-state) (cl-loop with attrs = (doom--map-state)
for (state . defs) in doom--map-batch-forms for (state . defs) in doom--map-batch-forms
if (or doom--map-evil-p (not state)) if (or doom--map-evil-p (not state))
collect `(,doom--map-fn ,@(if state `(:states ',state)) ,@attrs ,@(nreverse defs)) collect `(,doom--map-fn ,@(if state `(:states ',state)) ,@attrs
,@(mapcan #'identity (nreverse defs)))
into forms into forms
finally do (push (macroexp-progn forms) doom--map-forms)) finally do (push (macroexp-progn forms) doom--map-forms))
(setq doom--map-batch-forms nil))) (setq doom--map-batch-forms nil)))
@ -266,8 +273,7 @@ For example, :nvi will map to (list 'normal 'visual 'insert). See
:non-normal-prefix (doom--map-append-keys :non-normal-prefix) :non-normal-prefix (doom--map-append-keys :non-normal-prefix)
:keymaps :keymaps
(append (plist-get doom--map-parent-state :keymaps) (append (plist-get doom--map-parent-state :keymaps)
(plist-get doom--map-state :keymaps) (plist-get doom--map-state :keymaps)))
nil))
doom--map-state doom--map-state
nil)) nil))
newplist) newplist)
@ -327,8 +333,7 @@ Example
(:when IS-MAC (:when IS-MAC
:n \"M-s\" 'some-fn :n \"M-s\" 'some-fn
:i \"M-o\" (lambda (interactive) (message \"Hi\"))))" :i \"M-o\" (lambda (interactive) (message \"Hi\"))))"
`(let ((general-implicit-kbd t)) (doom--map-process rest))
,(doom--map-process rest)))
(provide 'core-keybinds) (provide 'core-keybinds)
;;; core-keybinds.el ends here ;;; core-keybinds.el ends here

View file

@ -1,10 +1,12 @@
;; -*- no-byte-compile: t; -*- ;; -*- no-byte-compile: t; -*-
;;; core/test/test-core-keybinds.el ;;; core/test/test-core-keybinds.el
(buttercup-define-matcher :to-bind (src result) (buttercup-define-matcher :to-expand-into (src result)
;; TODO Add expect messages (let ((src (funcall src))
(equal (caddr (macroexpand-1 (funcall src))) (result (funcall result)))
(funcall result))) (or (equal (macroexpand-1 src) result)
(error "'%s' expanded into '%s' instead of '%s'"
src (macroexpand-1 src) result))))
(describe "core/keybinds" (describe "core/keybinds"
(describe "map!" (describe "map!"
@ -21,23 +23,24 @@
(describe "Single keybinds" (describe "Single keybinds"
(it "binds a global key" (it "binds a global key"
(expect '(map! "C-." #'a) :to-bind '(general-define-key "C-." #'a))) (expect '(map! "C-." #'a) :to-expand-into '(general-define-key "C-." #'a)))
(it "binds a key in one evil state" (it "binds a key in one evil state"
(dolist (state doom-map-states) (dolist (state doom-map-states)
(expect `(map! ,(car state) "C-." #'a) (expect `(map! ,(car state) "C-." #'a)
:to-bind `(general-define-key :states ',(cdr state) "C-." #'a)))) :to-expand-into
`(general-define-key :states ',(cdr state) "C-." #'a))))
(it "binds a key in multiple evil states" (it "binds a key in multiple evil states"
(expect `(map! :nvi "C-." #'a) (expect `(map! :nvi "C-." #'a)
:to-bind :to-expand-into
'(progn (general-define-key :states 'insert "C-." #'a) '(progn (general-define-key :states 'insert "C-." #'a)
(general-define-key :states 'visual "C-." #'a) (general-define-key :states 'visual "C-." #'a)
(general-define-key :states 'normal "C-." #'a)))) (general-define-key :states 'normal "C-." #'a))))
(it "binds evil keybinds together with global keybinds" (it "binds evil keybinds together with global keybinds"
(expect '(map! :ng "C-." #'a) (expect '(map! :ng "C-." #'a)
:to-bind :to-expand-into
'(progn '(progn
(general-define-key :states 'normal "C-." #'a) (general-define-key :states 'normal "C-." #'a)
(general-define-key "C-." #'a))))) (general-define-key "C-." #'a)))))
@ -45,7 +48,7 @@
(describe "Multiple keybinds" (describe "Multiple keybinds"
(it "binds global keys and preserves order" (it "binds global keys and preserves order"
(expect '(map! "C-." #'a "C-," #'b "C-/" #'c) (expect '(map! "C-." #'a "C-," #'b "C-/" #'c)
:to-bind :to-expand-into
'(general-define-key "C-." #'a "C-," #'b "C-/" #'c))) '(general-define-key "C-." #'a "C-," #'b "C-/" #'c)))
(it "binds multiple keybinds in an evil state and preserve order" (it "binds multiple keybinds in an evil state and preserve order"
@ -53,7 +56,7 @@
(expect `(map! ,(car state) "a" #'a (expect `(map! ,(car state) "a" #'a
,(car state) "b" #'b ,(car state) "b" #'b
,(car state) "c" #'c) ,(car state) "c" #'c)
:to-bind :to-expand-into
`(general-define-key :states ',(cdr state) `(general-define-key :states ',(cdr state)
"a" #'a "a" #'a
"b" #'b "b" #'b
@ -65,7 +68,7 @@
:n "e" #'e :n "e" #'e
:v "c" #'c :v "c" #'c
:i "d" #'d) :i "d" #'d)
:to-bind :to-expand-into
`(progn (general-define-key :states 'insert "d" #'d) `(progn (general-define-key :states 'insert "d" #'d)
(general-define-key :states 'visual "c" #'c) (general-define-key :states 'visual "c" #'c)
(general-define-key :states 'normal "a" #'a "b" #'b "e" #'e)))) (general-define-key :states 'normal "a" #'a "b" #'b "e" #'e))))
@ -76,7 +79,7 @@
:n "b" #'b :n "b" #'b
:i "d" #'d :i "d" #'d
:n "e" #'e) :n "e" #'e)
:to-bind :to-expand-into
`(progn (general-define-key :states 'insert "d" #'d) `(progn (general-define-key :states 'insert "d" #'d)
(general-define-key :states 'visual "c" #'c) (general-define-key :states 'visual "c" #'c)
(general-define-key :states 'normal "a" #'a "b" #'b "e" #'e)))) (general-define-key :states 'normal "a" #'a "b" #'b "e" #'e))))
@ -85,7 +88,7 @@
(expect `(map! :nvi "a" #'a (expect `(map! :nvi "a" #'a
:nvi "b" #'b :nvi "b" #'b
:nvi "c" #'c) :nvi "c" #'c)
:to-bind :to-expand-into
'(progn (general-define-key :states 'insert "a" #'a "b" #'b "c" #'c) '(progn (general-define-key :states 'insert "a" #'a "b" #'b "c" #'c)
(general-define-key :states 'visual "a" #'a "b" #'b "c" #'c) (general-define-key :states 'visual "a" #'a "b" #'b "c" #'c)
(general-define-key :states 'normal "a" #'a "b" #'b "c" #'c))))) (general-define-key :states 'normal "a" #'a "b" #'b "c" #'c)))))
@ -95,27 +98,25 @@
(expect '(map! "C-." #'a (expect '(map! "C-." #'a
("C-a" #'b) ("C-a" #'b)
("C-x" #'c)) ("C-x" #'c))
:to-bind :to-expand-into
'(progn '(progn (general-define-key "C-." #'a)
(general-define-key "C-." #'a) (general-define-key "C-a" #'b)
(general-define-key "C-a" #'b) (general-define-key "C-x" #'c))))
(general-define-key "C-x" #'c))))
(it "binds nested evil keybinds" (it "binds nested evil keybinds"
(expect '(map! :n "C-." #'a (expect '(map! :n "C-." #'a
(:n "C-a" #'b) (:n "C-a" #'b)
(:n "C-x" #'c)) (:n "C-x" #'c))
:to-bind :to-expand-into
'(progn '(progn (general-define-key :states 'normal "C-." #'a)
(general-define-key :states 'normal "C-." #'a) (general-define-key :states 'normal "C-a" #'b)
(general-define-key :states 'normal "C-a" #'b) (general-define-key :states 'normal "C-x" #'c))))
(general-define-key :states 'normal "C-x" #'c))))
(it "binds global keybinds in between evil keybinds" (it "binds global keybinds in between evil keybinds"
(expect '(map! :n "a" #'a (expect '(map! :n "a" #'a
"b" #'b "b" #'b
:n "c" #'c) :n "c" #'c)
:to-bind :to-expand-into
'(progn (general-define-key "b" #'b) '(progn (general-define-key "b" #'b)
(general-define-key :states 'normal "a" #'a "c" #'c))))) (general-define-key :states 'normal "a" #'a "c" #'c)))))
@ -125,15 +126,15 @@
(it "wraps `general-define-key' in a `after!' block" (it "wraps `general-define-key' in a `after!' block"
(dolist (form '((map! :after helm "a" #'a "b" #'b) (dolist (form '((map! :after helm "a" #'a "b" #'b)
(map! (:after helm "a" #'a "b" #'b)))) (map! (:after helm "a" #'a "b" #'b))))
(expect form :to-bind '(after! helm (general-define-key "a" #'a "b" #'b)))) (expect form :to-expand-into '(after! helm (general-define-key "a" #'a "b" #'b))))
(expect '(map! "a" #'a (:after helm "b" #'b "c" #'c)) (expect '(map! "a" #'a (:after helm "b" #'b "c" #'c))
:to-bind :to-expand-into
'(progn '(progn
(general-define-key "a" #'a) (general-define-key "a" #'a)
(after! helm (after! helm
(general-define-key "b" #'b "c" #'c)))) (general-define-key "b" #'b "c" #'c))))
(expect '(map! (:after helm "b" #'b "c" #'c) "a" #'a) (expect '(map! (:after helm "b" #'b "c" #'c) "a" #'a)
:to-bind :to-expand-into
'(progn '(progn
(after! helm (after! helm
(general-define-key "b" #'b "c" #'c)) (general-define-key "b" #'b "c" #'c))
@ -143,7 +144,7 @@
(expect '(map! :after x "a" #'a (expect '(map! :after x "a" #'a
(:after y "b" #'b (:after y "b" #'b
(:after z "c" #'c))) (:after z "c" #'c)))
:to-bind :to-expand-into
'(after! x '(after! x
(progn (progn
(general-define-key "a" #'a) (general-define-key "a" #'a)
@ -157,7 +158,7 @@
(expect '(map! :after x "a" #'a (expect '(map! :after x "a" #'a
(:when t "b" #'b (:when t "b" #'b
(:after z "c" #'c))) (:after z "c" #'c)))
:to-bind :to-expand-into
'(after! x '(after! x
(progn (progn
(general-define-key "a" #'a) (general-define-key "a" #'a)
@ -169,7 +170,7 @@
(describe ":desc" (describe ":desc"
(it "add a :which-key property to a keybind's DEF" (it "add a :which-key property to a keybind's DEF"
(expect '(map! :desc "A" "a" #'a) (expect '(map! :desc "A" "a" #'a)
:to-bind :to-expand-into
`(general-define-key "a" (list :def #'a :which-key "A"))))) `(general-define-key "a" (list :def #'a :which-key "A")))))
(describe ":if/:when/:unless" (describe ":if/:when/:unless"
@ -177,15 +178,15 @@
(dolist (prop '(:if :when :unless)) (dolist (prop '(:if :when :unless))
(let ((prop-fn (intern (doom-keyword-name prop)))) (let ((prop-fn (intern (doom-keyword-name prop))))
(expect `(map! ,prop t "a" #'a "b" #'b) (expect `(map! ,prop t "a" #'a "b" #'b)
:to-bind :to-expand-into
`(,prop-fn t (general-define-key "a" #'a "b" #'b))) `(,prop-fn t (general-define-key "a" #'a "b" #'b)))
(expect `(map! (,prop t "a" #'a "b" #'b)) (expect `(map! (,prop t "a" #'a "b" #'b))
:to-bind :to-expand-into
`(,prop-fn t (general-define-key "a" #'a "b" #'b)))))) `(,prop-fn t (general-define-key "a" #'a "b" #'b))))))
(it "nests conditional blocks" (it "nests conditional blocks"
(expect '(map! (:when t "a" #'a (:when t "b" #'b))) (expect '(map! (:when t "a" #'a (:when t "b" #'b)))
:to-bind :to-expand-into
'(when t '(when t
(progn (general-define-key "a" #'a) (progn (general-define-key "a" #'a)
(when t (general-define-key "b" #'b))))))) (when t (general-define-key "b" #'b)))))))
@ -193,85 +194,85 @@
(describe ":leader" (describe ":leader"
(it "uses leader definer" (it "uses leader definer"
(expect '(map! :leader "a" #'a "b" #'b) (expect '(map! :leader "a" #'a "b" #'b)
:to-bind :to-expand-into
'(define-leader-key! "a" #'a "b" #'b))) '(define-leader-key! "a" #'a "b" #'b)))
(it "it persists for nested keys" (it "it persists for nested keys"
(expect '(map! :leader "a" #'a ("b" #'b)) (expect '(map! :leader "a" #'a ("b" #'b))
:to-bind :to-expand-into
'(progn (define-leader-key! "a" #'a) '(progn (define-leader-key! "a" #'a)
(define-leader-key! "b" #'b))))) (define-leader-key! "b" #'b)))))
(describe ":localleader" (describe ":localleader"
(it "uses localleader definer" (it "uses localleader definer"
(expect '(map! :localleader "a" #'a "b" #'b) (expect '(map! :localleader "a" #'a "b" #'b)
:to-bind :to-expand-into
'(define-localleader-key! "a" #'a "b" #'b))) '(define-localleader-key! "a" #'a "b" #'b)))
(it "it persists for nested keys" (it "it persists for nested keys"
(expect '(map! :localleader "a" #'a ("b" #'b)) (expect '(map! :localleader "a" #'a ("b" #'b))
:to-bind :to-expand-into
'(progn (define-localleader-key! "a" #'a) '(progn (define-localleader-key! "a" #'a)
(define-localleader-key! "b" #'b))))) (define-localleader-key! "b" #'b)))))
(describe ":map/:keymap" (describe ":map/:keymap"
(it "specifies a single keymap for keys" (it "specifies a single keymap for keys"
(expect '(map! :map emacs-lisp-mode-map "a" #'a) (expect '(map! :map emacs-lisp-mode-map "a" #'a)
:to-bind :to-expand-into
'(general-define-key :keymaps '(emacs-lisp-mode-map) "a" #'a))) '(general-define-key :keymaps '(emacs-lisp-mode-map) "a" #'a)))
(it "specifies multiple keymap for keys" (it "specifies multiple keymap for keys"
(expect '(map! :map (lisp-mode-map emacs-lisp-mode-map) "a" #'a) (expect '(map! :map (lisp-mode-map emacs-lisp-mode-map) "a" #'a)
:to-bind :to-expand-into
'(general-define-key :keymaps '(lisp-mode-map emacs-lisp-mode-map) "a" #'a)))) '(general-define-key :keymaps '(lisp-mode-map emacs-lisp-mode-map) "a" #'a))))
(describe ":mode" (describe ":mode"
(it "appends -map to MODE" (it "appends -map to MODE"
(expect '(map! :mode emacs-lisp-mode "a" #'a) (expect '(map! :mode emacs-lisp-mode "a" #'a)
:to-bind :to-expand-into
'(general-define-key :keymaps '(emacs-lisp-mode-map) "a" #'a)))) '(general-define-key :keymaps '(emacs-lisp-mode-map) "a" #'a))))
(describe ":prefix" (describe ":prefix"
(it "specifies a prefix for all keys" (it "specifies a prefix for all keys"
(expect '(map! :prefix "a" "x" #'x "y" #'y "z" #'z) (expect '(map! :prefix "a" "x" #'x "y" #'y "z" #'z)
:to-bind :to-expand-into
'(general-define-key :prefix "a" "x" #'x "y" #'y "z" #'z))) '(general-define-key :prefix "a" "x" #'x "y" #'y "z" #'z)))
(it "overwrites previous inline :prefix properties" (it "overwrites previous inline :prefix properties"
(expect '(map! :prefix "a" "x" #'x "y" #'y :prefix "b" "z" #'z) (expect '(map! :prefix "a" "x" #'x "y" #'y :prefix "b" "z" #'z)
:to-bind :to-expand-into
'(progn (general-define-key :prefix "a" "x" #'x "y" #'y) '(progn (general-define-key :prefix "a" "x" #'x "y" #'y)
(general-define-key :prefix "b" "z" #'z)))) (general-define-key :prefix "b" "z" #'z))))
(it "accumulates keys when nested" (it "accumulates keys when nested"
(expect '(map! (:prefix "a" "x" #'x (:prefix "b" "x" #'x))) (expect '(map! (:prefix "a" "x" #'x (:prefix "b" "x" #'x)))
:to-bind :to-expand-into
`(progn (general-define-key :prefix "a" "x" #'x) `(progn (general-define-key :prefix "a" "x" #'x)
(general-define-key :prefix (general--concat t "a" "b") (general-define-key :prefix (general--concat nil "a" "b")
"x" #'x))))) "x" #'x)))))
(describe ":alt-prefix" (describe ":alt-prefix"
(it "specifies a prefix for all keys" (it "specifies a prefix for all keys"
(expect '(map! :alt-prefix "a" "x" #'x "y" #'y "z" #'z) (expect '(map! :alt-prefix "a" "x" #'x "y" #'y "z" #'z)
:to-bind :to-expand-into
'(general-define-key :non-normal-prefix "a" "x" #'x "y" #'y "z" #'z))) '(general-define-key :non-normal-prefix "a" "x" #'x "y" #'y "z" #'z)))
(it "overwrites previous inline :alt-prefix properties" (it "overwrites previous inline :alt-prefix properties"
(expect '(map! :alt-prefix "a" "x" #'x "y" #'y :alt-prefix "b" "z" #'z) (expect '(map! :alt-prefix "a" "x" #'x "y" #'y :alt-prefix "b" "z" #'z)
:to-bind :to-expand-into
'(progn (general-define-key :non-normal-prefix "a" "x" #'x "y" #'y) '(progn (general-define-key :non-normal-prefix "a" "x" #'x "y" #'y)
(general-define-key :non-normal-prefix "b" "z" #'z)))) (general-define-key :non-normal-prefix "b" "z" #'z))))
(it "accumulates keys when nested" (it "accumulates keys when nested"
(expect '(map! (:alt-prefix "a" "x" #'x (:alt-prefix "b" "x" #'x))) (expect '(map! (:alt-prefix "a" "x" #'x (:alt-prefix "b" "x" #'x)))
:to-bind :to-expand-into
`(progn (general-define-key :non-normal-prefix "a" "x" #'x) `(progn (general-define-key :non-normal-prefix "a" "x" #'x)
(general-define-key :non-normal-prefix (general--concat t "a" "b") (general-define-key :non-normal-prefix (general--concat nil "a" "b")
"x" #'x))))) "x" #'x)))))
(describe ":textobj" (describe ":textobj"
(it "defines keys in evil-{inner,outer}-text-objects-map" (it "defines keys in evil-{inner,outer}-text-objects-map"
(expect '(map! :textobj "a" #'inner #'outer) (expect '(map! :textobj "a" #'inner #'outer)
:to-bind :to-expand-into
'(map! (:map evil-inner-text-objects-map "a" #'inner) '(map! (:map evil-inner-text-objects-map "a" #'inner)
(:map evil-outer-text-objects-map "a" #'outer)))))))) (:map evil-outer-text-objects-map "a" #'outer))))))))