Home | History | Annotate | Line # | Download | only in info
      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 (at) ai.mit.edu). */
     24 
     25 #include "info.h"
     26 #include "indices.h"
     27 
     28 /* User-visible variable controls the output of info-index-next. */
     29 int show_index_match = 1;
     30 
     31 /* In the Info sense, an index is a menu.  This variable holds the last
     32    parsed index. */
     33 static REFERENCE **index_index = (REFERENCE **)NULL;
     34 
     35 /* The offset of the most recently selected index element. */
     36 static int index_offset = 0;
     37 
     38 /* Variable which holds the last string searched for. */
     39 static char *index_search = (char *)NULL;
     40 
     41 /* A couple of "globals" describing where the initial index was found. */
     42 static char *initial_index_filename = (char *)NULL;
     43 static char *initial_index_nodename = (char *)NULL;
     44 
     45 /* A structure associating index names with index offset ranges. */
     46 typedef 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. */
     53 static INDEX_NAME_ASSOC **index_nodenames = (INDEX_NAME_ASSOC **)NULL;
     54 static int index_nodenames_index = 0;
     55 static 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. */
     59 static void
     60 add_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. */
     90 REFERENCE **
     91 info_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 
    100 REFERENCE **
    101 info_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 
    173 DECLARE_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.  */
    181 void
    182 do_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 
    281 int
    282 index_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 
    326 DECLARE_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 /* **************************************************************** */
    462 /*                                                                  */
    463 /*                 Info APROPOS: Search every known index.          */
    464 /*                                                                  */
    465 /* **************************************************************** */
    466 
    467 /* For every menu item in DIR, search the indices of that file for
    468    SEARCH_STRING. */
    469 REFERENCE **
    470 apropos_in_all_indices (char *search_string, int inform)
    471 {
    472   register int i, dir_index;
    473   REFERENCE **all_indices = (REFERENCE **)NULL;
    474   REFERENCE **dir_menu = (REFERENCE **)NULL;
    475   NODE *dir_node;
    476 
    477   dir_node = info_get_node ("dir", "Top");
    478   if (dir_node)
    479     dir_menu = info_menu_of_node (dir_node);
    480 
    481   if (!dir_menu)
    482     return NULL;
    483 
    484   /* For every menu item in DIR, get the associated node's file buffer and
    485      read the indices of that file buffer.  Gather all of the indices into
    486      one large one. */
    487   for (dir_index = 0; dir_menu[dir_index]; dir_index++)
    488     {
    489       REFERENCE **this_index, *this_item;
    490       NODE *this_node;
    491       FILE_BUFFER *this_fb;
    492       int dir_node_duplicated = 0;
    493 
    494       this_item = dir_menu[dir_index];
    495 
    496       if (!this_item->filename)
    497         {
    498 	  dir_node_duplicated = 1;
    499           if (dir_node->parent)
    500             this_item->filename = xstrdup (dir_node->parent);
    501           else
    502             this_item->filename = xstrdup (dir_node->filename);
    503         }
    504 
    505       /* Find this node.  If we cannot find it, try using the label of the
    506          entry as a file (i.e., "(LABEL)Top"). */
    507       this_node = info_get_node (this_item->filename, this_item->nodename);
    508 
    509       if (!this_node && this_item->nodename &&
    510           (strcmp (this_item->label, this_item->nodename) == 0))
    511         this_node = info_get_node (this_item->label, "Top");
    512 
    513       if (!this_node)
    514 	{
    515 	  if (dir_node_duplicated)
    516 	    free (this_item->filename);
    517 	  continue;
    518 	}
    519 
    520       /* Get the file buffer associated with this node. */
    521       {
    522         char *files_name;
    523 
    524         files_name = this_node->parent;
    525         if (!files_name)
    526           files_name = this_node->filename;
    527 
    528         this_fb = info_find_file (files_name);
    529 
    530 	/* If we already scanned this file, don't do that again.
    531 	   In addition to being faster, this also avoids having
    532 	   multiple identical entries in the *Apropos* menu.  */
    533 	for (i = 0; i < dir_index; i++)
    534 	  if (FILENAME_CMP (this_fb->filename, dir_menu[i]->filename) == 0)
    535 	    break;
    536 	if (i < dir_index)
    537 	  {
    538 	    if (dir_node_duplicated)
    539 	      free (this_item->filename);
    540 	    continue;
    541 	  }
    542 
    543         if (this_fb && inform)
    544           message_in_echo_area ((char *) _("Scanning indices of `%s'..."),
    545               files_name, NULL);
    546 
    547         this_index = info_indices_of_file_buffer (this_fb);
    548         free (this_node);
    549 
    550         if (this_fb && inform)
    551           unmessage_in_echo_area ();
    552       }
    553 
    554       if (this_index)
    555         {
    556           /* Remember the filename which contains this set of references. */
    557           for (i = 0; this_index && this_index[i]; i++)
    558             if (!this_index[i]->filename)
    559               this_index[i]->filename = xstrdup (this_fb->filename);
    560 
    561           /* Concatenate with the other indices.  */
    562           all_indices = info_concatenate_references (all_indices, this_index);
    563         }
    564     }
    565 
    566   info_free_references (dir_menu);
    567 
    568   /* Build a list of the references which contain SEARCH_STRING. */
    569   if (all_indices)
    570     {
    571       REFERENCE *entry, **apropos_list = (REFERENCE **)NULL;
    572       int apropos_list_index = 0;
    573       int apropos_list_slots = 0;
    574 
    575       for (i = 0; (entry = all_indices[i]); i++)
    576         {
    577           if (string_in_line (search_string, entry->label) != -1)
    578             {
    579               add_pointer_to_array
    580                 (entry, apropos_list_index, apropos_list, apropos_list_slots,
    581                  100, REFERENCE *);
    582             }
    583           else
    584             {
    585               maybe_free (entry->label);
    586               maybe_free (entry->filename);
    587               maybe_free (entry->nodename);
    588               free (entry);
    589             }
    590         }
    591 
    592       free (all_indices);
    593       all_indices = apropos_list;
    594     }
    595   return (all_indices);
    596 }
    597 
    598 #define APROPOS_NONE \
    599    N_("No available info files have `%s' in their indices.")
    600 
    601 void
    602 info_apropos (char *string)
    603 {
    604   REFERENCE **apropos_list;
    605 
    606   apropos_list = apropos_in_all_indices (string, 0);
    607 
    608   if (!apropos_list)
    609     info_error ((char *) _(APROPOS_NONE), string, NULL);
    610   else
    611     {
    612       register int i;
    613       REFERENCE *entry;
    614 
    615       for (i = 0; (entry = apropos_list[i]); i++)
    616         fprintf (stdout, "\"(%s)%s\" -- %s\n",
    617                  entry->filename, entry->nodename, entry->label);
    618     }
    619   info_free_references (apropos_list);
    620 }
    621 
    622 static char *apropos_list_nodename = "*Apropos*";
    623 
    624 DECLARE_INFO_COMMAND (info_index_apropos,
    625    _("Grovel all known info file's indices for a string and build a menu"))
    626 {
    627   char *line;
    628 
    629   line = info_read_in_echo_area (window, (char *) _("Index apropos: "));
    630 
    631   window = active_window;
    632 
    633   /* User aborted? */
    634   if (!line)
    635     {
    636       info_abort_key (window, 1, 1);
    637       return;
    638     }
    639 
    640   /* User typed something? */
    641   if (*line)
    642     {
    643       REFERENCE **apropos_list;
    644       NODE *apropos_node;
    645 
    646       apropos_list = apropos_in_all_indices (line, 1);
    647 
    648       if (!apropos_list)
    649         info_error ((char *) _(APROPOS_NONE), line, NULL);
    650       else
    651         {
    652           register int i;
    653           char *line_buffer;
    654 
    655           initialize_message_buffer ();
    656           printf_to_message_buffer
    657             ((char *) _("\n* Menu: Nodes whose indices contain `%s':\n"),
    658              line, NULL, NULL);
    659           line_buffer = (char *)xmalloc (500);
    660 
    661           for (i = 0; apropos_list[i]; i++)
    662             {
    663               int len;
    664 	      /* The label might be identical to that of another index
    665 		 entry in another Info file.  Therefore, we make the file
    666 		 name part of the menu entry, to make them all distinct.  */
    667               sprintf (line_buffer, "* %s [%s]: ",
    668 		       apropos_list[i]->label, apropos_list[i]->filename);
    669               len = pad_to (40, line_buffer);
    670               sprintf (line_buffer + len, "(%s)%s.",
    671                        apropos_list[i]->filename, apropos_list[i]->nodename);
    672               printf_to_message_buffer ("%s\n", line_buffer, NULL, NULL);
    673             }
    674           free (line_buffer);
    675         }
    676 
    677       apropos_node = message_buffer_to_node ();
    678       add_gcable_pointer (apropos_node->contents);
    679       name_internal_node (apropos_node, apropos_list_nodename);
    680 
    681       /* Even though this is an internal node, we don't want the window
    682          system to treat it specially.  So we turn off the internalness
    683          of it here. */
    684       apropos_node->flags &= ~N_IsInternal;
    685 
    686       /* Find/Create a window to contain this node. */
    687       {
    688         WINDOW *new;
    689         NODE *node;
    690 
    691         set_remembered_pagetop_and_point (window);
    692 
    693         /* If a window is visible and showing an apropos list already,
    694            re-use it. */
    695         for (new = windows; new; new = new->next)
    696           {
    697             node = new->node;
    698 
    699             if (internal_info_node_p (node) &&
    700                 (strcmp (node->nodename, apropos_list_nodename) == 0))
    701               break;
    702           }
    703 
    704         /* If we couldn't find an existing window, try to use the next window
    705            in the chain. */
    706         if (!new && window->next)
    707           new = window->next;
    708 
    709         /* If we still don't have a window, make a new one to contain
    710            the list. */
    711         if (!new)
    712           {
    713             WINDOW *old_active;
    714 
    715             old_active = active_window;
    716             active_window = window;
    717             new = window_make_window ((NODE *)NULL);
    718             active_window = old_active;
    719           }
    720 
    721         /* If we couldn't make a new window, use this one. */
    722         if (!new)
    723           new = window;
    724 
    725         /* Lines do not wrap in this window. */
    726         new->flags |= W_NoWrap;
    727 
    728         window_set_node_of_window (new, apropos_node);
    729         remember_window_and_node (new, apropos_node);
    730         active_window = new;
    731       }
    732       info_free_references (apropos_list);
    733     }
    734   free (line);
    735 
    736   if (!info_error_was_printed)
    737     window_clear_echo_area ();
    738 }
    739