Merge from vendor branch AWK:
[dragonfly.git] / contrib / texinfo / info / infodoc.c
1 /* infodoc.c -- Functions which build documentation nodes.
2    $Id: infodoc.c,v 1.28 2002/02/27 13:37:33 karl Exp $
3
4    Copyright (C) 1993, 97, 98, 99, 2001, 02 Free Software Foundation, Inc.
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2, or (at your option)
9    any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20    Written by Brian Fox (bfox@ai.mit.edu). */
21
22 #include "info.h"
23 #include "funs.h"
24
25 /* HELP_NODE_GETS_REGENERATED is always defined now that keys may get
26    rebound, or other changes in the help text may occur.  */
27 #define HELP_NODE_GETS_REGENERATED 1
28
29 /* The name of the node used in the help window. */
30 static char *info_help_nodename = "*Info Help*";
31
32 /* A node containing printed key bindings and their documentation. */
33 static NODE *internal_info_help_node = (NODE *)NULL;
34
35 /* A pointer to the contents of the help node. */
36 static char *internal_info_help_node_contents = (char *)NULL;
37
38 /* The (more or less) static text which appears in the internal info
39    help node.  The actual key bindings are inserted.  Keep the 
40    underlines (****, etc.) in the same N_ call as  the text lines they
41    refer to, so translations can make the number of *'s or -'s match.  */
42 #if defined(INFOKEY)
43
44 static char *info_internal_help_text[] = {
45   N_("Basic Commands in Info Windows\n\
46 ******************************\n"),
47   "\n",
48   N_("\\%-10[quit-help]  Quit this help.\n"),
49   N_("\\%-10[quit]  Quit Info altogether.\n"),
50   N_("\\%-10[get-info-help-node]  Invoke the Info tutorial.\n"),
51   "\n",
52   N_("Selecting other nodes:\n\
53 ----------------------\n"),
54   N_("\\%-10[next-node]  Move to the \"next\" node of this node.\n"),
55   N_("\\%-10[prev-node]  Move to the \"previous\" node of this node.\n"),
56   N_("\\%-10[up-node]  Move \"up\" from this node.\n"),
57   N_("\\%-10[menu-item]  Pick menu item specified by name.\n\
58               Picking a menu item causes another node to be selected.\n"),
59   N_("\\%-10[xref-item]  Follow a cross reference.  Reads name of reference.\n"),
60   N_("\\%-10[history-node]  Move to the last node seen in this window.\n"),
61   N_("\\%-10[move-to-next-xref]  Skip to next hypertext link within this node.\n"),
62   N_("\\%-10[move-to-prev-xref]  Skip to previous hypertext link within this node.\n"),
63   N_("\\%-10[select-reference-this-line]  Follow the hypertext link under cursor.\n"),
64   N_("\\%-10[dir-node]  Move to the `directory' node.  Equivalent to `\\[goto-node] (DIR)'.\n"),
65   N_("\\%-10[top-node]  Move to the Top node.  Equivalent to `\\[goto-node] Top'.\n"),
66   "\n",
67   N_("Moving within a node:\n\
68 ---------------------\n"),
69   N_("\\%-10[scroll-forward]  Scroll forward a page.\n"),
70   N_("\\%-10[scroll-backward]  Scroll backward a page.\n"),
71   N_("\\%-10[beginning-of-node]  Go to the beginning of this node.\n"),
72   N_("\\%-10[end-of-node]  Go to the end of this node.\n"),
73   N_("\\%-10[scroll-forward]  Scroll forward 1 line.\n"),
74   N_("\\%-10[scroll-backward]  Scroll backward 1 line.\n"),
75   "\n",
76   N_("Other commands:\n\
77 ---------------\n"),
78   N_("\\%-10[menu-digit]  Pick first ... ninth item in node's menu.\n"),
79   N_("\\%-10[last-menu-item]  Pick last item in node's menu.\n"),
80   N_("\\%-10[index-search]  Search for a specified string in the index entries of this Info\n\
81               file, and select the node referenced by the first entry found.\n"),
82   N_("\\%-10[goto-node]  Move to node specified by name.\n\
83               You may include a filename as well, as in (FILENAME)NODENAME.\n"),
84   N_("\\%-10[search]  Search forward for a specified string\n\
85               and select the node in which the next occurrence is found.\n"),
86   N_("\\%-10[search-backward]  Search backward for a specified string\n\
87               and select the node in which the previous occurrence is found.\n"),
88   NULL
89 };
90
91 #else /* !INFOKEY */
92
93 static char *info_internal_help_text[] = {
94   N_("Basic Commands in Info Windows\n\
95 ******************************\n"),
96   "\n",
97   N_("  %-10s  Quit this help.\n"),
98   N_("  %-10s  Quit Info altogether.\n"),
99   N_("  %-10s  Invoke the Info tutorial.\n"),
100   "\n",
101   N_("Selecting other nodes:\n\
102 ----------------------\n",
103   N_("  %-10s  Move to the `next' node of this node.\n"),
104   N_("  %-10s  Move to the `previous' node of this node.\n"),
105   N_("  %-10s  Move `up' from this node.\n"),
106   N_("  %-10s  Pick menu item specified by name.\n"),
107   N_("              Picking a menu item causes another node to be selected.\n"),
108   N_("  %-10s  Follow a cross reference.  Reads name of reference.\n"),
109   N_("  %-10s  Move to the last node seen in this window.\n"),
110   N_("  %-10s  Skip to next hypertext link within this node.\n"),
111   N_("  %-10s  Follow the hypertext link under cursor.\n"),
112   N_("  %-10s  Move to the `directory' node.  Equivalent to `g (DIR)'.\n"),
113   N_("  %-10s  Move to the Top node.  Equivalent to `g Top'.\n"),
114   "\n",
115   N_("Moving within a node:\n\
116 ---------------------\n"),
117   N_("  %-10s  Scroll forward a page.\n"),
118   N_("  %-10s  Scroll backward a page.\n"),
119   N_("  %-10s  Go to the beginning of this node.\n"),
120   N_("  %-10s  Go to the end of this node.\n"),
121   N_("  %-10s  Scroll forward 1 line.\n"),
122   N_("  %-10s  Scroll backward 1 line.\n"),
123   "\n",
124   N_("Other commands:\n\
125 ---------------\n"),
126   N_("  %-10s  Pick first ... ninth item in node's menu.\n"),
127   N_("  %-10s  Pick last item in node's menu.\n"),
128   N_("  %-10s  Search for a specified string in the index entries of this Info\n"),
129   N_("              file, and select the node referenced by the first entry found.\n"),
130   N_("  %-10s  Move to node specified by name.\n"),
131   N_("              You may include a filename as well, as in (FILENAME)NODENAME.\n"),
132   N_("  %-10s  Search forward for a specified string,\n"),
133   N_("              and select the node in which the next occurrence is found.\n"),
134   N_("  %-10s  Search backward for a specified string\n"),
135   N_("              and select the node in which the next occurrence is found.\n"),
136   NULL
137 };
138
139 static char *info_help_keys_text[][2] = {
140   { "", "" },
141   { "", "" },
142   { "", "" },
143   { "CTRL-x 0", "CTRL-x 0" },
144   { "q", "q" },
145   { "h", "ESC h" },
146   { "", "" },
147   { "", "" },
148   { "", "" },
149   { "SPC", "SPC" },
150   { "DEL", "b" },
151   { "b", "ESC b" },
152   { "e", "ESC e" },
153   { "ESC 1 SPC", "RET" },
154   { "ESC 1 DEL", "y" },
155   { "", "" },
156   { "", "" },
157   { "", "" },
158   { "n", "CTRL-x n" },
159   { "p", "CTRL-x p" },
160   { "u", "CTRL-x u" },
161   { "m", "ESC m" },
162   { "", "" },
163   { "f", "ESC f" },
164   { "l", "l" },
165   { "TAB", "TAB" },
166   { "RET", "CTRL-x RET" },
167   { "d", "ESC d" },
168   { "t", "ESC t" },
169   { "", "" },
170   { "", "" },
171   { "", "" },
172   { "1-9", "ESC 1-9" },
173   { "0", "ESC 0" },
174   { "i", "CTRL-x i" },
175   { "", "" },
176   { "g", "CTRL-x g" },
177   { "", "" },
178   { "s", "/" },
179   { "", "" },
180   { "ESC - s", "?" },
181   { "", "" },
182   NULL
183 };
184
185 #endif /* !INFOKEY */
186
187 static char *where_is_internal ();
188
189 void
190 dump_map_to_message_buffer (prefix, map)
191      char *prefix;
192      Keymap map;
193 {
194   register int i;
195   unsigned prefix_len = strlen (prefix);
196   char *new_prefix = (char *)xmalloc (prefix_len + 2);
197
198   strncpy (new_prefix, prefix, prefix_len);
199   new_prefix[prefix_len + 1] = '\0';
200
201   for (i = 0; i < 256; i++)
202     {
203       new_prefix[prefix_len] = i;
204       if (map[i].type == ISKMAP)
205         {
206           dump_map_to_message_buffer (new_prefix, (Keymap)map[i].function);
207         }
208       else if (map[i].function)
209         {
210           register int last;
211           char *doc, *name;
212
213           doc = function_documentation (map[i].function);
214           name = function_name (map[i].function);
215
216           if (!*doc)
217             continue;
218
219           /* Find out if there is a series of identical functions, as in
220              ea_insert (). */
221           for (last = i + 1; last < 256; last++)
222             if ((map[last].type != ISFUNC) ||
223                 (map[last].function != map[i].function))
224               break;
225
226           if (last - 1 != i)
227             {
228               printf_to_message_buffer ("%s .. ", pretty_keyseq (new_prefix));
229               new_prefix[prefix_len] = last - 1;
230               printf_to_message_buffer ("%s\t", pretty_keyseq (new_prefix));
231               i = last - 1;
232             }
233           else
234             printf_to_message_buffer ("%s\t", pretty_keyseq (new_prefix));
235
236 #if defined (NAMED_FUNCTIONS)
237           /* Print the name of the function, and some padding before the
238              documentation string is printed. */
239           {
240             int length_so_far;
241             int desired_doc_start = 40; /* Must be multiple of 8. */
242
243             printf_to_message_buffer ("(%s)", name);
244             length_so_far = message_buffer_length_this_line ();
245
246             if ((desired_doc_start + strlen (doc)) >= the_screen->width)
247               printf_to_message_buffer ("\n     ");
248             else
249               {
250                 while (length_so_far < desired_doc_start)
251                   {
252                     printf_to_message_buffer ("\t");
253                     length_so_far += character_width ('\t', length_so_far);
254                   }
255               }
256           }
257 #endif /* NAMED_FUNCTIONS */
258           printf_to_message_buffer ("%s\n", doc);
259         }
260     }
261   free (new_prefix);
262 }
263
264 /* How to create internal_info_help_node.  HELP_IS_ONLY_WINDOW_P says
265    whether we're going to end up in a second (or more) window of our
266    own, or whether there's only one window and we're going to usurp it.
267    This determines how to quit the help window.  Maybe we should just
268    make q do the right thing in both cases.  */
269
270 static void
271 create_internal_info_help_node (help_is_only_window_p)
272      int help_is_only_window_p;
273 {
274   register int i;
275   NODE *node;
276   char *contents = NULL;
277   char *exec_keys;
278
279 #ifndef HELP_NODE_GETS_REGENERATED
280   if (internal_info_help_node_contents)
281     contents = internal_info_help_node_contents;
282 #endif /* !HELP_NODE_GETS_REGENERATED */
283
284   if (!contents)
285     {
286       int printed_one_mx = 0;
287
288       initialize_message_buffer ();
289
290       for (i = 0; info_internal_help_text[i]; i++)
291         {
292 #ifdef INFOKEY
293           printf_to_message_buffer (replace_in_documentation (
294            _(info_internal_help_text[i]), help_is_only_window_p));
295 #else
296           /* Don't translate blank lines, gettext outputs the po file
297              header in that case.  We want a blank line.  */
298           char *msg = *(info_internal_help_text[i])
299                       ? _(info_internal_help_text[i])
300                       : info_internal_help_text[i];
301           char *key = info_help_keys_text[i][vi_keys_p];
302           
303           /* If we have only one window (because the window size was too
304              small to split it), CTRL-x 0 doesn't work to `quit' help.  */
305           if (STREQ (key, "CTRL-x 0") && help_is_only_window_p)
306             key = "l";
307
308           printf_to_message_buffer (msg, key);
309 #endif /* !INFOKEY */
310         }
311
312       printf_to_message_buffer ("---------------------\n\n");
313       printf_to_message_buffer (_("The current search path is:\n"));
314       printf_to_message_buffer ("  %s\n", infopath);
315       printf_to_message_buffer ("---------------------\n\n");
316       printf_to_message_buffer (_("Commands available in Info windows:\n\n"));
317       dump_map_to_message_buffer ("", info_keymap);
318       printf_to_message_buffer ("---------------------\n\n");
319       printf_to_message_buffer (_("Commands available in the echo area:\n\n"));
320       dump_map_to_message_buffer ("", echo_area_keymap);
321
322 #if defined (NAMED_FUNCTIONS)
323       /* Get a list of commands which have no keystroke equivs. */
324       exec_keys = where_is (info_keymap, InfoCmd(info_execute_command));
325       if (exec_keys)
326         exec_keys = xstrdup (exec_keys);
327       for (i = 0; function_doc_array[i].func; i++)
328         {
329           InfoCommand *cmd = DocInfoCmd(&function_doc_array[i]);
330
331           if (InfoFunction(cmd) != info_do_lowercase_version
332               && !where_is_internal (info_keymap, cmd)
333               && !where_is_internal (echo_area_keymap, cmd))
334             {
335               if (!printed_one_mx)
336                 {
337                   printf_to_message_buffer ("---------------------\n\n");
338                   if (exec_keys && exec_keys[0])
339                       printf_to_message_buffer
340                         (_("The following commands can only be invoked via %s:\n\n"), exec_keys);
341                   else
342                       printf_to_message_buffer
343                         (_("The following commands cannot be invoked at all:\n\n"));
344                   printed_one_mx = 1;
345                 }
346
347               printf_to_message_buffer
348                 ("%s %s\n     %s\n",
349                  exec_keys,
350                  function_doc_array[i].func_name,
351                  replace_in_documentation (strlen (function_doc_array[i].doc)
352                                            ? _(function_doc_array[i].doc)
353                                            : "")
354                 );
355
356             }
357         }
358
359       if (printed_one_mx)
360         printf_to_message_buffer ("\n");
361
362       maybe_free (exec_keys);
363 #endif /* NAMED_FUNCTIONS */
364
365       printf_to_message_buffer
366         ("%s", replace_in_documentation
367          (_("--- Use `\\[history-node]' or `\\[kill-node]' to exit ---\n")));
368       node = message_buffer_to_node ();
369       internal_info_help_node_contents = node->contents;
370     }
371   else
372     {
373       /* We already had the right contents, so simply use them. */
374       node = build_message_node ("", 0, 0);
375       free (node->contents);
376       node->contents = contents;
377       node->nodelen = 1 + strlen (contents);
378     }
379
380   internal_info_help_node = node;
381
382   /* Do not GC this node's contents.  It never changes, and we never need
383      to delete it once it is made.  If you change some things (such as
384      placing information about dynamic variables in the help text) then
385      you will need to allow the contents to be gc'd, and you will have to
386      arrange to always regenerate the help node. */
387 #if defined (HELP_NODE_GETS_REGENERATED)
388   add_gcable_pointer (internal_info_help_node->contents);
389 #endif
390
391   name_internal_node (internal_info_help_node, info_help_nodename);
392
393   /* Even though this is an internal node, we don't want the window
394      system to treat it specially.  So we turn off the internalness
395      of it here. */
396   internal_info_help_node->flags &= ~N_IsInternal;
397 }
398
399 /* Return a window which is the window showing help in this Info. */
400
401 /* If the eligible window's height is >= this, split it to make the help
402    window.  Otherwise display the help window in the current window.  */
403 #define HELP_SPLIT_SIZE 24
404
405 static WINDOW *
406 info_find_or_create_help_window ()
407 {
408   int help_is_only_window_p;
409   WINDOW *eligible = NULL;
410   WINDOW *help_window = get_window_of_node (internal_info_help_node);
411
412   /* If we couldn't find the help window, then make it. */
413   if (!help_window)
414     {
415       WINDOW *window;
416       int max = 0;
417
418       for (window = windows; window; window = window->next)
419         {
420           if (window->height > max)
421             {
422               max = window->height;
423               eligible = window;
424             }
425         }
426
427       if (!eligible)
428         return NULL;
429     }
430 #ifndef HELP_NODE_GETS_REGENERATED
431   else
432     /* help window is static, just return it.  */
433     return help_window;
434 #endif /* not HELP_NODE_GETS_REGENERATED */
435
436   /* Make sure that we have a node containing the help text.  The
437      argument is false if help will be the only window (so l must be used
438      to quit help), true if help will be one of several visible windows
439      (so CTRL-x 0 must be used to quit help).  */
440   help_is_only_window_p
441      = (help_window && !windows->next
442         || !help_window && eligible->height < HELP_SPLIT_SIZE);
443   create_internal_info_help_node (help_is_only_window_p);
444
445   /* Either use the existing window to display the help node, or create
446      a new window if there was no existing help window. */
447   if (!help_window)
448     { /* Split the largest window into 2 windows, and show the help text
449          in that window. */
450       if (eligible->height >= HELP_SPLIT_SIZE)
451         {
452           active_window = eligible;
453           help_window = window_make_window (internal_info_help_node);
454         }
455       else
456         {
457           set_remembered_pagetop_and_point (active_window);
458           window_set_node_of_window (active_window, internal_info_help_node);
459           help_window = active_window;
460         }
461     }
462   else
463     { /* Case where help node always gets regenerated, and we have an
464          existing window in which to place the node. */
465       if (active_window != help_window)
466         {
467           set_remembered_pagetop_and_point (active_window);
468           active_window = help_window;
469         }
470       window_set_node_of_window (active_window, internal_info_help_node);
471     }
472   remember_window_and_node (help_window, help_window->node);
473   return help_window;
474 }
475
476 /* Create or move to the help window. */
477 DECLARE_INFO_COMMAND (info_get_help_window, _("Display help message"))
478 {
479   WINDOW *help_window;
480
481   help_window = info_find_or_create_help_window ();
482   if (help_window)
483     {
484       active_window = help_window;
485       active_window->flags |= W_UpdateWindow;
486     }
487   else
488     {
489       info_error (msg_cant_make_help);
490     }
491 }
492
493 /* Show the Info help node.  This means that the "info" file is installed
494    where it can easily be found on your system. */
495 DECLARE_INFO_COMMAND (info_get_info_help_node, _("Visit Info node `(info)Help'"))
496 {
497   NODE *node;
498   char *nodename;
499
500   /* If there is a window on the screen showing the node "(info)Help" or
501      the node "(info)Help-Small-Screen", simply select that window. */
502   {
503     WINDOW *win;
504
505     for (win = windows; win; win = win->next)
506       {
507         if (win->node && win->node->filename &&
508             (strcasecmp
509              (filename_non_directory (win->node->filename), "info") == 0) &&
510             ((strcmp (win->node->nodename, "Help") == 0) ||
511              (strcmp (win->node->nodename, "Help-Small-Screen") == 0)))
512           {
513             active_window = win;
514             return;
515           }
516       }
517   }
518
519   /* If the current window is small, show the small screen help. */
520   if (active_window->height < 24)
521     nodename = "Help-Small-Screen";
522   else
523     nodename = "Help";
524
525   /* Try to get the info file for Info. */
526   node = info_get_node ("Info", nodename);
527
528   if (!node)
529     {
530       if (info_recent_file_error)
531         info_error (info_recent_file_error);
532       else
533         info_error (msg_cant_file_node, "Info", nodename);
534     }
535   else
536     {
537       /* If the current window is very large (greater than 45 lines),
538          then split it and show the help node in another window.
539          Otherwise, use the current window. */
540
541       if (active_window->height > 45)
542         active_window = window_make_window (node);
543       else
544         {
545           set_remembered_pagetop_and_point (active_window);
546           window_set_node_of_window (active_window, node);
547         }
548
549       remember_window_and_node (active_window, node);
550     }
551 }
552 \f
553 /* **************************************************************** */
554 /*                                                                  */
555 /*                   Groveling Info Keymaps and Docs                */
556 /*                                                                  */
557 /* **************************************************************** */
558
559 /* Return the documentation associated with the Info command FUNCTION. */
560 char *
561 function_documentation (cmd)
562      InfoCommand *cmd;
563 {
564   char *doc;
565
566 #if defined (INFOKEY)
567
568   doc = cmd->doc;
569
570 #else /* !INFOKEY */
571
572   register int i;
573
574   for (i = 0; function_doc_array[i].func; i++)
575     if (InfoFunction(cmd) == function_doc_array[i].func)
576       break;
577
578   doc = function_doc_array[i].func ? function_doc_array[i].doc : "";
579
580 #endif /* !INFOKEY */
581
582   return replace_in_documentation ((strlen (doc) == 0) ? doc : _(doc));
583 }
584
585 #if defined (NAMED_FUNCTIONS)
586 /* Return the user-visible name of the function associated with the
587    Info command FUNCTION. */
588 char *
589 function_name (cmd)
590      InfoCommand *cmd;
591 {
592 #if defined (INFOKEY)
593
594   return cmd->func_name;
595
596 #else /* !INFOKEY */
597
598   register int i;
599
600   for (i = 0; function_doc_array[i].func; i++)
601     if (InfoFunction(cmd) == function_doc_array[i].func)
602       break;
603
604   return (function_doc_array[i].func_name);
605
606 #endif /* !INFOKEY */
607 }
608
609 /* Return a pointer to the info command for function NAME. */
610 InfoCommand *
611 named_function (name)
612      char *name;
613 {
614   register int i;
615
616   for (i = 0; function_doc_array[i].func; i++)
617     if (strcmp (function_doc_array[i].func_name, name) == 0)
618       break;
619
620   return (DocInfoCmd(&function_doc_array[i]));
621 }
622 #endif /* NAMED_FUNCTIONS */
623
624 /* Return the documentation associated with KEY in MAP. */
625 char *
626 key_documentation (key, map)
627      char key;
628      Keymap map;
629 {
630   InfoCommand *function = map[key].function;
631
632   if (function)
633     return (function_documentation (function));
634   else
635     return ((char *)NULL);
636 }
637
638 DECLARE_INFO_COMMAND (describe_key, _("Print documentation for KEY"))
639 {
640   char keys[50];
641   unsigned char keystroke;
642   char *k = keys;
643   Keymap map;
644
645   *k = '\0';
646   map = window->keymap;
647
648   for (;;)
649     {
650       message_in_echo_area (_("Describe key: %s"), pretty_keyseq (keys));
651       keystroke = info_get_input_char ();
652       unmessage_in_echo_area ();
653
654 #if !defined (INFOKEY)
655       if (Meta_p (keystroke))
656         {
657           if (map[ESC].type != ISKMAP)
658             {
659               window_message_in_echo_area
660               (_("ESC %s is undefined."), pretty_keyname (UnMeta (keystroke)));
661               return;
662             }
663
664           *k++ = '\e';
665           keystroke = UnMeta (keystroke);
666           map = (Keymap)map[ESC].function;
667         }
668 #endif /* !INFOKEY */
669
670       /* Add the KEYSTROKE to our list. */
671       *k++ = keystroke;
672       *k = '\0';
673
674       if (map[keystroke].function == (InfoCommand *)NULL)
675         {
676           message_in_echo_area (_("%s is undefined."), pretty_keyseq (keys));
677           return;
678         }
679       else if (map[keystroke].type == ISKMAP)
680         {
681           map = (Keymap)map[keystroke].function;
682           continue;
683         }
684       else
685         {
686           char *keyname, *message, *fundoc, *funname = "";
687
688 #if defined (INFOKEY)
689           /* If the key is bound to do-lowercase-version, but its
690              lower-case variant is undefined, say that this key is
691              also undefined.  This is especially important for unbound
692              edit keys that emit an escape sequence: it's terribly
693              confusing to see a message "Home (do-lowercase-version)"
694              or some such when Home is unbound.  */
695           if (InfoFunction(map[keystroke].function) == info_do_lowercase_version)
696             {
697               unsigned char lowerkey = Meta_p(keystroke)
698                                        ? Meta (tolower (UnMeta (keystroke)))
699                                        : tolower (keystroke);
700
701               if (map[lowerkey].function == (InfoCommand *)NULL)
702                 {
703                   message_in_echo_area (_("%s is undefined."),
704                                         pretty_keyseq (keys));
705                   return;
706                 }
707             }
708 #endif
709
710           keyname = pretty_keyseq (keys);
711
712 #if defined (NAMED_FUNCTIONS)
713           funname = function_name (map[keystroke].function);
714 #endif /* NAMED_FUNCTIONS */
715
716           fundoc = function_documentation (map[keystroke].function);
717
718           message = (char *)xmalloc
719             (10 + strlen (keyname) + strlen (fundoc) + strlen (funname));
720
721 #if defined (NAMED_FUNCTIONS)
722           sprintf (message, "%s (%s): %s.", keyname, funname, fundoc);
723 #else
724           sprintf (message, _("%s is defined to %s."), keyname, fundoc);
725 #endif /* !NAMED_FUNCTIONS */
726
727           window_message_in_echo_area ("%s", message);
728           free (message);
729           break;
730         }
731     }
732 }
733
734 /* Return the pretty printable name of a single character. */
735 char *
736 pretty_keyname (key)
737      unsigned char key;
738 {
739   static char rep_buffer[30];
740   char *rep;
741
742   if (Meta_p (key))
743     {
744       char temp[20];
745
746       rep = pretty_keyname (UnMeta (key));
747
748 #if defined (INFOKEY)
749       sprintf (temp, "M-%s", rep);
750 #else /* !INFOKEY */
751       sprintf (temp, "ESC %s", rep);
752 #endif /* !INFOKEY */
753       strcpy (rep_buffer, temp);
754       rep = rep_buffer;
755     }
756   else if (Control_p (key))
757     {
758       switch (key)
759         {
760         case '\n': rep = "LFD"; break;
761         case '\t': rep = "TAB"; break;
762         case '\r': rep = "RET"; break;
763         case ESC:  rep = "ESC"; break;
764
765         default:
766           sprintf (rep_buffer, "C-%c", UnControl (key));
767           rep = rep_buffer;
768         }
769     }
770   else
771     {
772       switch (key)
773         {
774         case ' ': rep = "SPC"; break;
775         case DEL: rep = "DEL"; break;
776         default:
777           rep_buffer[0] = key;
778           rep_buffer[1] = '\0';
779           rep = rep_buffer;
780         }
781     }
782   return (rep);
783 }
784
785 /* Return the pretty printable string which represents KEYSEQ. */
786
787 static void pretty_keyseq_internal ();
788
789 char *
790 pretty_keyseq (keyseq)
791      char *keyseq;
792 {
793   static char keyseq_rep[200];
794
795   keyseq_rep[0] = '\0';
796   if (*keyseq)
797     pretty_keyseq_internal (keyseq, keyseq_rep);
798   return (keyseq_rep);
799 }
800
801 static void
802 pretty_keyseq_internal (keyseq, rep)
803      char *keyseq, *rep;
804 {
805   if (term_kP && strncmp(keyseq, term_kP, strlen(term_kP)) == 0)
806     {
807       strcpy(rep, "PgUp");
808       keyseq += strlen(term_kP);
809     }
810   else if (term_kN && strncmp(keyseq, term_kN, strlen(term_kN)) == 0)
811     {
812       strcpy(rep, "PgDn");
813       keyseq += strlen(term_kN);
814     }
815 #if defined(INFOKEY)
816   else if (term_kh && strncmp(keyseq, term_kh, strlen(term_kh)) == 0)
817     {
818       strcpy(rep, "Home");
819       keyseq += strlen(term_kh);
820     }
821   else if (term_ke && strncmp(keyseq, term_ke, strlen(term_ke)) == 0)
822     {
823       strcpy(rep, "End");
824       keyseq += strlen(term_ke);
825     }
826   else if (term_ki && strncmp(keyseq, term_ki, strlen(term_ki)) == 0)
827     {
828       strcpy(rep, "INS");
829       keyseq += strlen(term_ki);
830     }
831   else if (term_kx && strncmp(keyseq, term_kx, strlen(term_kx)) == 0)
832     {
833       strcpy(rep, "DEL");
834       keyseq += strlen(term_kx);
835     }
836 #endif /* INFOKEY */
837   else if (term_ku && strncmp(keyseq, term_ku, strlen(term_ku)) == 0)
838     {
839       strcpy(rep, "Up");
840       keyseq += strlen(term_ku);
841     }
842   else if (term_kd && strncmp(keyseq, term_kd, strlen(term_kd)) == 0)
843     {
844       strcpy(rep, "Down");
845       keyseq += strlen(term_kd);
846     }
847   else if (term_kl && strncmp(keyseq, term_kl, strlen(term_kl)) == 0)
848     {
849       strcpy(rep, "Left");
850       keyseq += strlen(term_kl);
851     }
852   else if (term_kr && strncmp(keyseq, term_kr, strlen(term_kr)) == 0)
853     {
854       strcpy(rep, "Right");
855       keyseq += strlen(term_kr);
856     }
857   else
858     {
859       strcpy (rep, pretty_keyname (keyseq[0]));
860       keyseq++;
861     }
862   if (*keyseq)
863     {
864       strcat (rep, " ");
865       pretty_keyseq_internal (keyseq, rep + strlen(rep));
866     }
867 }
868
869 /* Return a pointer to the last character in s that is found in f. */
870 static char *
871 strrpbrk (s, f)
872      const char *s, *f;
873 {
874   register const char *e = s + strlen(s);
875   register const char *t;
876
877   while (e-- != s)
878     {
879       for (t = f; *t; t++)
880         if (*e == *t)
881           return (char *)e;
882     }
883   return NULL;
884 }
885
886 /* Replace the names of functions with the key that invokes them. */
887 char *
888 replace_in_documentation (string, help_is_only_window_p)
889      char *string;
890      int help_is_only_window_p;
891 {
892   unsigned reslen = strlen (string);
893   register int i, start, next;
894   static char *result = (char *)NULL;
895
896   maybe_free (result);
897   result = (char *)xmalloc (1 + reslen);
898
899   i = next = start = 0;
900
901   /* Skip to the beginning of a replaceable function. */
902   for (i = start; string[i]; i++)
903     {
904       int j = i + 1;
905
906       /* Is this the start of a replaceable function name? */
907       if (string[i] == '\\')
908         {
909           char *fmt = NULL;
910           unsigned min = 0;
911           unsigned max = 0;
912
913           if(string[j] == '%')
914             {
915               if (string[++j] == '-')
916                 j++;
917               if (isdigit(string[j]))
918                 {
919                   min = atoi(string + j);
920                   while (isdigit(string[j]))
921                     j++;
922                   if (string[j] == '.' && isdigit(string[j + 1]))
923                     {
924                       j += 1;
925                       max = atoi(string + j);
926                       while (isdigit(string[j]))
927                         j++;
928                     }
929                   fmt = (char *)xmalloc (j - i + 2);
930                   strncpy (fmt, string + i + 1, j - i);
931                   fmt[j - i - 1] = 's';
932                   fmt[j - i] = '\0';
933                 }
934               else
935                 j = i + 1;
936             }
937           if (string[j] == '[')
938             {
939               unsigned arg = 0;
940               char *argstr = NULL;
941               char *rep_name, *fun_name, *rep;
942               InfoCommand *command;
943               char *repstr = NULL;
944               unsigned replen;
945
946               /* Copy in the old text. */
947               strncpy (result + next, string + start, i - start);
948               next += (i - start);
949               start = j + 1;
950
951               /* Look for an optional numeric arg. */
952               i = start;
953               if (isdigit(string[i])
954                   || (string[i] == '-' && isdigit(string[i + 1])) )
955                 {
956                   arg = atoi(string + i);
957                   if (string[i] == '-')
958                     i++;
959                   while (isdigit(string[i]))
960                     i++;
961                 }
962               start = i;
963
964               /* Move to the end of the function name. */
965               for (i = start; string[i] && (string[i] != ']'); i++);
966
967               rep_name = (char *)xmalloc (1 + i - start);
968               strncpy (rep_name, string + start, i - start);
969               rep_name[i - start] = '\0';
970
971             /* If we have only one window (because the window size was too
972                small to split it), we have to quit help by going back one
973                noew in the history list, not deleting the window.  */
974               if (strcmp (rep_name, "quit-help") == 0)
975                 fun_name = help_is_only_window_p ? "history-node"
976                                                  : "delete-window";
977               else
978                 fun_name = rep_name;
979
980               /* Find a key which invokes this function in the info_keymap. */
981               command = named_function (fun_name);
982
983               free (rep_name);
984
985               /* If the internal documentation string fails, there is a
986                  serious problem with the associated command's documentation.
987                  We croak so that it can be fixed immediately. */
988               if (!command)
989                 abort ();
990
991               if (arg)
992                 {
993                   char *argrep, *p;
994
995                   argrep = where_is (info_keymap, InfoCmd(info_add_digit_to_numeric_arg));
996                   p = argrep ? strrpbrk (argrep, "0123456789-") : NULL;
997                   if (p)
998                     {
999                       argstr = (char *)xmalloc (p - argrep + 21);
1000                       strncpy (argstr, argrep, p - argrep);
1001                       sprintf (argstr + (p - argrep), "%d", arg);
1002                     }
1003                   else
1004                     command = NULL;
1005                 }
1006               rep = command ? where_is (info_keymap, command) : NULL;
1007               if (!rep)
1008                 rep = "N/A";
1009               replen = (argstr ? strlen (argstr) + 1 : 0) + strlen (rep);
1010               repstr = (char *)xmalloc (replen);
1011               repstr[0] = '\0';
1012               if (argstr)
1013                 {
1014                   strcat(repstr, argstr);
1015                   strcat(repstr, " ");
1016                   free (argstr);
1017                 }
1018               strcat(repstr, rep);
1019
1020               if (fmt)
1021                 {
1022                   if (replen > max)
1023                     replen = max;
1024                   if (replen < min)
1025                     replen = min;
1026                 }
1027               if (next + replen > reslen)
1028                 {
1029                   reslen = next + replen + 1;
1030                   result = (char *)xrealloc (result, reslen + 1);
1031                 }
1032
1033               if (fmt)
1034                   sprintf (result + next, fmt, repstr);
1035               else
1036                   strcpy (result + next, repstr);
1037
1038               next = strlen (result);
1039               free (repstr);
1040
1041               start = i;
1042               if (string[i])
1043                 start++;
1044             }
1045
1046           maybe_free (fmt);
1047         }
1048     }
1049   strcpy (result + next, string + start);
1050   return (result);
1051 }
1052
1053 /* Return a string of characters which could be typed from the keymap
1054    MAP to invoke FUNCTION. */
1055 static char *where_is_rep = (char *)NULL;
1056 static int where_is_rep_index = 0;
1057 static int where_is_rep_size = 0;
1058
1059 char *
1060 where_is (map, cmd)
1061      Keymap map;
1062      InfoCommand *cmd;
1063 {
1064   char *rep;
1065
1066   if (!where_is_rep_size)
1067     where_is_rep = (char *)xmalloc (where_is_rep_size = 100);
1068   where_is_rep_index = 0;
1069
1070   rep = where_is_internal (map, cmd);
1071
1072   /* If it couldn't be found, return "M-x Foo" (or equivalent). */
1073   if (!rep)
1074     {
1075       char *name;
1076
1077       name = function_name (cmd);
1078       if (!name)
1079         return NULL; /* no such function */
1080
1081       rep = where_is_internal (map, InfoCmd(info_execute_command));
1082       if (!rep)
1083         return ""; /* function exists but can't be got to by user */
1084
1085       sprintf (where_is_rep, "%s %s", rep, name);
1086
1087       rep = where_is_rep;
1088     }
1089   return (rep);
1090 }
1091
1092 /* Return the printed rep of the keystrokes that invoke FUNCTION,
1093    as found in MAP, or NULL. */
1094 static char *
1095 where_is_internal (map, cmd)
1096      Keymap map;
1097      InfoCommand *cmd;
1098 {
1099 #if defined(INFOKEY)
1100
1101   register FUNCTION_KEYSEQ *k;
1102
1103   for (k = cmd->keys; k; k = k->next)
1104     if (k->map == map)
1105       return pretty_keyseq (k->keyseq);
1106
1107   return NULL;
1108
1109 #else /* !INFOKEY */
1110
1111   register int i;
1112
1113   /* If the function is directly invokable in MAP, return the representation
1114      of that keystroke. */
1115   for (i = 0; i < 256; i++)
1116     if ((map[i].type == ISFUNC) && map[i].function == cmd)
1117       {
1118         sprintf (where_is_rep + where_is_rep_index, "%s", pretty_keyname (i));
1119         return (where_is_rep);
1120       }
1121
1122   /* Okay, search subsequent maps for this function. */
1123   for (i = 0; i < 256; i++)
1124     {
1125       if (map[i].type == ISKMAP)
1126         {
1127           int saved_index = where_is_rep_index;
1128           char *rep;
1129
1130           sprintf (where_is_rep + where_is_rep_index, "%s ",
1131                    pretty_keyname (i));
1132
1133           where_is_rep_index = strlen (where_is_rep);
1134           rep = where_is_internal ((Keymap)map[i].function, cmd);
1135
1136           if (rep)
1137             return (where_is_rep);
1138
1139           where_is_rep_index = saved_index;
1140         }
1141     }
1142
1143   return NULL;
1144
1145 #endif /* INFOKEY */
1146 }
1147
1148 extern char *read_function_name ();
1149
1150 DECLARE_INFO_COMMAND (info_where_is,
1151    _("Show what to type to execute a given command"))
1152 {
1153   char *command_name;
1154
1155   command_name = read_function_name (_("Where is command: "), window);
1156
1157   if (!command_name)
1158     {
1159       info_abort_key (active_window, count, key);
1160       return;
1161     }
1162
1163   if (*command_name)
1164     {
1165       InfoCommand *command;
1166
1167       command = named_function (command_name);
1168
1169       if (command)
1170         {
1171           char *location;
1172
1173           location = where_is (active_window->keymap, command);
1174
1175           if (!location || !location[0])
1176             {
1177               info_error (_("`%s' is not on any keys"), command_name);
1178             }
1179           else
1180             {
1181               if (strstr (location, function_name (command)))
1182                 window_message_in_echo_area
1183                   (_("%s can only be invoked via %s."), command_name, location);
1184               else
1185                 window_message_in_echo_area
1186                   (_("%s can be invoked via %s."), command_name, location);
1187             }
1188         }
1189       else
1190         info_error (_("There is no function named `%s'"), command_name);
1191     }
1192
1193   free (command_name);
1194 }