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