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
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.  */
81int raw_escapes_p = 1;
82
83/* Non-zero means print the absolute location of the file to be loaded.  */
84static 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).  */
92int 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
102static 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__
129static char *short_options = "d:n:f:ho:ORswb";
130#else
131static char *short_options = "d:n:f:ho:ORws";
132#endif
133
134/* When non-zero, the Info window system has been initialized. */
135int info_windows_initialized_p = 0;
136
137/* Some "forward" declarations. */
138static void info_short_help (void);
139static void init_messages (void);
140
141
142/* **************************************************************** */
143/*                                                                  */
144/*                Main Entry Point to the Info Program              */
145/*                                                                  */
146/* **************************************************************** */
147
148int
149main (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\
296under the terms of the GNU General Public License.\n\
297For 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
516void
517add_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
539/* Error handling.  */
540
541/* Non-zero if an error has been signalled. */
542int info_error_was_printed = 0;
543
544/* Non-zero means ring terminal bell on errors. */
545int 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. */
550void
551info_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
585/* Produce a scaled down description of the available options to Info. */
586static void
587info_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 (_("\
598Usage: %s [OPTION]... [MENU-ITEM...]\n\
599\n\
600Read documentation in Info format.\n\
601\n\
602Options:\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\
620The first non-option argument, if present, is the menu entry to start from;\n\
621it is searched for in all `dir' files along INFOPATH.\n\
622If it is not present, info merges all `dir' files and shows the result.\n\
623Any remaining arguments are treated as the names of menu\n\
624items relative to the initial node visited.\n\
625\n\
626Examples:\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\
636Email bug reports to bug-texinfo@gnu.org,\n\
637general questions and discussion to help-texinfo@gnu.org.\n\
638Texinfo home page: http://www.gnu.org/software/texinfo/"));
639
640  xexit (0);
641}
642
643
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
649const char *msg_cant_find_node;
650const char *msg_cant_file_node;
651const char *msg_cant_find_window;
652const char *msg_cant_find_point;
653const char *msg_cant_kill_last;
654const char *msg_no_menu_node;
655const char *msg_no_foot_node;
656const char *msg_no_xref_node;
657const char *msg_no_pointer;
658const char *msg_unknown_command;
659const char *msg_term_too_dumb;
660const char *msg_at_node_bottom;
661const char *msg_at_node_top;
662const char *msg_one_window;
663const char *msg_win_too_small;
664const char *msg_cant_make_help;
665
666static void
667init_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}
686