changeset 5679:a81a739181dc

Add command remapping, a more robust alternative to #'substitute-key-definition src/ChangeLog addition: 2012-09-02 Aidan Kehoe <kehoea@parhasard.net> * keymap.c: Add command remapping, a more robust equivalent to #'substitute-key-definition. * keymap.c (CHECK_REMAPPING_POSITION): New. * keymap.c (keymap_equal): Correct a comment here. * keymap.c (Fdefine_key): Document the command remapping syntax. * keymap.c (Fremap_command): New. * keymap.c (command_remapping): New. * keymap.c (Fcommand_remapping): New. * keymap.c (commands_remapped_to_mapper): New. * keymap.c (commands_remapped_to_traverser): New. * keymap.c (Fcommands_remapped_to): New. * keymap.c (get_relevant_keymaps): Take a new POSITION argument. * keymap.c (Fcurrent_keymaps, event_binding): Supply the new POSITION argument to get_relevant_keymaps. * keymap.c (Fkey_binding): Add new arguments, NO-REMAP and POSITION. * keymap.c (map_keymap_mapper): * keymap.c (Fwhere_is_internal): * keymap.c (where_is_to_char): * keymap.c (where_is_recursive_mapper): Don't expose the key remapping in these functions. This conflicts with GNU, but is more sane for our callers. Access to command remapping is with the functions #'command-remapping, #'commands-remapped-to, and #'remap-command, not with the general keymap functions, apart from the compatibility hack in #'define-key. * keymap.c (syms_of_keymap): * keymap.c (vars_of_keymap): * keymap.c (complex_vars_of_keymap): * lisp.h: New CHECK_COMMAND macro. man/ChangeLog addition: 2012-09-02 Aidan Kehoe <kehoea@parhasard.net> * lispref/keymaps.texi (Keymaps): * lispref/keymaps.texi (Changing Key Bindings): * lispref/keymaps.texi (Scanning Keymaps): * lispref/keymaps.texi (Remapping commands): * lispref/keymaps.texi (XEmacs): New. * lispref/keymaps.texi (Other Keymap Functions): Document the new command remapping functionality in this file. lisp/ChangeLog addition: 2012-09-02 Aidan Kehoe <kehoea@parhasard.net> * help.el (describe-function-1): Document any command remapping that has been done in this function. tests/ChangeLog addition: 2012-09-02 Aidan Kehoe <kehoea@parhasard.net> * automated/keymap-tests.el: Test the new command remapping functionality.
author Aidan Kehoe <kehoea@parhasard.net>
date Sun, 02 Sep 2012 14:31:40 +0100
parents b0d40183ac79
children 8a2ac78cb97d
files lisp/ChangeLog lisp/help.el man/ChangeLog man/lispref/keymaps.texi src/ChangeLog src/keymap.c src/lisp.h tests/ChangeLog tests/automated/keymap-tests.el
diffstat 9 files changed, 540 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/lisp/ChangeLog	Sun Aug 12 11:32:36 2012 +0100
+++ b/lisp/ChangeLog	Sun Sep 02 14:31:40 2012 +0100
@@ -2,6 +2,11 @@
 
 	* XEmacs 21.5.32 "habanero" is released.
 
+2012-09-02  Aidan Kehoe  <kehoea@parhasard.net>
+
+	* help.el (describe-function-1):
+	Document any command remapping that has been done in this function.
+
 2012-05-14  Aidan Kehoe  <kehoea@parhasard.net>
 
 	* byte-optimize.el (byte-optimize-letX):
--- a/lisp/help.el	Sun Aug 12 11:32:36 2012 +0100
+++ b/lisp/help.el	Sun Sep 02 14:31:40 2012 +0100
@@ -1497,7 +1497,9 @@
 		       (global-tty-binding 
 			(where-is-internal function global-tty-map))
 		       (global-window-system-binding 
-			(where-is-internal function global-window-system-map)))
+			(where-is-internal function global-window-system-map))
+                       (command-remapping (command-remapping function))
+                       (commands-remapped-to (commands-remapped-to function)))
                    (if (or global-binding global-tty-binding
                            global-window-system-binding)
                        (if (and (equal global-binding
@@ -1531,11 +1533,23 @@
                              "\n%s\n        -- generally (that is, unless\
  overridden by TTY- or
            window-system-specific mappings)\n"
-                             (mapconcat #'key-description
-                                        global-binding
+                             (mapconcat #'key-description global-binding
                                         ", ")))))
