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