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