-                     (princ (substitute-command-keys
-                             (format "\n\\[%s]" function))))))))))))
+                       (if command-remapping
+                           (progn
+                             (princ "Its keys are remapped to `")
+                             (princ (symbol-name command-remapping))
+                             (princ "'.\n"))
+                           (princ (substitute-command-keys
+                                   (format "\n\\[%s]" function))))
+                       (when commands-remapped-to
+                         (if (cdr commands-remapped-to)
+                             (princ (format "The following functions are \
+remapped to it:\n`%s'" (mapconcat #'prin1-to-string commands-remapped-to
+                                  "', `")))
+                           (princ (format "`%s' is remapped to it.\n"
+                                          (car
+                                           commands-remapped-to))))))))))))))
 
 ;;; [Obnoxious, whining people who complain very LOUDLY on Usenet
 ;;; are binding this to keys.]
--- a/man/ChangeLog	Sun Aug 12 11:32:36 2012 +0100
+++ b/man/ChangeLog	Sun Sep 02 14:31:40 2012 +0100
@@ -8,6 +8,16 @@
 
 	* XEmacs 21.5.32 "habanero" is released.
 
+2012-09-02  Aidan Kehoe  <kehoea@parhasard.net>
+
+	* lispref/keymaps.texi (Keymaps):
+	* lispref/keymaps.texi (Changing Key Bindings):
+	* lispref/keymaps.texi (Scanning Keymaps):
+	* lispref/keymaps.texi (Remapping commands):
+	* lispref/keymaps.texi (XEmacs): New.
+	* lispref/keymaps.texi (Other Keymap Functions):
+	Document the new command remapping functionality in this file.
+
 2012-05-06  Aidan Kehoe  <kehoea@parhasard.net>
 
 	* lispref/macros.texi (Expansion):
--- a/man/lispref/keymaps.texi	Sun Aug 12 11:32:36 2012 +0100
+++ b/man/lispref/keymaps.texi	Sun Sep 02 14:31:40 2012 +0100
@@ -33,6 +33,8 @@
 * Changing Key Bindings::    Redefining a key in a keymap.
 * Key Binding Commands::     Interactive interfaces for redefining keys.
 * Scanning Keymaps::         Looking through all keymaps, for printing help.
+* Remapping commands::       Specifying that one command should override
+                                another.
 * Other Keymap Functions::   Miscellaneous keymap functions.
 @end menu
 
@@ -1168,7 +1170,8 @@
 @var{olddef} is replaced with @var{newdef} wherever it appears.  Prefix
 keymaps are checked recursively.
 
-The function returns @code{nil}.
+The function returns @code{nil}.  @pxref{Remapping commands} for a more
+robust way of doing the same thing.
 
 For example, this redefines @kbd{C-x C-f}, if you do it in an XEmacs with
 standard bindings:
@@ -1581,6 +1584,37 @@
 displayed.
 @end deffn
 
+@node Remapping commands
+@section Remapping commands
+
+This section describes some functionality to allow commands to be
+remapped, e.g. when providing workalike commands.
+
+@defun remap-command keymap old new
+This function ensures that in @var{keymap} any command lookups that
+would previously have given @var{old} now give @var{new}.  This is
+equivalent to the following GNU-compatible code, which also works in
+XEmacs:
+
+@smallexample
+(define-key KEYMAP [remap OLD] NEW)
+@end smallexample
+@end defun
+
+@defun command-remapping command &optional position keymaps
+If @var{command} has a remapping in @var{keymaps}, this function returns
+that remapping.  Otherwise it returns @var{nil}.  @var{keymaps} defaults
+to the currently active keymaps. @var{position} specifies the relevant buffer
+position where keymaps should be searched for, and overrides
+@var{keymaps}.  It can also be a marker or an event.
+@end defun
+
+@defun commands-remapped-to command &optional position keymaps
+This is the inverse operation of @code{command-remapping}; it returns a
+list of the commands that will never be executed in @var{keymaps}
+because @var{command} will be execute instead.
+@end defun
+
 @node Other Keymap Functions
 @section Other Keymap Functions
 
--- a/src/ChangeLog	Sun Aug 12 11:32:36 2012 +0100
+++ b/src/ChangeLog	Sun Sep 02 14:31:40 2012 +0100
@@ -21,6 +21,41 @@
 
 	* XEmacs 21.5.32 "habanero" is released.
 
