Introduce general.el & rewrite map!
+ Now uses an overriding keymap for leader keys, so that it is always available, even outside of normal/visual states. In insert/emacs states, or in sessions where evil is absent, an alternative prefix is used for leader/localleader keys. See these variables: + doom-leader-prefix + doom-leader-alt-prefix + doom-localleader-prefix + doom-localleader-alt-prefix + Keybinds now support alternative prefixes through the new :alt-prefix property. This is useful for non-evil users and non-normal evil states. By default, this is M-SPC (leader) and M-SPC m (localleader). + Removed +evil-commands flag from config/default (moved to feature/evil/+commands.el). + config/default/+bindings.el has been split into config/default/+{evil,emacs}-bindings.el, which one is loaded depends on whether evil is present or not. The latter is blank, but will soon be populated with a keybinding scheme for non-evil users (perhaps inspired by #641). + The define-key! macro has been replaced; it is now an alias for general-def. + Added unmap! as an alias for general-unbind. + The following modifier key conventions are now enforced for consistency, across all OSes: alt/option = meta windows/command = super It used to be alt/option = alt windows/command = meta Many of the default keybinds have been updated to reflect this switch, but it is likely to affect personal meta/super keybinds! The map! macro has also been rewritten to use general-define-key. Here is what has been changed: + map! no longer works with characters, e.g. (map! ?x #'do-something) is no longer supported. Keys must be kbd-able strings like "C-c x" or vectors like [?C-c ?x]. + The :map and :map* properties are now the same thing. If specified keymaps aren't defined when binding keys, it is automatically deferred. + The way you bind local keybinds has changed: ;; Don't do this (map! :l "a" #'func-a :l "b" #'func-b) ;; Do this (map! :map 'local "a" #'func-a "b" #'func-b) + map! now supports the following new blocks: + (:if COND THEN-FORM ELSE-FORM...) + (:alt-prefix PREFIX KEYS...) -- this prefix will be used for non-normal evil states. Equivalent to :non-normal-prefix in general. + The way you declare a which-key label for a prefix key has changed: ;; before (map! :desc "label" :prefix "a" ...) ;; now (map! :prefix ("a" . "label") ...) + It used to be that map! supported binding a key to a key sequence, like so: (map! "a" [?x]) ; pressing a is like pressing x This functionality was removed *temporarily* while I figure out the implementation. Addresses: #448, #814, #860 Mentioned in: #940
This commit is contained in:
parent
ce38a80cf8
commit
4daa9271a0
26 changed files with 1616 additions and 1501 deletions
|
@ -289,11 +289,11 @@ savehist file."
|
|||
|
||||
|
||||
;; `helpful' --- a better *help* buffer
|
||||
(define-key! 'global
|
||||
[remap describe-function] #'helpful-callable
|
||||
[remap describe-command] #'helpful-command
|
||||
[remap describe-variable] #'helpful-variable
|
||||
[remap describe-key] #'helpful-key)
|
||||
(let ((map (current-global-map)))
|
||||
(define-key map [remap describe-function] #'helpful-callable)
|
||||
(define-key map [remap describe-command] #'helpful-command)
|
||||
(define-key map [remap describe-variable] #'helpful-variable)
|
||||
(define-key map [remap describe-key] #'helpful-key))
|
||||
|
||||
|
||||
(def-package! ws-butler
|
||||
|
|
|
@ -5,21 +5,21 @@
|
|||
;; never loaded, then evil bindings set with `map!' will be ignored.
|
||||
|
||||
(defvar doom-leader-key "SPC"
|
||||
"The leader prefix key, for global commands.")
|
||||
"The leader prefix key for Evil users.")
|
||||
|
||||
(defvar doom-leader-alt-key "M-SPC"
|
||||
"An alternative leader prefix key, used for Insert and Emacs states, and for
|
||||
non-evil users.")
|
||||
|
||||
(defvar doom-localleader-key "SPC m"
|
||||
"The localleader prefix key, for major-mode specific commands.")
|
||||
|
||||
(defvar doom-evil-state-alist
|
||||
'((?n . normal)
|
||||
(?v . visual)
|
||||
(?i . insert)
|
||||
(?e . emacs)
|
||||
(?o . operator)
|
||||
(?m . motion)
|
||||
(?r . replace)
|
||||
(?g . global))
|
||||
"A list of cons cells that map a letter to a evil state symbol.")
|
||||
(defvar doom-localleader-alt-key "M-SPC m"
|
||||
"The localleader prefix key, for major-mode specific commands.")
|
||||
|
||||
(defvar doom-leader-map (make-sparse-keymap)
|
||||
"An overriding keymap for <leader> keys.")
|
||||
|
||||
|
||||
;;
|
||||
(defvar doom-escape-hook nil
|
||||
|
@ -45,6 +45,50 @@ If any hook returns non-nil, all hooks after it are ignored.")
|
|||
|
||||
|
||||
;;
|
||||
;; General
|
||||
|
||||
(require 'general)
|
||||
|
||||
;; Convenience aliases
|
||||
(defalias 'define-key! #'general-def)
|
||||
(defalias 'unmap! #'general-unbind)
|
||||
|
||||
;; <leader>
|
||||
(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)
|
||||
(general-define-key :states '(emacs insert) doom-leader-alt-key 'doom-leader)
|
||||
|
||||
(defun general-leader-define-key (_state _keymap key def orig-def _kargs)
|
||||
(let (general-implicit-kbd)
|
||||
(general-define-key
|
||||
:keymaps 'doom-leader-map
|
||||
:wk-full-keys nil
|
||||
key orig-def)))
|
||||
|
||||
;; <localleader>
|
||||
(defun general-localleader-define-key (state keymap key _def orig-def kargs)
|
||||
(unless keymap
|
||||
(signal 'wrong-type-argument (list 'keymapp keymap)))
|
||||
(let (general-implicit-kbd)
|
||||
(apply #'general-define-key
|
||||
:major-modes t
|
||||
:keymaps keymap
|
||||
(append
|
||||
;; :non-normal-prefix isn't respected when evil is absent, so this
|
||||
;; is necessary:
|
||||
(if (featurep 'evil)
|
||||
(list :states '(normal visual motion)
|
||||
:prefix doom-localleader-key
|
||||
:non-normal-prefix doom-localleader-alt-key)
|
||||
(list :prefix doom-localleader-alt-key))
|
||||
(list key orig-def)
|
||||
nil))))
|
||||
|
||||
|
||||
;;
|
||||
;; Packages
|
||||
|
||||
(def-package! which-key
|
||||
:defer 1
|
||||
:after-call pre-command-hook
|
||||
|
@ -68,18 +112,16 @@ If any hook returns non-nil, all hooks after it are ignored.")
|
|||
|
||||
|
||||
;;
|
||||
(defun doom--keybind-register (key desc &optional modes)
|
||||
"Register a description for KEY with `which-key' in MODES.
|
||||
|
||||
KEYS should be a string in kbd format.
|
||||
DESC should be a string describing what KEY does.
|
||||
MODES should be a list of major mode symbols."
|
||||
(after! which-key
|
||||
(if modes
|
||||
(dolist (mode modes)
|
||||
(which-key-add-major-mode-key-based-replacements mode key desc))
|
||||
(which-key-add-key-based-replacements key desc))))
|
||||
|
||||
(defvar doom-evil-state-alist
|
||||
'((?n . normal)
|
||||
(?v . visual)
|
||||
(?i . insert)
|
||||
(?e . emacs)
|
||||
(?o . operator)
|
||||
(?m . motion)
|
||||
(?r . replace)
|
||||
(?g . global))
|
||||
"A list of cons cells that map a letter to a evil state symbol.")
|
||||
|
||||
(defun doom--keyword-to-states (keyword)
|
||||
"Convert a KEYWORD into a list of evil state symbols.
|
||||
|
@ -95,202 +137,197 @@ For example, :nvi will map to (list 'normal 'visual 'insert). See
|
|||
(put :after 'lisp-indent-function 'defun)
|
||||
(put :desc 'lisp-indent-function 'defun)
|
||||
(put :leader 'lisp-indent-function 'defun)
|
||||
(put :local 'lisp-indent-function 'defun)
|
||||
(put :localleader 'lisp-indent-function 'defun)
|
||||
(put :map 'lisp-indent-function 'defun)
|
||||
(put :map* 'lisp-indent-function 'defun)
|
||||
(put :keymap 'lisp-indent-function 'defun)
|
||||
(put :mode 'lisp-indent-function 'defun)
|
||||
(put :prefix 'lisp-indent-function 'defun)
|
||||
(put :textobj 'lisp-indent-function 'defun)
|
||||
(put :alt-prefix 'lisp-indent-function 'defun)
|
||||
(put :unless 'lisp-indent-function 'defun)
|
||||
(put :if 'lisp-indent-function 'defun)
|
||||
(put :when 'lisp-indent-function 'defun)
|
||||
|
||||
;; specials
|
||||
(defvar doom--keymaps nil)
|
||||
(defvar doom--prefix nil)
|
||||
(defvar doom--defer nil)
|
||||
(defvar doom--local nil)
|
||||
(defvar doom--map-forms nil)
|
||||
(defvar doom--map-batch-forms nil)
|
||||
(defvar doom--map-state '(:dummy t))
|
||||
(defvar doom--map-parent-state nil)
|
||||
(defvar doom--map-evil-p nil)
|
||||
(after! evil (setq doom--map-evil-p t))
|
||||
|
||||
(defun doom--map-process (rest)
|
||||
(let (doom--map-state
|
||||
doom--map-forms
|
||||
desc)
|
||||
(while rest
|
||||
(let ((key (pop rest)))
|
||||
(cond ((listp key)
|
||||
(doom--map-nested nil key))
|
||||
|
||||
((keywordp key)
|
||||
(pcase key
|
||||
(:leader
|
||||
(doom--map-set :definer '(quote leader)))
|
||||
(:localleader
|
||||
(doom--map-set :definer '(quote localleader)))
|
||||
(:after
|
||||
(doom--map-nested (list 'after! (pop rest)) rest)
|
||||
(setq rest nil))
|
||||
(:desc
|
||||
(setq desc (pop rest)))
|
||||
((or :map :map* :keymap)
|
||||
(doom--map-set :keymaps `(quote ,(doom-enlist (pop rest)))))
|
||||
(:mode
|
||||
(push (cl-loop for m in (doom-enlist (pop rest))
|
||||
collect (intern (concat (symbol-name m) "-map")))
|
||||
rest)
|
||||
(push :map rest))
|
||||
((or :if :when :unless)
|
||||
(doom--map-nested (list (intern (doom-keyword-name key)) (pop rest)) rest)
|
||||
(setq rest nil))
|
||||
(:prefix
|
||||
(cl-destructuring-bind (prefix . desc) (doom-enlist (pop rest))
|
||||
(doom--map-set :prefix prefix)
|
||||
(when (stringp desc)
|
||||
(setq rest (append (list :desc desc "" nil) rest)))))
|
||||
(:alt-prefix
|
||||
(cl-destructuring-bind (prefix . desc) (doom-enlist (pop rest))
|
||||
(doom--map-set :non-normal-prefix prefix)
|
||||
(when (stringp desc)
|
||||
(setq rest (append (list :desc desc "" nil) rest)))))
|
||||
(:textobj
|
||||
(let* ((key (pop rest))
|
||||
(inner (pop rest))
|
||||
(outer (pop rest)))
|
||||
(push `(map! (:map evil-inner-text-objects-map ,key ,inner)
|
||||
(:map evil-outer-text-objects-map ,key ,outer))
|
||||
doom--map-forms)))
|
||||
(_
|
||||
(condition-case e
|
||||
(doom--map-def (pop rest) (pop rest) (doom--keyword-to-states key) desc)
|
||||
(error
|
||||
(error "Not a valid `map!' property: %s" key))))))
|
||||
|
||||
((doom--map-def key (pop rest) nil desc)))))
|
||||
|
||||
(doom--map-commit)
|
||||
(macroexp-progn (nreverse (delq nil doom--map-forms)))))
|
||||
|
||||
(defun doom--map-append-keys (prop)
|
||||
(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)
|
||||
(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))))
|
||||
(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))
|
||||
(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))
|
||||
(dolist (state states)
|
||||
(when desc
|
||||
(setq def
|
||||
(if (and (equal key "")
|
||||
(null def))
|
||||
`(quote (nil :which-key ,desc))
|
||||
`(list ,@(plist-put (general--normalize-extended-def def)
|
||||
:which-key desc)))))
|
||||
(push key (alist-get state doom--map-batch-forms))
|
||||
(push 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 `(general-define-key ,@(if state `(:states ',state)) ,@attrs ,@(nreverse defs))
|
||||
into forms
|
||||
finally do (push (macroexp-progn forms) doom--map-forms))
|
||||
(setq doom--map-batch-forms nil)))
|
||||
|
||||
(defun doom--map-state ()
|
||||
(let ((plist
|
||||
(append (list :prefix (doom--map-append-keys :prefix)
|
||||
:non-normal-prefix (doom--map-append-keys :non-normal-prefix)
|
||||
:definer (plist-get doom--map-parent-state :definer)
|
||||
:keymaps
|
||||
(append (plist-get doom--map-parent-state :keymaps)
|
||||
(plist-get doom--map-state :keymaps)
|
||||
nil))
|
||||
doom--map-state
|
||||
nil))
|
||||
newplist)
|
||||
(while plist
|
||||
(let ((key (pop plist))
|
||||
(val (pop plist)))
|
||||
(when (and val (not (plist-member newplist key)))
|
||||
(push val newplist)
|
||||
(push key newplist))))
|
||||
newplist))
|
||||
|
||||
;;
|
||||
(defmacro map! (&rest rest)
|
||||
"A nightmare of a key-binding macro that will use `evil-define-key*',
|
||||
`define-key', `local-set-key' and `global-set-key' depending on context and
|
||||
plist key flags (and whether evil is loaded or not). It was designed to make
|
||||
binding multiple keys more concise, like in vim.
|
||||
"A convenience macro for defining keybinds, powered by `general'.
|
||||
|
||||
If evil isn't loaded, it will ignore evil-specific bindings.
|
||||
If evil isn't loaded, evil-specific bindings are ignored.
|
||||
|
||||
States
|
||||
:n normal
|
||||
:v visual
|
||||
:i insert
|
||||
:e emacs
|
||||
:o operator
|
||||
:m motion
|
||||
:r replace
|
||||
:n normal
|
||||
:v visual
|
||||
:i insert
|
||||
:e emacs
|
||||
:o operator
|
||||
:m motion
|
||||
:r replace
|
||||
:g global (will work without evil)
|
||||
|
||||
These can be combined (order doesn't matter), e.g. :nvi will apply to
|
||||
normal, visual and insert mode. The state resets after the following
|
||||
key=>def pair.
|
||||
These can be combined in any order, e.g. :nvi will apply to normal, visual and
|
||||
insert mode. The state resets after the following key=>def pair. If states are
|
||||
omitted the keybind will be global (no emacs state; this is different from
|
||||
evil's Emacs state and will work in the absence of `evil-mode').
|
||||
|
||||
If states are omitted the keybind will be global.
|
||||
Properties
|
||||
:leader [...] an alias for (:prefix doom-leader-key ...)
|
||||
:localleader [...] bind to localleader; requires a keymap
|
||||
:mode [MODE(s)] [...] inner keybinds are applied to major MODE(s)
|
||||
:map [KEYMAP(s)] [...] inner keybinds are applied to KEYMAP(S)
|
||||
:keymap [KEYMAP(s)] [...] same as :map
|
||||
:prefix [PREFIX] [...] set keybind prefix for following keys
|
||||
:alt-prefix [PREFIX] [...] use non-normal-prefix for following keys
|
||||
:after [FEATURE] [...] apply keybinds when [FEATURE] loads
|
||||
:textobj KEY INNER-FN OUTER-FN define a text object keybind pair
|
||||
:if [CONDITION] [...]
|
||||
:when [CONDITION] [...]
|
||||
:unless [CONDITION] [...]
|
||||
|
||||
This can be customized with `doom-evil-state-alist'.
|
||||
|
||||
:textobj is a special state that takes a key and two commands, one for the
|
||||
inner binding, another for the outer.
|
||||
|
||||
Flags
|
||||
(:leader [...]) an alias for (:prefix doom-leader-key ...)
|
||||
(:localleader [...]) an alias for (:prefix doom-localleader-key ...)
|
||||
(:mode [MODE(s)] [...]) inner keybinds are applied to major MODE(s)
|
||||
(:map [KEYMAP(s)] [...]) inner keybinds are applied to KEYMAP(S)
|
||||
(:map* [KEYMAP(s)] [...]) same as :map, but deferred
|
||||
(:prefix [PREFIX] [...]) assign prefix to all inner keybindings
|
||||
(:after [FEATURE] [...]) apply keybinds when [FEATURE] loads
|
||||
(:local [...]) make bindings buffer local; incompatible with keymaps!
|
||||
|
||||
Conditional keybinds
|
||||
(:when [CONDITION] [...])
|
||||
(:unless [CONDITION] [...])
|
||||
Any of the above properties may be nested, so that they only apply to a
|
||||
certain group of keybinds.
|
||||
|
||||
Example
|
||||
(map! :map magit-mode-map
|
||||
:m \"C-r\" 'do-something ; assign C-r in motion state
|
||||
:nv \"q\" 'magit-mode-quit-window ; assign to 'q' in normal and visual states
|
||||
\"C-x C-r\" 'a-global-keybind
|
||||
(map! :map magit-mode-map
|
||||
:m \"C-r\" 'do-something ; C-r in motion state
|
||||
:nv \"q\" 'magit-mode-quit-window ; q in normal+visual states
|
||||
\"C-x C-r\" 'a-global-keybind
|
||||
:g \"C-x C-r\" 'another-global-keybind ; same as above
|
||||
|
||||
(:when IS-MAC
|
||||
:n \"M-s\" 'some-fn
|
||||
:i \"M-o\" (lambda (interactive) (message \"Hi\"))))"
|
||||
(let ((doom--keymaps doom--keymaps)
|
||||
(doom--prefix doom--prefix)
|
||||
(doom--defer doom--defer)
|
||||
(doom--local doom--local)
|
||||
key def states forms desc modes)
|
||||
(while rest
|
||||
(setq key (pop rest))
|
||||
(cond
|
||||
;; it's a sub expr
|
||||
((listp key)
|
||||
(push (macroexpand `(map! ,@key)) forms))
|
||||
|
||||
;; it's a flag
|
||||
((keywordp key)
|
||||
(cond ((eq key :leader)
|
||||
(push 'doom-leader-key rest)
|
||||
(setq key :prefix
|
||||
desc "<leader>"))
|
||||
((eq key :localleader)
|
||||
(push 'doom-localleader-key rest)
|
||||
(setq key :prefix
|
||||
desc "<localleader>")))
|
||||
(pcase key
|
||||
(:when (push `(if ,(pop rest) ,(macroexpand `(map! ,@rest))) forms) (setq rest '()))
|
||||
(:unless (push `(if (not ,(pop rest)) ,(macroexpand `(map! ,@rest))) forms) (setq rest '()))
|
||||
(:after (push `(after! ,(pop rest) ,(macroexpand `(map! ,@rest))) forms) (setq rest '()))
|
||||
(:desc (setq desc (pop rest)))
|
||||
((or :map :map*)
|
||||
(setq doom--keymaps (doom-enlist (pop rest))
|
||||
doom--defer (eq key :map*)))
|
||||
(:mode
|
||||
(setq modes (doom-enlist (pop rest)))
|
||||
(unless doom--keymaps
|
||||
(setq doom--keymaps
|
||||
(cl-loop for m in modes
|
||||
collect (intern (format "%s-map" (symbol-name m)))))))
|
||||
(:textobj
|
||||
(let* ((key (pop rest))
|
||||
(inner (pop rest))
|
||||
(outer (pop rest)))
|
||||
(push (macroexpand `(map! (:map evil-inner-text-objects-map ,key ,inner)
|
||||
(:map evil-outer-text-objects-map ,key ,outer)))
|
||||
forms)))
|
||||
(:prefix
|
||||
(let ((def (pop rest)))
|
||||
(setq doom--prefix
|
||||
`(vconcat ,doom--prefix
|
||||
,(if (or (stringp def)
|
||||
(and (symbolp def)
|
||||
(stringp (symbol-value def))))
|
||||
`(kbd ,def)
|
||||
def)))
|
||||
(when desc
|
||||
(push `(doom--keybind-register ,(key-description (eval doom--prefix))
|
||||
,desc ',modes)
|
||||
forms)
|
||||
(setq desc nil))))
|
||||
(:local
|
||||
(setq doom--local t))
|
||||
(_ ; might be a state doom--prefix
|
||||
(setq states (doom--keyword-to-states key)))))
|
||||
|
||||
;; It's a key-def pair
|
||||
((or (stringp key)
|
||||
(characterp key)
|
||||
(vectorp key)
|
||||
(symbolp key))
|
||||
(unwind-protect
|
||||
(catch 'skip
|
||||
(when (symbolp key)
|
||||
(setq key `(kbd ,key)))
|
||||
(when (stringp key)
|
||||
(setq key (kbd key)))
|
||||
(when doom--prefix
|
||||
(setq key (append doom--prefix (list key))))
|
||||
(unless (> (length rest) 0)
|
||||
(user-error "map! has no definition for %s key" key))
|
||||
(setq def (pop rest))
|
||||
(when (or (vectorp def)
|
||||
(stringp def))
|
||||
(setq def
|
||||
`(lambda () (interactive)
|
||||
(setq unread-command-events
|
||||
(nconc (mapcar (lambda (ev) (cons t ev))
|
||||
(listify-key-sequence
|
||||
,(cond ((vectorp def) def)
|
||||
((stringp def) (kbd def)))))
|
||||
unread-command-events)))))
|
||||
(when desc
|
||||
(push `(doom--keybind-register ,(key-description (eval key))
|
||||
,desc ',modes)
|
||||
forms))
|
||||
(cond ((and doom--local doom--keymaps)
|
||||
(push `(lwarn 'doom-map :warning
|
||||
"Can't local bind '%s' key to a keymap; skipped"
|
||||
,key)
|
||||
forms)
|
||||
(throw 'skip 'local))
|
||||
((and doom--keymaps states)
|
||||
(dolist (keymap doom--keymaps)
|
||||
(when (memq 'global states)
|
||||
(push `(define-key ,keymap ,key ,def) forms))
|
||||
(when (featurep 'evil)
|
||||
(when-let* ((states (delq 'global states)))
|
||||
(push `(,(if doom--defer #'evil-define-key #'evil-define-key*)
|
||||
',states ,keymap ,key ,def)
|
||||
forms)))))
|
||||
(states
|
||||
(dolist (state states)
|
||||
(if (eq state 'global)
|
||||
(push `(global-set-key ,key ,def) forms)
|
||||
(when (featurep 'evil)
|
||||
(push (if doom--local
|
||||
`(evil-local-set-key ',state ,key ,def)
|
||||
`(evil-define-key* ',state 'global ,key ,def))
|
||||
forms)))))
|
||||
(doom--keymaps
|
||||
(dolist (keymap doom--keymaps)
|
||||
(push `(define-key ,keymap ,key ,def) forms)))
|
||||
(t
|
||||
(push `(,(if doom--local #'local-set-key #'global-set-key)
|
||||
,key ,def)
|
||||
forms))))
|
||||
(setq states '()
|
||||
doom--local nil
|
||||
desc nil)))
|
||||
|
||||
(t (user-error "Invalid key %s" key))))
|
||||
`(progn ,@(nreverse forms))))
|
||||
(:when IS-MAC
|
||||
:n \"M-s\" 'some-fn
|
||||
:i \"M-o\" (lambda (interactive) (message \"Hi\"))))"
|
||||
(doom--map-process rest))
|
||||
|
||||
(provide 'core-keybinds)
|
||||
;;; core-keybinds.el ends here
|
||||
|
|
|
@ -369,34 +369,6 @@ For example:
|
|||
,(doom--resolve-path-forms spec '--directory--))
|
||||
(doom--resolve-path-forms spec)))
|
||||
|
||||
(defmacro define-key! (keymaps key def &rest rest)
|
||||
"Like `define-key', but accepts a variable number of KEYMAPS and/or KEY+DEFs.
|
||||
|
||||
KEYMAPS can also be (or contain) 'global or 'local, to make this equivalent to
|
||||
using `global-set-key' and `local-set-key'.
|
||||
|
||||
KEY is a key string or vector. It is *not* piped through `kbd'."
|
||||
(declare (indent defun))
|
||||
(or (cl-evenp (length rest))
|
||||
(signal 'wrong-number-of-arguments (list 'evenp (length rest))))
|
||||
(if (and (listp keymaps)
|
||||
(not (eq (car-safe keymaps) 'quote)))
|
||||
`(dolist (map (list ,@keymaps))
|
||||
,(macroexpand `(define-key! map ,key ,def ,@rest)))
|
||||
(when (eq (car-safe keymaps) 'quote)
|
||||
(pcase (cadr keymaps)
|
||||
(`global (setq keymaps '(current-global-map)))
|
||||
(`local (setq keymaps '(current-local-map)))
|
||||
(x (error "%s is not a valid keymap" x))))
|
||||
`(let ((map ,keymaps))
|
||||
(define-key map ,key ,def)
|
||||
,@(let (forms)
|
||||
(while rest
|
||||
(let ((key (pop rest))
|
||||
(def (pop rest)))
|
||||
(push `(define-key map ,key ,def) forms)))
|
||||
(nreverse forms)))))
|
||||
|
||||
(defmacro load! (filename &optional path noerror)
|
||||
"Load a file relative to the current executing file (`load-file-name').
|
||||
|
||||
|
|
|
@ -20,9 +20,13 @@
|
|||
(when (featurep 'exec-path-from-shell)
|
||||
`(exec-path-from-shell-copy-envs ,@vars)))
|
||||
|
||||
;; key conventions:
|
||||
;; alt/option = meta
|
||||
;; windows/command = super
|
||||
|
||||
(cond (IS-MAC
|
||||
(setq mac-command-modifier 'meta
|
||||
mac-option-modifier 'alt
|
||||
(setq mac-command-modifier 'super
|
||||
mac-option-modifier 'meta
|
||||
;; sane trackpad/mouse scroll settings
|
||||
mac-redisplay-dont-reset-vscroll t
|
||||
mac-mouse-wheel-smooth-scroll nil
|
||||
|
@ -60,11 +64,16 @@
|
|||
|
||||
(IS-LINUX
|
||||
(setq x-gtk-use-system-tooltips nil ; native tooltips are ugly!
|
||||
x-underline-at-descent-line t)) ; draw underline lower
|
||||
x-underline-at-descent-line t ; draw underline lower
|
||||
x-alt-keysym 'meta))
|
||||
|
||||
(IS-WINDOWS
|
||||
(setq w32-get-true-file-attributes nil) ; fix file io slowdowns
|
||||
))
|
||||
(setq w32-get-true-file-attributes nil ; fix file io slowdowns
|
||||
;; map window keys to super
|
||||
w32-pass-lwindow-to-system nil
|
||||
w32-lwindow-modifier 'super
|
||||
w32-pass-rwindow-to-system nil
|
||||
w32-rwindow-modifier 'super)))
|
||||
|
||||
(provide 'core-os)
|
||||
;;; core-os.el ends here
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
(package! projectile)
|
||||
|
||||
;; core-keybinds.el
|
||||
(package! general)
|
||||
(package! which-key)
|
||||
(package! hydra)
|
||||
|
||||
|
|
266
core/test/test-core-keybinds.el
Normal file
266
core/test/test-core-keybinds.el
Normal file
|
@ -0,0 +1,266 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; core/test/test-core-keybinds.el
|
||||
|
||||
|
||||
(describe "core/keybinds"
|
||||
(describe "map!"
|
||||
:var (doom--map-evil-p doom-map-states)
|
||||
(before-each
|
||||
(setq doom--map-evil-p t
|
||||
doom-map-states '((:n . normal)
|
||||
(:v . visual)
|
||||
(:i . insert)
|
||||
(:e . emacs)
|
||||
(:o . operator)
|
||||
(:m . motion)
|
||||
(:r . replace))))
|
||||
|
||||
(describe "Single keybinds"
|
||||
(it "binds a global key"
|
||||
(expect (macroexpand '(map! "C-." #'a))
|
||||
:to-equal '(general-define-key "C-." #'a)))
|
||||
|
||||
(it "binds a key in one evil state"
|
||||
(dolist (state doom-map-states)
|
||||
(expect (macroexpand `(map! ,(car state) "C-." #'a))
|
||||
:to-equal `(general-define-key :states ',(cdr state) "C-." #'a))))
|
||||
|
||||
(it "binds a key in multiple evil states"
|
||||
(expect (cdr (macroexpand `(map! :nvi "C-." #'a)))
|
||||
:to-have-same-items-as
|
||||
'((general-define-key :states 'normal "C-." #'a)
|
||||
(general-define-key :states 'visual "C-." #'a)
|
||||
(general-define-key :states 'insert "C-." #'a))))
|
||||
|
||||
(it "binds evil keybinds together with global keybinds"
|
||||
(expect (macroexpand '(map! :ng "C-." #'a))
|
||||
:to-equal
|
||||
'(progn
|
||||
(general-define-key :states 'normal "C-." #'a)
|
||||
(general-define-key "C-." #'a)))))
|
||||
|
||||
(describe "Multiple keybinds"
|
||||
(it "binds global keys and preserves order"
|
||||
(expect (macroexpand '(map! "C-." #'a
|
||||
"C-," #'b
|
||||
"C-/" #'c))
|
||||
:to-equal '(general-define-key "C-." #'a
|
||||
"C-," #'b
|
||||
"C-/" #'c)))
|
||||
|
||||
(it "binds multiple keybinds in an evil state and preserve order"
|
||||
(dolist (state doom-map-states)
|
||||
(expect (macroexpand `(map! ,(car state) "a" #'a
|
||||
,(car state) "b" #'b
|
||||
,(car state) "c" #'c))
|
||||
:to-equal
|
||||
`(general-define-key :states ',(cdr state)
|
||||
"a" #'a
|
||||
"b" #'b
|
||||
"c" #'c))))
|
||||
|
||||
(it "binds multiple keybinds in different evil states"
|
||||
(expect (cdr (macroexpand `(map! :n "a" #'a
|
||||
:n "b" #'b
|
||||
:n "e" #'e
|
||||
:v "c" #'c
|
||||
:i "d" #'d)))
|
||||
:to-have-same-items-as
|
||||
`((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))))
|
||||
|
||||
(it "groups multi-state keybinds while preserving same-group key order"
|
||||
(expect (cdr (macroexpand `(map! :n "a" #'a
|
||||
:v "c" #'c
|
||||
:n "b" #'b
|
||||
:i "d" #'d
|
||||
:n "e" #'e)))
|
||||
:to-have-same-items-as
|
||||
`((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))))
|
||||
|
||||
(it "binds multiple keybinds in multiple evil states"
|
||||
(expect (cdr (macroexpand `(map! :nvi "a" #'a
|
||||
:nvi "b" #'b
|
||||
:nvi "c" #'c)))
|
||||
:to-have-same-items-as
|
||||
'((general-define-key :states 'normal "a" #'a "b" #'b "c" #'c)
|
||||
(general-define-key :states 'visual "a" #'a "b" #'b "c" #'c)
|
||||
(general-define-key :states 'insert "a" #'a "b" #'b "c" #'c)))))
|
||||
|
||||
(describe "Nested keybinds"
|
||||
(it "binds global keys"
|
||||
(expect (macroexpand '(map! "C-." #'a
|
||||
("C-a" #'b)
|
||||
("C-x" #'c)))
|
||||
:to-equal '(progn
|
||||
(general-define-key "C-." #'a)
|
||||
(general-define-key "C-a" #'b)
|
||||
(general-define-key "C-x" #'c))))
|
||||
|
||||
(it "binds nested evil keybinds"
|
||||
(expect (macroexpand '(map! :n "C-." #'a
|
||||
(:n "C-a" #'b)
|
||||
(:n "C-x" #'c)))
|
||||
:to-equal
|
||||
'(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 (cdr (macroexpand-1 '(map! :n "a" #'a
|
||||
"b" #'b
|
||||
:i "c" #'c)))
|
||||
:to-have-same-items-as
|
||||
'((general-define-key :states 'insert "c" #'c)
|
||||
(general-define-key "b" #'b)
|
||||
(general-define-key :states 'normal "a" #'a)))))
|
||||
|
||||
;;
|
||||
(describe "Properties"
|
||||
(describe ":after"
|
||||
(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 (macroexpand-1 form)
|
||||
:to-equal
|
||||
'(after! helm (general-define-key "a" #'a "b" #'b))))
|
||||
(expect (macroexpand-1 '(map! "a" #'a (:after helm "b" #'b "c" #'c)))
|
||||
:to-equal
|
||||
'(progn
|
||||
(general-define-key "a" #'a)
|
||||
(after! helm
|
||||
(general-define-key "b" #'b "c" #'c))))
|
||||
(expect (macroexpand-1 '(map! (:after helm "b" #'b "c" #'c) "a" #'a))
|
||||
:to-equal
|
||||
'(progn
|
||||
(after! helm
|
||||
(general-define-key "b" #'b "c" #'c))
|
||||
(general-define-key "a" #'a))))
|
||||
|
||||
(it "nests `after!' blocks"
|
||||
(expect (macroexpand-1 '(map! :after x "a" #'a
|
||||
(:after y "b" #'b
|
||||
(:after z "c" #'c))))
|
||||
:to-equal
|
||||
'(after! x (progn (general-define-key "a" #'a)
|
||||
(after! y (progn (general-define-key "b" #'b)
|
||||
(after! z (general-define-key "c" #'c))))))))
|
||||
|
||||
(it "nests `after!' blocks in other nested blocks"
|
||||
(expect (macroexpand-1 '(map! :after x "a" #'a
|
||||
(:when t "b" #'b
|
||||
(:after z "c" #'c))))
|
||||
:to-equal
|
||||
'(after! x
|
||||
(progn
|
||||
(general-define-key "a" #'a)
|
||||
(when t
|
||||
(progn
|
||||
(general-define-key "b" #'b)
|
||||
(after! z (general-define-key "c" #'c)))))))))
|
||||
|
||||
(describe ":desc"
|
||||
(it "add a :which-key property to a keybind's DEF"
|
||||
(expect (macroexpand-1 '(map! :desc "A" "a" #'a))
|
||||
:to-equal `(general-define-key "a" (list :def #'a :which-key "A")))))
|
||||
|
||||
(describe ":if/:when/:unless"
|
||||
(it "wraps keys in a conditional block"
|
||||
(dolist (prop '(:if :when :unless))
|
||||
(let ((prop-fn (intern (doom-keyword-name prop))))
|
||||
(expect (macroexpand-1 `(map! ,prop t "a" #'a "b" #'b))
|
||||
:to-equal `(,prop-fn t (general-define-key "a" #'a "b" #'b)))
|
||||
(expect (macroexpand-1 `(map! (,prop t "a" #'a "b" #'b)))
|
||||
:to-equal `(,prop-fn t (general-define-key "a" #'a "b" #'b))))))
|
||||
|
||||
(it "nests conditional blocks"
|
||||
(expect (macroexpand-1 '(map! (:when t "a" #'a (:when t "b" #'b))))
|
||||
:to-equal '(when t
|
||||
(progn (general-define-key "a" #'a)
|
||||
(when t (general-define-key "b" #'b)))))))
|
||||
|
||||
(describe ":leader"
|
||||
(it "uses leader definer"
|
||||
(expect (macroexpand-1 '(map! :leader "a" #'a "b" #'b))
|
||||
:to-equal '(general-define-key :definer 'leader "a" #'a "b" #'b)))
|
||||
|
||||
(it "it persists for nested keys"
|
||||
(expect (cdr (macroexpand-1 '(map! :leader "a" #'a ("b" #'b))))
|
||||
:to-equal '((general-define-key :definer 'leader "a" #'a)
|
||||
(general-define-key :definer 'leader "b" #'b)))))
|
||||
|
||||
(describe ":localleader"
|
||||
(it "uses localleader definer"
|
||||
(expect (macroexpand-1 '(map! :localleader "a" #'a "b" #'b))
|
||||
:to-equal '(general-define-key :definer 'localleader "a" #'a "b" #'b)))
|
||||
|
||||
(it "it persists for nested keys"
|
||||
(expect (cdr (macroexpand-1 '(map! :localleader "a" #'a ("b" #'b))))
|
||||
:to-equal '((general-define-key :definer 'localleader "a" #'a)
|
||||
(general-define-key :definer 'localleader "b" #'b)))))
|
||||
|
||||
(describe ":map/:keymap"
|
||||
(it "specifies a single keymap for keys"
|
||||
(expect (macroexpand-1 '(map! :map emacs-lisp-mode-map "a" #'a))
|
||||
:to-equal
|
||||
'(general-define-key :keymaps '(emacs-lisp-mode-map) "a" #'a)))
|
||||
|
||||
(it "specifies multiple keymap for keys"
|
||||
(expect (macroexpand-1 '(map! :map (lisp-mode-map emacs-lisp-mode-map) "a" #'a))
|
||||
:to-equal
|
||||
'(general-define-key :keymaps '(lisp-mode-map emacs-lisp-mode-map) "a" #'a))))
|
||||
|
||||
(describe ":mode"
|
||||
(it "appends -map to MODE"
|
||||
(expect (macroexpand-1 '(map! :mode emacs-lisp-mode "a" #'a))
|
||||
:to-equal
|
||||
'(general-define-key :keymaps '(emacs-lisp-mode-map) "a" #'a))))
|
||||
|
||||
(describe ":prefix"
|
||||
(it "specifies a prefix for all keys"
|
||||
(expect (macroexpand-1 '(map! :prefix "a" "x" #'x "y" #'y "z" #'z))
|
||||
:to-equal
|
||||
'(general-define-key :prefix "a" "x" #'x "y" #'y "z" #'z)))
|
||||
|
||||
(it "overwrites previous inline :prefix properties"
|
||||
(expect (cdr (macroexpand-1 '(map! :prefix "a" "x" #'x "y" #'y :prefix "b" "z" #'z)))
|
||||
:to-equal
|
||||
'((general-define-key :prefix "a" "x" #'x "y" #'y)
|
||||
(general-define-key :prefix "b" "z" #'z))))
|
||||
|
||||
(it "accumulates keys when nested"
|
||||
(expect (cdr (macroexpand-1 '(map! (:prefix "a" "x" #'x (:prefix "b" "x" #'x)))))
|
||||
:to-equal
|
||||
`((general-define-key :prefix "a" "x" #'x)
|
||||
(general-define-key :prefix (general--concat t "a" "b")
|
||||
"x" #'x)))))
|
||||
|
||||
(describe ":alt-prefix"
|
||||
(it "specifies a prefix for all keys"
|
||||
(expect (macroexpand-1 '(map! :alt-prefix "a" "x" #'x "y" #'y "z" #'z))
|
||||
:to-equal
|
||||
'(general-define-key :non-normal-prefix "a" "x" #'x "y" #'y "z" #'z)))
|
||||
|
||||
(it "overwrites previous inline :alt-prefix properties"
|
||||
(expect (cdr (macroexpand-1 '(map! :alt-prefix "a" "x" #'x "y" #'y :alt-prefix "b" "z" #'z)))
|
||||
:to-equal
|
||||
'((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 (cdr (macroexpand-1 '(map! (:alt-prefix "a" "x" #'x (:alt-prefix "b" "x" #'x)))))
|
||||
:to-equal
|
||||
`((general-define-key :non-normal-prefix "a" "x" #'x)
|
||||
(general-define-key :non-normal-prefix (general--concat t "a" "b")
|
||||
"x" #'x)))))
|
||||
|
||||
(describe ":textobj"
|
||||
(it "defines keys in evil-{inner,outer}-text-objects-map"
|
||||
(expect (macroexpand-1 '(map! :textobj "a" #'inner #'outer))
|
||||
:to-equal
|
||||
'(map! (:map evil-inner-text-objects-map "a" #'inner)
|
||||
(:map evil-outer-text-objects-map "a" #'outer))))))))
|
Loading…
Add table
Add a link
Reference in a new issue