Skip to content

Commit 6f992a9

Browse files
committed
Allow multiple keymaps in :map argument
This updates the bind-keys functions to accept either a symbol or a list as argument for the `:map' keyword, with additional related fixes: (1) Handle the keymap name `nil' as a synonym for `global-map'; (2) Fail if an invalid argument is specified for `:prefix-map' or `:repeat-map' keywords.
1 parent 1867b7d commit 6f992a9

File tree

5 files changed

+193
-49
lines changed

5 files changed

+193
-49
lines changed

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,14 @@ The effect of this statement is to wait until `helm` has loaded, and then to
246246
bind the key `C-c h` to `helm-execute-persistent-action` within Helm's local
247247
keymap, `helm-command-map`.
248248

249+
Multiple keymaps can be specified as a list:
250+
251+
``` elisp
252+
(use-package helm
253+
:bind (:map (lisp-mode-map emacs-lisp-mode-map)
254+
("C-c x" . eval-print-last-sexp)))
255+
```
256+
249257
Multiple uses of `:map` may be specified. Any binding occurring before the
250258
first use of `:map` are applied to the global keymap:
251259

bind-key.el

+41-36
Original file line numberDiff line numberDiff line change
@@ -262,12 +262,13 @@ In contrast to `define-key', this function removes the binding from the keymap."
262262
"Similar to `bind-key', but overrides any mode-specific bindings."
263263
`(bind-key ,key-name ,command override-global-map ,predicate))
264264

265-
(defun bind-keys-form (args keymap)
265+
(defun bind-keys-form (args keymaps)
266266
"Bind multiple keys at once.
267267
268268
Accepts keyword arguments:
269-
:map MAP - a keymap into which the keybindings should be
270-
added
269+
:map MAPS - a keymap into which the keybindings should be
270+
added, or a list of such keymaps, where `nil'
271+
stands for `global-map'
271272
:prefix KEY - prefix key for these bindings
272273
:prefix-map MAP - name of the prefix map that should be created
273274
for these bindings
@@ -290,7 +291,7 @@ Accepts keyword arguments:
290291
291292
The rest of the arguments are conses of keybinding string and a
292293
function symbol (unquoted)."
293-
(let (map
294+
(let (maps
294295
prefix-doc
295296
prefix-map
296297
prefix
@@ -307,20 +308,18 @@ function symbol (unquoted)."
307308
(while (and cont args)
308309
(if (cond ((and (eq :map (car args))
309310
(not prefix-map))
310-
(setq map (cadr args)))
311+
(setq maps
312+
(let ((arg (cadr args)))
313+
(if (consp arg) arg (list arg)))))
311314
((eq :prefix-docstring (car args))
312315
(setq prefix-doc (cadr args)))
313-
((and (eq :prefix-map (car args))
314-
(not (memq map '(global-map
315-
override-global-map))))
316-
(setq prefix-map (cadr args)))
316+
((eq :prefix-map (car args))
317+
(setq prefix-map (or (cadr args) 'global-map)))
317318
((eq :repeat-docstring (car args))
318319
(setq repeat-doc (cadr args)))
319-
((and (eq :repeat-map (car args))
320-
(not (memq map '(global-map
321-
override-global-map))))
322-
(setq repeat-map (cadr args))
323-
(setq map repeat-map))
320+
((eq :repeat-map (car args))
321+
(setq repeat-map (or (cadr args) 'global-map))
322+
(setq maps (list repeat-map)))
324323
((eq :continue (car args))
325324
(setq repeat-type :continue
326325
arg-change-func 'cdr))
@@ -342,14 +341,20 @@ function symbol (unquoted)."
342341
(and prefix (not prefix-map)))
343342
(error "Both :prefix-map and :prefix must be supplied"))
344343

344+
(when (memq prefix-map '(global-map override-global-map))
345+
(error "Invalid :prefix-map"))
346+
347+
(when (memq repeat-map '(global-map override-global-map))
348+
(error "Invalid :repeat-map"))
349+
345350
(when repeat-type
346351
(unless repeat-map
347352
(error ":continue and :exit require specifying :repeat-map")))
348353

349354
(when (and menu-name (not prefix))
350355
(error "If :menu-name is supplied, :prefix must be too"))
351356

352-
(unless map (setq map keymap))
357+
(setq maps (or maps keymaps (list nil)))
353358

354359
;; Process key binding arguments
355360
(let (first next)
@@ -381,40 +386,40 @@ function symbol (unquoted)."
381386
,@(if menu-name
382387
`((define-prefix-command ',prefix-map nil ,menu-name))
383388
`((define-prefix-command ',prefix-map)))
384-
,@(if (and map (not (eq map 'global-map)))
385-
(wrap map `((bind-key ,prefix ',prefix-map ,map ,filter)))
386-
`((bind-key ,prefix ',prefix-map nil ,filter)))))
389+
,@(cl-mapcan
390+
(lambda (map)
391+
(wrap map `((bind-key ,prefix ',prefix-map ,map ,filter))))
392+
maps)))
387393
(when repeat-map
388394
`((defvar ,repeat-map (make-sparse-keymap)
389395
,@(when repeat-doc `(,repeat-doc)))))
390-
(wrap map
391-
(cl-mapcan
392-
(lambda (form)
393-
(let ((fun (and (cdr form) (list 'function (cdr form)))))
394-
(if prefix-map
395-
`((bind-key ,(car form) ,fun ,prefix-map ,filter))
396-
(if (and map (not (eq map 'global-map)))
397-
;; Only needed in this branch, since when
398-
;; repeat-map is non-nil, map is always
399-
;; non-nil
400-
`(,@(when (and repeat-map (not (eq repeat-type :exit)))
401-
`((put ,fun 'repeat-map ',repeat-map)))
402-
(bind-key ,(car form) ,fun ,map ,filter))
403-
`((bind-key ,(car form) ,fun nil ,filter))))))
404-
first))
396+
(cl-mapcan
397+
(lambda (map)
398+
(wrap map
399+
(cl-mapcan
400+
(lambda (form)
401+
(let ((fun (and (cdr form) (list 'function (cdr form)))))
402+
(if prefix-map
403+
`((bind-key ,(car form) ,fun ,prefix-map ,filter))
404+
`(,@(when (and repeat-map (not (eq repeat-type :exit)))
405+
`((put ,fun 'repeat-map ',repeat-map)))
406+
(bind-key ,(car form) ,fun ,map ,filter)))))
407+
first)))
408+
maps)
405409
(when next
406410
(bind-keys-form `(,@(when repeat-map `(:repeat-map ,repeat-map))
407411
,@(if pkg
408412
(cons :package (cons pkg next))
409-
next)) map)))))))
413+
next))
414+
maps)))))))
410415

411416
;;;###autoload
412417
(defmacro bind-keys (&rest args)
413418
"Bind multiple keys at once.
414419
415420
Accepts keyword arguments:
416421
:map MAP - a keymap into which the keybindings should be
417-
added
422+
added, or a list of such keymaps
418423
:prefix KEY - prefix key for these bindings
419424
:prefix-map MAP - name of the prefix map that should be created
420425
for these bindings
@@ -446,7 +451,7 @@ Accepts the same keyword arguments as `bind-keys' (which see).
446451
447452
This binds keys in such a way that bindings are not overridden by
448453
other modes. See `override-global-mode'."
449-
(macroexp-progn (bind-keys-form args 'override-global-map)))
454+
(macroexp-progn (bind-keys-form args '(override-global-map))))
450455

451456
(defun get-binding-description (elem)
452457
(cond

use-package-bind-key.el

+7-6
Original file line numberDiff line numberDiff line change
@@ -92,19 +92,20 @@ deferred until the prefix key sequence is pressed."
9292
;; :prefix-docstring STRING
9393
;; :prefix-map SYMBOL
9494
;; :prefix STRING
95-
;; :repeat-docstring STRING
95+
;; :repeat-docstring STRING
9696
;; :repeat-map SYMBOL
9797
;; :filter SEXP
9898
;; :menu-name STRING
9999
;; :package SYMBOL
100-
;; :continue and :exit are used within :repeat-map
101-
((or (and (eq x :map) (symbolp (cadr arg)))
100+
;; :continue and :exit are used within :repeat-map
101+
((or (and (eq x :map) (or (symbolp (cadr arg))
102+
(listp (cadr arg))))
102103
(and (eq x :prefix) (stringp (cadr arg)))
103104
(and (eq x :prefix-map) (symbolp (cadr arg)))
104105
(and (eq x :prefix-docstring) (stringp (cadr arg)))
105-
(and (eq x :repeat-map) (symbolp (cadr arg)))
106-
(eq x :continue)
107-
(eq x :exit)
106+
(and (eq x :repeat-map) (symbolp (cadr arg)))
107+
(eq x :continue)
108+
(eq x :exit)
108109
(and (eq x :repeat-docstring) (stringp (cadr arg)))
109110
(eq x :filter)
110111
(and (eq x :menu-name) (stringp (cadr arg)))

use-package-tests.el

+129-7
Original file line numberDiff line numberDiff line change
@@ -1930,17 +1930,139 @@
19301930
(autoload #'nonexistent "nonexistent" nil t))
19311931
(add-hook 'lisp-mode-hook #'nonexistent)))))
19321932

1933-
(ert-deftest bind-key/:prefix-map ()
1933+
(ert-deftest bind-key-test/:map-1 ()
1934+
(match-expansion
1935+
(bind-keys
1936+
("C-1" . command-1)
1937+
("C-2" . command-2)
1938+
:map keymap-1
1939+
("C-3" . command-3)
1940+
("C-4" . command-4)
1941+
:map (keymap-2 keymap-3)
1942+
("C-5" . command-5)
1943+
("C-6" . command-6))
1944+
`(progn (bind-key "C-1" #'command-1 nil nil)
1945+
(bind-key "C-2" #'command-2 nil nil)
1946+
(bind-key "C-3" #'command-3 keymap-1 nil)
1947+
(bind-key "C-4" #'command-4 keymap-1 nil)
1948+
(bind-key "C-5" #'command-5 keymap-2 nil)
1949+
(bind-key "C-6" #'command-6 keymap-2 nil)
1950+
(bind-key "C-5" #'command-5 keymap-3 nil)
1951+
(bind-key "C-6" #'command-6 keymap-3 nil))))
1952+
1953+
(ert-deftest bind-key-test/:map-2 ()
1954+
(match-expansion
1955+
(bind-keys :package p
1956+
("C-1" . c1)
1957+
:map m1 ("C-2" . c2)
1958+
:map (m2 m3) ("C-3" . c3)
1959+
:map (nil m4) ("C-4" . c4)
1960+
:map (global-map m5) ("C-5" . c5))
1961+
`(progn (bind-key "C-1" #'c1 nil nil)
1962+
(if (boundp 'm1)
1963+
(bind-key "C-2" #'c2 m1 nil)
1964+
(eval-after-load 'p '(bind-key "C-2" #'c2 m1 nil)))
1965+
(if (boundp 'm2)
1966+
(bind-key "C-3" #'c3 m2 nil)
1967+
(eval-after-load 'p '(bind-key "C-3" #'c3 m2 nil)))
1968+
(if (boundp 'm3)
1969+
(bind-key "C-3" #'c3 m3 nil)
1970+
(eval-after-load 'p '(bind-key "C-3" #'c3 m3 nil)))
1971+
(bind-key "C-4" #'c4 nil nil)
1972+
(if (boundp 'm4)
1973+
(bind-key "C-4" #'c4 m4 nil)
1974+
(eval-after-load 'p '(bind-key "C-4" #'c4 m4 nil)))
1975+
(bind-key "C-5" #'c5 global-map nil)
1976+
(if (boundp 'm5)
1977+
(bind-key "C-5" #'c5 m5 nil)
1978+
(eval-after-load 'p '(bind-key "C-5" #'c5 m5 nil))))))
1979+
1980+
(ert-deftest bind-key-test/:map-3 ()
1981+
(should-error
1982+
(expand-minimally
1983+
(bind-keys :prefix "x" :prefix-map nil ("y" . x))))
1984+
(should-error
1985+
(expand-minimally
1986+
(bind-keys :prefix "x" :prefix-map global-map ("y" . x))))
1987+
(should-error
1988+
(expand-minimally
1989+
(bind-keys :prefix "x" :prefix-map override-global-map ("y" . x))))
1990+
(should-error
1991+
(expand-minimally (bind-keys :repeat-map nil ("y" . x))))
1992+
(should-error
1993+
(expand-minimally (bind-keys :repeat-map global-map ("y" . x))))
1994+
(should-error
1995+
(expand-minimally
1996+
(bind-keys :repeat-map override-global-map ("y" . x)))))
1997+
1998+
(ert-deftest bind-key-test/:prefix-map ()
19341999
(match-expansion
1935-
(bind-keys :prefix "<f1>"
1936-
:prefix-map my/map)
2000+
(bind-keys ("C-1" . command-1)
2001+
:prefix "<f1>"
2002+
:prefix-map my/map
2003+
("C-2" . command-2)
2004+
("C-3" . command-3))
19372005
`(progn
2006+
(bind-key "C-1" #'command-1 nil nil)
19382007
(defvar my/map)
19392008
(define-prefix-command 'my/map)
1940-
(bind-key "<f1>" 'my/map nil nil))))
1941-
1942-
1943-
(ert-deftest bind-key/845 ()
2009+
(bind-key "<f1>" 'my/map nil nil)
2010+
(bind-key "C-2" #'command-2 my/map nil)
2011+
(bind-key "C-3" #'command-3 my/map nil))))
2012+
2013+
(ert-deftest bind-key-test/:repeat-map-1 ()
2014+
;; NOTE: This test is pulled from the discussion in issue #964,
2015+
;; adjusting for the final syntax that was implemented.
2016+
(match-expansion
2017+
(bind-keys
2018+
("C-c n" . git-gutter+-next-hunk)
2019+
("C-c p" . git-gutter+-previous-hunk)
2020+
("C-c s" . git-gutter+-stage-hunks)
2021+
("C-c r" . git-gutter+-revert-hunk)
2022+
:repeat-map my/git-gutter+-repeat-map
2023+
("n" . git-gutter+-next-hunk)
2024+
("p" . git-gutter+-previous-hunk)
2025+
("s" . git-gutter+-stage-hunks)
2026+
("r" . git-gutter+-revert-hunk)
2027+
:repeat-docstring
2028+
"Keymap to repeat git-gutter+-* commands.")
2029+
`(progn
2030+
(bind-key "C-c n" #'git-gutter+-next-hunk nil nil)
2031+
(bind-key "C-c p" #'git-gutter+-previous-hunk nil nil)
2032+
(bind-key "C-c s" #'git-gutter+-stage-hunks nil nil)
2033+
(bind-key "C-c r" #'git-gutter+-revert-hunk nil nil)
2034+
(defvar my/git-gutter+-repeat-map (make-sparse-keymap))
2035+
(put #'git-gutter+-next-hunk 'repeat-map 'my/git-gutter+-repeat-map)
2036+
(bind-key "n" #'git-gutter+-next-hunk my/git-gutter+-repeat-map nil)
2037+
(put #'git-gutter+-previous-hunk 'repeat-map 'my/git-gutter+-repeat-map)
2038+
(bind-key "p" #'git-gutter+-previous-hunk my/git-gutter+-repeat-map nil)
2039+
(put #'git-gutter+-stage-hunks 'repeat-map 'my/git-gutter+-repeat-map)
2040+
(bind-key "s" #'git-gutter+-stage-hunks my/git-gutter+-repeat-map nil)
2041+
(put #'git-gutter+-revert-hunk 'repeat-map 'my/git-gutter+-repeat-map)
2042+
(bind-key "r" #'git-gutter+-revert-hunk my/git-gutter+-repeat-map nil)
2043+
(defvar my/git-gutter+-repeat-map (make-sparse-keymap) "Keymap to repeat git-gutter+-* commands."))))
2044+
2045+
(ert-deftest bind-key-test/:repeat-map-2 ()
2046+
(match-expansion
2047+
(bind-keys :map m ("x" . cmd1) :repeat-map rm ("y" . cmd2))
2048+
`(progn
2049+
(bind-key "x" #'cmd1 m nil)
2050+
(defvar rm (make-sparse-keymap))
2051+
(put #'cmd2 'repeat-map 'rm)
2052+
(bind-key "y" #'cmd2 rm nil))))
2053+
2054+
(ert-deftest bind-key-test/:repeat-map-3 ()
2055+
(match-expansion
2056+
(bind-keys :repeat-map rm ("y" . cmd2) :map m ("x" . cmd1))
2057+
`(progn
2058+
(defvar rm (make-sparse-keymap))
2059+
(put #'cmd2 'repeat-map 'rm)
2060+
(bind-key "y" #'cmd2 rm nil)
2061+
(defvar rm (make-sparse-keymap))
2062+
(put #'cmd1 'repeat-map 'rm)
2063+
(bind-key "x" #'cmd1 m nil))))
2064+
2065+
(ert-deftest bind-key-test/845 ()
19442066
(defvar test-map (make-keymap))
19452067
(bind-key "<f1>" 'ignore 'test-map)
19462068
(should (eq (lookup-key test-map (kbd "<f1>")) 'ignore))

use-package.texi

+8
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,14 @@ and then to bind the key @code{C-c h} to
906906
@code{helm-execute-persistent-action} within Helm's local keymap,
907907
@code{helm-command-map}.
908908

909+
Multiple keymaps can be specified as a list:
910+
911+
@lisp
912+
(use-package helm
913+
:bind (:map (lisp-mode-map emacs-lisp-mode-map)
914+
("C-c x" . eval-print-last-sexp)))
915+
@end lisp
916+
909917
Multiple uses of @code{:map} may be specified. Any binding occurring
910918
before the first use of @code{:map} are applied to the global keymap:
911919

0 commit comments

Comments
 (0)