1 /* indices.c -- deal with an Info file index. 2 $Id: indices.c,v 1.5 2006/07/17 16:12:36 espie Exp $ 3 4 Copyright (C) 1993, 1997, 1998, 1999, 2002, 2003, 2004 Free Software 5 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 Originally written by Brian Fox (bfox@ai.mit.edu). */ 22 23 #include "info.h" 24 #include "indices.h" 25 26 /* User-visible variable controls the output of info-index-next. */ 27 int show_index_match = 1; 28 29 /* In the Info sense, an index is a menu. This variable holds the last 30 parsed index. */ 31 static REFERENCE **index_index = (REFERENCE **)NULL; 32 33 /* The offset of the most recently selected index element. */ 34 static int index_offset = 0; 35 36 /* Variable which holds the last string searched for. */ 37 static char *index_search = (char *)NULL; 38 39 /* A couple of "globals" describing where the initial index was found. */ 40 static char *initial_index_filename = (char *)NULL; 41 static char *initial_index_nodename = (char *)NULL; 42 43 /* A structure associating index names with index offset ranges. */ 44 typedef struct { 45 char *name; /* The nodename of this index. */ 46 int first; /* The index in our list of the first entry. */ 47 int last; /* The index in our list of the last entry. */ 48 } INDEX_NAME_ASSOC; 49 50 /* An array associating index nodenames with index offset ranges. */ 51 static INDEX_NAME_ASSOC **index_nodenames = (INDEX_NAME_ASSOC **)NULL; 52 static int index_nodenames_index = 0; 53 static int index_nodenames_slots = 0; 54 55 /* Add the name of NODE, and the range of the associated index elements 56 (passed in ARRAY) to index_nodenames. */ 57 static void 58 add_index_to_index_nodenames (REFERENCE **array, NODE *node) 59 { 60 register int i, last; 61 INDEX_NAME_ASSOC *assoc; 62 63 for (last = 0; array[last + 1]; last++); 64 assoc = (INDEX_NAME_ASSOC *)xmalloc (sizeof (INDEX_NAME_ASSOC)); 65 assoc->name = xstrdup (node->nodename); 66 67 if (!index_nodenames_index) 68 { 69 assoc->first = 0; 70 assoc->last = last; 71 } 72 else 73 { 74 for (i = 0; index_nodenames[i + 1]; i++); 75 assoc->first = 1 + index_nodenames[i]->last; 76 assoc->last = assoc->first + last; 77 } 78 add_pointer_to_array 79 (assoc, index_nodenames_index, index_nodenames, index_nodenames_slots, 80 10, INDEX_NAME_ASSOC *); 81 } 82 83 /* Find and return the indices of WINDOW's file. The indices are defined 84 as the first node in the file containing the word "Index" and any 85 immediately following nodes whose names also contain "Index". All such 86 indices are concatenated and the result returned. If WINDOW's info file 87 doesn't have any indices, a NULL pointer is returned. */ 88 REFERENCE ** 89 info_indices_of_window (WINDOW *window) 90 { 91 FILE_BUFFER *fb; 92 93 fb = file_buffer_of_window (window); 94 95 return (info_indices_of_file_buffer (fb)); 96 } 97 98 REFERENCE ** 99 info_indices_of_file_buffer (FILE_BUFFER *file_buffer) 100 { 101 register int i; 102 REFERENCE **result = (REFERENCE **)NULL; 103 104 /* No file buffer, no indices. */ 105 if (!file_buffer) 106 return ((REFERENCE **)NULL); 107 108 /* Reset globals describing where the index was found. */ 109 maybe_free (initial_index_filename); 110 maybe_free (initial_index_nodename); 111 initial_index_filename = (char *)NULL; 112 initial_index_nodename = (char *)NULL; 113 114 if (index_nodenames) 115 { 116 for (i = 0; index_nodenames[i]; i++) 117 { 118 free (index_nodenames[i]->name); 119 free (index_nodenames[i]); 120 } 121 122 index_nodenames_index = 0; 123 index_nodenames[0] = (INDEX_NAME_ASSOC *)NULL; 124 } 125 126 /* Grovel the names of the nodes found in this file. */ 127 if (file_buffer->tags) 128 { 129 TAG *tag; 130 131 for (i = 0; (tag = file_buffer->tags[i]); i++) 132 { 133 if (string_in_line ("Index", tag->nodename) != -1) 134 { 135 NODE *node; 136 REFERENCE **menu; 137 138 /* Found one. Get its menu. */ 139 node = info_get_node (tag->filename, tag->nodename); 140 if (!node) 141 continue; 142 143 /* Remember the filename and nodename of this index. */ 144 initial_index_filename = xstrdup (file_buffer->filename); 145 initial_index_nodename = xstrdup (tag->nodename); 146 147 menu = info_menu_of_node (node); 148 149 /* If we have a menu, add this index's nodename and range 150 to our list of index_nodenames. */ 151 if (menu) 152 { 153 add_index_to_index_nodenames (menu, node); 154 155 /* Concatenate the references found so far. */ 156 result = info_concatenate_references (result, menu); 157 } 158 free (node); 159 } 160 } 161 } 162 163 /* If there is a result, clean it up so that every entry has a filename. */ 164 for (i = 0; result && result[i]; i++) 165 if (!result[i]->filename) 166 result[i]->filename = xstrdup (file_buffer->filename); 167 168 return (result); 169 } 170 171 DECLARE_INFO_COMMAND (info_index_search, 172 _("Look up a string in the index for this file")) 173 { 174 do_info_index_search (window, count, 0); 175 } 176 177 /* Look up SEARCH_STRING in the index for this file. If SEARCH_STRING 178 is NULL, prompt user for input. */ 179 void 180 do_info_index_search (WINDOW *window, int count, char *search_string) 181 { 182 FILE_BUFFER *fb; 183 char *line; 184 185 /* Reset the index offset, since this is not the info-index-next command. */ 186 index_offset = 0; 187 188 /* The user is selecting a new search string, so flush the old one. */ 189 maybe_free (index_search); 190 index_search = (char *)NULL; 191 192 /* If this window's file is not the same as the one that we last built an 193 index for, build and remember an index now. */ 194 fb = file_buffer_of_window (window); 195 if (!initial_index_filename || 196 (FILENAME_CMP (initial_index_filename, fb->filename) != 0)) 197 { 198 info_free_references (index_index); 199 window_message_in_echo_area ((char *) _("Finding index entries..."), 200 NULL, NULL); 201 index_index = info_indices_of_file_buffer (fb); 202 } 203 204 /* If there is no index, quit now. */ 205 if (!index_index) 206 { 207 info_error ((char *) _("No indices found."), NULL, NULL); 208 return; 209 } 210 211 /* Okay, there is an index. Look for SEARCH_STRING, or, if it is 212 empty, prompt for one. */ 213 if (search_string && *search_string) 214 line = xstrdup (search_string); 215 else 216 { 217 line = info_read_maybe_completing (window, (char *) _("Index entry: "), 218 index_index); 219 window = active_window; 220 221 /* User aborted? */ 222 if (!line) 223 { 224 info_abort_key (active_window, 1, 0); 225 return; 226 } 227 228 /* Empty line means move to the Index node. */ 229 if (!*line) 230 { 231 free (line); 232 233 if (initial_index_filename && initial_index_nodename) 234 { 235 NODE *node; 236 237 node = info_get_node (initial_index_filename, 238 initial_index_nodename); 239 set_remembered_pagetop_and_point (window); 240 window_set_node_of_window (window, node); 241 remember_window_and_node (window, node); 242 window_clear_echo_area (); 243 return; 244 } 245 } 246 } 247 248 /* The user typed either a completed index label, or a partial string. 249 Find an exact match, or, failing that, the first index entry containing 250 the partial string. So, we just call info_next_index_match () with minor 251 manipulation of INDEX_OFFSET. */ 252 { 253 int old_offset; 254 255 /* Start the search right after/before this index. */ 256 if (count < 0) 257 { 258 register int i; 259 for (i = 0; index_index[i]; i++); 260 index_offset = i; 261 } 262 else 263 index_offset = -1; 264 265 old_offset = index_offset; 266 267 /* The "last" string searched for is this one. */ 268 index_search = line; 269 270 /* Find it, or error. */ 271 info_next_index_match (window, count, 0); 272 273 /* If the search failed, return the index offset to where it belongs. */ 274 if (index_offset == old_offset) 275 index_offset = 0; 276 } 277 } 278 279 int 280 index_entry_exists (WINDOW *window, char *string) 281 { 282 register int i; 283 FILE_BUFFER *fb; 284 285 /* If there is no previous search string, the user hasn't built an index 286 yet. */ 287 if (!string) 288 return 0; 289 290 fb = file_buffer_of_window (window); 291 if (!initial_index_filename 292 || (FILENAME_CMP (initial_index_filename, fb->filename) != 0)) 293 { 294 info_free_references (index_index); 295 index_index = info_indices_of_file_buffer (fb); 296 } 297 298 /* If there is no index, that is an error. */ 299 if (!index_index) 300 return 0; 301 302 for (i = 0; (i > -1) && (index_index[i]); i++) 303 if (strcmp (string, index_index[i]->label) == 0) 304 break; 305 306 /* If that failed, look for the next substring match. */ 307 if ((i < 0) || (!index_index[i])) 308 { 309 for (i = 0; (i > -1) && (index_index[i]); i++) 310 if (string_in_line (string, index_index[i]->label) != -1) 311 break; 312 313 if ((i > -1) && (index_index[i])) 314 string_in_line (string, index_index[i]->label); 315 } 316 317 /* If that failed, return 0. */ 318 if ((i < 0) || (!index_index[i])) 319 return 0; 320 321 return 1; 322 } 323 324 DECLARE_INFO_COMMAND (info_next_index_match, 325 _("Go to the next matching index item from the last `\\[index-search]' command")) 326 { 327 register int i; 328 int partial, dir; 329 NODE *node; 330 331 /* If there is no previous search string, the user hasn't built an index 332 yet. */ 333 if (!index_search) 334 { 335 info_error ((char *) _("No previous index search string."), NULL, NULL); 336 return; 337 } 338 339 /* If there is no index, that is an error. */ 340 if (!index_index) 341 { 342 info_error ((char *) _("No index entries."), NULL, NULL); 343 return; 344 } 345 346 /* The direction of this search is controlled by the value of the 347 numeric argument. */ 348 if (count < 0) 349 dir = -1; 350 else 351 dir = 1; 352 353 /* Search for the next occurence of index_search. First try to find 354 an exact match. */ 355 partial = 0; 356 357 for (i = index_offset + dir; (i > -1) && (index_index[i]); i += dir) 358 if (strcmp (index_search, index_index[i]->label) == 0) 359 break; 360 361 /* If that failed, look for the next substring match. */ 362 if ((i < 0) || (!index_index[i])) 363 { 364 for (i = index_offset + dir; (i > -1) && (index_index[i]); i += dir) 365 if (string_in_line (index_search, index_index[i]->label) != -1) 366 break; 367 368 if ((i > -1) && (index_index[i])) 369 partial = string_in_line (index_search, index_index[i]->label); 370 } 371 372 /* If that failed, print an error. */ 373 if ((i < 0) || (!index_index[i])) 374 { 375 info_error ((char *) _("No %sindex entries containing `%s'."), 376 index_offset > 0 ? (char *) _("more ") : "", index_search); 377 return; 378 } 379 380 /* Okay, we found the next one. Move the offset to the current entry. */ 381 index_offset = i; 382 383 /* Report to the user on what we have found. */ 384 { 385 register int j; 386 const char *name = _("CAN'T SEE THIS"); 387 char *match; 388 389 for (j = 0; index_nodenames[j]; j++) 390 { 391 if ((i >= index_nodenames[j]->first) && 392 (i <= index_nodenames[j]->last)) 393 { 394 name = index_nodenames[j]->name; 395 break; 396 } 397 } 398 399 /* If we had a partial match, indicate to the user which part of the 400 string matched. */ 401 match = xstrdup (index_index[i]->label); 402 403 if (partial && show_index_match) 404 { 405 int k, ls, start, upper; 406 407 ls = strlen (index_search); 408 start = partial - ls; 409 upper = isupper (match[start]) ? 1 : 0; 410 411 for (k = 0; k < ls; k++) 412 if (upper) 413 match[k + start] = info_tolower (match[k + start]); 414 else 415 match[k + start] = info_toupper (match[k + start]); 416 } 417 418 { 419 char *format; 420 421 format = replace_in_documentation 422 ((char *) _("Found `%s' in %s. (`\\[next-index-match]' tries to find next.)"), 423 0); 424 425 window_message_in_echo_area (format, match, (char *) name); 426 } 427 428 free (match); 429 } 430 431 /* Select the node corresponding to this index entry. */ 432 node = info_get_node (index_index[i]->filename, index_index[i]->nodename); 433 434 if (!node) 435 { 436 info_error ((char *) msg_cant_file_node, 437 index_index[i]->filename, index_index[i]->nodename); 438 return; 439 } 440 441 info_set_node_of_window (1, window, node); 442 443 /* Try to find an occurence of LABEL in this node. */ 444 { 445 long start, loc; 446 447 start = window->line_starts[1] - window->node->contents; 448 loc = info_target_search_node (node, index_index[i]->label, start); 449 450 if (loc != -1) 451 { 452 window->point = loc; 453 window_adjust_pagetop (window); 454 } 455 } 456 } 457 458 /* **************************************************************** */ 459 /* */ 460 /* Info APROPOS: Search every known index. */ 461 /* */ 462 /* **************************************************************** */ 463 464 /* For every menu item in DIR, search the indices of that file for 465 SEARCH_STRING. */ 466 REFERENCE ** 467 apropos_in_all_indices (char *search_string, int inform) 468 { 469 register int i, dir_index; 470 REFERENCE **all_indices = (REFERENCE **)NULL; 471 REFERENCE **dir_menu = (REFERENCE **)NULL; 472 NODE *dir_node; 473 474 dir_node = info_get_node ("dir", "Top"); 475 if (dir_node) 476 dir_menu = info_menu_of_node (dir_node); 477 478 if (!dir_menu) 479 return NULL; 480 481 /* For every menu item in DIR, get the associated node's file buffer and 482 read the indices of that file buffer. Gather all of the indices into 483 one large one. */ 484 for (dir_index = 0; dir_menu[dir_index]; dir_index++) 485 { 486 REFERENCE **this_index, *this_item; 487 NODE *this_node; 488 FILE_BUFFER *this_fb; 489 int dir_node_duplicated = 0; 490 491 this_item = dir_menu[dir_index]; 492 493 if (!this_item->filename) 494 { 495 dir_node_duplicated = 1; 496 if (dir_node->parent) 497 this_item->filename = xstrdup (dir_node->parent); 498 else 499 this_item->filename = xstrdup (dir_node->filename); 500 } 501 502 /* Find this node. If we cannot find it, try using the label of the 503 entry as a file (i.e., "(LABEL)Top"). */ 504 this_node = info_get_node (this_item->filename, this_item->nodename); 505 506 if (!this_node && this_item->nodename && 507 (strcmp (this_item->label, this_item->nodename) == 0)) 508 this_node = info_get_node (this_item->label, "Top"); 509 510 if (!this_node) 511 { 512 if (dir_node_duplicated) 513 free (this_item->filename); 514 continue; 515 } 516 517 /* Get the file buffer associated with this node. */ 518 { 519 char *files_name; 520 521 files_name = this_node->parent; 522 if (!files_name) 523 files_name = this_node->filename; 524 525 this_fb = info_find_file (files_name); 526 527 /* If we already scanned this file, don't do that again. 528 In addition to being faster, this also avoids having 529 multiple identical entries in the *Apropos* menu. */ 530 for (i = 0; i < dir_index; i++) 531 if (FILENAME_CMP (this_fb->filename, dir_menu[i]->filename) == 0) 532 break; 533 if (i < dir_index) 534 { 535 if (dir_node_duplicated) 536 free (this_item->filename); 537 continue; 538 } 539 540 if (this_fb && inform) 541 message_in_echo_area ((char *) _("Scanning indices of `%s'..."), 542 files_name, NULL); 543 544 this_index = info_indices_of_file_buffer (this_fb); 545 free (this_node); 546 547 if (this_fb && inform) 548 unmessage_in_echo_area (); 549 } 550 551 if (this_index) 552 { 553 /* Remember the filename which contains this set of references. */ 554 for (i = 0; this_index && this_index[i]; i++) 555 if (!this_index[i]->filename) 556 this_index[i]->filename = xstrdup (this_fb->filename); 557 558 /* Concatenate with the other indices. */ 559 all_indices = info_concatenate_references (all_indices, this_index); 560 } 561 } 562 563 info_free_references (dir_menu); 564 565 /* Build a list of the references which contain SEARCH_STRING. */ 566 if (all_indices) 567 { 568 REFERENCE *entry, **apropos_list = (REFERENCE **)NULL; 569 int apropos_list_index = 0; 570 int apropos_list_slots = 0; 571 572 for (i = 0; (entry = all_indices[i]); i++) 573 { 574 if (string_in_line (search_string, entry->label) != -1) 575 { 576 add_pointer_to_array 577 (entry, apropos_list_index, apropos_list, apropos_list_slots, 578 100, REFERENCE *); 579 } 580 else 581 { 582 maybe_free (entry->label); 583 maybe_free (entry->filename); 584 maybe_free (entry->nodename); 585 free (entry); 586 } 587 } 588 589 free (all_indices); 590 all_indices = apropos_list; 591 } 592 return (all_indices); 593 } 594 595 #define APROPOS_NONE \ 596 N_("No available info files have `%s' in their indices.") 597 598 void 599 info_apropos (char *string) 600 { 601 REFERENCE **apropos_list; 602 603 apropos_list = apropos_in_all_indices (string, 0); 604 605 if (!apropos_list) 606 info_error ((char *) _(APROPOS_NONE), string, NULL); 607 else 608 { 609 register int i; 610 REFERENCE *entry; 611 612 for (i = 0; (entry = apropos_list[i]); i++) 613 fprintf (stdout, "\"(%s)%s\" -- %s\n", 614 entry->filename, entry->nodename, entry->label); 615 } 616 info_free_references (apropos_list); 617 } 618 619 static char *apropos_list_nodename = "*Apropos*"; 620 621 DECLARE_INFO_COMMAND (info_index_apropos, 622 _("Grovel all known info file's indices for a string and build a menu")) 623 { 624 char *line; 625 626 line = info_read_in_echo_area (window, (char *) _("Index apropos: ")); 627 628 window = active_window; 629 630 /* User aborted? */ 631 if (!line) 632 { 633 info_abort_key (window, 1, 1); 634 return; 635 } 636 637 /* User typed something? */ 638 if (*line) 639 { 640 REFERENCE **apropos_list; 641 NODE *apropos_node; 642 643 apropos_list = apropos_in_all_indices (line, 1); 644 645 if (!apropos_list) 646 info_error ((char *) _(APROPOS_NONE), line, NULL); 647 else 648 { 649 register int i; 650 char *line_buffer; 651 652 initialize_message_buffer (); 653 printf_to_message_buffer 654 ((char *) _("\n* Menu: Nodes whose indices contain `%s':\n"), 655 line, NULL, NULL); 656 line_buffer = (char *)xmalloc (500); 657 658 for (i = 0; apropos_list[i]; i++) 659 { 660 int len; 661 /* The label might be identical to that of another index 662 entry in another Info file. Therefore, we make the file 663 name part of the menu entry, to make them all distinct. */ 664 sprintf (line_buffer, "* %s [%s]: ", 665 apropos_list[i]->label, apropos_list[i]->filename); 666 len = pad_to (40, line_buffer); 667 sprintf (line_buffer + len, "(%s)%s.", 668 apropos_list[i]->filename, apropos_list[i]->nodename); 669 printf_to_message_buffer ("%s\n", line_buffer, NULL, NULL); 670 } 671 free (line_buffer); 672 } 673 674 apropos_node = message_buffer_to_node (); 675 add_gcable_pointer (apropos_node->contents); 676 name_internal_node (apropos_node, apropos_list_nodename); 677 678 /* Even though this is an internal node, we don't want the window 679 system to treat it specially. So we turn off the internalness 680 of it here. */ 681 apropos_node->flags &= ~N_IsInternal; 682 683 /* Find/Create a window to contain this node. */ 684 { 685 WINDOW *new; 686 NODE *node; 687 688 set_remembered_pagetop_and_point (window); 689 690 /* If a window is visible and showing an apropos list already, 691 re-use it. */ 692 for (new = windows; new; new = new->next) 693 { 694 node = new->node; 695 696 if (internal_info_node_p (node) && 697 (strcmp (node->nodename, apropos_list_nodename) == 0)) 698 break; 699 } 700 701 /* If we couldn't find an existing window, try to use the next window 702 in the chain. */ 703 if (!new && window->next) 704 new = window->next; 705 706 /* If we still don't have a window, make a new one to contain 707 the list. */ 708 if (!new) 709 { 710 WINDOW *old_active; 711 712 old_active = active_window; 713 active_window = window; 714 new = window_make_window ((NODE *)NULL); 715 active_window = old_active; 716 } 717 718 /* If we couldn't make a new window, use this one. */ 719 if (!new) 720 new = window; 721 722 /* Lines do not wrap in this window. */ 723 new->flags |= W_NoWrap; 724 725 window_set_node_of_window (new, apropos_node); 726 remember_window_and_node (new, apropos_node); 727 active_window = new; 728 } 729 info_free_references (apropos_list); 730 } 731 free (line); 732 733 if (!info_error_was_printed) 734 window_clear_echo_area (); 735 } 736