3 /* indices.c -- deal with an Info file index.
4 Id: indices.c,v 1.5 2004/04/11 17:56:45 karl Exp
6 Copyright (C) 1993, 1997, 1998, 1999, 2002, 2003, 2004 Free Software
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)
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.
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.
23 Originally written by Brian Fox (bfox@ai.mit.edu). */
28 /* User-visible variable controls the output of info-index-next. */
29 int show_index_match
= 1;
31 /* In the Info sense, an index is a menu. This variable holds the last
33 static REFERENCE
**index_index
= (REFERENCE
**)NULL
;
35 /* The offset of the most recently selected index element. */
36 static int index_offset
= 0;
38 /* Variable which holds the last string searched for. */
39 static char *index_search
= (char *)NULL
;
41 /* A couple of "globals" describing where the initial index was found. */
42 static char *initial_index_filename
= (char *)NULL
;
43 static char *initial_index_nodename
= (char *)NULL
;
45 /* A structure associating index names with index offset ranges. */
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. */
52 /* An array associating index nodenames with index offset ranges. */
53 static INDEX_NAME_ASSOC
**index_nodenames
= (INDEX_NAME_ASSOC
**)NULL
;
54 static int index_nodenames_index
= 0;
55 static int index_nodenames_slots
= 0;
57 /* Add the name of NODE, and the range of the associated index elements
58 (passed in ARRAY) to index_nodenames. */
60 add_index_to_index_nodenames (REFERENCE
**array
, NODE
*node
)
63 INDEX_NAME_ASSOC
*assoc
;
65 for (last
= 0; array
[last
+ 1]; last
++);
66 assoc
= (INDEX_NAME_ASSOC
*)xmalloc (sizeof (INDEX_NAME_ASSOC
));
67 assoc
->name
= xstrdup (node
->nodename
);
69 if (!index_nodenames_index
)
76 for (i
= 0; index_nodenames
[i
+ 1]; i
++);
77 assoc
->first
= 1 + index_nodenames
[i
]->last
;
78 assoc
->last
= assoc
->first
+ last
;
81 (assoc
, index_nodenames_index
, index_nodenames
, index_nodenames_slots
,
82 10, INDEX_NAME_ASSOC
*);
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. */
91 info_indices_of_window (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
*file_buffer
)
104 REFERENCE
**result
= (REFERENCE
**)NULL
;
106 /* No file buffer, no indices. */
108 return ((REFERENCE
**)NULL
);
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
;
118 for (i
= 0; index_nodenames
[i
]; i
++)
120 free (index_nodenames
[i
]->name
);
121 free (index_nodenames
[i
]);
124 index_nodenames_index
= 0;
125 index_nodenames
[0] = (INDEX_NAME_ASSOC
*)NULL
;
128 /* Grovel the names of the nodes found in this file. */
129 if (file_buffer
->tags
)
133 for (i
= 0; (tag
= file_buffer
->tags
[i
]); i
++)
135 if (string_in_line ("Index", tag
->nodename
) != -1)
140 /* Found one. Get its menu. */
141 node
= info_get_node (tag
->filename
, tag
->nodename
);
145 /* Remember the filename and nodename of this index. */
146 initial_index_filename
= xstrdup (file_buffer
->filename
);
147 initial_index_nodename
= xstrdup (tag
->nodename
);
149 menu
= info_menu_of_node (node
);
151 /* If we have a menu, add this index's nodename and range
152 to our list of index_nodenames. */
155 add_index_to_index_nodenames (menu
, node
);
157 /* Concatenate the references found so far. */
158 result
= info_concatenate_references (result
, menu
);
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
);
173 DECLARE_INFO_COMMAND (info_index_search
,
174 _("Look up a string in the index for this file"))
176 do_info_index_search (window
, count
, 0);
179 /* Look up SEARCH_STRING in the index for this file. If SEARCH_STRING
180 is NULL, prompt user for input. */
182 do_info_index_search (WINDOW
*window
, int count
, char *search_string
)
187 /* Reset the index offset, since this is not the info-index-next command. */
190 /* The user is selecting a new search string, so flush the old one. */
191 maybe_free (index_search
);
192 index_search
= (char *)NULL
;
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))
200 info_free_references (index_index
);
201 window_message_in_echo_area ((char *) _("Finding index entries..."),
203 index_index
= info_indices_of_file_buffer (fb
);
206 /* If there is no index, quit now. */
209 info_error ((char *) _("No indices found."), NULL
, NULL
);
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
);
219 line
= info_read_maybe_completing (window
, (char *) _("Index entry: "),
221 window
= active_window
;
226 info_abort_key (active_window
, 1, 0);
230 /* Empty line means move to the Index node. */
235 if (initial_index_filename
&& initial_index_nodename
)
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 ();
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. */
257 /* Start the search right after/before this index. */
261 for (i
= 0; index_index
[i
]; i
++);
267 old_offset
= index_offset
;
269 /* The "last" string searched for is this one. */
272 /* Find it, or error. */
273 info_next_index_match (window
, count
, 0);
275 /* If the search failed, return the index offset to where it belongs. */
276 if (index_offset
== old_offset
)
282 index_entry_exists (WINDOW
*window
, char *string
)
287 /* If there is no previous search string, the user hasn't built an index
292 fb
= file_buffer_of_window (window
);
293 if (!initial_index_filename
294 || (FILENAME_CMP (initial_index_filename
, fb
->filename
) != 0))
296 info_free_references (index_index
);
297 index_index
= info_indices_of_file_buffer (fb
);
300 /* If there is no index, that is an error. */
304 for (i
= 0; (i
> -1) && (index_index
[i
]); i
++)
305 if (strcmp (string
, index_index
[i
]->label
) == 0)
308 /* If that failed, look for the next substring match. */
309 if ((i
< 0) || (!index_index
[i
]))
311 for (i
= 0; (i
> -1) && (index_index
[i
]); i
++)
312 if (string_in_line (string
, index_index
[i
]->label
) != -1)
315 if ((i
> -1) && (index_index
[i
]))
316 string_in_line (string
, index_index
[i
]->label
);
319 /* If that failed, return 0. */
320 if ((i
< 0) || (!index_index
[i
]))
326 DECLARE_INFO_COMMAND (info_next_index_match
,
327 _("Go to the next matching index item from the last `\\[index-search]' command"))
333 /* If there is no previous search string, the user hasn't built an index
337 info_error ((char *) _("No previous index search string."), NULL
, NULL
);
341 /* If there is no index, that is an error. */
344 info_error ((char *) _("No index entries."), NULL
, NULL
);
348 /* The direction of this search is controlled by the value of the
355 /* Search for the next occurence of index_search. First try to find
359 for (i
= index_offset
+ dir
; (i
> -1) && (index_index
[i
]); i
+= dir
)
360 if (strcmp (index_search
, index_index
[i
]->label
) == 0)
363 /* If that failed, look for the next substring match. */
364 if ((i
< 0) || (!index_index
[i
]))
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)
370 if ((i
> -1) && (index_index
[i
]))
371 partial
= string_in_line (index_search
, index_index
[i
]->label
);
374 /* If that failed, print an error. */
375 if ((i
< 0) || (!index_index
[i
]))
377 info_error ((char *) _("No %sindex entries containing `%s'."),
378 index_offset
> 0 ? (char *) _("more ") : "", index_search
);
382 /* Okay, we found the next one. Move the offset to the current entry. */
385 /* Report to the user on what we have found. */
388 const char *name
= _("CAN'T SEE THIS");
391 for (j
= 0; index_nodenames
[j
]; j
++)
393 if ((i
>= index_nodenames
[j
]->first
) &&
394 (i
<= index_nodenames
[j
]->last
))
396 name
= index_nodenames
[j
]->name
;
401 /* If we had a partial match, indicate to the user which part of the
403 match
= xstrdup (index_index
[i
]->label
);
405 if (partial
&& show_index_match
)
407 int k
, ls
, start
, upper
;
409 ls
= strlen (index_search
);
410 start
= partial
- ls
;
411 upper
= isupper (match
[start
]) ? 1 : 0;
413 for (k
= 0; k
< ls
; k
++)
415 match
[k
+ start
] = info_tolower (match
[k
+ start
]);
417 match
[k
+ start
] = info_toupper (match
[k
+ start
]);
423 format
= replace_in_documentation
424 ((char *) _("Found `%s' in %s. (`\\[next-index-match]' tries to find next.)"),
427 window_message_in_echo_area (format
, match
, (char *) name
);
433 /* Select the node corresponding to this index entry. */
434 node
= info_get_node (index_index
[i
]->filename
, index_index
[i
]->nodename
);
438 info_error ((char *) msg_cant_file_node
,
439 index_index
[i
]->filename
, index_index
[i
]->nodename
);
443 info_set_node_of_window (1, window
, node
);
445 /* Try to find an occurence of LABEL in this node. */
449 start
= window
->line_starts
[1] - window
->node
->contents
;
450 loc
= info_target_search_node (node
, index_index
[i
]->label
, start
);
455 window_adjust_pagetop (window
);
460 /* **************************************************************** */
462 /* Info APROPOS: Search every known index. */
464 /* **************************************************************** */
466 /* For every menu item in DIR, search the indices of that file for
469 apropos_in_all_indices (char *search_string
, int inform
)
471 register int i
, dir_index
;
472 REFERENCE
**all_indices
= (REFERENCE
**)NULL
;
473 REFERENCE
**dir_menu
= (REFERENCE
**)NULL
;
476 dir_node
= info_get_node ("dir", "Top");
478 dir_menu
= info_menu_of_node (dir_node
);
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
486 for (dir_index
= 0; dir_menu
[dir_index
]; dir_index
++)
488 REFERENCE
**this_index
, *this_item
;
490 FILE_BUFFER
*this_fb
;
491 int dir_node_duplicated
= 0;
493 this_item
= dir_menu
[dir_index
];
495 if (!this_item
->filename
)
497 dir_node_duplicated
= 1;
498 if (dir_node
->parent
)
499 this_item
->filename
= xstrdup (dir_node
->parent
);
501 this_item
->filename
= xstrdup (dir_node
->filename
);
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
);
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");
514 if (dir_node_duplicated
)
515 free (this_item
->filename
);
519 /* Get the file buffer associated with this node. */
523 files_name
= this_node
->parent
;
525 files_name
= this_node
->filename
;
527 this_fb
= info_find_file (files_name
);
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)
537 if (dir_node_duplicated
)
538 free (this_item
->filename
);
542 if (this_fb
&& inform
)
543 message_in_echo_area ((char *) _("Scanning indices of `%s'..."),
546 this_index
= info_indices_of_file_buffer (this_fb
);
549 if (this_fb
&& inform
)
550 unmessage_in_echo_area ();
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
);
560 /* Concatenate with the other indices. */
561 all_indices
= info_concatenate_references (all_indices
, this_index
);
565 info_free_references (dir_menu
);
567 /* Build a list of the references which contain SEARCH_STRING. */
570 REFERENCE
*entry
, **apropos_list
= (REFERENCE
**)NULL
;
571 int apropos_list_index
= 0;
572 int apropos_list_slots
= 0;
574 for (i
= 0; (entry
= all_indices
[i
]); i
++)
576 if (string_in_line (search_string
, entry
->label
) != -1)
579 (entry
, apropos_list_index
, apropos_list
, apropos_list_slots
,
584 maybe_free (entry
->label
);
585 maybe_free (entry
->filename
);
586 maybe_free (entry
->nodename
);
592 all_indices
= apropos_list
;
594 return (all_indices
);
597 #define APROPOS_NONE \
598 N_("No available info files have `%s' in their indices.")
601 info_apropos (char *string
)
603 REFERENCE
**apropos_list
;
605 apropos_list
= apropos_in_all_indices (string
, 0);
608 info_error ((char *) _(APROPOS_NONE
), string
, NULL
);
614 for (i
= 0; (entry
= apropos_list
[i
]); i
++)
615 fprintf (stdout
, "\"(%s)%s\" -- %s\n",
616 entry
->filename
, entry
->nodename
, entry
->label
);
618 info_free_references (apropos_list
);
621 static char *apropos_list_nodename
= "*Apropos*";
623 DECLARE_INFO_COMMAND (info_index_apropos
,
624 _("Grovel all known info file's indices for a string and build a menu"))
628 line
= info_read_in_echo_area (window
, (char *) _("Index apropos: "));
630 window
= active_window
;
635 info_abort_key (window
, 1, 1);
639 /* User typed something? */
642 REFERENCE
**apropos_list
;
645 apropos_list
= apropos_in_all_indices (line
, 1);
648 info_error ((char *) _(APROPOS_NONE
), line
, NULL
);
654 initialize_message_buffer ();
655 printf_to_message_buffer
656 ((char *) _("\n* Menu: Nodes whose indices contain `%s':\n"),
658 line_buffer
= (char *)xmalloc (500);
660 for (i
= 0; apropos_list
[i
]; i
++)
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
);
676 apropos_node
= message_buffer_to_node ();
677 add_gcable_pointer (apropos_node
->contents
);
678 name_internal_node (apropos_node
, apropos_list_nodename
);
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
683 apropos_node
->flags
&= ~N_IsInternal
;
685 /* Find/Create a window to contain this node. */
690 set_remembered_pagetop_and_point (window
);
692 /* If a window is visible and showing an apropos list already,
694 for (new = windows
; new; new = new->next
)
698 if (internal_info_node_p (node
) &&
699 (strcmp (node
->nodename
, apropos_list_nodename
) == 0))
703 /* If we couldn't find an existing window, try to use the next window
705 if (!new && window
->next
)
708 /* If we still don't have a window, make a new one to contain
714 old_active
= active_window
;
715 active_window
= window
;
716 new = window_make_window ((NODE
*)NULL
);
717 active_window
= old_active
;
720 /* If we couldn't make a new window, use this one. */
724 /* Lines do not wrap in this window. */
725 new->flags
|= W_NoWrap
;
727 window_set_node_of_window (new, apropos_node
);
728 remember_window_and_node (new, apropos_node
);
731 info_free_references (apropos_list
);
735 if (!info_error_was_printed
)
736 window_clear_echo_area ();