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