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