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