+2012-09-02  Aidan Kehoe  <kehoea@parhasard.net>
+
+	* keymap.c:
+	Add command remapping, a more robust equivalent to
+	#'substitute-key-definition.
+	* keymap.c (CHECK_REMAPPING_POSITION): New.
+	* keymap.c (keymap_equal): Correct a comment here.
+	* keymap.c (Fdefine_key): Document the command remapping syntax.
+	* keymap.c (Fremap_command): New.
+	* keymap.c (command_remapping): New.
+	* keymap.c (Fcommand_remapping): New.
+	* keymap.c (commands_remapped_to_mapper): New.
+	* keymap.c (commands_remapped_to_traverser): New.
+	* keymap.c (Fcommands_remapped_to): New.
+	* keymap.c (get_relevant_keymaps): Take a new POSITION argument.
+	* keymap.c (Fcurrent_keymaps, event_binding):
+	Supply the new POSITION argument to get_relevant_keymaps.
+	* keymap.c (Fkey_binding):
+	Add new arguments, NO-REMAP and POSITION.
+
+	* keymap.c (map_keymap_mapper):
+	* keymap.c (Fwhere_is_internal):
+	* keymap.c (where_is_to_char):
+	* keymap.c (where_is_recursive_mapper):
+	Don't expose the key remapping in these functions. This conflicts
+	with GNU, but is more sane for our callers. Access to command
+	remapping is with the functions #'command-remapping,
+	#'commands-remapped-to, and #'remap-command, not with the general
+	keymap functions, apart from the compatibility hack in #'define-key.
+
+	* keymap.c (syms_of_keymap):
+	* keymap.c (vars_of_keymap):
+	* keymap.c (complex_vars_of_keymap):
+	* lisp.h: New CHECK_COMMAND macro.
+
 2012-05-14  Aidan Kehoe  <kehoea@parhasard.net>
 
 	* minibuf.c (Ftest_completion):
--- a/src/keymap.c	Sun Aug 12 11:32:36 2012 +0100
+++ b/src/keymap.c	Sun Sep 02 14:31:40 2012 +0100
@@ -188,9 +188,14 @@
 Lisp_Object Qmodeline_map;
 Lisp_Object Qtoolbar_map;
 
+Lisp_Object Qremap;
+Lisp_Object Qxemacs_command_remapping; /* Uninterned, so there's no conflict
+                                          with any key named remap. */
+
 EXFUN (Fkeymap_fullness, 1);
 EXFUN (Fset_keymap_name, 2);
 EXFUN (Fsingle_key_description, 1);
+EXFUN (Fremap_command, 3);
 
 static void describe_command (Lisp_Object definition, Lisp_Object buffer);
 static void describe_map (Lisp_Object keymap, Lisp_Object elt_prefix,
@@ -200,6 +205,14 @@
 			  int mice_only_p,
 			  Lisp_Object buffer);
 static Lisp_Object keymap_submaps (Lisp_Object keymap);
+static int get_relevant_keymaps (Lisp_Object, Lisp_Object, int,
+                                 Lisp_Object maps[]);
+static Lisp_Object lookup_keys (Lisp_Object, int, Lisp_Object *, int);
+static void map_keymap (Lisp_Object keymap_table, int sort_first,
+                        void (*function) (const Lisp_Key_Data *key,
+                                          Lisp_Object binding,
+                                          void *fn_arg),
+                        void *fn_arg);
 
 Lisp_Object Qcontrol, Qctrl, Qmeta, Qsuper, Qhyper, Qalt, Qshift;
 
@@ -220,17 +233,21 @@
 /* Kludge kludge kludge */
 Lisp_Object QLFD, QTAB, QRET, QESC, QDEL, QSPC, QBS;
 
+#define CHECK_REMAPPING_POSITION(object)        do                      \
+    {                                                                   \
+      if (!(NILP (object) || FIXNUMP (object) || MARKERP (object)       \
+            || EVENTP (object)))                                        \
+        {                                                               \
+          wtaerror ("Not a valid POSITION", object);                    \
+        }                                                               \
+    } while (0)
+
 
 /************************************************************************/
 /*                     The keymap Lisp object                           */
 /************************************************************************/
 
-/* Keymaps are equal if Faces are equal if all of their display attributes are equal.  We
-   don't compare names or doc-strings, because that would make equal
-   be eq.
-
-   This isn't concerned with "unspecified" attributes, that's what
-   #'face-differs-from-default-p is for. */
+/* Keymaps are equal if all of their attributes are equal. */
 static int
 keymap_equal (Lisp_Object obj1, Lisp_Object obj2, int depth,
 	      int UNUSED (foldcase))
