Back to index

tetex-bin  3.0
infodoc.c
Go to the documentation of this file.
00001 /* infodoc.c -- functions which build documentation nodes.
00002    $Id: infodoc.c,v 1.8 2004/04/11 17:56:45 karl Exp $
00003 
00004    Copyright (C) 1993, 1997, 1998, 1999, 2001, 2002, 2003, 2004 Free Software
00005    Foundation, Inc.
00006 
00007    This program is free software; you can redistribute it and/or modify
00008    it under the terms of the GNU General Public License as published by
00009    the Free Software Foundation; either version 2, or (at your option)
00010    any later version.
00011 
00012    This program is distributed in the hope that it will be useful,
00013    but WITHOUT ANY WARRANTY; without even the implied warranty of
00014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015    GNU General Public License for more details.
00016 
00017    You should have received a copy of the GNU General Public License
00018    along with this program; if not, write to the Free Software
00019    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00020 
00021    Written by Brian Fox (bfox@ai.mit.edu). */
00022 
00023 #include "info.h"
00024 #include "funs.h"
00025 
00026 /* HELP_NODE_GETS_REGENERATED is always defined now that keys may get
00027    rebound, or other changes in the help text may occur.  */
00028 #define HELP_NODE_GETS_REGENERATED 1
00029 
00030 /* The name of the node used in the help window. */
00031 static char *info_help_nodename = "*Info Help*";
00032 
00033 /* A node containing printed key bindings and their documentation. */
00034 static NODE *internal_info_help_node = (NODE *)NULL;
00035 
00036 /* A pointer to the contents of the help node. */
00037 static char *internal_info_help_node_contents = (char *)NULL;
00038 
00039 /* The (more or less) static text which appears in the internal info
00040    help node.  The actual key bindings are inserted.  Keep the
00041    underlines (****, etc.) in the same N_ call as  the text lines they
00042    refer to, so translations can make the number of *'s or -'s match.  */
00043 #if defined(INFOKEY)
00044 
00045 static char *info_internal_help_text[] = {
00046   N_("Basic Commands in Info Windows\n\
00047 ******************************\n"),
00048   "\n",
00049   N_("\\%-10[quit-help]  Quit this help.\n"),
00050   N_("\\%-10[quit]  Quit Info altogether.\n"),
00051   N_("\\%-10[get-info-help-node]  Invoke the Info tutorial.\n"),
00052   "\n",
00053   N_("Selecting other nodes:\n\
00054 ----------------------\n"),
00055   N_("\\%-10[next-node]  Move to the \"next\" node of this node.\n"),
00056   N_("\\%-10[prev-node]  Move to the \"previous\" node of this node.\n"),
00057   N_("\\%-10[up-node]  Move \"up\" from this node.\n"),
00058   N_("\\%-10[menu-item]  Pick menu item specified by name.\n\
00059               Picking a menu item causes another node to be selected.\n"),
00060   N_("\\%-10[xref-item]  Follow a cross reference.  Reads name of reference.\n"),
00061   N_("\\%-10[history-node]  Move to the last node seen in this window.\n"),
00062   N_("\\%-10[move-to-next-xref]  Skip to next hypertext link within this node.\n"),
00063   N_("\\%-10[move-to-prev-xref]  Skip to previous hypertext link within this node.\n"),
00064   N_("\\%-10[select-reference-this-line]  Follow the hypertext link under cursor.\n"),
00065   N_("\\%-10[dir-node]  Move to the `directory' node.  Equivalent to `\\[goto-node] (DIR)'.\n"),
00066   N_("\\%-10[top-node]  Move to the Top node.  Equivalent to `\\[goto-node] Top'.\n"),
00067   "\n",
00068   N_("Moving within a node:\n\
00069 ---------------------\n"),
00070   N_("\\%-10[beginning-of-node]  Go to the beginning of this node.\n"),
00071   N_("\\%-10[end-of-node]  Go to the end of this node.\n"),
00072   N_("\\%-10[next-line]  Scroll forward 1 line.\n"),
00073   N_("\\%-10[prev-line]  Scroll backward 1 line.\n"),
00074   N_("\\%-10[scroll-forward]  Scroll forward a page.\n"),
00075   N_("\\%-10[scroll-backward]  Scroll backward a page.\n"),
00076   "\n",
00077   N_("Other commands:\n\
00078 ---------------\n"),
00079   N_("\\%-10[menu-digit]  Pick first ... ninth item in node's menu.\n"),
00080   N_("\\%-10[last-menu-item]  Pick last item in node's menu.\n"),
00081   N_("\\%-10[index-search]  Search for a specified string in the index entries of this Info\n\
00082               file, and select the node referenced by the first entry found.\n"),
00083   N_("\\%-10[goto-node]  Move to node specified by name.\n\
00084               You may include a filename as well, as in (FILENAME)NODENAME.\n"),
00085   N_("\\%-10[search]  Search forward for a specified string\n\
00086               and select the node in which the next occurrence is found.\n"),
00087   N_("\\%-10[search-backward]  Search backward for a specified string\n\
00088               and select the node in which the previous occurrence is found.\n"),
00089   NULL
00090 };
00091 
00092 #else /* !INFOKEY */
00093 
00094 static char *info_internal_help_text[] = {
00095   N_("Basic Commands in Info Windows\n\
00096 ******************************\n"),
00097   "\n",
00098   N_("  %-10s  Quit this help.\n"),
00099   N_("  %-10s  Quit Info altogether.\n"),
00100   N_("  %-10s  Invoke the Info tutorial.\n"),
00101   "\n",
00102   N_("Selecting other nodes:\n\
00103 ----------------------\n",
00104   N_("  %-10s  Move to the `next' node of this node.\n"),
00105   N_("  %-10s  Move to the `previous' node of this node.\n"),
00106   N_("  %-10s  Move `up' from this node.\n"),
00107   N_("  %-10s  Pick menu item specified by name.\n"),
00108   N_("              Picking a menu item causes another node to be selected.\n"),
00109   N_("  %-10s  Follow a cross reference.  Reads name of reference.\n"),
00110   N_("  %-10s  Move to the last node seen in this window.\n"),
00111   N_("  %-10s  Skip to next hypertext link within this node.\n"),
00112   N_("  %-10s  Follow the hypertext link under cursor.\n"),
00113   N_("  %-10s  Move to the `directory' node.  Equivalent to `g (DIR)'.\n"),
00114   N_("  %-10s  Move to the Top node.  Equivalent to `g Top'.\n"),
00115   "\n",
00116   N_("Moving within a node:\n\
00117 ---------------------\n"),
00118   N_("  %-10s  Scroll forward a page.\n"),
00119   N_("  %-10s  Scroll backward a page.\n"),
00120   N_("  %-10s  Go to the beginning of this node.\n"),
00121   N_("  %-10s  Go to the end of this node.\n"),
00122   N_("  %-10s  Scroll forward 1 line.\n"),
00123   N_("  %-10s  Scroll backward 1 line.\n"),
00124   "\n",
00125   N_("Other commands:\n\
00126 ---------------\n"),
00127   N_("  %-10s  Pick first ... ninth item in node's menu.\n"),
00128   N_("  %-10s  Pick last item in node's menu.\n"),
00129   N_("  %-10s  Search for a specified string in the index entries of this Info\n"),
00130   N_("              file, and select the node referenced by the first entry found.\n"),
00131   N_("  %-10s  Move to node specified by name.\n"),
00132   N_("              You may include a filename as well, as in (FILENAME)NODENAME.\n"),
00133   N_("  %-10s  Search forward for a specified string,\n"),
00134   N_("              and select the node in which the next occurrence is found.\n"),
00135   N_("  %-10s  Search backward for a specified string\n"),
00136   N_("              and select the node in which the next occurrence is found.\n"),
00137   NULL
00138 };
00139 
00140 static char *info_help_keys_text[][2] = {
00141   { "", "" },
00142   { "", "" },
00143   { "", "" },
00144   { "CTRL-x 0", "CTRL-x 0" },
00145   { "q", "q" },
00146   { "h", "ESC h" },
00147   { "", "" },
00148   { "", "" },
00149   { "", "" },
00150   { "SPC", "SPC" },
00151   { "DEL", "b" },
00152   { "b", "ESC b" },
00153   { "e", "ESC e" },
00154   { "ESC 1 SPC", "RET" },
00155   { "ESC 1 DEL", "y" },
00156   { "", "" },
00157   { "", "" },
00158   { "", "" },
00159   { "n", "CTRL-x n" },
00160   { "p", "CTRL-x p" },
00161   { "u", "CTRL-x u" },
00162   { "m", "ESC m" },
00163   { "", "" },
00164   { "f", "ESC f" },
00165   { "l", "l" },
00166   { "TAB", "TAB" },
00167   { "RET", "CTRL-x RET" },
00168   { "d", "ESC d" },
00169   { "t", "ESC t" },
00170   { "", "" },
00171   { "", "" },
00172   { "", "" },
00173   { "1-9", "ESC 1-9" },
00174   { "0", "ESC 0" },
00175   { "i", "CTRL-x i" },
00176   { "", "" },
00177   { "g", "CTRL-x g" },
00178   { "", "" },
00179   { "s", "/" },
00180   { "", "" },
00181   { "ESC - s", "?" },
00182   { "", "" },
00183   NULL
00184 };
00185 
00186 #endif /* !INFOKEY */
00187 
00188 static char *where_is_internal (Keymap map, InfoCommand *cmd);
00189 
00190 void
00191 dump_map_to_message_buffer (char *prefix, Keymap map)
00192 {
00193   register int i;
00194   unsigned prefix_len = strlen (prefix);
00195   char *new_prefix = (char *)xmalloc (prefix_len + 2);
00196 
00197   strncpy (new_prefix, prefix, prefix_len);
00198   new_prefix[prefix_len + 1] = '\0';
00199 
00200   for (i = 0; i < 256; i++)
00201     {
00202       new_prefix[prefix_len] = i;
00203       if (map[i].type == ISKMAP)
00204         {
00205           dump_map_to_message_buffer (new_prefix, (Keymap)map[i].function);
00206         }
00207       else if (map[i].function)
00208         {
00209           register int last;
00210           char *doc, *name;
00211 
00212           doc = function_documentation (map[i].function);
00213           name = function_name (map[i].function);
00214 
00215           if (!*doc)
00216             continue;
00217 
00218           /* Find out if there is a series of identical functions, as in
00219              ea_insert (). */
00220           for (last = i + 1; last < 256; last++)
00221             if ((map[last].type != ISFUNC) ||
00222                 (map[last].function != map[i].function))
00223               break;
00224 
00225           if (last - 1 != i)
00226             {
00227               printf_to_message_buffer ("%s .. ", pretty_keyseq (new_prefix),
00228                   NULL, NULL);
00229               new_prefix[prefix_len] = last - 1;
00230               printf_to_message_buffer ("%s\t", pretty_keyseq (new_prefix),
00231                   NULL, NULL);
00232               i = last - 1;
00233             }
00234           else
00235             printf_to_message_buffer ("%s\t", pretty_keyseq (new_prefix),
00236                 NULL, NULL);
00237 
00238 #if defined (NAMED_FUNCTIONS)
00239           /* Print the name of the function, and some padding before the
00240              documentation string is printed. */
00241           {
00242             int length_so_far;
00243             int desired_doc_start = 40; /* Must be multiple of 8. */
00244 
00245             printf_to_message_buffer ("(%s)", name, NULL, NULL);
00246             length_so_far = message_buffer_length_this_line ();
00247 
00248             if ((desired_doc_start + strlen (doc))
00249                 >= (unsigned int) the_screen->width)
00250               printf_to_message_buffer ("\n     ", NULL, NULL, NULL);
00251             else
00252               {
00253                 while (length_so_far < desired_doc_start)
00254                   {
00255                     printf_to_message_buffer ("\t", NULL, NULL, NULL);
00256                     length_so_far += character_width ('\t', length_so_far);
00257                   }
00258               }
00259           }
00260 #endif /* NAMED_FUNCTIONS */
00261           printf_to_message_buffer ("%s\n", doc, NULL, NULL);
00262         }
00263     }
00264   free (new_prefix);
00265 }
00266 
00267 /* How to create internal_info_help_node.  HELP_IS_ONLY_WINDOW_P says
00268    whether we're going to end up in a second (or more) window of our
00269    own, or whether there's only one window and we're going to usurp it.
00270    This determines how to quit the help window.  Maybe we should just
00271    make q do the right thing in both cases.  */
00272 
00273 static void
00274 create_internal_info_help_node (int help_is_only_window_p)
00275 {
00276   register int i;
00277   NODE *node;
00278   char *contents = NULL;
00279   char *exec_keys;
00280 
00281 #ifndef HELP_NODE_GETS_REGENERATED
00282   if (internal_info_help_node_contents)
00283     contents = internal_info_help_node_contents;
00284 #endif /* !HELP_NODE_GETS_REGENERATED */
00285 
00286   if (!contents)
00287     {
00288       int printed_one_mx = 0;
00289 
00290       initialize_message_buffer ();
00291 
00292       for (i = 0; info_internal_help_text[i]; i++)
00293         {
00294 #ifdef INFOKEY
00295           printf_to_message_buffer (replace_in_documentation
00296               ((char *) _(info_internal_help_text[i]), help_is_only_window_p),
00297               NULL, NULL, NULL);
00298 #else
00299           /* Don't translate blank lines, gettext outputs the po file
00300              header in that case.  We want a blank line.  */
00301           char *msg = *(info_internal_help_text[i])
00302                       ? _(info_internal_help_text[i])
00303                       : info_internal_help_text[i];
00304           char *key = info_help_keys_text[i][vi_keys_p];
00305 
00306           /* If we have only one window (because the window size was too
00307              small to split it), CTRL-x 0 doesn't work to `quit' help.  */
00308           if (STREQ (key, "CTRL-x 0") && help_is_only_window_p)
00309             key = "l";
00310 
00311           printf_to_message_buffer (msg, key, NULL, NULL);
00312 #endif /* !INFOKEY */
00313         }
00314 
00315       printf_to_message_buffer ("---------------------\n\n", NULL, NULL, NULL);
00316       printf_to_message_buffer ((char *) _("The current search path is:\n"),
00317           NULL, NULL, NULL);
00318       printf_to_message_buffer ("  %s\n", infopath, NULL, NULL);
00319       printf_to_message_buffer ("---------------------\n\n", NULL, NULL, NULL);
00320       printf_to_message_buffer ((char *) _("Commands available in Info windows:\n\n"),
00321           NULL, NULL, NULL);
00322       dump_map_to_message_buffer ("", info_keymap);
00323       printf_to_message_buffer ("---------------------\n\n", NULL, NULL, NULL);
00324       printf_to_message_buffer ((char *) _("Commands available in the echo area:\n\n"),
00325           NULL, NULL, NULL);
00326       dump_map_to_message_buffer ("", echo_area_keymap);
00327 
00328 #if defined (NAMED_FUNCTIONS)
00329       /* Get a list of commands which have no keystroke equivs. */
00330       exec_keys = where_is (info_keymap, InfoCmd(info_execute_command));
00331       if (exec_keys)
00332         exec_keys = xstrdup (exec_keys);
00333       for (i = 0; function_doc_array[i].func; i++)
00334         {
00335           InfoCommand *cmd = DocInfoCmd(&function_doc_array[i]);
00336 
00337           if (InfoFunction(cmd) != (VFunction *) info_do_lowercase_version
00338               && !where_is_internal (info_keymap, cmd)
00339               && !where_is_internal (echo_area_keymap, cmd))
00340             {
00341               if (!printed_one_mx)
00342                 {
00343                   printf_to_message_buffer ("---------------------\n\n",
00344                       NULL, NULL, NULL);
00345                   if (exec_keys && exec_keys[0])
00346                       printf_to_message_buffer
00347                         ((char *) _("The following commands can only be invoked via %s:\n\n"),
00348                          exec_keys, NULL, NULL);
00349                   else
00350                       printf_to_message_buffer
00351                         ((char *) _("The following commands cannot be invoked at all:\n\n"),
00352                          NULL, NULL, NULL);
00353                   printed_one_mx = 1;
00354                 }
00355 
00356               printf_to_message_buffer
00357                 ("%s %s\n     %s\n",
00358                  exec_keys,
00359                  function_doc_array[i].func_name,
00360                  replace_in_documentation (strlen (function_doc_array[i].doc)
00361                    ? (char *) _(function_doc_array[i].doc) : "", 0)
00362                 );
00363 
00364             }
00365         }
00366 
00367       if (printed_one_mx)
00368         printf_to_message_buffer ("\n", NULL, NULL, NULL);
00369 
00370       maybe_free (exec_keys);
00371 #endif /* NAMED_FUNCTIONS */
00372 
00373       printf_to_message_buffer
00374         ("%s", replace_in_documentation
00375          ((char *) _("--- Use `\\[history-node]' or `\\[kill-node]' to exit ---\n"), 0),
00376          NULL, NULL);
00377       node = message_buffer_to_node ();
00378       internal_info_help_node_contents = node->contents;
00379     }
00380   else
00381     {
00382       /* We already had the right contents, so simply use them. */
00383       node = build_message_node ("", 0, 0);
00384       free (node->contents);
00385       node->contents = contents;
00386       node->nodelen = 1 + strlen (contents);
00387     }
00388 
00389   internal_info_help_node = node;
00390 
00391   /* Do not GC this node's contents.  It never changes, and we never need
00392      to delete it once it is made.  If you change some things (such as
00393      placing information about dynamic variables in the help text) then
00394      you will need to allow the contents to be gc'd, and you will have to
00395      arrange to always regenerate the help node. */
00396 #if defined (HELP_NODE_GETS_REGENERATED)
00397   add_gcable_pointer (internal_info_help_node->contents);
00398 #endif
00399 
00400   name_internal_node (internal_info_help_node, info_help_nodename);
00401 
00402   /* Even though this is an internal node, we don't want the window
00403      system to treat it specially.  So we turn off the internalness
00404      of it here. */
00405   internal_info_help_node->flags &= ~N_IsInternal;
00406 }
00407 
00408 /* Return a window which is the window showing help in this Info. */
00409 
00410 /* If the eligible window's height is >= this, split it to make the help
00411    window.  Otherwise display the help window in the current window.  */
00412 #define HELP_SPLIT_SIZE 24
00413 
00414 static WINDOW *
00415 info_find_or_create_help_window (void)
00416 {
00417   int help_is_only_window_p;
00418   WINDOW *eligible = NULL;
00419   WINDOW *help_window = get_window_of_node (internal_info_help_node);
00420 
00421   /* If we couldn't find the help window, then make it. */
00422   if (!help_window)
00423     {
00424       WINDOW *window;
00425       int max = 0;
00426 
00427       for (window = windows; window; window = window->next)
00428         {
00429           if (window->height > max)
00430             {
00431               max = window->height;
00432               eligible = window;
00433             }
00434         }
00435 
00436       if (!eligible)
00437         return NULL;
00438     }
00439 #ifndef HELP_NODE_GETS_REGENERATED
00440   else
00441     /* help window is static, just return it.  */
00442     return help_window;
00443 #endif /* not HELP_NODE_GETS_REGENERATED */
00444 
00445   /* Make sure that we have a node containing the help text.  The
00446      argument is false if help will be the only window (so l must be used
00447      to quit help), true if help will be one of several visible windows
00448      (so CTRL-x 0 must be used to quit help).  */
00449   help_is_only_window_p = ((help_window && !windows->next)
00450         || (!help_window && eligible->height < HELP_SPLIT_SIZE));
00451   create_internal_info_help_node (help_is_only_window_p);
00452 
00453   /* Either use the existing window to display the help node, or create
00454      a new window if there was no existing help window. */
00455   if (!help_window)
00456     { /* Split the largest window into 2 windows, and show the help text
00457          in that window. */
00458       if (eligible->height >= HELP_SPLIT_SIZE)
00459         {
00460           active_window = eligible;
00461           help_window = window_make_window (internal_info_help_node);
00462         }
00463       else
00464         {
00465           set_remembered_pagetop_and_point (active_window);
00466           window_set_node_of_window (active_window, internal_info_help_node);
00467           help_window = active_window;
00468         }
00469     }
00470   else
00471     { /* Case where help node always gets regenerated, and we have an
00472          existing window in which to place the node. */
00473       if (active_window != help_window)
00474         {
00475           set_remembered_pagetop_and_point (active_window);
00476           active_window = help_window;
00477         }
00478       window_set_node_of_window (active_window, internal_info_help_node);
00479     }
00480   remember_window_and_node (help_window, help_window->node);
00481   return help_window;
00482 }
00483 
00484 /* Create or move to the help window. */
00485 DECLARE_INFO_COMMAND (info_get_help_window, _("Display help message"))
00486 {
00487   WINDOW *help_window;
00488 
00489   help_window = info_find_or_create_help_window ();
00490   if (help_window)
00491     {
00492       active_window = help_window;
00493       active_window->flags |= W_UpdateWindow;
00494     }
00495   else
00496     {
00497       info_error ((char *) msg_cant_make_help, NULL, NULL);
00498     }
00499 }
00500 
00501 /* Show the Info help node.  This means that the "info" file is installed
00502    where it can easily be found on your system. */
00503 DECLARE_INFO_COMMAND (info_get_info_help_node, _("Visit Info node `(info)Help'"))
00504 {
00505   NODE *node;
00506   char *nodename;
00507 
00508   /* If there is a window on the screen showing the node "(info)Help" or
00509      the node "(info)Help-Small-Screen", simply select that window. */
00510   {
00511     WINDOW *win;
00512 
00513     for (win = windows; win; win = win->next)
00514       {
00515         if (win->node && win->node->filename &&
00516             (strcasecmp
00517              (filename_non_directory (win->node->filename), "info") == 0) &&
00518             ((strcmp (win->node->nodename, "Help") == 0) ||
00519              (strcmp (win->node->nodename, "Help-Small-Screen") == 0)))
00520           {
00521             active_window = win;
00522             return;
00523           }
00524       }
00525   }
00526 
00527   /* If the current window is small, show the small screen help. */
00528   if (active_window->height < 24)
00529     nodename = "Help-Small-Screen";
00530   else
00531     nodename = "Help";
00532 
00533   /* Try to get the info file for Info. */
00534   node = info_get_node ("Info", nodename);
00535 
00536   if (!node)
00537     {
00538       if (info_recent_file_error)
00539         info_error (info_recent_file_error, NULL, NULL);
00540       else
00541         info_error ((char *) msg_cant_file_node, "Info", nodename);
00542     }
00543   else
00544     {
00545       /* If the current window is very large (greater than 45 lines),
00546          then split it and show the help node in another window.
00547          Otherwise, use the current window. */
00548 
00549       if (active_window->height > 45)
00550         active_window = window_make_window (node);
00551       else
00552         {
00553           set_remembered_pagetop_and_point (active_window);
00554           window_set_node_of_window (active_window, node);
00555         }
00556 
00557       remember_window_and_node (active_window, node);
00558     }
00559 }
00560 
00561 /* **************************************************************** */
00562 /*                                                                  */
00563 /*                   Groveling Info Keymaps and Docs                */
00564 /*                                                                  */
00565 /* **************************************************************** */
00566 
00567 /* Return the documentation associated with the Info command FUNCTION. */
00568 char *
00569 function_documentation (InfoCommand *cmd)
00570 {
00571   char *doc;
00572 
00573 #if defined (INFOKEY)
00574 
00575   doc = cmd->doc;
00576 
00577 #else /* !INFOKEY */
00578 
00579   register int i;
00580 
00581   for (i = 0; function_doc_array[i].func; i++)
00582     if (InfoFunction(cmd) == function_doc_array[i].func)
00583       break;
00584 
00585   doc = function_doc_array[i].func ? function_doc_array[i].doc : "";
00586 
00587 #endif /* !INFOKEY */
00588 
00589   return replace_in_documentation ((strlen (doc) == 0) ? doc : (char *) _(doc), 0);
00590 }
00591 
00592 #if defined (NAMED_FUNCTIONS)
00593 /* Return the user-visible name of the function associated with the
00594    Info command FUNCTION. */
00595 char *
00596 function_name (InfoCommand *cmd)
00597 {
00598 #if defined (INFOKEY)
00599 
00600   return cmd->func_name;
00601 
00602 #else /* !INFOKEY */
00603 
00604   register int i;
00605 
00606   for (i = 0; function_doc_array[i].func; i++)
00607     if (InfoFunction(cmd) == function_doc_array[i].func)
00608       break;
00609 
00610   return (function_doc_array[i].func_name);
00611 
00612 #endif /* !INFOKEY */
00613 }
00614 
00615 /* Return a pointer to the info command for function NAME. */
00616 InfoCommand *
00617 named_function (char *name)
00618 {
00619   register int i;
00620 
00621   for (i = 0; function_doc_array[i].func; i++)
00622     if (strcmp (function_doc_array[i].func_name, name) == 0)
00623       break;
00624 
00625   return (DocInfoCmd(&function_doc_array[i]));
00626 }
00627 #endif /* NAMED_FUNCTIONS */
00628 
00629 /* Return the documentation associated with KEY in MAP. */
00630 char *
00631 key_documentation (char key, Keymap map)
00632 {
00633   InfoCommand *function = map[key].function;
00634 
00635   if (function)
00636     return (function_documentation (function));
00637   else
00638     return ((char *)NULL);
00639 }
00640 
00641 DECLARE_INFO_COMMAND (describe_key, _("Print documentation for KEY"))
00642 {
00643   char keys[50];
00644   unsigned char keystroke;
00645   char *k = keys;
00646   Keymap map;
00647 
00648   *k = '\0';
00649   map = window->keymap;
00650 
00651   for (;;)
00652     {
00653       message_in_echo_area ((char *) _("Describe key: %s"),
00654           pretty_keyseq (keys), NULL);
00655       keystroke = info_get_input_char ();
00656       unmessage_in_echo_area ();
00657 
00658 #if !defined (INFOKEY)
00659       if (Meta_p (keystroke))
00660         {
00661           if (map[ESC].type != ISKMAP)
00662             {
00663               window_message_in_echo_area
00664               (_("ESC %s is undefined."), pretty_keyname (UnMeta (keystroke)));
00665               return;
00666             }
00667 
00668           *k++ = '\e';
00669           keystroke = UnMeta (keystroke);
00670           map = (Keymap)map[ESC].function;
00671         }
00672 #endif /* !INFOKEY */
00673 
00674       /* Add the KEYSTROKE to our list. */
00675       *k++ = keystroke;
00676       *k = '\0';
00677 
00678       if (map[keystroke].function == (InfoCommand *)NULL)
00679         {
00680           message_in_echo_area ((char *) _("%s is undefined."),
00681               pretty_keyseq (keys), NULL);
00682           return;
00683         }
00684       else if (map[keystroke].type == ISKMAP)
00685         {
00686           map = (Keymap)map[keystroke].function;
00687           continue;
00688         }
00689       else
00690         {
00691           char *keyname, *message, *fundoc, *funname = "";
00692 
00693 #if defined (INFOKEY)
00694           /* If the key is bound to do-lowercase-version, but its
00695              lower-case variant is undefined, say that this key is
00696              also undefined.  This is especially important for unbound
00697              edit keys that emit an escape sequence: it's terribly
00698              confusing to see a message "Home (do-lowercase-version)"
00699              or some such when Home is unbound.  */
00700           if (InfoFunction(map[keystroke].function)
00701               == (VFunction *) info_do_lowercase_version)
00702             {
00703               unsigned char lowerkey = Meta_p(keystroke)
00704                                        ? Meta (tolower (UnMeta (keystroke)))
00705                                        : tolower (keystroke);
00706 
00707               if (map[lowerkey].function == (InfoCommand *)NULL)
00708                 {
00709                   message_in_echo_area ((char *) _("%s is undefined."),
00710                                         pretty_keyseq (keys), NULL);
00711                   return;
00712                 }
00713             }
00714 #endif
00715 
00716           keyname = pretty_keyseq (keys);
00717 
00718 #if defined (NAMED_FUNCTIONS)
00719           funname = function_name (map[keystroke].function);
00720 #endif /* NAMED_FUNCTIONS */
00721 
00722           fundoc = function_documentation (map[keystroke].function);
00723 
00724           message = (char *)xmalloc
00725             (10 + strlen (keyname) + strlen (fundoc) + strlen (funname));
00726 
00727 #if defined (NAMED_FUNCTIONS)
00728           sprintf (message, "%s (%s): %s.", keyname, funname, fundoc);
00729 #else
00730           sprintf (message, _("%s is defined to %s."), keyname, fundoc);
00731 #endif /* !NAMED_FUNCTIONS */
00732 
00733           window_message_in_echo_area ("%s", message, NULL);
00734           free (message);
00735           break;
00736         }
00737     }
00738 }
00739 
00740 /* Return the pretty printable name of a single character. */
00741 char *
00742 pretty_keyname (unsigned char key)
00743 {
00744   static char rep_buffer[30];
00745   char *rep;
00746 
00747   if (Meta_p (key))
00748     {
00749       char temp[20];
00750 
00751       rep = pretty_keyname (UnMeta (key));
00752 
00753 #if defined (INFOKEY)
00754       sprintf (temp, "M-%s", rep);
00755 #else /* !INFOKEY */
00756       sprintf (temp, "ESC %s", rep);
00757 #endif /* !INFOKEY */
00758       strcpy (rep_buffer, temp);
00759       rep = rep_buffer;
00760     }
00761   else if (Control_p (key))
00762     {
00763       switch (key)
00764         {
00765         case '\n': rep = "LFD"; break;
00766         case '\t': rep = "TAB"; break;
00767         case '\r': rep = "RET"; break;
00768         case ESC:  rep = "ESC"; break;
00769 
00770         default:
00771           sprintf (rep_buffer, "C-%c", UnControl (key));
00772           rep = rep_buffer;
00773         }
00774     }
00775   else
00776     {
00777       switch (key)
00778         {
00779         case ' ': rep = "SPC"; break;
00780         case DEL: rep = "DEL"; break;
00781         default:
00782           rep_buffer[0] = key;
00783           rep_buffer[1] = '\0';
00784           rep = rep_buffer;
00785         }
00786     }
00787   return (rep);
00788 }
00789 
00790 /* Return the pretty printable string which represents KEYSEQ. */
00791 
00792 static void pretty_keyseq_internal (char *keyseq, char *rep);
00793 
00794 char *
00795 pretty_keyseq (char *keyseq)
00796 {
00797   static char keyseq_rep[200];
00798 
00799   keyseq_rep[0] = '\0';
00800   if (*keyseq)
00801     pretty_keyseq_internal (keyseq, keyseq_rep);
00802   return (keyseq_rep);
00803 }
00804 
00805 static void
00806 pretty_keyseq_internal (char *keyseq, char *rep)
00807 {
00808   if (term_kP && strncmp(keyseq, term_kP, strlen(term_kP)) == 0)
00809     {
00810       strcpy(rep, "PgUp");
00811       keyseq += strlen(term_kP);
00812     }
00813   else if (term_kN && strncmp(keyseq, term_kN, strlen(term_kN)) == 0)
00814     {
00815       strcpy(rep, "PgDn");
00816       keyseq += strlen(term_kN);
00817     }
00818 #if defined(INFOKEY)
00819   else if (term_kh && strncmp(keyseq, term_kh, strlen(term_kh)) == 0)
00820     {
00821       strcpy(rep, "Home");
00822       keyseq += strlen(term_kh);
00823     }
00824   else if (term_ke && strncmp(keyseq, term_ke, strlen(term_ke)) == 0)
00825     {
00826       strcpy(rep, "End");
00827       keyseq += strlen(term_ke);
00828     }
00829   else if (term_ki && strncmp(keyseq, term_ki, strlen(term_ki)) == 0)
00830     {
00831       strcpy(rep, "INS");
00832       keyseq += strlen(term_ki);
00833     }
00834   else if (term_kx && strncmp(keyseq, term_kx, strlen(term_kx)) == 0)
00835     {
00836       strcpy(rep, "DEL");
00837       keyseq += strlen(term_kx);
00838     }
00839 #endif /* INFOKEY */
00840   else if (term_ku && strncmp(keyseq, term_ku, strlen(term_ku)) == 0)
00841     {
00842       strcpy(rep, "Up");
00843       keyseq += strlen(term_ku);
00844     }
00845   else if (term_kd && strncmp(keyseq, term_kd, strlen(term_kd)) == 0)
00846     {
00847       strcpy(rep, "Down");
00848       keyseq += strlen(term_kd);
00849     }
00850   else if (term_kl && strncmp(keyseq, term_kl, strlen(term_kl)) == 0)
00851     {
00852       strcpy(rep, "Left");
00853       keyseq += strlen(term_kl);
00854     }
00855   else if (term_kr && strncmp(keyseq, term_kr, strlen(term_kr)) == 0)
00856     {
00857       strcpy(rep, "Right");
00858       keyseq += strlen(term_kr);
00859     }
00860   else
00861     {
00862       strcpy (rep, pretty_keyname (keyseq[0]));
00863       keyseq++;
00864     }
00865   if (*keyseq)
00866     {
00867       strcat (rep, " ");
00868       pretty_keyseq_internal (keyseq, rep + strlen(rep));
00869     }
00870 }
00871 
00872 /* Return a pointer to the last character in s that is found in f. */
00873 static char *
00874 strrpbrk (const char *s, const char *f)
00875 {
00876   register const char *e = s + strlen(s);
00877   register const char *t;
00878 
00879   while (e-- != s)
00880     {
00881       for (t = f; *t; t++)
00882         if (*e == *t)
00883           return (char *)e;
00884     }
00885   return NULL;
00886 }
00887 
00888 /* Replace the names of functions with the key that invokes them. */
00889 char *
00890 replace_in_documentation (char *string, int help_is_only_window_p)
00891 {
00892   unsigned reslen = strlen (string);
00893   register int i, start, next;
00894   static char *result = (char *)NULL;
00895 
00896   maybe_free (result);
00897   result = (char *)xmalloc (1 + reslen);
00898 
00899   i = next = start = 0;
00900 
00901   /* Skip to the beginning of a replaceable function. */
00902   for (i = start; string[i]; i++)
00903     {
00904       int j = i + 1;
00905 
00906       /* Is this the start of a replaceable function name? */
00907       if (string[i] == '\\')
00908         {
00909           char *fmt = NULL;
00910           unsigned min = 0;
00911           unsigned max = 0;
00912 
00913           if(string[j] == '%')
00914             {
00915               if (string[++j] == '-')
00916                 j++;
00917               if (isdigit(string[j]))
00918                 {
00919                   min = atoi(string + j);
00920                   while (isdigit(string[j]))
00921                     j++;
00922                   if (string[j] == '.' && isdigit(string[j + 1]))
00923                     {
00924                       j += 1;
00925                       max = atoi(string + j);
00926                       while (isdigit(string[j]))
00927                         j++;
00928                     }
00929                   fmt = (char *)xmalloc (j - i + 2);
00930                   strncpy (fmt, string + i + 1, j - i);
00931                   fmt[j - i - 1] = 's';
00932                   fmt[j - i] = '\0';
00933                 }
00934               else
00935                 j = i + 1;
00936             }
00937           if (string[j] == '[')
00938             {
00939               unsigned arg = 0;
00940               char *argstr = NULL;
00941               char *rep_name, *fun_name, *rep;
00942               InfoCommand *command;
00943               char *repstr = NULL;
00944               unsigned replen;
00945 
00946               /* Copy in the old text. */
00947               strncpy (result + next, string + start, i - start);
00948               next += (i - start);
00949               start = j + 1;
00950 
00951               /* Look for an optional numeric arg. */
00952               i = start;
00953               if (isdigit(string[i])
00954                   || (string[i] == '-' && isdigit(string[i + 1])) )
00955                 {
00956                   arg = atoi(string + i);
00957                   if (string[i] == '-')
00958                     i++;
00959                   while (isdigit(string[i]))
00960                     i++;
00961                 }
00962               start = i;
00963 
00964               /* Move to the end of the function name. */
00965               for (i = start; string[i] && (string[i] != ']'); i++);
00966 
00967               rep_name = (char *)xmalloc (1 + i - start);
00968               strncpy (rep_name, string + start, i - start);
00969               rep_name[i - start] = '\0';
00970 
00971             /* If we have only one window (because the window size was too
00972                small to split it), we have to quit help by going back one
00973                noew in the history list, not deleting the window.  */
00974               if (strcmp (rep_name, "quit-help") == 0)
00975                 fun_name = help_is_only_window_p ? "history-node"
00976                                                  : "delete-window";
00977               else
00978                 fun_name = rep_name;
00979 
00980               /* Find a key which invokes this function in the info_keymap. */
00981               command = named_function (fun_name);
00982 
00983               free (rep_name);
00984 
00985               /* If the internal documentation string fails, there is a
00986                  serious problem with the associated command's documentation.
00987                  We croak so that it can be fixed immediately. */
00988               if (!command)
00989                 abort ();
00990 
00991               if (arg)
00992                 {
00993                   char *argrep, *p;
00994 
00995                   argrep = where_is (info_keymap, InfoCmd(info_add_digit_to_numeric_arg));
00996                   p = argrep ? strrpbrk (argrep, "0123456789-") : NULL;
00997                   if (p)
00998                     {
00999                       argstr = (char *)xmalloc (p - argrep + 21);
01000                       strncpy (argstr, argrep, p - argrep);
01001                       sprintf (argstr + (p - argrep), "%d", arg);
01002                     }
01003                   else
01004                     command = NULL;
01005                 }
01006               rep = command ? where_is (info_keymap, command) : NULL;
01007               if (!rep)
01008                 rep = "N/A";
01009               replen = (argstr ? strlen (argstr) : 0) + strlen (rep) + 1;
01010               repstr = (char *)xmalloc (replen);
01011               repstr[0] = '\0';
01012               if (argstr)
01013                 {
01014                   strcat(repstr, argstr);
01015                   strcat(repstr, " ");
01016                   free (argstr);
01017                 }
01018               strcat(repstr, rep);
01019 
01020               if (fmt)
01021                 {
01022                   if (replen > max)
01023                     replen = max;
01024                   if (replen < min)
01025                     replen = min;
01026                 }
01027               if (next + replen > reslen)
01028                 {
01029                   reslen = next + replen + 1;
01030                   result = (char *)xrealloc (result, reslen + 1);
01031                 }
01032 
01033               if (fmt)
01034                   sprintf (result + next, fmt, repstr);
01035               else
01036                   strcpy (result + next, repstr);
01037 
01038               next = strlen (result);
01039               free (repstr);
01040 
01041               start = i;
01042               if (string[i])
01043                 start++;
01044             }
01045 
01046           maybe_free (fmt);
01047         }
01048     }
01049   strcpy (result + next, string + start);
01050   return (result);
01051 }
01052 
01053 /* Return a string of characters which could be typed from the keymap
01054    MAP to invoke FUNCTION. */
01055 static char *where_is_rep = (char *)NULL;
01056 static int where_is_rep_index = 0;
01057 static int where_is_rep_size = 0;
01058 
01059 char *
01060 where_is (Keymap map, InfoCommand *cmd)
01061 {
01062   char *rep;
01063 
01064   if (!where_is_rep_size)
01065     where_is_rep = (char *)xmalloc (where_is_rep_size = 100);
01066   where_is_rep_index = 0;
01067 
01068   rep = where_is_internal (map, cmd);
01069 
01070   /* If it couldn't be found, return "M-x Foo" (or equivalent). */
01071   if (!rep)
01072     {
01073       char *name;
01074 
01075       name = function_name (cmd);
01076       if (!name)
01077         return NULL; /* no such function */
01078 
01079       rep = where_is_internal (map, InfoCmd(info_execute_command));
01080       if (!rep)
01081         return ""; /* function exists but can't be got to by user */
01082 
01083       sprintf (where_is_rep, "%s %s", rep, name);
01084 
01085       rep = where_is_rep;
01086     }
01087   return (rep);
01088 }
01089 
01090 /* Return the printed rep of the keystrokes that invoke FUNCTION,
01091    as found in MAP, or NULL. */
01092 static char *
01093 where_is_internal (Keymap map, InfoCommand *cmd)
01094 {
01095 #if defined(INFOKEY)
01096 
01097   register FUNCTION_KEYSEQ *k;
01098 
01099   for (k = cmd->keys; k; k = k->next)
01100     if (k->map == map)
01101       return pretty_keyseq (k->keyseq);
01102 
01103   return NULL;
01104 
01105 #else /* !INFOKEY */
01106   /* There is a bug in that create_internal_info_help_node calls
01107      where_is_internal without setting where_is_rep_index to zero.  This
01108      was found by Mandrake and reported by Thierry Vignaud
01109      <tvignaud@mandrakesoft.com> around April 24, 2002.
01110 
01111      I think the best fix is to make where_is_rep_index another
01112      parameter to this recursively-called function, instead of a static
01113      variable.  But this [!INFOKEY] branch of the code is not enabled
01114      any more, so let's just skip the whole thing.  --karl, 28sep02.  */
01115   register int i;
01116 
01117   /* If the function is directly invokable in MAP, return the representation
01118      of that keystroke. */
01119   for (i = 0; i < 256; i++)
01120     if ((map[i].type == ISFUNC) && map[i].function == cmd)
01121       {
01122         sprintf (where_is_rep + where_is_rep_index, "%s", pretty_keyname (i));
01123         return (where_is_rep);
01124       }
01125 
01126   /* Okay, search subsequent maps for this function. */
01127   for (i = 0; i < 256; i++)
01128     {
01129       if (map[i].type == ISKMAP)
01130         {
01131           int saved_index = where_is_rep_index;
01132           char *rep;
01133 
01134           sprintf (where_is_rep + where_is_rep_index, "%s ",
01135                    pretty_keyname (i));
01136 
01137           where_is_rep_index = strlen (where_is_rep);
01138           rep = where_is_internal ((Keymap)map[i].function, cmd);
01139 
01140           if (rep)
01141             return (where_is_rep);
01142 
01143           where_is_rep_index = saved_index;
01144         }
01145     }
01146 
01147   return NULL;
01148 
01149 #endif /* INFOKEY */
01150 }
01151 
01152 DECLARE_INFO_COMMAND (info_where_is,
01153    _("Show what to type to execute a given command"))
01154 {
01155   char *command_name;
01156 
01157   command_name = read_function_name ((char *) _("Where is command: "), window);
01158 
01159   if (!command_name)
01160     {
01161       info_abort_key (active_window, count, key);
01162       return;
01163     }
01164 
01165   if (*command_name)
01166     {
01167       InfoCommand *command;
01168 
01169       command = named_function (command_name);
01170 
01171       if (command)
01172         {
01173           char *location;
01174 
01175           location = where_is (active_window->keymap, command);
01176 
01177           if (!location || !location[0])
01178             {
01179               info_error ((char *) _("`%s' is not on any keys"),
01180                   command_name, NULL);
01181             }
01182           else
01183             {
01184               if (strstr (location, function_name (command)))
01185                 window_message_in_echo_area
01186                   ((char *) _("%s can only be invoked via %s."),
01187                    command_name, location);
01188               else
01189                 window_message_in_echo_area
01190                   ((char *) _("%s can be invoked via %s."),
01191                    command_name, location);
01192             }
01193         }
01194       else
01195         info_error ((char *) _("There is no function named `%s'"),
01196             command_name, NULL);
01197     }
01198 
01199   free (command_name);
01200 }