emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

master e36d3fc 1/2: Support a new ["..."] key binding syntax


From: Lars Ingebrigtsen
Subject: master e36d3fc 1/2: Support a new ["..."] key binding syntax
Date: Sun, 17 Oct 2021 14:51:38 -0400 (EDT)

branch: master
commit e36d3fc452735d1a1a2293e18b8e4ef944f8793d
Author: Lars Ingebrigtsen <larsi@gnus.org>
Commit: Lars Ingebrigtsen <larsi@gnus.org>

    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.
---
 doc/lispref/keymaps.texi    | 79 ++++++++++++++++++++++++++++++++-------------
 etc/NEWS                    |  7 ++++
 lisp/emacs-lisp/byte-opt.el | 61 ++++++++++++++++++++++++++++++++++
 src/keymap.c                | 19 +++++++++++
 4 files changed, 143 insertions(+), 23 deletions(-)

diff --git a/doc/lispref/keymaps.texi b/doc/lispref/keymaps.texi
index 4277c71..899499e 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-<home>"]
+@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 fcc9b4a..d618891 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -174,6 +174,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
 calculate the keys dynamically.
diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el
index c8990f2..f4650de 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 5324f7f..60e736e 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");
 }



reply via email to

[Prev in Thread] Current Thread [Next in Thread]