@@ -1826,6 +1843,12 @@
 `define-key' also accepts a number of abbreviations, aliases, and variants
 for convenience, compatibility, and internal use.
 
+A key sequence can also be the vector [remap COMMAND]; this shadows any
+bindings for COMMAND in KEYMAP, using DEF instead of COMMAND.  See
+`command-remapping' and `remap-command'.  Specify [(remap) KEYSTROKE] if
+your keyboard has a key with the name `remap' and you'd like to use it as a
+prefix.
+
 A keystroke may be represented by a key; this is treated as though it were a
 list containing that key as the only element.  A keystroke may also be
 represented by an event object, as returned by the `next-command-event' and
@@ -1916,6 +1939,12 @@
 
   GCPRO3 (keymap, keys, def);
 
+  /* Allow access to any keys named remap, use our uninterned symbol. */
+  if (2 == len && VECTORP (keys) && EQ (Qremap, XVECTOR_DATA (keys) [0]))
+    {
+      return Fremap_command (keymap, XVECTOR_DATA (keys) [1], def);
+    }
+
   /* ASCII grunge.
      When the user defines a key which, in a strictly ASCII world, would be
      produced by two different keys (^J and linefeed, or ^H and backspace,
@@ -2036,7 +2065,206 @@
       }
     }
 }
-
+
+DEFUN ("remap-command", Fremap_command, 3, 3, 0, /*
+Ensure that NEW is called when previously OLD would be, in KEYMAP.
+
+NEW and OLD are both command symbols.  KEYMAP is a keymap object.
+
+This is equivalent to `(define-key KEYMAP [remap OLD] NEW])'.  See also
+`substitute-key-definition', an older way of doing a similar thing.
+*/
+       (keymap, old, new_))
+{
+  Lisp_Object cmd;
+  Lisp_Key_Data parsed;
+
+  keymap = get_keymap (keymap, 1, 1);
+  CHECK_COMMAND (old);
+  CHECK_COMMAND (new_);
+
+  define_key_parser (Qxemacs_command_remapping, &parsed);
+  cmd = keymap_lookup_1 (keymap, &parsed, 0);
+  if (NILP (cmd))
+    {
+      cmd = Fmake_sparse_keymap (Qnil);
+      XKEYMAP (cmd)->name /* for debugging */
+        = list2 (make_key_description (&parsed, 1), keymap);
+      keymap_store (keymap, &parsed, cmd);
+    }
+
+  assert (!NILP (Fkeymapp (cmd)));
+  define_key_parser (old, &parsed);
+  keymap_store (cmd, &parsed, new_);
+  return new_;
+}
+
+static Lisp_Object
+command_remapping (Lisp_Object definition, int nmaps, Lisp_Object *maps)
+{
+  Lisp_Object remapping = Qnil;
+  Lisp_Object keys[2] = { Qxemacs_command_remapping, definition };
+  int jj;
+
+  for (jj = 0; jj < nmaps; jj++)
+    {
+      remapping = lookup_keys (maps[jj], countof (keys), keys, 0);
+      if (!NILP (remapping) && !FIXNUMP (remapping))
+        {
+          return remapping;
+        }
+    }
+
+  return Qnil;
+}
+
+DEFUN ("command-remapping", Fcommand_remapping, 1, 3, 0, /*
+Return the remapping for command COMMAND.
+
+Return nil if COMMAND is not remapped (or not a symbol).  The remapping is
+the command that is executed when some key sequence in the relevant keymaps
+would normally execute COMMAND, but this has been intercepted by
+`remap-command' or the [remap COMMAND] syntax for KEYS in `define-key'.
+
+If the optional argument POSITION is non-nil, it specifies an event, and the
+remapping occurs in the keymaps associated with it.  It can also be a number
+or marker, in which case the keymap properties at the specified buffer
+position instead of point are used.  The KEYMAPS argument is ignored if
+POSITION is non-nil.
+
+If the optional argument KEYMAPS is non-nil, it should be a list of
+keymaps to search for command remapping.  Otherwise, search for the
+remapping in all currently active keymaps.
+*/
+       (command, position, keymaps))
+{
+  Lisp_Object maps[100];
+  Lisp_Object *gubbish = maps;
+  int nmaps, maps_count = countof (maps);
+
+  CHECK_COMMAND (command);
+  CHECK_LIST (keymaps);
+  CHECK_REMAPPING_POSITION (position);
+
+  /* Get keymaps as an array */
+  if (NILP (keymaps) || !NILP (position))
+    {
+      nmaps = get_relevant_keymaps (Qnil, position, maps_count, gubbish);
+    }
+  else
+    {
+      Elemcount jj = 0;
+      nmaps = XFIXNUM (Flength (keymaps));
+      if (nmaps > maps_count)
+        {
+          gubbish = alloca_array (Lisp_Object, nmaps);
+        }
+
+      {
+        LIST_LOOP_2 (elt, keymaps)
+          {
+            gubbish[jj++] = elt;
+          }
+      }
+    }
+
+  if (nmaps > maps_count)
+    {
+      gubbish = alloca_array (Lisp_Object, nmaps);
+      nmaps = get_relevant_keymaps (Qnil, position, nmaps, gubbish);
+    }
+
+  return command_remapping (command, nmaps, gubbish);
+}
+
+struct commands_remapped_to_closure
+{
+  Lisp_Object command;
+  Lisp_Object result;
+};
+
+static void
+commands_remapped_to_mapper (const Lisp_Key_Data *key, Lisp_Object binding,
+                             void *data)
+{
+  struct commands_remapped_to_closure *crtc
+    = (struct commands_remapped_to_closure *) data;
+
+  if (EQ (binding, crtc->command))
+    {
+      crtc->result = Fcons (key->keysym, crtc->result);
+    }
+}
+
+static Lisp_Object
+commands_remapped_to_traverser (Lisp_Object k, void *arg)
+{
+  Lisp_Object remapping
+    = lookup_keys (k, 1, &Qxemacs_command_remapping, 0);
+  if (KEYMAPP (remapping))
+    {
+      map_keymap (XKEYMAP (remapping)->table, 0, commands_remapped_to_mapper,
+                  arg);
+    }
+
+  return Qnil;
+}
+
+DEFUN ("commands-remapped-to", Fcommands_remapped_to, 1, 3, 0, /*
+Return a list of symbols for which COMMAND is their remapping in KEYMAPS.
+
+This is the inverse operation of `command-remapping', which see.
+*/
+       (command, keymaps, position))
+{
+  Lisp_Object maps[100];
+  Lisp_Object *gubbish = maps;
+  int nmaps, maps_count = countof (maps), jj;
+  struct commands_remapped_to_closure closure = { command, Qnil };
+  struct gcpro gcpro1;
+  
+  CHECK_COMMAND (command);
+  CHECK_LIST (keymaps);
+  CHECK_REMAPPING_POSITION (position);
+
+  /* Get keymaps as an array */
+  if (NILP (keymaps) || !NILP (position))
+    {
+      nmaps = get_relevant_keymaps (Qnil, position, maps_count, gubbish);
+    }
+  else
+    {
+      jj = 0;
+      nmaps = XFIXNUM (Flength (keymaps));
+      if (nmaps > maps_count)
+        {
+          gubbish = alloca_array (Lisp_Object, nmaps);
+        }
+
+      {
+        LIST_LOOP_2 (elt, keymaps)
+          {
+            gubbish[jj++] = elt;
+          }
+      }
+    }
+
+  if (nmaps > maps_count)
+    {
+      gubbish = alloca_array (Lisp_Object, nmaps);
+      nmaps = get_relevant_keymaps (Qnil, position, nmaps, gubbish);
+    }
+
+  GCPRO1 (closure.result);
+  
+  for (jj = 0; jj < nmaps; jj++)
+    {
+      traverse_keymaps (maps[jj], Qnil, commands_remapped_to_traverser,
+                        (void *) (&closure));
+    }
+
+  RETURN_UNGCPRO (closure.result);
+}
 
 /************************************************************************/
 /*                      Looking up keys in keymaps                      */
