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