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