@@ -2318,8 +2546,8 @@
 }
 
 static int
-get_relevant_keymaps (Lisp_Object keys,
-                      int max_maps, Lisp_Object maps[])
+get_relevant_keymaps (Lisp_Object keys, Lisp_Object position, int max_maps,
+                      Lisp_Object maps[])
 {
   /* This function can GC */
   Lisp_Object terminal = Qnil;
@@ -2464,6 +2692,31 @@
     }
 #endif /* HAVE_WINDOW_SYSTEM */
 
+  if (FIXNUMP (position))
+    {
+      get_relevant_extent_keymaps (position, wrap_buffer (current_buffer),
+                                   Qnil, &closure);
+    }
+  else if (MARKERP (position) && !NILP (Fmarker_buffer (position)))
+    {
+      get_relevant_extent_keymaps (Fmarker_position (position),
+                                   Fmarker_buffer (position),
+                                   Qnil, &closure);
+    }
+  else if (EVENTP (position))
+    {
+      Lisp_Object ew = Fevent_window (position);
+
+      get_relevant_extent_keymaps (Fevent_point (position),
+                                   WINDOWP (ew) ?
+                                   Fwindow_buffer (Fevent_window (position))
+                                   : Qnil, Qnil, &closure);
+    }
+  else
+    {
+      assert (NILP (position));
+    }
+
   if (CONSOLE_TTY_P (con))
     relevant_map_push (Vglobal_tty_map, &closure);
   else
