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. */
27int show_index_match = 1;
28
29/* In the Info sense, an index is a menu.  This variable holds the last
30   parsed index. */
31static REFERENCE **index_index = (REFERENCE **)NULL;
32
33/* The offset of the most recently selected index element. */
34static int index_offset = 0;
35
36/* Variable which holds the last string searched for. */
37static char *index_search = (char *)NULL;
38
39/* A couple of "globals" describing where the initial index was found. */
40static char *initial_index_filename = (char *)NULL;
41static char *initial_index_nodename = (char *)NULL;
42
43/* A structure associating index names with index offset ranges. */
44typedef 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. */
51static INDEX_NAME_ASSOC **index_nodenames = (INDEX_NAME_ASSOC **)NULL;
52static int index_nodenames_index = 0;
53static 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. */
57static void
58add_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. */
88REFERENCE **
89info_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
98REFERENCE **
99info_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
171DECLARE_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.  */
179void
180do_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
279int
280index_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
324DECLARE_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
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. */
466REFERENCE **
467apropos_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
598void
599info_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
619static char *apropos_list_nodename = "*Apropos*";
620
621DECLARE_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}
736