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