info.c revision 100513
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
31static char *program_name = "info";
32
33/* Non-zero means search all indices for APROPOS_SEARCH_STRING. */
34static int apropos_p = 0;
35
36/* Variable containing the string to search for when apropos_p is non-zero. */
37static 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. */
41static 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.  */
46static int goto_invocation_p = 0;
47
48/* Variable containing the string to search for when index_search_p is
49   non-zero. */
50static char *index_search_string = (char *)NULL;
51
52/* Non-zero means print version info only. */
53static int print_version_p = 0;
54
55/* Non-zero means print a short description of the options. */
56static int print_help_p = 0;
57
58/* Array of the names of nodes that the user specified with "--node" on the
59   command line. */
60static char **user_nodenames = (char **)NULL;
61static int user_nodenames_index = 0;
62static 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. */
66static 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. */
70static 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. */
75int dump_subnodes = 0;
76
77/* Non-zero means make default keybindings be loosely modeled on vi(1).  */
78int vi_keys_p = 0;
79
80/* Non-zero means don't remove ANSI escape sequences from man pages.  */
81int 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).  */
89int 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
99static 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__
123static char *short_options = "d:n:f:o:ORsb";
124#else
125static char *short_options = "d:n:f:o:ORs";
126#endif
127
128/* When non-zero, the Info window system has been initialized. */
129int info_windows_initialized_p = 0;
130
131/* Some "forward" declarations. */
132static void info_short_help (), remember_info_program_name ();
133static void init_messages ();
134extern void add_file_directory_to_path ();
135
136
137/* **************************************************************** */
138/*                                                                  */
139/*                Main Entry Point to the Info Program              */
140/*                                                                  */
141/* **************************************************************** */
142
143int
144main (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\
280There is NO warranty.  You may redistribute this software\n\
281under the terms of the GNU General Public License.\n\
282For 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
478void
479add_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
502/* Error handling.  */
503
504/* Non-zero if an error has been signalled. */
505int info_error_was_printed = 0;
506
507/* Non-zero means ring terminal bell on errors. */
508int 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. */
513void
514info_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
550/* Produce a scaled down description of the available options to Info. */
551static void
552info_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 (_("\
563Usage: %s [OPTION]... [MENU-ITEM...]\n\
564\n\
565Read documentation in Info format.\n\
566\n\
567Options:\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\
583The first non-option argument, if present, is the menu entry to start from;\n\
584it is searched for in all `dir' files along INFOPATH.\n\
585If it is not present, info merges all `dir' files and shows the result.\n\
586Any remaining arguments are treated as the names of menu\n\
587items relative to the initial node visited.\n\
588\n\
589Examples:\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\
599Email bug reports to bug-texinfo@gnu.org,\n\
600general questions and discussion to help-texinfo@gnu.org.\n\
601Texinfo home page: http://www.gnu.org/software/texinfo/"));
602
603  xexit (0);
604}
605
606
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
612char *msg_cant_find_node;
613char *msg_cant_file_node;
614char *msg_cant_find_window;
615char *msg_cant_find_point;
616char *msg_cant_kill_last;
617char *msg_no_menu_node;
618char *msg_no_foot_node;
619char *msg_no_xref_node;
620char *msg_no_pointer;
621char *msg_unknown_command;
622char *msg_term_too_dumb;
623char *msg_at_node_bottom;
624char *msg_at_node_top;
625char *msg_one_window;
626char *msg_win_too_small;
627char *msg_cant_make_help;
628
629static void
630init_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}
649