xref: /openbsd/gnu/usr.bin/texinfo/info/indices.c (revision 73471bf0)
1 /* indices.c -- deal with an Info file index.
2    $Id: indices.c,v 1.5 2006/07/17 16:12:36 espie 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. */
27 int show_index_match = 1;
28 
29 /* In the Info sense, an index is a menu.  This variable holds the last
30    parsed index. */
31 static REFERENCE **index_index = (REFERENCE **)NULL;
32 
33 /* The offset of the most recently selected index element. */
34 static int index_offset = 0;
35 
36 /* Variable which holds the last string searched for. */
37 static char *index_search = (char *)NULL;
38 
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;
42 
43 /* A structure associating index names with index offset ranges. */
44 typedef 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. */
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;
54 
55 /* Add the name of NODE, and the range of the associated index elements
56    (passed in ARRAY) to index_nodenames. */
57 static void
58 add_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. */
88 REFERENCE **
89 info_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 
98 REFERENCE **
99 info_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 
171 DECLARE_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.  */
179 void
180 do_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 
279 int
280 index_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 
324 DECLARE_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 /*                 Info APROPOS: Search every known index.          */
461 /*                                                                  */
462 /* **************************************************************** */
463 
464 /* For every menu item in DIR, search the indices of that file for
465    SEARCH_STRING. */
466 REFERENCE **
467 apropos_in_all_indices (char *search_string, int inform)
468 {
469   register int i, dir_index;
470   REFERENCE **all_indices = (REFERENCE **)NULL;
471   REFERENCE **dir_menu = (REFERENCE **)NULL;
472   NODE *dir_node;
473 
474   dir_node = info_get_node ("dir", "Top");
475   if (dir_node)
476     dir_menu = info_menu_of_node (dir_node);
477 
478   if (!dir_menu)
479     return NULL;
480 
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
483      one large one. */
484   for (dir_index = 0; dir_menu[dir_index]; dir_index++)
485     {
486       REFERENCE **this_index, *this_item;
487       NODE *this_node;
488       FILE_BUFFER *this_fb;
489       int dir_node_duplicated = 0;
490 
491       this_item = dir_menu[dir_index];
492 
493       if (!this_item->filename)
494         {
495 	  dir_node_duplicated = 1;
496           if (dir_node->parent)
497             this_item->filename = xstrdup (dir_node->parent);
498           else
499             this_item->filename = xstrdup (dir_node->filename);
500         }
501 
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);
505 
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");
509 
510       if (!this_node)
511 	{
512 	  if (dir_node_duplicated)
513 	    free (this_item->filename);
514 	  continue;
515 	}
516 
517       /* Get the file buffer associated with this node. */
518       {
519         char *files_name;
520 
521         files_name = this_node->parent;
522         if (!files_name)
523           files_name = this_node->filename;
524 
525         this_fb = info_find_file (files_name);
526 
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)
532 	    break;
533 	if (i < dir_index)
534 	  {
535 	    if (dir_node_duplicated)
536 	      free (this_item->filename);
537 	    continue;
538 	  }
539 
540         if (this_fb && inform)
541           message_in_echo_area ((char *) _("Scanning indices of `%s'..."),
542               files_name, NULL);
543 
544         this_index = info_indices_of_file_buffer (this_fb);
545         free (this_node);
546 
547         if (this_fb && inform)
548           unmessage_in_echo_area ();
549       }
550 
551       if (this_index)
552         {
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);
557 
558           /* Concatenate with the other indices.  */
559           all_indices = info_concatenate_references (all_indices, this_index);
560         }
561     }
562 
563   info_free_references (dir_menu);
564 
565   /* Build a list of the references which contain SEARCH_STRING. */
566   if (all_indices)
567     {
568       REFERENCE *entry, **apropos_list = (REFERENCE **)NULL;
569       int apropos_list_index = 0;
570       int apropos_list_slots = 0;
571 
572       for (i = 0; (entry = all_indices[i]); i++)
573         {
574           if (string_in_line (search_string, entry->label) != -1)
575             {
576               add_pointer_to_array
577                 (entry, apropos_list_index, apropos_list, apropos_list_slots,
578                  100, REFERENCE *);
579             }
580           else
581             {
582               maybe_free (entry->label);
583               maybe_free (entry->filename);
584               maybe_free (entry->nodename);
585               free (entry);
586             }
587         }
588 
589       free (all_indices);
590       all_indices = apropos_list;
591     }
592   return (all_indices);
593 }
594 
595 #define APROPOS_NONE \
596    N_("No available info files have `%s' in their indices.")
597 
598 void
599 info_apropos (char *string)
600 {
601   REFERENCE **apropos_list;
602 
603   apropos_list = apropos_in_all_indices (string, 0);
604 
605   if (!apropos_list)
606     info_error ((char *) _(APROPOS_NONE), string, NULL);
607   else
608     {
609       register int i;
610       REFERENCE *entry;
611 
612       for (i = 0; (entry = apropos_list[i]); i++)
613         fprintf (stdout, "\"(%s)%s\" -- %s\n",
614                  entry->filename, entry->nodename, entry->label);
615     }
616   info_free_references (apropos_list);
617 }
618 
619 static char *apropos_list_nodename = "*Apropos*";
620 
621 DECLARE_INFO_COMMAND (info_index_apropos,
622    _("Grovel all known info file's indices for a string and build a menu"))
623 {
624   char *line;
625 
626   line = info_read_in_echo_area (window, (char *) _("Index apropos: "));
627 
628   window = active_window;
629 
630   /* User aborted? */
631   if (!line)
632     {
633       info_abort_key (window, 1, 1);
634       return;
635     }
636 
637   /* User typed something? */
638   if (*line)
639     {
640       REFERENCE **apropos_list;
641       NODE *apropos_node;
642 
643       apropos_list = apropos_in_all_indices (line, 1);
644 
645       if (!apropos_list)
646         info_error ((char *) _(APROPOS_NONE), line, NULL);
647       else
648         {
649           register int i;
650           char *line_buffer;
651 
652           initialize_message_buffer ();
653           printf_to_message_buffer
654             ((char *) _("\n* Menu: Nodes whose indices contain `%s':\n"),
655              line, NULL, NULL);
656           line_buffer = (char *)xmalloc (500);
657 
658           for (i = 0; apropos_list[i]; i++)
659             {
660               int len;
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);
670             }
671           free (line_buffer);
672         }
673 
674       apropos_node = message_buffer_to_node ();
675       add_gcable_pointer (apropos_node->contents);
676       name_internal_node (apropos_node, apropos_list_nodename);
677 
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
680          of it here. */
681       apropos_node->flags &= ~N_IsInternal;
682 
683       /* Find/Create a window to contain this node. */
684       {
685         WINDOW *new;
686         NODE *node;
687 
688         set_remembered_pagetop_and_point (window);
689 
690         /* If a window is visible and showing an apropos list already,
691            re-use it. */
692         for (new = windows; new; new = new->next)
693           {
694             node = new->node;
695 
696             if (internal_info_node_p (node) &&
697                 (strcmp (node->nodename, apropos_list_nodename) == 0))
698               break;
699           }
700 
701         /* If we couldn't find an existing window, try to use the next window
702            in the chain. */
703         if (!new && window->next)
704           new = window->next;
705 
706         /* If we still don't have a window, make a new one to contain
707            the list. */
708         if (!new)
709           {
710             WINDOW *old_active;
711 
712             old_active = active_window;
713             active_window = window;
714             new = window_make_window ((NODE *)NULL);
715             active_window = old_active;
716           }
717 
718         /* If we couldn't make a new window, use this one. */
719         if (!new)
720           new = window;
721 
722         /* Lines do not wrap in this window. */
723         new->flags |= W_NoWrap;
724 
725         window_set_node_of_window (new, apropos_node);
726         remember_window_and_node (new, apropos_node);
727         active_window = new;
728       }
729       info_free_references (apropos_list);
730     }
731   free (line);
732 
733   if (!info_error_was_printed)
734     window_clear_echo_area ();
735 }
736