Add core-keybinds.el

This commit is contained in:
Henrik Lissner 2017-02-19 07:03:05 -05:00
parent 72577b823c
commit 704099a7b9
4 changed files with 205 additions and 153 deletions

200
core/core-keybinds.el Normal file
View file

@ -0,0 +1,200 @@
;;; core-keybinds.el
;; A centralized keybinds system. Uses `which-key' to preview the available keys
;; when using `doom-leader-key' or `doom-localleader-key'.
(defvar doom-leader-key ","
"The leader prefix key, for global commands.")
(defvar doom-localleader-key "\\"
"The leader prefix key, for global commands.")
(defvar doom--which-key-defs '(nil))
(@def-package which-key
:defer 1
:config
(setq which-key-sort-order 'which-key-prefix-then-key-order
;; only pop-up for leader/localleader keys
which-key-allow-regexps (list (format "^%s" (regexp-quote doom-leader-key))
(format "^%s" (regexp-quote doom-localleader-key)))
;; perdy unicode icons
which-key-replacement-alist
(append '((("TAB" . nil) . ("" . nil))
(("RET" . nil) . ("" . nil))
(("DEL" . nil) . ("" . nil))
(("SPC" . nil) . ("" . nil)))
which-key-replacement-alist))
;; embolden local bindings
(set-face-attribute 'which-key-local-map-description-face nil :weight 'bold)
(which-key-mode +1)
(when (cdr doom--which-key-defs)
(apply 'which-key-add-key-based-replacements (cdr doom--which-key-defs))))
(defun doom-keyword-to-states (keyword &optional ignore)
(let ((states '(("n" . normal)
("v" . visual)
("i" . insert)
("e" . emacs)
("o" . operator)
("m" . motion)
("r" . replace))))
(delq nil
(mapcar (lambda (l)
(let ((state (cdr (assoc l states))))
(unless state
(unless (or (eq ignore t) (member l ignore))
(error "not a valid state: %s" l)))
state))
(split-string (substring (symbol-name keyword) 1) "" t)))))
;; Register keywords for proper indentation (see `@map')
(put ':prefix 'lisp-indent-function 'defun)
(put ':map '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)
(put ':desc 'lisp-indent-function 'defun)
(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.
If evil isn't loaded, it will ignore evil-specific bindings.
States
:n normal
:v visual
:i insert
:e emacs
:o operator
:m motion
:r replace
:L local
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.
If states are omitted the keybind will be global.
:L cannot be in a :map.
Flags
(:map [KEYMAP] [...]) ; inner keybinds are applied to KEYMAP(S)
(:map* [KEYMAP] [...]) ; same as :map, but deferred
(:prefix [PREFIX] [...]) ; assign prefix to all inner keybindings
(:after [FEATURE] [...]) ; apply keybinds when [FEATURE] loads
Conditional keybinds
(:when [CONDITION] [...])
(:unless [CONDITION] [...])
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
(:when IS-MAC
:n \"M-s\" 'some-fn
:i \"M-o\" (lambda (interactive) (message \"Hi\"))))"
(let ((keymaps (if (boundp 'keymaps) keymaps))
(prefix (if (boundp 'prefix) prefix))
(defer (if (boundp 'defer) defer))
local key def states forms desc)
(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 (prog1 `((if ,(pop rest) ,(macroexpand `(@map ,@rest)))) (setq rest '())))
(:unless (prog1 `((if (not ,(pop rest)) ,(macroexpand `(@map ,@rest)))) (setq rest '())))
(:after (prog1 `((@after ,(pop rest) ,(macroexpand `(@map ,@rest)))) (setq rest '())))
(:desc (setq desc (pop rest)))
(:map* (setq defer t) (push :map rest))
(:map
(setq keymaps
(let ((car (pop rest)))
(if (listp car) car (list car)))))
(:prefix
(let ((def (pop rest)))
(setq prefix
(if (or (symbolp def) (listp def))
`(vconcat ,prefix (if (stringp ,def) (kbd ,def) ,def))
`(vconcat ,prefix ,(if (stringp def) (kbd def) def))))
(when desc
(push `(nconc doom--which-key-defs (list ,(key-description (eval prefix)) ,desc))
forms)
(setq desc nil))))
(otherwise ; might be a state prefix
(setq states (doom-keyword-to-states key '("L")))
(when (let (case-fold-search)
(string-match-p "L" (symbol-name key)))
(setq local t)
(cond ((= (length states) 0)
(user-error "local keybinding for %s must accompany another state" key))
((> (length keymaps) 0)
(user-error "local keybinding for %s cannot accompany a keymap" key)))))))
;; It's a key-def pair
((or (stringp key)
(characterp key)
(vectorp key))
(unwind-protect
(catch 'skip
(when (stringp key)
(setq key (kbd key)))
(when prefix
(setq key (append prefix (list key))))
(unless (> (length rest) 0)
(user-error "@map has no definition for %s key" key))
(setq def (pop rest))
(when desc
(push `(nconc doom--which-key-defs (list ,(key-description (eval key)) ,desc))
forms))
(cond ((and keymaps states)
(unless (featurep 'evil) (throw 'skip 'evil))
(dolist (keymap keymaps)
(push `(,(if defer 'evil-define-key 'evil-define-key*)
',states ,keymap ,key ,def)
forms)))
(states
(unless (featurep 'evil) (throw 'skip 'evil))
(dolist (state states)
(push `(define-key
,(intern (format "evil-%s-state-%smap" state (if local "local-" "")))
,key ,def)
forms)))
(keymaps
(dolist (keymap keymaps)
(push `(define-key ,keymap ,key ,def) forms)))
(t (push `(,(if local 'local-set-key 'global-set-key) ,key ,def)
forms))))
(setq states '()
local nil
desc nil)))
(t (user-error "Invalid key %s" key))))
`(progn ,@(reverse forms))))
(provide 'core-keybinds)
;;; core-keybinds.el ends here

View file

@ -135,157 +135,5 @@ Examples:
(t (user-error "@associate invalid rules for mode [%s] (in %s) (match %s) (files %s)"
mode in match files))))))
;; Register keywords for proper indentation (see `@map')
(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)
(put ':leader 'lisp-indent-function 'defun)
(put ':localleader 'lisp-indent-function 'defun)
(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. It was designed to make binding multiple keys more concise,
like in vim.
If evil isn't loaded, it will ignore evil-specific bindings.
Yes, it tries to do too much. Yes, I only did it to make the \"frontend\" config
that little bit more concise. Yes, I could simply have used the above functions.
But it takes a little insanity to custom write your own emacs.d, so what else
were you expecting?
States
:n normal
:v visual
:i insert
:e emacs
:o operator
:m motion
:r replace
:L local
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.
Capitalize the state flag to make it a local binding.
If omitted, the keybind will be defined globally.
Flags
(:map [KEYMAP] [...]) ; inner keybinds are applied to KEYMAP
(:prefix [PREFIX] [...]) ; assign prefix to all inner keybindings
(:after [FEATURE] [...]) ; apply keybinds when [FEATURE] loads
Conditional keybinds
(:when [CONDITION] [...])
(:unless [CONDITION] [...])
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
(:when IS-MAC
:n \"M-s\" 'some-fn
:i \"M-o\" (lambda (interactive) (message \"Hi\"))))"
(let ((keymaps (if (boundp 'keymaps) keymaps))
(prefix (if (boundp 'prefix) prefix))
(state-map '(("n" . normal)
("v" . visual)
("i" . insert)
("e" . emacs)
("o" . operator)
("m" . motion)
("r" . replace)))
local key def states forms)
(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)
(when (memq key '(:leader :localleader))
(cond ((eq key :leader)
(push '+evil-leader rest))
((eq key :localleader)
(push '+evil-localleader rest)))
(setq key :prefix))
(pcase key
(:ignore)
(:prefix
(let ((def (pop rest)))
(setq prefix
(if (or (symbolp def) (listp def))
`(vconcat ,prefix (if (stringp ,def) (kbd ,def) ,def))
`(vconcat ,prefix ,(if (stringp def) (kbd def) def))))))
(:map (setq keymaps (-list (pop rest))))
(:after (prog1 `((@after ,(pop rest) ,(macroexpand `(@map ,@rest)))) (setq rest '())))
(:when (prog1 `((if ,(pop rest) ,(macroexpand `(@map ,@rest)))) (setq rest '())))
(:unless (prog1 `((if (not ,(pop rest)) ,(macroexpand `(@map ,@rest)))) (setq rest '())))
(otherwise ; might be a state prefix
(mapc (lambda (letter)
(cond ((assoc letter state-map)
(push (cdr (assoc letter state-map)) states))
((string= letter "L")
(setq local t))
(t (user-error "Invalid mode prefix %s in key %s" letter key))))
(split-string (substring (symbol-name key) 1) "" t))
(unless states
(user-error "Unrecognized keyword %s" key))
(when local
(cond ((= (length states) 0)
(user-error "local keybinding for %s must accompany another state" key))
((> (length keymaps) 0)
(user-error "local keybinding for %s cannot accompany a keymap" key)))))))
;; It's a key-def pair
((or (stringp key)
(characterp key)
(vectorp key))
(unwind-protect
(catch 'skip
(when (stringp key)
(setq key (kbd key)))
(when prefix
(setq key (append prefix (list key))))
(unless (> (length rest) 0)
(user-error "Map has no definition for %s" key))
(setq def (pop rest))
(push (cond ((and keymaps states)
(unless (featurep 'evil)
(throw 'skip 'evil))
`(progn
,@(mapcar (lambda (keymap) `(evil-define-key* ',states ,keymap ,key ,def))
keymaps)))
(keymaps
`(progn
,@(mapcar (lambda (keymap) `(define-key ,keymap ,key ,def))
keymaps)))
(states
(unless (featurep 'evil)
(throw 'skip 'evil))
`(progn
,@(mapcar (lambda (state)
`(define-key
,(intern (format "evil-%s-state-%smap" state (if local "local-" "")))
,key ,def))
states)))
(t `(,(if local 'local-set-key 'global-set-key)
,key ,def)))
forms))
(setq states '()
local nil)))
(t (user-error "Invalid key %s" key))))
`(progn ,@(reverse forms))))
(provide 'core-lib)
;;; core-lib.el ends here

View file

@ -173,7 +173,8 @@ enable multiple minor modes for the same regexp.")
(require 'core-ui) ; draw me like one of your French editors
(require 'core-popups) ; taming sudden yet inevitable windows
(require 'core-editor) ; baseline configuration for text editing
(require 'core-projects))) ; making Emacs project-aware
(require 'core-projects) ; making Emacs project-aware
(require 'core-keybinds))) ; centralized keybind system + which-key
(provide 'core)
;;; core.el ends here

View file

@ -43,3 +43,6 @@
;; core-projects.el
(@package projectile)
;; core-keybinds.el
(@package which-key)