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