(root)/
texinfo-7.1/
info/
indices.c
       1  /* indices.c -- deal with an Info file index.
       2  
       3     Copyright 1993-2023 Free Software Foundation, Inc.
       4  
       5     This program is free software: you can redistribute it and/or modify
       6     it under the terms of the GNU General Public License as published by
       7     the Free Software Foundation, either version 3 of the License, or
       8     (at your option) any later version.
       9  
      10     This program is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13     GNU General Public License for more details.
      14  
      15     You should have received a copy of the GNU General Public License
      16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
      17  
      18     Originally written by Brian Fox. */
      19  
      20  #include "info.h"
      21  #include "scan.h"
      22  #include "util.h"
      23  #include "session.h"
      24  #include "echo-area.h"
      25  #include "indices.h"
      26  #include "variables.h"
      27  
      28  /* User-visible variable controls the output of info-index-next. */
      29  int show_index_match = 1;
      30  
      31  /* The combined indices of the last file processed by
      32     info_indices_of_file_buffer. */
      33  static REFERENCE **index_index = NULL;
      34  
      35  /* The offset of the most recently selected index element. */
      36  static int index_offset = -1;
      37  
      38  /* Whether we are doing initial index search. */
      39  static int index_initial = 0;
      40  
      41  /* Whether we are doing partial index search */
      42  static int index_partial = 0;
      43  
      44  /* Variable which holds the last string searched for. */
      45  static char *index_search = NULL;
      46  
      47  /* A couple of "globals" describing where the initial index was found. */
      48  static char *initial_index_filename = NULL;
      49  static char *initial_index_nodename = NULL;
      50  
      51  /* A structure associating index names with index offset ranges. */
      52  typedef struct {
      53    char *name;                   /* The nodename of this index. */
      54    int first;                    /* The index in our list of the first entry. */
      55    int last;                     /* The index in our list of the last entry. */
      56  } INDEX_NAME_ASSOC;
      57  
      58  /* An array associating index nodenames with index offset ranges.  Used
      59     for reporting to the user which index node an index entry was found
      60     in. */
      61  static INDEX_NAME_ASSOC **index_nodenames = NULL;
      62  static size_t index_nodenames_index = 0;
      63  static size_t index_nodenames_slots = 0;
      64  
      65  /* Add the name of NODE, and the range of the associated index elements
      66     (passed in ARRAY) to index_nodenames.  ARRAY must have at least one
      67     element. */
      68  static void
      69  add_index_to_index_nodenames (REFERENCE **array, NODE *node)
      70  {
      71    register int i, last;
      72    INDEX_NAME_ASSOC *assoc;
      73  
      74    for (last = 0; array[last + 1]; last++);
      75    assoc = xmalloc (sizeof (INDEX_NAME_ASSOC));
      76    assoc->name = xstrdup (node->nodename);
      77  
      78    if (!index_nodenames_index)
      79      {
      80        assoc->first = 0;
      81        assoc->last = last;
      82      }
      83    else
      84      {
      85        for (i = 0; index_nodenames[i + 1]; i++);
      86        assoc->first = 1 + index_nodenames[i]->last;
      87        assoc->last = assoc->first + last;
      88      }
      89    add_pointer_to_array (assoc, index_nodenames_index, index_nodenames, 
      90                          index_nodenames_slots, 10);
      91  }
      92  
      93  static void
      94  clear_index_nodenames (void)
      95  {
      96    int i;
      97    if (!index_nodenames)
      98      return;
      99    for (i = 0; index_nodenames[i]; i++)
     100      {
     101        free (index_nodenames[i]->name);
     102        free (index_nodenames[i]);
     103      }
     104  
     105    index_nodenames_index = 0;
     106    index_nodenames[0] = NULL;
     107  }
     108  
     109  /* Find and concatenate the indices of FILE_BUFFER, saving the result in 
     110     INDEX_INDEX.  The indices are defined as the first node in the file 
     111     containing the word "Index" and any immediately following nodes whose names 
     112     also contain "Index".  All such indices are concatenated and the result 
     113     returned. */
     114  static void
     115  info_indices_of_file_buffer (FILE_BUFFER *file_buffer)
     116  {
     117    register int i;
     118    REFERENCE **result = NULL;
     119  
     120    /* No file buffer, no indices. */
     121    if (!file_buffer)
     122      {
     123        free (index_index);
     124        index_index = 0;
     125        return;
     126      }
     127  
     128    /* If the file is the same as the one that we last built an
     129       index for, don't do anything. */
     130    if (initial_index_filename
     131        && FILENAME_CMP (initial_index_filename, file_buffer->filename) == 0)
     132      {
     133        return;
     134      }
     135  
     136    /* Display a message because finding the index entries might take a while. */
     137    if (info_windows_initialized_p)
     138      window_message_in_echo_area (_("Finding index entries..."));
     139  
     140    /* Reset globals describing where the index was found. */
     141    free (initial_index_filename);
     142    free (initial_index_nodename);
     143    initial_index_filename = NULL;
     144    initial_index_nodename = NULL;
     145  
     146    clear_index_nodenames ();
     147  
     148    /* Grovel the names of the nodes found in this file. */
     149    if (file_buffer->tags)
     150      {
     151        TAG *tag;
     152  
     153        for (i = 0; (tag = file_buffer->tags[i]); i++)
     154          {
     155            if (strcasestr (tag->nodename, "Index")
     156                && tag->cache.nodelen != 0) /* Not an anchor. */
     157              {
     158                NODE *node;
     159                REFERENCE **menu;
     160  
     161                node = info_node_of_tag (file_buffer, &file_buffer->tags[i]);
     162  
     163                if (!node)
     164                  continue;
     165  
     166                if ((node->flags & N_IsIndex) && !initial_index_filename)
     167                  {
     168                    /* Remember the filename and nodename of this index. */
     169                    initial_index_filename = xstrdup (file_buffer->filename);
     170                    initial_index_nodename = xstrdup (tag->nodename);
     171  
     172                    /* Clear list in case earlier node had "Index" in name. */
     173                    clear_index_nodenames ();
     174                  }
     175  
     176                menu = node->references;
     177  
     178                /* If we have a non-empty menu, add this index's nodename
     179                   and range to our list of index_nodenames. */
     180                if (menu && menu[0])
     181                  {
     182                    add_index_to_index_nodenames (menu, node);
     183  
     184                    /* Concatenate the references found so far. */
     185                    {
     186                    REFERENCE **old_result = result;
     187                    result = info_concatenate_references (result, menu);
     188                    free (old_result);
     189                    }
     190                  }
     191                free_history_node (node);
     192              }
     193          }
     194      }
     195  
     196    /* If there is a result, clean it up so that every entry has a filename. */
     197    for (i = 0; result && result[i]; i++)
     198      if (!result[i]->filename)
     199        result[i]->filename = xstrdup (file_buffer->filename);
     200  
     201    free (index_index);
     202    index_index = result;
     203  
     204    if (info_windows_initialized_p)
     205      window_clear_echo_area ();
     206  }
     207  
     208  void info_next_index_match (WINDOW *window, int count);
     209  
     210  DECLARE_INFO_COMMAND (info_index_search,
     211     _("Look up a string in the index for this file"))
     212  {
     213    FILE_BUFFER *fb;
     214    char *line;
     215    int old_offset;
     216  
     217    fb = file_buffer_of_window (window);
     218    if (fb)
     219      info_indices_of_file_buffer (fb); /* Sets index_index. */
     220  
     221    if (!fb || !index_index)
     222      {
     223        info_error (_("No indices found"));
     224        return;
     225      }
     226  
     227    line = info_read_maybe_completing (_("Index entry: "), index_index);
     228  
     229    /* User aborted? */
     230    if (!line)
     231      {
     232        info_abort_key (window, 1);
     233        return;
     234      }
     235  
     236    /* Empty line means move to the Index node. */
     237    if (!*line)
     238      {
     239        free (line);
     240  
     241        if (initial_index_filename && initial_index_nodename)
     242          {
     243            NODE *node;
     244  
     245            node = info_get_node (initial_index_filename,
     246                                  initial_index_nodename);
     247            info_set_node_of_window (window, node);
     248          }
     249        return;
     250      }
     251  
     252    /* Start the search either at the first or last index entry. */
     253    if (count < 0)
     254      {
     255        register int i;
     256        for (i = 0; index_index[i]; i++);
     257        index_offset = i;
     258      }
     259    else
     260      {
     261        index_offset = -1;
     262        index_initial = 0;
     263        index_partial = 0;
     264      }
     265    
     266    old_offset = index_offset;
     267  
     268    /* The "last" string searched for is this one. */
     269    free (index_search);
     270    index_search = line;
     271  
     272    info_next_index_match (window, count);
     273  
     274    /* If the search failed, return the index offset to where it belongs. */
     275    if (index_offset == old_offset)
     276      index_offset = -1;
     277  }
     278  
     279  /* Return true if ENT->label matches "S( <[0-9]+>)?", where S stands
     280     for the first LEN characters from STR. */
     281  static int
     282  index_entry_matches (REFERENCE *ent, const char *str, size_t len)
     283  {
     284    char *p;
     285    
     286    if (strncmp (ent->label, str, len))
     287      return 0;
     288    p = ent->label + len;
     289    if (!*p)
     290      return 1;
     291    if (p[0] == ' ' && p[1] == '<')
     292      {
     293        for (p += 2; *p; p++)
     294  	{
     295  	  if (p[0] == '>' && p[1] == 0)
     296  	    return 1;
     297  	  else if (!isdigit (*p))
     298  	    return 0;
     299  	}
     300      }
     301    return 0;
     302  }
     303  
     304  /* Search for the next occurence of STRING in FB's indices starting at OFFSET 
     305     in direction DIR.
     306     
     307     Try to get an exact match, If no match found, progress onto looking for 
     308     initial matches, then non-initial substrings, updating the values of 
     309     INDEX_INITIAL and INDEX_PARTIAL.
     310  
     311     If a match is found, return a pointer to the matching index entry, and
     312     set *FOUND_OFFSET to its offset in INDEX_INDEX.  Otherwise, return null.
     313     If we found a partial match, set *MATCH_OFFSET to the end of the match 
     314     within the index entry text, else to 0.  */
     315  REFERENCE *
     316  next_index_match (FILE_BUFFER *fb, char *string, int offset, int dir,
     317                    int *found_offset, int *match_offset)
     318  {
     319    int i;
     320    int partial_match;
     321    size_t search_len;
     322    REFERENCE *result;
     323  
     324    partial_match = 0;
     325    search_len = strlen (string);
     326  
     327    info_indices_of_file_buffer (fb); /* Sets index_index. */
     328    if (!index_index)
     329      {
     330        info_error (_("No indices found."));
     331        return 0;
     332      }
     333  
     334    if (index_search != string)
     335      {
     336        free (index_search); index_search = string;
     337      }
     338  
     339    if (!index_initial && !index_partial)
     340      {
     341        /* First try to find an exact match. */
     342        for (i = offset + dir; i > -1 && index_index[i]; i += dir)
     343          if (index_entry_matches (index_index[i], string, search_len))
     344            {
     345              *match_offset = 0;
     346              break;
     347            }
     348  
     349        if (i < 0 || !index_index[i])
     350  	{
     351            offset = -1;
     352            index_initial = 1;
     353  	}
     354      }
     355  
     356    if (index_initial)
     357      {
     358        for (i = offset + dir; i > -1 && index_index[i]; i += dir)
     359          if (!index_entry_matches (index_index[i], string, search_len)
     360              && !strncmp (index_index[i]->label, string, search_len))
     361            {
     362              *match_offset = search_len;
     363              break;
     364            }
     365  
     366        if (i < 0 || !index_index[i])
     367  	{
     368            offset = -1;
     369            index_initial = 0;
     370            index_partial = 1;
     371  	}
     372      }
     373  
     374    if (index_partial)
     375      {
     376        /* Look for substrings, excluding case-matching inital matches. */
     377        for (i = offset + dir; i > -1 && index_index[i]; i += dir)
     378          {
     379            if (strncmp (index_index[i]->label, string, search_len) != 0)
     380              {
     381                partial_match = string_in_line (string, index_index[i]->label);
     382                if (partial_match != -1)
     383                  {
     384                    *match_offset = partial_match;
     385                    break;
     386                  }
     387              }
     388          }
     389        if (partial_match <= 0)
     390          index_partial = 0;
     391      }
     392  
     393    if (i < 0 || !index_index[i])
     394      result = 0;
     395    else
     396      {
     397        index_offset = i;
     398        result = index_index[i];
     399      }
     400  
     401    *found_offset = i;
     402    return result;
     403  }
     404  
     405  /* Display a message saying where the index match was found. */
     406  void
     407  report_index_match (int i, int match_offset)
     408  {
     409    register int j;
     410    const char *name = "CAN'T SEE THIS";
     411    char *match;
     412  
     413    for (j = 0; index_nodenames[j]; j++)
     414      {
     415        if ((i >= index_nodenames[j]->first) &&
     416            (i <= index_nodenames[j]->last))
     417          {
     418            name = index_nodenames[j]->name;
     419            break;
     420          }
     421      }
     422  
     423    /* If we had a partial match, indicate to the user which part of the
     424       string matched. */
     425    match = xstrdup (index_index[i]->label);
     426  
     427    if (match_offset > 0 && show_index_match)
     428      {
     429        int k, ls, start, upper;
     430  
     431        ls = strlen (index_search);
     432        start = match_offset - ls;
     433        upper = isupper (match[start]) ? 1 : 0;
     434  
     435        for (k = 0; k < ls; k++)
     436          if (upper)
     437            match[k + start] = tolower (match[k + start]);
     438          else
     439            match[k + start] = toupper (match[k + start]);
     440      }
     441  
     442      {
     443        char *format;
     444  
     445        format = replace_in_documentation
     446          (_("Found '%s' in %s. ('\\[next-index-match]' tries to find next.)"),
     447           0);
     448  
     449        window_message_in_echo_area (format, match, (char *) name);
     450      }
     451  
     452    free (match);
     453  }
     454  
     455  DECLARE_INFO_COMMAND (info_next_index_match,
     456   _("Go to the next matching index item from the last '\\[index-search]' command"))
     457  {
     458    int i;
     459    int match_offset;
     460    int dir;
     461    REFERENCE *result;
     462    
     463    /* If there is no previous search string, the user hasn't built an index
     464       yet. */
     465    if (!index_search)
     466      {
     467        info_error (_("No previous index search string"));
     468        return;
     469      }
     470  
     471    /* The direction of this search is controlled by the value of the
     472       numeric argument. */
     473    if (count < 0)
     474      dir = -1;
     475    else
     476      dir = 1;
     477  
     478    result = next_index_match (file_buffer_of_window (window), index_search, 
     479                               index_offset, dir, &i, &match_offset);
     480  
     481    /* If that failed, print an error. */
     482    if (!result)
     483      {
     484        info_error (index_offset >= 0 ?
     485                    _("No more index entries containing '%s'") :
     486                    _("No index entries containing '%s'"),
     487                    index_search);
     488        index_offset = -1;
     489        return;
     490      }
     491  
     492    /* Report to the user on what we have found. */
     493    report_index_match (i, match_offset);
     494  
     495    info_select_reference (window, result);
     496  }
     497  
     498  /* Look for the best match of STRING in the indices of FB.  If SLOPPY, allow 
     499     case-insensitive initial substrings to match.  Return null if no match is 
     500     found.  Return value should not be freed or modified.  This differs from the 
     501     behaviour of next_index_match in that only _initial_ substrings are 
     502     considered. */
     503  REFERENCE *
     504  look_in_indices (FILE_BUFFER *fb, char *string, int sloppy)
     505  {
     506    REFERENCE **index_ptr;
     507    REFERENCE *nearest = 0;
     508  
     509    /* Remember the search string so we can use it as the default for 
     510       'virtual-index' or 'next-index-match'. */
     511    free (index_search);
     512    index_search = xstrdup (string);
     513  
     514    info_indices_of_file_buffer (fb); /* Sets index_index. */
     515    if (!index_index)
     516      return 0;
     517  
     518    for (index_ptr = index_index; *index_ptr; index_ptr++)
     519      {
     520        if (!strcmp (string, (*index_ptr)->label))
     521          {
     522            nearest = *index_ptr;
     523            break;
     524          }
     525        /* Case-insensitive initial substring. */
     526        if (sloppy && !nearest && !mbsncasecmp (string, (*index_ptr)->label,
     527                                      mbslen (string)))
     528          {
     529            nearest = *index_ptr;
     530          }
     531      }
     532    return nearest;
     533  }
     534  
     535  /* **************************************************************** */
     536  /*                                                                  */
     537  /*                 Info APROPOS: Search every known index.          */
     538  /*                                                                  */
     539  /* **************************************************************** */
     540  
     541  /* For every menu item in DIR, search the indices of that file for
     542     SEARCH_STRING. */
     543  REFERENCE **
     544  apropos_in_all_indices (char *search_string, int inform)
     545  {
     546    size_t i, dir_index;
     547    REFERENCE **all_indices = NULL;
     548    REFERENCE **dir_menu = NULL;
     549    NODE *dir_node;
     550  
     551    dir_node = get_dir_node ();
     552  
     553    /* It should be safe to assume that dir nodes do not contain any
     554       cross-references, i.e., its references list only contains
     555       menu items. */
     556    if (dir_node)
     557      dir_menu = dir_node->references;
     558  
     559    if (!dir_menu)
     560      {
     561        free (dir_node);
     562        return NULL;
     563      }
     564  
     565    /* For every menu item in DIR, get the associated file buffer and
     566       read the indices of that file buffer.  Gather all of the indices into
     567       one large one. */
     568    for (dir_index = 0; dir_menu[dir_index]; dir_index++)
     569      {
     570        REFERENCE **this_index, *this_item;
     571        FILE_BUFFER *this_fb, *loaded_file = 0;
     572  
     573        this_item = dir_menu[dir_index];
     574        if (!this_item->filename)
     575          continue;
     576  
     577        /* If we already scanned this file, don't do that again.
     578           In addition to being faster, this also avoids having
     579           multiple identical entries in the *Apropos* menu.  */
     580        for (i = 0; i < dir_index; i++)
     581          if (dir_menu[i]->filename
     582              && FILENAME_CMP (this_item->filename, dir_menu[i]->filename) == 0)
     583            break;
     584        if (i < dir_index)
     585          continue;
     586  
     587        this_fb = check_loaded_file (this_item->filename);
     588  
     589        if (!this_fb)
     590          this_fb = loaded_file = info_find_file (this_item->filename);
     591  
     592        if (!this_fb)
     593          continue; /* Couldn't load file. */
     594  
     595        if (this_fb && inform)
     596          message_in_echo_area (_("Scanning indices of '%s'..."), this_item->filename);
     597  
     598        info_indices_of_file_buffer (this_fb);
     599        this_index = index_index;
     600  
     601        if (this_fb && inform)
     602          unmessage_in_echo_area ();
     603  
     604        if (this_index)
     605          {
     606            /* Remember the filename which contains this set of references. */
     607            for (i = 0; this_index && this_index[i]; i++)
     608              if (!this_index[i]->filename)
     609                this_index[i]->filename = xstrdup (this_fb->filename);
     610  
     611            /* Concatenate with the other indices.  */
     612            {
     613            REFERENCE **old_indices = all_indices;
     614            all_indices = info_concatenate_references (all_indices, this_index);
     615            free (old_indices);
     616            }
     617          }
     618  
     619        /* Try to avoid running out of memory by not loading all of the
     620           Info files on the system into memory.  This is risky because we
     621           may have a pointer into the file buffer, so only free the contents
     622           if we have just loaded the file. */
     623        if (loaded_file)
     624          {
     625            free (loaded_file->contents);
     626            loaded_file->contents = NULL;
     627          }
     628      }
     629  
     630    /* Build a list of the references which contain SEARCH_STRING. */
     631    if (all_indices)
     632      {
     633        REFERENCE *entry, **apropos_list = NULL;
     634        size_t apropos_list_index = 0;
     635        size_t apropos_list_slots = 0;
     636  
     637        for (i = 0; (entry = all_indices[i]); i++)
     638          {
     639            if (string_in_line (search_string, entry->label) != -1)
     640              {
     641                add_pointer_to_array (entry, apropos_list_index, apropos_list, 
     642                                      apropos_list_slots, 100);
     643              }
     644          }
     645  
     646        free (all_indices);
     647        all_indices = apropos_list;
     648      }
     649    free (dir_node);
     650    return all_indices;
     651  }
     652  
     653  static char *apropos_list_nodename = "*Apropos*";
     654  
     655  DECLARE_INFO_COMMAND (info_index_apropos,
     656     _("Grovel all known info file's indices for a string and build a menu"))
     657  {
     658    char *line, *prompt;
     659    REFERENCE **apropos_list;
     660    NODE *apropos_node;
     661    struct text_buffer message;
     662  
     663    if (index_search)
     664      xasprintf (&prompt, "%s [%s]: ", _("Index apropos"), index_search);
     665    else
     666      xasprintf (&prompt, "%s: ", _("Index apropos"));
     667    line = info_read_in_echo_area (prompt);
     668    free (prompt);
     669  
     670    window = active_window;
     671  
     672    /* User aborted? */
     673    if (!line)
     674      {
     675        info_abort_key (window, 1);
     676        return;
     677      }
     678  
     679    /* User typed something? */
     680    if (*line)
     681      {
     682        free (index_search);
     683        index_search = line;
     684      }
     685    else
     686      free (line); /* Try to use the last search string. */
     687  
     688    if (index_search && *index_search)
     689      {
     690        apropos_list = apropos_in_all_indices (index_search, 1);
     691  
     692        if (!apropos_list)
     693          { 
     694            info_error (_(APROPOS_NONE), index_search);
     695            return;
     696          }
     697        else
     698          {
     699            /* Create the node.  FIXME: Labels and node names taken from the
     700               indices of Info files may be in a different character encoding to 
     701               the one currently being used.
     702               This problem is reduced by makeinfo not putting quotation marks 
     703               from @samp, etc., into node names and index entries. */
     704            register int i;
     705  
     706            text_buffer_init (&message);
     707            text_buffer_add_char (&message, '\n');
     708            text_buffer_printf (&message, _("Index entries containing "
     709                                "'%s':\n"), index_search);
     710            text_buffer_printf (&message, "\n* Menu:");
     711            text_buffer_add_string (&message, "\0\b[index\0\b]", 11);
     712            text_buffer_add_char (&message, '\n');
     713  
     714            for (i = 0; apropos_list[i]; i++)
     715              {
     716                int line_start = text_buffer_off (&message);
     717                char *filename;
     718  
     719                /* Remove file extension. */
     720                filename = program_name_from_file_name
     721                  (apropos_list[i]->filename);
     722  
     723                /* The label might be identical to that of another index
     724                   entry in another Info file.  Therefore, we make the file
     725                   name part of the menu entry, to make them all distinct.  */
     726                text_buffer_printf (&message, "* %s [%s]: ",
     727                        apropos_list[i]->label, filename);
     728  
     729                while (text_buffer_off (&message) - line_start < 40)
     730                  text_buffer_add_char (&message, ' ');
     731                text_buffer_printf (&message, "(%s)%s.",
     732                                    filename, apropos_list[i]->nodename);
     733                text_buffer_printf (&message, " (line %ld)\n",
     734                                    apropos_list[i]->line_number);
     735                free (filename);
     736              }
     737          }
     738  
     739        apropos_node = text_buffer_to_node (&message);
     740        {
     741          char *old_contents = apropos_node->contents;
     742          scan_node_contents (apropos_node, 0, 0);
     743          if (old_contents != apropos_node->contents)
     744            free (old_contents);
     745        }
     746  
     747        name_internal_node (apropos_node, xstrdup (apropos_list_nodename));
     748  
     749        /* Find/Create a window to contain this node. */
     750        {
     751          WINDOW *new;
     752          NODE *node;
     753  
     754          /* If a window is visible and showing an apropos list already,
     755             re-use it. */
     756          for (new = windows; new; new = new->next)
     757            {
     758              node = new->node;
     759  
     760              if (internal_info_node_p (node) &&
     761                  (strcmp (node->nodename, apropos_list_nodename) == 0))
     762                break;
     763            }
     764  
     765          /* If we couldn't find an existing window, try to use the next window
     766             in the chain. */
     767          if (!new && window->next)
     768            new = window->next;
     769  
     770          /* If we still don't have a window, make a new one to contain
     771             the list. */
     772          if (!new)
     773            new = window_make_window ();
     774  
     775          /* If we couldn't make a new window, use this one. */
     776          if (!new)
     777            new = window;
     778  
     779          /* Lines do not wrap in this window. */
     780          new->flags |= W_NoWrap;
     781  
     782          info_set_node_of_window (new, apropos_node);
     783          active_window = new;
     784        }
     785        free (apropos_list);
     786      }
     787  }
     788  
     789  #define NODECOL 41
     790  #define LINECOL 62
     791  
     792  static void
     793  format_reference (REFERENCE *ref, const char *filename, struct text_buffer *buf)
     794  {
     795    size_t n;
     796    
     797    n = text_buffer_printf (buf, "* %s: ", ref->label);
     798    if (n < NODECOL)
     799      n += text_buffer_fill (buf, ' ', NODECOL - n);
     800    
     801    if (ref->filename && strcmp (ref->filename, filename))
     802      n += text_buffer_printf (buf, "(%s)", ref->filename);
     803    n += text_buffer_printf (buf, "%s. ", ref->nodename);
     804  
     805    if (n < LINECOL)
     806      n += text_buffer_fill (buf, ' ', LINECOL - n);
     807    else
     808      {
     809        text_buffer_add_char (buf, '\n');
     810        text_buffer_fill (buf, ' ', LINECOL);
     811      }
     812    
     813    text_buffer_printf (buf, "(line %4d)\n", ref->line_number);
     814  }
     815  
     816  NODE *
     817  create_virtual_index (FILE_BUFFER *file_buffer, char *index_search)
     818  {
     819    struct text_buffer text;
     820    int i;
     821    size_t cnt;
     822    NODE *node;
     823  
     824    text_buffer_init (&text);
     825    text_buffer_printf (&text,
     826                        "File: %s,  Node: Index for '%s'\n\n",
     827                        file_buffer->filename, index_search);
     828    text_buffer_printf (&text, _("Virtual Index\n"
     829                                 "*************\n\n"
     830                                 "Index entries that match '%s':\n"),
     831                        index_search);
     832    text_buffer_add_string (&text, "\0\b[index\0\b]", 11);
     833    text_buffer_printf (&text, "\n* Menu:\n\n");
     834  
     835    cnt = 0;
     836  
     837    index_offset = -1;
     838    index_initial = 0;
     839    index_partial = 0;
     840    while (1)
     841      {
     842        REFERENCE *result;
     843        int match_offset;
     844  
     845        result = next_index_match (file_buffer, index_search, index_offset, 1,
     846                                   &i, &match_offset);
     847        if (!result)
     848          break;
     849        format_reference (index_index[i],
     850                          file_buffer->filename, &text);
     851        cnt++;
     852      }
     853    text_buffer_add_char (&text, '\0');
     854  
     855    if (cnt == 0)
     856      {
     857        text_buffer_free (&text);
     858        return 0;
     859      }
     860  
     861    node = info_create_node ();
     862    xasprintf (&node->nodename, "Index for '%s'", index_search);
     863    node->fullpath = file_buffer->filename;
     864    node->contents = text_buffer_base (&text);
     865    node->nodelen = text_buffer_off (&text) - 1;
     866    node->body_start = strcspn (node->contents, "\n");
     867    node->flags |= N_IsInternal | N_WasRewritten;
     868  
     869    scan_node_contents (node, 0, 0);
     870  
     871    return node;
     872  }
     873  
     874  DECLARE_INFO_COMMAND (info_virtual_index,
     875     _("List all matches of a string in the index"))
     876  {
     877    char *prompt, *line;
     878    FILE_BUFFER *fb;
     879    NODE *node;
     880    
     881    fb = file_buffer_of_window (window);
     882  
     883    if (!initial_index_filename ||
     884        !fb ||
     885        (FILENAME_CMP (initial_index_filename, fb->filename) != 0))
     886      {
     887        window_message_in_echo_area (_("Finding index entries..."));
     888        info_indices_of_file_buffer (fb);
     889      }
     890  
     891    if (!index_index)
     892      {
     893        info_error (_("No indices found."));
     894        return;
     895      }
     896      
     897    /* Default to last search if there is one. */
     898    if (index_search)
     899      xasprintf (&prompt, "%s [%s]: ", _("Index topic"), index_search);
     900    else
     901      xasprintf (&prompt, "%s: ", _("Index topic"));
     902    line = info_read_maybe_completing (prompt, index_index);
     903    free (prompt);
     904  
     905    /* User aborted? */
     906    if (!line)
     907      {
     908        info_abort_key (window, 1);
     909        return;
     910      }
     911  
     912    if (*line)
     913      {
     914        free (index_search);
     915        index_search = line;
     916      }
     917    else if (!index_search)
     918      {
     919        free (line);
     920        return; /* No previous search string, and no string given. */
     921      }
     922    
     923    node = create_virtual_index (fb, index_search);
     924    if (!node)
     925      {
     926        info_error (_("No index entries containing '%s'."), index_search);
     927        return;
     928      }
     929    info_set_node_of_window (window, node);
     930  }