Function: where-is-internal

where-is-internal is a function defined in keymap.c.

Signature

(where-is-internal DEFINITION &optional KEYMAP FIRSTONLY NOINDIRECT NO-REMAP)

Documentation

Return list of keys that invoke DEFINITION.

If KEYMAP is a keymap, search only KEYMAP and the global keymap. If KEYMAP is nil, search all the currently active keymaps, except
 for overriding-local-map (which is ignored).
If KEYMAP is a list of keymaps, search only those keymaps.

If optional 3rd arg FIRSTONLY is non-nil, return the first key sequence found, rather than a list of all possible key sequences. If FIRSTONLY is the symbol non-ascii, return the first binding found, no matter what it is. If FIRSTONLY has another non-nil value, prefer bindings that use the modifier key specified in where-is-preferred-modifier
(or their meta variants) and entirely reject menu bindings.

If optional 4th arg NOINDIRECT is non-nil, don't extract the commands inside menu-items. This makes it possible to search for a menu-item itself.

The optional 5th arg NO-REMAP alters how command remapping is handled:

- If another command OTHER-COMMAND is remapped to DEFINITION, normally
  search for the bindings of OTHER-COMMAND and include them in the
  returned list. But if NO-REMAP is non-nil, include the vector
  [remap OTHER-COMMAND] in the returned list instead, without
  searching for those other bindings.

- If DEFINITION is remapped to OTHER-COMMAND, normally return the
  bindings for OTHER-COMMAND. But if NO-REMAP is non-nil, return the
  bindings for DEFINITION instead, ignoring its remapping.

Keys that are represented as events that have a non-key-event non-nil symbol property are ignored.

View in manual

Probably introduced at or before Emacs version 18.

Source Code

