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