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