source: trunk/texinfo/info/indices.c@ 3003

Last change on this file since 3003 was 2617, checked in by bird, 19 years ago

GNU Texinfo 4.8

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