1 /* indices.c -- deal with an Info file index.
2 $Id: indices.c,v 1.15 2002/03/11 13:43:52 karl Exp $
4 Copyright (C) 1993, 97, 98, 99, 2002 Free Software Foundation, Inc.
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)
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.
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.
20 Written by Brian Fox (bfox@ai.mit.edu). */
25 /* User-visible variable controls the output of info-index-next. */
26 int show_index_match = 1;
28 /* In the Info sense, an index is a menu. This variable holds the last
30 static REFERENCE **index_index = (REFERENCE **)NULL;
32 /* The offset of the most recently selected index element. */
33 static int index_offset = 0;
35 /* Variable which holds the last string searched for. */
36 static char *index_search = (char *)NULL;
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;
42 /* A structure associating index names with index offset ranges. */
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. */
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;
54 /* Add the name of NODE, and the range of the associated index elements
55 (passed in ARRAY) to index_nodenames. */
57 add_index_to_index_nodenames (array, node)
62 INDEX_NAME_ASSOC *assoc;
64 for (last = 0; array[last + 1]; last++);
65 assoc = (INDEX_NAME_ASSOC *)xmalloc (sizeof (INDEX_NAME_ASSOC));
66 assoc->name = xstrdup (node->nodename);
68 if (!index_nodenames_index)
75 for (i = 0; index_nodenames[i + 1]; i++);
76 assoc->first = 1 + index_nodenames[i]->last;
77 assoc->last = assoc->first + last;
80 (assoc, index_nodenames_index, index_nodenames, index_nodenames_slots,
81 10, INDEX_NAME_ASSOC *);
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. */
90 info_indices_of_window (window)
95 fb = file_buffer_of_window (window);
97 return (info_indices_of_file_buffer (fb));
101 info_indices_of_file_buffer (file_buffer)
102 FILE_BUFFER *file_buffer;
105 REFERENCE **result = (REFERENCE **)NULL;
107 /* No file buffer, no indices. */
109 return ((REFERENCE **)NULL);
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;
119 for (i = 0; index_nodenames[i]; i++)
121 free (index_nodenames[i]->name);
122 free (index_nodenames[i]);
125 index_nodenames_index = 0;
126 index_nodenames[0] = (INDEX_NAME_ASSOC *)NULL;
129 /* Grovel the names of the nodes found in this file. */
130 if (file_buffer->tags)
134 for (i = 0; (tag = file_buffer->tags[i]); i++)
136 if (string_in_line ("Index", tag->nodename) != -1)
141 /* Found one. Get its menu. */
142 node = info_get_node (tag->filename, tag->nodename);
146 /* Remember the filename and nodename of this index. */
147 initial_index_filename = xstrdup (file_buffer->filename);
148 initial_index_nodename = xstrdup (tag->nodename);
150 menu = info_menu_of_node (node);
152 /* If we have a menu, add this index's nodename and range
153 to our list of index_nodenames. */
156 add_index_to_index_nodenames (menu, node);
158 /* Concatenate the references found so far. */
159 result = info_concatenate_references (result, menu);
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);
174 DECLARE_INFO_COMMAND (info_index_search,
175 _("Look up a string in the index for this file"))
177 do_info_index_search (window, count, 0);
180 /* Look up SEARCH_STRING in the index for this file. If SEARCH_STRING
181 is NULL, prompt user for input. */
183 do_info_index_search (window, count, search_string)
191 /* Reset the index offset, since this is not the info-index-next command. */
194 /* The user is selecting a new search string, so flush the old one. */
195 maybe_free (index_search);
196 index_search = (char *)NULL;
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))
204 info_free_references (index_index);
205 window_message_in_echo_area (_("Finding index entries..."));
206 index_index = info_indices_of_file_buffer (fb);
209 /* If there is no index, quit now. */
212 info_error (_("No indices found."));
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);
222 line = info_read_maybe_completing (window, _("Index entry: "),
224 window = active_window;
229 info_abort_key (active_window, 1, 0);
233 /* Empty line means move to the Index node. */
238 if (initial_index_filename && initial_index_nodename)
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 ();
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. */
260 /* Start the search right after/before this index. */
264 for (i = 0; index_index[i]; i++);
270 old_offset = index_offset;
272 /* The "last" string searched for is this one. */
275 /* Find it, or error. */
276 info_next_index_match (window, count, 0);
278 /* If the search failed, return the index offset to where it belongs. */
279 if (index_offset == old_offset)
285 index_entry_exists (window, string)
292 /* If there is no previous search string, the user hasn't built an index
297 fb = file_buffer_of_window (window);
298 if (!initial_index_filename
299 || (FILENAME_CMP (initial_index_filename, fb->filename) != 0))
301 info_free_references (index_index);
302 index_index = info_indices_of_file_buffer (fb);
305 /* If there is no index, that is an error. */
309 for (i = 0; (i > -1) && (index_index[i]); i++)
310 if (strcmp (string, index_index[i]->label) == 0)
313 /* If that failed, look for the next substring match. */
314 if ((i < 0) || (!index_index[i]))
316 for (i = 0; (i > -1) && (index_index[i]); i++)
317 if (string_in_line (string, index_index[i]->label) != -1)
320 if ((i > -1) && (index_index[i]))
321 string_in_line (string, index_index[i]->label);
324 /* If that failed, return 0. */
325 if ((i < 0) || (!index_index[i]))
331 DECLARE_INFO_COMMAND (info_next_index_match,
332 _("Go to the next matching index item from the last `\\[index-search]' command"))
338 /* If there is no previous search string, the user hasn't built an index
342 info_error (_("No previous index search string."));
346 /* If there is no index, that is an error. */
349 info_error (_("No index entries."));
353 /* The direction of this search is controlled by the value of the
360 /* Search for the next occurence of index_search. First try to find
364 for (i = index_offset + dir; (i > -1) && (index_index[i]); i += dir)
365 if (strcmp (index_search, index_index[i]->label) == 0)
368 /* If that failed, look for the next substring match. */
369 if ((i < 0) || (!index_index[i]))
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)
375 if ((i > -1) && (index_index[i]))
376 partial = string_in_line (index_search, index_index[i]->label);
379 /* If that failed, print an error. */
380 if ((i < 0) || (!index_index[i]))
382 info_error (_("No %sindex entries containing \"%s\"."),
383 index_offset > 0 ? _("more ") : "", index_search);
387 /* Okay, we found the next one. Move the offset to the current entry. */
390 /* Report to the user on what we have found. */
393 char *name = _("CAN'T SEE THIS");
396 for (j = 0; index_nodenames[j]; j++)
398 if ((i >= index_nodenames[j]->first) &&
399 (i <= index_nodenames[j]->last))
401 name = index_nodenames[j]->name;
406 /* If we had a partial match, indicate to the user which part of the
408 match = xstrdup (index_index[i]->label);
410 if (partial && show_index_match)
412 int j, ls, start, upper;
414 ls = strlen (index_search);
415 start = partial - ls;
416 upper = isupper (match[start]) ? 1 : 0;
418 for (j = 0; j < ls; j++)
420 match[j + start] = info_tolower (match[j + start]);
422 match[j + start] = info_toupper (match[j + start]);
428 format = replace_in_documentation
429 (_("Found \"%s\" in %s. (`\\[next-index-match]' tries to find next.)"));
431 window_message_in_echo_area (format, match, name);
437 /* Select the node corresponding to this index entry. */
438 node = info_get_node (index_index[i]->filename, index_index[i]->nodename);
442 info_error (msg_cant_file_node,
443 index_index[i]->filename, index_index[i]->nodename);
447 info_set_node_of_window (1, window, node);
449 /* Try to find an occurence of LABEL in this node. */
453 start = window->line_starts[1] - window->node->contents;
454 loc = info_target_search_node (node, index_index[i]->label, start);
459 window_adjust_pagetop (window);
464 /* **************************************************************** */
466 /* Info APROPOS: Search every known index. */
468 /* **************************************************************** */
470 /* For every menu item in DIR, search the indices of that file for
473 apropos_in_all_indices (search_string, inform)
477 register int i, dir_index;
478 REFERENCE **all_indices = (REFERENCE **)NULL;
479 REFERENCE **dir_menu = (REFERENCE **)NULL;
482 dir_node = info_get_node ("dir", "Top");
484 dir_menu = info_menu_of_node (dir_node);
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
492 for (dir_index = 0; dir_menu[dir_index]; dir_index++)
494 REFERENCE **this_index, *this_item;
496 FILE_BUFFER *this_fb;
497 int dir_node_duplicated = 0;
499 this_item = dir_menu[dir_index];
501 if (!this_item->filename)
503 dir_node_duplicated = 1;
504 if (dir_node->parent)
505 this_item->filename = xstrdup (dir_node->parent);
507 this_item->filename = xstrdup (dir_node->filename);
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);
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");
520 if (dir_node_duplicated)
521 free (this_item->filename);
525 /* Get the file buffer associated with this node. */
529 files_name = this_node->parent;
531 files_name = this_node->filename;
533 this_fb = info_find_file (files_name);
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)
543 if (dir_node_duplicated)
544 free (this_item->filename);
548 if (this_fb && inform)
549 message_in_echo_area (_("Scanning indices of \"%s\"..."), files_name);
551 this_index = info_indices_of_file_buffer (this_fb);
554 if (this_fb && inform)
555 unmessage_in_echo_area ();
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);
565 /* Concatenate with the other indices. */
566 all_indices = info_concatenate_references (all_indices, this_index);
570 info_free_references (dir_menu);
572 /* Build a list of the references which contain SEARCH_STRING. */
575 REFERENCE *entry, **apropos_list = (REFERENCE **)NULL;
576 int apropos_list_index = 0;
577 int apropos_list_slots = 0;
579 for (i = 0; (entry = all_indices[i]); i++)
581 if (string_in_line (search_string, entry->label) != -1)
584 (entry, apropos_list_index, apropos_list, apropos_list_slots,
589 maybe_free (entry->label);
590 maybe_free (entry->filename);
591 maybe_free (entry->nodename);
597 all_indices = apropos_list;
599 return (all_indices);
602 #define APROPOS_NONE \
603 N_("No available info files have \"%s\" in their indices.")
606 info_apropos (string)
609 REFERENCE **apropos_list;
611 apropos_list = apropos_in_all_indices (string, 0);
615 info_error (_(APROPOS_NONE), string);
622 for (i = 0; (entry = apropos_list[i]); i++)
623 fprintf (stdout, "\"(%s)%s\" -- %s\n",
624 entry->filename, entry->nodename, entry->label);
626 info_free_references (apropos_list);
629 static char *apropos_list_nodename = "*Apropos*";
631 DECLARE_INFO_COMMAND (info_index_apropos,
632 _("Grovel all known info file's indices for a string and build a menu"))
636 line = info_read_in_echo_area (window, _("Index apropos: "));
638 window = active_window;
643 info_abort_key (window, 1, 1);
647 /* User typed something? */
650 REFERENCE **apropos_list;
653 apropos_list = apropos_in_all_indices (line, 1);
657 info_error (_(APROPOS_NONE), line);
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);
669 for (i = 0; apropos_list[i]; i++)
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);
685 apropos_node = message_buffer_to_node ();
686 add_gcable_pointer (apropos_node->contents);
687 name_internal_node (apropos_node, apropos_list_nodename);
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
692 apropos_node->flags &= ~N_IsInternal;
694 /* Find/Create a window to contain this node. */
699 set_remembered_pagetop_and_point (window);
701 /* If a window is visible and showing an apropos list already,
703 for (new = windows; new; new = new->next)
707 if (internal_info_node_p (node) &&
708 (strcmp (node->nodename, apropos_list_nodename) == 0))
712 /* If we couldn't find an existing window, try to use the next window
714 if (!new && window->next)
717 /* If we still don't have a window, make a new one to contain
723 old_active = active_window;
724 active_window = window;
725 new = window_make_window ((NODE *)NULL);
726 active_window = old_active;
729 /* If we couldn't make a new window, use this one. */
733 /* Lines do not wrap in this window. */
734 new->flags |= W_NoWrap;
736 window_set_node_of_window (new, apropos_node);
737 remember_window_and_node (new, apropos_node);
740 info_free_references (apropos_list);
744 if (!info_error_was_printed)
745 window_clear_echo_area ();