Initial import from FreeBSD RELENG_4:
[dragonfly.git] / contrib / texinfo / info / indices.c
1 /* indices.c -- deal with an Info file index.
2    $Id: indices.c,v 1.15 2002/03/11 13:43:52 karl 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 \f
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