11573Srgrimes/* infodoc.c -- Functions which build documentation nodes.
21573Srgrimes   $Id: infodoc.c,v 1.1 2004/10/28 18:14:09 zooey Exp $
31573Srgrimes
41573Srgrimes   Copyright (C) 1993, 97 Free Software Foundation, Inc.
51573Srgrimes
61573Srgrimes   This program is free software; you can redistribute it and/or modify
71573Srgrimes   it under the terms of the GNU General Public License as published by
81573Srgrimes   the Free Software Foundation; either version 2, or (at your option)
91573Srgrimes   any later version.
101573Srgrimes
111573Srgrimes   This program is distributed in the hope that it will be useful,
121573Srgrimes   but WITHOUT ANY WARRANTY; without even the implied warranty of
131573Srgrimes   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
141573Srgrimes   GNU General Public License for more details.
151573Srgrimes
161573Srgrimes   You should have received a copy of the GNU General Public License
171573Srgrimes   along with this program; if not, write to the Free Software
181573Srgrimes   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
191573Srgrimes
201573Srgrimes   Written by Brian Fox (bfox@ai.mit.edu). */
211573Srgrimes
221573Srgrimes#include "info.h"
231573Srgrimes
241573Srgrimes/* Normally we do not define HELP_NODE_GETS_REGENERATED because the
251573Srgrimes   contents of the help node currently can never change once an info
261573Srgrimes   session has been started.   You should consider defining this in
271573Srgrimes   the case that you place information about dynamic variables in the
2823660Speter   help text.  When that happens, the contents of the help node will
2950476Speter   change dependent on the value of those variables, and the user will
301573Srgrimes   expect to see those changes. */
31207734Sjilles/* #define HELP_NODE_GETS_REGENERATED 1 */
321573Srgrimes
3379531Sru/* **************************************************************** */
341573Srgrimes/*                                                                  */
351573Srgrimes/*                        Info Help Windows                         */
361573Srgrimes/*                                                                  */
3759460Sphantom/* **************************************************************** */
3859460Sphantom
391573Srgrimes/* The name of the node used in the help window. */
4084306Srustatic char *info_help_nodename = "*Info Help*";
411573Srgrimes
42207734Sjilles/* A node containing printed key bindings and their documentation. */
43103202Sarchiestatic NODE *internal_info_help_node = (NODE *)NULL;
44103202Sarchie
45103202Sarchie/* A pointer to the contents of the help node. */
46103202Sarchiestatic char *internal_info_help_node_contents = (char *)NULL;
471573Srgrimes
481573Srgrimes/* The static text which appears in the internal info help node. */
49104368Srobertstatic char *info_internal_help_text[] = {
501573Srgrimes  N_ ("Basic Commands in Info Windows"),
51108028Sru  "******************************",
52108028Sru  "",
53108028Sru  "  h          Invoke the Info tutorial.",
54207734Sjilles  "  CTRL-x 0   Quit this help.",
55207734Sjilles  "  q          Quit Info altogether.",
561573Srgrimes  "",
571573Srgrimes  "Selecting other nodes:",
581573Srgrimes  "----------------------",
591573Srgrimes  "  n   Move to the \"next\" node of this node.",
601573Srgrimes  "  p   Move to the \"previous\" node of this node.",
61207734Sjilles  "  u   Move \"up\" from this node.",
621573Srgrimes  "  m   Pick menu item specified by name.",
631573Srgrimes  "      Picking a menu item causes another node to be selected.",
641573Srgrimes  "  f   Follow a cross reference.  Reads name of reference.",
65108028Sru  "  l   Move to the last node seen in this window.",
66207734Sjilles  "  d   Move to the `directory' node.  Equivalent to `g(DIR)'.",
67207734Sjilles  "",
681573Srgrimes  "Moving within a node:",
6979754Sdd  "---------------------",
701573Srgrimes  "  SPC Scroll forward a page.",
71207734Sjilles  "  DEL Scroll backward a page.",
72207734Sjilles  "  b   Go to the beginning of this node.",
731573Srgrimes  "  e   Go to the end of this node.",
7414336Speter  "",
751573Srgrimes  "Other commands:",
761573Srgrimes  "--------------------",
7723793Smpp  "  1   Pick first item in node's menu.",
781573Srgrimes  "  2-9 Pick second ... ninth item in node's menu.",
791573Srgrimes  "  0   Pick last item in node's menu.",
801573Srgrimes  "  g   Move to node specified by name.",
811573Srgrimes  "      You may include a filename as well, as in (FILENAME)NODENAME.",
821573Srgrimes  "  s   Search through this Info file for a specified string,",
831573Srgrimes  "      and select the node in which the next occurrence is found.",
841573Srgrimes  NULL
851573Srgrimes};
861573Srgrimes
871573Srgrimesstatic char *where_is (), *where_is_internal ();
881573Srgrimes
891573Srgrimesvoid
901573Srgrimesdump_map_to_message_buffer (prefix, map)
911573Srgrimes     char *prefix;
9214336Speter     Keymap map;
93207734Sjilles{
9414336Speter  register int i;
951573Srgrimes
961573Srgrimes  for (i = 0; i < 256; i++)
971573Srgrimes    {
981573Srgrimes      if (map[i].type == ISKMAP)
991573Srgrimes        {
1001573Srgrimes          char *new_prefix, *keyname;
1011573Srgrimes
1021573Srgrimes          keyname = pretty_keyname (i);
10323793Smpp          new_prefix = (char *)
1041573Srgrimes            xmalloc (3 + strlen (prefix) + strlen (keyname));
1051573Srgrimes          sprintf (new_prefix, "%s%s%s ", prefix, *prefix ? " " : "", keyname);
1061573Srgrimes
107211966Skib          dump_map_to_message_buffer (new_prefix, (Keymap)map[i].function);
1081573Srgrimes          free (new_prefix);
1091573Srgrimes        }
1101573Srgrimes      else if (map[i].function)
1111573Srgrimes        {
1121573Srgrimes          register int last;
1131573Srgrimes          char *doc, *name;
1141573Srgrimes
1151573Srgrimes          doc = function_documentation (map[i].function);
1161573Srgrimes          name = function_name (map[i].function);
1171573Srgrimes
1181573Srgrimes          if (!*doc)
1191573Srgrimes            continue;
1201573Srgrimes
1211573Srgrimes          /* Find out if there is a series of identical functions, as in
1221573Srgrimes             ea_insert (). */
1231573Srgrimes          for (last = i + 1; last < 256; last++)
1241573Srgrimes            if ((map[last].type != ISFUNC) ||
1251573Srgrimes                (map[last].function != map[i].function))
1261573Srgrimes              break;
1271573Srgrimes
12881352Syar          if (last - 1 != i)
1291573Srgrimes            {
130108028Sru              printf_to_message_buffer
131108028Sru                ("%s%s .. ", prefix, pretty_keyname (i));
132108028Sru              printf_to_message_buffer
1331573Srgrimes                ("%s%s\t", prefix, pretty_keyname (last - 1));
1341573Srgrimes              i = last - 1;
13559954Sphantom            }
1361573Srgrimes          else
1371573Srgrimes            printf_to_message_buffer ("%s%s\t", prefix, pretty_keyname (i));
1381573Srgrimes
1391573Srgrimes#if defined (NAMED_FUNCTIONS)
1401573Srgrimes          /* Print the name of the function, and some padding before the
1411573Srgrimes             documentation string is printed. */
1421573Srgrimes          {
143207734Sjilles            int length_so_far;
144207734Sjilles            int desired_doc_start = 40; /* Must be multiple of 8. */
1451573Srgrimes
146207734Sjilles            printf_to_message_buffer ("(%s)", name);
147207734Sjilles            length_so_far = message_buffer_length_this_line ();
148207734Sjilles
1491573Srgrimes            if ((desired_doc_start + strlen (doc)) >= the_screen->width)
1501573Srgrimes              printf_to_message_buffer ("\n     ");
1511573Srgrimes            else
1521573Srgrimes              {
1531573Srgrimes                while (length_so_far < desired_doc_start)
1541573Srgrimes                  {
1551573Srgrimes                    printf_to_message_buffer ("\t");
1561573Srgrimes                    length_so_far += character_width ('\t', length_so_far);
1571573Srgrimes                  }
15817782Smpp              }
1591573Srgrimes          }
1601573Srgrimes#endif /* NAMED_FUNCTIONS */
1611573Srgrimes          printf_to_message_buffer ("%s\n", doc);
1621573Srgrimes        }
163    }
164}
165
166/* How to create internal_info_help_node. */
167static void
168create_internal_info_help_node ()
169{
170  register int i;
171  char *contents = (char *)NULL;
172  NODE *node;
173
174#if !defined (HELP_NODE_GETS_REGENERATED)
175  if (internal_info_help_node_contents)
176    contents = internal_info_help_node_contents;
177#endif /* !HELP_NODE_GETS_REGENERATED */
178
179  if (!contents)
180    {
181      int printed_one_mx = 0;
182
183      initialize_message_buffer ();
184
185      for (i = 0; info_internal_help_text[i]; i++)
186        printf_to_message_buffer ("%s\n", info_internal_help_text[i]);
187
188      printf_to_message_buffer ("---------------------\n\n");
189      printf_to_message_buffer ("The current search path is:\n");
190      printf_to_message_buffer ("  \"%s\"\n", infopath);
191      printf_to_message_buffer ("---------------------\n\n");
192      printf_to_message_buffer ("Commands available in Info windows:\n\n");
193      dump_map_to_message_buffer ("", info_keymap);
194      printf_to_message_buffer ("---------------------\n\n");
195      printf_to_message_buffer ("Commands available in the echo area:\n\n");
196      dump_map_to_message_buffer ("", echo_area_keymap);
197
198#if defined (NAMED_FUNCTIONS)
199      /* Get a list of the M-x commands which have no keystroke equivs. */
200      for (i = 0; function_doc_array[i].func; i++)
201        {
202          VFunction *func = function_doc_array[i].func;
203
204          if ((!where_is_internal (info_keymap, func)) &&
205              (!where_is_internal (echo_area_keymap, func)))
206            {
207              if (!printed_one_mx)
208                {
209                  printf_to_message_buffer ("---------------------\n\n");
210                  printf_to_message_buffer
211                    (_("The following commands can only be invoked via M-x:\n\n"));
212                  printed_one_mx = 1;
213                }
214
215              printf_to_message_buffer
216                ("M-x %s\n     %s\n",
217                 function_doc_array[i].func_name,
218                 replace_in_documentation (function_doc_array[i].doc));
219            }
220        }
221
222      if (printed_one_mx)
223        printf_to_message_buffer ("\n");
224#endif /* NAMED_FUNCTIONS */
225
226      printf_to_message_buffer
227        ("%s", replace_in_documentation
228         (_("--- Use `\\[history-node]' or `\\[kill-node]' to exit ---\n")));
229      node = message_buffer_to_node ();
230      internal_info_help_node_contents = node->contents;
231    }
232  else
233    {
234      /* We already had the right contents, so simply use them. */
235      node = build_message_node ("", 0, 0);
236      free (node->contents);
237      node->contents = contents;
238      node->nodelen = 1 + strlen (contents);
239    }
240
241  internal_info_help_node = node;
242
243  /* Do not GC this node's contents.  It never changes, and we never need
244     to delete it once it is made.  If you change some things (such as
245     placing information about dynamic variables in the help text) then
246     you will need to allow the contents to be gc'd, and you will have to
247     arrange to always regenerate the help node. */
248#if defined (HELP_NODE_GETS_REGENERATED)
249  add_gcable_pointer (internal_info_help_node->contents);
250#endif
251
252  name_internal_node (internal_info_help_node, info_help_nodename);
253
254  /* Even though this is an internal node, we don't want the window
255     system to treat it specially.  So we turn off the internalness
256     of it here. */
257  internal_info_help_node->flags &= ~N_IsInternal;
258}
259
260/* Return a window which is the window showing help in this Info. */
261static WINDOW *
262info_find_or_create_help_window ()
263{
264  WINDOW *help_window, *eligible, *window;
265
266  eligible = (WINDOW *)NULL;
267  help_window = get_internal_info_window (info_help_nodename);
268
269  /* If we couldn't find the help window, then make it. */
270  if (!help_window)
271    {
272      int max = 0;
273
274      for (window = windows; window; window = window->next)
275        {
276          if (window->height > max)
277            {
278              max = window->height;
279              eligible = window;
280            }
281        }
282
283      if (!eligible)
284        return ((WINDOW *)NULL);
285    }
286#if !defined (HELP_NODE_GETS_REGENERATED)
287  else
288    return (help_window);
289#endif /* !HELP_NODE_GETS_REGENERATED */
290
291  /* Make sure that we have a node containing the help text. */
292  create_internal_info_help_node ();
293
294  /* Either use the existing window to display the help node, or create
295     a new window if there was no existing help window. */
296  if (!help_window)
297    {
298      /* Split the largest window into 2 windows, and show the help text
299         in that window. */
300      if (eligible->height > 30)
301        {
302          active_window = eligible;
303          help_window = window_make_window (internal_info_help_node);
304        }
305      else
306        {
307          set_remembered_pagetop_and_point (active_window);
308          window_set_node_of_window (active_window, internal_info_help_node);
309          help_window = active_window;
310        }
311    }
312  else
313    {
314      /* Case where help node always gets regenerated, and we have an
315         existing window in which to place the node. */
316      if (active_window != help_window)
317        {
318          set_remembered_pagetop_and_point (active_window);
319          active_window = help_window;
320        }
321      window_set_node_of_window (active_window, internal_info_help_node);
322    }
323  remember_window_and_node (help_window, help_window->node);
324  return (help_window);
325}
326
327/* Create or move to the help window. */
328DECLARE_INFO_COMMAND (info_get_help_window, _("Display help message"))
329{
330  WINDOW *help_window;
331
332  help_window = info_find_or_create_help_window ();
333  if (help_window)
334    {
335      active_window = help_window;
336      active_window->flags |= W_UpdateWindow;
337    }
338  else
339    {
340      info_error (CANT_MAKE_HELP);
341    }
342}
343
344/* Show the Info help node.  This means that the "info" file is installed
345   where it can easily be found on your system. */
346DECLARE_INFO_COMMAND (info_get_info_help_node, _("Visit Info node `(info)Help'"))
347{
348  NODE *node;
349  char *nodename;
350
351  /* If there is a window on the screen showing the node "(info)Help" or
352     the node "(info)Help-Small-Screen", simply select that window. */
353  {
354    WINDOW *win;
355
356    for (win = windows; win; win = win->next)
357      {
358        if (win->node && win->node->filename &&
359            (strcasecmp
360             (filename_non_directory (win->node->filename), "info") == 0) &&
361            ((strcmp (win->node->nodename, "Help") == 0) ||
362             (strcmp (win->node->nodename, "Help-Small-Screen") == 0)))
363          {
364            active_window = win;
365            return;
366          }
367      }
368  }
369
370  /* If the current window is small, show the small screen help. */
371  if (active_window->height < 24)
372    nodename = "Help-Small-Screen";
373  else
374    nodename = "Help";
375
376  /* Try to get the info file for Info. */
377  node = info_get_node ("Info", nodename);
378
379  if (!node)
380    {
381      if (info_recent_file_error)
382        info_error (info_recent_file_error);
383      else
384        info_error (CANT_FILE_NODE, "Info", nodename);
385    }
386  else
387    {
388      /* If the current window is very large (greater than 45 lines),
389         then split it and show the help node in another window.
390         Otherwise, use the current window. */
391
392      if (active_window->height > 45)
393        active_window = window_make_window (node);
394      else
395        {
396          set_remembered_pagetop_and_point (active_window);
397          window_set_node_of_window (active_window, node);
398        }
399
400      remember_window_and_node (active_window, node);
401    }
402}
403
404/* **************************************************************** */
405/*                                                                  */
406/*                   Groveling Info Keymaps and Docs                */
407/*                                                                  */
408/* **************************************************************** */
409
410/* Return the documentation associated with the Info command FUNCTION. */
411char *
412function_documentation (function)
413     VFunction *function;
414{
415  register int i;
416
417  for (i = 0; function_doc_array[i].func; i++)
418    if (function == function_doc_array[i].func)
419      break;
420
421  return (replace_in_documentation (function_doc_array[i].doc));
422}
423
424#if defined (NAMED_FUNCTIONS)
425/* Return the user-visible name of the function associated with the
426   Info command FUNCTION. */
427char *
428function_name (function)
429
430     VFunction *function;
431{
432  register int i;
433
434  for (i = 0; function_doc_array[i].func; i++)
435    if (function == function_doc_array[i].func)
436      break;
437
438  return (function_doc_array[i].func_name);
439}
440
441/* Return a pointer to the function named NAME. */
442VFunction *
443named_function (name)
444     char *name;
445{
446  register int i;
447
448  for (i = 0; function_doc_array[i].func; i++)
449    if (strcmp (function_doc_array[i].func_name, name) == 0)
450      break;
451
452  return (function_doc_array[i].func);
453}
454#endif /* NAMED_FUNCTIONS */
455
456/* Return the documentation associated with KEY in MAP. */
457char *
458key_documentation (key, map)
459     char key;
460     Keymap map;
461{
462  VFunction *function = map[key].function;
463
464  if (function)
465    return (function_documentation (function));
466  else
467    return ((char *)NULL);
468}
469
470DECLARE_INFO_COMMAND (describe_key, _("Print documentation for KEY"))
471{
472  char keyname[50];
473  int keyname_index = 0;
474  unsigned char keystroke;
475  char *rep;
476  Keymap map;
477
478  keyname[0] = '\0';
479  map = window->keymap;
480
481  while (1)
482    {
483      message_in_echo_area (_("Describe key: %s"), keyname);
484      keystroke = info_get_input_char ();
485      unmessage_in_echo_area ();
486
487      if (Meta_p (keystroke) && (!ISO_Latin_p || key < 160))
488        {
489          if (map[ESC].type != ISKMAP)
490            {
491              window_message_in_echo_area
492                (_("ESC %s is undefined."), pretty_keyname (UnMeta (keystroke)));
493              return;
494            }
495
496          strcpy (keyname + keyname_index, "ESC ");
497          keyname_index = strlen (keyname);
498          keystroke = UnMeta (keystroke);
499          map = (Keymap)map[ESC].function;
500        }
501
502      /* Add the printed representation of KEYSTROKE to our keyname. */
503      rep = pretty_keyname (keystroke);
504      strcpy (keyname + keyname_index, rep);
505      keyname_index = strlen (keyname);
506
507      if (map[keystroke].function == (VFunction *)NULL)
508        {
509          message_in_echo_area (_("%s is undefined."), keyname);
510          return;
511        }
512      else if (map[keystroke].type == ISKMAP)
513        {
514          map = (Keymap)map[keystroke].function;
515          strcat (keyname, " ");
516          keyname_index = strlen (keyname);
517          continue;
518        }
519      else
520        {
521          char *message, *fundoc, *funname = "";
522
523#if defined (NAMED_FUNCTIONS)
524          funname = function_name (map[keystroke].function);
525#endif /* NAMED_FUNCTIONS */
526
527          fundoc = function_documentation (map[keystroke].function);
528
529          message = (char *)xmalloc
530            (10 + strlen (keyname) + strlen (fundoc) + strlen (funname));
531
532#if defined (NAMED_FUNCTIONS)
533          sprintf (message, "%s (%s): %s.", keyname, funname, fundoc);
534#else
535          sprintf (message, _("%s is defined to %s."), keyname, fundoc);
536#endif /* !NAMED_FUNCTIONS */
537
538          window_message_in_echo_area ("%s", message);
539          free (message);
540          break;
541        }
542    }
543}
544
545/* How to get the pretty printable name of a character. */
546static char rep_buffer[30];
547
548char *
549pretty_keyname (key)
550     unsigned char key;
551{
552  char *rep;
553
554  if (Meta_p (key))
555    {
556      char temp[20];
557
558      rep = pretty_keyname (UnMeta (key));
559
560      sprintf (temp, "ESC %s", rep);
561      strcpy (rep_buffer, temp);
562      rep = rep_buffer;
563    }
564  else if (Control_p (key))
565    {
566      switch (key)
567        {
568        case '\n': rep = "LFD"; break;
569        case '\t': rep = "TAB"; break;
570        case '\r': rep = "RET"; break;
571        case ESC:  rep = "ESC"; break;
572
573        default:
574          sprintf (rep_buffer, "C-%c", UnControl (key));
575          rep = rep_buffer;
576        }
577    }
578  else
579    {
580      switch (key)
581        {
582        case ' ': rep = "SPC"; break;
583        case DEL: rep = "DEL"; break;
584        default:
585          rep_buffer[0] = key;
586          rep_buffer[1] = '\0';
587          rep = rep_buffer;
588        }
589    }
590  return (rep);
591}
592
593/* Replace the names of functions with the key that invokes them. */
594char *
595replace_in_documentation (string)
596     char *string;
597{
598  register int i, start, next;
599  static char *result = (char *)NULL;
600
601  maybe_free (result);
602  result = (char *)xmalloc (1 + strlen (string));
603
604  i = next = start = 0;
605
606  /* Skip to the beginning of a replaceable function. */
607  for (i = start; string[i]; i++)
608    {
609      /* Is this the start of a replaceable function name? */
610      if (string[i] == '\\' && string[i + 1] == '[')
611        {
612          char *fun_name, *rep;
613          VFunction *function;
614
615          /* Copy in the old text. */
616          strncpy (result + next, string + start, i - start);
617          next += (i - start);
618          start = i + 2;
619
620          /* Move to the end of the function name. */
621          for (i = start; string[i] && (string[i] != ']'); i++);
622
623          fun_name = (char *)xmalloc (1 + i - start);
624          strncpy (fun_name, string + start, i - start);
625          fun_name[i - start] = '\0';
626
627          /* Find a key which invokes this function in the info_keymap. */
628          function = named_function (fun_name);
629
630          /* If the internal documentation string fails, there is a
631             serious problem with the associated command's documentation.
632             We croak so that it can be fixed immediately. */
633          if (!function)
634            abort ();
635
636          rep = where_is (info_keymap, function);
637          strcpy (result + next, rep);
638          next = strlen (result);
639
640          start = i;
641          if (string[i])
642            start++;
643        }
644    }
645  strcpy (result + next, string + start);
646  return (result);
647}
648
649/* Return a string of characters which could be typed from the keymap
650   MAP to invoke FUNCTION. */
651static char *where_is_rep = (char *)NULL;
652static int where_is_rep_index = 0;
653static int where_is_rep_size = 0;
654
655static char *
656where_is (map, function)
657     Keymap map;
658     VFunction *function;
659{
660  char *rep;
661
662  if (!where_is_rep_size)
663    where_is_rep = (char *)xmalloc (where_is_rep_size = 100);
664  where_is_rep_index = 0;
665
666  rep = where_is_internal (map, function);
667
668  /* If it couldn't be found, return "M-x Foo". */
669  if (!rep)
670    {
671      char *name;
672
673      name = function_name (function);
674
675      if (name)
676        sprintf (where_is_rep, "M-x %s", name);
677
678      rep = where_is_rep;
679    }
680  return (rep);
681}
682
683/* Return the printed rep of FUNCTION as found in MAP, or NULL. */
684static char *
685where_is_internal (map, function)
686     Keymap map;
687     VFunction *function;
688{
689  register int i;
690
691  /* If the function is directly invokable in MAP, return the representation
692     of that keystroke. */
693  for (i = 0; i < 256; i++)
694    if ((map[i].type == ISFUNC) && map[i].function == function)
695      {
696        sprintf (where_is_rep + where_is_rep_index, "%s", pretty_keyname (i));
697        return (where_is_rep);
698      }
699
700  /* Okay, search subsequent maps for this function. */
701  for (i = 0; i < 256; i++)
702    {
703      if (map[i].type == ISKMAP)
704        {
705          int saved_index = where_is_rep_index;
706          char *rep;
707
708          sprintf (where_is_rep + where_is_rep_index, "%s ",
709                   pretty_keyname (i));
710
711          where_is_rep_index = strlen (where_is_rep);
712          rep = where_is_internal ((Keymap)map[i].function, function);
713
714          if (rep)
715            return (where_is_rep);
716
717          where_is_rep_index = saved_index;
718        }
719    }
720
721  return ((char *)NULL);
722}
723
724extern char *read_function_name ();
725
726DECLARE_INFO_COMMAND (info_where_is,
727   "Show what to type to execute a given command")
728{
729  char *command_name;
730
731  command_name = read_function_name (_("Where is command: "), window);
732
733  if (!command_name)
734    {
735      info_abort_key (active_window, count, key);
736      return;
737    }
738
739  if (*command_name)
740    {
741      VFunction *function;
742
743      function = named_function (command_name);
744
745      if (function)
746        {
747          char *location;
748
749          location = where_is (active_window->keymap, function);
750
751          if (!location)
752            {
753              info_error (_("`%s' is not on any keys"), command_name);
754            }
755          else
756            {
757              if (strncmp (location, "M-x ", 4) == 0)
758                window_message_in_echo_area
759                  (_("%s can only be invoked via %s."), command_name, location);
760              else
761                window_message_in_echo_area
762                  (_("%s can be invoked via %s."), command_name, location);
763            }
764        }
765      else
766        info_error (_("There is no function named `%s'"), command_name);
767    }
768
769  free (command_name);
770}
771