Back to index

tetex-bin  3.0
indices.c
Go to the documentation of this file.
00001 /* indices.c -- deal with an Info file index.
00002    $Id: indices.c,v 1.5 2004/04/11 17:56:45 karl Exp $
00003 
00004    Copyright (C) 1993, 1997, 1998, 1999, 2002, 2003, 2004 Free Software
00005    Foundation, Inc.
00006 
00007    This program is free software; you can redistribute it and/or modify
00008    it under the terms of the GNU General Public License as published by
00009    the Free Software Foundation; either version 2, or (at your option)
00010    any later version.
00011 
00012    This program is distributed in the hope that it will be useful,
00013    but WITHOUT ANY WARRANTY; without even the implied warranty of
00014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015    GNU General Public License for more details.
00016 
00017    You should have received a copy of the GNU General Public License
00018    along with this program; if not, write to the Free Software
00019    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00020 
00021    Originally written by Brian Fox (bfox@ai.mit.edu). */
00022 
00023 #include "info.h"
00024 #include "indices.h"
00025 
00026 /* User-visible variable controls the output of info-index-next. */
00027 int show_index_match = 1;
00028 
00029 /* In the Info sense, an index is a menu.  This variable holds the last
00030    parsed index. */
00031 static REFERENCE **index_index = (REFERENCE **)NULL;
00032 
00033 /* The offset of the most recently selected index element. */
00034 static int index_offset = 0;
00035 
00036 /* Variable which holds the last string searched for. */
00037 static char *index_search = (char *)NULL;
00038 
00039 /* A couple of "globals" describing where the initial index was found. */
00040 static char *initial_index_filename = (char *)NULL;
00041 static char *initial_index_nodename = (char *)NULL;
00042 
00043 /* A structure associating index names with index offset ranges. */
00044 typedef struct {
00045   char *name;                   /* The nodename of this index. */
00046   int first;                    /* The index in our list of the first entry. */
00047   int last;                     /* The index in our list of the last entry. */
00048 } INDEX_NAME_ASSOC;
00049 
00050 /* An array associating index nodenames with index offset ranges. */
00051 static INDEX_NAME_ASSOC **index_nodenames = (INDEX_NAME_ASSOC **)NULL;
00052 static int index_nodenames_index = 0;
00053 static int index_nodenames_slots = 0;
00054 
00055 /* Add the name of NODE, and the range of the associated index elements
00056    (passed in ARRAY) to index_nodenames. */
00057 static void
00058 add_index_to_index_nodenames (REFERENCE **array, NODE *node)
00059 {
00060   register int i, last;
00061   INDEX_NAME_ASSOC *assoc;
00062 
00063   for (last = 0; array[last + 1]; last++);
00064   assoc = (INDEX_NAME_ASSOC *)xmalloc (sizeof (INDEX_NAME_ASSOC));
00065   assoc->name = xstrdup (node->nodename);
00066 
00067   if (!index_nodenames_index)
00068     {
00069       assoc->first = 0;
00070       assoc->last = last;
00071     }
00072   else
00073     {
00074       for (i = 0; index_nodenames[i + 1]; i++);
00075       assoc->first = 1 + index_nodenames[i]->last;
00076       assoc->last = assoc->first + last;
00077     }
00078   add_pointer_to_array
00079     (assoc, index_nodenames_index, index_nodenames, index_nodenames_slots,
00080      10, INDEX_NAME_ASSOC *);
00081 }
00082 
00083 /* Find and return the indices of WINDOW's file.  The indices are defined
00084    as the first node in the file containing the word "Index" and any
00085    immediately following nodes whose names also contain "Index".  All such
00086    indices are concatenated and the result returned.  If WINDOW's info file
00087    doesn't have any indices, a NULL pointer is returned. */
00088 REFERENCE **
00089 info_indices_of_window (WINDOW *window)
00090 {
00091   FILE_BUFFER *fb;
00092 
00093   fb = file_buffer_of_window (window);
00094 
00095   return (info_indices_of_file_buffer (fb));
00096 }
00097 
00098 REFERENCE **
00099 info_indices_of_file_buffer (FILE_BUFFER *file_buffer)
00100 {
00101   register int i;
00102   REFERENCE **result = (REFERENCE **)NULL;
00103 
00104   /* No file buffer, no indices. */
00105   if (!file_buffer)
00106     return ((REFERENCE **)NULL);
00107 
00108   /* Reset globals describing where the index was found. */
00109   maybe_free (initial_index_filename);
00110   maybe_free (initial_index_nodename);
00111   initial_index_filename = (char *)NULL;
00112   initial_index_nodename = (char *)NULL;
00113 
00114   if (index_nodenames)
00115     {
00116       for (i = 0; index_nodenames[i]; i++)
00117         {
00118           free (index_nodenames[i]->name);
00119           free (index_nodenames[i]);
00120         }
00121 
00122       index_nodenames_index = 0;
00123       index_nodenames[0] = (INDEX_NAME_ASSOC *)NULL;
00124     }
00125 
00126   /* Grovel the names of the nodes found in this file. */
00127   if (file_buffer->tags)
00128     {
00129       TAG *tag;
00130 
00131       for (i = 0; (tag = file_buffer->tags[i]); i++)
00132         {
00133           if (string_in_line ("Index", tag->nodename) != -1)
00134             {
00135               NODE *node;
00136               REFERENCE **menu;
00137 
00138               /* Found one.  Get its menu. */
00139               node = info_get_node (tag->filename, tag->nodename);
00140               if (!node)
00141                 continue;
00142 
00143               /* Remember the filename and nodename of this index. */
00144               initial_index_filename = xstrdup (file_buffer->filename);
00145               initial_index_nodename = xstrdup (tag->nodename);
00146 
00147               menu = info_menu_of_node (node);
00148 
00149               /* If we have a menu, add this index's nodename and range
00150                  to our list of index_nodenames. */
00151               if (menu)
00152                 {
00153                   add_index_to_index_nodenames (menu, node);
00154 
00155                   /* Concatenate the references found so far. */
00156                   result = info_concatenate_references (result, menu);
00157                 }
00158               free (node);
00159             }
00160         }
00161     }
00162 
00163   /* If there is a result, clean it up so that every entry has a filename. */
00164   for (i = 0; result && result[i]; i++)
00165     if (!result[i]->filename)
00166       result[i]->filename = xstrdup (file_buffer->filename);
00167 
00168   return (result);
00169 }
00170 
00171 DECLARE_INFO_COMMAND (info_index_search,
00172    _("Look up a string in the index for this file"))
00173 {
00174   do_info_index_search (window, count, 0);
00175 }
00176 
00177 /* Look up SEARCH_STRING in the index for this file.  If SEARCH_STRING
00178    is NULL, prompt user for input.  */ 
00179 void
00180 do_info_index_search (WINDOW *window, int count, char *search_string)
00181 {
00182   FILE_BUFFER *fb;
00183   char *line;
00184 
00185   /* Reset the index offset, since this is not the info-index-next command. */
00186   index_offset = 0;
00187 
00188   /* The user is selecting a new search string, so flush the old one. */
00189   maybe_free (index_search);
00190   index_search = (char *)NULL;
00191 
00192   /* If this window's file is not the same as the one that we last built an
00193      index for, build and remember an index now. */
00194   fb = file_buffer_of_window (window);
00195   if (!initial_index_filename ||
00196       (FILENAME_CMP (initial_index_filename, fb->filename) != 0))
00197     {
00198       info_free_references (index_index);
00199       window_message_in_echo_area ((char *) _("Finding index entries..."),
00200           NULL, NULL);
00201       index_index = info_indices_of_file_buffer (fb);
00202     }
00203 
00204   /* If there is no index, quit now. */
00205   if (!index_index)
00206     {
00207       info_error ((char *) _("No indices found."), NULL, NULL);
00208       return;
00209     }
00210 
00211   /* Okay, there is an index.  Look for SEARCH_STRING, or, if it is
00212      empty, prompt for one.  */
00213   if (search_string && *search_string)
00214     line = xstrdup (search_string);
00215   else
00216     {
00217       line = info_read_maybe_completing (window, (char *) _("Index entry: "),
00218                                          index_index);
00219       window = active_window;
00220 
00221       /* User aborted? */
00222       if (!line)
00223         {
00224           info_abort_key (active_window, 1, 0);
00225           return;
00226         }
00227 
00228       /* Empty line means move to the Index node. */
00229       if (!*line)
00230         {
00231           free (line);
00232 
00233           if (initial_index_filename && initial_index_nodename)
00234             {
00235               NODE *node;
00236 
00237               node = info_get_node (initial_index_filename,
00238                                     initial_index_nodename);
00239               set_remembered_pagetop_and_point (window);
00240               window_set_node_of_window (window, node);
00241               remember_window_and_node (window, node);
00242               window_clear_echo_area ();
00243               return;
00244             }
00245         }
00246     }
00247 
00248   /* The user typed either a completed index label, or a partial string.
00249      Find an exact match, or, failing that, the first index entry containing
00250      the partial string.  So, we just call info_next_index_match () with minor
00251      manipulation of INDEX_OFFSET. */
00252   {
00253     int old_offset;
00254 
00255     /* Start the search right after/before this index. */
00256     if (count < 0)
00257       {
00258         register int i;
00259         for (i = 0; index_index[i]; i++);
00260         index_offset = i;
00261       }
00262     else
00263       index_offset = -1;
00264 
00265     old_offset = index_offset;
00266 
00267     /* The "last" string searched for is this one. */
00268     index_search = line;
00269 
00270     /* Find it, or error. */
00271     info_next_index_match (window, count, 0);
00272 
00273     /* If the search failed, return the index offset to where it belongs. */
00274     if (index_offset == old_offset)
00275       index_offset = 0;
00276   }
00277 }
00278 
00279 int
00280 index_entry_exists (WINDOW *window, char *string)
00281 {
00282   register int i;
00283   FILE_BUFFER *fb;
00284 
00285   /* If there is no previous search string, the user hasn't built an index
00286      yet. */
00287   if (!string)
00288     return 0;
00289 
00290   fb = file_buffer_of_window (window);
00291   if (!initial_index_filename
00292       || (FILENAME_CMP (initial_index_filename, fb->filename) != 0))
00293     {
00294       info_free_references (index_index);
00295       index_index = info_indices_of_file_buffer (fb);
00296     }
00297 
00298   /* If there is no index, that is an error. */
00299   if (!index_index)
00300     return 0;
00301 
00302   for (i = 0; (i > -1) && (index_index[i]); i++)
00303     if (strcmp (string, index_index[i]->label) == 0)
00304       break;
00305 
00306   /* If that failed, look for the next substring match. */
00307   if ((i < 0) || (!index_index[i]))
00308     {
00309       for (i = 0; (i > -1) && (index_index[i]); i++)
00310         if (string_in_line (string, index_index[i]->label) != -1)
00311           break;
00312 
00313       if ((i > -1) && (index_index[i]))
00314         string_in_line (string, index_index[i]->label);
00315     }
00316 
00317   /* If that failed, return 0. */
00318   if ((i < 0) || (!index_index[i]))
00319     return 0;
00320 
00321   return 1;
00322 }
00323 
00324 DECLARE_INFO_COMMAND (info_next_index_match,
00325  _("Go to the next matching index item from the last `\\[index-search]' command"))
00326 {
00327   register int i;
00328   int partial, dir;
00329   NODE *node;
00330 
00331   /* If there is no previous search string, the user hasn't built an index
00332      yet. */
00333   if (!index_search)
00334     {
00335       info_error ((char *) _("No previous index search string."), NULL, NULL);
00336       return;
00337     }
00338 
00339   /* If there is no index, that is an error. */
00340   if (!index_index)
00341     {
00342       info_error ((char *) _("No index entries."), NULL, NULL);
00343       return;
00344     }
00345 
00346   /* The direction of this search is controlled by the value of the
00347      numeric argument. */
00348   if (count < 0)
00349     dir = -1;
00350   else
00351     dir = 1;
00352 
00353   /* Search for the next occurence of index_search.  First try to find
00354      an exact match. */
00355   partial = 0;
00356 
00357   for (i = index_offset + dir; (i > -1) && (index_index[i]); i += dir)
00358     if (strcmp (index_search, index_index[i]->label) == 0)
00359       break;
00360 
00361   /* If that failed, look for the next substring match. */
00362   if ((i < 0) || (!index_index[i]))
00363     {
00364       for (i = index_offset + dir; (i > -1) && (index_index[i]); i += dir)
00365         if (string_in_line (index_search, index_index[i]->label) != -1)
00366           break;
00367 
00368       if ((i > -1) && (index_index[i]))
00369         partial = string_in_line (index_search, index_index[i]->label);
00370     }
00371 
00372   /* If that failed, print an error. */
00373   if ((i < 0) || (!index_index[i]))
00374     {
00375       info_error ((char *) _("No %sindex entries containing `%s'."),
00376                   index_offset > 0 ? (char *) _("more ") : "", index_search);
00377       return;
00378     }
00379 
00380   /* Okay, we found the next one.  Move the offset to the current entry. */
00381   index_offset = i;
00382 
00383   /* Report to the user on what we have found. */
00384   {
00385     register int j;
00386     const char *name = _("CAN'T SEE THIS");
00387     char *match;
00388 
00389     for (j = 0; index_nodenames[j]; j++)
00390       {
00391         if ((i >= index_nodenames[j]->first) &&
00392             (i <= index_nodenames[j]->last))
00393           {
00394             name = index_nodenames[j]->name;
00395             break;
00396           }
00397       }
00398 
00399     /* If we had a partial match, indicate to the user which part of the
00400        string matched. */
00401     match = xstrdup (index_index[i]->label);
00402 
00403     if (partial && show_index_match)
00404       {
00405         int k, ls, start, upper;
00406 
00407         ls = strlen (index_search);
00408         start = partial - ls;
00409         upper = isupper (match[start]) ? 1 : 0;
00410 
00411         for (k = 0; k < ls; k++)
00412           if (upper)
00413             match[k + start] = info_tolower (match[k + start]);
00414           else
00415             match[k + start] = info_toupper (match[k + start]);
00416       }
00417 
00418     {
00419       char *format;
00420 
00421       format = replace_in_documentation
00422         ((char *) _("Found `%s' in %s. (`\\[next-index-match]' tries to find next.)"),
00423          0);
00424 
00425       window_message_in_echo_area (format, match, (char *) name);
00426     }
00427 
00428     free (match);
00429   }
00430 
00431   /* Select the node corresponding to this index entry. */
00432   node = info_get_node (index_index[i]->filename, index_index[i]->nodename);
00433 
00434   if (!node)
00435     {
00436       info_error ((char *) msg_cant_file_node,
00437                   index_index[i]->filename, index_index[i]->nodename);
00438       return;
00439     }
00440 
00441   info_set_node_of_window (1, window, node);
00442 
00443   /* Try to find an occurence of LABEL in this node. */
00444   {
00445     long start, loc;
00446 
00447     start = window->line_starts[1] - window->node->contents;
00448     loc = info_target_search_node (node, index_index[i]->label, start);
00449 
00450     if (loc != -1)
00451       {
00452         window->point = loc;
00453         window_adjust_pagetop (window);
00454       }
00455   }
00456 }
00457 
00458 /* **************************************************************** */
00459 /*                                                                  */
00460 /*                 Info APROPOS: Search every known index.          */
00461 /*                                                                  */
00462 /* **************************************************************** */
00463 
00464 /* For every menu item in DIR, search the indices of that file for
00465    SEARCH_STRING. */
00466 REFERENCE **
00467 apropos_in_all_indices (char *search_string, int inform)
00468 {
00469   register int i, dir_index;
00470   REFERENCE **all_indices = (REFERENCE **)NULL;
00471   REFERENCE **dir_menu = (REFERENCE **)NULL;
00472   NODE *dir_node;
00473 
00474   dir_node = info_get_node ("dir", "Top");
00475   if (dir_node)
00476     dir_menu = info_menu_of_node (dir_node);
00477 
00478   if (!dir_menu)
00479     return NULL;
00480 
00481   /* For every menu item in DIR, get the associated node's file buffer and
00482      read the indices of that file buffer.  Gather all of the indices into
00483      one large one. */
00484   for (dir_index = 0; dir_menu[dir_index]; dir_index++)
00485     {
00486       REFERENCE **this_index, *this_item;
00487       NODE *this_node;
00488       FILE_BUFFER *this_fb;
00489       int dir_node_duplicated = 0;
00490 
00491       this_item = dir_menu[dir_index];
00492 
00493       if (!this_item->filename)
00494         {
00495          dir_node_duplicated = 1;
00496           if (dir_node->parent)
00497             this_item->filename = xstrdup (dir_node->parent);
00498           else
00499             this_item->filename = xstrdup (dir_node->filename);
00500         }
00501 
00502       /* Find this node.  If we cannot find it, try using the label of the
00503          entry as a file (i.e., "(LABEL)Top"). */
00504       this_node = info_get_node (this_item->filename, this_item->nodename);
00505 
00506       if (!this_node && this_item->nodename &&
00507           (strcmp (this_item->label, this_item->nodename) == 0))
00508         this_node = info_get_node (this_item->label, "Top");
00509 
00510       if (!this_node)
00511        {
00512          if (dir_node_duplicated)
00513            free (this_item->filename);
00514          continue;
00515        }
00516 
00517       /* Get the file buffer associated with this node. */
00518       {
00519         char *files_name;
00520 
00521         files_name = this_node->parent;
00522         if (!files_name)
00523           files_name = this_node->filename;
00524 
00525         this_fb = info_find_file (files_name);
00526 
00527        /* If we already scanned this file, don't do that again.
00528           In addition to being faster, this also avoids having
00529           multiple identical entries in the *Apropos* menu.  */
00530        for (i = 0; i < dir_index; i++)
00531          if (FILENAME_CMP (this_fb->filename, dir_menu[i]->filename) == 0)
00532            break;
00533        if (i < dir_index)
00534          {
00535            if (dir_node_duplicated)
00536              free (this_item->filename);
00537            continue;
00538          }
00539 
00540         if (this_fb && inform)
00541           message_in_echo_area ((char *) _("Scanning indices of `%s'..."),
00542               files_name, NULL);
00543 
00544         this_index = info_indices_of_file_buffer (this_fb);
00545         free (this_node);
00546 
00547         if (this_fb && inform)
00548           unmessage_in_echo_area ();
00549       }
00550 
00551       if (this_index)
00552         {
00553           /* Remember the filename which contains this set of references. */
00554           for (i = 0; this_index && this_index[i]; i++)
00555             if (!this_index[i]->filename)
00556               this_index[i]->filename = xstrdup (this_fb->filename);
00557 
00558           /* Concatenate with the other indices.  */
00559           all_indices = info_concatenate_references (all_indices, this_index);
00560         }
00561     }
00562 
00563   info_free_references (dir_menu);
00564 
00565   /* Build a list of the references which contain SEARCH_STRING. */
00566   if (all_indices)
00567     {
00568       REFERENCE *entry, **apropos_list = (REFERENCE **)NULL;
00569       int apropos_list_index = 0;
00570       int apropos_list_slots = 0;
00571 
00572       for (i = 0; (entry = all_indices[i]); i++)
00573         {
00574           if (string_in_line (search_string, entry->label) != -1)
00575             {
00576               add_pointer_to_array
00577                 (entry, apropos_list_index, apropos_list, apropos_list_slots,
00578                  100, REFERENCE *);
00579             }
00580           else
00581             {
00582               maybe_free (entry->label);
00583               maybe_free (entry->filename);
00584               maybe_free (entry->nodename);
00585               free (entry);
00586             }
00587         }
00588 
00589       free (all_indices);
00590       all_indices = apropos_list;
00591     }
00592   return (all_indices);
00593 }
00594 
00595 #define APROPOS_NONE \
00596    N_("No available info files have `%s' in their indices.")
00597 
00598 void
00599 info_apropos (char *string)
00600 {
00601   REFERENCE **apropos_list;
00602 
00603   apropos_list = apropos_in_all_indices (string, 0);
00604 
00605   if (!apropos_list)
00606     info_error ((char *) _(APROPOS_NONE), string, NULL);
00607   else
00608     {
00609       register int i;
00610       REFERENCE *entry;
00611 
00612       for (i = 0; (entry = apropos_list[i]); i++)
00613         fprintf (stdout, "\"(%s)%s\" -- %s\n",
00614                  entry->filename, entry->nodename, entry->label);
00615     }
00616   info_free_references (apropos_list);
00617 }
00618 
00619 static char *apropos_list_nodename = "*Apropos*";
00620 
00621 DECLARE_INFO_COMMAND (info_index_apropos,
00622    _("Grovel all known info file's indices for a string and build a menu"))
00623 {
00624   char *line;
00625 
00626   line = info_read_in_echo_area (window, (char *) _("Index apropos: "));
00627 
00628   window = active_window;
00629 
00630   /* User aborted? */
00631   if (!line)
00632     {
00633       info_abort_key (window, 1, 1);
00634       return;
00635     }
00636 
00637   /* User typed something? */
00638   if (*line)
00639     {
00640       REFERENCE **apropos_list;
00641       NODE *apropos_node;
00642 
00643       apropos_list = apropos_in_all_indices (line, 1);
00644 
00645       if (!apropos_list)
00646         info_error ((char *) _(APROPOS_NONE), line, NULL);
00647       else
00648         {
00649           register int i;
00650           char *line_buffer;
00651 
00652           initialize_message_buffer ();
00653           printf_to_message_buffer
00654             ((char *) _("\n* Menu: Nodes whose indices contain `%s':\n"),
00655              line, NULL, NULL);
00656           line_buffer = (char *)xmalloc (500);
00657 
00658           for (i = 0; apropos_list[i]; i++)
00659             {
00660               int len;
00661              /* The label might be identical to that of another index
00662                entry in another Info file.  Therefore, we make the file
00663                name part of the menu entry, to make them all distinct.  */
00664               sprintf (line_buffer, "* %s [%s]: ",
00665                      apropos_list[i]->label, apropos_list[i]->filename);
00666               len = pad_to (40, line_buffer);
00667               sprintf (line_buffer + len, "(%s)%s.",
00668                        apropos_list[i]->filename, apropos_list[i]->nodename);
00669               printf_to_message_buffer ("%s\n", line_buffer, NULL, NULL);
00670             }
00671           free (line_buffer);
00672         }
00673 
00674       apropos_node = message_buffer_to_node ();
00675       add_gcable_pointer (apropos_node->contents);
00676       name_internal_node (apropos_node, apropos_list_nodename);
00677 
00678       /* Even though this is an internal node, we don't want the window
00679          system to treat it specially.  So we turn off the internalness
00680          of it here. */
00681       apropos_node->flags &= ~N_IsInternal;
00682 
00683       /* Find/Create a window to contain this node. */
00684       {
00685         WINDOW *new;
00686         NODE *node;
00687 
00688         set_remembered_pagetop_and_point (window);
00689 
00690         /* If a window is visible and showing an apropos list already,
00691            re-use it. */
00692         for (new = windows; new; new = new->next)
00693           {
00694             node = new->node;
00695 
00696             if (internal_info_node_p (node) &&
00697                 (strcmp (node->nodename, apropos_list_nodename) == 0))
00698               break;
00699           }
00700 
00701         /* If we couldn't find an existing window, try to use the next window
00702            in the chain. */
00703         if (!new && window->next)
00704           new = window->next;
00705 
00706         /* If we still don't have a window, make a new one to contain
00707            the list. */
00708         if (!new)
00709           {
00710             WINDOW *old_active;
00711 
00712             old_active = active_window;
00713             active_window = window;
00714             new = window_make_window ((NODE *)NULL);
00715             active_window = old_active;
00716           }
00717 
00718         /* If we couldn't make a new window, use this one. */
00719         if (!new)
00720           new = window;
00721 
00722         /* Lines do not wrap in this window. */
00723         new->flags |= W_NoWrap;
00724 
00725         window_set_node_of_window (new, apropos_node);
00726         remember_window_and_node (new, apropos_node);
00727         active_window = new;
00728       }
00729       info_free_references (apropos_list);
00730     }
00731   free (line);
00732 
00733   if (!info_error_was_printed)
00734     window_clear_echo_area ();
00735 }