This commit was generated by cvs2svn to compensate for changes in r18765,
[platform/upstream/gcc.git] / texinfo / info / indices.c
1 /* indices.c -- Commands for dealing with an Info file Index.
2    $Id: indices.c,v 1.6 1997/07/24 21:25:53 karl Exp $
3
4    Copyright (C) 1993, 97 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]; 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       (strcmp (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       || (strcmp (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 (CANT_FILE_NODE,
443                   index_index[i]->filename, index_index[i]->nodename);
444       return;
445     }
446
447   set_remembered_pagetop_and_point (window);
448   window_set_node_of_window (window, node);
449   remember_window_and_node (window, node);
450
451
452   /* Try to find an occurence of LABEL in this node. */
453   {
454     long start, loc;
455
456     start = window->line_starts[1] - window->node->contents;
457     loc = info_target_search_node (node, index_index[i]->label, start);
458
459     if (loc != -1)
460       {
461         window->point = loc;
462         window_adjust_pagetop (window);
463       }
464   }
465 }
466 \f
467 /* **************************************************************** */
468 /*                                                                  */
469 /*                 Info APROPOS: Search every known index.          */
470 /*                                                                  */
471 /* **************************************************************** */
472
473 /* For every menu item in DIR, search the indices of that file for
474    SEARCH_STRING. */
475 REFERENCE **
476 apropos_in_all_indices (search_string, inform)
477      char *search_string;
478      int inform;
479 {
480   register int i, dir_index;
481   REFERENCE **all_indices = (REFERENCE **)NULL;
482   REFERENCE **dir_menu = (REFERENCE **)NULL;
483   NODE *dir_node;
484
485   dir_node = info_get_node ("dir", "Top");
486   if (dir_node)
487     dir_menu = info_menu_of_node (dir_node);
488
489   if (!dir_menu)
490     return NULL;
491
492   /* For every menu item in DIR, get the associated node's file buffer and
493      read the indices of that file buffer.  Gather all of the indices into
494      one large one. */
495   for (dir_index = 0; dir_menu[dir_index]; dir_index++)
496     {
497       REFERENCE **this_index, *this_item;
498       NODE *this_node;
499       FILE_BUFFER *this_fb;
500
501       this_item = dir_menu[dir_index];
502
503       if (!this_item->filename)
504         {
505           if (dir_node->parent)
506             this_item->filename = xstrdup (dir_node->parent);
507           else
508             this_item->filename = xstrdup (dir_node->filename);
509         }
510
511       /* Find this node.  If we cannot find it, try using the label of the
512          entry as a file (i.e., "(LABEL)Top"). */
513       this_node = info_get_node (this_item->filename, this_item->nodename);
514
515       if (!this_node && this_item->nodename &&
516           (strcmp (this_item->label, this_item->nodename) == 0))
517         this_node = info_get_node (this_item->label, "Top");
518
519       if (!this_node)
520         continue;
521
522       /* Get the file buffer associated with this node. */
523       {
524         char *files_name;
525
526         files_name = this_node->parent;
527         if (!files_name)
528           files_name = this_node->filename;
529
530         this_fb = info_find_file (files_name);
531
532         if (this_fb && inform)
533           message_in_echo_area (_("Scanning indices of \"%s\"..."), files_name);
534
535         this_index = info_indices_of_file_buffer (this_fb);
536         free (this_node);
537
538         if (this_fb && inform)
539           unmessage_in_echo_area ();
540       }
541
542       if (this_index)
543         {
544           /* Remember the filename which contains this set of references. */
545           for (i = 0; this_index && this_index[i]; i++)
546             if (!this_index[i]->filename)
547               this_index[i]->filename = xstrdup (this_fb->filename);
548
549           /* Concatenate with the other indices.  */
550           all_indices = info_concatenate_references (all_indices, this_index);
551         }
552     }
553
554   info_free_references (dir_menu);
555
556   /* Build a list of the references which contain SEARCH_STRING. */
557   if (all_indices)
558     {
559       REFERENCE *entry, **apropos_list = (REFERENCE **)NULL;
560       int apropos_list_index = 0;
561       int apropos_list_slots = 0;
562
563       for (i = 0; (entry = all_indices[i]); i++)
564         {
565           if (string_in_line (search_string, entry->label) != -1)
566             {
567               add_pointer_to_array
568                 (entry, apropos_list_index, apropos_list, apropos_list_slots,
569                  100, REFERENCE *);
570             }
571           else
572             {
573               maybe_free (entry->label);
574               maybe_free (entry->filename);
575               maybe_free (entry->nodename);
576               free (entry);
577             }
578         }
579
580       free (all_indices);
581       all_indices = apropos_list;
582     }
583   return (all_indices);
584 }
585
586 #define APROPOS_NONE \
587    _("No available info files reference \"%s\" in their indices.")
588
589 void
590 info_apropos (string)
591      char *string;
592 {
593   REFERENCE **apropos_list;
594
595   apropos_list = apropos_in_all_indices (string, 0);
596
597   if (!apropos_list)
598     {
599       info_error (APROPOS_NONE, string);
600     }
601   else
602     {
603       register int i;
604       REFERENCE *entry;
605
606       for (i = 0; (entry = apropos_list[i]); i++)
607         fprintf (stderr, "\"(%s)%s\" -- %s\n",
608                  entry->filename, entry->nodename, entry->label);
609     }
610   info_free_references (apropos_list);
611 }
612
613 static char *apropos_list_nodename = "*Apropos*";
614
615 DECLARE_INFO_COMMAND (info_index_apropos,
616    _("Grovel all known info file's indices for a string and build a menu"))
617 {
618   char *line;
619
620   line = info_read_in_echo_area (window, _("Index apropos: "));
621
622   window = active_window;
623
624   /* User aborted? */
625   if (!line)
626     {
627       info_abort_key (window, 1, 1);
628       return;
629     }
630
631   /* User typed something? */
632   if (*line)
633     {
634       REFERENCE **apropos_list;
635       NODE *apropos_node;
636
637       apropos_list = apropos_in_all_indices (line, 1);
638
639       if (!apropos_list)
640         {
641           info_error (APROPOS_NONE, line);
642         }
643       else
644         {
645           register int i;
646           char *line_buffer;
647
648           initialize_message_buffer ();
649           printf_to_message_buffer
650             (_("\n* Menu: Nodes whoses indices contain \"%s\":\n"), line);
651           line_buffer = (char *)xmalloc (500);
652
653           for (i = 0; apropos_list[i]; i++)
654             {
655               int len;
656               sprintf (line_buffer, "* (%s)%s::",
657                        apropos_list[i]->filename, apropos_list[i]->nodename);
658               len = pad_to (36, line_buffer);
659               sprintf (line_buffer + len, "%s", apropos_list[i]->label);
660               printf_to_message_buffer ("%s\n", line_buffer);
661             }
662           free (line_buffer);
663         }
664
665       apropos_node = message_buffer_to_node ();
666       add_gcable_pointer (apropos_node->contents);
667       name_internal_node (apropos_node, apropos_list_nodename);
668
669       /* Even though this is an internal node, we don't want the window
670          system to treat it specially.  So we turn off the internalness
671          of it here. */
672       apropos_node->flags &= ~N_IsInternal;
673
674       /* Find/Create a window to contain this node. */
675       {
676         WINDOW *new;
677         NODE *node;
678
679         set_remembered_pagetop_and_point (window);
680
681         /* If a window is visible and showing an apropos list already,
682            re-use it. */
683         for (new = windows; new; new = new->next)
684           {
685             node = new->node;
686
687             if (internal_info_node_p (node) &&
688                 (strcmp (node->nodename, apropos_list_nodename) == 0))
689               break;
690           }
691
692         /* If we couldn't find an existing window, try to use the next window
693            in the chain. */
694         if (!new && window->next)
695           new = window->next;
696
697         /* If we still don't have a window, make a new one to contain
698            the list. */
699         if (!new)
700           {
701             WINDOW *old_active;
702
703             old_active = active_window;
704             active_window = window;
705             new = window_make_window ((NODE *)NULL);
706             active_window = old_active;
707           }
708
709         /* If we couldn't make a new window, use this one. */
710         if (!new)
711           new = window;
712
713         /* Lines do not wrap in this window. */
714         new->flags |= W_NoWrap;
715
716         window_set_node_of_window (new, apropos_node);
717         remember_window_and_node (new, apropos_node);
718         active_window = new;
719       }
720       info_free_references (apropos_list);
721     }
722   free (line);
723
724   if (!info_error_was_printed)
725     window_clear_echo_area ();
726 }
727