@@ -2580,23 +2833,34 @@
   int nmaps;
 
   GCPRO1 (event_or_keys);
-  nmaps = get_relevant_keymaps (event_or_keys, countof (maps),
+  nmaps = get_relevant_keymaps (event_or_keys, Qnil, countof (maps),
 				gubbish);
   if (nmaps > countof (maps))
     {
       gubbish = alloca_array (Lisp_Object, nmaps);
-      nmaps = get_relevant_keymaps (event_or_keys, nmaps, gubbish);
+      nmaps = get_relevant_keymaps (event_or_keys, Qnil, nmaps, gubbish);
     }
   UNGCPRO;
   return Flist (nmaps, gubbish);
 }
 
-DEFUN ("key-binding", Fkey_binding, 1, 2, 0, /*
+DEFUN ("key-binding", Fkey_binding, 1, 4, 0, /*
 Return the binding for command KEYS in current keymaps.
+
 KEYS is a string, a vector of events, or a vector of key-description lists
 as described in the documentation for the `define-key' function.
-The binding is probably a symbol with a function definition; see
-the documentation for `lookup-key' for more information.
+
+NO-REMAP, if non-nil, specifies that any substitutions that have been
+specified by `remap-command' (or, equivalently, by `(define-key KEYMAP
+\[remap OLD] NEW)') should be ignored.
+
+POSITION, if non-nil, specifies a marker (and its associated buffer) or an
+integer position (in the current buffer) to examine for relevant keymaps.
+It can also be an event, in which case the associated buffer and position of
+that event will be used.
+
+The binding is probably a symbol with a function definition; see the
+documentation for `lookup-key' for more information.
 
 For key-presses, the order of keymaps searched is:
   - the `keymap' property of any extent(s) at point;
@@ -2650,7 +2914,7 @@
    generate and display a list of possible key sequences and bindings
    given the prefix so far generated.
 */
-       (keys, accept_default))
+       (keys, accept_default, no_remap, position))
 {
   /* This function can GC */
   int i;
@@ -2659,7 +2923,7 @@
   struct gcpro gcpro1, gcpro2;
   GCPRO2 (keys, accept_default); /* get_relevant_keymaps may autoload */
 
-  nmaps = get_relevant_keymaps (keys, countof (maps), maps);
+  nmaps = get_relevant_keymaps (keys, position, countof (maps), maps);
 
   UNGCPRO;
 
@@ -2668,15 +2932,19 @@
 
   for (i = 0; i < nmaps; i++)
     {
-      Lisp_Object tem = Flookup_key (maps[i], keys,
-				     accept_default);
+      Lisp_Object tem = Flookup_key (maps[i], keys, accept_default);
+
       if (FIXNUMP (tem))
 	{
 	  /* Too long in some local map means don't look at global map */
 	  return Qnil;
 	}
-      else if (!NILP (tem))
-	return tem;
+
+      if (!NILP (tem) && NILP (no_remap) && SYMBOLP (tem))
+        {
+          Lisp_Object remap = command_remapping (tem, nmaps, maps);
+          return NILP (remap) ? tem : remap;
+        }
     }
   return Qnil;
 }
@@ -2724,7 +2992,7 @@
 
   assert (EVENTP (event0));
 
-  nmaps = get_relevant_keymaps (event0, countof (maps), maps);
+  nmaps = get_relevant_keymaps (event0, Qnil, countof (maps), maps);
   if (nmaps > countof (maps))
     nmaps = countof (maps);
   return process_event_binding_result (lookup_events (event0, nmaps, maps,
@@ -3024,6 +3292,13 @@
   /* This function can GC */
   Lisp_Object fn;
   fn = GET_LISP_FROM_VOID (function);
+
+  /* Don't expose our remapping here. */
+  if (EQ (KEY_DATA_KEYSYM (key), Qxemacs_command_remapping))
+    {
+      return;
+    }
+
   call2 (fn, make_key_description (key, 1), binding);
 }
 
@@ -3483,12 +3758,12 @@
   /* Get keymaps as an array */
   if (NILP (keymaps))
     {
-      nmaps = get_relevant_keymaps (event_or_keys, countof (maps),
-				    gubbish);
+      nmaps = get_relevant_keymaps (event_or_keys, Qnil, countof (maps),
+                                    gubbish);
       if (nmaps > countof (maps))
 	{
 	  gubbish = alloca_array (Lisp_Object, nmaps);
-	  nmaps = get_relevant_keymaps (event_or_keys, nmaps, gubbish);
+	  nmaps = get_relevant_keymaps (event_or_keys, Qnil, nmaps, gubbish);
 	}
     }
   else if (CONSP (keymaps))
@@ -3518,6 +3793,11 @@
 	}
     }
 
