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