1 /* indices.c -- deal with an Info file index.
2 $Id: indices.c,v 1.11 2008/06/11 09:55:42 gray Exp $
4 Copyright (C) 1993, 1997, 1998, 1999, 2002, 2003, 2004, 2007, 2008
5 Free Software Foundation, Inc.
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 3 of the License, or
10 (at your option) any later version.
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.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 Originally 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 = 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 = NULL;
38 /* A couple of "globals" describing where the initial index was found. */
39 static char *initial_index_filename = NULL;
40 static char *initial_index_nodename = 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 = 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 (REFERENCE **array, NODE *node)
60 INDEX_NAME_ASSOC *assoc;
62 for (last = 0; array[last + 1]; last++);
63 assoc = xmalloc (sizeof (INDEX_NAME_ASSOC));
64 assoc->name = xstrdup (node->nodename);
66 if (!index_nodenames_index)
73 for (i = 0; index_nodenames[i + 1]; i++);
74 assoc->first = 1 + index_nodenames[i]->last;
75 assoc->last = assoc->first + last;
78 (assoc, index_nodenames_index, index_nodenames, index_nodenames_slots,
79 10, INDEX_NAME_ASSOC *);
82 /* Find and return the indices of WINDOW's file. The indices are defined
83 as the first node in the file containing the word "Index" and any
84 immediately following nodes whose names also contain "Index". All such
85 indices are concatenated and the result returned. If WINDOW's info file
86 doesn't have any indices, a NULL pointer is returned. */
88 info_indices_of_window (WINDOW *window)
92 fb = file_buffer_of_window (window);
94 return info_indices_of_file_buffer (fb);
98 info_indices_of_file_buffer (FILE_BUFFER *file_buffer)
101 REFERENCE **result = NULL;
103 /* No file buffer, no indices. */
107 /* Reset globals describing where the index was found. */
108 maybe_free (initial_index_filename);
109 maybe_free (initial_index_nodename);
110 initial_index_filename = NULL;
111 initial_index_nodename = NULL;
115 for (i = 0; index_nodenames[i]; i++)
117 free (index_nodenames[i]->name);
118 free (index_nodenames[i]);
121 index_nodenames_index = 0;
122 index_nodenames[0] = NULL;
125 /* Grovel the names of the nodes found in this file. */
126 if (file_buffer->tags)
130 for (i = 0; (tag = file_buffer->tags[i]); i++)
132 if (string_in_line ("Index", tag->nodename) != -1)
137 /* Found one. Get its menu. */
138 node = info_get_node (tag->filename, tag->nodename);
142 /* Remember the filename and nodename of this index. */
143 initial_index_filename = xstrdup (file_buffer->filename);
144 initial_index_nodename = xstrdup (tag->nodename);
146 menu = info_menu_of_node (node);
148 /* If we have a menu, add this index's nodename and range
149 to our list of index_nodenames. */
152 add_index_to_index_nodenames (menu, node);
154 /* Concatenate the references found so far. */
155 result = info_concatenate_references (result, menu);
162 /* If there is a result, clean it up so that every entry has a filename. */
163 for (i = 0; result && result[i]; i++)
164 if (!result[i]->filename)
165 result[i]->filename = xstrdup (file_buffer->filename);
170 DECLARE_INFO_COMMAND (info_index_search,
171 _("Look up a string in the index for this file"))
173 do_info_index_search (window, count, 0);
176 /* Look up SEARCH_STRING in the index for this file. If SEARCH_STRING
177 is NULL, prompt user for input. */
179 do_info_index_search (WINDOW *window, int count, char *search_string)
184 /* Reset the index offset, since this is not the info-index-next command. */
187 /* The user is selecting a new search string, so flush the old one. */
188 maybe_free (index_search);
191 /* If this window's file is not the same as the one that we last built an
192 index for, build and remember an index now. */
193 fb = file_buffer_of_window (window);
194 if (!initial_index_filename ||
195 (FILENAME_CMP (initial_index_filename, fb->filename) != 0))
197 info_free_references (index_index);
198 window_message_in_echo_area (_("Finding index entries..."),
200 index_index = info_indices_of_file_buffer (fb);
203 /* If there is no index, quit now. */
206 info_error (_("No indices found."), NULL, NULL);
210 /* Okay, there is an index. Look for SEARCH_STRING, or, if it is
211 empty, prompt for one. */
212 if (search_string && *search_string)
213 line = xstrdup (search_string);
216 line = info_read_maybe_completing (window, _("Index entry: "),
218 window = active_window;
223 info_abort_key (active_window, 1, 0);
227 /* Empty line means move to the Index node. */
232 if (initial_index_filename && initial_index_nodename)
236 node = info_get_node (initial_index_filename,
237 initial_index_nodename);
238 set_remembered_pagetop_and_point (window);
239 window_set_node_of_window (window, node);
240 remember_window_and_node (window, node);
241 window_clear_echo_area ();
247 /* The user typed either a completed index label, or a partial string.
248 Find an exact match, or, failing that, the first index entry containing
249 the partial string. So, we just call info_next_index_match () with minor
250 manipulation of INDEX_OFFSET. */
254 /* Start the search right after/before this index. */
258 for (i = 0; index_index[i]; i++);
264 old_offset = index_offset;
266 /* The "last" string searched for is this one. */
269 /* Find it, or error. */
270 info_next_index_match (window, count, 0);
272 /* If the search failed, return the index offset to where it belongs. */
273 if (index_offset == old_offset)
279 index_entry_exists (WINDOW *window, char *string)
284 /* If there is no previous search string, the user hasn't built an index
289 fb = file_buffer_of_window (window);
290 if (!initial_index_filename
291 || (FILENAME_CMP (initial_index_filename, fb->filename) != 0))
293 info_free_references (index_index);
294 index_index = info_indices_of_file_buffer (fb);
297 /* If there is no index, that is an error. */
301 for (i = 0; (i > -1) && (index_index[i]); i++)
302 if (strcmp (string, index_index[i]->label) == 0)
305 /* If that failed, look for the next substring match. */
306 if ((i < 0) || (!index_index[i]))
308 for (i = 0; (i > -1) && (index_index[i]); i++)
309 if (string_in_line (string, index_index[i]->label) != -1)
312 if ((i > -1) && (index_index[i]))
313 string_in_line (string, index_index[i]->label);
316 /* If that failed, return 0. */
317 if ((i < 0) || (!index_index[i]))
323 DECLARE_INFO_COMMAND (info_next_index_match,
324 _("Go to the next matching index item from the last `\\[index-search]' command"))
330 /* If there is no previous search string, the user hasn't built an index
334 info_error (_("No previous index search string."), NULL, NULL);
338 /* If there is no index, that is an error. */
341 info_error (_("No index entries."), NULL, NULL);
345 /* The direction of this search is controlled by the value of the
352 /* Search for the next occurence of index_search. First try to find
356 for (i = index_offset + dir; (i > -1) && (index_index[i]); i += dir)
357 if (strcmp (index_search, index_index[i]->label) == 0)
360 /* If that failed, look for the next substring match. */
361 if ((i < 0) || (!index_index[i]))
363 for (i = index_offset + dir; (i > -1) && (index_index[i]); i += dir)
364 if (string_in_line (index_search, index_index[i]->label) != -1)
367 if ((i > -1) && (index_index[i]))
368 partial = string_in_line (index_search, index_index[i]->label);
371 /* If that failed, print an error. */
372 if ((i < 0) || (!index_index[i]))
374 info_error (_("No %sindex entries containing `%s'."),
375 index_offset > 0 ? (char *) _("more ") : "", index_search);
379 /* Okay, we found the next one. Move the offset to the current entry. */
382 /* Report to the user on what we have found. */
385 const char *name = _("CAN'T SEE THIS");
388 for (j = 0; index_nodenames[j]; j++)
390 if ((i >= index_nodenames[j]->first) &&
391 (i <= index_nodenames[j]->last))
393 name = index_nodenames[j]->name;
398 /* If we had a partial match, indicate to the user which part of the
400 match = xstrdup (index_index[i]->label);
402 if (partial && show_index_match)
404 int k, ls, start, upper;
406 ls = strlen (index_search);
407 start = partial - ls;
408 upper = isupper (match[start]) ? 1 : 0;
410 for (k = 0; k < ls; k++)
412 match[k + start] = info_tolower (match[k + start]);
414 match[k + start] = info_toupper (match[k + start]);
420 format = replace_in_documentation
421 (_("Found `%s' in %s. (`\\[next-index-match]' tries to find next.)"),
424 window_message_in_echo_area (format, match, (char *) name);
430 /* Select the node corresponding to this index entry. */
431 node = info_get_node (index_index[i]->filename, index_index[i]->nodename);
435 info_error (msg_cant_file_node,
436 index_index[i]->filename, index_index[i]->nodename);
440 info_set_node_of_window (1, window, node);
444 long line = index_index[i]->line_number - 1;
446 if (line >= 0 && line < window->line_count)
448 /* Jump to the line number specified in the index entry. */
449 loc = window->line_starts[line] - window->node->contents;
453 /* Try to find an occurence of LABEL in this node. */
454 long start = window->line_starts[1] - window->node->contents;
455 loc = info_target_search_node (node, index_index[i]->label, start);
461 window_adjust_pagetop (window);
466 /* **************************************************************** */
468 /* Info APROPOS: Search every known index. */
470 /* **************************************************************** */
472 /* For every menu item in DIR, search the indices of that file for
475 apropos_in_all_indices (char *search_string, int inform)
477 register int i, dir_index;
478 REFERENCE **all_indices = NULL;
479 REFERENCE **dir_menu = 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'..."),
552 this_index = info_indices_of_file_buffer (this_fb);
555 if (this_fb && inform)
556 unmessage_in_echo_area ();
561 /* Remember the filename which contains this set of references. */
562 for (i = 0; this_index && this_index[i]; i++)
563 if (!this_index[i]->filename)
564 this_index[i]->filename = xstrdup (this_fb->filename);
566 /* Concatenate with the other indices. */
567 all_indices = info_concatenate_references (all_indices, this_index);
571 info_free_references (dir_menu);
573 /* Build a list of the references which contain SEARCH_STRING. */
576 REFERENCE *entry, **apropos_list = NULL;
577 int apropos_list_index = 0;
578 int apropos_list_slots = 0;
580 for (i = 0; (entry = all_indices[i]); i++)
582 if (string_in_line (search_string, entry->label) != -1)
585 (entry, apropos_list_index, apropos_list, apropos_list_slots,
590 maybe_free (entry->label);
591 maybe_free (entry->filename);
592 maybe_free (entry->nodename);
598 all_indices = apropos_list;
603 #define APROPOS_NONE \
604 N_("No available info files have `%s' in their indices.")
607 info_apropos (char *string)
609 REFERENCE **apropos_list;
611 apropos_list = apropos_in_all_indices (string, 0);
614 info_error (_(APROPOS_NONE), string, NULL);
620 for (i = 0; (entry = apropos_list[i]); i++)
621 fprintf (stdout, "\"(%s)%s\" -- %s\n",
622 entry->filename, entry->nodename, entry->label);
624 info_free_references (apropos_list);
627 static char *apropos_list_nodename = "*Apropos*";
629 DECLARE_INFO_COMMAND (info_index_apropos,
630 _("Grovel all known info file's indices for a string and build a menu"))
634 line = info_read_in_echo_area (window, _("Index apropos: "));
636 window = active_window;
641 info_abort_key (window, 1, 1);
645 /* User typed something? */
648 REFERENCE **apropos_list;
651 apropos_list = apropos_in_all_indices (line, 1);
654 info_error (_(APROPOS_NONE), line, NULL);
660 initialize_message_buffer ();
661 printf_to_message_buffer
662 (_("\n* Menu: Nodes whose indices contain `%s':\n"),
664 line_buffer = xmalloc (500);
666 for (i = 0; apropos_list[i]; i++)
669 /* The label might be identical to that of another index
670 entry in another Info file. Therefore, we make the file
671 name part of the menu entry, to make them all distinct. */
672 sprintf (line_buffer, "* %s [%s]: ",
673 apropos_list[i]->label, apropos_list[i]->filename);
674 len = pad_to (40, line_buffer);
675 sprintf (line_buffer + len, "(%s)%s.",
676 apropos_list[i]->filename, apropos_list[i]->nodename);
677 printf_to_message_buffer ("%s\n", line_buffer, NULL, NULL);
682 apropos_node = message_buffer_to_node ();
683 add_gcable_pointer (apropos_node->contents);
684 name_internal_node (apropos_node, apropos_list_nodename);
686 /* Even though this is an internal node, we don't want the window
687 system to treat it specially. So we turn off the internalness
689 apropos_node->flags &= ~N_IsInternal;
691 /* Find/Create a window to contain this node. */
696 set_remembered_pagetop_and_point (window);
698 /* If a window is visible and showing an apropos list already,
700 for (new = windows; new; new = new->next)
704 if (internal_info_node_p (node) &&
705 (strcmp (node->nodename, apropos_list_nodename) == 0))
709 /* If we couldn't find an existing window, try to use the next window
711 if (!new && window->next)
714 /* If we still don't have a window, make a new one to contain
720 old_active = active_window;
721 active_window = window;
722 new = window_make_window (NULL);
723 active_window = old_active;
726 /* If we couldn't make a new window, use this one. */
730 /* Lines do not wrap in this window. */
731 new->flags |= W_NoWrap;
733 window_set_node_of_window (new, apropos_node);
734 remember_window_and_node (new, apropos_node);
737 info_free_references (apropos_list);
741 if (!info_error_was_printed)
742 window_clear_echo_area ();