// Defined in /usr/src/emacs/src/keymap.c
// Skipping highlighting due to helpful-max-highlight.
{
  /* The keymaps in which to search.  */
  Lisp_Object keymaps;
  /* Potentially relevant bindings in "shortest to longest" order.  */
  Lisp_Object sequences = Qnil;
    /* Actually relevant bindings.  */
  Lisp_Object found = Qnil;
  /* 1 means ignore all menu bindings entirely.  */
  bool nomenus = !NILP (firstonly) && !EQ (firstonly, Qnon_ascii);
  /* List of sequences found via remapping.  Keep them in a separate
     variable, so as to push them later, since we prefer
     non-remapped binding.  */
  Lisp_Object remapped_sequences = Qnil;
  /* Whether or not we're handling remapped sequences.  This is needed
     because remapping is not done recursively by Fcommand_remapping: you
     can't remap a remapped command.  */
  bool remapped = false;

  /* Refresh the C version of the modifier preference.  */
  where_is_preferred_modifier
    = parse_solitary_modifier (Vwhere_is_preferred_modifier);

  /* Find the relevant keymaps.  */
  if (CONSP (keymap) && KEYMAPP (XCAR (keymap)))
    keymaps = keymap;
  else if (!NILP (keymap))
    keymaps = list2 (keymap, current_global_map);
  else
    keymaps = Fcurrent_active_maps (Qnil, Qnil);

  Lisp_Object tem = Fcommand_remapping (definition, Qnil, keymaps);
  /* If `definition' is remapped to `tem', then OT1H no key will run
     that command (since they will run `tem' instead), so we should
     return nil; but OTOH all keys bound to `definition' (or to `tem')
     will run the same command.
     So for menu-shortcut purposes, we want to find all the keys bound (maybe
     via remapping) to `tem'.  But for the purpose of finding the keys that
     run `definition', then we'd want to just return nil.
     We choose to make it work right for menu-shortcuts, since it's the most
     common use.
     Known bugs: if you remap switch-to-buffer to toto, C-h f switch-to-buffer
     will tell you that switch-to-buffer is bound to C-x b even though C-x b
     will run toto instead.  And if `toto' is itself remapped to forward-char,
     then C-h f toto will tell you that it's bound to C-f even though C-f does
     not run toto and it won't tell you that C-x b does run toto.  */
  if (NILP (no_remap) && !NILP (tem))
    definition = tem;

  if (SYMBOLP (definition)
      && !NILP (firstonly)
      && !NILP (tem = Fget (definition, QCadvertised_binding)))
    {
      /* We have a list of advertised bindings.  */
      /* FIXME: Not sure why we use false for shadow_lookup's remapping,
         nor why we use `EQ' here but `Fequal' in the call further down.  */
      while (CONSP (tem))
	if (EQ (shadow_lookup (keymaps, XCAR (tem), Qnil, 0), definition))
	  return XCAR (tem);
	else
	  tem = XCDR (tem);
      if (EQ (shadow_lookup (keymaps, tem, Qnil, 0), definition))
	return tem;
    }

  sequences = Freverse (where_is_internal (definition, keymaps,
					   !NILP (noindirect), nomenus));

  while (CONSP (sequences)
	 /* If we're at the end of the `sequences' list and we haven't
	    considered remapped sequences yet, copy them over and
	    process them.  */
	 || (!remapped && (sequences = remapped_sequences,
			   remapped = true,
			   CONSP (sequences))))
    {
      Lisp_Object sequence, function;

      sequence = XCAR (sequences);
      sequences = XCDR (sequences);

      /* Verify that this key binding is not shadowed by another
	 binding for the same key, before we say it exists.

	 Mechanism: look for local definition of this key and if
	 it is defined and does not match what we found then
	 ignore this key.

	 Either nil or number as value from Flookup_key
	 means undefined.  */
      if (NILP (Fequal (shadow_lookup (keymaps, sequence, Qnil, remapped),
			definition)))
	continue;

      /* If the current sequence is a command remapping with
	 format [remap COMMAND], find the key sequences
	 which run COMMAND, and use those sequences instead.  */
      if (NILP (no_remap) && !remapped
	  && VECTORP (sequence) && ASIZE (sequence) == 2
	  && EQ (AREF (sequence, 0), Qremap)
	  && (function = AREF (sequence, 1), SYMBOLP (function)))
	{
	  Lisp_Object seqs = where_is_internal (function, keymaps,
						!NILP (noindirect), nomenus);
	  remapped_sequences = nconc2 (Freverse (seqs), remapped_sequences);
	  continue;
	}

      /* Don't annoy user with strings from a menu such as the
	 entries from the "Edit => Paste from Kill Menu".
	 Change them all to "(any string)", so that there
	 seems to be only one menu item to report.  */
      if (! NILP (sequence))
	{
	  Lisp_Object tem1;
	  tem1 = Faref (sequence, make_fixnum (ASIZE (sequence) - 1));
	  if (STRINGP (tem1))
	    Faset (sequence, make_fixnum (ASIZE (sequence) - 1),
		   build_string ("(any string)"));
	}

      /* It is a true unshadowed match.  Record it, unless it's already
	 been seen (as could happen when inheriting keymaps).  */
      if (NILP (Fmember (sequence, found))
	  /* Filter out non key events.  */
	  && !(VECTORP (sequence)
	       && ASIZE (sequence) == 1
	       && SYMBOLP (AREF (sequence, 0))
	       && !NILP (Fget (AREF (sequence, 0), Qnon_key_event))))
	found = Fcons (sequence, found);

      /* If firstonly is Qnon_ascii, then we can return the first
	 binding we find.  If firstonly is not Qnon_ascii but not
	 nil, then we should return the first ascii-only binding
	 we find.  */
      if (EQ (firstonly, Qnon_ascii))
	return sequence;
      else if (!NILP (firstonly)
	       && 2 == preferred_sequence_p (sequence))
	return sequence;
    }

  found = Fnreverse (found);

  /* firstonly may have been t, but we may have gone all the way through
     the keymaps without finding an all-ASCII key sequence.  So just
     return the best we could find.  */
  if (NILP (firstonly))
    return found;
  else if (where_is_preferred_modifier == 0)
    return Fcar (found);
  else
    { /* Maybe we did not find a preferred_modifier binding, but we did find
	 some ASCII binding.  */
      Lisp_Object bindings = found;
      while (CONSP (bindings))
	if (preferred_sequence_p (XCAR (bindings)))
	  return XCAR (bindings);
	else
	  bindings = XCDR (bindings);
      return Fcar (found);
    }
}