changeset 171:d6fae6ef3e60

[project @ 1993-10-21 22:39:46 by jwe] Initial revision
author jwe
date Thu, 21 Oct 1993 22:39:46 +0000
parents a76cfc0fc794
children 0597476bbe6d
files info/Makefile.in info/dir.c info/display.c info/display.h info/doc.h info/dribble.c info/dribble.h info/echo_area.c info/echo_area.h info/filesys.c info/filesys.h info/footnotes.c info/footnotes.h info/gc.c info/gc.h info/general.h info/indices.c info/indices.h info/info-utils.c info/info-utils.h info/info.c info/info.h info/infodoc.c info/infomap.c info/infomap.h info/m-x.c info/makedoc.c info/nodemenu.c info/nodes.c info/nodes.h info/search.c info/search.h info/session.c info/session.h info/signals.c info/signals.h info/termdep.h info/terminal.c info/terminal.h info/tilde.c info/tilde.h info/variables.c info/variables.h info/window.c info/window.h info/xmalloc.c
diffstat 46 files changed, 18220 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/Makefile.in	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,211 @@
+# Makefile for texinfo/info.	-*- Indented-Text -*-
+# Copyright (C) 1993 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+#### Start of system configuration section. ####
+
+srcdir = @srcdir@
+VPATH  = $(srcdir):$(common)
+
+common = $(srcdir)/../libtxi
+
+CC = @CC@
+
+INSTALL = @INSTALL@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_DATA = @INSTALL_DATA@
+
+LN	= ln
+RM	= rm -f
+TAR	= tar
+MKDIR	= mkdir
+MAKEINFO= ../makeinfo/makeinfo
+COMPRESS= compress
+
+DEFS = @DEFS@
+
+LDEFS = -DNAMED_FUNCTIONS=1 -DDEFAULT_INFOPATH='"$(DEFAULT_INFOPATH)"'
+
+TERMLIBS = @TERMLIBS@
+LIBS = $(TERMLIBS) -L../libtxi -ltxi @LIBS@
+LOADLIBES = $(LIBS)
+
+SHELL = /bin/sh
+
+CFLAGS = -g
+LDFLAGS = -g
+
+prefix = /usr/local
+exec_prefix = $(prefix)
+bindir = $(exec_prefix)/bin
+# Prefix for each installed program, normally empty or `g'.
+binprefix = 
+libdir = $(prefix)/lib
+# Prefix for each installed man page, normally empty or `g'.
+manprefix = 
+mandir = $(prefix)/man/man1
+manext = 1
+infodir = $(prefix)/info
+
+#### End of system configuration section. ####
+
+SRCS =	dir.c		display.c	echo_area.c	filesys.c \
+	info-utils.c	info.c		infodoc.c	infomap.c \
+	m-x.c		nodes.c		search.c	session.c \
+	signals.c	terminal.c	tilde.c		window.c \
+	xmalloc.c	indices.c	makedoc.c	nodemenu.c \
+	footnotes.c	dribble.c	variables.c	gc.c
+
+HDRS =	display.h	doc.h		echo_area.h	filesys.h \
+	general.h	getopt.h	info-utils.h	info.h \
+	infomap.h	nodes.h		search.h 	session.h \
+	signals.h	termdep.h	terminal.h	tilde.h \
+	indices.h	window.h	footnotes.h	dribble.h \
+	variables.h	gc.h
+
+OBJS =	dir.o display.o doc.o echo_area.o filesys.o info-utils.o info.o \
+	infodoc.o infomap.o m-x.o nodes.o search.o session.o signals.o \
+	terminal.o tilde.o window.o indices.o xmalloc.o nodemenu.o \
+	footnotes.o dribble.o variables.o gc.o 
+
+# The names of files which declare info commands.
+CMDFILES = $(srcdir)/session.c $(srcdir)/echo_area.c $(srcdir)/infodoc.c \
+	   $(srcdir)/m-x.c $(srcdir)/indices.c $(srcdir)/nodemenu.c \
+	   $(srcdir)/footnotes.c $(srcdir)/variables.c
+
+# The name of the program which builds documentation structure from CMDFILES.
+MAKEDOC_OBJECTS = makedoc.o xmalloc.o
+MAKEDOC_SOURCE = makedoc.c xmalloc.c
+
+.c.o:
+	$(CC) -c $(CPPFLAGS) $(LDEFS) $(DEFS) -I. -I$(srcdir) -I$(common) $(CFLAGS) $<
+
+all: info info.info info-stnd.info
+sub-all: all
+
+install: all
+	$(INSTALL_PROGRAM) info $(bindir)/info
+	-d=$(srcdir); test -f ./info.info && d=.; $(INSTALL_DATA) $$d/info.info $(infodir)/info.info
+	-d=$(srcdir); test -f ./info-stnd.info && d=.; $(INSTALL_DATA) $$d/info-stnd.info $(infodir)/info-stnd.info
+	-$(INSTALL_DATA) $(srcdir)/info.1 $(mandir)/info.$(manext)
+
+uninstall:
+	rm -f $(bindir)/info
+	rm -f $(infodir)/info.info
+	rm -f $(infodir)/info-stnd.info
+	rm -f $(mandir)/info.$(manext)
+
+info: $(OBJS) ../libtxi/libtxi.a
+	$(CC) $(LDFLAGS) -o info $(OBJS) $(LOADLIBES)
+
+info.info: info.texi
+	$(MAKEINFO) --no-split -I$(srcdir) info.texi
+
+info-stnd.info: info-stnd.texi
+	$(MAKEINFO) --no-split -I$(srcdir) info-stnd.texi
+
+makedoc: $(MAKEDOC_OBJECTS) ../libtxi/libtxi.a
+	$(CC) $(LDFLAGS) -o makedoc $(MAKEDOC_OBJECTS) $(LOADLIBES)
+
+Makefile: $(srcdir)/Makefile.in ../config.status
+	cd ..; sh config.status
+
+clean:
+	rm -f info funs.h doc.c makedoc $(OBJS) $(MAKEDOC_OBJECTS)
+
+distclean: clean texclean
+	rm -f Makefile config.status *~ core core.* *.BAK TAGS
+
+mostlyclean: clean
+
+realclean: distclean
+	rm -f funs.h doc.c
+	rm -f info.info info-stnd.info
+
+TAGS: $(SRCS) makedoc-TAGS
+	etags $(SRCS)
+	cat makedoc-TAGS >>TAGS && rm -f makedoc-TAGS
+
+makedoc-TAGS: $(CMDFILES)
+	./makedoc -tags $(CMDFILES) >makedoc-TAGS
+
+texclean:
+	rm -f *.toc *.aux *.log *.cp *.fn *.tp *.vr *.pg *.ky *.cps \
+	*.tps *.fns *.kys *.pgs *.vrs
+
+check: info
+
+# The files `doc.c' and `funs.h' are created by ./makedoc run over the source
+# files which contain DECLARE_INFO_COMMAND.  `funs.h' is a header file
+# listing the functions found.  `doc.c' is a structure containing pointers
+# to those functions along with completable names and documentation strings.
+funs.h: makedoc $(CMDFILES)
+	-@if test -f funs.h; then mv -f funs.h old-funs.h; fi; :
+	-@if test -f doc.c; then mv -f doc.c old-doc.c; fi; :
+	./makedoc $(CMDFILES)
+	-@if cmp -s old-funs.h funs.h; then mv old-funs.h funs.h; \
+	  else rm -f old-funs.h; fi; :
+	-@if cmp -s old-doc.c doc.c; then mv old-doc.c doc.c; \
+	  else rm -f old-doc.c; fi; :
+
+doc.c: 		funs.h
+dribble.o:	dribble.c dribble.h
+display.o:	display.c
+echo_area.o:	echo_area.c
+filesys.o:	filesys.c
+info-utils.o:	info-utils.c
+info.o:		info.c
+infodoc.o:	infodoc.c
+infomap.o:	infomap.c
+m-x.o:		m-x.c
+nodes.o:	nodes.c
+search.o:	search.c
+session.o:	session.c
+signals.o:	signals.c
+terminal.o:	terminal.c
+tilde.o:	tilde.c
+window.o:	window.c
+xmalloc.o:	xmalloc.c
+indices.o:	indices.c
+makedoc.o:	makedoc.c
+
+dir.o: 		dir.c
+display.o: 	nodes.h info-utils.h search.h
+display.o: 	terminal.h window.h display.h
+echo_area.o: 	info.h
+filesys.o: 	general.h tilde.h filesys.h
+footnotes.o: 	footnotes.h
+info-utils.o: 	info-utils.h nodes.h search.h
+info.o: 	info.h $(common)/getopt.h
+infodoc.o: 	info.h doc.h
+infomap.o: 	infomap.h funs.h
+gc.o:		info.h
+m-x.o: 		info.h
+nodes.o: 	search.h filesys.h
+nodes.o: 	nodes.h info-utils.h
+search.o: 	general.h search.h nodes.h
+session.o: 	info.h
+signals.o: 	info.h signals.h
+terminal.o: 	terminal.h termdep.h
+tilde.o: 	tilde.h
+variables.c: 	variables.h
+window.o: 	nodes.h window.h display.h
+window.o: 	info-utils.h search.h infomap.h
+
+# Prevent GNU make v3 from overflowing arg limit on SysV.
+.NOEXPORT:
+
+# eof
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/dir.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,224 @@
+/* dir.c -- How to build a special "dir" node from "localdir" files. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/errno.h>
+#include "info-utils.h"
+#include "filesys.h"
+#include "tilde.h"
+
+/* The "dir" node can be built from the contents of a file called "dir",
+   with the addition of the menus of every file called "localdir" found in
+   INFOPATH. */
+
+static void add_menu_to_file_buffer (), insert_text_into_fb_at_binding ();
+
+void
+maybe_build_dir_node (dirname, from_files_named)
+     char *dirname;
+     char *from_files_named;
+{
+  FILE_BUFFER *dir_buffer;
+
+  /* See if the file has already been loaded and exists. */
+  dir_buffer = info_find_file (dirname);
+
+  /* If there is no "dir" in the current info path, we cannot build one
+     from nothing. */
+  if (!dir_buffer)
+    return;
+
+  /* If this directory has already been built, return now. */
+  if (dir_buffer->flags & N_CannotGC)
+    return;
+
+  dir_buffer->flags |= N_CannotGC;
+
+  /* For every file named FROM_FILES_NAMED in the search path, add the
+     contents of that file's menu to our "dir" node. */
+  {
+    struct stat finfo;
+    char *this_dir;
+    int namelen, path_index;
+    int update_tags = 0;
+
+    namelen = strlen (from_files_named);
+    path_index = 0;
+
+    /* Using each element of the path, check for "localdir".  Do not check
+       for "localdir.info.Z" or anything else.  Only files explictly named
+       "localdir" are eligible.  This is a design decision.  There can be
+       an info file name "localdir.info" which contains information on the
+       setting up of "localdir" files. */
+    while (this_dir = extract_colon_unit (infopath, &path_index))
+      {
+	char *fullpath;
+	int statable;
+
+	/* Expand a leading tilde if one is present. */
+	if (*this_dir == '~')
+	  {
+	    char *tilde_expanded_dirname;
+
+	    tilde_expanded_dirname = tilde_expand_word (this_dir);
+	    free (this_dir);
+	    this_dir = tilde_expanded_dirname;
+	  }
+
+	fullpath = (char *)xmalloc (3 + strlen (this_dir) + namelen);
+	strcpy (fullpath, this_dir);
+	if (fullpath[strlen (fullpath) - 1] != '/')
+	  strcat (fullpath, "/");
+	strcat (fullpath, from_files_named);
+
+	statable = (stat (fullpath, &finfo) == 0);
+
+	if (statable && S_ISREG (finfo.st_mode))
+	  {
+	    long filesize;
+	    char *contents;
+
+	    contents = filesys_read_info_file (fullpath, &filesize, &finfo);
+
+	    if (contents)
+	      {
+		update_tags++;
+		add_menu_to_file_buffer (contents, filesize, dir_buffer);
+		free (contents);
+	      }
+	  }
+
+	free (fullpath);
+	free (this_dir);
+      }
+    if (update_tags)
+      build_tags_and_nodes (dir_buffer);
+  }
+}
+
+/* Given CONTENTS and FB (a file buffer), add the menu found in CONTENTS
+   to the menu found in FB->contents.  Second argument SIZE is the total
+   size of CONTENTS. */
+static void
+add_menu_to_file_buffer (contents, size, fb)
+     char *contents;
+     long size;
+     FILE_BUFFER *fb;
+{
+  SEARCH_BINDING contents_binding, fb_binding;
+  long contents_offset, fb_offset;
+
+  contents_binding.buffer = contents;
+  contents_binding.start = 0;
+  contents_binding.end = size;
+  contents_binding.flags = S_FoldCase | S_SkipDest;
+
+  fb_binding.buffer = fb->contents;
+  fb_binding.start = 0;
+  fb_binding.end = fb->filesize;
+  fb_binding.flags = S_FoldCase | S_SkipDest;
+
+  /* Move to the start of the menus in CONTENTS and FB. */
+  contents_offset = search_forward (INFO_MENU_LABEL, &contents_binding);
+  fb_offset = search_forward (INFO_MENU_LABEL, &fb_binding);
+
+  /* If there is no menu in CONTENTS, quit now. */
+  if (contents_offset == -1)
+    return;
+
+  /* If there is no menu in FB, make one. */
+  if (fb_offset == -1)
+    {
+      /* Find the start of the second node in this file buffer.  If there
+	 is only one node, we will be adding the contents to the end of
+	 this node. */
+      fb_offset = find_node_separator (&fb_binding);
+
+      /* If not even a single node separator, give up. */
+      if (fb_offset == -1)
+	return;
+
+      fb_binding.start = fb_offset;
+      fb_binding.start +=
+	skip_node_separator (fb_binding.buffer + fb_binding.start);
+
+      /* Try to find the next node separator. */
+      fb_offset = find_node_separator (&fb_binding);
+
+      /* If found one, consider that the start of the menu.  Otherwise, the
+	 start of this menu is the end of the file buffer (i.e., fb->size). */
+      if (fb_offset != -1)
+	fb_binding.start = fb_offset;
+      else
+	fb_binding.start = fb_binding.end;
+
+      insert_text_into_fb_at_binding
+	(fb, &fb_binding, INFO_MENU_LABEL, strlen (INFO_MENU_LABEL));
+
+      fb_binding.buffer = fb->contents;
+      fb_binding.start = 0;
+      fb_binding.end = fb->filesize;
+      fb_offset = search_forward (INFO_MENU_LABEL, &fb_binding);
+      if (fb_offset == -1)
+	abort ();
+    }
+
+  /* CONTENTS_OFFSET and FB_OFFSET point to the starts of the menus that
+     appear in their respective buffers.  Add the remainder of CONTENTS
+     to the end of FB's menu. */
+  fb_binding.start = fb_offset;
+  fb_offset = find_node_separator (&fb_binding);
+  if (fb_offset != -1)
+    fb_binding.start = fb_offset;
+  else
+    fb_binding.start = fb_binding.end;
+
+  insert_text_into_fb_at_binding
+    (fb, &fb_binding, contents + contents_offset, size - contents_offset);
+}
+
+static void
+insert_text_into_fb_at_binding (fb, binding, text, textlen)
+     FILE_BUFFER *fb;
+     SEARCH_BINDING *binding;
+     char *text;
+     int textlen;
+{
+  char *contents;
+  long start, end;
+
+  start = binding->start;
+  end = fb->filesize;
+
+  contents = (char *)xmalloc (fb->filesize + textlen + 1);
+  memcpy (contents, fb->contents, start);
+  memcpy (contents + start, text, textlen);
+  memcpy (contents + start + textlen, fb->contents + start, end - start);
+  free (fb->contents);
+  fb->contents = contents;
+  fb->filesize += textlen;
+  fb->finfo.st_size = fb->filesize;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/display.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,561 @@
+/* display.c -- How to display Info windows. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "display.h"
+
+extern int info_any_buffered_input_p (); /* Found in session.c. */
+
+static void free_display ();
+static DISPLAY_LINE **make_display ();
+
+/* An array of display lines which tell us what is currently visible on
+   the display.  */
+DISPLAY_LINE **the_display = (DISPLAY_LINE **)NULL;
+
+/* Non-zero means do no output. */
+int display_inhibited = 0;
+
+/* Initialize THE_DISPLAY to WIDTH and HEIGHT, with nothing in it. */
+void
+display_initialize_display (width, height)
+     int width, height;
+{
+  free_display (the_display);
+  the_display = make_display (width, height);
+  display_clear_display (the_display);
+}
+
+/* Clear all of the lines in DISPLAY making the screen blank. */
+void
+display_clear_display (display)
+     DISPLAY_LINE **display;
+{
+  register int i;
+  register DISPLAY_LINE *display_line;
+
+  for (i = 0; display_line = display[i]; i++)
+    {
+      display[i]->text[0] = '\0';
+      display[i]->textlen = 0;
+      display[i]->inverse = 0;
+    }
+}
+
+/* Non-zero if we didn't completely redisplay a window. */
+int display_was_interrupted_p = 0;
+
+/* Update the windows pointed to by WINDOW in the_display.  This actually
+   writes the text on the screen. */
+void
+display_update_display (window)
+     WINDOW *window;
+{
+  register WINDOW *win;
+
+  display_was_interrupted_p = 0;
+
+  /* For every window in the list, check contents against the display. */
+  for (win = window; win; win = win->next)
+    {
+      /* Only re-display visible windows which need updating. */
+      if (((win->flags & W_WindowVisible) == 0) ||
+	  ((win->flags & W_UpdateWindow) == 0) ||
+	  (win->height == 0))
+	continue;
+
+      display_update_one_window (win);
+      if (display_was_interrupted_p)
+	break;
+    }
+
+  /* Always update the echo area. */
+  display_update_one_window (the_echo_area);
+}
+
+/* Display WIN on the_display.  Unlike display_update_display (), this
+   function only does one window. */
+void
+display_update_one_window (win)
+     WINDOW *win;
+{
+  register char *nodetext;	/* Current character to display. */
+  register char *last_node_char; /* Position of the last character in node. */
+  register int i;		/* General use index. */
+  char *printed_line;		/* Buffer for a printed line. */
+  int pl_index = 0;		/* Index into PRINTED_LINE. */
+  int line_index = 0;		/* Number of lines done so far. */
+  DISPLAY_LINE **display = the_display;
+
+  /* If display is inhibited, that counts as an interrupted display. */
+  if (display_inhibited)
+    display_was_interrupted_p = 1;
+
+  /* If the window has no height, or display is inhibited, quit now. */
+  if (!win->height || display_inhibited)
+    return;
+
+  /* If the window's first row doesn't appear in the_screen, then it
+     cannot be displayed.  This can happen when the_echo_area is the
+     window to be displayed, and the screen has shrunk to less than one
+     line. */
+  if ((win->first_row < 0) || (win->first_row > the_screen->height))
+    return;
+
+  /* Print each line in the window into our local buffer, and then
+     check the contents of that buffer against the display.  If they
+     differ, update the display. */
+  printed_line = (char *)xmalloc (1 + win->width);
+
+  if (!win->node || !win->line_starts)
+    goto done_with_node_display;
+
+  nodetext = win->line_starts[win->pagetop];
+  last_node_char = win->node->contents + win->node->nodelen;
+
+  for (; nodetext < last_node_char; nodetext++)
+    {
+      char *rep, *rep_carried_over, rep_temp[2];
+      int replen;
+
+      if (isprint (*nodetext))
+	{
+	  rep_temp[0] = *nodetext;
+	  replen = 1;
+	  rep_temp[1] = '\0';
+	  rep = rep_temp;
+	}
+      else
+	{
+	  if (*nodetext == '\r' || *nodetext == '\n')
+	    {
+	      replen = win->width - pl_index;
+	    }
+	  else
+	    {
+	      rep = printed_representation (*nodetext, pl_index);
+	      replen = strlen (rep);
+	    }
+	}
+
+      /* If this character can be printed without passing the width of
+	 the line, then stuff it into the line. */
+      if (replen + pl_index < win->width)
+	{
+	  /* Optimize if possible. */
+	  if (replen == 1)
+	    {
+	      printed_line[pl_index++] = *rep;
+	    }
+	  else
+	    {
+	      for (i = 0; i < replen; i++)
+		printed_line[pl_index++] = rep[i];
+	    }
+	}
+      else
+	{
+	  DISPLAY_LINE *entry;
+
+	  /* If this character cannot be printed in this line, we have
+	     found the end of this line as it would appear on the screen.
+	     Carefully print the end of the line, and then compare. */
+	  if (*nodetext == '\n' || *nodetext == '\r' || *nodetext == '\t')
+	    {
+	      printed_line[pl_index] = '\0';
+	      rep_carried_over = (char *)NULL;
+	    }
+	  else
+	    {
+	      /* The printed representation of this character extends into
+		 the next line.  Remember the offset of the last character
+		 printed out of REP so that we can carry the character over
+		 to the next line. */
+	      for (i = 0; pl_index < (win->width - 1);)
+		printed_line[pl_index++] = rep[i++];
+	      
+	      rep_carried_over = rep + i;
+
+	      /* If printing the last character in this window couldn't
+		 possibly cause the screen to scroll, place a backslash
+		 in the rightmost column. */
+	      if (1 + line_index + win->first_row < the_screen->height)
+		{
+		  if (win->flags & W_NoWrap)
+		    printed_line[pl_index++] = '$';
+		  else
+		    printed_line[pl_index++] = '\\';
+		}
+	      printed_line[pl_index] = '\0';
+	    }
+
+	  /* We have the exact line as it should appear on the screen.
+	     Check to see if this line matches the one already appearing
+	     on the screen. */
+	  entry = display[line_index + win->first_row];
+
+	  /* If the screen line is inversed, then we have to clear
+	     the line from the screen first.  Why, I don't know. */
+	  if (entry->inverse)
+	    {
+	      terminal_goto_xy (0, line_index + win->first_row);
+	      terminal_clear_to_eol ();
+	      entry->inverse = 0;
+	      entry->text[0] = '\0';
+	      entry->textlen = 0;
+	    }
+
+	  /* Find the offset where these lines differ. */
+	  for (i = 0; i < pl_index; i++)
+	    if (printed_line[i] != entry->text[i])
+	      break;
+
+	  /* If the lines are not the same length, or if they differed
+	     at all, we must do some redrawing. */
+	  if ((i != pl_index) || (pl_index != entry->textlen))
+	    {
+	      /* Move to the proper point on the terminal. */
+	      terminal_goto_xy (i, line_index + win->first_row);
+
+	      /* If there is any text to print, print it. */
+	      if (i != pl_index)
+		terminal_put_text (printed_line + i);
+
+	      /* If the printed text didn't extend all the way to the edge
+		 of the window, and text was appearing between here and the
+		 edge of the window, clear from here to the end of the line. */
+	      if ((pl_index < win->width && pl_index < entry->textlen) ||
+		  (entry->inverse))
+		terminal_clear_to_eol ();
+
+	      fflush (stdout);
+
+	      /* Update the display text buffer. */
+	      strcpy (entry->text + i, printed_line + i);
+	      entry->textlen = pl_index;
+
+	      /* Lines showing node text are not in inverse.  Only modelines
+		 have that distinction. */
+	      entry->inverse = 0;
+	    }
+
+	  /* We have done at least one line.  Increment our screen line
+	     index, and check against the bottom of the window. */
+	  if (++line_index == win->height)
+	    break;
+
+	  /* A line has been displayed, and the screen reflects that state.
+	     If there is typeahead pending, then let that typeahead be read
+	     now, instead of continuing with the display. */
+	  if (info_any_buffered_input_p ())
+	    {
+	      free (printed_line);
+	      display_was_interrupted_p = 1;
+	      return;
+	    }
+
+	  /* Reset PL_INDEX to the start of the line. */
+	  pl_index = 0;
+
+	  /* If there are characters from REP left to print, stuff them
+	     into the buffer now. */
+	  if (rep_carried_over)
+	    for (; rep[pl_index]; pl_index++)
+	      printed_line[pl_index] = rep[pl_index];
+
+	  /* If this window has chosen not to wrap lines, skip to the end
+	     of the physical line in the buffer, and start a new line here. */
+	  if (pl_index && (win->flags & W_NoWrap))
+	    {
+	      char *begin;
+
+	      pl_index = 0;
+	      printed_line[0] = '\0';
+
+	      begin = nodetext;
+	      
+	      while ((nodetext < last_node_char) && (*nodetext != '\n'))
+		nodetext++;
+	    }
+	}
+    }
+
+ done_with_node_display:
+  /* We have reached the end of the node or the end of the window.  If it
+     is the end of the node, then clear the lines of the window from here
+     to the end of the window. */
+  for (; line_index < win->height; line_index++)
+    {
+      DISPLAY_LINE *entry = display[line_index + win->first_row];
+
+      /* If this line has text on it then make it go away. */
+      if (entry && entry->textlen)
+	{
+	  entry->textlen = 0;
+	  entry->text[0] = '\0';
+
+	  terminal_goto_xy (0, line_index + win->first_row);
+	  terminal_clear_to_eol ();
+	}
+    }
+
+  /* Finally, if this window has a modeline it might need to be redisplayed.
+     Check the window's modeline against the one in the display, and update
+     if necessary. */
+  if ((win->flags & W_InhibitMode) == 0)
+    {
+      window_make_modeline (win);
+      line_index = win->first_row + win->height;
+
+      /* This display line must both be in inverse, and have the same
+	 contents. */
+      if ((!display[line_index]->inverse) ||
+	  (strcmp (display[line_index]->text, win->modeline) != 0))
+	{
+	  terminal_goto_xy (0, line_index);
+	  terminal_begin_inverse ();
+	  terminal_put_text (win->modeline);
+	  terminal_end_inverse ();
+	  strcpy (display[line_index]->text, win->modeline);
+	  display[line_index]->inverse = 1;
+	  display[line_index]->textlen = strlen (win->modeline);
+	  fflush (stdout);
+	}
+    }
+
+  /* Okay, this window doesn't need updating anymore. */
+  win->flags &= ~W_UpdateWindow;
+  free (printed_line);
+  fflush (stdout);
+}
+
+/* Scroll the region of the_display starting at START, ending at END, and
+   moving the lines AMOUNT lines.  If AMOUNT is less than zero, the lines
+   are moved up in the screen, otherwise down.  Actually, it is possible
+   for no scrolling to take place in the case that the terminal doesn't
+   support it.  This doesn't matter to us. */
+void
+display_scroll_display (start, end, amount)
+     int start, end, amount;
+{
+  register int i, last;
+  DISPLAY_LINE *temp;
+
+  /* If this terminal cannot do scrolling, give up now. */
+  if (!terminal_can_scroll)
+    return;
+
+  /* If there isn't anything displayed on the screen because it is too
+     small, quit now. */
+  if (!the_display[0])
+    return;
+
+  /* If there is typeahead pending, then don't actually do any scrolling. */
+  if (info_any_buffered_input_p ())
+    return;
+
+  /* Do it on the screen. */
+  terminal_scroll_terminal (start, end, amount);
+
+  /* Now do it in the display buffer so our contents match the screen. */
+  if (amount > 0)
+    {
+      last = end + amount;
+
+      /* Shift the lines to scroll right into place. */
+      for (i = 0; i < (end - start); i++)
+	{
+	  temp = the_display[last - i];
+	  the_display[last - i] = the_display[end - i];
+	  the_display[end - i] = temp;
+	}
+
+      /* The lines have been shifted down in the buffer.  Clear all of the
+	 lines that were vacated. */
+      for (i = start; i != (start + amount); i++)
+	{
+	  the_display[i]->text[0] = '\0';
+	  the_display[i]->textlen = 0;
+	  the_display[i]->inverse = 0;
+	}
+    }
+
+  if (amount < 0)
+    {
+      last = start + amount;
+      for (i = 0; i < (end - start); i++)
+	{
+	  temp = the_display[last + i];
+	  the_display[last + i] = the_display[start + i];
+	  the_display[start + i] = temp;
+	}
+
+      /* The lines have been shifted up in the buffer.  Clear all of the
+	 lines that are left over. */
+      for (i = end + amount; i != end; i++)
+	{
+	  the_display[i]->text[0] = '\0';
+	  the_display[i]->textlen = 0;
+	  the_display[i]->inverse = 0;
+	}
+    }
+}
+
+/* Try to scroll lines in WINDOW.  OLD_PAGETOP is the pagetop of WINDOW before
+   having had its line starts recalculated.  OLD_STARTS is the list of line
+   starts that used to appear in this window.  OLD_COUNT is the number of lines
+   that appear in the OLD_STARTS array. */
+void
+display_scroll_line_starts (window, old_pagetop, old_starts, old_count)
+     WINDOW *window;
+     int old_pagetop, old_count;
+     char **old_starts;
+{
+  register int i, old, new;	/* Indices into the line starts arrays. */
+  int last_new, last_old;	/* Index of the last visible line. */
+  int old_first, new_first;	/* Index of the first changed line. */
+  int unchanged_at_top = 0;
+  int already_scrolled = 0;
+
+  /* Locate the first line which was displayed on the old window. */
+  old_first = old_pagetop;
+  new_first = window->pagetop;
+
+  /* Find the last line currently visible in this window. */
+  last_new = window->pagetop + (window->height - 1);
+  if (last_new > window->line_count)
+    last_new = window->line_count - 1;
+
+  /* Find the last line which used to be currently visible in this window. */
+  last_old = old_pagetop + (window->height - 1);
+  if (last_old > old_count)
+    last_old = old_count - 1;
+
+  for (old = old_first, new = new_first;
+       old < last_old && new < last_new;
+       old++, new++)
+    if (old_starts[old] != window->line_starts[new])
+      break;
+    else
+      unchanged_at_top++;
+
+  /* Loop through the old lines looking for a match in the new lines. */
+  for (old = old_first + unchanged_at_top; old < last_old; old++)
+    {
+      for (new = new_first; new < last_new; new++)
+	if (old_starts[old] == window->line_starts[new])
+	  {
+	    /* Find the extent of the matching lines. */
+	    for (i = 0; (old + i) < last_old; i++)
+	      if (old_starts[old + i] != window->line_starts[new + i])
+		break;
+
+	    /* Scroll these lines if there are enough of them. */
+	    {
+	      int start, end, amount;
+
+	      start = (window->first_row
+		       + ((old + already_scrolled) - old_pagetop));
+	      amount = new - (old + already_scrolled);
+	      end = window->first_row + window->height;
+
+	      /* If we are shifting the block of lines down, then the last
+		 AMOUNT lines will become invisible.  Thus, don't bother
+		 scrolling them. */
+	      if (amount > 0)
+		end -= amount;
+
+	      if ((end - start) > 0)
+		{
+		  display_scroll_display (start, end, amount);
+
+		  /* Some lines have been scrolled.  Simulate the scrolling
+		     by offsetting the value of the old index. */
+		  old += i;
+		  already_scrolled += amount;
+		}
+	    }
+	  }
+    }
+}
+
+/* Move the screen cursor to directly over the current character in WINDOW. */
+void
+display_cursor_at_point (window)
+     WINDOW *window;
+{
+  int vpos, hpos;
+
+  vpos = window_line_of_point (window) - window->pagetop + window->first_row;
+  hpos = window_get_cursor_column (window);
+  terminal_goto_xy (hpos, vpos);
+}
+
+/* **************************************************************** */
+/*								    */
+/*		     Functions Static to this File		    */
+/*								    */
+/* **************************************************************** */
+
+/* Make a DISPLAY_LINE ** with width and height. */
+static DISPLAY_LINE **
+make_display (width, height)
+     int width, height;
+{
+  register int i;
+  DISPLAY_LINE **display;
+
+  display = (DISPLAY_LINE **)xmalloc ((1 + height) * sizeof (DISPLAY_LINE *));
+
+  for (i = 0; i < height; i++)
+    {
+      display[i] = (DISPLAY_LINE *)xmalloc (sizeof (DISPLAY_LINE));
+      display[i]->text = (char *)xmalloc (1 + width);
+      display[i]->textlen = 0;
+      display[i]->inverse = 0;
+    }
+  display[i] = (DISPLAY_LINE *)NULL;
+  return (display);
+}
+
+/* Free the storage allocated to DISPLAY. */
+static void
+free_display (display)
+     DISPLAY_LINE **display;
+{
+  register int i;
+  register DISPLAY_LINE *display_line;
+
+  if (!display)
+    return;
+
+  for (i = 0; display_line = display[i]; i++)
+    {
+      free (display_line->text);
+      free (display_line);
+    }
+  free (display);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/display.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,76 @@
+/* display.h -- How the display in Info is done. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_DISPLAY_H_)
+#define _DISPLAY_H_
+
+#include "info-utils.h"
+#include "terminal.h"
+
+typedef struct {
+  char *text;			/* Text of the line as it appears. */
+  int textlen;			/* Printable Length of TEXT. */
+  int inverse;			/* Non-zero means this line is inverse. */
+} DISPLAY_LINE;
+
+/* An array of display lines which tell us what is currently visible on
+   the display.  */
+extern DISPLAY_LINE **the_display;
+
+/* Non-zero means do no output. */
+extern int display_inhibited;
+
+/* Non-zero if we didn't completely redisplay a window. */
+extern int display_was_interrupted_p;
+
+/* Initialize THE_DISPLAY to WIDTH and HEIGHT, with nothing in it. */
+extern void display_initialize_display ();
+
+/* Clear all of the lines in DISPLAY making the screen blank. */
+extern void display_clear_display ();
+
+/* Update the windows pointed to by WINDOWS in THE_DISPLAY.  This actually
+   writes the text on the screen. */
+extern void display_update_display ();
+
+/* Display WIN on THE_DISPLAY.  Unlike display_update_display (), this
+   function only does one window. */
+extern void display_update_one_window ();
+
+/* Move the screen cursor to directly over the current character in WINDOW. */
+extern void display_cursor_at_point ();
+
+/* Scroll the region of the_display starting at START, ending at END, and
+   moving the lines AMOUNT lines.  If AMOUNT is less than zero, the lines
+   are moved up in the screen, otherwise down.  Actually, it is possible
+   for no scrolling to take place in the case that the terminal doesn't
+   support it.  This doesn't matter to us. */
+extern void display_scroll_display ();
+
+/* Try to scroll lines in WINDOW.  OLD_PAGETOP is the pagetop of WINDOW before
+   having had its line starts recalculated.  OLD_STARTS is the list of line
+   starts that used to appear in this window.  OLD_COUNT is the number of lines
+   that appear in the OLD_STARTS array. */
+extern void display_scroll_line_starts ();
+
+#endif /* !_DISPLAY_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/doc.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,58 @@
+/* doc.h -- Structure associating function pointers with documentation. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_DOC_H_)
+#define _DOC_H_
+
+#if !defined (NULL)
+#  define NULL 0x0
+#endif /* !NULL */
+
+#if !defined (__FUNCTION_DEF)
+#  define __FUNCTION_DEF
+typedef int Function ();
+typedef void VFunction ();
+#endif /* _FUNCTION_DEF */
+
+typedef struct {
+  VFunction *func;
+#if defined (NAMED_FUNCTIONS)
+  char *func_name;
+#endif /* NAMED_FUNCTIONS */
+  char *doc;
+} FUNCTION_DOC;
+
+extern FUNCTION_DOC function_doc_array[];
+
+extern char *function_documentation ();
+extern char *key_documentation ();
+extern char *pretty_keyname ();
+extern char *replace_in_documentation ();
+extern void info_document_key ();
+extern void dump_map_to_message_buffer ();
+
+#if defined (NAMED_FUNCTIONS)
+extern char *function_name ();
+extern VFunction *named_function ();
+#endif /* NAMED_FUNCTIONS */
+#endif /* !_DOC_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/dribble.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,71 @@
+/* dribble.c -- Dribble files for Info. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include <stdio.h>
+#include "dribble.h"
+
+/* When non-zero, it is a stream to write all input characters to for the
+   duration of this info session. */
+FILE *info_dribble_file = (FILE *)NULL;
+
+/* Open a dribble file named NAME, perhaps closing an already open one.
+   This sets the global variable INFO_DRIBBLE_FILE to the open stream. */
+void
+open_dribble_file (name)
+     char *name;
+{
+  /* Perhaps close existing dribble file. */
+  close_dribble_file ();
+
+  info_dribble_file = fopen (name, "w");
+
+#if defined (HAVE_SETVBUF)
+  if (info_dribble_file)
+#  if defined (SETVBUF_REVERSED)
+    setvbuf (info_dribble_file, _IONBF, (char *)NULL, 1);
+#  else
+    setvbuf (info_dribble_file, (char *)NULL, _IONBF, 1);
+#  endif /* !SETVBUF_REVERSED */
+#endif /* HAVE_SETVBUF */
+}
+
+/* If there is a dribble file already open, close it. */
+void
+close_dribble_file ()
+{
+  if (info_dribble_file)
+    {
+      fflush (info_dribble_file);
+      fclose (info_dribble_file);
+      info_dribble_file = (FILE *)NULL;
+    }
+}
+
+/* Write some output to our existing dribble file. */
+void
+dribble (byte)
+     unsigned char byte;
+{
+  if (info_dribble_file)
+    fwrite (&byte, sizeof (unsigned char), 1, info_dribble_file);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/dribble.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,41 @@
+/* dribble.h -- Functions and vars declared in dribble.c. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_DRIBBLE_H_)
+#define _DRIBBLE_H_
+
+/* When non-zero, it is a stream to write all input characters to for the
+   duration of this info session. */
+extern FILE *info_dribble_file;
+
+/* Open a dribble file named NAME, perhaps closing an already open one.
+   This sets the global variable INFO_DRIBBLE_FILE to the open stream. */
+extern void open_dribble_file ();
+
+/* If there is a dribble file already open, close it. */
+extern void close_dribble_file ();
+
+/* Write some output to our existing dribble file. */
+extern void dribble ();
+
+#endif /* !_DRIBBLE_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/echo_area.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,1500 @@
+/* echo_area.c -- How to read a line in the echo area. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+
+/* Non-zero means that C-g was used to quit reading input. */
+int info_aborted_echo_area = 0;
+
+/* Non-zero means that the echo area is being used to read input. */
+int echo_area_is_active = 0;
+
+/* The address of the last command executed in the echo area. */
+VFunction *ea_last_executed_command = (VFunction *)NULL;
+
+/* Non-zero means that the last command executed while reading input
+   killed some text. */
+int echo_area_last_command_was_kill = 0;
+
+/* Variables which hold on to the current state of the input line. */
+static char input_line[1 + EA_MAX_INPUT];
+static char *input_line_prompt;
+static int input_line_point;
+static int input_line_beg;
+static int input_line_end;
+static NODE input_line_node = {
+  (char *)NULL, (char *)NULL, (char *)NULL, input_line, EA_MAX_INPUT, 0
+};
+
+static void echo_area_initialize_node ();
+static void push_echo_area (), pop_echo_area ();
+static int echo_area_stack_depth (), echo_area_stack_contains_completions_p ();
+
+static void ea_kill_text ();
+
+/* Non-zero means we force the user to complete. */
+static int echo_area_must_complete_p = 0;
+static int completions_window_p ();
+
+/* If non-null, this is a window which was specifically created to display
+   possible completions output.  We remember it so we can delete it when
+   appropriate. */
+static WINDOW *echo_area_completions_window = (WINDOW *)NULL;
+
+/* Variables which keep track of the window which was active prior to
+   entering the echo area. */
+static WINDOW *calling_window = (WINDOW *)NULL;
+static NODE *calling_window_node = (NODE *)NULL;
+static long calling_window_point = 0;
+static long calling_window_pagetop = 0;
+
+/* Remember the node and pertinent variables of the calling window. */
+static void
+remember_calling_window (window)
+     WINDOW *window;
+{
+  /* Only do this if the calling window is not the completions window, or,
+     if it is the completions window and there is no other window. */
+  if (!completions_window_p (window) ||
+      ((window == windows) && !(window->next)))
+    {
+      calling_window = window;
+      calling_window_node = window->node;
+      calling_window_point = window->point;
+      calling_window_pagetop = window->pagetop;
+    }
+}
+
+/* Restore the caller's window so that it shows the node that it was showing
+   on entry to info_read_xxx_echo_area (). */
+static void
+restore_calling_window ()
+{
+  register WINDOW *win, *compwin = (WINDOW *)NULL;
+
+  /* If the calling window is still visible, and it is the window that
+     we used for completions output, then restore the calling window. */
+  for (win = windows; win; win = win->next)
+    {
+      if (completions_window_p (win))
+	compwin = win;
+
+      if (win == calling_window && win == compwin)
+	{
+	  window_set_node_of_window (calling_window, calling_window_node);
+	  calling_window->point = calling_window_point;
+	  calling_window->pagetop = calling_window_pagetop;
+	  compwin = (WINDOW *)NULL;
+	  break;
+	}
+    }
+
+  /* Delete the completions window if it is still present, it isn't the
+     last window on the screen, and there aren't any prior echo area reads
+     pending which created a completions window. */
+  if (compwin)
+    {
+      if ((compwin != windows || windows->next) &&
+	  !echo_area_stack_contains_completions_p ())
+	{
+	  WINDOW *next;
+	  int pagetop, start, end, amount;
+
+	  next = compwin->next;
+	  if (next)
+	    {
+	      start = next->first_row;
+	      end = start + next->height;
+	      amount = - (compwin->height + 1);
+	      pagetop = next->pagetop;
+	    }
+
+	  info_delete_window_internal (compwin);
+
+	  /* This is not necessary because info_delete_window_internal ()
+	     calls echo_area_inform_of_deleted_window (), which does the
+	     right thing. */
+#if defined (UNNECESSARY)
+	  echo_area_completions_window = (WINDOW *)NULL;
+#endif /* UNNECESSARY */
+
+	  if (next)
+	    {
+	      display_scroll_display (start, end, amount);
+	      next->pagetop = pagetop;
+	      display_update_display (windows);
+	    }
+	}
+    }
+}
+
+/* Set up a new input line with PROMPT. */
+static void
+initialize_input_line (prompt)
+     char *prompt;
+{
+  input_line_prompt = prompt;
+  if (prompt)
+    strcpy (input_line, prompt);
+  else
+    input_line[0] = '\0';
+
+  input_line_beg = input_line_end = input_line_point = strlen (prompt);
+}
+
+static char *
+echo_area_after_read ()
+{
+  char *return_value;
+
+  if (info_aborted_echo_area)
+    {
+      info_aborted_echo_area = 0;
+      return_value = (char *)NULL;
+    }
+  else
+    {
+      if (input_line_beg == input_line_end)
+	return_value = savestring ("");
+      else
+	{
+	  int line_len = input_line_end - input_line_beg;
+	  return_value = (char *) xmalloc (1 + line_len);
+	  strncpy (return_value, &input_line[input_line_beg], line_len);
+	  return_value[line_len] = '\0';
+	}
+    }
+  return (return_value);
+}
+
+/* Read a line of text in the echo area.  Return a malloc ()'ed string,
+   or NULL if the user aborted out of this read.  WINDOW is the currently
+   active window, so that we can restore it when we need to.  PROMPT, if
+   non-null, is a prompt to print before reading the line. */
+char *
+info_read_in_echo_area (window, prompt)
+     WINDOW *window;
+     char *prompt;
+{
+  char *line;
+
+  /* If the echo area is already active, remember the current state. */
+  if (echo_area_is_active)
+    push_echo_area ();
+
+  /* Initialize our local variables. */
+  initialize_input_line (prompt);
+
+  /* Initialize the echo area for the first (but maybe not the last) time. */
+  echo_area_initialize_node ();
+
+  /* Save away the original node of this window, and the window itself,
+     so echo area commands can temporarily use this window. */
+  remember_calling_window (window);
+
+  /* Let the rest of Info know that the echo area is active. */
+  echo_area_is_active++;
+  active_window = the_echo_area;
+
+  /* Read characters in the echo area. */
+  info_read_and_dispatch ();
+
+  echo_area_is_active--;
+
+  /* Restore the original active window and show point in it. */
+  active_window = calling_window;
+  restore_calling_window ();
+  display_cursor_at_point (active_window);
+  fflush (stdout);
+
+  /* Get the value of the line. */
+  line = echo_area_after_read ();
+
+  /* If there is a previous loop waiting for us, restore it now. */
+  if (echo_area_is_active)
+    pop_echo_area ();
+
+  /* Return the results to the caller. */
+  return (line);
+}
+
+/* (re) Initialize the echo area node. */
+static void
+echo_area_initialize_node ()
+{
+  register int i;
+
+  for (i = input_line_end; i < sizeof (input_line); i++)
+    input_line[i] = ' ';
+
+  input_line[i - 1] = '\n';
+  window_set_node_of_window (the_echo_area, &input_line_node);
+  input_line[input_line_end] = '\n';
+}
+
+/* Prepare to read characters in the echo area.  This can initialize the
+   echo area node, but its primary purpose is to side effect the input
+   line buffer contents. */
+void
+echo_area_prep_read ()
+{
+  if (the_echo_area->node != &input_line_node)
+    echo_area_initialize_node ();
+
+  the_echo_area->point = input_line_point;
+  input_line[input_line_end] = '\n';
+  display_update_one_window (the_echo_area);
+  display_cursor_at_point (active_window);
+}
+
+
+/* **************************************************************** */
+/*								    */
+/*		     Echo Area Movement Commands		    */
+/*								    */
+/* **************************************************************** */
+
+DECLARE_INFO_COMMAND (ea_forward, "Move forward a character")
+{
+  if (count < 0)
+    ea_backward (window, -count, key);
+  else
+    {
+      input_line_point += count;
+      if (input_line_point > input_line_end)
+	input_line_point = input_line_end;
+    }
+}
+
+DECLARE_INFO_COMMAND (ea_backward, "Move backward a character")
+{
+  if (count < 0)
+    ea_forward (window, -count, key);
+  else
+    {
+      input_line_point -= count;
+      if (input_line_point < input_line_beg)
+	input_line_point = input_line_beg;
+    }
+}
+
+DECLARE_INFO_COMMAND (ea_beg_of_line, "Move to the start of this line")
+{
+  input_line_point = input_line_beg;
+}
+
+DECLARE_INFO_COMMAND (ea_end_of_line, "Move to the end of this line")
+{
+  input_line_point = input_line_end;
+}
+
+#define alphabetic(c) (islower (c) || isupper (c) || isdigit (c))
+
+/* Move forward a word in the input line. */
+DECLARE_INFO_COMMAND (ea_forward_word, "Move forward a word")
+{
+  int c;
+
+  if (count < 0)
+    ea_backward_word (window, -count, key);
+  else
+    {
+      while (count--)
+	{
+	  if (input_line_point == input_line_end)
+	    return;
+
+	  /* If we are not in a word, move forward until we are in one.
+	     Then, move forward until we hit a non-alphabetic character. */
+	  c = input_line[input_line_point];
+
+	  if (!alphabetic (c))
+	    {
+	      while (++input_line_point < input_line_end)
+		{
+		  c = input_line[input_line_point];
+		  if (alphabetic (c))
+		    break;
+		}
+	    }
+
+	  if (input_line_point == input_line_end)
+	    return;
+
+	  while (++input_line_point < input_line_end)
+	    {
+	      c = input_line[input_line_point];
+	      if (!alphabetic (c))
+		break;
+	    }
+	}
+    }
+}
+
+DECLARE_INFO_COMMAND (ea_backward_word, "Move backward a word")
+{
+  int c;
+
+  if (count < 0)
+    ea_forward_word (window, -count, key);
+  else
+    {
+      while (count--)
+	{
+	  if (input_line_point == input_line_beg)
+	    return;
+
+	  /* Like ea_forward_word (), except that we look at the
+	     characters just before point. */
+
+	  c = input_line[input_line_point - 1];
+
+	  if (!alphabetic (c))
+	    {
+	      while (--input_line_point)
+		{
+		  c = input_line[input_line_point - 1];
+		  if (alphabetic (c))
+		    break;
+		}
+	    }
+
+	  while (input_line_point != input_line_beg)
+	    {
+	      c = input_line[input_line_point - 1];
+	      if (!alphabetic (c))
+		break;
+	      else
+		--input_line_point;
+	    }
+	}
+    }
+}
+
+DECLARE_INFO_COMMAND (ea_delete, "Delete the character under the cursor")
+{
+  register int i;
+
+  if (count < 0)
+    ea_rubout (window, -count, key);
+  else
+    {
+      if (input_line_point == input_line_end)
+	return;
+
+      if (info_explicit_arg || count > 1)
+	{
+	  int orig_point;
+
+	  orig_point = input_line_point;
+	  ea_forward (window, count, key);
+	  ea_kill_text (orig_point, input_line_point);
+	  input_line_point = orig_point;
+	}
+      else
+	{
+	  for (i = input_line_point; i < input_line_end; i++)
+	    input_line[i] = input_line[i + 1];
+
+	  input_line_end--;
+	}
+    }
+}
+
+DECLARE_INFO_COMMAND (ea_rubout, "Delete the character behind the cursor")
+{
+  if (count < 0)
+    ea_delete (window, -count, key);
+  else
+    {
+      int start;
+
+      if (input_line_point == input_line_beg)
+	return;
+
+      start = input_line_point;
+      ea_backward (window, count, key);
+
+      if (info_explicit_arg || count > 1)
+	ea_kill_text (start, input_line_point);
+      else
+	ea_delete (window, count, key);
+    }
+}
+
+DECLARE_INFO_COMMAND (ea_abort, "Cancel or quit operation")
+{
+  /* If any text, just discard it, and restore the calling window's node.
+     If no text, quit. */
+  if (input_line_end != input_line_beg)
+    {
+      terminal_ring_bell ();
+      input_line_end = input_line_point = input_line_beg;
+      if (calling_window->node != calling_window_node)
+	restore_calling_window ();
+    }
+  else
+    info_aborted_echo_area = 1;
+}
+
+DECLARE_INFO_COMMAND (ea_newline, "Accept (or force completion of) this line")
+{
+  /* Stub does nothing.  Simply here to see if it has been executed. */
+}
+
+DECLARE_INFO_COMMAND (ea_quoted_insert, "Insert next character verbatim")
+{
+  unsigned char character;
+
+  character = info_get_another_input_char ();
+  ea_insert (window, count, character);
+}
+
+DECLARE_INFO_COMMAND (ea_insert, "Insert this character")
+{
+  register int i;
+
+  if ((input_line_end + 1) == EA_MAX_INPUT)
+    {
+      terminal_ring_bell ();
+      return;
+    }
+
+  for (i = input_line_end + 1; i != input_line_point; i--)
+    input_line[i] = input_line[i - 1];
+
+  input_line[input_line_point] = key;
+  input_line_point++;
+  input_line_end++;
+}
+
+DECLARE_INFO_COMMAND (ea_tab_insert, "Insert a TAB character")
+{
+  ea_insert (window, count, '\t');
+}
+
+/* Transpose the characters at point.  If point is at the end of the line,
+   then transpose the characters before point. */
+DECLARE_INFO_COMMAND (ea_transpose_chars, "Transpose characters at point")
+{
+  /* Handle conditions that would make it impossible to transpose
+     characters. */
+  if (!count || !input_line_point || (input_line_end - input_line_beg) < 2)
+    return;
+
+  while (count)
+    {
+      int t;
+      if (input_line_point == input_line_end)
+	{
+	  t = input_line[input_line_point - 1];
+
+	  input_line[input_line_point - 1] = input_line[input_line_point - 2];
+	  input_line[input_line_point - 2] = t;
+	}
+      else
+	{
+	  t = input_line[input_line_point];
+
+	  input_line[input_line_point] = input_line[input_line_point - 1];
+	  input_line[input_line_point - 1] = t;
+
+	  if (count < 0 && input_line_point != input_line_beg)
+	    input_line_point--;
+	  else
+	    input_line_point++;
+	}
+
+      if (count < 0)
+	count++;
+      else
+	count--;
+    }
+}
+
+/* **************************************************************** */
+/*								    */
+/*		     Echo Area Killing and Yanking		    */
+/*								    */
+/* **************************************************************** */
+
+static char **kill_ring = (char **)NULL;
+static int kill_ring_index = 0;	/* Number of kills appearing in KILL_RING. */
+static int kill_ring_slots = 0;	/* Number of slots allocated to KILL_RING. */
+static int kill_ring_loc = 0;	/* Location of current yank pointer. */
+
+/* The largest number of kills that we remember at one time. */
+static int max_retained_kills = 15;
+
+DECLARE_INFO_COMMAND (ea_yank, "Yank back the contents of the last kill")
+{
+  register int i;
+  register char *text;
+
+  if (!kill_ring_index)
+    {
+      inform_in_echo_area ("Kill ring is empty");
+      return;
+    }
+
+  text = kill_ring[kill_ring_loc];
+
+  for (i = 0; text[i]; i++)
+    ea_insert (window, 1, text[i]);
+}
+
+/* If the last command was yank, or yank_pop, and the text just before
+   point is identical to the current kill item, then delete that text
+   from the line, rotate the index down, and yank back some other text. */
+DECLARE_INFO_COMMAND (ea_yank_pop, "Yank back a previous kill")
+{
+  register int len;
+
+  if (((ea_last_executed_command != ea_yank) &&
+       (ea_last_executed_command != ea_yank_pop)) ||
+      (kill_ring_index == 0))
+    return;
+
+  len = strlen (kill_ring[kill_ring_loc]);
+
+  /* Delete the last yanked item from the line. */
+  {
+    register int i, counter;
+
+    counter = input_line_end - input_line_point;
+    
+    for (i = input_line_point - len; counter; i++, counter--)
+      input_line[i] = input_line[i + len];
+
+    input_line_end -= len;
+    input_line_point -= len;
+  }
+
+  /* Get a previous kill, and yank that. */
+  kill_ring_loc--;
+  if (kill_ring_loc < 0)
+    kill_ring_loc = kill_ring_index - 1;
+
+  ea_yank (window, count, key);
+}
+
+/* Delete the text from point to end of line. */
+DECLARE_INFO_COMMAND (ea_kill_line, "Kill to the end of the line")
+{
+  if (count < 0)
+    {
+      ea_kill_text (input_line_point, input_line_beg);
+      input_line_point = input_line_beg;
+    }
+  else
+    ea_kill_text (input_line_point, input_line_end);
+}
+
+/* Delete the text from point to beg of line. */
+DECLARE_INFO_COMMAND (ea_backward_kill_line,
+		      "Kill to the beginning of the line")
+{
+  if (count < 0)
+    ea_kill_text (input_line_point, input_line_end);
+  else
+    {
+      ea_kill_text (input_line_point, input_line_beg);
+      input_line_point = input_line_beg;
+    }
+}
+
+/* Delete from point to the end of the current word. */
+DECLARE_INFO_COMMAND (ea_kill_word, "Kill the word following the cursor")
+{
+  int orig_point = input_line_point;
+
+  if (count < 0)
+    ea_backward_kill_word (window, -count, key);
+  else
+    {
+      ea_forward_word (window, count, key);
+
+      if (input_line_point != orig_point)
+	ea_kill_text (orig_point, input_line_point);
+
+      input_line_point = orig_point;
+    }
+}
+
+/* Delete from point to the start of the current word. */
+DECLARE_INFO_COMMAND (ea_backward_kill_word,
+		      "Kill the word preceding the cursor")
+{
+  int orig_point = input_line_point;
+
+  if (count < 0)
+    ea_kill_word (window, -count, key);
+  else
+    {
+      ea_backward_word (window, count, key);
+
+      if (input_line_point != orig_point)
+	ea_kill_text (orig_point, input_line_point);
+    }
+}
+
+/* The way to kill something.  This appends or prepends to the last
+   kill, if the last command was a kill command.  If FROM is less
+   than TO, then the killed text is appended to the most recent kill,
+   otherwise it is prepended.  If the last command was not a kill command,
+   then a new slot is made for this kill. */
+static void
+ea_kill_text (from, to)
+     int from, to;
+{
+  register int i, counter, distance;
+  int killing_backwards, slot;
+  char *killed_text;
+
+  killing_backwards = (from > to);
+
+  /* If killing backwards, reverse the values of FROM and TO. */
+  if (killing_backwards)
+    {
+      int temp = from;
+      from = to;
+      to = temp;
+    }
+
+  /* Remember the text that we are about to delete. */
+  distance = to - from;
+  killed_text = (char *)xmalloc (1 + distance);
+  strncpy (killed_text, &input_line[from], distance);
+  killed_text[distance] = '\0';
+
+  /* Actually delete the text from the line. */
+  counter = input_line_end - to;
+
+  for (i = from; counter; i++, counter--)
+    input_line[i] = input_line[i + distance];
+
+  input_line_end -= distance;
+
+  /* If the last command was a kill, append or prepend the killed text to
+     the last command's killed text. */
+  if (echo_area_last_command_was_kill)
+    {
+      char *old, *new;
+
+      slot = kill_ring_loc;
+      old = kill_ring[slot];
+      new = (char *)xmalloc (1 + strlen (old) + strlen (killed_text));
+
+      if (killing_backwards)
+	{
+	  /* Prepend TEXT to current kill. */
+	  strcpy (new, killed_text);
+	  strcat (new, old);
+	}
+      else
+	{
+	  /* Append TEXT to current kill. */
+	  strcpy (new, old);
+	  strcat (new, killed_text);
+	}
+
+      free (old);
+      free (killed_text);
+      kill_ring[slot] = new;
+    }
+  else
+    {
+      /* Try to store the kill in a new slot, unless that would cause there
+	 to be too many remembered kills. */
+      slot = kill_ring_index;
+
+      if (slot == max_retained_kills)
+	slot = 0;
+
+      if (slot + 1 > kill_ring_slots)
+	kill_ring = (char **) xrealloc
+	  (kill_ring,
+	   (kill_ring_slots += max_retained_kills) * sizeof (char *));
+
+      if (slot != kill_ring_index)
+	free (kill_ring[slot]);
+      else
+	kill_ring_index++;
+
+      kill_ring[slot] = killed_text;
+
+      kill_ring_loc = slot;
+    }
+
+  /* Notice that the last command was a kill. */
+  echo_area_last_command_was_kill++;
+}
+
+/* **************************************************************** */
+/*								    */
+/*			Echo Area Completion			    */
+/*								    */
+/* **************************************************************** */
+
+/* Pointer to an array of REFERENCE to complete over. */
+static REFERENCE **echo_area_completion_items = (REFERENCE **)NULL;
+
+/* Sorted array of REFERENCE * which is the possible completions found in
+   the variable echo_area_completion_items.  If there is only one element,
+   it is the only possible completion. */
+static REFERENCE **completions_found = (REFERENCE **)NULL;
+static int completions_found_index = 0;
+static int completions_found_slots = 0;
+
+/* The lowest common denominator found while completing. */
+static REFERENCE *LCD_completion;
+
+/* Internal functions used by the user calls. */
+static void build_completions (), completions_must_be_rebuilt ();
+
+/* Variable which holds the output of completions. */
+static NODE *possible_completions_output_node = (NODE *)NULL;
+
+static char *compwin_name = "*Completions*";
+
+/* Return non-zero if WINDOW is a window used for completions output. */
+static int
+completions_window_p (window)
+     WINDOW *window;
+{
+  int result = 0;
+
+  if (internal_info_node_p (window->node) &&
+      (strcmp (window->node->nodename, compwin_name) == 0))
+    result = 1;
+
+  return (result);
+}
+
+/* Workhorse for completion readers.  If FORCE is non-zero, the user cannot
+   exit unless the line read completes, or is empty. */
+char *
+info_read_completing_internal (window, prompt, completions, force)
+     WINDOW *window;
+     char *prompt;
+     REFERENCE **completions;
+     int force;
+{
+  char *line;
+
+  /* If the echo area is already active, remember the current state. */
+  if (echo_area_is_active)
+    push_echo_area ();
+
+  echo_area_must_complete_p = force;
+
+  /* Initialize our local variables. */
+  initialize_input_line (prompt);
+
+  /* Initialize the echo area for the first (but maybe not the last) time. */
+  echo_area_initialize_node ();
+
+  /* Save away the original node of this window, and the window itself,
+     so echo area commands can temporarily use this window. */
+  remember_calling_window (window);
+
+  /* Save away the list of items to complete over. */
+  echo_area_completion_items = completions;
+  completions_must_be_rebuilt ();
+
+  active_window = the_echo_area;
+  echo_area_is_active++;
+
+  /* Read characters in the echo area. */
+  while (1)
+    {
+      info_read_and_dispatch ();
+
+      line = echo_area_after_read ();
+
+      /* Force the completion to take place if the user hasn't accepted
+	 a default or aborted, and if FORCE is active. */
+      if (force && line && *line && completions)
+	{
+	  register int i;
+
+	  build_completions ();
+
+	  /* If there is only one completion, then make the line be that
+	     completion. */
+	  if (completions_found_index == 1)
+	    {
+	      free (line);
+	      line = savestring (completions_found[0]->label);
+	      break;
+	    }
+
+	  /* If one of the completions matches exactly, then that is okay, so
+	     return the current line. */
+	  for (i = 0; i < completions_found_index; i++)
+	    if (stricmp (completions_found[i]->label, line) == 0)
+	      {
+		free (line);
+		line = savestring (completions_found[i]->label);
+		break;
+	      }
+
+	  /* If no match, go back and try again. */
+	  if (i == completions_found_index)
+	    {
+	      inform_in_echo_area ("Not complete");
+	      continue;
+	    }
+	}
+      break;
+    }
+  echo_area_is_active--;
+
+  /* Restore the original active window and show point in it. */
+  active_window = calling_window;
+  restore_calling_window ();
+  display_cursor_at_point (active_window);
+  fflush (stdout);
+
+  echo_area_completion_items = (REFERENCE **)NULL;
+  completions_must_be_rebuilt ();
+
+  /* If there is a previous loop waiting for us, restore it now. */
+  if (echo_area_is_active)
+    pop_echo_area ();
+
+  return (line);
+}
+  
+/* Read a line in the echo area with completion over COMPLETIONS. */
+char *
+info_read_completing_in_echo_area (window, prompt, completions)
+     WINDOW *window;
+     char *prompt;
+     REFERENCE **completions;
+{
+  return (info_read_completing_internal (window, prompt, completions, 1));
+}
+
+/* Read a line in the echo area allowing completion over COMPLETIONS, but
+   not requiring it. */
+char *
+info_read_maybe_completing (window, prompt, completions)
+     WINDOW *window;
+     char *prompt;
+     REFERENCE **completions;
+{
+  return (info_read_completing_internal (window, prompt, completions, 0));
+}
+
+DECLARE_INFO_COMMAND (ea_possible_completions, "List possible completions")
+{
+  if (!echo_area_completion_items)
+    {
+      ea_insert (window, count, key);
+      return;
+    }
+
+  build_completions ();
+
+  if (!completions_found_index)
+    {
+      terminal_ring_bell ();
+      inform_in_echo_area ("No completions");
+    }
+  else if ((completions_found_index == 1) && (key != '?'))
+    {
+      inform_in_echo_area ("Sole completion");
+    }
+  else
+    {
+      register int i, l;
+      int limit, count, max_label = 0;
+
+      initialize_message_buffer ();
+      printf_to_message_buffer
+	("There %s %d ", completions_found_index == 1 ? "is" : "are",
+	 completions_found_index);
+      printf_to_message_buffer
+	("completion%s:\n", completions_found_index == 1 ? "" : "s");
+
+      /* Find the maximum length of a label. */
+      for (i = 0; i < completions_found_index; i++)
+	{
+	  int len = strlen (completions_found[i]->label);
+	  if (len > max_label)
+	    max_label = len;
+	}
+
+      max_label += 4;
+
+      /* Find out how many columns we should print in. */
+      limit = calling_window->width / max_label;
+      if (limit != 1 && (limit * max_label == calling_window->width))
+	limit--;
+
+      /* Avoid a possible floating exception.  If max_label > width then
+	 the limit will be 0 and a divide-by-zero fault will result. */
+      if (limit == 0)
+	limit = 1;
+
+      /* How many iterations of the printing loop? */
+      count = (completions_found_index + (limit - 1)) / limit;
+
+      /* Watch out for special case.  If the number of completions is less
+	 than LIMIT, then just do the inner printing loop. */
+      if (completions_found_index < limit)
+	count = 1;
+
+      /* Print the sorted items, up-and-down alphabetically. */
+      for (i = 0; i < count; i++)
+	{
+	  register int j;
+
+	  for (j = 0, l = i; j < limit; j++)
+	    {
+	      if (l >= completions_found_index)
+		break;
+	      else
+		{
+		  char *label;
+		  int printed_length, k;
+
+		  label = completions_found[l]->label;
+		  printed_length = strlen (label);
+		  printf_to_message_buffer ("%s", label);
+
+		  if (j + 1 < limit)
+		    {
+		      for (k = 0; k < max_label - printed_length; k++)
+			printf_to_message_buffer (" ");
+		    }
+		}
+	      l += count;
+	    }
+	  printf_to_message_buffer ("\n");
+	}
+
+      /* Make a new node to hold onto possible completions.  Don't destroy
+	 dangling pointers. */
+      {
+	NODE *temp;
+
+	temp = message_buffer_to_node ();
+	add_gcable_pointer (temp->contents);
+	name_internal_node (temp, compwin_name);
+	possible_completions_output_node = temp;
+      }
+
+      /* Find a suitable window for displaying the completions output.
+	 First choice is an existing window showing completions output.
+	 If there is only one window, and it is large, make another
+	 (smaller) window, and use that one.  Otherwise, use the caller's
+	 window. */
+      {
+	WINDOW *compwin;
+
+	compwin = get_internal_info_window (compwin_name);
+
+	if (!compwin)
+	  {
+	    /* If we can split the window to display most of the completion
+	       items, then do so. */
+	    if (calling_window->height > (count * 2))
+	      {
+		int start, end, pagetop;
+
+		active_window = calling_window;
+
+		/* Perhaps we can scroll this window on redisplay. */
+		start = calling_window->first_row;
+		pagetop = calling_window->pagetop;
+
+		compwin =
+		  window_make_window (possible_completions_output_node);
+		active_window = the_echo_area;
+		window_change_window_height
+		  (compwin, -(compwin->height - (count + 2)));
+
+		window_adjust_pagetop (calling_window);
+		remember_calling_window (calling_window);
+
+#if defined (SPLIT_BEFORE_ACTIVE)
+		/* If the pagetop hasn't changed, scrolling the calling
+		   window is a reasonable thing to do. */
+		if (pagetop == calling_window->pagetop)
+		  {
+		    end = start + calling_window->height;
+		    display_scroll_display
+		      (start, end, calling_window->prev->height + 1);
+		  }
+#else /* !SPLIT_BEFORE_ACTIVE */
+		/* If the pagetop has changed, set the new pagetop here. */
+		if (pagetop != calling_window->pagetop)
+		  {
+		    int newtop = calling_window->pagetop;
+		    calling_window->pagetop = pagetop;
+		    set_window_pagetop (calling_window, newtop);
+		  }
+#endif /* !SPLIT_BEFORE_ACTIVE */
+
+		echo_area_completions_window = compwin;
+		remember_window_and_node (compwin, compwin->node);
+	      }
+	    else
+	      compwin = calling_window;
+	  }
+
+	if (compwin->node != possible_completions_output_node)
+	  {
+	    window_set_node_of_window
+	      (compwin, possible_completions_output_node);
+	    remember_window_and_node (compwin, compwin->node);
+	  }
+
+	display_update_display (windows);
+      }
+    }
+}
+
+DECLARE_INFO_COMMAND (ea_complete, "Insert completion")
+{
+  if (!echo_area_completion_items)
+    {
+      ea_insert (window, count, key);
+      return;
+    }
+
+  /* If KEY is SPC, and we are not forcing completion to take place, simply
+     insert the key. */
+  if (!echo_area_must_complete_p && key == SPC)
+    {
+      ea_insert (window, count, key);
+      return;
+    }
+
+  if (ea_last_executed_command == ea_complete)
+    {
+      /* If the keypress is a SPC character, and we have already tried
+	 completing once, and there are several completions, then check
+	 the batch of completions to see if any continue with a space.
+	 If there are some, insert the space character and continue. */
+      if (key == SPC && completions_found_index > 1)
+	{
+	  register int i, offset;
+
+	  offset = input_line_end - input_line_beg;
+
+	  for (i = 0; i < completions_found_index; i++)
+	    if (completions_found[i]->label[offset] == ' ')
+	      break;
+
+	  if (completions_found[i])
+	    ea_insert (window, 1, ' ');
+	  else
+	    {
+	      ea_possible_completions (window, count, key);
+	      return;
+	    }
+	}
+      else
+	{
+	  ea_possible_completions (window, count, key);
+	  return;
+	}
+    }
+
+  input_line_point = input_line_end;
+  build_completions ();
+
+  if (!completions_found_index)
+    terminal_ring_bell ();
+  else if (LCD_completion->label[0] == '\0')
+    ea_possible_completions (window, count, key);
+  else
+    {
+      register int i;
+      input_line_point = input_line_end = input_line_beg;
+      for (i = 0; LCD_completion->label[i]; i++)
+	ea_insert (window, 1, LCD_completion->label[i]);
+    }
+}
+
+/* Utility REFERENCE used to store possible LCD. */
+static REFERENCE LCD_reference = { (char *)NULL, (char *)NULL, (char *)NULL };
+
+static void remove_completion_duplicates ();
+
+/* Variables which remember the state of the most recent call
+   to build_completions (). */
+static char *last_completion_request = (char *)NULL;
+static REFERENCE **last_completion_items = (REFERENCE **)NULL;
+
+/* How to tell the completion builder to reset internal state. */
+static void
+completions_must_be_rebuilt ()
+{
+  maybe_free (last_completion_request);
+  last_completion_request = (char *)NULL;
+  last_completion_items = (REFERENCE **)NULL;
+}
+
+/* Build a list of possible completions from echo_area_completion_items,
+   and the contents of input_line. */
+static void
+build_completions ()
+{
+  register int i, len;
+  register REFERENCE *entry;
+  char *request;
+  int informed_of_lengthy_job = 0;
+
+  /* If there are no items to complete over, exit immediately. */
+  if (!echo_area_completion_items)
+    {
+      completions_found_index = 0;
+      LCD_completion = (REFERENCE *)NULL;
+      return;
+    }
+
+  /* Check to see if this call to build completions is the same as the last
+     call to build completions. */
+  len = input_line_end - input_line_beg;
+  request = (char *)xmalloc (1 + len);
+  strncpy (request, &input_line[input_line_beg], len);
+  request[len] = '\0';
+
+  if (last_completion_request && last_completion_items &&
+      last_completion_items == echo_area_completion_items &&
+      (strcmp (last_completion_request, request) == 0))
+    {
+      free (request);
+      return;
+    }
+
+  maybe_free (last_completion_request);
+  last_completion_request = request;
+  last_completion_items = echo_area_completion_items;
+
+  /* Always start at the beginning of the list. */
+  completions_found_index = 0;
+  LCD_completion = (REFERENCE *)NULL;
+
+  for (i = 0; entry = echo_area_completion_items[i]; i++)
+    {
+      if (strnicmp (request, entry->label, len) == 0)
+	add_pointer_to_array (entry, completions_found_index,
+			      completions_found, completions_found_slots,
+			      20, REFERENCE *);
+
+      if (!informed_of_lengthy_job && completions_found_index > 100)
+	{
+	  informed_of_lengthy_job = 1;
+	  window_message_in_echo_area ("Building completions...");
+	}
+    }
+
+  if (!completions_found_index)
+    return;
+
+  /* Sort and prune duplicate entries from the completions array. */
+  remove_completion_duplicates ();
+
+  /* If there is only one completion, just return that. */
+  if (completions_found_index == 1)
+    {
+      LCD_completion = completions_found[0];
+      return;
+    }
+
+  /* Find the least common denominator. */
+  {
+    long shortest = 100000;
+
+    for (i = 1; i < completions_found_index; i++)
+      {
+	register int j;
+	int c1, c2;
+
+	for (j = 0;
+	     (c1 = info_tolower (completions_found[i - 1]->label[j])) &&
+	     (c2 = info_tolower (completions_found[i]->label[j]));
+	     j++)
+	  if (c1 != c2)
+	    break;
+
+	if (shortest > j)
+	  shortest = j;
+      }
+
+    maybe_free (LCD_reference.label);
+    LCD_reference.label = (char *)xmalloc (1 + shortest);
+    strncpy (LCD_reference.label, completions_found[0]->label, shortest);
+    LCD_reference.label[shortest] = '\0';
+    LCD_completion = &LCD_reference;
+  }
+
+  if (informed_of_lengthy_job)
+    echo_area_initialize_node ();
+}
+
+/* Function called by qsort. */
+static int
+compare_references (entry1, entry2)
+     REFERENCE **entry1, **entry2;
+{
+  return (stricmp ((*entry1)->label, (*entry2)->label));
+}
+
+/* Prune duplicate entries from COMPLETIONS_FOUND. */
+static void
+remove_completion_duplicates ()
+{
+  register int i, j;
+  REFERENCE **temp;
+  int newlen;
+
+  if (!completions_found_index)
+    return;
+
+  /* Sort the items. */
+  qsort (completions_found, completions_found_index, sizeof (REFERENCE *),
+	 compare_references);
+
+  for (i = 0, newlen = 1; i < completions_found_index - 1; i++)
+    {
+      if (strcmp (completions_found[i]->label,
+		  completions_found[i + 1]->label) == 0)
+	completions_found[i] = (REFERENCE *)NULL;
+      else
+	newlen++;
+    }
+
+  /* We have marked all the dead slots.  It is faster to copy the live slots
+     twice than to prune the dead slots one by one. */
+  temp = (REFERENCE **)xmalloc ((1 + newlen) * sizeof (REFERENCE *));
+  for (i = 0, j = 0; i < completions_found_index; i++)
+    if (completions_found[i])
+      temp[j++] = completions_found[i];
+
+  for (i = 0; i < newlen; i++)
+    completions_found[i] = temp[i];
+
+  completions_found[i] = (REFERENCE *)NULL;
+  completions_found_index = newlen;
+  free (temp);
+}
+
+/* Scroll the "other" window.  If there is a window showing completions, scroll
+   that one, otherwise scroll the window which was active on entering the read
+   function. */
+DECLARE_INFO_COMMAND (ea_scroll_completions_window, "Scroll the completions window")
+{
+  WINDOW *compwin;
+  int old_pagetop;
+
+  compwin = get_internal_info_window (compwin_name);
+
+  if (!compwin)
+    compwin = calling_window;
+
+  old_pagetop = compwin->pagetop;
+
+  /* Let info_scroll_forward () do the work, and print any messages that
+     need to be displayed. */
+  info_scroll_forward (compwin, count, key);
+}
+
+/* Function which gets called when an Info window is deleted while the
+   echo area is active.  WINDOW is the window which has just been deleted. */
+void
+echo_area_inform_of_deleted_window (window)
+     WINDOW *window;
+{
+  /* If this is the calling_window, forget what we remembered about it. */
+  if (window == calling_window)
+    {
+      if (active_window != the_echo_area)
+	remember_calling_window (active_window);
+      else
+	remember_calling_window (windows);
+    }
+
+  /* If this window was the echo_area_completions_window, then notice that
+     the window has been deleted. */
+  if (window == echo_area_completions_window)
+    echo_area_completions_window = (WINDOW *)NULL;
+}
+
+/* **************************************************************** */
+/*								    */
+/*		   Pushing and Popping the Echo Area		    */
+/*								    */
+/* **************************************************************** */
+
+/* Push and Pop the echo area. */
+typedef struct {
+  char *line;
+  char *prompt;
+  REFERENCE **comp_items;
+  int point, beg, end;
+  int must_complete;
+  NODE node;
+  WINDOW *compwin;
+} PUSHED_EA;
+
+static PUSHED_EA **pushed_echo_areas = (PUSHED_EA **)NULL;
+static int pushed_echo_areas_index = 0;
+static int pushed_echo_areas_slots = 0;
+
+/* Pushing the echo_area has a side effect of zeroing the completion_items. */
+static void
+push_echo_area ()
+{
+  PUSHED_EA *pushed;
+
+  pushed = (PUSHED_EA *)xmalloc (sizeof (PUSHED_EA));
+  pushed->line = savestring (input_line);
+  pushed->prompt = input_line_prompt;
+  pushed->point = input_line_point;
+  pushed->beg = input_line_beg;
+  pushed->end = input_line_end;
+  pushed->node = input_line_node;
+  pushed->comp_items = echo_area_completion_items;
+  pushed->must_complete = echo_area_must_complete_p;
+  pushed->compwin = echo_area_completions_window;
+
+  add_pointer_to_array (pushed, pushed_echo_areas_index, pushed_echo_areas,
+			pushed_echo_areas_slots, 4, PUSHED_EA *);
+
+  echo_area_completion_items = (REFERENCE **)NULL;
+}
+
+static void
+pop_echo_area ()
+{
+  PUSHED_EA *popped;
+
+  popped = pushed_echo_areas[--pushed_echo_areas_index];
+
+  strcpy (input_line, popped->line);
+  free (popped->line);
+  input_line_prompt = popped->prompt;
+  input_line_point = popped->point;
+  input_line_beg = popped->beg;
+  input_line_end = popped->end;
+  input_line_node = popped->node;
+  echo_area_completion_items = popped->comp_items;
+  echo_area_must_complete_p = popped->must_complete;
+  echo_area_completions_window = popped->compwin;
+  completions_must_be_rebuilt ();
+
+  /* If the completion window no longer exists, forget about it. */
+  if (echo_area_completions_window)
+    {
+      register WINDOW *win;
+
+      for (win = windows; win; win = win->next)
+	if (echo_area_completions_window == win)
+	  break;
+
+      /* If the window wasn't found, then it has already been deleted. */
+      if (!win)
+	echo_area_completions_window = (WINDOW *)NULL;
+    }
+
+  free (popped);
+}
+
+static int
+echo_area_stack_depth ()
+{
+  return (pushed_echo_areas_index);
+}
+
+/* Returns non-zero if any of the prior stacked calls to read in the echo
+   area produced a completions window. */
+static int
+echo_area_stack_contains_completions_p ()
+{
+  register int i;
+
+  for (i = 0; i < pushed_echo_areas_index; i++)
+    if (pushed_echo_areas[i]->compwin)
+      return (1);
+
+  return (0);
+}
+
+/* **************************************************************** */
+/*								    */
+/*	       Error Messages While Reading in Echo Area	    */
+/*								    */
+/* **************************************************************** */
+
+#if defined (HAVE_SYS_TIME_H)
+#  include <sys/time.h>
+#  define HAVE_STRUCT_TIMEVAL
+#endif /* HAVE_SYS_TIME_H */
+
+static void
+pause_or_input ()
+{
+#if defined (FD_SET)
+  struct timeval timer;
+  fd_set readfds;
+  int ready;
+
+  FD_ZERO (&readfds);
+  FD_SET (fileno (stdin), &readfds);
+  timer.tv_sec = 2;
+  timer.tv_usec = 750;
+  ready = select (1, &readfds, (fd_set *)NULL, (fd_set *)NULL, &timer);
+#endif /* FD_SET */
+}
+
+/* Print MESSAGE right after the end of the current line, and wait
+   for input or 2.75 seconds, whichever comes first.  Then flush the
+   informational message that was printed. */
+void
+inform_in_echo_area (message)
+     char *message;
+{
+  register int i;
+  char *text;
+
+  text = savestring (message);
+  for (i = 0; text[i] && text[i] != '\n'; i++);
+  text[i] = '\0';
+
+  echo_area_initialize_node ();
+  sprintf (&input_line[input_line_end], "%s[%s]\n",
+	   echo_area_is_active ? " ": "", text);
+  free (text);
+  the_echo_area->point = input_line_point;
+  display_update_one_window (the_echo_area);
+  display_cursor_at_point (active_window);
+  fflush (stdout);
+  pause_or_input ();
+  echo_area_initialize_node ();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/echo_area.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,63 @@
+/* echo_area.h -- Functions used in reading information from the echo area. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_ECHO_AREA_H_)
+#define _ECHO_AREA_H_
+
+#define EA_MAX_INPUT 256
+
+extern int echo_area_is_active, info_aborted_echo_area;
+
+/* Non-zero means that the last command executed while reading input
+   killed some text. */
+extern int echo_area_last_command_was_kill;
+
+extern void inform_in_echo_area (), echo_area_inform_of_deleted_window ();
+extern void echo_area_prep_read ();
+extern VFunction *ea_last_executed_command;
+
+/* Read a line of text in the echo area.  Return a malloc ()'ed string,
+   or NULL if the user aborted out of this read.  WINDOW is the currently
+   active window, so that we can restore it when we need to.  PROMPT, if
+   non-null, is a prompt to print before reading the line. */
+extern char *info_read_in_echo_area ();
+
+/* Read a line in the echo area with completion over COMPLETIONS.
+   Takes arguments of WINDOW, PROMPT, and COMPLETIONS, a REFERENCE **. */
+char *info_read_completing_in_echo_area ();
+
+/* Read a line in the echo area allowing completion over COMPLETIONS, but
+   not requiring it.  Takes arguments of WINDOW, PROMPT, and COMPLETIONS,
+   a REFERENCE **. */
+extern char *info_read_maybe_completing ();
+
+extern void ea_insert (), ea_quoted_insert ();
+extern void ea_beg_of_line (), ea_backward (), ea_delete (), ea_end_of_line ();
+extern void ea_forward (), ea_abort (), ea_rubout (), ea_complete ();
+extern void ea_newline (), ea_kill_line (), ea_transpose_chars ();
+extern void ea_yank (), ea_tab_insert (), ea_possible_completions ();
+extern void ea_backward_word (), ea_kill_word (), ea_forward_word ();
+extern void ea_yank_pop (), ea_backward_kill_word ();
+extern void ea_scroll_completions_window ();
+
+#endif /* _ECHO_AREA_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/filesys.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,624 @@
+/* filesys.c -- File system specific functions for hacking this system. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/errno.h>
+#include "general.h"
+#include "tilde.h"
+#include "filesys.h"
+
+#if !defined (O_RDONLY)
+#if defined (HAVE_SYS_FCNTL_H)
+#include <sys/fcntl.h>
+#else /* !HAVE_SYS_FCNTL_H */
+#include <fcntl.h>
+#endif /* !HAVE_SYS_FCNTL_H */
+#endif /* !O_RDONLY */
+
+#if !defined (errno)
+extern int errno;
+#endif /* !errno */
+
+/* Found in info-utils.c. */
+extern char *filename_non_directory ();
+
+#if !defined (BUILDING_LIBRARY)
+/* Found in session.c */
+extern int info_windows_initialized_p;
+
+/* Found in window.c. */
+extern void message_in_echo_area (), unmessage_in_echo_area ();
+#endif /* !BUILDING_LIBRARY */
+
+/* Local to this file. */
+static char *info_file_in_path (), *lookup_info_filename ();
+static void remember_info_filename (), maybe_initialize_infopath ();
+
+#if !defined (NULL)
+#  define NULL 0x0
+#endif /* !NULL */
+
+typedef struct {
+  char *suffix;
+  char *decompressor;
+} COMPRESSION_ALIST;
+
+static char *info_suffixes[] = {
+  "",
+  ".info",
+  "-info",
+  (char *)NULL
+};
+
+static COMPRESSION_ALIST compress_suffixes[] = {
+  { ".Z", "uncompress" },
+  { ".Y", "unyabba" },
+  { ".z", "gunzip" },
+  { (char *)NULL, (char *)NULL }
+};
+
+/* The path on which we look for info files.  You can initialize this
+   from the environment variable INFOPATH if there is one, or you can
+   call info_add_path () to add paths to the beginning or end of it.
+   You can call zap_infopath () to make the path go away. */
+char *infopath = (char *)NULL;
+static int infopath_size = 0;
+
+/* Expand the filename in PARTIAL to make a real name for this operating
+   system.  This looks in INFO_PATHS in order to find the correct file.
+   If it can't find the file, it returns NULL. */
+static char *local_temp_filename = (char *)NULL;
+static int local_temp_filename_size = 0;
+
+char *
+info_find_fullpath (partial)
+     char *partial;
+{
+  int initial_character;
+  char *temp;
+
+  filesys_error_number = 0;
+
+  maybe_initialize_infopath ();
+
+  if (partial && (initial_character = *partial))
+    {
+      char *expansion;
+
+      expansion = lookup_info_filename (partial);
+
+      if (expansion)
+	return (expansion);
+
+      /* If we have the full path to this file, we still may have to add
+	 various extensions to it.  I guess we have to stat this file
+	 after all. */
+      if (initial_character == '/')
+	temp = info_file_in_path (partial + 1, "/");
+      else if (initial_character == '~')
+	{
+	  expansion = tilde_expand_word (partial);
+	  if (*expansion == '/')
+	    {
+	      temp = info_file_in_path (expansion + 1, "/");
+	      free (expansion);
+	    }
+	  else
+	    temp = expansion;
+	}
+      else if (initial_character == '.' &&
+	       (partial[1] == '/' || (partial[1] == '.' && partial[2] == '/')))
+	{
+	  if (local_temp_filename_size < 1024)
+	    local_temp_filename = (char *)xrealloc
+	      (local_temp_filename, (local_temp_filename_size = 1024));
+#if defined (HAVE_GETCWD)
+	  if (!getcwd (local_temp_filename, local_temp_filename_size))
+#else /*  !HAVE_GETCWD */
+	  if (!getwd (local_temp_filename))
+#endif /* !HAVE_GETCWD */
+	    {
+	      filesys_error_number = errno;
+	      return (partial);
+	    }
+
+	  strcat (local_temp_filename, "/");
+	  strcat (local_temp_filename, partial);
+	  return (local_temp_filename);
+	}
+      else
+	temp = info_file_in_path (partial, infopath);
+
+      if (temp)
+	{
+	  remember_info_filename (partial, temp);
+	  if (strlen (temp) > local_temp_filename_size)
+	    local_temp_filename = (char *) xrealloc
+	      (local_temp_filename,
+	       (local_temp_filename_size = (50 + strlen (temp))));
+	  strcpy (local_temp_filename, temp);
+	  free (temp);
+	  return (local_temp_filename);
+	}
+    }
+  return (partial);
+}
+
+/* Scan the list of directories in PATH looking for FILENAME.  If we find
+   one that is a regular file, return it as a new string.  Otherwise, return
+   a NULL pointer. */
+static char *
+info_file_in_path (filename, path)
+     char *filename, *path;
+{
+  struct stat finfo;
+  char *temp_dirname;
+  int statable, dirname_index;
+
+  dirname_index = 0;
+
+  while (temp_dirname = extract_colon_unit (path, &dirname_index))
+    {
+      register int i, pre_suffix_length;
+      char *temp;
+
+      /* Expand a leading tilde if one is present. */
+      if (*temp_dirname == '~')
+	{
+	  char *expanded_dirname;
+
+	  expanded_dirname = tilde_expand_word (temp_dirname);
+	  free (temp_dirname);
+	  temp_dirname = expanded_dirname;
+	}
+
+      temp = (char *)xmalloc (30 + strlen (temp_dirname) + strlen (filename));
+      strcpy (temp, temp_dirname);
+      if (temp[(strlen (temp)) - 1] != '/')
+	strcat (temp, "/");
+      strcat (temp, filename);
+
+      pre_suffix_length = strlen (temp);
+
+      free (temp_dirname);
+
+      for (i = 0; info_suffixes[i]; i++)
+	{
+	  strcpy (temp + pre_suffix_length, info_suffixes[i]);
+
+	  statable = (stat (temp, &finfo) == 0);
+
+	  /* If we have found a regular file, then use that.  Else, if we
+	     have found a directory, look in that directory for this file. */
+	  if (statable)
+	    {
+	      if (S_ISREG (finfo.st_mode))
+		{
+		  return (temp);
+		}
+	      else if (S_ISDIR (finfo.st_mode))
+		{
+		  char *newpath, *filename_only, *newtemp;
+
+		  newpath = savestring (temp);
+		  filename_only = filename_non_directory (filename);
+		  newtemp = info_file_in_path (filename_only, newpath);
+
+		  free (newpath);
+		  if (newtemp)
+		    {
+		      free (temp);
+		      return (newtemp);
+		    }
+		}
+	    }
+	  else
+	    {
+	      /* Add various compression suffixes to the name to see if
+		 the file is present in compressed format. */
+	      register int j, pre_compress_suffix_length;
+
+	      pre_compress_suffix_length = strlen (temp);
+
+	      for (j = 0; compress_suffixes[j].suffix; j++)
+		{
+		  strcpy (temp + pre_compress_suffix_length,
+			  compress_suffixes[j].suffix);
+
+		  statable = (stat (temp, &finfo) == 0);
+		  if (statable && (S_ISREG (finfo.st_mode)))
+		    return (temp);
+		}
+	    }
+	}
+      free (temp);
+    }
+  return ((char *)NULL);
+}
+
+/* Given a string containing units of information separated by colons,
+   return the next one pointed to by IDX, or NULL if there are no more.
+   Advance IDX to the character after the colon. */
+char *
+extract_colon_unit (string, idx)
+     char *string;
+     int *idx;
+{
+  register int i, start;
+
+  i = start = *idx;
+  if ((i >= strlen (string)) || !string)
+    return ((char *) NULL);
+
+  while (string[i] && string[i] != ':')
+    i++;
+  if (i == start)
+    {
+      return ((char *) NULL);
+    }
+  else
+    {
+      char *value = (char *) xmalloc (1 + (i - start));
+      strncpy (value, &string[start], (i - start));
+      value[i - start] = '\0';
+      if (string[i])
+	++i;
+      *idx = i;
+      return (value);
+    }
+}
+
+/* A structure which associates a filename with its expansion. */
+typedef struct {
+  char *filename;
+  char *expansion;
+} FILENAME_LIST;
+
+/* An array of remembered arguments and results. */
+static FILENAME_LIST **names_and_files = (FILENAME_LIST **)NULL;
+static int names_and_files_index = 0;
+static int names_and_files_slots = 0;
+
+/* Find the result for having already called info_find_fullpath () with
+   FILENAME. */
+static char *
+lookup_info_filename (filename)
+     char *filename;
+{
+  if (filename && names_and_files)
+    {
+      register int i;
+      for (i = 0; names_and_files[i]; i++)
+	{
+	  if (strcmp (names_and_files[i]->filename, filename) == 0)
+	    return (names_and_files[i]->expansion);
+	}
+    }
+  return (char *)NULL;;
+}
+
+/* Add a filename and its expansion to our list. */
+static void
+remember_info_filename (filename, expansion)
+     char *filename, *expansion;
+{
+  FILENAME_LIST *new;
+
+  if (names_and_files_index + 2 > names_and_files_slots)
+    {
+      int alloc_size;
+      names_and_files_slots += 10;
+
+      alloc_size = names_and_files_slots * sizeof (FILENAME_LIST *);
+
+      names_and_files =
+	(FILENAME_LIST **) xrealloc (names_and_files, alloc_size);
+    }
+
+  new = (FILENAME_LIST *)xmalloc (sizeof (FILENAME_LIST));
+  new->filename = savestring (filename);
+  new->expansion = expansion ? savestring (expansion) : (char *)NULL;
+
+  names_and_files[names_and_files_index++] = new;
+  names_and_files[names_and_files_index] = (FILENAME_LIST *)NULL;
+}
+
+static void
+maybe_initialize_infopath ()
+{
+  if (!infopath_size)
+    {
+      infopath = (char *)
+	xmalloc (infopath_size = (1 + strlen (DEFAULT_INFOPATH)));
+
+      strcpy (infopath, DEFAULT_INFOPATH);
+    }
+}
+
+/* Add PATH to the list of paths found in INFOPATH.  2nd argument says
+   whether to put PATH at the front or end of INFOPATH. */
+void
+info_add_path (path, where)
+     char *path;
+     int where;
+{
+  int len;
+
+  if (!infopath)
+    {
+      infopath = (char *)xmalloc (infopath_size = 200 + strlen (path));
+      infopath[0] = '\0';
+    }
+
+  len = strlen (path) + strlen (infopath);
+
+  if (len + 2 >= infopath_size)
+    infopath = (char *)xrealloc (infopath, (infopath_size += (2 * len) + 2));
+
+  if (!*infopath)
+    strcpy (infopath, path);
+  else if (where == INFOPATH_APPEND)
+    {
+      strcat (infopath, ":");
+      strcat (infopath, path);
+    }
+  else if (where == INFOPATH_PREPEND)
+    {
+      char *temp = savestring (infopath);
+      strcpy (infopath, path);
+      strcat (infopath, ":");
+      strcat (infopath, temp);
+      free (temp);
+    }
+}
+
+/* Make INFOPATH have absolutely nothing in it. */
+void
+zap_infopath ()
+{
+  if (infopath)
+    free (infopath);
+
+  infopath = (char *)NULL;
+  infopath_size = 0;
+}
+
+/* Read the contents of PATHNAME, returning a buffer with the contents of
+   that file in it, and returning the size of that buffer in FILESIZE.
+   FINFO is a stat struct which has already been filled in by the caller.
+   If the file cannot be read, return a NULL pointer. */
+char *
+filesys_read_info_file (pathname, filesize, finfo)
+     char *pathname;
+     long *filesize;
+     struct stat *finfo;
+{
+  *filesize = filesys_error_number = 0;
+
+  if (compressed_filename_p (pathname))
+    return (filesys_read_compressed (pathname, filesize, finfo));
+  else
+    {
+      int descriptor;
+      char *contents;
+
+      descriptor = open (pathname, O_RDONLY, 0666);
+
+      /* If the file couldn't be opened, give up. */
+      if (descriptor < 0)
+	{
+	  filesys_error_number = errno;
+	  return ((char *)NULL);
+	}
+
+      /* Try to read the contents of this file. */
+      contents = (char *)xmalloc (1 + finfo->st_size);
+      if ((read (descriptor, contents, finfo->st_size)) != finfo->st_size)
+	{
+	  filesys_error_number = errno;
+	  close (descriptor);
+	  free (contents);
+	  return ((char *)NULL);
+	}
+
+      close (descriptor);
+
+      *filesize = finfo->st_size;
+      return (contents);
+    }
+}
+
+/* Typically, pipe buffers are 4k. */
+#define BASIC_PIPE_BUFFER (4 * 1024)
+
+/* We use some large multiple of that. */
+#define FILESYS_PIPE_BUFFER_SIZE (16 * BASIC_PIPE_BUFFER)
+
+char *
+filesys_read_compressed (pathname, filesize, finfo)
+     char *pathname;
+     long *filesize;
+     struct stat *finfo;
+{
+  FILE *stream;
+  char *command, *decompressor;
+  char *contents = (char *)NULL;
+
+  *filesize = filesys_error_number = 0;
+
+  decompressor = filesys_decompressor_for_file (pathname);
+
+  if (!decompressor)
+    return ((char *)NULL);
+
+  command = (char *)xmalloc (10 + strlen (pathname) + strlen (decompressor));
+  sprintf (command, "%s < %s", decompressor, pathname);
+
+#if !defined (BUILDING_LIBRARY)
+  if (info_windows_initialized_p)
+    {
+      char *temp;
+
+      temp = (char *)xmalloc (5 + strlen (command));
+      sprintf (temp, "%s...", command);
+      message_in_echo_area ("%s", temp);
+      free (temp);
+    }
+#endif /* !BUILDING_LIBRARY */
+
+  stream = popen (command, "r");
+  free (command);
+
+  /* Read chunks from this file until there are none left to read. */
+  if (stream)
+    {
+      int offset, size;
+      char *chunk;
+    
+      offset = size = 0;
+      chunk = (char *)xmalloc (FILESYS_PIPE_BUFFER_SIZE);
+
+      while (1)
+	{
+	  int bytes_read;
+
+	  bytes_read = fread (chunk, 1, FILESYS_PIPE_BUFFER_SIZE, stream);
+
+	  if (bytes_read + offset >= size)
+	    contents = (char *)xrealloc
+	      (contents, size += (2 * FILESYS_PIPE_BUFFER_SIZE));
+
+	  memcpy (contents + offset, chunk, bytes_read);
+	  offset += bytes_read;
+	  if (bytes_read != FILESYS_PIPE_BUFFER_SIZE)
+	    break;
+	}
+
+      free (chunk);
+      pclose (stream);
+      contents = (char *)xrealloc (contents, offset + 1);
+      *filesize = offset;
+    }
+  else
+    {
+      filesys_error_number = errno;
+    }
+
+#if !defined (BUILDING_LIBARARY)
+  if (info_windows_initialized_p)
+    unmessage_in_echo_area ();
+#endif /* !BUILDING_LIBRARY */
+  return (contents);
+}
+
+/* Return non-zero if FILENAME belongs to a compressed file. */
+int
+compressed_filename_p (filename)
+     char *filename;
+{
+  char *decompressor;
+
+  /* Find the final extension of this filename, and see if it matches one
+     of our known ones. */
+  decompressor = filesys_decompressor_for_file (filename);
+
+  if (decompressor)
+    return (1);
+  else
+    return (0);
+}
+
+/* Return the command string that would be used to decompress FILENAME. */
+char *
+filesys_decompressor_for_file (filename)
+     char *filename;
+{
+  register int i;
+  char *extension = (char *)NULL;
+
+  /* Find the final extension of FILENAME, and see if it appears in our
+     list of known compression extensions. */
+  for (i = strlen (filename) - 1; i > 0; i--)
+    if (filename[i] == '.')
+      {
+	extension = filename + i;
+	break;
+      }
+
+  if (!extension)
+    return ((char *)NULL);
+
+  for (i = 0; compress_suffixes[i].suffix; i++)
+    if (strcmp (extension, compress_suffixes[i].suffix) == 0)
+      return (compress_suffixes[i].decompressor);
+
+  return ((char *)NULL);
+}
+
+/* The number of the most recent file system error. */
+int filesys_error_number = 0;
+
+#if !defined (HAVE_STRERROR)
+extern char *sys_errlist[];
+extern int sys_nerr;
+
+char *
+strerror (num)
+     int num;
+{
+  if (num >= sys_nerr)
+    return ("");
+  else
+    return (sys_errlist[num]);
+}
+#endif /* !HAVE_STRERROR */
+
+/* A function which returns a pointer to a static buffer containing
+   an error message for FILENAME and ERROR_NUM. */
+static char *errmsg_buf = (char *)NULL;
+static int errmsg_buf_size = 0;
+
+char *
+filesys_error_string (filename, error_num)
+     char *filename;
+     int error_num;
+{
+  int len;
+  char *result;
+
+  if (error_num == 0)
+    return ((char *)NULL);
+
+  result = strerror (error_num);
+
+  len = 4 + strlen (filename) + strlen (result);
+  if (len >= errmsg_buf_size)
+    errmsg_buf = (char *)xrealloc (errmsg_buf, (errmsg_buf_size = 2 + len));
+
+  sprintf (errmsg_buf, "%s: %s", filename, result);
+  return (errmsg_buf);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/filesys.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,84 @@
+/* filesys.h -- External declarations of functions and vars in filesys.c. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_FILESYS_H_)
+#define _FILESYS_H_
+
+/* The path on which we look for info files.  You can initialize this
+   from the environment variable INFOPATH if there is one, or you can
+   call info_add_path () to add paths to the beginning or end of it. */
+extern char *infopath;
+
+/* Make INFOPATH have absolutely nothing in it. */
+extern void zap_infopath ();
+
+/* Add PATH to the list of paths found in INFOPATH.  2nd argument says
+   whether to put PATH at the front or end of INFOPATH. */
+extern void info_add_path ();
+
+/* Defines that are passed along with the pathname to info_add_path (). */
+#define INFOPATH_PREPEND 0
+#define INFOPATH_APPEND  1
+
+/* Expand the filename in PARTIAL to make a real name for this operating
+   system.  This looks in INFO_PATHS in order to find the correct file.
+   If it can't find the file, it returns NULL. */
+extern char *info_find_fullpath ();
+
+/* Read the contents of PATHNAME, returning a buffer with the contents of
+   that file in it, and returning the size of that buffer in FILESIZE.
+   FINFO is a stat struct which has already been filled in by the caller.
+   If the file cannot be read, return a NULL pointer. */
+extern char *filesys_read_info_file ();
+extern char *filesys_read_compressed ();
+
+/* Return the command string that would be used to decompress FILENAME. */
+extern char *filesys_decompressor_for_file ();
+extern int compressed_filename_p ();
+
+/* A function which returns a pointer to a static buffer containing
+   an error message for FILENAME and ERROR_NUM. */
+extern char *filesys_error_string ();
+
+/* The number of the most recent file system error. */
+extern int filesys_error_number;
+
+/* Given a string containing units of information separated by colons,
+   return the next one pointed to by IDX, or NULL if there are no more.
+   Advance IDX to the character after the colon. */
+extern char *extract_colon_unit ();
+
+/* The default value of INFOPATH. */
+#if !defined (DEFAULT_INFOPATH)
+#  define DEFAULT_INFOPATH "/usr/gnu/info:/local/gnu/info:/usr/gnu/lib/info:/usr/gnu/lib/emacs/info:/usr/local/gnu/info:/usr/local/gnu/lib/info:/usr/local/gnu/lib/emacs/info:/usr/local/lib/info:/usr/local/lib/emacs/info:/usr/local/emacs/info:."
+#endif /* !DEFAULT_INFOPATH */
+
+#if !defined (S_ISREG) && defined (S_IFREG)
+#  define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
+#endif /* !S_ISREG && S_IFREG */
+
+#if !defined (S_ISDIR) && defined (S_IFDIR)
+#  define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
+#endif /* !S_ISDIR && S_IFDIR */
+
+#endif /* !_FILESYS_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/footnotes.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,264 @@
+/* footnotes.c -- Some functions for manipulating footnotes. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+
+/* Non-zero means attempt to show footnotes when displaying a new window. */
+int auto_footnotes_p = 1;
+
+static char *footnote_nodename = "*Footnotes*";
+
+#define FOOTNOTE_HEADER_FORMAT \
+   "*** Footnotes appearing in the node \"%s\" ***\n"
+
+/* Find the window currently showing footnotes. */
+static WINDOW *
+find_footnotes_window ()
+{
+  WINDOW *win;
+
+  /* Try to find an existing window first. */
+  for (win = windows; win; win = win->next)
+    if (internal_info_node_p (win->node) &&
+	(strcmp (win->node->nodename, footnote_nodename) == 0))
+      break;
+
+  return (win);
+}
+
+/* Manufacture a node containing the footnotes of this node, and
+   return the manufactured node.  If NODE has no footnotes, return a 
+   NULL pointer. */
+NODE *
+make_footnotes_node (node)
+     NODE *node;
+{
+  NODE *fn_node, *result = (NODE *)NULL;
+  long fn_start;
+
+  /* Make the initial assumption that the footnotes appear as simple
+     text within this windows node. */
+  fn_node = node;
+
+  /* See if this node contains the magic footnote label. */
+  fn_start =
+    info_search_in_node (FOOTNOTE_LABEL, node, 0, (WINDOW *)NULL, 1);
+
+  /* If it doesn't, check to see if it has an associated footnotes node. */
+  if (fn_start == -1)
+    {
+      REFERENCE **refs;
+
+      refs = info_xrefs_of_node (node);
+
+      if (refs)
+	{
+	  register int i;
+	  char *refname;
+
+	  refname = (char *)xmalloc
+	    (1 + strlen ("-Footnotes") + strlen (node->nodename));
+
+	  strcpy (refname, node->nodename);
+	  strcat (refname, "-Footnotes");
+
+	  for (i = 0; refs[i]; i++)
+	    if (strcmp (refs[i]->nodename, refname) == 0)
+	      {
+		char *filename;
+
+		filename = node->parent;
+		if (!filename)
+		  filename = node->filename;
+
+		fn_node = info_get_node (filename, refname);
+
+		if (fn_node)
+		  fn_start = 0;
+
+		break;
+	      }
+
+	  free (refname);
+	  info_free_references (refs);
+	}
+    }
+
+  /* If we never found the start of a footnotes area, quit now. */
+  if (fn_start == -1)
+    return ((NODE *)NULL);
+
+  /* Make the new node. */
+  result = (NODE *)xmalloc (sizeof (NODE));
+  result->flags = 0;
+
+  /* Get the size of the footnotes appearing within this node. */
+  {
+    char *header;
+    long text_start = fn_start;
+
+    header = (char *)xmalloc
+      (1 + strlen (node->nodename) + strlen (FOOTNOTE_HEADER_FORMAT));
+    sprintf (header, FOOTNOTE_HEADER_FORMAT, node->nodename);
+
+    /* Move the start of the displayed text to right after the first line.
+       This effectively skips either "---- footno...", or "File: foo...". */
+    while (text_start < fn_node->nodelen)
+      if (fn_node->contents[text_start++] == '\n')
+	break;
+  
+    result->nodelen = strlen (header) + fn_node->nodelen - text_start;
+
+    /* Set the contents of this node. */
+    result->contents = (char *)xmalloc (1 + result->nodelen);
+    sprintf (result->contents, "%s", header);
+    memcpy (result->contents + strlen (header),
+	    fn_node->contents + text_start, fn_node->nodelen - text_start);
+
+    name_internal_node (result, footnote_nodename);
+    free (header);
+  }
+
+#if defined (NOTDEF)
+  /* If the footnotes were gleaned from the node that we were called with,
+     shorten the calling node's display length. */
+  if (fn_node == node)
+    narrow_node (node, 0, fn_start);
+#endif /* NOTDEF */
+
+  return (result);
+}
+
+/* Create or delete the footnotes window depending on whether footnotes
+   exist in WINDOW's node or not.  Returns FN_FOUND if footnotes were found
+   and displayed.  Returns FN_UNFOUND if there were no footnotes found
+   in WINDOW's node.  Returns FN_UNABLE if there were footnotes, but the
+   window to show them couldn't be made. */
+int
+info_get_or_remove_footnotes (window)
+     WINDOW *window;
+{
+  WINDOW *fn_win;
+  NODE *new_footnotes;
+
+  fn_win = find_footnotes_window ();
+
+  /* If we are in the footnotes window, change nothing. */
+  if (fn_win == window)
+    return (FN_FOUND);
+
+  /* Try to find footnotes for this window's node. */
+  new_footnotes = make_footnotes_node (window->node);
+
+  /* If there was a window showing footnotes, and there are no footnotes
+     for the current window, delete the old footnote window. */
+  if (fn_win && !new_footnotes)
+    {
+      if (windows->next)
+	info_delete_window_internal (fn_win);
+    }
+
+  /* If there are footnotes for this window's node, but no window around
+     showing footnotes, try to make a new window. */
+  if (new_footnotes && !fn_win)
+    {
+      WINDOW *old_active;
+      WINDOW *last, *win;
+
+      /* Always make this window be the last one appearing in the list.  Find
+	 the last window in the chain. */
+      for (win = windows, last = windows; win; last = win, win = win->next);
+
+      /* Try to split this window, and make the split window the one to
+	 contain the footnotes. */
+      old_active = active_window;
+      active_window = last;
+      fn_win = window_make_window (new_footnotes);
+      active_window = old_active;
+
+      if (!fn_win)
+	{
+	  free (new_footnotes->contents);
+	  free (new_footnotes);
+
+	  /* If we are hacking automatic footnotes, and there are footnotes
+	     but we couldn't display them, print a message to that effect. */
+	  if (auto_footnotes_p)
+	    inform_in_echo_area ("Footnotes could not be displayed");
+	  return (FN_UNABLE);
+	}
+    }
+
+  /* If there are footnotes, and there is a window to display them,
+     make that window be the number of lines appearing in the footnotes. */
+  if (new_footnotes && fn_win)
+    {
+      window_set_node_of_window (fn_win, new_footnotes);
+
+      window_change_window_height
+	(fn_win, fn_win->line_count - fn_win->height);
+
+      remember_window_and_node (fn_win, new_footnotes);
+      add_gcable_pointer (new_footnotes->contents);
+    }
+
+  if (!new_footnotes)
+    return (FN_UNFOUND);
+  else
+    return (FN_FOUND);
+}
+
+/* Show the footnotes associated with this node in another window. */
+DECLARE_INFO_COMMAND (info_show_footnotes,
+   "Show the footnotes associated with this node in another window")
+{
+  int result;
+
+  /* A negative argument means just make the window go away. */
+  if (count < 0)
+    {
+      WINDOW *fn_win = find_footnotes_window ();
+
+      /* If there is an old footnotes window, and it isn't the only window
+	 on the screen, delete it. */
+      if (fn_win && windows->next)
+	info_delete_window_internal (fn_win);
+    }
+  else
+    {
+      int result;
+
+      result = info_get_or_remove_footnotes (window);
+
+      switch (result)
+	{
+	case FN_UNFOUND:
+	  info_error (NO_FOOT_NODE);
+	  break;
+
+	case FN_UNABLE:
+	  info_error (WIN_TOO_SMALL);
+	  break;
+	}
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/footnotes.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,46 @@
+/* footnotes.h -- Some functions for manipulating footnotes. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_FOOTNOTES_H_)
+#define _FOOTNOTES_H_
+
+/* Magic string which indicates following text is footnotes. */
+#define FOOTNOTE_LABEL "---------- Footnotes ----------"
+
+#define FN_FOUND   0
+#define FN_UNFOUND 1
+#define FN_UNABLE  2
+
+
+/* Create or delete the footnotes window depending on whether footnotes
+   exist in WINDOW's node or not.  Returns FN_FOUND if footnotes were found
+   and displayed.  Returns FN_UNFOUND if there were no footnotes found
+   in WINDOW's node.  Returns FN_UNABLE if there were footnotes, but the
+   window to show them couldn't be made. */
+extern int info_get_or_remove_footnotes ();
+
+/* Non-zero means attempt to show footnotes when displaying a new window. */
+extern int auto_footnotes_p;
+
+#endif /* !_FOOTNOTES_H_ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/gc.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,95 @@
+/* gc.c -- Functions to remember and garbage collect unused node contents. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+
+/* Array of pointers to the contents of gc-able nodes.  A pointer on this
+   list can be garbage collected when no info window contains a node whose
+   contents member match the pointer. */
+static char **gcable_pointers = (char **)NULL;
+static int gcable_pointers_index = 0;
+static int gcable_pointers_slots = 0;
+
+/* Add POINTER to the list of garbage collectible pointers.  A pointer
+   is not actually garbage collected until no info window contains a node
+   whose contents member is equal to the pointer. */
+void
+add_gcable_pointer (pointer)
+     char *pointer;
+{
+  gc_pointers ();
+  add_pointer_to_array (pointer, gcable_pointers_index, gcable_pointers,
+			gcable_pointers_slots, 10, char *);
+}
+
+/* Grovel the list of info windows and gc-able pointers finding those
+   node->contents which are collectible, and free them. */
+void
+gc_pointers ()
+{
+  register int i, j, k;
+  INFO_WINDOW *iw;
+  char **new = (char **)NULL;
+  int new_index = 0;
+  int new_slots = 0;
+
+  if (!info_windows || !gcable_pointers_index)
+    return;
+
+  for (i = 0; iw = info_windows[i]; i++)
+    {
+      for (j = 0; j < iw->nodes_index; j++)
+	{
+	  NODE *node = iw->nodes[j];
+
+	  /* If this node->contents appears in our list of gcable_pointers,
+	     it is not gc-able, so save it. */
+	  for (k = 0; k < gcable_pointers_index; k++)
+	    if (gcable_pointers[k] == node->contents)
+	      {
+		add_pointer_to_array
+		  (node->contents, new_index, new, new_slots, 10, char *);
+		break;
+	      }
+	}
+    }
+
+  /* We have gathered all of the pointers which need to be saved.  Free any
+     of the original pointers which do not appear in the new list. */
+  for (i = 0; i < gcable_pointers_index; i++)
+    {
+      for (j = 0; j < new_index; j++)
+	if (gcable_pointers[i] == new[j])
+	  break;
+
+      /* If we got all the way through the new list, then the old pointer
+	 can be garbage collected. */
+      if (new && !new[j])
+	free (gcable_pointers[i]);
+    }
+
+  free (gcable_pointers);
+  gcable_pointers = new;
+  gcable_pointers_slots = new_slots;
+  gcable_pointers_index = new_index;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/gc.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,36 @@
+/* gc.h -- Functions for garbage collecting unused node contents. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_GC_H_)
+#define _GC_H_
+
+/* Add POINTER to the list of garbage collectible pointers.  A pointer
+   is not actually garbage collected until no info window contains a node
+   whose contents member is equal to the pointer. */
+extern void add_gcable_pointer ();
+
+/* Grovel the list of info windows and gc-able pointers finding those
+   node->contents which are collectible, and free them. */
+extern void gc_pointers ();
+
+#endif /* !_GC_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/general.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,81 @@
+/* general.h -- Some generally useful defines. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_GENERAL_H_)
+#define _GENERAL_H_
+
+extern void *xmalloc (), *xrealloc ();
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+
+#if defined (HAVE_STRING_H)
+#  include <string.h>
+#else
+#  include <strings.h>
+#endif /* !HAVE_STRING_H */
+
+#if !defined (savestring)
+#  define savestring(x) (char *)strcpy ((char *)xmalloc (1 + strlen (x)), x)
+#endif /* !savestring */
+
+#define info_toupper(x) (islower (x) ? toupper (x) : x)
+#define info_tolower(x) (isupper (x) ? tolower (x) : x)
+
+#if !defined (whitespace)
+#  define whitespace(c) ((c == ' ') || (c == '\t'))
+#endif /* !whitespace */
+
+#if !defined (whitespace_or_newline)
+#  define whitespace_or_newline(c) (whitespace (c) || (c == '\n'))
+#endif /* !whitespace_or_newline */
+
+#if !defined (__FUNCTION_DEF)
+#  define __FUNCTION_DEF
+typedef int Function ();
+typedef void VFunction ();
+#endif /* _FUNCTION_DEF */
+
+/* Add POINTER to the list of pointers found in ARRAY.  SLOTS is the number
+   of slots that have already been allocated.  INDEX is the index into the
+   array where POINTER should be added.  GROW is the number of slots to grow
+   ARRAY by, in the case that it needs growing.  TYPE is a cast of the type
+   of object stored in ARRAY (e.g., NODE_ENTRY *. */
+#define add_pointer_to_array(pointer, idx, array, slots, grow, type) \
+  do { \
+    if (idx + 2 >= slots) \
+      array = (type *)(xrealloc (array, (slots += grow) * sizeof (type))); \
+    array[idx++] = (type)pointer; \
+    array[idx] = (type)NULL; \
+  } while (0)
+
+#define maybe_free(x) do { if (x) free (x); } while (0)
+
+#if !defined (HAVE_BZERO)
+#  define zero_mem(mem, length) memset (mem, 0, length)
+#else
+#  define zero_mem(mem, length) bzero (mem, length)
+#endif /* !BZERO_MISSING */
+
+#endif /* !_GENERAL_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/indices.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,667 @@
+/* indices.c -- Commands for dealing with an Info file Index. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include "indices.h"
+
+/* User-visible variable controls the output of info-index-next. */
+int show_index_match = 1;
+
+/* In the Info sense, an index is a menu.  This variable holds the last
+   parsed index. */
+static REFERENCE **index_index = (REFERENCE **)NULL;
+
+/* The offset of the most recently selected index element. */
+static int index_offset = 0;
+
+/* Variable which holds the last string searched for. */
+static char *index_search = (char *)NULL;
+
+/* A couple of "globals" describing where the initial index was found. */
+static char *initial_index_filename = (char *)NULL;
+static char *initial_index_nodename = (char *)NULL;
+
+/* A structure associating index names with index offset ranges. */
+typedef struct {
+  char *name;			/* The nodename of this index. */
+  int first;			/* The index in our list of the first entry. */
+  int last;			/* The index in our list of the last entry. */
+} INDEX_NAME_ASSOC;
+
+/* An array associating index nodenames with index offset ranges. */
+static INDEX_NAME_ASSOC **index_nodenames = (INDEX_NAME_ASSOC **)NULL;
+static int index_nodenames_index = 0;
+static int index_nodenames_slots = 0;
+
+/* Add the name of NODE, and the range of the associated index elements
+   (passed in ARRAY) to index_nodenames. */
+static void
+add_index_to_index_nodenames (array, node)
+     REFERENCE **array;
+     NODE *node;
+{
+  register int i, last;
+  INDEX_NAME_ASSOC *assoc;
+
+  for (last = 0; array[last]; last++);
+  assoc = (INDEX_NAME_ASSOC *)xmalloc (sizeof (INDEX_NAME_ASSOC));
+  assoc->name = savestring (node->nodename);
+
+  if (!index_nodenames_index)
+    {
+      assoc->first = 0;
+      assoc->last = last;
+    }
+  else
+    {
+      for (i = 0; index_nodenames[i + 1]; i++);
+      assoc->first = 1 + index_nodenames[i]->last;
+      assoc->last = assoc->first + last;
+    }
+  add_pointer_to_array
+    (assoc, index_nodenames_index, index_nodenames, index_nodenames_slots,
+     10, INDEX_NAME_ASSOC *);
+}
+
+/* Find and return the indices of WINDOW's file.  The indices are defined
+   as the first node in the file containing the word "Index" and any
+   immediately following nodes whose names also contain "Index".  All such
+   indices are concatenated and the result returned.  If WINDOW's info file
+   doesn't have any indices, a NULL pointer is returned. */
+REFERENCE **
+info_indices_of_window (window)
+     WINDOW *window;
+{
+  FILE_BUFFER *fb;
+
+  fb = file_buffer_of_window (window);
+
+  return (info_indices_of_file_buffer (fb));
+}
+
+REFERENCE **
+info_indices_of_file_buffer (file_buffer)
+     FILE_BUFFER *file_buffer;
+{
+  register int i;
+  REFERENCE **result = (REFERENCE **)NULL;
+
+  /* No file buffer, no indices. */
+  if (!file_buffer)
+    return ((REFERENCE **)NULL);
+
+  /* Reset globals describing where the index was found. */
+  maybe_free (initial_index_filename);
+  maybe_free (initial_index_nodename);
+  initial_index_filename = (char *)NULL;
+  initial_index_nodename = (char *)NULL;
+
+  if (index_nodenames)
+    {
+      for (i = 0; index_nodenames[i]; i++)
+	{
+	  free (index_nodenames[i]->name);
+	  free (index_nodenames[i]);
+	}
+
+      index_nodenames_index = 0;
+      index_nodenames[0] = (INDEX_NAME_ASSOC *)NULL;
+    }
+
+  /* Grovel the names of the nodes found in this file. */
+  if (file_buffer->tags)
+    {
+      TAG *tag;
+
+      for (i = 0; tag = file_buffer->tags[i]; i++)
+	{
+	  if (string_in_line ("Index", tag->nodename) != -1)
+	    {
+	      NODE *node;
+	      REFERENCE **menu;
+
+	      /* Found one.  Get its menu. */
+	      node = info_get_node (tag->filename, tag->nodename);
+	      if (!node)
+		continue;
+
+	      /* Remember the filename and nodename of this index. */
+	      initial_index_filename = savestring (file_buffer->filename);
+	      initial_index_nodename = savestring (tag->nodename);
+
+	      menu = info_menu_of_node (node);
+
+	      /* If we have a menu, add this index's nodename and range
+		 to our list of index_nodenames. */
+	      if (menu)
+		{
+		  add_index_to_index_nodenames (menu, node);
+
+		  /* Concatenate the references found so far. */
+		  result = info_concatenate_references (result, menu);
+		}
+	      free (node);
+	    }
+	}
+    }
+
+  /* If there is a result, clean it up so that every entry has a filename. */
+  for (i = 0; result && result[i]; i++)
+    if (!result[i]->filename)
+      result[i]->filename = savestring (file_buffer->filename);
+
+  return (result);
+}
+
+DECLARE_INFO_COMMAND (info_index_search,
+   "Look up a string in the index for this file")
+{
+  FILE_BUFFER *fb;
+  char *line;
+
+  /* Reset the index offset, since this is not the info-index-next command. */
+  index_offset = 0;
+
+  /* The user is selecting a new search string, so flush the old one. */
+  maybe_free (index_search);
+  index_search = (char *)NULL;
+
+  /* If this window's file is not the same as the one that we last built an
+     index for, build and remember an index now. */
+  fb = file_buffer_of_window (window);
+  if (!initial_index_filename ||
+      (strcmp (initial_index_filename, fb->filename) != 0))
+    {
+      info_free_references (index_index);
+      window_message_in_echo_area ("Finding index entries...");
+      index_index = info_indices_of_file_buffer (fb);
+    }
+
+  /* If there is no index, quit now. */
+  if (!index_index)
+    {
+      info_error ("No indices found.");
+      return;
+    }
+
+  /* Okay, there is an index.  Let the user select one of the members of it. */
+  line =
+    info_read_maybe_completing (window, "Index entry: ", index_index);
+
+  window = active_window;
+
+  /* User aborted? */
+  if (!line)
+    {
+      info_abort_key (active_window, 1, 0);
+      return;
+    }
+
+  /* Empty line means move to the Index node. */
+  if (!*line)
+    {
+      free (line);
+
+      if (initial_index_filename && initial_index_nodename)
+	{
+	  NODE *node;
+
+	  node =
+	    info_get_node (initial_index_filename, initial_index_nodename);
+	  set_remembered_pagetop_and_point (window);
+	  window_set_node_of_window (window, node);
+	  remember_window_and_node (window, node);
+	  window_clear_echo_area ();
+	  return;
+	}
+    }
+
+  /* The user typed either a completed index label, or a partial string.
+     Find an exact match, or, failing that, the first index entry containing
+     the partial string.  So, we just call info_next_index_match () with minor
+     manipulation of INDEX_OFFSET. */
+  {
+    int old_offset;
+
+    /* Start the search right after/before this index. */
+    if (count < 0)
+      {
+	register int i;
+	for (i = 0; index_index[i]; i++);
+	index_offset = i;
+      }
+    else
+      index_offset = -1;
+
+    old_offset == index_offset;
+
+    /* The "last" string searched for is this one. */
+    index_search = line;
+
+    /* Find it, or error. */
+    info_next_index_match (window, count, 0);
+
+    /* If the search failed, return the index offset to where it belongs. */
+    if (index_offset == old_offset)
+      index_offset = 0;
+  }
+}
+
+DECLARE_INFO_COMMAND (info_next_index_match,
+ "Go to the next matching index item from the last `\\[index-search]' command")
+{
+  register int i;
+  int partial, dir;
+  NODE *node;
+
+  /* If there is no previous search string, the user hasn't built an index
+     yet. */
+  if (!index_search)
+    {
+      info_error ("No previous index search string.");
+      return;
+    }
+
+  /* If there is no index, that is an error. */
+  if (!index_index)
+    {
+      info_error ("No index entries.");
+      return;
+    }
+
+  /* The direction of this search is controlled by the value of the
+     numeric argument. */
+  if (count < 0)
+    dir = -1;
+  else
+    dir = 1;
+
+  /* Search for the next occurence of index_search.  First try to find
+     an exact match. */
+  partial = 0;
+
+  for (i = index_offset + dir; (i > -1) && (index_index[i]); i += dir)
+    if (strcmp (index_search, index_index[i]->label) == 0)
+      break;
+
+  /* If that failed, look for the next substring match. */
+  if ((i < 0) || (!index_index[i]))
+    {
+      for (i = index_offset + dir; (i > -1) && (index_index[i]); i += dir)
+	if (string_in_line (index_search, index_index[i]->label) != -1)
+	  break;
+
+      if ((i > -1) && (index_index[i]))
+	partial = string_in_line (index_search, index_index[i]->label);
+    }
+
+  /* If that failed, print an error. */
+  if ((i < 0) || (!index_index[i]))
+    {
+      info_error ("No %sindex entries containing \"%s\".",
+		  index_offset > 0 ? "more " : "", index_search);
+      return;
+    }
+
+  /* Okay, we found the next one.  Move the offset to the current entry. */
+  index_offset = i;
+
+  /* Report to the user on what we have found. */
+  {
+    register int j;
+    char *name = "CAN'T SEE THIS";
+    char *match;
+
+    for (j = 0; index_nodenames[j]; j++)
+      {
+	if ((i >= index_nodenames[j]->first) &&
+	    (i <= index_nodenames[j]->last))
+	  {
+	    name = index_nodenames[j]->name;
+	    break;
+	  }
+      }
+
+    /* If we had a partial match, indicate to the user which part of the
+       string matched. */
+    match = savestring (index_index[i]->label);
+
+    if (partial && show_index_match)
+      {
+	int j, ls, start, upper;
+
+	ls = strlen (index_search);
+	start = partial - ls;
+	upper = isupper (match[start]) ? 1 : 0;
+
+	for (j = 0; j < ls; j++)
+	  if (upper)
+	    match[j + start] = info_tolower (match[j + start]);
+	  else
+	    match[j + start] = info_toupper (match[j + start]);
+      }
+
+    {
+      char *format;
+
+      format = replace_in_documentation
+	("Found \"%s\" in %s. (`\\[next-index-match]' tries to find next.)");
+
+      window_message_in_echo_area (format, match, name);
+    }
+
+    free (match);
+  }
+
+  /* Select the node corresponding to this index entry. */
+  node = info_get_node (index_index[i]->filename, index_index[i]->nodename);
+
+  if (!node)
+    {
+      info_error (CANT_FILE_NODE,
+		  index_index[i]->filename, index_index[i]->nodename);
+      return;
+    }
+
+  set_remembered_pagetop_and_point (window);
+  window_set_node_of_window (window, node);
+  remember_window_and_node (window, node);
+
+
+  /* Try to find an occurence of LABEL in this node. */
+  {
+    long start, loc;
+
+    start = window->line_starts[1] - window->node->contents;
+    loc = info_target_search_node (node, index_index[i]->label, start);
+
+    if (loc != -1)
+      {
+	window->point = loc;
+	window_adjust_pagetop (window);
+      }
+  }
+}
+
+/* **************************************************************** */
+/*								    */
+/*		   Info APROPOS: Search every known index.	    */
+/*								    */
+/* **************************************************************** */
+
+/* For every menu item in DIR, search the indices of that file for
+   SEARCH_STRING. */
+REFERENCE **
+apropos_in_all_indices (search_string, inform)
+     char *search_string;
+     int inform;
+{
+  register int i, dir_index;
+  REFERENCE **all_indices = (REFERENCE **)NULL;
+  REFERENCE **dir_menu = (REFERENCE **)NULL;
+  NODE *dir_node;
+  int printed = 0;
+
+  dir_node = info_get_node ("dir", "Top");
+  if (dir_node)
+    dir_menu = info_menu_of_node (dir_node);
+
+  if (!dir_menu)
+    return;
+
+  /* For every menu item in DIR, get the associated node's file buffer and
+     read the indices of that file buffer.  Gather all of the indices into
+     one large one. */
+  for (dir_index = 0; dir_menu[dir_index]; dir_index++)
+    {
+      REFERENCE **this_index, *this_item;
+      NODE *this_node;
+      FILE_BUFFER *this_fb;
+
+      this_item = dir_menu[dir_index];
+
+      if (!this_item->filename)
+	{
+	  if (dir_node->parent)
+	    this_item->filename = savestring (dir_node->parent);
+	  else
+	    this_item->filename = savestring (dir_node->filename);
+	}
+
+      /* Find this node.  If we cannot find it, try using the label of the
+	 entry as a file (i.e., "(LABEL)Top"). */
+      this_node = info_get_node (this_item->filename, this_item->nodename);
+
+      if (!this_node && this_item->nodename &&
+	  (strcmp (this_item->label, this_item->nodename) == 0))
+	this_node = info_get_node (this_item->label, "Top");
+
+      if (!this_node)
+	continue;
+
+      /* Get the file buffer associated with this node. */
+      {
+	char *files_name;
+
+	files_name = this_node->parent;
+	if (!files_name)
+	  files_name = this_node->filename;
+
+	this_fb = info_find_file (files_name);
+
+	if (this_fb && inform)
+	  message_in_echo_area ("Scanning indices of \"%s\"...", files_name);
+
+	this_index = info_indices_of_file_buffer (this_fb);
+	free (this_node);
+
+	if (this_fb && inform)
+	  unmessage_in_echo_area ();
+      }
+
+      if (this_index)
+	{
+	  /* Remember the filename which contains this set of references. */
+	  for (i = 0; this_index && this_index[i]; i++)
+	    if (!this_index[i]->filename)
+	      this_index[i]->filename = savestring (this_fb->filename);
+
+	  /* Concatenate with the other indices.  */
+	  all_indices = info_concatenate_references (all_indices, this_index);
+	}
+    }
+
+  info_free_references (dir_menu);
+
+  /* Build a list of the references which contain SEARCH_STRING. */
+  if (all_indices)
+    {
+      REFERENCE *entry, **apropos_list = (REFERENCE **)NULL;
+      int apropos_list_index = 0;
+      int apropos_list_slots = 0;
+
+      for (i = 0; (entry = all_indices[i]); i++)
+	{
+	  if (string_in_line (search_string, entry->label) != -1)
+	    {
+	      add_pointer_to_array
+		(entry, apropos_list_index, apropos_list, apropos_list_slots,
+		 100, REFERENCE *);
+	    }
+	  else
+	    {
+	      maybe_free (entry->label);
+	      maybe_free (entry->filename);
+	      maybe_free (entry->nodename);
+	      free (entry);
+	    }
+	}
+
+      free (all_indices);
+      all_indices = apropos_list;
+    }
+  return (all_indices);
+}
+
+#define APROPOS_NONE \
+   "No available info files reference \"%s\" in their indices."
+
+void
+info_apropos (string)
+     char *string;
+{
+  REFERENCE **apropos_list;
+
+  apropos_list = apropos_in_all_indices (string, 0);
+
+  if (!apropos_list)
+    {
+      info_error (APROPOS_NONE, string);
+    }
+  else
+    {
+      register int i;
+      REFERENCE *entry;
+
+      for (i = 0; (entry = apropos_list[i]); i++)
+	fprintf (stderr, "\"(%s)%s\" -- %s\n",
+		 entry->filename, entry->nodename, entry->label);
+    }
+  info_free_references (apropos_list);
+}
+
+static char *apropos_list_nodename = "*Apropos*";
+
+DECLARE_INFO_COMMAND (info_index_apropos,
+   "Grovel all known info file's indices for a string and build a menu")
+{
+  char *line;
+
+  line = info_read_in_echo_area (window, "Index apropos: ");
+
+  window = active_window;
+
+  /* User aborted? */
+  if (!line)
+    {
+      info_abort_key (window, 1, 1);
+      return;
+    }
+
+  /* User typed something? */
+  if (*line)
+    {
+      REFERENCE **apropos_list;
+      NODE *apropos_node;
+
+      apropos_list = apropos_in_all_indices (line, 1);
+
+      if (!apropos_list)
+	{
+	  info_error (APROPOS_NONE, line);
+	}
+      else
+	{
+	  register int i;
+	  char *line_buffer;
+
+	  initialize_message_buffer ();
+	  printf_to_message_buffer
+	    ("\n* Menu: Nodes whoses indices contain \"%s\":\n", line);
+	  line_buffer = (char *)xmalloc (500);
+
+	  for (i = 0; apropos_list[i]; i++)
+	    {
+	      int len;
+	      sprintf (line_buffer, "* (%s)%s::",
+		       apropos_list[i]->filename, apropos_list[i]->nodename);
+	      len = pad_to (36, line_buffer);
+	      sprintf (line_buffer + len, "%s", apropos_list[i]->label);
+	      printf_to_message_buffer ("%s\n", line_buffer);
+	    }
+	  free (line_buffer);
+	}
+
+      apropos_node = message_buffer_to_node ();
+      add_gcable_pointer (apropos_node);
+      name_internal_node (apropos_node, apropos_list_nodename);
+
+      /* Even though this is an internal node, we don't want the window
+	 system to treat it specially.  So we turn off the internalness
+	 of it here. */
+      apropos_node->flags &= ~N_IsInternal;
+
+      /* Find/Create a window to contain this node. */
+      {
+	WINDOW *new;
+	NODE *node;
+
+	set_remembered_pagetop_and_point (window);
+
+	/* If a window is visible and showing an apropos list already,
+	   re-use it. */
+	for (new = windows; new; new = new->next)
+	  {
+	    node = new->node;
+
+	    if (internal_info_node_p (node) &&
+		(strcmp (node->nodename, apropos_list_nodename) == 0))
+	      break;
+	  }
+
+	/* If we couldn't find an existing window, try to use the next window
+	   in the chain. */
+	if (!new && window->next)
+	  new = window->next;
+
+	/* If we still don't have a window, make a new one to contain
+	   the list. */
+	if (!new)
+	  {
+	    WINDOW *old_active;
+
+	    old_active = active_window;
+	    active_window = window;
+	    new = window_make_window ((NODE *)NULL);
+	    active_window = old_active;
+	  }
+
+	/* If we couldn't make a new window, use this one. */
+	if (!new)
+	  new = window;
+
+	/* Lines do not wrap in this window. */
+	new->flags |= W_NoWrap;
+
+	window_set_node_of_window (new, apropos_node);
+	remember_window_and_node (new, apropos_node);
+	active_window = new;
+      }
+      info_free_references (apropos_list);
+    }
+  free (line);
+
+  if (!info_error_was_printed)
+    window_clear_echo_area ();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/indices.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,39 @@
+/* indices.h -- Functions defined in indices.c. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_INDICES_H_)
+#define _INDICES_H_
+
+/* User-visible variable controls the output of info-index-next. */
+extern int show_index_match;
+
+extern REFERENCE **info_indices_of_window (), **info_indices_of_file_buffer ();
+extern void info_apropos ();
+
+/* For every menu item in DIR, search the indices of that file for STRING. */
+REFERENCE **apropos_in_all_indices ();
+
+/* User visible functions declared in indices.c. */
+extern void info_index_search (), info_next_index_match ();
+
+#endif /* !_INDICES_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/info-utils.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,653 @@
+/* info-utils.c -- Useful functions for manipulating Info file quirks. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include <stdio.h>		/* For "NULL".  Yechhh! */
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "info-utils.h"
+
+/* When non-zero, various display and input functions handle ISO Latin
+   character sets correctly. */
+int ISO_Latin_p = 0;
+
+/* Variable which holds the most recent filename parsed as a result of
+   calling info_parse_xxx (). */
+char *info_parsed_filename = (char *)NULL;
+
+/* Variable which holds the most recent nodename parsed as a result of
+   calling info_parse_xxx (). */
+char *info_parsed_nodename = (char *)NULL;
+
+/* Functions to remember a filename or nodename for later return. */
+static void save_filename (), saven_filename ();
+static void save_nodename (), saven_nodename ();
+
+/* How to get a reference (either menu or cross). */
+static REFERENCE **info_references_internal ();
+
+/* Parse the filename and nodename out of STRING.  If STRING doesn't
+   contain a filename (i.e., it is NOT (FILENAME)NODENAME) then set
+   INFO_PARSED_FILENAME to NULL.  If second argument NEWLINES_OKAY is
+   non-zero, it says to allow the nodename specification to cross a
+   newline boundary (i.e., only `,', `.', or `TAB' can end the spec). */
+void
+info_parse_node (string, newlines_okay)
+     char *string;
+     int newlines_okay;
+{
+  register int i = 0;
+
+  /* Default the answer. */
+  save_filename ((char *)NULL);
+  save_nodename ((char *)NULL);
+
+  /* Special case of nothing passed.  Return nothing. */
+  if (!string || !*string)
+    return;
+
+  string += skip_whitespace (string);
+
+  /* Check for (FILENAME)NODENAME. */
+  if (*string == '(')
+    {
+      i = 0;
+      /* Advance past the opening paren. */
+      string++;
+
+      /* Find the closing paren. */
+      while (string[i] && string[i] != ')')
+	i++;
+
+      /* Remember parsed filename. */
+      saven_filename (string, i);
+
+      /* Point directly at the nodename. */
+      string += i;
+
+      if (*string)
+	string++;
+    }
+
+  /* Parse out nodename. */
+  i = skip_node_characters (string, newlines_okay);
+  saven_nodename (string, i);
+  canonicalize_whitespace (info_parsed_nodename);
+  if (info_parsed_nodename && !*info_parsed_nodename)
+    {
+      free (info_parsed_nodename);
+      info_parsed_nodename = (char *)NULL;
+    }
+}
+
+/* Return the node addressed by LABEL in NODE (usually one of "Prev:",
+   "Next:", "Up:", "File:", or "Node:".  After a call to this function,
+   the global INFO_PARSED_NODENAME and INFO_PARSED_FILENAME contain
+   the information. */
+void
+info_parse_label (label, node)
+     char *label;
+     NODE *node;
+{
+  register int i;
+  char *nodeline;
+
+  /* Default answer to failure. */
+  save_nodename ((char *)NULL);
+  save_filename ((char *)NULL);
+
+  /* Find the label in the first line of this node. */
+  nodeline = node->contents;
+  i = string_in_line (label, nodeline);
+
+  if (i == -1)
+    return;
+
+  nodeline += i;
+  nodeline += skip_whitespace (nodeline);
+  info_parse_node (nodeline, DONT_SKIP_NEWLINES);
+}
+
+/* **************************************************************** */
+/*								    */
+/*		    Finding and Building Menus			    */
+/*								    */
+/* **************************************************************** */
+
+/* Return a NULL terminated array of REFERENCE * which represents the menu
+   found in NODE.  If there is no menu in NODE, just return a NULL pointer. */
+REFERENCE **
+info_menu_of_node (node)
+     NODE *node;
+{
+  long position;
+  SEARCH_BINDING search;
+  REFERENCE **menu = (REFERENCE **)NULL;
+
+  search.buffer = node->contents;
+  search.start = 0;
+  search.end = node->nodelen;
+  search.flags = S_FoldCase;
+
+  /* Find the start of the menu. */
+  position = search_forward (INFO_MENU_LABEL, &search);
+
+  if (position == -1)
+    return ((REFERENCE **) NULL);
+
+  /* We have the start of the menu now.  Glean menu items from the rest
+     of the node. */
+  search.start = position + strlen (INFO_MENU_LABEL);
+  search.start += skip_line (search.buffer + search.start);
+  search.start--;
+  menu = info_menu_items (&search);
+  return (menu);
+}
+
+/* Return a NULL terminated array of REFERENCE * which represents the cross
+   refrences found in NODE.  If there are no cross references in NODE, just
+   return a NULL pointer. */
+REFERENCE **
+info_xrefs_of_node (node)
+     NODE *node;
+{
+  SEARCH_BINDING search;
+
+  search.buffer = node->contents;
+  search.start = 0;
+  search.end = node->nodelen;
+  search.flags = S_FoldCase;
+
+  return (info_xrefs (&search));
+}
+
+/* Glean menu entries from BINDING->buffer + BINDING->start until we
+   have looked at the entire contents of BINDING.  Return an array
+   of REFERENCE * that represents each menu item in this range. */
+REFERENCE **
+info_menu_items (binding)
+     SEARCH_BINDING *binding;
+{
+  return (info_references_internal (INFO_MENU_ENTRY_LABEL, binding));
+}
+
+/* Glean cross references from BINDING->buffer + BINDING->start until
+   BINDING->end.  Return an array of REFERENCE * that represents each
+   cross reference in this range. */
+REFERENCE **
+info_xrefs (binding)
+     SEARCH_BINDING *binding;
+{
+  return (info_references_internal (INFO_XREF_LABEL, binding));
+}
+
+/* Glean cross references or menu items from BINDING.  Return an array
+   of REFERENCE * that represents the items found. */
+static REFERENCE **
+info_references_internal (label, binding)
+     char *label;
+     SEARCH_BINDING *binding;
+{
+  SEARCH_BINDING search;
+  REFERENCE **refs = (REFERENCE **)NULL;
+  int refs_index = 0, refs_slots = 0;
+  int searching_for_menu_items = 0;
+  long position;
+
+  search.buffer = binding->buffer;
+  search.start = binding->start;
+  search.end = binding->end;
+  search.flags = S_FoldCase | S_SkipDest;
+
+  searching_for_menu_items = (stricmp (label, INFO_MENU_ENTRY_LABEL) == 0);
+
+  while ((position = search_forward (label, &search)) != -1)
+    {
+      int offset, start;
+      char *refdef;
+      REFERENCE *entry;
+
+      search.start = position;
+      search.start += skip_whitespace (search.buffer + search.start);
+      start = search.start - binding->start;
+      refdef = search.buffer + search.start;
+      offset = string_in_line (":", refdef);
+
+      /* When searching for menu items, if no colon, there is no
+	 menu item on this line. */
+      if (offset == -1)
+	{
+	  if (searching_for_menu_items)
+	    continue;
+	  else
+	    {
+	      int temp;
+
+	      temp = skip_line (refdef);
+	      offset = string_in_line (":", refdef + temp);
+	      if (offset == -1)
+		continue;	/* Give up? */
+	      else
+		offset += temp;
+	    }
+	}
+
+      entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
+      entry->filename = (char *)NULL;
+      entry->nodename = (char *)NULL;
+      entry->label = (char *)xmalloc (offset);
+      strncpy (entry->label, refdef, offset - 1);
+      entry->label[offset - 1] = '\0';
+      canonicalize_whitespace (entry->label);
+
+      refdef += offset;
+      entry->start = start;
+      entry->end = refdef - binding->buffer;
+
+      /* If this reference entry continues with another ':' then the
+	 nodename is the same as the label. */
+      if (*refdef == ':')
+	{
+	  entry->nodename = savestring (entry->label);
+	}
+      else
+	{
+	  /* This entry continues with a specific nodename.  Parse the
+	     nodename from the specification. */
+
+	  refdef += skip_whitespace_and_newlines (refdef);
+
+	  if (searching_for_menu_items)
+	    info_parse_node (refdef, DONT_SKIP_NEWLINES);
+	  else
+	    info_parse_node (refdef, SKIP_NEWLINES);
+
+	  if (info_parsed_filename)
+	    entry->filename = savestring (info_parsed_filename);
+
+	  if (info_parsed_nodename)
+	    entry->nodename = savestring (info_parsed_nodename);
+	}
+
+      add_pointer_to_array
+	(entry, refs_index, refs, refs_slots, 50, REFERENCE *);
+    }
+  return (refs);
+}
+
+/* Get the entry associated with LABEL in MENU.  Return a pointer to the
+   REFERENCE if found, or NULL. */
+REFERENCE *
+info_get_labeled_reference (label, references)
+     char *label;
+     REFERENCE **references;
+{
+  register int i;
+  REFERENCE *entry;
+
+  for (i = 0; references && (entry = references[i]); i++)
+    {
+      if (strcmp (label, entry->label) == 0)
+	return (entry);
+    }
+  return ((REFERENCE *)NULL);
+}
+
+/* A utility function for concatenating REFERENCE **.  Returns a new
+   REFERENCE ** which is the concatenation of REF1 and REF2.  The REF1
+   and REF2 arrays are freed, but their contents are not. */
+REFERENCE **
+info_concatenate_references (ref1, ref2)
+     REFERENCE **ref1, **ref2;
+{
+  register int i, j;
+  REFERENCE **result;
+  int size;
+
+  /* With one argument passed as NULL, simply return the other arg. */
+  if (!ref1)
+    return (ref2);
+  else if (!ref2)
+    return (ref1);
+
+  /* Get the total size of the slots that we will need. */
+  for (i = 0; ref1[i]; i++);
+  size = i;
+  for (i = 0; ref2[i]; i++);
+  size += i;
+
+  result = (REFERENCE **)xmalloc ((1 + size) * sizeof (REFERENCE *));
+
+  /* Copy the contents over. */
+  for (i = 0; ref1[i]; i++)
+    result[i] = ref1[i];
+
+  j = i;
+  for (i = 0; ref2[i]; i++)
+    result[j++] = ref2[i];
+
+  result[j] = (REFERENCE *)NULL;
+  free (ref1);
+  free (ref2);
+  return (result);
+}
+
+/* Free the data associated with REFERENCES. */
+void
+info_free_references (references)
+     REFERENCE **references;
+{
+  register int i;
+  REFERENCE *entry;
+
+  if (references)
+    {
+      for (i = 0; references && (entry = references[i]); i++)
+	{
+	  maybe_free (entry->label);
+	  maybe_free (entry->filename);
+	  maybe_free (entry->nodename);
+
+	  free (entry);
+	}
+
+      free (references);
+    }
+}
+
+/* Search for sequences of whitespace or newlines in STRING, replacing
+   all such sequences with just a single space.  Remove whitespace from
+   start and end of string. */
+void
+canonicalize_whitespace (string)
+     char *string;
+{
+  register int i, j;
+  int len, whitespace_found, whitespace_loc;
+  char *temp;
+
+  if (!string)
+    return;
+
+  len = strlen (string);
+  temp = (char *)xmalloc (1 + len);
+
+  /* Search for sequences of whitespace or newlines.  Replace all such
+     sequences in the string with just a single space. */
+
+  whitespace_found = 0;
+  for (i = 0, j = 0; string[i]; i++)
+    {
+      if (whitespace_or_newline (string[i]))
+	{
+	  whitespace_found++;
+	  whitespace_loc = i;
+	  continue;
+	}
+      else
+	{
+	  if (whitespace_found && whitespace_loc)
+	    {
+	      whitespace_found = 0;
+	      temp[j++] = ' ';
+	    }
+
+	  temp[j++] = string[i];
+	}
+    }
+
+  /* Kill trailing whitespace. */
+  if (j && whitespace (temp[j - 1]))
+    j--;
+
+  temp[j] = '\0';
+  strcpy (string, temp);
+  free (temp);
+}
+
+/* String representation of a char returned by printed_representation (). */
+static char the_rep[10];
+
+/* Return a pointer to a string which is the printed representation
+   of CHARACTER if it were printed at HPOS. */
+char *
+printed_representation (character, hpos)
+     unsigned char character;
+     int hpos;
+{
+  register int i = 0;
+  int printable_limit;
+
+  if (ISO_Latin_p)
+    printable_limit = 160;
+  else
+    printable_limit = 127;
+
+  if (character == '\177')
+    {
+      the_rep[i++] = '^';
+      the_rep[i++] = '?';
+    }
+  else if (iscntrl (character))
+    {
+      switch (character)
+	{
+	case '\r':
+	case '\n':
+	  the_rep[i++] = character;
+	  break;
+
+	case '\t':
+	  {
+	    int tw;
+
+	    tw = ((hpos + 8) & 0xf8) - hpos;
+	    while (i < tw)
+	      the_rep[i++] = ' ';
+	  }
+	  break;
+
+	default:
+	  the_rep[i++] = '^';
+	  the_rep[i++] = (character | 0x40);
+	}
+    }
+  else if (character > printable_limit)
+    {
+      sprintf (the_rep + i, "\\%0o", character);
+      i = strlen (the_rep);
+    }
+  else
+    the_rep[i++] = character;
+
+  the_rep[i] = '\0';
+
+  return (the_rep);
+}
+
+
+/* **************************************************************** */
+/*								    */
+/*		    Functions Static To This File		    */
+/*								    */
+/* **************************************************************** */
+
+/* Amount of space allocated to INFO_PARSED_FILENAME via xmalloc (). */
+static int parsed_filename_size = 0;
+
+/* Amount of space allocated to INFO_PARSED_NODENAME via xmalloc (). */
+static int parsed_nodename_size = 0;
+
+static void save_string (), saven_string ();
+
+/* Remember FILENAME in PARSED_FILENAME.  An empty FILENAME is translated
+   to a NULL pointer in PARSED_FILENAME. */
+static void
+save_filename (filename)
+     char *filename;
+{
+  save_string (filename, &info_parsed_filename, &parsed_filename_size);
+}
+
+/* Just like save_filename (), but you pass the length of the string. */
+static void
+saven_filename (filename, len)
+     char *filename;
+     int len;
+{
+  saven_string (filename, len,
+		&info_parsed_filename, &parsed_filename_size);
+}
+
+/* Remember NODENAME in PARSED_NODENAME.  An empty NODENAME is translated
+   to a NULL pointer in PARSED_NODENAME. */
+static void
+save_nodename (nodename)
+     char *nodename;
+{
+  save_string (nodename, &info_parsed_nodename, &parsed_nodename_size);
+}
+
+/* Just like save_nodename (), but you pass the length of the string. */
+static void
+saven_nodename (nodename, len)
+     char *nodename;
+     int len;
+{
+  saven_string (nodename, len,
+		&info_parsed_nodename, &parsed_nodename_size);
+}
+
+/* Remember STRING in STRING_P.  STRING_P should currently have STRING_SIZE_P
+   bytes allocated to it.  An empty STRING is translated to a NULL pointer
+   in STRING_P. */
+static void
+save_string (string, string_p, string_size_p)
+     char *string;
+     char **string_p;
+     int *string_size_p;
+{
+  if (!string || !*string)
+    {
+      if (*string_p)
+	free (*string_p);
+
+      *string_p = (char *)NULL;
+      *string_size_p = 0;
+    }
+  else
+    {
+      if (strlen (string) >= *string_size_p)
+	*string_p = (char *)xrealloc
+	  (*string_p, (*string_size_p = 1 + strlen (string)));
+
+      strcpy (*string_p, string);
+    }
+}
+
+/* Just like save_string (), but you also pass the length of STRING. */
+static void
+saven_string (string, len, string_p, string_size_p)
+     char *string;
+     int len;
+     char **string_p;
+     int *string_size_p;
+{
+  if (!string)
+    {
+      if (*string_p)
+	free (*string_p);
+
+      *string_p = (char *)NULL;
+      *string_size_p = 0;
+    }
+  else
+    {
+      if (len >= *string_size_p)
+	*string_p = (char *)xrealloc (*string_p, (*string_size_p = 1 + len));
+
+      strncpy (*string_p, string, len);
+      (*string_p)[len] = '\0';
+    }
+}
+
+/* Return a pointer to the part of PATHNAME that simply defines the file. */
+char *
+filename_non_directory (pathname)
+     char *pathname;
+{
+  char *filename;
+
+  filename = (char *)rindex (pathname, '/');
+
+  if (filename)
+    filename++;
+  else
+    filename = pathname;
+
+  return (filename);
+}
+
+/* Return non-zero if NODE is one especially created by Info. */
+int
+internal_info_node_p (node)
+     NODE *node;
+{
+  if (node &&
+      (node->filename && !*node->filename) &&
+      !node->parent && node->nodename)
+    return (1);
+  else
+    return (0);
+}
+
+/* Make NODE appear to be one especially created by Info. */
+void
+name_internal_node (node, name)
+     NODE *node;
+     char *name;
+{
+  if (!node)
+    return;
+
+  node->filename = "";
+  node->parent = (char *)NULL;
+  node->nodename = name;
+  node->flags |= N_IsInternal;
+}
+
+/* Return the window displaying NAME, the name of an internally created
+   Info window. */
+WINDOW *
+get_internal_info_window (name)
+     char *name;
+{
+  WINDOW *win = (WINDOW *)NULL;
+
+  for (win = windows; win; win = win->next)
+    if (internal_info_node_p (win->node) &&
+	(strcmp (win->node->nodename, name) == 0))
+      break;
+
+  return (win);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/info-utils.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,144 @@
+/* info-utils.h -- Exported functions and variables from info-util.c. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_INFO_UTILS_H_)
+#define _INFO_UTILS_H_
+
+#if !defined (HAVE_RINDEX)
+#undef index
+#undef rindex
+#define index strchr
+#define rindex strrchr
+#endif
+
+#if !defined (HAVE_BCOPY)
+#undef bcopy
+#define bcopy(source, dest, count) memcpy(dest, source, count)
+#endif
+
+#include "nodes.h"
+#include "window.h"
+#include "search.h"
+
+/* Structure which describes a node reference, such as a menu entry or
+   cross reference.  Arrays of such references can be built by calling
+   info_menus_of_node () or info_xrefs_of_node (). */
+typedef struct {
+  char *label;		/* User Label. */
+  char *filename;	/* File where this node can be found. */
+  char *nodename;	/* Name of the node. */
+  int start, end;	/* Offsets within the containing node of LABEL. */
+} REFERENCE;
+
+/* When non-zero, various display and input functions handle ISO Latin
+   character sets correctly. */
+extern int ISO_Latin_p;
+
+/* Variable which holds the most recent filename parsed as a result of
+   calling info_parse_xxx (). */
+extern char *info_parsed_filename;
+
+/* Variable which holds the most recent nodename parsed as a result of
+   calling info_parse_xxx (). */
+extern char *info_parsed_nodename;
+
+/* Parse the filename and nodename out of STRING.  If STRING doesn't
+   contain a filename (i.e., it is NOT (FILENAME)NODENAME) then set
+   INFO_PARSED_FILENAME to NULL.  If second argument NEWLINES_OKAY is
+   non-zero, it says to allow the nodename specification to cross a
+   newline boundary (i.e., only `,', `.', or `TAB' can end the spec). */
+void info_parse_node ();
+
+/* Return a NULL terminated array of REFERENCE * which represents the menu
+   found in NODE.  If there is no menu in NODE, just return a NULL pointer. */
+extern REFERENCE **info_menu_of_node ();
+
+/* Return a NULL terminated array of REFERENCE * which represents the cross
+   refrences found in NODE.  If there are no cross references in NODE, just
+   return a NULL pointer. */
+extern REFERENCE **info_xrefs_of_node ();
+
+/* Glean cross references from BINDING->buffer + BINDING->start until
+   BINDING->end.  Return an array of REFERENCE * that represents each
+   cross reference in this range. */
+extern REFERENCE **info_xrefs ();
+
+/* Get the entry associated with LABEL in REFERENCES.  Return a pointer to
+   the reference if found, or NULL. */
+extern REFERENCE *info_get_labeled_reference ();
+
+/* Glean menu entries from BINDING->buffer + BINDING->start until we
+   have looked at the entire contents of BINDING.  Return an array
+   of REFERENCE * that represents each menu item in this range. */
+extern REFERENCE **info_menu_items ();
+
+/* A utility function for concatenating REFERENCE **.  Returns a new
+   REFERENCE ** which is the concatenation of REF1 and REF2.  The REF1
+   and REF2 arrays are freed, but their contents are not. */
+REFERENCE **info_concatenate_references ();
+
+/* Free the data associated with REFERENCES. */
+extern void info_free_references ();
+
+/* Search for sequences of whitespace or newlines in STRING, replacing
+   all such sequences with just a single space.  Remove whitespace from
+   start and end of string. */
+void canonicalize_whitespace ();
+
+/* Return a pointer to a string which is the printed representation
+   of CHARACTER if it were printed at HPOS. */
+extern char *printed_representation ();
+
+/* Return a pointer to the part of PATHNAME that simply defines the file. */
+extern char *filename_non_directory ();
+
+/* Return non-zero if NODE is one especially created by Info. */
+extern int internal_info_node_p ();
+
+/* Make NODE appear to be one especially created by Info, and give it NAME. */
+extern void name_internal_node ();
+
+/* Return the window displaying NAME, the name of an internally created
+   Info window. */
+extern WINDOW *get_internal_info_window ();
+
+/* Return the node addressed by LABEL in NODE (usually one of "Prev:",
+   "Next:", "Up:", "File:", or "Node:".  After a call to this function,
+   the global INFO_PARSED_NODENAME and INFO_PARSED_FILENAME contain
+   the information. */
+extern void info_parse_label (/* label, node */);
+
+#define info_label_was_found \
+   (info_parsed_nodename != NULL || info_parsed_filename != NULL)
+
+#define info_file_label_of_node(n) info_parse_label (INFO_FILE_LABEL, n)
+#define info_next_label_of_node(n) info_parse_label (INFO_NEXT_LABEL, n)
+#define info_up_label_of_node(n)   info_parse_label (INFO_UP_LABEL, n)
+#define info_prev_label_of_node(n) \
+  do { \
+    info_parse_label (INFO_PREV_LABEL, n); \
+    if (!info_label_was_found) \
+      info_parse_label (INFO_ALTPREV_LABEL, n); \
+  } while (0)
+
+#endif /* !_INFO_UTILS_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/info.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,511 @@
+/* info.c -- Display nodes of Info files in multiple windows. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include "dribble.h"
+#include "getopt.h"
+
+/* The version numbers of this version of Info. */
+int info_major_version = 2;
+int info_minor_version = 10;
+int info_patch_level = 1;
+
+/* Non-zero means search all indices for APROPOS_SEARCH_STRING. */
+static int apropos_p = 0;
+
+/* Variable containing the string to search for when apropos_p is non-zero. */
+static char *apropos_search_string = (char *)NULL;
+
+/* Non-zero means print version info only. */
+static int print_version_p = 0;
+
+/* Non-zero means print a short description of the options. */
+static int print_help_p = 0;
+
+/* Array of the names of nodes that the user specified with "--node" on the
+   command line. */
+static char **user_nodenames = (char **)NULL;
+static int user_nodenames_index = 0;
+static int user_nodenames_slots = 0;
+
+/* String specifying the first file to load.  This string can only be set
+   by the user specifying "--file" on the command line. */
+static char *user_filename = (char *)NULL;
+
+/* String specifying the name of the file to dump nodes to.  This value is
+   filled if the user speficies "--output" on the command line. */
+static char *user_output_filename = (char *)NULL;
+
+/* Non-zero indicates that when "--output" is specified, all of the menu
+   items of the specified nodes (and their subnodes as well) should be
+   dumped in the order encountered.  This basically can print a book. */
+int dump_subnodes = 0;
+
+/* Structure describing the options that Info accepts.  We pass this structure
+   to getopt_long ().  If you add or otherwise change this structure, you must
+   also change the string which follows it. */
+#define APROPOS_OPTION 1
+#define DRIBBLE_OPTION 2
+#define RESTORE_OPTION 3
+static struct option long_options[] = {
+  { "apropos", 1, 0, APROPOS_OPTION },
+  { "directory", 1, 0, 'd' },
+  { "node", 1, 0, 'n' },
+  { "file", 1, 0, 'f' },
+  { "subnodes", 0, &dump_subnodes, 1 },
+  { "output", 1, 0, 'o' },
+  { "help", 0, &print_help_p, 1 },
+  { "version", 0, &print_version_p, 1 },
+  { "dribble", 1, 0, DRIBBLE_OPTION },
+  { "restore", 1, 0, RESTORE_OPTION },
+  {NULL, 0, NULL, 0}
+};
+
+/* String describing the shorthand versions of the long options found above. */
+static char *short_options = "d:n:f:o:s";
+
+/* When non-zero, the Info window system has been initialized. */
+int info_windows_initialized_p = 0;
+
+/* Some "forward" declarations. */
+static void usage (), info_short_help (), remember_info_program_name ();
+
+
+/* **************************************************************** */
+/*								    */
+/*		  Main Entry Point to the Info Program		    */
+/*								    */
+/* **************************************************************** */
+
+int
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  int getopt_long_index;	/* Index returned by getopt_long (). */
+  NODE *initial_node;		/* First node loaded by Info. */
+
+#if defined (NeXT) && defined (NOTDEF)
+  malloc_debug (0x0ffffffff);
+#endif /* NeXT && NOTDEF */
+
+  remember_info_program_name (argv[0]);
+
+  while (1)
+    {
+      int option_character;
+
+      option_character = getopt_long
+	(argc, argv, short_options, long_options, &getopt_long_index);
+
+      /* getopt_long () returns EOF when there are no more long options. */
+      if (option_character == EOF)
+	break;
+
+      /* If this is a long option, then get the short version of it. */
+      if (option_character == 0 && long_options[getopt_long_index].flag == 0)
+	option_character = long_options[getopt_long_index].val;
+
+      /* Case on the option that we have received. */
+      switch (option_character)
+	{
+	case 0:
+	  break;
+
+	  /* User wants to add a directory. */
+	case 'd':
+	  info_add_path (optarg, INFOPATH_PREPEND);
+	  break;
+
+	  /* User is specifying a particular node. */
+	case 'n':
+	  add_pointer_to_array (optarg, user_nodenames_index, user_nodenames,
+				user_nodenames_slots, 10, char *);
+	  break;
+
+	  /* User is specifying a particular Info file. */
+	case 'f':
+	  if (user_filename)
+	    free (user_filename);
+
+	  user_filename = savestring (optarg);
+	  break;
+
+	  /* User is specifying the name of a file to output to. */
+	case 'o':
+	  if (user_output_filename)
+	    free (user_output_filename);
+	  user_output_filename = savestring (optarg);
+	  break;
+
+	  /* User is specifying that she wishes to dump the subnodes of
+	     the node that she is dumping. */
+	case 's':
+	  dump_subnodes = 1;
+	  break;
+
+	  /* User has specified a string to search all indices for. */
+	case APROPOS_OPTION:
+	  apropos_p = 1;
+	  maybe_free (apropos_search_string);
+	  apropos_search_string = savestring (optarg);
+	  break;
+
+	  /* User has specified a dribble file to receive keystrokes. */
+	case DRIBBLE_OPTION:
+	  close_dribble_file ();
+	  open_dribble_file (optarg);
+	  break;
+
+	  /* User has specified an alternate input stream. */
+	case RESTORE_OPTION:
+	  info_set_input_from_file (optarg);
+	  break;
+
+	default:
+	  usage ();
+	}
+    }
+
+  /* If the user specified --version, then show the version and exit. */
+  if (print_version_p)
+    {
+      printf ("GNU Info, Version %s.\n", version_string ());
+      exit (0);
+    }
+
+  /* If the `--help' option was present, show the help and exit. */
+  if (print_help_p)
+    {
+      info_short_help ();
+      exit (0);
+    }
+  
+  /* If the user hasn't specified a path for Info files, default that path
+     now. */
+  if (!infopath)
+    {
+      char *path_from_env, *getenv ();
+
+      path_from_env = getenv ("INFOPATH");
+
+      if (path_from_env)
+	info_add_path (path_from_env);
+      else
+	info_add_path (DEFAULT_INFOPATH);
+    }
+
+  /* If the user specified a particular filename, add the path of that
+     file to the contents of INFOPATH. */
+  if (user_filename)
+    {
+      char *directory_name, *temp;
+
+      directory_name = savestring (user_filename);
+      temp = filename_non_directory (directory_name);
+
+      if (temp != directory_name)
+	{
+	  *temp = 0;
+	  info_add_path (directory_name, INFOPATH_PREPEND);
+	}
+
+      free (directory_name);
+    }
+
+  /* If the user wants to search every known index for a given string,
+     do that now, and report the results. */
+  if (apropos_p)
+    {
+      info_apropos (apropos_search_string);
+      exit (0);
+    }
+
+  /* Get the initial Info node.  It is either "(dir)Top", or what the user
+     specifed with values in user_filename and user_nodenames. */
+  if (user_nodenames)
+    initial_node = info_get_node (user_filename, user_nodenames[0]);
+  else
+    initial_node = info_get_node (user_filename, (char *)NULL);
+
+  /* If we couldn't get the initial node, this user is in trouble. */
+  if (!initial_node)
+    {
+      if (info_recent_file_error)
+	info_error (info_recent_file_error);
+      else
+	info_error
+	  (CANT_FIND_NODE, user_nodenames ? user_nodenames[0] : "Top");
+      exit (1);
+    }
+
+  /* Special cases for when the user specifies multiple nodes.  If we are
+     dumping to an output file, dump all of the nodes specified.  Otherwise,
+     attempt to create enough windows to handle the nodes that this user wants
+     displayed. */
+  if (user_nodenames_index > 1)
+    {
+      free (initial_node);
+
+      if (user_output_filename)
+	dump_nodes_to_file
+	  (user_filename, user_nodenames, user_output_filename, dump_subnodes);
+      else
+	begin_multiple_window_info_session (user_filename, user_nodenames);
+
+      exit (0);
+    }
+
+  /* If there are arguments remaining, they are the names of menu items
+     in sequential info files starting from the first one loaded.  That
+     file name is either "dir", or the contents of user_filename if one
+     was specified. */
+  while (optind != argc)
+    {
+      REFERENCE **menu;
+      REFERENCE *entry;
+      NODE *node;
+      char *arg;
+
+      /* Remember the name of the menu entry we want. */
+      arg = argv[optind++];
+
+      /* Build and return a list of the menu items in this node. */
+      menu = info_menu_of_node (initial_node);
+
+      /* If there wasn't a menu item in this node, stop here, but let
+	 the user continue to use Info.  Perhaps they wanted this node
+	 and didn't realize it. */
+      if (!menu)
+	{
+	  begin_info_session_with_error
+	    (initial_node, "There is no menu in this node.");
+	  exit (0);
+	}
+
+      /* Find the specified menu item. */
+      entry = info_get_labeled_reference (arg, menu);
+
+      /* If the item wasn't found, search the list sloppily.  Perhaps this
+	 user typed "buffer" when they really meant "Buffers". */
+      if (!entry)
+	{
+	  register int i;
+
+	  for (i = 0; entry = menu[i]; i++)
+	    if (strnicmp (entry->label, arg, strlen (arg)) == 0)
+	      break;
+	}
+
+      /* If we failed to find the reference, start Info with the current
+	 node anyway.  It is probably a misspelling. */
+      if (!entry)
+	{
+	  char *error_message = "There is no menu item \"%s\" in this node.";
+
+	  info_free_references (menu);
+
+	  /* If we were supposed to dump this node, complain. */
+	  if (user_output_filename)
+	    info_error (error_message, arg);
+	  else
+	    begin_info_session_with_error (initial_node, error_message, arg);
+
+	  exit (0);
+	}
+
+      /* We have found the reference that the user specified.  Clean it
+	 up a little bit. */
+      if (!entry->filename)
+	{
+	  if (initial_node->parent)
+	    entry->filename = savestring (initial_node->parent);
+	  else
+	    entry->filename = savestring (initial_node->filename);
+	}
+
+      /* Find this node.  If we can find it, then turn the initial_node
+	 into this one.  If we cannot find it, try using the label of the
+	 entry as a file (i.e., "(LABEL)Top").  Otherwise the Info file is
+	 malformed in some way, and we will just use the current value of
+	 initial node. */
+      node = info_get_node (entry->filename, entry->nodename);
+
+      if (!node && entry->nodename &&
+	  (strcmp (entry->label, entry->nodename) == 0))
+	node = info_get_node (entry->label, "Top");
+
+      if (node)
+	{
+	  free (initial_node);
+	  initial_node = node;
+	  info_free_references (menu);
+	}
+      else
+	{
+	  char *temp = savestring (entry->label);
+	  char *error_message;
+
+	  error_message = "Unable to find the node referenced by \"%s\".";
+
+	  info_free_references (menu);
+
+	  /* If we were trying to dump the node, then give up.  Otherwise,
+	     start the session with an error message. */
+	  if (user_output_filename)
+	    info_error (error_message, temp);
+	  else
+	    begin_info_session_with_error (initial_node, error_message, temp);
+
+	  exit (0);
+	}
+    }
+
+  /* If the user specified that this node should be output, then do that
+     now.  Otherwise, start the Info session with this node. */
+  if (user_output_filename)
+    dump_node_to_file (initial_node, user_output_filename, dump_subnodes);
+  else
+    begin_info_session (initial_node);
+
+  exit (0);
+}
+
+/* Return a string describing the current version of Info. */
+char *
+version_string ()
+{
+  static char *vstring = (char *)NULL;
+
+  if (!vstring)
+    {
+      vstring = (char *)xmalloc (50);
+      sprintf (vstring, "%d.%d", info_major_version, info_minor_version);
+      if (info_patch_level)
+	sprintf (vstring + strlen (vstring), "-p%d", info_patch_level);
+    }
+  return (vstring);
+}
+
+/* **************************************************************** */
+/*								    */
+/*		   Error Handling for Info			    */
+/*								    */
+/* **************************************************************** */
+
+static char *program_name = (char *)NULL;
+
+static void
+remember_info_program_name (fullpath)
+     char *fullpath;
+{
+  char *filename;
+
+  filename = filename_non_directory (fullpath);
+  program_name = savestring (filename);
+}
+
+/* Non-zero if an error has been signalled. */
+int info_error_was_printed = 0;
+
+/* Non-zero means ring terminal bell on errors. */
+int info_error_rings_bell_p = 1;
+
+/* Print FORMAT with ARG1 and ARG2.  If the window system was initialized,
+   then the message is printed in the echo area.  Otherwise, a message is
+   output to stderr. */
+void
+info_error (format, arg1, arg2)
+     char *format;
+     void *arg1, *arg2;
+{
+  info_error_was_printed = 1;
+
+  if (!info_windows_initialized_p || display_inhibited)
+    {
+      fprintf (stderr, "%s: ", program_name);
+      fprintf (stderr, format, arg1, arg2);
+      fprintf (stderr, "\n");
+      fflush (stderr);
+    }
+  else
+    {
+      if (!echo_area_is_active)
+	{
+	  if (info_error_rings_bell_p)
+	    terminal_ring_bell ();
+	  window_message_in_echo_area (format, arg1, arg2);
+	}
+      else
+	{
+	  NODE *temp;
+
+	  temp = build_message_node (format, arg1, arg2);
+	  if (info_error_rings_bell_p)
+	    terminal_ring_bell ();
+	  inform_in_echo_area (temp->contents);
+	  free (temp->contents);
+	  free (temp);
+	}
+    }
+}
+
+/* Produce a very brief descripton of the available options and exit with
+   an error. */
+static void
+usage ()
+{
+  fprintf (stderr,"%s\n%s\n%s\n%s\n%s\n",
+"Usage: info [-d dir-path] [-f info-file] [-o output-file] [-n node-name]...",
+"            [--directory dir-path] [--file info-file] [--node node-name]...",
+"            [--help] [--output output-file] [--subnodes] [--version]",
+"            [--dribble dribble-file] [--restore from-file]",
+"            [menu-selection ...]");
+  exit (1);
+}
+
+/* Produce a scaled down description of the available options to Info. */
+static void
+info_short_help ()
+{
+  printf ("%s", "\
+Here is a quick description of Info's options.  For a more complete\n\
+description of how to use Info, type `info info options'.\n\
+\n\
+   --directory DIR		Add DIR to INFOPATH.\n\
+   --file FILENAME		Specify Info file to visit.\n\
+   --node NODENAME		Specify nodes in first visited Info file.\n\
+   --output FILENAME		Output selected nodes to FILENAME.\n\
+   --dribble FILENAME		Remember user keystrokes in FILENAME.\n\
+   --restore FILENAME		Read initial keystrokes from FILENAME.\n\
+   --subnodes			Recursively output menu items.\n\
+   --help			Get this help message.\n\
+   --version			Display Info's version information.\n\
+\n\
+Remaining arguments to Info are treated as the names of menu\n\
+items in the initial node visited.  You can easily move to the\n\
+node of your choice by specifying the menu names which describe\n\
+the path to that node.  For example, `info emacs buffers'.\n");
+
+  exit (0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/info.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,96 @@
+/* info.h -- Header file which includes all of the other headers. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_INFO_H_)
+#define _INFO_H_
+
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "filesys.h"
+#include "display.h"
+#include "session.h"
+#include "echo_area.h"
+#include "doc.h"
+#include "footnotes.h"
+#include "gc.h"
+
+/* A structure associating the nodes visited in a particular window. */
+typedef struct {
+  WINDOW *window;		/* The window that this list is attached to. */
+  NODE **nodes;			/* Array of nodes visited in this window. */
+  int *pagetops;		/* For each node in NODES, the pagetop. */
+  long *points;			/* For each node in NODES, the point. */
+  int current;			/* Index in NODES of the current node. */
+  int nodes_index;		/* Index where to add the next node. */
+  int nodes_slots;		/* Number of slots allocated to NODES. */
+} INFO_WINDOW;
+
+/* Array of structures describing for each window which nodes have been
+   visited in that window. */
+extern INFO_WINDOW **info_windows;
+
+/* For handling errors.  If you initialize the window system, you should
+   also set info_windows_initialized_p to non-zero.  It is used by the
+   info_error () function to determine how to format and output errors. */
+extern int info_windows_initialized_p;
+
+/* Non-zero if an error message has been printed. */
+extern int info_error_was_printed;
+
+/* Non-zero means ring terminal bell on errors. */
+extern int info_error_rings_bell_p;
+
+/* Print FORMAT with ARG1 and ARG2.  If the window system was initialized,
+   then the message is printed in the echo area.  Otherwise, a message is
+   output to stderr. */
+extern void info_error ();
+
+/* The version numbers of Info. */
+extern int info_major_version, info_minor_version, info_patch_level;
+
+/* How to get the version string for this version of Info.  Returns
+   something similar to "2.9". */
+extern char *version_string ();
+
+/* Error message defines. */
+#define CANT_FIND_NODE	"Cannot find the node \"%s\"."
+#define CANT_FILE_NODE	"Cannot find the node \"(%s)%s\"."
+#define CANT_FIND_WIND	"Cannot find a window!"
+#define CANT_FIND_POINT	"Point doesn't appear within this window's node!"
+#define CANT_KILL_LAST	"Cannot delete the last window."
+#define NO_MENU_NODE	"No menu in this node."
+#define NO_FOOT_NODE	"No footnotes in this node."
+#define NO_XREF_NODE	"No cross references in this node."
+#define NO_POINTER	"No \"%s\" pointer for this node."
+#define UNKNOWN_COMMAND	"Unknown Info command `%c'.  `?' for help."
+#define TERM_TOO_DUMB	"Terminal type \"%s\" is not smart enough to run Info."
+#define AT_NODE_BOTTOM	"You are already at the last page of this node."
+#define AT_NODE_TOP	"You are already at the first page of this node."
+#define ONE_WINDOW	"Only one window."
+#define WIN_TOO_SMALL	"Resulting window would be too small."
+#define CANT_MAKE_HELP	\
+"There isn't enough room to make a help window.  Please delete a window."
+
+#endif /* !_INFO_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/infodoc.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,689 @@
+/* infohelp.c -- Functions which build documentation nodes. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+
+/* **************************************************************** */
+/*								    */
+/*			  Info Help Windows			    */
+/*								    */
+/* **************************************************************** */
+
+/* The name of the node used in the help window. */
+static char *info_help_nodename = "*Info Help*";
+
+/* A node containing printed key bindings and their documentation. */
+static NODE *internal_info_help_node = (NODE *)NULL;
+
+/* The static text which appears in the internal info help node. */
+static char *info_internal_help_text[] = {
+  "Basic Commands in Info Windows",
+  "******************************",
+  "",
+  "  h   Invoke the Info tutorial.",
+  "",
+  "Selecting other nodes:",
+  "----------------------",
+  "  n   Move to the \"next\" node of this node.",
+  "  p   Move to the \"previous\" node of this node.",
+  "  u   Move \"up\" from this node.",
+  "  m   Pick menu item specified by name.",
+  "      Picking a menu item causes another node to be selected.",
+  "  f   Follow a cross reference.  Reads name of reference.",
+  "  l   Move to the last node seen in this window.",
+  "  d   Move to the `directory' node.  Equivalent to `g(DIR)'.",
+  "",
+  "Moving within a node:",
+  "---------------------",
+  "  SPC Scroll forward a page.",
+  "  DEL Scroll backward a page.",
+  "  b   Go to the beginning of this node.",
+  "  e   Go to the end of this node.",
+  "",
+  "\"Advanced\" commands:",
+  "--------------------",
+  "  q   Quit Info.",
+  "  1   Pick first item in node's menu.",
+  "  2-9 Pick second ... ninth item in node's menu.",
+  "  0   Pick last item in node's menu.",
+  "  g   Move to node specified by name.",
+  "      You may include a filename as well, as in (FILENAME)NODENAME.",
+  "  s   Search through this Info file for a specified string,",
+  "      and select the node in which the next occurrence is found.",
+  (char *)NULL
+};
+
+void
+dump_map_to_message_buffer (prefix, map)
+     char *prefix;
+     Keymap map;
+{
+  register int i;
+
+  for (i = 0; i < 256; i++)
+    {
+      if (map[i].type == ISKMAP)
+	{
+	  char *new_prefix, *keyname;
+
+	  keyname = pretty_keyname (i);
+	  new_prefix = (char *)
+	    xmalloc (3 + strlen (prefix) + strlen (keyname));
+	  sprintf (new_prefix, "%s%s%s ", prefix, *prefix ? " " : "", keyname);
+
+	  dump_map_to_message_buffer (new_prefix, (Keymap)map[i].function);
+	  free (new_prefix);
+	}
+      else if (map[i].function)
+	{
+	  register int last;
+	  char *doc, *name;
+
+	  doc = function_documentation (map[i].function);
+	  name = function_name (map[i].function);
+
+	  if (!*doc)
+	    continue;
+
+	  /* Find out if there is a series of identical functions, as in
+	     ea_insert (). */
+	  for (last = i + 1; last < 256; last++)
+	    if ((map[last].type != ISFUNC) ||
+		(map[last].function != map[i].function))
+	      break;
+
+	  if (last - 1 != i)
+	    {
+	      printf_to_message_buffer
+		("%s%s .. ", prefix, pretty_keyname (i));
+	      printf_to_message_buffer
+		("%s%s\t", prefix, pretty_keyname (last - 1));
+	      i = last - 1;
+	    }
+	  else
+	    printf_to_message_buffer ("%s%s\t", prefix, pretty_keyname (i));
+
+#if defined (NAMED_FUNCTIONS)
+	  /* Print the name of the function, and some padding before the
+	     documentation string is printed. */
+	  {
+	    int length_so_far;
+	    int desired_doc_start = 40;	/* Must be multiple of 8. */
+
+	    printf_to_message_buffer ("(%s)", name);
+	    length_so_far = message_buffer_length_this_line ();
+
+	    if ((desired_doc_start + strlen (doc)) >= the_screen->width)
+	      printf_to_message_buffer ("\n     ");
+	    else
+	      {
+		while (length_so_far < desired_doc_start)
+		  {
+		    printf_to_message_buffer ("\t");
+		    length_so_far += character_width ('\t', length_so_far);
+		  }
+	      }
+	  }
+#endif /* NAMED_FUNCTIONS */
+	  printf_to_message_buffer ("%s\n", doc);
+	}
+    }
+}
+
+/* How to create internal_info_help_node. */
+static void
+create_internal_info_help_node ()
+{
+  register int i;
+
+  initialize_message_buffer ();
+
+  for (i = 0; info_internal_help_text[i]; i++)
+    printf_to_message_buffer ("%s\n", info_internal_help_text[i]);
+
+  printf_to_message_buffer ("---------------------\n\n");
+  printf_to_message_buffer ("The current search path is:\n");
+  printf_to_message_buffer ("  \"%s\"\n", infopath);
+  printf_to_message_buffer ("---------------------\n\n");
+  printf_to_message_buffer ("Commands available in Info windows:\n\n");
+  dump_map_to_message_buffer ("", info_keymap);
+  printf_to_message_buffer ("---------------------\n\n");
+  printf_to_message_buffer ("Commands available in the echo area:\n\n");
+  dump_map_to_message_buffer ("", echo_area_keymap);
+
+  {
+    char *message;
+
+    message = replace_in_documentation
+      ("--- Use `\\[history-node]' or `\\[kill-node]' to exit ---\n");
+    printf_to_message_buffer ("%s", message);
+  }
+
+  internal_info_help_node = message_buffer_to_node ();
+  add_gcable_pointer (internal_info_help_node->contents);
+  name_internal_node (internal_info_help_node, info_help_nodename);
+
+  /* Even though this is an internal node, we don't want the window
+     system to treat it specially.  So we turn off the internalness
+     of it here. */
+  internal_info_help_node->flags &= ~N_IsInternal;
+}
+
+/* Return a window which is the window showing help in this Info. */
+static WINDOW *
+info_find_or_create_help_window ()
+{
+  WINDOW *help_window;
+
+  help_window = get_internal_info_window (info_help_nodename);
+
+  /* If we couldn't find the help window, then make it. */
+  if (!help_window)
+    {
+      WINDOW *window, *eligible = (WINDOW *)NULL;
+      int max = 0;
+
+      for (window = windows; window; window = window->next)
+	{
+	  if (window->height > max)
+	    {
+	      max = window->height;
+	      eligible = window;
+	    }
+	}
+
+      if (!eligible)
+	return ((WINDOW *)NULL);
+      else
+	{
+	  /* Make a new node containing the help text.  Split the largest
+	     window into 2 windows, and show the help text in that window. */
+	  if (!internal_info_help_node)
+	    create_internal_info_help_node ();
+
+	  if (eligible->height > 30)
+	    {
+	      active_window = eligible;
+	      help_window = window_make_window (internal_info_help_node);
+	    }
+	  else
+	    {
+	      set_remembered_pagetop_and_point (active_window);
+	      window_set_node_of_window
+		(active_window, internal_info_help_node);
+	      help_window = active_window;
+	    }
+
+	  remember_window_and_node (help_window, help_window->node);
+	}
+    }
+  return (help_window);
+}
+
+/* Create or move to the help window. */
+DECLARE_INFO_COMMAND (info_get_help_window, "Display help message")
+{
+  WINDOW *help_window;
+
+  help_window = info_find_or_create_help_window ();
+  if (help_window)
+    {
+      active_window = help_window;
+      active_window->flags |= W_UpdateWindow;
+    }
+  else
+    {
+      info_error (CANT_MAKE_HELP);
+    }
+}
+
+/* Show the Info help node.  This means that the "info" file is installed
+   where it can easily be found on your system. */
+DECLARE_INFO_COMMAND (info_get_info_help_node, "Visit Info node `(info)Help'")
+{
+  NODE *node;
+  char *nodename;
+
+  /* If there is a window on the screen showing the node "(info)Help" or
+     the node "(info)Help-Small-Screen", simply select that window. */
+  {
+    WINDOW *win;
+
+    for (win = windows; win; win = win->next)
+      {
+	if (win->node && win->node->filename &&
+	    (stricmp
+	     (filename_non_directory (win->node->filename), "info") == 0) &&
+	    ((strcmp (win->node->nodename, "Help") == 0) ||
+	     (strcmp (win->node->nodename, "Help-Small-Screen") == 0)))
+	  {
+	    active_window = win;
+	    return;
+	  }
+      }
+  }
+
+  /* If the current window is small, show the small screen help. */
+  if (active_window->height < 24)
+    nodename = "Help-Small-Screen";
+  else
+    nodename = "Help";
+
+  /* Try to get the info file for Info. */
+  node = info_get_node ("Info", nodename);
+
+  if (!node)
+    {
+      if (info_recent_file_error)
+	info_error (info_recent_file_error);
+      else
+	info_error (CANT_FILE_NODE, "Info", nodename);
+    }
+  else
+    {
+      /* If the current window is very large (greater than 45 lines),
+	 then split it and show the help node in another window.
+	 Otherwise, use the current window. */
+
+      if (active_window->height > 45)
+	active_window = window_make_window (node);
+      else
+	{
+	  set_remembered_pagetop_and_point (active_window);
+	  window_set_node_of_window (active_window, node);
+	}
+
+      remember_window_and_node (active_window, node);
+    }
+}
+
+/* **************************************************************** */
+/*								    */
+/*		     Groveling Info Keymaps and Docs		    */
+/*								    */
+/* **************************************************************** */
+
+/* Return the documentation associated with the Info command FUNCTION. */
+char *
+function_documentation (function)
+     VFunction *function;
+{
+  register int i;
+
+  for (i = 0; function_doc_array[i].func; i++)
+    if (function == function_doc_array[i].func)
+      break;
+
+  return (replace_in_documentation (function_doc_array[i].doc));
+}
+
+#if defined (NAMED_FUNCTIONS)
+/* Return the user-visible name of the function associated with the
+   Info command FUNCTION. */
+char *
+function_name (function)
+
+     VFunction *function;
+{
+  register int i;
+
+  for (i = 0; function_doc_array[i].func; i++)
+    if (function == function_doc_array[i].func)
+      break;
+
+  return (function_doc_array[i].func_name);
+}
+
+/* Return a pointer to the function named NAME. */
+VFunction *
+named_function (name)
+     char *name;
+{
+  register int i;
+
+  for (i = 0; function_doc_array[i].func; i++)
+    if (strcmp (function_doc_array[i].func_name, name) == 0)
+      break;
+
+  return (function_doc_array[i].func);
+}
+#endif /* NAMED_FUNCTIONS */
+
+/* Return the documentation associated with KEY in MAP. */
+char *
+key_documentation (key, map)
+     char key;
+     Keymap map;
+{
+  VFunction *function = map[key].function;
+
+  if (function)
+    return (function_documentation (function));
+  else
+    return ((char *)NULL);
+}
+
+DECLARE_INFO_COMMAND (describe_key, "Print documentation for KEY")
+{
+  char keyname[50];
+  int keyname_index = 0;
+  unsigned char keystroke;
+  char *rep;
+  Keymap map;
+
+  keyname[0] = '\0';
+  map = window->keymap;
+
+  while (1)
+    {
+      message_in_echo_area ("Describe key: %s", keyname);
+      keystroke = info_get_input_char ();
+      unmessage_in_echo_area ();
+
+      if (Meta_p (keystroke) && (!ISO_Latin_p || key < 160))
+	{
+	  if (map[ESC].type != ISKMAP)
+	    {
+	      window_message_in_echo_area
+		("ESC %s is undefined.", pretty_keyname (UnMeta (keystroke)));
+	      return;
+	    }
+
+	  strcpy (keyname + keyname_index, "ESC ");
+	  keyname_index = strlen (keyname);
+	  keystroke = UnMeta (keystroke);
+	  map = (Keymap)map[ESC].function;
+	}
+
+      /* Add the printed representation of KEYSTROKE to our keyname. */
+      rep = pretty_keyname (keystroke);
+      strcpy (keyname + keyname_index, rep);
+      keyname_index = strlen (keyname);
+
+      if (map[keystroke].function == (VFunction *)NULL)
+	{
+	  message_in_echo_area ("%s is undefined.", keyname);
+	  return;
+	}
+      else if (map[keystroke].type == ISKMAP)
+	{
+	  map = (Keymap)map[keystroke].function;
+	  strcat (keyname, " ");
+	  keyname_index = strlen (keyname);
+	  continue;
+	}
+      else
+	{
+	  char *message, *fundoc, *funname = "";
+
+#if defined (NAMED_FUNCTIONS)
+	  funname = function_name (map[keystroke].function);
+#endif /* NAMED_FUNCTIONS */
+
+	  fundoc = function_documentation (map[keystroke].function);
+
+	  message = (char *)xmalloc
+	    (10 + strlen (keyname) + strlen (fundoc) + strlen (funname));
+
+#if defined (NAMED_FUNCTIONS)
+	  sprintf (message, "%s (%s): %s.", keyname, funname, fundoc);
+#else
+	  sprintf (message, "%s is defined to %s.", keyname, fundoc);
+#endif /* !NAMED_FUNCTIONS */
+
+	  window_message_in_echo_area ("%s", message);
+	  free (message);
+	  break;
+	}
+    }
+}
+
+/* How to get the pretty printable name of a character. */
+static char rep_buffer[30];
+
+char *
+pretty_keyname (key)
+     unsigned char key;
+{
+  char *rep;
+
+  if (Meta_p (key))
+    {
+      char temp[20];
+
+      rep = pretty_keyname (UnMeta (key));
+
+      sprintf (temp, "ESC %s", rep);
+      strcpy (rep_buffer, temp);
+      rep = rep_buffer;
+    }
+  else if (Control_p (key))
+    {
+      switch (key)
+	{
+	case '\n': rep = "LFD"; break;
+	case '\t': rep = "TAB"; break;
+	case '\r': rep = "RET"; break;
+	case ESC:  rep = "ESC"; break;
+
+	default:
+	  sprintf (rep_buffer, "C-%c", UnControl (key));
+	  rep = rep_buffer;
+	}
+    }
+  else
+    {
+      switch (key)
+	{
+	case ' ': rep = "SPC"; break;
+	case DEL: rep = "DEL"; break;
+	default:
+	  rep_buffer[0] = key;
+	  rep_buffer[1] = '\0';
+	  rep = rep_buffer;
+	}
+    }
+  return (rep);
+}
+
+/* Replace the names of functions with the key that invokes them. */
+static char *where_is (), *where_is_internal ();
+
+char *
+replace_in_documentation (string)
+     char *string;
+{
+  register int i, start, next;
+  static char *result = (char *)NULL;
+
+  maybe_free (result);
+  result = (char *)xmalloc (1 + strlen (string));
+
+  i = next = start = 0;
+
+  /* Skip to the beginning of a replaceable function. */
+  for (i = start; string[i]; i++)
+    {
+      /* Is this the start of a replaceable function name? */
+      if (string[i] == '\\' && string[i + 1] == '[')
+	{
+	  char *fun_name, *rep;
+	  VFunction *function;
+
+	  /* Copy in the old text. */
+	  strncpy (result + next, string + start, i - start);
+	  next += (i - start);
+	  start = i + 2;
+
+	  /* Move to the end of the function name. */
+	  for (i = start; string[i] && (string[i] != ']'); i++);
+
+	  fun_name = (char *)xmalloc (1 + i - start);
+	  strncpy (fun_name, string + start, i - start);
+	  fun_name[i - start] = '\0';
+
+	  /* Find a key which invokes this function in the info_keymap. */
+	  function = named_function (fun_name);
+
+	  /* If the internal documentation string fails, there is a 
+	     serious problem with the associated command's documentation.
+	     We croak so that it can be fixed immediately. */
+	  if (!function)
+	    abort ();
+
+	  rep = where_is (info_keymap, function);
+	  strcpy (result + next, rep);
+	  next = strlen (result);
+
+	  start = i;
+	  if (string[i])
+	    start++;
+	}
+    }
+  strcpy (result + next, string + start);
+  return (result);
+}
+
+/* Return a string of characters which could be typed from the keymap
+   MAP to invoke FUNCTION. */
+static char *where_is_rep = (char *)NULL;
+static int where_is_rep_index = 0;
+static int where_is_rep_size = 0;
+
+static char *
+where_is (map, function)
+     Keymap map;
+     VFunction *function;
+{
+  char *rep;
+
+  if (!where_is_rep_size)
+    where_is_rep = (char *)xmalloc (where_is_rep_size = 100);
+  where_is_rep_index = 0;
+
+  rep = where_is_internal (map, function);
+
+  /* If it couldn't be found, return "M-x Foo". */
+  if (!rep)
+    {
+      char *name;
+
+      name = function_name (function);
+
+      if (name)
+	sprintf (where_is_rep, "M-x %s", name);
+
+      rep = where_is_rep;
+    }
+  return (rep);
+}
+
+/* Return the printed rep of FUNCTION as found in MAP, or NULL. */
+static char *
+where_is_internal (map, function)
+     Keymap map;
+     VFunction *function;
+{
+  register int i;
+  
+  /* If the function is directly invokable in MAP, return the representation
+     of that keystroke. */
+  for (i = 0; i < 256; i++)
+    if ((map[i].type == ISFUNC) && map[i].function == function)
+      {
+	sprintf (where_is_rep + where_is_rep_index, "%s", pretty_keyname (i));
+	return (where_is_rep);
+      }
+
+  /* Okay, search subsequent maps for this function. */
+  for (i = 0; i < 256; i++)
+    {
+      if (map[i].type == ISKMAP)
+	{
+	  int saved_index = where_is_rep_index;
+	  char *rep;
+
+	  sprintf (where_is_rep + where_is_rep_index, "%s ",
+		   pretty_keyname (i));
+
+	  where_is_rep_index = strlen (where_is_rep);
+	  rep = where_is_internal ((Keymap)map[i].function, function);
+
+	  if (rep)
+	    return (where_is_rep);
+
+	  where_is_rep_index = saved_index;
+	}
+    }
+
+  return ((char *)NULL);
+}
+
+extern char *read_function_name ();
+
+DECLARE_INFO_COMMAND (info_where_is,
+   "Show what to type to execute a given command")
+{
+  char *command_name;
+
+  command_name = read_function_name ("Where is command: ", window);
+
+  if (!command_name)
+    {
+      info_abort_key (active_window, count, key);
+      return;
+    }
+
+  if (*command_name)
+    {
+      VFunction *function;
+
+      function = named_function (command_name);
+
+      if (function)
+	{
+	  char *location;
+
+	  location = where_is (active_window->keymap, function);
+
+	  if (!location)
+	    {
+	      info_error ("`%s' is not on any keys", command_name);
+	    }
+	  else
+	    {
+	      if (strncmp (location, "M-x ", 4) == 0)
+		window_message_in_echo_area
+		  ("%s can only be invoked via %s.", command_name, location);
+	      else
+		window_message_in_echo_area
+		  ("%s can be invoked via %s.", command_name, location);
+	    }
+	}
+      else
+	info_error ("There is no function named `%s'", command_name);
+    }
+
+  free (command_name);
+}
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/infomap.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,269 @@
+/* infomap.c -- Keymaps for Info. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "stdio.h"
+#include "ctype.h"
+#include "infomap.h"
+#include "funs.h"
+
+/* Return a new keymap which has all the uppercase letters mapped to run
+   the function info_do_lowercase_version (). */
+Keymap
+keymap_make_keymap ()
+{
+  register int i;
+  Keymap keymap;
+
+  keymap = (Keymap)xmalloc (256 * sizeof (KEYMAP_ENTRY));
+
+  for (i = 0; i < 256; i++)
+    {
+      keymap[i].type = ISFUNC;
+      keymap[i].function = (VFunction *)NULL;
+    }
+
+  for (i = 'A'; i < ('Z' + 1); i++)
+    {
+      keymap[i].type = ISFUNC;
+      keymap[i].function = info_do_lowercase_version;
+    }
+
+  return (keymap);
+}
+
+/* Return a new keymap which is a copy of MAP. */
+Keymap
+keymap_copy_keymap (map)
+     Keymap map;
+{
+  register int i;
+  Keymap keymap;
+
+  keymap = keymap_make_keymap ();
+
+  for (i = 0; i < 256; i++)
+    {
+      keymap[i].type = map[i].type;
+      keymap[i].function = map[i].function;
+    }
+  return (keymap);
+}
+
+/* Free the keymap and it's descendents. */
+void
+keymap_discard_keymap (map)
+     Keymap (map);
+{
+  register int i;
+
+  if (!map)
+    return;
+
+  for (i = 0; i < 256; i++)
+    {
+      switch (map[i].type)
+	{
+	case ISFUNC:
+	  break;
+
+	case ISKMAP:
+	  keymap_discard_keymap ((Keymap)map[i].function);
+	  break;
+
+	}
+    }
+}
+
+/* Initialize the standard info keymaps. */
+
+Keymap info_keymap = (Keymap)NULL;
+Keymap echo_area_keymap = (Keymap)NULL;
+
+void
+initialize_info_keymaps ()
+{
+  register int i;
+  Keymap map;
+
+  if (!info_keymap)
+    {
+      info_keymap = keymap_make_keymap ();
+      info_keymap[ESC].type = ISKMAP;
+      info_keymap[ESC].function = (VFunction *)keymap_make_keymap ();
+      info_keymap[Control ('x')].type = ISKMAP;
+      info_keymap[Control ('x')].function = (VFunction *)keymap_make_keymap ();
+      echo_area_keymap = keymap_make_keymap ();
+      echo_area_keymap[ESC].type = ISKMAP;
+      echo_area_keymap[ESC].function = (VFunction *)keymap_make_keymap ();
+      echo_area_keymap[Control ('x')].type = ISKMAP;
+      echo_area_keymap[Control ('x')].function =
+	(VFunction *)keymap_make_keymap ();
+    }
+
+  /* Bind numeric arg functions for both echo area and info window maps. */
+  for (i = '0'; i < '9' + 1; i++)
+    {
+      ((Keymap) info_keymap[ESC].function)[i].function =
+	((Keymap) echo_area_keymap[ESC].function)[i].function =
+	  info_add_digit_to_numeric_arg;
+    }
+  ((Keymap) info_keymap[ESC].function)['-'].function =
+    ((Keymap) echo_area_keymap[ESC].function)['-'].function =
+      info_add_digit_to_numeric_arg;
+
+  /* Bind the echo area routines. */
+  map = echo_area_keymap;
+
+  /* Bind the echo area insert routines. */
+  for (i = 0; i < 160; i++)
+    if (isprint (i))
+      map[i].function = ea_insert;
+
+  map[Control ('a')].function = ea_beg_of_line;
+  map[Control ('b')].function = ea_backward;
+  map[Control ('d')].function = ea_delete;
+  map[Control ('e')].function = ea_end_of_line;
+  map[Control ('f')].function = ea_forward;
+  map[Control ('g')].function = ea_abort;
+  map[Control ('h')].function = ea_rubout;
+  map[Control ('k')].function = ea_kill_line;
+  map[Control ('l')].function = info_redraw_display;
+  map[Control ('q')].function = ea_quoted_insert;
+  map[Control ('t')].function = ea_transpose_chars;
+  map[Control ('u')].function = info_universal_argument;
+  map[Control ('y')].function = ea_yank;
+
+  map[LFD].function = ea_newline;
+  map[RET].function = ea_newline;
+  map[SPC].function = ea_complete;
+  map[TAB].function = ea_complete;
+  map['?'].function = ea_possible_completions;
+  map[DEL].function = ea_rubout;
+
+  /* Bind the echo area ESC keymap. */
+  map = (Keymap)echo_area_keymap[ESC].function;
+
+  map[Control ('g')].function = ea_abort;
+  map[Control ('v')].function = ea_scroll_completions_window;
+  map['b'].function = ea_backward_word;
+  map['d'].function = ea_kill_word;
+  map['f'].function = ea_forward_word;
+#if defined (NAMED_FUNCTIONS)
+  /* map['x'].function = info_execute_command; */
+#endif /* NAMED_FUNCTIONS */
+  map['y'].function = ea_yank_pop;
+  map['?'].function = ea_possible_completions;
+  map[TAB].function = ea_tab_insert;
+  map[DEL].function = ea_backward_kill_word;
+
+  /* Bind the echo area Control-x keymap. */
+  map = (Keymap)echo_area_keymap[Control ('x')].function;
+
+  map['o'].function = info_next_window;
+  map[DEL].function = ea_backward_kill_line;
+
+  /* Bind commands for Info window keymaps. */
+  map = info_keymap;
+  map[TAB].function = info_move_to_next_xref;
+  map[LFD].function = info_select_reference_this_line;
+  map[RET].function = info_select_reference_this_line;
+  map[SPC].function = info_scroll_forward;
+  map[Control ('a')].function = info_beginning_of_line;
+  map[Control ('b')].function = info_backward_char;
+  map[Control ('e')].function = info_end_of_line;
+  map[Control ('f')].function = info_forward_char;
+  map[Control ('g')].function = info_abort_key;
+  map[Control ('h')].function = info_get_help_window;
+  map[Control ('l')].function = info_redraw_display;
+  map[Control ('n')].function = info_next_line;
+  map[Control ('p')].function = info_prev_line;
+  map[Control ('r')].function = isearch_backward;
+  map[Control ('s')].function = isearch_forward;
+  map[Control ('u')].function = info_universal_argument;
+  map[Control ('v')].function = info_scroll_forward;
+  map[','].function = info_next_index_match;
+
+  for (i = '1'; i < '9' + 1; i++)
+    map[i].function = info_menu_digit;
+  map['0'].function = info_last_menu_item;
+
+  map['<'].function = info_first_node;
+  map['>'].function = info_last_node;
+  map['?'].function = info_get_help_window;
+  map['['].function = info_global_prev_node;
+  map[']'].function = info_global_next_node;
+
+  map['b'].function = info_beginning_of_node;
+  map['d'].function = info_dir_node;
+  map['e'].function = info_end_of_node;
+  map['f'].function = info_xref_item;
+  map['g'].function = info_goto_node;
+  map['h'].function = info_get_info_help_node;
+  map['i'].function = info_index_search;
+  map['l'].function = info_history_node;
+  map['m'].function = info_menu_item;
+  map['n'].function = info_next_node;
+  map['p'].function = info_prev_node;
+  map['q'].function = info_quit;
+  map['r'].function = info_xref_item;
+  map['s'].function = info_search;
+  map['t'].function = info_top_node;
+  map['u'].function = info_up_node;
+  map[DEL].function = info_scroll_backward;
+
+  /* Bind members in the ESC map for Info windows. */
+  map = (Keymap)info_keymap[ESC].function;
+  map[Control ('f')].function = info_show_footnotes;
+  map[Control ('g')].function = info_abort_key;
+  map[TAB].function = info_move_to_prev_xref;
+  map[Control ('v')].function = info_scroll_other_window;
+  map['<'].function = info_beginning_of_node;
+  map['>'].function = info_end_of_node;
+  map['b'].function = info_backward_word;
+  map['f'].function = info_forward_word;
+  map['r'].function = info_move_to_window_line;
+  map['v'].function = info_scroll_backward;
+#if defined (NAMED_FUNCTIONS)
+  map['x'].function = info_execute_command;
+#endif /* NAMED_FUNCTIONS */
+
+  /* Bind members in the Control-X map for Info windows. */
+  map = (Keymap)info_keymap[Control ('x')].function;
+
+  map[Control ('b')].function = list_visited_nodes;
+  map[Control ('c')].function = info_quit;
+  map[Control ('f')].function = info_view_file;
+  map[Control ('g')].function = info_abort_key;
+  map[Control ('v')].function = info_view_file;
+  map['0'].function = info_delete_window;
+  map['1'].function = info_keep_one_window;
+  map['2'].function = info_split_window;
+  map['^'].function = info_grow_window;
+  map['b'].function = select_visited_node;
+  map['k'].function = info_kill_node;
+  map['o'].function = info_next_window;
+  map['t'].function = info_tile_windows;
+  map['w'].function = info_toggle_wrap;
+}
+
+  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/infomap.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,82 @@
+/* infomap.h -- Description of a keymap in Info and related functions. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_INFOMAP_H_)
+#define _INFOMAP_H_
+
+#include "general.h"
+
+#define ESC '\033'
+#define DEL '\177'
+#define TAB '\011'	
+#define RET '\r'
+#define LFD '\n'
+#define SPC ' '
+
+#define meta_character_threshold (DEL + 1)
+#define control_character_threshold (SPC)
+
+#define meta_character_bit 0x80
+#define control_character_bit 0x40
+
+#define Meta_p(c) (((c) > meta_character_threshold))
+#define Control_p(c) ((c) < control_character_threshold)
+
+#define Meta(c) ((c) | (meta_character_bit))
+#define UnMeta(c) ((c) & (~meta_character_bit))
+#define Control(c) ((toupper (c)) & (~control_character_bit))
+#define UnControl(c) (tolower ((c) | control_character_bit))
+
+/* A keymap contains one entry for each key in the ASCII set.
+   Each entry consists of a type and a pointer.
+   FUNCTION is the address of a function to run, or the
+   address of a keymap to indirect through.
+   TYPE says which kind of thing FUNCTION is. */
+typedef struct {
+  char type;
+  VFunction *function;
+} KEYMAP_ENTRY;
+
+typedef KEYMAP_ENTRY *Keymap;
+
+/* The values that TYPE can have in a keymap entry. */
+#define ISFUNC 0
+#define ISKMAP 1
+
+extern Keymap info_keymap;
+extern Keymap echo_area_keymap;
+
+/* Return a new keymap which has all the uppercase letters mapped to run
+   the function info_do_lowercase_version (). */
+extern Keymap keymap_make_keymap ();
+
+/* Return a new keymap which is a copy of MAP. */
+extern Keymap keymap_copy_keymap ();
+
+/* Free MAP and it's descendents. */
+extern void keymap_discard_keymap ();
+
+/* Initialize the info keymaps. */
+extern void initialize_info_keymaps ();
+
+#endif /* !_INFOMAP_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/m-x.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,195 @@
+/* m-x.c -- Meta-X minibuffer reader. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+
+/* **************************************************************** */
+/*								    */
+/*		       Reading Named Commands			    */
+/*								    */
+/* **************************************************************** */
+
+/* Read the name of an Info function in the echo area and return the
+   name.  A return value of NULL indicates that no function name could
+   be read. */
+char *
+read_function_name (prompt, window)
+     char *prompt;
+     WINDOW *window;
+{
+  register int i;
+  char *line;
+  REFERENCE **array = (REFERENCE **)NULL;
+  int array_index = 0, array_slots = 0;
+
+  /* Make an array of REFERENCE which actually contains the names of
+     the functions available in Info. */
+  for (i = 0; function_doc_array[i].func; i++)
+    {
+      REFERENCE *entry;
+
+      entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
+      entry->label = savestring (function_doc_array[i].func_name);
+      entry->nodename = (char *)NULL;
+      entry->filename = (char *)NULL;
+
+      add_pointer_to_array
+	(entry, array_index, array, array_slots, 200, REFERENCE *);
+    }
+
+  line = info_read_completing_in_echo_area (window, prompt, array);
+
+  info_free_references (array);
+
+  if (!echo_area_is_active)
+    window_clear_echo_area ();
+
+  return (line);
+}
+
+DECLARE_INFO_COMMAND (describe_command,
+   "Read the name of an Info command and describe it")
+{
+  char *line;
+
+  line = read_function_name ("Describe command: ", window);
+
+  if (!line)
+    {
+      info_abort_key (active_window, count, key);
+      return;
+    }
+
+  /* Describe the function named in "LINE". */
+  if (*line)
+    {
+      char *fundoc;
+      VFunction *fun;
+
+      fun = named_function (line);
+
+      if (!fun)
+	return;
+
+      window_message_in_echo_area ("%s: %s.",
+				   line, function_documentation (fun));
+    }
+  free (line);
+}
+
+DECLARE_INFO_COMMAND (info_execute_command,
+   "Read a command name in the echo area and execute it")
+{
+  char *line;
+
+  /* Ask the completer to read a reference for us. */
+  if (info_explicit_arg || count != 1)
+    {
+      char *prompt;
+
+      prompt = (char *)xmalloc (20);
+      sprintf (prompt, "%d M-x ", count);
+      line = read_function_name (prompt, window);
+    }
+  else
+    line = read_function_name ("M-x ", window);
+
+  /* User aborted? */
+  if (!line)
+    {
+      info_abort_key (active_window, count, key);
+      return;
+    }
+
+  /* User accepted "default"?  (There is none.) */
+  if (!*line)
+    {
+      free (line);
+      return;
+    }
+
+  /* User wants to execute a named command.  Do it. */
+  {
+    VFunction *function;
+
+    if ((active_window != the_echo_area) &&
+	(strncmp (line, "echo-area-", 10) == 0))
+      {
+	free (line);
+	info_error ("Cannot execute an `echo-area' command here.");
+	return;
+      }
+
+    function = named_function (line);
+    free (line);
+
+    if (!function)
+      return;
+
+    (*function) (active_window, count, 0);
+  }
+}
+
+/* Okay, now that we have M-x, let the user set the screen height. */
+DECLARE_INFO_COMMAND (set_screen_height,
+  "Set the height of the displayed window")
+{
+  int new_height;
+
+  if (info_explicit_arg || count != 1)
+    new_height = count;
+  else
+    {
+      char prompt[80];
+      char *line;
+
+      new_height = screenheight;
+
+      sprintf (prompt, "Set screen height to (%d): ", new_height);
+
+      line = info_read_in_echo_area (window, prompt);
+
+      /* If the user aborted, do that now. */
+      if (!line)
+	{
+	  info_abort_key (active_window, count, 0);
+	  return;
+	}
+
+      /* Find out what the new height is supposed to be. */
+      if (*line)
+	new_height = atoi (line);
+
+      /* Clear the echo area if it isn't active. */
+      if (!echo_area_is_active)
+	window_clear_echo_area ();
+
+      free (line);
+    }
+
+  terminal_clear_screen ();
+  display_clear_display (the_display);
+  screenheight = new_height;
+  display_initialize_display (screenwidth, screenheight);
+  window_new_screen_size (screenwidth, screenheight);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/makedoc.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,477 @@
+/* makedoc.c -- Make DOC.C and FUNS.H from input files. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+/* This program grovels the contents of the source files passed as arguments
+   and writes out a file of function pointers and documentation strings, and
+   a header file which describes the contents.  This only does the functions
+   declared with DECLARE_INFO_COMMAND. */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include "general.h"
+
+#if !defined (O_RDONLY)
+#if defined (HAVE_SYS_FCNTL_H)
+#include <sys/fcntl.h>
+#else /* !HAVE_SYS_FCNTL_H */
+#include <fcntl.h>
+#endif /* !HAVE_SYS_FCNTL_H */
+#endif /* !O_RDONLY */
+
+extern void *xmalloc (), *xrealloc ();
+static void fatal_file_error ();
+
+/* Name of the header file which receives the declarations of functions. */
+static char *funs_filename = "funs.h";
+
+/* Name of the documentation to function pointer file. */
+static char *doc_filename = "doc.c";
+
+static char *doc_header[] = {
+  "/* doc.c -- Generated structure containing function names and doc strings.",
+  "",
+  "   This file was automatically made from various source files with the",
+  "   command \"%s\".  DO NOT EDIT THIS FILE, only \"%s.c\".",
+  (char *)NULL
+};
+
+static char *doc_header_1[] = {
+  "   An entry in the array FUNCTION_DOC_ARRAY is made for each command",
+  "   found in the above files; each entry consists of a function pointer,",
+#if defined (NAMED_FUNCTIONS)
+  "   a string which is the user-visible name of the function,",
+#endif /* NAMED_FUNCTIONS */
+  "   and a string which documents its purpose. */",
+  "",
+  "#include \"doc.h\"",
+  "#include \"funs.h\"",
+  "",
+  "FUNCTION_DOC function_doc_array[] = {",
+  "",
+  (char *)NULL
+};
+
+/* How to remember the locations of the functions found so that Emacs
+   can use the information in a tag table. */
+typedef struct {
+  char *name;			/* Name of the tag. */
+  int line;			/* Line number at which it appears. */
+  long char_offset;		/* Character offset at which it appears. */
+} EMACS_TAG;
+
+typedef struct {
+  char *filename;		/* Name of the file containing entries. */
+  long entrylen;		/* Total number of characters in tag block. */
+  EMACS_TAG **entries;		/* Entries found in FILENAME. */
+  int entries_index;
+  int entries_slots;
+} EMACS_TAG_BLOCK;
+
+EMACS_TAG_BLOCK **emacs_tags = (EMACS_TAG_BLOCK **)NULL;
+int emacs_tags_index = 0;
+int emacs_tags_slots = 0;
+
+#define DECLARATION_STRING "\nDECLARE_INFO_COMMAND"
+
+static void process_one_file ();
+static void maybe_dump_tags ();
+static FILE *must_fopen ();
+
+int
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  register int i;
+  int tags_only = 0;
+  FILE *funs_stream, *doc_stream;
+
+  for (i = 1; i < argc; i++)
+    if (strcmp (argv[i], "-tags") == 0)
+      {
+	tags_only++;
+	break;
+      }
+
+  if (tags_only)
+    {
+      funs_filename = "/dev/null";
+      doc_filename = "/dev/null";
+    }
+  
+  funs_stream = must_fopen (funs_filename, "w");
+  doc_stream = must_fopen (doc_filename, "w");
+
+  fprintf (funs_stream,
+	   "/* %s -- Generated declarations for Info commands. */\n",
+	   funs_filename);
+
+  for (i = 0; doc_header[i]; i++)
+    {
+      fprintf (doc_stream, doc_header[i], argv[0], argv[0]);
+      fprintf (doc_stream, "\n");
+    }
+
+  fprintf (doc_stream,
+	   "   Source files groveled to make this file include:\n\n");
+
+  for (i = 1; i < argc; i++)
+    fprintf (doc_stream, "\t%s\n", argv[i]);
+
+  fprintf (doc_stream, "\n");
+
+  for (i = 0; doc_header_1[i]; i++)
+    fprintf (doc_stream, "%s\n", doc_header_1[i]);
+
+
+  for (i = 1; i < argc; i++)
+    {
+      char *curfile;
+      curfile = argv[i];
+
+      if (*curfile == '-')
+	continue;
+
+      fprintf (doc_stream, "/* Commands found in \"%s\". */\n", curfile);
+      fprintf (funs_stream, "\n/* Functions declared in \"%s\". */\n",
+	       curfile);
+
+      process_one_file (curfile, doc_stream, funs_stream);
+    }
+
+  fprintf (doc_stream,
+	   "   { (VFunction *)NULL, (char *)NULL, (char *)NULL }\n};\n");
+
+  fclose (funs_stream);
+  fclose (doc_stream);
+
+  if (tags_only)
+    maybe_dump_tags (stdout);
+  exit (0);
+}
+
+/* Dumping out the contents of an Emacs tags table. */
+static void
+maybe_dump_tags (stream)
+     FILE *stream;
+{
+  register int i;
+
+  /* Print out the information for each block. */
+  for (i = 0; i < emacs_tags_index; i++)
+    {
+      register int j;
+      register EMACS_TAG_BLOCK *block;
+      register EMACS_TAG *etag;
+      long block_len;
+
+      block_len = 0;
+      block = emacs_tags[i];
+
+      /* Calculate the length of the dumped block first. */
+      for (j = 0; j < block->entries_index; j++)
+	{
+	  char digits[30];
+	  etag = block->entries[j];
+	  block_len += 3 + strlen (etag->name);
+	  sprintf (digits, "%d,%d", etag->line, etag->char_offset);
+	  block_len += strlen (digits);
+	}
+
+      /* Print out the defining line. */
+      fprintf (stream, "\f\n%s,%d\n", block->filename, block_len);
+
+      /* Print out the individual tags. */
+      for (j = 0; j < block->entries_index; j++)
+	{
+	  etag = block->entries[j];
+
+	  fprintf (stream, "%s,\177%d,%d\n",
+		   etag->name, etag->line, etag->char_offset);
+	}
+    }
+}
+
+/* Keeping track of names, line numbers and character offsets of functions
+   found in source files. */
+static EMACS_TAG_BLOCK *
+make_emacs_tag_block (filename)
+     char *filename;
+{
+  EMACS_TAG_BLOCK *block;
+
+  block = (EMACS_TAG_BLOCK *)xmalloc (sizeof (EMACS_TAG_BLOCK));
+  block->filename = savestring (filename);
+  block->entrylen = 0;
+  block->entries = (EMACS_TAG **)NULL;
+  block->entries_index = 0;
+  block->entries_slots = 0;
+  return (block);
+}
+
+static void
+add_tag_to_block (block, name, line, char_offset)
+     EMACS_TAG_BLOCK *block;
+     char *name;
+     int line;
+     long char_offset;
+{
+  EMACS_TAG *tag;
+
+  tag = (EMACS_TAG *)xmalloc (sizeof (EMACS_TAG));
+  tag->name = name;
+  tag->line = line;
+  tag->char_offset = char_offset;
+  add_pointer_to_array (tag, block->entries_index, block->entries,
+			block->entries_slots, 50, EMACS_TAG *);
+}
+
+/* Read the file represented by FILENAME into core, and search it for Info
+   function declarations.  Output the declarations in various forms to the
+   DOC_STREAM and FUNS_STREAM. */
+static void
+process_one_file (filename, doc_stream, funs_stream)
+     char *filename;
+     FILE *doc_stream, *funs_stream;
+{
+  int descriptor, decl_len;
+  char *buffer, *decl_str;
+  struct stat finfo;
+  long offset;
+  EMACS_TAG_BLOCK *block;
+
+  if (stat (filename, &finfo) == -1)
+    fatal_file_error (filename);
+
+  descriptor = open (filename, O_RDONLY, 0666);
+
+  if (descriptor == -1)
+    fatal_file_error (filename);
+
+  buffer = (char *)xmalloc (1 + finfo.st_size);
+  read (descriptor, buffer, finfo.st_size);
+  close (descriptor);
+
+  offset = 0;
+  decl_str = DECLARATION_STRING;
+  decl_len = strlen (decl_str);
+
+  block = make_emacs_tag_block (filename);
+
+  while (1)
+    {
+      long point = 0;
+      long line_start = 0;
+      int line_number = 0;
+
+      char *func, *doc;
+#if defined (NAMED_FUNCTIONS)
+      char *func_name;
+#endif /* NAMED_FUNCTIONS */
+
+      for (; offset < (finfo.st_size - decl_len); offset++)
+	{
+	  if (buffer[offset] == '\n')
+	    {
+	      line_number++;
+	      line_start = offset + 1;
+	    }
+
+	  if (strncmp (buffer + offset, decl_str, decl_len) == 0)
+	    {
+	      offset += decl_len;
+	      point = offset;
+	      break;
+	    }
+	}
+
+      if (!point)
+	break;
+
+      /* Skip forward until we find the open paren. */
+      while (point < finfo.st_size)
+	{
+	  if (buffer[point] == '\n')
+	    {
+	      line_number++;
+	      line_start = point + 1;
+	    }
+	  else if (buffer[point] == '(')
+	    break;
+
+	  point++;
+	}
+
+      while (point++ < finfo.st_size)
+	{
+	  if (!whitespace_or_newline (buffer[point]))
+	    break;
+	  else if (buffer[point] == '\n')
+	    {
+	      line_number++;
+	      line_start = point + 1;
+	    }
+	}
+
+      if (point >= finfo.st_size)
+	break;
+
+      /* Now looking at name of function.  Get it. */
+      for (offset = point; buffer[offset] != ','; offset++);
+      func = (char *)xmalloc (1 + (offset - point));
+      strncpy (func, buffer + point, offset - point);
+      func[offset - point] = '\0';
+
+      /* Remember this tag in the current block. */
+      {
+	char *tag_name;
+
+	tag_name = (char *)xmalloc (1 + (offset - line_start));
+	strncpy (tag_name, buffer + line_start, offset - line_start);
+	tag_name[offset - line_start] = '\0';
+	add_tag_to_block (block, tag_name, line_number, point);
+      }
+
+#if defined (NAMED_FUNCTIONS)
+      /* Generate the user-visible function name from the function's name. */
+      {
+	register int i;
+	char *name_start;
+
+	name_start = func;
+
+	if (strncmp (name_start, "info_", 5) == 0)
+	  name_start += 5;
+
+	func_name = savestring (name_start);
+
+	/* Fix up "ea" commands. */
+	if (strncmp (func_name, "ea_", 3) == 0)
+	  {
+	    char *temp_func_name;
+
+	    temp_func_name = (char *)xmalloc (10 + strlen (func_name));
+	    strcpy (temp_func_name, "echo_area_");
+	    strcat (temp_func_name, func_name + 3);
+	    free (func_name);
+	    func_name = temp_func_name;
+	  }
+
+	for (i = 0; func_name[i]; i++)
+	  if (func_name[i] == '_')
+	    func_name[i] = '-';
+      }
+#endif /* NAMED_FUNCTIONS */
+
+      /* Find doc string. */
+      point = offset + 1;
+
+      while (point < finfo.st_size)
+	{
+	  if (buffer[point] == '\n')
+	    {
+	      line_number++;
+	      line_start = point + 1;
+	    }
+
+	  if (buffer[point] == '"')
+	    break;
+	  else
+	    point++;
+	}
+
+      offset = point + 1;
+
+      while (offset < finfo.st_size)
+	{
+	  if (buffer[offset] == '\n')
+	    {
+	      line_number++;
+	      line_start = offset + 1;
+	    }
+
+	  if (buffer[offset] == '\\')
+	    offset += 2;
+	  else if (buffer[offset] == '"')
+	    break;
+	  else
+	    offset++;
+	}
+
+      offset++;
+      if (offset >= finfo.st_size)
+	break;
+
+      doc = (char *)xmalloc (1 + (offset - point));
+      strncpy (doc, buffer + point, offset - point);
+      doc[offset - point] = '\0';
+
+#if defined (NAMED_FUNCTIONS)
+      fprintf (doc_stream, "   { %s, \"%s\", %s },\n", func, func_name, doc);
+      free (func_name);
+#else /* !NAMED_FUNCTIONS */
+      fprintf (doc_stream, "   { %s, %s },\n", func, doc);
+#endif /* !NAMED_FUNCTIONS */
+
+      fprintf (funs_stream, "extern void %s ();\n", func);
+      free (func);
+      free (doc);
+    }
+  free (buffer);
+
+  /* If we created any tags, remember this file on our global list.  Otherwise,
+     free the memory already allocated to it. */
+  if (block->entries)
+    add_pointer_to_array (block, emacs_tags_index, emacs_tags,
+			  emacs_tags_slots, 10, EMACS_TAG_BLOCK *);
+  else
+    {
+      free (block->filename);
+      free (block);
+    }
+}
+
+static void
+fatal_file_error (filename)
+     char *filename;
+{
+  fprintf (stderr, "Couldn't manipulate the file %s.\n", filename);
+  exit (2);
+}
+
+static FILE *
+must_fopen (filename, mode)
+     char *filename, *mode;
+{
+  FILE *stream;
+
+  stream = fopen (filename, mode);
+  if (!stream)
+    fatal_file_error (filename);
+
+  return (stream);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/nodemenu.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,321 @@
+/* nodemenu.c -- Produce a menu of all visited nodes. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+
+/* Return a line describing the format of a node information line. */
+static char *
+nodemenu_format_info ()
+{
+  return ("\n\
+* Menu:\n\
+  (File)Node                        Lines   Size   Containing File\n\
+  ----------                        -----   ----   ---------------");
+}
+
+/* Produce a formatted line of information about NODE.  Here is what we want
+   the output listing to look like:
+
+* Menu:
+  (File)Node                        Lines   Size   Containing File
+  ----------                        -----   ----   ---------------
+* (emacs)Buffers::                  48      2230   /usr/gnu/info/emacs/emacs-1
+* (autoconf)Writing configure.in::  123     58789  /usr/gnu/info/autoconf/autoconf-1
+* (dir)Top::			    40      589    /usr/gnu/info/dir
+*/
+static char *
+format_node_info (node)
+     NODE *node;
+{
+  register int i, len;
+  char *parent, *containing_file;
+  static char *line_buffer = (char *)NULL;
+
+  if (!line_buffer)
+    line_buffer = (char *)xmalloc (1000);
+
+  if (node->parent)
+    {
+      parent = filename_non_directory (node->parent);
+      if (!parent)
+	parent = node->parent;
+    }
+  else
+    parent = (char *)NULL;
+
+  containing_file = node->filename;
+
+  if (!parent && !*containing_file)
+    sprintf (line_buffer, "* %s::", node->nodename);
+  else
+    {
+      char *file = (char *)NULL;
+
+      if (parent)
+	file = parent;
+      else
+	file = filename_non_directory (containing_file);
+
+      if (!file)
+	file = containing_file;
+
+      if (!*file)
+	file = "dir";
+
+      sprintf (line_buffer, "* (%s)%s::", file, node->nodename);
+    }
+
+  len = pad_to (36, line_buffer);
+
+  {
+    int lines = 1;
+
+    for (i = 0; i < node->nodelen; i++)
+      if (node->contents[i] == '\n')
+	lines++;
+
+    sprintf (line_buffer + len, "%d", lines);
+  }
+
+  len = pad_to (44, line_buffer);
+  sprintf (line_buffer + len, "%d", node->nodelen);
+
+  if (node->filename && *(node->filename))
+    {
+      len = pad_to (51, line_buffer);
+      sprintf (line_buffer + len, node->filename);
+    }
+
+  return (savestring (line_buffer));
+}
+
+/* Little string comparison routine for qsort (). */
+static int
+compare_strings (string1, string2)
+     char **string1, **string2;
+{
+  return (stricmp (*string1, *string2));
+}
+
+/* The name of the nodemenu node. */
+static char *nodemenu_nodename = "*Node Menu*";
+
+/* Produce an informative listing of all the visited nodes, and return it
+   in a node.  If FILTER_FUNC is non-null, it is a function which filters
+   which nodes will appear in the listing.  FILTER_FUNC takes an argument
+   of NODE, and returns non-zero if the node should appear in the listing. */
+NODE *
+get_visited_nodes (filter_func)
+     Function *filter_func;
+{
+  register int i, iw_index;
+  INFO_WINDOW *info_win;
+  NODE *node;
+  char **lines = (char **)NULL;
+  int lines_index = 0, lines_slots = 0;
+
+  if (!info_windows)
+    return ((NODE *)NULL);
+
+  for (iw_index = 0; info_win = info_windows[iw_index]; iw_index++)
+    {
+      for (i = 0; i < info_win->nodes_index; i++)
+	{
+	  node = info_win->nodes[i];
+
+	  /* We skip mentioning "*Node Menu*" nodes. */
+	  if (internal_info_node_p (node) &&
+	      (strcmp (node->nodename, nodemenu_nodename) == 0))
+	    continue;
+
+	  if (node && (!filter_func || (*filter_func) (node)))
+	    {
+	      char *line;
+
+	      line = format_node_info (node);
+	      add_pointer_to_array
+		(line, lines_index, lines, lines_slots, 20, char *);
+	    }
+	}
+    }
+
+  /* Sort the array of information lines. */
+  qsort (lines, lines_index, sizeof (char *), compare_strings);
+
+  /* Delete duplicates. */
+  {
+    register int j, newlen;
+    char **temp;
+
+    for (i = 0, newlen = 1; i < lines_index - 1; i++)
+      {
+	if (strcmp (lines[i], lines[i + 1]) == 0)
+	  {
+	    free (lines[i]);
+	    lines[i] = (char *)NULL;
+	  }
+	else
+	  newlen++;
+      }
+
+    /* We have free ()'d and marked all of the duplicate slots.  Copy the
+       live slots rather than pruning the dead slots. */
+    temp = (char **)xmalloc ((1 + newlen) * sizeof (char *));
+    for (i = 0, j = 0; i < lines_index; i++)
+      if (lines[i])
+	temp[j++] = lines[i];
+
+    temp[j] = (char *)NULL;
+    free (lines);
+    lines = temp;
+    lines_index = newlen;
+  }
+
+  initialize_message_buffer ();
+  printf_to_message_buffer
+    ("Here is a menu of nodes you could select with info-history-node:\n");
+  printf_to_message_buffer ("%s\n", nodemenu_format_info ());
+  for (i = 0; i < lines_index; i++)
+    {
+      printf_to_message_buffer ("%s\n", lines[i]);
+      free (lines[i]);
+    }
+  free (lines);
+
+  node = message_buffer_to_node ();
+  add_gcable_pointer (node->contents);
+  return (node);
+}
+
+DECLARE_INFO_COMMAND (list_visited_nodes,
+   "Make a window containing a menu of all of the currently visited nodes")
+{
+  WINDOW *new;
+  NODE *node;
+
+  set_remembered_pagetop_and_point (window);
+
+  /* If a window is visible and showing the buffer list already, re-use it. */
+  for (new = windows; new; new = new->next)
+    {
+      node = new->node;
+
+      if (internal_info_node_p (node) &&
+	  (strcmp (node->nodename, nodemenu_nodename) == 0))
+	break;
+    }
+
+  /* If we couldn't find an existing window, try to use the next window
+     in the chain. */
+  if (!new && window->next)
+    new = window->next;
+
+  /* If we still don't have a window, make a new one to contain the list. */
+  if (!new)
+    {
+      WINDOW *old_active;
+
+      old_active = active_window;
+      active_window = window;
+      new = window_make_window ((NODE *)NULL);
+      active_window = old_active;
+    }
+
+  /* If we couldn't make a new window, use this one. */
+  if (!new)
+    new = window;
+
+  /* Lines do not wrap in this window. */
+  new->flags |= W_NoWrap;
+  node = get_visited_nodes ((Function *)NULL);
+  name_internal_node (node, nodemenu_nodename);
+
+  /* Even if this is an internal node, we don't want the window
+     system to treat it specially.  So we turn off the internalness
+     of it here. */
+  node->flags &= ~N_IsInternal;
+
+  /* If this window is already showing a node menu, reuse the existing node
+     slot. */
+  {
+    int remember_me = 1;
+
+#if defined (NOTDEF)
+    if (internal_info_node_p (new->node) &&
+	(strcmp (new->node->nodename, nodemenu_nodename) == 0))
+      remember_me = 0;
+#endif /* NOTDEF */
+
+    window_set_node_of_window (new, node);
+
+    if (remember_me)
+      remember_window_and_node (new, node);
+  }
+
+  active_window = new;
+}
+
+DECLARE_INFO_COMMAND (select_visited_node,
+      "Select a node which has been previously visited in a visible window")
+{
+  char *line;
+  NODE *node;
+  REFERENCE **menu;
+
+  node = get_visited_nodes ((Function *)NULL);
+
+  menu = info_menu_of_node (node);
+  free (node);
+
+  line =
+    info_read_completing_in_echo_area (window, "Select visited node: ", menu);
+
+  window = active_window;
+
+  /* User aborts, just quit. */
+  if (!line)
+    {
+      info_abort_key (window, 0, 0);
+      info_free_references (menu);
+      return;
+    }
+
+  if (*line)
+    {
+      REFERENCE *entry;
+
+      /* Find the selected label in the references. */
+      entry = info_get_labeled_reference (line, menu);
+
+      if (!entry)
+	info_error ("The reference disappeared! (%s).", line);
+      else
+	info_select_reference (window, entry);
+    }
+
+  free (line);
+  info_free_references (menu);
+
+  if (!info_error_was_printed)
+    window_clear_echo_area ();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/nodes.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,1167 @@
+/* nodes.c -- How to get an Info file and node. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/file.h>
+#include <sys/errno.h>
+#include <sys/stat.h>
+#include "nodes.h"
+#include "search.h"
+#include "filesys.h"
+#include "info-utils.h"
+
+#if !defined (O_RDONLY)
+#if defined (HAVE_SYS_FCNTL_H)
+#include <sys/fcntl.h>
+#else /* !HAVE_SYS_FCNTL_H */
+#include <fcntl.h>
+#endif /* !HAVE_SYS_FCNTL_H */
+#endif /* !O_RDONLY */
+
+#if !defined (errno)
+extern int errno;
+#endif /* !errno */
+
+/* **************************************************************** */
+/*								    */
+/*		     Functions Static to this File		    */
+/*								    */
+/* **************************************************************** */
+
+static void forget_info_file (), remember_info_file ();
+static void free_file_buffer_tags (), free_info_tag ();
+static void get_nodes_of_tags_table (), get_nodes_of_info_file ();
+static void get_tags_of_indirect_tags_table ();
+static void info_reload_file_buffer_contents ();
+static char *adjust_nodestart ();
+static FILE_BUFFER *make_file_buffer ();
+static FILE_BUFFER *info_load_file_internal (), *info_find_file_internal ();
+static NODE *info_node_of_file_buffer_tags ();
+
+static long get_node_length ();
+
+/* Magic number that RMS used to decide how much a tags table pointer could
+   be off by.  I feel that it should be much smaller, like on the order of
+   4. */
+#define DEFAULT_INFO_FUDGE 1000
+
+/* Passed to *_internal functions.  INFO_GET_TAGS says to do what is
+   neccessary to fill in the nodes or tags arrays in FILE_BUFFER. */
+#define INFO_NO_TAGS  0
+#define INFO_GET_TAGS 1
+
+/* **************************************************************** */
+/*								    */
+/*			 Global Variables			    */
+/*								    */
+/* **************************************************************** */
+
+/* When non-zero, this is a string describing the recent file error. */
+char *info_recent_file_error = (char *)NULL;
+
+/* The list of already loaded nodes. */
+FILE_BUFFER **info_loaded_files = (FILE_BUFFER **)NULL;
+
+/* The number of slots currently allocated to LOADED_FILES. */
+int info_loaded_files_slots = 0;
+
+/* **************************************************************** */
+/*								    */
+/*		 Public Functions for Node Manipulation		    */
+/*								    */
+/* **************************************************************** */
+
+/* Used to build "dir" menu from "localdir" files found in INFOPATH. */
+extern void maybe_build_dir_node ();
+
+/* Return a pointer to a NODE structure for the Info node (FILENAME)NODENAME.
+   FILENAME can be passed as NULL, in which case the filename of "dir" is used.
+   NODENAME can be passed as NULL, in which case the nodename of "Top" is used.
+   If the node cannot be found, return a NULL pointer. */
+NODE *
+info_get_node (filename, nodename)
+     char *filename, *nodename;
+{
+  FILE_BUFFER *file_buffer;
+  NODE *node;
+
+  file_buffer = (FILE_BUFFER *)NULL;
+  info_recent_file_error = (char *)NULL;
+
+  info_parse_node (nodename, DONT_SKIP_NEWLINES);
+  nodename = (char *)NULL;
+
+  if (info_parsed_filename)
+    filename = info_parsed_filename;
+
+  if (info_parsed_nodename)
+    nodename = info_parsed_nodename;
+
+  /* If FILENAME is not specified, it defaults to "dir". */
+  if (!filename)
+    filename = "dir";
+
+  /* If the file to be looked up is "dir", build the contents from all of
+     the "localdir"'s found in INFOPATH. */
+  if (stricmp (filename, "dir") == 0)
+    maybe_build_dir_node (filename, "localdir");
+
+  /* Find the correct info file. */
+  file_buffer = info_find_file (filename);
+
+  if (!file_buffer)
+    {
+      if (filesys_error_number)
+	info_recent_file_error =
+	  filesys_error_string (filename, filesys_error_number);
+      return ((NODE *)NULL);
+    }
+
+  node = info_get_node_of_file_buffer (nodename, file_buffer);
+  /* If the node looked for was "Top", try again looking for the node under
+     a slightly different name. */
+  if (!node && (nodename == NULL || stricmp (nodename, "Top") == 0))
+    {
+      node = info_get_node_of_file_buffer ("Top", file_buffer);
+      if (!node)
+	node = info_get_node_of_file_buffer ("top", file_buffer);
+      if (!node)
+	node = info_get_node_of_file_buffer ("TOP", file_buffer);
+    }
+  return (node);
+}
+
+/* Return a pointer to a NODE structure for the Info node NODENAME in
+   FILE_BUFFER.  NODENAME can be passed as NULL, in which case the
+   nodename of "Top" is used.  If the node cannot be found, return a
+   NULL pointer. */
+NODE *
+info_get_node_of_file_buffer (nodename, file_buffer)
+     char *nodename;
+     FILE_BUFFER *file_buffer;
+{
+  NODE *node = (NODE *)NULL;
+
+  /* If we are unable to find the file, we have to give up.  There isn't
+     anything else we can do. */
+  if (!file_buffer)
+    return ((NODE *)NULL);
+
+  /* If the file buffer was gc'ed, reload the contents now. */
+  if (!file_buffer->contents)
+    info_reload_file_buffer_contents (file_buffer);
+
+  /* If NODENAME is not specified, it defaults to "Top". */
+  if (!nodename)
+    nodename = "Top";
+
+  /* If the name of the node that we wish to find is exactly "*", then the
+     node body is the contents of the entire file.  Create and return such
+     a node. */
+  if (strcmp (nodename, "*") == 0)
+    {
+      node = (NODE *)xmalloc (sizeof (NODE));
+      node->filename = file_buffer->fullpath;
+      node->parent   = (char *)NULL;
+      node->nodename = savestring ("*");
+      node->contents = file_buffer->contents;
+      node->nodelen = file_buffer->filesize;
+      node->flags = 0;
+    }
+
+  /* If this is the "main" info file, it might contain a tags table.  Search
+     the tags table for an entry which matches the node that we want.  If
+     there is a tags table, get the file which contains this node, but don't
+     bother building a node list for it. */
+  else if (file_buffer->tags)
+    node = info_node_of_file_buffer_tags (file_buffer, nodename);
+
+  /* Return the results of our node search. */
+  return (node);
+}
+
+/* Locate the file named by FILENAME, and return the information structure
+   describing this file.  The file may appear in our list of loaded files
+   already, or it may not.  If it does not already appear, find the file,
+   and add it to the list of loaded files.  If the file cannot be found,
+   return a NULL FILE_BUFFER *. */
+FILE_BUFFER *
+info_find_file (filename)
+     char *filename;
+{
+  return (info_find_file_internal (filename, INFO_GET_TAGS));
+}
+
+/* Load the info file FILENAME, remembering information about it in a
+   file buffer. */
+FILE_BUFFER *
+info_load_file (filename)
+     char *filename;
+{
+  return (info_load_file_internal (filename, INFO_GET_TAGS));
+}
+
+
+/* **************************************************************** */
+/*								    */
+/*		    Private Functions Implementation		    */
+/*								    */
+/* **************************************************************** */
+
+/* The workhorse for info_find_file ().  Non-zero 2nd argument says to
+   try to build a tags table (or otherwise glean the nodes) for this
+   file once found.  By default, we build the tags table, but when this
+   function is called by info_get_node () when we already have a valid
+   tags table describing the nodes, it is unnecessary. */
+static FILE_BUFFER *
+info_find_file_internal (filename, get_tags)
+     char *filename;
+     int get_tags;
+{
+  register int i;
+  register FILE_BUFFER *file_buffer;
+
+  /* First try to find the file in our list of already loaded files. */
+  if (info_loaded_files)
+    {
+      for (i = 0; file_buffer = info_loaded_files[i]; i++)
+	if ((strcmp (filename, file_buffer->filename) == 0) ||
+	    (strcmp (filename, file_buffer->fullpath) == 0) ||
+	    ((*filename != '/') &&
+	     strcmp (filename,
+		     filename_non_directory (file_buffer->fullpath)) == 0))
+	  {
+	    struct stat new_info, *old_info;
+
+	    /* This file is loaded.  If the filename that we want is
+	       specifically "dir", then simply return the file buffer. */
+	    if (stricmp (filename_non_directory (filename), "dir") == 0)
+	      return (file_buffer);
+
+	    /* The file appears to be already loaded, and it is not "dir".
+	       Check to see if it has changed since the last time it was
+	       loaded. */
+	    if (stat (file_buffer->fullpath, &new_info) == -1)
+	      {
+		filesys_error_number = errno;
+		return ((FILE_BUFFER *)NULL);
+	      }
+
+	    old_info = &file_buffer->finfo;
+
+	    if ((new_info.st_size != old_info->st_size) ||
+		(new_info.st_mtime != old_info->st_mtime))
+	      {
+		/* The file has changed.  Forget that we ever had loaded it
+		   in the first place. */
+		forget_info_file (filename);
+		break;
+	      }
+	    else
+	      {
+		/* The info file exists, and has not changed since the last
+		   time it was loaded.  If the caller requested a nodes list
+		   for this file, and there isn't one here, build the nodes
+		   for this file_buffer.  In any case, return the file_buffer
+		   object. */
+		if (get_tags && !file_buffer->tags)
+		  build_tags_and_nodes (file_buffer);
+
+		return (file_buffer);
+	      }
+	  }
+    }
+
+  /* The file wasn't loaded.  Try to load it now. */
+  file_buffer = info_load_file_internal (filename, get_tags);
+
+  /* If the file was loaded, remember the name under which it was found. */
+  if (file_buffer)
+    remember_info_file (file_buffer);
+
+  return (file_buffer);
+}
+
+/* The workhorse function for info_load_file ().  Non-zero second argument
+   says to build a list of tags (or nodes) for this file.  This is the
+   default behaviour when info_load_file () is called, but it is not
+   necessary when loading a subfile for which we already have tags. */
+static FILE_BUFFER *
+info_load_file_internal (filename, get_tags)
+     char *filename;
+     int get_tags;
+{
+  char *fullpath, *contents;
+  long filesize;
+  struct stat finfo;
+  int retcode;
+  FILE_BUFFER *file_buffer = (FILE_BUFFER *)NULL;
+
+  /* Get the full pathname of this file, as known by the info system.
+     That is to say, search along INFOPATH and expand tildes, etc. */
+  fullpath = info_find_fullpath (filename);
+
+  /* Did we actually find the file? */
+  retcode = stat (fullpath, &finfo);
+
+  /* If the file referenced by the name returned from info_find_fullpath ()
+     doesn't exist, then try again with the last part of the filename
+     appearing in lowercase. */
+  if (retcode < 0)
+    {
+      char *lowered_name;
+      char *basename;
+
+      lowered_name = savestring (filename);
+      basename = (char *)rindex (lowered_name, '/');
+
+      if (basename)
+	basename++;
+      else
+	basename = lowered_name;
+
+      while (*basename)
+	{
+	  if (isupper (*basename))
+	    *basename = tolower (*basename);
+
+	  basename++;
+	}
+
+      fullpath = info_find_fullpath (lowered_name);
+      free (lowered_name);
+
+      retcode = stat (fullpath, &finfo);
+    }
+
+  /* If the file wasn't found, give up, returning a NULL pointer. */
+  if (retcode < 0)
+    {
+      filesys_error_number = errno;
+      return ((FILE_BUFFER *)NULL);
+    }
+
+  /* Otherwise, try to load the file. */
+  contents = filesys_read_info_file (fullpath, &filesize, &finfo);
+
+  if (!contents)
+    return ((FILE_BUFFER *)NULL);
+
+  /* The file was found, and can be read.  Allocate FILE_BUFFER and fill
+     in the various members. */
+  file_buffer = make_file_buffer ();
+  file_buffer->filename = savestring (filename);
+  file_buffer->fullpath = savestring (fullpath);
+  file_buffer->finfo = finfo;
+  file_buffer->filesize = filesize;
+  file_buffer->contents = contents;
+  if (file_buffer->filesize != file_buffer->finfo.st_size)
+    file_buffer->flags |= N_IsCompressed;
+
+  /* If requested, build the tags and nodes for this file buffer. */
+  if (get_tags)
+    build_tags_and_nodes (file_buffer);
+
+  return (file_buffer);
+}
+
+/* Grovel FILE_BUFFER->contents finding tags and nodes, and filling in the
+   various slots.  This can also be used to rebuild a tag or node table. */
+void
+build_tags_and_nodes (file_buffer)
+     FILE_BUFFER *file_buffer;
+{
+  SEARCH_BINDING binding;
+  long position;
+
+  free_file_buffer_tags (file_buffer);
+  file_buffer->flags &= ~N_HasTagsTable;
+
+  /* See if there is a tags table in this info file. */
+  binding.buffer = file_buffer->contents;
+  binding.start = file_buffer->filesize;
+  binding.end = binding.start - 1000;
+  if (binding.end < 0)
+    binding.end = 0;
+  binding.flags = S_FoldCase;
+
+  position = search_backward (TAGS_TABLE_END_LABEL, &binding);
+
+  /* If there is a tag table, find the start of it, and grovel over it
+     extracting tag information. */
+  if (position != -1)
+    while (1)
+      {
+	long tags_table_begin, tags_table_end;
+
+	binding.end = position;
+	binding.start = binding.end - 5 - strlen (TAGS_TABLE_END_LABEL);
+	if (binding.start < 0)
+	  binding.start = 0;
+
+	position = find_node_separator (&binding);
+
+	/* For this test, (and all others here) failure indicates a bogus
+	   tags table.  Grovel the file. */
+	if (position == -1)
+	  break;
+
+	/* Remember the end of the tags table. */
+	binding.start = position;
+	tags_table_end = binding.start;
+	binding.end = 0;
+
+	/* Locate the start of the tags table. */
+	position = search_backward (TAGS_TABLE_BEG_LABEL, &binding);
+
+	if (position == -1)
+	  break;
+
+	binding.end = position;
+	binding.start = binding.end - 5 - strlen (TAGS_TABLE_BEG_LABEL);
+	position = find_node_separator (&binding);
+
+	if (position == -1)
+	  break;
+
+	/* The file contains a valid tags table.  Fill the FILE_BUFFER's
+	   tags member. */
+	file_buffer->flags |= N_HasTagsTable;
+	tags_table_begin = position;
+
+	/* If this isn't an indirect tags table, just remember the nodes
+	   described locally in this tags table.  Note that binding.end
+	   is pointing to just after the beginning label. */
+	binding.start = binding.end;
+	binding.end = file_buffer->filesize;
+
+	if (!looking_at (TAGS_TABLE_IS_INDIRECT_LABEL, &binding))
+	  {
+	    binding.start = tags_table_begin;
+	    binding.end = tags_table_end;
+	    get_nodes_of_tags_table (file_buffer, &binding);
+	    return;
+	  }
+	else
+	  {
+	    /* This is an indirect tags table.  Build TAGS member. */
+	    SEARCH_BINDING indirect;
+
+	    indirect.start = tags_table_begin;
+	    indirect.end = 0;
+	    indirect.buffer = binding.buffer;
+	    indirect.flags = S_FoldCase;
+
+	    position = search_backward (INDIRECT_TAGS_TABLE_LABEL, &indirect);
+
+	    if (position == -1)
+	      {
+		/* This file is malformed.  Give up. */
+		return;
+	      }
+
+	    indirect.start = position;
+	    indirect.end = tags_table_begin;
+	    binding.start = tags_table_begin;
+	    binding.end = tags_table_end;
+	    get_tags_of_indirect_tags_table (file_buffer, &indirect, &binding);
+	    return;
+	  }
+      }
+
+  /* This file doesn't contain any kind of tags table.  Grovel the
+     file and build node entries for it. */
+  get_nodes_of_info_file (file_buffer);
+}
+
+/* Search through FILE_BUFFER->contents building an array of TAG *,
+   one entry per each node present in the file.  Store the tags in
+   FILE_BUFFER->tags, and the number of allocated slots in
+   FILE_BUFFER->tags_slots. */
+static void
+get_nodes_of_info_file (file_buffer)
+     FILE_BUFFER *file_buffer;
+{
+  long nodestart;
+  int tags_index = 0;
+  SEARCH_BINDING binding;
+
+  binding.buffer = file_buffer->contents;
+  binding.start = 0;
+  binding.end = file_buffer->filesize;
+  binding.flags = S_FoldCase;
+
+  while ((nodestart = find_node_separator (&binding)) != -1)
+    {
+      int start, end;
+      char *nodeline;
+      TAG *entry;
+
+      /* Skip past the characters just found. */
+      binding.start = nodestart;
+      binding.start += skip_node_separator (binding.buffer + binding.start);
+
+      /* Move to the start of the line defining the node. */
+      nodeline = binding.buffer + binding.start;
+
+      /* Find "Node:" */
+      start = string_in_line (INFO_NODE_LABEL, nodeline);
+
+      /* If not there, this is not the start of a node. */
+      if (start == -1)
+	continue;
+
+      /* Find the start of the nodename. */
+      start += skip_whitespace (nodeline + start);
+
+      /* Find the end of the nodename. */
+      end = start +
+	skip_node_characters (nodeline + start, DONT_SKIP_NEWLINES);
+
+      /* Okay, we have isolated the node name, and we know where the
+	 node starts.  Remember this information in a NODE structure. */
+      entry = (TAG *)xmalloc (sizeof (TAG));
+      entry->nodename = (char *)xmalloc (1 + (end - start));
+      strncpy (entry->nodename, nodeline + start, end - start);
+      entry->nodename[end - start] = '\0';
+      entry->nodestart = nodestart;
+      {
+	SEARCH_BINDING node_body;
+
+	node_body.buffer = binding.buffer + binding.start;
+	node_body.start = 0;
+	node_body.end = binding.end - binding.start;
+	node_body.flags = S_FoldCase;
+	entry->nodelen = get_node_length (&node_body);
+      }
+
+      entry->filename = file_buffer->fullpath;
+
+      /* Add this tag to the array of tag structures in this FILE_BUFFER. */
+      add_pointer_to_array (entry, tags_index, file_buffer->tags,
+			    file_buffer->tags_slots, 100, TAG *);
+    }
+}
+
+/* Return the length of the node which starts at BINDING. */
+static long
+get_node_length (binding)
+     SEARCH_BINDING *binding;
+{
+  register int i;
+  char *body;
+
+  /* From the Info-RFC file:
+     [A node] ends with either a ^_, a ^L, or the end of file. */
+  for (i = binding->start, body = binding->buffer; i < binding->end; i++)
+    {
+      if (body[i] == INFO_FF || body[i] == INFO_COOKIE)
+	break;
+    }
+  return ((long) i - binding->start);
+}
+
+/* Build and save the array of nodes in FILE_BUFFER by searching through the
+   contents of BUFFER_BINDING for a tags table, and groveling the contents. */
+static void
+get_nodes_of_tags_table (file_buffer, buffer_binding)
+     FILE_BUFFER *file_buffer;
+     SEARCH_BINDING *buffer_binding;
+{
+  int offset, tags_index = 0;
+  SEARCH_BINDING *search;
+  long position;
+
+  search = copy_binding (buffer_binding);
+
+  /* Find the start of the tags table. */
+  position = find_tags_table (search);
+
+  /* If none, we're all done. */
+  if (position == -1)
+    return;
+
+  /* Move to one character before the start of the actual table. */
+  search->start = position;
+  search->start += skip_node_separator (search->buffer + search->start);
+  search->start += strlen (TAGS_TABLE_BEG_LABEL);
+  search->start--;
+
+  /* The tag table consists of lines containing node names and positions.
+     Do each line until we find one that doesn't contain a node name. */
+  while ((position = search_forward ("\n", search)) != -1)
+    {
+      TAG *entry;
+      char *nodedef;
+
+      /* Prepare to skip this line. */
+      search->start = position;
+      search->start++;
+
+      /* Skip past informative "(Indirect)" tags table line. */
+      if (!tags_index && looking_at (TAGS_TABLE_IS_INDIRECT_LABEL, search))
+	continue;
+
+      /* Find the label preceding the node name. */
+      offset =
+	string_in_line (INFO_NODE_LABEL, search->buffer + search->start);
+
+      /* If not there, not a defining line, so we must be out of the
+	 tags table. */
+      if (offset == -1)
+	break;
+
+      /* Point to the beginning of the node definition. */
+      search->start += offset;
+      nodedef = search->buffer + search->start;
+      nodedef += skip_whitespace (nodedef);
+
+      /* Move past the node's name. */
+      for (offset = 0;
+	   (nodedef[offset]) && (nodedef[offset] != INFO_TAGSEP);
+	   offset++);
+
+      if (nodedef[offset] != INFO_TAGSEP)
+	continue;
+
+      entry = (TAG *)xmalloc (sizeof (TAG));
+      entry->nodename = (char *)xmalloc (1 + offset);
+      strncpy (entry->nodename, nodedef, offset);
+      entry->nodename[offset] = '\0';
+      offset++;
+      entry->nodestart = (long) atol (nodedef + offset);
+
+      /* We don't know the length of this node yet. */
+      entry->nodelen = -1;
+
+      /* The filename of this node is currently known as the same as the
+	 name of this file. */
+      entry->filename = file_buffer->fullpath;
+
+      /* Add this node structure to the array of node structures in this
+	 FILE_BUFFER. */
+      add_pointer_to_array (entry, tags_index, file_buffer->tags,
+			    file_buffer->tags_slots, 100, TAG *);
+    }
+  free (search);
+}
+
+/* A structure used only in get_tags_of_indirect_tags_table () to hold onto
+   an intermediate value. */
+typedef struct {
+  char *filename;
+  long first_byte;
+} SUBFILE;
+
+/* Remember in FILE_BUFFER the nodenames, subfilenames, and offsets within the
+   subfiles of every node which appears in TAGS_BINDING.  The 2nd argument is
+   a binding surrounding the indirect files list. */
+static void
+get_tags_of_indirect_tags_table (file_buffer, indirect_binding, tags_binding)
+     FILE_BUFFER *file_buffer;
+     SEARCH_BINDING *indirect_binding, *tags_binding;
+{
+  register int i;
+  SUBFILE **subfiles = (SUBFILE **)NULL;
+  int subfiles_index = 0, subfiles_slots = 0;
+  TAG *entry;
+
+  /* First get the list of tags from the tags table.  Then lookup the
+     associated file in the indirect list for each tag, and update it. */
+  get_nodes_of_tags_table (file_buffer, tags_binding);
+
+  /* We have the list of tags in file_buffer->tags.  Get the list of
+     subfiles from the indirect table. */
+  {
+    char *start, *end, *line;
+    SUBFILE *subfile;
+
+    start = indirect_binding->buffer + indirect_binding->start;
+    end = indirect_binding->buffer + indirect_binding->end;
+    line = start;
+
+    while (line < end)
+      {
+	int colon;
+
+	colon = string_in_line (":", line);
+
+	if (colon == -1)
+	  break;
+
+	subfile = (SUBFILE *)xmalloc (sizeof (SUBFILE));
+	subfile->filename = (char *)xmalloc (colon);
+	strncpy (subfile->filename, line, colon - 1);
+	subfile->filename[colon - 1] = '\0';
+	subfile->first_byte = (long) atol (line + colon);
+
+	add_pointer_to_array
+	  (subfile, subfiles_index, subfiles, subfiles_slots, 10, SUBFILE *);
+
+	while (*line++ != '\n');
+      }
+  }
+
+  /* If we have successfully built the indirect files table, then
+     merge the information in the two tables. */
+  if (!subfiles)
+    {
+      free_file_buffer_tags (file_buffer);
+      return;
+    }
+  else
+    {
+      register int tags_index;
+      long header_length;
+      SEARCH_BINDING binding;
+
+      /* Find the length of the header of the file containing the indirect
+	 tags table.  This header appears at the start of every file.  We
+	 want the absolute position of each node within each subfile, so
+	 we subtract the start of the containing subfile from the logical
+	 position of the node, and then add the length of the header in. */
+      binding.buffer = file_buffer->contents;
+      binding.start = 0;
+      binding.end = file_buffer->filesize;
+      binding.flags = S_FoldCase;
+
+      header_length = find_node_separator (&binding);
+      if (header_length == -1)
+	header_length = 0;
+
+      /* Build the file buffer's list of subfiles. */
+      {
+	char *containing_dir, *temp;
+	int len_containing_dir;
+
+	containing_dir = savestring (file_buffer->fullpath);
+	temp = (char *)rindex (containing_dir, '/');
+
+	if (temp)
+	  *temp = '\0';
+
+	len_containing_dir = strlen (containing_dir);
+
+	for (i = 0; subfiles[i]; i++);
+
+	file_buffer->subfiles = (char **) xmalloc ((1 + i) * sizeof (char *));
+
+	for (i = 0; subfiles[i]; i++)
+	  {
+	    char *fullpath;
+
+	    fullpath = (char *) xmalloc
+	      (2 + strlen (subfiles[i]->filename) + len_containing_dir);
+
+	    sprintf (fullpath, "%s/%s",
+		     containing_dir, subfiles[i]->filename);
+
+	    file_buffer->subfiles[i] = fullpath;
+	  }
+	file_buffer->subfiles[i] = (char *)NULL;
+	free (containing_dir);
+      }
+
+      /* For each node in the file's tags table, remember the starting
+	 position. */
+      for (tags_index = 0;
+	   entry = file_buffer->tags[tags_index];
+	   tags_index++)
+	{
+	  for (i = 0;
+	       subfiles[i] && entry->nodestart >= subfiles[i]->first_byte;
+	       i++);
+
+	  /* If the Info file containing the indirect tags table is
+	     malformed, then give up. */
+	  if (!i)
+	    {
+	      /* The Info file containing the indirect tags table is
+		 malformed.  Give up. */
+	      for (i = 0; subfiles[i]; i++)
+		{
+		  free (subfiles[i]->filename);
+		  free (subfiles[i]);
+		  free (file_buffer->subfiles[i]);
+		}
+	      file_buffer->subfiles = (char **)NULL;
+	      free_file_buffer_tags (file_buffer);
+	      return;
+	    }
+
+	  /* SUBFILES[i] is the index of the first subfile whose logical
+	     first byte is greater than the logical offset of this node's
+	     starting position.  This means that the subfile directly
+	     preceding this one is the one containing the node. */
+
+	  entry->filename = file_buffer->subfiles[i - 1];
+	  entry->nodestart -= subfiles[i -1]->first_byte;
+	  entry->nodestart += header_length;
+	  entry->nodelen = -1;
+	}
+
+      /* We have successfully built the tags table.  Remember that it
+	 was indirect. */
+      file_buffer->flags |= N_TagsIndirect;
+    }
+
+  /* Free the structures assigned to SUBFILES.  Free the names as well
+     as the structures themselves, then finally, the array. */
+  for (i = 0; subfiles[i]; i++)
+    {
+      free (subfiles[i]->filename);
+      free (subfiles[i]);
+    }
+  free (subfiles);
+}
+
+/* Return the node from FILE_BUFFER which matches NODENAME by searching
+   the tags table in FILE_BUFFER.  If the node could not be found, return
+   a NULL pointer. */
+static NODE *
+info_node_of_file_buffer_tags (file_buffer, nodename)
+     FILE_BUFFER *file_buffer;
+     char *nodename;
+{
+  register int i;
+  TAG *tag;
+
+  for (i = 0; tag = file_buffer->tags[i]; i++)
+    if (strcmp (nodename, tag->nodename) == 0)
+      {
+	FILE_BUFFER *subfile;
+
+	subfile = info_find_file_internal (tag->filename, INFO_NO_TAGS);
+
+	if (!subfile)
+	  return ((NODE *)NULL);
+
+	if (!subfile->contents)
+	  info_reload_file_buffer_contents (subfile);
+
+	if (!subfile->contents)
+	  return ((NODE *)NULL);
+
+	/* If we were able to find this file and load it, then return
+	   the node within it. */
+	{
+	  NODE *node;
+
+	  node = (NODE *)xmalloc (sizeof (NODE));
+	  node->filename = (subfile->fullpath);
+	  node->nodename = tag->nodename;
+	  node->contents = subfile->contents + tag->nodestart;
+	  node->flags	 = 0;
+	  node->parent	 = (char *)NULL;
+
+	  if (file_buffer->flags & N_HasTagsTable)
+	    {
+	      node->flags |= N_HasTagsTable;
+
+	      if (file_buffer->flags & N_TagsIndirect)
+		{
+		  node->flags |= N_TagsIndirect;
+		  node->parent = file_buffer->fullpath;
+		}
+	    }
+
+	  if (subfile->flags & N_IsCompressed)
+	    node->flags |= N_IsCompressed;
+
+	  /* If TAG->nodelen hasn't been calculated yet, then we aren't
+	     in a position to trust the entry pointer.  Adjust things so
+	     that ENTRY->nodestart gets the exact address of the start of
+	     the node separator which starts this node, and NODE->contents
+	     gets the address of the line defining this node.  If we cannot
+	     do that, the node isn't really here. */
+	  if (tag->nodelen == -1)
+	    {
+	      int min, max;
+	      char *node_sep;
+	      SEARCH_BINDING node_body;
+	      char *buff_end;
+
+	      min = max = DEFAULT_INFO_FUDGE;
+
+	      if (tag->nodestart < DEFAULT_INFO_FUDGE)
+		min = tag->nodestart;
+
+	      if (DEFAULT_INFO_FUDGE >
+		  (subfile->filesize - tag->nodestart))
+		max = subfile->filesize - tag->nodestart;
+
+	      /* NODE_SEP gets the address of the separator which defines
+		 this node, or (char *)NULL if the node wasn't found.
+		 NODE->contents is side-effected to point to right after
+		 the separator. */
+	      node_sep = adjust_nodestart (node, min, max);
+	      if (node_sep == (char *)NULL)
+		{
+		  free (node);
+		  return ((NODE *)NULL);
+		}
+	      /* Readjust tag->nodestart. */
+	      tag->nodestart = node_sep - subfile->contents;
+
+	      /* Calculate the length of the current node. */
+	      buff_end = subfile->contents + subfile->filesize;
+
+	      node_body.buffer = node->contents;
+	      node_body.start = 0;
+	      node_body.end = buff_end - node_body.buffer;
+	      node_body.flags = 0;
+	      tag->nodelen = get_node_length (&node_body);
+	    }
+	  else
+	    {
+	      /* Since we know the length of this node, we have already
+		 adjusted tag->nodestart to point to the exact start of
+		 it.  Simply skip the node separator. */
+	      node->contents += skip_node_separator (node->contents);
+	    }
+
+	  node->nodelen = tag->nodelen;
+	  return (node);
+	}
+      }
+
+  /* There was a tag table for this file, and the node wasn't found.
+     Return NULL, since this file doesn't contain the desired node. */
+  return ((NODE *)NULL);
+}
+
+/* **************************************************************** */
+/*								    */
+/*		Managing file_buffers, nodes, and tags.		    */
+/*								    */
+/* **************************************************************** */
+
+static FILE_BUFFER *
+make_file_buffer ()
+{
+  FILE_BUFFER *file_buffer;
+
+  file_buffer = (FILE_BUFFER *)xmalloc (sizeof (FILE_BUFFER));
+  file_buffer->filename = file_buffer->fullpath = (char *)NULL;
+  file_buffer->contents = (char *)NULL;
+  file_buffer->tags = (TAG **)NULL;
+  file_buffer->subfiles = (char **)NULL;
+  file_buffer->tags_slots = 0;
+  file_buffer->flags = 0;
+
+  return (file_buffer);
+}
+
+/* Add FILE_BUFFER to our list of already loaded info files. */
+static void
+remember_info_file (file_buffer)
+     FILE_BUFFER *file_buffer;
+{
+  int i;
+
+  for (i = 0; info_loaded_files && info_loaded_files[i]; i++)
+    ;
+
+  add_pointer_to_array (file_buffer, i, info_loaded_files,
+			info_loaded_files_slots, 10, FILE_BUFFER *);
+}
+
+/* Forget the contents, tags table, nodes list, and names of FILENAME. */
+static void
+forget_info_file (filename)
+     char *filename;
+{
+  register int i;
+  FILE_BUFFER *file_buffer;
+
+  if (!info_loaded_files)
+    return;
+
+  for (i = 0; file_buffer = info_loaded_files[i]; i++)
+    if ((strcmp (filename, file_buffer->filename) == 0) ||
+	(strcmp (filename, file_buffer->fullpath) == 0))
+      {
+	free (file_buffer->filename);
+	free (file_buffer->fullpath);
+
+	if (file_buffer->contents)
+	  free (file_buffer->contents);
+	
+	/* Note that free_file_buffer_tags () also kills the subfiles
+	   list, since the subfiles list is only of use in conjunction
+	   with tags. */
+	free_file_buffer_tags (file_buffer);
+
+	while (info_loaded_files[i] = info_loaded_files[++i])
+	  ;
+
+	break;
+      }
+}
+
+/* Free the tags (if any) associated with FILE_BUFFER. */
+static void
+free_file_buffer_tags (file_buffer)
+     FILE_BUFFER *file_buffer;
+{
+  register int i;
+
+  if (file_buffer->tags)
+    {
+      register TAG *tag;
+
+      for (i = 0; tag = file_buffer->tags[i]; i++)
+	free_info_tag (tag);
+
+      free (file_buffer->tags);
+      file_buffer->tags = (TAG **)NULL;
+      file_buffer->tags_slots = 0;
+    }
+
+  if (file_buffer->subfiles)
+    {
+      for (i = 0; file_buffer->subfiles[i]; i++)
+	free (file_buffer->subfiles[i]);
+
+      free (file_buffer->subfiles);
+      file_buffer->subfiles = (char **)NULL;
+    }
+}
+
+/* Free the data associated with TAG, as well as TAG itself. */
+static void
+free_info_tag (tag)
+     TAG *tag;
+{
+  free (tag->nodename);
+
+  /* We don't free tag->filename, because that filename is part of the
+     subfiles list for the containing FILE_BUFFER.  free_info_tags ()
+     will free the subfiles when it is appropriate. */
+
+  free (tag);
+}
+
+/* Load the contents of FILE_BUFFER->contents.  This function is called
+   when a file buffer was loaded, and then in order to conserve memory, the
+   file buffer's contents were freed and the pointer was zero'ed.  Note that
+   the file was already loaded at least once successfully, so the tags and/or
+   nodes members are still correctly filled. */
+static void
+info_reload_file_buffer_contents (fb)
+     FILE_BUFFER *fb;
+{
+  fb->flags &= ~N_IsCompressed;
+
+  /* Let the filesystem do all the work for us. */
+  fb->contents =
+    filesys_read_info_file (fb->fullpath, &(fb->filesize), &(fb->finfo));
+  if (fb->filesize != fb->finfo.st_size)
+    fb->flags |= N_IsCompressed;
+}
+
+/* Return the actual starting memory location of NODE, side-effecting
+   NODE->contents.  MIN and MAX are bounds for a search if one is necessary.
+   Because of the way that tags are implemented, the physical nodestart may
+   not actually be where the tag says it is.  If that is the case, but the
+   node was found anyway, set N_UpdateTags in NODE->flags.  If the node is
+   found, return non-zero.  NODE->contents is returned positioned right after
+   the node separator that precedes this node, while the return value is
+   position directly on the separator that precedes this node.  If the node
+   could not be found, return a NULL pointer. */
+static char *
+adjust_nodestart (node, min, max)
+     NODE *node;
+     int min, max;
+{
+  long position;
+  SEARCH_BINDING node_body;
+
+  /* Define the node body. */
+  node_body.buffer = node->contents;
+  node_body.start = 0;
+  node_body.end = max;
+  node_body.flags = 0;
+
+  /* Try the optimal case first.  Who knows?  This file may actually be
+     formatted (mostly) correctly. */
+  if (node_body.buffer[0] != INFO_COOKIE && min > 2)
+    node_body.buffer -= 3;
+
+  position = find_node_separator (&node_body);
+
+  /* If we found a node start, then check it out. */
+  if (position != -1)
+    {
+      int sep_len;
+
+      sep_len = skip_node_separator (node->contents);
+
+      /* If we managed to skip a node separator, then check for this node
+	 being the right one. */
+      if (sep_len != 0)
+	{
+	  char *nodedef, *nodestart;
+	  int offset;
+
+	  nodestart = node_body.buffer + position + sep_len;
+	  nodedef = nodestart;
+	  offset = string_in_line (INFO_NODE_LABEL, nodedef);
+
+	  if (offset != -1)
+	    {
+	      nodedef += offset;
+	      nodedef += skip_whitespace (nodedef);
+	      offset = skip_node_characters (nodedef, DONT_SKIP_NEWLINES);
+	      if ((offset == strlen (node->nodename)) &&
+		  (strncmp (node->nodename, nodedef, offset) == 0))
+		{
+		  node->contents = nodestart;
+		  return (node_body.buffer + position);
+		}
+	    }
+	}
+    }
+
+  /* Oh well, I guess we have to try to find it in a larger area. */
+  node_body.buffer = node->contents - min;
+  node_body.start = 0;
+  node_body.end = min + max;
+  node_body.flags = 0;
+
+  position = find_node_in_binding (node->nodename, &node_body);
+
+  /* If the node couldn't be found, we lose big. */
+  if (position == -1)
+    return ((char *)NULL);
+
+  /* Otherwise, the node was found, but the tags table could need updating
+     (if we used a tag to get here, that is).  Set the flag in NODE->flags. */
+  node->contents = node_body.buffer + position;
+  node->contents += skip_node_separator (node->contents);
+  if (node->flags & N_HasTagsTable)
+    node->flags |= N_UpdateTags;
+  return (node_body.buffer + position);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/nodes.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,164 @@
+/* nodes.h -- How we represent nodes internally. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_NODES_H_)
+#define _NODES_H_
+
+#include "general.h"
+
+/* **************************************************************** */
+/*								    */
+/*		      User Code Interface			    */
+/*								    */
+/* **************************************************************** */
+
+/* Callers generally only want the node itself.  This structure is used
+   to pass node information around.  None of the information in this
+   structure should ever be directly freed.  The structure itself can
+   be passed to free ().  Note that NODE->parent is non-null if this
+   node's file is a subfile.  In that case, NODE->parent is the logical
+   name of the file containing this node.  Both names are given as full
+   paths, so you might have: node->filename = "/usr/gnu/info/emacs-1",
+   with node->parent = "/usr/gnu/info/emacs". */
+typedef struct {
+  char *filename;		/* The physical file containing this node. */
+  char *parent;			/* Non-null is the logical file name. */
+  char *nodename;		/* The name of this node. */
+  char *contents;		/* Characters appearing in this node. */
+  long nodelen;			/* The length of the CONTENTS member. */
+  int flags;			/* See immediately below. */
+} NODE;
+
+/* Defines that can appear in NODE->flags.  All informative. */
+#define N_HasTagsTable 0x01	/* This node was found through a tags table. */
+#define N_TagsIndirect 0x02	/* The tags table was an indirect one. */
+#define N_UpdateTags   0x04	/* The tags table is out of date. */
+#define N_IsCompressed 0x08	/* The file is compressed on disk. */
+#define N_IsInternal   0x10	/* This node was made by Info. */
+#define N_CannotGC     0x20	/* File buffer cannot be gc'ed. */
+
+/* **************************************************************** */
+/*								    */
+/*		       Internal Data Structures			    */
+/*								    */
+/* **************************************************************** */
+
+/* Some defines describing details about Info file contents. */
+
+/* String Constants. */
+#define INFO_FILE_LABEL			"File:"
+#define INFO_NODE_LABEL			"Node:"
+#define INFO_PREV_LABEL			"Prev:"
+#define INFO_ALTPREV_LABEL		"Previous:"
+#define INFO_NEXT_LABEL			"Next:"
+#define INFO_UP_LABEL			"Up:"
+#define INFO_MENU_LABEL			"\n* Menu:"
+#define INFO_MENU_ENTRY_LABEL		"\n* "
+#define INFO_XREF_LABEL			"*Note"
+#define TAGS_TABLE_END_LABEL		"\nEnd Tag Table"
+#define TAGS_TABLE_BEG_LABEL		"Tag Table:\n"
+#define INDIRECT_TAGS_TABLE_LABEL	"Indirect:\n"
+#define TAGS_TABLE_IS_INDIRECT_LABEL	"(Indirect)"
+
+/* Character Constants. */
+#define INFO_COOKIE '\037'
+#define INFO_FF     '\014'
+#define INFO_TAGSEP '\177'
+
+/* For each logical file that we have loaded, we keep a list of the names
+   of the nodes that are found in that file.  A pointer to a node in an
+   info file is called a "tag".  For split files, the tag pointer is
+   "indirect"; that is, the pointer also contains the name of the split
+   file where the node can be found.  For non-split files, the filename
+   member in the structure below simply contains the name of the current
+   file.  The following structure describes a single node within a file. */
+typedef struct {
+  char *filename;		/* The file where this node can be found. */
+  char *nodename;		/* The node pointed to by this tag. */
+  long nodestart;		/* The offset of the start of this node. */
+  long nodelen;			/* The length of this node. */
+} TAG;
+
+/* The following structure is used to remember information about the contents
+   of Info files that we have loaded at least once before.  The FINFO member
+   is present so that we can reload the file if it has been modified since
+   last being loaded.  All of the arrays appearing within this structure
+   are NULL terminated, and each array which can change size has a
+   corresponding SLOTS member which says how many slots have been allocated
+   (with malloc ()) for this array. */
+typedef struct {
+  char *filename;		/* The filename used to find this file. */
+  char *fullpath;		/* The full pathname of this info file. */
+  struct stat finfo;		/* Information about this file. */
+  char *contents;		/* The contents of this particular file. */
+  long filesize;		/* The number of bytes this file expands to. */
+  char **subfiles;		/* If non-null, the list of subfiles. */
+  TAG **tags;			/* If non-null, the indirect tags table. */
+  int tags_slots;		/* Number of slots allocated for TAGS. */
+  int flags;			/* Various flags.  Mimics of N_* flags. */
+} FILE_BUFFER;
+
+/* **************************************************************** */
+/*								    */
+/*		    Externally Visible Functions		    */
+/*								    */
+/* **************************************************************** */
+
+/* Array of FILE_BUFFER * which represents the currently loaded info files. */
+extern FILE_BUFFER **info_loaded_files;
+
+/* The number of slots currently allocated to INFO_LOADED_FILES. */
+extern int info_loaded_files_slots;
+
+/* Locate the file named by FILENAME, and return the information structure
+   describing this file.  The file may appear in our list of loaded files
+   already, or it may not.  If it does not already appear, find the file,
+   and add it to the list of loaded files.  If the file cannot be found,
+   return a NULL FILE_BUFFER *. */
+extern FILE_BUFFER *info_find_file ();
+
+/* Force load the file named FILENAME, and return the information structure
+   describing this file.  Even if the file was already loaded, this loads
+   a new buffer, rebuilds tags and nodes, and returns a new FILE_BUFFER *. */
+extern FILE_BUFFER *info_load_file ();
+
+/* Return a pointer to a NODE structure for the Info node (FILENAME)NODENAME.
+   FILENAME can be passed as NULL, in which case the filename of "dir" is used.
+   NODENAME can be passed as NULL, in which case the nodename of "Top" is used.
+   If the node cannot be found, return a NULL pointer. */
+extern NODE *info_get_node ();
+
+/* Return a pointer to a NODE structure for the Info node NODENAME in
+   FILE_BUFFER.  NODENAME can be passed as NULL, in which case the
+   nodename of "Top" is used.  If the node cannot be found, return a
+   NULL pointer. */
+extern NODE *info_get_node_of_file_buffer ();
+
+/* Grovel FILE_BUFFER->contents finding tags and nodes, and filling in the
+   various slots.  This can also be used to rebuild a tag or node table. */
+extern void build_tags_and_nodes ();
+
+/* When non-zero, this is a string describing the most recent file error. */
+extern char *info_recent_file_error;
+
+#endif /* !_NODES_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/search.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,566 @@
+/* search.c -- How to search large bodies of text. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "general.h"
+#include "search.h"
+#include "nodes.h"
+
+#if !defined (NULL)
+#  define NULL 0x0
+#endif /* !NULL */
+
+/* The search functions take two arguments:
+
+     1) a string to search for, and
+
+     2) a pointer to a SEARCH_BINDING which contains the buffer, start,
+        and end of the search.
+
+   They return a long, which is the offset from the start of the buffer
+   at which the match was found.  An offset of -1 indicates failure. */
+
+/* A function which makes a binding with buffer and bounds. */
+SEARCH_BINDING *
+make_binding (buffer, start, end)
+     char *buffer;
+     long start, end;
+{
+  SEARCH_BINDING *binding;
+
+  binding = (SEARCH_BINDING *)xmalloc (sizeof (SEARCH_BINDING));
+  binding->buffer = buffer;
+  binding->start = start;
+  binding->end = end;
+  binding->flags = 0;
+
+  return (binding);
+}
+
+/* Make a copy of BINDING without duplicating the data. */
+SEARCH_BINDING *
+copy_binding (binding)
+     SEARCH_BINDING *binding;
+{
+  SEARCH_BINDING *copy;
+
+  copy = make_binding (binding->buffer, binding->start, binding->end);
+  copy->flags = binding->flags;
+  return (copy);
+}
+
+
+/* **************************************************************** */
+/*								    */
+/*		   The Actual Searching Functions		    */
+/*								    */
+/* **************************************************************** */
+
+/* Search forwards or backwards for the text delimited by BINDING.
+   The search is forwards if BINDING->start is greater than BINDING->end. */
+long
+search (string, binding)
+     char *string;
+     SEARCH_BINDING *binding;
+{
+  long result;
+
+  /* If the search is backwards, then search backwards, otherwise forwards. */
+  if (binding->start > binding->end)
+    result = search_backward (string, binding);
+  else
+    result = search_forward (string, binding);
+
+  return (result);
+}
+
+/* Search forwards for STRING through the text delimited in BINDING. */
+long
+search_forward (string, binding)
+     char *string;
+     SEARCH_BINDING *binding;
+{
+  register int c, i, len;
+  register char *buff, *end;
+  char *alternate = (char *)NULL;
+
+  len = strlen (string);
+
+  /* We match characters in the search buffer against STRING and ALTERNATE.
+     ALTERNATE is a case reversed version of STRING; this is cheaper than
+     case folding each character before comparison.   Alternate is only
+     used if the case folding bit is turned on in the passed BINDING. */
+
+  if (binding->flags & S_FoldCase)
+    {
+      alternate = savestring (string);
+
+      for (i = 0; i < len; i++)
+	{
+	  if (islower (alternate[i]))
+	    alternate[i] = toupper (alternate[i]);
+	  else if (isupper (alternate[i]))
+	    alternate[i] = tolower (alternate[i]);
+	}
+    }
+
+  buff = binding->buffer + binding->start;
+  end = binding->buffer + binding->end + 1;
+
+  while (buff < (end - len))
+    {
+      for (i = 0; i < len; i++)
+	{
+	  c = buff[i];
+
+	  if ((c != string[i]) && (!alternate || c != alternate[i]))
+	    break;
+	}
+
+      if (!string[i])
+	{
+	  if (alternate)
+	    free (alternate);
+	  if (binding->flags & S_SkipDest)
+	    buff += len;
+	  return ((long) (buff - binding->buffer));
+	}
+
+      buff++;
+    }
+
+  if (alternate)
+    free (alternate);
+
+  return ((long) -1);
+}
+
+/* Search for STRING backwards through the text delimited in BINDING. */
+long
+search_backward (input_string, binding)
+     char *input_string;
+     SEARCH_BINDING *binding;
+{
+  register int c, i, len;
+  register char *buff, *end;
+  char *string;
+  char *alternate = (char *)NULL;
+
+  len = strlen (input_string);
+
+  /* Reverse the characters in the search string. */
+  string = (char *)xmalloc (1 + len);
+  for (c = 0, i = len - 1; input_string[c]; c++, i--)
+    string[i] = input_string[c];
+
+  string[c] = '\0';
+
+  /* We match characters in the search buffer against STRING and ALTERNATE.
+     ALTERNATE is a case reversed version of STRING; this is cheaper than
+     case folding each character before comparison.   ALTERNATE is only
+     used if the case folding bit is turned on in the passed BINDING. */
+
+  if (binding->flags & S_FoldCase)
+    {
+      alternate = savestring (string);
+
+      for (i = 0; i < len; i++)
+	{
+	  if (islower (alternate[i]))
+	    alternate[i] = toupper (alternate[i]);
+	  else if (isupper (alternate[i]))
+	    alternate[i] = tolower (alternate[i]);
+	}
+    }
+
+  buff = binding->buffer + binding->start;
+  end = binding->buffer + binding->end;
+
+  while (buff > end + len)
+    {
+      for (i = 0; i < len; i++)
+	{
+	  c = *(buff - i);
+
+	  if (c != string[i] && (alternate && c != alternate[i]))
+	    break;
+	}
+
+      if (!string[i])
+	{
+	  free (string);
+	  if (alternate)
+	    free (alternate);
+
+	  if (binding->flags & S_SkipDest)
+	    buff -= len;
+	  return ((long) (1 + (buff - binding->buffer)));
+	}
+
+      buff--;
+    }
+
+  free (string);
+  if (alternate)
+    free (alternate);
+
+  return ((long) -1);
+}
+
+/* Find STRING in LINE, returning the offset of the end of the string.
+   Return an offset of -1 if STRING does not appear in LINE.  The search
+   is bound by the end of the line (i.e., either NEWLINE or 0). */
+int
+string_in_line (string, line)
+     char *string, *line;
+{
+  register int end;
+  SEARCH_BINDING binding;
+
+  /* Find the end of the line. */
+  for (end = 0; line[end] && line[end] != '\n'; end++);
+
+  /* Search for STRING within these confines. */
+  binding.buffer = line;
+  binding.start = 0;
+  binding.end = end;
+  binding.flags = S_FoldCase | S_SkipDest;
+
+  return (search_forward (string, &binding));
+}
+
+/* Return non-zero if STRING is the first text to appear at BINDING. */
+int
+looking_at (string, binding)
+     char *string;
+     SEARCH_BINDING *binding;
+{
+  long search_end;
+
+  search_end = search (string, binding);
+
+  /* If the string was not found, SEARCH_END is -1.  If the string was found,
+     but not right away, SEARCH_END is != binding->start.  Otherwise, the
+     string was found at binding->start. */
+  return (search_end == binding->start);
+}
+
+/* **************************************************************** */
+/*								    */
+/*		      Small String Searches			    */
+/*								    */
+/* **************************************************************** */
+
+/* Function names that start with "skip" are passed a string, and return
+   an offset from the start of that string.  Function names that start
+   with "find" are passed a SEARCH_BINDING, and return an absolute position
+   marker of the item being searched for.  "Find" functions return a value
+   of -1 if the item being looked for couldn't be found. */
+
+/* Return the index of the first non-whitespace character in STRING. */
+int
+skip_whitespace (string)
+     char *string;
+{
+  register int i;
+
+  for (i = 0; string && whitespace (string[i]); i++);
+  return (i);
+}
+
+/* Return the index of the first non-whitespace or newline character in
+   STRING. */
+int
+skip_whitespace_and_newlines (string)
+     char *string;
+{
+  register int i;
+
+  for (i = 0; string && (whitespace (string[i]) || string[i] == '\n'); i++);
+  return (i);
+}
+
+/* Return the index of the first whitespace character in STRING. */
+int
+skip_non_whitespace (string)
+     char *string;
+{
+  register int i;
+
+  for (i = 0; string && !whitespace (string[i]); i++);
+  return (i);
+}
+
+/* Return the index of the first non-node character in STRING.  Note that
+   this function contains quite a bit of hair to ignore periods in some
+   special cases.  This is because we here at GNU ship some info files which
+   contain nodenames that contain periods.  No such nodename can start with
+   a period, or continue with whitespace, newline, or ')' immediately following
+   the period.  If second argument NEWLINES_OKAY is non-zero, newlines should
+   be skipped while parsing out the nodename specification. */
+int
+skip_node_characters (string, newlines_okay)
+     char *string;
+     int newlines_okay;
+{
+  register int c, i = 0;
+  int paren_seen = 0;
+  int paren = 0;
+
+  /* Handle special case.  This is when another function has parsed out the
+     filename component of the node name, and we just want to parse out the
+     nodename proper.  In that case, a period at the start of the nodename
+     indicates an empty nodename. */
+  if (string && *string == '.')
+    return (0);
+
+  if (string && *string == '(')
+    {
+      paren++;
+      paren_seen++;
+      i++;
+    }
+
+  for (; string && (c = string[i]); i++)
+    {
+      if (paren)
+	{
+	  if (c == '(')
+	    paren++;
+	  else if (c == ')')
+	    paren--;
+
+	  continue;
+	}
+      
+      /* If the character following the close paren is a space or period,
+	 then this node name has no more characters associated with it. */
+      if (c == '\t' ||
+	  c == ','  ||
+	  c == INFO_TAGSEP ||
+	  ((!newlines_okay) && (c == '\n')) ||
+	  ((paren_seen && string[i - 1] == ')') &&
+	   (c == ' ' || c == '.')) ||
+	  (c == '.' &&
+	   ((!string[i + 1]) ||
+	    (whitespace_or_newline (string[i + 1])) ||
+	    (string[i + 1] == ')'))))
+	break;
+    }
+  return (i);
+}
+
+/* Unix doesn't have stricmp () functions. */
+int
+stricmp (string1, string2)
+     char *string1, *string2;
+{
+  char ch1, ch2;
+
+  for (;;)
+    {
+      ch1 = *string1++;
+      ch2 = *string2++;
+
+      if (!(ch1 | ch2))
+	return (0);
+
+      ch1 = info_toupper (ch1);
+      ch2 = info_toupper (ch2);
+
+      if (ch1 != ch2)
+	return (ch1 - ch2);
+    }
+}
+
+/* Compare at most COUNT characters from string1 to string2.  Case
+   doesn't matter. */
+int
+strnicmp (string1, string2, count)
+     char *string1, *string2;
+     int count;
+{
+  register char ch1, ch2;
+
+  while (count)
+    {
+      ch1 = *string1++;
+      ch2 = *string2++;
+
+      ch1 = info_toupper (ch1);
+      ch2 = info_toupper (ch2);
+
+      if (ch1 == ch2)
+	count--;
+      else
+	break;
+    }
+  return (count);
+}
+
+/* **************************************************************** */
+/*								    */
+/*		     Searching FILE_BUFFER's			    */
+/*								    */
+/* **************************************************************** */
+
+/* Return the absolute position of the first occurence of a node separator in
+   BINDING-buffer.  The search starts at BINDING->start.  Return -1 if no node
+   separator was found. */
+long
+find_node_separator (binding)
+     SEARCH_BINDING *binding;
+{
+  register long i;
+  char *body;
+
+  body = binding->buffer;
+
+  /* A node is started by [^L]^_[^L]\n.  That is to say, the C-l's are
+     optional, but the DELETE and NEWLINE are not.  This separator holds
+     true for all separated elements in an Info file, including the tags
+     table (if present) and the indirect tags table (if present). */
+  for (i = binding->start; i < binding->end - 1; i++)
+    if (((body[i] == INFO_FF && body[i + 1] == INFO_COOKIE) &&
+	 (body[i + 2] == '\n' ||
+	  (body[i + 2] == INFO_FF && body[i + 3] == '\n'))) ||
+	((body[i] == INFO_COOKIE) &&
+	 (body[i + 1] == '\n' ||
+	  (body[i + 1] == INFO_FF && body[i + 2] == '\n'))))
+      return (i);
+  return (-1);
+}
+
+/* Return the length of the node separator characters that BODY is
+   currently pointing at. */
+int
+skip_node_separator (body)
+     char *body;
+{
+  register int i;
+
+  i = 0;
+
+  if (body[i] == INFO_FF)
+    i++;
+
+  if (body[i++] != INFO_COOKIE)
+    return (0);
+
+  if (body[i] == INFO_FF)
+    i++;
+
+  if (body[i++] != '\n')
+    return (0);
+
+  return (i);
+}
+
+/* Return the number of characters from STRING to the start of
+   the next line. */
+int
+skip_line (string)
+     char *string;
+{
+  register int i;
+
+  for (i = 0; string && string[i] && string[i] != '\n'; i++);
+
+  if (string[i] == '\n')
+    i++;
+
+  return (i);
+}
+
+/* Return the absolute position of the beginning of a tags table in this
+   binding starting the search at binding->start. */
+long
+find_tags_table (binding)
+     SEARCH_BINDING *binding;
+{
+  SEARCH_BINDING search;
+  long position;
+
+  search.buffer = binding->buffer;
+  search.start = binding->start;
+  search.end = binding->end;
+  search.flags = S_FoldCase;
+
+  while ((position = find_node_separator (&search)) != -1 )
+    {
+      search.start = position;
+      search.start += skip_node_separator (search.buffer + search.start);
+
+      if (looking_at (TAGS_TABLE_BEG_LABEL, &search))
+	return (position);
+    }
+  return (-1);
+}
+
+/* Return the absolute position of the node named NODENAME in BINDING.
+   This is a brute force search, and we wish to avoid it when possible.
+   This function is called when a tag (indirect or otherwise) doesn't
+   really point to the right node.  It returns the absolute position of
+   the separator preceding the node. */
+long
+find_node_in_binding (nodename, binding)
+     char *nodename;
+     SEARCH_BINDING *binding;
+{
+  register long position;
+  register int offset, namelen;
+  SEARCH_BINDING search;
+
+  namelen = strlen (nodename);
+
+  search.buffer = binding->buffer;
+  search.start = binding->start;
+  search.end = binding->end;
+  search.flags = 0;
+
+  while ((position = find_node_separator (&search)) != -1)
+    {
+      search.start = position;
+      search.start += skip_node_separator (search.buffer + search.start);
+
+      offset = string_in_line (INFO_NODE_LABEL, search.buffer + search.start);
+
+      if (offset == -1)
+	continue;
+
+      search.start += offset;
+      search.start += skip_whitespace (search.buffer + search.start);
+      offset = skip_node_characters
+	(search.buffer + search.start, DONT_SKIP_NEWLINES);
+
+      /* Notice that this is an exact match.  You cannot grovel through
+	 the buffer with this function looking for random nodes. */
+       if ((offset == namelen) &&
+	   (search.buffer[search.start] == nodename[0]) &&
+	   (strncmp (search.buffer + search.start, nodename, offset) == 0))
+	 return (position);
+    }
+  return (-1);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/search.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,78 @@
+/* search.h -- Structure used to search large bodies of text, with bounds. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+/* The search functions take two arguments:
+
+     1) a string to search for, and
+
+     2) a pointer to a SEARCH_BINDING which contains the buffer, start,
+        and end of the search.
+
+   They return a long, which is the offset from the start of the buffer
+   at which the match was found.  An offset of -1 indicates failure. */
+
+#if !defined (_SEARCH_H_)
+#define _SEARCH_H_
+
+typedef struct {
+  char *buffer;			/* The buffer of text to search. */
+  long start;			/* Offset of the start of the search. */
+  long end;			/* Offset of the end of the searh. */
+  int flags;			/* Flags controlling the type of search. */
+} SEARCH_BINDING;
+
+#define S_FoldCase	0x01	/* Set means fold case in searches. */
+#define S_SkipDest	0x02	/* Set means return pointing after the dest. */
+
+SEARCH_BINDING *make_binding (), *copy_binding ();
+extern long search_forward (), search_backward (), search ();
+extern int looking_at ();
+
+/* Note that STRING_IN_LINE () always returns the offset of the 1st character
+   after the string. */
+extern int string_in_line ();
+
+/* Unix doesn't have stricmp () functions. */
+/* strcmp (), but caseless. */
+extern int stricmp ();
+
+/* Compare at most COUNT characters from STRING1 to STRING2.  Case
+   doesn't matter. */
+extern int strnicmp ();
+
+/* Function names that start with "skip" are passed a string, and return
+   an offset from the start of that string.  Function names that start
+   with "find" are passed a SEARCH_BINDING, and return an absolute position
+   marker of the item being searched for.  "Find" functions return a value
+   of -1 if the item being looked for couldn't be found. */
+extern int skip_whitespace (), skip_non_whitespace ();
+extern int skip_whitespace_and_newlines (), skip_line ();
+extern int skip_node_characters (), skip_node_separator ();
+#define DONT_SKIP_NEWLINES 0
+#define SKIP_NEWLINES 1
+
+extern long find_node_separator (), find_tags_table ();
+extern long find_node_in_binding ();
+
+#endif /* !_SEARCH_H_ */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/session.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,4167 @@
+/* session.c -- The user windowing interface to Info. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+
+#if defined (HAVE_SYS_TIME_H)
+#  include <sys/time.h>
+#  define HAVE_STRUCT_TIMEVAL
+#endif /* HAVE_SYS_TIME_H */
+
+static void info_clear_pending_input (), info_set_pending_input ();
+static void info_handle_pointer ();
+
+/* **************************************************************** */
+/*								    */
+/*		     Running an Info Session			    */
+/*								    */
+/* **************************************************************** */
+
+/* The place that we are reading input from. */
+static FILE *info_input_stream = (FILE *)NULL;
+
+/* The last executed command. */
+VFunction *info_last_executed_command = (VFunction *)NULL;
+
+/* Becomes non-zero when 'q' is typed to an Info window. */
+int quit_info_immediately = 0;
+
+/* Array of structures describing for each window which nodes have been
+   visited in that window. */
+INFO_WINDOW **info_windows = (INFO_WINDOW **)NULL;
+
+/* Where to add the next window, if we need to add one. */
+static int info_windows_index = 0;
+
+/* Number of slots allocated to INFO_WINDOWS. */
+static int info_windows_slots = 0;
+
+void remember_window_and_node (), forget_window_and_nodes ();
+void initialize_info_session (), info_session ();
+void display_startup_message_and_start ();
+
+/* Begin an info session finding the nodes specified by FILENAME and NODENAMES.
+   For each loaded node, create a new window.  Always split the largest of the
+   available windows. */
+void
+begin_multiple_window_info_session (filename, nodenames)
+     char *filename;
+     char **nodenames;
+{
+  register int i;
+  WINDOW *window = (WINDOW *)NULL;
+
+  for (i = 0; nodenames[i]; i++)
+    {
+      NODE *node;
+
+      node = info_get_node (filename, nodenames[i]);
+
+      if (!node)
+	break;
+
+      /* If this is the first node, initialize the info session. */
+      if (!window)
+	{
+	  initialize_info_session (node);
+	  window = active_window;
+	}
+      else
+	{
+	  /* Find the largest window in WINDOWS, and make that be the active
+	     one.  Then split it and add our window and node to the list
+	     of remembered windows and nodes.  Then tile the windows. */
+	  register WINDOW *win, *largest = (WINDOW *)NULL;
+	  int max_height = 0;
+
+	  for (win = windows; win; win = win->next)
+	    if (win->height > max_height)
+	      {
+		max_height = win->height;
+		largest = win;
+	      }
+
+	  if (!largest)
+	    {
+	      display_update_display (windows);
+	      info_error (CANT_FIND_WIND);
+	      info_session ();
+	      exit (0);
+	    }
+
+	  active_window = largest;
+	  window = window_make_window (node);
+	  if (window)
+	    {
+	      window_tile_windows (TILE_INTERNALS);
+	      remember_window_and_node (window, node);
+	    }
+	  else
+	    {
+	      display_update_display (windows);
+	      info_error (WIN_TOO_SMALL);
+	      info_session ();
+	      exit (0);
+	    }
+	}
+    }
+  display_startup_message_and_start ();
+}
+
+/* Start an info session with INITIAL_NODE, and an error message in the echo
+   area made from FORMAT and ARG. */
+void
+begin_info_session_with_error (initial_node, format, arg)
+     NODE *initial_node;
+     char *format;
+     void *arg;
+{
+  initialize_info_session (initial_node);
+  info_error (format, arg, (void *)NULL);
+  info_session ();
+}
+
+/* Start an info session with INITIAL_NODE. */
+void
+begin_info_session (initial_node)
+     NODE *initial_node;
+{
+  initialize_info_session (initial_node);
+  display_startup_message_and_start ();
+}
+
+void
+display_startup_message_and_start ()
+{
+  char *format;
+
+  format = replace_in_documentation
+    ("Welcome to Info version %s.  Type \"\\[get-help-window]\" for help.");
+
+  window_message_in_echo_area (format, version_string ());
+  info_session ();
+}
+
+/* Run an info session with an already initialized window and node. */
+void
+info_session ()
+{
+  terminal_prep_terminal ();
+  display_update_display (windows);
+  info_last_executed_command = (VFunction *)NULL;
+  info_read_and_dispatch ();
+  terminal_unprep_terminal ();
+  close_dribble_file ();
+}
+
+/* Here is a window-location dependent event loop.  Called from the
+   functions info_session (), and from read_xxx_in_echo_area (). */
+void
+info_read_and_dispatch ()
+{
+  unsigned char key;
+  int done;
+  done = 0;
+
+  while (!done && !quit_info_immediately)
+    {
+      int lk;
+
+      /* If we haven't just gone up or down a line, there is no
+	 goal column for this window. */
+      if ((info_last_executed_command != info_next_line) &&
+	  (info_last_executed_command != info_prev_line))
+	active_window->goal_column = -1;
+
+      if (echo_area_is_active)
+	{
+	  lk = echo_area_last_command_was_kill;
+	  echo_area_prep_read ();
+	}
+
+      if (!info_any_buffered_input_p ())
+	display_update_display (windows);
+
+      display_cursor_at_point (active_window);
+      info_initialize_numeric_arg ();
+
+      initialize_keyseq ();
+      key = info_get_input_char ();
+
+      /* No errors yet.  We just read a character, that's all.  Only clear
+	 the echo_area if it is not currently active. */
+      if (!echo_area_is_active)
+	window_clear_echo_area ();
+
+      info_error_was_printed = 0;
+
+      /* Do the selected command. */
+      info_dispatch_on_key (key, active_window->keymap);
+
+      if (echo_area_is_active)
+	{
+	  /* Echo area commands that do killing increment the value of
+	     ECHO_AREA_LAST_COMMAND_WAS_KILL.  Thus, if there is no
+	     change in the value of this variable, the last command
+	     executed was not a kill command. */
+	  if (lk == echo_area_last_command_was_kill)
+	    echo_area_last_command_was_kill = 0;
+
+	  if (ea_last_executed_command == ea_newline ||
+	      info_aborted_echo_area)
+	    {
+	      ea_last_executed_command = (VFunction *)NULL;
+	      done = 1;
+	    }
+
+	  if (info_last_executed_command == info_quit)
+	    quit_info_immediately = 1;
+	}
+      else if (info_last_executed_command == info_quit)
+	done = 1;
+    }
+}
+
+/* Found in signals.c */
+extern void initialize_info_signal_handler ();
+
+/* Initialize the first info session by starting the terminal, window,
+   and display systems. */
+void
+initialize_info_session (node)
+     NODE *node;
+{
+  char *getenv (), *term_name;
+
+  term_name = getenv ("TERM");
+  terminal_initialize_terminal (term_name);
+
+  if (terminal_is_dumb_p)
+    {
+      if (!term_name)
+	term_name = "dumb";
+
+      info_error (TERM_TOO_DUMB, term_name);
+      exit (1);
+    }
+  terminal_clear_screen ();
+  initialize_info_keymaps ();
+  window_initialize_windows (screenwidth, screenheight);
+  initialize_info_signal_handler ();
+  display_initialize_display (screenwidth, screenheight);
+  info_set_node_of_window (active_window, node);
+
+  /* Tell the window system how to notify us when a window needs to be
+     asynchronously deleted (e.g., user resizes window very small). */
+  window_deletion_notifier = forget_window_and_nodes;
+
+  /* If input has not been redirected yet, make it come from STDIN. */
+  if (!info_input_stream)
+    info_input_stream = stdin;
+
+  info_windows_initialized_p = 1;
+}
+
+/* Tell Info that input is coming from the file FILENAME. */
+void
+info_set_input_from_file (filename)
+     char *filename;
+{
+  FILE *stream;
+
+  stream = fopen (filename, "r");
+
+  if (!stream)
+    return;
+
+  if (info_input_stream != stdin)
+    fclose (info_input_stream);
+
+  info_input_stream = stream;
+
+  if (stream != stdin)
+    display_inhibited = 1;
+}
+
+/* Return the INFO_WINDOW containing WINDOW, or NULL if there isn't one. */
+static INFO_WINDOW *
+get_info_window_of_window (window)
+     WINDOW *window;
+{
+  register int i;
+  INFO_WINDOW *info_win = (INFO_WINDOW *)NULL;
+
+  for (i = 0; info_windows && (info_win = info_windows[i]); i++)
+    if (info_win->window == window)
+      break;
+
+  return (info_win);
+}
+
+/* Reset the remembered pagetop and point of WINDOW to WINDOW's current
+   values if the window and node are the same as the current one being
+   displayed. */
+void
+set_remembered_pagetop_and_point (window)
+     WINDOW *window;
+{
+  INFO_WINDOW *info_win;
+
+  info_win = get_info_window_of_window (window);
+
+  if (!info_win)
+    return;
+
+  if (info_win->nodes_index &&
+      (info_win->nodes[info_win->current] == window->node))
+    {
+      info_win->pagetops[info_win->current] = window->pagetop;
+      info_win->points[info_win->current] = window->point;
+    }
+}
+
+void
+remember_window_and_node (window, node)
+     WINDOW *window;
+     NODE *node;
+{
+  INFO_WINDOW *info_win;
+
+  /* See if we already have this window in our list. */
+  info_win = get_info_window_of_window (window);
+
+  /* If the window wasn't already on our list, then make a new entry. */
+  if (!info_win)
+    {
+      info_win = (INFO_WINDOW *)xmalloc (sizeof (INFO_WINDOW));
+      info_win->window = window;
+      info_win->nodes = (NODE **)NULL;
+      info_win->pagetops = (int *)NULL;
+      info_win->points = (long *)NULL;
+      info_win->current = 0;
+      info_win->nodes_index = 0;
+      info_win->nodes_slots = 0;
+
+      add_pointer_to_array (info_win, info_windows_index, info_windows,
+			    info_windows_slots, 10, INFO_WINDOW *);
+    }
+
+  /* If this node, the current pagetop, and the current point are the
+     same as the last saved node and pagetop, don't really add this to
+     the list of history nodes. */
+  {
+    int ni = info_win->nodes_index - 1;
+
+    if ((ni != -1) &&
+	(info_win->nodes[ni]->contents == node->contents) &&
+	(info_win->pagetops[ni] == window->pagetop) &&
+	(info_win->points[ni] == window->point))
+    return;
+  }
+
+  /* Remember this node, the currently displayed pagetop, and the current
+     location of point in this window.  Because we are updating pagetops
+     and points as well as nodes, it is more efficient to avoid the
+     add_pointer_to_array macro here. */
+  if (info_win->nodes_index + 2 >= info_win->nodes_slots)
+    {
+      info_win->nodes = (NODE **)
+	xrealloc (info_win->nodes,
+		  (info_win->nodes_slots += 20) * sizeof (NODE *));
+
+      info_win->pagetops = (int *)
+	xrealloc (info_win->pagetops, info_win->nodes_slots * sizeof (int));
+
+      info_win->points = (long *)
+	xrealloc (info_win->points, info_win->nodes_slots * sizeof (long));
+    }
+
+  info_win->nodes[info_win->nodes_index] = node;
+  info_win->pagetops[info_win->nodes_index] = window->pagetop;
+  info_win->points[info_win->nodes_index] = window->point;
+  info_win->current = info_win->nodes_index++;
+  info_win->nodes[info_win->nodes_index] = (NODE *)NULL;
+  info_win->pagetops[info_win->nodes_index] = 0;
+  info_win->points[info_win->nodes_index] = 0;
+}
+
+#define DEBUG_FORGET_WINDOW_AND_NODES
+#if defined (DEBUG_FORGET_WINDOW_AND_NODES)
+static void
+consistency_check_info_windows ()
+{
+  register int i;
+  INFO_WINDOW *info_win;
+
+  for (i = 0; i < info_windows_index; i++)
+    {
+      WINDOW *win;
+
+      for (win = windows; win; win = win->next)
+	if (win == info_windows[i]->window)
+	  break;
+
+      if (!win)
+	abort ();
+    }
+}
+#endif /* DEBUG_FORGET_WINDOW_AND_NODES */
+
+/* Remove WINDOW and its associated list of nodes from INFO_WINDOWS. */
+void
+forget_window_and_nodes (window)
+     WINDOW *window;
+{
+  register int i;
+  INFO_WINDOW *info_win = (INFO_WINDOW *)NULL;
+
+  for (i = 0; info_windows && (info_win = info_windows[i]); i++)
+    if (info_win->window == window)
+      break;
+
+  /* If we found the window to forget, then do so. */
+  if (info_win)
+    {
+      while (i < info_windows_index)
+	info_windows[i] = info_windows[++i];
+
+      info_windows_index--;
+      info_windows[info_windows_index] = (INFO_WINDOW *)NULL;
+
+      if (info_win->nodes)
+	{
+	  for (i = 0; info_win->nodes[i]; i++)
+	    if (internal_info_node_p (info_win->nodes[i]))
+	      free (info_win->nodes[i]);
+	  free (info_win->nodes);
+
+	  maybe_free (info_win->pagetops);
+	  maybe_free (info_win->points);
+	}
+
+      free (info_win);
+    }
+#if defined (DEBUG_FORGET_WINDOW_AND_NODES)
+  consistency_check_info_windows ();
+#endif /* DEBUG_FORGET_WINDOW_AND_NODES */
+}
+
+/* Set WINDOW to show NODE.  Remember the new window in our list of Info
+   windows.  If we are doing automatic footnote display, also try to display
+   the footnotes for this window. */
+void
+info_set_node_of_window (window, node)
+     WINDOW *window;
+     NODE *node;
+{
+  /* Put this node into the window. */
+  window_set_node_of_window (window, node);
+
+  /* Remember this node and window in our list of info windows. */
+  remember_window_and_node (window, node);
+
+  /* If doing auto-footnote display/undisplay, show the footnotes belonging
+     to this window's node. */
+  if (auto_footnotes_p)
+    info_get_or_remove_footnotes (window);
+}
+
+
+/* **************************************************************** */
+/*								    */
+/*		       Info Movement Commands			    */
+/*								    */
+/* **************************************************************** */
+
+/* Change the pagetop of WINDOW to DESIRED_TOP, perhaps scrolling the screen
+   to do so. */
+void
+set_window_pagetop (window, desired_top)
+     WINDOW *window;
+     int desired_top;
+{
+  int point_line, old_pagetop;
+
+  if (desired_top < 0)
+    desired_top = 0;
+  else if (desired_top > window->line_count)
+    desired_top = window->line_count - 1;
+
+  if (window->pagetop == desired_top)
+    return;
+
+  old_pagetop = window->pagetop;
+  window->pagetop = desired_top;
+
+  /* Make sure that point appears in this window. */
+  point_line = window_line_of_point (window);
+  if ((point_line < window->pagetop) ||
+      ((point_line - window->pagetop) > window->height - 1))
+    window->point =
+      window->line_starts[window->pagetop] - window->node->contents;
+
+  window->flags |= W_UpdateWindow;
+
+  /* Find out which direction to scroll, and scroll the window in that
+     direction.  Do this only if there would be a savings in redisplay
+     time.  This is true if the amount to scroll is less than the height
+     of the window, and if the number of lines scrolled would be greater
+     than 10 % of the window's height. */
+  if (old_pagetop < desired_top)
+    {
+      int start, end, amount;
+
+      amount = desired_top - old_pagetop;
+
+      if ((amount >= window->height) ||
+	  (((window->height - amount) * 10) < window->height))
+	return;
+
+      start = amount + window->first_row;
+      end = window->height + window->first_row;
+
+      display_scroll_display (start, end, -amount);
+    }
+  else
+    {
+      int start, end, amount;
+
+      amount = old_pagetop - desired_top;
+
+      if ((amount >= window->height) ||
+	  (((window->height - amount) * 10) < window->height))
+	return;
+
+      start = window->first_row;
+      end = (window->first_row + window->height) - amount;
+      display_scroll_display (start, end, amount);
+    }
+}
+
+/* Immediately make WINDOW->point visible on the screen, and move the
+   terminal cursor there. */
+static void
+info_show_point (window)
+     WINDOW *window;
+{
+  int old_pagetop;
+
+  old_pagetop = window->pagetop;
+  window_adjust_pagetop (window);
+  if (old_pagetop != window->pagetop)
+    {
+      int new_pagetop;
+
+      new_pagetop = window->pagetop;
+      window->pagetop = old_pagetop;
+      set_window_pagetop (window, new_pagetop);
+    }
+
+  if (window->flags & W_UpdateWindow)
+    display_update_one_window (window);
+
+  display_cursor_at_point (window);
+}
+
+/* Move WINDOW->point from OLD line index to NEW line index. */
+static void
+move_to_new_line (old, new, window)
+     int old, new;
+     WINDOW *window;
+{
+  if (old == -1)
+    {
+      info_error (CANT_FIND_POINT);
+    }
+  else
+    {
+      int goal;
+
+      if (new >= window->line_count || new < 0)
+	return;
+
+      goal = window_get_goal_column (window);
+      window->goal_column = goal;
+
+      window->point = window->line_starts[new] - window->node->contents;
+      window->point += window_chars_to_goal (window->line_starts[new], goal);
+      info_show_point (window);
+    }
+}
+
+/* Move WINDOW's point down to the next line if possible. */
+DECLARE_INFO_COMMAND (info_next_line, "Move down to the next line")
+{
+  int old_line, new_line;
+
+  if (count < 0)
+    info_prev_line (window, -count, key);
+  else
+    {
+      old_line = window_line_of_point (window);
+      new_line = old_line + count;
+      move_to_new_line (old_line, new_line, window);
+    }
+}
+
+/* Move WINDOW's point up to the previous line if possible. */
+DECLARE_INFO_COMMAND (info_prev_line, "Move up to the previous line")
+{
+  int old_line, new_line;
+
+  if (count < 0)
+    info_next_line (window, -count, key);
+  else
+    {
+      old_line = window_line_of_point (window);
+      new_line = old_line - count;
+      move_to_new_line (old_line, new_line, window);
+    }
+}
+
+/* Move WINDOW's point to the end of the true line. */
+DECLARE_INFO_COMMAND (info_end_of_line, "Move to the end of the line")
+{
+  register int point, len;
+  register char *buffer;
+
+  buffer = window->node->contents;
+  len = window->node->nodelen;
+
+  for (point = window->point;
+       (point < len) && (buffer[point] != '\n');
+       point++);
+
+  if (point != window->point)
+    {
+      window->point = point;
+      info_show_point (window);
+    }
+}
+
+/* Move WINDOW's point to the beginning of the true line. */
+DECLARE_INFO_COMMAND (info_beginning_of_line, "Move to the start of the line")
+{
+  register int point;
+  register char *buffer;
+
+  buffer = window->node->contents;
+  point = window->point;
+
+  for (; (point) && (buffer[point - 1] != '\n'); point--);
+
+  /* If at a line start alreay, do nothing. */
+  if (point != window->point)
+    {
+      window->point = point;
+      info_show_point (window);
+    }
+}
+
+/* Move point forward in the node. */
+DECLARE_INFO_COMMAND (info_forward_char, "Move forward a character")
+{
+  if (count < 0)
+    info_backward_char (window, -count, key);
+  else
+    {
+      window->point += count;
+
+      if (window->point >= window->node->nodelen)
+	window->point = window->node->nodelen - 1;
+
+      info_show_point (window);
+    }
+}
+
+/* Move point backward in the node. */
+DECLARE_INFO_COMMAND (info_backward_char, "Move backward a character")
+{
+  if (count < 0)
+    info_forward_char (window, -count, key);
+  else
+    {
+      window->point -= count;
+
+      if (window->point < 0)
+	window->point = 0;
+
+      info_show_point (window);
+    }
+}
+
+#define alphabetic(c) (islower (c) || isupper (c) || isdigit (c))
+
+/* Move forward a word in this node. */
+DECLARE_INFO_COMMAND (info_forward_word, "Move forward a word")
+{
+  long point;
+  char *buffer;
+  int end, c;
+
+  if (count < 0)
+    {
+      info_backward_word (window, -count, key);
+      return;
+    }
+
+  point = window->point;
+  buffer = window->node->contents;
+  end = window->node->nodelen;
+
+  while (count)
+    {
+      if (point + 1 >= end)
+	return;
+
+      /* If we are not in a word, move forward until we are in one.
+	 Then, move forward until we hit a non-alphabetic character. */
+      c = buffer[point];
+
+      if (!alphabetic (c))
+	{
+	  while (++point < end)
+	    {
+	      c = buffer[point];
+	      if (alphabetic (c))
+		break;
+	    }
+	}
+
+      if (point >= end) return;
+
+      while (++point < end)
+	{
+	  c = buffer[point];
+	  if (!alphabetic (c))
+	    break;
+	}
+      --count;
+    }
+  window->point = point;
+  info_show_point (window);
+}
+
+DECLARE_INFO_COMMAND (info_backward_word, "Move backward a word")
+{
+  long point;
+  char *buffer;
+  int c;
+
+  if (count < 0)
+    {
+      info_forward_word (window, -count, key);
+      return;
+    }
+
+  buffer = window->node->contents;
+  point = window->point;
+
+  while (count)
+    {
+      if (point == 0)
+	break;
+
+      /* Like info_forward_word (), except that we look at the
+	 characters just before point. */
+
+      c = buffer[point - 1];
+
+      if (!alphabetic (c))
+	{
+	  while (--point)
+	    {
+	      c = buffer[point - 1];
+	      if (alphabetic (c))
+		break;
+	    }
+	}
+
+      while (point)
+	{
+	  c = buffer[point - 1];
+	  if (!alphabetic (c))
+	    break;
+	  else
+	    --point;
+	}
+      --count;
+    }
+  window->point = point;
+  info_show_point (window);
+}
+
+/* Here is a list of time counter names which correspond to ordinal numbers.
+   It is used to print "once" instead of "1". */
+static char *counter_names[] = {
+  "not at all", "once", "twice", "three", "four", "five", "six",
+  (char *)NULL
+};
+
+/* Buffer used to return values from times_description (). */
+static char td_buffer[50];
+
+/* Function returns a static string fully describing the number of times
+   present in COUNT. */
+static char *
+times_description (count)
+     int count;
+{
+  register int i;
+
+  td_buffer[0] = '\0';
+
+  for (i = 0; counter_names[i]; i++)
+    if (count == i)
+      break;
+
+  if (counter_names[i])
+    sprintf (td_buffer, "%s%s", counter_names[i], count > 2 ? " times" : "");
+  else
+    sprintf (td_buffer, "%d times", count);
+
+  return (td_buffer);
+}
+
+/* Variable controlling the behaviour of default scrolling when you are
+   already at the bottom of a node.  Possible values are defined in session.h.
+   The meanings are:
+
+   IS_Continuous	Try to get first menu item, or failing that, the
+			"Next:" pointer, or failing that, the "Up:" and
+			"Next:" of the up.
+   IS_NextOnly		Try to get "Next:" menu item.
+   IS_PageOnly		Simply give up at the bottom of a node. */
+
+int info_scroll_behaviour = IS_Continuous;
+
+/* Choices used by the completer when reading a value for the user-visible
+   variable "scroll-behaviour". */
+char *info_scroll_choices[] = {
+  "Continuous", "Next Only", "Page Only", (char *)NULL
+};
+
+/* Move to 1st menu item, Next, Up/Next, or error in this window. */
+static void
+forward_move_node_structure (window, behaviour)
+     WINDOW *window;
+     int behaviour;
+{
+  switch (behaviour)
+    {
+    case IS_PageOnly:
+      info_error (AT_NODE_BOTTOM);
+      break;
+
+    case IS_NextOnly:
+      info_next_label_of_node (window->node);
+      if (!info_parsed_nodename && !info_parsed_filename)
+	info_error ("No \"Next\" pointer for this node.");
+      else
+	{
+	  window_message_in_echo_area ("Following \"Next\" node...");
+	  info_handle_pointer ("Next", window);
+	}
+      break;
+
+    case IS_Continuous:
+      {
+	/* First things first.  If this node contains a menu, move down
+	   into the menu. */
+	{
+	  REFERENCE **menu;
+
+	  menu = info_menu_of_node (window->node);
+
+	  if (menu)
+	    {
+	      info_free_references (menu);
+	      window_message_in_echo_area ("Selecting first menu item...");
+	      info_menu_digit (window, 1, '1');
+	      return;
+	    }
+	}
+
+	/* Okay, this node does not contain a menu.  If it contains a
+	   "Next:" pointer, use that. */
+	info_next_label_of_node (window->node);
+	if (info_label_was_found)
+	  {
+	    window_message_in_echo_area ("Selecting \"Next\" node...");
+	    info_handle_pointer ("Next", window);
+	    return;
+	  }
+
+	/* Okay, there wasn't a "Next:" for this node.  Move "Up:" until we
+	   can move "Next:".  If that isn't possible, complain that there
+	   are no more nodes. */
+	{
+	  int up_counter, old_current;
+	  INFO_WINDOW *info_win;
+
+	  /* Remember the current node and location. */
+	  info_win = get_info_window_of_window (window);
+	  old_current = info_win->current;
+
+	  /* Back up through the "Up:" pointers until we have found a "Next:"
+	     that isn't the same as the first menu item found in that node. */
+	  up_counter = 0;
+	  while (!info_error_was_printed)
+	    {
+	      info_up_label_of_node (window->node);
+	      if (info_label_was_found)
+		{
+		  info_handle_pointer ("Up", window);
+		  if (info_error_was_printed)
+		    continue;
+
+		  up_counter++;
+
+		  info_next_label_of_node (window->node);
+
+		  /* If no "Next" pointer, keep backing up. */
+		  if (!info_label_was_found)
+		    continue;
+
+		  /* If this node's first menu item is the same as this node's
+		     Next pointer, keep backing up. */
+		  if (!info_parsed_filename)
+		    {
+		      REFERENCE **menu;
+		      char *next_nodename;
+
+		      /* Remember the name of the Next node, since reading
+			 the menu can overwrite the contents of the
+			 info_parsed_xxx strings. */
+		      next_nodename = savestring (info_parsed_nodename);
+
+		      menu = info_menu_of_node (window->node);
+		      if (menu &&
+			  (strcmp
+			   (menu[0]->nodename, next_nodename) == 0))
+			{
+			  info_free_references (menu);
+			  free (next_nodename);
+			  continue;
+			}
+		      else
+			{
+			  /* Restore the world to where it was before
+			     reading the menu contents. */
+			  info_free_references (menu);
+			  free (next_nodename);
+			  info_next_label_of_node (window->node);
+			}
+		    }
+
+		  /* This node has a "Next" pointer, and it is not the
+		     same as the first menu item found in this node. */
+		  window_message_in_echo_area
+		    ("Moving \"Up\" %s, then \"Next\".",
+		     times_description (up_counter));
+
+		  info_handle_pointer ("Next", window);
+		  return;
+		}
+	      else
+		{
+		  /* No more "Up" pointers.  Print an error, and call it
+		     quits. */
+		  register int i;
+
+		  for (i = 0; i < up_counter; i++)
+		    {
+		      info_win->nodes_index--;
+		      free (info_win->nodes[info_win->nodes_index]);
+		      info_win->nodes[info_win->nodes_index] = (NODE *)NULL;
+		    }
+		  info_win->current = old_current;
+		  window->node = info_win->nodes[old_current];
+		  window->pagetop = info_win->pagetops[old_current];
+		  window->point = info_win->points[old_current];
+		  recalculate_line_starts (window);
+		  window->flags |= W_UpdateWindow;
+		  info_error ("No more nodes.");
+		}
+	    }
+	}
+	break;
+      }
+    }
+}
+
+/* Move Prev, Up or error in WINDOW depending on BEHAVIOUR. */
+static void
+backward_move_node_structure (window, behaviour)
+     WINDOW *window;
+     int behaviour;
+{
+  switch (behaviour)
+    {
+    case IS_PageOnly:
+      info_error (AT_NODE_TOP);
+      break;
+
+    case IS_NextOnly:
+      info_prev_label_of_node (window->node);
+      if (!info_parsed_nodename && !info_parsed_filename)
+	info_error ("No \"Prev\" for this node.");
+      else
+	{
+	  window_message_in_echo_area ("Moving \"Prev\" in this window.");
+	  info_handle_pointer ("Prev", window);
+	}
+      break;
+
+    case IS_Continuous:
+      info_prev_label_of_node (window->node);
+
+      if (!info_parsed_nodename && !info_parsed_filename)
+	{
+	  info_up_label_of_node (window->node);
+	  if (!info_parsed_nodename && !info_parsed_filename)
+	    info_error ("No \"Prev\" or \"Up\" for this node.");
+	  else
+	    {
+	      window_message_in_echo_area ("Moving \"Up\" in this window.");
+	      info_handle_pointer ("Up", window);
+	    }
+	}
+      else
+	{
+	  REFERENCE **menu;
+	  int inhibit_menu_traversing = 0;
+
+	  /* Watch out!  If this node's Prev is the same as the Up, then
+	     move Up.  Otherwise, we could move Prev, and then to the last
+	     menu item in the Prev.  This would cause the user to loop
+	     through a subsection of the info file. */
+	  if (!info_parsed_filename && info_parsed_nodename)
+	    {
+	      char *pnode;
+
+	      pnode = savestring (info_parsed_nodename);
+	      info_up_label_of_node (window->node);
+
+	      if (!info_parsed_filename && info_parsed_nodename &&
+		  strcmp (info_parsed_nodename, pnode) == 0)
+		{
+		  /* The nodes are the same.  Inhibit moving to the last
+		     menu item. */
+		  free (pnode);
+		  inhibit_menu_traversing = 1;
+		}
+	      else
+		{
+		  free (pnode);
+		  info_prev_label_of_node (window->node);
+		}
+	    }
+
+	  /* Move to the previous node.  If this node now contains a menu,
+	     and we have not inhibited movement to it, move to the node
+	     corresponding to the last menu item. */
+	  window_message_in_echo_area ("Moving \"Prev\" in this window.");
+	  info_handle_pointer ("Prev", window);
+
+	  if (!inhibit_menu_traversing)
+	    {
+	      while (!info_error_was_printed &&
+		     (menu = info_menu_of_node (window->node)))
+		{
+		  info_free_references (menu);
+		  window_message_in_echo_area
+		    ("Moving to \"Prev\"'s last menu item.");
+		  info_menu_digit (window, 1, '0');
+		}
+	    }
+	}
+      break;
+    }
+}
+
+/* Move continuously forward through the node structure of this info file. */
+DECLARE_INFO_COMMAND (info_global_next_node,
+		      "Move forwards or down through node structure")
+{
+  if (count < 0)
+    info_global_prev_node (window, -count, key);
+  else
+    {
+      while (count && !info_error_was_printed)
+	{
+	  forward_move_node_structure (window, IS_Continuous);
+	  count--;
+	}
+    }
+}
+
+/* Move continuously backward through the node structure of this info file. */
+DECLARE_INFO_COMMAND (info_global_prev_node,
+		      "Move backwards or up through node structure")
+{
+  if (count < 0)
+    info_global_next_node (window, -count, key);
+  else
+    {
+      while (count && !info_error_was_printed)
+	{
+	  backward_move_node_structure (window, IS_Continuous);
+	  count--;
+	}
+    }
+}
+
+/* Show the next screen of WINDOW's node. */
+DECLARE_INFO_COMMAND (info_scroll_forward, "Scroll forward in this window")
+{
+  if (count < 0)
+    info_scroll_backward (window, -count, key);
+  else
+    {
+      int desired_top;
+
+      /* Without an explicit numeric argument, scroll the bottom two
+	 lines to the top of this window,  Or, if at bottom of window,
+	 and the user wishes to scroll through nodes get the "Next" node
+	 for this window. */
+      if (!info_explicit_arg && count == 1)
+	{
+	  desired_top = window->pagetop + (window->height - 2);
+
+	  /* If there are no more lines to scroll here, error, or get
+	     another node, depending on INFO_SCROLL_BEHAVIOUR. */
+	  if (desired_top > window->line_count)
+	    {
+	      int behaviour = info_scroll_behaviour;
+
+	      /* Here is a hack.  If the key being used is not SPC, do the
+		 PageOnly behaviour. */
+	      if (key != SPC && key != DEL)
+		behaviour = IS_PageOnly;
+
+	      forward_move_node_structure (window, behaviour);
+	      return;
+	    }
+	}
+      else
+	desired_top = window->pagetop + count;
+
+      if (desired_top >= window->line_count)
+	desired_top = window->line_count - 2;
+
+      if (window->pagetop > desired_top)
+	return;
+      else
+	set_window_pagetop (window, desired_top);
+    }
+}
+
+/* Show the previous screen of WINDOW's node. */
+DECLARE_INFO_COMMAND (info_scroll_backward, "Scroll backward in this window")
+{
+  if (count < 0)
+    info_scroll_forward (window, -count, key);
+  else
+    {
+      int desired_top;
+
+      /* Without an explicit numeric argument, scroll the top two lines
+	 to the bottom of this window, or move to the previous, or Up'th
+	 node. */
+      if (!info_explicit_arg && count == 1)
+	{
+	  desired_top = window->pagetop - (window->height - 2);
+
+	  if ((desired_top < 0) && (window->pagetop == 0))
+	    {
+	      int behaviour = info_scroll_behaviour;
+
+	      /* Same kind of hack as in info_scroll_forward.  If the key
+		 used to invoke this command is not DEL, do only the PageOnly
+		 behaviour. */
+	      if (key != DEL && key != SPC)
+		behaviour = IS_PageOnly;
+
+	      backward_move_node_structure (window, behaviour);
+	      return;
+	    }
+	}
+      else
+	desired_top = window->pagetop - count;
+
+      if (desired_top < 0)
+	desired_top = 0;
+
+      set_window_pagetop (window, desired_top);
+    }
+}
+
+/* Move to the beginning of the node. */
+DECLARE_INFO_COMMAND (info_beginning_of_node, "Move to the start of this node")
+{
+  window->pagetop = window->point = 0;
+  window->flags |= W_UpdateWindow;
+}
+
+/* Move to the end of the node. */
+DECLARE_INFO_COMMAND (info_end_of_node, "Move to the end of this node")
+{
+  window->point = window->node->nodelen - 1;
+  info_show_point (window);
+}
+
+/* **************************************************************** */
+/*								    */
+/*		   Commands for Manipulating Windows		    */
+/*								    */
+/* **************************************************************** */
+
+/* Make the next window in the chain be the active window. */
+DECLARE_INFO_COMMAND (info_next_window, "Select the next window")
+{
+  if (count < 0)
+    {
+      info_prev_window (window, -count, key);
+      return;
+    }
+
+  /* If no other window, error now. */
+  if (!windows->next && !echo_area_is_active)
+    {
+      info_error (ONE_WINDOW);
+      return;
+    }
+
+  while (count--)
+    {
+      if (window->next)
+	window = window->next;
+      else
+	{
+	  if (window == the_echo_area || !echo_area_is_active)
+	    window = windows;
+	  else
+	    window = the_echo_area;
+	}
+    }
+
+  if (active_window != window)
+    {
+      if (auto_footnotes_p)
+	info_get_or_remove_footnotes (window);
+
+      window->flags |= W_UpdateWindow;
+      active_window = window;
+    }
+}
+
+/* Make the previous window in the chain be the active window. */
+DECLARE_INFO_COMMAND (info_prev_window, "Select the previous window")
+{
+  if (count < 0)
+    {
+      info_next_window (window, -count, key);
+      return;
+    }
+
+  /* Only one window? */
+
+  if (!windows->next && !echo_area_is_active)
+    {
+      info_error (ONE_WINDOW);
+      return;
+    }
+
+  while (count--)
+    {
+      /* If we are in the echo area, or if the echo area isn't active and we
+	 are in the first window, find the last window in the chain. */
+      if (window == the_echo_area ||
+	  (window == windows && !echo_area_is_active))
+	{
+	  register WINDOW *win, *last;
+
+	  for (win = windows; win; win = win->next)
+	    last = win;
+
+	  window = last;
+	}
+      else
+	{
+	  if (window == windows)
+	    window = the_echo_area;
+	  else
+	    window = window->prev;
+	}
+    }
+
+  if (active_window != window)
+    {
+      if (auto_footnotes_p)
+	info_get_or_remove_footnotes (window);
+
+      window->flags |= W_UpdateWindow;
+      active_window = window;
+    }
+}
+
+/* Split WINDOW into two windows, both showing the same node.  If we
+   are automatically tiling windows, re-tile after the split. */
+DECLARE_INFO_COMMAND (info_split_window, "Split the current window")
+{
+  WINDOW *split, *old_active;
+  int pagetop;
+
+  /* Remember the current pagetop of the window being split.  If it doesn't
+     change, we can scroll its contents around after the split. */
+  pagetop = window->pagetop;
+
+  /* Make the new window. */
+  old_active = active_window;
+  active_window = window;
+  split = window_make_window (window->node);
+  active_window = old_active;
+
+  if (!split)
+    {
+      info_error (WIN_TOO_SMALL);
+    }
+  else
+    {
+#if defined (SPLIT_BEFORE_ACTIVE)
+      /* Try to scroll the old window into its new postion. */
+      if (pagetop == window->pagetop)
+	{
+	  int start, end, amount;
+
+	  start = split->first_row;
+	  end = start + window->height;
+	  amount = split->height + 1;
+	  display_scroll_display (start, end, amount);
+	}
+#else /* !SPLIT_BEFORE_ACTIVE */
+      /* Make sure point still appears in the active window. */
+      info_show_point (window);
+#endif /* !SPLIT_BEFORE_ACTIVE */
+
+      /* If the window just split was one internal to Info, try to display
+	 something else in it. */
+      if (internal_info_node_p (split->node))
+	{
+	  register int i, j;
+	  INFO_WINDOW *iw;
+	  NODE *node = (NODE *)NULL;
+	  char *filename;
+
+	  for (i = 0; iw = info_windows[i]; i++)
+	    {
+	      for (j = 0; j < iw->nodes_index; j++)
+		if (!internal_info_node_p (iw->nodes[j]))
+		  {
+		    if (iw->nodes[j]->parent)
+		      filename = iw->nodes[j]->parent;
+		    else
+		      filename = iw->nodes[j]->filename;
+
+		    node = info_get_node (filename, iw->nodes[j]->nodename);
+		    if (node)
+		      {
+			window_set_node_of_window (split, node);
+			i = info_windows_index - 1;
+			break;
+		      }
+		  }
+	    }
+	}
+      split->pagetop = window->pagetop;
+
+      if (auto_tiling_p)
+	window_tile_windows (DONT_TILE_INTERNALS);
+      else
+	window_adjust_pagetop (split);
+
+      remember_window_and_node (split, split->node);
+    }
+}
+
+/* Delete WINDOW, forgetting the list of last visited nodes.  If we are
+   automatically displaying footnotes, show or remove the footnotes
+   window.  If we are automatically tiling windows, re-tile after the
+   deletion. */
+DECLARE_INFO_COMMAND (info_delete_window, "Delete the current window")
+{
+  if (!windows->next)
+    {
+      info_error (CANT_KILL_LAST);
+    }
+  else if (window->flags & W_WindowIsPerm)
+    {
+      info_error ("Cannot delete a permanent window");
+    }
+  else
+    {
+      info_delete_window_internal (window);
+
+      if (auto_footnotes_p)
+	info_get_or_remove_footnotes (active_window);
+
+      if (auto_tiling_p)
+	window_tile_windows (DONT_TILE_INTERNALS);
+    }
+}
+
+/* Do the physical deletion of WINDOW, and forget this window and
+   associated nodes. */
+void
+info_delete_window_internal (window)
+     WINDOW *window;
+{
+  if (windows->next && ((window->flags & W_WindowIsPerm) == 0))
+    {
+      /* We not only delete the window from the display, we forget it from
+	 our list of remembered windows. */
+      forget_window_and_nodes (window);
+      window_delete_window (window);
+
+      if (echo_area_is_active)
+	echo_area_inform_of_deleted_window (window);
+    }
+}
+
+/* Just keep WINDOW, deleting all others. */
+DECLARE_INFO_COMMAND (info_keep_one_window, "Delete all other windows")
+{
+  int num_deleted;		/* The number of windows we deleted. */
+  int pagetop, start, end;
+
+  /* Remember a few things about this window.  We may be able to speed up
+     redisplay later by scrolling its contents. */
+  pagetop = window->pagetop;
+  start = window->first_row;
+  end = start + window->height;
+
+  num_deleted = 0;
+
+  while (1)
+    {
+      WINDOW *win;
+
+      /* Find an eligible window and delete it.  If no eligible windows
+	 are found, we are done.  A window is eligible for deletion if
+	 is it not permanent, and it is not WINDOW. */
+      for (win = windows; win; win = win->next)
+	if (win != window && ((win->flags & W_WindowIsPerm) == 0))
+	  break;
+
+      if (!win)
+	break;
+
+      info_delete_window_internal (win);
+      num_deleted++;
+    }
+
+  /* Scroll the contents of this window into the right place so that the
+     user doesn't have to wait any longer than necessary for redisplay. */
+  if (num_deleted)
+    {
+      int amount;
+
+      amount = (window->first_row - start);
+      amount -= (window->pagetop - pagetop);
+      display_scroll_display (start, end, amount);
+    }
+
+  window->flags |= W_UpdateWindow;
+}
+
+/* Scroll the "other" window of WINDOW. */
+DECLARE_INFO_COMMAND (info_scroll_other_window, "Scroll the other window")
+{
+  WINDOW *other;
+
+  /* If only one window, give up. */
+  if (!windows->next)
+    {
+      info_error (ONE_WINDOW);
+      return;
+    }
+
+  other = window->next;
+
+  if (!other)
+    other = window->prev;
+
+  info_scroll_forward (other, count, key);
+}
+
+/* Change the size of WINDOW by AMOUNT. */
+DECLARE_INFO_COMMAND (info_grow_window, "Grow (or shrink) this window")
+{
+  window_change_window_height (window, count);
+}
+
+/* When non-zero, tiling takes place automatically when info_split_window
+   is called. */
+int auto_tiling_p = 0;
+
+/* Tile all of the visible windows. */
+DECLARE_INFO_COMMAND (info_tile_windows,
+    "Divide the available screen space among the visible windows")
+{
+  window_tile_windows (TILE_INTERNALS);
+}
+
+/* Toggle the state of this window's wrapping of lines. */
+DECLARE_INFO_COMMAND (info_toggle_wrap,
+	      "Toggle the state of line wrapping in the current window")
+{
+  window_toggle_wrap (window);
+}
+
+/* **************************************************************** */
+/*								    */
+/*			Info Node Commands			    */
+/*								    */
+/* **************************************************************** */
+
+/* Using WINDOW for various defaults, select the node referenced by ENTRY
+   in it.  If the node is selected, the window and node are remembered. */
+void
+info_select_reference (window, entry)
+     WINDOW *window;
+     REFERENCE *entry;
+{
+  NODE *node;
+  char *filename, *nodename, *file_system_error;
+
+  file_system_error = (char *)NULL;
+
+  filename = entry->filename;
+  if (!filename)
+    filename = window->node->parent;
+  if (!filename)
+    filename = window->node->filename;
+
+  if (filename)
+    filename = savestring (filename);
+
+  if (entry->nodename)
+    nodename = savestring (entry->nodename);
+  else
+    nodename = savestring ("Top");
+
+  node = info_get_node (filename, nodename);
+
+  /* Try something a little weird.  If the node couldn't be found, and the
+     reference was of the form "foo::", see if the entry->label can be found
+     as a file, with a node of "Top". */
+  if (!node)
+    {
+      if (info_recent_file_error)
+	file_system_error = savestring (info_recent_file_error);
+
+      if (entry->nodename && (strcmp (entry->nodename, entry->label) == 0))
+	{
+	  node = info_get_node (entry->label, "Top");
+	  if (!node && info_recent_file_error)
+	    {
+	      maybe_free (file_system_error);
+	      file_system_error = savestring (info_recent_file_error);
+	    }
+	}
+    }
+
+  if (!node)
+    {
+      if (file_system_error)
+	info_error (file_system_error);
+      else
+	info_error (CANT_FIND_NODE, nodename);
+    }
+
+  maybe_free (file_system_error);
+  maybe_free (filename);
+  maybe_free (nodename);
+
+  if (node)
+    {
+      set_remembered_pagetop_and_point (window);
+      info_set_node_of_window (window, node);
+    }
+}
+
+/* Parse the node specification in LINE using WINDOW to default the filename.
+   Select the parsed node in WINDOW and remember it, or error if the node
+   couldn't be found. */
+static void
+info_parse_and_select (line, window)
+     char *line;
+     WINDOW *window;
+{
+  REFERENCE entry;
+
+  info_parse_node (line, DONT_SKIP_NEWLINES);
+
+  entry.nodename = info_parsed_nodename;
+  entry.filename = info_parsed_filename;
+  entry.label = "*info-parse-and-select*";
+
+  info_select_reference (window, &entry);
+}
+
+/* Given that the values of INFO_PARSED_FILENAME and INFO_PARSED_NODENAME
+   are previously filled, try to get the node represented by them into
+   WINDOW.  The node should have been pointed to by the LABEL pointer of
+   WINDOW->node. */
+static void
+info_handle_pointer (label, window)
+     char *label;
+     WINDOW *window;
+{
+  if (info_parsed_filename || info_parsed_nodename)
+    {
+      char *filename, *nodename;
+      NODE *node;
+
+      filename = nodename = (char *)NULL;
+
+      if (info_parsed_filename)
+	filename = savestring (info_parsed_filename);
+      else
+	{
+	  if (window->node->parent)
+	    filename = savestring (window->node->parent);
+	  else if (window->node->filename)
+	    filename = savestring (window->node->filename);
+	}
+
+      if (info_parsed_nodename)
+	nodename = savestring (info_parsed_nodename);
+      else
+	nodename = savestring ("Top");
+
+      node = info_get_node (filename, nodename);
+
+      if (node)
+	{
+	  INFO_WINDOW *info_win;
+
+	  info_win = get_info_window_of_window (window);
+	  if (info_win)
+	    {
+	      info_win->pagetops[info_win->current] = window->pagetop;
+	      info_win->points[info_win->current] = window->point;
+	    }
+	  set_remembered_pagetop_and_point (window);
+	  info_set_node_of_window (window, node);
+	}
+      else
+	{
+	  if (info_recent_file_error)
+	    info_error (info_recent_file_error);
+	  else
+	    info_error (CANT_FILE_NODE, filename, nodename);
+	}
+
+      free (filename);
+      free (nodename);
+    }
+  else
+    {
+      info_error (NO_POINTER, label);
+    }
+}
+
+/* Make WINDOW display the "Next:" node of the node currently being
+   displayed. */
+DECLARE_INFO_COMMAND (info_next_node, "Select the `Next' node")
+{
+  info_next_label_of_node (window->node);
+  info_handle_pointer ("Next", window);
+}
+
+/* Make WINDOW display the "Prev:" node of the node currently being
+   displayed. */
+DECLARE_INFO_COMMAND (info_prev_node, "Select the `Prev' node")
+{
+  info_prev_label_of_node (window->node);
+  info_handle_pointer ("Prev", window);
+}
+
+/* Make WINDOW display the "Up:" node of the node currently being
+   displayed. */
+DECLARE_INFO_COMMAND (info_up_node, "Select the `Up' node")
+{
+  info_up_label_of_node (window->node);
+  info_handle_pointer ("Up", window);
+}
+
+/* Make WINDOW display the last node of this info file. */
+DECLARE_INFO_COMMAND (info_last_node, "Select the last node in this file")
+{
+  register int i;
+  FILE_BUFFER *fb = file_buffer_of_window (window);
+  NODE *node = (NODE *)NULL;
+
+  if (fb && fb->tags)
+    {
+      for (i = 0; fb->tags[i]; i++);
+      node = info_get_node (fb->filename, fb->tags[i - 1]->nodename);
+    }
+
+  if (!node)
+    info_error ("This window has no additional nodes");
+  else
+    {
+      set_remembered_pagetop_and_point (window);
+      info_set_node_of_window (window, node);
+    }
+}
+
+/* Make WINDOW display the first node of this info file. */
+DECLARE_INFO_COMMAND (info_first_node, "Select the first node in this file")
+{
+  FILE_BUFFER *fb = file_buffer_of_window (window);
+  NODE *node = (NODE *)NULL;
+
+  if (fb && fb->tags)
+    node = info_get_node (fb->filename, fb->tags[0]->nodename);
+
+  if (!node)
+    info_error ("This window has no additional nodes");
+  else
+    {
+      set_remembered_pagetop_and_point (window);
+      info_set_node_of_window (window, node);
+    }
+}
+
+/* Make WINDOW display the previous node displayed in this window. */
+DECLARE_INFO_COMMAND (info_history_node,
+		      "Select the most recently selected node")
+{
+  INFO_WINDOW *info_win;
+
+  /* Find the INFO_WINDOW which contains WINDOW. */
+  info_win = get_info_window_of_window (window);
+
+  if (!info_win)
+    {
+      info_error ("Requested window is not present!");
+      return;
+    }
+
+  set_remembered_pagetop_and_point (window);
+  if (!info_win->current)
+    {
+      if (info_win->nodes_index > 1)
+	{
+	  window_message_in_echo_area
+	    ("Now wrapped around to beginning of history.");
+	  info_win->current = info_win->nodes_index;
+	}
+      else
+	{
+	  info_error ("No earlier nodes in this window.");
+	  return;
+	}
+    }
+
+  info_win->current--;
+  window_set_node_of_window (window, info_win->nodes[info_win->current]);
+  window->pagetop = info_win->pagetops[info_win->current];
+  window->point   = info_win->points[info_win->current];
+  window->flags |= W_UpdateWindow;
+  if (auto_footnotes_p)
+    info_get_or_remove_footnotes (window);
+}
+
+/* Select the last menu item in WINDOW->node. */
+DECLARE_INFO_COMMAND (info_last_menu_item,
+   "Select the last item in this node's menu")
+{
+  info_menu_digit (window, 1, '0');
+}
+
+/* Use KEY (a digit) to select the Nth menu item in WINDOW->node. */
+DECLARE_INFO_COMMAND (info_menu_digit, "Select this menu item")
+{
+  register int i, item;
+  register REFERENCE *entry, **menu;
+
+  menu = info_menu_of_node (window->node);
+
+  if (!menu)
+    {
+      info_error (NO_MENU_NODE);
+      return;
+    }
+
+  /* We have the menu.  See if there are this many items in it. */
+  item = key - '0';
+
+  /* Special case.  Item "0" is the last item in this menu. */
+  if (item == 0)
+    for (i = 0; menu[i + 1]; i++);
+  else
+    {
+      for (i = 0; entry = menu[i]; i++)
+	if (i == item - 1)
+	  break;
+    }
+
+  if (menu[i])
+    info_select_reference (window, menu[i]);
+  else
+    info_error ("There aren't %d items in this menu.", item);
+
+  info_free_references (menu);
+  return;
+}
+
+/* Read a menu or followed reference from the user defaulting to the
+   reference found on the current line, and select that node.  The
+   reading is done with completion.  BUILDER is the function used
+   to build the list of references.  ASK_P is non-zero if the user
+   should be prompted, or zero to select the default item. */
+static void
+info_menu_or_ref_item (window, count, key, builder, ask_p)
+     WINDOW *window;
+     int count;
+     unsigned char key;
+     REFERENCE **(*builder) ();
+     int ask_p;
+{
+  REFERENCE **menu, *entry, *defentry = (REFERENCE *)NULL;
+  char *line;
+
+  menu = (*builder) (window->node);
+
+  if (!menu)
+    {
+      if (builder == info_menu_of_node)
+	info_error (NO_MENU_NODE);
+      else
+	info_error (NO_XREF_NODE);
+      return;
+    }
+
+  /* Default the selected reference to the one which is on the line that
+     point is in.  */
+  {
+    REFERENCE **refs = (REFERENCE **)NULL;
+    int point_line;
+
+    point_line = window_line_of_point (window);
+
+    if (point_line != -1)
+      {
+	SEARCH_BINDING binding;
+
+	binding.start = 0;
+	binding.buffer = window->line_starts[point_line];
+	if (window->line_starts[point_line + 1])
+	  binding.end = window->line_starts[point_line + 1] - binding.buffer;
+	else
+	  binding.end =
+	    (window->node->contents + window->node->nodelen) - binding.buffer;
+	binding.flags = 0;
+
+	if (builder == info_menu_of_node)
+	  {
+	    if (point_line)
+	      {
+		binding.buffer--;
+		binding.end++;
+
+		refs = info_menu_items (&binding);
+	      }
+	  }
+	else
+	  refs = info_xrefs (&binding);
+
+	if (refs)
+	  {
+	    if ((strcmp (refs[0]->label, "Menu") != 0) ||
+		(builder == info_xrefs_of_node))
+	      {
+		defentry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
+		defentry->label = savestring (refs[0]->label);
+		defentry->filename = refs[0]->filename;
+		defentry->nodename = refs[0]->nodename;
+
+		if (defentry->filename)
+		  defentry->filename = savestring (defentry->filename);
+		if (defentry->nodename)
+		  defentry->nodename = savestring (defentry->nodename);
+	      }
+	    info_free_references (refs);
+	  }
+      }
+  }
+
+  /* If we are going to ask the user a question, do it now. */
+  if (ask_p)
+    {
+      char *prompt;
+
+      /* Build the prompt string. */
+      if (defentry)
+	prompt = (char *)xmalloc (20 + strlen (defentry->label));
+      else
+	prompt = (char *)xmalloc (20);
+
+      if (builder == info_menu_of_node)
+	{
+	  if (defentry)
+	    sprintf (prompt, "Menu item (%s): ", defentry->label);
+	  else
+	    sprintf (prompt, "Menu item: ");
+	}
+      else
+	{
+	  if (defentry)
+	    sprintf (prompt, "Follow xref (%s): ", defentry->label);
+	  else
+	    sprintf (prompt, "Follow xref: ");
+	}
+
+      line = info_read_completing_in_echo_area (window, prompt, menu);
+      free (prompt);
+
+      window = active_window;
+
+      /* User aborts, just quit. */
+      if (!line)
+	{
+	  maybe_free (defentry);
+	  info_free_references (menu);
+	  info_abort_key (window, 0, 0);
+	  return;
+	}
+
+      /* If we had a default and the user accepted it, use that. */
+      if (!*line)
+	{
+	  free (line);
+	  if (defentry)
+	    line = savestring (defentry->label);
+	  else
+	    line = (char *)NULL;
+	}
+    }
+  else
+    {
+      /* Not going to ask any questions.  If we have a default entry, use
+	 that, otherwise return. */
+      if (!defentry)
+	return;
+      else
+	line = savestring (defentry->label);
+    }
+
+  if (line)
+    {
+      /* Find the selected label in the references. */
+      entry = info_get_labeled_reference (line, menu);
+
+      if (!entry && defentry)
+	info_error ("The reference disappeared! (%s).", line);
+      else
+	{
+	  NODE *orig;
+
+	  orig = window->node;
+	  info_select_reference (window, entry);
+	  if ((builder == info_xrefs_of_node) && (window->node != orig))
+	    {
+	      long offset;
+	      long start;
+
+	      if (window->line_count > 0)
+		start = window->line_starts[1] - window->node->contents;
+	      else
+		start = 0;
+
+	      offset =
+		info_target_search_node (window->node, entry->label, start);
+
+	      if (offset != -1)
+		{
+		  window->point = offset;
+		  window_adjust_pagetop (window);
+		}
+	    }
+	}
+
+      free (line);
+      if (defentry)
+	{
+	  free (defentry->label);
+	  maybe_free (defentry->filename);
+	  maybe_free (defentry->nodename);
+	  free (defentry);
+	}
+    }
+
+  info_free_references (menu);
+
+  if (!info_error_was_printed)
+    window_clear_echo_area ();
+}
+
+/* Read a line (with completion) which is the name of a menu item,
+   and select that item. */
+DECLARE_INFO_COMMAND (info_menu_item, "Read a menu item and select its node")
+{
+  info_menu_or_ref_item (window, count, key, info_menu_of_node, 1);
+}
+
+/* Read a line (with completion) which is the name of a reference to
+   follow, and select the node. */
+DECLARE_INFO_COMMAND
+  (info_xref_item, "Read a footnote or cross reference and select its node")
+{
+  info_menu_or_ref_item (window, count, key, info_xrefs_of_node, 1);
+}
+
+/* Position the cursor at the start of this node's menu. */
+DECLARE_INFO_COMMAND (info_find_menu, "Move to the start of this node's menu")
+{
+  SEARCH_BINDING binding;
+  long position;
+
+  binding.buffer = window->node->contents;
+  binding.start  = 0;
+  binding.end = window->node->nodelen;
+  binding.flags = S_FoldCase | S_SkipDest;
+
+  position = search (INFO_MENU_LABEL, &binding);
+
+  if (position == -1)
+    info_error (NO_MENU_NODE);
+  else
+    {
+      window->point = position;
+      window_adjust_pagetop (window);
+      window->flags |= W_UpdateWindow;
+    }
+}
+
+/* Visit as many menu items as is possible, each in a separate window. */
+DECLARE_INFO_COMMAND (info_visit_menu,
+  "Visit as many menu items at once as possible")
+{
+  register int i;
+  REFERENCE *entry, **menu;
+
+  menu = info_menu_of_node (window->node);
+
+  if (!menu)
+    info_error (NO_MENU_NODE);
+
+  for (i = 0; (!info_error_was_printed) && (entry = menu[i]); i++)
+    {
+      WINDOW *new;
+
+      new = window_make_window (window->node);
+      window_tile_windows (TILE_INTERNALS);
+
+      if (!new)
+	info_error (WIN_TOO_SMALL);
+      else
+	{
+	  active_window = new;
+	  info_select_reference (new, entry);
+	}
+    }
+}
+
+/* Read a line of input which is a node name, and go to that node. */
+DECLARE_INFO_COMMAND (info_goto_node, "Read a node name and select it")
+{
+  char *line;
+  NODE *node;
+
+#define GOTO_COMPLETES
+#if defined (GOTO_COMPLETES)
+  /* Build a completion list of all of the known nodes. */
+  {
+    register int fbi, i;
+    FILE_BUFFER *current;
+    REFERENCE **items = (REFERENCE **)NULL;
+    int items_index = 0;
+    int items_slots = 0;
+
+    current = file_buffer_of_window (window);
+
+    for (fbi = 0; info_loaded_files && info_loaded_files[fbi]; fbi++)
+      {
+	FILE_BUFFER *fb;
+	REFERENCE *entry;
+	int this_is_the_current_fb;
+
+	fb = info_loaded_files[fbi];
+	this_is_the_current_fb = (current == fb);
+
+	entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
+	entry->filename = entry->nodename = (char *)NULL;
+	entry->label = (char *)xmalloc (4 + strlen (fb->filename));
+	sprintf (entry->label, "(%s)*", fb->filename);
+
+	add_pointer_to_array
+	  (entry, items_index, items, items_slots, 10, REFERENCE *);
+
+	if (fb->tags)
+	  {
+	    for (i = 0; fb->tags[i]; i++)
+	      {
+		entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
+		entry->filename = entry->nodename = (char *)NULL;
+		entry->label = (char *) xmalloc
+		  (4 + strlen (fb->filename) + strlen (fb->tags[i]->nodename));
+		sprintf (entry->label, "(%s)%s",
+			 fb->filename, fb->tags[i]->nodename);
+
+		add_pointer_to_array
+		  (entry, items_index, items, items_slots, 100, REFERENCE *);
+	      }		
+
+	    if (this_is_the_current_fb)
+	      {
+		for (i = 0; fb->tags[i]; i++)
+		  {
+		    entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
+		    entry->filename = entry->nodename = (char *)NULL;
+		    entry->label = savestring (fb->tags[i]->nodename);
+		    add_pointer_to_array (entry, items_index, items,
+					  items_slots, 100, REFERENCE *);
+		  }
+	      }
+	  }
+      }
+    line = info_read_maybe_completing (window, "Goto Node: ", items);
+    info_free_references (items);
+  }
+#else /* !GOTO_COMPLETES */
+  line = info_read_in_echo_area (window, "Goto Node: ");
+#endif /* !GOTO_COMPLETES */
+
+  /* If the user aborted, quit now. */
+  if (!line)
+    {
+      info_abort_key (window, 0, 0);
+      return;
+    }
+
+  canonicalize_whitespace (line);
+
+  if (*line)
+    info_parse_and_select (line, window);
+
+  free (line);
+  if (!info_error_was_printed)
+    window_clear_echo_area ();
+}
+
+/* Move to the "Top" node in this file. */
+DECLARE_INFO_COMMAND (info_top_node, "Select the node `Top' in this file")
+{
+  info_parse_and_select ("Top", window);
+}
+
+/* Move to the node "(dir)Top". */
+DECLARE_INFO_COMMAND (info_dir_node, "Select the node `(dir)'")
+{
+  info_parse_and_select ("(dir)Top", window);
+}
+
+/* Try to delete the current node appearing in this window, showing the most
+   recently selected node in this window. */
+DECLARE_INFO_COMMAND (info_kill_node, "Kill this node")
+{
+  register int iw, i;
+  register INFO_WINDOW *info_win;
+  char *nodename = (char *)NULL;
+  NODE *temp = (NODE *)NULL;
+
+  /* Read the name of a node to kill.  The list of available nodes comes
+     from the nodes appearing in the current window configuration. */
+  {
+    REFERENCE **menu = (REFERENCE **)NULL;
+    int menu_index = 0, menu_slots = 0;
+    char *default_nodename, *prompt;
+
+    for (iw = 0; info_win = info_windows[iw]; iw++)
+      {
+	REFERENCE *entry;
+
+	entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
+	entry->label = savestring (info_win->window->node->nodename);
+	entry->filename = entry->nodename = (char *)NULL;
+
+	add_pointer_to_array
+	  (entry, menu_index, menu, menu_slots, 10, REFERENCE *);
+      }
+
+    default_nodename = savestring (active_window->node->nodename);
+    prompt = (char *)xmalloc (40 + strlen (default_nodename));
+    sprintf (prompt, "Kill node (%s): ", default_nodename);
+
+    nodename = info_read_completing_in_echo_area (window, prompt, menu);
+    free (prompt);
+    info_free_references (menu);
+    if (nodename && !*nodename)
+      {
+	free (nodename);
+	nodename = default_nodename;
+      }
+    else
+      free (default_nodename);
+  }
+
+  /* If there is no nodename to kill, quit now. */
+  if (!nodename)
+    {
+      info_abort_key (window, 0, 0);
+      return;
+    }
+
+  /* If there is a nodename, find it in our window list. */
+  for (iw = 0; info_win = info_windows[iw]; iw++)
+    if (strcmp (nodename, info_win->nodes[info_win->current]->nodename) == 0)
+      break;
+
+  if (!info_win)
+    {
+      if (*nodename)
+	info_error ("Cannot kill the node `%s'", nodename);
+      else
+	window_clear_echo_area ();
+
+      return;
+    }
+
+  /* If there are no more nodes left anywhere to view, complain and exit. */
+  if (info_windows_index == 1 && info_windows[0]->nodes_index == 1)
+    {
+      info_error ("Cannot kill the last node");
+      return;
+    }
+
+  /* INFO_WIN contains the node that the user wants to stop viewing.
+     Delete this node from the list of nodes previously shown in this
+     window. */
+  for (i = info_win->current; i < info_win->nodes_index; i++)
+    info_win->nodes[i] = info_win->nodes[i++];
+
+  /* There is one less node in this window's history list. */
+  info_win->nodes_index--;
+
+  /* Make this window show the most recent history node. */
+  info_win->current = info_win->nodes_index - 1;
+
+  /* If there aren't any nodes left in this window, steal one from the
+     next window. */
+  if (info_win->current < 0)
+    {
+      INFO_WINDOW *stealer;
+      int which, pagetop;
+      long point;
+
+      if (info_windows[iw + 1])
+	stealer = info_windows[iw + 1];
+      else
+	stealer = info_windows[0];
+
+      /* If the node being displayed in the next window is not the most
+	 recently loaded one, get the most recently loaded one. */
+      if ((stealer->nodes_index - 1) != stealer->current)
+	which = stealer->nodes_index - 1;
+
+      /* Else, if there is another node behind the stealers current node,
+	 use that one. */
+      else if (stealer->current > 0)
+	which = stealer->current - 1;
+
+      /* Else, just use the node appearing in STEALER's window. */
+      else
+	which = stealer->current;
+
+      /* Copy this node. */
+      {
+	NODE *copy;
+
+	temp = stealer->nodes[which];
+	point = stealer->points[which];
+	pagetop = stealer->pagetops[which];
+
+	copy = (NODE *)xmalloc (sizeof (NODE));
+	copy->filename = temp->filename;
+	copy->parent = temp->parent;
+	copy->nodename = temp->nodename;
+	copy->contents = temp->contents;
+	copy->nodelen = temp->nodelen;
+	copy->flags = temp->flags;
+
+	temp = copy;
+      }
+
+      window_set_node_of_window (info_win->window, temp);
+      window->point = point;
+      window->pagetop = pagetop;
+      remember_window_and_node (info_win->window, temp);
+    }
+  else
+    {
+      temp = info_win->nodes[info_win->current];
+      window_set_node_of_window (info_win->window, temp);
+    }
+  if (!info_error_was_printed)
+    window_clear_echo_area ();
+}
+
+/* Read the name of a file and select the entire file. */
+DECLARE_INFO_COMMAND (info_view_file, "Read the name of a file and select it")
+{
+  char *line;
+
+  line = info_read_in_echo_area (window, "Find file: ");
+  if (!line)
+    {
+      info_abort_key (active_window, 1, 0);
+      return;
+    }
+
+  if (*line)
+    {
+      NODE *node;
+
+      node = info_get_node (line, "*");
+      if (!node)
+	{
+	  if (info_recent_file_error)
+	    info_error (info_recent_file_error);
+	  else
+	    info_error ("Cannot find \"%s\".", line);
+	}
+      else
+	{
+	  set_remembered_pagetop_and_point (active_window);
+	  info_set_node_of_window (window, node);
+	}
+      free (line);
+    }
+
+  if (!info_error_was_printed)
+    window_clear_echo_area ();
+}
+
+/* **************************************************************** */
+/*								    */
+/*		   Dumping and Printing Nodes			    */
+/*								    */
+/* **************************************************************** */
+
+#define VERBOSE_NODE_DUMPING
+static void write_node_to_stream ();
+static void dump_node_to_stream ();
+static void initialize_dumping ();
+
+/* Dump the nodes specified by FILENAME and NODENAMES to the file named
+   in OUTPUT_FILENAME.  If DUMP_SUBNODES is non-zero, recursively dump
+   the nodes which appear in the menu of each node dumped. */
+void
+dump_nodes_to_file (filename, nodenames, output_filename, dump_subnodes)
+     char *filename;
+     char **nodenames;
+     char *output_filename;
+     int dump_subnodes;
+{
+  register int i;
+  FILE *output_stream;
+
+  /* Get the stream to print the nodes to.  Special case of an output
+     filename of "-" means to dump the nodes to stdout. */
+  if (strcmp (output_filename, "-") == 0)
+    output_stream = stdout;
+  else
+    output_stream = fopen (output_filename, "w");
+
+  if (!output_stream)
+    {
+      info_error ("Could not create output file \"%s\".", output_filename);
+      return;
+    }
+
+  /* Print each node to stream. */
+  initialize_dumping ();
+  for (i = 0; nodenames[i]; i++)
+    dump_node_to_stream (filename, nodenames[i], output_stream, dump_subnodes);
+
+  if (output_stream != stdout)
+    fclose (output_stream);
+
+#if defined (VERBOSE_NODE_DUMPING)
+  info_error ("Done.");
+#endif /* VERBOSE_NODE_DUMPING */
+}
+
+/* A place to remember already dumped nodes. */
+static char **dumped_already = (char **)NULL;
+static int dumped_already_index = 0;
+static int dumped_already_slots = 0;
+
+static void
+initialize_dumping ()
+{
+  dumped_already_index = 0;
+}
+
+/* Get and print the node specified by FILENAME and NODENAME to STREAM.
+   If DUMP_SUBNODES is non-zero, recursively dump the nodes which appear
+   in the menu of each node dumped. */
+static void
+dump_node_to_stream (filename, nodename, stream, dump_subnodes)
+     char *filename, *nodename;
+     FILE *stream;
+     int dump_subnodes;
+{
+  register int i;
+  NODE *node;
+
+  node = info_get_node (filename, nodename);
+
+  if (!node)
+    {
+      if (info_recent_file_error)
+	info_error (info_recent_file_error);
+      else
+	{
+	  if (filename && *nodename != '(')
+	    info_error
+	      (CANT_FILE_NODE, filename_non_directory (filename), nodename);
+	  else
+	    info_error (CANT_FIND_NODE, nodename);
+	}
+      return;
+    }
+
+  /* If we have already dumped this node, don't dump it again. */
+  for (i = 0; i < dumped_already_index; i++)
+    if (strcmp (node->nodename, dumped_already[i]) == 0)
+      {
+	free (node);
+	return;
+      }
+  add_pointer_to_array (node->nodename, dumped_already_index, dumped_already,
+			dumped_already_slots, 50, char *);
+
+#if defined (VERBOSE_NODE_DUMPING)
+  /* Maybe we should print some information about the node being output. */
+  if (node->filename)
+    info_error ("Writing node \"(%s)%s\"...",
+		filename_non_directory (node->filename), node->nodename);
+  else
+    info_error ("Writing node \"%s\"...", node->nodename);
+#endif /* VERBOSE_NODE_DUMPING */
+
+  write_node_to_stream (node, stream);
+
+  /* If we are dumping subnodes, get the list of menu items in this node,
+     and dump each one recursively. */
+  if (dump_subnodes)
+    {
+      REFERENCE **menu = (REFERENCE **)NULL;
+
+      /* If this node is an Index, do not dump the menu references. */
+      if (string_in_line ("Index", node->nodename) == -1)
+	menu = info_menu_of_node (node);
+
+      if (menu)
+	{
+	  for (i = 0; menu[i]; i++)
+	    {
+	      /* We don't dump Info files which are different than the
+		 current one. */
+	      if (!menu[i]->filename)
+		dump_node_to_stream
+		  (filename, menu[i]->nodename, stream, dump_subnodes);
+	    }
+	  info_free_references (menu);
+	}
+    }
+
+  free (node);
+}
+
+/* Dump NODE to FILENAME.  If DUMP_SUBNODES is non-zero, recursively dump
+   the nodes which appear in the menu of each node dumped. */
+void
+dump_node_to_file (node, filename, dump_subnodes)
+     NODE *node;
+     char *filename;
+     int dump_subnodes;
+{
+  FILE *output_stream;
+  char *nodes_filename;
+
+  /* Get the stream to print this node to.  Special case of an output
+     filename of "-" means to dump the nodes to stdout. */
+  if (strcmp (filename, "-") == 0)
+    output_stream = stdout;
+  else
+    output_stream = fopen (filename, "w");
+
+  if (!output_stream)
+    {
+      info_error ("Could not create output file \"%s\".", filename);
+      return;
+    }
+
+  if (node->parent)
+    nodes_filename = node->parent;
+  else
+    nodes_filename = node->filename;
+
+  initialize_dumping ();
+  dump_node_to_stream
+    (nodes_filename, node->nodename, output_stream, dump_subnodes);
+
+  if (output_stream != stdout)
+    fclose (output_stream);
+
+#if defined (VERBOSE_NODE_DUMPING)
+  info_error ("Done.");
+#endif /* VERBOSE_NODE_DUMPING */
+}
+
+#if !defined (DEFAULT_INFO_PRINT_COMMAND)
+#  define DEFAULT_INFO_PRINT_COMMAND "lpr"
+#endif /* !DEFAULT_INFO_PRINT_COMMAND */
+
+DECLARE_INFO_COMMAND (info_print_node,
+ "Pipe the contents of this node through INFO_PRINT_COMMAND")
+{
+  print_node (window->node);
+}
+
+/* Print NODE on a printer piping it into INFO_PRINT_COMMAND. */
+void
+print_node (node)
+     NODE *node;
+{
+  char *print_command, *getenv ();
+  FILE *printer_pipe;
+
+  print_command = getenv ("INFO_PRINT_COMMAND");
+
+  if (!print_command || !*print_command)
+    print_command = DEFAULT_INFO_PRINT_COMMAND;
+
+  printer_pipe = popen (print_command, "w");
+
+  if (!printer_pipe)
+    {
+      info_error ("Cannot open pipe to \"%s\".", print_command);
+      return;
+    }
+
+#if defined (VERBOSE_NODE_DUMPING)
+  /* Maybe we should print some information about the node being output. */
+  if (node->filename)
+    info_error ("Printing node \"(%s)%s\"...",
+		filename_non_directory (node->filename), node->nodename);
+  else
+    info_error ("Printing node \"%s\"...", node->nodename);
+#endif /* VERBOSE_NODE_DUMPING */
+
+  write_node_to_stream (node, printer_pipe);
+  pclose (printer_pipe);
+
+#if defined (VERBOSE_NODE_DUMPING)
+  info_error ("Done.");
+#endif /* VERBOSE_NODE_DUMPING */
+}
+
+static void
+write_node_to_stream (node, stream)
+     NODE *node;
+     FILE *stream;
+{
+  fwrite (node->contents, 1, node->nodelen, stream);
+}
+
+/* **************************************************************** */
+/*								    */
+/*		      Info Searching Commands			    */
+/*								    */
+/* **************************************************************** */
+
+/* Variable controlling the garbage collection of files briefly visited
+   during searches.  Such files are normally gc'ed, unless they were
+   compressed to begin with.  If this variable is non-zero, it says
+   to gc even those file buffer contents which had to be uncompressed. */
+int gc_compressed_files = 0;
+
+static void info_gc_file_buffers ();
+
+static char *search_string = (char *)NULL;
+static int search_string_index = 0;
+static int search_string_size = 0;
+static int isearch_is_active = 0;
+
+/* Return the file buffer which belongs to WINDOW's node. */
+FILE_BUFFER *
+file_buffer_of_window (window)
+     WINDOW *window;
+{
+  /* If this window has no node, then it has no file buffer. */
+  if (!window->node)
+    return ((FILE_BUFFER *)NULL);
+
+  if (window->node->parent)
+    return (info_find_file (window->node->parent));
+
+  if (window->node->filename)
+    return (info_find_file (window->node->filename));
+
+  return ((FILE_BUFFER *)NULL);
+}
+
+/* Search for STRING in NODE starting at START.  Return -1 if the string
+   was not found, or the location of the string if it was.  If WINDOW is
+   passed as non-null, set the window's node to be NODE, its point to be
+   the found string, and readjust the window's pagetop.  Final argument
+   DIR says which direction to search in.  If it is positive, search
+   forward, else backwards. */
+long
+info_search_in_node (string, node, start, window, dir)
+     char *string;
+     NODE *node;
+     long start;
+     WINDOW *window;
+     int dir;
+{
+  SEARCH_BINDING binding;
+  long offset;
+
+  binding.buffer = node->contents;
+  binding.start = start;
+  binding.end = node->nodelen;
+  binding.flags = S_FoldCase;
+
+  if (dir < 0)
+    {
+      binding.end = 0;
+      binding.flags |= S_SkipDest;
+    }
+
+  if (binding.start < 0)
+    return (-1);
+
+  /* For incremental searches, we always wish to skip past the string. */
+  if (isearch_is_active)
+    binding.flags |= S_SkipDest;
+
+  offset = search (string, &binding);
+
+  if (offset != -1 && window)
+    {
+      set_remembered_pagetop_and_point (window);
+      if (window->node != node)
+	window_set_node_of_window (window, node);
+      window->point = offset;
+      window_adjust_pagetop (window);
+    }
+  return (offset);
+}
+
+/* Search NODE, looking for the largest possible match of STRING.  Start the
+   search at START.  Return the absolute position of the match, or -1, if
+   no part of the string could be found. */
+long
+info_target_search_node (node, string, start)
+     NODE *node;
+     char *string;
+     long start;
+{
+  register int i;
+  long offset;
+  char *target;
+
+  target = savestring (string);
+  i = strlen (target);
+
+  /* Try repeatedly searching for this string while removing words from
+     the end of it. */
+  while (i)
+    {
+      target[i] = '\0';
+      offset = info_search_in_node (target, node, start, (WINDOW *)NULL, 1);
+
+      if (offset != -1)
+	break;
+
+      /* Delete the last word from TARGET. */
+      for (; i && (!whitespace (target[i]) && (target[i] != ',')); i--);
+    }
+  free (target);
+  return (offset);
+}
+
+/* Search for STRING starting in WINDOW at point.  If the string is found
+   in this node, set point to that position.  Otherwise, get the file buffer
+   associated with WINDOW's node, and search through each node in that file.
+   If the search fails, return non-zero, else zero.  Side-effect window
+   leaving the node and point where the string was found current. */
+static char *last_searched_for_string = (char *)NULL;
+static int
+info_search_internal (string, window, dir)
+     char *string;
+     WINDOW *window;
+     int dir;
+{
+  register int i;
+  FILE_BUFFER *file_buffer;
+  char *initial_nodename;
+  long ret, start = 0;
+
+  file_buffer = file_buffer_of_window (window);
+  initial_nodename = window->node->nodename;
+
+  if ((info_last_executed_command == info_search) &&
+      (last_searched_for_string) &&
+      (strcmp (last_searched_for_string, string) == 0))
+    {
+      ret = info_search_in_node
+	(string, window->node, window->point + dir, window, dir);
+    }
+  else
+    {
+      ret = info_search_in_node
+	(string, window->node, window->point, window, dir);
+    }
+
+  maybe_free (last_searched_for_string);
+  last_searched_for_string = savestring (string);
+
+  if (ret != -1)
+    {
+      /* We won! */
+      if (!echo_area_is_active && !isearch_is_active)
+	window_clear_echo_area ();
+      return (0);
+    }
+
+  /* The string wasn't found in the current node.  Search through the
+     window's file buffer, iff the current node is not "*". */
+  if (!file_buffer || (strcmp (initial_nodename, "*") == 0))
+    return (-1);
+
+  /* If this file has tags, search through every subfile, starting at
+     this node's subfile and node.  Otherwise, search through the
+     file's node list. */
+  if (file_buffer->tags)
+    {
+      register int current_tag, number_of_tags;
+      char *last_subfile;
+      TAG *tag;
+
+      /* Find number of tags and current tag. */
+      last_subfile = (char *)NULL;
+      for (i = 0; file_buffer->tags[i]; i++)
+	if (strcmp (initial_nodename, file_buffer->tags[i]->nodename) == 0)
+	  {
+	    current_tag = i;
+	    last_subfile = file_buffer->tags[i]->filename;
+	  }
+
+      number_of_tags = i;
+
+      /* If there is no last_subfile, our tag wasn't found. */
+      if (!last_subfile)
+	return (-1);
+
+      /* Search through subsequent nodes, wrapping around to the top
+	 of the info file until we find the string or return to this
+	 window's node and point. */
+      while (1)
+	{
+	  NODE *node;
+
+	  /* Allow C-g to quit the search, failing it if pressed. */
+	  return_if_control_g (-1);
+
+	  current_tag += dir;
+
+	  if (current_tag < 0)
+	    current_tag = number_of_tags - 1;
+	  else if (current_tag == number_of_tags)
+	    current_tag = 0;
+
+	  tag = file_buffer->tags[current_tag];
+
+	  if (!echo_area_is_active && (last_subfile != tag->filename))
+	    {
+	      window_message_in_echo_area
+		("Searching subfile \"%s\"...",
+		 filename_non_directory (tag->filename));
+
+	      last_subfile = tag->filename;
+	    }
+
+	  node = info_get_node (file_buffer->filename, tag->nodename);
+
+	  if (!node)
+	    {
+	      /* If not doing i-search... */
+	      if (!echo_area_is_active)
+		{
+		  if (info_recent_file_error)
+		    info_error (info_recent_file_error);
+		  else
+		    info_error (CANT_FILE_NODE,
+				filename_non_directory (file_buffer->filename),
+				tag->nodename);
+		}
+	      return (-1);
+	    }
+
+	  if (dir < 0)
+	    start = tag->nodelen;
+
+	  ret =
+	    info_search_in_node (string, node, start, window, dir);
+
+	  /* Did we find the string in this node? */
+	  if (ret != -1)
+	    {
+	      /* Yes!  We win. */
+	      remember_window_and_node (window, node);
+	      if (!echo_area_is_active)
+		window_clear_echo_area ();
+	      return (0);
+	    }
+
+	  /* No.  Free this node, and make sure that we haven't passed
+	     our starting point. */
+	  free (node);
+
+	  if (strcmp (initial_nodename, tag->nodename) == 0)
+	    return (-1);
+	}
+    }
+  return (-1);
+}
+
+DECLARE_INFO_COMMAND (info_search, "Read a string and search for it")
+{
+  char *line, *prompt;
+  int result, old_pagetop;
+  int direction;
+
+  if (count < 0)
+    direction = -1;
+  else
+    direction = 1;
+
+  /* Read a string from the user, defaulting the search to SEARCH_STRING. */
+  if (!search_string)
+    {
+      search_string = (char *)xmalloc (search_string_size = 100);
+      search_string[0] = '\0';
+    }
+
+  prompt = (char *)xmalloc (50 + strlen (search_string));
+
+  sprintf (prompt, "%s for string [%s]: ",
+	   direction < 0 ? "Search backward" : "Search",
+	   search_string);
+
+  line = info_read_in_echo_area (window, prompt);
+  free (prompt);
+
+  if (!line)
+    {
+      info_abort_key ();
+      return;
+    }
+
+  if (*line)
+    {
+      if (strlen (line) + 1 > search_string_size)
+	search_string = (char *)
+	  xrealloc (search_string, (search_string_size += 50 + strlen (line)));
+
+      strcpy (search_string, line);
+      search_string_index = strlen (line);
+      free (line);
+    }
+
+  old_pagetop = active_window->pagetop;
+  result = info_search_internal (search_string, active_window, direction);
+
+  if (result != 0 && !info_error_was_printed)
+    info_error ("Search failed.");
+  else if (old_pagetop != active_window->pagetop)
+    {
+      int new_pagetop;
+
+      new_pagetop = active_window->pagetop;
+      active_window->pagetop = old_pagetop;
+      set_window_pagetop (active_window, new_pagetop);
+      if (auto_footnotes_p)
+	info_get_or_remove_footnotes (active_window);
+    }
+
+  /* Perhaps free the unreferenced file buffers that were searched, but
+     not retained. */
+  info_gc_file_buffers ();
+}
+
+/* **************************************************************** */
+/*								    */
+/*			Incremental Searching			    */
+/*								    */
+/* **************************************************************** */
+
+static void incremental_search ();
+
+DECLARE_INFO_COMMAND (isearch_forward,
+		      "Search interactively for a string as you type it")
+{
+  incremental_search (window, count, key);
+}
+
+DECLARE_INFO_COMMAND (isearch_backward,
+		      "Search interactively for a string as you type it")
+{
+  incremental_search (window, -count, key);
+}
+
+/* Incrementally search for a string as it is typed. */
+/* The last accepted incremental search string. */
+static char *last_isearch_accepted = (char *)NULL;
+
+/* The current incremental search string. */
+static char *isearch_string = (char *)NULL;
+static int isearch_string_index = 0;
+static int isearch_string_size = 0;
+static unsigned char isearch_terminate_search_key = ESC;
+
+/* Structure defining the current state of an incremental search. */
+typedef struct {
+  WINDOW_STATE_DECL;	/* The node, pagetop and point. */
+  int search_index;	/* Offset of the last char in the search string. */
+  int direction;	/* The direction that this search is heading in. */
+  int failing;		/* Whether or not this search failed. */
+} SEARCH_STATE;
+
+/* Array of search states. */
+static SEARCH_STATE **isearch_states = (SEARCH_STATE **)NULL;
+static int isearch_states_index = 0;
+static int isearch_states_slots = 0;
+
+/* Push the state of this search. */
+static void
+push_isearch (window, search_index, direction, failing)
+     WINDOW *window;
+     int search_index, direction, failing;
+{
+  SEARCH_STATE *state;
+
+  state = (SEARCH_STATE *)xmalloc (sizeof (SEARCH_STATE));
+  window_get_state (window, state);
+  state->search_index = search_index;
+  state->direction = direction;
+  state->failing = failing;
+
+  add_pointer_to_array (state, isearch_states_index, isearch_states,
+			isearch_states_slots, 20, SEARCH_STATE *);
+}
+
+/* Pop the state of this search to WINDOW, SEARCH_INDEX, and DIRECTION. */
+static void
+pop_isearch (window, search_index, direction, failing)
+     WINDOW *window;
+     int *search_index, *direction, *failing;
+{
+  SEARCH_STATE *state;
+
+  if (isearch_states_index)
+    {
+      isearch_states_index--;
+      state = isearch_states[isearch_states_index];
+      window_set_state (window, state);
+      *search_index = state->search_index;
+      *direction = state->direction;
+      *failing = state->failing;
+
+      free (state);
+      isearch_states[isearch_states_index] = (SEARCH_STATE *)NULL;
+    }
+}
+
+/* Free the memory used by isearch_states. */
+static void
+free_isearch_states ()
+{
+  register int i;
+
+  for (i = 0; i < isearch_states_index; i++)
+    {
+      free (isearch_states[i]);
+      isearch_states[i] = (SEARCH_STATE *)NULL;
+    }
+  isearch_states_index = 0;
+}
+
+/* Display the current search in the echo area. */
+static void
+show_isearch_prompt (dir, string, failing_p)
+     int dir;
+     unsigned char *string;
+     int failing_p;
+{
+  register int i;
+  char *prefix, *prompt, *p_rep;
+  int prompt_len, p_rep_index, p_rep_size;
+
+  if (dir < 0)
+    prefix = "I-search backward: ";
+  else
+    prefix = "I-search: ";
+
+  p_rep_index = p_rep_size = 0;
+  p_rep = (char *)NULL;
+  for (i = 0; string[i]; i++)
+    {
+      char *rep;
+
+      switch (string[i])
+	{
+	case ' ': rep = " "; break;
+	case LFD: rep = "\\n"; break;
+	case TAB: rep = "\\t"; break;
+	default:
+	  rep = pretty_keyname (string[i]);
+	}
+      if ((p_rep_index + strlen (rep) + 1) >= p_rep_size)
+	p_rep = (char *)xrealloc (p_rep, p_rep_size += 100);
+
+      strcpy (p_rep + p_rep_index, rep);
+      p_rep_index += strlen (rep);
+    }
+
+  prompt_len = strlen (prefix) + p_rep_index + 20;
+  prompt = (char *)xmalloc (prompt_len);
+  sprintf (prompt, "%s%s%s", failing_p ? "Failing " : "", prefix,
+	   p_rep ? p_rep : "");
+
+  window_message_in_echo_area ("%s", prompt);
+  maybe_free (p_rep);
+  free (prompt);
+  display_cursor_at_point (active_window);
+}
+
+static void
+incremental_search (window, count, ignore)
+     WINDOW *window;
+     int count;
+     unsigned char ignore;
+{
+  unsigned char key;
+  int last_search_result, search_result, dir;
+  SEARCH_STATE mystate, orig_state;
+
+  if (count < 0)
+    dir = -1;
+  else
+    dir = 1;
+
+  last_search_result = search_result = 0;
+
+  window_get_state (window, &orig_state);
+
+  isearch_string_index = 0;
+  if (!isearch_string_size)
+    isearch_string = (char *)xmalloc (isearch_string_size = 50);
+
+  /* Show the search string in the echo area. */
+  isearch_string[isearch_string_index] = '\0';
+  show_isearch_prompt (dir, isearch_string, search_result);
+
+  isearch_is_active = 1;
+
+  while (isearch_is_active)
+    {
+      VFunction *func = (VFunction *)NULL;
+      int quoted = 0;
+
+      /* If a recent display was interrupted, then do the redisplay now if
+	 it is convenient. */
+      if (!info_any_buffered_input_p () && display_was_interrupted_p)
+	{
+	  display_update_one_window (window);
+	  display_cursor_at_point (active_window);
+	}
+
+      /* Read a character and dispatch on it. */
+      key = info_get_input_char ();
+      window_get_state (window, &mystate);
+
+      if (key == DEL)
+	{
+	  /* User wants to delete one level of search? */
+	  if (!isearch_states_index)
+	    {
+	      terminal_ring_bell ();
+	      continue;
+	    }
+	  else
+	    {
+	      pop_isearch
+		(window, &isearch_string_index, &dir, &search_result);
+	      isearch_string[isearch_string_index] = '\0';
+	      show_isearch_prompt (dir, isearch_string, search_result);
+	      goto after_search;
+	    }
+	}
+      else if (key == Control ('q'))
+	{
+	  key = info_get_input_char ();
+	  quoted = 1;
+	}
+
+      /* We are about to search again, or quit.  Save the current search. */
+      push_isearch (window, isearch_string_index, dir, search_result);
+
+      if (quoted)
+	goto insert_and_search;
+
+      if (!Meta_p (key) || (ISO_Latin_p && key < 160))
+	{
+	  func = window->keymap[key].function;
+
+	  /* If this key invokes an incremental search, then this means that
+	     we will either search again in the same direction, search
+	     again in the reverse direction, or insert the last search
+	     string that was accepted through incremental searching. */
+	  if (func == isearch_forward || func == isearch_backward)
+	    {
+	      if ((func == isearch_forward && dir > 0) ||
+		  (func == isearch_backward && dir < 0))
+		{
+		  /* If the user has typed no characters, then insert the
+		     last successful search into the current search string. */
+		  if (isearch_string_index == 0)
+		    {
+		      /* Of course, there must be something to insert. */
+		      if (last_isearch_accepted)
+			{
+			  if (strlen (last_isearch_accepted) + 1 >=
+			      isearch_string_size)
+			    isearch_string = (char *)
+			      xrealloc (isearch_string,
+					isearch_string_size += 10 +
+					strlen (last_isearch_accepted));
+			  strcpy (isearch_string, last_isearch_accepted);
+			  isearch_string_index = strlen (isearch_string);
+			  goto search_now;
+			}
+		      else
+			continue;
+		    }
+		  else
+		    {
+		      /* Search again in the same direction.  This means start
+			 from a new place if the last search was successful. */
+		      if (search_result == 0)
+			window->point += dir;
+		    }
+		}
+	      else
+		{
+		  /* Reverse the direction of the search. */
+		  dir = -dir;
+		}
+	    }
+	  else if (isprint (key) || func == (VFunction *)NULL)
+	    {
+	    insert_and_search:
+
+	      if (isearch_string_index + 2 >= isearch_string_size)
+		isearch_string = (char *)xrealloc
+		  (isearch_string, isearch_string_size += 100);
+
+	      isearch_string[isearch_string_index++] = key;
+	      isearch_string[isearch_string_index] = '\0';
+	      goto search_now;
+	    }
+	  else if (func == info_abort_key)
+	    {
+	      /* If C-g pressed, and the search is failing, pop the search
+		 stack back to the last unfailed search. */
+	      if (isearch_states_index && (search_result != 0))
+		{
+		  terminal_ring_bell ();
+		  while (isearch_states_index && (search_result != 0))
+		    pop_isearch
+		      (window, &isearch_string_index, &dir, &search_result);
+		  isearch_string[isearch_string_index] = '\0';
+		  show_isearch_prompt (dir, isearch_string, search_result);
+		  continue;
+		}
+	      else
+		goto exit_search;
+	    }
+	  else
+	    goto exit_search;
+	}
+      else
+	{
+	exit_search:
+	  /* The character is not printable, or it has a function which is
+	     non-null.  Exit the search, remembering the search string.  If
+	     the key is not the same as the isearch_terminate_search_key,
+	     then push it into pending input. */
+	  if (isearch_string_index && func != info_abort_key)
+	    {
+	      maybe_free (last_isearch_accepted);
+	      last_isearch_accepted = savestring (isearch_string);
+	    }
+
+	  if (key != isearch_terminate_search_key)
+	    info_set_pending_input (key);
+
+	  if (func == info_abort_key)
+	    {
+	      if (isearch_states_index)
+		window_set_state (window, &orig_state);
+	    }
+
+	  if (!echo_area_is_active)
+	    window_clear_echo_area ();
+
+	  if (auto_footnotes_p)
+	    info_get_or_remove_footnotes (active_window);
+
+	  isearch_is_active = 0;
+	  continue;
+	}
+
+      /* Search for the contents of isearch_string. */
+    search_now:
+      show_isearch_prompt (dir, isearch_string, search_result);
+
+      if (search_result == 0)
+	{
+	  /* Check to see if the current search string is right here.  If
+	     we are looking at it, then don't bother calling the search
+	     function. */
+	  if (((dir < 0) &&
+	       (strnicmp (window->node->contents + window->point,
+			 isearch_string, isearch_string_index) == 0)) ||
+	      ((dir > 0) &&
+	       ((window->point - isearch_string_index) >= 0) &&
+	       (strnicmp (window->node->contents +
+			  (window->point - (isearch_string_index - 1)),
+			  isearch_string, isearch_string_index) == 0)))
+	    {
+	      if (dir > 0)
+		window->point++;
+	    }
+	  else
+	    search_result = info_search_internal (isearch_string, window, dir);
+	}
+
+      /* If this search failed, and we didn't already have a failed search,
+	 then ring the terminal bell. */
+      if (search_result != 0 && last_search_result == 0)
+	terminal_ring_bell ();
+
+    after_search:
+      show_isearch_prompt (dir, isearch_string, search_result);
+
+      if (search_result == 0)
+	{
+	  if ((mystate.node == window->node) &&
+	      (mystate.pagetop != window->pagetop))
+	    {
+	      int newtop = window->pagetop;
+	      window->pagetop = mystate.pagetop;
+	      set_window_pagetop (window, newtop);
+	    }
+	  display_update_one_window (window);
+	  display_cursor_at_point (window);
+	}
+
+      last_search_result = search_result;
+    }
+
+  /* Free the memory used to remember each search state. */
+  free_isearch_states ();
+
+  /* Perhaps GC some file buffers. */
+  info_gc_file_buffers ();
+
+  /* After searching, leave the window in the correct state. */
+  if (!echo_area_is_active)
+    window_clear_echo_area ();
+}
+
+/* GC some file buffers.  A file buffer can be gc-ed if there we have
+   no nodes in INFO_WINDOWS that reference this file buffer's contents.
+   Garbage collecting a file buffer means to free the file buffers
+   contents. */
+static void
+info_gc_file_buffers ()
+{
+  register int fb_index, iw_index, i;
+  register FILE_BUFFER *fb;
+  register INFO_WINDOW *iw;
+
+  if (!info_loaded_files)
+    return;
+
+  for (fb_index = 0; fb = info_loaded_files[fb_index]; fb_index++)
+    {
+      int fb_referenced_p = 0;
+
+      /* If already gc-ed, do nothing. */
+      if (!fb->contents)
+	continue;
+
+      /* If this file had to be uncompressed, check to see if we should
+	 gc it.  This means that the user-variable "gc-compressed-files"
+	 is non-zero. */
+      if ((fb->flags & N_IsCompressed) && !gc_compressed_files)
+	continue;
+
+      /* If this file's contents are not gc-able, move on. */
+      if (fb->flags & N_CannotGC)
+	continue;
+
+      /* Check each INFO_WINDOW to see if it has any nodes which reference
+	 this file. */
+      for (iw_index = 0; iw = info_windows[iw_index]; iw_index++)
+	{
+	  for (i = 0; iw->nodes && iw->nodes[i]; i++)
+	    {
+	      if ((strcmp (fb->fullpath, iw->nodes[i]->filename) == 0) ||
+		  (strcmp (fb->filename, iw->nodes[i]->filename) == 0))
+		{
+		  fb_referenced_p = 1;
+		  break;
+		}
+	    }
+	}
+
+      /* If this file buffer wasn't referenced, free its contents. */
+      if (!fb_referenced_p)
+	{
+	  free (fb->contents);
+	  fb->contents = (char *)NULL;
+	}
+    }
+}
+
+/* **************************************************************** */
+/*								    */
+/*		  Traversing and Selecting References		    */
+/*								    */
+/* **************************************************************** */
+
+/* Move to the next or previous cross reference in this node. */
+static void
+info_move_to_xref (window, count, key, dir)
+     WINDOW *window;
+     int count;
+     unsigned char key;
+     int dir;
+{
+  long firstmenu, firstxref;
+  long nextmenu, nextxref;
+  long placement = -1;
+  long start = 0;
+  NODE *node = window->node;
+
+  if (dir < 0)
+    start = node->nodelen;
+
+  /* This search is only allowed to fail if there is no menu or cross
+     reference in the current node.  Otherwise, the first menu or xref
+     found is moved to. */
+
+  firstmenu = info_search_in_node
+    (INFO_MENU_ENTRY_LABEL, node, start, (WINDOW *)NULL, dir);
+
+  /* FIRSTMENU may point directly to the line defining the menu.  Skip that
+     and go directly to the first item. */
+
+  if (firstmenu != -1)
+    {
+      char *text = node->contents + firstmenu;
+
+      if (strncmp (text, INFO_MENU_LABEL, strlen (INFO_MENU_LABEL)) == 0)
+	firstmenu = info_search_in_node
+	  (INFO_MENU_ENTRY_LABEL, node, firstmenu + dir, (WINDOW *)NULL, dir);
+    }
+
+  firstxref =
+    info_search_in_node (INFO_XREF_LABEL, node, start, (WINDOW *)NULL, dir);
+
+  if (firstmenu == -1 && firstxref == -1)
+    {
+      info_error ("No cross references in this node.");
+      return;
+    }
+
+  /* There is at least one cross reference or menu entry in this node.
+     Try hard to find the next available one. */
+
+  nextmenu = info_search_in_node
+    (INFO_MENU_ENTRY_LABEL, node, window->point + dir, (WINDOW *)NULL, dir);
+
+  nextxref = info_search_in_node
+    (INFO_XREF_LABEL, node, window->point + dir, (WINDOW *)NULL, dir);
+
+  /* Ignore "Menu:" as a menu item. */
+  if (nextmenu != -1)
+    {
+      char *text = node->contents + nextmenu;
+
+      if (strncmp (text, INFO_MENU_LABEL, strlen (INFO_MENU_LABEL)) == 0)
+	nextmenu = info_search_in_node
+	  (INFO_MENU_ENTRY_LABEL, node, nextmenu + dir, (WINDOW *)NULL, dir);
+    }
+
+  /* If there is both a next menu entry, and a next xref entry, choose the
+     one which occurs first.  Otherwise, select the one which actually
+     appears in this node following point. */
+  if (nextmenu != -1 && nextxref != -1)
+    {
+      if (((dir == 1) && (nextmenu < nextxref)) ||
+	  ((dir == -1) && (nextmenu > nextxref)))
+	placement = nextmenu + 1;
+      else
+	placement = nextxref;
+    }
+  else if (nextmenu != -1)
+    placement = nextmenu + 1;
+  else if (nextxref != -1)
+    placement = nextxref;
+
+  /* If there was neither a menu or xref entry appearing in this node after
+     point, choose the first menu or xref entry appearing in this node. */
+  if (placement == -1)
+    {
+      if (firstmenu != -1 && firstxref != -1)
+	{
+	  if (((dir == 1) && (firstmenu < firstxref)) ||
+	      ((dir == -1) && (firstmenu > firstxref)))
+	    placement = firstmenu + 1;
+	  else
+	    placement = firstxref;
+	}
+      else if (firstmenu != -1)
+	placement = firstmenu + 1;
+      else
+	placement = firstxref;
+    }
+  window->point = placement;
+  window_adjust_pagetop (window);
+  window->flags |= W_UpdateWindow;
+}
+
+DECLARE_INFO_COMMAND (info_move_to_prev_xref,
+		      "Move to the previous cross reference")
+{
+  if (count < 0)
+    info_move_to_prev_xref (window, -count, key);
+  else
+    info_move_to_xref (window, count, key, -1);
+}
+
+DECLARE_INFO_COMMAND (info_move_to_next_xref,
+		      "Move to the next cross reference")
+{
+  if (count < 0)
+    info_move_to_next_xref (window, -count, key);
+  else
+    info_move_to_xref (window, count, key, 1);
+}
+
+/* Select the menu item or reference that appears on this line. */
+DECLARE_INFO_COMMAND (info_select_reference_this_line,
+		      "Select reference or menu item appearing on this line")
+{
+  char *line;
+  NODE *orig;
+
+  line = window->line_starts[window_line_of_point (window)];
+  orig = window->node;
+
+  /* If this line contains a menu item, select that one. */
+  if (strncmp ("* ", line, 2) == 0)
+    info_menu_or_ref_item (window, count, key, info_menu_of_node, 0);
+  else
+    info_menu_or_ref_item (window, count, key, info_xrefs_of_node, 0);
+}
+
+/* **************************************************************** */
+/*								    */
+/*		    Miscellaneous Info Commands			    */
+/*								    */
+/* **************************************************************** */
+
+/* What to do when C-g is pressed in a window. */
+DECLARE_INFO_COMMAND (info_abort_key, "Cancel current operation")
+{
+  /* If error printing doesn't oridinarily ring the bell, do it now,
+     since C-g always rings the bell.  Otherwise, let the error printer
+     do it. */
+  if (!info_error_rings_bell_p)
+    terminal_ring_bell ();
+  info_error ("Quit");
+
+  info_initialize_numeric_arg ();
+  info_clear_pending_input ();
+  info_last_executed_command = (VFunction *)NULL;
+}
+
+/* Move the cursor to the desired line of the window. */
+DECLARE_INFO_COMMAND (info_move_to_window_line,
+   "Move to the cursor to a specific line of the window")
+{
+  int line;
+
+  /* With no numeric argument of any kind, default to the center line. */
+  if (!info_explicit_arg && count == 1)
+    line = (window->height / 2) + window->pagetop;
+  else
+    {
+      if (count < 0)
+	line = (window->height + count) + window->pagetop;
+      else
+	line = window->pagetop + count;
+    }
+
+  /* If the line doesn't appear in this window, make it do so. */
+  if ((line - window->pagetop) >= window->height)
+    line = window->pagetop + (window->height - 1);
+
+  /* If the line is too small, make it fit. */
+  if (line < window->pagetop)
+    line = window->pagetop;
+
+  /* If the selected line is past the bottom of the node, force it back. */
+  if (line >= window->line_count)
+    line = window->line_count - 1;
+
+  window->point = (window->line_starts[line] - window->node->contents);
+}
+
+/* Clear the screen and redraw its contents.  Given a numeric argument,
+   move the line the cursor is on to the COUNT'th line of the window. */
+DECLARE_INFO_COMMAND (info_redraw_display, "Redraw the display")
+{
+  if ((!info_explicit_arg && count == 1) || echo_area_is_active)
+    {
+      terminal_clear_screen ();
+      display_clear_display (the_display);
+      window_mark_chain (windows, W_UpdateWindow);
+      display_update_display (windows);
+    }
+  else
+    {
+      int desired_line, point_line;
+      int new_pagetop;
+
+      point_line = window_line_of_point (window) - window->pagetop;
+
+      if (count < 0)
+	desired_line = window->height + count;
+      else
+	desired_line = count;
+
+      if (desired_line < 0)
+	desired_line = 0;
+
+      if (desired_line >= window->height)
+	desired_line = window->height - 1;
+
+      if (desired_line == point_line)
+	return;
+
+      new_pagetop = window->pagetop + (point_line - desired_line);
+
+      set_window_pagetop (window, new_pagetop);
+    }
+}
+/* This command does nothing.  It is the fact that a key is bound to it
+   that has meaning.  See the code at the top of info_session (). */
+DECLARE_INFO_COMMAND (info_quit, "Quit using Info")
+{}
+
+
+/* **************************************************************** */
+/*								    */
+/*		 Reading Keys and Dispatching on Them		    */
+/*								    */
+/* **************************************************************** */
+
+/* Declaration only.  Special cased in info_dispatch_on_key (). */
+DECLARE_INFO_COMMAND (info_do_lowercase_version, "")
+{}
+
+static void
+dispatch_error (keyseq)
+     char *keyseq;
+{
+  char *rep;
+
+  rep = pretty_keyseq (keyseq);
+
+  if (!echo_area_is_active)
+    info_error ("Unknown command (%s).", rep);
+  else
+    {
+      char *temp;
+
+      temp = (char *)xmalloc (1 + strlen (rep) + strlen ("\"\" is invalid"));
+
+      sprintf (temp, "\"%s\" is invalid", rep);
+      terminal_ring_bell ();
+      inform_in_echo_area (temp);
+      free (temp);
+    }
+}
+
+/* Keeping track of key sequences. */
+static char *info_keyseq = (char *)NULL;
+static char keyseq_rep[100];
+static int info_keyseq_index = 0;
+static int info_keyseq_size = 0;
+static int info_keyseq_displayed_p = 0;
+
+/* Initialize the length of the current key sequence. */
+void
+initialize_keyseq ()
+{
+  info_keyseq_index = 0;
+  info_keyseq_displayed_p = 0;
+}
+
+/* Add CHARACTER to the current key sequence. */
+void
+add_char_to_keyseq (character)
+     char character;
+{
+  if (info_keyseq_index + 2 >= info_keyseq_size)
+    info_keyseq = (char *)xrealloc (info_keyseq, info_keyseq_size += 10);
+
+  info_keyseq[info_keyseq_index++] = character;
+  info_keyseq[info_keyseq_index] = '\0';
+}
+
+/* Return the pretty printable string which represents KEYSEQ. */
+char *
+pretty_keyseq (keyseq)
+     char *keyseq;
+{
+  register int i;
+
+  keyseq_rep[0] = '\0';
+
+  for (i = 0; keyseq[i]; i++)
+    {
+      sprintf (keyseq_rep + strlen (keyseq_rep), "%s%s",
+	       strlen (keyseq_rep) ? " " : "",
+	       pretty_keyname (keyseq[i]));
+    }
+
+  return (keyseq_rep);
+}
+
+/* Display the current value of info_keyseq.  If argument EXPECTING is
+   non-zero, input is expected to be read after the key sequence is
+   displayed, so add an additional prompting character to the sequence. */
+void
+display_info_keyseq (expecting_future_input)
+     int expecting_future_input;
+{
+  char *rep;
+
+  rep = pretty_keyseq (info_keyseq);
+  if (expecting_future_input)
+    strcat (rep, "-");
+
+  if (echo_area_is_active)
+    inform_in_echo_area (rep);
+  else
+    {
+      window_message_in_echo_area (rep);
+      display_cursor_at_point (active_window);
+    }
+  info_keyseq_displayed_p = 1;
+}
+
+/* Called by interactive commands to read a keystroke. */
+unsigned char
+info_get_another_input_char ()
+{
+  int ready = 0;
+
+  /* If there isn't any input currently available, then wait a
+     moment looking for input.  If we don't get it fast enough,
+     prompt a little bit with the current key sequence. */
+  if (!info_keyseq_displayed_p &&
+      !info_any_buffered_input_p () &&
+      !info_input_pending_p ())
+    {
+#if defined (FD_SET)
+      struct timeval timer;
+      fd_set readfds;
+
+      FD_ZERO (&readfds);
+      FD_SET (fileno (info_input_stream), &readfds);
+      timer.tv_sec = 1;
+      timer.tv_usec = 750;
+      ready = select (1, &readfds, (fd_set *)NULL, (fd_set *)NULL, &timer);
+#endif /* FD_SET */
+    }
+
+  if (!ready)
+    display_info_keyseq (1);
+
+  return (info_get_input_char ());
+}
+
+/* Do the command associated with KEY in MAP.  If the associated command is
+   really a keymap, then read another key, and dispatch into that map. */
+void
+info_dispatch_on_key (key, map)
+     unsigned char key;
+     Keymap map;
+{
+  if (Meta_p (key) && (!ISO_Latin_p || map[key].function != ea_insert))
+    {
+      if (map[ESC].type == ISKMAP)
+	{
+	  map = (Keymap)map[ESC].function;
+	  add_char_to_keyseq (ESC);
+	  key = UnMeta (key);
+	  info_dispatch_on_key (key, map);
+	}
+      else
+	{
+	  dispatch_error (info_keyseq);
+	}
+      return;
+    }
+
+  switch (map[key].type)
+    {
+    case ISFUNC:
+      {
+	VFunction *func;
+
+	func = map[key].function;
+	if (func != (VFunction *)NULL)
+	  {
+	    /* Special case info_do_lowercase_version (). */
+	    if (func == info_do_lowercase_version)
+	      {
+		info_dispatch_on_key (tolower (key), map);
+		return;
+	      }
+
+	    add_char_to_keyseq (key);
+
+	    if (info_keyseq_displayed_p)
+	      display_info_keyseq (0);
+
+	    {
+	      WINDOW *where;
+
+	      where = active_window;
+	      (*map[key].function)
+		(active_window, info_numeric_arg * info_numeric_arg_sign, key);
+
+	      /* If we have input pending, then the last command was a prefix
+		 command.  Don't change the value of the last function vars.
+		 Otherwise, remember the last command executed in the var
+		 appropriate to the window in which it was executed. */
+	      if (!info_input_pending_p ())
+		{
+		  if (where == the_echo_area)
+		    ea_last_executed_command = map[key].function;
+		  else
+		    info_last_executed_command = map[key].function;
+		}
+	    }
+	  }
+	else
+	  {
+	    add_char_to_keyseq (key);
+	    dispatch_error (info_keyseq);
+	    return;
+	  }
+      }
+      break;
+
+    case ISKMAP:
+      add_char_to_keyseq (key);
+      if (map[key].function != (VFunction *)NULL)
+	{
+	  unsigned char newkey;
+
+	  newkey = info_get_another_input_char ();
+	  info_dispatch_on_key (newkey, (Keymap)map[key].function);
+	}
+      else
+	{
+	  dispatch_error (info_keyseq);
+	  return;
+	}
+      break;
+    }
+}
+
+/* **************************************************************** */
+/*								    */
+/*			Numeric Arguments			    */
+/*								    */
+/* **************************************************************** */
+
+/* Handle C-u style numeric args, as well as M--, and M-digits. */
+
+/* Non-zero means that an explicit argument has been passed to this
+   command, as in C-u C-v. */
+int info_explicit_arg = 0;
+
+/* The sign of the numeric argument. */
+int info_numeric_arg_sign = 1;
+
+/* The value of the argument itself. */
+int info_numeric_arg = 1;
+
+/* Add the current digit to the argument in progress. */
+DECLARE_INFO_COMMAND (info_add_digit_to_numeric_arg,
+		      "Add this digit to the current numeric argument")
+{
+  info_numeric_arg_digit_loop (window, 0, key);
+}
+
+/* C-u, universal argument.  Multiply the current argument by 4.
+   Read a key.  If the key has nothing to do with arguments, then
+   dispatch on it.  If the key is the abort character then abort. */
+DECLARE_INFO_COMMAND (info_universal_argument,
+		      "Start (or multiply by 4) the current numeric argument")
+{
+  info_numeric_arg *= 4;
+  info_numeric_arg_digit_loop (window, 0, 0);
+}
+
+/* Create a default argument. */
+void
+info_initialize_numeric_arg ()
+{
+  info_numeric_arg = info_numeric_arg_sign = 1;
+  info_explicit_arg = 0;
+}
+
+DECLARE_INFO_COMMAND (info_numeric_arg_digit_loop, "")
+{
+  unsigned char pure_key;
+  Keymap keymap = window->keymap;
+
+  while (1)
+    {
+      if (key)
+	pure_key = key;
+      else
+	{
+	  if (display_was_interrupted_p && !info_any_buffered_input_p ())
+	    display_update_display (windows);
+
+	  if (active_window != the_echo_area)
+	    display_cursor_at_point (active_window);
+
+	  pure_key = key = info_get_another_input_char ();
+
+	  if (Meta_p (key))
+	    add_char_to_keyseq (ESC);
+
+	  add_char_to_keyseq (UnMeta (key));
+	}
+
+      if (Meta_p (key))
+	key = UnMeta (key);
+
+      if (keymap[key].type == ISFUNC &&
+	  keymap[key].function == info_universal_argument)
+	{
+	  info_numeric_arg *= 4;
+	  key = 0;
+	  continue;
+	}
+
+      if (isdigit (key))
+	{
+	  if (info_explicit_arg)
+	    info_numeric_arg = (info_numeric_arg * 10) + (key - '0');
+	  else
+	    info_numeric_arg = (key - '0');
+	  info_explicit_arg = 1;
+	}
+      else
+	{
+	  if (key == '-' && !info_explicit_arg)
+	    {
+	      info_numeric_arg_sign = -1;
+	      info_numeric_arg = 1;
+	    }
+	  else
+	    {
+	      info_keyseq_index--;
+	      info_dispatch_on_key (pure_key, keymap);
+	      return;
+	    }
+	}
+      key = 0;
+    }
+}
+
+/* **************************************************************** */
+/*								    */
+/*			Input Character Buffering       	    */
+/*								    */
+/* **************************************************************** */
+
+/* Character waiting to be read next. */
+static int pending_input_character = 0;
+
+/* How to make there be no pending input. */
+static void
+info_clear_pending_input ()
+{
+  pending_input_character = 0;
+}
+
+/* How to set the pending input character. */
+static void
+info_set_pending_input (key)
+     unsigned char key;
+{
+  pending_input_character = key;
+}
+
+/* How to see if there is any pending input. */
+unsigned char
+info_input_pending_p ()
+{
+  return (pending_input_character);
+}
+
+/* Largest number of characters that we can read in advance. */
+#define MAX_INFO_INPUT_BUFFERING 512
+
+static int pop_index = 0, push_index = 0;
+static unsigned char info_input_buffer[MAX_INFO_INPUT_BUFFERING];
+
+/* Add KEY to the buffer of characters to be read. */
+static void
+info_push_typeahead (key)
+     unsigned char key;
+{
+  /* Flush all pending input in the case of C-g pressed. */
+  if (key == Control ('g'))
+    {
+      push_index = pop_index;
+      info_set_pending_input (Control ('g'));
+    }
+  else
+    {
+      info_input_buffer[push_index++] = key;
+      if (push_index >= sizeof (info_input_buffer))
+	push_index = 0;
+    }
+}
+
+/* Return the amount of space available in INFO_INPUT_BUFFER for new chars. */
+static int
+info_input_buffer_space_available ()
+{
+  if (pop_index > push_index)
+    return (pop_index - push_index);
+  else
+    return (sizeof (info_input_buffer - (push_index - pop_index)));
+}
+
+/* Get a key from the buffer of characters to be read.
+   Return the key in KEY.
+   Result is non-zero if there was a key, or 0 if there wasn't. */
+static int
+info_get_key_from_typeahead (key)
+     unsigned char *key;
+{
+  if (push_index == pop_index)
+    return (0);
+
+  *key = info_input_buffer[pop_index++];
+
+  if (pop_index >= sizeof (info_input_buffer))
+    pop_index = 0;
+
+  return (1);
+}
+
+int
+info_any_buffered_input_p ()
+{
+  info_gather_typeahead ();
+  return (push_index != pop_index);
+}
+
+/* Push KEY into the *front* of the input buffer.  Returns non-zero if
+   successful, zero if there is no space left in the buffer. */
+static int
+info_replace_key_to_typeahead (key)
+     unsigned char key;
+{
+  if (info_input_buffer_space_available ())
+    {
+      pop_index--;
+      if (pop_index < 0)
+	pop_index = sizeof (info_input_buffer) - 1;
+      info_input_buffer[pop_index] = key;
+      return (1);
+    }
+  return (0);
+}
+
+/* If characters are available to be read, then read them and stuff them into
+   info_input_buffer.  Otherwise, do nothing. */
+void
+info_gather_typeahead ()
+{
+  int tty, space_avail;
+  long chars_avail;
+  unsigned char input[MAX_INFO_INPUT_BUFFERING];
+
+  tty = fileno (info_input_stream);
+  chars_avail = 0;
+
+  space_avail = info_input_buffer_space_available ();
+
+  /* If we can just find out how many characters there are to read, do so. */
+#if defined (FIONREAD)
+  {
+    ioctl (tty, FIONREAD, &chars_avail);
+
+    if (chars_avail > space_avail)
+      chars_avail = space_avail;
+
+    if (chars_avail)
+      read (tty, &input[0], chars_avail);
+  }
+#else /* !FIONREAD */
+#  if defined (O_NDELAY)
+  {
+    int flags;
+
+    flags = fcntl (tty, F_GETFL, 0);
+
+    fcntl (tty, F_SETFL, (flags | O_NDELAY));
+      chars_avail = read (tty, &input[0], space_avail);
+    fcntl (tty, F_SETFL, flags);
+
+    if (chars_avail == -1)
+      chars_avail = 0;
+  }
+#  endif /* O_NDELAY */
+#endif /* !FIONREAD */
+
+  /* Store the input characters just read into our input buffer. */
+  {
+    register int i;
+
+    for (i = 0; i < chars_avail; i++)
+      info_push_typeahead (input[i]);
+  }
+}
+
+/* How to read a single character. */
+unsigned char
+info_get_input_char ()
+{
+  unsigned char keystroke;
+
+  info_gather_typeahead ();
+
+  if (pending_input_character)
+    {
+      keystroke = pending_input_character;
+      pending_input_character = 0;
+    }
+  else if (info_get_key_from_typeahead (&keystroke) == 0)
+    {
+      int rawkey;
+
+      rawkey = getc (info_input_stream);
+      keystroke = rawkey;
+
+      if (rawkey == EOF)
+	{
+	  if (info_input_stream != stdin)
+	    {
+	      fclose (info_input_stream);
+	      info_input_stream = stdin;
+	      display_inhibited = 0;
+	      display_update_display (windows);
+	      display_cursor_at_point (active_window);
+	      rawkey = getc (info_input_stream);
+	      keystroke = rawkey;
+	    }
+
+	  if (rawkey == EOF)
+	    {
+	      terminal_unprep_terminal ();
+	      close_dribble_file ();
+	      exit (0);
+	    }
+	}
+    }
+
+  if (info_dribble_file)
+    dribble (keystroke);
+
+  return (keystroke);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/session.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,146 @@
+/* session.h -- Functions found in session.c. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_SESSION_H_)
+#define _SESSION_H_
+
+#include "general.h"
+#include "dribble.h"
+
+/* All commands that can be invoked from within info_session () receive
+   arguments in the same way.  This simple define declares the header
+   of a function named NAME, with associated documentation DOC.  The
+   documentation string is groveled out of the source files by the
+   utility program `builddoc', which is also responsible for making
+   the documentation/function-pointer maps. */
+#define DECLARE_INFO_COMMAND(name, doc) \
+void name (window, count, key) WINDOW *window; int count; unsigned char key;
+
+/* Variables found in session.h. */
+extern VFunction *info_last_executed_command;
+
+/* Variable controlling the garbage collection of files briefly visited
+   during searches.  Such files are normally gc'ed, unless they were
+   compressed to begin with.  If this variable is non-zero, it says
+   to gc even those file buffer contents which had to be uncompressed. */
+extern int gc_compressed_files;
+
+/* When non-zero, tiling takes place automatically when info_split_window
+   is called. */
+extern int auto_tiling_p;
+
+/* Variable controlling the behaviour of default scrolling when you are
+   already at the bottom of a node. */
+extern int info_scroll_behaviour;
+extern char *info_scroll_choices[];
+
+/* Values for info_scroll_behaviour. */
+#define IS_Continuous 0	/* Try to get first menu item, or failing that, the
+			   "Next:" pointer, or failing that, the "Up:" and
+			   "Next:" of the up. */
+#define IS_NextOnly   1 /* Try to get "Next:" menu item. */
+#define IS_PageOnly   2	/* Simply give up at the bottom of a node. */
+
+/* Utility functions found in session.c */
+extern void info_dispatch_on_key ();
+extern unsigned char info_get_input_char (), info_get_another_input_char ();
+extern unsigned char info_input_pending_p ();
+extern void remember_window_and_node (), set_remembered_pagetop_and_point ();
+extern void set_window_pagetop (), info_set_node_of_window ();
+extern char *pretty_keyseq ();
+extern void initialize_keyseq (), add_char_to_keyseq ();
+extern void info_gather_typeahead ();
+extern FILE_BUFFER *file_buffer_of_window ();
+extern long info_search_in_node (), info_target_search_node ();
+extern void info_select_reference ();
+extern int info_any_buffered_input_p ();
+extern void print_node ();
+extern void dump_node_to_file (), dump_nodes_to_file ();
+
+/* Do the physical deletion of WINDOW, and forget this window and
+   associated nodes. */
+extern void info_delete_window_internal ();
+
+/* Tell Info that input is coming from the file FILENAME. */
+extern void info_set_input_from_file ();
+
+#define return_if_control_g(val) \
+  do { \
+    info_gather_typeahead (); \
+    if (info_input_pending_p () == Control ('g')) \
+      return (val); \
+  } while (0)
+
+/* The names of the functions that run an info session. */
+
+/* Starting an info session. */
+extern void begin_multiple_window_info_session (), begin_info_session ();
+extern void begin_info_session_with_error (), info_session ();
+extern void info_read_and_dispatch ();
+
+/* Moving the point within a node. */
+extern void info_next_line (), info_prev_line ();
+extern void info_end_of_line (), info_beginning_of_line ();
+extern void info_forward_char (), info_backward_char ();
+extern void info_forward_word (), info_backward_word ();
+extern void info_beginning_of_node (), info_end_of_node ();
+extern void info_move_to_prev_xref (), info_move_to_next_xref ();
+
+/* Scrolling text within a window. */
+extern void info_scroll_forward (), info_scroll_backward ();
+extern void info_redraw_display (), info_toggle_wrap ();
+extern void info_move_to_window_line ();
+
+/* Manipulating multiple windows. */
+extern void info_split_window (), info_delete_window ();
+extern void info_keep_one_window (), info_grow_window ();
+extern void info_scroll_other_window (), info_tile_windows ();
+extern void info_next_window (), info_prev_window ();
+
+/* Selecting nodes. */
+extern void info_next_node (), info_prev_node (), info_up_node ();
+extern void info_last_node (), info_first_node (), info_history_node ();
+extern void info_goto_node (), info_top_node (), info_dir_node ();
+extern void info_global_next_node (), info_global_prev_node ();
+extern void info_kill_node (), info_view_file ();
+
+/* Selecting cross references. */
+extern void info_menu_digit (), info_menu_item (), info_xref_item ();
+extern void info_find_menu (), info_select_reference_this_line ();
+
+/* Hacking numeric arguments. */
+extern int info_explicit_arg, info_numeric_arg, info_numeric_arg_sign;
+
+extern void info_add_digit_to_numeric_arg (), info_universal_argument ();
+extern void info_initialize_numeric_arg (), info_numeric_arg_digit_loop ();
+
+/* Searching commands. */
+extern void info_search (), isearch_forward (), isearch_backward ();
+
+/* Dumping and printing nodes. */
+extern void info_print_node ();
+
+/* Miscellaneous commands. */
+extern void info_abort_key (), info_quit (), info_do_lowercase_version ();
+
+#endif /* _SESSION_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/signals.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,177 @@
+/* signals.c -- Install and maintain Info signal handlers. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include "signals.h"
+
+/* **************************************************************** */
+/*								    */
+/*		Pretending That We Have POSIX Signals		    */
+/*								    */
+/* **************************************************************** */
+
+#if !defined (_POSIX_VERSION)
+/* Perform OPERATION on NEWSET, perhaps leaving information in OLDSET. */
+static void
+sigprocmask (operation, newset, oldset)
+     int operation, *newset, *oldset;
+{
+  switch (operation)
+    {
+    case SIG_UNBLOCK:
+#if defined (HAVE_SIGSETMASK)
+      sigsetmask (sigblock (0) & ~(*newset));
+#endif /* HAVE_SIGSETMASK */
+      break;
+
+    case SIG_BLOCK:
+      *oldset = sigblock (*newset);
+      break;
+
+    case SIG_SETMASK:
+#if defined (HAVE_SIGSETMASK)
+      sigsetmask (*newset);
+#endif /* HAVE_SIGSETMASK */
+      break;
+
+    default:
+      abort ();
+    }
+}
+#endif /* !_POSIX_VERSION */
+
+/* **************************************************************** */
+/*								    */
+/*		    Signal Handling for Info			    */
+/*								    */
+/* **************************************************************** */
+
+typedef void SigHandlerType;
+typedef SigHandlerType SigHandler ();
+
+static SigHandlerType info_signal_handler ();
+static SigHandler *old_TSTP, *old_TTOU, *old_TTIN;
+static SigHandler *old_WINCH, *old_INT;
+
+void
+initialize_info_signal_handler ()
+{
+#if defined (SIGTSTP)
+  old_TSTP = (SigHandler *) signal (SIGTSTP, info_signal_handler);
+  old_TTOU = (SigHandler *) signal (SIGTTOU, info_signal_handler);
+  old_TTIN = (SigHandler *) signal (SIGTTIN, info_signal_handler);
+#endif /* SIGTSTP */
+
+#if defined (SIGWINCH)
+  old_WINCH = (SigHandler *) signal (SIGWINCH, info_signal_handler);
+#endif
+
+#if defined (SIGINT)
+  old_INT = (SigHandler *) signal (SIGINT, info_signal_handler);
+#endif
+}
+
+static void
+redisplay_after_signal ()
+{
+  terminal_clear_screen ();
+  display_clear_display (the_display);
+  window_mark_chain (windows, W_UpdateWindow);
+  display_update_display (windows);
+  display_cursor_at_point (active_window);
+  fflush (stdout);
+}
+
+static SigHandlerType
+info_signal_handler (sig)
+     int sig;
+{
+  SigHandler **old_signal_handler;
+
+  switch (sig)
+    {
+#if defined (SIGTSTP)
+    case SIGTSTP:
+    case SIGTTOU:
+    case SIGTTIN:
+#endif
+#if defined (SIGINT)
+    case SIGINT:
+#endif
+      {
+#if defined (SIGTSTP)
+	if (sig == SIGTSTP)
+	  old_signal_handler = &old_TSTP;
+	if (sig == SIGTTOU)
+	  old_signal_handler = &old_TTOU;
+	if (sig == SIGTTIN)
+	  old_signal_handler = &old_TTIN;
+#endif /* SIGTSTP */
+	if (sig == SIGINT)
+	  old_signal_handler = &old_INT;
+
+	/* For stop signals, restore the terminal IO, leave the cursor
+	   at the bottom of the window, and stop us. */
+	terminal_goto_xy (0, screenheight - 1);
+	terminal_clear_to_eol ();
+	fflush (stdout);
+	terminal_unprep_terminal ();
+	signal (sig, *old_signal_handler);
+ 	UNBLOCK_SIGNAL (sig);
+	kill (getpid (), sig);
+
+	/* The program is returning now.  Restore our signal handler,
+	   turn on terminal handling, redraw the screen, and place the
+	   cursor where it belongs. */
+	terminal_prep_terminal ();
+	*old_signal_handler = (SigHandler *) signal (sig, info_signal_handler);
+	redisplay_after_signal ();
+	fflush (stdout);
+      }
+      break;
+
+#if defined (SIGWINCH)
+    case SIGWINCH:
+      {
+	/* Turn off terminal IO, tell our parent that the window has changed,
+	   then reinitialize the terminal and rebuild our windows. */
+	old_signal_handler = &old_WINCH;
+	terminal_goto_xy (0, 0);
+	fflush (stdout);
+	terminal_unprep_terminal ();
+	signal (sig, *old_signal_handler);
+ 	UNBLOCK_SIGNAL (sig);
+	kill (getpid (), sig);
+
+	/* After our old signal handler returns... */
+	terminal_get_screen_size ();
+	terminal_prep_terminal ();
+	display_initialize_display (screenwidth, screenheight);
+	window_new_screen_size (screenwidth, screenheight, (VFunction *)NULL);
+	*old_signal_handler = (SigHandler *) signal (sig, info_signal_handler);
+	redisplay_after_signal ();
+      }
+      break;
+#endif /* SIGWINCH */
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/signals.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,85 @@
+/* signals.h -- Header to include system dependent signal definitions. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_SIGNALS_H_)
+#define _SIGNALS_H_
+
+#include <signal.h>
+
+#define HAVE_SIGSETMASK
+
+#if !defined (_POSIX_VERSION) && !defined (sigmask)
+#  define sigmask(x) (1 << ((x)-1))
+#endif /* !POSIX && !sigmask */
+
+#if !defined (_POSIX_VERSION)
+#  if !defined (SIG_BLOCK)
+#    define SIG_UNBLOCK 1
+#    define SIG_BLOCK   2
+#    define SIG_SETMASK 3
+#  endif /* SIG_BLOCK */
+
+/* Type of a signal set. */
+#  define sigset_t int
+
+/* Make SET have no signals in it. */
+#  define sigemptyset(set) (*(set) = (sigset_t)0x0)
+
+/* Make SET have the full range of signal specifications possible. */
+#  define sigfillset(set) (*(set) = (sigset_t)0xffffffffff)
+
+/* Add SIG to the contents of SET. */
+#  define sigaddset(set, sig) *(set) |= sigmask (sig)
+
+/* Delete SIG from the contents of SET. */
+#  define sigdelset(set, sig) *(set) &= ~(sigmask (sig))
+
+/* Tell if SET contains SIG. */
+#  define sigismember(set, sig) (*(set) & (sigmask (sig)))
+
+/* Suspend the process until the reception of one of the signals
+   not present in SET. */
+#  define sigsuspend(set) sigpause (*(set))
+#endif /* !_POSIX_VERSION */
+
+/* These definitions are used both in POSIX and non-POSIX implementations. */
+
+#define BLOCK_SIGNAL(sig) \
+  do { \
+    sigset_t nvar, ovar; \
+    sigemptyset (&nvar); \
+    sigemptyset (&ovar); \
+    sigaddset (&nvar, sig); \
+    sigprocmask (SIG_BLOCK, &nvar, &ovar); \
+  } while (0)
+
+#define UNBLOCK_SIGNAL(sig) \
+  do { \
+    sigset_t nvar, ovar; \
+    sigemptyset (&ovar); \
+    sigemptyset (&nvar); \
+    sigaddset (&nvar, sig); \
+    sigprocmask (SIG_UNBLOCK, &nvar, &ovar); \
+  } while (0)
+
+#endif /* !_SIGNALS_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/termdep.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,64 @@
+/* termdep.h -- System things that terminal.c depends on. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if defined (HAVE_SYS_FCNTL_H)
+#include <sys/fcntl.h>
+#else
+#include <fcntl.h>
+#endif /* !HAVE_SYS_FCNTL_H */
+
+#if defined (HAVE_TERMIO_H)
+#include <termio.h>
+#include <string.h>
+#if defined (HAVE_SYS_PTEM_H)
+#if !defined (M_XENIX)
+#include <sys/stream.h>
+#include <sys/ptem.h>
+#undef TIOCGETC
+#else /* M_XENIX */
+#define tchars tc
+#endif /* M_XENIX */
+#endif /* HAVE_SYS_PTEM_H */
+#else /* !HAVE_TERMIO_H */
+#include <sys/file.h>
+#include <sgtty.h>
+#include <strings.h>
+#endif /* !HAVE_TERMIO_H */
+
+#if defined (HAVE_SYS_TTOLD_H)
+#include <sys/ttold.h>
+#endif /* HAVE_SYS_TTOLD_H */
+
+#if !defined (HAVE_RINDEX)
+#undef index
+#undef rindex
+#define index strchr
+#define rindex strrchr
+#endif
+
+#if !defined (HAVE_BCOPY)
+#undef bcopy
+#define bcopy(source, dest, count) memcpy(dest, source, count)
+#endif
+
+/* eof */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/terminal.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,745 @@
+/* terminal.c -- How to handle the physical terminal for Info. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   This file has appeared in prior works by the Free Software Foundation;
+   thus it carries copyright dates from 1988 through 1993.
+
+   Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993 Free Software
+   Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "terminal.h"
+#include "termdep.h"
+
+extern void *xmalloc (), *xrealloc ();
+
+/* The Unix termcap interface code. */
+
+extern int tgetnum (), tgetflag (), tgetent ();
+extern char *tgetstr (), *tgoto ();
+extern char *getenv ();
+extern void tputs ();
+
+/* Function "hooks".  If you make one of these point to a function, that
+   function is called when appropriate instead of its namesake.  Your
+   function is called with exactly the same arguments that were passed
+   to the namesake function. */
+VFunction *terminal_begin_inverse_hook = (VFunction *)NULL;
+VFunction *terminal_end_inverse_hook = (VFunction *)NULL;
+VFunction *terminal_prep_terminal_hook = (VFunction *)NULL;
+VFunction *terminal_unprep_terminal_hook = (VFunction *)NULL;
+VFunction *terminal_up_line_hook = (VFunction *)NULL;
+VFunction *terminal_down_line_hook = (VFunction *)NULL;
+VFunction *terminal_clear_screen_hook = (VFunction *)NULL;
+VFunction *terminal_clear_to_eol_hook = (VFunction *)NULL;
+VFunction *terminal_get_screen_size_hook = (VFunction *)NULL;
+VFunction *terminal_goto_xy_hook = (VFunction *)NULL;
+VFunction *terminal_initialize_terminal_hook = (VFunction *)NULL;
+VFunction *terminal_new_terminal_hook = (VFunction *)NULL;
+VFunction *terminal_put_text_hook = (VFunction *)NULL;
+VFunction *terminal_ring_bell_hook = (VFunction *)NULL;
+VFunction *terminal_write_chars_hook = (VFunction *)NULL;
+VFunction *terminal_scroll_terminal_hook = (VFunction *)NULL;
+
+/* **************************************************************** */
+/*								    */
+/*			Terminal and Termcap			    */
+/*								    */
+/* **************************************************************** */
+
+/* On Solaris2, sys/types.h #includes sys/reg.h, which #defines PC.
+   Unfortunately, PC is a global variable used by the termcap library. */
+#undef PC
+
+/* TERMCAP requires these variables, whether we access them or not. */
+char PC;
+char *BC, *UP;
+short ospeed;
+
+/* A buffer which holds onto the current terminal description, and a pointer
+   used to float within it. */
+static char *term_buffer = (char *)NULL;
+static char *term_string_buffer = (char *)NULL;
+
+/* Some strings to control terminal actions.  These are output by tputs (). */
+static char *term_goto, *term_clreol, *term_cr, *term_clrpag;
+static char *term_begin_use, *term_end_use;
+static char *term_AL, *term_DL, *term_al, *term_dl;
+
+/* How to go up a line. */
+static char *term_up;
+
+/* How to go down a line. */
+static char *term_dn;
+
+/* An audible bell, if the terminal can be made to make noise. */
+static char *audible_bell;
+
+/* A visible bell, if the terminal can be made to flash the screen. */
+static char *visible_bell;
+
+/* The string to write to turn on the meta key, if this term has one. */
+static char *term_mm;
+
+/* The string to write to turn off the meta key, if this term has one. */
+static char *term_mo;
+
+/* The string to turn on inverse mode, if this term has one. */
+static char *term_invbeg;
+
+/* The string to turn off inverse mode, if this term has one. */
+static char *term_invend;
+
+static void
+output_character_function (c)
+     int c;
+{
+  putc (c, stdout);
+}
+
+/* Macro to send STRING to the terminal. */
+#define send_to_terminal(string) \
+  do { \
+    if (string) \
+      tputs (string, 1, output_character_function); \
+     } while (0)
+
+/* Tell the terminal that we will be doing cursor addressable motion. */
+static void
+terminal_begin_using_terminal ()
+{
+  send_to_terminal (term_begin_use);
+}
+
+/* Tell the terminal that we will not be doing any more cursor addressable
+   motion. */
+static void
+terminal_end_using_terminal ()
+{
+  send_to_terminal (term_end_use);
+}
+
+/* **************************************************************** */
+/*								    */
+/*		     Necessary Terminal Functions		    */
+/*								    */
+/* **************************************************************** */
+
+/* The functions and variables on this page implement the user visible
+   portion of the terminal interface. */
+
+/* The width and height of the terminal. */
+int screenwidth, screenheight;
+
+/* Non-zero means this terminal can't really do anything. */
+int terminal_is_dumb_p = 0;
+
+/* Non-zero means that this terminal has a meta key. */
+int terminal_has_meta_p = 0;
+
+/* Non-zero means that this terminal can produce a visible bell. */
+int terminal_has_visible_bell_p = 0;
+
+/* Non-zero means to use that visible bell if at all possible. */
+int terminal_use_visible_bell_p = 0;
+
+/* Non-zero means that the terminal can do scrolling. */
+int terminal_can_scroll = 0;
+
+/* The key sequences output by the arrow keys, if this terminal has any. */
+char *term_ku, *term_kd, *term_kr, *term_kl;
+
+/* Move the cursor to the terminal location of X and Y. */
+void
+terminal_goto_xy (x, y)
+     int x, y;
+{
+  if (terminal_goto_xy_hook)
+    (*terminal_goto_xy_hook) (x, y);
+  else
+    {
+      if (term_goto)
+	tputs (tgoto (term_goto, x, y), 1, output_character_function);
+    }
+}
+
+/* Print STRING to the terminal at the current position. */
+void
+terminal_put_text (string)
+     char *string;
+{
+  if (terminal_put_text_hook)
+    (*terminal_put_text_hook) (string);
+  else
+    {
+      printf ("%s", string);
+    }
+}
+
+/* Print NCHARS from STRING to the terminal at the current position. */
+void
+terminal_write_chars (string, nchars)
+     char *string;
+     int nchars;
+{
+  if (terminal_write_chars_hook)
+    (*terminal_write_chars_hook) (string, nchars);
+  else
+    {
+      if (nchars)
+	fwrite (string, 1, nchars, stdout);
+    }
+}
+
+/* Clear from the current position of the cursor to the end of the line. */
+void
+terminal_clear_to_eol ()
+{
+  if (terminal_clear_to_eol_hook)
+    (*terminal_clear_to_eol_hook) ();
+  else
+    {
+      send_to_terminal (term_clreol);
+    }
+}
+
+/* Clear the entire terminal screen. */
+void
+terminal_clear_screen ()
+{
+  if (terminal_clear_screen_hook)
+    (*terminal_clear_screen_hook) ();
+  else
+    {
+      send_to_terminal (term_clrpag);
+    }
+}
+
+/* Move the cursor up one line. */
+void
+terminal_up_line ()
+{
+  if (terminal_up_line_hook)
+    (*terminal_up_line_hook) ();
+  else
+    {
+      send_to_terminal (term_up);
+    }
+}
+
+/* Move the cursor down one line. */
+void
+terminal_down_line ()
+{
+  if (terminal_down_line_hook)
+    (*terminal_down_line_hook) ();
+  else
+    {
+      send_to_terminal (term_dn);
+    }
+}
+
+/* Turn on reverse video if possible. */
+void
+terminal_begin_inverse ()
+{
+  if (terminal_begin_inverse_hook)
+    (*terminal_begin_inverse_hook) ();
+  else
+    {
+      send_to_terminal (term_invbeg);
+    }
+}
+
+/* Turn off reverse video if possible. */
+void
+terminal_end_inverse ()
+{
+  if (terminal_end_inverse_hook)
+    (*terminal_end_inverse_hook) ();
+  else
+    {
+      send_to_terminal (term_invend);
+    }
+}
+
+/* Ring the terminal bell.  The bell is run visibly if it both has one and
+   terminal_use_visible_bell_p is non-zero. */
+void
+terminal_ring_bell ()
+{
+  if (terminal_ring_bell_hook)
+    (*terminal_ring_bell_hook) ();
+  else
+    {
+      if (terminal_has_visible_bell_p && terminal_use_visible_bell_p)
+	send_to_terminal (visible_bell);
+      else
+	send_to_terminal (audible_bell);
+    }
+}
+
+/* At the line START, delete COUNT lines from the terminal display. */
+static void
+terminal_delete_lines (start, count)
+     int start, count;
+{
+  int lines;
+
+  /* Normalize arguments. */
+  if (start < 0)
+    start = 0;
+
+  lines = screenheight - start;
+  terminal_goto_xy (0, start);
+  if (term_DL)
+    tputs (tgoto (term_DL, 0, count), lines, output_character_function);
+  else
+    {
+      while (count--)
+	tputs (term_dl, lines, output_character_function);
+    }
+
+  fflush (stdout);
+}
+
+/* At the line START, insert COUNT lines in the terminal display. */
+static void
+terminal_insert_lines (start, count)
+     int start, count;
+{
+  int lines;
+
+  /* Normalize arguments. */
+  if (start < 0)
+    start = 0;
+
+  lines = screenheight - start;
+  terminal_goto_xy (0, start);
+
+  if (term_AL)
+    tputs (tgoto (term_AL, 0, count), lines, output_character_function);
+  else
+    {
+      while (count--)
+	tputs (term_al, lines, output_character_function);
+    }
+
+  fflush (stdout);
+}
+
+/* Scroll an area of the terminal, starting with the region from START
+   to END, AMOUNT lines.  If AMOUNT is negative, the lines are scrolled
+   towards the top of the screen, else they are scrolled towards the
+   bottom of the screen. */
+void
+terminal_scroll_terminal (start, end, amount)
+     int start, end, amount;
+{
+  if (!terminal_can_scroll)
+    return;
+
+  /* Any scrolling at all? */
+  if (amount == 0)
+    return;
+
+  if (terminal_scroll_terminal_hook)
+    (*terminal_scroll_terminal_hook) (start, end, amount);
+  else
+    {
+      /* If we are scrolling down, delete AMOUNT lines at END.  Then insert
+	 AMOUNT lines at START. */
+      if (amount > 0)
+	{
+	  terminal_delete_lines (end, amount);
+	  terminal_insert_lines (start, amount);
+	}
+
+      /* If we are scrolling up, delete AMOUNT lines before START.  This
+	 actually does the upwards scroll.  Then, insert AMOUNT lines
+	 after the already scrolled region (i.e., END - AMOUNT). */
+      if (amount < 0)
+	{
+	  int abs_amount = -amount;
+	  terminal_delete_lines (start - abs_amount, abs_amount);
+	  terminal_insert_lines (end - abs_amount, abs_amount);
+	}
+    }
+}
+
+/* Re-initialize the terminal considering that the TERM/TERMCAP variable
+   has changed. */
+void
+terminal_new_terminal (terminal_name)
+     char *terminal_name;
+{
+  if (terminal_new_terminal_hook)
+    (*terminal_new_terminal_hook) (terminal_name);
+  else
+    {
+      terminal_initialize_terminal (terminal_name);
+    }
+}
+
+/* Set the global variables SCREENWIDTH and SCREENHEIGHT. */
+void
+terminal_get_screen_size ()
+{
+  if (terminal_get_screen_size_hook)
+    (*terminal_get_screen_size_hook) ();
+  else
+    {
+      screenwidth = screenheight = 0;
+
+#if defined (TIOCGWINSZ)
+      {
+	struct winsize window_size;
+
+	if (ioctl (fileno (stdout), TIOCGWINSZ, &window_size) == 0)
+	  {
+	    screenwidth = (int) window_size.ws_col;
+	    screenheight = (int) window_size.ws_row;
+	  }
+      }
+#endif				/* TIOCGWINSZ */
+
+      /* Environment variable COLUMNS overrides setting of "co". */
+      if (screenwidth <= 0)
+	{
+	  char *sw = getenv ("COLUMNS");
+
+	  if (sw)
+	    screenwidth = atoi (sw);
+
+	  if (screenwidth <= 0)
+	    screenwidth = tgetnum ("co");
+	}
+
+      /* Environment variable LINES overrides setting of "li". */
+      if (screenheight <= 0)
+	{
+	  char *sh = getenv ("LINES");
+
+	  if (sh)
+	    screenheight = atoi (sh);
+
+	  if (screenheight <= 0)
+	    screenheight = tgetnum ("li");
+	}
+
+      /* If all else fails, default to 80x24 terminal. */
+      if (screenwidth <= 0)
+	screenwidth = 80;
+
+      if (screenheight <= 0)
+	screenheight = 24;
+    }
+}
+
+/* Initialize the terminal which is known as TERMINAL_NAME.  If this terminal
+   doesn't have cursor addressability, TERMINAL_IS_DUMB_P becomes non-zero.
+   The variables SCREENHEIGHT and SCREENWIDTH are set to the dimensions that
+   this terminal actually has.  The variable TERMINAL_HAS_META_P becomes non-
+   zero if this terminal supports a Meta key.  Finally, the terminal screen is
+   cleared. */
+void
+terminal_initialize_terminal (terminal_name)
+     char *terminal_name;
+{
+  char *term, *buffer;
+
+  terminal_is_dumb_p = 0;
+
+  if (terminal_initialize_terminal_hook)
+    {
+      (*terminal_initialize_terminal_hook) (terminal_name);
+      return;
+    }
+
+  term = terminal_name ? terminal_name : getenv ("TERM");
+
+  if (!term_string_buffer)
+    term_string_buffer = (char *)xmalloc (2048);
+
+  if (!term_buffer)
+    term_buffer = (char *)xmalloc (2048);
+
+  buffer = term_string_buffer;
+
+  term_clrpag = term_cr = term_clreol = (char *)NULL;
+
+  if (!term)
+    term = "dumb";
+
+  if (tgetent (term_buffer, term) <= 0)
+    {
+      terminal_is_dumb_p = 1;
+      screenwidth = 80;
+      screenheight = 24;
+      term_cr = "\r";
+      term_up = term_dn = audible_bell = visible_bell = (char *)NULL;
+      term_ku = term_kd = term_kl = term_kr = (char *)NULL;
+      return;
+    }
+
+  BC = tgetstr ("pc", &buffer);
+  PC = BC ? *BC : 0;
+
+#if defined (TIOCGETP)
+  {
+    struct sgttyb sg;
+
+    if (ioctl (fileno (stdout), TIOCGETP, &sg) != -1)
+      ospeed = sg.sg_ospeed;
+    else
+      ospeed = B9600;
+  }
+#else
+  ospeed = B9600;
+#endif				/* !TIOCGETP */
+
+  term_cr = tgetstr ("cr", &buffer);
+  term_clreol = tgetstr ("ce", &buffer);
+  term_clrpag = tgetstr ("cl", &buffer);
+  term_goto = tgetstr ("cm", &buffer);
+
+  /* Find out about this terminals scrolling capability. */
+  term_AL = tgetstr ("AL", &buffer);
+  term_DL = tgetstr ("DL", &buffer);
+  term_al = tgetstr ("al", &buffer);
+  term_dl = tgetstr ("dl", &buffer);
+
+  terminal_can_scroll = ((term_AL || term_al) && (term_DL || term_dl));
+
+  term_invbeg = tgetstr ("mr", &buffer);
+  if (term_invbeg)
+    term_invend = tgetstr ("me", &buffer);
+  else
+    term_invend = (char *)NULL;
+
+  if (!term_cr)
+    term_cr =  "\r";
+
+  terminal_get_screen_size ();
+
+  term_up = tgetstr ("up", &buffer);
+  term_dn = tgetstr ("dn", &buffer);
+  visible_bell = tgetstr ("vb", &buffer);
+  terminal_has_visible_bell_p = (visible_bell != (char *)NULL);
+  audible_bell = tgetstr ("bl", &buffer);
+  if (!audible_bell)
+    audible_bell = "\007";
+  term_begin_use = tgetstr ("ti", &buffer);
+  term_end_use = tgetstr ("te", &buffer);
+
+  /* Check to see if this terminal has a meta key. */
+  terminal_has_meta_p = (tgetflag ("km") || tgetflag ("MT"));
+  if (terminal_has_meta_p)
+    {
+      term_mm = tgetstr ("mm", &buffer);
+      term_mo = tgetstr ("mo", &buffer);
+    }
+  else
+    {
+      term_mm = (char *)NULL;
+      term_mo = (char *)NULL;
+    }
+
+  /* Attempt to find the arrow keys.  */
+  term_ku = tgetstr ("ku", &buffer);
+  term_kd = tgetstr ("kd", &buffer);
+  term_kr = tgetstr ("kr", &buffer);
+  term_kl = tgetstr ("kl", &buffer);
+
+  /* If this terminal is not cursor addressable, then it is really dumb. */
+  if (!term_goto)
+    terminal_is_dumb_p = 1;
+
+  terminal_begin_using_terminal ();
+}
+
+/* **************************************************************** */
+/*								    */
+/*		 How to Read Characters From the Terminal	    */
+/*								    */
+/* **************************************************************** */
+
+#if defined (TIOCGETC)
+/* A buffer containing the terminal interrupt characters upon entry
+   to Info. */
+struct tchars original_tchars;
+#endif
+
+#if defined (TIOCGLTC)
+/* A buffer containing the local terminal mode characters upon entry
+   to Info. */
+struct ltchars original_ltchars;
+#endif
+
+#if defined (HAVE_TERMIO_H)
+/* A buffer containing the terminal mode flags upon entry to info. */
+struct termio original_termio, ttybuff;
+#else /* !HAVE_TERMIO_H */
+/* Buffers containing the terminal mode flags upon entry to info. */
+int original_tty_flags = 0;
+int original_lmode;
+struct sgttyb ttybuff;
+#endif /* !HAVE_TERMIO_H */
+
+/* Prepare to start using the terminal to read characters singly. */
+void
+terminal_prep_terminal ()
+{
+  int tty;
+
+  if (terminal_prep_terminal_hook)
+    {
+      (*terminal_prep_terminal_hook) ();
+      return;
+    }
+
+  tty = fileno (stdin);
+
+#if defined (HAVE_TERMIO_H)
+  ioctl (tty, TCGETA, &original_termio);
+  ioctl (tty, TCGETA, &ttybuff);
+  ttybuff.c_iflag &= (~ISTRIP & ~INLCR & ~IGNCR & ~ICRNL & ~IXON);
+  ttybuff.c_oflag &= (~ONLCR & ~OCRNL);
+  ttybuff.c_lflag &= (~ICANON & ~ECHO);
+
+  ttybuff.c_cc[VMIN] = 1;
+  ttybuff.c_cc[VTIME] = 0;
+
+  if (ttybuff.c_cc[VINTR] = '\177')
+    ttybuff.c_cc[VINTR] = -1;
+
+  if (ttybuff.c_cc[VQUIT] = '\177')
+    ttybuff.c_cc[VQUIT] = -1;
+
+  ioctl (tty, TCSETA, &ttybuff);
+
+#else /* !HAVE_TERMIO_H */
+
+  ioctl (tty, TIOCGETP, &ttybuff);
+
+  if (!original_tty_flags)
+    original_tty_flags = ttybuff.sg_flags;
+
+  /* Make this terminal pass 8 bits around while we are using it. */
+#if defined (PASS8)
+  ttybuff.sg_flags |= PASS8;
+#endif /* PASS8 */
+
+#if defined (TIOCLGET) && defined (LPASS8)
+  {
+    int flags;
+    ioctl (tty, TIOCLGET, &flags);
+    original_lmode = flags;
+    flags |= LPASS8;
+    ioctl (tty, TIOCLSET, &flags);
+  }
+#endif /* TIOCLGET && LPASS8 */
+
+#if defined (TIOCGETC)
+  {
+    struct tchars temp;
+
+    ioctl (tty, TIOCGETC, &original_tchars);
+    temp = original_tchars;
+
+    /* C-s and C-q. */
+    temp.t_startc = temp.t_stopc = -1;
+
+    /* Often set to C-d. */
+    temp.t_eofc = -1;
+
+    /* If the a quit or interrupt character conflicts with one of our
+       commands, then make it go away. */
+    if (temp.t_intrc == '\177')
+      temp.t_intrc = -1;
+
+    if (temp.t_quitc == '\177')
+      temp.t_quitc = -1;
+
+    ioctl (tty, TIOCSETC, &temp);
+  }
+#endif /* TIOCGETC */
+
+#if defined (TIOCGLTC)
+  {
+    struct ltchars temp;
+
+    ioctl (tty, TIOCGLTC, &original_ltchars);
+    temp = original_ltchars;
+
+    /* Make the interrupt keys go away.  Just enough to make people happy. */
+    temp.t_lnextc = -1;		/* C-v. */
+    temp.t_dsuspc = -1;		/* C-y. */
+    temp.t_flushc = -1;		/* C-o. */
+    ioctl (tty, TIOCSLTC, &temp);
+  }
+#endif /* TIOCGLTC */
+
+  ttybuff.sg_flags &= ~ECHO;
+  ttybuff.sg_flags |= CBREAK;
+  ioctl (tty, TIOCSETN, &ttybuff);
+#endif /* !HAVE_TERMIO_H */
+}
+
+/* Restore the tty settings back to what they were before we started using
+   this terminal. */
+void
+terminal_unprep_terminal ()
+{
+  int tty;
+
+  if (terminal_unprep_terminal_hook)
+    {
+      (*terminal_unprep_terminal_hook) ();
+      return;
+    }
+
+  tty = fileno (stdin);
+
+#if defined (HAVE_TERMIO_H)
+  ioctl (tty, TCSETA, &original_termio);
+#else /* !HAVE_TERMIO_H */
+  ioctl (tty, TIOCGETP, &ttybuff);
+  ttybuff.sg_flags = original_tty_flags;
+  ioctl (tty, TIOCSETN, &ttybuff);
+
+#if defined (TIOCGETC)
+  ioctl (tty, TIOCSETC, &original_tchars);
+#endif /* TIOCGETC */
+
+#if defined (TIOCGLTC)
+  ioctl (tty, TIOCSLTC, &original_ltchars);
+#endif /* TIOCGLTC */
+
+#if defined (TIOCLGET) && defined (LPASS8)
+  ioctl (tty, TIOCLSET, &original_lmode);
+#endif /* TIOCLGET && LPASS8 */
+
+#endif /* !HAVE_TERMIO_H */
+  terminal_end_using_terminal ();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/terminal.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,126 @@
+/* terminal.h -- The external interface to terminal I/O. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_TERMINAL_H_)
+#define _TERMINAL_H_
+
+/* We use the following data type to talk about pointers to functions. */
+#if !defined (__FUNCTION_DEF)
+#  define __FUNCTION_DEF
+typedef int Function ();
+typedef void VFunction ();
+#endif /* _FUNCTION_DEF */
+
+/* For almost every function externally visible from terminal.c, there is
+   a corresponding "hook" function which can be bound in order to replace
+   the functionality of the one found in terminal.c.  This is how we go
+   about implemented X window display. */
+
+/* The width and height of the terminal. */
+extern int screenwidth, screenheight;
+
+/* Non-zero means this terminal can't really do anything. */
+extern int terminal_is_dumb_p;
+
+/* Non-zero means that this terminal has a meta key. */
+extern int terminal_has_meta_p;
+
+/* Non-zero means that this terminal can produce a visible bell. */
+extern int terminal_has_visible_bell_p;
+
+/* Non-zero means to use that visible bell if at all possible. */
+extern int terminal_use_visible_bell_p;
+
+/* Non-zero means that this terminal can scroll lines up and down. */
+extern int terminal_can_scroll;
+
+/* Initialize the terminal which is known as TERMINAL_NAME.  If this terminal
+   doesn't have cursor addressability, TERMINAL_IS_DUMB_P becomes non-zero.
+   The variables SCREENHEIGHT and SCREENWIDTH are set to the dimensions that
+   this terminal actually has.  The variable TERMINAL_HAS_META_P becomes non-
+   zero if this terminal supports a Meta key. */
+extern void terminal_initialize_terminal ();
+extern VFunction *terminal_initialize_terminal_hook;
+
+/* Return the current screen width and height in the variables
+   SCREENWIDTH and SCREENHEIGHT. */
+extern void terminal_get_screen_size ();
+extern VFunction *terminal_get_screen_size_hook;
+
+/* Save and restore tty settings. */
+extern void terminal_prep_terminal (), terminal_unprep_terminal ();
+extern VFunction *terminal_prep_terminal_hook, *terminal_unprep_terminal_hook;
+
+/* Re-initialize the terminal to TERMINAL_NAME. */
+extern void terminal_new_terminal ();
+extern VFunction *terminal_new_terminal_hook;
+
+/* Move the cursor to the terminal location of X and Y. */
+extern void terminal_goto_xy ();
+extern VFunction *terminal_goto_xy_hook;
+
+/* Print STRING to the terminal at the current position. */
+extern void terminal_put_text ();
+extern VFunction *terminal_put_text_hook;
+
+/* Print NCHARS from STRING to the terminal at the current position. */
+extern void terminal_write_chars ();
+extern VFunction *terminal_write_chars_hook;
+
+/* Clear from the current position of the cursor to the end of the line. */
+extern void terminal_clear_to_eol ();
+extern VFunction *terminal_clear_to_eol_hook;
+
+/* Clear the entire terminal screen. */
+extern void terminal_clear_screen ();
+extern VFunction *terminal_clear_screen_hook;
+
+/* Move the cursor up one line. */
+extern void terminal_up_line ();
+extern VFunction *terminal_up_line_hook;
+
+/* Move the cursor down one line. */
+extern void terminal_down_line ();
+extern VFunction *terminal_down_line_hook;
+
+/* Turn on reverse video if possible. */
+extern void terminal_begin_inverse ();
+extern VFunction *terminal_begin_inverse_hook;
+
+/* Turn off reverse video if possible. */
+extern void terminal_end_inverse ();
+extern VFunction *terminal_end_inverse_hook;
+
+/* Scroll an area of the terminal, starting with the region from START
+   to END, AMOUNT lines.  If AMOUNT is negative, the lines are scrolled
+   towards the top of the screen, else they are scrolled towards the
+   bottom of the screen. */
+extern void terminal_scroll_terminal ();
+extern VFunction *terminal_scroll_terminal_hook;
+
+/* Ring the terminal bell.  The bell is run visibly if it both has one and
+   terminal_use_visible_bell_p is non-zero. */
+extern void terminal_ring_bell ();
+extern VFunction *terminal_ring_bell_hook;
+
+#endif /* !_TERMINAL_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/tilde.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,379 @@
+/* tilde.c -- Tilde expansion code (~/foo := $HOME/foo). */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   This file has appeared in prior works by the Free Software Foundation;
+   thus it carries copyright dates from 1988 through 1993.
+
+   Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993 Free Software
+   Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if defined (__GNUC__)
+#  define alloca __builtin_alloca
+#else /* !__GNUC__ */
+#  if defined (_AIX)
+ #pragma alloca
+#  else /* !_AIX */
+#    if defined (HAVE_ALLOCA_H)
+#      include <alloca.h>
+#    endif /* HAVE_ALLOCA_H */
+#  endif /* !AIX */
+#endif /* !__GNUC__ */
+
+#include "tilde.h"
+#include <pwd.h>
+
+#if !defined (savestring)
+#define savestring(x) (char *)strcpy (xmalloc (1 + strlen (x)), (x))
+#endif /* !savestring */
+
+#if !defined (NULL)
+#  define NULL 0x0
+#endif
+
+#if defined (TEST) || defined (STATIC_MALLOC)
+static char *xmalloc (), *xrealloc ();
+#else
+extern char *xmalloc (), *xrealloc ();
+#endif /* TEST || STATIC_MALLOC */
+
+/* The default value of tilde_additional_prefixes.  This is set to
+   whitespace preceding a tilde so that simple programs which do not
+   perform any word separation get desired behaviour. */
+static char *default_prefixes[] =
+  { " ~", "\t~", (char *)NULL };
+
+/* The default value of tilde_additional_suffixes.  This is set to
+   whitespace or newline so that simple programs which do not
+   perform any word separation get desired behaviour. */
+static char *default_suffixes[] =
+  { " ", "\n", (char *)NULL };
+
+/* If non-null, this contains the address of a function to call if the
+   standard meaning for expanding a tilde fails.  The function is called
+   with the text (sans tilde, as in "foo"), and returns a malloc()'ed string
+   which is the expansion, or a NULL pointer if there is no expansion. */
+Function *tilde_expansion_failure_hook = (Function *)NULL;
+
+/* When non-null, this is a NULL terminated array of strings which
+   are duplicates for a tilde prefix.  Bash uses this to expand
+   `=~' and `:~'. */
+char **tilde_additional_prefixes = default_prefixes;
+
+/* When non-null, this is a NULL terminated array of strings which match
+   the end of a username, instead of just "/".  Bash sets this to
+   `:' and `=~'. */
+char **tilde_additional_suffixes = default_suffixes;
+
+/* Find the start of a tilde expansion in STRING, and return the index of
+   the tilde which starts the expansion.  Place the length of the text
+   which identified this tilde starter in LEN, excluding the tilde itself. */
+static int
+tilde_find_prefix (string, len)
+     char *string;
+     int *len;
+{
+  register int i, j, string_len;
+  register char **prefixes = tilde_additional_prefixes;
+
+  string_len = strlen (string);
+  *len = 0;
+
+  if (!*string || *string == '~')
+    return (0);
+
+  if (prefixes)
+    {
+      for (i = 0; i < string_len; i++)
+	{
+	  for (j = 0; prefixes[j]; j++)
+	    {
+	      if (strncmp (string + i, prefixes[j], strlen (prefixes[j])) == 0)
+		{
+		  *len = strlen (prefixes[j]) - 1;
+		  return (i + *len);
+		}
+	    }
+	}
+    }
+  return (string_len);
+}
+
+/* Find the end of a tilde expansion in STRING, and return the index of
+   the character which ends the tilde definition.  */
+static int
+tilde_find_suffix (string)
+     char *string;
+{
+  register int i, j, string_len;
+  register char **suffixes = tilde_additional_suffixes;
+
+  string_len = strlen (string);
+
+  for (i = 0; i < string_len; i++)
+    {
+      if (string[i] == '/' || !string[i])
+	break;
+
+      for (j = 0; suffixes && suffixes[j]; j++)
+	{
+	  if (strncmp (string + i, suffixes[j], strlen (suffixes[j])) == 0)
+	    return (i);
+	}
+    }
+  return (i);
+}
+
+/* Return a new string which is the result of tilde expanding STRING. */
+char *
+tilde_expand (string)
+     char *string;
+{
+  char *result, *tilde_expand_word ();
+  int result_size, result_index;
+
+  result_size = result_index = 0;
+  result = (char *)NULL;
+
+  /* Scan through STRING expanding tildes as we come to them. */
+  while (1)
+    {
+      register int start, end;
+      char *tilde_word, *expansion;
+      int len;
+
+      /* Make START point to the tilde which starts the expansion. */
+      start = tilde_find_prefix (string, &len);
+
+      /* Copy the skipped text into the result. */
+      if ((result_index + start + 1) > result_size)
+	result = (char *)xrealloc (result, 1 + (result_size += (start + 20)));
+
+      strncpy (result + result_index, string, start);
+      result_index += start;
+
+      /* Advance STRING to the starting tilde. */
+      string += start;
+
+      /* Make END be the index of one after the last character of the
+	 username. */
+      end = tilde_find_suffix (string);
+
+      /* If both START and END are zero, we are all done. */
+      if (!start && !end)
+	break;
+
+      /* Expand the entire tilde word, and copy it into RESULT. */
+      tilde_word = (char *)xmalloc (1 + end);
+      strncpy (tilde_word, string, end);
+      tilde_word[end] = '\0';
+      string += end;
+
+      expansion = tilde_expand_word (tilde_word);
+      free (tilde_word);
+
+      len = strlen (expansion);
+      if ((result_index + len + 1) > result_size)
+	result = (char *)xrealloc (result, 1 + (result_size += (len + 20)));
+
+      strcpy (result + result_index, expansion);
+      result_index += len;
+      free (expansion);
+    }
+
+  result[result_index] = '\0';
+
+  return (result);
+}
+
+/* Do the work of tilde expansion on FILENAME.  FILENAME starts with a
+   tilde.  If there is no expansion, call tilde_expansion_failure_hook. */
+char *
+tilde_expand_word (filename)
+     char *filename;
+{
+  char *dirname;
+
+  dirname = filename ? savestring (filename) : (char *)NULL;
+
+  if (dirname && *dirname == '~')
+    {
+      char *temp_name;
+      if (!dirname[1] || dirname[1] == '/')
+	{
+	  /* Prepend $HOME to the rest of the string. */
+	  char *temp_home = (char *)getenv ("HOME");
+
+	  /* If there is no HOME variable, look up the directory in
+	     the password database. */
+	  if (!temp_home)
+	    {
+	      extern struct passwd *getpwuid ();
+	      struct passwd *entry;
+
+	      entry = getpwuid (getuid ());
+	      if (entry)
+		temp_home = entry->pw_dir;
+	    }
+
+	  temp_name = (char *)alloca (1 + strlen (&dirname[1])
+				      + (temp_home ? strlen (temp_home) : 0));
+	  temp_name[0] = '\0';
+	  if (temp_home)
+	    strcpy (temp_name, temp_home);
+	  strcat (temp_name, &dirname[1]);
+	  free (dirname);
+	  dirname = savestring (temp_name);
+	}
+      else
+	{
+	  struct passwd *getpwnam (), *user_entry;
+	  char *username = (char *)alloca (257);
+	  int i, c;
+
+	  for (i = 1; c = dirname[i]; i++)
+	    {
+	      if (c == '/')
+		break;
+	      else
+		username[i - 1] = c;
+	    }
+	  username[i - 1] = '\0';
+
+	  if (!(user_entry = getpwnam (username)))
+	    {
+	      /* If the calling program has a special syntax for
+		 expanding tildes, and we couldn't find a standard
+		 expansion, then let them try. */
+	      if (tilde_expansion_failure_hook)
+		{
+		  char *expansion;
+
+		  expansion =
+		    (char *)(*tilde_expansion_failure_hook) (username);
+
+		  if (expansion)
+		    {
+		      temp_name = (char *)alloca (1 + strlen (expansion)
+						  + strlen (&dirname[i]));
+		      strcpy (temp_name, expansion);
+		      strcat (temp_name, &dirname[i]);
+		      free (expansion);
+		      goto return_name;
+		    }
+		}
+	      /* We shouldn't report errors. */
+	    }
+	  else
+	    {
+	      temp_name = (char *)alloca (1 + strlen (user_entry->pw_dir)
+					  + strlen (&dirname[i]));
+	      strcpy (temp_name, user_entry->pw_dir);
+	      strcat (temp_name, &dirname[i]);
+	    return_name:
+	      free (dirname);
+	      dirname = savestring (temp_name);
+	    }
+	    endpwent ();
+	}
+    }
+  return (dirname);
+}
+
+
+#if defined (TEST)
+#undef NULL
+#include <stdio.h>
+
+main (argc, argv)
+     int argc;
+     char **argv;
+{
+  char *result, line[512];
+  int done = 0;
+
+  while (!done)
+    {
+      printf ("~expand: ");
+      fflush (stdout);
+
+      if (!gets (line))
+	strcpy (line, "done");
+
+      if ((strcmp (line, "done") == 0) ||
+	  (strcmp (line, "quit") == 0) ||
+	  (strcmp (line, "exit") == 0))
+	{
+	  done = 1;
+	  break;
+	}
+
+      result = tilde_expand (line);
+      printf ("  --> %s\n", result);
+      free (result);
+    }
+  exit (0);
+}
+
+static void memory_error_and_abort ();
+
+static char *
+xmalloc (bytes)
+     int bytes;
+{
+  char *temp = (char *)malloc (bytes);
+
+  if (!temp)
+    memory_error_and_abort ();
+  return (temp);
+}
+
+static char *
+xrealloc (pointer, bytes)
+     char *pointer;
+     int bytes;
+{
+  char *temp;
+
+  if (!pointer)
+    temp = (char *)malloc (bytes);
+  else
+    temp = (char *)realloc (pointer, bytes);
+
+  if (!temp)
+    memory_error_and_abort ();
+
+  return (temp);
+}
+
+static void
+memory_error_and_abort ()
+{
+  fprintf (stderr, "readline: Out of virtual memory!\n");
+  abort ();
+}
+
+/*
+ * Local variables:
+ * compile-command: "gcc -g -DTEST -o tilde tilde.c"
+ * end:
+ */
+#endif /* TEST */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/tilde.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,57 @@
+/* tilde.h: Externally available variables and function in libtilde.a. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   This file has appeared in prior works by the Free Software Foundation;
+   thus it carries copyright dates from 1988 through 1993.
+
+   Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993 Free Software
+   Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+/* Function pointers can be declared as (Function *)foo. */
+#if !defined (__FUNCTION_DEF)
+#  define __FUNCTION_DEF
+typedef int Function ();
+typedef void VFunction ();
+#endif /* _FUNCTION_DEF */
+
+/* If non-null, this contains the address of a function to call if the
+   standard meaning for expanding a tilde fails.  The function is called
+   with the text (sans tilde, as in "foo"), and returns a malloc()'ed string
+   which is the expansion, or a NULL pointer if there is no expansion. */
+extern Function *tilde_expansion_failure_hook;
+
+/* When non-null, this is a NULL terminated array of strings which
+   are duplicates for a tilde prefix.  Bash uses this to expand
+   `=~' and `:~'. */
+extern char **tilde_additional_prefixes;
+
+/* When non-null, this is a NULL terminated array of strings which match
+   the end of a username, instead of just "/".  Bash sets this to
+   `:' and `=~'. */
+extern char **tilde_additional_suffixes;
+
+/* Return a new string which is the result of tilde expanding STRING. */
+extern char *tilde_expand ();
+
+/* Do the work of tilde expansion on FILENAME.  FILENAME starts with a
+   tilde.  If there is no expansion, call tilde_expansion_failure_hook. */
+extern char *tilde_expand_word ();
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/variables.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,272 @@
+/* variables.c -- How to manipulate user visible variables in Info. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include "variables.h"
+
+/* **************************************************************** */
+/*								    */
+/*		    User Visible Variables in Info		    */
+/*								    */
+/* **************************************************************** */
+
+/* Choices used by the completer when reading a zero/non-zero value for
+   a variable. */
+static char *on_off_choices[] = { "Off", "On", (char *)NULL };
+
+VARIABLE_ALIST info_variables[] = {
+  { "automatic-footnotes",
+      "When \"On\", footnotes appear and disappear automatically",
+      &auto_footnotes_p, (char **)on_off_choices },
+
+  { "automatic-tiling",
+      "When \"On\", creating or deleting a window resizes other windows",
+      &auto_tiling_p, (char **)on_off_choices },
+
+  { "visible-bell",
+      "When \"On\", flash the screen instead of ringing the bell",
+      &terminal_use_visible_bell_p, (char **)on_off_choices },
+
+  { "errors-ring-bell",
+      "When \"On\", errors cause the bell to ring",
+      &info_error_rings_bell_p, (char **)on_off_choices },
+
+  { "gc-compressed-files",
+      "When \"On\", Info garbage collects files which had to be uncompressed",
+      &gc_compressed_files, (char **)on_off_choices },
+  { "show-index-match",
+      "When \"On\", the portion of the matched search string is highlighted",
+      &show_index_match, (char **)on_off_choices },
+
+  { "scroll-behaviour",
+      "Controls what happens when scrolling is requested at the end of a node",
+      &info_scroll_behaviour, (char **)info_scroll_choices },
+
+  { "scroll-step",
+      "The number lines to scroll when the cursor moves out of the window",
+      &window_scroll_step, (char **)NULL },
+
+  { "ISO-Latin",
+      "When \"On\", Info accepts and displays ISO Latin characters",
+      &ISO_Latin_p, (char **)on_off_choices },
+
+  { (char *)NULL, (char *)NULL, (int *)NULL, (char **)NULL }
+};
+
+DECLARE_INFO_COMMAND (describe_variable, "Explain the use of a variable")
+{
+  VARIABLE_ALIST *var;
+  char *description;
+
+  /* Get the variable's name. */
+  var = read_variable_name ("Describe variable: ", window);
+
+  if (!var)
+    return;
+
+  description = (char *)xmalloc (20 + strlen (var->name) + strlen (var->doc));
+
+  if (var->choices)
+    sprintf (description, "%s (%s): %s.",
+	     var->name, var->choices[*(var->value)], var->doc);
+  else
+    sprintf (description, "%s (%d): %s.", var->name, *(var->value), var->doc);
+
+  window_message_in_echo_area ("%s", description);
+  free (description);
+}
+
+DECLARE_INFO_COMMAND (set_variable, "Set the value of an Info variable")
+{
+  VARIABLE_ALIST *var;
+  char *line;
+
+  /* Get the variable's name and value. */
+  var = read_variable_name ("Set variable: ", window);
+
+  if (!var)
+    return;
+
+  /* Read a new value for this variable. */
+  {
+    char prompt[100];
+
+    if (!var->choices)
+      {
+	int potential_value;
+
+	if (info_explicit_arg || count != 1)
+	  potential_value = count;
+	else
+	  potential_value = *(var->value);
+
+	sprintf (prompt, "Set %s to value (%d): ",
+		 var->name, potential_value);
+	line = info_read_in_echo_area (active_window, prompt);
+
+	/* If no error was printed, clear the echo area. */
+	if (!info_error_was_printed)
+	  window_clear_echo_area ();
+
+	/* User aborted? */
+	if (!line)
+	  return;
+
+	/* If the user specified a value, get that, otherwise, we are done. */
+	canonicalize_whitespace (line);
+	if (*line)
+	  *(var->value) = atoi (line);
+	else
+	  *(var->value) = potential_value;
+
+	free (line);
+      }
+    else
+      {
+	register int i;
+	REFERENCE **array = (REFERENCE **)NULL;
+	int array_index = 0;
+	int array_slots = 0;
+
+	for (i = 0; var->choices[i]; i++)
+	  {
+	    REFERENCE *entry;
+
+	    entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
+	    entry->label = savestring (var->choices[i]);
+	    entry->nodename = (char *)NULL;
+	    entry->filename = (char *)NULL;
+
+	    add_pointer_to_array
+	      (entry, array_index, array, array_slots, 10, REFERENCE *);
+	  }
+
+	sprintf (prompt, "Set %s to value (%s): ",
+		 var->name, var->choices[*(var->value)]);
+
+	/* Ask the completer to read a variable value for us. */
+	line = info_read_completing_in_echo_area (window, prompt, array);
+
+	info_free_references (array);
+
+	if (!echo_area_is_active)
+	  window_clear_echo_area ();
+
+	/* User aborted? */
+	if (!line)
+	  {
+	    info_abort_key (active_window, 0, 0);
+	    return;
+	  }
+
+	/* User accepted default choice?  If so, no change. */
+	if (!*line)
+	  {
+	    free (line);
+	    return;
+	  }
+
+	/* Find the choice in our list of choices. */
+	for (i = 0; var->choices[i]; i++)
+	  if (strcmp (var->choices[i], line) == 0)
+	    break;
+
+	if (var->choices[i])
+	  *(var->value) = i;
+      }
+  }
+}
+
+/* Read the name of an Info variable in the echo area and return the
+   address of a VARIABLE_ALIST member.  A return value of NULL indicates
+   that no variable could be read. */
+VARIABLE_ALIST *
+read_variable_name (prompt, window)
+     char *prompt;
+     WINDOW *window;
+{
+  register int i;
+  char *line;
+  REFERENCE **variables;
+
+  /* Get the completion array of variable names. */
+  variables = make_variable_completions_array ();
+
+  /* Ask the completer to read a variable for us. */
+  line =
+    info_read_completing_in_echo_area (window, prompt, variables);
+
+  info_free_references (variables);
+
+  if (!echo_area_is_active)
+    window_clear_echo_area ();
+
+  /* User aborted? */
+  if (!line)
+    {
+      info_abort_key (active_window, 0, 0);
+      return ((VARIABLE_ALIST *)NULL);
+    }
+
+  /* User accepted "default"?  (There is none.) */
+  if (!*line)
+    {
+      free (line);
+      return ((VARIABLE_ALIST *)NULL);
+    }
+
+  /* Find the variable in our list of variables. */
+  for (i = 0; info_variables[i].name; i++)
+    if (strcmp (info_variables[i].name, line) == 0)
+      break;
+
+  if (!info_variables[i].name)
+    return ((VARIABLE_ALIST *)NULL);
+  else
+    return (&(info_variables[i]));
+}
+
+/* Make an array of REFERENCE which actually contains the names of the
+   variables available in Info. */
+REFERENCE **
+make_variable_completions_array ()
+{
+  register int i;
+  REFERENCE **array = (REFERENCE **)NULL;
+  int array_index = 0, array_slots = 0;
+
+  for (i = 0; info_variables[i].name; i++)
+    {
+      REFERENCE *entry;
+
+      entry = (REFERENCE *)xmalloc (sizeof (REFERENCE));
+      entry->label = savestring (info_variables[i].name);
+      entry->nodename = (char *)NULL;
+      entry->filename = (char *)NULL;
+
+      add_pointer_to_array
+	(entry, array_index, array, array_slots, 200, REFERENCE *);
+    }
+
+  return (array);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/variables.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,64 @@
+/* variables.h -- Description of user visible variables in Info. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_VARIABLES_H_)
+#define _VARIABLES_H_
+
+/* A variable (in the Info sense) is an integer value with a user-visible
+   name.  You may supply an array of strings to complete over when the
+   variable is set; in that case, the variable is set to the index of the
+   string that the user chose.  If you supply a null list, the user can
+   set the variable to a numeric value. */
+
+/* Structure describing a user visible variable. */
+typedef struct {
+  char *name;			/* Polite name. */
+  char *doc;			/* Documentation string. */
+  int *value;			/* Address of value. */
+  char **choices;		/* Array of strings or NULL if numeric only. */
+} VARIABLE_ALIST;
+
+/* Read the name of an Info variable in the echo area and return the
+   address of a VARIABLE_ALIST member.  A return value of NULL indicates
+   that no variable could be read. */
+extern VARIABLE_ALIST *read_variable_name ();
+
+/* Make an array of REFERENCE which actually contains the names of the
+   variables available in Info. */
+extern REFERENCE **make_variable_completions_array ();
+
+/* Set the value of an info variable. */
+extern void set_variable ();
+
+/* The list of user-visible variables. */
+extern int auto_footnotes_p;
+extern int auto_tiling_p;
+extern int terminal_use_visible_bell_p;
+extern int info_error_rings_bell_p;
+extern int gc_compressed_files;
+extern int show_index_match;
+extern int info_scroll_behaviour;
+extern int window_scroll_step;
+extern int ISO_Latin_p;
+
+#endif /* _VARIABLES_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/window.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,1478 @@
+/* window.c -- Windows in Info. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "nodes.h"
+#include "window.h"
+#include "display.h"
+#include "info-utils.h"
+#include "infomap.h"
+
+/* The window which describes the screen. */
+WINDOW *the_screen = (WINDOW *)NULL;
+
+/* The window which describes the echo area. */
+WINDOW *the_echo_area = (WINDOW *)NULL;
+
+/* The list of windows in Info. */
+WINDOW *windows = (WINDOW *)NULL;
+
+/* Pointer to the active window in WINDOW_LIST. */
+WINDOW *active_window = (WINDOW *)NULL;
+
+/* The size of the echo area in Info.  It never changes, irregardless of the
+   size of the screen. */
+#define ECHO_AREA_HEIGHT 1
+
+/* Macro returns the amount of space that the echo area truly requires relative
+   to the entire screen. */
+#define echo_area_required (1 + the_echo_area->height)
+
+/* Initalize the window system by creating THE_SCREEN and THE_ECHO_AREA.
+   Create the first window ever.
+   You pass the dimensions of the total screen size. */
+void
+window_initialize_windows (width, height)
+     int width, height;
+{
+  the_screen = (WINDOW *)xmalloc (sizeof (WINDOW));
+  the_echo_area = (WINDOW *)xmalloc (sizeof (WINDOW));
+  windows = (WINDOW *)xmalloc (sizeof (WINDOW));
+  active_window = windows;
+
+  zero_mem (the_screen, sizeof (WINDOW));
+  zero_mem (the_echo_area, sizeof (WINDOW));
+  zero_mem (active_window, sizeof (WINDOW));
+
+  /* None of these windows has a goal column yet. */
+  the_echo_area->goal_column = -1;
+  active_window->goal_column = -1;
+  the_screen->goal_column = -1;
+
+  /* The active and echo_area windows are visible.
+     The echo_area is permanent.
+     The screen is permanent. */
+  active_window->flags = W_WindowVisible;
+  the_echo_area->flags = W_WindowIsPerm | W_InhibitMode | W_WindowVisible;
+  the_screen->flags    = W_WindowIsPerm;
+
+  /* The height of the echo area never changes.  It is statically set right
+     here, and it must be at least 1 line for display.  The size of the
+     initial window cannot be the same size as the screen, since the screen
+     includes the echo area.  So, we make the height of the initial window
+     equal to the screen's displayable region minus the height of the echo
+     area. */
+  the_echo_area->height = ECHO_AREA_HEIGHT;
+  active_window->height = the_screen->height - 1 - the_echo_area->height;
+  window_new_screen_size (width, height, (VFunction *)NULL);
+
+  /* The echo area uses a different keymap than normal info windows. */
+  the_echo_area->keymap = echo_area_keymap;
+  active_window->keymap = info_keymap;
+}
+
+/* Given that the size of the screen has changed to WIDTH and HEIGHT
+   from whatever it was before (found in the_screen->height, ->width),
+   change the size (and possibly location) of each window in the screen.
+   If a window would become too small, call the function DELETER on it,
+   after deleting the window from our chain of windows.  If DELETER is NULL,
+   nothing extra is done.  The last window can never be deleted, but it can
+   become invisible. */
+
+/* If non-null, a function to call with WINDOW as argument when the function
+   window_new_screen_size () has deleted WINDOW. */
+VFunction *window_deletion_notifier = (VFunction *)NULL;
+
+void
+window_new_screen_size (width, height)
+     int width, height;
+{
+  register WINDOW *win;
+  int delta_height, delta_each, delta_leftover;
+  int numwins;
+
+  /* If no change, do nothing. */
+  if (width == the_screen->width && height == the_screen->height)
+    return;
+
+  /* If the new window height is too small, make it be zero. */
+  if (height < (WINDOW_MIN_SIZE + the_echo_area->height))
+    height = 0;
+  if (width < 0)
+    width = 0;
+
+  /* Find out how many windows will change. */
+  for (numwins = 0, win = windows; win; win = win->next, numwins++);
+
+  /* See if some windows will need to be deleted.  This is the case if
+     the screen is getting smaller, and the available space divided by
+     the number of windows is less than WINDOW_MIN_SIZE.  In that case,
+     delete some windows and try again until there is either enough
+     space to divy up among the windows, or until there is only one
+     window left. */
+  while ((height - echo_area_required) / numwins <= WINDOW_MIN_SIZE)
+    {
+      /* If only one window, make the size of it be zero, and return
+	 immediately. */
+      if (!windows->next)
+	{
+	  windows->height = 0;
+	  maybe_free (windows->line_starts);
+	  windows->line_starts = (char **)NULL;
+	  windows->line_count = 0;
+	  break;
+	}
+
+      /* If we have some temporary windows, delete one of them. */
+      for (win = windows; win; win = win->next)
+	if (win->flags & W_TempWindow)
+	  break;
+
+      /* Otherwise, delete the first window, and try again. */
+      if (!win)
+	win = windows;
+
+      if (window_deletion_notifier)
+	(*window_deletion_notifier) (win);
+
+      window_delete_window (win);
+      numwins--;
+    }
+
+  /* The screen has changed height and width. */
+  delta_height = height - the_screen->height;	/* This is how much. */
+  the_screen->height = height;			/* This is the new height. */
+  the_screen->width = width;			/* This is the new width. */
+
+  /* Set the start of the echo area. */
+  the_echo_area->first_row = height - the_echo_area->height;
+  the_echo_area->width = width;
+
+  /* Check to see if the screen can really be changed this way. */
+  if ((!windows->next) && ((windows->height == 0) && (delta_height < 0)))
+    return;
+
+  /* Divide the change in height among the available windows. */
+  delta_each = delta_height / numwins;
+  delta_leftover = delta_height - (delta_each * numwins);
+
+  /* Change the height of each window in the chain by delta_each.  Change
+     the height of the last window in the chain by delta_each and by the
+     leftover amount of change.  Change the width of each window to be
+     WIDTH. */
+  for (win = windows; win; win = win->next)
+    {
+      if ((win->width != width) && ((win->flags & W_InhibitMode) == 0))
+	{
+	  win->width = width;
+	  maybe_free (win->modeline);
+	  win->modeline = (char *)xmalloc (1 + width);
+	}
+
+      win->height += delta_each;
+
+      /* If the previous height of this window was zero, it was the only
+	 window, and it was not visible.  Thus we need to compensate for
+	 the echo_area. */
+      if (win->height == delta_each)
+	win->height -= (1 + the_echo_area->height);
+
+      /* If this is not the first window in the chain, then change the
+	 first row of it.  We cannot just add delta_each to the first row,
+	 since this window's first row is the sum of the collective increases
+	 that have gone before it.  So we just add one to the location of the
+	 previous window's modeline. */
+      if (win->prev)
+	win->first_row = (win->prev->first_row + win->prev->height) + 1;
+
+      /* The last window in the chain gets the extra space (or shrinkage). */
+      if (!win->next)
+	win->height += delta_leftover;
+
+      if (win->node)
+	recalculate_line_starts (win);
+
+      win->flags |= W_UpdateWindow;
+    }
+
+  /* If the screen got smaller, check over the windows just shrunk to
+     keep them within bounds.  Some of the windows may have gotten smaller
+     than WINDOW_MIN_HEIGHT in which case some of the other windows are
+     larger than the available display space in the screen.  Because of our
+     intial test above, we know that there is enough space for all of the
+     windows. */
+  if ((delta_each < 0) && ((windows->height != 0) && windows->next))
+    {
+      int avail;
+
+      avail = the_screen->height - (numwins + the_echo_area->height);
+      win = windows;
+
+      while (win)
+	{
+	  if ((win->height < WINDOW_MIN_HEIGHT) ||
+	      (win->height > avail))
+	    {
+	      WINDOW *lastwin;
+
+	      /* Split the space among the available windows. */
+	      delta_each = avail / numwins;
+	      delta_leftover = avail - (delta_each * numwins);
+
+	      for (win = windows; win; win = win->next)
+		{
+		  lastwin = win;
+		  if (win->prev)
+		    win->first_row =
+		      (win->prev->first_row + win->prev->height) + 1;
+		  win->height = delta_each;
+		}
+
+	      /* Give the leftover space (if any) to the last window. */
+	      lastwin->height += delta_leftover;
+	      break;
+	    }
+	  else
+	    win= win->next;
+	}
+    }
+}
+
+/* Make a new window showing NODE, and return that window structure.
+   If NODE is passed as NULL, then show the node showing in the active
+   window.  If the window could not be made return a NULL pointer.  The
+   active window is not changed.*/
+WINDOW *
+window_make_window (node)
+     NODE *node;
+{
+  WINDOW *window;
+
+  if (!node)
+    node = active_window->node;
+
+  /* If there isn't enough room to make another window, return now. */
+  if ((active_window->height / 2) < WINDOW_MIN_SIZE)
+    return ((WINDOW *)NULL);
+
+  /* Make and initialize the new window.
+     The fudging about with -1 and +1 is because the following window in the
+     chain cannot start at window->height, since that is where the modeline
+     for the previous window is displayed.  The inverse adjustment is made
+     in window_delete_window (). */
+  window = (WINDOW *)xmalloc (sizeof (WINDOW));
+  window->width = the_screen->width;
+  window->height = (active_window->height / 2) - 1;
+#if defined (SPLIT_BEFORE_ACTIVE)
+  window->first_row = active_window->first_row;
+#else
+  window->first_row = active_window->first_row +
+    (active_window->height - window->height);
+#endif
+  window->keymap = info_keymap;
+  window->goal_column = -1;
+  window->modeline = (char *)xmalloc (1 + window->width);
+  window->line_starts = (char **)NULL;
+  window->flags = W_UpdateWindow | W_WindowVisible;
+  window_set_node_of_window (window, node);
+
+  /* Adjust the height of the old active window. */
+  active_window->height -= (window->height + 1);
+#if defined (SPLIT_BEFORE_ACTIVE)
+  active_window->first_row += (window->height + 1);
+#endif
+  active_window->flags |= W_UpdateWindow;
+
+  /* Readjust the new and old windows so that their modelines and contents
+     will be displayed correctly. */
+#if defined (NOTDEF)
+  /* We don't have to do this for WINDOW since window_set_node_of_window ()
+     already did. */
+  window_adjust_pagetop (window);
+  window_make_modeline (window);
+#endif /* NOTDEF */
+
+  /* We do have to readjust the existing active window. */
+  window_adjust_pagetop (active_window);
+  window_make_modeline (active_window);
+
+#if defined (SPLIT_BEFORE_ACTIVE)
+  /* This window is just before the active one.  The active window gets
+     bumped down one.  The active window is not changed. */
+  window->next = active_window;
+
+  window->prev = active_window->prev;
+  active_window->prev = window;
+
+  if (window->prev)
+    window->prev->next = window;
+  else
+    windows = window;
+#else
+  /* This window is just after the active one.  Which window is active is
+     not changed. */
+  window->prev = active_window;
+  window->next = active_window->next;
+  active_window->next = window;
+  if (window->next)
+    window->next->prev = window;
+#endif /* !SPLIT_BEFORE_ACTIVE */
+  return (window);
+}
+
+/* These useful macros make it possible to read the code in
+   window_change_window_height (). */
+#define grow_me_shrinking_next(me, next, diff) \
+  do { \
+    me->height += diff; \
+    next->height -= diff; \
+    next->first_row += diff; \
+    window_adjust_pagetop (next); \
+  } while (0)
+
+#define grow_me_shrinking_prev(me, prev, diff) \
+  do { \
+    me->height += diff; \
+    prev->height -= diff; \
+    me->first_row -=diff; \
+    window_adjust_pagetop (prev); \
+  } while (0)
+
+#define shrink_me_growing_next(me, next, diff) \
+  do { \
+    me->height -= diff; \
+    next->height += diff; \
+    next->first_row -= diff; \
+    window_adjust_pagetop (next); \
+  } while (0)
+
+#define shrink_me_growing_prev(me, prev, diff) \
+  do { \
+    me->height -= diff; \
+    prev->height += diff; \
+    me->first_row += diff; \
+    window_adjust_pagetop (prev); \
+  } while (0)
+
+/* Change the height of WINDOW by AMOUNT.  This also automagically adjusts
+   the previous and next windows in the chain.  If there is only one user
+   window, then no change takes place. */
+void
+window_change_window_height (window, amount)
+     WINDOW *window;
+     int amount;
+{
+  register WINDOW *win, *prev, *next;
+
+  /* If there is only one window, or if the amount of change is zero,
+     return immediately. */
+  if (!windows->next || amount == 0)
+    return;
+
+  /* Find this window in our chain. */
+  for (win = windows; win; win = win->next)
+    if (win == window)
+      break;
+
+  /* If the window is isolated (i.e., doesn't appear in our window list,
+     then quit now. */
+  if (!win)
+    return;
+
+  /* Change the height of this window by AMOUNT, if that is possible.
+     It can be impossible if there isn't enough available room on the
+     screen, or if the resultant window would be too small. */
+
+    prev = window->prev;
+    next = window->next;
+
+  /* WINDOW decreasing in size? */
+  if (amount < 0)
+    {
+      int abs_amount = -amount;	/* It is easier to deal with this way. */
+
+      /* If the resultant window would be too small, stop here. */
+      if ((window->height - abs_amount) < WINDOW_MIN_HEIGHT)
+	return;
+
+      /* If we have two neighboring windows, choose the smaller one to get
+	 larger. */
+      if (next && prev)
+	{
+	  if (prev->height < next->height)
+	    shrink_me_growing_prev (window, prev, abs_amount);
+	  else
+	    shrink_me_growing_next (window, next, abs_amount);
+	}
+      else if (next)
+	shrink_me_growing_next (window, next, abs_amount);
+      else
+	shrink_me_growing_prev (window, prev, abs_amount);
+    }
+
+  /* WINDOW increasing in size? */
+  if (amount > 0)
+    {
+      int total_avail, next_avail = 0, prev_avail = 0;
+
+      if (next)
+	next_avail = next->height - WINDOW_MIN_SIZE;
+
+      if (prev)
+	prev_avail = prev->height - WINDOW_MIN_SIZE;
+
+      total_avail = next_avail + prev_avail;
+
+      /* If there isn't enough space available to grow this window, give up. */
+      if (amount > total_avail)
+	return;
+
+      /* If there aren't two neighboring windows, or if one of the neighbors
+	 is larger than the other one by at least AMOUNT, grow that one. */
+      if ((next && !prev) || ((next_avail - amount) >= prev_avail))
+	grow_me_shrinking_next (window, next, amount);
+      else if ((prev && !next) || ((prev_avail - amount) >= next_avail))
+	grow_me_shrinking_prev (window, prev, amount);
+      else
+	{
+	  int change;
+
+	  /* This window has two neighbors.  They both must be shrunk in to
+	     make enough space for WINDOW to grow.  Make them both the same
+	     size. */
+	  if (prev_avail > next_avail)
+	    {
+	      change = prev_avail - next_avail;
+	      grow_me_shrinking_prev (window, prev, change);
+	      amount -= change;
+	    }
+	  else
+	    {
+	      change = next_avail - prev_avail;
+	      grow_me_shrinking_next (window, next, change);
+	      amount -= change;
+	    }
+
+	  /* Both neighbors are the same size.  Split the difference in
+	     AMOUNT between them. */
+	  while (amount)
+	    {
+	      window->height++;
+	      amount--;
+
+	      /* Odd numbers grow next, even grow prev. */
+	      if (amount & 1)
+		{
+		  prev->height--;
+		  window->first_row--;
+		}
+	      else
+		{
+		  next->height--;
+		  next->first_row++;
+		}
+	    }
+	  window_adjust_pagetop (prev);
+	  window_adjust_pagetop (next);
+	}
+    }
+  if (prev)
+    prev->flags |= W_UpdateWindow;
+
+  if (next)
+    next->flags |= W_UpdateWindow;
+
+  window->flags |= W_UpdateWindow;
+  window_adjust_pagetop (window);
+}
+
+/* Tile all of the windows currently displayed in the global variable
+   WINDOWS.  If argument STYLE is TILE_INTERNALS, tile windows displaying
+   internal nodes as well, otherwise do not change the height of such
+   windows. */
+void
+window_tile_windows (style)
+     int style;
+{
+  WINDOW *win, *last_adjusted;
+  int numwins, avail, per_win_height, leftover;
+  int do_internals;
+
+  numwins = avail = 0;
+  do_internals = (style == TILE_INTERNALS);
+
+  for (win = windows; win; win = win->next)
+    if (do_internals || !win->node ||
+	(win->node->flags & N_IsInternal) == 0)
+      {
+	avail += win->height;
+	numwins++;
+      }
+
+  if (numwins <= 1 || !the_screen->height)
+    return;
+
+  /* Find the size for each window.  Divide the size of the usable portion
+     of the screen by the number of windows. */
+  per_win_height = avail / numwins;
+  leftover = avail - (per_win_height * numwins);
+
+  last_adjusted = (WINDOW *)NULL;
+  for (win = windows; win; win = win->next)
+    {
+      if (do_internals || !win->node ||
+	  (win->node->flags & N_IsInternal) == 0)
+	{
+	  last_adjusted = win;
+	  win->height = per_win_height;
+	}
+    }
+
+  if (last_adjusted)
+    last_adjusted->height += leftover;
+
+  /* Readjust the first_row of every window in the chain. */
+  for (win = windows; win; win = win->next)
+    {
+      if (win->prev)
+	win->first_row = win->prev->first_row + win->prev->height + 1;
+
+      window_adjust_pagetop (win);
+      win->flags |= W_UpdateWindow;
+    }
+}
+
+/* Toggle the state of line wrapping in WINDOW.  This can do a bit of fancy
+   redisplay. */
+void
+window_toggle_wrap (window)
+     WINDOW *window;
+{
+  if (window->flags & W_NoWrap)
+    window->flags &= ~W_NoWrap;
+  else
+    window->flags |= W_NoWrap;
+
+  if (window != the_echo_area)
+    {
+      char **old_starts;
+      int old_lines, old_pagetop;
+
+      old_starts = window->line_starts;
+      old_lines = window->line_count;
+      old_pagetop = window->pagetop;
+
+      calculate_line_starts (window);
+
+      /* Make sure that point appears within this window. */
+      window_adjust_pagetop (window);
+
+      /* If the pagetop hasn't changed maybe we can do some scrolling now
+	 to speed up the display.  Many of the line starts will be the same,
+	 so scrolling here is a very good optimization.*/
+      if (old_pagetop == window->pagetop)
+	display_scroll_line_starts
+	  (window, old_pagetop, old_starts, old_lines);
+      maybe_free (old_starts);
+    }
+  window->flags |= W_UpdateWindow;
+}
+
+/* Set WINDOW to display NODE. */
+void
+window_set_node_of_window (window, node)
+     WINDOW *window;
+     NODE *node;
+{
+  window->node = node;
+  window->pagetop = 0;
+  window->point = 0;
+  recalculate_line_starts (window);
+  window->flags |= W_UpdateWindow;
+  window_adjust_pagetop (window);
+  window_make_modeline (window);
+}
+
+/* Delete WINDOW from the list of known windows.  If this window was the
+   active window, make the next window in the chain be the active window.
+   If the active window is the next or previous window, choose that window
+   as the recipient of the extra space.  Otherwise, prefer the next window. */
+void
+window_delete_window (window)
+     WINDOW *window;
+{
+  WINDOW *next, *prev, *window_to_fix;
+
+  next = window->next;
+  prev = window->prev;
+
+  /* You cannot delete the only window or a permanent window. */
+  if ((!next && !prev) || (window->flags & W_WindowIsPerm))
+    return;
+
+  if (next)
+    next->prev = prev;
+
+  if (!prev)
+    windows = next;
+  else
+    prev->next = next;
+
+  if (window->line_starts)
+    free (window->line_starts);
+
+  if (window->modeline)
+    free (window->modeline);
+
+  if (window == active_window)
+    {
+      /* If there isn't a next window, then there must be a previous one,
+	 since we cannot delete the last window.  If there is a next window,
+	 prefer to use that as the active window. */
+      if (next)
+	active_window = next;
+      else
+	active_window = prev;
+    }
+
+  if (next && active_window == next)
+    window_to_fix = next;
+  else if (prev && active_window == prev)
+    window_to_fix = prev;
+  else if (next)
+    window_to_fix = next;
+  else if (prev)
+    window_to_fix = prev;
+  else
+    window_to_fix = windows;
+    
+  if (window_to_fix->first_row > window->first_row)
+    {
+      int diff;
+
+      /* Try to adjust the visible part of the node so that as little
+	 text as possible has to move. */
+      diff = window_to_fix->first_row - window->first_row;
+      window_to_fix->first_row = window->first_row;
+
+      window_to_fix->pagetop -= diff;
+      if (window_to_fix->pagetop < 0)
+	window_to_fix->pagetop = 0;
+    }
+
+  /* The `+ 1' is to offset the difference between the first_row locations.
+     See the code in window_make_window (). */
+  window_to_fix->height += window->height + 1;
+  window_to_fix->flags |= W_UpdateWindow;
+
+  free (window);
+}
+
+/* For every window in CHAIN, set the flags member to have FLAG set. */
+void
+window_mark_chain (chain, flag)
+     WINDOW *chain;
+     int flag;
+{
+  register WINDOW *win;
+
+  for (win = chain; win; win = win->next)
+    win->flags |= flag;
+}
+
+/* For every window in CHAIN, clear the flags member of FLAG. */
+void
+window_unmark_chain (chain, flag)
+     WINDOW *chain;
+     int flag;
+{
+  register WINDOW *win;
+
+  for (win = chain; win; win = win->next)
+    win->flags &= ~flag;
+}
+
+/* Return the number of characters it takes to display CHARACTER on the
+   screen at HPOS. */
+int
+character_width (character, hpos)
+     int character, hpos;
+{
+  int printable_limit = 127;
+  int width = 1;
+
+  if (ISO_Latin_p)
+    printable_limit = 160;
+
+  if (character > printable_limit)
+    width = 3;
+  else if (iscntrl (character))
+    {
+      switch (character)
+	{
+	case '\r':
+	case '\n':
+	  width = the_screen->width - hpos;
+	  break;
+	case '\t':
+	  width = ((hpos + 8) & 0xf8) - hpos;
+	  break;
+	default:
+	  width = 2;
+	}
+    }
+  else if (character == DEL)
+    width = 2;
+
+  return (width);
+}
+
+/* Return the number of characters it takes to display STRING on the screen
+   at HPOS. */
+int
+string_width (string, hpos)
+     char *string;
+     int hpos;
+{
+  register int i, width, this_char_width;
+
+  for (width = 0, i = 0; string[i]; i++)
+    {
+      this_char_width = character_width (string[i], hpos);
+      width += this_char_width;
+      hpos += this_char_width;
+    }
+  return (width);
+}
+
+/* Quickly guess the approximate number of lines to that NODE would
+   take to display.  This really only counts carriage returns. */
+int
+window_physical_lines (node)
+     NODE *node;
+{
+  register int i, lines;
+  char *contents;
+
+  if (!node)
+    return (0);
+
+  contents = node->contents;
+  for (i = 0, lines = 1; i < node->nodelen; i++)
+    if (contents[i] == '\n')
+      lines++;
+
+  return (lines);
+}
+
+/* Calculate a list of line starts for the node belonging to WINDOW.  The line
+   starts are pointers to the actual text within WINDOW->NODE. */
+void
+calculate_line_starts (window)
+     WINDOW *window;
+{
+  register int i, hpos;
+  char **line_starts = (char **)NULL;
+  int line_starts_index = 0, line_starts_slots = 0;
+  int bump_index;
+  NODE *node;
+
+  window->line_starts = (char **)NULL;
+  window->line_count = 0;
+  node = window->node;
+
+  if (!node)
+    return;
+
+  /* Grovel the node starting at the top, and for each line calculate the
+     width of the characters appearing in that line.  Add each line start
+     to our array. */
+  i = 0;
+  hpos = 0;
+  bump_index = 0;
+
+  while (i < node->nodelen)
+    {
+      char *line = node->contents + i;
+      unsigned int cwidth, c;
+
+      add_pointer_to_array (line, line_starts_index, line_starts,
+			    line_starts_slots, 100, char *);
+      if (bump_index)
+	{
+	  i++;
+	  bump_index = 0;
+	}
+
+      while (1)
+	{
+	  c = node->contents[i];
+	  cwidth = character_width (c, hpos);
+
+	  /* If this character fits within this line, just do the next one. */
+	  if ((hpos + cwidth) < window->width)
+	    {
+	      i++;
+	      hpos += cwidth;
+	      continue;
+	    }
+	  else
+	    {
+	      /* If this character would position the cursor at the start of
+		 the next printed screen line, then do the next line. */
+	      if (c == '\n' || c == '\r' || c == '\t')
+		{
+		  i++;
+		  hpos = 0;
+		  break;
+		}
+	      else
+		{
+		  /* This character passes the window width border.  Postion
+		     the cursor after the printed character, but remember this
+		     line start as where this character is.  A bit tricky. */
+
+		  /* If this window doesn't wrap lines, proceed to the next
+		     physical line here. */
+		  if (window->flags & W_NoWrap)
+		    {
+		      hpos = 0;
+		      while (i < node->nodelen && node->contents[i] != '\n')
+			i++;
+
+		      if (node->contents[i] == '\n')
+			i++;
+		    }
+		  else
+		    {
+		      hpos = the_screen->width - hpos;
+		      bump_index++;
+		    }
+		  break;
+		}
+	    }
+	}
+    }
+  window->line_starts = line_starts;
+  window->line_count = line_starts_index;
+}
+
+/* Given WINDOW, recalculate the line starts for the node it displays. */
+void
+recalculate_line_starts (window)
+     WINDOW *window;
+{
+  maybe_free (window->line_starts);
+  calculate_line_starts (window);
+}
+
+/* Global variable control redisplay of scrolled windows.  If non-zero, it
+   is the desired number of lines to scroll the window in order to make
+   point visible.  A user might set this to 1 for smooth scrolling.  If
+   set to zero, the line containing point is centered within the window. */
+int window_scroll_step = 0;
+
+/* Adjust the pagetop of WINDOW such that the cursor point will be visible. */
+void
+window_adjust_pagetop (window)
+     WINDOW *window;
+{
+  register int line = 0;
+  char *contents;
+
+  if (!window->node)
+    return;
+
+  contents = window->node->contents;
+
+  /* Find the first printed line start which is after WINDOW->point. */
+  for (line = 0; line < window->line_count; line++)
+    {
+      char *line_start;
+
+      line_start = window->line_starts[line];
+
+      if ((line_start - contents) > window->point)
+	break;
+    }
+
+  /* The line index preceding the line start which is past point is the
+     one containing point. */
+  line--;
+
+  /* If this line appears in the current displayable page, do nothing.
+     Otherwise, adjust the top of the page to make this line visible. */
+  if ((line < window->pagetop) ||
+      (line - window->pagetop > (window->height - 1)))
+    {
+      /* The user-settable variable "scroll-step" is used to attempt
+	 to make point visible, iff it is non-zero.  If that variable
+	 is zero, then the line containing point is centered within
+	 the window. */
+      if (window_scroll_step < window->height)
+	{
+	  if ((line < window->pagetop) &&
+	      ((window->pagetop - window_scroll_step) <= line))
+	    window->pagetop -= window_scroll_step;
+	  else if ((line - window->pagetop > (window->height - 1)) &&
+		   ((line - (window->pagetop + window_scroll_step)
+		     < window->height)))
+	    window->pagetop += window_scroll_step;
+	  else
+	    window->pagetop = line - ((window->height - 1) / 2);
+	}
+      else
+	window->pagetop = line - ((window->height - 1) / 2);
+
+      if (window->pagetop < 0)
+	window->pagetop = 0;
+      window->flags |= W_UpdateWindow;
+    }
+}
+
+/* Return the index of the line containing point. */
+int
+window_line_of_point (window)
+     WINDOW *window;
+{
+  register int i, start = 0;
+
+  /* Try to optimize.  Check to see if point is past the pagetop for
+     this window, and if so, start searching forward from there. */
+  if ((window->pagetop > -1 && window->pagetop < window->line_count) &&
+      (window->line_starts[window->pagetop] - window->node->contents)
+      <= window->point)
+    start = window->pagetop;
+
+  for (i = start; i < window->line_count; i++)
+    {
+      if ((window->line_starts[i] - window->node->contents) > window->point)
+	break;
+    }
+
+  return (i - 1);
+}
+
+/* Get and return the goal column for this window. */
+int
+window_get_goal_column (window)
+     WINDOW *window;
+{
+  if (!window->node)
+    return (-1);
+
+  if (window->goal_column != -1)
+    return (window->goal_column);
+
+  /* Okay, do the work.  Find the printed offset of the cursor
+     in this window. */
+  return (window_get_cursor_column (window));
+}
+
+/* Get and return the printed column offset of the cursor in this window. */
+int
+window_get_cursor_column (window)
+     WINDOW *window;
+{
+  int i, hpos, end;
+  char *line;
+
+  i = window_line_of_point (window);
+
+  if (i < 0)
+    return (-1);
+
+  line = window->line_starts[i];
+  end = window->point - (line - window->node->contents);
+
+  for (hpos = 0, i = 0; i < end; i++)
+    hpos += character_width (line[i], hpos);
+
+  return (hpos);
+}
+
+/* Count the number of characters in LINE that precede the printed column
+   offset of GOAL. */
+int
+window_chars_to_goal (line, goal)
+     char *line;
+     int goal;
+{
+  register int i, check, hpos;
+
+  for (hpos = 0, i = 0; line[i] != '\n'; i++)
+    {
+
+      check = hpos + character_width (line[i], hpos);
+
+      if (check > goal)
+	break;
+
+      hpos = check;
+    }
+  return (i);
+}
+
+/* Create a modeline for WINDOW, and store it in window->modeline. */
+void
+window_make_modeline (window)
+     WINDOW *window;
+{
+  register int i;
+  char *modeline;
+  char location_indicator[4];
+  int lines_remaining;
+
+  /* Only make modelines for those windows which have one. */
+  if (window->flags & W_InhibitMode)
+    return;
+
+  /* Find the number of lines actually displayed in this window. */
+  lines_remaining = window->line_count - window->pagetop;
+
+  if (window->pagetop == 0)
+    {
+      if (lines_remaining <= window->height)
+	strcpy (location_indicator, "All");
+      else
+	strcpy (location_indicator, "Top");
+    }
+  else
+    {
+      if (lines_remaining <= window->height)
+	strcpy (location_indicator, "Bot");
+      else
+	{
+	  float pt, lc;
+	  int percentage;
+
+	  pt = (float)window->pagetop;
+	  lc = (float)window->line_count;
+
+	  percentage = 100 * (pt / lc);
+
+	  sprintf (location_indicator, "%2d%%", percentage);
+	}
+    }
+
+  /* Calculate the maximum size of the information to stick in MODELINE. */
+  {
+    int modeline_len = 0;
+    char *parent = (char *)NULL, *filename = "*no file*";
+    char *nodename = "*no node*";
+    char *update_message = (char *)NULL;
+    NODE *node = window->node;
+
+    if (node)
+      {
+	if (node->nodename)
+	  nodename = node->nodename;
+
+	if (node->parent)
+	  {
+	    parent = filename_non_directory (node->parent);
+	    modeline_len += strlen ("Subfile: ") + strlen (node->filename);
+	  }
+
+	if (node->filename)
+	  filename = filename_non_directory (node->filename);
+
+	if (node->flags & N_UpdateTags)
+	  update_message = "--*** Tags out of Date ***";
+      }
+
+    if (update_message)
+      modeline_len += strlen (update_message);
+    modeline_len += strlen (filename);
+    modeline_len += strlen (nodename);
+    modeline_len += 4;		/* strlen (location_indicator). */
+
+    /* 10 for the decimal representation of the number of lines in this
+       node, and the remainder of the text that can appear in the line. */
+    modeline_len += 10 + strlen ("-----Info: (), lines ----, ");
+    modeline_len += window->width;
+
+    modeline = (char *)xmalloc (1 + modeline_len);
+
+    /* Special internal windows have no filename. */
+    if (!parent && !*filename)
+      sprintf (modeline, "-%s---Info: %s, %d lines --%s--",
+	       (window->flags & W_NoWrap) ? "$" : "-",
+	       nodename, window->line_count, location_indicator);
+    else
+      sprintf (modeline, "-%s%s-Info: (%s)%s, %d lines --%s--",
+	       (window->flags & W_NoWrap) ? "$" : "-",
+	       (node && (node->flags & N_IsCompressed)) ? "zz" : "--",
+	       parent ? parent : filename,
+	       nodename, window->line_count, location_indicator);
+
+    if (parent)
+      sprintf (modeline + strlen (modeline), " Subfile: %s", filename);
+
+    if (update_message)
+      sprintf (modeline + strlen (modeline), "%s", update_message);
+
+    i = strlen (modeline);
+
+    if (i >= window->width)
+      modeline[window->width] = '\0';
+    else
+      {
+	while (i < window->width)
+	  modeline[i++] = '-';
+	modeline[i] = '\0';
+      }
+
+    strcpy (window->modeline, modeline);
+    free (modeline);
+  }
+}
+
+/* Make WINDOW start displaying at PERCENT percentage of its node. */
+void
+window_goto_percentage (window, percent)
+     WINDOW *window;
+     int percent;
+{
+  int desired_line;
+
+  if (!percent)
+    desired_line = 0;
+  else
+    desired_line =
+      (int) ((float)window->line_count * ((float)percent / 100.0));
+
+  window->pagetop = desired_line;
+  window->point =
+    window->line_starts[window->pagetop] - window->node->contents;
+  window->flags |= W_UpdateWindow;
+  window_make_modeline (window);
+}
+
+/* Get the state of WINDOW, and save it in STATE. */
+void
+window_get_state (window, state)
+     WINDOW *window;
+     WINDOW_STATE *state;
+{
+  state->node = window->node;
+  state->pagetop = window->pagetop;
+  state->point = window->point;
+}
+
+/* Set the node, pagetop, and point of WINDOW. */
+void
+window_set_state (window, state)
+     WINDOW *window;
+     WINDOW_STATE *state;
+{
+  if (window->node != state->node)
+    window_set_node_of_window (window, state->node);
+  window->pagetop = state->pagetop;
+  window->point = state->point;
+}
+
+
+/* **************************************************************** */
+/*								    */
+/*		   Manipulating Home-Made Nodes			    */
+/*								    */
+/* **************************************************************** */
+
+/* A place to buffer echo area messages. */
+static NODE *echo_area_node = (NODE *)NULL;
+
+/* Make the node of the_echo_area be an empty one. */
+static void
+free_echo_area ()
+{
+  if (echo_area_node)
+    {
+      maybe_free (echo_area_node->contents);
+      free (echo_area_node);
+    }
+
+  echo_area_node = (NODE *)NULL;
+  window_set_node_of_window (the_echo_area, echo_area_node);
+}
+  
+/* Clear the echo area, removing any message that is already present.
+   The echo area is cleared immediately. */
+void
+window_clear_echo_area ()
+{
+  free_echo_area ();
+  display_update_one_window (the_echo_area);
+}
+
+/* Make a message appear in the echo area, built from FORMAT, ARG1 and ARG2.
+   The arguments are treated similar to printf () arguments, but not all of
+   printf () hair is present.  The message appears immediately.  If there was
+   already a message appearing in the echo area, it is removed. */
+void
+window_message_in_echo_area (format, arg1, arg2)
+     char *format;
+     void *arg1, *arg2;
+{
+  free_echo_area ();
+  echo_area_node = build_message_node (format, arg1, arg2);
+  window_set_node_of_window (the_echo_area, echo_area_node);
+  display_update_one_window (the_echo_area);
+}
+
+/* Place a temporary message in the echo area built from FORMAT, ARG1
+   and ARG2.  The message appears immediately, but does not destroy
+   any existing message.  A future call to unmessage_in_echo_area ()
+   restores the old contents. */
+static NODE **old_echo_area_nodes = (NODE **)NULL;
+static int old_echo_area_nodes_index = 0;
+static int old_echo_area_nodes_slots = 0;
+
+void
+message_in_echo_area (format, arg1, arg2)
+     char *format;
+     void *arg1, *arg2;
+{
+  if (echo_area_node)
+    {
+      add_pointer_to_array (echo_area_node, old_echo_area_nodes_index,
+			    old_echo_area_nodes, old_echo_area_nodes_slots,
+			    4, NODE *);
+    }
+  echo_area_node = (NODE *)NULL;
+  window_message_in_echo_area (format, arg1, arg2);
+}
+
+void
+unmessage_in_echo_area ()
+{
+  free_echo_area ();
+
+  if (old_echo_area_nodes_index)
+    echo_area_node = old_echo_area_nodes[--old_echo_area_nodes_index];
+
+  window_set_node_of_window (the_echo_area, echo_area_node);
+  display_update_one_window (the_echo_area);
+}
+
+/* A place to build a message. */
+static char *message_buffer = (char *)NULL;
+static int message_buffer_index = 0;
+static int message_buffer_size = 0;
+
+/* Ensure that there is enough space to stuff LENGTH characters into
+   MESSAGE_BUFFER. */
+static void
+message_buffer_resize (length)
+     int length;
+{
+  if (!message_buffer)
+    {
+      message_buffer_size = length + 1;
+      message_buffer = (char *)xmalloc (message_buffer_size);
+      message_buffer_index = 0;
+    }
+
+  while (message_buffer_size <= message_buffer_index + length)
+    message_buffer = (char *)
+      xrealloc (message_buffer,
+		message_buffer_size += 100 + (2 * length));
+}
+
+/* Format MESSAGE_BUFFER with the results of printing FORMAT with ARG1 and
+   ARG2. */
+static void
+build_message_buffer (format, arg1, arg2)
+     char *format;
+     void *arg1, *arg2;
+{
+  register int i, len;
+  void *args[2];
+  int arg_index = 0;
+
+  args[0] = arg1;
+  args[1] = arg2;
+
+  len = strlen (format);
+
+  message_buffer_resize (len);
+
+  for (i = 0; format[i]; i++)
+    {
+      if (format[i] != '%')
+	{
+	  message_buffer[message_buffer_index++] = format[i];
+	  len--;
+	}
+      else
+	{
+	  char c;
+
+	  c = format[++i];
+
+	  switch (c)
+	    {
+	    case '%':		/* Insert a percent sign. */
+	      message_buffer_resize (len + 1);
+	      message_buffer[message_buffer_index++] = '%';
+	      break;
+
+	    case 's':		/* Insert the current arg as a string. */
+	      {
+		char *string;
+		int string_len;
+
+		string = (char *)args[arg_index++];
+		string_len = strlen (string);
+
+		message_buffer_resize (len + string_len);
+		sprintf
+		  (message_buffer + message_buffer_index, "%s", string);
+		message_buffer_index += string_len;
+	      }
+	      break;
+
+	    case 'd':		/* Insert the current arg as an integer. */
+	      {
+		int integer;
+
+		integer = (int)args[arg_index++];
+
+		message_buffer_resize (len + 32);
+		sprintf
+		  (message_buffer + message_buffer_index, "%d", integer);
+		message_buffer_index = strlen (message_buffer);
+	      }
+	      break;
+
+	    case 'c':		/* Insert the current arg as a character. */
+	      {
+		int character;
+
+		character = (int)args[arg_index++];
+
+		message_buffer_resize (len + 1);
+		message_buffer[message_buffer_index++] = character;
+	      }
+	      break;
+
+	    default:
+	      abort ();
+	    }
+	}
+    }
+  message_buffer[message_buffer_index] = '\0';
+}
+
+/* Build a new node which has FORMAT printed with ARG1 and ARG2 as the
+   contents. */
+NODE *
+build_message_node (format, arg1, arg2)
+     char *format;
+     void *arg1, *arg2;
+{
+  NODE *node;
+
+  message_buffer_index = 0;
+  build_message_buffer (format, arg1, arg2);
+
+  node = message_buffer_to_node ();
+  return (node);
+}
+
+/* Convert the contents of the message buffer to a node. */
+NODE *
+message_buffer_to_node ()
+{
+  NODE *node;
+
+  node = (NODE *)xmalloc (sizeof (NODE));
+  node->filename = (char *)NULL;
+  node->parent = (char *)NULL;
+  node->nodename = (char *)NULL;
+  node->flags = 0;
+
+  /* Make sure that this buffer ends with a newline. */
+  node->nodelen = 1 + strlen (message_buffer);
+  node->contents = (char *)xmalloc (1 + node->nodelen);
+  strcpy (node->contents, message_buffer);
+  node->contents[node->nodelen - 1] = '\n';
+  node->contents[node->nodelen] = '\0';
+  return (node);
+}
+
+/* Useful functions can be called from outside of window.c. */
+void
+initialize_message_buffer ()
+{
+  message_buffer_index = 0;
+}
+
+/* Print FORMAT with ARG1,2 to the end of the current message buffer. */
+void
+printf_to_message_buffer (format, arg1, arg2)
+     char *format;
+     void *arg1, *arg2;
+{
+  build_message_buffer (format, arg1, arg2);
+}
+
+/* Return the current horizontal position of the "cursor" on the most
+   recently output message buffer line. */
+int
+message_buffer_length_this_line ()
+{
+  register int i;
+
+  if (!message_buffer_index)
+    return (0);
+
+  for (i = message_buffer_index; i && message_buffer[i - 1] != '\n'; i--);
+
+  return (string_width (message_buffer + i, 0));
+}
+
+/* Pad STRING to COUNT characters by inserting blanks. */
+int
+pad_to (count, string)
+     int count;
+     char *string;
+{
+  register int i;
+
+  i = strlen (string);
+
+  if (i >= count)
+    string[i++] = ' ';
+  else
+    {
+      while (i < count)
+	string[i++] = ' ';
+    }
+  string[i] = '\0';
+
+  return (i);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/window.h	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,229 @@
+/* window.h -- Structure and flags used in manipulating Info windows. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   Copyright (C) 1993 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (_WINDOW_H_)
+#define _WINDOW_H_
+
+#include "nodes.h"
+#include "infomap.h"
+
+/* Smallest number of visible lines in a window.  The actual height is
+   always one more than this number because each window has a modeline. */
+#define WINDOW_MIN_HEIGHT 2
+
+/* Smallest number of screen lines that can be used to fully present a
+   window.  This number includes the modeline of the window. */
+#define WINDOW_MIN_SIZE (WINDOW_MIN_HEIGHT + 1)
+
+/* The exact same elements are used within the WINDOW_STATE structure and a
+   subsection of the WINDOW structure.  We could define a structure which
+   contains this elements, and include that structure in each of WINDOW_STATE
+   and WINDOW.  But that would lead references in the code such as
+   window->state->node which we would like to avoid.  Instead, we #define the
+   elements here, and simply include the define in both data structures. Thus,
+   if you need to change window state information, here is where you would
+   do it.  NB> The last element does NOT end with a semi-colon. */
+#define WINDOW_STATE_DECL \
+   NODE *node;		/* The node displayed in this window. */ \
+   int pagetop;		/* LINE_STARTS[PAGETOP] is first line in WINDOW. */ \
+   long point		/* Offset within NODE of the cursor position. */
+
+/* Structure which defines a window.  Windows are doubly linked, next
+   and prev. The list of windows is kept on WINDOWS.  The structure member
+   window->height is the total height of the window.  The position location
+   (0, window->height + window->first_row) is the first character of this
+   windows modeline.  The number of lines that can be displayed in a window
+   is equal to window->height - 1. */
+typedef struct __window__ {
+  struct __window__ *next;	/* Next window in this chain. */
+  struct __window__ *prev;	/* Previous window in this chain. */
+  int width;		/* Width of this window. */
+  int height;		/* Height of this window. */
+  int first_row;	/* Offset of the first line in the_screen. */
+  int goal_column;	/* The column we would like the cursor to appear in. */
+  Keymap keymap;	/* Keymap used to read commands in this window. */
+  WINDOW_STATE_DECL;	/* Node, pagetop and point. */
+  char *modeline;	/* Calculated text of the modeline for this window. */
+  char **line_starts;	/* Array of printed line starts for this node. */
+  int line_count;	/* Number of lines appearing in LINE_STARTS. */
+  int flags;		/* See below for details. */
+} WINDOW;
+
+typedef struct {
+  WINDOW_STATE_DECL;		/* What gets saved. */
+} WINDOW_STATE;
+
+#define W_UpdateWindow	0x01	/* WINDOW needs updating. */
+#define W_WindowIsPerm	0x02	/* This WINDOW is a permanent object. */
+#define W_WindowVisible	0x04	/* This WINDOW is currently visible. */
+#define W_InhibitMode	0x08	/* This WINDOW has no modeline. */
+#define W_NoWrap	0x10	/* Lines do not wrap in this window. */
+#define W_InputWindow	0x20	/* Window accepts input. */
+#define W_TempWindow	0x40	/* Window is less important. */
+
+extern WINDOW *windows;		/* List of visible Info windows. */
+extern WINDOW *active_window;	/* The currently active window. */
+extern WINDOW *the_screen;	/* The Info screen is just another window. */
+extern WINDOW *the_echo_area;	/* THE_ECHO_AREA is a window in THE_SCREEN. */
+
+/* Global variable control redisplay of scrolled windows.  If non-zero, it
+   is the desired number of lines to scroll the window in order to make
+   point visible.  A user might set this to 1 for smooth scrolling.  If
+   set to zero, the line containing point is centered within the window. */
+extern int window_scroll_step;
+
+ /* Make the modeline member for WINDOW. */
+extern void window_make_modeline ();
+
+/* Initalize the window system by creating THE_SCREEN and THE_ECHO_AREA.
+   Create the first window ever, and make it permanent.
+   You pass WIDTH and HEIGHT; the dimensions of the total screen size. */
+extern void window_initialize_windows ();
+
+/* Make a new window showing NODE, and return that window structure.
+   The new window is made to be the active window.  If NODE is passed
+   as NULL, then show the node showing in the active window.  If the
+   window could not be made return a NULL pointer.  The active window
+   is not changed.*/
+extern WINDOW *window_make_window ();
+
+/* Delete WINDOW from the list of known windows.  If this window was the
+   active window, make the next window in the chain be the active window,
+   or the previous window in the chain if there is no next window. */
+extern void window_delete_window ();
+
+/* A function to call when the screen changes size, and some windows have
+   to get deleted.  The function is called with the window to be deleted
+   as an argument, and it can't do anything about the window getting deleted;
+   it can only clean up dangling references to that window. */
+extern VFunction *window_deletion_notifier;
+
+/* Set WINDOW to display NODE. */
+extern void window_set_node_of_window ();
+
+/* Tell the window system that the size of the screen has changed.  This
+   causes lots of interesting things to happen.  The permanent windows
+   are resized, as well as every visible window.  You pass WIDTH and HEIGHT;
+   the dimensions of the total screen size. */
+extern void window_new_screen_size ();
+
+/* Change the height of WINDOW by AMOUNT.  This also automagically adjusts
+   the previous and next windows in the chain.  If there is only one user
+   window, then no change takes place. */
+extern void window_change_window_height ();
+
+/* Adjust the pagetop of WINDOW such that the cursor point will be visible. */
+extern void window_adjust_pagetop ();
+
+/* Tile all of the windows currently displayed in the global variable
+   WINDOWS.  If argument DO_INTERNALS is non-zero, tile windows displaying
+   internal nodes as well. */
+#define DONT_TILE_INTERNALS 0
+#define TILE_INTERNALS      1
+extern void window_tile_windows ();
+
+/* Toggle the state of line wrapping in WINDOW.  This can do a bit of fancy
+   redisplay. */
+extern void window_toggle_wrap ();
+
+/* For every window in CHAIN, set the flags member to have FLAG set. */
+extern void window_mark_chain ();
+
+/* For every window in CHAIN, clear the flags member of FLAG. */
+extern void window_unmark_chain ();
+
+/* Make WINDOW start displaying at PERCENT percentage of its node. */
+extern void window_goto_percentage ();
+
+/* Build a new node which has FORMAT printed with ARG1 and ARG2 as the
+   contents. */
+extern NODE *build_message_node ();
+
+/* Useful functions can be called from outside of window.c. */
+extern void initialize_message_buffer ();
+
+/* Print FORMAT with ARG1,2 to the end of the current message buffer. */
+extern void printf_to_message_buffer ();
+
+/* Convert the contents of the message buffer to a node. */
+extern NODE *message_buffer_to_node ();
+
+/* Return the length of the most recently printed line in message buffer. */
+extern int message_buffer_length_this_line ();
+
+/* Pad STRING to COUNT characters by inserting blanks. */
+extern int pad_to ();
+
+/* Make a message appear in the echo area, built from FORMAT, ARG1 and ARG2.
+   The arguments are treated similar to printf () arguments, but not all of
+   printf () hair is present.  The message appears immediately.  If there was
+   already a message appearing in the echo area, it is removed. */
+extern void window_message_in_echo_area ();
+
+/* Place a temporary message in the echo area built from FORMAT, ARG1
+   and ARG2.  The message appears immediately, but does not destroy
+   any existing message.  A future call to unmessage_in_echo_area ()
+   restores the old contents. */
+extern void message_in_echo_area ();
+extern void unmessage_in_echo_area ();
+
+/* Clear the echo area, removing any message that is already present.
+   The echo area is cleared immediately. */
+extern void window_clear_echo_area ();
+
+/* Quickly guess the approximate number of lines to that NODE would
+   take to display.  This really only counts carriage returns. */
+extern int window_physical_lines ();
+
+/* Calculate a list of line starts for the node belonging to WINDOW.  The line
+   starts are pointers to the actual text within WINDOW->NODE. */
+extern void calculate_line_starts ();
+
+/* Given WINDOW, recalculate the line starts for the node it displays. */
+extern void recalculate_line_starts ();
+
+/* Return the number of characters it takes to display CHARACTER on the
+   screen at HPOS. */
+extern int character_width ();
+
+/* Return the number of characters it takes to display STRING on the
+   screen at HPOS. */
+extern int string_width ();
+
+/* Return the index of the line containing point. */
+extern int window_line_of_point ();
+
+/* Get and return the goal column for this window. */
+extern int window_get_goal_column ();
+
+/* Get and return the printed column offset of the cursor in this window. */
+extern int window_get_cursor_column ();
+
+/* Get and Set the node, pagetop, and point of WINDOW. */
+extern void window_get_state (), window_set_state ();
+
+/* Count the number of characters in LINE that precede the printed column
+   offset of GOAL. */
+extern int window_chars_to_goal ();
+
+#endif /* !_WINDOW_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/info/xmalloc.c	Thu Oct 21 22:39:46 1993 +0000
@@ -0,0 +1,78 @@
+/* xmalloc.c -- safe versions of malloc and realloc */
+
+/* This file is part of GNU Info, a program for reading online documentation
+   stored in Info format.
+
+   This file has appeared in prior works by the Free Software Foundation;
+   thus it carries copyright dates from 1988 through 1993.
+
+   Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993 Free Software
+   Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (ALREADY_HAVE_XMALLOC)
+#include <stdio.h>
+
+static void memory_error_and_abort ();
+
+/* **************************************************************** */
+/*								    */
+/*		   Memory Allocation and Deallocation.		    */
+/*								    */
+/* **************************************************************** */
+
+/* Return a pointer to free()able block of memory large enough
+   to hold BYTES number of bytes.  If the memory cannot be allocated,
+   print an error message and abort. */
+void *
+xmalloc (bytes)
+     int bytes;
+{
+  void *temp = (void *)malloc (bytes);
+
+  if (!temp)
+    memory_error_and_abort ("xmalloc");
+  return (temp);
+}
+
+void *
+xrealloc (pointer, bytes)
+     void *pointer;
+     int bytes;
+{
+  void *temp;
+
+  if (!pointer)
+    temp = (void *)malloc (bytes);
+  else
+    temp = (void *)realloc (pointer, bytes);
+
+  if (!temp)
+    memory_error_and_abort ("xrealloc");
+
+  return (temp);
+}
+
+static void
+memory_error_and_abort (fname)
+     char *fname;
+{
+  fprintf (stderr, "%s: Out of virtual memory!\n", fname);
+  abort ();
+}
+#endif /* !ALREADY_HAVE_XMALLOC */