diff --git a/core/core-keybinds.el b/core/core-keybinds.el index 4409f562f..2c1f4b5bb 100644 --- a/core/core-keybinds.el +++ b/core/core-keybinds.el @@ -53,17 +53,23 @@ If any hook returns non-nil, all hooks after it are ignored.") (defalias 'define-key! #'general-def) (defalias 'unmap! #'general-unbind) -;; +;; leader/localleader keys (define-prefix-command 'doom-leader 'doom-leader-map) (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 '(normal visual motion replace) doom-leader-key 'doom-leader) -(general-create-definer define-leader-key! - :wk-full-keys nil - :keymaps 'doom-leader-map) +;; We avoid `general-create-definer' to ensure that :states, :wk-full-keys and +;; :keymaps cannot be overwritten. +(defmacro define-leader-key! (&rest args) + `(general-define-key + :states nil + :wk-full-keys nil + :keymaps 'doom-leader-map + ,@args)) -;; (general-create-definer define-localleader-key! :major-modes t :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)) (b (plist-get doom--map-state prop))) (if (and a b) - `(general--concat t ,a ,b) + `(general--concat nil ,a ,b) (or a b)))) (defun doom--map-nested (wrapper rest) (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 (append wrapper (list (doom--map-process rest))) (doom--map-process rest)) doom--map-forms))) -(defun doom--map-set (prop &optional value inhibit-commit) - (unless (or inhibit-commit - (equal (plist-get doom--map-state prop) value)) +(defun doom--map-set (prop &optional value) + (unless (equal (plist-get doom--map-state prop) value) (doom--map-commit)) (setq doom--map-state (plist-put doom--map-state prop value))) (defun doom--map-def (key def &optional states desc) - (when (or (memq 'global states) (null states)) - (setq states (delq 'global states)) - (push 'nil states)) - (if (and (listp def) - (memq (car-safe (doom-unquote def)) '(:def :ignore :keymap))) - (setq def `(quote ,(plist-put (general--normalize-extended-def (doom-unquote def)) - :which-key desc))) - (when desc - (setq def `(list ,@(cond ((and (equal key "") - (null def)) - `(nil :which-key ,desc)) - ((plist-put (general--normalize-extended-def def) - :which-key desc))))))) + (when (or (memq 'global states) + (null states)) + (setq states (cons 'nil (delq 'global states)))) + (when desc + (let (unquoted) + (cond ((and (listp def) + (keywordp (car-safe (setq unquoted (doom-unquote def))))) + (setq def (list 'quote (plist-put unquoted :which-key desc)))) + ((setq def (cons 'list + (if (and (equal key "") + (null def)) + `(nil :which-key ,desc) + (plist-put (general--normalize-extended-def def) + :which-key desc)))))))) (dolist (state states) - (push key (alist-get state doom--map-batch-forms)) - (push def (alist-get state doom--map-batch-forms)))) + (push (list key def) + (alist-get state doom--map-batch-forms)))) (defun doom--map-commit () (when doom--map-batch-forms (cl-loop with attrs = (doom--map-state) for (state . defs) in doom--map-batch-forms 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 finally do (push (macroexp-progn forms) doom--map-forms)) (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) :keymaps (append (plist-get doom--map-parent-state :keymaps) - (plist-get doom--map-state :keymaps) - nil)) + (plist-get doom--map-state :keymaps))) doom--map-state nil)) newplist) @@ -327,8 +333,7 @@ Example (:when IS-MAC :n \"M-s\" 'some-fn :i \"M-o\" (lambda (interactive) (message \"Hi\"))))" - `(let ((general-implicit-kbd t)) - ,(doom--map-process rest))) + (doom--map-process rest)) (provide 'core-keybinds) ;;; core-keybinds.el ends here diff --git a/core/test/test-core-keybinds.el b/core/test/test-core-keybinds.el index c66dc3a92..ea6bac845 100644 --- a/core/test/test-core-keybinds.el +++ b/core/test/test-core-keybinds.el @@ -1,10 +1,12 @@ ;; -*- no-byte-compile: t; -*- ;;; core/test/test-core-keybinds.el -(buttercup-define-matcher :to-bind (src result) - ;; TODO Add expect messages - (equal (caddr (macroexpand-1 (funcall src))) - (funcall result))) +(buttercup-define-matcher :to-expand-into (src result) + (let ((src (funcall src)) + (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 "map!" @@ -21,23 +23,24 @@ (describe "Single keybinds" (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" (dolist (state doom-map-states) (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" (expect `(map! :nvi "C-." #'a) - :to-bind + :to-expand-into '(progn (general-define-key :states 'insert "C-." #'a) (general-define-key :states 'visual "C-." #'a) (general-define-key :states 'normal "C-." #'a)))) (it "binds evil keybinds together with global keybinds" (expect '(map! :ng "C-." #'a) - :to-bind + :to-expand-into '(progn (general-define-key :states 'normal "C-." #'a) (general-define-key "C-." #'a))))) @@ -45,7 +48,7 @@ (describe "Multiple keybinds" (it "binds global keys and preserves order" (expect '(map! "C-." #'a "C-," #'b "C-/" #'c) - :to-bind + :to-expand-into '(general-define-key "C-." #'a "C-," #'b "C-/" #'c))) (it "binds multiple keybinds in an evil state and preserve order" @@ -53,7 +56,7 @@ (expect `(map! ,(car state) "a" #'a ,(car state) "b" #'b ,(car state) "c" #'c) - :to-bind + :to-expand-into `(general-define-key :states ',(cdr state) "a" #'a "b" #'b @@ -65,7 +68,7 @@ :n "e" #'e :v "c" #'c :i "d" #'d) - :to-bind + :to-expand-into `(progn (general-define-key :states 'insert "d" #'d) (general-define-key :states 'visual "c" #'c) (general-define-key :states 'normal "a" #'a "b" #'b "e" #'e)))) @@ -76,7 +79,7 @@ :n "b" #'b :i "d" #'d :n "e" #'e) - :to-bind + :to-expand-into `(progn (general-define-key :states 'insert "d" #'d) (general-define-key :states 'visual "c" #'c) (general-define-key :states 'normal "a" #'a "b" #'b "e" #'e)))) @@ -85,7 +88,7 @@ (expect `(map! :nvi "a" #'a :nvi "b" #'b :nvi "c" #'c) - :to-bind + :to-expand-into '(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 'normal "a" #'a "b" #'b "c" #'c))))) @@ -95,27 +98,25 @@ (expect '(map! "C-." #'a ("C-a" #'b) ("C-x" #'c)) - :to-bind - '(progn - (general-define-key "C-." #'a) - (general-define-key "C-a" #'b) - (general-define-key "C-x" #'c)))) + :to-expand-into + '(progn (general-define-key "C-." #'a) + (general-define-key "C-a" #'b) + (general-define-key "C-x" #'c)))) (it "binds nested evil keybinds" (expect '(map! :n "C-." #'a (:n "C-a" #'b) (:n "C-x" #'c)) - :to-bind - '(progn - (general-define-key :states 'normal "C-." #'a) - (general-define-key :states 'normal "C-a" #'b) - (general-define-key :states 'normal "C-x" #'c)))) + :to-expand-into + '(progn (general-define-key :states 'normal "C-." #'a) + (general-define-key :states 'normal "C-a" #'b) + (general-define-key :states 'normal "C-x" #'c)))) (it "binds global keybinds in between evil keybinds" (expect '(map! :n "a" #'a "b" #'b :n "c" #'c) - :to-bind + :to-expand-into '(progn (general-define-key "b" #'b) (general-define-key :states 'normal "a" #'a "c" #'c))))) @@ -125,15 +126,15 @@ (it "wraps `general-define-key' in a `after!' block" (dolist (form '((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)) - :to-bind + :to-expand-into '(progn (general-define-key "a" #'a) (after! helm (general-define-key "b" #'b "c" #'c)))) (expect '(map! (:after helm "b" #'b "c" #'c) "a" #'a) - :to-bind + :to-expand-into '(progn (after! helm (general-define-key "b" #'b "c" #'c)) @@ -143,7 +144,7 @@ (expect '(map! :after x "a" #'a (:after y "b" #'b (:after z "c" #'c))) - :to-bind + :to-expand-into '(after! x (progn (general-define-key "a" #'a) @@ -157,7 +158,7 @@ (expect '(map! :after x "a" #'a (:when t "b" #'b (:after z "c" #'c))) - :to-bind + :to-expand-into '(after! x (progn (general-define-key "a" #'a) @@ -169,7 +170,7 @@ (describe ":desc" (it "add a :which-key property to a keybind's DEF" (expect '(map! :desc "A" "a" #'a) - :to-bind + :to-expand-into `(general-define-key "a" (list :def #'a :which-key "A"))))) (describe ":if/:when/:unless" @@ -177,15 +178,15 @@ (dolist (prop '(:if :when :unless)) (let ((prop-fn (intern (doom-keyword-name prop)))) (expect `(map! ,prop t "a" #'a "b" #'b) - :to-bind + :to-expand-into `(,prop-fn t (general-define-key "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)))))) (it "nests conditional blocks" (expect '(map! (:when t "a" #'a (:when t "b" #'b))) - :to-bind + :to-expand-into '(when t (progn (general-define-key "a" #'a) (when t (general-define-key "b" #'b))))))) @@ -193,85 +194,85 @@ (describe ":leader" (it "uses leader definer" (expect '(map! :leader "a" #'a "b" #'b) - :to-bind + :to-expand-into '(define-leader-key! "a" #'a "b" #'b))) (it "it persists for nested keys" (expect '(map! :leader "a" #'a ("b" #'b)) - :to-bind + :to-expand-into '(progn (define-leader-key! "a" #'a) (define-leader-key! "b" #'b))))) (describe ":localleader" (it "uses localleader definer" (expect '(map! :localleader "a" #'a "b" #'b) - :to-bind + :to-expand-into '(define-localleader-key! "a" #'a "b" #'b))) (it "it persists for nested keys" (expect '(map! :localleader "a" #'a ("b" #'b)) - :to-bind + :to-expand-into '(progn (define-localleader-key! "a" #'a) (define-localleader-key! "b" #'b))))) (describe ":map/:keymap" (it "specifies a single keymap for keys" (expect '(map! :map emacs-lisp-mode-map "a" #'a) - :to-bind + :to-expand-into '(general-define-key :keymaps '(emacs-lisp-mode-map) "a" #'a))) (it "specifies multiple keymap for keys" (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)))) (describe ":mode" (it "appends -map to MODE" (expect '(map! :mode emacs-lisp-mode "a" #'a) - :to-bind + :to-expand-into '(general-define-key :keymaps '(emacs-lisp-mode-map) "a" #'a)))) (describe ":prefix" (it "specifies a prefix for all keys" (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))) (it "overwrites previous inline :prefix properties" (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) (general-define-key :prefix "b" "z" #'z)))) (it "accumulates keys when nested" (expect '(map! (:prefix "a" "x" #'x (:prefix "b" "x" #'x))) - :to-bind + :to-expand-into `(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))))) (describe ":alt-prefix" (it "specifies a prefix for all keys" (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))) (it "overwrites previous inline :alt-prefix properties" (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) (general-define-key :non-normal-prefix "b" "z" #'z)))) (it "accumulates keys when nested" (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) - (general-define-key :non-normal-prefix (general--concat t "a" "b") + (general-define-key :non-normal-prefix (general--concat nil "a" "b") "x" #'x))))) (describe ":textobj" (it "defines keys in evil-{inner,outer}-text-objects-map" (expect '(map! :textobj "a" #'inner #'outer) - :to-bind + :to-expand-into '(map! (:map evil-inner-text-objects-map "a" #'inner) (:map evil-outer-text-objects-map "a" #'outer))))))))