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