+  if (!NILP (command_remapping (definition, nmaps, gubbish)))
+    {
+      return Qnil;
+    }
+
   return where_is_internal (definition, gubbish, nmaps, firstonly, 0);
 }
 
@@ -3536,11 +3816,11 @@
   int nmaps;
 
   /* Get keymaps as an array */
-  nmaps = get_relevant_keymaps (Qnil, countof (maps), gubbish);
+  nmaps = get_relevant_keymaps (Qnil, Qnil, countof (maps), gubbish);
   if (nmaps > countof (maps))
     {
       gubbish = alloca_array (Lisp_Object, nmaps);
-      nmaps = get_relevant_keymaps (Qnil, nmaps, gubbish);
+      nmaps = get_relevant_keymaps (Qnil, Qnil, nmaps, gubbish);
     }
 
   where_is_internal (definition, maps, nmaps, Qt, buffer);
@@ -3714,6 +3994,12 @@
 	  continue;
 	}
 
+      /* Don't expose the command remapping to #'where-is-internal */
+      if (EQ (key, Qxemacs_command_remapping))
+        {
+          continue;
+        }
+
       /* If the map is a "bucky" map, then add a bit to the
 	 modifiers_so_far list.
 	 Otherwise, add a new raw_key onto the end of keys_so_far.
@@ -4307,6 +4593,7 @@
 
   DEFSYMBOL (Qmodeline_map);
   DEFSYMBOL (Qtoolbar_map);
+  DEFSYMBOL (Qremap);
 
   DEFSUBR (Fkeymap_parents);
   DEFSUBR (Fset_keymap_parents);
@@ -4326,6 +4613,9 @@
   DEFSUBR (Fmap_keymap);
   DEFSUBR (Fevent_matches_key_specifier_p);
   DEFSUBR (Fdefine_key);
+  DEFSUBR (Fremap_command);
+  DEFSUBR (Fcommands_remapped_to);
+  DEFSUBR (Fcommand_remapping);
   DEFSUBR (Flookup_key);
   DEFSUBR (Fkey_binding);
   DEFSUBR (Fuse_global_map);
@@ -4455,6 +4745,10 @@
 
   Vsingle_space_string = make_string ((const Ibyte *) " ", 1);
   staticpro (&Vsingle_space_string);
+
+  Qxemacs_command_remapping
+    = Fmake_symbol (build_ascstring ("xemacs-command-remapping"));
+  staticpro (&Qxemacs_command_remapping);
 }
 
 void
--- a/src/lisp.h	Sun Aug 12 11:32:36 2012 +0100
+++ b/src/lisp.h	Sun Sep 02 14:31:40 2012 +0100
@@ -3521,6 +3521,13 @@
  while (NILP (Ffunctionp (fun)))		\
    signal_invalid_function_error (fun);		\
  } while (0)
+
+#define CHECK_COMMAND(x) do {                           \
+    if (NILP (Fcommandp (x)))                           \
+      {                                                 \
+        dead_wrong_type_argument (Qcommandp, x);        \
+      }                                                 \
+  } while (0)
 
 /************************************************************************/
 /*                      Parsing keyword arguments                       */
--- a/tests/ChangeLog	Sun Aug 12 11:32:36 2012 +0100
+++ b/tests/ChangeLog	Sun Sep 02 14:31:40 2012 +0100
@@ -2,6 +2,11 @@
 
 	* XEmacs 21.5.32 "habanero" is released.
 
+2012-09-02  Aidan Kehoe  <kehoea@parhasard.net>
+
+	* automated/keymap-tests.el:
+	Test the new command remapping functionality.
+
 2012-05-12  Aidan Kehoe  <kehoea@parhasard.net>
 
 	* automated/mule-tests.el:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/automated/keymap-tests.el	Sun Sep 02 14:31:40 2012 +0100
