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