Merge from vendor branch BINUTILS:
[dragonfly.git] / contrib / texinfo / info / info.c
1 /* info.c -- Display nodes of Info files in multiple windows.
2    $Id: info.c,v 1.60 2002/03/11 19:54:29 karl Exp $
3
4    Copyright (C) 1993, 96, 97, 98, 99, 2000, 01, 02
5    Free Software 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 "indices.h"
25 #include "dribble.h"
26 #include "getopt.h"
27 #if defined (HANDLE_MAN_PAGES)
28 #  include "man.h"
29 #endif /* HANDLE_MAN_PAGES */
30
31 static char *program_name = "info";
32
33 /* Non-zero means search all indices for APROPOS_SEARCH_STRING. */
34 static int apropos_p = 0;
35
36 /* Variable containing the string to search for when apropos_p is non-zero. */
37 static char *apropos_search_string = (char *)NULL;
38
39 /* Non-zero means search all indices for INDEX_SEARCH_STRING.  Unlike
40    apropos, this puts the user at the node, running info. */
41 static int index_search_p = 0;
42
43 /* Non-zero means look for the node which describes the invocation
44    and command-line options of the program, and start the info
45    session at that node.  */
46 static int goto_invocation_p = 0;
47
48 /* Variable containing the string to search for when index_search_p is
49    non-zero. */
50 static char *index_search_string = (char *)NULL;
51
52 /* Non-zero means print version info only. */
53 static int print_version_p = 0;
54
55 /* Non-zero means print a short description of the options. */
56 static int print_help_p = 0;
57
58 /* Array of the names of nodes that the user specified with "--node" on the
59    command line. */
60 static char **user_nodenames = (char **)NULL;
61 static int user_nodenames_index = 0;
62 static int user_nodenames_slots = 0;
63
64 /* String specifying the first file to load.  This string can only be set
65    by the user specifying "--file" on the command line. */
66 static char *user_filename = (char *)NULL;
67
68 /* String specifying the name of the file to dump nodes to.  This value is
69    filled if the user speficies "--output" on the command line. */
70 static char *user_output_filename = (char *)NULL;
71
72 /* Non-zero indicates that when "--output" is specified, all of the menu
73    items of the specified nodes (and their subnodes as well) should be
74    dumped in the order encountered.  This basically can print a book. */
75 int dump_subnodes = 0;
76
77 /* Non-zero means make default keybindings be loosely modeled on vi(1).  */
78 int vi_keys_p = 0;
79
80 /* Non-zero means don't remove ANSI escape sequences from man pages.  */
81 int raw_escapes_p = 0;
82
83 #ifdef __MSDOS__
84 /* Non-zero indicates that screen output should be made 'speech-friendly'.
85    Since on MSDOS the usual behavior is to write directly to the video
86    memory, speech synthesizer software cannot grab the output.  Therefore,
87    we provide a user option which tells us to avoid direct screen output
88    and use stdout instead (which loses the color output).  */
89 int speech_friendly = 0;
90 #endif
91
92 /* Structure describing the options that Info accepts.  We pass this structure
93    to getopt_long ().  If you add or otherwise change this structure, you must
94    also change the string which follows it. */
95 #define APROPOS_OPTION 1
96 #define DRIBBLE_OPTION 2
97 #define RESTORE_OPTION 3
98 #define IDXSRCH_OPTION 4
99 static struct option long_options[] = {
100   { "apropos", 1, 0, APROPOS_OPTION },
101   { "directory", 1, 0, 'd' },
102   { "dribble", 1, 0, DRIBBLE_OPTION },
103   { "file", 1, 0, 'f' },
104   { "help", 0, &print_help_p, 1 },
105   { "index-search", 1, 0, IDXSRCH_OPTION },
106   { "node", 1, 0, 'n' },
107   { "output", 1, 0, 'o' },
108   { "raw-escapes", 0, &raw_escapes_p, 1 },
109   { "restore", 1, 0, RESTORE_OPTION },
110   { "show-options", 0, 0, 'O' },
111   { "subnodes", 0, &dump_subnodes, 1 },
112   { "usage", 0, 0, 'O' },
113   { "version", 0, &print_version_p, 1 },
114   { "vi-keys", 0, &vi_keys_p, 1 },
115 #ifdef __MSDOS__
116   { "speech-friendly", 0, &speech_friendly, 1 },
117 #endif
118   {NULL, 0, NULL, 0}
119 };
120
121 /* String describing the shorthand versions of the long options found above. */
122 #ifdef __MSDOS__
123 static char *short_options = "d:n:f:o:ORsb";
124 #else
125 static char *short_options = "d:n:f:o:ORs";
126 #endif
127
128 /* When non-zero, the Info window system has been initialized. */
129 int info_windows_initialized_p = 0;
130
131 /* Some "forward" declarations. */
132 static void info_short_help (), remember_info_program_name ();
133 static void init_messages ();
134 extern void add_file_directory_to_path ();
135
136 \f
137 /* **************************************************************** */
138 /*                                                                  */
139 /*                Main Entry Point to the Info Program              */
140 /*                                                                  */
141 /* **************************************************************** */
142
143 int
144 main (argc, argv)
145      int argc;
146      char **argv;
147 {
148   int getopt_long_index;        /* Index returned by getopt_long (). */
149   NODE *initial_node;           /* First node loaded by Info. */
150
151 #ifdef HAVE_SETLOCALE
152   /* Set locale via LC_ALL.  */
153   setlocale (LC_ALL, "");
154 #endif
155
156   /* Set the text message domain.  */
157   bindtextdomain (PACKAGE, LOCALEDIR);
158   textdomain (PACKAGE);
159
160   init_messages ();
161   
162   while (1)
163     {
164       int option_character;
165
166       option_character = getopt_long
167         (argc, argv, short_options, long_options, &getopt_long_index);
168
169       /* getopt_long () returns EOF when there are no more long options. */
170       if (option_character == EOF)
171         break;
172
173       /* If this is a long option, then get the short version of it. */
174       if (option_character == 0 && long_options[getopt_long_index].flag == 0)
175         option_character = long_options[getopt_long_index].val;
176
177       /* Case on the option that we have received. */
178       switch (option_character)
179         {
180         case 0:
181           break;
182
183           /* User wants to add a directory. */
184         case 'd':
185           info_add_path (optarg, INFOPATH_PREPEND);
186           break;
187
188           /* User is specifying a particular node. */
189         case 'n':
190           add_pointer_to_array (optarg, user_nodenames_index, user_nodenames,
191                                 user_nodenames_slots, 10, char *);
192           break;
193
194           /* User is specifying a particular Info file. */
195         case 'f':
196           if (user_filename)
197             free (user_filename);
198
199           user_filename = xstrdup (optarg);
200           break;
201
202           /* User is specifying the name of a file to output to. */
203         case 'o':
204           if (user_output_filename)
205             free (user_output_filename);
206           user_output_filename = xstrdup (optarg);
207           break;
208
209          /* User has specified that she wants to find the "Options"
210              or "Invocation" node for the program.  */
211         case 'O':
212           goto_invocation_p = 1;
213           break;
214
215           /* User has specified that she wants the escape sequences
216              in man pages to be passed thru unaltered.  */
217         case 'R':
218           raw_escapes_p = 1;
219           break;
220
221           /* User is specifying that she wishes to dump the subnodes of
222              the node that she is dumping. */
223         case 's':
224           dump_subnodes = 1;
225           break;
226
227 #ifdef __MSDOS__
228           /* User wants speech-friendly output.  */
229         case 'b':
230           speech_friendly = 1;
231           break;
232 #endif /* __MSDOS__ */
233
234           /* User has specified a string to search all indices for. */
235         case APROPOS_OPTION:
236           apropos_p = 1;
237           maybe_free (apropos_search_string);
238           apropos_search_string = xstrdup (optarg);
239           break;
240
241           /* User has specified a dribble file to receive keystrokes. */
242         case DRIBBLE_OPTION:
243           close_dribble_file ();
244           open_dribble_file (optarg);
245           break;
246
247           /* User has specified an alternate input stream. */
248         case RESTORE_OPTION:
249           info_set_input_from_file (optarg);
250           break;
251
252           /* User has specified a string to search all indices for. */
253         case IDXSRCH_OPTION:
254           index_search_p = 1;
255           maybe_free (index_search_string);
256           index_search_string = xstrdup (optarg);
257           break;
258
259         default:
260           fprintf (stderr, _("Try --help for more information.\n"));
261           xexit (1);
262         }
263     }
264
265   /* If the output device is not a terminal, and no output filename has been
266      specified, make user_output_filename be "-", so that the info is written
267      to stdout, and turn on the dumping of subnodes. */
268   if ((!isatty (fileno (stdout))) && (user_output_filename == (char *)NULL))
269     {
270       user_output_filename = xstrdup ("-");
271       dump_subnodes = 1;
272     }
273
274   /* If the user specified --version, then show the version and exit. */
275   if (print_version_p)
276     {
277       printf ("%s (GNU %s) %s\n", program_name, PACKAGE, VERSION);
278       puts ("");
279       printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
280 There is NO warranty.  You may redistribute this software\n\
281 under the terms of the GNU General Public License.\n\
282 For more information about these matters, see the files named COPYING.\n"),
283                   "2002");
284       xexit (0);
285     }
286
287   /* If the `--help' option was present, show the help and exit. */
288   if (print_help_p)
289     {
290       info_short_help ();
291       xexit (0);
292     }
293
294   /* If the user hasn't specified a path for Info files, default it.
295      Lowest priority is our messy hardwired list in filesys.h.
296      Then comes the user's INFODIR from the Makefile.
297      Highest priority is the environment variable, if set.  */
298   if (!infopath)
299     {
300       char *path_from_env = getenv ("INFOPATH");
301
302       if (path_from_env)
303         {
304           unsigned len = strlen (path_from_env);
305           /* Trailing : on INFOPATH means insert the default path.  */
306           if (len && path_from_env[len - 1] == PATH_SEP[0])
307             {
308               path_from_env[len - 1] = 0;
309               info_add_path (DEFAULT_INFOPATH, INFOPATH_PREPEND);
310             }
311 #ifdef INFODIR /* from the Makefile */
312           info_add_path (INFODIR, INFOPATH_PREPEND);
313 #endif
314           info_add_path (path_from_env, INFOPATH_PREPEND);
315         }
316       else
317         {
318           info_add_path (DEFAULT_INFOPATH, INFOPATH_PREPEND);
319 #ifdef INFODIR /* from the Makefile */
320          info_add_path (INFODIR, INFOPATH_PREPEND);
321 #endif
322         }
323     }
324
325   /* If the user specified a particular filename, add the path of that
326      file to the contents of INFOPATH. */
327   if (user_filename)
328     add_file_directory_to_path (user_filename);
329
330   /* If the user wants to search every known index for a given string,
331      do that now, and report the results. */
332   if (apropos_p)
333     {
334       info_apropos (apropos_search_string);
335       xexit (0);
336     }
337
338   /* Get the initial Info node.  It is either "(dir)Top", or what the user
339      specifed with values in user_filename and user_nodenames. */
340   initial_node = info_get_node (user_filename,
341                                 user_nodenames ? user_nodenames[0] : 0);
342
343   /* If we couldn't get the initial node, this user is in trouble. */
344   if (!initial_node)
345     {
346       if (info_recent_file_error)
347         info_error (info_recent_file_error);
348       else
349         info_error (msg_cant_find_node,
350                     user_nodenames ? user_nodenames[0] : "Top");
351       xexit (1);
352     }
353
354   /* Special cases for when the user specifies multiple nodes.  If we
355      are dumping to an output file, dump all of the nodes specified.
356      Otherwise, attempt to create enough windows to handle the nodes
357      that this user wants displayed. */
358   if (user_nodenames_index > 1)
359     {
360       free (initial_node);
361
362       if (user_output_filename)
363         dump_nodes_to_file
364           (user_filename, user_nodenames, user_output_filename, dump_subnodes);
365       else
366         begin_multiple_window_info_session (user_filename, user_nodenames);
367
368       xexit (0);
369     }
370
371   /* If there are arguments remaining, they are the names of menu items
372      in sequential info files starting from the first one loaded.  That
373      file name is either "dir", or the contents of user_filename if one
374      was specified. */
375   {
376     char *errstr, *errarg1, *errarg2;
377     NODE *new_initial_node = info_follow_menus (initial_node, argv + optind,
378                                                 &errstr, &errarg1, &errarg2);
379
380     if (new_initial_node && new_initial_node != initial_node)
381       initial_node = new_initial_node;
382
383     /* If the user specified that this node should be output, then do that
384        now.  Otherwise, start the Info session with this node.  Or act
385        accordingly if the initial node was not found.  */
386     if (user_output_filename && !goto_invocation_p)
387       {
388         if (!errstr)
389           dump_node_to_file (initial_node, user_output_filename,
390                              dump_subnodes);
391         else
392           info_error (errstr, errarg1, errarg2);
393       }
394     else
395       {
396
397         if (errstr)
398           begin_info_session_with_error (initial_node, errstr,
399                                          errarg1, errarg2);
400         /* If the user specified `--index-search=STRING' or
401            --show-options, start the info session in the node
402            corresponding to what they want. */
403         else if (index_search_p || goto_invocation_p)
404           {
405             int status = 0;
406
407             initialize_info_session (initial_node, 0);
408
409             if (goto_invocation_p
410                 || index_entry_exists (windows, index_search_string))
411               {
412                 terminal_prep_terminal ();
413                 terminal_clear_screen ();
414                 info_last_executed_command = (VFunction *)NULL;
415
416                 if (index_search_p)
417                   do_info_index_search (windows, 0, index_search_string);
418                 else
419                   {
420                     /* If they said "info --show-options foo bar baz",
421                        the last of the arguments is the program whose
422                        options they want to see.  */
423                     char **p = argv + optind;
424                     char *program;
425
426                     if (*p)
427                       {
428                         while (p[1])
429                           p++;
430                         program = xstrdup (*p);
431                       }
432                     else if (user_filename)
433                       /* If there's no command-line arguments to
434                          supply the program name, use the Info file
435                          name (sans extension and leading directories)
436                          instead.  */
437                       program = program_name_from_file_name (user_filename);
438                     else
439                       program = xstrdup ("");
440
441                     info_intuit_options_node (windows, initial_node, program);
442                     free (program);
443                   }
444
445                 if (user_output_filename)
446                   {
447                     dump_node_to_file (windows->node, user_output_filename,
448                                        dump_subnodes);
449                   }
450                 else
451                   info_read_and_dispatch ();
452
453                 /* On program exit, leave the cursor at the bottom of the
454                    window, and restore the terminal IO. */
455                 terminal_goto_xy (0, screenheight - 1);
456                 terminal_clear_to_eol ();
457                 fflush (stdout);
458                 terminal_unprep_terminal ();
459               }
460             else
461               {
462                 fprintf (stderr, _("no index entries found for `%s'\n"),
463                          index_search_string);
464                 status = 2;
465               }
466
467             close_dribble_file ();
468             xexit (status);
469           }
470         else
471           begin_info_session (initial_node);
472       }
473
474     xexit (0);
475   }
476 }
477
478 void
479 add_file_directory_to_path (filename)
480      char *filename;
481 {
482   char *directory_name = xstrdup (filename);
483   char *temp = filename_non_directory (directory_name);
484
485   if (temp != directory_name)
486     {
487       if (HAVE_DRIVE (directory_name) && temp == directory_name + 2)
488         {
489           /* The directory of "d:foo" is stored as "d:.", to avoid
490              mixing it with "d:/" when a slash is appended.  */
491           *temp = '.';
492           temp += 2;
493         }
494       temp[-1] = 0;
495       info_add_path (directory_name, INFOPATH_PREPEND);
496     }
497
498   free (directory_name);
499 }
500
501 \f
502 /* Error handling.  */
503
504 /* Non-zero if an error has been signalled. */
505 int info_error_was_printed = 0;
506
507 /* Non-zero means ring terminal bell on errors. */
508 int info_error_rings_bell_p = 1;
509
510 /* Print FORMAT with ARG1 and ARG2.  If the window system was initialized,
511    then the message is printed in the echo area.  Otherwise, a message is
512    output to stderr. */
513 void
514 info_error (format, arg1, arg2)
515      char *format;
516      void *arg1, *arg2;
517 {
518   info_error_was_printed = 1;
519
520   if (!info_windows_initialized_p || display_inhibited)
521     {
522       fprintf (stderr, "%s: ", program_name);
523       fprintf (stderr, format, arg1, arg2);
524       fprintf (stderr, "\n");
525       fflush (stderr);
526     }
527   else
528     {
529       if (!echo_area_is_active)
530         {
531           if (info_error_rings_bell_p)
532             terminal_ring_bell ();
533           window_message_in_echo_area (format, arg1, arg2);
534         }
535       else
536         {
537           NODE *temp;
538
539           temp = build_message_node (format, arg1, arg2);
540           if (info_error_rings_bell_p)
541             terminal_ring_bell ();
542           inform_in_echo_area (temp->contents);
543           free (temp->contents);
544           free (temp);
545         }
546     }
547 }
548
549 \f
550 /* Produce a scaled down description of the available options to Info. */
551 static void
552 info_short_help ()
553 {
554 #ifdef __MSDOS__
555   static const char speech_friendly_string[] = N_("\
556   -b, --speech-friendly        be friendly to speech synthesizers.\n");
557 #else
558   static const char speech_friendly_string[] = "";
559 #endif
560
561     
562   printf (_("\
563 Usage: %s [OPTION]... [MENU-ITEM...]\n\
564 \n\
565 Read documentation in Info format.\n\
566 \n\
567 Options:\n\
568       --apropos=STRING         look up STRING in all indices of all manuals.\n\
569   -d, --directory=DIR          add DIR to INFOPATH.\n\
570       --dribble=FILENAME       remember user keystrokes in FILENAME.\n\
571   -f, --file=FILENAME          specify Info file to visit.\n\
572   -h, --help                   display this help and exit.\n\
573       --index-search=STRING    go to node pointed by index entry STRING.\n\
574   -n, --node=NODENAME          specify nodes in first visited Info file.\n\
575   -o, --output=FILENAME        output selected nodes to FILENAME.\n\
576   -R, --raw-escapes            don't remove ANSI escapes from man pages.\n\
577       --restore=FILENAME       read initial keystrokes from FILENAME.\n\
578   -O, --show-options, --usage  go to command-line options node.\n%s\
579       --subnodes               recursively output menu items.\n\
580       --vi-keys                use vi-like and less-like key bindings.\n\
581       --version                display version information and exit.\n\
582 \n\
583 The first non-option argument, if present, is the menu entry to start from;\n\
584 it is searched for in all `dir' files along INFOPATH.\n\
585 If it is not present, info merges all `dir' files and shows the result.\n\
586 Any remaining arguments are treated as the names of menu\n\
587 items relative to the initial node visited.\n\
588 \n\
589 Examples:\n\
590   info                       show top-level dir menu\n\
591   info emacs                 start at emacs node from top-level dir\n\
592   info emacs buffers         start at buffers node within emacs manual\n\
593   info --show-options emacs  start at node with emacs' command line options\n\
594   info -f ./foo.info         show file ./foo.info, not searching dir\n\
595 "),
596   program_name, speech_friendly_string);
597
598   puts (_("\n\
599 Email bug reports to bug-texinfo@gnu.org,\n\
600 general questions and discussion to help-texinfo@gnu.org.\n\
601 Texinfo home page: http://www.gnu.org/software/texinfo/"));
602
603   xexit (0);
604 }
605
606 \f
607 /* Initialize strings for gettext.  Because gettext doesn't handle N_ or
608    _ within macro definitions, we put shared messages into variables and
609    use them that way.  This also has the advantage that there's only one
610    copy of the strings.  */
611
612 char *msg_cant_find_node;
613 char *msg_cant_file_node;
614 char *msg_cant_find_window;
615 char *msg_cant_find_point;
616 char *msg_cant_kill_last;
617 char *msg_no_menu_node;
618 char *msg_no_foot_node;
619 char *msg_no_xref_node;
620 char *msg_no_pointer;
621 char *msg_unknown_command;
622 char *msg_term_too_dumb;
623 char *msg_at_node_bottom;
624 char *msg_at_node_top;
625 char *msg_one_window;
626 char *msg_win_too_small;
627 char *msg_cant_make_help;
628
629 static void
630 init_messages ()
631 {
632   msg_cant_find_node   = _("Cannot find node `%s'.");
633   msg_cant_file_node   = _("Cannot find node `(%s)%s'.");
634   msg_cant_find_window = _("Cannot find a window!");
635   msg_cant_find_point  = _("Point doesn't appear within this window's node!");
636   msg_cant_kill_last   = _("Cannot delete the last window.");
637   msg_no_menu_node     = _("No menu in this node.");
638   msg_no_foot_node     = _("No footnotes in this node.");
639   msg_no_xref_node     = _("No cross references in this node.");
640   msg_no_pointer       = _("No `%s' pointer for this node.");
641   msg_unknown_command  = _("Unknown Info command `%c'; try `?' for help.");
642   msg_term_too_dumb    = _("Terminal type `%s' is not smart enough to run Info.");
643   msg_at_node_bottom   = _("You are already at the last page of this node.");
644   msg_at_node_top      = _("You are already at the first page of this node.");
645   msg_one_window       = _("Only one window.");
646   msg_win_too_small    = _("Resulting window would be too small.");
647   msg_cant_make_help   = _("Not enough room for a help window, please delete a window.");
648 }