@@ -0,0 +1,104 @@
+;; Copyright (C) 2012 Free Software Foundation, Inc.
+
+;; Author: Aidan Kehoe <kehoea@parhasard.net>
+;; Maintainers: Aidan Kehoe <kehoea@parhasard.net>
+;; Created: 2012
+;; Keywords: tests
+
+;; This file is part of XEmacs.
+
+;; XEmacs is free software: you can redistribute it and/or modify it
+;; under the terms of the GNU General Public License as published by the
+;; Free Software Foundation, either version 3 of the License, or (at your
+;; option) any later version.
+
+;; XEmacs is distributed in the hope that it will be useful, but WITHOUT
+;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+;; FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+;; for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with XEmacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Synched up with: Not in FSF.
+
+(let* ((map (make-keymap 'help-map-copy))
+       (parent-map (make-keymap 'help-map-copy-parent))
+       (help-map-copy t)
+       (minor-mode-map-alist (acons 'help-map-copy map minor-mode-map-alist)))
+  (set-keymap-parent map parent-map)
+  (loop for (keys def) on '((shift tab) help-prev-symbol tab
+                            help-next-symbol c customize-variable V
+                            find-variable-at-point q
+                            help-mode-quit f find-function-at-point d
+                            describe-function-at-point v
+                            describe-variable-at-point i Info-elisp-ref F
+                            find-function-at-point Q help-mode-bury button2
+                            help-mouse-find-source-or-track p
+                            help-prev-section n help-next-section return
+                            help-find-source-or-scroll-up)
+        by #'cddr
+        do (define-key map (vector keys) def))
+  (loop for (keys def) on '(u view-scroll-some-lines-down % view-goto-percent
+                            \2 digit-argument p view-goto-percent \? 
+                            view-search-backward - negative-argument k
+                            view-scroll-lines-down backspace scroll-down G
+                            view-last-windowful f scroll-up \5
+                            digit-argument s view-repeat-search \0
+                            digit-argument n view-repeat-search = what-line
+                            \\ view-search-backward delete scroll-down \8
+                            digit-argument E view-file d
+                            view-scroll-some-lines-up \3 digit-argument q
+                            view-quit ! shell-command (control j)
+                            view-scroll-lines-up (control m)
+                            view-scroll-lines-up y view-scroll-lines-down
+                            linefeed view-scroll-lines-up g view-goto-line
+                            \6 digit-argument t toggle-truncate-lines C
+                            view-cleanup-backspaces b scroll-down \1
+                            digit-argument P view-buffer return
+                            view-scroll-lines-up | shell-command-on-region j
+                            view-scroll-lines-up \9 digit-argument \'
+                            register-to-point e view-scroll-lines-up \4
+                            digit-argument r recenter space scroll-up /
+                            view-search-forward N view-buffer m
+                            point-to-register h view-mode-describe \7
+                            digit-argument
+                            find-function-at-point view-mode-describe)
+        by #'cddr
+        do (define-key parent-map (vector keys) def))
+  (Assert (eq (key-binding [F]) 'find-function-at-point)
+          "checking normal key lookup works, F")
+  (Assert (eq (key-binding [c]) 'customize-variable)
+          "checking normal key lookup works, c")
+  (Assert (eq (key-binding [\2]) 'digit-argument)
+          "checking normal key parent lookup works, \\2")
+  (Assert (eq (key-binding [|]) 'shell-command-on-region)
+          "checking normal key parent lookup works, |")
+  (define-key map [remap find-function-at-point] #'find-file)
+  (Assert (eq (key-binding [F]) 'find-file)
+          "checking remapped key lookup works, F")
+  (Assert (eq (key-binding [f]) 'find-file)
+          "checking remapped key lookup works, f")
+  (Assert (eq (key-binding [\2]) 'digit-argument)
+          "checking normal key parent lookup works, \\2")
+  (Assert (eq (key-binding [|]) 'shell-command-on-region)
+          "checking normal key parent lookup works, |")
+  (Assert (eq (key-binding [find-function-at-point]) 'view-mode-describe)
+          "checking remapped function doesn't affect key name mapping")
+  (define-key parent-map [remap help-next-symbol] #'find-file)
+  (Assert (eq (key-binding [tab]) 'find-file)
+          "checking remapping in parent extends to child")
+  (Assert (equal (commands-remapped-to 'find-file)
+		 '(help-next-symbol find-function-at-point))
+	  "checking #'commands-remapped-to is sane")
+  (Check-Error wrong-type-argument (commands-remapped-to pi))
+  (Check-Error wrong-type-argument (commands-remapped-to 'find-file pi))
+  (Check-Error wrong-type-argument (commands-remapped-to 'find-file nil pi))
+  (Assert (eq (command-remapping 'find-function-at-point) 'find-file)
+	  "checking #'command-remapping is sane")
+  (Check-Error wrong-type-argument (command-remapping pi))
+  (Check-Error wrong-type-argument (command-remapping 'find-function-at-point
+						      pi))
+  (Check-Error wrong-type-argument (command-remapping 'find-function-at-point
+						      nil pi)))
+