1 /* indices.c -- deal with an Info file index.
2 $Id: indices.c,v 1.5 2004/04/11 17:56:45 karl Exp $
4 Copyright (C) 1993, 1997, 1998, 1999, 2002, 2003, 2004 Free Software
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 2, or (at your option)
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, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 Originally written by Brian Fox (bfox@ai.mit.edu). */
26 /* User-visible variable controls the output of info-index-next. */
27 int show_index_match
= 1;
29 /* In the Info sense, an index is a menu. This variable holds the last
31 static REFERENCE
**index_index
= (REFERENCE
**)NULL
;
33 /* The offset of the most recently selected index element. */
34 static int index_offset
= 0;
36 /* Variable which holds the last string searched for. */
37 static char *index_search
= (char *)NULL
;
39 /* A couple of "globals" describing where the initial index was found. */
40 static char *initial_index_filename
= (char *)NULL
;
41 static char *initial_index_nodename
= (char *)NULL
;
43 /* A structure associating index names with index offset ranges. */
45 char *name
; /* The nodename of this index. */
46 int first
; /* The index in our list of the first entry. */
47 int last
; /* The index in our list of the last entry. */
50 /* An array associating index nodenames with index offset ranges. */
51 static INDEX_NAME_ASSOC
**index_nodenames
= (INDEX_NAME_ASSOC
**)NULL
;
52 static int index_nodenames_index
= 0;
53 static int index_nodenames_slots
= 0;
55 /* Add the name of NODE, and the range of the associated index elements
56 (passed in ARRAY) to index_nodenames. */
58 add_index_to_index_nodenames (REFERENCE
**array
, NODE
*node
)
61 INDEX_NAME_ASSOC
*assoc
;
63 for (last
= 0; array
[last
+ 1]; last
++);
64 assoc
= (INDEX_NAME_ASSOC
*)xmalloc (sizeof (INDEX_NAME_ASSOC
));
65 assoc
->name
= xstrdup (node
->nodename
);
67 if (!index_nodenames_index
)
74 for (i
= 0; index_nodenames
[i
+ 1]; i
++);
75 assoc
->first
= 1 + index_nodenames
[i
]->last
;
76 assoc
->last
= assoc
->first
+ last
;
79 (assoc
, index_nodenames_index
, index_nodenames
, index_nodenames_slots
,
80 10, INDEX_NAME_ASSOC
*);
83 /* Find and return the indices of WINDOW's file. The indices are defined
84 as the first node in the file containing the word "Index" and any
85 immediately following nodes whose names also contain "Index". All such
86 indices are concatenated and the result returned. If WINDOW's info file
87 doesn't have any indices, a NULL pointer is returned. */
89 info_indices_of_window (WINDOW
*window
)
93 fb
= file_buffer_of_window (window
);
95 return (info_indices_of_file_buffer (fb
));
99 info_indices_of_file_buffer (FILE_BUFFER
*file_buffer
)
102 REFERENCE
**result
= (REFERENCE
**)NULL
;
104 /* No file buffer, no indices. */
106 return ((REFERENCE
**)NULL
);
108 /* Reset globals describing where the index was found. */
109 maybe_free (initial_index_filename
);
110 maybe_free (initial_index_nodename
);
111 initial_index_filename
= (char *)NULL
;
112 initial_index_nodename
= (char *)NULL
;
116 for (i
= 0; index_nodenames
[i
]; i
++)
118 free (index_nodenames
[i
]->name
);
119 free (index_nodenames
[i
]);
122 index_nodenames_index
= 0;
123 index_nodenames
[0] = (INDEX_NAME_ASSOC
*)NULL
;
126 /* Grovel the names of the nodes found in this file. */
127 if (file_buffer
->tags
)
131 for (i
= 0; (tag
= file_buffer
->tags
[i
]); i
++)
133 if (string_in_line ("Index", tag
->nodename
) != -1)
138 /* Found one. Get its menu. */
139 node
= info_get_node (tag
->filename
, tag
->nodename
);
143 /* Remember the filename and nodename of this index. */
144 initial_index_filename
= xstrdup (file_buffer
->filename
);
145 initial_index_nodename
= xstrdup (tag
->nodename
);
147 menu
= info_menu_of_node (node
);
149 /* If we have a menu, add this index's nodename and range
150 to our list of index_nodenames. */
153 add_index_to_index_nodenames (menu
, node
);
155 /* Concatenate the references found so far. */
156 result
= info_concatenate_references (result
, menu
);
163 /* If there is a result, clean it up so that every entry has a filename. */
164 for (i
= 0; result
&& result
[i
]; i
++)
165 if (!result
[i
]->filename
)
166 result
[i
]->filename
= xstrdup (file_buffer
->filename
);
171 DECLARE_INFO_COMMAND (info_index_search
,
172 _("Look up a string in the index for this file"))
174 do_info_index_search (window
, count
, 0);
177 /* Look up SEARCH_STRING in the index for this file. If SEARCH_STRING
178 is NULL, prompt user for input. */
180 do_info_index_search (WINDOW
*window
, int count
, char *search_string
)
185 /* Reset the index offset, since this is not the info-index-next command. */
188 /* The user is selecting a new search string, so flush the old one. */
189 maybe_free (index_search
);
190 index_search
= (char *)NULL
;
192 /* If this window's file is not the same as the one that we last built an
193 index for, build and remember an index now. */
194 fb
= file_buffer_of_window (window
);
195 if (!initial_index_filename
||
196 (FILENAME_CMP (initial_index_filename
, fb
->filename
) != 0))
198 info_free_references (index_index
);
199 window_message_in_echo_area ((char *) _("Finding index entries..."),
201 index_index
= info_indices_of_file_buffer (fb
);
204 /* If there is no index, quit now. */
207 info_error ((char *) _("No indices found."), NULL
, NULL
);
211 /* Okay, there is an index. Look for SEARCH_STRING, or, if it is
212 empty, prompt for one. */
213 if (search_string
&& *search_string
)
214 line
= xstrdup (search_string
);
217 line
= info_read_maybe_completing (window
, (char *) _("Index entry: "),
219 window
= active_window
;
224 info_abort_key (active_window
, 1, 0);
228 /* Empty line means move to the Index node. */
233 if (initial_index_filename
&& initial_index_nodename
)
237 node
= info_get_node (initial_index_filename
,
238 initial_index_nodename
);
239 set_remembered_pagetop_and_point (window
);
240 window_set_node_of_window (window
, node
);
241 remember_window_and_node (window
, node
);
242 window_clear_echo_area ();
248 /* The user typed either a completed index label, or a partial string.
249 Find an exact match, or, failing that, the first index entry containing
250 the partial string. So, we just call info_next_index_match () with minor
251 manipulation of INDEX_OFFSET. */
255 /* Start the search right after/before this index. */
259 for (i
= 0; index_index
[i
]; i
++);
265 old_offset
= index_offset
;
267 /* The "last" string searched for is this one. */
270 /* Find it, or error. */
271 info_next_index_match (window
, count
, 0);
273 /* If the search failed, return the index offset to where it belongs. */
274 if (index_offset
== old_offset
)
280 index_entry_exists (WINDOW
*window
, char *string
)
285 /* If there is no previous search string, the user hasn't built an index
290 fb
= file_buffer_of_window (window
);
291 if (!initial_index_filename
292 || (FILENAME_CMP (initial_index_filename
, fb
->filename
) != 0))
294 info_free_references (index_index
);
295 index_index
= info_indices_of_file_buffer (fb
);
298 /* If there is no index, that is an error. */
302 for (i
= 0; (i
> -1) && (index_index
[i
]); i
++)
303 if (strcmp (string
, index_index
[i
]->label
) == 0)
306 /* If that failed, look for the next substring match. */
307 if ((i
< 0) || (!index_index
[i
]))
309 for (i
= 0; (i
> -1) && (index_index
[i
]); i
++)
310 if (string_in_line (string
, index_index
[i
]->label
) != -1)
313 if ((i
> -1) && (index_index
[i
]))
314 string_in_line (string
, index_index
[i
]->label
);
317 /* If that failed, return 0. */
318 if ((i
< 0) || (!index_index
[i
]))
324 DECLARE_INFO_COMMAND (info_next_index_match
,
325 _("Go to the next matching index item from the last `\\[index-search]' command"))
331 /* If there is no previous search string, the user hasn't built an index
335 info_error ((char *) _("No previous index search string."), NULL
, NULL
);
339 /* If there is no index, that is an error. */
342 info_error ((char *) _("No index entries."), NULL
, NULL
);
346 /* The direction of this search is controlled by the value of the
353 /* Search for the next occurence of index_search. First try to find
357 for (i
= index_offset
+ dir
; (i
> -1) && (index_index
[i
]); i
+= dir
)
358 if (strcmp (index_search
, index_index
[i
]->label
) == 0)
361 /* If that failed, look for the next substring match. */
362 if ((i
< 0) || (!index_index
[i
]))
364 for (i
= index_offset
+ dir
; (i
> -1) && (index_index
[i
]); i
+= dir
)
365 if (string_in_line (index_search
, index_index
[i
]->label
) != -1)
368 if ((i
> -1) && (index_index
[i
]))
369 partial
= string_in_line (index_search
, index_index
[i
]->label
);
372 /* If that failed, print an error. */
373 if ((i
< 0) || (!index_index
[i
]))
375 info_error ((char *) _("No %sindex entries containing `%s'."),
376 index_offset
> 0 ? (char *) _("more ") : "", index_search
);
380 /* Okay, we found the next one. Move the offset to the current entry. */
383 /* Report to the user on what we have found. */
386 const char *name
= _("CAN'T SEE THIS");
389 for (j
= 0; index_nodenames
[j
]; j
++)
391 if ((i
>= index_nodenames
[j
]->first
) &&
392 (i
<= index_nodenames
[j
]->last
))
394 name
= index_nodenames
[j
]->name
;
399 /* If we had a partial match, indicate to the user which part of the
401 match
= xstrdup (index_index
[i
]->label
);
403 if (partial
&& show_index_match
)
405 int k
, ls
, start
, upper
;
407 ls
= strlen (index_search
);
408 start
= partial
- ls
;
409 upper
= isupper (match
[start
]) ? 1 : 0;
411 for (k
= 0; k
< ls
; k
++)
413 match
[k
+ start
] = info_tolower (match
[k
+ start
]);
415 match
[k
+ start
] = info_toupper (match
[k
+ start
]);
421 format
= replace_in_documentation
422 ((char *) _("Found `%s' in %s. (`\\[next-index-match]' tries to find next.)"),
425 window_message_in_echo_area (format
, match
, (char *) name
);
431 /* Select the node corresponding to this index entry. */
432 node
= info_get_node (index_index
[i
]->filename
, index_index
[i
]->nodename
);
436 info_error ((char *) msg_cant_file_node
,
437 index_index
[i
]->filename
, index_index
[i
]->nodename
);
441 info_set_node_of_window (1, window
, node
);
443 /* Try to find an occurence of LABEL in this node. */
447 start
= window
->line_starts
[1] - window
->node
->contents
;
448 loc
= info_target_search_node (node
, index_index
[i
]->label
, start
);
453 window_adjust_pagetop (window
);
458 /* **************************************************************** */
460 /* Info APROPOS: Search every known index. */
462 /* **************************************************************** */
464 /* For every menu item in DIR, search the indices of that file for
467 apropos_in_all_indices (char *search_string
, int inform
)
469 register int i
, dir_index
;
470 REFERENCE
**all_indices
= (REFERENCE
**)NULL
;
471 REFERENCE
**dir_menu
= (REFERENCE
**)NULL
;
474 dir_node
= info_get_node ("dir", "Top");
476 dir_menu
= info_menu_of_node (dir_node
);
481 /* For every menu item in DIR, get the associated node's file buffer and
482 read the indices of that file buffer. Gather all of the indices into
484 for (dir_index
= 0; dir_menu
[dir_index
]; dir_index
++)
486 REFERENCE
**this_index
, *this_item
;
488 FILE_BUFFER
*this_fb
;
489 int dir_node_duplicated
= 0;
491 this_item
= dir_menu
[dir_index
];
493 if (!this_item
->filename
)
495 dir_node_duplicated
= 1;
496 if (dir_node
->parent
)
497 this_item
->filename
= xstrdup (dir_node
->parent
);
499 this_item
->filename
= xstrdup (dir_node
->filename
);
502 /* Find this node. If we cannot find it, try using the label of the
503 entry as a file (i.e., "(LABEL)Top"). */
504 this_node
= info_get_node (this_item
->filename
, this_item
->nodename
);
506 if (!this_node
&& this_item
->nodename
&&
507 (strcmp (this_item
->label
, this_item
->nodename
) == 0))
508 this_node
= info_get_node (this_item
->label
, "Top");
512 if (dir_node_duplicated
)
513 free (this_item
->filename
);
517 /* Get the file buffer associated with this node. */
521 files_name
= this_node
->parent
;
523 files_name
= this_node
->filename
;
525 this_fb
= info_find_file (files_name
);
527 /* If we already scanned this file, don't do that again.
528 In addition to being faster, this also avoids having
529 multiple identical entries in the *Apropos* menu. */
530 for (i
= 0; i
< dir_index
; i
++)
531 if (FILENAME_CMP (this_fb
->filename
, dir_menu
[i
]->filename
) == 0)
535 if (dir_node_duplicated
)
536 free (this_item
->filename
);
540 if (this_fb
&& inform
)
541 message_in_echo_area ((char *) _("Scanning indices of `%s'..."),
544 this_index
= info_indices_of_file_buffer (this_fb
);
547 if (this_fb
&& inform
)
548 unmessage_in_echo_area ();
553 /* Remember the filename which contains this set of references. */
554 for (i
= 0; this_index
&& this_index
[i
]; i
++)
555 if (!this_index
[i
]->filename
)
556 this_index
[i
]->filename
= xstrdup (this_fb
->filename
);
558 /* Concatenate with the other indices. */
559 all_indices
= info_concatenate_references (all_indices
, this_index
);
563 info_free_references (dir_menu
);
565 /* Build a list of the references which contain SEARCH_STRING. */
568 REFERENCE
*entry
, **apropos_list
= (REFERENCE
**)NULL
;
569 int apropos_list_index
= 0;
570 int apropos_list_slots
= 0;
572 for (i
= 0; (entry
= all_indices
[i
]); i
++)
574 if (string_in_line (search_string
, entry
->label
) != -1)
577 (entry
, apropos_list_index
, apropos_list
, apropos_list_slots
,
582 maybe_free (entry
->label
);
583 maybe_free (entry
->filename
);
584 maybe_free (entry
->nodename
);
590 all_indices
= apropos_list
;
592 return (all_indices
);
595 #define APROPOS_NONE \
596 N_("No available info files have `%s' in their indices.")
599 info_apropos (char *string
)
601 REFERENCE
**apropos_list
;
603 apropos_list
= apropos_in_all_indices (string
, 0);
606 info_error ((char *) _(APROPOS_NONE
), string
, NULL
);
612 for (i
= 0; (entry
= apropos_list
[i
]); i
++)
613 fprintf (stdout
, "\"(%s)%s\" -- %s\n",
614 entry
->filename
, entry
->nodename
, entry
->label
);
616 info_free_references (apropos_list
);
619 static char *apropos_list_nodename
= "*Apropos*";
621 DECLARE_INFO_COMMAND (info_index_apropos
,
622 _("Grovel all known info file's indices for a string and build a menu"))
626 line
= info_read_in_echo_area (window
, (char *) _("Index apropos: "));
628 window
= active_window
;
633 info_abort_key (window
, 1, 1);
637 /* User typed something? */
640 REFERENCE
**apropos_list
;
643 apropos_list
= apropos_in_all_indices (line
, 1);
646 info_error ((char *) _(APROPOS_NONE
), line
, NULL
);
652 initialize_message_buffer ();
653 printf_to_message_buffer
654 ((char *) _("\n* Menu: Nodes whose indices contain `%s':\n"),
656 line_buffer
= (char *)xmalloc (500);
658 for (i
= 0; apropos_list
[i
]; i
++)
661 /* The label might be identical to that of another index
662 entry in another Info file. Therefore, we make the file
663 name part of the menu entry, to make them all distinct. */
664 sprintf (line_buffer
, "* %s [%s]: ",
665 apropos_list
[i
]->label
, apropos_list
[i
]->filename
);
666 len
= pad_to (40, line_buffer
);
667 sprintf (line_buffer
+ len
, "(%s)%s.",
668 apropos_list
[i
]->filename
, apropos_list
[i
]->nodename
);
669 printf_to_message_buffer ("%s\n", line_buffer
, NULL
, NULL
);
674 apropos_node
= message_buffer_to_node ();
675 add_gcable_pointer (apropos_node
->contents
);
676 name_internal_node (apropos_node
, apropos_list_nodename
);
678 /* Even though this is an internal node, we don't want the window
679 system to treat it specially. So we turn off the internalness
681 apropos_node
->flags
&= ~N_IsInternal
;
683 /* Find/Create a window to contain this node. */
688 set_remembered_pagetop_and_point (window
);
690 /* If a window is visible and showing an apropos list already,
692 for (new = windows
; new; new = new->next
)
696 if (internal_info_node_p (node
) &&
697 (strcmp (node
->nodename
, apropos_list_nodename
) == 0))
701 /* If we couldn't find an existing window, try to use the next window
703 if (!new && window
->next
)
706 /* If we still don't have a window, make a new one to contain
712 old_active
= active_window
;
713 active_window
= window
;
714 new = window_make_window ((NODE
*)NULL
);
715 active_window
= old_active
;
718 /* If we couldn't make a new window, use this one. */
722 /* Lines do not wrap in this window. */
723 new->flags
|= W_NoWrap
;
725 window_set_node_of_window (new, apropos_node
);
726 remember_window_and_node (new, apropos_node
);
729 info_free_references (apropos_list
);
733 if (!info_error_was_printed
)
734 window_clear_echo_area ();