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 }