info.c revision 56160
1/* info.c -- Display nodes of Info files in multiple windows.
2   $Id: info.c,v 1.41 1999/09/25 16:10:04 karl 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
30static char *program_name = "info";
31
32/* Non-zero means search all indices for APROPOS_SEARCH_STRING. */
33static int apropos_p = 0;
34
35/* Variable containing the string to search for when apropos_p is non-zero. */
36static 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. */
40static 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.  */
45static int goto_invocation_p = 0;
46
47/* Variable containing the string to search for when index_search_p is
48   non-zero. */
49static char *index_search_string = (char *)NULL;
50
51/* Non-zero means print version info only. */
52static int print_version_p = 0;
53
54/* Non-zero means print a short description of the options. */
55static int print_help_p = 0;
56
57/* Array of the names of nodes that the user specified with "--node" on the
58   command line. */
59static char **user_nodenames = (char **)NULL;
60static int user_nodenames_index = 0;
61static 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. */
65static 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. */
69static 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. */
74int dump_subnodes = 0;
75
76/* Non-zero means make default keybindings be loosely modeled on vi(1).  */
77int 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).  */
85int 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
95static 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__
118static char *short_options = "d:n:f:o:Osb";
119#else
120static char *short_options = "d:n:f:o:Os";
121#endif
122
123/* When non-zero, the Info window system has been initialized. */
124int info_windows_initialized_p = 0;
125
126/* Some "forward" declarations. */
127static void info_short_help (), remember_info_program_name ();
128static void init_messages ();
129
130
131/* **************************************************************** */
132/*                                                                  */
133/*                Main Entry Point to the Info Program              */
134/*                                                                  */
135/* **************************************************************** */
136
137int
138main (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\
268There is NO warranty.  You may redistribute this software\n\
269under the terms of the GNU General Public License.\n\
270For 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. */
481int info_error_was_printed = 0;
482
483/* Non-zero means ring terminal bell on errors. */
484int 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. */
489void
490info_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. */
527static void
528info_short_help ()
529{
530  printf (_("\
531Usage: %s [OPTION]... [MENU-ITEM...]\n\
532\n\
533Read documentation in Info format.\n\
534\n\
535Options:\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\
550The first non-option argument, if present, is the menu entry to start from;\n\
551it is searched for in all `dir' files along INFOPATH.\n\
552If it is not present, info merges all `dir' files and shows the result.\n\
553Any remaining arguments are treated as the names of menu\n\
554items relative to the initial node visited.\n\
555\n\
556Examples:\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\
563Email bug reports to bug-texinfo@gnu.org,\n\
564general 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
584char *msg_cant_find_node;
585char *msg_cant_file_node;
586char *msg_cant_find_window;
587char *msg_cant_find_point;
588char *msg_cant_kill_last;
589char *msg_no_menu_node;
590char *msg_no_foot_node;
591char *msg_no_xref_node;
592char *msg_no_pointer;
593char *msg_unknown_command;
594char *msg_term_too_dumb;
595char *msg_at_node_bottom;
596char *msg_at_node_top;
597char *msg_one_window;
598char *msg_win_too_small;
599char *msg_cant_make_help;
600
601static void
602init_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