From: Lars Ingebrigtsen Date: Sun, 17 Oct 2021 18:48:01 +0000 (+0200) Subject: Support a new ["..."] key binding syntax X-Git-Tag: emacs-29.0.90~3671^2~531 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=e36d3fc452735d1a1a2293e18b8e4ef944f8793d;p=emacs.git Support a new ["..."] key binding syntax * doc/lispref/keymaps.texi (Key Sequences): (Changing Key Bindings): Document the various key syntaxes. * lisp/emacs-lisp/byte-opt.el (byte-optimize-define-key) (byte-optimize-define-keymap) (byte-optimize-define-keymap--define): New functions to check and expand ["..."] syntax at compile time. * src/keymap.c (Fdefine_key): Understand the ["..."] syntax. (syms_of_keymap): Define `kbd' symbols. --- diff --git a/doc/lispref/keymaps.texi b/doc/lispref/keymaps.texi index 4277c718fea..899499ed46e 100644 --- a/doc/lispref/keymaps.texi +++ b/doc/lispref/keymaps.texi @@ -100,6 +100,16 @@ The @code{kbd} function is very permissive, and will try to return something sensible even if the syntax used isn't completely conforming. To check whether the syntax is actually valid, use the @code{kbd-valid-p} function. + +@code{define-key} also supports using the shorthand syntax +@samp{["..."]} syntax to define a key. The string has to be a +strictly valid @code{kbd} sequence, and if it's not valid, an error +will be signalled. For instance, to bind @key{C-c f}, you can say: + +@lisp +(define-key global-map ["C-c f"] #'find-file-literally) +@end lisp + @end defun @@ -1285,24 +1295,46 @@ Binding Conventions}). @cindex meta character key constants @cindex control character key constants - In writing the key sequence to rebind, it is good to use the special -escape sequences for control and meta characters (@pxref{String Type}). -The syntax @samp{\C-} means that the following character is a control -character and @samp{\M-} means that the following character is a meta -character. Thus, the string @code{"\M-x"} is read as containing a -single @kbd{M-x}, @code{"\C-f"} is read as containing a single -@kbd{C-f}, and @code{"\M-\C-x"} and @code{"\C-\M-x"} are both read as -containing a single @kbd{C-M-x}. You can also use this escape syntax in -vectors, as well as others that aren't allowed in strings; one example -is @samp{[?\C-\H-x home]}. @xref{Character Type}. - - The key definition and lookup functions accept an alternate syntax for -event types in a key sequence that is a vector: you can use a list -containing modifier names plus one base event (a character or function -key name). For example, @code{(control ?a)} is equivalent to -@code{?\C-a} and @code{(hyper control left)} is equivalent to -@code{C-H-left}. One advantage of such lists is that the precise -numeric codes for the modifier bits don't appear in compiled files. + @code{define-key} (and other functions that are used to rebind keys) +understand a number of different syntaxes for the keys. + +@table @asis +@item A vector containing a single string. +This is the preferred way to represent a key sequence. Here's a +couple of examples: + +@example +["C-c M-f"] +["S-"] +@end example + +The syntax is the same as the one used by Emacs when displaying key +bindings, for instance in @samp{*Help*} buffers and help texts. + +If the syntax isn't valid, an error will be raised when running +@code{define-key}, or when byte-compiling code that has these calls. + +@item A vector containing lists of keys. +You can use a list containing modifier names plus one base event (a +character or function key name). For example, @code{[(control ?a) +(meta b)]} is equivalent to @kbd{C-a M-b} and @code{[(hyper control +left)]} is equivalent to @kbd{C-H-left}. + +@item A string with control and meta characters. +Internally, key sequences are often represented as strings using the +special escape sequences for control and meta characters +(@pxref{String Type}), but this representation can also be used by +users when rebinding keys. A string like @code{"\M-x"} is read as +containing a single @kbd{M-x}, @code{"\C-f"} is read as containing a +single @kbd{C-f}, and @code{"\M-\C-x"} and @code{"\C-\M-x"} are both +read as containing a single @kbd{C-M-x}. + +@item a vector of characters. +This is the other internal representation of key sequences, and +supports a fuller range of modifiers than the string representation. +One example is @samp{[?\C-\H-x home]}, which represents the @kbd{C-H-x +home} key sequence. @xref{Character Type}. +@end table The functions below signal an error if @var{keymap} is not a keymap, or if @var{key} is not a string or vector representing a key sequence. @@ -1344,7 +1376,7 @@ bindings in it: @result{} (keymap) @end group @group -(define-key map "\C-f" 'forward-char) +(define-key map ["C-f"] 'forward-char) @result{} forward-char @end group @group @@ -1354,7 +1386,7 @@ map @group ;; @r{Build sparse submap for @kbd{C-x} and bind @kbd{f} in that.} -(define-key map (kbd "C-x f") 'forward-word) +(define-key map ["C-x f"] 'forward-word) @result{} forward-word @end group @group @@ -1367,14 +1399,14 @@ map @group ;; @r{Bind @kbd{C-p} to the @code{ctl-x-map}.} -(define-key map (kbd "C-p") ctl-x-map) +(define-key map ["C-p"] ctl-x-map) ;; @code{ctl-x-map} @result{} [nil @dots{} find-file @dots{} backward-kill-sentence] @end group @group ;; @r{Bind @kbd{C-f} to @code{foo} in the @code{ctl-x-map}.} -(define-key map (kbd "C-p C-f") 'foo) +(define-key map ["C-p C-f"] 'foo) @result{} 'foo @end group @group @@ -1404,7 +1436,8 @@ keys. Here's a very basic example: @lisp (define-keymap "n" #'forward-line - "f" #'previous-line) + "f" #'previous-line + ["C-c C-c"] #'quit-window) @end lisp This function creates a new sparse keymap, defines the two keystrokes diff --git a/etc/NEWS b/etc/NEWS index fcc9b4ad32f..d6188919155 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -173,6 +173,13 @@ with recent versions of Firefox. * Lisp Changes in Emacs 29.1 ++++ +** 'define-key' now understands a new strict 'kbd' representation for keys. +The (define-key map ["C-c M-f"] #'some-command) syntax is now +supported, and is like the 'kbd' representation, but is stricter. If +the string doesn't represent a valid key sequence, an error is +signalled (both when evaluating and byte compiling). + +++ ** :keys in 'menu-item' can now be a function. If so, it is called whenever the menu is computed, and can be used to diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index c8990f23531..f4650de5e05 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el @@ -1186,6 +1186,67 @@ See Info node `(elisp) Integer Basics'." (put 'concat 'byte-optimizer #'byte-optimize-concat) +(defun byte-optimize-define-key (form) + "Expand key bindings in FORM." + (let ((key (nth 2 form))) + (if (and (vectorp key) + (= (length key) 1) + (stringp (aref key 0))) + ;; We have key on the form ["C-c C-c"]. + (if (not (kbd-valid-p (aref key 0))) + (error "Invalid `kbd' syntax: %S" key) + (list (nth 0 form) (nth 1 form) + (kbd (aref key 0)) (nth 4 form))) + ;; No improvement. + form))) + +(put 'define-key 'byte-optimizer #'byte-optimize-define-key) + +(defun byte-optimize-define-keymap (form) + "Expand key bindings in FORM." + (let ((result nil) + (orig-form form) + improved) + (push (pop form) result) + (while (and form + (keywordp (car form)) + (not (eq (car form) :menu))) + (push (pop form) result) + (when (null form) + (error "Uneven number of keywords in %S" form)) + (push (pop form) result)) + ;; Bindings. + (while form + (let ((key (pop form))) + (if (and (vectorp key) + (= (length key) 1) + (stringp (aref key 0))) + (progn + (unless (kbd-valid-p (aref key 0)) + (error "Invalid `kbd' syntax: %S" key)) + (push (kbd (aref key 0)) result) + (setq improved t)) + ;; No improvement. + (push key result))) + (when (null form) + (error "Uneven number of key bindings in %S" form)) + (push (pop form) result)) + (if improved + (nreverse result) + orig-form))) + +(defun byte-optimize-define-keymap--define (form) + "Expand key bindings in FORM." + (let ((optimized (byte-optimize-define-keymap (nth 1 form)))) + (if (eq optimized (nth 1 form)) + ;; No improvement. + form + (list (car form) optimized)))) + +(put 'define-keymap 'byte-optimizer #'byte-optimize-define-keymap) +(put 'define-keymap--define 'byte-optimizer + #'byte-optimize-define-keymap--define) + ;; I'm not convinced that this is necessary. Doesn't the optimizer loop ;; take care of this? - Jamie ;; I think this may some times be necessary to reduce ie (quote 5) to 5, diff --git a/src/keymap.c b/src/keymap.c index 5324f7f0213..60e736efc71 100644 --- a/src/keymap.c +++ b/src/keymap.c @@ -1084,6 +1084,22 @@ binding KEY to DEF is added at the front of KEYMAP. */) def = tmp; } + if (VECTORP (key) && ASIZE (key) == 1 && STRINGP (AREF (key, 0))) + { + /* KEY is on the ["C-c"] format, so translate to internal + format. */ + if (NILP (Ffboundp (Qkbd_valid_p))) + xsignal2 (Qerror, + build_string ("`kbd-valid-p' is not defined, so this syntax can't be used: %s"), + key); + if (NILP (call1 (Qkbd_valid_p, AREF (key, 0)))) + xsignal2 (Qerror, build_string ("Invalid `kbd' syntax: %S"), key); + key = call1 (Qkbd, AREF (key, 0)); + length = CHECK_VECTOR_OR_STRING (key); + if (length == 0) + xsignal2 (Qerror, build_string ("Invalid `kbd' syntax: %S"), key); + } + ptrdiff_t idx = 0; while (1) { @@ -3263,4 +3279,7 @@ that describe key bindings. That is why the default is nil. */); defsubr (&Stext_char_description); defsubr (&Swhere_is_internal); defsubr (&Sdescribe_buffer_bindings); + + DEFSYM (Qkbd, "kbd"); + DEFSYM (Qkbd_valid_p, "kbd-valid-p"); }