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