summaryrefslogtreecommitdiff
path: root/info
diff options
context:
space:
mode:
Diffstat (limited to 'info')
-rw-r--r--info/Makefile.am82
-rw-r--r--info/Makefile.in810
-rw-r--r--info/README30
-rw-r--r--info/dir.c290
-rw-r--r--info/display.c526
-rw-r--r--info/display.h79
-rw-r--r--info/doc.h102
-rw-r--r--info/dribble.c67
-rw-r--r--info/dribble.h40
-rw-r--r--info/echo-area.c1525
-rw-r--r--info/echo-area.h84
-rw-r--r--info/filesys.c710
-rw-r--r--info/filesys.h92
-rw-r--r--info/footnotes.c267
-rw-r--r--info/footnotes.h43
-rw-r--r--info/gc.c91
-rw-r--r--info/gc.h36
-rw-r--r--info/indices.c743
-rw-r--r--info/indices.h44
-rw-r--r--info/info-utils.c729
-rw-r--r--info/info-utils.h136
-rw-r--r--info/info.c707
-rw-r--r--info/info.h163
-rw-r--r--info/infodoc.c1193
-rw-r--r--info/infokey.c924
-rw-r--r--info/infokey.h127
-rw-r--r--info/infomap.c1285
-rw-r--r--info/infomap.h81
-rw-r--r--info/key.h34
-rw-r--r--info/m-x.c213
-rw-r--r--info/makedoc.c584
-rw-r--r--info/man.c678
-rw-r--r--info/man.h38
-rw-r--r--info/nodemenu.c346
-rw-r--r--info/nodes.c1269
-rw-r--r--info/nodes.h156
-rw-r--r--info/pcterm.c759
-rw-r--r--info/search.c677
-rw-r--r--info/search.h77
-rw-r--r--info/session.c5455
-rw-r--r--info/session.h249
-rw-r--r--info/signals.c294
-rw-r--r--info/signals.h96
-rw-r--r--info/termdep.h58
-rw-r--r--info/terminal.c872
-rw-r--r--info/terminal.h129
-rw-r--r--info/tilde.c340
-rw-r--r--info/tilde.h53
-rw-r--r--info/variables.c319
-rw-r--r--info/variables.h67
-rw-r--r--info/window.c1958
-rw-r--r--info/window.h270
52 files changed, 25997 insertions, 0 deletions
diff --git a/info/Makefile.am b/info/Makefile.am
new file mode 100644
index 0000000..5a3d35a
--- /dev/null
+++ b/info/Makefile.am
@@ -0,0 +1,82 @@
+# $Id: Makefile.am,v 1.15 2008/03/28 23:39:20 karl Exp $
+# Makefile.am for texinfo/info.
+# Run automake in .. to produce Makefile.in from this.
+#
+# This file is free software; as a special exception the author gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+# Use `ginfo' for building to avoid confusion with the standard `info'
+# target. Removes the `g' in the install rule before applying any
+# user-specified name transformations.
+bin_PROGRAMS = ginfo infokey
+transform = s/ginfo/info/; $(program_transform_name)
+
+localedir = $(datadir)/locale
+infodir2 = $(datadir)/info
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/gnulib/lib \
+ -I$(top_builddir)/gnulib/lib \
+ -DLOCALEDIR=\"$(localedir)\" \
+ -DINFODIR=\"$(infodir)\" \
+ -DINFODIR2=\"$(infodir2)\"
+
+LDADD = ../lib/libtxi.a $(top_builddir)/gnulib/lib/libgnu.a \
+ $(TERMLIBS) $(LIBINTL)
+infokey_LDADD = ../lib/libtxi.a $(top_builddir)/gnulib/lib/libgnu.a $(LIBINTL)
+
+EXTRA_DIST = README pcterm.c
+
+ginfo_SOURCES = dir.c display.c display.h doc.h dribble.c dribble.h \
+ echo-area.c echo-area.h \
+ filesys.c filesys.h footnotes.c footnotes.h gc.c gc.h \
+ indices.c indices.h info-utils.c info-utils.h info.c info.h infodoc.c \
+ infomap.c infomap.h m-x.c man.c man.h nodemenu.c nodes.c nodes.h \
+ search.c search.h session.c session.h signals.c signals.h \
+ termdep.h terminal.c terminal.h tilde.c tilde.h \
+ variables.c variables.h window.c window.h
+nodist_ginfo_SOURCES = doc.c funs.h
+infokey_SOURCES = infokey.c infokey.h key.h
+nodist_infokey_SOURCES = key.c funs.h
+
+# The files `doc.c', `key.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.
+#
+generated_sources = doc.c key.c funs.h
+
+# We need a linear ordering to prevent race condition in parallel make.
+# See the node ``(automake)Multiple Outputs'' for an explanation.
+# Make sure this matches with order in which makedoc.c creates these
+# files.
+doc.c: key.c
+key.c: funs.h
+
+# These files are not distributed.
+DISTCLEANFILES = $(generated_sources)
+
+noinst_PROGRAMS = makedoc
+makedoc_SOURCES = makedoc.c
+
+# Files with Info commands defined that makedoc should read.
+cmd_sources = $(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
+
+# Make the target only funs.h even though we create all the
+# $(generated_sources) so that parallel makes won't do the rm && makedoc
+# more than once.
+funs.h: makedoc$(EXEEXT) $(cmd_sources)
+ rm -f $(generated_sources)
+ $(top_builddir)/$(native_tools)/info/makedoc $(cmd_sources)
+
+# The following hack is necessary to hint make before the automatic
+# dependencies are built.
+BUILT_SOURCES = funs.h
diff --git a/info/Makefile.in b/info/Makefile.in
new file mode 100644
index 0000000..d4fea81
--- /dev/null
+++ b/info/Makefile.in
@@ -0,0 +1,810 @@
+# Makefile.in generated by automake 1.10.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# $Id: Makefile.am,v 1.15 2008/03/28 23:39:20 karl Exp $
+# Makefile.am for texinfo/info.
+# Run automake in .. to produce Makefile.in from this.
+#
+# This file is free software; as a special exception the author gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+VPATH = @srcdir@
+pkgdatadir = $(datadir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+bin_PROGRAMS = ginfo$(EXEEXT) infokey$(EXEEXT)
+noinst_PROGRAMS = makedoc$(EXEEXT)
+subdir = info
+DIST_COMMON = README $(srcdir)/Makefile.am $(srcdir)/Makefile.in
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/gnulib/m4/alloca.m4 \
+ $(top_srcdir)/gnulib/m4/argz.m4 \
+ $(top_srcdir)/gnulib/m4/codeset.m4 \
+ $(top_srcdir)/gnulib/m4/eealloc.m4 \
+ $(top_srcdir)/gnulib/m4/environ.m4 \
+ $(top_srcdir)/gnulib/m4/error.m4 \
+ $(top_srcdir)/gnulib/m4/exitfail.m4 \
+ $(top_srcdir)/gnulib/m4/extensions.m4 \
+ $(top_srcdir)/gnulib/m4/getopt.m4 \
+ $(top_srcdir)/gnulib/m4/gettext.m4 \
+ $(top_srcdir)/gnulib/m4/gettimeofday.m4 \
+ $(top_srcdir)/gnulib/m4/glibc21.m4 \
+ $(top_srcdir)/gnulib/m4/gnulib-common.m4 \
+ $(top_srcdir)/gnulib/m4/gnulib-comp.m4 \
+ $(top_srcdir)/gnulib/m4/iconv.m4 \
+ $(top_srcdir)/gnulib/m4/include_next.m4 \
+ $(top_srcdir)/gnulib/m4/inline.m4 \
+ $(top_srcdir)/gnulib/m4/intlmacosx.m4 \
+ $(top_srcdir)/gnulib/m4/lib-ld.m4 \
+ $(top_srcdir)/gnulib/m4/lib-link.m4 \
+ $(top_srcdir)/gnulib/m4/lib-prefix.m4 \
+ $(top_srcdir)/gnulib/m4/localcharset.m4 \
+ $(top_srcdir)/gnulib/m4/longlong.m4 \
+ $(top_srcdir)/gnulib/m4/malloc.m4 \
+ $(top_srcdir)/gnulib/m4/malloca.m4 \
+ $(top_srcdir)/gnulib/m4/mbchar.m4 \
+ $(top_srcdir)/gnulib/m4/mbiter.m4 \
+ $(top_srcdir)/gnulib/m4/mbrtowc.m4 \
+ $(top_srcdir)/gnulib/m4/mbscasecmp.m4 \
+ $(top_srcdir)/gnulib/m4/mbschr.m4 \
+ $(top_srcdir)/gnulib/m4/mbslen.m4 \
+ $(top_srcdir)/gnulib/m4/mbsncasecmp.m4 \
+ $(top_srcdir)/gnulib/m4/mbsstr.m4 \
+ $(top_srcdir)/gnulib/m4/mbstate_t.m4 \
+ $(top_srcdir)/gnulib/m4/mbswidth.m4 \
+ $(top_srcdir)/gnulib/m4/memchr.m4 \
+ $(top_srcdir)/gnulib/m4/memcmp.m4 \
+ $(top_srcdir)/gnulib/m4/memcpy.m4 \
+ $(top_srcdir)/gnulib/m4/memmem.m4 \
+ $(top_srcdir)/gnulib/m4/memmove.m4 \
+ $(top_srcdir)/gnulib/m4/mempcpy.m4 \
+ $(top_srcdir)/gnulib/m4/mkstemp.m4 \
+ $(top_srcdir)/gnulib/m4/nls.m4 \
+ $(top_srcdir)/gnulib/m4/onceonly.m4 \
+ $(top_srcdir)/gnulib/m4/po.m4 \
+ $(top_srcdir)/gnulib/m4/progtest.m4 \
+ $(top_srcdir)/gnulib/m4/setenv.m4 \
+ $(top_srcdir)/gnulib/m4/stdbool.m4 \
+ $(top_srcdir)/gnulib/m4/stdint.m4 \
+ $(top_srcdir)/gnulib/m4/stdlib_h.m4 \
+ $(top_srcdir)/gnulib/m4/stpcpy.m4 \
+ $(top_srcdir)/gnulib/m4/strdup.m4 \
+ $(top_srcdir)/gnulib/m4/strerror.m4 \
+ $(top_srcdir)/gnulib/m4/string_h.m4 \
+ $(top_srcdir)/gnulib/m4/strndup.m4 \
+ $(top_srcdir)/gnulib/m4/strnlen.m4 \
+ $(top_srcdir)/gnulib/m4/sys_stat_h.m4 \
+ $(top_srcdir)/gnulib/m4/sys_time_h.m4 \
+ $(top_srcdir)/gnulib/m4/tempname.m4 \
+ $(top_srcdir)/gnulib/m4/unistd_h.m4 \
+ $(top_srcdir)/gnulib/m4/wchar.m4 \
+ $(top_srcdir)/gnulib/m4/wchar_t.m4 \
+ $(top_srcdir)/gnulib/m4/wctype.m4 \
+ $(top_srcdir)/gnulib/m4/wcwidth.m4 \
+ $(top_srcdir)/gnulib/m4/wint_t.m4 \
+ $(top_srcdir)/gnulib/m4/xalloc.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+binPROGRAMS_INSTALL = $(INSTALL_PROGRAM)
+PROGRAMS = $(bin_PROGRAMS) $(noinst_PROGRAMS)
+am_ginfo_OBJECTS = dir.$(OBJEXT) display.$(OBJEXT) dribble.$(OBJEXT) \
+ echo-area.$(OBJEXT) filesys.$(OBJEXT) footnotes.$(OBJEXT) \
+ gc.$(OBJEXT) indices.$(OBJEXT) info-utils.$(OBJEXT) \
+ info.$(OBJEXT) infodoc.$(OBJEXT) infomap.$(OBJEXT) \
+ m-x.$(OBJEXT) man.$(OBJEXT) nodemenu.$(OBJEXT) nodes.$(OBJEXT) \
+ search.$(OBJEXT) session.$(OBJEXT) signals.$(OBJEXT) \
+ terminal.$(OBJEXT) tilde.$(OBJEXT) variables.$(OBJEXT) \
+ window.$(OBJEXT)
+nodist_ginfo_OBJECTS = doc.$(OBJEXT)
+ginfo_OBJECTS = $(am_ginfo_OBJECTS) $(nodist_ginfo_OBJECTS)
+ginfo_LDADD = $(LDADD)
+am__DEPENDENCIES_1 =
+ginfo_DEPENDENCIES = ../lib/libtxi.a \
+ $(top_builddir)/gnulib/lib/libgnu.a $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+am_infokey_OBJECTS = infokey.$(OBJEXT)
+nodist_infokey_OBJECTS = key.$(OBJEXT)
+infokey_OBJECTS = $(am_infokey_OBJECTS) $(nodist_infokey_OBJECTS)
+infokey_DEPENDENCIES = ../lib/libtxi.a \
+ $(top_builddir)/gnulib/lib/libgnu.a $(am__DEPENDENCIES_1)
+am_makedoc_OBJECTS = makedoc.$(OBJEXT)
+makedoc_OBJECTS = $(am_makedoc_OBJECTS)
+makedoc_LDADD = $(LDADD)
+makedoc_DEPENDENCIES = ../lib/libtxi.a \
+ $(top_builddir)/gnulib/lib/libgnu.a $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1)
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
+am__depfiles_maybe = depfiles
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+SOURCES = $(ginfo_SOURCES) $(nodist_ginfo_SOURCES) $(infokey_SOURCES) \
+ $(nodist_infokey_SOURCES) $(makedoc_SOURCES)
+DIST_SOURCES = $(ginfo_SOURCES) $(infokey_SOURCES) $(makedoc_SOURCES)
+ETAGS = etags
+CTAGS = ctags
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+transform = s/ginfo/info/; $(program_transform_name)
+ACLOCAL = @ACLOCAL@
+ALLOCA = @ALLOCA@
+ALLOCA_H = @ALLOCA_H@
+AMTAR = @AMTAR@
+ARGZ_H = @ARGZ_H@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BITSIZEOF_PTRDIFF_T = @BITSIZEOF_PTRDIFF_T@
+BITSIZEOF_SIG_ATOMIC_T = @BITSIZEOF_SIG_ATOMIC_T@
+BITSIZEOF_SIZE_T = @BITSIZEOF_SIZE_T@
+BITSIZEOF_WCHAR_T = @BITSIZEOF_WCHAR_T@
+BITSIZEOF_WINT_T = @BITSIZEOF_WINT_T@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+GETOPT_H = @GETOPT_H@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIBC21 = @GLIBC21@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GNULIB_CALLOC_POSIX = @GNULIB_CALLOC_POSIX@
+GNULIB_CHOWN = @GNULIB_CHOWN@
+GNULIB_DUP2 = @GNULIB_DUP2@
+GNULIB_ENVIRON = @GNULIB_ENVIRON@
+GNULIB_FCHDIR = @GNULIB_FCHDIR@
+GNULIB_FTRUNCATE = @GNULIB_FTRUNCATE@
+GNULIB_GETCWD = @GNULIB_GETCWD@
+GNULIB_GETLOGIN_R = @GNULIB_GETLOGIN_R@
+GNULIB_GETPAGESIZE = @GNULIB_GETPAGESIZE@
+GNULIB_GETSUBOPT = @GNULIB_GETSUBOPT@
+GNULIB_LCHOWN = @GNULIB_LCHOWN@
+GNULIB_LSEEK = @GNULIB_LSEEK@
+GNULIB_MALLOC_POSIX = @GNULIB_MALLOC_POSIX@
+GNULIB_MBSCASECMP = @GNULIB_MBSCASECMP@
+GNULIB_MBSCASESTR = @GNULIB_MBSCASESTR@
+GNULIB_MBSCHR = @GNULIB_MBSCHR@
+GNULIB_MBSCSPN = @GNULIB_MBSCSPN@
+GNULIB_MBSLEN = @GNULIB_MBSLEN@
+GNULIB_MBSNCASECMP = @GNULIB_MBSNCASECMP@
+GNULIB_MBSNLEN = @GNULIB_MBSNLEN@
+GNULIB_MBSPBRK = @GNULIB_MBSPBRK@
+GNULIB_MBSPCASECMP = @GNULIB_MBSPCASECMP@
+GNULIB_MBSRCHR = @GNULIB_MBSRCHR@
+GNULIB_MBSSEP = @GNULIB_MBSSEP@
+GNULIB_MBSSPN = @GNULIB_MBSSPN@
+GNULIB_MBSSTR = @GNULIB_MBSSTR@
+GNULIB_MBSTOK_R = @GNULIB_MBSTOK_R@
+GNULIB_MEMMEM = @GNULIB_MEMMEM@
+GNULIB_MEMPCPY = @GNULIB_MEMPCPY@
+GNULIB_MEMRCHR = @GNULIB_MEMRCHR@
+GNULIB_MKDTEMP = @GNULIB_MKDTEMP@
+GNULIB_MKSTEMP = @GNULIB_MKSTEMP@
+GNULIB_PUTENV = @GNULIB_PUTENV@
+GNULIB_RAWMEMCHR = @GNULIB_RAWMEMCHR@
+GNULIB_READLINK = @GNULIB_READLINK@
+GNULIB_REALLOC_POSIX = @GNULIB_REALLOC_POSIX@
+GNULIB_RPMATCH = @GNULIB_RPMATCH@
+GNULIB_SETENV = @GNULIB_SETENV@
+GNULIB_SLEEP = @GNULIB_SLEEP@
+GNULIB_STPCPY = @GNULIB_STPCPY@
+GNULIB_STPNCPY = @GNULIB_STPNCPY@
+GNULIB_STRCASESTR = @GNULIB_STRCASESTR@
+GNULIB_STRCHRNUL = @GNULIB_STRCHRNUL@
+GNULIB_STRDUP = @GNULIB_STRDUP@
+GNULIB_STRERROR = @GNULIB_STRERROR@
+GNULIB_STRNDUP = @GNULIB_STRNDUP@
+GNULIB_STRNLEN = @GNULIB_STRNLEN@
+GNULIB_STRPBRK = @GNULIB_STRPBRK@
+GNULIB_STRSEP = @GNULIB_STRSEP@
+GNULIB_STRSIGNAL = @GNULIB_STRSIGNAL@
+GNULIB_STRSTR = @GNULIB_STRSTR@
+GNULIB_STRTOD = @GNULIB_STRTOD@
+GNULIB_STRTOK_R = @GNULIB_STRTOK_R@
+GNULIB_UNSETENV = @GNULIB_UNSETENV@
+GNULIB_WCWIDTH = @GNULIB_WCWIDTH@
+GREP = @GREP@
+HAVE_CALLOC_POSIX = @HAVE_CALLOC_POSIX@
+HAVE_DECL_ENVIRON = @HAVE_DECL_ENVIRON@
+HAVE_DECL_GETLOGIN_R = @HAVE_DECL_GETLOGIN_R@
+HAVE_DECL_MEMMEM = @HAVE_DECL_MEMMEM@
+HAVE_DECL_MEMRCHR = @HAVE_DECL_MEMRCHR@
+HAVE_DECL_STRDUP = @HAVE_DECL_STRDUP@
+HAVE_DECL_STRERROR = @HAVE_DECL_STRERROR@
+HAVE_DECL_STRNDUP = @HAVE_DECL_STRNDUP@
+HAVE_DECL_STRNLEN = @HAVE_DECL_STRNLEN@
+HAVE_DECL_STRSIGNAL = @HAVE_DECL_STRSIGNAL@
+HAVE_DECL_STRTOK_R = @HAVE_DECL_STRTOK_R@
+HAVE_DECL_WCWIDTH = @HAVE_DECL_WCWIDTH@
+HAVE_DUP2 = @HAVE_DUP2@
+HAVE_FTRUNCATE = @HAVE_FTRUNCATE@
+HAVE_GETPAGESIZE = @HAVE_GETPAGESIZE@
+HAVE_GETSUBOPT = @HAVE_GETSUBOPT@
+HAVE_INTTYPES_H = @HAVE_INTTYPES_H@
+HAVE_ISWCNTRL = @HAVE_ISWCNTRL@
+HAVE_LONG_LONG_INT = @HAVE_LONG_LONG_INT@
+HAVE_LSTAT = @HAVE_LSTAT@
+HAVE_MALLOC_POSIX = @HAVE_MALLOC_POSIX@
+HAVE_MEMPCPY = @HAVE_MEMPCPY@
+HAVE_MKDTEMP = @HAVE_MKDTEMP@
+HAVE_OS_H = @HAVE_OS_H@
+HAVE_RAWMEMCHR = @HAVE_RAWMEMCHR@
+HAVE_READLINK = @HAVE_READLINK@
+HAVE_REALLOC_POSIX = @HAVE_REALLOC_POSIX@
+HAVE_RPMATCH = @HAVE_RPMATCH@
+HAVE_SETENV = @HAVE_SETENV@
+HAVE_SIGNED_SIG_ATOMIC_T = @HAVE_SIGNED_SIG_ATOMIC_T@
+HAVE_SIGNED_WCHAR_T = @HAVE_SIGNED_WCHAR_T@
+HAVE_SIGNED_WINT_T = @HAVE_SIGNED_WINT_T@
+HAVE_SLEEP = @HAVE_SLEEP@
+HAVE_STDINT_H = @HAVE_STDINT_H@
+HAVE_STPCPY = @HAVE_STPCPY@
+HAVE_STPNCPY = @HAVE_STPNCPY@
+HAVE_STRCASESTR = @HAVE_STRCASESTR@
+HAVE_STRCHRNUL = @HAVE_STRCHRNUL@
+HAVE_STRNDUP = @HAVE_STRNDUP@
+HAVE_STRPBRK = @HAVE_STRPBRK@
+HAVE_STRSEP = @HAVE_STRSEP@
+HAVE_STRTOD = @HAVE_STRTOD@
+HAVE_STRUCT_TIMEVAL = @HAVE_STRUCT_TIMEVAL@
+HAVE_SYS_BITYPES_H = @HAVE_SYS_BITYPES_H@
+HAVE_SYS_INTTYPES_H = @HAVE_SYS_INTTYPES_H@
+HAVE_SYS_PARAM_H = @HAVE_SYS_PARAM_H@
+HAVE_SYS_TIME_H = @HAVE_SYS_TIME_H@
+HAVE_SYS_TYPES_H = @HAVE_SYS_TYPES_H@
+HAVE_UNISTD_H = @HAVE_UNISTD_H@
+HAVE_UNSETENV = @HAVE_UNSETENV@
+HAVE_UNSIGNED_LONG_LONG_INT = @HAVE_UNSIGNED_LONG_LONG_INT@
+HAVE_WCHAR_H = @HAVE_WCHAR_H@
+HAVE_WCTYPE_H = @HAVE_WCTYPE_H@
+HAVE_WINT_T = @HAVE_WINT_T@
+HAVE__BOOL = @HAVE__BOOL@
+HELP2MAN = @HELP2MAN@
+HEVEA = @HEVEA@
+INCLUDE_NEXT = @INCLUDE_NEXT@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+LDFLAGS = @LDFLAGS@
+LIBGNU_LIBDEPS = @LIBGNU_LIBDEPS@
+LIBGNU_LTLIBDEPS = @LIBGNU_LTLIBDEPS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LOCALCHARSET_TESTS_ENVIRONMENT = @LOCALCHARSET_TESTS_ENVIRONMENT@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NEXT_STDINT_H = @NEXT_STDINT_H@
+NEXT_STDLIB_H = @NEXT_STDLIB_H@
+NEXT_STRING_H = @NEXT_STRING_H@
+NEXT_SYS_STAT_H = @NEXT_SYS_STAT_H@
+NEXT_SYS_TIME_H = @NEXT_SYS_TIME_H@
+NEXT_UNISTD_H = @NEXT_UNISTD_H@
+NEXT_WCHAR_H = @NEXT_WCHAR_H@
+NEXT_WCTYPE_H = @NEXT_WCTYPE_H@
+OBJEXT = @OBJEXT@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+POSUB = @POSUB@
+PTRDIFF_T_SUFFIX = @PTRDIFF_T_SUFFIX@
+RANLIB = @RANLIB@
+REPLACE_CHOWN = @REPLACE_CHOWN@
+REPLACE_FCHDIR = @REPLACE_FCHDIR@
+REPLACE_GETCWD = @REPLACE_GETCWD@
+REPLACE_GETPAGESIZE = @REPLACE_GETPAGESIZE@
+REPLACE_GETTIMEOFDAY = @REPLACE_GETTIMEOFDAY@
+REPLACE_ISWCNTRL = @REPLACE_ISWCNTRL@
+REPLACE_LCHOWN = @REPLACE_LCHOWN@
+REPLACE_LSEEK = @REPLACE_LSEEK@
+REPLACE_MEMMEM = @REPLACE_MEMMEM@
+REPLACE_MKDIR = @REPLACE_MKDIR@
+REPLACE_MKSTEMP = @REPLACE_MKSTEMP@
+REPLACE_PUTENV = @REPLACE_PUTENV@
+REPLACE_STRCASESTR = @REPLACE_STRCASESTR@
+REPLACE_STRERROR = @REPLACE_STRERROR@
+REPLACE_STRSIGNAL = @REPLACE_STRSIGNAL@
+REPLACE_STRSTR = @REPLACE_STRSTR@
+REPLACE_STRTOD = @REPLACE_STRTOD@
+REPLACE_WCWIDTH = @REPLACE_WCWIDTH@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SIG_ATOMIC_T_SUFFIX = @SIG_ATOMIC_T_SUFFIX@
+SIZE_T_SUFFIX = @SIZE_T_SUFFIX@
+STDBOOL_H = @STDBOOL_H@
+STDINT_H = @STDINT_H@
+STRIP = @STRIP@
+SYS_STAT_H = @SYS_STAT_H@
+SYS_TIME_H = @SYS_TIME_H@
+TERMLIBS = @TERMLIBS@
+TEX = @TEX@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+VOID_UNSETENV = @VOID_UNSETENV@
+WCHAR_H = @WCHAR_H@
+WCHAR_T_SUFFIX = @WCHAR_T_SUFFIX@
+WCTYPE_H = @WCTYPE_H@
+WINT_T_SUFFIX = @WINT_T_SUFFIX@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_CC = @ac_ct_CC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+gl_LIBOBJS = @gl_LIBOBJS@
+gl_LTLIBOBJS = @gl_LTLIBOBJS@
+gltests_LIBOBJS = @gltests_LIBOBJS@
+gltests_LTLIBOBJS = @gltests_LTLIBOBJS@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = $(datadir)/locale
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+native_tools = @native_tools@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+infodir2 = $(datadir)/info
+AM_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/gnulib/lib \
+ -I$(top_builddir)/gnulib/lib \
+ -DLOCALEDIR=\"$(localedir)\" \
+ -DINFODIR=\"$(infodir)\" \
+ -DINFODIR2=\"$(infodir2)\"
+
+LDADD = ../lib/libtxi.a $(top_builddir)/gnulib/lib/libgnu.a \
+ $(TERMLIBS) $(LIBINTL)
+
+infokey_LDADD = ../lib/libtxi.a $(top_builddir)/gnulib/lib/libgnu.a $(LIBINTL)
+EXTRA_DIST = README pcterm.c
+ginfo_SOURCES = dir.c display.c display.h doc.h dribble.c dribble.h \
+ echo-area.c echo-area.h \
+ filesys.c filesys.h footnotes.c footnotes.h gc.c gc.h \
+ indices.c indices.h info-utils.c info-utils.h info.c info.h infodoc.c \
+ infomap.c infomap.h m-x.c man.c man.h nodemenu.c nodes.c nodes.h \
+ search.c search.h session.c session.h signals.c signals.h \
+ termdep.h terminal.c terminal.h tilde.c tilde.h \
+ variables.c variables.h window.c window.h
+
+nodist_ginfo_SOURCES = doc.c funs.h
+infokey_SOURCES = infokey.c infokey.h key.h
+nodist_infokey_SOURCES = key.c funs.h
+
+# The files `doc.c', `key.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.
+#
+generated_sources = doc.c key.c funs.h
+
+# These files are not distributed.
+DISTCLEANFILES = $(generated_sources)
+makedoc_SOURCES = makedoc.c
+
+# Files with Info commands defined that makedoc should read.
+cmd_sources = $(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 following hack is necessary to hint make before the automatic
+# dependencies are built.
+BUILT_SOURCES = funs.h
+all: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \
+ && exit 0; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu info/Makefile'; \
+ cd $(top_srcdir) && \
+ $(AUTOMAKE) --gnu info/Makefile
+.PRECIOUS: Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)"
+ @list='$(bin_PROGRAMS)'; for p in $$list; do \
+ p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \
+ if test -f $$p \
+ ; then \
+ f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \
+ echo " $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(bindir)/$$f'"; \
+ $(INSTALL_PROGRAM_ENV) $(binPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(bindir)/$$f" || exit 1; \
+ else :; fi; \
+ done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; for p in $$list; do \
+ f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \
+ echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \
+ rm -f "$(DESTDIR)$(bindir)/$$f"; \
+ done
+
+clean-binPROGRAMS:
+ -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+
+clean-noinstPROGRAMS:
+ -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS)
+ginfo$(EXEEXT): $(ginfo_OBJECTS) $(ginfo_DEPENDENCIES)
+ @rm -f ginfo$(EXEEXT)
+ $(LINK) $(ginfo_OBJECTS) $(ginfo_LDADD) $(LIBS)
+infokey$(EXEEXT): $(infokey_OBJECTS) $(infokey_DEPENDENCIES)
+ @rm -f infokey$(EXEEXT)
+ $(LINK) $(infokey_OBJECTS) $(infokey_LDADD) $(LIBS)
+makedoc$(EXEEXT): $(makedoc_OBJECTS) $(makedoc_DEPENDENCIES)
+ @rm -f makedoc$(EXEEXT)
+ $(LINK) $(makedoc_OBJECTS) $(makedoc_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dir.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/display.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dribble.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/echo-area.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filesys.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/footnotes.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/indices.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/info-utils.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/info.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/infodoc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/infokey.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/infomap.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/key.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/m-x.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/makedoc.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/man.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nodemenu.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nodes.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/search.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/signals.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/terminal.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tilde.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/variables.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/window.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'`
+
+ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonemtpy = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ mkid -fID $$unique
+tags: TAGS
+
+TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ here=`pwd`; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$tags $$unique; \
+ fi
+ctags: CTAGS
+CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \
+ $(TAGS_FILES) $(LISP)
+ tags=; \
+ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | \
+ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in files) print i; }; }'`; \
+ test -z "$(CTAGS_ARGS)$$tags$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$tags $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && cd $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) $$here
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
+ fi; \
+ cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
+ else \
+ test -f $(distdir)/$$file \
+ || cp -p $$d/$$file $(distdir)/$$file \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ `test -z '$(STRIP)' || \
+ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+ -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-noinstPROGRAMS \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-info: install-info-am
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-ps: install-ps-am
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -rf ./$(DEPDIR)
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \
+ clean-generic clean-noinstPROGRAMS ctags distclean \
+ distclean-compile distclean-generic distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-binPROGRAMS install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-ps install-ps-am \
+ install-strip installcheck installcheck-am installdirs \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic pdf pdf-am ps ps-am \
+ tags uninstall uninstall-am uninstall-binPROGRAMS
+
+
+# We need a linear ordering to prevent race condition in parallel make.
+# See the node ``(automake)Multiple Outputs'' for an explanation.
+# Make sure this matches with order in which makedoc.c creates these
+# files.
+doc.c: key.c
+key.c: funs.h
+
+# Make the target only funs.h even though we create all the
+# $(generated_sources) so that parallel makes won't do the rm && makedoc
+# more than once.
+funs.h: makedoc$(EXEEXT) $(cmd_sources)
+ rm -f $(generated_sources)
+ $(top_builddir)/$(native_tools)/info/makedoc $(cmd_sources)
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/info/README b/info/README
new file mode 100644
index 0000000..4178a01
--- /dev/null
+++ b/info/README
@@ -0,0 +1,30 @@
+$Id: README,v 1.3 2004/04/11 17:56:45 karl Exp $
+texinfo/info/README
+
+ Copyright (C) 2002 Free Software Foundation, Inc.
+
+ Copying and distribution of this file, with or without modification,
+ are permitted in any medium without royalty provided the copyright
+ notice and this notice are preserved.
+
+Info 2.0 is a complete rewrite of the original standalone Info I wrote in
+1987, the first program I wrote for rms. That program was something like
+my second Unix program ever, and my die-hard machine language coding habits
+tended to show through. I found the original Info hard to read and
+maintain, and thus decided to write this one.
+
+The rewrite consists of about 12,000 lines of code written in about 12
+days. I believe this version of Info to be in much better shape than the
+original Info.
+
+Info 2.0 is substantially different from its original standalone
+predecessor. It appears almost identical to the GNU Emacs version, but has
+the advantages of smaller size, ease of portability, and a built in library
+which can be used in other programs (to get or display documentation from
+Info files, for example).
+
+A full listing of the commands available in Info can be gotten by typing
+`?' while within an Info window. This produces a node in a window which
+can be viewed just like any Info node.
+
+--Brian Fox <bfox@gnu.org>
diff --git a/info/dir.c b/info/dir.c
new file mode 100644
index 0000000..8dbc239
--- /dev/null
+++ b/info/dir.c
@@ -0,0 +1,290 @@
+/* dir.c -- how to build a special "dir" node from "localdir" files.
+ $Id: dir.c,v 1.8 2008/06/11 09:55:41 gray Exp $
+
+ Copyright (C) 1993, 1997, 1998, 2004, 2007,
+ 2008 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.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 named in the array
+ dirs_to_add which are found in INFOPATH. */
+
+static void add_menu_to_file_buffer (char *contents, long int size,
+ FILE_BUFFER *fb);
+static void insert_text_into_fb_at_binding (FILE_BUFFER *fb,
+ SEARCH_BINDING *binding, char *text, int textlen);
+void maybe_build_dir_node (char *dirname);
+
+static char *dirs_to_add[] = {
+ "dir", "localdir", NULL
+};
+
+
+/* Return zero if the file represented in the stat structure TEST has
+ already been seen, nonzero else. */
+
+typedef struct
+{
+ dev_t device;
+ ino_t inode;
+} dir_file_list_entry_type;
+
+static int
+new_dir_file_p (struct stat *test)
+{
+ static unsigned dir_file_list_len = 0;
+ static dir_file_list_entry_type *dir_file_list = NULL;
+ unsigned i;
+
+ for (i = 0; i < dir_file_list_len; i++)
+ {
+ dir_file_list_entry_type entry;
+ entry = dir_file_list[i];
+ if (entry.device == test->st_dev && entry.inode == test->st_ino)
+ return 0;
+ }
+
+ dir_file_list_len++;
+ dir_file_list = xrealloc (dir_file_list,
+ dir_file_list_len * sizeof (dir_file_list_entry_type));
+ dir_file_list[dir_file_list_len - 1].device = test->st_dev;
+ dir_file_list[dir_file_list_len - 1].inode = test->st_ino;
+ return 1;
+}
+
+
+void
+maybe_build_dir_node (char *dirname)
+{
+ int path_index, update_tags;
+ char *this_dir;
+ FILE_BUFFER *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;
+
+ /* Initialize the list we use to avoid reading the same dir file twice
+ with the dir file just found. */
+ new_dir_file_p (&dir_buffer->finfo);
+
+ path_index = update_tags = 0;
+
+ /* Using each element of the path, check for one of the files in
+ DIRS_TO_ADD. Do not check for "localdir.info.Z" or anything else.
+ Only files explictly named 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)))
+ {
+ register int da_index;
+ char *from_file;
+
+ /* Expand a leading tilde if one is present. */
+ if (*this_dir == '~')
+ {
+ char *tilde_expanded_dirname;
+
+ tilde_expanded_dirname = tilde_expand_word (this_dir);
+ if (tilde_expanded_dirname != this_dir)
+ {
+ free (this_dir);
+ this_dir = tilde_expanded_dirname;
+ }
+ }
+
+ /* For every different file named in DIRS_TO_ADD found in the
+ search path, add that file's menu to our "dir" node. */
+ for (da_index = 0; (from_file = dirs_to_add[da_index]); da_index++)
+ {
+ struct stat finfo;
+ int statable;
+ int namelen = strlen (from_file);
+ char *fullpath = xmalloc (3 + strlen (this_dir) + namelen);
+
+ strcpy (fullpath, this_dir);
+ if (!IS_SLASH (fullpath[strlen (fullpath) - 1]))
+ strcat (fullpath, "/");
+ strcat (fullpath, from_file);
+
+ statable = (stat (fullpath, &finfo) == 0);
+
+ /* Only add this file if we have not seen it before. */
+ if (statable && S_ISREG (finfo.st_mode) && new_dir_file_p (&finfo))
+ {
+ long filesize;
+ int compressed;
+ char *contents = filesys_read_info_file (fullpath, &filesize,
+ &finfo, &compressed);
+ 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);
+
+ /* Flag that the dir buffer has been built. */
+ dir_buffer->flags |= N_CannotGC;
+}
+
+/* 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 (char *contents, long int 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;
+
+ /* There is a menu in CONTENTS, and contents_offset points to the first
+ character following the menu starter string. Skip all whitespace
+ and newline characters. */
+ contents_offset += skip_whitespace_and_newlines (contents + contents_offset);
+
+ /* 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;
+
+ /* Leave exactly one blank line between directory entries. */
+ {
+ int num_found = 0;
+
+ while ((fb_binding.start > 0) &&
+ (whitespace_or_newline (fb_binding.buffer[fb_binding.start - 1])))
+ {
+ num_found++;
+ fb_binding.start--;
+ }
+
+ /* Optimize if possible. */
+ if (num_found >= 2)
+ {
+ fb_binding.buffer[fb_binding.start++] = '\n';
+ fb_binding.buffer[fb_binding.start++] = '\n';
+ }
+ else
+ {
+ /* Do it the hard way. */
+ insert_text_into_fb_at_binding (fb, &fb_binding, "\n\n", 2);
+ fb_binding.start += 2;
+ }
+ }
+
+ /* Insert the new menu. */
+ insert_text_into_fb_at_binding
+ (fb, &fb_binding, contents + contents_offset, size - contents_offset);
+}
+
+static void
+insert_text_into_fb_at_binding (FILE_BUFFER *fb,
+ SEARCH_BINDING *binding, char *text, int textlen)
+{
+ char *contents;
+ long start, end;
+
+ start = binding->start;
+ end = fb->filesize;
+
+ contents = 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;
+}
diff --git a/info/display.c b/info/display.c
new file mode 100644
index 0000000..28ed5cb
--- /dev/null
+++ b/info/display.c
@@ -0,0 +1,526 @@
+/* display.c -- How to display Info windows.
+ $Id: display.c,v 1.16 2008/06/11 09:55:41 gray Exp $
+
+ Copyright (C) 1993, 1997, 2003, 2004, 2006, 2007, 2008
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Originally written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include "display.h"
+
+extern int info_any_buffered_input_p (void); /* Found in session.c. */
+
+static void free_display (DISPLAY_LINE **display);
+static DISPLAY_LINE **make_display (int width, int height);
+
+void handle_tag (char *tag);
+void handle_tag_start (char *tag);
+void handle_tag_end (char *tag);
+
+/* An array of display lines which tell us what is currently visible on
+ the display. */
+DISPLAY_LINE **the_display = 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 (int width, int 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_LINE **display)
+{
+ register int i;
+
+ for (i = 0; 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)
+{
+ 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);
+}
+
+void
+handle_tag_start (char *tag)
+{
+ /* TODO really handle this tag. */
+ return;
+}
+
+void
+handle_tag_end (char *tag)
+{
+ /* TODO really handle this tag. */
+ return;
+}
+
+void
+handle_tag (char *tag)
+{
+ if (tag[0] == '/')
+ {
+ tag++;
+ handle_tag_end (tag);
+ }
+ else
+ handle_tag_start (tag);
+}
+
+
+struct display_node_closure {
+ WINDOW *win;
+ DISPLAY_LINE **display;
+};
+
+static int
+find_diff (const char *a, size_t alen, const char *b, size_t blen, int *ppos)
+{
+ mbi_iterator_t itra, itrb;
+ int i = 0;
+ int pos = 0;
+
+ for (i = 0, mbi_init (itra, a, alen), mbi_init (itrb, b, blen);
+ mbi_avail (itra) && mbi_avail (itrb);
+ mbi_advance (itra), mbi_advance (itrb))
+ {
+ if (mb_cmp (mbi_cur (itra), mbi_cur (itrb)))
+ break;
+ pos += mb_len (mbi_cur (itra));
+ }
+ *ppos = pos;
+ return i;
+}
+
+int
+display_node_text(void *closure, size_t line_index,
+ const char *src_line,
+ char *printed_line, size_t pl_index, size_t pl_count)
+{
+ struct display_node_closure *dn = closure;
+ WINDOW *win = dn->win;
+ DISPLAY_LINE **display = dn->display;
+ DISPLAY_LINE *entry = display[win->first_row + line_index];
+
+ /* 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. */
+
+ /* If the window is very small, entry might be NULL. */
+ if (entry)
+ {
+ int i, off;
+
+ /* If the screen line is inversed, then we have to clear
+ the line from the screen first. Why, I don't know.
+ (But don't do this if we have no visible entries, as can
+ happen if the window is shrunk very small.) */
+ if (entry->inverse
+ /* Need to erase the line if it has escape sequences. */
+ || (raw_escapes_p && mbschr (entry->text, '\033') != 0))
+ {
+ terminal_goto_xy (0, win->first_row + line_index);
+ terminal_clear_to_eol ();
+ entry->inverse = 0;
+ entry->text[0] = '\0';
+ entry->textlen = 0;
+ }
+
+ i = find_diff (printed_line, pl_index,
+ entry->text, strlen (entry->text), &off);
+
+ /* If the lines are not the same length, or if they differed
+ at all, we must do some redrawing. */
+ if (i != pl_count || pl_count != entry->textlen)
+ {
+ /* Move to the proper point on the terminal. */
+ terminal_goto_xy (i, win->first_row + line_index);
+ /* If there is any text to print, print it. */
+ if (i != pl_count)
+ 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_count < win->width && pl_count < entry->textlen)
+ || entry->inverse)
+ terminal_clear_to_eol ();
+
+ fflush (stdout);
+
+ /* Update the display text buffer. */
+ if (strlen (printed_line) > (unsigned int) screenwidth)
+ /* printed_line[] can include more than screenwidth
+ characters, e.g. if multibyte encoding is used or
+ if we are under -R and there are escape sequences
+ in it. However, entry->text was allocated (in
+ display_initialize_display) for screenwidth
+ bytes only. */
+ entry->text = xrealloc (entry->text, strlen (printed_line) + 1);
+ strcpy (entry->text + off, printed_line + off);
+ entry->textlen = pl_count;
+
+ /* Lines showing node text are not in inverse. Only modelines
+ have that distinction. */
+ entry->inverse = 0;
+ }
+ }
+
+ /* 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 ())
+ {
+ display_was_interrupted_p = 1;
+ return 1;
+ }
+
+ if (line_index + 1 == win->height)
+ return 1;
+
+ return 0;
+}
+
+void
+display_update_one_window (WINDOW *win)
+{
+ size_t line_index = 0;
+ 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.
+ Strictly speaking, it should only be necessary to test if the
+ values are equal to zero, since window_new_screen_size should
+ ensure that the window height/width never becomes negative, but
+ since historically this has often been the culprit for crashes, do
+ our best to be doubly safe. */
+ if (win->height <= 0 || win->width <= 0 || 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;
+
+ if (win->node && win->line_starts)
+ {
+ struct display_node_closure dnc;
+
+ dnc.win = win;
+ dnc.display = the_display;
+
+ line_index = process_node_text (win, win->line_starts[win->pagetop],
+ 1,
+ display_node_text,
+ &dnc);
+ if (display_was_interrupted_p)
+ return;
+ }
+
+ /* 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[win->first_row + line_index];
+
+ /* 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, win->first_row + line_index);
+ 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))
+ {
+ 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);
+ }
+ }
+
+ fflush (stdout);
+
+ /* Okay, this window doesn't need updating anymore. */
+ win->flags &= ~W_UpdateWindow;
+}
+
+/* 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 (int start, int end, int 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 *window, int old_pagetop,
+ char **old_starts, int old_count)
+{
+ 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)
+{
+ 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);
+ fflush (stdout);
+}
+
+/* **************************************************************** */
+/* */
+/* Functions Static to this File */
+/* */
+/* **************************************************************** */
+
+/* Make a DISPLAY_LINE ** with width and height. */
+static DISPLAY_LINE **
+make_display (int width, int height)
+{
+ register int i;
+ DISPLAY_LINE **display;
+
+ display = xmalloc ((1 + height) * sizeof (DISPLAY_LINE *));
+
+ for (i = 0; i < height; i++)
+ {
+ display[i] = xmalloc (sizeof (DISPLAY_LINE));
+ display[i]->text = xmalloc (1 + width);
+ display[i]->textlen = 0;
+ display[i]->inverse = 0;
+ }
+ display[i] = NULL;
+ return display;
+}
+
+/* Free the storage allocated to DISPLAY. */
+static void
+free_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);
+}
+
diff --git a/info/display.h b/info/display.h
new file mode 100644
index 0000000..e92a572
--- /dev/null
+++ b/info/display.h
@@ -0,0 +1,79 @@
+/* display.h -- How the display in Info is done.
+ $Id: display.h,v 1.8 2008/06/09 22:51:20 gray Exp $
+
+ This file is part of GNU Info, a program for reading online documentation
+ stored in Info format.
+
+ Copyright (C) 1993, 1997, 2004, 2007 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#ifndef INFO_DISPLAY_H
+#define INFO_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 (int width, int height);
+
+/* Clear all of the lines in DISPLAY making the screen blank. */
+extern void display_clear_display (DISPLAY_LINE **display);
+
+/* Update the windows pointed to by WINDOWS in THE_DISPLAY. This actually
+ writes the text on the screen. */
+extern void display_update_display (WINDOW *window);
+
+/* Display WIN on THE_DISPLAY. Unlike display_update_display (), this
+ function only does one window. */
+extern void display_update_one_window (WINDOW *win);
+
+/* Move the screen cursor to directly over the current character in WINDOW. */
+extern void display_cursor_at_point (WINDOW *window);
+
+/* 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 (int start, int end, int amount);
+
+/* 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 (WINDOW *window, int old_pagetop,
+ char **old_starts, int old_count);
+
+void handle_tag (char *tag);
+
+#endif /* not INFO_DISPLAY_H */
diff --git a/info/doc.h b/info/doc.h
new file mode 100644
index 0000000..98d1ec6
--- /dev/null
+++ b/info/doc.h
@@ -0,0 +1,102 @@
+/* doc.h -- Structures associating function pointers with documentation.
+ $Id: doc.h,v 1.7 2008/02/26 16:51:05 karl Exp $
+
+ Copyright (C) 1993, 2001, 2004, 2007 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (DOC_H)
+#define DOC_H
+
+#include "info.h" /* for NAMED_FUNCTIONS, VFunction, etc. */
+
+#if defined (INFOKEY)
+/* For each function, we keep track of the first defined key sequence
+ which invokes that function, for each different map. This is so that
+ the dynamic documentation generation in infodoc.c (a) doesn't have to
+ search through copious KEYMAP_ENTRYs, and, more importantly, (b) the
+ user and programmer can choose the preferred key sequence that is
+ printed for any given function -- it's just the first one that
+ appears in the user's infokey file or the default keymaps in
+ infomap.c.
+
+ Each FUNCTION_DOC has a linked list of FUNCTION_KEYSEQ structs
+ hanging off it, which are created on startup when the user and/or
+ default keymaps are being parsed. */
+typedef struct function_keyseq
+{
+ struct function_keyseq *next;
+ struct keymap_entry *map;
+ char *keyseq;
+} FUNCTION_KEYSEQ;
+
+#endif /* INFOKEY */
+
+
+/* An array of FUNCTION_DOC structures is defined in doc.c, which is
+ automagically generated by the makedoc utility, whose job is to scan
+ through the source files for command function declarations and
+ compile a list of all the ones it finds. This saves tedious
+ housekeeping and avoids errors of omission. */
+typedef struct
+{
+ VFunction *func;
+#if defined (NAMED_FUNCTIONS)
+ char *func_name;
+#endif /* NAMED_FUNCTIONS */
+#if defined (INFOKEY)
+ FUNCTION_KEYSEQ *keys;
+#endif /* INFOKEY */
+ char *doc;
+} FUNCTION_DOC;
+
+extern FUNCTION_DOC function_doc_array[];
+
+/* Under the old key-binding system, an info command is specified by
+ the pointer to its function. Under the new INFOKEY binding system,
+ it is specified by a pointer to the command's FUNCTION_DOC structure,
+ defined in doc.c, from which the pointer to the function can be
+ easily divined using the InfoFunction() extractor. */
+#if defined(INFOKEY)
+typedef FUNCTION_DOC InfoCommand;
+/* The cast to VFunction * prevents pgcc from complaining about
+ dereferencing a void *. */
+#define InfoFunction(ic) ((ic) ? (ic)->func : (VFunction *) NULL)
+#define InfoCmd(fn) (&function_doc_array[A_##fn])
+#define DocInfoCmd(fd) ((fd) && (fd)->func ? (fd) : NULL)
+#else /* !INFOKEY */
+typedef VFunction InfoCommand;
+#define InfoFunction(vf) ((vf))
+#define InfoCmd(fn) fn
+#define DocInfoCmd(fd) ((fd)->func)
+#endif /* !INFOKEY */
+
+#include "infomap.h" /* for Keymap. */
+
+#if defined (NAMED_FUNCTIONS)
+extern char *function_name (InfoCommand *cmd);
+extern InfoCommand *named_function (char *name);
+#endif /* NAMED_FUNCTIONS */
+
+extern char *function_documentation (InfoCommand *cmd);
+extern char *key_documentation (char key, Keymap map);
+extern char *pretty_keyname (unsigned char key);
+extern char *pretty_keyseq (char *keyseq);
+extern char *where_is (Keymap map, InfoCommand *cmd);
+extern char *replace_in_documentation (const char *string, int help_is_only_window_p);
+extern void dump_map_to_message_buffer (char *prefix, Keymap map);
+
+#endif /* !DOC_H */
diff --git a/info/dribble.c b/info/dribble.c
new file mode 100644
index 0000000..e77370b
--- /dev/null
+++ b/info/dribble.c
@@ -0,0 +1,67 @@
+/* dribble.c -- dribble files for Info.
+ $Id: dribble.c,v 1.7 2008/06/11 09:55:41 gray Exp $
+
+ Copyright (C) 1993, 1998, 2004, 2007, 2008 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.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 = 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 (char *name)
+{
+ /* Perhaps close existing dribble file. */
+ close_dribble_file ();
+
+ /* Keystrokes can be non-printable characters, so we need binary I/O. */
+ info_dribble_file = fopen (name, FOPEN_WBIN);
+
+#if defined (HAVE_SETVBUF)
+ if (info_dribble_file)
+# if defined (SETVBUF_REVERSED)
+ setvbuf (info_dribble_file, _IONBF, NULL, 1);
+# else
+ setvbuf (info_dribble_file, NULL, _IONBF, 1);
+# endif /* !SETVBUF_REVERSED */
+#endif /* HAVE_SETVBUF */
+}
+
+/* If there is a dribble file already open, close it. */
+void
+close_dribble_file (void)
+{
+ if (info_dribble_file)
+ {
+ fflush (info_dribble_file);
+ fclose (info_dribble_file);
+ info_dribble_file = NULL;
+ }
+}
+
+/* Write some output to our existing dribble file. */
+void
+dribble (unsigned char byte)
+{
+ if (info_dribble_file)
+ fwrite (&byte, sizeof (unsigned char), 1, info_dribble_file);
+}
diff --git a/info/dribble.h b/info/dribble.h
new file mode 100644
index 0000000..5f8fbfa
--- /dev/null
+++ b/info/dribble.h
@@ -0,0 +1,40 @@
+/* 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, 2004, 2007 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ 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 (char *name);
+
+/* If there is a dribble file already open, close it. */
+extern void close_dribble_file (void);
+
+/* Write some output to our existing dribble file. */
+extern void dribble (unsigned char byte);
+
+#endif /* !_DRIBBLE_H_ */
diff --git a/info/echo-area.c b/info/echo-area.c
new file mode 100644
index 0000000..c16ef35
--- /dev/null
+++ b/info/echo-area.c
@@ -0,0 +1,1525 @@
+/* echo-area.c -- how to read a line in the echo area.
+ $Id: echo-area.c,v 1.14 2008/06/11 09:55:41 gray Exp $
+
+ Copyright (C) 1993, 1997, 1998, 1999, 2001, 2004, 2007, 2008
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+
+#if defined (FD_SET)
+# if defined (hpux)
+# define fd_set_cast(x) (int *)(x)
+# else
+# define fd_set_cast(x) (fd_set *)(x)
+# endif /* !hpux */
+#endif /* FD_SET */
+
+/* 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 = 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 const char *input_line_prompt;
+static int input_line_point;
+static int input_line_beg;
+static int input_line_end;
+static NODE input_line_node = {
+ NULL, NULL, NULL, input_line,
+ EA_MAX_INPUT, 0, N_IsInternal
+};
+
+static void echo_area_initialize_node (void);
+static void push_echo_area (void), pop_echo_area (void);
+static int echo_area_stack_contains_completions_p (void);
+
+static void ea_kill_text (int from, int to);
+
+/* Non-zero means we force the user to complete. */
+static int echo_area_must_complete_p = 0;
+static int completions_window_p (WINDOW *window);
+
+/* 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 = NULL;
+
+/* Variables which keep track of the window which was active prior to
+ entering the echo area. */
+static WINDOW *calling_window = NULL;
+static NODE *calling_window_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)
+{
+ /* 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 (void)
+{
+ register WINDOW *win, *compwin = 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 = 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 = 0;
+ int start = 0;
+ int end = 0;
+ int amount = 0;
+
+ 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 = 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 (const 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 (void)
+{
+ char *return_value;
+
+ if (info_aborted_echo_area)
+ {
+ info_aborted_echo_area = 0;
+ return_value = NULL;
+ }
+ else
+ {
+ if (input_line_beg == input_line_end)
+ return_value = xstrdup ("");
+ else
+ {
+ int line_len = input_line_end - input_line_beg;
+ return_value = 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 *window, const 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 (void)
+{
+ register int i;
+
+ for (i = input_line_end; (unsigned int) 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 (void)
+{
+ 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) != input_line_beg)
+ {
+ 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++;
+ window_line_map_init (window);
+}
+
+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 = 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 != (VFunction *) ea_yank) &&
+ (ea_last_executed_command != (VFunction *) 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;
+ }
+ window_line_map_init (window);
+}
+
+/* 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);
+ }
+ window_line_map_init (window);
+}
+
+/* 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 (int from, int 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 = 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 = 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 = 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 = 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 = 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 (void), completions_must_be_rebuilt (void);
+
+/* Variable which holds the output of completions. */
+static NODE *possible_completions_output_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)
+{
+ 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 *window, const 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 = xstrdup (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 (mbscasecmp (completions_found[i]->label, line) == 0)
+ {
+ free (line);
+ line = xstrdup (completions_found[i]->label);
+ break;
+ }
+
+ /* If no match, go back and try again. */
+ if (i == completions_found_index)
+ {
+ if (!completions_found_index)
+ inform_in_echo_area (_("No completions"));
+ else
+ 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 = 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 *window,
+ const 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 *window,
+ const 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, iterations, max_label = 0;
+
+ initialize_message_buffer ();
+ printf_to_message_buffer (completions_found_index == 1
+ ? _("One completion:\n")
+ : _("%d completions:\n"),
+ (void *) (long) completions_found_index,
+ NULL, NULL);
+
+ /* 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? */
+ iterations = (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)
+ iterations = 1;
+
+ /* Print the sorted items, up-and-down alphabetically. */
+ for (i = 0; i < iterations; 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, NULL, NULL);
+
+ if (j + 1 < limit)
+ {
+ for (k = 0; k < max_label - printed_length; k++)
+ printf_to_message_buffer (" ", NULL, NULL, NULL);
+ }
+ }
+ l += iterations;
+ }
+ printf_to_message_buffer ("\n", NULL, NULL, NULL);
+ }
+
+ /* 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 > (iterations * 2)
+ && calling_window->height / 2 >= WINDOW_MIN_SIZE)
+ {
+ int start, pagetop;
+#ifdef SPLIT_BEFORE_ACTIVE
+ int end;
+#endif
+
+ 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 - (iterations + 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 == (VFunction *) 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 = {
+ NULL, NULL, NULL, 0, 0, 0
+};
+
+static void remove_completion_duplicates (void);
+
+/* Variables which remember the state of the most recent call
+ to build_completions (). */
+static char *last_completion_request = NULL;
+static REFERENCE **last_completion_items = NULL;
+
+/* How to tell the completion builder to reset internal state. */
+static void
+completions_must_be_rebuilt (void)
+{
+ maybe_free (last_completion_request);
+ last_completion_request = NULL;
+ last_completion_items = NULL;
+}
+
+/* Build a list of possible completions from echo_area_completion_items,
+ and the contents of input_line. */
+static void
+build_completions (void)
+{
+ 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 = 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 = 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 = NULL;
+
+ for (i = 0; (entry = echo_area_completion_items[i]); i++)
+ {
+ if (mbsncasecmp (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..."),
+ NULL, NULL);
+ }
+ }
+
+ 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 = xmalloc (1 + shortest);
+ /* Since both the sorting done inside remove_completion_duplicates
+ and all the comparisons above are case-insensitive, it's
+ possible that the completion we are going to return is
+ identical to what the user typed but for the letter-case. This
+ is confusing, since the user could type FOOBAR<TAB> and get her
+ string change letter-case for no good reason. So try to find a
+ possible completion whose letter-case is identical, and if so,
+ use that. */
+ if (completions_found_index > 1)
+ {
+ int req_len = strlen (request);
+
+ for (i = 0; i < completions_found_index; i++)
+ if (strncmp (request, completions_found[i]->label, req_len) == 0)
+ break;
+ /* If none of the candidates match exactly, use the first one. */
+ if (i >= completions_found_index)
+ i = 0;
+ }
+ strncpy (LCD_reference.label, completions_found[i]->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 (const void *entry1, const void *entry2)
+{
+ REFERENCE **e1 = (REFERENCE **) entry1;
+ REFERENCE **e2 = (REFERENCE **) entry2;
+
+ return mbscasecmp ((*e1)->label, (*e2)->label);
+}
+
+/* Prune duplicate entries from COMPLETIONS_FOUND. */
+static void
+remove_completion_duplicates (void)
+{
+ 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] = 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 = 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] = 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)
+{
+ /* 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 = NULL;
+}
+
+/* **************************************************************** */
+/* */
+/* Pushing and Popping the Echo Area */
+/* */
+/* **************************************************************** */
+
+/* Push and Pop the echo area. */
+typedef struct {
+ char *line;
+ const char *prompt;
+ REFERENCE **comp_items;
+ int point, beg, end;
+ int must_complete;
+ NODE node;
+ WINDOW *compwin;
+} PUSHED_EA;
+
+static PUSHED_EA **pushed_echo_areas = 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 (void)
+{
+ PUSHED_EA *pushed;
+
+ pushed = xmalloc (sizeof (PUSHED_EA));
+ pushed->line = xstrdup (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 = NULL;
+}
+
+static void
+pop_echo_area (void)
+{
+ 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 = NULL;
+ }
+
+ free (popped);
+}
+
+/* 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 (void)
+{
+ 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 (void)
+{
+#ifdef 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 = 0;
+ ready = select (fileno (stdin) + 1, &readfds, NULL, NULL, &timer);
+#endif /* FD_SET */
+}
+
+/* Print MESSAGE right after the end of the current line, and wait
+ for input or a couple of seconds, whichever comes first. Then flush the
+ informational message that was printed. */
+void
+inform_in_echo_area (const char *message)
+{
+ int i;
+ char *text;
+ int avail = EA_MAX_INPUT + 1 - input_line_end;
+
+ text = xstrdup (message);
+ for (i = 0; text[i] && text[i] != '\n' && i < avail; 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 ();
+}
diff --git a/info/echo-area.h b/info/echo-area.h
new file mode 100644
index 0000000..da397b6
--- /dev/null
+++ b/info/echo-area.h
@@ -0,0 +1,84 @@
+/* echo-area.h -- Functions used in reading information from the echo area.
+ $Id: echo-area.h,v 1.8 2008/02/26 16:51:05 karl Exp $
+
+ This file is part of GNU Info, a program for reading online documentation
+ stored in Info format.
+
+ Copyright (C) 1993, 1997, 2004, 2007 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#ifndef INFO_ECHO_AREA_H
+#define INFO_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 (const char *message);
+extern void echo_area_inform_of_deleted_window (WINDOW *window);
+extern void echo_area_prep_read (void);
+extern VFunction *ea_last_executed_command;
+extern char * info_read_completing_internal (WINDOW *window, const char *prompt,
+ REFERENCE **completions, int force);
+
+/* 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 (WINDOW *window, const char *prompt);
+
+/* 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 (WINDOW *window,
+ const char *prompt, REFERENCE **completions);
+
+/* 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 (WINDOW *window,
+ const char *prompt, REFERENCE **completions);
+
+extern void ea_insert (WINDOW *window, int count, unsigned char key);
+extern void ea_quoted_insert (WINDOW *window, int count, unsigned char key);
+extern void ea_beg_of_line (WINDOW *window, int count, unsigned char key);
+extern void ea_backward (WINDOW *window, int count, unsigned char key);
+extern void ea_delete (WINDOW *window, int count, unsigned char key);
+extern void ea_end_of_line (WINDOW *window, int count, unsigned char key);
+extern void ea_forward (WINDOW *window, int count, unsigned char key);
+extern void ea_abort (WINDOW *window, int count, unsigned char key);
+extern void ea_rubout (WINDOW *window, int count, unsigned char key);
+extern void ea_complete (WINDOW *window, int count, unsigned char key);
+extern void ea_newline (WINDOW *window, int count, unsigned char key);
+extern void ea_kill_line (WINDOW *window, int count, unsigned char key);
+extern void ea_backward_kill_line (WINDOW *window, int count, unsigned char key);
+extern void ea_transpose_chars (WINDOW *window, int count, unsigned char key);
+extern void ea_yank (WINDOW *window, int count, unsigned char key);
+extern void ea_tab_insert (WINDOW *window, int count, unsigned char key);
+extern void ea_possible_completions (WINDOW *window, int count, unsigned char key);
+extern void ea_backward_word (WINDOW *window, int count, unsigned char key);
+extern void ea_kill_word (WINDOW *window, int count, unsigned char key);
+extern void ea_forward_word (WINDOW *window, int count, unsigned char key);
+extern void ea_yank_pop (WINDOW *window, int count, unsigned char key);
+extern void ea_backward_kill_word (WINDOW *window, int count, unsigned char key);
+extern void ea_scroll_completions_window (WINDOW *window, int count,
+ unsigned char key);
+
+#endif /* not INFO_ECHO_AREA_H */
diff --git a/info/filesys.c b/info/filesys.c
new file mode 100644
index 0000000..dcf5c67
--- /dev/null
+++ b/info/filesys.c
@@ -0,0 +1,710 @@
+/* filesys.c -- filesystem specific functions.
+ $Id: filesys.c,v 1.12 2008/06/11 09:55:42 gray Exp $
+
+ Copyright (C) 1993, 1997, 1998, 2000, 2002, 2003, 2004, 2007, 2008
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+
+#include "tilde.h"
+#include "filesys.h"
+
+/* Local to this file. */
+static char *info_file_in_path (char *filename, char *path);
+static char *lookup_info_filename (char *filename);
+static char *info_absolute_file (char *fname);
+
+static void remember_info_filename (char *filename, char *expansion);
+static void maybe_initialize_infopath (void);
+
+typedef struct
+{
+ char *suffix;
+ char *decompressor;
+} COMPRESSION_ALIST;
+
+static char *info_suffixes[] = {
+ ".info",
+ "-info",
+ "/index",
+ ".inf", /* 8+3 file on filesystem which supports long file names */
+#ifdef __MSDOS__
+ /* 8+3 file names strike again... */
+ ".in", /* for .inz, .igz etc. */
+ ".i",
+#endif
+ "",
+ NULL
+};
+
+static COMPRESSION_ALIST compress_suffixes[] = {
+ { ".gz", "gunzip" },
+ { ".bz2", "bunzip2" },
+ { ".lzma", "unlzma" },
+ { ".z", "gunzip" },
+ { ".Z", "uncompress" },
+ { ".Y", "unyabba" },
+#ifdef __MSDOS__
+ { "gz", "gunzip" },
+ { "z", "gunzip" },
+#endif
+ { NULL, 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 = 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 = NULL;
+static int local_temp_filename_size = 0;
+
+char *
+info_find_fullpath (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 (IS_ABSOLUTE (partial))
+ temp = info_absolute_file (partial);
+ else if (initial_character == '~')
+ {
+ expansion = tilde_expand_word (partial);
+ if (IS_ABSOLUTE (expansion))
+ {
+ temp = info_absolute_file (expansion);
+ free (expansion);
+ }
+ else
+ temp = expansion;
+ }
+ else if (initial_character == '.' &&
+ (IS_SLASH (partial[1]) ||
+ (partial[1] == '.' && IS_SLASH (partial[2]))))
+ {
+ if (local_temp_filename_size < 1024)
+ local_temp_filename = 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);
+ temp = info_absolute_file (local_temp_filename); /* try extensions */
+ if (!temp)
+ partial = local_temp_filename;
+ }
+ else
+ temp = info_file_in_path (partial, infopath);
+
+ if (temp)
+ {
+ remember_info_filename (partial, temp);
+ if (strlen (temp) > (unsigned int) local_temp_filename_size)
+ local_temp_filename = 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 (char *filename, char *path)
+{
+ struct stat finfo;
+ char *temp_dirname;
+ int statable, dirname_index;
+
+ /* Reject ridiculous cases up front, to prevent infinite recursion
+ later on. E.g., someone might say "info '(.)foo'"... */
+ if (!*filename || STREQ (filename, ".") || STREQ (filename, ".."))
+ return NULL;
+
+ 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 = xmalloc (30 + strlen (temp_dirname) + strlen (filename));
+ strcpy (temp, temp_dirname);
+ if (!IS_SLASH (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 = xstrdup (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 NULL;
+}
+
+/* Assume FNAME is an absolute file name, and check whether it is
+ a regular file. If it is, return it as a new string; otherwise
+ return a NULL pointer. We do it by taking the file name apart
+ into its directory and basename parts, and calling info_file_in_path.*/
+static char *
+info_absolute_file (char *fname)
+{
+ char *containing_dir = xstrdup (fname);
+ char *base = filename_non_directory (containing_dir);
+
+ if (base > containing_dir)
+ base[-1] = '\0';
+
+ return info_file_in_path (filename_non_directory (fname), containing_dir);
+}
+
+
+/* Given a string containing units of information separated by the
+ PATH_SEP character, return the next one after IDX, or NULL if there
+ are no more. Advance IDX to the character after the colon. */
+
+char *
+extract_colon_unit (char *string, int *idx)
+{
+ unsigned int i = (unsigned int) *idx;
+ unsigned int start = i;
+
+ if (!string || i >= strlen (string))
+ return NULL;
+
+ if (!string[i]) /* end of string */
+ return NULL;
+
+ /* Advance to next PATH_SEP. */
+ while (string[i] && string[i] != PATH_SEP[0])
+ i++;
+
+ {
+ char *value = xmalloc ((i - start) + 1);
+ strncpy (value, &string[start], (i - start));
+ value[i - start] = 0;
+
+ i++; /* move past PATH_SEP */
+ *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 = 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 (char *filename)
+{
+ if (filename && names_and_files)
+ {
+ register int i;
+ for (i = 0; names_and_files[i]; i++)
+ {
+ if (FILENAME_CMP (names_and_files[i]->filename, filename) == 0)
+ return names_and_files[i]->expansion;
+ }
+ }
+ return NULL;
+}
+
+/* Add a filename and its expansion to our list. */
+static void
+remember_info_filename (char *filename, char *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 = xrealloc (names_and_files, alloc_size);
+ }
+
+ new = xmalloc (sizeof (FILENAME_LIST));
+ new->filename = xstrdup (filename);
+ new->expansion = expansion ? xstrdup (expansion) : NULL;
+
+ names_and_files[names_and_files_index++] = new;
+ names_and_files[names_and_files_index] = NULL;
+}
+
+static void
+maybe_initialize_infopath (void)
+{
+ 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 (char *path, int where)
+{
+ int len;
+
+ if (!infopath)
+ {
+ infopath = xmalloc (infopath_size = 200 + strlen (path));
+ infopath[0] = '\0';
+ }
+
+ len = strlen (path) + strlen (infopath);
+
+ if (len + 2 >= infopath_size)
+ infopath = xrealloc (infopath, (infopath_size += (2 * len) + 2));
+
+ if (!*infopath)
+ strcpy (infopath, path);
+ else if (where == INFOPATH_APPEND)
+ {
+ strcat (infopath, PATH_SEP);
+ strcat (infopath, path);
+ }
+ else if (where == INFOPATH_PREPEND)
+ {
+ char *temp = xstrdup (infopath);
+ strcpy (infopath, path);
+ strcat (infopath, PATH_SEP);
+ strcat (infopath, temp);
+ free (temp);
+ }
+}
+
+/* Make INFOPATH have absolutely nothing in it. */
+void
+zap_infopath (void)
+{
+ if (infopath)
+ free (infopath);
+
+ infopath = NULL;
+ infopath_size = 0;
+}
+
+/* Given a chunk of text and its length, convert all CRLF pairs at every
+ end-of-line into a single Newline character. Return the length of
+ produced text.
+
+ This is required because the rest of code is too entrenched in having
+ a single newline at each EOL; in particular, searching for various
+ Info headers and cookies can become extremely tricky if that assumption
+ breaks.
+
+ FIXME: this could also support Mac-style text files with a single CR
+ at the EOL, but what about random CR characters in non-Mac files? Can
+ we afford converting them into newlines as well? Maybe implement some
+ heuristics here, like in Emacs 20.
+
+ FIXME: is it a good idea to show the EOL type on the modeline? */
+long
+convert_eols (char *text, long int textlen)
+{
+ register char *s = text;
+ register char *d = text;
+
+ while (textlen--)
+ {
+ if (*s == '\r' && textlen && s[1] == '\n')
+ {
+ s++;
+ textlen--;
+ }
+ *d++ = *s++;
+ }
+
+ return d - text;
+}
+
+/* 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 turns out to be compressed, set IS_COMPRESSED to non-zero.
+ If the file cannot be read, return a NULL pointer. */
+char *
+filesys_read_info_file (char *pathname, long int *filesize,
+ struct stat *finfo, int *is_compressed)
+{
+ long st_size;
+
+ *filesize = filesys_error_number = 0;
+
+ if (compressed_filename_p (pathname))
+ {
+ *is_compressed = 1;
+ return filesys_read_compressed (pathname, filesize);
+ }
+ else
+ {
+ int descriptor;
+ char *contents;
+
+ *is_compressed = 0;
+ descriptor = open (pathname, O_RDONLY | O_BINARY, 0666);
+
+ /* If the file couldn't be opened, give up. */
+ if (descriptor < 0)
+ {
+ filesys_error_number = errno;
+ return NULL;
+ }
+
+ /* Try to read the contents of this file. */
+ st_size = (long) finfo->st_size;
+ contents = xmalloc (1 + st_size);
+ if ((read (descriptor, contents, st_size)) != st_size)
+ {
+ filesys_error_number = errno;
+ close (descriptor);
+ free (contents);
+ return NULL;
+ }
+
+ close (descriptor);
+
+ /* Convert any DOS-style CRLF EOLs into Unix-style NL.
+ Seems like a good idea to have even on Unix, in case the Info
+ files are coming from some Windows system across a network. */
+ *filesize = convert_eols (contents, st_size);
+
+ /* EOL conversion can shrink the text quite a bit. We don't
+ want to waste storage. */
+ if (*filesize < st_size)
+ contents = xrealloc (contents, 1 + *filesize);
+ contents[*filesize] = '\0';
+
+ 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 (char *pathname, long int *filesize)
+{
+ FILE *stream;
+ char *command, *decompressor;
+ char *contents = NULL;
+
+ *filesize = filesys_error_number = 0;
+
+ decompressor = filesys_decompressor_for_file (pathname);
+
+ if (!decompressor)
+ return NULL;
+
+ command = xmalloc (15 + strlen (pathname) + strlen (decompressor));
+ /* Explicit .exe suffix makes the diagnostics of `popen'
+ better on systems where COMMAND.COM is the stock shell. */
+ sprintf (command, "%s%s < %s",
+ decompressor, STRIP_DOT_EXE ? ".exe" : "", pathname);
+
+#if !defined (BUILDING_LIBRARY)
+ if (info_windows_initialized_p)
+ {
+ char *temp;
+
+ temp = xmalloc (5 + strlen (command));
+ sprintf (temp, "%s...", command);
+ message_in_echo_area ("%s", temp, NULL);
+ free (temp);
+ }
+#endif /* !BUILDING_LIBRARY */
+
+ stream = popen (command, FOPEN_RBIN);
+ free (command);
+
+ /* Read chunks from this file until there are none left to read. */
+ if (stream)
+ {
+ long offset, size;
+ char *chunk;
+
+ offset = size = 0;
+ chunk = 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 = 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);
+ if (pclose (stream) == -1)
+ {
+ if (contents)
+ free (contents);
+ contents = NULL;
+ filesys_error_number = errno;
+ }
+ else
+ {
+ *filesize = convert_eols (contents, offset);
+ contents = xrealloc (contents, 1 + *filesize);
+ contents[*filesize] = '\0';
+ }
+ }
+ 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 (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 (char *filename)
+{
+ register int i;
+ char *extension = 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 NULL;
+
+ for (i = 0; compress_suffixes[i].suffix; i++)
+ if (FILENAME_CMP (extension, compress_suffixes[i].suffix) == 0)
+ return compress_suffixes[i].decompressor;
+
+#if defined (__MSDOS__)
+ /* If no other suffix matched, allow any extension which ends
+ with `z' to be decompressed by gunzip. Due to limited 8+3 DOS
+ file namespace, we can expect many such cases, and supporting
+ every weird suffix thus produced would be a pain. */
+ if (extension[strlen (extension) - 1] == 'z' ||
+ extension[strlen (extension) - 1] == 'Z')
+ return "gunzip";
+#endif
+
+ return NULL;
+}
+
+/* The number of the most recent file system error. */
+int filesys_error_number = 0;
+
+/* A function which returns a pointer to a static buffer containing
+ an error message for FILENAME and ERROR_NUM. */
+static char *errmsg_buf = NULL;
+static int errmsg_buf_size = 0;
+
+char *
+filesys_error_string (char *filename, int error_num)
+{
+ int len;
+ char *result;
+
+ if (error_num == 0)
+ return NULL;
+
+ result = strerror (error_num);
+
+ len = 4 + strlen (filename) + strlen (result);
+ if (len >= errmsg_buf_size)
+ errmsg_buf = xrealloc (errmsg_buf, (errmsg_buf_size = 2 + len));
+
+ sprintf (errmsg_buf, "%s: %s", filename, result);
+ return errmsg_buf;
+}
+
+
+/* Check for "dir" with all the possible info and compression suffixes,
+ in combination. */
+
+int
+is_dir_name (char *filename)
+{
+ unsigned i;
+
+ for (i = 0; info_suffixes[i]; i++)
+ {
+ unsigned c;
+ char trydir[50];
+ strcpy (trydir, "dir");
+ strcat (trydir, info_suffixes[i]);
+
+ if (mbscasecmp (filename, trydir) == 0)
+ return 1;
+
+ for (c = 0; compress_suffixes[c].suffix; c++)
+ {
+ char dir_compressed[50]; /* can be short */
+ strcpy (dir_compressed, trydir);
+ strcat (dir_compressed, compress_suffixes[c].suffix);
+ if (mbscasecmp (filename, dir_compressed) == 0)
+ return 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/info/filesys.h b/info/filesys.h
new file mode 100644
index 0000000..611a709
--- /dev/null
+++ b/info/filesys.h
@@ -0,0 +1,92 @@
+/* filesys.h -- external declarations for filesys.c.
+ $Id: filesys.h,v 1.7 2007/07/01 21:20:29 karl Exp $
+
+ Copyright (C) 1993, 1997, 1998, 2002, 2004, 2005, 2007 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#ifndef INFO_FILESYS_H
+#define INFO_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 (void);
+
+/* 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 (char *path, int where);
+
+/* 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 (char *partial);
+
+/* Given a chunk of text and its length, convert all CRLF pairs at the
+ EOLs into a single Newline character. Return the length of produced
+ text. */
+long convert_eols (char *text, long textlen);
+
+/* 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 (char *pathname, long int *filesize,
+ struct stat *finfo, int *is_compressed);
+
+extern char *filesys_read_compressed (char *pathname, long int *filesize);
+
+/* Return the command string that would be used to decompress FILENAME. */
+extern char *filesys_decompressor_for_file (char *filename);
+extern int compressed_filename_p (char *filename);
+
+/* A function which returns a pointer to a static buffer containing
+ an error message for FILENAME and ERROR_NUM. */
+extern char *filesys_error_string (char *filename, int error_num);
+
+/* 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 (char *string, int *idx);
+
+/* Return true if FILENAME is `dir', with a possible compression suffix. */
+extern int is_dir_name (char *filename);
+
+/* The default value of INFOPATH. */
+#if !defined (DEFAULT_INFOPATH)
+# define DEFAULT_INFOPATH ".:/usr/local/info:/usr/info:/usr/local/lib/info:/usr/lib/info:/usr/local/gnu/info:/usr/local/gnu/lib/info:/usr/gnu/info:/usr/gnu/lib/info:/opt/gnu/info:/usr/share/info:/usr/share/lib/info:/usr/local/share/info:/usr/local/share/lib/info:/usr/gnu/lib/emacs/info:/usr/local/gnu/lib/emacs/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 /* not INFO_FILESYS_H */
diff --git a/info/footnotes.c b/info/footnotes.c
new file mode 100644
index 0000000..552ae2a
--- /dev/null
+++ b/info/footnotes.c
@@ -0,0 +1,267 @@
+/* footnotes.c -- Some functions for manipulating footnotes.
+ $Id: footnotes.c,v 1.9 2008/06/11 09:55:42 gray Exp $
+
+ Copyright (C) 1993, 1997, 1998, 1999, 2002, 2004, 2007,
+ 2008 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Originally written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+
+/* Nonzero means attempt to show footnotes when displaying a new window. */
+int auto_footnotes_p = 0;
+
+static char *footnote_nodename = "*Footnotes*";
+
+NODE * make_footnotes_node (NODE *node);
+
+#define FOOTNOTE_HEADER_FORMAT \
+ "*** Footnotes appearing in the node `%s' ***\n"
+
+/* Find the window currently showing footnotes. */
+static WINDOW *
+find_footnotes_window (void)
+{
+ 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 *fn_node, *result = 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, NULL, 1, 0);
+
+ /* 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;
+ int reflen = strlen ("-Footnotes") + strlen (node->nodename);
+
+ refname = xmalloc (reflen + 1);
+
+ strcpy (refname, node->nodename);
+ strcat (refname, "-Footnotes");
+
+ for (i = 0; refs[i]; i++)
+ if ((refs[i]->nodename != NULL) &&
+ /* Support both the older "foo-Footnotes" and the new
+ style "foo-Footnote-NN" references. */
+ (strcmp (refs[i]->nodename, refname) == 0 ||
+ (strncmp (refs[i]->nodename, refname, reflen - 1) == 0 &&
+ refs[i]->nodename[reflen - 1] == '-' &&
+ isdigit (refs[i]->nodename[reflen]))))
+ {
+ 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 NULL;
+
+ /* Make the new node. */
+ result = xmalloc (sizeof (NODE));
+ result->flags = 0;
+ result->display_pos = 0;
+
+ /* Get the size of the footnotes appearing within this node. */
+ {
+ char *header;
+ long text_start = fn_start;
+
+ header = 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 = 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 *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"))
+{
+ /* 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 (msg_no_foot_node, NULL, NULL);
+ break;
+
+ case FN_UNABLE:
+ info_error (msg_win_too_small, NULL, NULL);
+ break;
+ }
+ }
+}
diff --git a/info/footnotes.h b/info/footnotes.h
new file mode 100644
index 0000000..6a82828
--- /dev/null
+++ b/info/footnotes.h
@@ -0,0 +1,43 @@
+/* footnotes.h -- Some functions for manipulating footnotes.
+ $Id: footnotes.h,v 1.6 2007/07/01 21:20:29 karl Exp $
+
+ Copyright (C) 1993, 1997, 1998, 2002, 2004, 2007
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#ifndef INFO_FOOTNOTES_H
+#define INFO_FOOTNOTES_H
+
+/* Magic string which indicates following text is footnotes. */
+#define FOOTNOTE_LABEL N_("---------- 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 (WINDOW *window);
+
+/* Non-zero means attempt to show footnotes when displaying a new window. */
+extern int auto_footnotes_p;
+
+#endif /* not INFO_FOOTNOTES_H */
diff --git a/info/gc.c b/info/gc.c
new file mode 100644
index 0000000..e883e4b
--- /dev/null
+++ b/info/gc.c
@@ -0,0 +1,91 @@
+/* gc.c -- Functions to remember and garbage collect unused node contents.
+ $Id: gc.c,v 1.7 2008/06/11 09:55:42 gray Exp $
+
+ Copyright (C) 1993, 2004, 2007, 2008 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ 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 = 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 (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 (void)
+{
+ register int i, j, k;
+ INFO_WINDOW *iw;
+ char **new = 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;
+}
diff --git a/info/gc.h b/info/gc.h
new file mode 100644
index 0000000..a35fcac
--- /dev/null
+++ b/info/gc.h
@@ -0,0 +1,36 @@
+/* gc.h -- Functions for garbage collecting unused node contents.
+ $Id: gc.h,v 1.6 2007/07/01 21:20:30 karl Exp $
+
+ This file is part of GNU Info, a program for reading online documentation
+ stored in Info format.
+
+ Copyright (C) 1993, 1997, 2004, 2007 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#ifndef INFO_GC_H
+#define INFO_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 (char *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 (void);
+
+#endif /* not INFO_GC_H */
diff --git a/info/indices.c b/info/indices.c
new file mode 100644
index 0000000..af04cdc
--- /dev/null
+++ b/info/indices.c
@@ -0,0 +1,743 @@
+/* indices.c -- deal with an Info file index.
+ $Id: indices.c,v 1.11 2008/06/11 09:55:42 gray Exp $
+
+ Copyright (C) 1993, 1997, 1998, 1999, 2002, 2003, 2004, 2007, 2008
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Originally 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 = 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 = NULL;
+
+/* A couple of "globals" describing where the initial index was found. */
+static char *initial_index_filename = NULL;
+static char *initial_index_nodename = 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 = 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 (REFERENCE **array, NODE *node)
+{
+ register int i, last;
+ INDEX_NAME_ASSOC *assoc;
+
+ for (last = 0; array[last + 1]; last++);
+ assoc = xmalloc (sizeof (INDEX_NAME_ASSOC));
+ assoc->name = xstrdup (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)
+{
+ 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)
+{
+ register int i;
+ REFERENCE **result = NULL;
+
+ /* No file buffer, no indices. */
+ if (!file_buffer)
+ return NULL;
+
+ /* Reset globals describing where the index was found. */
+ maybe_free (initial_index_filename);
+ maybe_free (initial_index_nodename);
+ initial_index_filename = NULL;
+ initial_index_nodename = 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] = 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 = xstrdup (file_buffer->filename);
+ initial_index_nodename = xstrdup (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 = xstrdup (file_buffer->filename);
+
+ return result;
+}
+
+DECLARE_INFO_COMMAND (info_index_search,
+ _("Look up a string in the index for this file"))
+{
+ do_info_index_search (window, count, 0);
+}
+
+/* Look up SEARCH_STRING in the index for this file. If SEARCH_STRING
+ is NULL, prompt user for input. */
+void
+do_info_index_search (WINDOW *window, int count, char *search_string)
+{
+ 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 = 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 ||
+ (FILENAME_CMP (initial_index_filename, fb->filename) != 0))
+ {
+ info_free_references (index_index);
+ window_message_in_echo_area (_("Finding index entries..."),
+ NULL, NULL);
+ index_index = info_indices_of_file_buffer (fb);
+ }
+
+ /* If there is no index, quit now. */
+ if (!index_index)
+ {
+ info_error (_("No indices found."), NULL, NULL);
+ return;
+ }
+
+ /* Okay, there is an index. Look for SEARCH_STRING, or, if it is
+ empty, prompt for one. */
+ if (search_string && *search_string)
+ line = xstrdup (search_string);
+ else
+ {
+ 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;
+ }
+}
+
+int
+index_entry_exists (WINDOW *window, char *string)
+{
+ register int i;
+ FILE_BUFFER *fb;
+
+ /* If there is no previous search string, the user hasn't built an index
+ yet. */
+ if (!string)
+ return 0;
+
+ fb = file_buffer_of_window (window);
+ if (!initial_index_filename
+ || (FILENAME_CMP (initial_index_filename, fb->filename) != 0))
+ {
+ info_free_references (index_index);
+ index_index = info_indices_of_file_buffer (fb);
+ }
+
+ /* If there is no index, that is an error. */
+ if (!index_index)
+ return 0;
+
+ for (i = 0; (i > -1) && (index_index[i]); i++)
+ if (strcmp (string, index_index[i]->label) == 0)
+ break;
+
+ /* If that failed, look for the next substring match. */
+ if ((i < 0) || (!index_index[i]))
+ {
+ for (i = 0; (i > -1) && (index_index[i]); i++)
+ if (string_in_line (string, index_index[i]->label) != -1)
+ break;
+
+ if ((i > -1) && (index_index[i]))
+ string_in_line (string, index_index[i]->label);
+ }
+
+ /* If that failed, return 0. */
+ if ((i < 0) || (!index_index[i]))
+ return 0;
+
+ return 1;
+}
+
+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."), NULL, NULL);
+ return;
+ }
+
+ /* If there is no index, that is an error. */
+ if (!index_index)
+ {
+ info_error (_("No index entries."), NULL, NULL);
+ 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 ? (char *) _("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;
+ const 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 = xstrdup (index_index[i]->label);
+
+ if (partial && show_index_match)
+ {
+ int k, ls, start, upper;
+
+ ls = strlen (index_search);
+ start = partial - ls;
+ upper = isupper (match[start]) ? 1 : 0;
+
+ for (k = 0; k < ls; k++)
+ if (upper)
+ match[k + start] = info_tolower (match[k + start]);
+ else
+ match[k + start] = info_toupper (match[k + start]);
+ }
+
+ {
+ char *format;
+
+ format = replace_in_documentation
+ (_("Found `%s' in %s. (`\\[next-index-match]' tries to find next.)"),
+ 0);
+
+ window_message_in_echo_area (format, match, (char *) 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 (msg_cant_file_node,
+ index_index[i]->filename, index_index[i]->nodename);
+ return;
+ }
+
+ info_set_node_of_window (1, window, node);
+
+ {
+ long loc;
+ long line = index_index[i]->line_number - 1;
+
+ if (line >= 0 && line < window->line_count)
+ {
+ /* Jump to the line number specified in the index entry. */
+ loc = window->line_starts[line] - window->node->contents;
+ }
+ else
+ {
+ /* Try to find an occurence of LABEL in this node. */
+ long 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 (char *search_string, int inform)
+{
+ register int i, dir_index;
+ REFERENCE **all_indices = NULL;
+ REFERENCE **dir_menu = NULL;
+ NODE *dir_node;
+
+ dir_node = info_get_node ("dir", "Top");
+ if (dir_node)
+ dir_menu = info_menu_of_node (dir_node);
+
+ if (!dir_menu)
+ return NULL;
+
+ /* 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;
+ int dir_node_duplicated = 0;
+
+ this_item = dir_menu[dir_index];
+
+ if (!this_item->filename)
+ {
+ dir_node_duplicated = 1;
+ if (dir_node->parent)
+ this_item->filename = xstrdup (dir_node->parent);
+ else
+ this_item->filename = xstrdup (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)
+ {
+ if (dir_node_duplicated)
+ free (this_item->filename);
+ 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 we already scanned this file, don't do that again.
+ In addition to being faster, this also avoids having
+ multiple identical entries in the *Apropos* menu. */
+ for (i = 0; i < dir_index; i++)
+ if (FILENAME_CMP (this_fb->filename, dir_menu[i]->filename) == 0)
+ break;
+ if (i < dir_index)
+ {
+ if (dir_node_duplicated)
+ free (this_item->filename);
+ continue;
+ }
+
+ if (this_fb && inform)
+ message_in_echo_area (_("Scanning indices of `%s'..."),
+ files_name, NULL);
+
+ 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 = xstrdup (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 = 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 \
+ N_("No available info files have `%s' in their indices.")
+
+void
+info_apropos (char *string)
+{
+ REFERENCE **apropos_list;
+
+ apropos_list = apropos_in_all_indices (string, 0);
+
+ if (!apropos_list)
+ info_error (_(APROPOS_NONE), string, NULL);
+ else
+ {
+ register int i;
+ REFERENCE *entry;
+
+ for (i = 0; (entry = apropos_list[i]); i++)
+ fprintf (stdout, "\"(%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, NULL);
+ else
+ {
+ register int i;
+ char *line_buffer;
+
+ initialize_message_buffer ();
+ printf_to_message_buffer
+ (_("\n* Menu: Nodes whose indices contain `%s':\n"),
+ line, NULL, NULL);
+ line_buffer = xmalloc (500);
+
+ for (i = 0; apropos_list[i]; i++)
+ {
+ int len;
+ /* The label might be identical to that of another index
+ entry in another Info file. Therefore, we make the file
+ name part of the menu entry, to make them all distinct. */
+ sprintf (line_buffer, "* %s [%s]: ",
+ apropos_list[i]->label, apropos_list[i]->filename);
+ len = pad_to (40, line_buffer);
+ sprintf (line_buffer + len, "(%s)%s.",
+ apropos_list[i]->filename, apropos_list[i]->nodename);
+ printf_to_message_buffer ("%s\n", line_buffer, NULL, NULL);
+ }
+ free (line_buffer);
+ }
+
+ apropos_node = message_buffer_to_node ();
+ add_gcable_pointer (apropos_node->contents);
+ 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 (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 ();
+}
diff --git a/info/indices.h b/info/indices.h
new file mode 100644
index 0000000..712fa88
--- /dev/null
+++ b/info/indices.h
@@ -0,0 +1,44 @@
+/* indices.h -- Functions defined in indices.c.
+ $Id: indices.h,v 1.6 2007/07/01 21:20:30 karl Exp $
+
+ This file is part of GNU Info, a program for reading online documentation
+ stored in Info format.
+
+ Copyright (C) 1993, 1997, 2004, 2007 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#ifndef INFO_INDICES_H
+#define INFO_INDICES_H
+
+/* User-visible variable controls the output of info-index-next. */
+extern int show_index_match;
+
+extern REFERENCE **info_indices_of_window (WINDOW *window);
+extern REFERENCE **info_indices_of_file_buffer (FILE_BUFFER *file_buffer);
+extern void info_apropos (char *string);
+
+/* For every menu item in DIR, search the indices of that file for STRING. */
+REFERENCE **apropos_in_all_indices (char *search_string, int inform);
+
+/* User visible functions declared in indices.c. */
+extern void info_index_search (WINDOW *window, int count, unsigned char key);
+extern void info_next_index_match (WINDOW *window, int count, unsigned char key);
+extern void info_index_apropos (WINDOW *window, int count, unsigned char key);
+extern void do_info_index_search (WINDOW *window, int count, char *search_string);
+extern int index_entry_exists (WINDOW *window, char *string);
+
+#endif /* not INFO_INDICES_H */
diff --git a/info/info-utils.c b/info/info-utils.c
new file mode 100644
index 0000000..7236452
--- /dev/null
+++ b/info/info-utils.c
@@ -0,0 +1,729 @@
+/* info-utils.c -- miscellanous.
+ $Id: info-utils.c,v 1.12 2008/06/11 09:55:42 gray Exp $
+
+ Copyright (C) 1993, 1998, 2003, 2004, 2007, 2008
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Originally written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include "info-utils.h"
+#if defined (HANDLE_MAN_PAGES)
+# include "man.h"
+#endif /* HANDLE_MAN_PAGES */
+
+/* When non-zero, various display and input functions handle ISO Latin
+ character sets correctly. */
+int ISO_Latin_p = 1;
+
+/* Variable which holds the most recent filename parsed as a result of
+ calling info_parse_xxx (). */
+char *info_parsed_filename = NULL;
+
+/* Variable which holds the most recent nodename parsed as a result of
+ calling info_parse_xxx (). */
+char *info_parsed_nodename = NULL;
+
+/* Variable which holds the most recent line number parsed as a result of
+ calling info_parse_xxx (). */
+int info_parsed_line_number = 0;
+
+/* Functions to remember a filename or nodename for later return. */
+static void save_filename (char *filename);
+static void saven_filename (char *filename, int len);
+static void save_nodename (char *nodename);
+static void saven_nodename (char *nodename, int len);
+
+/* How to get a reference (either menu or cross). */
+static REFERENCE **info_references_internal (char *label,
+ SEARCH_BINDING *binding);
+
+/* 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 (char *string, int newlines_okay)
+{
+ register int i = 0;
+
+ /* Default the answer. */
+ save_filename (NULL);
+ save_nodename (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 = NULL;
+ }
+
+ /* Parse ``(line ...)'' part of menus, if any. */
+ {
+ char *rest = string + i;
+
+ /* Advance only if it's not already at end of string. */
+ if (*rest)
+ rest++;
+
+ /* Skip any whitespace first, and then a newline in case the item
+ was so long to contain the ``(line ...)'' string in the same
+ physical line. */
+ while (whitespace(*rest))
+ rest++;
+ if (*rest == '\n')
+ {
+ rest++;
+ while (whitespace(*rest))
+ rest++;
+ }
+
+ /* Are we looking at an opening parenthesis? That can only mean
+ we have a winner. :) */
+ if (strncmp (rest, "(line ", strlen ("(line ")) == 0)
+ {
+ rest += strlen ("(line ");
+ info_parsed_line_number = strtol (rest, NULL, 0);
+ }
+ else
+ info_parsed_line_number = 0;
+ }
+}
+
+/* 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 (char *label, NODE *node)
+{
+ register int i;
+ char *nodeline;
+
+ /* Default answer to failure. */
+ save_nodename (NULL);
+ save_filename (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)
+{
+ long position;
+ SEARCH_BINDING tmp_search;
+ REFERENCE **menu = NULL;
+
+ tmp_search.buffer = node->contents;
+ tmp_search.start = 0;
+ tmp_search.end = node->nodelen;
+ tmp_search.flags = S_FoldCase;
+
+ /* Find the start of the menu. */
+ position = search_forward (INFO_MENU_LABEL, &tmp_search);
+
+ if (position == -1)
+ return NULL;
+
+ /* We have the start of the menu now. Glean menu items from the rest
+ of the node. */
+ tmp_search.start = position + strlen (INFO_MENU_LABEL);
+ tmp_search.start += skip_line (tmp_search.buffer + tmp_search.start);
+ tmp_search.start--;
+ menu = info_menu_items (&tmp_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)
+{
+ SEARCH_BINDING tmp_search;
+
+#if defined (HANDLE_MAN_PAGES)
+ if (node->flags & N_IsManPage)
+ return xrefs_of_manpage (node);
+#endif
+
+ tmp_search.buffer = node->contents;
+ tmp_search.start = 0;
+ tmp_search.end = node->nodelen;
+ tmp_search.flags = S_FoldCase;
+
+ return info_xrefs (&tmp_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 (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 (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 (char *label, SEARCH_BINDING *binding)
+{
+ SEARCH_BINDING tmp_search;
+ REFERENCE **refs = NULL;
+ int refs_index = 0, refs_slots = 0;
+ int searching_for_menu_items = 0;
+ long position;
+
+ tmp_search.buffer = binding->buffer;
+ tmp_search.start = binding->start;
+ tmp_search.end = binding->end;
+ tmp_search.flags = S_FoldCase | S_SkipDest;
+
+ searching_for_menu_items = (mbscasecmp (label, INFO_MENU_ENTRY_LABEL) == 0);
+
+ while ((position = search_forward (label, &tmp_search)) != -1)
+ {
+ int offset, start;
+ char *refdef;
+ REFERENCE *entry;
+
+ tmp_search.start = position;
+ tmp_search.start += skip_whitespace (tmp_search.buffer + tmp_search.start);
+ start = tmp_search.start - binding->start;
+ refdef = tmp_search.buffer + tmp_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 = xmalloc (sizeof (REFERENCE));
+ entry->filename = NULL;
+ entry->nodename = NULL;
+ entry->label = xmalloc (offset);
+ strncpy (entry->label, refdef, offset - 1);
+ entry->label[offset - 1] = '\0';
+ canonicalize_whitespace (entry->label);
+ entry->line_number = 0;
+
+ 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 = xstrdup (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 = xstrdup (info_parsed_filename);
+
+ if (info_parsed_nodename)
+ entry->nodename = xstrdup (info_parsed_nodename);
+
+ entry->line_number = info_parsed_line_number;
+ }
+
+ add_pointer_to_array
+ (entry, refs_index, refs, refs_slots, 50, REFERENCE *);
+ }
+ return refs;
+}
+
+/* Get the entry associated with LABEL in REFERENCES. Return a pointer
+ to the ENTRY if found, or NULL. */
+REFERENCE *
+info_get_labeled_reference (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 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 (REFERENCE **ref1, REFERENCE **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 = 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] = NULL;
+ free (ref1);
+ free (ref2);
+ return result;
+}
+
+
+
+/* Copy a reference structure. Since we tend to free everything at
+ every opportunity, we don't share any points, but copy everything into
+ new memory. */
+REFERENCE *
+info_copy_reference (REFERENCE *src)
+{
+ REFERENCE *dest = xmalloc (sizeof (REFERENCE));
+ dest->label = src->label ? xstrdup (src->label) : NULL;
+ dest->filename = src->filename ? xstrdup (src->filename) : NULL;
+ dest->nodename = src->nodename ? xstrdup (src->nodename) : NULL;
+ dest->start = src->start;
+ dest->end = src->end;
+ dest->line_number = 0;
+
+ return dest;
+}
+
+
+
+/* Free the data associated with REFERENCES. */
+void
+info_free_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 (char *string)
+{
+ register int i, j;
+ int len, whitespace_found, whitespace_loc = 0;
+ char *temp;
+
+ if (!string)
+ return;
+
+ len = strlen (string);
+ temp = 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;
+
+ /* Suppress whitespace at start of string. */
+ if (j)
+ 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;
+static size_t the_rep_size;
+
+/* Return a pointer to a string which is the printed representation
+ of CHARACTER if it were printed at HPOS. */
+char *
+printed_representation (const unsigned char *cp, size_t len, size_t hpos,
+ /* Return: */
+ size_t *plen)
+{
+ register int i = 0;
+ int printable_limit = ISO_Latin_p ? 255 : 127;
+#define REPSPACE(s) \
+ do \
+ { \
+ while (the_rep_size < s) \
+ { \
+ if (the_rep_size == 0) \
+ the_rep_size = 8; /* Initial allocation */ \
+ the_rep = x2realloc (the_rep, &the_rep_size); \
+ } \
+ } \
+ while (0)
+
+#define SC(c) \
+ do \
+ { \
+ REPSPACE(i + 1); \
+ the_rep[i++] = c; \
+ } \
+ while (0)
+
+ for (; len > 0; cp++, len--)
+ {
+ if (raw_escapes_p && *cp == '\033')
+ SC(*cp);
+ /* Show CTRL-x as ^X. */
+ else if (iscntrl (*cp) && *cp < 127)
+ {
+ switch (*cp)
+ {
+ case '\r':
+ case '\n':
+ SC(*cp);
+ break;
+
+ case '\t':
+ {
+ int tw;
+
+ tw = ((hpos + 8) & 0xf8) - hpos;
+ while (i < tw)
+ SC(' ');
+ break;
+ }
+
+ default:
+ SC('^');
+ SC(*cp | 0x40);
+ }
+ }
+ /* Show META-x as 0370. */
+ else if (*cp > printable_limit)
+ {
+ REPSPACE (i + 5);
+ sprintf (the_rep + i, "\\%0o", *cp);
+ i = strlen (the_rep);
+ }
+ else if (*cp == DEL)
+ {
+ SC('^');
+ SC('?');
+ }
+ else
+ SC(*cp);
+ }
+
+ SC(0);
+ *plen = i - 1;
+ 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 (char *string, char **string_p, int *string_size_p);
+static void saven_string (char *string, int len, char **string_p,
+ int *string_size_p);
+
+/* Remember FILENAME in PARSED_FILENAME. An empty FILENAME is translated
+ to a NULL pointer in PARSED_FILENAME. */
+static void
+save_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 (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 (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 (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 (char *string, char **string_p, int *string_size_p)
+{
+ if (!string || !*string)
+ {
+ if (*string_p)
+ free (*string_p);
+
+ *string_p = NULL;
+ *string_size_p = 0;
+ }
+ else
+ {
+ if (strlen (string) >= (unsigned int) *string_size_p)
+ *string_p = 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 (char *string, int len, char **string_p, int *string_size_p)
+{
+ if (!string)
+ {
+ if (*string_p)
+ free (*string_p);
+
+ *string_p = NULL;
+ *string_size_p = 0;
+ }
+ else
+ {
+ if (len >= *string_size_p)
+ *string_p = 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 (char *pathname)
+{
+ register char *filename = pathname + strlen (pathname);
+
+ if (HAVE_DRIVE (pathname))
+ pathname += 2;
+
+ while (filename > pathname && !IS_SLASH (filename[-1]))
+ filename--;
+
+ return filename;
+}
+
+/* Return non-zero if NODE is one especially created by Info. */
+int
+internal_info_node_p (NODE *node)
+{
+#if defined (NEVER)
+ if (node &&
+ (node->filename && !*node->filename) &&
+ !node->parent && node->nodename)
+ return 1;
+ else
+ return 0;
+#else
+ return (node != NULL) && ((node->flags & N_IsInternal) != 0);
+#endif /* !NEVER */
+}
+
+/* Make NODE appear to be one especially created by Info. */
+void
+name_internal_node (NODE *node, char *name)
+{
+ if (!node)
+ return;
+
+ node->filename = "";
+ node->parent = 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 (char *name)
+{
+ WINDOW *win;
+
+ for (win = windows; win; win = win->next)
+ if (internal_info_node_p (win->node) &&
+ (strcmp (win->node->nodename, name) == 0))
+ break;
+
+ return win;
+}
+
+/* Return a window displaying the node NODE. */
+WINDOW *
+get_window_of_node (NODE *node)
+{
+ WINDOW *win = NULL;
+
+ for (win = windows; win; win = win->next)
+ if (win->node == node)
+ break;
+
+ return win;
+}
diff --git a/info/info-utils.h b/info/info-utils.h
new file mode 100644
index 0000000..6d8d7ac
--- /dev/null
+++ b/info/info-utils.h
@@ -0,0 +1,136 @@
+/* info-utils.h -- Exported functions and variables from info-utils.c.
+ $Id: info-utils.h,v 1.8 2008/05/10 14:39:04 gray Exp $
+
+ Copyright (C) 1993, 1996, 1998, 2002, 2003, 2004, 2007 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#ifndef INFO_UTILS_H
+#define INFO_UTILS_H
+
+#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. */
+ int line_number; /* Specific line number a menu item points to. */
+} 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 (char *string, int newlines_okay);
+
+/* 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 (NODE *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 (NODE *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 (SEARCH_BINDING *binding);
+
+/* Get the entry associated with LABEL in REFERENCES. Return a pointer to
+ the reference if found, or NULL. */
+extern REFERENCE *info_get_labeled_reference (char *label,
+ REFERENCE **references);
+
+/* 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 (SEARCH_BINDING *binding);
+
+/* 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 (REFERENCE **ref1, REFERENCE **ref2);
+
+/* Copy an existing reference into new memory. */
+extern REFERENCE *info_copy_reference (REFERENCE *src);
+
+/* Free the data associated with REFERENCES. */
+extern void info_free_references (REFERENCE **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 (char *string);
+
+/* Return a pointer to a string which is the printed representation
+ of CHARACTER if it were printed at HPOS. */
+extern char *printed_representation (const unsigned char *cp, size_t len,
+ size_t hpos, size_t *plen);
+
+/* Return a pointer to the part of PATHNAME that simply defines the file. */
+extern char *filename_non_directory (char *pathname);
+
+/* Return non-zero if NODE is one especially created by Info. */
+extern int internal_info_node_p (NODE *node);
+
+/* Make NODE appear to be one especially created by Info, and give it NAME. */
+extern void name_internal_node (NODE *node, char *name);
+
+/* Return the window displaying NAME, the name of an internally created
+ Info window. */
+extern WINDOW *get_internal_info_window (char *name);
+
+/* Return a window displaying the node NODE. */
+extern WINDOW *get_window_of_node (NODE *node);
+
+/* Return the node addressed by LABEL in NODE (usually one of "Prev:",
+ "Next:", "Up:", "File:", or "Node:". After a call to this function,
+ the globals `info_parsed_nodename' and `info_parsed_filename' contain
+ the information. */
+extern void info_parse_label (char *label, NODE *node);
+
+#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_parsed_nodename && !info_parsed_filename) \
+ info_parse_label (INFO_ALTPREV_LABEL, n); \
+ } while (0)
+
+#endif /* not INFO_UTILS_H */
diff --git a/info/info.c b/info/info.c
new file mode 100644
index 0000000..4e204c1
--- /dev/null
+++ b/info/info.c
@@ -0,0 +1,707 @@
+/* info.c -- Display nodes of Info files in multiple windows.
+ $Id: info.c,v 1.33 2008/08/14 17:36:13 karl Exp $
+
+ Copyright (C) 1993, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
+ 2004, 2005, 2007, 2008 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Originally written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include "indices.h"
+#include "dribble.h"
+#include "getopt.h"
+#if defined (HANDLE_MAN_PAGES)
+# include "man.h"
+#endif /* HANDLE_MAN_PAGES */
+
+char *program_name = "info";
+
+/* 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 = NULL;
+
+/* Non-zero means search all indices for INDEX_SEARCH_STRING. Unlike
+ apropos, this puts the user at the node, running info. */
+static int index_search_p = 0;
+
+/* Non-zero means look for the node which describes the invocation
+ and command-line options of the program, and start the info
+ session at that node. */
+static int goto_invocation_p = 0;
+
+/* Variable containing the string to search for when index_search_p is
+ non-zero. */
+static char *index_search_string = 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 = 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 = 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 = 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;
+
+/* Non-zero means make default keybindings be loosely modeled on vi(1). */
+int vi_keys_p = 0;
+
+/* Non-zero means don't remove ANSI escape sequences. */
+int raw_escapes_p = 1;
+
+/* Non-zero means print the absolute location of the file to be loaded. */
+static int print_where_p = 0;
+
+#ifdef __MSDOS__
+/* Non-zero indicates that screen output should be made 'speech-friendly'.
+ Since on MSDOS the usual behavior is to write directly to the video
+ memory, speech synthesizer software cannot grab the output. Therefore,
+ we provide a user option which tells us to avoid direct screen output
+ and use stdout instead (which loses the color output). */
+int speech_friendly = 0;
+#endif
+
+/* 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 DRIBBLE_OPTION 2
+#define RESTORE_OPTION 3
+#define IDXSRCH_OPTION 4
+static struct option long_options[] = {
+ { "apropos", 1, 0, 'k' },
+ { "directory", 1, 0, 'd' },
+ { "dribble", 1, 0, DRIBBLE_OPTION },
+ { "file", 1, 0, 'f' },
+ { "help", 0, &print_help_p, 1 },
+ { "index-search", 1, 0, IDXSRCH_OPTION },
+ { "location", 0, &print_where_p, 1 },
+ { "node", 1, 0, 'n' },
+ { "output", 1, 0, 'o' },
+ { "raw-escapes", 0, &raw_escapes_p, 1 },
+ { "no-raw-escapes", 0, &raw_escapes_p, 0 },
+ { "show-malformed-multibytes", 0, &show_malformed_multibyte_p, 1 },
+ { "no-show-malformed-multibytes", 0, &show_malformed_multibyte_p, 0 },
+ { "restore", 1, 0, RESTORE_OPTION },
+ { "show-options", 0, 0, 'O' },
+ { "subnodes", 0, &dump_subnodes, 1 },
+ { "usage", 0, 0, 'O' },
+ { "version", 0, &print_version_p, 1 },
+ { "vi-keys", 0, &vi_keys_p, 1 },
+ { "where", 0, &print_where_p, 1 },
+#ifdef __MSDOS__
+ { "speech-friendly", 0, &speech_friendly, 1 },
+#endif
+ {NULL, 0, NULL, 0}
+};
+
+/* String describing the shorthand versions of the long options found above. */
+#ifdef __MSDOS__
+static char *short_options = "k:d:n:f:ho:ORswb";
+#else
+static char *short_options = "k:d:n:f:ho:ORws";
+#endif
+
+/* When non-zero, the Info window system has been initialized. */
+int info_windows_initialized_p = 0;
+
+/* Some "forward" declarations. */
+static void info_short_help (void);
+static void init_messages (void);
+
+
+/* **************************************************************** */
+/* */
+/* Main Entry Point to the Info Program */
+/* */
+/* **************************************************************** */
+
+int
+main (int argc, char *argv[])
+{
+ int getopt_long_index; /* Index returned by getopt_long (). */
+ NODE *initial_node; /* First node loaded by Info. */
+
+#ifdef HAVE_SETLOCALE
+ /* Set locale via LC_ALL. */
+ setlocale (LC_ALL, "");
+#endif /* HAVE_SETLOCALE */
+
+#ifdef ENABLE_NLS
+ /* Set the text message domain. */
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+#endif
+
+ init_messages ();
+
+ 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 = xstrdup (optarg);
+ break;
+
+ /* Treat -h like --help. */
+ case 'h':
+ print_help_p = 1;
+ 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 = xstrdup (optarg);
+ break;
+
+ /* User has specified that she wants to find the "Options"
+ or "Invocation" node for the program. */
+ case 'O':
+ goto_invocation_p = 1;
+ break;
+
+ /* User has specified that she wants the escape sequences
+ in man pages to be passed thru unaltered. */
+ case 'R':
+ raw_escapes_p = 1;
+ break;
+
+ /* User is specifying that she wishes to dump the subnodes of
+ the node that she is dumping. */
+ case 's':
+ dump_subnodes = 1;
+ break;
+
+ /* For compatibility with man, -w is --where. */
+ case 'w':
+ print_where_p = 1;
+ break;
+
+#ifdef __MSDOS__
+ /* User wants speech-friendly output. */
+ case 'b':
+ speech_friendly = 1;
+ break;
+#endif /* __MSDOS__ */
+
+ /* User has specified a string to search all indices for. */
+ case 'k':
+ apropos_p = 1;
+ maybe_free (apropos_search_string);
+ apropos_search_string = xstrdup (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;
+
+ /* User has specified a string to search all indices for. */
+ case IDXSRCH_OPTION:
+ index_search_p = 1;
+ maybe_free (index_search_string);
+ index_search_string = xstrdup (optarg);
+ break;
+
+ default:
+ fprintf (stderr, _("Try --help for more information.\n"));
+ xexit (1);
+ }
+ }
+
+ /* If the output device is not a terminal, and no output filename has been
+ specified, make user_output_filename be "-", so that the info is written
+ to stdout, and turn on the dumping of subnodes. */
+ if ((!isatty (fileno (stdout))) && (user_output_filename == NULL))
+ {
+ user_output_filename = xstrdup ("-");
+ dump_subnodes = 1;
+ }
+
+ /* If the user specified --version, then show the version and exit. */
+ if (print_version_p)
+ {
+ printf ("info (GNU %s) %s\n", PACKAGE, VERSION);
+ puts ("");
+ printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
+License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
+This is free software: you are free to change and redistribute it.\n\
+There is NO WARRANTY, to the extent permitted by law.\n"),
+ "2008");
+ xexit (0);
+ }
+
+ /* If the `--help' option was present, show the help and exit. */
+ if (print_help_p)
+ {
+ info_short_help ();
+ xexit (0);
+ }
+
+ /* If the user hasn't specified a path for Info files, default it.
+ Lowest priority is our messy hardwired list in filesys.h.
+ Then comes the user's INFODIR from the Makefile.
+ Highest priority is the environment variable, if set. */
+ if (!infopath)
+ {
+ char *path_from_env = getenv ("INFOPATH");
+
+ if (path_from_env)
+ {
+ unsigned len = strlen (path_from_env);
+ /* Trailing : on INFOPATH means insert the default path. */
+ if (len && path_from_env[len - 1] == PATH_SEP[0])
+ {
+ path_from_env[len - 1] = 0;
+ info_add_path (DEFAULT_INFOPATH, INFOPATH_PREPEND);
+ }
+#ifdef INFODIR /* from the Makefile */
+ info_add_path (INFODIR, INFOPATH_PREPEND);
+#endif
+ info_add_path (path_from_env, INFOPATH_PREPEND);
+ }
+ else
+ {
+ info_add_path (DEFAULT_INFOPATH, INFOPATH_PREPEND);
+#ifdef INFODIR /* from the Makefile */
+ info_add_path (INFODIR, INFOPATH_PREPEND);
+#endif
+#ifdef INFODIR2 /* from the Makefile, too */
+# ifdef INFODIR
+ if (!STREQ (INFODIR, INFODIR2))
+# endif
+ info_add_path (INFODIR2, INFOPATH_PREPEND);
+#endif
+ }
+ }
+
+ /* If the user specified a particular filename, add the path of that
+ file to the contents of INFOPATH. */
+ if (user_filename)
+ add_file_directory_to_path (user_filename);
+
+ /* 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);
+ xexit (0);
+ }
+
+ /* Get the initial Info node. It is either "(dir)Top", or what the user
+ specifed with values in user_filename and user_nodenames. */
+ initial_node = info_get_node (user_filename,
+ user_nodenames ? user_nodenames[0] : 0);
+
+ /* 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, NULL, NULL);
+ else
+ info_error (msg_cant_find_node,
+ user_nodenames ? user_nodenames[0] : "Top", NULL);
+ xexit (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 (print_where_p)
+ printf ("%s\n", user_filename ? user_filename : "unknown?!");
+ else 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);
+
+ xexit (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. */
+ {
+ const char *errstr;
+ char *errarg1, *errarg2;
+ NODE *new_initial_node;
+
+ /* If they say info -O info, we want to show them the invocation node
+ for standalone info; there's nothing useful in info.texi. */
+ if (goto_invocation_p && argv[optind]
+ && mbscasecmp (argv[optind], "info") == 0)
+ argv[optind] = "info-stnd";
+
+ new_initial_node = info_follow_menus (initial_node, argv + optind,
+ &errstr, &errarg1, &errarg2);
+
+ if (new_initial_node && new_initial_node != initial_node)
+ initial_node = new_initial_node;
+
+ if (print_where_p)
+ {
+ if (initial_node->parent)
+ printf ("%s\n", initial_node->parent);
+ else if (initial_node->filename
+ && !is_dir_name (filename_non_directory (initial_node->filename)))
+ printf ("%s\n", initial_node->filename);
+ else
+ xexit (1);
+ xexit (0);
+ }
+
+ /* If the user specified that this node should be output, then do that
+ now. Otherwise, start the Info session with this node. Or act
+ accordingly if the initial node was not found. */
+ if (user_output_filename && !goto_invocation_p)
+ {
+ if (!errstr)
+ dump_node_to_file (initial_node, user_output_filename,
+ dump_subnodes);
+ else
+ info_error (errstr, errarg1, errarg2);
+ }
+ else
+ {
+
+ if (errstr)
+ begin_info_session_with_error (initial_node, errstr,
+ errarg1, errarg2);
+ /* If the user specified `--index-search=STRING' or
+ --show-options, start the info session in the node
+ corresponding to what they want. */
+ else if (index_search_p || goto_invocation_p)
+ {
+ int status = 0;
+
+ initialize_info_session (initial_node, 0);
+
+ if (goto_invocation_p
+ || index_entry_exists (windows, index_search_string))
+ {
+ terminal_prep_terminal ();
+ terminal_clear_screen ();
+ info_last_executed_command = NULL;
+
+ if (index_search_p)
+ do_info_index_search (windows, 0, index_search_string);
+ else
+ {
+ /* If they said "info --show-options foo bar baz",
+ the last of the arguments is the program whose
+ options they want to see. */
+ char **p = argv + optind;
+ char *program;
+
+ if (*p)
+ {
+ while (p[1])
+ p++;
+ program = xstrdup (*p);
+ }
+ else if (user_filename)
+ /* If there's no command-line arguments to
+ supply the program name, use the Info file
+ name (sans extension and leading directories)
+ instead. */
+ program = program_name_from_file_name (user_filename);
+ else
+ program = xstrdup ("");
+
+ info_intuit_options_node (windows, initial_node, program);
+ free (program);
+ }
+
+ if (user_output_filename)
+ {
+ dump_node_to_file (windows->node, user_output_filename,
+ dump_subnodes);
+ }
+ else
+ info_read_and_dispatch ();
+
+ /* On program exit, leave the cursor at the bottom of the
+ window, and restore the terminal IO. */
+ terminal_goto_xy (0, screenheight - 1);
+ terminal_clear_to_eol ();
+ fflush (stdout);
+ terminal_unprep_terminal ();
+ }
+ else
+ {
+ fprintf (stderr, _("no index entries found for `%s'\n"),
+ index_search_string);
+ status = 2;
+ }
+
+ close_dribble_file ();
+ xexit (status);
+ }
+ else
+ begin_info_session (initial_node);
+ }
+
+ xexit (0);
+ }
+
+ return 0; /* Avoid bogus warnings. */
+}
+
+void
+add_file_directory_to_path (char *filename)
+{
+ char *directory_name = xstrdup (filename);
+ char *temp = filename_non_directory (directory_name);
+
+ if (temp != directory_name)
+ {
+ if (HAVE_DRIVE (directory_name) && temp == directory_name + 2)
+ {
+ /* The directory of "d:foo" is stored as "d:.", to avoid
+ mixing it with "d:/" when a slash is appended. */
+ *temp = '.';
+ temp += 2;
+ }
+ temp[-1] = 0;
+ info_add_path (directory_name, INFOPATH_PREPEND);
+ }
+
+ free (directory_name);
+}
+
+
+/* Error handling. */
+
+/* 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 (const char *format, void *arg1, void *arg2)
+{
+ info_error_was_printed = 1;
+
+ if (!info_windows_initialized_p || display_inhibited)
+ {
+ fprintf (stderr, "%s: ", program_name);
+ if (arg1)
+ fprintf (stderr, format, arg1, arg2);
+ else
+ /* If we're passed a string, just print it. Otherwise a % in a
+ filename gets treated as a format specifier. */
+ fputs (format, stderr);
+ 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 = 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 scaled down description of the available options to Info. */
+static void
+info_short_help (void)
+{
+ printf (_("\
+Usage: %s [OPTION]... [MENU-ITEM...]\n\
+\n\
+Read documentation in Info format.\n"), program_name);
+ puts ("");
+
+ puts (_("\
+Options:\n\
+ -k, --apropos=STRING look up STRING in all indices of all manuals.\n\
+ -d, --directory=DIR add DIR to INFOPATH.\n\
+ --dribble=FILENAME remember user keystrokes in FILENAME.\n\
+ -f, --file=FILENAME specify Info file to visit."));
+
+ puts (_("\
+ -h, --help display this help and exit.\n\
+ --index-search=STRING go to node pointed by index entry STRING.\n\
+ -n, --node=NODENAME specify nodes in first visited Info file.\n\
+ -o, --output=FILENAME output selected nodes to FILENAME."));
+
+ puts (_("\
+ -R, --raw-escapes output \"raw\" ANSI escapes (default).\n\
+ --no-raw-escapes output escapes as literal text.\n\
+ --restore=FILENAME read initial keystrokes from FILENAME.\n\
+ -O, --show-options, --usage go to command-line options node."));
+
+#ifdef __MSDOS__
+ puts (_("\
+ -b, --speech-friendly be friendly to speech synthesizers."));
+#endif
+
+ puts (_("\
+ --subnodes recursively output menu items.\n\
+ --vi-keys use vi-like and less-like key bindings.\n\
+ --version display version information and exit.\n\
+ -w, --where, --location print physical location of Info file."));
+
+ puts (_("\n\
+The first non-option argument, if present, is the menu entry to start from;\n\
+it is searched for in all `dir' files along INFOPATH.\n\
+If it is not present, info merges all `dir' files and shows the result.\n\
+Any remaining arguments are treated as the names of menu\n\
+items relative to the initial node visited."));
+
+ puts (_("\n\
+For a summary of key bindings, type h within Info."));
+
+ puts (_("\n\
+Examples:\n\
+ info show top-level dir menu\n\
+ info info show the general manual for Info readers\n\
+ info info-stnd show the manual specific to this Info program\n\
+ info emacs start at emacs node from top-level dir\n\
+ info emacs buffers start at buffers node within emacs manual\n\
+ info --show-options emacs start at node with emacs' command line options\n\
+ info --subnodes -o out.txt emacs dump entire manual to out.txt\n\
+ info -f ./foo.info show file ./foo.info, not searching dir"));
+
+ puts (_("\n\
+Email bug reports to bug-texinfo@gnu.org,\n\
+general questions and discussion to help-texinfo@gnu.org.\n\
+Texinfo home page: http://www.gnu.org/software/texinfo/"));
+
+ xexit (0);
+}
+
+
+/* Initialize strings for gettext. Because gettext doesn't handle N_ or
+ _ within macro definitions, we put shared messages into variables and
+ use them that way. This also has the advantage that there's only one
+ copy of the strings. */
+
+const char *msg_cant_find_node;
+const char *msg_cant_file_node;
+const char *msg_cant_find_window;
+const char *msg_cant_find_point;
+const char *msg_cant_kill_last;
+const char *msg_no_menu_node;
+const char *msg_no_foot_node;
+const char *msg_no_xref_node;
+const char *msg_no_pointer;
+const char *msg_unknown_command;
+const char *msg_term_too_dumb;
+const char *msg_at_node_bottom;
+const char *msg_at_node_top;
+const char *msg_one_window;
+const char *msg_win_too_small;
+const char *msg_cant_make_help;
+
+static void
+init_messages (void)
+{
+ msg_cant_find_node = _("Cannot find node `%s'.");
+ msg_cant_file_node = _("Cannot find node `(%s)%s'.");
+ msg_cant_find_window = _("Cannot find a window!");
+ msg_cant_find_point = _("Point doesn't appear within this window's node!");
+ msg_cant_kill_last = _("Cannot delete the last window.");
+ msg_no_menu_node = _("No menu in this node.");
+ msg_no_foot_node = _("No footnotes in this node.");
+ msg_no_xref_node = _("No cross references in this node.");
+ msg_no_pointer = _("No `%s' pointer for this node.");
+ msg_unknown_command = _("Unknown Info command `%c'; try `?' for help.");
+ msg_term_too_dumb = _("Terminal type `%s' is not smart enough to run Info.");
+ msg_at_node_bottom = _("You are already at the last page of this node.");
+ msg_at_node_top = _("You are already at the first page of this node.");
+ msg_one_window = _("Only one window.");
+ msg_win_too_small = _("Resulting window would be too small.");
+ msg_cant_make_help = _("Not enough room for a help window, please delete a window.");
+}
diff --git a/info/info.h b/info/info.h
new file mode 100644
index 0000000..8a3d0c0
--- /dev/null
+++ b/info/info.h
@@ -0,0 +1,163 @@
+/* info.h -- Header file which includes all of the other headers.
+ $Id: info.h,v 1.9 2008/05/10 14:39:05 gray Exp $
+
+ Copyright (C) 1993, 1997, 1998, 1999, 2001, 2002, 2003, 2004, 2007
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#ifndef INFO_H
+#define INFO_H
+
+/* We always want these, so why clutter up the compile command? */
+#define HANDLE_MAN_PAGES
+#define NAMED_FUNCTIONS
+#define INFOKEY
+
+/* System dependencies. */
+#include "system.h"
+
+/* Some of our other include files use these. */
+typedef int Function ();
+typedef void VFunction ();
+typedef char *CFunction ();
+
+#include "filesys.h"
+#include "doc.h"
+#include "display.h"
+#include "session.h"
+#include "echo-area.h"
+#include "footnotes.h"
+#include "gc.h"
+
+#include "string.h"
+#include "mbiter.h"
+#include "mbchar.h"
+
+#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 */
+
+/* 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 (zero_mem) && defined (HAVE_MEMSET)
+# define zero_mem(mem, length) memset (mem, 0, length)
+#endif /* !zero_mem && HAVE_MEMSET */
+
+#if !defined (zero_mem) && defined (HAVE_BZERO)
+# define zero_mem(mem, length) bzero (mem, length)
+#endif /* !zero_mem && HAVE_BZERO */
+
+#if !defined (zero_mem)
+# define zero_mem(mem, length) \
+ do { \
+ register int zi; \
+ register unsigned char *place; \
+ \
+ place = (unsigned char *)mem; \
+ for (zi = 0; zi < length; zi++) \
+ place[zi] = 0; \
+ } while (0)
+#endif /* !zero_mem */
+
+
+/* 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;
+
+/* Non-zero means default keybindings are loosely modeled on vi(1). */
+extern int vi_keys_p;
+
+/* Non-zero means don't remove ANSI escape sequences from man pages. */
+extern int raw_escapes_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 (const char *format, void *arg1, void *arg2);
+
+extern void add_file_directory_to_path (char *filename);
+
+/* Error message defines. */
+extern const char *msg_cant_find_node;
+extern const char *msg_cant_file_node;
+extern const char *msg_cant_find_window;
+extern const char *msg_cant_find_point;
+extern const char *msg_cant_kill_last;
+extern const char *msg_no_menu_node;
+extern const char *msg_no_foot_node;
+extern const char *msg_no_xref_node;
+extern const char *msg_no_pointer;
+extern const char *msg_unknown_command;
+extern const char *msg_term_too_dumb;
+extern const char *msg_at_node_bottom;
+extern const char *msg_at_node_top;
+extern const char *msg_one_window;
+extern const char *msg_win_too_small;
+extern const char *msg_cant_make_help;
+
+
+#if defined(INFOKEY)
+/* Found in variables.c. */
+extern void set_variable_to_value (char *name, char *value);
+#endif /* INFOKEY */
+
+/* Found in m-x.c. */
+extern char *read_function_name (const char *prompt, WINDOW *window);
+
+#endif /* !INFO_H */
diff --git a/info/infodoc.c b/info/infodoc.c
new file mode 100644
index 0000000..706d53f
--- /dev/null
+++ b/info/infodoc.c
@@ -0,0 +1,1193 @@
+/* infodoc.c -- functions which build documentation nodes.
+ $Id: infodoc.c,v 1.26 2008/06/11 09:55:42 gray Exp $
+
+ Copyright (C) 1993, 1997, 1998, 1999, 2001, 2002, 2003, 2004, 2006,
+ 2007, 2008 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include "funs.h"
+
+/* HELP_NODE_GETS_REGENERATED is always defined now that keys may get
+ rebound, or other changes in the help text may occur. */
+#define HELP_NODE_GETS_REGENERATED 1
+
+/* 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 = NULL;
+
+/* A pointer to the contents of the help node. */
+static char *internal_info_help_node_contents = NULL;
+
+/* The (more or less) static text which appears in the internal info
+ help node. The actual key bindings are inserted. Keep the
+ underlines (****, etc.) in the same N_ call as the text lines they
+ refer to, so translations can make the number of *'s or -'s match. */
+#if defined(INFOKEY)
+
+static char *info_internal_help_text[] = {
+ N_("Basic Info command keys\n"),
+ "\n",
+ N_("\\%-10[quit-help] Close this help window.\n"),
+ N_("\\%-10[quit] Quit Info altogether.\n"),
+ N_("\\%-10[get-info-help-node] Invoke the Info tutorial.\n"),
+ "\n",
+ N_("\\%-10[prev-line] Move up one line.\n"),
+ N_("\\%-10[next-line] Move down one line.\n"),
+ N_("\\%-10[scroll-backward] Scroll backward one screenful.\n"),
+ N_("\\%-10[scroll-forward] Scroll forward one screenful.\n"),
+ N_("\\%-10[beginning-of-node] Go to the beginning of this node.\n"),
+ N_("\\%-10[end-of-node] Go to the end of this node.\n"),
+ "\n",
+ N_("\\%-10[move-to-next-xref] Skip to the next hypertext link.\n"),
+ N_("\\%-10[select-reference-this-line] Follow the hypertext link under the cursor.\n"),
+ N_("\\%-10[history-node] Go back to the last node seen in this window.\n"),
+ "\n",
+ N_("\\%-10[global-prev-node] Go to the previous node in the document.\n"),
+ N_("\\%-10[global-next-node] Go to the next node in the document.\n"),
+ N_("\\%-10[prev-node] Go to the previous node on this level.\n"),
+ N_("\\%-10[next-node] Go to the next node on this level.\n"),
+ N_("\\%-10[up-node] Go up one level.\n"),
+ N_("\\%-10[top-node] Go to the top node of this document.\n"),
+ N_("\\%-10[dir-node] Go to the main `directory' node.\n"),
+ "\n",
+ N_("1...9 Pick the first...ninth item in this node's menu.\n"),
+ N_("\\%-10[last-menu-item] Pick the last item in this node's menu.\n"),
+ N_("\\%-10[menu-item] Pick a menu item specified by name.\n"),
+ N_("\\%-10[xref-item] Follow a cross reference specified by name.\n"),
+ N_("\\%-10[goto-node] Go to a node specified by name.\n"),
+ "\n",
+ N_("\\%-10[search] Search forward for a specified string.\n"),
+ N_("\\%-10[search-previous] Search for previous occurrence.\n"),
+ N_("\\%-10[search-next] Search for next occurrence.\n"),
+ N_("\\%-10[index-search] Search for a specified string in the index, and\n\
+ select the node referenced by the first entry found.\n"),
+ N_("\\%-10[abort-key] Cancel the current operation.\n"),
+ "\n",
+ NULL
+};
+
+#else /* !INFOKEY */
+
+static char *info_internal_help_text[] = {
+ N_("Basic Commands in Info Windows\n\
+******************************\n"),
+ "\n",
+ N_(" %-10s Quit this help.\n"),
+ N_(" %-10s Quit Info altogether.\n"),
+ N_(" %-10s Invoke the Info tutorial.\n"),
+ "\n",
+ N_("Selecting other nodes:\n\
+----------------------\n",
+ N_(" %-10s Move to the `next' node of this node.\n"),
+ N_(" %-10s Move to the `previous' node of this node.\n"),
+ N_(" %-10s Move `up' from this node.\n"),
+ N_(" %-10s Pick menu item specified by name.\n\
+ Picking a menu item causes another node to be selected.\n"),
+ N_(" %-10s Follow a cross reference. Reads name of reference.\n"),
+ N_(" %-10s Move to the last node seen in this window.\n"),
+ N_(" %-10s Skip to next hypertext link within this node.\n"),
+ N_(" %-10s Follow the hypertext link under cursor.\n"),
+ N_(" %-10s Move to the `directory' node. Equivalent to `g (DIR)'.\n"),
+ N_(" %-10s Move to the Top node. Equivalent to `g Top'.\n"),
+ "\n",
+ N_("Moving within a node:\n\
+---------------------\n"),
+ N_(" %-10s Scroll forward a page.\n"),
+ N_(" %-10s Scroll backward a page.\n"),
+ N_(" %-10s Go to the beginning of this node.\n"),
+ N_(" %-10s Go to the end of this node.\n"),
+ N_(" %-10s Scroll forward 1 line.\n"),
+ N_(" %-10s Scroll backward 1 line.\n"),
+ "\n",
+ N_("Other commands:\n\
+---------------\n"),
+ N_(" %-10s Pick first...ninth item in node's menu.\n"),
+ N_(" %-10s Pick last item in node's menu.\n"),
+ /* The next four strings are each a unity, so they each need to be
+ kept as one string for the translators. */
+ N_(" %-10s Search for a specified string in the index entries of this Info\n\
+ file, and select the node referenced by the first entry found.\n"),
+ N_(" %-10s Move to node specified by name.\n\
+ You may include a filename as well, as in (FILENAME)NODENAME.\n"),
+ N_(" %-10s Search forward for a specified string,\n\
+ and select the node in which the next occurrence is found.\n"),
+ N_(" %-10s Search backward for a specified string,\n\
+ and select the node in which the next occurrence is found.\n"),
+ NULL
+};
+
+static char *info_help_keys_text[][2] = {
+ { "", "" },
+ { "", "" },
+ { "", "" },
+ { "CTRL-x 0", "CTRL-x 0" },
+ { "q", "q" },
+ { "h", "ESC h" },
+ { "", "" },
+ { "", "" },
+ { "", "" },
+ { "SPC", "SPC" },
+ { "DEL", "b" },
+ { "b", "ESC b" },
+ { "e", "ESC e" },
+ { "ESC 1 SPC", "RET" },
+ { "ESC 1 DEL", "y" },
+ { "", "" },
+ { "", "" },
+ { "", "" },
+ { "n", "CTRL-x n" },
+ { "p", "CTRL-x p" },
+ { "u", "CTRL-x u" },
+ { "m", "ESC m" },
+ { "", "" },
+ { "f", "ESC f" },
+ { "l", "l" },
+ { "TAB", "TAB" },
+ { "RET", "CTRL-x RET" },
+ { "d", "ESC d" },
+ { "t", "ESC t" },
+ { "", "" },
+ { "", "" },
+ { "", "" },
+ { "1-9", "ESC 1-9" },
+ { "0", "ESC 0" },
+ { "i", "CTRL-x i" },
+ { "", "" },
+ { "g", "CTRL-x g" },
+ { "", "" },
+ { "s", "/" },
+ { "", "" },
+ { "ESC - s", "?" },
+ { "", "" },
+ NULL
+};
+
+#endif /* !INFOKEY */
+
+static char *where_is_internal (Keymap map, InfoCommand *cmd);
+
+void
+dump_map_to_message_buffer (char *prefix, Keymap map)
+{
+ register int i;
+ unsigned prefix_len = strlen (prefix);
+ char *new_prefix = xmalloc (prefix_len + 2);
+
+ strncpy (new_prefix, prefix, prefix_len);
+ new_prefix[prefix_len + 1] = '\0';
+
+ for (i = 0; i < 256; i++)
+ {
+ new_prefix[prefix_len] = i;
+ if (map[i].type == ISKMAP)
+ {
+ dump_map_to_message_buffer (new_prefix, (Keymap)map[i].function);
+ }
+ 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 .. ", pretty_keyseq (new_prefix),
+ NULL, NULL);
+ new_prefix[prefix_len] = last - 1;
+ printf_to_message_buffer ("%s\t", pretty_keyseq (new_prefix),
+ NULL, NULL);
+ i = last - 1;
+ }
+ else
+ printf_to_message_buffer ("%s\t", pretty_keyseq (new_prefix),
+ NULL, NULL);
+
+#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, NULL, NULL);
+ length_so_far = message_buffer_length_this_line ();
+
+ if ((desired_doc_start + strlen (doc))
+ >= (unsigned int) the_screen->width)
+ printf_to_message_buffer ("\n ", NULL, NULL, NULL);
+ else
+ {
+ while (length_so_far < desired_doc_start)
+ {
+ printf_to_message_buffer ("\t", NULL, NULL, NULL);
+ length_so_far += character_width ('\t', length_so_far);
+ }
+ }
+ }
+#endif /* NAMED_FUNCTIONS */
+ printf_to_message_buffer ("%s\n", doc, NULL, NULL);
+ }
+ }
+ free (new_prefix);
+}
+
+/* How to create internal_info_help_node. HELP_IS_ONLY_WINDOW_P says
+ whether we're going to end up in a second (or more) window of our
+ own, or whether there's only one window and we're going to usurp it.
+ This determines how to quit the help window. Maybe we should just
+ make q do the right thing in both cases. */
+
+static void
+create_internal_info_help_node (int help_is_only_window_p)
+{
+ register int i;
+ NODE *node;
+ char *contents = NULL;
+ char *exec_keys;
+
+#ifndef HELP_NODE_GETS_REGENERATED
+ if (internal_info_help_node_contents)
+ contents = internal_info_help_node_contents;
+#endif /* !HELP_NODE_GETS_REGENERATED */
+
+ if (!contents)
+ {
+ int printed_one_mx = 0;
+
+ initialize_message_buffer ();
+
+ for (i = 0; info_internal_help_text[i]; i++)
+ {
+#ifdef INFOKEY
+ printf_to_message_buffer (replace_in_documentation
+ (_(info_internal_help_text[i]), help_is_only_window_p),
+ NULL, NULL, NULL);
+#else
+ /* Don't translate blank lines, gettext outputs the po file
+ header in that case. We want a blank line. */
+ char *msg = *(info_internal_help_text[i])
+ ? _(info_internal_help_text[i])
+ : info_internal_help_text[i];
+ char *key = info_help_keys_text[i][vi_keys_p];
+
+ /* If we have only one window (because the window size was too
+ small to split it), CTRL-x 0 doesn't work to `quit' help. */
+ if (STREQ (key, "CTRL-x 0") && help_is_only_window_p)
+ key = "l";
+
+ printf_to_message_buffer (msg, key, NULL, NULL);
+#endif /* !INFOKEY */
+ }
+
+ printf_to_message_buffer ("---------------------\n", NULL, NULL, NULL);
+ printf_to_message_buffer (_("The current search path is:\n"),
+ NULL, NULL, NULL);
+ printf_to_message_buffer ("%s\n", infopath, NULL, NULL);
+ printf_to_message_buffer ("---------------------\n\n", NULL, NULL, NULL);
+ printf_to_message_buffer (_("Commands available in Info windows:\n\n"),
+ NULL, NULL, NULL);
+ dump_map_to_message_buffer ("", info_keymap);
+ printf_to_message_buffer ("---------------------\n\n", NULL, NULL, NULL);
+ printf_to_message_buffer (_("Commands available in the echo area:\n\n"),
+ NULL, NULL, NULL);
+ dump_map_to_message_buffer ("", echo_area_keymap);
+
+#if defined (NAMED_FUNCTIONS)
+ /* Get a list of commands which have no keystroke equivs. */
+ exec_keys = where_is (info_keymap, InfoCmd(info_execute_command));
+ if (exec_keys)
+ exec_keys = xstrdup (exec_keys);
+ for (i = 0; function_doc_array[i].func; i++)
+ {
+ InfoCommand *cmd = DocInfoCmd(&function_doc_array[i]);
+
+ if (InfoFunction(cmd) != (VFunction *) info_do_lowercase_version
+ && !where_is_internal (info_keymap, cmd)
+ && !where_is_internal (echo_area_keymap, cmd))
+ {
+ if (!printed_one_mx)
+ {
+ printf_to_message_buffer ("---------------------\n\n",
+ NULL, NULL, NULL);
+ if (exec_keys && exec_keys[0])
+ printf_to_message_buffer
+ (_("The following commands can only be invoked via %s:\n\n"),
+ exec_keys, NULL, NULL);
+ else
+ printf_to_message_buffer
+ (_("The following commands cannot be invoked at all:\n\n"),
+ NULL, NULL, NULL);
+ printed_one_mx = 1;
+ }
+
+ printf_to_message_buffer
+ ("%s %s\n %s\n",
+ exec_keys,
+ function_doc_array[i].func_name,
+ replace_in_documentation (strlen (function_doc_array[i].doc)
+ ? _(function_doc_array[i].doc) : "", 0)
+ );
+
+ }
+ }
+
+ if (printed_one_mx)
+ printf_to_message_buffer ("\n", NULL, NULL, NULL);
+
+ maybe_free (exec_keys);
+#endif /* NAMED_FUNCTIONS */
+
+ node = message_buffer_to_node ();
+ internal_info_help_node_contents = node->contents;
+ }
+ else
+ {
+ /* We already had the right contents, so simply use them. */
+ node = build_message_node ("", 0, 0);
+ free (node->contents);
+ node->contents = contents;
+ node->nodelen = 1 + strlen (contents);
+ }
+
+ internal_info_help_node = node;
+
+ /* Do not GC this node's contents. It never changes, and we never need
+ to delete it once it is made. If you change some things (such as
+ placing information about dynamic variables in the help text) then
+ you will need to allow the contents to be gc'd, and you will have to
+ arrange to always regenerate the help node. */
+#if defined (HELP_NODE_GETS_REGENERATED)
+ add_gcable_pointer (internal_info_help_node->contents);
+#endif
+
+ 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. */
+
+/* If the eligible window's height is >= this, split it to make the help
+ window. Otherwise display the help window in the current window. */
+#define HELP_SPLIT_SIZE 24
+
+static WINDOW *
+info_find_or_create_help_window (void)
+{
+ int help_is_only_window_p;
+ WINDOW *eligible = NULL;
+ WINDOW *help_window = get_window_of_node (internal_info_help_node);
+
+ /* If we couldn't find the help window, then make it. */
+ if (!help_window)
+ {
+ WINDOW *window;
+ int max = 0;
+
+ for (window = windows; window; window = window->next)
+ {
+ if (window->height > max)
+ {
+ max = window->height;
+ eligible = window;
+ }
+ }
+
+ if (!eligible)
+ return NULL;
+ }
+#ifndef HELP_NODE_GETS_REGENERATED
+ else
+ /* help window is static, just return it. */
+ return help_window;
+#endif /* not HELP_NODE_GETS_REGENERATED */
+
+ /* Make sure that we have a node containing the help text. The
+ argument is false if help will be the only window (so l must be used
+ to quit help), true if help will be one of several visible windows
+ (so CTRL-x 0 must be used to quit help). */
+ help_is_only_window_p = ((help_window && !windows->next)
+ || (!help_window && eligible->height < HELP_SPLIT_SIZE));
+ create_internal_info_help_node (help_is_only_window_p);
+
+ /* Either use the existing window to display the help node, or create
+ a new window if there was no existing help window. */
+ if (!help_window)
+ { /* Split the largest window into 2 windows, and show the help text
+ in that window. */
+ if (eligible->height >= HELP_SPLIT_SIZE)
+ {
+ 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;
+ }
+ }
+ else
+ { /* Case where help node always gets regenerated, and we have an
+ existing window in which to place the node. */
+ if (active_window != help_window)
+ {
+ set_remembered_pagetop_and_point (active_window);
+ active_window = help_window;
+ }
+ window_set_node_of_window (active_window, internal_info_help_node);
+ }
+ 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 (msg_cant_make_help, NULL, NULL);
+ }
+}
+
+/* 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 &&
+ (mbscasecmp
+ (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, NULL, NULL);
+ else
+ info_error (msg_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 (InfoCommand *cmd)
+{
+ char *doc;
+
+#if defined (INFOKEY)
+
+ doc = cmd->doc;
+
+#else /* !INFOKEY */
+
+ register int i;
+
+ for (i = 0; function_doc_array[i].func; i++)
+ if (InfoFunction(cmd) == function_doc_array[i].func)
+ break;
+
+ doc = function_doc_array[i].func ? function_doc_array[i].doc : "";
+
+#endif /* !INFOKEY */
+
+ return replace_in_documentation ((strlen (doc) == 0) ? doc : _(doc), 0);
+}
+
+#if defined (NAMED_FUNCTIONS)
+/* Return the user-visible name of the function associated with the
+ Info command FUNCTION. */
+char *
+function_name (InfoCommand *cmd)
+{
+#if defined (INFOKEY)
+
+ return cmd->func_name;
+
+#else /* !INFOKEY */
+
+ register int i;
+
+ for (i = 0; function_doc_array[i].func; i++)
+ if (InfoFunction(cmd) == function_doc_array[i].func)
+ break;
+
+ return function_doc_array[i].func_name;
+
+#endif /* !INFOKEY */
+}
+
+/* Return a pointer to the info command for function NAME. */
+InfoCommand *
+named_function (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 DocInfoCmd(&function_doc_array[i]);
+}
+#endif /* NAMED_FUNCTIONS */
+
+/* Return the documentation associated with KEY in MAP. */
+char *
+key_documentation (char key, Keymap map)
+{
+ InfoCommand *function = map[key].function;
+
+ if (function)
+ return function_documentation (function);
+ else
+ return NULL;
+}
+
+DECLARE_INFO_COMMAND (describe_key, _("Print documentation for KEY"))
+{
+ char keys[50];
+ unsigned char keystroke;
+ char *k = keys;
+ Keymap map;
+
+ *k = '\0';
+ map = window->keymap;
+
+ for (;;)
+ {
+ message_in_echo_area (_("Describe key: %s"),
+ pretty_keyseq (keys), NULL);
+ keystroke = info_get_input_char ();
+ unmessage_in_echo_area ();
+
+#if !defined (INFOKEY)
+ if (Meta_p (keystroke))
+ {
+ if (map[ESC].type != ISKMAP)
+ {
+ window_message_in_echo_area
+ (_("ESC %s is undefined."), pretty_keyname (UnMeta (keystroke)));
+ return;
+ }
+
+ *k++ = '\e';
+ keystroke = UnMeta (keystroke);
+ map = (Keymap)map[ESC].function;
+ }
+#endif /* !INFOKEY */
+
+ /* Add the KEYSTROKE to our list. */
+ *k++ = keystroke;
+ *k = '\0';
+
+ if (map[keystroke].function == NULL)
+ {
+ message_in_echo_area (_("%s is undefined."),
+ pretty_keyseq (keys), NULL);
+ return;
+ }
+ else if (map[keystroke].type == ISKMAP)
+ {
+ map = (Keymap)map[keystroke].function;
+ continue;
+ }
+ else
+ {
+ char *keyname, *message, *fundoc, *funname = "";
+
+#if defined (INFOKEY)
+ /* If the key is bound to do-lowercase-version, but its
+ lower-case variant is undefined, say that this key is
+ also undefined. This is especially important for unbound
+ edit keys that emit an escape sequence: it's terribly
+ confusing to see a message "Home (do-lowercase-version)"
+ or some such when Home is unbound. */
+ if (InfoFunction(map[keystroke].function)
+ == (VFunction *) info_do_lowercase_version)
+ {
+ unsigned char lowerkey = Meta_p(keystroke)
+ ? Meta (tolower (UnMeta (keystroke)))
+ : tolower (keystroke);
+
+ if (map[lowerkey].function == NULL)
+ {
+ message_in_echo_area (_("%s is undefined."),
+ pretty_keyseq (keys), NULL);
+ return;
+ }
+ }
+#endif
+
+ keyname = pretty_keyseq (keys);
+
+#if defined (NAMED_FUNCTIONS)
+ funname = function_name (map[keystroke].function);
+#endif /* NAMED_FUNCTIONS */
+
+ fundoc = function_documentation (map[keystroke].function);
+
+ message = 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, NULL);
+ free (message);
+ break;
+ }
+ }
+}
+
+/* Return the pretty printable name of a single character. */
+char *
+pretty_keyname (unsigned char key)
+{
+ static char rep_buffer[30];
+ char *rep;
+
+ if (Meta_p (key))
+ {
+ char temp[20];
+
+ rep = pretty_keyname (UnMeta (key));
+
+#if defined (INFOKEY)
+ sprintf (temp, "M-%s", rep);
+#else /* !INFOKEY */
+ sprintf (temp, "ESC %s", rep);
+#endif /* !INFOKEY */
+ 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;
+}
+
+/* Return the pretty printable string which represents KEYSEQ. */
+
+static void pretty_keyseq_internal (char *keyseq, char *rep);
+
+char *
+pretty_keyseq (char *keyseq)
+{
+ static char keyseq_rep[200];
+
+ keyseq_rep[0] = '\0';
+ if (*keyseq)
+ pretty_keyseq_internal (keyseq, keyseq_rep);
+ return keyseq_rep;
+}
+
+static void
+pretty_keyseq_internal (char *keyseq, char *rep)
+{
+ if (term_kP && strncmp(keyseq, term_kP, strlen(term_kP)) == 0)
+ {
+ strcpy(rep, "PgUp");
+ keyseq += strlen(term_kP);
+ }
+ else if (term_kN && strncmp(keyseq, term_kN, strlen(term_kN)) == 0)
+ {
+ strcpy(rep, "PgDn");
+ keyseq += strlen(term_kN);
+ }
+#if defined(INFOKEY)
+ else if (term_kh && strncmp(keyseq, term_kh, strlen(term_kh)) == 0)
+ {
+ strcpy(rep, "Home");
+ keyseq += strlen(term_kh);
+ }
+ else if (term_ke && strncmp(keyseq, term_ke, strlen(term_ke)) == 0)
+ {
+ strcpy(rep, "End");
+ keyseq += strlen(term_ke);
+ }
+ else if (term_ki && strncmp(keyseq, term_ki, strlen(term_ki)) == 0)
+ {
+ strcpy(rep, "INS");
+ keyseq += strlen(term_ki);
+ }
+ else if (term_kx && strncmp(keyseq, term_kx, strlen(term_kx)) == 0)
+ {
+ strcpy(rep, "DEL");
+ keyseq += strlen(term_kx);
+ }
+#endif /* INFOKEY */
+ else if (term_ku && strncmp(keyseq, term_ku, strlen(term_ku)) == 0)
+ {
+ strcpy(rep, "Up");
+ keyseq += strlen(term_ku);
+ }
+ else if (term_kd && strncmp(keyseq, term_kd, strlen(term_kd)) == 0)
+ {
+ strcpy(rep, "Down");
+ keyseq += strlen(term_kd);
+ }
+ else if (term_kl && strncmp(keyseq, term_kl, strlen(term_kl)) == 0)
+ {
+ strcpy(rep, "Left");
+ keyseq += strlen(term_kl);
+ }
+ else if (term_kr && strncmp(keyseq, term_kr, strlen(term_kr)) == 0)
+ {
+ strcpy(rep, "Right");
+ keyseq += strlen(term_kr);
+ }
+ else
+ {
+ strcpy (rep, pretty_keyname (keyseq[0]));
+ keyseq++;
+ }
+ if (*keyseq)
+ {
+ strcat (rep, " ");
+ pretty_keyseq_internal (keyseq, rep + strlen(rep));
+ }
+}
+
+/* Return a pointer to the last character in s that is found in f. */
+static const char *
+strrpbrk (const char *s, const char *f)
+{
+ register const char *e = s + strlen(s);
+ register const char *t;
+
+ while (e-- != s)
+ {
+ for (t = f; *t; t++)
+ if (*e == *t)
+ return e;
+ }
+ return NULL;
+}
+
+/* Replace the names of functions with the key that invokes them. */
+char *
+replace_in_documentation (const char *string, int help_is_only_window_p)
+{
+ unsigned reslen = strlen (string);
+ register int i, start, next;
+ static char *result = NULL;
+
+ maybe_free (result);
+ result = xmalloc (1 + reslen);
+
+ i = next = start = 0;
+
+ /* Skip to the beginning of a replaceable function. */
+ for (i = start; string[i]; i++)
+ {
+ int j = i + 1;
+
+ /* Is this the start of a replaceable function name? */
+ if (string[i] == '\\')
+ {
+ char *fmt = NULL;
+ unsigned min = 0;
+ unsigned max = 0;
+
+ if(string[j] == '%')
+ {
+ if (string[++j] == '-')
+ j++;
+ if (isdigit(string[j]))
+ {
+ min = atoi(string + j);
+ while (isdigit(string[j]))
+ j++;
+ if (string[j] == '.' && isdigit(string[j + 1]))
+ {
+ j += 1;
+ max = atoi(string + j);
+ while (isdigit(string[j]))
+ j++;
+ }
+ fmt = xmalloc (j - i + 2);
+ strncpy (fmt, string + i + 1, j - i);
+ fmt[j - i - 1] = 's';
+ fmt[j - i] = '\0';
+ }
+ else
+ j = i + 1;
+ }
+ if (string[j] == '[')
+ {
+ unsigned arg = 0;
+ char *argstr = NULL;
+ char *rep_name, *fun_name, *rep;
+ InfoCommand *command;
+ char *repstr = NULL;
+ unsigned replen;
+
+ /* Copy in the old text. */
+ strncpy (result + next, string + start, i - start);
+ next += (i - start);
+ start = j + 1;
+
+ /* Look for an optional numeric arg. */
+ i = start;
+ if (isdigit(string[i])
+ || (string[i] == '-' && isdigit(string[i + 1])) )
+ {
+ arg = atoi(string + i);
+ if (string[i] == '-')
+ i++;
+ while (isdigit(string[i]))
+ i++;
+ }
+ start = i;
+
+ /* Move to the end of the function name. */
+ for (i = start; string[i] && (string[i] != ']'); i++);
+
+ rep_name = xmalloc (1 + i - start);
+ strncpy (rep_name, string + start, i - start);
+ rep_name[i - start] = '\0';
+
+ /* If we have only one window (because the window size was too
+ small to split it), we have to quit help by going back one
+ node in the history list, not deleting the window. */
+ if (strcmp (rep_name, "quit-help") == 0)
+ fun_name = help_is_only_window_p ? "history-node"
+ : "delete-window";
+ else
+ fun_name = rep_name;
+
+ /* Find a key which invokes this function in the info_keymap. */
+ command = named_function (fun_name);
+
+ free (rep_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 (!command)
+ abort ();
+
+ if (arg)
+ {
+ char *argrep;
+ const char *p;
+
+ argrep = where_is (info_keymap, InfoCmd(info_add_digit_to_numeric_arg));
+ p = argrep ? strrpbrk (argrep, "0123456789-") : NULL;
+ if (p)
+ {
+ argstr = xmalloc (p - argrep + 21);
+ strncpy (argstr, argrep, p - argrep);
+ sprintf (argstr + (p - argrep), "%d", arg);
+ }
+ else
+ command = NULL;
+ }
+ rep = command ? where_is (info_keymap, command) : NULL;
+ if (!rep)
+ rep = "N/A";
+ replen = (argstr ? strlen (argstr) : 0) + strlen (rep) + 1;
+ repstr = xmalloc (replen);
+ repstr[0] = '\0';
+ if (argstr)
+ {
+ strcat(repstr, argstr);
+ strcat(repstr, " ");
+ free (argstr);
+ }
+ strcat(repstr, rep);
+
+ if (fmt)
+ {
+ if (replen > max)
+ replen = max;
+ if (replen < min)
+ replen = min;
+ }
+ if (next + replen > reslen)
+ {
+ reslen = next + replen + 1;
+ result = xrealloc (result, reslen + 1);
+ }
+
+ if (fmt)
+ sprintf (result + next, fmt, repstr);
+ else
+ strcpy (result + next, repstr);
+
+ next = strlen (result);
+ free (repstr);
+
+ start = i;
+ if (string[i])
+ start++;
+ }
+
+ maybe_free (fmt);
+ }
+ }
+ 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 = NULL;
+static int where_is_rep_index = 0;
+static int where_is_rep_size = 0;
+
+char *
+where_is (Keymap map, InfoCommand *cmd)
+{
+ char *rep;
+
+ if (!where_is_rep_size)
+ where_is_rep = xmalloc (where_is_rep_size = 100);
+ where_is_rep_index = 0;
+
+ rep = where_is_internal (map, cmd);
+
+ /* If it couldn't be found, return "M-x Foo" (or equivalent). */
+ if (!rep)
+ {
+ char *name;
+
+ name = function_name (cmd);
+ if (!name)
+ return NULL; /* no such function */
+
+ rep = where_is_internal (map, InfoCmd(info_execute_command));
+ if (!rep)
+ return ""; /* function exists but can't be got to by user */
+
+ sprintf (where_is_rep, "%s %s", rep, name);
+
+ rep = where_is_rep;
+ }
+ return rep;
+}
+
+/* Return the printed rep of the keystrokes that invoke FUNCTION,
+ as found in MAP, or NULL. */
+static char *
+where_is_internal (Keymap map, InfoCommand *cmd)
+{
+#if defined(INFOKEY)
+
+ register FUNCTION_KEYSEQ *k;
+
+ for (k = cmd->keys; k; k = k->next)
+ if (k->map == map)
+ return pretty_keyseq (k->keyseq);
+
+ return NULL;
+
+#else /* !INFOKEY */
+ /* There is a bug in that create_internal_info_help_node calls
+ where_is_internal without setting where_is_rep_index to zero. This
+ was found by Mandrake and reported by Thierry Vignaud
+ <tvignaud@mandrakesoft.com> around April 24, 2002.
+
+ I think the best fix is to make where_is_rep_index another
+ parameter to this recursively-called function, instead of a static
+ variable. But this [!INFOKEY] branch of the code is not enabled
+ any more, so let's just skip the whole thing. --karl, 28sep02. */
+ 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 == cmd)
+ {
+ 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, cmd);
+
+ if (rep)
+ return where_is_rep;
+
+ where_is_rep_index = saved_index;
+ }
+ }
+
+ return NULL;
+
+#endif /* INFOKEY */
+}
+
+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)
+ {
+ InfoCommand *command;
+
+ command = named_function (command_name);
+
+ if (command)
+ {
+ char *location;
+
+ location = where_is (active_window->keymap, command);
+
+ if (!location || !location[0])
+ {
+ info_error (_("`%s' is not on any keys"),
+ command_name, NULL);
+ }
+ else
+ {
+ if (strstr (location, function_name (command)))
+ 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, NULL);
+ }
+
+ free (command_name);
+}
diff --git a/info/infokey.c b/info/infokey.c
new file mode 100644
index 0000000..5c30a26
--- /dev/null
+++ b/info/infokey.c
@@ -0,0 +1,924 @@
+/* infokey.c -- compile ~/.infokey to ~/.info.
+ $Id: infokey.c,v 1.18 2008/06/11 09:55:42 gray Exp $
+
+ Copyright (C) 1999, 2001, 2002, 2003, 2004, 2005, 2007, 2008
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Andrew Bettison <andrewb@zip.com.au>. */
+
+#include "info.h"
+#include "infomap.h"
+#include "infokey.h"
+#include "key.h"
+#include "getopt.h"
+
+char *program_name = "infokey";
+
+/* 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;
+
+/* String specifying the source file. This is set by the user on the
+ command line, or a default is used. */
+static char *input_filename = NULL;
+
+/* String specifying the name of the file to output to. This is
+ set by the user on the command line, or a default is used. */
+static char *output_filename = NULL;
+
+/* Structure describing the options that Infokey 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. */
+static struct option long_options[] =
+{
+ {"output", 1, 0, 'o'},
+ {"help", 0, &print_help_p, 1},
+ {"version", 0, &print_version_p, 1},
+ {NULL, 0, NULL, 0}
+};
+
+/* String describing the shorthand versions of the long options found above. */
+static char *short_options = "o:";
+
+/* Structure for holding the compiled sections. */
+enum sect_e
+ {
+ info = 0,
+ ea = 1,
+ var = 2
+ };
+struct sect
+ {
+ unsigned int cur;
+ unsigned char data[INFOKEY_MAX_SECTIONLEN];
+ };
+
+/* Some "forward" declarations. */
+static char *mkpath (const char *dir, const char *file);
+static int compile (FILE *fp, const char *filename, struct sect *sections);
+static int write_infokey_file (FILE *fp, struct sect *sections);
+static void syntax_error (const char *filename,
+ unsigned int linenum, const char *fmt,
+ const void *a1, const void *a2, const void *a3, const void *a4);
+static void error_message (int error_code, const char *fmt,
+ const void *a1, const void *a2, const void *a3, const void *a4);
+static void suggest_help (void);
+static void short_help (void);
+
+
+/* **************************************************************** */
+/* */
+/* Main Entry Point to the Infokey Program */
+/* */
+/* **************************************************************** */
+
+int
+main (int argc, char **argv)
+{
+ int getopt_long_index; /* Index returned by getopt_long (). */
+
+#ifdef HAVE_SETLOCALE
+ /* Set locale via LC_ALL. */
+ setlocale (LC_ALL, "");
+#endif
+
+#ifdef ENABLE_NLS
+ /* Set the text message domain. */
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+#endif
+
+ 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 is specifying the name of a file to output to. */
+ case 'o':
+ if (output_filename)
+ free (output_filename);
+ output_filename = xstrdup (optarg);
+ break;
+
+ default:
+ suggest_help ();
+ xexit (1);
+ }
+ }
+
+ /* If the user specified --version, then show the version and exit. */
+ if (print_version_p)
+ {
+ printf ("%s (GNU %s) %s\n", program_name, PACKAGE, VERSION);
+ puts ("");
+ printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
+License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
+This is free software: you are free to change and redistribute it.\n\
+There is NO WARRANTY, to the extent permitted by law.\n"),
+ "2008");
+ xexit (0);
+ }
+
+ /* If the `--help' option was present, show the help and exit. */
+ if (print_help_p)
+ {
+ short_help ();
+ xexit (0);
+ }
+
+ /* If there is one argument remaining, it is the name of the input
+ file. */
+ if (optind == argc - 1)
+ {
+ if (input_filename)
+ free (input_filename);
+ input_filename = xstrdup (argv[optind]);
+ }
+ else if (optind != argc)
+ {
+ error_message (0, _("incorrect number of arguments"),
+ NULL, NULL, NULL, NULL);
+ suggest_help ();
+ xexit (1);
+ }
+
+ /* Use default filenames where none given. */
+ {
+ char *homedir;
+
+ homedir = getenv ("HOME");
+#ifdef __MSDOS__
+ if (!homedir)
+ homedir = ".";
+#endif
+ if (!input_filename)
+ input_filename = mkpath (homedir, INFOKEY_SRCFILE);
+ if (!output_filename)
+ output_filename = mkpath (homedir, INFOKEY_FILE);
+ }
+
+ {
+ FILE *inf;
+ FILE *outf;
+ int write_error;
+ static struct sect sections[3];
+
+ /* Open the input file. */
+ inf = fopen (input_filename, "r");
+ if (!inf)
+ {
+ error_message (errno, _("cannot open input file `%s'"),
+ input_filename, NULL, NULL, NULL);
+ xexit (1);
+ }
+
+ /* Compile the input file to its verious sections, then write the
+ section data to the output file. */
+
+ if (compile (inf, input_filename, sections))
+ {
+ /* Open the output file. */
+ outf = fopen (output_filename, FOPEN_WBIN);
+ if (!outf)
+ {
+ error_message (errno, _("cannot create output file `%s'"),
+ output_filename, NULL, NULL, NULL);
+ xexit (1);
+ }
+
+ /* Write the contents of the output file and close it. If there is
+ an error writing to the file, delete it and exit with a failure
+ status. */
+ write_error = 0;
+ if (!write_infokey_file (outf, sections))
+ {
+ error_message (errno, _("error writing to `%s'"),
+ output_filename, NULL, NULL, NULL);
+ write_error = 1;
+ }
+ if (fclose (outf) == EOF)
+ {
+ error_message (errno, _("error closing output file `%s'"),
+ output_filename, NULL, NULL, NULL);
+ write_error = 1;
+ }
+ if (write_error)
+ {
+ unlink (output_filename);
+ xexit (1);
+ }
+ }
+
+ /* Close the input file. */
+ fclose (inf);
+ }
+
+ return 0;
+}
+
+static char *
+mkpath (const char *dir, const char *file)
+{
+ char *p;
+
+ p = xmalloc (strlen (dir) + 1 + strlen (file) + 2);
+ strcpy (p, dir);
+ strcat (p, "/");
+ strcat (p, file);
+ return p;
+}
+
+
+/* Compilation - the real work.
+
+ Source file syntax
+ ------------------
+ The source file is a line-based text file with the following
+ structure:
+
+ # comments
+ # more comments
+
+ #info
+ u prev-line
+ d next-line
+ ^a invalid # just beep
+ \ku prev-line
+ #stop
+ \kd next-line
+ q quit # of course!
+
+ #echo-area
+ ^a echo-area-beg-of-line
+ ^e echo-area-end-of-line
+ \kr echo-area-forward
+ \kl echo-area-backward
+ \kh echo-area-beg-of-line
+ \ke echo-area-end-of-line
+
+ #var
+ scroll-step=1
+ ISO-Latin=Off
+
+ Lines starting with '#' are comments, and are ignored. Blank
+ lines are ignored. Each section is introduced by one of the
+ following lines:
+
+ #info
+ #echo-area
+ #var
+
+ The sections may occur in any order. Each section may be
+ omitted completely. If the 'info' section is the first in the
+ file, its '#info' line may be omitted.
+
+ The 'info' and 'echo-area' sections
+ -----------------------------------
+ Each line in the 'info' or 'echo-area' sections has the
+ following syntax:
+
+ key-sequence SPACE action-name [ SPACE [ # comment ] ] \n
+
+ Where SPACE is one or more white space characters excluding
+ newline, "action-name" is the name of a GNU Info command,
+ "comment" is any sequence of characters excluding newline, and
+ "key-sequence" is a concatenation of one or more key definitions
+ using the following syntax:
+
+ 1. A carat ^ followed by one character indicates a single
+ control character;
+
+ 2. A backslash \ followed by one, two, or three octal
+ digits indicates a single character having that ASCII
+ code;
+
+ 3. \n indicates a single NEWLINE;
+ \e indicates a single ESC;
+ \r indicates a single CR;
+ \t indicates a single TAB;
+ \b indicates a single BACKSPACE;
+
+ 4. \ku indicates the Up Arrow key;
+ \kd indicates the Down Arrow key;
+ \kl indicates the Left Arrow key;
+ \kr indicates the Right Arrow key;
+ \kP indicates the Page Up (PRIOR) key;
+ \kN indicates the Page Down (NEXT) key;
+ \kh indicates the Home key;
+ \ke indicates the End key;
+ \kx indicates the DEL key;
+ \k followed by any other character indicates a single
+ control-K, and the following character is interpreted
+ as in rules 1, 2, 3, 5 and 6.
+
+ 5. \m followed by any sequence defined in rules 1, 2, 3, 4
+ or 6 indicates the "Meta" modification of that key.
+
+ 6. A backslash \ followed by any character not described
+ above indicates that character itself. In particular:
+ \\ indicates a single backslash \,
+ \ (backslash-space) indicates a single space,
+ \^ indicates a single caret ^,
+
+ If the following line:
+
+ #stop
+
+ occurs anywhere in an 'info' or 'echo-area' section, that
+ indicates to GNU Info to suppress all of its default key
+ bindings in that context.
+
+ The 'var' section
+ -----------------
+ Each line in the 'var' section has the following syntax:
+
+ variable-name = value \n
+
+ Where "variable-name" is the name of a GNU Info variable and
+ "value" is the value that GNU Info will assign to that variable
+ when commencing execution. There must be no white space in the
+ variable name, nor between the variable name and the '='. All
+ characters immediately following the '=', up to but not
+ including the terminating newline, are considered to be the
+ value that will be assigned. In other words, white space
+ following the '=' is not ignored.
+ */
+
+static int add_to_section (struct sect *s, const char *str, unsigned int len);
+static int lookup_action (const char *actname);
+
+/* Compile the input file into its various sections. Return true if no
+ error was encountered.
+ */
+static int
+compile (FILE *fp, const char *filename, struct sect *sections)
+{
+ int error = 0;
+ char rescan = 0;
+ unsigned int lnum = 0;
+ int c = 0;
+
+ /* This parser is a true state machine, with no sneaky fetching
+ of input characters inside the main loop. In other words, all
+ state is fully represented by the following variables:
+ */
+ enum
+ {
+ start_of_line,
+ start_of_comment,
+ in_line_comment,
+ in_trailing_comment,
+ get_keyseq,
+ got_keyseq,
+ get_action,
+ got_action,
+ get_varname,
+ got_varname,
+ get_equals,
+ got_equals,
+ get_value
+ }
+ state = start_of_line;
+ enum sect_e section = info;
+ enum
+ {
+ normal,
+ slosh,
+ control,
+ octal,
+ special_key
+ }
+ seqstate; /* used if state == get_keyseq */
+ char meta = 0;
+ char ocnt = 0; /* used if state == get_keyseq && seqstate == octal */
+
+ /* Data is accumulated in the following variables. The code
+ avoids overflowing these strings, and throws an error
+ where appropriate if a string limit is exceeded. These string
+ lengths are arbitrary (and should be large enough) and their
+ lengths are not hard-coded anywhere else, so increasing them
+ here will not break anything. */
+ char oval = 0;
+ char comment[10];
+ unsigned int clen = 0;
+ char seq[20];
+ unsigned int slen = 0;
+ char act[80];
+ unsigned int alen = 0;
+ char varn[80];
+ unsigned int varlen = 0;
+ char val[80];
+ unsigned int vallen = 0;
+
+#define To_seq(c) \
+ do { \
+ if (slen < sizeof seq) \
+ seq[slen++] = meta ? Meta(c) : (c); \
+ else \
+ { \
+ syntax_error(filename, lnum, _("key sequence too long"), \
+ NULL, NULL, NULL, NULL); \
+ error = 1; \
+ } \
+ meta = 0; \
+ } while (0)
+
+ sections[info].cur = 1;
+ sections[info].data[0] = 0;
+ sections[ea].cur = 1;
+ sections[ea].data[0] = 0;
+ sections[var].cur = 0;
+
+ while (!error && (rescan || (c = fgetc (fp)) != EOF))
+ {
+ rescan = 0;
+ switch (state)
+ {
+ case start_of_line:
+ lnum++;
+ if (c == '#')
+ state = start_of_comment;
+ else if (c != '\n')
+ {
+ switch (section)
+ {
+ case info:
+ case ea:
+ state = get_keyseq;
+ seqstate = normal;
+ slen = 0;
+ break;
+ case var:
+ state = get_varname;
+ varlen = 0;
+ break;
+ }
+ rescan = 1;
+ }
+ break;
+
+ case start_of_comment:
+ clen = 0;
+ state = in_line_comment;
+ /* fall through */
+ case in_line_comment:
+ if (c == '\n')
+ {
+ state = start_of_line;
+ comment[clen] = '\0';
+ if (strcmp (comment, "info") == 0)
+ section = info;
+ else if (strcmp (comment, "echo-area") == 0)
+ section = ea;
+ else if (strcmp (comment, "var") == 0)
+ section = var;
+ else if (strcmp (comment, "stop") == 0
+ && (section == info || section == ea))
+ sections[section].data[0] = 1;
+ }
+ else if (clen < sizeof comment - 1)
+ comment[clen++] = c;
+ break;
+
+ case in_trailing_comment:
+ if (c == '\n')
+ state = start_of_line;
+ break;
+
+ case get_keyseq:
+ switch (seqstate)
+ {
+ case normal:
+ if (c == '\n' || isspace (c))
+ {
+ state = got_keyseq;
+ rescan = 1;
+ if (slen == 0)
+ {
+ syntax_error (filename, lnum, _("missing key sequence"),
+ NULL, NULL, NULL, NULL);
+ error = 1;
+ }
+ }
+ else if (c == '\\')
+ seqstate = slosh;
+ else if (c == '^')
+ seqstate = control;
+ else
+ To_seq (c);
+ break;
+
+ case slosh:
+ switch (c)
+ {
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ seqstate = octal;
+ oval = c - '0';
+ ocnt = 1;
+ break;
+ case 'b':
+ To_seq ('\b');
+ seqstate = normal;
+ break;
+ case 'e':
+ To_seq ('\033');
+ seqstate = normal;
+ break;
+ case 'n':
+ To_seq ('\n');
+ seqstate = normal;
+ break;
+ case 'r':
+ To_seq ('\r');
+ seqstate = normal;
+ break;
+ case 't':
+ To_seq ('\t');
+ seqstate = normal;
+ break;
+ case 'm':
+ meta = 1;
+ seqstate = normal;
+ break;
+ case 'k':
+ seqstate = special_key;
+ break;
+ default:
+ /* Backslash followed by any other char
+ just means that char. */
+ To_seq (c);
+ seqstate = normal;
+ break;
+ }
+ break;
+
+ case octal:
+ switch (c)
+ {
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ if (++ocnt <= 3)
+ oval = oval * 8 + c - '0';
+ if (ocnt == 3)
+ seqstate = normal;
+ break;
+ default:
+ ocnt = 4;
+ seqstate = normal;
+ rescan = 1;
+ break;
+ }
+ if (seqstate != octal)
+ {
+ if (oval)
+ To_seq (oval);
+ else
+ {
+ syntax_error (filename, lnum,
+ _("NUL character (\\000) not permitted"),
+ NULL, NULL, NULL, NULL);
+ error = 1;
+ }
+ }
+ break;
+
+ case special_key:
+ To_seq (SK_ESCAPE);
+ switch (c)
+ {
+ case 'u': To_seq (SK_UP_ARROW); break;
+ case 'd': To_seq (SK_DOWN_ARROW); break;
+ case 'r': To_seq (SK_RIGHT_ARROW); break;
+ case 'l': To_seq (SK_LEFT_ARROW); break;
+ case 'U': To_seq (SK_PAGE_UP); break;
+ case 'D': To_seq (SK_PAGE_DOWN); break;
+ case 'h': To_seq (SK_HOME); break;
+ case 'e': To_seq (SK_END); break;
+ case 'x': To_seq (SK_DELETE); break;
+ default: To_seq (SK_LITERAL); rescan = 1; break;
+ }
+ seqstate = normal;
+ break;
+
+ case control:
+ if (CONTROL (c))
+ To_seq (CONTROL (c));
+ else
+ {
+ syntax_error (filename, lnum,
+ _("NUL character (^%c) not permitted"),
+ (void *) (long) c, NULL, NULL, NULL);
+ error = 1;
+ }
+ seqstate = normal;
+ break;
+ }
+ break;
+
+ case got_keyseq:
+ if (isspace (c) && c != '\n')
+ break;
+ state = get_action;
+ alen = 0;
+ /* fall through */
+ case get_action:
+ if (c == '\n' || isspace (c))
+ {
+ int a;
+
+ state = got_action;
+ rescan = 1;
+ if (alen == 0)
+ {
+ syntax_error (filename, lnum, _("missing action name"),
+ (void *) (long) c, NULL, NULL, NULL);
+ error = 1;
+ }
+ else
+ {
+ act[alen] = '\0';
+ a = lookup_action (act);
+ if (a != -1)
+ {
+ char av = a;
+
+ if (!(add_to_section (&sections[section], seq, slen)
+ && add_to_section (&sections[section], "", 1)
+ && add_to_section (&sections[section], &av, 1)))
+ {
+ syntax_error (filename, lnum, _("section too long"),
+ NULL, NULL, NULL, NULL);
+ error = 1;
+ }
+ }
+ else
+ {
+ syntax_error (filename, lnum, _("unknown action `%s'"),
+ act, NULL, NULL, NULL);
+ error = 1;
+ }
+ }
+ }
+ else if (alen < sizeof act - 1)
+ act[alen++] = c;
+ else
+ {
+ syntax_error (filename, lnum, _("action name too long"),
+ NULL, NULL, NULL, NULL);
+ error = 1;
+ }
+ break;
+
+ case got_action:
+ if (c == '#')
+ state = in_trailing_comment;
+ else if (c == '\n')
+ state = start_of_line;
+ else if (!isspace (c))
+ {
+ syntax_error (filename, lnum,
+ _("extra characters following action `%s'"),
+ act, NULL, NULL, NULL);
+ error = 1;
+ }
+ break;
+
+ case get_varname:
+ if (c == '=')
+ {
+ if (varlen == 0)
+ {
+ syntax_error (filename, lnum, _("missing variable name"),
+ NULL, NULL, NULL, NULL);
+ error = 1;
+ }
+ state = get_value;
+ vallen = 0;
+ }
+ else if (c == '\n' || isspace (c))
+ {
+ syntax_error (filename, lnum,
+ _("missing `=' immediately after variable name"),
+ NULL, NULL, NULL, NULL);
+ error = 1;
+ }
+ else if (varlen < sizeof varn)
+ varn[varlen++] = c;
+ else
+ {
+ syntax_error (filename, lnum, _("variable name too long"),
+ NULL, NULL, NULL, NULL);
+ error = 1;
+ }
+ break;
+
+ case get_value:
+ if (c == '\n')
+ {
+ state = start_of_line;
+ if (!(add_to_section (&sections[section], varn, varlen)
+ && add_to_section (&sections[section], "", 1)
+ && add_to_section (&sections[section], val, vallen)
+ && add_to_section (&sections[section], "", 1)))
+ {
+ syntax_error (filename, lnum, _("section too long"),
+ NULL, NULL, NULL, NULL);
+ error = 1;
+ }
+ }
+ else if (vallen < sizeof val)
+ val[vallen++] = c;
+ else
+ {
+ syntax_error (filename, lnum, _("value too long"),
+ NULL, NULL, NULL, NULL);
+ error = 1;
+ }
+ break;
+
+ case get_equals:
+ case got_equals:
+ case got_varname:
+ break;
+ }
+ }
+
+#undef To_seq
+
+ return !error;
+}
+
+/* Add some characters to a section's data. Return true if all the
+ characters fit, or false if the section's size limit was exceeded.
+ */
+static int
+add_to_section (struct sect *s, const char *str, unsigned int len)
+{
+ if (s->cur + len > sizeof s->data)
+ return 0;
+ strncpy ((char *) s->data + s->cur, str, len);
+ s->cur += len;
+ return 1;
+}
+
+/* Translate from an action name to its numeric code. This uses the
+ auto-generated array in key.c.
+ */
+static int
+lookup_action (const char *actname)
+{
+ int i;
+
+ if (strcmp ("invalid", actname) == 0)
+ return A_INVALID;
+ for (i = 0; function_key_array[i].name != NULL; i++)
+ if (strcmp (function_key_array[i].name, actname) == 0)
+ return function_key_array[i].code;
+ return -1;
+}
+
+/* Put an integer to an infokey file.
+ Integers are stored as two bytes, low order first,
+ in radix INFOKEY_RADIX.
+ */
+static int
+putint (int i, FILE *fp)
+{
+ return fputc (i % INFOKEY_RADIX, fp) != EOF
+ && fputc ((i / INFOKEY_RADIX) % INFOKEY_RADIX, fp) != EOF;
+}
+
+/* Write an entire section to an infokey file. If the section is
+ empty, simply omit it.
+ */
+static int
+putsect (struct sect *s, int code, FILE *fp)
+{
+ if (s->cur == 0)
+ return 1;
+ return fputc (code, fp) != EOF
+ && putint (s->cur, fp)
+ && fwrite (s->data, s->cur, 1, fp) == 1;
+}
+
+/* Write an entire infokey file, given an array containing its sections.
+ */
+static int
+write_infokey_file (FILE *fp, struct sect *sections)
+{
+ /* Get rid of sections with no effect. */
+ if (sections[info].cur == 1 && sections[info].data[0] == 0)
+ sections[info].cur = 0;
+ if (sections[ea].cur == 1 && sections[ea].data[0] == 0)
+ sections[ea].cur = 0;
+
+ /* Write all parts of the file out in order (no lseeks),
+ checking for errors all the way. */
+ return fputc (INFOKEY_MAGIC_S0, fp) != EOF
+ && fputc (INFOKEY_MAGIC_S1, fp) != EOF
+ && fputc (INFOKEY_MAGIC_S2, fp) != EOF
+ && fputc (INFOKEY_MAGIC_S3, fp) != EOF
+ && fputs (VERSION, fp) != EOF
+ && fputc ('\0', fp) != EOF
+ && putsect (&sections[info], INFOKEY_SECTION_INFO, fp)
+ && putsect (&sections[ea], INFOKEY_SECTION_EA, fp)
+ && putsect (&sections[var], INFOKEY_SECTION_VAR, fp)
+ && fputc (INFOKEY_MAGIC_E0, fp) != EOF
+ && fputc (INFOKEY_MAGIC_E1, fp) != EOF
+ && fputc (INFOKEY_MAGIC_E2, fp) != EOF
+ && fputc (INFOKEY_MAGIC_E3, fp) != EOF;
+}
+
+
+/* Error handling. */
+
+/* Give the user a "syntax error" message in the form
+ progname: "filename", line N: message
+ */
+static void
+error_message (int error_code, const char *fmt,
+ const void *a1, const void *a2, const void *a3, const void *a4)
+{
+ fprintf (stderr, "%s: ", program_name);
+ fprintf (stderr, fmt, a1, a2, a3, a4);
+ if (error_code)
+ fprintf (stderr, " - %s", strerror (error_code));
+ fprintf (stderr, "\n");
+}
+
+/* Give the user a generic error message in the form
+ progname: message
+ */
+static void
+syntax_error (const char *filename,
+ unsigned int linenum, const char *fmt,
+ const void *a1, const void *a2, const void *a3, const void *a4)
+{
+ fprintf (stderr, "%s: ", program_name);
+ fprintf (stderr, _("\"%s\", line %u: "), filename, linenum);
+ fprintf (stderr, fmt, a1, a2, a3, a4);
+ fprintf (stderr, "\n");
+}
+
+/* Produce a gentle rtfm. */
+static void
+suggest_help (void)
+{
+ fprintf (stderr, _("Try --help for more information.\n"));
+}
+
+/* Produce a scaled down description of the available options to Info. */
+static void
+short_help (void)
+{
+ printf (_("\
+Usage: %s [OPTION]... [INPUT-FILE]\n\
+\n\
+Compile infokey source file to infokey file. Reads INPUT-FILE (default\n\
+$HOME/.infokey) and writes compiled key file to (by default) $HOME/.info.\n\
+\n\
+Options:\n\
+ --output FILE output to FILE instead of $HOME/.info\n\
+ --help display this help and exit.\n\
+ --version display version information and exit.\n\
+"), program_name);
+
+ puts (_("\n\
+Email bug reports to bug-texinfo@gnu.org,\n\
+general questions and discussion to help-texinfo@gnu.org.\n\
+Texinfo home page: http://www.gnu.org/software/texinfo/"));
+
+ xexit (0);
+}
diff --git a/info/infokey.h b/info/infokey.h
new file mode 100644
index 0000000..783d921
--- /dev/null
+++ b/info/infokey.h
@@ -0,0 +1,127 @@
+/* infokey.h -- Custom keystroke definition support.
+ $Id: infokey.h,v 1.5 2007/07/01 21:20:30 karl Exp $
+
+ Copyright (C) 1999, 2002, 2007 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Andrew Bettison <andrewb@zip.com.au>.
+
+ This design was derived from the "lesskey" system in less 3.4.0. by
+ Mark Nudelman.
+
+ The following terminology is confusing:
+ source file = $HOME/.infokey
+ infokey file = $HOME/.info
+ Oh, well.
+ */
+
+
+/* Default source file, where user writes text definitions to be
+ compiled to the infokey file. MS-DOS doesn't allow leading
+ dots in file names. */
+#ifdef __MSDOS__
+#define INFOKEY_SRCFILE "_infokey"
+#else
+#define INFOKEY_SRCFILE ".infokey"
+#endif
+
+/* Default "infokey file", where compiled user defs are kept and
+ read by Info. MS-DOS doesn't allow leading dots in file names. */
+#ifdef __MSDOS__
+#define INFOKEY_FILE "_info"
+#else
+#define INFOKEY_FILE ".info"
+#endif
+
+/*
+Format of entire infokey file:
+
+ 4 bytes magic number S
+ X bytes version string
+ 1 byte '\0' terminator
+
+ any number of sections:
+ 1 byte section id
+ 2 bytes section length (N)
+ N bytes section definitions: format depends on section
+
+ 4 bytes magic number E
+
+Format of INFO and EA sections:
+
+ 1 byte flag: 1 == suppress default key bindings
+ Repeat:
+ X bytes key sequence
+ 1 byte '\0' terminator
+ 1 byte action code (A_xxx)
+
+Format of VAR section:
+
+ Repeat:
+ X bytes variable name
+ 1 byte '\0' terminator
+ Y bytes value
+ 1 byte '\0' terminator
+
+*/
+
+#define INFOKEY_NMAGIC 8
+
+#define INFOKEY_MAGIC_S0 '\001'
+#define INFOKEY_MAGIC_S1 'I'
+#define INFOKEY_MAGIC_S2 'n'
+#define INFOKEY_MAGIC_S3 'f'
+
+#define INFOKEY_SECTION_INFO 'i'
+#define INFOKEY_SECTION_EA 'e'
+#define INFOKEY_SECTION_VAR 'v'
+
+#define INFOKEY_MAGIC_E0 'A'
+#define INFOKEY_MAGIC_E1 'l'
+#define INFOKEY_MAGIC_E2 'f'
+#define INFOKEY_MAGIC_E3 'n'
+
+#define INFOKEY_RADIX 64
+#define INFOKEY_MAX_SECTIONLEN 500
+#define INFOKEY_MAX_DEFLEN 16
+
+#define A_MAX_COMMAND 120
+#define A_INVALID 121
+
+/* Character transformations (independent of info's own) */
+
+#define CONTROL(c) ((c) & 0x1f)
+#define ISCONTROL(c) (((c) & ~0x1f) == 0)
+#define META(c) ((c) | 0x80)
+#define UNMETA(c) ((c) & ~0x80)
+#define ISMETA(c) (((c) & 0x80) != 0)
+
+/* Special keys (keys which output different strings on different terminals) */
+
+#define SK_ESCAPE CONTROL('k')
+#define SK_RIGHT_ARROW 1
+#define SK_LEFT_ARROW 2
+#define SK_UP_ARROW 3
+#define SK_DOWN_ARROW 4
+#define SK_PAGE_UP 5
+#define SK_PAGE_DOWN 6
+#define SK_HOME 7
+#define SK_END 8
+#define SK_DELETE 9
+#define SK_INSERT 10
+#define SK_CTL_LEFT_ARROW 11
+#define SK_CTL_RIGHT_ARROW 12
+#define SK_CTL_DELETE 13
+#define SK_LITERAL 40
diff --git a/info/infomap.c b/info/infomap.c
new file mode 100644
index 0000000..b964d01
--- /dev/null
+++ b/info/infomap.c
@@ -0,0 +1,1285 @@
+/* infomap.c -- keymaps for Info.
+ $Id: infomap.c,v 1.21 2008/06/11 09:55:42 gray Exp $
+
+ Copyright (C) 1993, 1997, 1998, 1999, 2001, 2002, 2003, 2004, 2007, 2008
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include "infomap.h"
+#include "funs.h"
+#include "terminal.h"
+
+#if defined(INFOKEY)
+#include "infokey.h"
+#include "variables.h"
+#endif /* INFOKEY */
+
+static int keymap_bind_keyseq (Keymap map, const char *keyseq,
+ KEYMAP_ENTRY *keyentry);
+
+/* Return a new keymap which has all the uppercase letters mapped to run
+ the function info_do_lowercase_version (). */
+Keymap
+keymap_make_keymap (void)
+{
+ int i;
+ Keymap keymap;
+
+ keymap = (Keymap)xmalloc (256 * sizeof (KEYMAP_ENTRY));
+
+ for (i = 0; i < 256; i++)
+ {
+ keymap[i].type = ISFUNC;
+ keymap[i].function = NULL;
+ }
+
+ for (i = 'A'; i < ('Z' + 1); i++)
+ {
+ keymap[i].type = ISFUNC;
+#if defined(INFOKEY)
+ keymap[Meta(i)].type = ISFUNC;
+ keymap[Meta(i)].function =
+#endif /* INFOKEY */
+ keymap[i].function = InfoCmd(info_do_lowercase_version);
+ }
+
+ return keymap;
+}
+
+#if defined(INFOKEY)
+static FUNCTION_KEYSEQ *
+find_function_keyseq (Keymap map, int c, Keymap rootmap)
+{
+ FUNCTION_KEYSEQ *k;
+
+ if (map[c].type != ISFUNC)
+ abort();
+ if (map[c].function == NULL)
+ return NULL;
+ for (k = map[c].function->keys; k; k = k->next)
+ {
+ const unsigned char *p;
+ Keymap m = rootmap;
+ if (k->map != rootmap)
+ continue;
+ for (p = (unsigned char *) k->keyseq; *p && m[*p].type == ISKMAP; p++)
+ m = (Keymap)m[*p].function;
+ if (*p != c || p[1])
+ continue;
+ if (m[*p].type != ISFUNC)
+ abort ();
+ break;
+ }
+ return k;
+}
+
+static void
+add_function_keyseq (InfoCommand *function,
+ const char *keyseq, Keymap rootmap)
+{
+ FUNCTION_KEYSEQ *ks;
+
+ if (function == NULL ||
+ function == InfoCmd(info_do_lowercase_version) ||
+ function == InfoCmd(ea_insert))
+ return;
+ ks = xmalloc (sizeof(FUNCTION_KEYSEQ));
+ ks->next = function->keys;
+ ks->map = rootmap;
+ ks->keyseq = xstrdup(keyseq);
+ function->keys = ks;
+}
+
+static void
+remove_function_keyseq (InfoCommand *function,
+ const char *keyseq, Keymap rootmap)
+{
+
+ FUNCTION_KEYSEQ *k, *kp;
+
+ if (function == NULL ||
+ function == InfoCmd(info_do_lowercase_version) ||
+ function == InfoCmd(ea_insert))
+ return;
+ for (kp = NULL, k = function->keys; k; kp = k, k = k->next)
+ if (k->map == rootmap && strcmp(k->keyseq, keyseq) == 0)
+ break;
+ if (!k)
+ abort ();
+ if (kp)
+ kp->next = k->next;
+ else
+ function->keys = k->next;
+}
+#endif /* INFOKEY */
+
+/* Return a new keymap which is a copy of MAP. */
+Keymap
+keymap_copy_keymap (Keymap map, Keymap rootmap, Keymap newroot)
+{
+ int i;
+ Keymap keymap;
+#if defined(INFOKEY)
+ FUNCTION_KEYSEQ *ks;
+#endif /* INFOKEY */
+
+ keymap = keymap_make_keymap ();
+ if (!newroot)
+ newroot = keymap;
+
+ for (i = 0; i < 256; i++)
+ {
+ keymap[i].type = map[i].type;
+ switch (map[i].type)
+ {
+ case ISFUNC:
+ keymap[i].function = map[i].function;
+#if defined(INFOKEY)
+ ks = find_function_keyseq (map, i, rootmap);
+ if (ks)
+ add_function_keyseq(map[i].function, ks->keyseq, newroot);
+#endif /* INFOKEY */
+ break;
+ case ISKMAP:
+ keymap[i].function = (InfoCommand *)keymap_copy_keymap
+ ((Keymap)map[i].function, rootmap, NULL);
+ break;
+ }
+ }
+ return keymap;
+}
+
+/* Free the keymap and its descendants. */
+void
+keymap_discard_keymap (Keymap map, Keymap rootmap)
+{
+ int i;
+
+ if (!map)
+ return;
+ if (!rootmap)
+ rootmap = map;
+
+ for (i = 0; i < 256; i++)
+ {
+#if defined(INFOKEY)
+ FUNCTION_KEYSEQ *ks;
+#endif /* INFOKEY */
+ switch (map[i].type)
+ {
+ case ISFUNC:
+#if defined(INFOKEY)
+ ks = find_function_keyseq(map, i, rootmap);
+ if (ks)
+ remove_function_keyseq (map[i].function, ks->keyseq, rootmap);
+#endif /* INFOKEY */
+ break;
+
+ case ISKMAP:
+ keymap_discard_keymap ((Keymap)map[i].function, rootmap);
+ break;
+
+ }
+ }
+ free(map);
+}
+
+/* Conditionally bind key sequence. */
+static int
+keymap_bind_keyseq (Keymap map,
+ const char *keyseq, KEYMAP_ENTRY *keyentry)
+{
+ Keymap m = map;
+ const unsigned char *s = (unsigned char *) keyseq;
+ int c;
+
+ if (s == NULL || *s == '\0') return 0;
+
+ while ((c = *s++) != '\0')
+ {
+#if defined(INFOKEY)
+ FUNCTION_KEYSEQ *ks;
+#endif /* INFOKEY */
+ switch (m[c].type)
+ {
+ case ISFUNC:
+#if defined(INFOKEY)
+ ks = find_function_keyseq(m, c, map);
+ if (ks)
+ remove_function_keyseq (m[c].function, ks->keyseq, map);
+#else /* !INFOKEY */
+ if (!(m[c].function == NULL || (
+ m != map &&
+ m[c].function == InfoCmd(info_do_lowercase_version))
+ ))
+ return 0;
+#endif /* !INFOKEY */
+
+ if (*s != '\0')
+ {
+ m[c].type = ISKMAP;
+ /* Here we are casting the Keymap pointer returned from
+ keymap_make_keymap to an InfoCommand pointer. Ugh.
+ This makes the `function' structure garbage
+ if it's actually interpreted as an InfoCommand.
+ Should really be using a union, and taking steps to
+ avoid the possible error. */
+ m[c].function = (InfoCommand *)keymap_make_keymap ();
+ }
+ break;
+
+ case ISKMAP:
+#if defined(INFOKEY)
+ if (*s == '\0')
+ keymap_discard_keymap ((Keymap)m[c].function, map);
+#else /* !INFOKEY */
+ if (*s == '\0')
+ return 0;
+#endif
+ break;
+ }
+ if (*s != '\0')
+ {
+ m = (Keymap)m[c].function;
+ }
+ else
+ {
+#if defined(INFOKEY)
+ add_function_keyseq (keyentry->function, keyseq, map);
+#endif /* INFOKEY */
+ m[c] = *keyentry;
+ }
+ }
+
+ return 1;
+}
+
+
+/* Initialize the standard info keymaps. */
+
+Keymap info_keymap = NULL;
+Keymap echo_area_keymap = NULL;
+
+/* Make sure that we don't have too many command codes defined. */
+
+#if A_NCOMMANDS > A_MAX_COMMAND + 1
+#error "too many commands defined"
+#endif
+
+/* Initialize the keymaps from the .info keymap file. */
+
+#define NUL '\0'
+
+static unsigned char default_emacs_like_info_keys[] =
+{
+ 0, /* suppress-default-keybindings flag */
+ TAB, NUL, A_info_move_to_next_xref,
+ LFD, NUL, A_info_select_reference_this_line,
+ RET, NUL, A_info_select_reference_this_line,
+ CONTROL('a'), NUL, A_info_beginning_of_line,
+ CONTROL('b'), NUL, A_info_backward_char,
+ CONTROL('e'), NUL, A_info_end_of_line,
+ CONTROL('f'), NUL, A_info_forward_char,
+ CONTROL('h'), NUL, A_info_get_help_window,
+ CONTROL('l'), NUL, A_info_redraw_display,
+ CONTROL('n'), NUL, A_info_next_line,
+ CONTROL('p'), NUL, A_info_prev_line,
+ CONTROL('r'), NUL, A_isearch_backward,
+ CONTROL('s'), NUL, A_isearch_forward,
+ CONTROL('u'), NUL, A_info_universal_argument,
+ CONTROL('v'), NUL, A_info_scroll_forward_page_only,
+ ',', NUL, A_info_next_index_match,
+ '/', NUL, A_info_search,
+ '0', NUL, A_info_last_menu_item,
+ '1', NUL, A_info_menu_digit,
+ '2', NUL, A_info_menu_digit,
+ '3', NUL, A_info_menu_digit,
+ '4', NUL, A_info_menu_digit,
+ '5', NUL, A_info_menu_digit,
+ '6', NUL, A_info_menu_digit,
+ '7', NUL, A_info_menu_digit,
+ '8', NUL, A_info_menu_digit,
+ '9', NUL, A_info_menu_digit,
+ '<', NUL, A_info_first_node,
+ '>', NUL, A_info_last_node,
+ '?', NUL, A_info_get_help_window,
+ '[', NUL, A_info_global_prev_node,
+ ']', NUL, A_info_global_next_node,
+ 'b', NUL, A_info_beginning_of_node,
+ 'd', NUL, A_info_dir_node,
+ 'e', NUL, A_info_end_of_node,
+ 'f', NUL, A_info_xref_item,
+ 'g', NUL, A_info_goto_node,
+ 'G', NUL, A_info_menu_sequence,
+ 'h', NUL, A_info_get_help_window,
+ 'H', NUL, A_info_get_info_help_node,
+ 'i', NUL, A_info_index_search,
+ 'l', NUL, A_info_history_node,
+ 'm', NUL, A_info_menu_item,
+ 'n', NUL, A_info_next_node,
+ 'O', NUL, A_info_goto_invocation_node,
+ 'p', NUL, A_info_prev_node,
+ 'r', NUL, A_info_xref_item,
+ 'R', NUL, A_info_toggle_regexp,
+ 's', NUL, A_info_search,
+ 'S', NUL, A_info_search_case_sensitively,
+ 't', NUL, A_info_top_node,
+ 'u', NUL, A_info_up_node,
+ ESC, '0', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '1', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '2', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '3', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '4', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '5', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '6', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '7', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '8', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '9', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '-', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, CONTROL('f'), NUL, A_info_show_footnotes,
+ ESC, CONTROL('g'), NUL, A_info_abort_key,
+ ESC, TAB, NUL, A_info_move_to_prev_xref,
+ ESC, CONTROL('v'), NUL, A_info_scroll_other_window,
+ ESC, '<', NUL, A_info_beginning_of_node,
+ ESC, '>', NUL, A_info_end_of_node,
+ ESC, 'b', NUL, A_info_backward_word,
+ ESC, 'f', NUL, A_info_forward_word,
+ ESC, 'r', NUL, A_info_move_to_window_line,
+ ESC, 'v', NUL, A_info_scroll_backward_page_only,
+ Meta('0'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('1'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('2'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('3'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('4'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('5'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('6'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('7'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('8'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('9'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('-'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta(CONTROL('f')), NUL, A_info_show_footnotes,
+ Meta(CONTROL('g')), NUL, A_info_abort_key,
+ Meta(TAB), NUL, A_info_move_to_prev_xref,
+ Meta(CONTROL('v')), NUL, A_info_scroll_other_window,
+ Meta('<'), NUL, A_info_beginning_of_node,
+ Meta('>'), NUL, A_info_end_of_node,
+ Meta('b'), NUL, A_info_backward_word,
+ Meta('f'), NUL, A_info_forward_word,
+ Meta('r'), NUL, A_info_move_to_window_line,
+ Meta('v'), NUL, A_info_scroll_backward_page_only,
+#if defined (NAMED_FUNCTIONS)
+ ESC, 'x', NUL, A_info_execute_command,
+ Meta('x'), NUL, A_info_execute_command,
+#endif /* NAMED_FUNCTIONS */
+
+ CONTROL('x'), CONTROL('b'), NUL, A_list_visited_nodes,
+ CONTROL('x'), CONTROL('c'), NUL, A_info_quit,
+ CONTROL('x'), CONTROL('f'), NUL, A_info_view_file,
+ CONTROL('x'), CONTROL('g'), NUL, A_info_abort_key,
+ CONTROL('x'), CONTROL('v'), NUL, A_info_view_file,
+ CONTROL('x'), '0', NUL, A_info_delete_window,
+ CONTROL('x'), '1', NUL, A_info_keep_one_window,
+ CONTROL('x'), '2', NUL, A_info_split_window,
+ CONTROL('x'), '^', NUL, A_info_grow_window,
+ CONTROL('x'), 'b', NUL, A_select_visited_node,
+ CONTROL('x'), 'k', NUL, A_info_kill_node,
+ CONTROL('x'), 'n', NUL, A_info_search_next,
+ CONTROL('x'), 'N', NUL, A_info_search_previous,
+ CONTROL('x'), 'o', NUL, A_info_next_window,
+ CONTROL('x'), 't', NUL, A_info_tile_windows,
+ CONTROL('x'), 'w', NUL, A_info_toggle_wrap,
+
+/* Arrow key bindings for info keymaps. It seems that some
+ terminals do not match their termcap entries, so it's best to just
+ define everything with both of the usual prefixes. */
+
+ SK_ESCAPE, SK_PAGE_UP, NUL, A_info_scroll_backward,
+ SK_ESCAPE, SK_PAGE_DOWN, NUL, A_info_scroll_forward,
+ '\033', 'O', 'A', NUL, A_info_prev_line,
+ '\033', '[', 'A', NUL, A_info_prev_line,
+ '\033', 'O', 'B', NUL, A_info_next_line,
+ '\033', '[', 'B', NUL, A_info_next_line,
+ SK_ESCAPE, SK_RIGHT_ARROW, NUL, A_info_forward_char,
+ '\033', 'O', 'C', NUL, A_info_forward_char,
+ '\033', '[', 'C', NUL, A_info_forward_char,
+ SK_ESCAPE, SK_LEFT_ARROW, NUL, A_info_backward_char,
+ '\033', 'O', 'D', NUL, A_info_backward_char,
+ '\033', '[', 'D', NUL, A_info_backward_char,
+ SK_ESCAPE, SK_HOME, NUL, A_info_beginning_of_node,
+ SK_ESCAPE, SK_END, NUL, A_info_end_of_node,
+ SK_ESCAPE, SK_DELETE, NUL, A_info_scroll_backward,
+
+ ESC, SK_ESCAPE, SK_PAGE_UP, NUL, A_info_scroll_other_window_backward,
+ ESC, SK_ESCAPE, SK_PAGE_DOWN, NUL, A_info_scroll_other_window,
+ ESC, SK_ESCAPE, SK_UP_ARROW, NUL, A_info_prev_line,
+ ESC, '\033', 'O', 'A', NUL, A_info_prev_line,
+ ESC, '\033', '[', 'A', NUL, A_info_prev_line,
+ ESC, SK_ESCAPE, SK_DOWN_ARROW, NUL, A_info_next_line,
+ ESC, '\033', 'O', 'B', NUL, A_info_next_line,
+ ESC, '\033', '[', 'B', NUL, A_info_next_line,
+ ESC, SK_ESCAPE, SK_RIGHT_ARROW, NUL, A_info_forward_word,
+ ESC, '\033', 'O', 'C', NUL, A_info_forward_word,
+ ESC, '\033', '[', 'C', NUL, A_info_forward_word,
+ ESC, SK_ESCAPE, SK_LEFT_ARROW, NUL, A_info_backward_word,
+ ESC, '\033', 'O', 'D', NUL, A_info_backward_word,
+ ESC, '\033', '[', 'D', NUL, A_info_backward_word,
+
+ /* We want help to report q, not C-x C-c, etc. */
+ 'q', NUL, A_info_quit,
+ 'x', NUL, A_info_delete_window,
+ SPC, NUL, A_info_scroll_forward,
+ DEL, NUL, A_info_scroll_backward,
+ '{', NUL, A_info_search_previous,
+ '}', NUL, A_info_search_next,
+ CONTROL('g'), NUL, A_info_abort_key,
+ SK_ESCAPE, SK_UP_ARROW, NUL, A_info_prev_line,
+ SK_ESCAPE, SK_DOWN_ARROW, NUL, A_info_next_line,
+};
+
+
+static unsigned char default_emacs_like_ea_keys[] =
+{
+ 0, /* suppress-default-keybindings flag */
+ ESC, '0', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '1', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '2', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '3', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '4', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '5', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '6', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '7', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '8', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '9', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '-', NUL, A_info_add_digit_to_numeric_arg,
+ Meta('0'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('1'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('2'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('3'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('4'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('5'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('6'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('7'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('8'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('9'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('-'), NUL, A_info_add_digit_to_numeric_arg,
+ ESC, CONTROL('g'), NUL, A_ea_abort,
+ ESC, CONTROL('v'), NUL, A_ea_scroll_completions_window,
+ ESC, 'b', NUL, A_ea_backward_word,
+ ESC, 'd', NUL, A_ea_kill_word,
+ ESC, 'f', NUL, A_ea_forward_word,
+ ESC, 'y', NUL, A_ea_yank_pop,
+ ESC, '?', NUL, A_ea_possible_completions,
+ ESC, TAB, NUL, A_ea_tab_insert,
+ ESC, DEL, NUL, A_ea_backward_kill_word,
+ Meta(CONTROL('g')), NUL, A_ea_abort,
+ Meta(CONTROL('v')), NUL, A_ea_scroll_completions_window,
+ Meta('b'), NUL, A_ea_backward_word,
+ Meta('d'), NUL, A_ea_kill_word,
+ Meta('f'), NUL, A_ea_forward_word,
+ Meta('y'), NUL, A_ea_yank_pop,
+ Meta('?'), NUL, A_ea_possible_completions,
+ Meta(TAB), NUL, A_ea_tab_insert,
+ Meta(DEL), NUL, A_ea_backward_kill_word,
+ CONTROL('a'), NUL, A_ea_beg_of_line,
+ CONTROL('b'), NUL, A_ea_backward,
+ CONTROL('d'), NUL, A_ea_delete,
+ CONTROL('e'), NUL, A_ea_end_of_line,
+ CONTROL('f'), NUL, A_ea_forward,
+ CONTROL('g'), NUL, A_ea_abort,
+ CONTROL('h'), NUL, A_ea_rubout,
+/* CONTROL('k') */
+ SK_ESCAPE, SK_LITERAL, NUL, A_ea_kill_line,
+ CONTROL('l'), NUL, A_info_redraw_display,
+ CONTROL('q'), NUL, A_ea_quoted_insert,
+ CONTROL('t'), NUL, A_ea_transpose_chars,
+ CONTROL('u'), NUL, A_info_universal_argument,
+ CONTROL('y'), NUL, A_ea_yank,
+ LFD, NUL, A_ea_newline,
+ RET, NUL, A_ea_newline,
+ SPC, NUL, A_ea_complete,
+ TAB, NUL, A_ea_complete,
+ '?', NUL, A_ea_possible_completions,
+#ifdef __MSDOS__
+ /* PC users will lynch me if I don't give them their usual DEL
+ effect... */
+ DEL, NUL, A_ea_delete,
+#else
+ DEL, NUL, A_ea_rubout,
+#endif
+#if defined (NAMED_FUNCTIONS)
+ /* ESC, 'x', NUL, A_info_execute_command, */
+ /* Meta('x'), NUL, A_info_execute_command, */
+#endif /* NAMED_FUNCTIONS */
+ CONTROL('x'), 'o', NUL, A_info_next_window,
+ CONTROL('x'), DEL, NUL, A_ea_backward_kill_line,
+
+/* Arrow key bindings for echo area keymaps. It seems that some
+ terminals do not match their termcap entries, so it's best to just
+ define everything with both of the usual prefixes. */
+
+ SK_ESCAPE, SK_RIGHT_ARROW, NUL, A_ea_forward,
+ '\033', 'O', 'C', NUL, A_ea_forward,
+ '\033', '[', 'C', NUL, A_ea_forward,
+ SK_ESCAPE, SK_LEFT_ARROW, NUL, A_ea_backward,
+ '\033', 'O', 'D', NUL, A_ea_backward,
+ '\033', '[', 'D', NUL, A_ea_backward,
+ ESC, SK_ESCAPE, SK_RIGHT_ARROW, NUL, A_ea_forward_word,
+ ESC, '\033', 'O', 'C', NUL, A_ea_forward_word,
+ ESC, '\033', '[', 'C', NUL, A_ea_forward_word,
+ ESC, SK_ESCAPE, SK_LEFT_ARROW, NUL, A_ea_backward_word,
+ ESC, '\033', 'O', 'D', NUL, A_ea_backward_word,
+ ESC, '\033', '[', 'D', NUL, A_ea_backward_word,
+#ifdef __MSDOS__
+ SK_ESCAPE, SK_DELETE, NUL, A_ea_delete,
+#else
+ SK_ESCAPE, SK_DELETE, NUL, A_ea_rubout,
+#endif
+ SK_ESCAPE, SK_HOME, NUL, A_ea_beg_of_line,
+ SK_ESCAPE, SK_END, NUL, A_ea_end_of_line,
+ ESC, SK_ESCAPE, SK_DELETE, NUL, A_ea_backward_kill_word,
+ CONTROL('x'), SK_ESCAPE, SK_DELETE, NUL,A_ea_backward_kill_line,
+};
+
+
+static unsigned char default_vi_like_info_keys[] =
+{
+ 0, /* suppress-default-keybindings flag */
+ '0', NUL, A_info_add_digit_to_numeric_arg,
+ '1', NUL, A_info_add_digit_to_numeric_arg,
+ '2', NUL, A_info_add_digit_to_numeric_arg,
+ '3', NUL, A_info_add_digit_to_numeric_arg,
+ '4', NUL, A_info_add_digit_to_numeric_arg,
+ '5', NUL, A_info_add_digit_to_numeric_arg,
+ '6', NUL, A_info_add_digit_to_numeric_arg,
+ '7', NUL, A_info_add_digit_to_numeric_arg,
+ '8', NUL, A_info_add_digit_to_numeric_arg,
+ '9', NUL, A_info_add_digit_to_numeric_arg,
+ '-', NUL, A_info_add_digit_to_numeric_arg,
+ TAB, NUL, A_info_move_to_next_xref,
+ LFD, NUL, A_info_down_line,
+ RET, NUL, A_info_down_line,
+ CONTROL('a'), NUL, A_info_beginning_of_line,
+ CONTROL('b'), NUL, A_info_scroll_backward_page_only,
+ CONTROL('d'), NUL, A_info_scroll_half_screen_down,
+ CONTROL('e'), NUL, A_info_down_line,
+ CONTROL('f'), NUL, A_info_scroll_forward_page_only,
+ CONTROL('k'), NUL, A_info_up_line,
+ CONTROL('l'), NUL, A_info_redraw_display,
+ CONTROL('n'), NUL, A_info_down_line,
+ CONTROL('p'), NUL, A_info_up_line,
+ CONTROL('r'), NUL, A_info_redraw_display,
+ CONTROL('s'), NUL, A_isearch_forward,
+ CONTROL('u'), NUL, A_info_scroll_half_screen_up,
+ CONTROL('v'), NUL, A_info_scroll_forward_page_only,
+ CONTROL('y'), NUL, A_info_up_line,
+ ',', NUL, A_info_next_index_match,
+ '/', NUL, A_info_search,
+ ESC, '0', NUL, A_info_last_menu_item,
+ ESC, '1', NUL, A_info_menu_digit,
+ ESC, '2', NUL, A_info_menu_digit,
+ ESC, '3', NUL, A_info_menu_digit,
+ ESC, '4', NUL, A_info_menu_digit,
+ ESC, '5', NUL, A_info_menu_digit,
+ ESC, '6', NUL, A_info_menu_digit,
+ ESC, '7', NUL, A_info_menu_digit,
+ ESC, '8', NUL, A_info_menu_digit,
+ ESC, '9', NUL, A_info_menu_digit,
+ Meta('0'), NUL, A_info_last_menu_item,
+ Meta('1'), NUL, A_info_menu_digit,
+ Meta('2'), NUL, A_info_menu_digit,
+ Meta('3'), NUL, A_info_menu_digit,
+ Meta('4'), NUL, A_info_menu_digit,
+ Meta('5'), NUL, A_info_menu_digit,
+ Meta('6'), NUL, A_info_menu_digit,
+ Meta('7'), NUL, A_info_menu_digit,
+ Meta('8'), NUL, A_info_menu_digit,
+ Meta('9'), NUL, A_info_menu_digit,
+ '<', NUL, A_info_first_node,
+ '>', NUL, A_info_last_node,
+ '?', NUL, A_info_search_backward,
+ '[', NUL, A_info_global_prev_node,
+ ']', NUL, A_info_global_next_node,
+ '\'', NUL, A_info_history_node,
+ 'b', NUL, A_info_scroll_backward,
+ 'd', NUL, A_info_scroll_half_screen_down,
+ 'e', NUL, A_info_down_line,
+ 'E', NUL, A_info_view_file,
+ ':', 'e', NUL, A_info_view_file,
+ 'f', NUL, A_info_scroll_forward_page_only,
+ 'F', NUL, A_info_scroll_forward_page_only,
+ 'g', NUL, A_info_first_node,
+ 'G', NUL, A_info_last_node,
+ 'h', NUL, A_info_get_help_window,
+ 'H', NUL, A_info_get_help_window,
+ 'i', NUL, A_info_index_search,
+ 'I', NUL, A_info_goto_invocation_node,
+ 'j', NUL, A_info_next_line,
+ 'k', NUL, A_info_prev_line,
+ 'l', NUL, A_info_history_node,
+ 'm', NUL, A_info_menu_item,
+ 'n', NUL, A_info_search_next,
+ 'N', NUL, A_info_search_previous,
+ 'O', NUL, A_info_goto_invocation_node,
+ 'p', NUL, A_info_prev_node,
+ 'Q', NUL, A_info_quit,
+ ':', 'q', NUL, A_info_quit,
+ ':', 'Q', NUL, A_info_quit,
+ 'Z', 'Z', NUL, A_info_quit,
+ 'r', NUL, A_info_redraw_display,
+ 'R', NUL, A_info_toggle_regexp,
+ 's', NUL, A_info_search,
+ 'S', NUL, A_info_search_case_sensitively,
+ 't', NUL, A_info_top_node,
+ 'u', NUL, A_info_scroll_half_screen_up,
+ 'w', NUL, A_info_scroll_backward_page_only_set_window,
+ 'y', NUL, A_info_up_line,
+ 'z', NUL, A_info_scroll_forward_page_only_set_window,
+ ESC, CONTROL('f'), NUL, A_info_show_footnotes,
+ ESC, CONTROL('g'), NUL, A_info_abort_key,
+ ESC, TAB, NUL, A_info_move_to_prev_xref,
+ ESC, SPC, NUL, A_info_scroll_forward_page_only,
+ ESC, CONTROL('v'), NUL, A_info_scroll_other_window,
+ ESC, '<', NUL, A_info_beginning_of_node,
+ ESC, '>', NUL, A_info_end_of_node,
+ ESC, '/', NUL, A_info_search,
+ ESC, '?', NUL, A_info_search_backward,
+ ESC, 'b', NUL, A_info_beginning_of_node,
+ ESC, 'd', NUL, A_info_dir_node,
+ ESC, 'e', NUL, A_info_end_of_node,
+ ESC, 'f', NUL, A_info_xref_item,
+ ESC, 'g', NUL, A_info_select_reference_this_line,
+ ESC, 'h', NUL, A_info_get_info_help_node,
+ ESC, 'm', NUL, A_info_menu_item,
+ ESC, 'n', NUL, A_info_search,
+ ESC, 'N', NUL, A_info_search_backward,
+ ESC, 'r', NUL, A_isearch_backward,
+ ESC, 's', NUL, A_isearch_forward,
+ ESC, 't', NUL, A_info_top_node,
+ ESC, 'v', NUL, A_info_scroll_backward_page_only,
+#if defined (NAMED_FUNCTIONS)
+ ESC, 'x', NUL, A_info_execute_command,
+ Meta('x'), NUL, A_info_execute_command,
+#endif /* NAMED_FUNCTIONS */
+ ESC, DEL, NUL, A_info_scroll_other_window_backward,
+ CONTROL('x'), CONTROL('b'), NUL, A_list_visited_nodes,
+ CONTROL('x'), CONTROL('c'), NUL, A_info_quit,
+ CONTROL('x'), CONTROL('f'), NUL, A_info_view_file,
+ CONTROL('x'), CONTROL('g'), NUL, A_info_abort_key,
+ CONTROL('x'), CONTROL('v'), NUL, A_info_view_file,
+ CONTROL('x'), LFD, NUL, A_info_select_reference_this_line,
+ CONTROL('x'), RET, NUL, A_info_select_reference_this_line,
+ CONTROL('x'), '0', NUL, A_info_delete_window,
+ CONTROL('x'), '1', NUL, A_info_keep_one_window,
+ CONTROL('x'), '2', NUL, A_info_split_window,
+ CONTROL('x'), '^', NUL, A_info_grow_window,
+ CONTROL('x'), 'b', NUL, A_select_visited_node,
+ CONTROL('x'), 'g', NUL, A_info_goto_node,
+ CONTROL('x'), 'i', NUL, A_info_index_search,
+ CONTROL('x'), 'I', NUL, A_info_goto_invocation_node,
+ CONTROL('x'), 'k', NUL, A_info_kill_node,
+ CONTROL('x'), 'n', NUL, A_info_next_node,
+ CONTROL('x'), 'o', NUL, A_info_next_window,
+ CONTROL('x'), 'O', NUL, A_info_goto_invocation_node,
+ CONTROL('x'), 'p', NUL, A_info_prev_node,
+ CONTROL('x'), 'r', NUL, A_info_xref_item,
+ CONTROL('x'), 't', NUL, A_info_tile_windows,
+ CONTROL('x'), 'u', NUL, A_info_up_node,
+ CONTROL('x'), 'w', NUL, A_info_toggle_wrap,
+ CONTROL('x'), ',', NUL, A_info_next_index_match,
+
+/* Arrow key bindings for info keymaps. It seems that some
+ terminals do not match their termcap entries, so it's best to just
+ define everything with both of the usual prefixes. */
+
+ SK_ESCAPE, SK_PAGE_UP, NUL, A_info_scroll_backward,
+ SK_ESCAPE, SK_PAGE_DOWN, NUL, A_info_scroll_forward,
+ '\033', 'O', 'A', NUL, A_info_up_line,
+ '\033', '[', 'A', NUL, A_info_up_line,
+ '\033', 'O', 'B', NUL, A_info_down_line,
+ '\033', '[', 'B', NUL, A_info_down_line,
+ SK_ESCAPE, SK_RIGHT_ARROW, NUL, A_info_scroll_forward_page_only,
+ '\033', 'O', 'C', NUL, A_info_scroll_forward_page_only,
+ '\033', '[', 'C', NUL, A_info_scroll_forward_page_only,
+ SK_ESCAPE, SK_LEFT_ARROW, NUL, A_info_scroll_backward_page_only,
+ '\033', 'O', 'D', NUL, A_info_scroll_backward_page_only,
+ '\033', '[', 'D', NUL, A_info_scroll_backward_page_only,
+ SK_ESCAPE, SK_HOME, NUL, A_info_beginning_of_node,
+ SK_ESCAPE, SK_END, NUL, A_info_end_of_node,
+ ESC, SK_ESCAPE, SK_PAGE_DOWN, NUL, A_info_scroll_other_window,
+ ESC, SK_ESCAPE, SK_PAGE_UP, NUL, A_info_scroll_other_window_backward,
+ ESC, SK_ESCAPE, SK_DELETE, NUL, A_info_scroll_other_window_backward,
+ ESC, SK_ESCAPE, SK_UP_ARROW, NUL, A_info_prev_node,
+ ESC, '\033', 'O', 'A', NUL, A_info_prev_node,
+ ESC, '\033', '[', 'A', NUL, A_info_prev_node,
+ ESC, SK_ESCAPE, SK_DOWN_ARROW, NUL, A_info_next_node,
+ ESC, '\033', 'O', 'B', NUL, A_info_next_node,
+ ESC, '\033', '[', 'B', NUL, A_info_next_node,
+ ESC, SK_ESCAPE, SK_RIGHT_ARROW, NUL, A_info_xref_item,
+ ESC, '\033', 'O', 'C', NUL, A_info_xref_item,
+ ESC, '\033', '[', 'C', NUL, A_info_xref_item,
+ ESC, SK_ESCAPE, SK_LEFT_ARROW, NUL, A_info_beginning_of_node,
+ ESC, '\033', 'O', 'D', NUL, A_info_beginning_of_node,
+ ESC, '\033', '[', 'D', NUL, A_info_beginning_of_node,
+ CONTROL('x'), SK_ESCAPE, SK_DELETE, NUL,A_ea_backward_kill_line,
+
+ /* We want help to report q, not C-x C-c, etc. */
+ 'q', NUL, A_info_quit,
+ 'x', NUL, A_info_delete_window,
+ SPC, NUL, A_info_scroll_forward,
+ DEL, NUL, A_info_scroll_backward,
+ '{', NUL, A_info_search_previous,
+ '}', NUL, A_info_search_next,
+ CONTROL('g'), NUL, A_info_abort_key,
+ SK_ESCAPE, SK_UP_ARROW, NUL, A_info_up_line,
+ SK_ESCAPE, SK_DOWN_ARROW, NUL, A_info_down_line,
+};
+
+
+static unsigned char default_vi_like_ea_keys[] =
+{
+ 0, /* suppress-default-keybindings flag */
+ ESC, '1', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '2', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '3', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '4', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '5', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '6', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '7', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '8', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '9', NUL, A_info_add_digit_to_numeric_arg,
+ ESC, '-', NUL, A_info_add_digit_to_numeric_arg,
+ Meta('1'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('2'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('3'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('4'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('5'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('6'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('7'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('8'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('9'), NUL, A_info_add_digit_to_numeric_arg,
+ Meta('-'), NUL, A_info_add_digit_to_numeric_arg,
+ ESC, CONTROL('g'), NUL, A_ea_abort,
+ ESC, CONTROL('h'), NUL, A_ea_backward_kill_word,
+ ESC, CONTROL('v'), NUL, A_ea_scroll_completions_window,
+ ESC, '0', NUL, A_ea_beg_of_line,
+ ESC, '$', NUL, A_ea_end_of_line,
+ ESC, 'b', NUL, A_ea_backward_word,
+ ESC, 'd', NUL, A_ea_kill_word,
+ ESC, 'f', NUL, A_ea_forward_word,
+ ESC, 'h', NUL, A_ea_forward,
+ ESC, 'l', NUL, A_ea_backward,
+ ESC, 'w', NUL, A_ea_forward_word,
+ ESC, 'x', NUL, A_ea_delete,
+ ESC, 'X', NUL, A_ea_kill_word,
+ ESC, 'y', NUL, A_ea_yank_pop,
+ ESC, '?', NUL, A_ea_possible_completions,
+ ESC, TAB, NUL, A_ea_tab_insert,
+ ESC, DEL, NUL, A_ea_kill_word,
+ Meta(CONTROL('g')), NUL, A_ea_abort,
+ Meta(CONTROL('h')), NUL, A_ea_backward_kill_word,
+ Meta(CONTROL('v')), NUL, A_ea_scroll_completions_window,
+ Meta('0'), NUL, A_ea_beg_of_line,
+ Meta('$'), NUL, A_ea_end_of_line,
+ Meta('b'), NUL, A_ea_backward_word,
+ Meta('d'), NUL, A_ea_kill_word,
+ Meta('f'), NUL, A_ea_forward_word,
+ Meta('h'), NUL, A_ea_forward,
+ Meta('l'), NUL, A_ea_backward,
+ Meta('w'), NUL, A_ea_forward_word,
+ Meta('x'), NUL, A_ea_delete,
+ Meta('X'), NUL, A_ea_kill_word,
+ Meta('y'), NUL, A_ea_yank_pop,
+ Meta('?'), NUL, A_ea_possible_completions,
+ Meta(TAB), NUL, A_ea_tab_insert,
+ Meta(DEL), NUL, A_ea_kill_word,
+ CONTROL('a'), NUL, A_ea_beg_of_line,
+ CONTROL('b'), NUL, A_ea_backward,
+ CONTROL('d'), NUL, A_ea_delete,
+ CONTROL('e'), NUL, A_ea_end_of_line,
+ CONTROL('f'), NUL, A_ea_forward,
+ CONTROL('g'), NUL, A_ea_abort,
+ CONTROL('h'), NUL, A_ea_rubout,
+/* CONTROL('k') */
+ SK_ESCAPE, SK_LITERAL, NUL, A_ea_kill_line,
+ CONTROL('l'), NUL, A_info_redraw_display,
+ CONTROL('q'), NUL, A_ea_quoted_insert,
+ CONTROL('t'), NUL, A_ea_transpose_chars,
+ CONTROL('u'), NUL, A_ea_abort,
+ CONTROL('v'), NUL, A_ea_quoted_insert,
+ CONTROL('y'), NUL, A_ea_yank,
+ LFD, NUL, A_ea_newline,
+ RET, NUL, A_ea_newline,
+ SPC, NUL, A_ea_complete,
+ TAB, NUL, A_ea_complete,
+ '?', NUL, A_ea_possible_completions,
+#ifdef __MSDOS__
+ /* PC users will lynch me if I don't give them their usual DEL
+ effect... */
+ DEL, NUL, A_ea_delete,
+#else
+ DEL, NUL, A_ea_rubout,
+#endif
+ CONTROL('x'), 'o', NUL, A_info_next_window,
+ CONTROL('x'), DEL, NUL, A_ea_backward_kill_line,
+
+ /* Arrow key bindings for echo area keymaps. It seems that some
+ terminals do not match their termcap entries, so it's best to just
+ define everything with both of the usual prefixes. */
+
+ SK_ESCAPE, SK_RIGHT_ARROW, NUL, A_ea_forward,
+ '\033', 'O', 'C', NUL, A_ea_forward,
+ '\033', '[', 'C', NUL, A_ea_forward,
+ SK_ESCAPE, SK_LEFT_ARROW, NUL, A_ea_backward,
+ '\033', 'O', 'D', NUL, A_ea_backward,
+ '\033', '[', 'D', NUL, A_ea_backward,
+ SK_ESCAPE, SK_HOME, NUL, A_ea_beg_of_line,
+ SK_ESCAPE, SK_END, NUL, A_ea_end_of_line,
+#ifdef __MSDOS__
+ SK_ESCAPE, SK_DELETE, NUL, A_ea_delete,
+#else
+ SK_DELETE, SK_DELETE, NUL, A_ea_rubout,
+#endif
+ ESC, SK_ESCAPE, SK_RIGHT_ARROW, NUL, A_ea_forward_word,
+ ESC, '\033', 'O', 'C', NUL, A_ea_forward_word,
+ ESC, '\033', '[', 'C', NUL, A_ea_forward_word,
+ ESC, SK_ESCAPE, SK_LEFT_ARROW, NUL, A_ea_backward_word,
+ ESC, '\033', 'O', 'D', NUL, A_ea_backward_word,
+ ESC, '\033', '[', 'D', NUL, A_ea_backward_word,
+ ESC, SK_ESCAPE, SK_DELETE, NUL, A_ea_kill_word,
+ CONTROL('x'), SK_ESCAPE, SK_DELETE, NUL,A_ea_backward_kill_line,
+};
+
+
+static unsigned char *user_info_keys;
+static unsigned int user_info_keys_len;
+static unsigned char *user_ea_keys;
+static unsigned int user_ea_keys_len;
+static unsigned char *user_vars;
+static unsigned int user_vars_len;
+
+/*
+ * Return the size of a file, or 0 if the size can't be determined.
+ */
+static unsigned long
+filesize (int f)
+{
+ long pos = lseek(f, 0L, SEEK_CUR);
+ long sz = -1L;
+ if (pos != -1L)
+ {
+ sz = lseek(f, 0L, SEEK_END);
+ lseek(f, pos, SEEK_SET);
+ }
+ return sz == -1L ? 0L : sz;
+}
+
+/* Get an integer from a infokey file.
+ Integers are stored as two bytes, low order first, in radix INFOKEY_RADIX.
+ */
+static int
+getint (unsigned char **sp)
+{
+ int n;
+
+ if ( !((*sp)[0] < INFOKEY_RADIX && (*sp)[1] < INFOKEY_RADIX) )
+ return -1;
+ n = (*sp)[0] + (*sp)[1] * INFOKEY_RADIX;
+ *sp += 2;
+ return n;
+}
+
+
+/* Fetch the contents of the standard infokey file "$HOME/.info". Return
+ true if ok, false if not. */
+static int
+fetch_user_maps (void)
+{
+ char *filename = NULL;
+ char *homedir;
+ int f;
+ unsigned char *buf;
+ unsigned long len;
+ long nread;
+ unsigned char *p;
+ int n;
+
+ /* Find and open file. */
+ if ((filename = getenv("INFOKEY")) != NULL)
+ filename = xstrdup(filename);
+ else if ((homedir = getenv("HOME")) != NULL)
+ {
+ filename = xmalloc(strlen(homedir) + 2 + strlen(INFOKEY_FILE));
+ strcpy(filename, homedir);
+ strcat(filename, "/");
+ strcat(filename, INFOKEY_FILE);
+ }
+#ifdef __MSDOS__
+ /* Poor baby, she doesn't have a HOME... */
+ else
+ filename = xstrdup(INFOKEY_FILE); /* try current directory */
+#endif
+ if (filename == NULL || (f = open(filename, O_RDONLY)) == (-1))
+ {
+ if (filename && errno != ENOENT)
+ {
+ info_error(filesys_error_string(filename, errno),
+ NULL, NULL);
+ free(filename);
+ }
+ return 0;
+ }
+ SET_BINARY (f);
+
+ /* Ensure that the file is a reasonable size. */
+ len = filesize(f);
+ if (len < INFOKEY_NMAGIC + 2 || len > 100 * 1024)
+ {
+ /* Bad file (a valid file must have at least 9 chars, and
+ more than 100 KB is a problem). */
+ if (len < INFOKEY_NMAGIC + 2)
+ info_error(_("Ignoring invalid infokey file `%s' - too small"),
+ filename, NULL);
+ else
+ info_error(_("Ignoring invalid infokey file `%s' - too big"),
+ filename, NULL);
+ close(f);
+ free(filename);
+ return 0;
+ }
+
+ /* Read the file into a buffer. */
+ buf = xmalloc((int)len);
+ nread = read(f, buf, (unsigned int) len);
+ close(f);
+ if ((unsigned int) nread != len)
+ {
+ info_error(_("Error reading infokey file `%s' - short read"),
+ filename, NULL);
+ free(buf);
+ free(filename);
+ return 0;
+ }
+
+ /* Check the header, trailer, and version of the file to increase
+ our confidence that the contents are valid. */
+ if ( buf[0] != INFOKEY_MAGIC_S0
+ || buf[1] != INFOKEY_MAGIC_S1
+ || buf[2] != INFOKEY_MAGIC_S2
+ || buf[3] != INFOKEY_MAGIC_S3
+ || buf[len - 4] != INFOKEY_MAGIC_E0
+ || buf[len - 3] != INFOKEY_MAGIC_E1
+ || buf[len - 2] != INFOKEY_MAGIC_E2
+ || buf[len - 1] != INFOKEY_MAGIC_E3
+ )
+ {
+ info_error(_("Invalid infokey file `%s' (bad magic numbers) -- run infokey to update it"),
+ filename, NULL);
+ free(filename);
+ return 0;
+ }
+ if (len < INFOKEY_NMAGIC + strlen(VERSION) + 1
+ || strcmp(VERSION, (char *) (buf + 4)) != 0)
+ {
+ info_error
+ (_("Your infokey file `%s' is out of date -- run infokey to update it"),
+ filename, NULL);
+ free(filename);
+ return 0;
+ }
+
+ /* Extract the pieces. */
+ for (p = buf + 4 + strlen(VERSION) + 1;
+ (unsigned int) (p - buf) < len - 4;
+ p += n)
+ {
+ int s = *p++;
+
+ n = getint(&p);
+ if (n < 0 || (unsigned int) n > len - 4 - (p - buf))
+ {
+ info_error(_("Invalid infokey file `%s' (bad section length) -- run infokey to update it"),
+ filename, NULL);
+ free(filename);
+ return 0;
+ }
+
+ switch (s)
+ {
+ case INFOKEY_SECTION_INFO:
+ user_info_keys = p;
+ user_info_keys_len = n;
+ break;
+ case INFOKEY_SECTION_EA:
+ user_ea_keys = p;
+ user_ea_keys_len = n;
+ break;
+ case INFOKEY_SECTION_VAR:
+ user_vars = p;
+ user_vars_len = n;
+ break;
+ default:
+ info_error(_("Invalid infokey file `%s' (bad section code) -- run infokey to update it"),
+ filename, NULL);
+ free(filename);
+ return 0;
+ }
+ }
+
+ free(filename);
+ return 1;
+}
+
+/* Decode special key sequences from the infokey file. Return zero
+ if the key sequence includes special keys which the terminal
+ doesn't define.
+ */
+static int
+decode_keys(unsigned char *src, unsigned int slen,
+ unsigned char *dst, unsigned int dlen)
+{
+ unsigned char *s = src;
+ unsigned char *d = dst;
+
+#define To_dst(c) do { \
+ if ((unsigned int) (d - dst) < dlen) *d++ = (c); \
+} while (0)
+
+ while ((unsigned int) (s - src) < slen)
+ {
+ unsigned char c = ISMETA(*s) ? UNMETA(*s) : *s;
+
+ if (c == SK_ESCAPE)
+ {
+ char *t;
+ static char lit[] = { SK_ESCAPE, NUL };
+
+ switch ((unsigned int) (s + 1 - src) < slen ? s[1] : '\0')
+ {
+ case SK_RIGHT_ARROW: t = term_kr; break;
+ case SK_LEFT_ARROW: t = term_kl; break;
+ case SK_UP_ARROW: t = term_ku; break;
+ case SK_DOWN_ARROW: t = term_kd; break;
+ case SK_PAGE_UP: t = term_kP; break;
+ case SK_PAGE_DOWN: t = term_kN; break;
+ case SK_HOME: t = term_kh; break;
+ case SK_END: t = term_ke; break;
+ case SK_DELETE: t = term_kx; break;
+ case SK_INSERT: t = term_ki; break;
+ case SK_LITERAL:
+ default: t = lit; break;
+ }
+ if (t == NULL)
+ return 0;
+ while (*t)
+ To_dst(ISMETA(*s) ? Meta(*t++) : *t++);
+ s += 2;
+ }
+ else
+ {
+ if (ISMETA(*s))
+ To_dst(Meta(*s++));
+ else
+ To_dst(*s++);
+ }
+ }
+
+ To_dst('\0');
+
+ return 1;
+
+#undef To_dst
+
+}
+
+/* Convert an infokey file section to keymap bindings. Return false if
+ the default bindings are to be suppressed. */
+static int
+section_to_keymaps(Keymap map, unsigned char *table, unsigned int len)
+{
+ int stop;
+ unsigned char *p;
+ unsigned char *seq = NULL;
+ unsigned int seqlen = 0;
+ enum { getseq, gotseq, getaction } state = getseq;
+
+ stop = len > 0 ? table[0] : 0;
+
+ for (p = table + 1; (unsigned int) (p - table) < len; p++)
+ {
+ switch (state)
+ {
+ case getseq:
+ if (*p)
+ {
+ seq = p;
+ state = gotseq;
+ }
+ break;
+
+ case gotseq:
+ if (!*p)
+ {
+ seqlen = p - seq;
+ state = getaction;
+ }
+ break;
+
+ case getaction:
+ {
+ unsigned int action = *p;
+ unsigned char keyseq[256];
+ KEYMAP_ENTRY ke;
+
+ state = getseq;
+ /* If decode_keys returns zero, it
+ means that seq includes keys which
+ the terminal doesn't support, like
+ PageDown. In that case, don't bind
+ the key sequence. */
+ if (decode_keys(seq, seqlen, keyseq,
+ sizeof keyseq))
+ {
+ keyseq[sizeof keyseq - 1] = '\0';
+ ke.type = ISFUNC;
+ ke.function =
+ action < A_NCOMMANDS
+ ? &function_doc_array[action]
+ : NULL;
+ keymap_bind_keyseq(map,
+ (const char *) keyseq, &ke);
+ }
+ }
+ break;
+ }
+ }
+ if (state != getseq)
+ info_error(_("Bad data in infokey file -- some key bindings ignored"),
+ NULL, NULL);
+ return !stop;
+}
+
+/* Convert an infokey file section to variable settings.
+ */
+static void
+section_to_vars(unsigned char *table, unsigned int len)
+{
+ enum { getvar, gotvar, getval, gotval } state = getvar;
+ unsigned char *var = NULL;
+ unsigned char *val = NULL;
+ unsigned char *p;
+
+ for (p = table; (unsigned int) (p - table) < len; p++)
+ {
+ switch (state)
+ {
+ case getvar:
+ if (*p)
+ {
+ var = p;
+ state = gotvar;
+ }
+ break;
+
+ case gotvar:
+ if (!*p)
+ state = getval;
+ break;
+
+ case getval:
+ if (*p)
+ {
+ val = p;
+ state = gotval;
+ }
+ break;
+
+ case gotval:
+ if (!*p)
+ {
+ set_variable_to_value((char *) var, (char *) val);
+ state = getvar;
+ }
+ break;
+ }
+ }
+ if (state != getvar)
+ info_error(_("Bad data in infokey file -- some var settings ignored"),
+ NULL, NULL);
+}
+
+void
+initialize_info_keymaps (void)
+{
+ int i;
+ int suppress_info_default_bindings = 0;
+ int suppress_ea_default_bindings = 0;
+
+ if (!info_keymap)
+ {
+ info_keymap = keymap_make_keymap ();
+ echo_area_keymap = keymap_make_keymap ();
+ }
+
+ /* Bind the echo area insert routines. */
+ for (i = 0; i < 256; i++)
+ if (isprint (i))
+ echo_area_keymap[i].function = InfoCmd(ea_insert);
+
+ /* Get user-defined keys and variables. */
+ if (fetch_user_maps())
+ {
+ if (user_info_keys_len && user_info_keys[0])
+ suppress_info_default_bindings = 1;
+ if (user_ea_keys_len && user_ea_keys[0])
+ suppress_ea_default_bindings = 1;
+ }
+
+ /* Apply the default bindings, unless the user says to suppress
+ them. */
+ if (vi_keys_p)
+ {
+ if (!suppress_info_default_bindings)
+ section_to_keymaps(info_keymap, default_vi_like_info_keys,
+ sizeof(default_vi_like_info_keys));
+ if (!suppress_ea_default_bindings)
+ section_to_keymaps(echo_area_keymap, default_vi_like_ea_keys,
+ sizeof(default_vi_like_ea_keys));
+ }
+ else
+ {
+ if (!suppress_info_default_bindings)
+ section_to_keymaps(info_keymap, default_emacs_like_info_keys,
+ sizeof(default_emacs_like_info_keys));
+ if (!suppress_ea_default_bindings)
+ section_to_keymaps(echo_area_keymap, default_emacs_like_ea_keys,
+ sizeof(default_emacs_like_ea_keys));
+ }
+
+ /* If the user specified custom bindings, apply them on top of the
+ default ones. */
+ if (user_info_keys_len)
+ section_to_keymaps(info_keymap, user_info_keys, user_info_keys_len);
+
+ if (user_ea_keys_len)
+ section_to_keymaps(echo_area_keymap, user_ea_keys, user_ea_keys_len);
+
+ if (user_vars_len)
+ section_to_vars(user_vars, user_vars_len);
+}
+
+/* vim: set sw=2 cino={1s>2sn-s^-se-s: */
diff --git a/info/infomap.h b/info/infomap.h
new file mode 100644
index 0000000..1e67069
--- /dev/null
+++ b/info/infomap.h
@@ -0,0 +1,81 @@
+/* infomap.h -- description of a keymap in Info and related functions.
+ $Id: infomap.h,v 1.6 2007/07/01 21:20:30 karl Exp $
+
+ Copyright (C) 1993, 2001, 2004, 2007 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#ifndef INFOMAP_H
+#define INFOMAP_H
+
+#include "info.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 keymap_entry
+{
+ char type;
+ InfoCommand *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 (void);
+
+/* Return a new keymap which is a copy of MAP. */
+extern Keymap keymap_copy_keymap (Keymap map, Keymap rootmap,
+ Keymap newroot);
+
+/* Free MAP and it's descendents. */
+extern void keymap_discard_keymap (Keymap map, Keymap rootmap);
+
+/* Initialize the info keymaps. */
+extern void initialize_info_keymaps (void);
+
+#endif /* not INFOMAP_H */
diff --git a/info/key.h b/info/key.h
new file mode 100644
index 0000000..e8409da
--- /dev/null
+++ b/info/key.h
@@ -0,0 +1,34 @@
+/* key.h -- Structure associating function names with numeric codes. */
+
+/* This file is part of GNU Info, a program for reading online documentation
+ stored in Info format.
+
+ Copyright (C) 1993, 2007 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Andrew Bettison <andrewb@zip.com.au> */
+
+#if !defined (KEY_H)
+#define KEY_H
+
+typedef struct {
+ char *name;
+ unsigned char code;
+}
+ FUNCTION_KEY;
+
+extern FUNCTION_KEY function_key_array[];
+
+#endif /* !KEY_H */
diff --git a/info/m-x.c b/info/m-x.c
new file mode 100644
index 0000000..a66f3ed
--- /dev/null
+++ b/info/m-x.c
@@ -0,0 +1,213 @@
+/* m-x.c -- Meta-x minibuffer reader.
+ $Id: m-x.c,v 1.8 2008/06/11 09:55:42 gray Exp $
+
+ Copyright (C) 1993, 1997, 1998, 2001, 2002, 2004, 2007, 2008
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Originally written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include "funs.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 (const char *prompt, WINDOW *window)
+{
+ register int i;
+ char *line;
+ REFERENCE **array = 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 = xmalloc (sizeof (REFERENCE));
+ entry->label = xstrdup (function_doc_array[i].func_name);
+ entry->nodename = NULL;
+ entry->filename = 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)
+ {
+ InfoCommand *cmd = named_function (line);
+
+ if (!cmd)
+ return;
+
+ window_message_in_echo_area ("%s: %s.",
+ line, function_documentation (cmd));
+ }
+ free (line);
+}
+
+DECLARE_INFO_COMMAND (info_execute_command,
+ _("Read a command name in the echo area and execute it"))
+{
+ char *line;
+ char *keys;
+ char *prompt;
+
+ prompt = xmalloc (20);
+
+ keys = where_is (info_keymap, InfoCmd(info_execute_command));
+ /* If the where_is () function thinks that this command doesn't exist,
+ there's something very wrong! */
+ if (!keys)
+ abort();
+
+ if (info_explicit_arg || count != 1)
+ sprintf (prompt, "%d %s ", count, keys);
+ else
+ sprintf (prompt, "%s ", keys);
+
+ /* Ask the completer to read a reference for us. */
+ line = read_function_name (prompt, 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. */
+ {
+ InfoCommand *command;
+
+ if ((active_window != the_echo_area) &&
+ (strncmp (line, "echo-area-", 10) == 0))
+ {
+ free (line);
+ info_error (_("Cannot execute an `echo-area' command here."),
+ NULL, NULL);
+ return;
+ }
+
+ command = named_function (line);
+ free (line);
+
+ if (!command)
+ return;
+
+ if (InfoFunction(command))
+ (*InfoFunction(command)) (active_window, count, 0);
+ else
+ info_error (_("Undefined command: %s"), line, NULL);
+ }
+}
+
+/* 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, old_height = screenheight;
+
+ 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;
+#ifdef SET_SCREEN_SIZE_HELPER
+ SET_SCREEN_SIZE_HELPER;
+#endif
+ if (screenheight == old_height)
+ {
+ /* Display dimensions didn't actually change, so
+ window_new_screen_size won't do anything, but we've
+ already cleared the display above. Undo the damage. */
+ window_mark_chain (windows, W_UpdateWindow);
+ display_update_display (windows);
+ }
+ else
+ {
+ display_initialize_display (screenwidth, screenheight);
+ window_new_screen_size (screenwidth, screenheight);
+ }
+}
diff --git a/info/makedoc.c b/info/makedoc.c
new file mode 100644
index 0000000..21fcb3b
--- /dev/null
+++ b/info/makedoc.c
@@ -0,0 +1,584 @@
+/* makedoc.c -- make doc.c and funs.h from input files.
+ $Id: makedoc.c,v 1.10 2008/06/11 09:55:42 gray Exp $
+
+ Copyright (C) 1993, 1997, 1998, 1999, 2001, 2002, 2003, 2004, 2007,
+ 2008 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Originally 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 "info.h"
+#include "infokey.h"
+
+char *program_name = "makedoc";
+
+static void fatal_file_error (char *filename);
+
+/* 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 *key_filename = "key.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'.",
+ 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 \"info.h\"",
+ "#include \"funs.h\"",
+ "",
+ "FUNCTION_DOC function_doc_array[] = {",
+ "",
+ NULL
+};
+
+static char *key_header[] = {
+ "/* key.c -- Generated array containing function names.",
+ "",
+ " This file was automatically made from various source files with the",
+ " command \"%s\". DO NOT EDIT THIS FILE, only \"%s.c\".",
+ "",
+ NULL
+};
+
+static char *key_header_1[] = {
+ " An entry in the array FUNCTION_KEY_ARRAY is made for each command",
+ " found in the above files; each entry consists of",
+ " a string which is the user-visible name of the function. */",
+ "",
+ "#include \"key.h\"",
+ "#include \"funs.h\"",
+ "",
+ "FUNCTION_KEY function_key_array[] = {",
+ "",
+ 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 = NULL;
+int emacs_tags_index = 0;
+int emacs_tags_slots = 0;
+
+#define DECLARATION_STRING "\nDECLARE_INFO_COMMAND"
+
+static void process_one_file (char *filename, FILE *doc_stream,
+ FILE *key_stream, FILE *funs_stream);
+static void maybe_dump_tags (FILE *stream);
+static FILE *must_fopen (char *filename, char *mode);
+static void init_func_key (unsigned int val);
+static unsigned int next_func_key (void);
+
+int
+main (int argc, char **argv)
+{
+ register int i;
+ int tags_only = 0;
+ FILE *funs_stream, *doc_stream;
+ FILE *key_stream;
+
+#if STRIP_DOT_EXE
+ {
+ char *dot = strrchr (argv[0], '.');
+
+ if (dot && FILENAME_CMP (dot, ".exe") == 0)
+ *dot = 0;
+ }
+#endif
+
+ for (i = 1; i < argc; i++)
+ if (strcmp (argv[i], "-tags") == 0)
+ {
+ tags_only++;
+ break;
+ }
+
+ if (tags_only)
+ {
+ funs_filename = NULL_DEVICE;
+ doc_filename = NULL_DEVICE;
+ key_filename = NULL_DEVICE;
+ }
+
+ /* The order of these calls depends exactly on the order in the
+ Makefile.{in,am}, or they might fail on filesystems with
+ high-precision times; see also the fclose calls below. */
+ funs_stream = must_fopen (funs_filename, "w");
+ key_stream = must_fopen (key_filename, "w");
+ doc_stream = must_fopen (doc_filename, "w");
+
+ fprintf (funs_stream,
+ "/* %s -- Generated declarations for Info commands. */\n\n\
+#include \"info.h\"\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 = 0; key_header[i]; i++)
+ {
+ fprintf (key_stream, key_header[i], argv[0], argv[0]);
+ fprintf (key_stream, "\n");
+ }
+ fprintf (key_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 (key_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]);
+
+ fprintf (key_stream, "\n");
+ for (i = 0; key_header_1[i]; i++)
+ fprintf (key_stream, "%s\n", key_header_1[i]);
+
+ init_func_key(0);
+
+ for (i = 1; i < argc; i++)
+ {
+ char *curfile;
+ curfile = argv[i];
+
+ if (*curfile == '-')
+ continue;
+
+ fprintf (doc_stream, "/* Commands found in \"%s\". */\n", curfile);
+ fprintf (key_stream, "/* Commands found in \"%s\". */\n", curfile);
+ fprintf (funs_stream, "\n/* Functions declared in \"%s\". */\n",
+ curfile);
+
+ process_one_file (curfile, doc_stream, key_stream, funs_stream);
+ }
+
+#if defined (INFOKEY)
+
+#if defined (NAMED_FUNCTIONS)
+ fprintf (doc_stream,
+ " { NULL, NULL, NULL, NULL }\n};\n");
+#else /* !NAMED_FUNCTIONS */
+ fprintf (doc_stream, " { NULL, NULL, NULL }\n};\n");
+#endif /* !NAMED_FUNCTIONS */
+
+#else /* !INFOKEY */
+
+#if defined (NAMED_FUNCTIONS)
+ fprintf (doc_stream,
+ " { NULL, NULL, NULL }\n};\n");
+#else /* !NAMED_FUNCTIONS */
+ fprintf (doc_stream, " { NULL, NULL }\n};\n");
+#endif /* !NAMED_FUNCTIONS */
+
+#endif /* !INFOKEY */
+
+ fprintf (key_stream, " { NULL, 0 }\n};\n");
+ fprintf (funs_stream, "\n#define A_NCOMMANDS %u\n", next_func_key());
+
+ /* The order of these calls also depends exactly on the order in the
+ * Makefile.{in,am}; see the must_fopen calls above. */
+ fclose (funs_stream);
+ fclose (key_stream);
+ fclose (doc_stream);
+
+ if (tags_only)
+ maybe_dump_tags (stdout);
+ return 0;
+}
+
+/* Dumping out the contents of an Emacs tags table. */
+static void
+maybe_dump_tags (FILE *stream)
+{
+ register int i;
+
+ /* Emacs needs its TAGS file to be in Unix text format (i.e., only
+ newline at end of every line, no CR), so when we generate a
+ TAGS table, we must switch the output stream to binary mode.
+ (If the table is written to a terminal, this is obviously not needed.) */
+ SET_BINARY (fileno (stream));
+
+ /* 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,%ld", etag->line, etag->char_offset);
+ block_len += strlen (digits);
+ }
+
+ /* Print out the defining line. */
+ fprintf (stream, "\f\n%s,%ld\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,%ld\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 (char *filename)
+{
+ EMACS_TAG_BLOCK *block;
+
+ block = xmalloc (sizeof (EMACS_TAG_BLOCK));
+ block->filename = xstrdup (filename);
+ block->entrylen = 0;
+ block->entries = NULL;
+ block->entries_index = 0;
+ block->entries_slots = 0;
+ return block;
+}
+
+static void
+add_tag_to_block (EMACS_TAG_BLOCK *block,
+ char *name, int line, long int char_offset)
+{
+ EMACS_TAG *tag;
+
+ 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, KEY_STREAM, and FUNS_STREAM. */
+static void
+process_one_file (char *filename, FILE *doc_stream,
+ FILE *key_stream, FILE *funs_stream)
+{
+ int descriptor, decl_len;
+ char *buffer, *decl_str;
+ struct stat finfo;
+ long offset;
+ long file_size;
+ 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);
+
+ file_size = (long) finfo.st_size;
+ buffer = xmalloc (1 + file_size);
+ /* On some systems, the buffer will actually contain
+ less characters than the full file's size, because
+ the CR characters are removed from line endings. */
+ file_size = read (descriptor, buffer, file_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 (INFOKEY) || defined (NAMED_FUNCTIONS)
+ char *func_name;
+#endif /* INFOKEY || NAMED_FUNCTIONS */
+
+ for (; offset < (file_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 < file_size)
+ {
+ if (buffer[point] == '\n')
+ {
+ line_number++;
+ line_start = point + 1;
+ }
+ else if (buffer[point] == '(')
+ break;
+
+ point++;
+ }
+
+ while (point++ < file_size)
+ {
+ if (!whitespace_or_newline (buffer[point]))
+ break;
+ else if (buffer[point] == '\n')
+ {
+ line_number++;
+ line_start = point + 1;
+ }
+ }
+
+ if (point >= file_size)
+ break;
+
+ /* Now looking at name of function. Get it. */
+ for (offset = point; buffer[offset] != ','; offset++);
+ func = 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 = 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 (INFOKEY) || 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 = xstrdup (name_start);
+
+ /* Fix up "ea" commands. */
+ if (strncmp (func_name, "ea_", 3) == 0)
+ {
+ char *temp_func_name;
+
+ temp_func_name = 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 /* INFOKEY || NAMED_FUNCTIONS */
+
+ /* Find doc string. */
+ point = offset + 1;
+
+ while (point < file_size)
+ {
+ if (buffer[point] == '\n')
+ {
+ line_number++;
+ line_start = point + 1;
+ }
+
+ if (buffer[point] == '"')
+ break;
+ else
+ point++;
+ }
+
+ offset = point + 1;
+
+ while (offset < file_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 >= file_size)
+ break;
+
+ doc = xmalloc (1 + (offset - point));
+ strncpy (doc, buffer + point, offset - point);
+ doc[offset - point] = '\0';
+
+#if defined (INFOKEY)
+
+#if defined (NAMED_FUNCTIONS)
+ fprintf (doc_stream,
+ " { (VFunction *)%s, \"%s\", (FUNCTION_KEYSEQ *)0, %s },\n",
+ func, func_name, doc);
+#else /* !NAMED_FUNCTIONS */
+ fprintf (doc_stream,
+ " { (VFunction *) %s, (FUNCTION_KEYSEQ *)0, %s },\n", func, doc);
+#endif /* !NAMED_FUNCTIONS */
+
+ fprintf (key_stream, " { \"%s\", A_%s },\n", func_name, func);
+
+#else /* !INFOKEY */
+
+#if defined (NAMED_FUNCTIONS)
+ fprintf (doc_stream, " { %s, \"%s\", %s },\n", func, func_name, doc);
+#else /* !NAMED_FUNCTIONS */
+ fprintf (doc_stream, " { %s, %s },\n", func, doc);
+#endif /* !NAMED_FUNCTIONS */
+
+#endif /* !INFOKEY */
+
+#if defined (INFOKEY) || defined (NAMED_FUNCTIONS)
+ free (func_name);
+#endif /* INFOKEY || NAMED_FUNCTIONS */
+
+#if defined (INFOKEY)
+ fprintf (funs_stream, "#define A_%s %u\n", func, next_func_key());
+#endif /* INFOKEY */
+ fprintf (funs_stream,
+ "extern void %s (WINDOW *window, int count, unsigned char key);\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 (char *filename)
+{
+ fprintf (stderr, _("Couldn't manipulate the file %s.\n"), filename);
+ xexit (2);
+}
+
+static FILE *
+must_fopen (char *filename, char *mode)
+{
+ FILE *stream;
+
+ stream = fopen (filename, mode);
+ if (!stream)
+ fatal_file_error (filename);
+
+ return stream;
+}
+
+static unsigned int func_key;
+
+static void
+init_func_key(unsigned int val)
+{
+ func_key = val;
+}
+
+static unsigned int
+next_func_key(void)
+{
+ return func_key++;
+}
diff --git a/info/man.c b/info/man.c
new file mode 100644
index 0000000..27d1c27
--- /dev/null
+++ b/info/man.c
@@ -0,0 +1,678 @@
+/* man.c: How to read and format man files.
+ $Id: man.c,v 1.14 2008/06/28 08:09:32 gray Exp $
+
+ Copyright (C) 1995, 1997, 1998, 1999, 2000, 2002, 2003, 2004, 2005,
+ 2007, 2008 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Originally written by Brian Fox Thu May 4 09:17:52 1995. */
+
+#include "info.h"
+#include <sys/ioctl.h>
+#include "signals.h"
+#if defined (HAVE_SYS_TIME_H)
+#include <sys/time.h>
+#endif
+#if defined (HAVE_SYS_WAIT_H)
+#include <sys/wait.h>
+#endif
+
+#include "tilde.h"
+#include "man.h"
+
+#if !defined (_POSIX_VERSION)
+#define pid_t int
+#endif
+
+#if defined (FD_SET)
+# if defined (hpux)
+# define fd_set_cast(x) (int *)(x)
+# else
+# define fd_set_cast(x) (fd_set *)(x)
+# endif /* !hpux */
+#endif /* FD_SET */
+
+#if STRIP_DOT_EXE
+static char const * const exec_extensions[] = {
+ ".exe", ".com", ".bat", ".btm", ".sh", ".ksh", ".pl", ".sed", "", NULL
+};
+#else
+static char const * const exec_extensions[] = { "", NULL };
+#endif
+
+static char *read_from_fd (int fd);
+static NODE *manpage_node_of_file_buffer (FILE_BUFFER *file_buffer,
+ char *pagename);
+static char *get_manpage_contents (char *pagename);
+
+NODE *
+make_manpage_node (char *pagename)
+{
+ return info_get_node (MANPAGE_FILE_BUFFER_NAME, pagename);
+}
+
+NODE *
+get_manpage_node (FILE_BUFFER *file_buffer, char *pagename)
+{
+ NODE *node;
+
+ node = manpage_node_of_file_buffer (file_buffer, pagename);
+
+ if (!node)
+ {
+ char *page;
+
+ page = get_manpage_contents (pagename);
+
+ if (page)
+ {
+ char header[1024];
+ long oldsize, newsize;
+ int hlen, plen;
+ char *old_contents = file_buffer->contents;
+
+ sprintf (header, "\n\n%c\n%s %s, %s %s, %s (dir)\n\n",
+ INFO_COOKIE,
+ INFO_FILE_LABEL, file_buffer->filename,
+ INFO_NODE_LABEL, pagename,
+ INFO_UP_LABEL);
+ oldsize = file_buffer->filesize;
+ hlen = strlen (header);
+ plen = strlen (page);
+ newsize = (oldsize + hlen + plen);
+ file_buffer->contents = xrealloc (file_buffer->contents, 1 + newsize);
+ memcpy (file_buffer->contents + oldsize, header, hlen);
+ memcpy (file_buffer->contents + oldsize + hlen, page, plen);
+ file_buffer->contents[newsize] = '\0';
+ file_buffer->filesize = newsize;
+ file_buffer->finfo.st_size = newsize;
+ build_tags_and_nodes (file_buffer);
+ free (page);
+ /* We have just relocated file_buffer->contents from under
+ the feet of info_windows[] array. Therefore, all the
+ nodes on that list which are showing man pages have their
+ contents member pointing into the blue. Undo that harm. */
+ if (old_contents && oldsize && old_contents != file_buffer->contents)
+ {
+ int iw;
+ INFO_WINDOW *info_win;
+ char *old_contents_end = old_contents + oldsize;
+
+ for (iw = 0; (info_win = info_windows[iw]); iw++)
+ {
+ int in;
+
+ for (in = 0; in < info_win->nodes_index; in++)
+ {
+ NODE *tmp_node = info_win->nodes[in];
+
+ /* It really only suffices to see that node->filename
+ is "*manpages*". But after several hours of
+ debugging this, would you blame me for being a bit
+ paranoid? */
+ if (tmp_node && tmp_node->filename
+ && tmp_node->contents
+ && strcmp (tmp_node->filename,
+ MANPAGE_FILE_BUFFER_NAME) == 0
+ && tmp_node->contents >= old_contents
+ && tmp_node->contents + tmp_node->nodelen
+ <= old_contents_end)
+ {
+ info_win->nodes[in] =
+ manpage_node_of_file_buffer (file_buffer,
+ tmp_node->nodename);
+ free (tmp_node->nodename);
+ free (tmp_node);
+ }
+ }
+ }
+ }
+ }
+
+ node = manpage_node_of_file_buffer (file_buffer, pagename);
+ }
+
+ return node;
+}
+
+FILE_BUFFER *
+create_manpage_file_buffer (void)
+{
+ FILE_BUFFER *file_buffer = make_file_buffer ();
+ file_buffer->filename = xstrdup (MANPAGE_FILE_BUFFER_NAME);
+ file_buffer->fullpath = xstrdup (MANPAGE_FILE_BUFFER_NAME);
+ file_buffer->finfo.st_size = 0;
+ file_buffer->filesize = 0;
+ file_buffer->contents = NULL;
+ file_buffer->flags = (N_IsInternal | N_CannotGC | N_IsManPage);
+
+ return file_buffer;
+}
+
+/* Scan the list of directories in PATH looking for FILENAME. If we find
+ one that is an executable file, return it as a new string. Otherwise,
+ return a NULL pointer. */
+static char *
+executable_file_in_path (char *filename, char *path)
+{
+ struct stat finfo;
+ char *temp_dirname;
+ int statable, dirname_index;
+
+ dirname_index = 0;
+
+ while ((temp_dirname = extract_colon_unit (path, &dirname_index)))
+ {
+ char *temp;
+ char *temp_end;
+ int i;
+
+ /* 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 = xmalloc (34 + strlen (temp_dirname) + strlen (filename));
+ strcpy (temp, temp_dirname);
+ if (!IS_SLASH (temp[(strlen (temp)) - 1]))
+ strcat (temp, "/");
+ strcat (temp, filename);
+ temp_end = temp + strlen (temp);
+
+ free (temp_dirname);
+
+ /* Look for FILENAME, possibly with any of the extensions
+ in EXEC_EXTENSIONS[]. */
+ for (i = 0; exec_extensions[i]; i++)
+ {
+ if (exec_extensions[i][0])
+ strcpy (temp_end, exec_extensions[i]);
+ statable = (stat (temp, &finfo) == 0);
+
+ /* If we have found a regular executable file, then use it. */
+ if ((statable) && (S_ISREG (finfo.st_mode)) &&
+ (access (temp, X_OK) == 0))
+ return temp;
+ }
+
+ free (temp);
+ }
+ return NULL;
+}
+
+/* Return the full pathname of the system man page formatter. */
+static char *
+find_man_formatter (void)
+{
+ char *man_command = getenv ("INFO_MAN_COMMAND");
+ return man_command ? man_command :
+ executable_file_in_path ("man", getenv ("PATH"));
+}
+
+static char *manpage_pagename = NULL;
+static char *manpage_section = NULL;
+
+static void
+get_page_and_section (char *pagename)
+{
+ register int i;
+
+ if (manpage_pagename)
+ free (manpage_pagename);
+
+ if (manpage_section)
+ free (manpage_section);
+
+ manpage_pagename = NULL;
+ manpage_section = NULL;
+
+ for (i = 0; pagename[i] != '\0' && pagename[i] != '('; i++);
+
+ manpage_pagename = xmalloc (1 + i);
+ strncpy (manpage_pagename, pagename, i);
+ manpage_pagename[i] = '\0';
+
+ if (pagename[i] == '(')
+ {
+ int start;
+
+ start = i + 1;
+
+ for (i = start; pagename[i] != '\0' && pagename[i] != ')'; i++);
+
+ manpage_section = xmalloc (1 + (i - start));
+ strncpy (manpage_section, pagename + start, (i - start));
+ manpage_section[i - start] = '\0';
+ }
+}
+
+#if PIPE_USE_FORK
+static void
+reap_children (int sig)
+{
+ wait (NULL);
+}
+#endif
+
+static char *
+get_manpage_contents (char *pagename)
+{
+ static char *formatter_args[4] = { NULL };
+ int pipes[2];
+ pid_t child;
+ RETSIGTYPE (*sigsave) (int signum);
+ char *formatted_page = NULL;
+ int arg_index = 1;
+
+ if (formatter_args[0] == NULL)
+ formatter_args[0] = find_man_formatter ();
+
+ if (formatter_args[0] == NULL)
+ return NULL;
+
+ get_page_and_section (pagename);
+
+ if (manpage_section)
+ formatter_args[arg_index++] = manpage_section;
+ else
+ formatter_args[arg_index++] = "-a";
+
+ formatter_args[arg_index++] = manpage_pagename;
+ formatter_args[arg_index] = NULL;
+
+ /* Open a pipe to this program, read the output, and save it away
+ in FORMATTED_PAGE. The reader end of the pipe is pipes[0]; the
+ writer end is pipes[1]. */
+#if PIPE_USE_FORK
+ pipe (pipes);
+
+ sigsave = signal (SIGCHLD, reap_children);
+
+ child = fork ();
+ if (child == -1)
+ return NULL;
+
+ if (child != 0)
+ {
+ /* In the parent, close the writing end of the pipe, and read from
+ the exec'd child. */
+ close (pipes[1]);
+ formatted_page = read_from_fd (pipes[0]);
+ close (pipes[0]);
+ signal (SIGCHLD, sigsave);
+ }
+ else
+ { /* In the child, close the read end of the pipe, make the write end
+ of the pipe be stdout, and execute the man page formatter. */
+ close (pipes[0]);
+ freopen (NULL_DEVICE, "w", stderr);
+ freopen (NULL_DEVICE, "r", stdin);
+ dup2 (pipes[1], fileno (stdout));
+
+ execv (formatter_args[0], formatter_args);
+
+ /* If we get here, we couldn't exec, so close out the pipe and
+ exit. */
+ close (pipes[1]);
+ xexit (0);
+ }
+#else /* !PIPE_USE_FORK */
+ /* Cannot fork/exec, but can popen/pclose. */
+ {
+ FILE *fpipe;
+ char *cmdline = xmalloc (strlen (formatter_args[0])
+ + strlen (manpage_pagename)
+ + (arg_index > 2 ? strlen (manpage_section) : 0)
+ + 3);
+ int save_stderr = dup (fileno (stderr));
+ int fd_err = open (NULL_DEVICE, O_WRONLY, 0666);
+
+ if (fd_err > 2)
+ dup2 (fd_err, fileno (stderr)); /* Don't print errors. */
+ sprintf (cmdline, "%s %s %s", formatter_args[0], manpage_pagename,
+ arg_index > 2 ? manpage_section : "");
+ fpipe = popen (cmdline, "r");
+ free (cmdline);
+ if (fd_err > 2)
+ close (fd_err);
+ dup2 (save_stderr, fileno (stderr));
+ if (fpipe == 0)
+ return NULL;
+ formatted_page = read_from_fd (fileno (fpipe));
+ if (pclose (fpipe) == -1)
+ {
+ if (formatted_page)
+ free (formatted_page);
+ return NULL;
+ }
+ }
+#endif /* !PIPE_USE_FORK */
+
+ /* If we have the page, then clean it up. */
+ if (formatted_page)
+ clean_manpage (formatted_page);
+
+ return formatted_page;
+}
+
+static NODE *
+manpage_node_of_file_buffer (FILE_BUFFER *file_buffer, char *pagename)
+{
+ NODE *node = NULL;
+ TAG *tag = NULL;
+
+ if (file_buffer->contents)
+ {
+ register int i;
+
+ for (i = 0; (tag = file_buffer->tags[i]); i++)
+ {
+ if (mbscasecmp (pagename, tag->nodename) == 0)
+ break;
+ }
+ }
+
+ if (tag)
+ {
+ node = xmalloc (sizeof (NODE));
+ node->filename = file_buffer->filename;
+ node->nodename = xstrdup (tag->nodename);
+ node->contents = file_buffer->contents + tag->nodestart;
+ node->nodelen = tag->nodelen;
+ node->flags = 0;
+ node->display_pos = 0;
+ node->parent = NULL;
+ node->flags = (N_HasTagsTable | N_IsManPage);
+ node->contents += skip_node_separator (node->contents);
+ }
+
+ return node;
+}
+
+static char *
+read_from_fd (int fd)
+{
+ struct timeval timeout;
+ char *buffer = NULL;
+ int bsize = 0;
+ int bindex = 0;
+ int select_result;
+#if defined (FD_SET)
+ fd_set read_fds;
+
+ timeout.tv_sec = 15;
+ timeout.tv_usec = 0;
+
+ FD_ZERO (&read_fds);
+ FD_SET (fd, &read_fds);
+
+ select_result = select (fd + 1, fd_set_cast (&read_fds), 0, 0, &timeout);
+#else /* !FD_SET */
+ select_result = 1;
+#endif /* !FD_SET */
+
+ switch (select_result)
+ {
+ case 0:
+ case -1:
+ break;
+
+ default:
+ {
+ int amount_read;
+ int done = 0;
+
+ while (!done)
+ {
+ while ((bindex + 1024) > (bsize))
+ buffer = xrealloc (buffer, (bsize += 1024));
+ buffer[bindex] = '\0';
+
+ amount_read = read (fd, buffer + bindex, 1023);
+
+ if (amount_read < 0)
+ {
+ done = 1;
+ }
+ else
+ {
+ bindex += amount_read;
+ buffer[bindex] = '\0';
+ if (amount_read == 0)
+ done = 1;
+ }
+ }
+ }
+ }
+
+ if ((buffer != NULL) && (*buffer == '\0'))
+ {
+ free (buffer);
+ buffer = NULL;
+ }
+
+ return buffer;
+}
+
+static char *reference_section_starters[] =
+{
+ "\nRELATED INFORMATION",
+ "\nRELATED\tINFORMATION",
+ "RELATED INFORMATION\n",
+ "RELATED\tINFORMATION\n",
+ "\nSEE ALSO",
+ "\nSEE\tALSO",
+ "SEE ALSO\n",
+ "SEE\tALSO\n",
+ NULL
+};
+
+static SEARCH_BINDING frs_binding;
+
+static SEARCH_BINDING *
+find_reference_section (NODE *node)
+{
+ register int i;
+ long position = -1;
+
+ frs_binding.buffer = node->contents;
+ frs_binding.start = 0;
+ frs_binding.end = node->nodelen;
+ frs_binding.flags = S_SkipDest;
+
+ for (i = 0; reference_section_starters[i] != NULL; i++)
+ {
+ position = search_forward (reference_section_starters[i], &frs_binding);
+ if (position != -1)
+ break;
+ }
+
+ if (position == -1)
+ return NULL;
+
+ /* We found the start of the reference section, and point is right after
+ the string which starts it. The text from here to the next header
+ (or end of buffer) contains the only references in this manpage. */
+ frs_binding.start = position;
+
+ for (i = frs_binding.start; i < frs_binding.end - 2; i++)
+ {
+ if ((frs_binding.buffer[i] == '\n') &&
+ (!whitespace (frs_binding.buffer[i + 1])))
+ {
+ frs_binding.end = i;
+ break;
+ }
+ }
+
+ return &frs_binding;
+}
+
+REFERENCE **
+xrefs_of_manpage (NODE *node)
+{
+ SEARCH_BINDING *reference_section;
+ REFERENCE **refs = NULL;
+ int refs_index = 0;
+ int refs_slots = 0;
+ long position;
+
+ reference_section = find_reference_section (node);
+
+ if (reference_section == NULL)
+ return NULL;
+
+ /* Grovel the reference section building a list of references found there.
+ A reference is alphabetic characters followed by non-whitespace text
+ within parenthesis. */
+ reference_section->flags = 0;
+
+ while ((position = search_forward ("(", reference_section)) != -1)
+ {
+ register int start, end;
+
+ for (start = position; start > reference_section->start; start--)
+ if (whitespace (reference_section->buffer[start]))
+ break;
+
+ start++;
+
+ for (end = position; end < reference_section->end; end++)
+ {
+ if (whitespace (reference_section->buffer[end]))
+ {
+ end = start;
+ break;
+ }
+
+ if (reference_section->buffer[end] == ')')
+ {
+ end++;
+ break;
+ }
+ }
+
+ if (end != start)
+ {
+ REFERENCE *entry;
+ int len = end - start;
+
+ entry = xmalloc (sizeof (REFERENCE));
+ entry->label = xmalloc (1 + len);
+ strncpy (entry->label, (reference_section->buffer) + start, len);
+ entry->label[len] = '\0';
+ entry->filename = xstrdup (node->filename);
+ entry->nodename = xstrdup (entry->label);
+ entry->start = start;
+ entry->end = end;
+
+ add_pointer_to_array
+ (entry, refs_index, refs, refs_slots, 10, REFERENCE *);
+ }
+
+ reference_section->start = position + 1;
+ }
+
+ return refs;
+}
+
+long
+locate_manpage_xref (NODE *node, long int start, int dir)
+{
+ REFERENCE **refs;
+ long position = -1;
+
+ refs = xrefs_of_manpage (node);
+
+ if (refs)
+ {
+ register int i, count;
+ REFERENCE *entry;
+
+ for (i = 0; refs[i]; i++);
+ count = i;
+
+ if (dir > 0)
+ {
+ for (i = 0; (entry = refs[i]); i++)
+ if (entry->start > start)
+ {
+ position = entry->start;
+ break;
+ }
+ }
+ else
+ {
+ for (i = count - 1; i > -1; i--)
+ {
+ entry = refs[i];
+
+ if (entry->start < start)
+ {
+ position = entry->start;
+ break;
+ }
+ }
+ }
+
+ info_free_references (refs);
+ }
+ return position;
+}
+
+/* This one was a little tricky. The binding buffer that is passed in has
+ a START and END value of 0 -- strlen (window-line-containing-point).
+ The BUFFER is a pointer to the start of that line. */
+REFERENCE **
+manpage_xrefs_in_binding (NODE *node, SEARCH_BINDING *binding)
+{
+ register int i;
+ REFERENCE **all_refs = xrefs_of_manpage (node);
+ REFERENCE **brefs = NULL;
+ REFERENCE *entry;
+ int brefs_index = 0;
+ int brefs_slots = 0;
+ int start, end;
+
+ if (!all_refs)
+ return NULL;
+
+ start = binding->start + (binding->buffer - node->contents);
+ end = binding->end + (binding->buffer - node->contents);
+
+ for (i = 0; (entry = all_refs[i]); i++)
+ {
+ if ((entry->start > start) && (entry->end < end))
+ {
+ add_pointer_to_array
+ (entry, brefs_index, brefs, brefs_slots, 10, REFERENCE *);
+ }
+ else
+ {
+ maybe_free (entry->label);
+ maybe_free (entry->filename);
+ maybe_free (entry->nodename);
+ free (entry);
+ }
+ }
+
+ free (all_refs);
+ return brefs;
+}
diff --git a/info/man.h b/info/man.h
new file mode 100644
index 0000000..89377cb
--- /dev/null
+++ b/info/man.h
@@ -0,0 +1,38 @@
+/* man.h: Defines and external function declarations for man.c.
+ $Id: man.h,v 1.6 2007/07/01 21:20:30 karl Exp $
+
+ This file is part of GNU Info, a program for reading online documentation
+ stored in Info format.
+
+ Copyright (C) 1993, 1997, 2004, 2007 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Author: Brian J. Fox (bfox@ai.mit.edu) Sat May 6 16:19:13 1995. */
+
+#ifndef INFO_MAN_H
+#define INFO_MAN_H
+
+#define MANPAGE_FILE_BUFFER_NAME "*manpages*"
+
+extern NODE *make_manpage_node (char *pagename);
+extern NODE *get_manpage_node (FILE_BUFFER *file_buffer,
+ char *pagename);
+extern FILE_BUFFER *create_manpage_file_buffer (void);
+extern long locate_manpage_xref (NODE *node, long int start, int dir);
+extern REFERENCE **xrefs_of_manpage (NODE *node);
+extern REFERENCE **manpage_xrefs_in_binding (NODE *node,
+ SEARCH_BINDING *binding);
+
+#endif /* INFO_MAN_H */
diff --git a/info/nodemenu.c b/info/nodemenu.c
new file mode 100644
index 0000000..9b46014
--- /dev/null
+++ b/info/nodemenu.c
@@ -0,0 +1,346 @@
+/* nodemenu.c -- produce a menu of all visited nodes.
+ $Id: nodemenu.c,v 1.11 2008/06/11 09:55:42 gray Exp $
+
+ Copyright (C) 1993, 1997, 1998, 2002, 2003, 2004, 2007, 2008
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+
+NODE * get_visited_nodes (Function *filter_func);
+
+/* Return a line describing the format of a node information line. */
+static const char *
+nodemenu_format_info (void)
+{
+ 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)
+{
+ register int i, len;
+ char *parent, *containing_file;
+ static char *line_buffer = NULL;
+
+ if (!line_buffer)
+ line_buffer = xmalloc (1000);
+
+ if (node->parent)
+ {
+ parent = filename_non_directory (node->parent);
+ if (!parent)
+ parent = node->parent;
+ }
+ else
+ parent = NULL;
+
+ containing_file = node->filename;
+
+ if (!parent && !*containing_file)
+ sprintf (line_buffer, "* %s::", node->nodename);
+ else
+ {
+ char *file = 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, "%ld", node->nodelen);
+
+ if (node->filename && *(node->filename))
+ {
+ len = pad_to (51, line_buffer);
+ strcpy (line_buffer + len, node->filename);
+ }
+
+ return xstrdup (line_buffer);
+}
+
+/* Little string comparison routine for qsort (). */
+static int
+compare_strings (const void *entry1, const void *entry2)
+{
+ char **e1 = (char **) entry1;
+ char **e2 = (char **) entry2;
+
+ return mbscasecmp (*e1, *e2);
+}
+
+/* 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 (Function *filter_func)
+{
+ register int i, iw_index;
+ INFO_WINDOW *info_win;
+ NODE *node;
+ char **lines = NULL;
+ int lines_index = 0, lines_slots = 0;
+
+ if (!info_windows)
+ return 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, if there are any. */
+ if (lines)
+ {
+ register int j, newlen;
+ char **temp;
+
+ qsort (lines, lines_index, sizeof (char *), compare_strings);
+
+ /* Delete duplicates. */
+ for (i = 0, newlen = 1; i < lines_index - 1; i++)
+ {
+ /* Use FILENAME_CMP here, since the most important piece
+ of info in each line is the file name of the node. */
+ if (FILENAME_CMP (lines[i], lines[i + 1]) == 0)
+ {
+ free (lines[i]);
+ lines[i] = 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 = xmalloc ((1 + newlen) * sizeof (char *));
+ for (i = 0, j = 0; i < lines_index; i++)
+ if (lines[i])
+ temp[j++] = lines[i];
+
+ temp[j] = NULL;
+ free (lines);
+ lines = temp;
+ lines_index = newlen;
+ }
+
+ initialize_message_buffer ();
+
+ printf_to_message_buffer
+ ("%s", replace_in_documentation
+ (_("Here is the menu of nodes you have recently visited.\n\
+Select one from this menu, or use `\\[history-node]' in another window.\n"), 0),
+ NULL, NULL);
+
+ printf_to_message_buffer ("%s\n", (char *) nodemenu_format_info (),
+ NULL, NULL);
+
+ for (i = 0; (lines != NULL) && (i < lines_index); i++)
+ {
+ printf_to_message_buffer ("%s\n", lines[i], NULL, NULL);
+ free (lines[i]);
+ }
+
+ if (lines)
+ 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)
+ {
+ if (window->next)
+ new = window->next;
+ /* If there is more than one window, wrap around. */
+ else if (window != windows)
+ new = windows;
+ }
+
+ /* 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 (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 (NULL);
+ name_internal_node (node, nodemenu_nodename);
+
+#if 0
+ /* 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. */
+ /* Why? We depend on internal_info_node_p returning true, so we must
+ not remove the flag. Otherwise, the *Node Menu* nodes themselves
+ appear in the node menu. --Andreas Schwab
+ <schwab@issan.informatik.uni-dortmund.de>. */
+ node->flags &= ~N_IsInternal;
+#endif
+
+ /* 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 (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, NULL);
+ else
+ info_select_reference (window, entry);
+ }
+
+ free (line);
+ info_free_references (menu);
+
+ if (!info_error_was_printed)
+ window_clear_echo_area ();
+}
diff --git a/info/nodes.c b/info/nodes.c
new file mode 100644
index 0000000..41a748c
--- /dev/null
+++ b/info/nodes.c
@@ -0,0 +1,1269 @@
+/* nodes.c -- how to get an Info file and node.
+ $Id: nodes.c,v 1.11 2008/06/11 09:55:42 gray Exp $
+
+ Copyright (C) 1993, 1998, 1999, 2000, 2002, 2003, 2004, 2006, 2007,
+ 2008 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Originally written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+
+#include "nodes.h"
+#include "search.h"
+#include "filesys.h"
+#include "info-utils.h"
+
+#if defined (HANDLE_MAN_PAGES)
+# include "man.h"
+#endif /* HANDLE_MAN_PAGES */
+
+static void forget_info_file (char *filename);
+static void remember_info_file (FILE_BUFFER *file_buffer);
+static void free_file_buffer_tags (FILE_BUFFER *file_buffer);
+static void free_info_tag (TAG *tag);
+static void get_nodes_of_tags_table (FILE_BUFFER *file_buffer,
+ SEARCH_BINDING *buffer_binding);
+static void get_nodes_of_info_file (FILE_BUFFER *file_buffer);
+static void get_tags_of_indirect_tags_table (FILE_BUFFER *file_buffer,
+ SEARCH_BINDING *indirect_binding, SEARCH_BINDING *tags_binding);
+static void info_reload_file_buffer_contents (FILE_BUFFER *fb);
+static char *adjust_nodestart (NODE *node, int min, int max);
+static FILE_BUFFER *info_load_file_internal (char *filename, int get_tags);
+static FILE_BUFFER *info_find_file_internal (char *filename, int get_tags);
+static NODE *info_node_of_file_buffer_tags (FILE_BUFFER *file_buffer,
+ char *nodename);
+
+static long get_node_length (SEARCH_BINDING *binding);
+
+/* 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 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 = NULL;
+
+/* The list of already loaded nodes. */
+FILE_BUFFER **info_loaded_files = 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 (char *dirname);
+
+/* Return a pointer to a NODE structure for the Info node (FILENAME)NODENAME.
+ If FILENAME is NULL, `dir' is used.
+ IF NODENAME is NULL, `Top' is used.
+ If the node cannot be found, return NULL. */
+NODE *
+info_get_node (char *filename, char *nodename)
+{
+ NODE *node;
+ FILE_BUFFER *file_buffer = NULL;
+
+ info_recent_file_error = NULL;
+ info_parse_node (nodename, DONT_SKIP_NEWLINES);
+ nodename = 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 "dir"s and "localdir"s found in INFOPATH. */
+ if (is_dir_name (filename))
+ maybe_build_dir_node (filename);
+
+ /* Find the correct info file, or give up. */
+ 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 NULL;
+ }
+
+ /* Look for the node. */
+ node = info_get_node_of_file_buffer (nodename, file_buffer);
+
+ /* If the node not found was "Top", try again with different case,
+ unless this was a man page. */
+ if (!node
+ && mbscasecmp (filename, MANPAGE_FILE_BUFFER_NAME) != 0
+ && (nodename == NULL || mbscasecmp (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 (char *nodename, FILE_BUFFER *file_buffer)
+{
+ NODE *node = NULL;
+ int implicit_nodename = 0;
+
+ /* 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 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";
+ implicit_nodename = 1; /* don't return man page for 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 = xmalloc (sizeof (NODE));
+ node->filename = file_buffer->fullpath;
+ node->parent = NULL;
+ node->nodename = xstrdup ("*");
+ node->contents = file_buffer->contents;
+ node->nodelen = file_buffer->filesize;
+ node->flags = 0;
+ node->display_pos = 0;
+ }
+#if defined (HANDLE_MAN_PAGES)
+ /* If the file buffer is the magic one associated with manpages, call
+ the manpage node finding function instead. */
+ else if (!implicit_nodename && file_buffer->flags & N_IsManPage)
+ {
+ node = get_manpage_node (file_buffer, nodename);
+ }
+#endif /* HANDLE_MAN_PAGES */
+ /* 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 (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 (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 (char *filename, int get_tags)
+{
+ int i;
+ 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 ((FILENAME_CMP (filename, file_buffer->filename) == 0)
+ || (FILENAME_CMP (filename, file_buffer->fullpath) == 0)
+ || (!IS_ABSOLUTE (filename)
+ && FILENAME_CMP (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 (is_dir_name (filename_non_directory (filename)))
+ return file_buffer;
+
+#if defined (HANDLE_MAN_PAGES)
+ /* Do the same for the magic MANPAGE file. */
+ if (file_buffer->flags & N_IsManPage)
+ return file_buffer;
+#endif /* HANDLE_MAN_PAGES */
+
+ /* The file appears to be already loaded, and is not "dir". Check
+ to see if it's changed since the last time it was loaded. */
+ if (stat (file_buffer->fullpath, &new_info) == -1)
+ {
+ filesys_error_number = errno;
+ return 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 (!file_buffer->contents)
+ {
+ /* The file's contents have been gc'ed. Reload it. */
+ info_reload_file_buffer_contents (file_buffer);
+ if (!file_buffer->contents)
+ return NULL;
+ }
+
+ 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. */
+#if defined (HANDLE_MAN_PAGES)
+ /* If the name of the file that we want is our special file buffer for
+ Unix manual pages, then create the file buffer, and return it now. */
+ if (mbscasecmp (filename, MANPAGE_FILE_BUFFER_NAME) == 0)
+ file_buffer = create_manpage_file_buffer ();
+ else
+#endif /* HANDLE_MAN_PAGES */
+ 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 (char *filename, int get_tags)
+{
+ char *fullpath, *contents;
+ long filesize;
+ struct stat finfo;
+ int retcode, compressed;
+ 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. */
+ /* This is probably not needed at all on those systems which define
+ FILENAME_CMP to be mbscasecmp. But let's do it anyway, lest some
+ network redirector supports case sensitivity. */
+ if (retcode < 0)
+ {
+ char *lowered_name;
+ char *tmp_basename;
+
+ lowered_name = xstrdup (filename);
+ tmp_basename = filename_non_directory (lowered_name);
+
+ while (*tmp_basename)
+ {
+ if (isupper (*tmp_basename))
+ *tmp_basename = tolower (*tmp_basename);
+
+ tmp_basename++;
+ }
+
+ fullpath = info_find_fullpath (lowered_name);
+
+ retcode = stat (fullpath, &finfo);
+ free (lowered_name);
+ }
+
+ /* If the file wasn't found, give up, returning a NULL pointer. */
+ if (retcode < 0)
+ {
+ filesys_error_number = errno;
+ return NULL;
+ }
+
+ /* Otherwise, try to load the file. */
+ contents = filesys_read_info_file (fullpath, &filesize, &finfo, &compressed);
+
+ if (!contents)
+ return 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 = xstrdup (filename);
+ file_buffer->fullpath = xstrdup (fullpath);
+ file_buffer->finfo = finfo;
+ file_buffer->filesize = filesize;
+ file_buffer->contents = contents;
+ if (compressed)
+ 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)
+{
+ 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)
+{
+ 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;
+ int anchor = 0;
+
+ /* 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);
+ /* No Node:. Maybe it's a Ref:. */
+ if (start == -1)
+ {
+ start = string_in_line (INFO_REF_LABEL, nodeline);
+ if (start != -1)
+ anchor = 1;
+ }
+
+ /* 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. */
+ entry = xmalloc (sizeof (TAG));
+ entry->nodename = xmalloc (1 + (end - start));
+ strncpy (entry->nodename, nodeline + start, end - start);
+ entry->nodename[end - start] = 0;
+ entry->nodestart = nodestart;
+ if (anchor)
+ entry->nodelen = 0;
+ else
+ {
+ 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 (SEARCH_BINDING *binding)
+{
+ int i;
+ char *body;
+
+ /* [A node] ends with either a ^_, a ^L, or 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 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 *file_buffer,
+ SEARCH_BINDING *buffer_binding)
+{
+ int name_offset;
+ SEARCH_BINDING *tmp_search;
+ long position;
+ int tags_index = 0;
+
+ tmp_search = copy_binding (buffer_binding);
+
+ /* Find the start of the tags table. */
+ position = find_tags_table (tmp_search);
+
+ /* If none, we're all done. */
+ if (position == -1)
+ return;
+
+ /* Move to one character before the start of the actual table. */
+ tmp_search->start = position;
+ tmp_search->start += skip_node_separator
+ (tmp_search->buffer + tmp_search->start);
+ tmp_search->start += strlen (TAGS_TABLE_BEG_LABEL);
+ tmp_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", tmp_search)) != -1)
+ {
+ TAG *entry;
+ char *nodedef;
+ unsigned p;
+ int anchor = 0;
+
+ /* Prepare to skip this line. */
+ tmp_search->start = position;
+ tmp_search->start++;
+
+ /* Skip past informative "(Indirect)" tags table line. */
+ if (!tags_index && looking_at (TAGS_TABLE_IS_INDIRECT_LABEL, tmp_search))
+ continue;
+
+ /* Find the label preceding the node name. */
+ name_offset =
+ string_in_line (INFO_NODE_LABEL, tmp_search->buffer + tmp_search->start);
+
+ /* If no node label, maybe it's an anchor. */
+ if (name_offset == -1)
+ {
+ name_offset = string_in_line (INFO_REF_LABEL,
+ tmp_search->buffer + tmp_search->start);
+ if (name_offset != -1)
+ anchor = 1;
+ }
+
+ /* If not there, not a defining line, so we must be out of the
+ tags table. */
+ if (name_offset == -1)
+ break;
+
+ entry = xmalloc (sizeof (TAG));
+
+ /* Find the beginning of the node definition. */
+ tmp_search->start += name_offset;
+ nodedef = tmp_search->buffer + tmp_search->start;
+ nodedef += skip_whitespace (nodedef);
+
+ /* Move past the node's name in this tag to the TAGSEP character. */
+ for (p = 0; nodedef[p] && nodedef[p] != INFO_TAGSEP; p++)
+ ;
+ if (nodedef[p] != INFO_TAGSEP)
+ continue;
+
+ entry->nodename = xmalloc (p + 1);
+ strncpy (entry->nodename, nodedef, p);
+ entry->nodename[p] = 0;
+ p++;
+ entry->nodestart = atol (nodedef + p);
+
+ /* If a node, we don't know the length yet, but if it's an
+ anchor, the length is 0. */
+ entry->nodelen = anchor ? 0 : -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 (tmp_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 *file_buffer,
+ SEARCH_BINDING *indirect_binding, SEARCH_BINDING *tags_binding)
+{
+ int i;
+ SUBFILE **subfiles = 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 = xmalloc (sizeof (SUBFILE));
+ subfile->filename = 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
+ {
+ 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 = xstrdup (file_buffer->fullpath);
+ char *temp = filename_non_directory (containing_dir);
+ int len_containing_dir;
+
+ if (temp > containing_dir)
+ {
+ if (HAVE_DRIVE (file_buffer->fullpath) &&
+ temp == containing_dir + 2)
+ {
+ /* Avoid converting "d:foo" into "d:/foo" below. */
+ *temp = '.';
+ temp += 2;
+ }
+ temp[-1] = 0;
+ }
+
+ len_containing_dir = strlen (containing_dir);
+
+ for (i = 0; subfiles[i]; i++);
+
+ file_buffer->subfiles = xmalloc ((1 + i) * sizeof (char *));
+
+ for (i = 0; subfiles[i]; i++)
+ {
+ char *fullpath;
+
+ fullpath = 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] = 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 = 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;
+ }
+
+ /* 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 that contains TAG in FILE_BUFFER, else
+ (pathologically) NULL. Called from info_node_of_file_buffer_tags. */
+static NODE *
+find_node_of_anchor (FILE_BUFFER *file_buffer, TAG *tag)
+{
+ int anchor_pos, node_pos;
+ TAG *node_tag;
+ NODE *node;
+
+ /* Look through the tag list for the anchor. */
+ for (anchor_pos = 0; file_buffer->tags[anchor_pos]; anchor_pos++)
+ {
+ TAG *t = file_buffer->tags[anchor_pos];
+ if (t->nodestart == tag->nodestart)
+ break;
+ }
+
+ /* Should not happen, because we should always find the anchor. */
+ if (!file_buffer->tags[anchor_pos])
+ return NULL;
+
+ /* We've found the anchor. Look backwards in the tag table for the
+ preceding node (we're assuming the tags are given in order),
+ skipping over any preceding anchors. */
+ for (node_pos = anchor_pos - 1;
+ node_pos >= 0 && file_buffer->tags[node_pos]->nodelen == 0;
+ node_pos--)
+ ;
+
+ /* An info file with an anchor before any nodes is pathological, but
+ it's possible, so don't crash. */
+ if (node_pos < 0)
+ return NULL;
+
+ /* We have the tag for the node that contained the anchor tag. */
+ node_tag = file_buffer->tags[node_pos];
+
+ /* Look up the node name in the tag table to get the actual node.
+ This is a recursive call, but it can't recurse again, because we
+ call it with a real node. */
+ node = info_node_of_file_buffer_tags (file_buffer, node_tag->nodename);
+
+ /* Start displaying the node at the anchor position. */
+ if (node)
+ { /* The nodestart for real nodes is three characters before the `F'
+ in the `File:' line (a newline, the CTRL-_, and another
+ newline). The nodestart for anchors is the actual position.
+ But we offset by only 2, rather than 3, because if an anchor is
+ at the beginning of a paragraph, it's nicer for it to end up on
+ the beginning of the first line of the paragraph rather than
+ the blank line before it. (makeinfo has no way of knowing that
+ a paragraph is going to start, so we can't fix it there.) */
+ node->display_pos = file_buffer->tags[anchor_pos]->nodestart
+ - (node_tag->nodestart + 2);
+
+ /* Otherwise an anchor at the end of a node ends up displaying at
+ the end of the last line of the node (way over on the right of
+ the screen), which looks wrong. */
+ if (node->display_pos >= (unsigned long) node->nodelen)
+ node->display_pos = node->nodelen - 1;
+
+ /* Don't search in the node for the xref text, it's not there. */
+ node->flags |= N_FromAnchor;
+ }
+
+ return node;
+}
+
+
+/* Return the node from FILE_BUFFER which matches NODENAME by searching
+ the tags table in FILE_BUFFER, or NULL. */
+static NODE *
+info_node_of_file_buffer_tags (FILE_BUFFER *file_buffer, char *nodename)
+{
+ TAG *tag;
+ int i;
+
+ /* If no tags at all (possibly a misformatted info file), quit. */
+ if (!file_buffer->tags) {
+ return NULL;
+ }
+
+ for (i = 0; (tag = file_buffer->tags[i]); i++)
+ if (strcmp (nodename, tag->nodename) == 0)
+ {
+ FILE_BUFFER *subfile = info_find_file_internal (tag->filename,
+ INFO_NO_TAGS);
+ if (!subfile)
+ return NULL;
+
+ if (!subfile->contents)
+ {
+ info_reload_file_buffer_contents (subfile);
+ if (!subfile->contents)
+ return NULL;
+ }
+
+ /* If we were able to find this file and load it, then return
+ the node within it. */
+ {
+ NODE *node = xmalloc (sizeof (NODE));
+ node->filename = subfile->fullpath;
+ node->parent = NULL;
+ node->nodename = tag->nodename;
+ node->contents = subfile->contents + tag->nodestart;
+ node->display_pos = 0;
+ node->flags = 0;
+
+ 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 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 == NULL)
+ {
+ free (node);
+ return 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);
+ node->nodelen = tag->nodelen;
+ }
+
+ else if (tag->nodelen == 0) /* anchor, return containing node */
+ {
+ free (node);
+ node = find_node_of_anchor (file_buffer, tag);
+ }
+
+ 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 NULL;
+}
+
+/* Managing file_buffers, nodes, and tags. */
+
+/* Create a new, empty file buffer. */
+FILE_BUFFER *
+make_file_buffer (void)
+{
+ FILE_BUFFER *file_buffer = xmalloc (sizeof (FILE_BUFFER));
+
+ file_buffer->filename = file_buffer->fullpath = NULL;
+ file_buffer->contents = NULL;
+ file_buffer->tags = NULL;
+ file_buffer->subfiles = 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)
+{
+ 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 (char *filename)
+{
+ int i;
+ FILE_BUFFER *file_buffer;
+
+ if (!info_loaded_files)
+ return;
+
+ for (i = 0; (file_buffer = info_loaded_files[i]); i++)
+ if (FILENAME_CMP (filename, file_buffer->filename) == 0
+ || FILENAME_CMP (filename, file_buffer->fullpath) == 0)
+ {
+ free (file_buffer->filename);
+ free (file_buffer->fullpath);
+
+ if (file_buffer->contents)
+ free (file_buffer->contents);
+
+ /* 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);
+
+ /* Move rest of list down. */
+ while (info_loaded_files[i + 1])
+ {
+ info_loaded_files[i] = info_loaded_files[i + 1];
+ i++;
+ }
+ info_loaded_files[i] = 0;
+
+ break;
+ }
+}
+
+/* Free the tags (if any) associated with FILE_BUFFER. */
+static void
+free_file_buffer_tags (FILE_BUFFER *file_buffer)
+{
+ int i;
+
+ if (file_buffer->tags)
+ {
+ TAG *tag;
+
+ for (i = 0; (tag = file_buffer->tags[i]); i++)
+ free_info_tag (tag);
+
+ free (file_buffer->tags);
+ file_buffer->tags = 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 = NULL;
+ }
+}
+
+/* Free the data associated with TAG, as well as TAG itself. */
+static void
+free_info_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 (FILE_BUFFER *fb)
+{
+ int is_compressed;
+
+#if defined (HANDLE_MAN_PAGES)
+ /* If this is the magic manpage node, don't try to reload, just give up. */
+ if (fb->flags & N_IsManPage)
+ return;
+#endif
+
+ 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),
+ &is_compressed);
+ if (is_compressed)
+ 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 *node, int min, int 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 (((unsigned int) 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 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;
+}
diff --git a/info/nodes.h b/info/nodes.h
new file mode 100644
index 0000000..213dc3c
--- /dev/null
+++ b/info/nodes.h
@@ -0,0 +1,156 @@
+/* nodes.h -- How we represent nodes internally.
+ $Id: nodes.h,v 1.6 2007/07/01 21:20:31 karl Exp $
+
+ Copyright (C) 1993, 1997, 1998, 2002, 2004, 2007
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#ifndef NODES_H
+#define NODES_H
+
+#include "info.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. */
+ unsigned long display_pos; /* Where to display at, if nonzero. */
+ 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. */
+#define N_IsManPage 0x40 /* This node is a manpage. */
+#define N_FromAnchor 0x80 /* Synthesized for an anchor reference. */
+
+/* Internal data structures. */
+
+/* String constants. */
+#define INFO_FILE_LABEL "File:"
+#define INFO_REF_LABEL "Ref:"
+#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 (char *filename);
+
+/* 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 (char *filename);
+
+/* 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 (char *filename, char *nodename);
+
+/* 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 (char *nodename,
+ FILE_BUFFER *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 (FILE_BUFFER *file_buffer);
+
+/* When non-zero, this is a string describing the most recent file error. */
+extern char *info_recent_file_error;
+
+/* Create a new, empty file buffer. */
+extern FILE_BUFFER *make_file_buffer (void);
+
+#endif /* not NODES_H */
diff --git a/info/pcterm.c b/info/pcterm.c
new file mode 100644
index 0000000..7eeb80f
--- /dev/null
+++ b/info/pcterm.c
@@ -0,0 +1,759 @@
+/* pcterm.c -- How to handle the PC terminal for Info under MS-DOS/MS-Windows.
+ $Id: pcterm.c,v 1.8 2008/06/11 09:55:42 gray Exp $
+
+ Copyright (C) 1998, 1999, 2003, 2004, 2007, 2008
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>. */
+
+
+/* WARNING WARNING WARNING!!!
+ This probably won't work as is with anything but DJGPP! However, Borland
+ should come close, and other PC compilers will need minor modifications. */
+
+/* intl/libintl.h defines a macro `gettext' which
+ conflicts with conio.h header. */
+#ifdef gettext
+# undef gettext
+# define gettext _gettext
+#endif
+
+#include <pc.h>
+#include <keys.h>
+#include <conio.h>
+
+#include "variables.h"
+
+extern int speech_friendly; /* defined in info.c */
+
+/* **************************************************************** */
+/* */
+/* PC Terminal Output Functions */
+/* */
+/* **************************************************************** */
+
+static struct text_info outside_info; /* holds screen params outside Info */
+static unsigned char norm_attr, inv_attr;
+
+static unsigned const char * find_sequence (int);
+
+/* Turn on reverse video. */
+static void
+pc_begin_inverse (void)
+{
+ textattr (inv_attr);
+}
+
+/* Turn off reverse video. */
+static void
+pc_end_inverse (void)
+{
+ textattr (norm_attr);
+}
+
+/* Move the cursor up one line. */
+static void
+pc_up_line (void)
+{
+ int x, y;
+ ScreenGetCursor (&y, &x);
+ ScreenSetCursor (MAX (y-1, 0), x);
+}
+
+/* Move the cursor down one line. */
+static void
+pc_down_line (void)
+{
+ int x, y;
+ ScreenGetCursor (&y, &x);
+ ScreenSetCursor (MIN (screenheight-1, y+1), x);
+}
+
+/* Clear the entire terminal screen. */
+static void
+pc_clear_screen (void)
+{
+ ScreenClear ();
+}
+
+/* Clear from the current position of the cursor to the end of the line. */
+static void
+pc_clear_to_eol (void)
+{
+ clreol (); /* perhaps to be replaced by a loop */
+}
+
+/* Set the global variables SCREENWIDTH and SCREENHEIGHT. */
+static void
+pc_get_screen_size(void)
+{
+ /* Current screen dimensions are the default. */
+ if (!outside_info.screenheight) /* paranoia */
+ gettextinfo (&outside_info);
+ screenwidth = outside_info.screenwidth;
+ screenheight = outside_info.screenheight;
+
+ /* Environment variable "LINES" overrides the default. */
+ if (getenv ("LINES") != NULL)
+ screenheight = atoi (getenv ("LINES"));
+
+ /* Environment variable "INFO_LINES" overrides "LINES". */
+ if (getenv ("INFO_LINES") != NULL)
+ screenheight = atoi (getenv ("INFO_LINES"));
+}
+
+/* Move the cursor to the terminal location of X and Y. */
+static void
+pc_goto_xy (x, y)
+ int x, y;
+{
+ ScreenSetCursor (y, x); /* yes, pc.h says ScreenSetCursor (row, column) !! */
+}
+
+/* Print STRING to the terminal at the current position. */
+static void
+pc_put_text (string)
+ char *string;
+{
+ if (speech_friendly)
+ fputs (string, stdout);
+ else
+ cputs (string);
+}
+
+/* Ring the terminal bell. The bell is rung visibly if the terminal is
+ capable of doing that, and if terminal_use_visible_bell_p is non-zero. */
+static void
+pc_ring_bell(void)
+{
+ if (terminal_has_visible_bell_p && terminal_use_visible_bell_p)
+ ScreenVisualBell ();
+ else
+ {
+ printf ("%c",'\a');
+ fflush (stdout);
+ }
+}
+
+/* Print NCHARS from STRING to the terminal at the current position. */
+static void
+pc_write_chars (string, nchars)
+ char *string;
+ int nchars;
+{
+ if (!nchars)
+ return;
+
+ if (speech_friendly)
+ printf ("%.*s",nchars, string);
+ else
+ cprintf ("%..*s",nchars, string);
+}
+
+/* Scroll an area of the terminal from START to (and excluding) 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. The lines of the old region which do not
+ overlap the new region are cleared, to mimic terminal operation. */
+static void
+pc_scroll_terminal (start, end, amount)
+ int start, end, amount;
+{
+ int line_to_clear = amount > 0 ? start : end + amount;
+
+ /* Move the text. Note that `movetext' expects 1-based coordinates. */
+ movetext (1, start + 1, ScreenCols (), end, 1, start + amount + 1);
+
+ /* Now clear the lines which were left unoccupied. */
+ if (amount < 0)
+ amount = -amount;
+ while (amount--)
+ {
+ ScreenSetCursor (line_to_clear++, 0);
+ clreol ();
+ }
+}
+
+/* Put the screen in the video mode and colors which Info will use.
+ Prepare to start using the terminal to read characters singly. */
+static void
+pc_prep_terminal (void)
+{
+ int tty;
+
+ /* Do not set screen height if we already have it, because
+ doing so erases the screen. */
+ if (screenheight != ScreenRows ())
+ _set_screen_lines (screenheight);
+
+ /* Don't fail if they asked for screen dimensions that their
+ hardware cannot support. */
+ screenheight = ScreenRows ();
+ screenwidth = ScreenCols ();
+
+ /* Try setting the colors user asked for. */
+ textattr (norm_attr);
+ ScreenClear ();
+
+ /* Switch console reads to binary mode. */
+ tty = fileno (stdin);
+#ifdef __DJGPP__
+ setmode (tty, O_BINARY);
+ __djgpp_set_ctrl_c (1); /* re-enable SIGINT generation by Ctrl-C */
+#endif
+}
+
+/* Restore the tty settings back to what they were before we started using
+ this terminal. */
+static void
+pc_unprep_terminal (void)
+{
+ int tty;
+
+ textattr (outside_info.normattr);
+
+ /* Do not set screen height if we already have it, because
+ doing so erases the screen. */
+ if (outside_info.screenheight != ScreenRows ())
+ {
+ _set_screen_lines (outside_info.screenheight);
+ textmode (LASTMODE);
+ }
+ else
+ pc_clear_to_eol (); /* for text attributes to really take effect */
+
+ /* Switch back to text mode on stdin. */
+ tty = fileno (stdin);
+#ifdef __DJGPP__
+ setmode (tty, O_TEXT);
+#endif
+}
+
+/* Initialize the terminal which is known as TERMINAL_NAME. If this
+ terminal doesn't have cursor addressability, `terminal_is_dumb_p'
+ becomes nonzero. The variables SCREENHEIGHT and SCREENWIDTH are set
+ to the dimensions that this terminal actually has. The variable
+ TERMINAL_HAS_META_P becomes nonzero if this terminal supports a Meta
+ key. Finally, the terminal screen is cleared. */
+static void
+pc_initialize_terminal (term_name)
+ char *term_name;
+{
+ char *info_colors;
+
+ if (!term_name)
+ {
+ term_name = getenv ("TERM");
+ if (!term_name)
+ term_name = "pc-dos"; /* ``what's in a name?'' */
+ }
+
+ /* Get current video information, to be restored later. */
+ if (outside_info.screenwidth == 0)
+ gettextinfo (&outside_info);
+
+ /* Current screen colors are the default. */
+ norm_attr = outside_info.normattr;
+ inv_attr = (((outside_info.normattr & 7) << 4) |
+ ((outside_info.normattr & 0x7f) >> 4));
+
+ /* Does the user want non-default colors? */
+ info_colors = getenv ("INFO_COLORS");
+ if ((info_colors != (char *)0) && !speech_friendly)
+ {
+ /* Decode a color from a string descriptor.
+ The descriptor string is a sequence of color specifiers separated
+ by a non-numeric character. Each color specifier should represent
+ a small integer which fits into an unsigned char, and can be given
+ in any base supported by strtoul. Examples of valid descriptors:
+
+ "10 31"
+ "0x13/0x45"
+ "007.077"
+
+ The separator between two color specifiers can be any character which
+ cannot be used in a printed representation of an integer number. */
+ char *endp;
+ unsigned long color_desc = strtoul (info_colors, &endp, 0);
+
+ if (color_desc <= UCHAR_MAX)
+ {
+ norm_attr = (unsigned char)color_desc;
+ color_desc = strtoul (endp + 1, &endp, 0);
+ if (color_desc <= UCHAR_MAX)
+ inv_attr = (unsigned char)color_desc;
+ }
+ }
+
+ /* We can scroll. */
+ terminal_can_scroll = 1;
+
+ /* We know how to produce a visible bell, if somebody's looking... */
+ if (!speech_friendly)
+ terminal_has_visible_bell_p = 1;
+
+ /* We have a Meta key. */
+ terminal_has_meta_p = 1;
+
+ /* We are *certainly* NOT dumb! */
+ terminal_is_dumb_p = 0;
+
+ pc_get_screen_size ();
+
+ /* Store the arrow keys. */
+ term_ku = (char *)find_sequence (K_Up);
+ term_kd = (char *)find_sequence (K_Down);
+ term_kr = (char *)find_sequence (K_Right);
+ term_kl = (char *)find_sequence (K_Left);
+
+ term_kP = (char *)find_sequence (K_PageUp);
+ term_kN = (char *)find_sequence (K_PageDown);
+
+#if defined(INFOKEY)
+ term_kh = (char *)find_sequence (K_Home);
+ term_ke = (char *)find_sequence (K_End);
+ term_ki = (char *)find_sequence (K_Insert);
+ term_kx = (char *)find_sequence (K_Delete);
+#endif
+
+ /* Set all the hooks to our PC-specific functions. */
+ terminal_begin_inverse_hook = pc_begin_inverse;
+ terminal_end_inverse_hook = pc_end_inverse;
+ terminal_prep_terminal_hook = pc_prep_terminal;
+ terminal_unprep_terminal_hook = pc_unprep_terminal;
+ terminal_up_line_hook = pc_up_line;
+ terminal_down_line_hook = pc_down_line;
+ terminal_clear_screen_hook = pc_clear_screen;
+ terminal_clear_to_eol_hook = pc_clear_to_eol;
+ terminal_get_screen_size_hook = pc_get_screen_size;
+ terminal_goto_xy_hook = pc_goto_xy;
+ terminal_put_text_hook = pc_put_text;
+ terminal_ring_bell_hook = pc_ring_bell;
+ terminal_write_chars_hook = pc_write_chars;
+ terminal_scroll_terminal_hook = pc_scroll_terminal;
+}
+
+/* **************************************************************** */
+/* */
+/* How to Read Characters From the PC Terminal */
+/* */
+/* **************************************************************** */
+
+/* This will most certainly work ONLY with DJGPP. */
+#ifdef __DJGPP__
+
+#include <errno.h>
+#include <sys/fsext.h>
+#include <dpmi.h>
+
+/* Translation table for some special keys.
+ Arrow keys which are standard on other keyboards are translated into
+ standard ESC-sequences, in case somebody rebinds the simple keys
+ (like C-f, C-b, C-n, etc.).
+
+ The strange "\033\061" prefix in some keys is a numeric argument of
+ one, which means ``do the next command once''. It is here so that
+ when the according PC key is pressed in the middle of an incremental
+ search, Info doesn't see just an ASCII character like `n' or `B',
+ and doesn't add it to the search string; instead, it will exit the
+ incremental search and then perform the command. */
+static struct
+{
+ int inkey;
+ unsigned char const * const sequence;
+} DJGPP_keytab[] = { /* these are for moving between nodes... */
+ {K_Control_PageDown, "\033\061n"},
+ {K_Control_PageUp, "\033\061p"},
+ {K_Control_Up, "\033\061u"},
+ {K_Control_Down, "\033\061m"},
+ {K_Control_Center, "\033\061l"},
+
+#if defined(INFOKEY)
+ {K_Home, "\033[H"}, /* ...and these are for moving IN a node */
+ {K_End, "\033[F"}, /* they're Numeric-Keypad-Keys, so */
+#else
+ {K_Home, "\001"},
+ {K_End, "\005"},
+#endif
+ {K_Left, "\033[D"}, /* NUMLOCK should be off !! */
+ {K_Right, "\033[C"},
+ {K_Down, "\033[B"},
+ {K_Up, "\033[A"},
+ {K_PageDown, "\033[G"},
+ {K_PageUp, "\033[I"},
+ {K_Control_Left, "\033b"},
+ {K_Control_Right, "\033f"},
+ {K_Control_Home, "\033<"},
+ {K_Control_End, "\033>"},
+
+#if defined(INFOKEY)
+ {K_EHome, "\033[H"}, /* these are also for moving IN a node */
+ {K_EEnd, "\033[F"}, /* they're the "extended" (Grey) keys */
+#else
+ {K_EHome, "\001"},
+ {K_EEnd, "\005"},
+#endif
+ {K_ELeft, "\033[D"},
+ {K_ERight, "\033[C"},
+ {K_EDown, "\033[B"},
+ {K_EUp, "\033[A"},
+ {K_EPageDown, "\033[G"},
+ {K_EPageUp, "\033[I"},
+ {K_Control_ELeft, "\033b"},
+ {K_Control_ERight, "\033f"},
+ {K_Control_EHome, "\033<"},
+ {K_Control_EEnd, "\033>"},
+
+ {K_BackTab, "\033\011"},
+ {K_F1, "\10"}, /* YEAH, gimme that good old F-one-thing */
+ {K_Delete, "\177"}, /* to make Kp-Del be DEL (0x7f) */
+ {K_EDelete, "\177"}, /* to make Delete be DEL (0x7f) */
+#if defined(INFOKEY)
+ {K_Insert, "\033[L"},
+ {K_EInsert, "\033[L"},
+#endif
+
+ /* These are here to map more Alt-X keys to ESC X sequences. */
+ {K_Alt_Q, "\033q"},
+ {K_Alt_W, "\033w"},
+ {K_Alt_E, "\033e"},
+ {K_Alt_R, "\033r"},
+ {K_Alt_T, "\033t"},
+ {K_Alt_Y, "\033y"},
+ {K_Alt_U, "\033u"},
+ {K_Alt_I, "\033i"},
+ {K_Alt_O, "\033o"},
+ {K_Alt_P, "\033p"},
+ {K_Alt_LBracket, "\033["},
+ {K_Alt_RBracket, "\033]"},
+ {K_Alt_Return, "\033\015"},
+ {K_Alt_A, "\033a"},
+ {K_Alt_S, "\033s"},
+ {K_Alt_D, "\033d"},
+ {K_Alt_F, "\033f"},
+ {K_Alt_G, "\033g"},
+ {K_Alt_H, "\033h"},
+ {K_Alt_J, "\033j"},
+ {K_Alt_K, "\033k"},
+ {K_Alt_L, "\033l"},
+ {K_Alt_Semicolon, "\033;"},
+ {K_Alt_Quote, "\033'"},
+ {K_Alt_Backquote, "\033`"},
+ {K_Alt_Backslash, "\033\\"},
+ {K_Alt_Z, "\033z"},
+ {K_Alt_X, "\033x"},
+ {K_Alt_C, "\033c"},
+ {K_Alt_V, "\033v"},
+ {K_Alt_B, "\033b"},
+ {K_Alt_N, "\033n"},
+ {K_Alt_M, "\033m"},
+ {K_Alt_Comma, "\033<"}, /* our reader cannot distinguish between */
+ {K_Alt_Period, "\033>"}, /* Alt-. and Alt->, so we cheat a little */
+ {K_Alt_Slash, "\033?"}, /* ditto, to get Alt-? */
+ {K_Alt_Backspace, "\033\177"}, /* M-DEL, to delete word backwards */
+ {K_Alt_1, "\033\061"},
+ {K_Alt_2, "\033\062"},
+ {K_Alt_3, "\033\063"},
+ {K_Alt_4, "\033\064"},
+ {K_Alt_5, "\033\065"},
+ {K_Alt_6, "\033\066"},
+ {K_Alt_7, "\033\067"},
+ {K_Alt_8, "\033\070"},
+ {K_Alt_9, "\033\071"},
+ {K_Alt_0, "\033\060"},
+ {K_Alt_Dash, "\033\055"},
+ {K_Alt_EPageUp, "\033\033[I"},
+ {K_Alt_EPageDown, "\033\033[G"},
+ {K_Alt_Equals, "\033\075"},
+ {K_Alt_EDelete, "\033\177"},
+ {K_Alt_Tab, "\033\011"},
+ {0, 0}
+};
+
+/* Given a key, return the sequence of characters which
+ our keyboard driver generates. */
+static unsigned const char *
+find_sequence (int key)
+{
+ int i;
+
+ for (i = 0; DJGPP_keytab[i].inkey; i++)
+ if (key == DJGPP_keytab[i].inkey)
+ return DJGPP_keytab[i].sequence;
+
+ return NULL;
+}
+
+/* Return zero if a key is pending in the
+ keyboard buffer, non-zero otherwise. */
+static int
+kbd_buffer_empty (void)
+{
+ __dpmi_regs r;
+ int retval;
+
+ r.h.ah = 0x11; /* Get enhanced keyboard status */
+ __dpmi_int (0x16, &r);
+
+ /* If the keyboard buffer is empty, the Zero Flag will be set. */
+ return (r.x.flags & 0x40) == 0x40;
+}
+
+/* The buffered characters pending to be read.
+ Actually, Info usually reads a single character, but when we
+ translate a key into a sequence of characters, we keep them here. */
+static unsigned char buffered[512];
+
+/* Index of the next buffered character to be returned. */
+static int buf_idx;
+
+/* Return the number of characters waiting to be read. */
+long
+pc_term_chars_avail (void)
+{
+ if (buf_idx >= sizeof (buffered)) /* paranoia */
+ {
+ buf_idx = 0;
+ buffered[buf_idx] = '\0';
+ return 0;
+ }
+ else
+ return strlen (buffered + buf_idx);
+}
+
+/* Our special terminal keyboard reader. It will be called by
+ low-level libc functions when the application calls `read' or
+ the ANSI-standard stream-oriented read functions. If the
+ caller wants to read the terminal, we redirect the call to
+ the BIOS keyboard functions, since that lets us recognize more
+ keys than DOS does. */
+static int
+keyboard_read (__FSEXT_Fnumber func, int *retval, va_list rest_args)
+{
+ /* When we are called, REST_ARGS are: file_descriptor, buf, nbytes. */
+ unsigned char *buf;
+ size_t nbytes, nread = 0;
+ int fd = va_arg (rest_args, int);
+
+ /* Is this call for us? */
+ if (func != __FSEXT_read || !isatty (fd))
+ return 0; /* and the usual DOS call will be issued */
+
+ buf = va_arg (rest_args, unsigned char *);
+ nbytes = va_arg (rest_args, size_t);
+
+ if (!buf)
+ {
+ errno = EINVAL;
+ *retval = -1;
+ return 1;
+ }
+ if (!nbytes)
+ {
+ *retval = 0;
+ return 1;
+ }
+
+ /* Loop here until enough bytes has been read. */
+ do
+ {
+ int key;
+
+ /* If any ``buffered characters'' are left, return as much
+ of them as the caller wanted. */
+ while (buffered[buf_idx] && nbytes)
+ {
+ *buf++ = buffered[buf_idx++];
+ nread++;
+ nbytes--;
+ }
+
+ if (nbytes <= 0)
+ break;
+
+ /* Wait for another key.
+ We do that in a busy-waiting loop so we don't get parked
+ inside a BIOS call, which will effectively disable signals.
+ While we wait for them to type something, we repeatedly
+ release the rest of our time slice, so that other programs
+ in a multitasking environment, such as Windows, get more cycles. */
+ while (kbd_buffer_empty ())
+ __dpmi_yield ();
+
+ key = getxkey ();
+
+ /* Translate the key if necessary.
+ Untranslated non-ASCII keys are silently ignored. */
+ if ((key & 0x300) != 0)
+ {
+ unsigned char const * key_sequence = find_sequence (key);
+
+ if (key_sequence != NULL)
+ {
+ strcpy (buffered, key_sequence);
+ buf_idx = 0;
+ }
+ }
+ else if (key == K_Control_Z)
+ raise (SIGUSR1); /* we don't have SIGTSTP, so simulate it */
+ else if (key <= 0xff)
+ {
+ *buf++ = key;
+ nbytes--;
+ nread++;
+ }
+ }
+ while (nbytes > 0);
+
+ *retval = nread;
+ return 1; /* meaning that we handled the call */
+}
+
+/* Install our keyboard handler.
+ This is called by the startup code before `main'. */
+static void __attribute__((constructor))
+install_keyboard_handler (void)
+{
+ __FSEXT_set_function (fileno (stdin), keyboard_read);
+
+ /* We need to set this single hook here; the rest
+ will be set by pc_initialize_terminal when it is called. */
+ terminal_initialize_terminal_hook = pc_initialize_terminal;
+}
+
+#endif /* __DJGPP__ */
+
+/* **************************************************************** */
+/* */
+/* Emulation of SIGTSTP on Ctrl-Z */
+/* */
+/* **************************************************************** */
+
+#include <limits.h>
+#include "signals.h"
+#include "session.h"
+
+#ifndef PATH_MAX
+# define PATH_MAX 512
+#endif
+
+/* Effectively disable signals which aren't defined
+ (assuming no signal can ever be zero).
+ SIGINT is ANSI, so we expect it to be always defined. */
+#ifndef SIGUSR1
+# define SIGUSR1 0
+#endif
+#ifndef SIGQUIT
+# define SIGQUIT 0
+#endif
+
+int
+kill (pid_t pid, int sig)
+{
+ static char interrupted_msg[] = "Interrupted\r\n";
+ static char stopped_msg[] = "Stopped. Type `exit RET' to return.\r\n";
+ char cwd[PATH_MAX + 1];
+
+ if (pid == getpid ()
+ || pid == 0
+ || pid == -1
+ || pid == -getpid ())
+ {
+ switch (sig)
+ {
+ RETSIGTYPE (*old_INT)(int), (*old_QUIT)(int);
+
+ case SIGINT:
+#ifdef __DJGPP__
+ /* If SIGINT was generated by a readable key, we want to remove
+ it from the PC keyboard buffer, so that DOS and other
+ programs never see it. DJGPP signal-handling mechanism
+ doesn't remove the INT key from the keyboard buffer. */
+ if (!kbd_buffer_empty ())
+ getxkey ();
+#endif
+ pc_write_chars (interrupted_msg, sizeof (interrupted_msg) - 1);
+ xexit (1);
+ case SIGUSR1:
+ /* Simulate SIGTSTP by invoking a subsidiary shell. */
+ pc_goto_xy (0, outside_info.screenheight - 1);
+ pc_clear_to_eol ();
+ pc_write_chars (stopped_msg, sizeof (stopped_msg) - 1);
+
+ /* The child shell can change the working directory, so
+ we need to save and restore it, since it is global. */
+ if (!getcwd (cwd, PATH_MAX)) /* should never happen */
+ cwd[0] = '\0';
+
+ /* We don't want to get fatal signals while the subshell runs. */
+ old_INT = signal (SIGINT, SIG_IGN);
+ old_QUIT = signal (SIGQUIT, SIG_IGN);
+ system ("");
+ if (*cwd)
+ chdir (cwd);
+ signal (SIGINT, old_INT);
+ signal (SIGQUIT, old_QUIT);
+ break;
+ default:
+ if (sig)
+ raise (sig);
+ break;
+ }
+ return 0;
+ }
+ else
+ return -1;
+}
+
+/* These should never be called, but they make the linker happy. */
+
+void tputs (char *a, int b, int (*c)())
+{
+ perror ("tputs");
+}
+
+char* tgoto (char*a, int b, int c)
+{
+ perror ("tgoto"); return 0; /* here and below, added dummy retvals */
+}
+
+int tgetnum (char*a)
+{
+ perror ("tgetnum"); return 0;
+}
+
+int tgetflag (char*a)
+{
+ perror ("tgetflag"); return 0;
+}
+
+char* tgetstr (char *a, char **b)
+{
+ perror ("tgetstr"); return 0;
+}
+
+int tgetent (char*a, char*b)
+{
+ perror ("tgetent"); return 0;
+}
+
+int tcgetattr(int fildes, struct termios *termios_p)
+{
+ perror ("tcgetattr"); return 0;
+}
+
+int tcsetattr(int fd, int opt_actions, const struct termios *termios_p)
+{
+ perror ("tcsetattr"); return 0;
+}
diff --git a/info/search.c b/info/search.c
new file mode 100644
index 0000000..44010bf
--- /dev/null
+++ b/info/search.c
@@ -0,0 +1,677 @@
+/* search.c -- searching large bodies of text.
+ $Id: search.c,v 1.9 2008/06/11 09:55:42 gray Exp $
+
+ Copyright (C) 1993, 1997, 1998, 2002, 2004, 2007, 2008
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Originally written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include <regex.h>
+
+#include "search.h"
+#include "nodes.h"
+
+/* 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 (char *buffer, long int start, long int end)
+{
+ SEARCH_BINDING *binding;
+
+ 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 (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 (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 or backwards for anything matching the regexp in the text
+ delimited by BINDING. The search is forwards if BINDING->start is greater
+ than BINDING->end.
+
+ If PRET is specified, it receives a copy of BINDING at the end of a
+ succeded search. Its START and END fields contain bounds of the found
+ string instance.
+*/
+long
+regexp_search (char *regexp, SEARCH_BINDING *binding, long length,
+ SEARCH_BINDING *pret)
+{
+ static char *previous_regexp = NULL;
+ static char *previous_content = NULL;
+ static int was_insensitive = 0;
+ static regex_t preg;
+ static regmatch_t *matches;
+ static int match_alloc = 0;
+ static int match_count = 0;
+ regoff_t pos;
+
+ if (previous_regexp == NULL
+ || ((binding->flags & S_FoldCase) != was_insensitive)
+ || (strcmp (previous_regexp, regexp) != 0))
+ {
+ /* need to compile a new regexp */
+ int result;
+ char *unescaped_regexp;
+ char *p, *q;
+
+ previous_content = NULL;
+
+ if (previous_regexp != NULL)
+ {
+ free (previous_regexp);
+ previous_regexp = NULL;
+ regfree (&preg);
+ }
+
+ was_insensitive = binding->flags & S_FoldCase;
+
+ /* expand the \n and \t in regexp */
+ unescaped_regexp = xmalloc (1 + strlen (regexp));
+ for (p = regexp, q = unescaped_regexp; *p != '\0'; p++, q++)
+ {
+ if (*p == '\\')
+ switch(*++p)
+ {
+ case 'n':
+ *q = '\n';
+ break;
+ case 't':
+ *q = '\t';
+ break;
+ case '\0':
+ *q = '\\';
+ p--;
+ break;
+ default:
+ *q++ = '\\';
+ *q = *p;
+ break;
+ }
+ else
+ *q = *p;
+ }
+ *q = '\0';
+
+ result = regcomp (&preg, unescaped_regexp,
+ REG_EXTENDED|
+ REG_NEWLINE|
+ (was_insensitive ? REG_ICASE : 0));
+ free (unescaped_regexp);
+
+ if (result != 0)
+ {
+ int size = regerror (result, &preg, NULL, 0);
+ char *buf = xmalloc (size);
+ regerror (result, &preg, buf, size);
+ info_error (_("regexp error: %s"), buf, NULL);
+ return -1;
+ }
+
+ previous_regexp = xstrdup(regexp);
+ }
+
+ if (previous_content != binding->buffer)
+ {
+ /* new buffer to search in, let's scan it */
+ regoff_t start = 0;
+ char saved_char;
+
+ previous_content = binding->buffer;
+ saved_char = previous_content[length-1];
+ previous_content[length-1] = '\0';
+
+ for (match_count = 0; start < length; )
+ {
+ int result = 0;
+ if (match_count >= match_alloc)
+ {
+ /* match list full. Initially allocate 256 entries, then double
+ every time we fill it */
+ match_alloc = (match_alloc > 0 ? match_alloc * 2 : 256);
+ matches = xrealloc (matches,
+ match_alloc * sizeof(regmatch_t));
+ }
+
+ result = regexec (&preg, &previous_content[start],
+ 1, &matches[match_count], 0);
+ if (result == 0)
+ {
+ if (matches[match_count].rm_eo == 0)
+ {
+ /* ignore empty matches */
+ start++;
+ }
+ else
+ {
+ matches[match_count].rm_so += start;
+ matches[match_count].rm_eo += start;
+ start = matches[match_count++].rm_eo;
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+ previous_content[length-1] = saved_char;
+ }
+
+ pos = binding->start;
+ if (pos > binding->end)
+ {
+ /* searching backward */
+ int i;
+ for (i = match_count - 1; i >= 0; i--)
+ {
+ if (matches[i].rm_so <= pos)
+ {
+ if (pret)
+ {
+ pret->buffer = binding->buffer;
+ pret->flags = binding->flags;
+ pret->start = matches[i].rm_so;
+ pret->end = matches[i].rm_eo;
+ }
+ return matches[i].rm_so;
+ }
+ }
+ }
+ else
+ {
+ /* searching forward */
+ int i;
+ for (i = 0; i < match_count; i++)
+ {
+ if (matches[i].rm_so >= pos)
+ {
+ if (pret)
+ {
+ pret->buffer = binding->buffer;
+ pret->flags = binding->flags;
+ pret->start = matches[i].rm_so;
+ pret->end = matches[i].rm_eo;
+ }
+ if (binding->flags & S_SkipDest)
+ return matches[i].rm_eo;
+ else
+ return matches[i].rm_so;
+ }
+ }
+ }
+
+ /* not found */
+ return -1;
+}
+
+/* Search forwards for STRING through the text delimited in BINDING. */
+long
+search_forward (char *string, SEARCH_BINDING *binding)
+{
+ register int c, i, len;
+ register char *buff, *end;
+ char *alternate = 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 = xstrdup (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 buff - binding->buffer;
+ }
+
+ buff++;
+ }
+
+ if (alternate)
+ free (alternate);
+
+ return -1;
+}
+
+/* Search for STRING backwards through the text delimited in BINDING. */
+long
+search_backward (char *input_string, SEARCH_BINDING *binding)
+{
+ register int c, i, len;
+ register char *buff, *end;
+ char *string;
+ char *alternate = NULL;
+
+ len = strlen (input_string);
+
+ /* Reverse the characters in the search string. */
+ string = 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 = xstrdup (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 - 1;
+ 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 1 + buff - binding->buffer;
+ }
+
+ buff--;
+ }
+
+ free (string);
+ if (alternate)
+ free (alternate);
+
+ return -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 (char *string, char *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 (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 (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 (char *string)
+{
+ register int i;
+
+ for (i = 0; string && whitespace_or_newline (string[i]); i++);
+ return i;
+}
+
+/* Return the index of the first whitespace character in STRING. */
+int
+skip_non_whitespace (char *string)
+{
+ register int i;
+
+ for (i = 0; string && string[i] && !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 (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 == '.' &&
+ (
+#if 0
+/* This test causes a node name ending in a period, like `This.', not to
+ be found. The trailing . is stripped. This occurs in the jargon
+ file (`I see no X here.' is a node name). */
+ (!string[i + 1]) ||
+#endif
+ (whitespace_or_newline (string[i + 1])) ||
+ (string[i + 1] == ')'))))
+ break;
+ }
+ return i;
+}
+
+
+/* **************************************************************** */
+/* */
+/* 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 (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 (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 (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 (SEARCH_BINDING *binding)
+{
+ SEARCH_BINDING tmp_search;
+ long position;
+
+ tmp_search.buffer = binding->buffer;
+ tmp_search.start = binding->start;
+ tmp_search.end = binding->end;
+ tmp_search.flags = S_FoldCase;
+
+ while ((position = find_node_separator (&tmp_search)) != -1 )
+ {
+ tmp_search.start = position;
+ tmp_search.start += skip_node_separator (tmp_search.buffer
+ + tmp_search.start);
+
+ if (looking_at (TAGS_TABLE_BEG_LABEL, &tmp_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 (char *nodename, SEARCH_BINDING *binding)
+{
+ long position;
+ int offset, namelen;
+ SEARCH_BINDING tmp_search;
+
+ namelen = strlen (nodename);
+
+ tmp_search.buffer = binding->buffer;
+ tmp_search.start = binding->start;
+ tmp_search.end = binding->end;
+ tmp_search.flags = 0;
+
+ while ((position = find_node_separator (&tmp_search)) != -1)
+ {
+ tmp_search.start = position;
+ tmp_search.start += skip_node_separator
+ (tmp_search.buffer + tmp_search.start);
+
+ offset = string_in_line
+ (INFO_NODE_LABEL, tmp_search.buffer + tmp_search.start);
+
+ if (offset == -1)
+ continue;
+
+ tmp_search.start += offset;
+ tmp_search.start += skip_whitespace (tmp_search.buffer + tmp_search.start);
+ offset = skip_node_characters
+ (tmp_search.buffer + tmp_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) &&
+ (tmp_search.buffer[tmp_search.start] == nodename[0]) &&
+ (strncmp (tmp_search.buffer + tmp_search.start, nodename, offset) == 0))
+ return position;
+ }
+ return -1;
+}
diff --git a/info/search.h b/info/search.h
new file mode 100644
index 0000000..ac2ce76
--- /dev/null
+++ b/info/search.h
@@ -0,0 +1,77 @@
+/* search.h -- Structure used to search large bodies of text, with bounds.
+ $Id: search.h,v 1.8 2008/06/11 09:02:11 gray Exp $
+
+ Copyright (C) 1993, 1997, 1998, 2002, 2004, 2007
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Originally 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. */
+
+#ifndef INFO_SEARCH_H
+#define INFO_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 (char *buffer, long int start, long int end);
+SEARCH_BINDING *copy_binding (SEARCH_BINDING *binding);
+extern long search_forward (char *string, SEARCH_BINDING *binding);
+extern long search_backward (char *input_string, SEARCH_BINDING *binding);
+extern long search (char *string, SEARCH_BINDING *binding);
+extern long regexp_search (char *regexp, SEARCH_BINDING *binding, long length,
+ SEARCH_BINDING *pret);
+extern int looking_at (char *string, SEARCH_BINDING *binding);
+
+/* Note that STRING_IN_LINE () always returns the offset of the 1st character
+ after the string. */
+extern int string_in_line (char *string, char *line);
+
+/* 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 (char *string);
+extern int skip_non_whitespace (char *string);
+extern int skip_whitespace_and_newlines (char *string);
+extern int skip_line (char *string);
+extern int skip_node_characters (char *string, int newlines_okay);
+extern int skip_node_separator (char *body);
+
+#define DONT_SKIP_NEWLINES 0
+#define SKIP_NEWLINES 1
+
+extern long find_node_separator (SEARCH_BINDING *binding);
+extern long find_tags_table (SEARCH_BINDING *binding);
+extern long find_node_in_binding (char *nodename, SEARCH_BINDING *binding);
+
+#endif /* not INFO_SEARCH_H */
diff --git a/info/session.c b/info/session.c
new file mode 100644
index 0000000..6eaea61
--- /dev/null
+++ b/info/session.c
@@ -0,0 +1,5455 @@
+/* session.c -- user windowing interface to Info.
+ $Id: session.c,v 1.43 2008/06/11 17:38:33 gray Exp $
+
+ Copyright (C) 1993, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
+ 2004, 2007, 2008 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Originally written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include "search.h"
+#include <sys/ioctl.h>
+
+#if defined (HAVE_SYS_TIME_H)
+# include <sys/time.h>
+# define HAVE_STRUCT_TIMEVAL
+#endif /* HAVE_SYS_TIME_H */
+
+#if defined (HANDLE_MAN_PAGES)
+# include "man.h"
+#endif
+
+static void info_clear_pending_input (void);
+static void info_set_pending_input (unsigned char key);
+static void info_handle_pointer (char *label, WINDOW *window);
+static void display_info_keyseq (int expecting_future_input);
+char *node_printed_rep (NODE *node);
+
+/* **************************************************************** */
+/* */
+/* Running an Info Session */
+/* */
+/* **************************************************************** */
+
+/* The place that we are reading input from. */
+static FILE *info_input_stream = NULL;
+
+/* The last executed command. */
+VFunction *info_last_executed_command = 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 = 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;
+
+/* Whether to use regexps or not for search. */
+static int use_regex = 1;
+
+void remember_window_and_node (WINDOW *window, NODE *node);
+void forget_window_and_nodes (WINDOW *window);
+void display_startup_message_and_start (void);
+
+/* 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 (char *filename, char **nodenames)
+{
+ register int i;
+ 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, 1);
+ 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. */
+ WINDOW *win, *largest = 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 (msg_cant_find_window, NULL, NULL);
+ info_session ();
+ xexit (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 (msg_win_too_small, NULL, NULL);
+ info_session ();
+ xexit (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 (NODE *initial_node, const char *format,
+ void *arg1, void *arg2)
+{
+ initialize_info_session (initial_node, 1);
+ info_error (format, arg1, arg2);
+ info_session ();
+}
+
+/* Start an info session with INITIAL_NODE. */
+void
+begin_info_session (NODE *initial_node)
+{
+ initialize_info_session (initial_node, 1);
+ display_startup_message_and_start ();
+}
+
+void
+display_startup_message_and_start (void)
+{
+ char *format;
+
+ format = replace_in_documentation
+ (_("Welcome to Info version %s. Type \\[get-help-window] for help, \\[menu-item] for menu item."),
+ 0);
+
+ window_message_in_echo_area (format, VERSION, NULL);
+ info_session ();
+}
+
+/* Run an info session with an already initialized window and node. */
+void
+info_session (void)
+{
+ display_update_display (windows);
+ info_last_executed_command = NULL;
+ info_read_and_dispatch ();
+ /* On program exit, leave the cursor at the bottom of the window, and
+ restore the terminal I/O. */
+ terminal_goto_xy (0, screenheight - 1);
+ terminal_clear_to_eol ();
+ fflush (stdout);
+ 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 (void)
+{
+ unsigned char key;
+ int done;
+ done = 0;
+
+ while (!done && !quit_info_immediately)
+ {
+ int lk = 0;
+
+ /* If we haven't just gone up or down a line, there is no
+ goal column for this window. */
+ if ((info_last_executed_command != (VFunction *) info_next_line) &&
+ (info_last_executed_command != (VFunction *) 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 == (VFunction *) ea_newline ||
+ info_aborted_echo_area)
+ {
+ ea_last_executed_command = NULL;
+ done = 1;
+ }
+
+ if (info_last_executed_command == (VFunction *) info_quit)
+ quit_info_immediately = 1;
+ }
+ else if (info_last_executed_command == (VFunction *) info_quit)
+ done = 1;
+ }
+}
+
+/* Found in signals.c */
+extern void initialize_info_signal_handler (void );
+
+/* Initialize the first info session by starting the terminal, window,
+ and display systems. If CLEAR_SCREEN is 0, don't clear the screen. */
+void
+initialize_info_session (NODE *node, int clear_screen)
+{
+ char *term_name = getenv ("TERM");
+ terminal_initialize_terminal (term_name);
+
+ if (terminal_is_dumb_p)
+ {
+ if (!term_name)
+ term_name = "dumb";
+
+ info_error (msg_term_too_dumb, term_name, NULL);
+ xexit (1);
+ }
+
+ if (clear_screen)
+ {
+ terminal_prep_terminal ();
+ 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 (0, 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 = (VFunction *) forget_window_and_nodes;
+
+ /* If input has not been redirected yet, make it come from unbuffered
+ standard input. */
+ if (!info_input_stream)
+ {
+ setbuf (stdin, NULL);
+ 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 (char *filename)
+{
+ FILE *stream;
+
+ /* Input may include binary characters. */
+ stream = fopen (filename, FOPEN_RBIN);
+
+ if (!stream)
+ return;
+
+ if ((info_input_stream != NULL) &&
+ (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)
+{
+ register int i;
+ INFO_WINDOW *info_win = 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)
+{
+ 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 *window, NODE *node)
+{
+ /* See if we already have this window in our list. */
+ INFO_WINDOW *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 = xmalloc (sizeof (INFO_WINDOW));
+ info_win->window = window;
+ info_win->nodes = NULL;
+ info_win->pagetops = NULL;
+ info_win->points = 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 current saved node and pagetop, don't really add this to
+ the list of history nodes. This may happen only at the very
+ beginning of the program, I'm not sure. --karl */
+ if (info_win->nodes
+ && info_win->current >= 0
+ && info_win->nodes[info_win->current]->contents == node->contents
+ && info_win->pagetops[info_win->current] == window->pagetop
+ && info_win->points[info_win->current] == 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_slots += 20;
+ info_win->nodes = (NODE **) xrealloc (info_win->nodes,
+ info_win->nodes_slots * 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] = 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 (void)
+{
+ register int i;
+
+ 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)
+{
+ register int i;
+ INFO_WINDOW *info_win = 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 + 1];
+ i++;
+ }
+
+ info_windows_index--;
+ info_windows[info_windows_index] = NULL;
+
+ if (info_win->nodes)
+ {
+ /* Free the node structures which held onto internal node contents
+ here. This doesn't free the contents; we have a garbage collector
+ which does that. */
+ 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. If REMEMBER is nonzero, first call
+ set_remembered_pagetop_and_point. */
+void
+info_set_node_of_window (int remember, WINDOW *window, NODE *node)
+{
+ if (remember)
+ set_remembered_pagetop_and_point (window);
+
+ /* 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 *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)
+{
+ 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 (int old, int new, WINDOW *window)
+{
+ if (old == -1)
+ {
+ info_error (msg_cant_find_point, NULL, NULL);
+ }
+ 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, goal);
+ info_show_point (window);
+ }
+}
+
+static int forward_move_node_structure (WINDOW *window, int behaviour);
+static int backward_move_node_structure (WINDOW *window, int behaviour);
+
+/* 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
+ while (count)
+ {
+ int diff;
+
+ old_line = window_line_of_point (window);
+ diff = window->line_count - old_line;
+ if (diff > count)
+ diff = count;
+
+ count -= diff;
+ new_line = old_line + diff;
+ if (new_line >= window->line_count)
+ {
+ if (cursor_movement_scrolls_p)
+ {
+ if (forward_move_node_structure (window,
+ info_scroll_behaviour))
+ break;
+ move_to_new_line (0, 0, window);
+ }
+ else
+ break;
+ }
+ else
+ 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
+ while (count)
+ {
+ int diff;
+
+ old_line = window_line_of_point (window);
+ diff = old_line + 1;
+ if (diff > count)
+ diff = count;
+
+ count -= diff;
+ new_line = old_line - diff;
+
+ if (new_line < 0
+ && cursor_movement_scrolls_p)
+ {
+ if (backward_move_node_structure (window, info_scroll_behaviour))
+ break;
+ if (window->line_count > window->height)
+ set_window_pagetop (window, window->line_count - window->height);
+ move_to_new_line (window->line_count,
+ window->line_count - 1, window);
+ }
+ else
+ move_to_new_line (old_line, new_line, window);
+ }
+}
+
+/* Return true if POINT sits on a newline character. */
+static int
+_looking_at_newline (WINDOW *win, long point)
+{
+ mbi_iterator_t iter;
+
+ mbi_init (iter, win->node->contents + point,
+ win->node->nodelen - point);
+ mbi_avail (iter);
+ return mbi_cur (iter).wc_valid && mbi_cur (iter).wc == '\n';
+}
+
+/* Advance point of WIN to the beginning of the next logical line.
+ Return 1 if there is no next line. */
+static int
+point_next_line (WINDOW *win)
+{
+ int line = window_line_of_point (win);
+ if (line + 1 >= win->line_count)
+ return 1;
+ win->point = win->line_starts[line + 1] - win->node->contents;
+ window_compute_line_map (win);
+ return 0;
+}
+
+/* Move point of WIN to the beginning of the previous logical
+ line.
+ Return 1 if there is no previous line. */
+static int
+point_prev_line (WINDOW *win)
+{
+ int line = window_line_of_point (win);
+ if (line == 0)
+ return 1;
+ win->point = win->line_starts[line - 1] - win->node->contents;
+ window_compute_line_map (win);
+ return 0;
+}
+
+/* Advance point to the next multibyte character. Return 1 if this would
+ cause pointing past the end of node buffer. */
+static int
+point_forward_char (WINDOW *win)
+{
+ long point = win->point;
+ int col;
+
+ window_compute_line_map (win);
+ col = window_point_to_column (win, point, &point) + 1;
+ if (col >= win->line_map.used)
+ {
+ if (point_next_line (win))
+ return 1;
+ col = 0;
+ }
+ win->point = win->line_map.map[col];
+ return 0;
+}
+
+/* Set point to the previous multibyte character.
+ Return 1 if already on the beginning of node buffer. */
+static int
+point_backward_char (WINDOW *win)
+{
+ long point = win->point;
+ int col;
+
+ window_compute_line_map (win);
+ col = window_point_to_column (win, point, &point);
+ for (; col >= 0 && win->line_map.map[col] == point; col--)
+ ;
+ if (col < 0)
+ {
+ if (point_prev_line (win))
+ return 1;
+ col = win->line_map.used - 1;
+ }
+ win->point = win->line_map.map[col];
+ return 0;
+}
+
+/* Skip forward any white space characters starting from column *PCOL in
+ the current line, advancing line if necessary. Return 1 if going past
+ the end of node buffer. */
+static int
+point_skip_ws_forward (WINDOW *win, int *pcol)
+{
+ mbi_iterator_t iter;
+ int col = *pcol;
+
+ while (1)
+ {
+ char *buffer = win->node->contents;
+ size_t buflen = win->node->nodelen;
+
+ for (; col < win->line_map.used; col++)
+ {
+ mbi_init (iter, buffer + win->line_map.map[col],
+ buflen - win->line_map.map[col]);
+ mbi_avail (iter);
+ if (!mbi_cur (iter).wc_valid || iswalnum (mbi_cur (iter).wc))
+ {
+ *pcol = col;
+ return 0;
+ }
+ }
+ if (point_next_line (win))
+ return 1;
+ col = 0;
+ }
+ return 1;
+}
+
+/* Skip backward any white space characters starting from column *PCOL in
+ the current line, retracting line if necessary. Return 1 if going
+ before the beginning of node buffer. */
+static int
+point_skip_ws_backward (WINDOW *win, int *pcol)
+{
+ mbi_iterator_t iter;
+ int col = *pcol;
+
+ while (1)
+ {
+ char *buffer = win->node->contents;
+ size_t buflen = win->node->nodelen;
+
+ for (; col > 0; col--)
+ {
+ mbi_init (iter, buffer + win->line_map.map[col],
+ buflen - win->line_map.map[col]);
+ mbi_avail (iter);
+ if (!mbi_cur (iter).wc_valid || iswalnum (mbi_cur (iter).wc))
+ {
+ *pcol = col;
+ return 0;
+ }
+ }
+ if (point_prev_line (win))
+ return 1;
+ col = win->line_map.used - 1;
+ }
+ return 1;
+}
+
+/* Advance window point to the beginning of the next word. Return 1
+ if there are no more words in the buffer. */
+static int
+point_forward_word (WINDOW *win)
+{
+ mbi_iterator_t iter;
+ int col;
+
+ window_compute_line_map (win);
+ col = window_point_to_column (win, win->point, &win->point);
+
+ if (point_skip_ws_forward (win, &col))
+ return 1;
+
+ while (1)
+ {
+ char *buffer = win->node->contents;
+ size_t buflen = win->node->nodelen;
+
+ for (; col < win->line_map.used; col++)
+ {
+ mbi_init (iter, buffer + win->line_map.map[col],
+ buflen - win->line_map.map[col]);
+ mbi_avail (iter);
+ if (!(mbi_cur (iter).wc_valid && iswalnum (mbi_cur (iter).wc)))
+ {
+ if (point_skip_ws_forward (win, &col))
+ return 1;
+ win->point = win->line_map.map[col];
+ return 0;
+ }
+ }
+ if (point_next_line (win))
+ return 1;
+ col = 0;
+ }
+ return 1;
+}
+
+/* Set window point to the beginning of the previous word. Return 1
+ if looking at the very first word in the buffer. */
+static int
+point_backward_word (WINDOW *win)
+{
+ mbi_iterator_t iter;
+ int col;
+
+ window_compute_line_map (win);
+ col = window_point_to_column (win, win->point, &win->point);
+
+ while (1)
+ {
+ long point;
+ char *buffer;
+ size_t buflen;
+
+ if (col <= 0)
+ {
+ if (point_prev_line (win))
+ return 1;
+ col = win->line_map.used;
+ }
+ col--;
+ if (point_skip_ws_backward (win, &col))
+ return 1;
+
+ buffer = win->node->contents;
+ buflen = win->node->nodelen;
+
+ for (; col >= 0; col--)
+ {
+ mbi_init (iter, buffer + win->line_map.map[col],
+ buflen - win->line_map.map[col]);
+ mbi_avail (iter);
+ if (!(mbi_cur (iter).wc_valid && iswalnum (mbi_cur (iter).wc)))
+ {
+ win->point = win->line_map.map[col+1];
+ return 0;
+ }
+ }
+ point = win->line_map.map[0] - 1;
+ if (point > 0 && _looking_at_newline (win, point))
+ {
+ win->point = win->line_map.map[0];
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/* 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"))
+{
+ int point = window_end_of_line (window);
+ 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"))
+{
+ int old_point = window->point;
+ int point;
+
+ while (1)
+ {
+ window_compute_line_map (window);
+ point = window->line_map.map[0];
+ if (point == 0 || _looking_at_newline (window, point-1))
+ break;
+ point_prev_line (window);
+ }
+
+ if (point != old_point)
+ {
+ window->point = point;
+ info_show_point (window);
+ }
+ else
+ window->point = old_point;
+}
+
+/* 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
+ {
+ while (count)
+ {
+ if (point_forward_char (window))
+ {
+ if (cursor_movement_scrolls_p
+ && forward_move_node_structure (window,
+ info_scroll_behaviour) == 0)
+ window->point = 0;
+ else
+ {
+ window->point = window->node->nodelen - 1;
+ break;
+ }
+ }
+ count--;
+ }
+ 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
+ {
+ while (count)
+ {
+ if (point_backward_char (window))
+ {
+ if (cursor_movement_scrolls_p
+ && backward_move_node_structure (window,
+ info_scroll_behaviour) == 0)
+ {
+ window->point = window->node->nodelen - 1;
+ if (window->line_count > window->height)
+ set_window_pagetop (window,
+ window->line_count - window->height);
+ }
+ else
+ {
+ window->point = 0;
+ break;
+ }
+ }
+ count--;
+ }
+ info_show_point (window);
+ }
+}
+
+/* Move forward a word in this node. */
+DECLARE_INFO_COMMAND (info_forward_word, _("Move forward a word"))
+{
+ if (count < 0)
+ {
+ info_backward_word (window, -count, key);
+ return;
+ }
+
+ while (count)
+ {
+ if (point_forward_word (window))
+ {
+ if (cursor_movement_scrolls_p
+ && forward_move_node_structure (window,
+ info_scroll_behaviour) == 0)
+ window->point = 0;
+ else
+ return;
+ }
+ --count;
+ }
+ info_show_point (window);
+}
+
+DECLARE_INFO_COMMAND (info_backward_word, _("Move backward a word"))
+{
+ if (count < 0)
+ {
+ info_forward_word (window, -count, key);
+ return;
+ }
+
+ while (count)
+ {
+ if (point_backward_word (window))
+ {
+ if (cursor_movement_scrolls_p
+ && backward_move_node_structure (window,
+ info_scroll_behaviour) == 0)
+ {
+ if (window->line_count > window->height)
+ set_window_pagetop (window,
+ window->line_count - window->height);
+ window->point = window->node->nodelen;
+ }
+ else
+ break;
+ }
+ --count;
+ }
+ info_show_point (window);
+}
+
+/* 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", NULL
+};
+
+/* Controls whether scroll-behavior affects line movement commands */
+int cursor_movement_scrolls_p = 1;
+
+/* Choices for the scroll-last-node variable */
+char *scroll_last_node_choices[] = {
+ "Stop", "Scroll", "Top", NULL
+};
+
+/* Controls what to do when a scrolling command is issued at the end of the
+ last node. */
+int scroll_last_node = SLN_Stop;
+
+/* Default window sizes for scrolling commands. */
+int default_window_size = -1; /* meaning 1 window-full */
+int default_scroll_size = -1; /* meaning half screen size */
+
+#define INFO_LABEL_FOUND() \
+ (info_parsed_nodename || (info_parsed_filename \
+ && !is_dir_name (info_parsed_filename)))
+
+static int
+last_node_p (NODE *node)
+{
+ info_next_label_of_node (node);
+ if (!INFO_LABEL_FOUND ())
+ {
+ info_up_label_of_node (node);
+ return !INFO_LABEL_FOUND () || strcmp (info_parsed_nodename, "Top") == 0;
+ }
+ return 0;
+}
+
+/* Move to 1st menu item, Next, Up/Next, or error in this window. */
+static int
+forward_move_node_structure (WINDOW *window, int behaviour)
+{
+ switch (behaviour)
+ {
+ case IS_PageOnly:
+ info_error (msg_at_node_bottom, NULL, NULL);
+ return 1;
+
+ case IS_NextOnly:
+ info_next_label_of_node (window->node);
+ if (!info_parsed_nodename && !info_parsed_filename)
+ {
+ info_error (msg_no_pointer, (char *) _("Next"), NULL);
+ return 1;
+ }
+ else
+ {
+ info_handle_pointer ("Next", window);
+ }
+ break;
+
+ case IS_Continuous:
+ {
+ if (last_node_p (window->node))
+ {
+ switch (scroll_last_node)
+ {
+ case SLN_Stop:
+ info_error (_("No more nodes within this document."),
+ NULL, NULL);
+ return 1;
+
+ case SLN_Scroll:
+ break;
+
+ case SLN_Top:
+ info_top_node (window, 1, 0);
+ return 0;
+
+ default:
+ abort ();
+ }
+ }
+
+ /* 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);
+ info_menu_digit (window, 1, '1');
+ return 0;
+ }
+ }
+
+ /* 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_FOUND ())
+ {
+ info_handle_pointer ("Next", window);
+ return 0;
+ }
+
+ /* 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_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_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 = xstrdup (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. */
+ info_handle_pointer ("Next", window);
+ return 0;
+ }
+ 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] = 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 within this document."),
+ NULL, NULL);
+ return 1;
+ }
+ }
+ }
+ break;
+ }
+ }
+ return info_error_was_printed; /*FIXME*/
+}
+
+/* Move Prev, Up or error in WINDOW depending on BEHAVIOUR. */
+static int
+backward_move_node_structure (WINDOW *window, int behaviour)
+{
+ switch (behaviour)
+ {
+ case IS_PageOnly:
+ info_error (msg_at_node_top, NULL, NULL);
+ return 1;
+
+ case IS_NextOnly:
+ info_prev_label_of_node (window->node);
+ if (!info_parsed_nodename && !info_parsed_filename)
+ {
+ info_error (_("No `Prev' for this node."), NULL, NULL);
+ return 1;
+ }
+ else
+ {
+ info_handle_pointer ("Prev", window);
+ }
+ break;
+
+ case IS_Continuous:
+ info_prev_label_of_node (window->node);
+
+ if (!info_parsed_nodename && (!info_parsed_filename
+ || is_dir_name (info_parsed_filename)))
+ {
+ info_up_label_of_node (window->node);
+ if (!info_parsed_nodename && (!info_parsed_filename
+ || is_dir_name (info_parsed_filename)))
+ {
+ info_error (
+ _("No `Prev' or `Up' for this node within this document."),
+ NULL, NULL);
+ return 1;
+ }
+ else
+ {
+ 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 = xstrdup (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. */
+ 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);
+ info_menu_digit (window, 1, '0');
+ }
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+/* 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--;
+ }
+ }
+}
+
+static void _scroll_forward(WINDOW *window, int count,
+ unsigned char key, int behaviour);
+static void _scroll_backward(WINDOW *window, int count,
+ unsigned char key, int behaviour);
+
+static void
+_scroll_forward(WINDOW *window, int count, unsigned char key, int behaviour)
+{
+ if (count < 0)
+ _scroll_backward (window, -count, key, behaviour);
+ 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 chosen behaviour is to scroll through nodes get the
+ "Next" node for this window. */
+ if (default_window_size > 0)
+ desired_top = window->pagetop + default_window_size;
+ else 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 BEHAVIOUR. */
+ if (desired_top > window->line_count)
+ {
+ if (forward_move_node_structure (window, behaviour))
+ info_end_of_node (window, 1, 0);
+ 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);
+ }
+}
+
+static void
+_scroll_backward(WINDOW *window, int count, unsigned char key, int behaviour)
+{
+ if (count < 0)
+ _scroll_forward (window, -count, key, behaviour);
+ else
+ {
+ int desired_top;
+
+ /* Without an explicit numeric argument, scroll the top two lines
+ to the bottom of this window, or, depending on the selected
+ behaviour, move to the previous, or Up'th node. */
+ if (default_window_size > 0)
+ desired_top = window->pagetop - default_window_size;
+ else if (!info_explicit_arg && count == 1)
+ {
+ desired_top = window->pagetop - (window->height - 2);
+
+ if ((desired_top < 0) && (window->pagetop == 0))
+ {
+ if ((backward_move_node_structure (window, behaviour) == 0)
+ && (cursor_movement_scrolls_p))
+ info_end_of_node (window, 1, 0);
+ window->point = (window->line_starts[window->pagetop]
+ - window->node->contents);
+ return;
+ }
+ }
+ else
+ desired_top = window->pagetop - count;
+
+ if (desired_top < 0)
+ desired_top = 0;
+
+ set_window_pagetop (window, desired_top);
+ window->point = (window->line_starts[window->pagetop]
+ - window->node->contents);
+ }
+}
+
+/* Show the next screen of WINDOW's node. */
+DECLARE_INFO_COMMAND (info_scroll_forward, _("Scroll forward in this window"))
+{
+ _scroll_forward (window, count, key, info_scroll_behaviour);
+}
+
+/* Like info_scroll_forward, but sets default_window_size as a side
+ effect. */
+DECLARE_INFO_COMMAND (info_scroll_forward_set_window,
+ _("Scroll forward in this window and set default window size"))
+{
+ if (info_explicit_arg)
+ default_window_size = count;
+ _scroll_forward (window, count, key, info_scroll_behaviour);
+}
+
+/* Show the next screen of WINDOW's node but never advance to next node. */
+DECLARE_INFO_COMMAND (info_scroll_forward_page_only, _("Scroll forward in this window staying within node"))
+{
+ _scroll_forward (window, count, key, IS_PageOnly);
+}
+
+/* Like info_scroll_forward_page_only, but sets default_window_size as a side
+ effect. */
+DECLARE_INFO_COMMAND (info_scroll_forward_page_only_set_window,
+ _("Scroll forward in this window staying within node and set default window size"))
+{
+ if (info_explicit_arg)
+ default_window_size = count;
+ _scroll_forward (window, count, key, IS_PageOnly);
+}
+
+/* Show the previous screen of WINDOW's node. */
+DECLARE_INFO_COMMAND (info_scroll_backward, _("Scroll backward in this window"))
+{
+ _scroll_backward (window, count, key, info_scroll_behaviour);
+}
+
+/* Like info_scroll_backward, but sets default_window_size as a side
+ effect. */
+DECLARE_INFO_COMMAND (info_scroll_backward_set_window,
+ _("Scroll backward in this window and set default window size"))
+{
+ if (info_explicit_arg)
+ default_window_size = count;
+ _scroll_backward (window, count, key, info_scroll_behaviour);
+}
+
+/* Show the previous screen of WINDOW's node but never move to previous
+ node. */
+DECLARE_INFO_COMMAND (info_scroll_backward_page_only, _("Scroll backward in this window staying within node"))
+{
+ _scroll_backward (window, count, key, IS_PageOnly);
+}
+
+/* Like info_scroll_backward_page_only, but sets default_window_size as a side
+ effect. */
+DECLARE_INFO_COMMAND (info_scroll_backward_page_only_set_window,
+ _("Scroll backward in this window staying within node and set default window size"))
+{
+ if (info_explicit_arg)
+ default_window_size = count;
+ _scroll_backward (window, count, key, IS_PageOnly);
+}
+
+/* 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);
+}
+
+/* Scroll the window forward by N lines. */
+DECLARE_INFO_COMMAND (info_down_line, _("Scroll down by lines"))
+{
+ if (count < 0)
+ info_up_line (window, -count, key);
+ else
+ {
+ int desired_top = window->pagetop + count;
+
+ if (desired_top >= window->line_count)
+ desired_top = window->line_count - 2;
+
+ if (window->pagetop <= desired_top)
+ set_window_pagetop (window, desired_top);
+ }
+}
+
+/* Scroll the window backward by N lines. */
+DECLARE_INFO_COMMAND (info_up_line, _("Scroll up by lines"))
+{
+ if (count < 0)
+ info_down_line (window, -count, key);
+ else
+ {
+ int desired_top = window->pagetop - count;
+
+ if (desired_top < 0)
+ desired_top = 0;
+
+ set_window_pagetop (window, desired_top);
+ }
+}
+
+/* Scroll the window forward by N lines and remember N as default for
+ subsequent commands. */
+DECLARE_INFO_COMMAND (info_scroll_half_screen_down,
+ _("Scroll down by half screen size"))
+{
+ if (count < 0)
+ info_scroll_half_screen_up (window, -count, key);
+ else
+ {
+ int scroll_size = (the_screen->height + 1) / 2;
+ int desired_top;
+
+ if (info_explicit_arg)
+ default_scroll_size = count;
+ if (default_scroll_size > 0)
+ scroll_size = default_scroll_size;
+
+ desired_top = window->pagetop + scroll_size;
+ if (desired_top >= window->line_count)
+ desired_top = window->line_count - 2;
+
+ if (window->pagetop <= desired_top)
+ set_window_pagetop (window, desired_top);
+ }
+}
+
+/* Scroll the window backward by N lines and remember N as default for
+ subsequent commands. */
+DECLARE_INFO_COMMAND (info_scroll_half_screen_up,
+ _("Scroll up by half screen size"))
+{
+ if (count < 0)
+ info_scroll_half_screen_down (window, -count, key);
+ else
+ {
+ int scroll_size = (the_screen->height + 1) / 2;
+ int desired_top;
+
+ if (info_explicit_arg)
+ default_scroll_size = count;
+ if (default_scroll_size > 0)
+ scroll_size = default_scroll_size;
+
+ desired_top = window->pagetop - scroll_size;
+ if (desired_top < 0)
+ desired_top = 0;
+
+ set_window_pagetop (window, desired_top);
+ }
+}
+
+/* **************************************************************** */
+/* */
+/* 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 (msg_one_window, NULL, NULL);
+ 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 (msg_one_window, NULL, NULL);
+ 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 = NULL;
+
+ 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 (msg_win_too_small, NULL, NULL);
+ }
+ 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 = 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 (msg_cant_kill_last, NULL, NULL);
+ }
+ else if (window->flags & W_WindowIsPerm)
+ {
+ info_error (_("Cannot delete a permanent window"), NULL, NULL);
+ }
+ 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)
+{
+ 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 (msg_one_window, NULL, NULL);
+ return;
+ }
+
+ other = window->next;
+
+ if (!other)
+ other = window->prev;
+
+ info_scroll_forward (other, count, key);
+}
+
+/* Scroll the "other" window of WINDOW. */
+DECLARE_INFO_COMMAND (info_scroll_other_window_backward,
+ _("Scroll the other window backward"))
+{
+ info_scroll_other_window (window, -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);
+}
+
+/* Toggle the usage of regular expressions in searches. */
+DECLARE_INFO_COMMAND (info_toggle_regexp,
+ _("Toggle the usage of regular expressions in searches"))
+{
+ use_regex = 1 - use_regex;
+ window_message_in_echo_area (use_regex
+ ? _("Using regular expressions for searches.")
+ : _("Using literal strings for searches."),
+ NULL, NULL);
+}
+
+/* **************************************************************** */
+/* */
+/* Info Node Commands */
+/* */
+/* **************************************************************** */
+
+/* Return (FILENAME)NODENAME for NODE, or just NODENAME if NODE's
+ filename is not set. */
+char *
+node_printed_rep (NODE *node)
+{
+ char *rep;
+
+ if (node->filename)
+ {
+ char *filename
+ = filename_non_directory (node->parent ? node->parent : node->filename);
+ rep = xmalloc (1 + strlen (filename) + 1 + strlen (node->nodename) + 1);
+ sprintf (rep, "(%s)%s", filename, node->nodename);
+ }
+ else
+ rep = node->nodename;
+
+ return rep;
+}
+
+
+/* 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 *window, REFERENCE *entry)
+{
+ NODE *node;
+ char *filename, *nodename, *file_system_error;
+
+ file_system_error = NULL;
+
+ filename = entry->filename;
+ if (!filename)
+ filename = window->node->parent;
+ if (!filename)
+ filename = window->node->filename;
+
+ if (filename)
+ filename = xstrdup (filename);
+
+ if (entry->nodename)
+ nodename = xstrdup (entry->nodename);
+ else
+ nodename = xstrdup ("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 = xstrdup (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 = xstrdup (info_recent_file_error);
+ }
+ }
+ }
+
+ if (!node)
+ {
+ if (file_system_error)
+ info_error (file_system_error, NULL, NULL);
+ else
+ info_error (msg_cant_find_node, nodename, NULL);
+ }
+
+ maybe_free (file_system_error);
+ maybe_free (filename);
+ maybe_free (nodename);
+
+ if (node)
+ info_set_node_of_window (1, 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 (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 (char *label, WINDOW *window)
+{
+ if (info_parsed_filename || info_parsed_nodename)
+ {
+ char *filename, *nodename;
+ NODE *node;
+
+ filename = nodename = NULL;
+
+ if (info_parsed_filename)
+ filename = xstrdup (info_parsed_filename);
+ else
+ {
+ if (window->node->parent)
+ filename = xstrdup (window->node->parent);
+ else if (window->node->filename)
+ filename = xstrdup (window->node->filename);
+ }
+
+ if (info_parsed_nodename)
+ nodename = xstrdup (info_parsed_nodename);
+ else
+ nodename = xstrdup ("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;
+ }
+ info_set_node_of_window (1, window, node);
+ }
+ else
+ {
+ if (info_recent_file_error)
+ info_error (info_recent_file_error, NULL, NULL);
+ else
+ info_error (msg_cant_file_node, filename, nodename);
+ }
+
+ free (filename);
+ free (nodename);
+ }
+ else
+ {
+ info_error (msg_no_pointer, label, NULL);
+ }
+}
+
+/* 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 = NULL;
+
+ if (fb && fb->tags)
+ {
+ int last_node_tag_idx = -1;
+
+ /* If no explicit argument, or argument of zero, default to the
+ last node. */
+ if (count == 0 || (count == 1 && !info_explicit_arg))
+ count = -1;
+ for (i = 0; count && fb->tags[i]; i++)
+ if (fb->tags[i]->nodelen != 0) /* don't count anchor tags */
+ {
+ count--;
+ last_node_tag_idx = i;
+ }
+ if (count > 0)
+ i = last_node_tag_idx + 1;
+ if (i > 0)
+ node = info_get_node (fb->filename, fb->tags[i - 1]->nodename);
+ }
+
+ if (!node)
+ info_error (_("This window has no additional nodes"), NULL, NULL);
+ else
+ info_set_node_of_window (1, 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 = NULL;
+
+ /* If no explicit argument, or argument of zero, default to the
+ first node. */
+ if (count == 0)
+ count = 1;
+ if (fb && fb->tags)
+ {
+ register int i;
+ int last_node_tag_idx = -1;
+
+ for (i = 0; count && fb->tags[i]; i++)
+ if (fb->tags[i]->nodelen != 0) /* don't count anchor tags */
+ {
+ count--;
+ last_node_tag_idx = i;
+ }
+ if (count > 0)
+ i = last_node_tag_idx + 1;
+ if (i > 0)
+ node = info_get_node (fb->filename, fb->tags[i - 1]->nodename);
+ }
+
+ if (!node)
+ info_error (_("This window has no additional nodes"), NULL, NULL);
+ else
+ info_set_node_of_window (1, window, node);
+}
+
+/* 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 **menu;
+
+ menu = info_menu_of_node (window->node);
+
+ if (!menu)
+ {
+ info_error (msg_no_menu_node, NULL, NULL);
+ 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; menu[i]; i++)
+ if (i == item - 1)
+ break;
+ }
+
+ if (menu[i])
+ {
+ info_select_reference (window, menu[i]);
+ if (menu[i]->line_number > 0)
+ info_next_line (window, menu[i]->line_number - 1, key);
+ }
+ else
+ info_error (_("There aren't %d items in this menu."),
+ (void *) (long) item, NULL);
+
+ info_free_references (menu);
+ return;
+}
+
+
+
+/* Return a pointer to the xref in XREF_LIST that is nearest to POS, or
+ NULL if XREF_LIST is empty. That is, if POS is within any of the
+ given xrefs, return that one. Otherwise, return the one with the
+ nearest beginning or end. If there are two that are equidistant,
+ prefer the one forward. The return is in newly-allocated memory,
+ since the caller frees it.
+
+ This is called from info_menu_or_ref_item with XREF_LIST being all
+ the xrefs in the node, and POS being point. The ui function that
+ starts it all off is select-reference-this-line.
+
+ This is not the same logic as in info.el. Info-get-token prefers
+ searching backwards to searching forwards, and has a hardwired search
+ limit of 200 chars (in Emacs 21.2). */
+
+static REFERENCE **
+nearest_xref (REFERENCE **xref_list, long int pos)
+{
+ int this_xref;
+ int nearest = -1;
+ long best_delta = -1;
+
+ for (this_xref = 0; xref_list[this_xref]; this_xref++)
+ {
+ long delta;
+ REFERENCE *xref = xref_list[this_xref];
+ if (xref->start <= pos && pos <= xref->end)
+ { /* POS is within this xref, we're done */
+ nearest = this_xref;
+ break;
+ }
+
+ /* See how far POS is from this xref. Take into account the
+ `*Note' that begins the xref, since as far as the user is
+ concerned, that's where it starts. */
+ delta = MIN (labs (pos - (xref->start - strlen (INFO_XREF_LABEL))),
+ labs (pos - xref->end));
+
+ /* It's the <= instead of < that makes us choose the forward xref
+ of POS if two are equidistant. Of course, because of all the
+ punctuation surrounding xrefs, it's not necessarily obvious
+ where one ends. */
+ if (delta <= best_delta || best_delta < 0)
+ {
+ nearest = this_xref;
+ best_delta = delta;
+ }
+ }
+
+ /* Maybe there was no list to search through. */
+ if (nearest < 0)
+ return NULL;
+
+ /* Ok, we have a nearest xref, make a list of it. */
+ {
+ REFERENCE **ret = xmalloc (sizeof (REFERENCE *) * 2);
+ ret[0] = info_copy_reference (xref_list[nearest]);
+ ret[1] = NULL;
+ return ret;
+ }
+}
+
+
+/* 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 *window, int count,
+ unsigned char key, REFERENCE **(*builder) (NODE *node), int ask_p)
+{
+ char *line;
+ REFERENCE *entry;
+ REFERENCE *defentry = NULL;
+ REFERENCE **menu = (*builder) (window->node);
+
+ if (!menu)
+ {
+ if (builder == info_menu_of_node)
+ info_error (msg_no_menu_node, NULL, NULL);
+ else
+ info_error (msg_no_xref_node, NULL, NULL);
+ return;
+ }
+
+ /* Default the selected reference to the one which is on the line that
+ point is in. */
+ {
+ REFERENCE **refs = NULL;
+ int point_line = window_line_of_point (window);
+
+ if (point_line != -1)
+ {
+ SEARCH_BINDING binding;
+
+ binding.buffer = window->node->contents;
+ binding.start = window->line_starts[point_line] - binding.buffer;
+ if (window->line_starts[point_line + 1])
+ binding.end = window->line_starts[point_line + 1] - binding.buffer;
+ else
+ binding.end = window->node->nodelen;
+ binding.flags = 0;
+
+ if (builder == info_menu_of_node)
+ {
+ if (point_line)
+ {
+ binding.start--;
+ refs = info_menu_items (&binding);
+ }
+ }
+ else
+ {
+#if defined (HANDLE_MAN_PAGES)
+ if (window->node->flags & N_IsManPage)
+ refs = manpage_xrefs_in_binding (window->node, &binding);
+ else
+#endif /* HANDLE_MAN_PAGES */
+ refs = nearest_xref (menu, window->point);
+ }
+
+ if (refs && refs[0])
+ {
+ if (strcmp (refs[0]->label, "Menu") != 0
+ || builder == info_xrefs_of_node)
+ {
+ int which = 0;
+
+ /* For xrefs, find the closest reference to point,
+ unless we only have one reference (as we will if
+ we've called nearest_xref above). It would be better
+ to have only one piece of code, but the conditions
+ when we call this are tangled. */
+ if (builder == info_xrefs_of_node && refs[1])
+ {
+ int closest = -1;
+
+ for (; refs[which]; which++)
+ {
+ if (window->point >= refs[which]->start
+ && window->point <= refs[which]->end)
+ {
+ closest = which;
+ break;
+ }
+ else if (window->point < refs[which]->start)
+ break;
+ }
+ if (which > 0)
+ {
+ if (closest == -1)
+ which--;
+ else
+ which = closest;
+ }
+ }
+
+ defentry = xmalloc (sizeof (REFERENCE));
+ defentry->label = xstrdup (refs[which]->label);
+ defentry->filename = refs[which]->filename;
+ defentry->nodename = refs[which]->nodename;
+ defentry->line_number = refs[which]->line_number;
+
+ if (defentry->filename)
+ defentry->filename = xstrdup (defentry->filename);
+ if (defentry->nodename)
+ defentry->nodename = xstrdup (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 (builder == info_menu_of_node)
+ {
+ if (defentry)
+ {
+ prompt = xmalloc (strlen (defentry->label)
+ + strlen (_("Menu item (%s): ")));
+ sprintf (prompt, _("Menu item (%s): "), defentry->label);
+ }
+ else
+ prompt = xstrdup (_("Menu item: "));
+ }
+ else
+ {
+ if (defentry)
+ {
+ prompt = xmalloc (strlen (defentry->label)
+ + strlen (_("Follow xref (%s): ")));
+ sprintf (prompt, _("Follow xref (%s): "), defentry->label);
+ }
+ else
+ prompt = xstrdup (_("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 = xstrdup (defentry->label);
+ else
+ line = NULL;
+ }
+ }
+ else
+ {
+ /* Not going to ask any questions. If we have a default entry, use
+ that, otherwise return. */
+ if (!defentry)
+ return;
+ else
+ line = xstrdup (defentry->label);
+ }
+
+ if (line)
+ {
+ /* It is possible that the references have more than a single
+ entry with the same label, and also LINE is down-cased, which
+ complicates matters even more. Try to be as accurate as we
+ can: if they've chosen the default, use defentry directly. */
+ if (defentry && strcmp (line, defentry->label) == 0)
+ entry = defentry;
+ else
+ /* Find the selected label in the references. If there are
+ more than one label which matches, find the one that's
+ closest to point. */
+ {
+ register int i;
+ int best = -1, min_dist = window->node->nodelen;
+ REFERENCE *ref;
+
+ for (i = 0; menu && (ref = menu[i]); i++)
+ {
+ /* Need to use mbscasecmp because LINE is downcased
+ inside info_read_completing_in_echo_area. */
+ if (mbscasecmp (line, ref->label) == 0)
+ {
+ /* ref->end is more accurate estimate of position
+ for menus than ref->start. Go figure. */
+ int dist = abs (window->point - ref->end);
+
+ if (dist < min_dist)
+ {
+ min_dist = dist;
+ best = i;
+ }
+ }
+ }
+ if (best != -1)
+ entry = menu[best];
+ else
+ entry = NULL;
+ }
+
+ if (!entry && defentry)
+ info_error (_("The reference disappeared! (%s)."), line, NULL);
+ else
+ {
+ NODE *orig = window->node;
+ info_select_reference (window, entry);
+
+ if (builder == info_xrefs_of_node && window->node != orig
+ && !(window->node->flags & N_FromAnchor))
+ { /* Search for this reference in the node. */
+ 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);
+ }
+ }
+
+ if (entry->line_number > 0)
+ /* next_line starts at line 1? Anyway, the -1 makes it
+ move to the right line. */
+ info_next_line (window, entry->line_number - 1, key);
+ }
+
+ 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 (msg_no_menu_node, NULL, NULL);
+ 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 (msg_no_menu_node, NULL, NULL);
+
+ 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 (msg_win_too_small, NULL, NULL);
+ 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;
+
+#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 = 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 = xmalloc (sizeof (REFERENCE));
+ entry->filename = entry->nodename = NULL;
+ entry->label = 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 = xmalloc (sizeof (REFERENCE));
+ entry->filename = entry->nodename = NULL;
+ if (this_is_the_current_fb)
+ entry->label = xstrdup (fb->tags[i]->nodename);
+ else
+ {
+ entry->label = 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 *);
+ }
+ }
+ }
+ 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 ();
+}
+
+/* Follow the menu list in MENUS (list of strings terminated by a NULL
+ entry) from INITIAL_NODE. If can't continue at any point (no menu or
+ no menu entry for the next item), return the node so far -- that
+ might be INITIAL_NODE itself. If error, *ERRSTR and *ERRARG[12] will
+ be set to the error message and argument for message, otherwise they
+ will be NULL. */
+
+NODE *
+info_follow_menus (NODE *initial_node, char **menus,
+ const char **errstr, char **errarg1, char **errarg2)
+{
+ NODE *node = NULL;
+ *errstr = *errarg1 = *errarg2 = NULL;
+
+ for (; *menus; menus++)
+ {
+ static char *first_arg = NULL;
+ REFERENCE **menu;
+ REFERENCE *entry;
+ char *arg = *menus; /* Remember the name of the menu entry we want. */
+
+ /* A leading space is certainly NOT part of a node name. Most
+ probably, they typed a space after the separating comma. The
+ strings in menus[] have their whitespace canonicalized, so
+ there's at most one space to ignore. */
+ if (*arg == ' ')
+ arg++;
+ if (!first_arg)
+ first_arg = arg;
+
+ /* Build and return a list of the menu items in this node. */
+ menu = info_menu_of_node (initial_node);
+
+ /* If no 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)
+ {
+ if (arg == first_arg)
+ {
+ node = make_manpage_node (first_arg);
+ if (node)
+ goto maybe_got_node;
+ }
+ *errstr = _("No menu in node `%s'.");
+ *errarg1 = node_printed_rep (initial_node);
+ return initial_node;
+ }
+
+ /* 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)
+ {
+ int i;
+ int best_guess = -1;
+
+ for (i = 0; (entry = menu[i]); i++)
+ {
+ if (mbscasecmp (entry->label, arg) == 0)
+ break;
+ else
+ if ((best_guess == -1)
+ && (mbsncasecmp (entry->label, arg, strlen (arg)) == 0))
+ best_guess = i;
+ }
+
+ if (!entry && best_guess != -1)
+ entry = menu[best_guess];
+ }
+
+ /* If we still failed to find the reference, start Info with the current
+ node anyway. It is probably a misspelling. */
+ if (!entry)
+ {
+ if (arg == first_arg)
+ {
+ /* Maybe they typed "info foo" instead of "info -f foo". */
+ node = info_get_node (first_arg, 0);
+ if (node)
+ add_file_directory_to_path (first_arg);
+ else
+ node = make_manpage_node (first_arg);
+ if (node)
+ goto maybe_got_node;
+ }
+
+ info_free_references (menu);
+ *errstr = _("No menu item `%s' in node `%s'.");
+ *errarg1 = arg;
+ *errarg2 = node_printed_rep (initial_node);
+ return initial_node;
+ }
+
+ /* We have found the reference that the user specified. If no
+ filename in this reference, define it. */
+ if (!entry->filename)
+ entry->filename = xstrdup (initial_node->parent ? initial_node->parent
+ : initial_node->filename);
+
+ /* Try to find this node. */
+ node = info_get_node (entry->filename, entry->nodename);
+ if (!node && arg == first_arg)
+ {
+ node = make_manpage_node (first_arg);
+ if (node)
+ goto maybe_got_node;
+ }
+
+ /* Since we cannot find it, try using the label of the entry as a
+ file, i.e., "(LABEL)Top". */
+ if (!node && entry->nodename
+ && strcmp (entry->label, entry->nodename) == 0)
+ node = info_get_node (entry->label, "Top");
+
+ maybe_got_node:
+ if (!node)
+ {
+ *errstr = _("Unable to find node referenced by `%s' in `%s'.");
+ *errarg1 = xstrdup (entry->label);
+ *errarg2 = node_printed_rep (initial_node);
+ info_free_references (menu);
+ return initial_node;
+ }
+
+ info_free_references (menu);
+
+ /* Success. Go round the loop again. */
+ free (initial_node);
+ initial_node = node;
+ }
+
+ return initial_node;
+}
+
+/* Split STR into individual node names by writing null bytes in wherever
+ there are commas and constructing a list of the resulting pointers.
+ (We can do this since STR has had canonicalize_whitespace called on it.)
+ Return array terminated with NULL. */
+
+static char **
+split_list_of_nodenames (char *str)
+{
+ unsigned len = 2;
+ char **nodes = xmalloc (len * sizeof (char *));
+
+ nodes[len - 2] = str;
+
+ while (*str++)
+ {
+ if (*str == ',')
+ {
+ *str++ = 0; /* get past the null byte */
+ len++;
+ nodes = xrealloc (nodes, len * sizeof (char *));
+ nodes[len - 2] = str;
+ }
+ }
+
+ nodes[len - 1] = NULL;
+
+ return nodes;
+}
+
+
+/* Read a line of input which is a sequence of menus (starting from
+ dir), and follow them. */
+DECLARE_INFO_COMMAND (info_menu_sequence,
+ _("Read a list of menus starting from dir and follow them"))
+{
+ char *line = info_read_in_echo_area (window, _("Follow menus: "));
+
+ /* If the user aborted, quit now. */
+ if (!line)
+ {
+ info_abort_key (window, 0, 0);
+ return;
+ }
+
+ canonicalize_whitespace (line);
+
+ if (*line)
+ {
+ const char *errstr;
+ char *errarg1, *errarg2;
+ NODE *dir_node = info_get_node (NULL, NULL);
+ char **nodes = split_list_of_nodenames (line);
+ NODE *node = NULL;
+
+ /* If DIR_NODE is NULL, they might be reading a file directly,
+ like in "info -d . -f ./foo". Try using "Top" instead. */
+ if (!dir_node)
+ {
+ char *file_name = window->node->parent;
+
+ if (!file_name)
+ file_name = window->node->filename;
+ dir_node = info_get_node (file_name, NULL);
+ }
+
+ /* If we still cannot find the starting point, give up.
+ We cannot allow a NULL pointer inside info_follow_menus. */
+ if (!dir_node)
+ info_error (msg_cant_find_node, "Top", NULL);
+ else
+ node = info_follow_menus (dir_node, nodes, &errstr, &errarg1, &errarg2);
+
+ free (nodes);
+ if (!errstr)
+ info_set_node_of_window (1, window, node);
+ else
+ info_error (errstr, errarg1, errarg2);
+ }
+
+ free (line);
+ if (!info_error_was_printed)
+ window_clear_echo_area ();
+}
+
+/* Search the menu MENU for a (possibly mis-spelled) entry ARG.
+ Return the menu entry, or the best guess for what they meant by ARG,
+ or NULL if there's nothing in this menu seems to fit the bill.
+ If EXACT is non-zero, allow only exact matches. */
+static REFERENCE *
+entry_in_menu (char *arg, REFERENCE **menu, int exact)
+{
+ REFERENCE *entry;
+
+ /* First, try to find the specified menu item verbatim. */
+ entry = info_get_labeled_reference (arg, menu);
+
+ /* If the item wasn't found, search the list sloppily. Perhaps we
+ have "Option Summary", but ARG is "option". */
+ if (!entry && !exact)
+ {
+ int i;
+ int best_guess = -1;
+
+ for (i = 0; (entry = menu[i]); i++)
+ {
+ if (mbscasecmp (entry->label, arg) == 0)
+ break;
+ else
+ if (mbsncasecmp (entry->label, arg, strlen (arg)) == 0)
+ best_guess = i;
+ }
+
+ if (!entry && best_guess != -1)
+ entry = menu[best_guess];
+ }
+
+ return entry;
+}
+
+/* Find the node that is the best candidate to list the PROGRAM's
+ invocation info and its command-line options, by looking for menu
+ items and chains of menu items with characteristic names. */
+void
+info_intuit_options_node (WINDOW *window, NODE *initial_node, char *program)
+{
+ /* The list of node names typical for GNU manuals where the program
+ usage and specifically the command-line arguments are described.
+ This is pure heuristics. I gathered these node names by looking
+ at all the Info files I could put my hands on. If you are
+ looking for evidence to complain to the GNU project about
+ non-uniform style of documentation, here you have your case! */
+ static const char *invocation_nodes[] = {
+ "%s invocation",
+ "Invoking %s",
+ "Preliminaries", /* m4 has Invoking under Preliminaries! */
+ "Invocation",
+ "Command Arguments",/* Emacs */
+ "Invoking `%s'",
+ "%s options",
+ "Options",
+ "Option ", /* e.g. "Option Summary" */
+ "Invoking",
+ "All options", /* tar, paxutils */
+ "Arguments",
+ "%s cmdline", /* ar */
+ "%s", /* last resort */
+ (const char *)0
+ };
+ NODE *node = NULL;
+ REFERENCE **menu;
+ const char **try_node;
+
+ /* We keep looking deeper and deeper in the menu structure until
+ there are no more menus or no menu items from the above list.
+ Some manuals have the invocation node sitting 3 or 4 levels deep
+ in the menu hierarchy... */
+ for (node = initial_node; node; initial_node = node)
+ {
+ REFERENCE *entry = NULL;
+
+ /* Build and return a list of the menu items in this node. */
+ menu = info_menu_of_node (initial_node);
+
+ /* If no menu item in this node, stop here. Perhaps this node
+ is the one they need. */
+ if (!menu)
+ break;
+
+ /* Look for node names typical for usage nodes in this menu. */
+ for (try_node = invocation_nodes; *try_node; try_node++)
+ {
+ char *nodename;
+
+ nodename = xmalloc (strlen (program) + strlen (*try_node));
+ sprintf (nodename, *try_node, program);
+ /* The last resort "%s" is dangerous, so we restrict it
+ to exact matches here. */
+ entry = entry_in_menu (nodename, menu,
+ strcmp (*try_node, "%s") == 0);
+ free (nodename);
+ if (entry)
+ break;
+ }
+
+ if (!entry)
+ break;
+
+ if (!entry->filename)
+ entry->filename = xstrdup (initial_node->parent ? initial_node->parent
+ : initial_node->filename);
+ /* Try to find this node. */
+ node = info_get_node (entry->filename, entry->nodename);
+ info_free_references (menu);
+ if (!node)
+ break;
+ }
+
+ /* We've got our best shot at the invocation node. Now select it. */
+ if (initial_node)
+ info_set_node_of_window (1, window, initial_node);
+ if (!info_error_was_printed)
+ window_clear_echo_area ();
+}
+
+/* Given a name of an Info file, find the name of the package it
+ describes by removing the leading directories and extensions. */
+char *
+program_name_from_file_name (char *file_name)
+{
+ int i;
+ char *program_name = xstrdup (filename_non_directory (file_name));
+
+ for (i = strlen (program_name) - 1; i > 0; i--)
+ if (program_name[i] == '.'
+ && (FILENAME_CMPN (program_name + i, ".info", 5) == 0
+ || FILENAME_CMPN (program_name + i, ".inf", 4) == 0
+#ifdef __MSDOS__
+ || FILENAME_CMPN (program_name + i, ".i", 2) == 0
+#endif
+ || isdigit (program_name[i + 1]))) /* a man page foo.1 */
+ {
+ program_name[i] = 0;
+ break;
+ }
+ return program_name;
+}
+
+DECLARE_INFO_COMMAND (info_goto_invocation_node,
+ _("Find the node describing program invocation"))
+{
+ const char *invocation_prompt = _("Find Invocation node of [%s]: ");
+ char *program_name, *line;
+ char *default_program_name, *prompt, *file_name;
+ NODE *top_node;
+
+ /* Intuit the name of the program they are likely to want.
+ We use the file name of the current Info file as a hint. */
+ file_name = window->node->parent ? window->node->parent
+ : window->node->filename;
+ default_program_name = program_name_from_file_name (file_name);
+
+ prompt = xmalloc (strlen (default_program_name) +
+ strlen (invocation_prompt));
+ sprintf (prompt, invocation_prompt, default_program_name);
+ line = info_read_in_echo_area (window, prompt);
+ free (prompt);
+ if (!line)
+ {
+ info_abort_key (window, 0, 0);
+ return;
+ }
+ if (*line)
+ program_name = line;
+ else
+ program_name = default_program_name;
+
+ /* In interactive usage they'd probably expect us to begin looking
+ from the Top node. */
+ top_node = info_get_node (file_name, NULL);
+ if (!top_node)
+ info_error (msg_cant_find_node, "Top", NULL);
+
+ info_intuit_options_node (window, top_node, program_name);
+ free (line);
+ free (default_program_name);
+}
+
+#if defined (HANDLE_MAN_PAGES)
+DECLARE_INFO_COMMAND (info_man, _("Read a manpage reference and select it"))
+{
+ char *line;
+
+ line = info_read_in_echo_area (window, _("Get Manpage: "));
+
+ if (!line)
+ {
+ info_abort_key (window, 0, 0);
+ return;
+ }
+
+ canonicalize_whitespace (line);
+
+ if (*line)
+ {
+ char *goto_command;
+
+ goto_command = xmalloc
+ (4 + strlen (MANPAGE_FILE_BUFFER_NAME) + strlen (line));
+
+ sprintf (goto_command, "(%s)%s", MANPAGE_FILE_BUFFER_NAME, line);
+
+ info_parse_and_select (goto_command, window);
+ free (goto_command);
+ }
+
+ free (line);
+ if (!info_error_was_printed)
+ window_clear_echo_area ();
+}
+#endif /* HANDLE_MAN_PAGES */
+
+/* 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);
+}
+
+
+/* Read the name of a node to kill. The list of available nodes comes
+ from the nodes appearing in the current window configuration. */
+static char *
+read_nodename_to_kill (WINDOW *window)
+{
+ int iw;
+ char *nodename;
+ INFO_WINDOW *info_win;
+ REFERENCE **menu = NULL;
+ int menu_index = 0, menu_slots = 0;
+ char *default_nodename = xstrdup (active_window->node->nodename);
+ char *prompt = xmalloc (strlen (_("Kill node (%s): ")) + strlen (default_nodename));
+
+ sprintf (prompt, _("Kill node (%s): "), default_nodename);
+
+ for (iw = 0; (info_win = info_windows[iw]); iw++)
+ {
+ REFERENCE *entry = xmalloc (sizeof (REFERENCE));
+ entry->label = xstrdup (info_win->window->node->nodename);
+ entry->filename = entry->nodename = NULL;
+
+ add_pointer_to_array (entry, menu_index, menu, menu_slots, 10,
+ REFERENCE *);
+ }
+
+ 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);
+
+ return nodename;
+}
+
+
+/* Delete NODENAME from this window, showing the most
+ recently selected node in this window. */
+static void
+kill_node (WINDOW *window, char *nodename)
+{
+ int iw, i;
+ INFO_WINDOW *info_win;
+ NODE *temp;
+
+ /* 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
+ && info_win->window == window)
+ break;
+
+ if (!info_win)
+ {
+ if (*nodename)
+ info_error (_("Cannot kill node `%s'"), nodename, NULL);
+ 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"), NULL, NULL);
+ 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 + 1];
+
+ /* 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 = xmalloc (sizeof (NODE));
+
+ temp = stealer->nodes[which];
+ point = stealer->points[which];
+ pagetop = stealer->pagetops[which];
+
+ copy->filename = temp->filename;
+ copy->parent = temp->parent;
+ copy->nodename = temp->nodename;
+ copy->contents = temp->contents;
+ copy->nodelen = temp->nodelen;
+ copy->flags = temp->flags;
+ copy->display_pos = temp->display_pos;
+
+ 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];
+ temp->display_pos = info_win->points[info_win->current];
+ window_set_node_of_window (info_win->window, temp);
+ }
+
+ if (!info_error_was_printed)
+ window_clear_echo_area ();
+
+ if (auto_footnotes_p)
+ info_get_or_remove_footnotes (window);
+}
+
+/* Kill current node, thus going back one in the node history. I (karl)
+ do not think this is completely correct yet, because of the
+ window-changing stuff in kill_node, but it's a lot better than the
+ previous implementation, which did not account for nodes being
+ visited twice at all. */
+DECLARE_INFO_COMMAND (info_history_node,
+ _("Select the most recently selected node"))
+{
+ kill_node (window, active_window->node->nodename);
+}
+
+/* Kill named node. */
+DECLARE_INFO_COMMAND (info_kill_node, _("Kill this node"))
+{
+ char *nodename = read_nodename_to_kill (window);
+ kill_node (window, nodename);
+}
+
+
+/* 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, NULL, NULL);
+ else
+ info_error (_("Cannot find `%s'."), line, NULL);
+ }
+ else
+ info_set_node_of_window (1, 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 (NODE *node, FILE *stream);
+static void dump_node_to_stream (char *filename, char *nodename,
+ FILE *stream, int dump_subnodes);
+static void initialize_dumping (void);
+
+/* 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 (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, NULL);
+ 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."), NULL, NULL);
+#endif /* VERBOSE_NODE_DUMPING */
+}
+
+/* A place to remember already dumped nodes. */
+static char **dumped_already = NULL;
+static int dumped_already_index = 0;
+static int dumped_already_slots = 0;
+
+static void
+initialize_dumping (void)
+{
+ 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 (char *filename, char *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, NULL, NULL);
+ else
+ {
+ if (filename && *nodename != '(')
+ info_error (msg_cant_file_node,
+ filename_non_directory (filename),
+ nodename);
+ else
+ info_error (msg_cant_find_node, nodename, NULL);
+ }
+ 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. */
+ info_error (_("Writing node %s..."), node_printed_rep (node), NULL);
+#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 = 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 *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,
+ NULL);
+ 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."), NULL, NULL);
+#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)
+{
+ FILE *printer_pipe;
+ char *print_command = getenv ("INFO_PRINT_COMMAND");
+ int piping = 0;
+
+ if (!print_command || !*print_command)
+ print_command = DEFAULT_INFO_PRINT_COMMAND;
+
+ /* Note that on MS-DOS/MS-Windows, this MUST open the pipe in the
+ (default) text mode, since the printer drivers there need to see
+ DOS-style CRLF pairs at the end of each line.
+
+ FIXME: if we are to support Mac-style text files, we might need
+ to convert the text here. */
+
+ /* INFO_PRINT_COMMAND which says ">file" means write to that file.
+ Presumably, the name of the file is the local printer device. */
+ if (*print_command == '>')
+ printer_pipe = fopen (++print_command, "w");
+ else
+ {
+ printer_pipe = popen (print_command, "w");
+ piping = 1;
+ }
+
+ if (!printer_pipe)
+ {
+ info_error (_("Cannot open pipe to `%s'."), print_command, NULL);
+ return;
+ }
+
+#if defined (VERBOSE_NODE_DUMPING)
+ /* Maybe we should print some information about the node being output. */
+ info_error (_("Printing node %s..."), node_printed_rep (node), NULL);
+#endif /* VERBOSE_NODE_DUMPING */
+
+ write_node_to_stream (node, printer_pipe);
+ if (piping)
+ pclose (printer_pipe);
+ else
+ fclose (printer_pipe);
+
+#if defined (VERBOSE_NODE_DUMPING)
+ info_error (_("Done."), NULL, NULL);
+#endif /* VERBOSE_NODE_DUMPING */
+}
+
+static void
+write_node_to_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 (void);
+static void info_search_1 (WINDOW *window, int count,
+ unsigned char key, int case_sensitive, int ask_for_string);
+
+static char *search_string = NULL;
+static int search_string_size = 0;
+static int isearch_is_active = 0;
+
+static int last_search_direction = 0;
+static int last_search_case_sensitive = 0;
+
+/* Return the file buffer which belongs to WINDOW's node. */
+FILE_BUFFER *
+file_buffer_of_window (WINDOW *window)
+{
+ /* If this window has no node, then it has no file buffer. */
+ if (!window->node)
+ return NULL;
+
+ if (window->node->parent)
+ return info_find_file (window->node->parent);
+
+ if (window->node->filename)
+ return info_find_file (window->node->filename);
+
+ return 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. The DIR argument
+ says which direction to search in. If it is positive, search
+ forward, else backwards.
+
+ The last argument, RESBND, makes sense only when USE_REGEX is set.
+ If the regexp search succeeds, RESBND is filled with the final state
+ of the search binding. In particular, its START and END fields contain
+ bounds of the found string instance.
+*/
+static long
+info_search_in_node_internal (char *string, NODE *node, long int start,
+ WINDOW *window, int dir, int case_sensitive,
+ SEARCH_BINDING *resbnd)
+{
+ SEARCH_BINDING binding;
+ long offset;
+
+ binding.buffer = node->contents;
+ binding.start = start;
+ binding.end = node->nodelen;
+ binding.flags = 0;
+ if (!case_sensitive)
+ 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 = (use_regex ?
+ regexp_search (string, &binding, node->nodelen, resbnd):
+ 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;
+}
+
+long
+info_search_in_node (char *string, NODE *node, long int start,
+ WINDOW *window, int dir, int case_sensitive)
+{
+ return info_search_in_node_internal (string, node, start,
+ window, dir, case_sensitive, NULL);
+}
+
+/* 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 *node, char *string, long int start)
+{
+ register int i;
+ long offset = 0;
+ char *target;
+
+ target = xstrdup (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, NULL, 1, 0);
+
+ 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. The starting position is determined
+ by DIR and RESBND argument. If the latter is given, and its START field
+ is not -1, it gives starting position. Otherwise, the search begins at
+ window point + DIR.
+
+ 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 succeeds and RESBND is given, its START and END fields
+ contain bounds of the found string instance (only for regexp searches).
+
+ If the search fails, return non-zero, else zero. Side-effect window
+ leaving the node and point where the string was found current. */
+static int
+info_search_internal (char *string, WINDOW *window,
+ int dir, int case_sensitive,
+ SEARCH_BINDING *resbnd)
+{
+ register int i;
+ FILE_BUFFER *file_buffer;
+ char *initial_nodename;
+ long ret, start;
+
+ file_buffer = file_buffer_of_window (window);
+ initial_nodename = window->node->nodename;
+
+ if (resbnd && resbnd->start != -1)
+ start = resbnd->start;
+ else
+ /* This used to begin from window->point, unless this was a repeated
+ search command. But invoking search with an argument loses with
+ that logic, since info_last_executed_command is then set to
+ info_add_digit_to_numeric_arg. I think there's no sense in
+ ``finding'' a string that is already under the cursor, anyway. */
+ start = window->point + dir;
+
+ ret = info_search_in_node_internal
+ (string, window->node, start, window, dir,
+ case_sensitive, resbnd);
+
+ if (ret != -1)
+ {
+ /* We won! */
+ if (!echo_area_is_active && !isearch_is_active)
+ window_clear_echo_area ();
+ return 0;
+ }
+
+ start = 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 = 0, number_of_tags;
+ char *last_subfile;
+ TAG *tag;
+ char *msg = NULL;
+
+ /* Find number of tags and current tag. */
+ last_subfile = 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);
+
+ /* Find the next tag that isn't an anchor. */
+ for (i = current_tag + dir; i != current_tag; i += dir)
+ {
+ if (i < 0)
+ {
+ msg = N_("Search continued from the end of the document.");
+ i = number_of_tags - 1;
+ }
+ else if (i == number_of_tags)
+ {
+ msg = N_("Search continued from the beginning of the document.");
+ i = 0;
+ }
+
+ tag = file_buffer->tags[i];
+ if (tag->nodelen != 0)
+ break;
+ }
+
+ /* If we got past out starting point, bail out. */
+ if (i == current_tag)
+ return -1;
+ current_tag = i;
+
+ if (!echo_area_is_active && (last_subfile != tag->filename))
+ {
+ window_message_in_echo_area
+ (_("Searching subfile %s ..."),
+ filename_non_directory (tag->filename), NULL);
+
+ 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, NULL, NULL);
+ else
+ info_error (msg_cant_file_node,
+ filename_non_directory (file_buffer->filename),
+ tag->nodename);
+ }
+ return -1;
+ }
+
+ if (dir < 0)
+ start = tag->nodelen;
+
+ ret =
+ info_search_in_node_internal (string, node, start, window, dir,
+ case_sensitive, resbnd);
+
+ /* 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)
+ {
+ if (msg)
+ window_message_in_echo_area ("%s", (char *) _(msg), NULL);
+ else
+ 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_case_sensitively,
+ _("Read a string and search for it case-sensitively"))
+{
+ last_search_direction = count > 0 ? 1 : -1;
+ last_search_case_sensitive = 1;
+ info_search_1 (window, count, key, 1, 1);
+}
+
+DECLARE_INFO_COMMAND (info_search, _("Read a string and search for it"))
+{
+ last_search_direction = count > 0 ? 1 : -1;
+ last_search_case_sensitive = 0;
+ info_search_1 (window, count, key, 0, 1);
+}
+
+DECLARE_INFO_COMMAND (info_search_backward,
+ _("Read a string and search backward for it"))
+{
+ last_search_direction = count > 0 ? -1 : 1;
+ last_search_case_sensitive = 0;
+ info_search_1 (window, -count, key, 0, 1);
+}
+
+static void
+info_search_1 (WINDOW *window, int count, unsigned char key,
+ int case_sensitive, int ask_for_string)
+{
+ char *line, *prompt;
+ int result, old_pagetop;
+ int direction;
+
+ if (count < 0)
+ {
+ direction = -1;
+ count = -count;
+ }
+ else
+ {
+ direction = 1;
+ if (count == 0)
+ count = 1; /* for backward compatibility */
+ }
+
+ /* Read a string from the user, defaulting the search to SEARCH_STRING. */
+ if (!search_string)
+ {
+ search_string = xmalloc (search_string_size = 100);
+ search_string[0] = '\0';
+ }
+
+ if (ask_for_string)
+ {
+ prompt = xmalloc (strlen (_("%s%s%s [%s]: "))
+ + strlen (_("Regexp search"))
+ + strlen (_(" case-sensitively"))
+ + strlen (_(" backward"))
+ + strlen (search_string));
+
+ sprintf (prompt, _("%s%s%s [%s]: "),
+ use_regex ? _("Regexp search") : _("Search"),
+ case_sensitive ? _(" case-sensitively") : "",
+ direction < 0 ? _(" backward") : "",
+ search_string);
+
+ line = info_read_in_echo_area (window, prompt);
+ free (prompt);
+
+ if (!line)
+ {
+ info_abort_key (window, 0, 0);
+ return;
+ }
+
+ if (*line)
+ {
+ if (strlen (line) + 1 > (unsigned int) search_string_size)
+ search_string = xrealloc
+ (search_string, (search_string_size += 50 + strlen (line)));
+
+ strcpy (search_string, line);
+ free (line);
+ }
+ }
+
+ /* If the search string includes upper-case letters, make the search
+ case-sensitive. */
+ if (case_sensitive == 0)
+ for (line = search_string; *line; line++)
+ if (isupper (*line))
+ {
+ case_sensitive = 1;
+ break;
+ }
+
+ old_pagetop = active_window->pagetop;
+ for (result = 0; result == 0 && count--; )
+ result = info_search_internal (search_string,
+ active_window, direction, case_sensitive,
+ NULL);
+
+ if (result != 0 && !info_error_was_printed)
+ info_error (_("Search failed."), NULL, NULL);
+ 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 ();
+}
+
+DECLARE_INFO_COMMAND (info_search_next,
+ _("Repeat last search in the same direction"))
+{
+ if (!last_search_direction)
+ info_error (_("No previous search string"), NULL, NULL);
+ else
+ info_search_1 (window, last_search_direction * count,
+ key, last_search_case_sensitive, 0);
+}
+
+DECLARE_INFO_COMMAND (info_search_previous,
+ _("Repeat last search in the reverse direction"))
+{
+ if (!last_search_direction)
+ info_error (_("No previous search string"), NULL, NULL);
+ else
+ info_search_1 (window, -last_search_direction * count,
+ key, last_search_case_sensitive, 0);
+}
+
+/* **************************************************************** */
+/* */
+/* Incremental Searching */
+/* */
+/* **************************************************************** */
+
+static void incremental_search (WINDOW *window, int count,
+ unsigned char ignore);
+
+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 = NULL;
+
+/* The current incremental search string. */
+static char *isearch_string = NULL;
+static int isearch_string_index = 0;
+static int isearch_string_size = 0;
+static unsigned char isearch_terminate_search_key = ESC;
+
+/* Array of search states. */
+static SEARCH_STATE **isearch_states = NULL;
+static int isearch_states_index = 0;
+static int isearch_states_slots = 0;
+
+/* Push the state of this search. */
+static void
+push_isearch (WINDOW *window, int search_index, int direction, int failing)
+{
+ SEARCH_STATE *state;
+
+ 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 *window, int *search_index, int *direction, int *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] = NULL;
+ }
+}
+
+/* Free the memory used by isearch_states. */
+static void
+free_isearch_states (void)
+{
+ register int i;
+
+ for (i = 0; i < isearch_states_index; i++)
+ {
+ free (isearch_states[i]);
+ isearch_states[i] = NULL;
+ }
+ isearch_states_index = 0;
+}
+
+/* Display the current search in the echo area. */
+static void
+show_isearch_prompt (int dir, unsigned char *string, int failing_p)
+{
+ register int i;
+ const char *prefix;
+ char *prompt, *p_rep;
+ unsigned int prompt_len, p_rep_index, p_rep_size;
+
+ if (dir < 0)
+ prefix = use_regex ? _("Regexp I-search backward: ")
+ : _("I-search backward: ");
+ else
+ prefix = use_regex ? _("Regexp I-search: ")
+ : _("I-search: ");
+
+ p_rep_index = p_rep_size = 0;
+ p_rep = 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 = 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 + 1;
+ if (failing_p)
+ prompt_len += strlen (_("Failing "));
+ prompt = xmalloc (prompt_len);
+ sprintf (prompt, "%s%s%s", failing_p ? _("Failing ") : "", prefix,
+ p_rep ? p_rep : "");
+
+ window_message_in_echo_area ("%s", prompt, NULL);
+ maybe_free (p_rep);
+ free (prompt);
+ display_cursor_at_point (active_window);
+}
+
+static void
+incremental_search (WINDOW *window, int count, unsigned char ignore)
+{
+ unsigned char key;
+ int last_search_result, search_result, dir;
+ SEARCH_STATE mystate, orig_state;
+ char *p;
+ int case_sensitive = 0;
+ SEARCH_BINDING bnd;
+
+ bnd.start = -1;
+
+ 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 = xmalloc (isearch_string_size = 50);
+
+ /* Show the search string in the echo area. */
+ isearch_string[isearch_string_index] = '\0';
+ show_isearch_prompt (dir, (unsigned char *) isearch_string, search_result);
+
+ isearch_is_active = 1;
+
+ while (isearch_is_active)
+ {
+ VFunction *func = 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 || key == Control ('h'))
+ {
+ /* 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, (unsigned char *) 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) || key > 32)
+ {
+ /* If this key is not a keymap, get its associated function,
+ if any. If it is a keymap, then it's probably ESC from an
+ arrow key, and we handle that case below. */
+ char type = window->keymap[key].type;
+ func = type == ISFUNC
+ ? InfoFunction(window->keymap[key].function)
+ : NULL; /* function member is a Keymap if ISKMAP */
+
+ if (isprint (key) || (type == ISFUNC && func == NULL))
+ {
+ insert_and_search:
+
+ if (isearch_string_index + 2 >= isearch_string_size)
+ isearch_string = 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 == (VFunction *) isearch_forward
+ || func == (VFunction *) isearch_backward)
+ {
+ /* 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 == (VFunction *) isearch_forward && dir > 0) ||
+ (func == (VFunction *) 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 ((char *) last_isearch_accepted) + 1
+ >= (unsigned int) 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;
+ bnd.start = -1;
+ }
+ }
+ }
+ else
+ {
+ /* Reverse the direction of the search. */
+ dir = -dir;
+ }
+ }
+ else if (func == (VFunction *) 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, (unsigned char *) 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 != (VFunction *) info_abort_key)
+ {
+ maybe_free (last_isearch_accepted);
+ last_isearch_accepted = xstrdup (isearch_string);
+ }
+
+ /* If the key is the isearch_terminate_search_key, but some buffered
+ input is pending, it is almost invariably because the ESC key is
+ actually the beginning of an escape sequence, like in case they
+ pressed an arrow key. So don't gobble the ESC key, push it back
+ into pending input. */
+ /* FIXME: this seems like a kludge! We need a more reliable
+ mechanism to know when ESC is a separate key and when it is
+ part of an escape sequence. */
+ if (key != RET /* Emacs addicts want RET to get lost */
+ && (key != isearch_terminate_search_key
+ || info_any_buffered_input_p ()))
+ info_set_pending_input (key);
+
+ if (func == (VFunction *) 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, (unsigned char *) isearch_string, search_result);
+
+ /* If the search string includes upper-case letters, make the
+ search case-sensitive. */
+ for (p = isearch_string; *p; p++)
+ if (isupper (*p))
+ {
+ case_sensitive = 1;
+ break;
+ }
+
+ /* Regex isearch means we better search again every time. We
+ might have had a failed search for "\", for example, but now we
+ have "\.". */
+ if (use_regex)
+ {
+ search_result = info_search_internal (isearch_string,
+ window, dir, case_sensitive,
+ &bnd);
+ }
+ else if (search_result == 0)
+ { /* We test for search_result being zero because a non-zero
+ value means the string was not found in entire document. */
+ /* 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) &&
+ ((case_sensitive ? strncmp : mbsncasecmp)
+ (window->node->contents + window->point,
+ isearch_string, isearch_string_index) == 0)) ||
+ ((dir > 0) &&
+ ((window->point - isearch_string_index) >= 0) &&
+ ((case_sensitive ? strncmp : mbsncasecmp)
+ (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, case_sensitive,
+ NULL);
+ }
+
+ /* 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, (unsigned char *) 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 (void)
+{
+ 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 ((FILENAME_CMP (fb->fullpath, iw->nodes[i]->filename) == 0) ||
+ (FILENAME_CMP (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 = NULL;
+ }
+ }
+}
+
+/* **************************************************************** */
+/* */
+/* Traversing and Selecting References */
+/* */
+/* **************************************************************** */
+
+/* Move to the next or previous cross reference in this node. */
+static int
+info_move_to_xref (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;
+ int save_use_regex = use_regex;
+
+ /* Most of our keywords contain * characters; don't use regexes. */
+ use_regex = 0;
+
+ 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, NULL, dir, 0);
+
+ /* 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, NULL, dir, 0);
+ }
+
+ firstxref =
+ info_search_in_node (INFO_XREF_LABEL, node, start, NULL, dir, 0);
+
+#if defined (HANDLE_MAN_PAGES)
+ if ((firstxref == -1) && (node->flags & N_IsManPage))
+ {
+ firstxref = locate_manpage_xref (node, start, dir);
+ }
+#endif /* HANDLE_MAN_PAGES */
+
+ if (firstmenu == -1 && firstxref == -1)
+ {
+ if (!cursor_movement_scrolls_p)
+ info_error (msg_no_xref_node, NULL, NULL);
+ use_regex = save_use_regex;
+ return cursor_movement_scrolls_p;
+ }
+
+ /* 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, NULL, dir, 0);
+
+ nextxref = info_search_in_node
+ (INFO_XREF_LABEL, node, window->point + dir, NULL, dir, 0);
+
+#if defined (HANDLE_MAN_PAGES)
+ if ((nextxref == -1) && (node->flags & N_IsManPage) && (firstxref != -1))
+ nextxref = locate_manpage_xref (node, window->point + dir, dir);
+#endif /* HANDLE_MAN_PAGES */
+
+ /* 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, NULL, dir, 0);
+ }
+
+ /* No more searches, back to whatever the user wanted. */
+ use_regex = save_use_regex;
+
+ /* 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 (cursor_movement_scrolls_p)
+ return 1;
+ else
+ {
+ 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;
+ return 0;
+}
+
+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
+ {
+ while (info_move_to_xref (window, count, key, -1))
+ {
+ info_error_was_printed = 0;
+ if (backward_move_node_structure (window, info_scroll_behaviour))
+ break;
+ move_to_new_line (window->line_count, window->line_count - 1,
+ window);
+ }
+ }
+}
+
+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
+ {
+ /* Note: This can cause some blinking when the next cross reference is
+ located several nodes further. This effect can be easily suppressed
+ by setting display_inhibited to 1, however this will also make
+ error messages to be dumped on stderr, instead on the echo area. */
+ while (info_move_to_xref (window, count, key, 1))
+ {
+ info_error_was_printed = 0;
+ if (forward_move_node_structure (window, info_scroll_behaviour))
+ break;
+ move_to_new_line (0, 0, window);
+ }
+ }
+}
+
+/* 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;
+
+ if (window->line_starts)
+ line = window->line_starts[window_line_of_point (window)];
+ else
+ line = "";
+
+ /* 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"), NULL, NULL);
+
+ info_initialize_numeric_arg ();
+ info_clear_pending_input ();
+ info_last_executed_command = NULL;
+}
+
+/* Move the cursor to the desired line of the window. */
+DECLARE_INFO_COMMAND (info_move_to_window_line,
+ _("Move 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 ().
+ Doc string is to avoid ugly results with describe_key etc. */
+DECLARE_INFO_COMMAND (info_do_lowercase_version,
+ _("Run command bound to this key's lowercase variant"))
+{}
+
+static void
+dispatch_error (char *keyseq)
+{
+ char *rep;
+
+ rep = pretty_keyseq (keyseq);
+
+ if (!echo_area_is_active)
+ info_error (_("Unknown command (%s)."), rep, NULL);
+ else
+ {
+ char *temp = xmalloc (1 + strlen (rep) + strlen (_("\"%s\" 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 = NULL;
+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 (void)
+{
+ info_keyseq_index = 0;
+ info_keyseq_displayed_p = 0;
+}
+
+/* Add CHARACTER to the current key sequence. */
+void
+add_char_to_keyseq (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';
+}
+
+/* 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. */
+static void
+display_info_keyseq (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, NULL, NULL);
+ 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 (void)
+{
+ int ready = !info_keyseq_displayed_p; /* ready if new and pending key */
+
+ /* 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)
+ {
+ ready = 1;
+ if (!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 (fileno(info_input_stream)+1, &readfds,
+ NULL, NULL, &timer);
+#else
+ ready = 0;
+#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 (unsigned char key, Keymap map)
+{
+#if !defined(INFOKEY)
+ 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;
+ }
+#endif /* INFOKEY */
+
+ switch (map[key].type)
+ {
+ case ISFUNC:
+ {
+ VFunction *func;
+
+ func = InfoFunction(map[key].function);
+ if (func != NULL)
+ {
+ /* Special case info_do_lowercase_version (). */
+ if (func == (VFunction *) info_do_lowercase_version)
+ {
+#if defined(INFOKEY)
+ unsigned char lowerkey;
+
+ lowerkey = Meta_p(key) ? Meta (tolower (UnMeta (key))) : tolower (key);
+ if (lowerkey == key)
+ {
+ add_char_to_keyseq (key);
+ dispatch_error (info_keyseq);
+ return;
+ }
+ info_dispatch_on_key (lowerkey, map);
+#else /* !INFOKEY */
+ info_dispatch_on_key (tolower (key), map);
+#endif /* INFOKEY */
+ return;
+ }
+
+ add_char_to_keyseq (key);
+
+ if (info_keyseq_displayed_p)
+ display_info_keyseq (0);
+
+ {
+ WINDOW *where;
+
+ where = active_window;
+ (*InfoFunction(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 = InfoFunction(map[key].function);
+ else
+ info_last_executed_command = InfoFunction(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 != 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 (void)
+{
+ info_numeric_arg = info_numeric_arg_sign = 1;
+ info_explicit_arg = 0;
+}
+
+DECLARE_INFO_COMMAND (info_numeric_arg_digit_loop,
+ _("Internally used by \\[universal-argument]"))
+{
+ 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 !defined(INFOKEY)
+ if (Meta_p (key))
+ add_char_to_keyseq (ESC);
+
+ add_char_to_keyseq (UnMeta (key));
+#else /* defined(INFOKEY) */
+ add_char_to_keyseq (key);
+#endif /* defined(INFOKEY) */
+ }
+
+#if !defined(INFOKEY)
+ if (Meta_p (key))
+ key = UnMeta (key);
+#endif /* !defined(INFOKEY) */
+
+ if (keymap[key].type == ISFUNC
+ && InfoFunction(keymap[key].function)
+ == (VFunction *) info_universal_argument)
+ {
+ info_numeric_arg *= 4;
+ key = 0;
+ continue;
+ }
+
+#if defined(INFOKEY)
+ if (Meta_p (key))
+ key = UnMeta (key);
+#endif /* !defined(INFOKEY) */
+
+
+ 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 (void)
+{
+ pending_input_character = 0;
+}
+
+/* How to set the pending input character. */
+static void
+info_set_pending_input (unsigned char key)
+{
+ pending_input_character = key;
+}
+
+/* How to see if there is any pending input. */
+unsigned char
+info_input_pending_p (void)
+{
+ 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 (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 ((unsigned int) 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 (void)
+{
+ 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 (unsigned char *key)
+{
+ if (push_index == pop_index)
+ return 0;
+
+ *key = info_input_buffer[pop_index++];
+
+ if ((unsigned int) pop_index >= sizeof (info_input_buffer))
+ pop_index = 0;
+
+ return 1;
+}
+
+int
+info_any_buffered_input_p (void)
+{
+ info_gather_typeahead ();
+ return push_index != pop_index;
+}
+
+/* If characters are available to be read, then read them and stuff them into
+ info_input_buffer. Otherwise, do nothing. */
+void
+info_gather_typeahead (void)
+{
+ register int i = 0;
+ 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)
+ 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;
+ }
+# else /* !O_NDELAY */
+# ifdef __DJGPP__
+ {
+ extern long pc_term_chars_avail (void);
+
+ if (isatty (tty))
+ chars_avail = pc_term_chars_avail ();
+ else
+ {
+ /* We could be more accurate by calling ltell, but we have no idea
+ whether tty is buffered by stdio functions, and if so, how many
+ characters are already waiting in the buffer. So we punt. */
+ struct stat st;
+
+ if (fstat (tty, &st) < 0)
+ chars_avail = 1;
+ else
+ chars_avail = st.st_size;
+ }
+ if (chars_avail > space_avail)
+ chars_avail = space_avail;
+ if (chars_avail)
+ chars_avail = read (tty, &input[0], chars_avail);
+ }
+# endif/* __DJGPP__ */
+# endif /* O_NDELAY */
+#endif /* !FIONREAD */
+
+ while (i < chars_avail)
+ {
+ info_push_typeahead (input[i]);
+ i++;
+ }
+}
+
+/* How to read a single character. */
+unsigned char
+info_get_input_char (void)
+{
+ 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;
+ unsigned char c;
+ int tty = fileno (info_input_stream);
+
+ /* Using stream I/O causes FIONREAD etc to fail to work
+ so unless someone can find a portable way of finding
+ out how many characters are currently buffered, we
+ should stay with away from stream I/O.
+ --Egil Kvaleberg <egilk@sn.no>, January 1997. */
+#ifdef EINTR
+ /* Keep reading if we got EINTR, so that we don't just exit.
+ --Andreas Schwab <schwab@issan.informatik.uni-dortmund.de>,
+ 22 Dec 1997. */
+ {
+ int n;
+ do
+ n = read (tty, &c, 1);
+ while (n == -1 && errno == EINTR);
+ rawkey = n == 1 ? c : EOF;
+ }
+#else
+ rawkey = (read (tty, &c, 1) == 1) ? c : EOF;
+#endif
+
+ keystroke = rawkey;
+
+ if (rawkey == EOF)
+ {
+ if (info_input_stream != stdin)
+ {
+ fclose (info_input_stream);
+ info_input_stream = stdin;
+ tty = fileno (info_input_stream);
+ display_inhibited = 0;
+ display_update_display (windows);
+ display_cursor_at_point (active_window);
+ rawkey = (read (tty, &c, 1) == 1) ? c : EOF;
+ keystroke = rawkey;
+ }
+
+ if (rawkey == EOF)
+ {
+ terminal_unprep_terminal ();
+ close_dribble_file ();
+ xexit (0);
+ }
+ }
+ }
+
+ if (info_dribble_file)
+ dribble (keystroke);
+
+ return keystroke;
+}
diff --git a/info/session.h b/info/session.h
new file mode 100644
index 0000000..2052d07
--- /dev/null
+++ b/info/session.h
@@ -0,0 +1,249 @@
+/* session.h -- Functions found in session.c.
+ $Id: session.h,v 1.10 2008/03/04 09:44:57 gray Exp $
+
+ Copyright (C) 1993, 1998, 1999, 2001, 2002, 2004, 2007
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Originally written by Brian Fox (bfox@ai.mit.edu). */
+
+#ifndef SESSION_H
+#define SESSION_H
+
+#include "info.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 `makedoc', which is also responsible for making
+ the documentation/function-pointer maps. */
+#define DECLARE_INFO_COMMAND(name, doc) \
+void name (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. */
+
+extern int cursor_movement_scrolls_p;
+
+/* Values for scroll_last_node */
+#define SLN_Stop 0 /* Stop at the last node */
+#define SLN_Scroll 1 /* Do usual scrolling */
+#define SLN_Top 2 /* Go to the top node */
+
+extern char *scroll_last_node_choices[];
+/* Controls what to do when a scrolling command is issued at the end of the
+ last node. */
+extern int scroll_last_node;
+
+/* Utility functions found in session.c */
+extern void info_dispatch_on_key (unsigned char key, Keymap map);
+extern unsigned char info_get_input_char (void);
+extern unsigned char info_get_another_input_char (void);
+extern unsigned char info_input_pending_p (void);
+extern void remember_window_and_node (WINDOW *window, NODE *node);
+extern void set_remembered_pagetop_and_point (WINDOW *window);
+extern void set_window_pagetop (WINDOW *window, int desired_top);
+extern void info_set_node_of_window (int remember, WINDOW *window,
+ NODE *node);
+extern void initialize_keyseq (void);
+extern void add_char_to_keyseq (char character);
+extern void info_gather_typeahead (void);
+extern FILE_BUFFER *file_buffer_of_window (WINDOW *window);
+extern long info_search_in_node (char *string, NODE *node,
+ long int start, WINDOW *window, int dir, int case_sensitive);
+extern long info_target_search_node (NODE *node, char *string,
+ long int start);
+extern void info_select_reference (WINDOW *window, REFERENCE *entry);
+extern int info_any_buffered_input_p (void);
+extern void print_node (NODE *node);
+extern void dump_node_to_file (NODE *node, char *filename,
+ int dump_subnodes);
+extern void dump_nodes_to_file (char *filename, char **nodenames,
+ char *output_filename, int dump_subnodes);
+extern char *program_name_from_file_name (char *file_name);
+
+/* Do the physical deletion of WINDOW, and forget this window and
+ associated nodes. */
+extern void info_delete_window_internal (WINDOW *window);
+
+/* Tell Info that input is coming from the file FILENAME. */
+extern void info_set_input_from_file (char *filename);
+
+#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 (char *filename,
+ char **nodenames);
+extern void begin_info_session (NODE *initial_node);
+extern void begin_info_session_with_error (NODE *initial_node,
+ const char *format, void *arg1, void *arg2);
+extern void info_session (void);
+extern void initialize_info_session (NODE *node, int clear_screen);
+extern void info_read_and_dispatch (void);
+extern void info_intuit_options_node (WINDOW *window,
+ NODE *initial_node, char *program);
+
+/* Moving the point within a node. */
+extern void info_next_line (WINDOW *window, int count, unsigned char key);
+extern void info_prev_line (WINDOW *window, int count, unsigned char key);
+extern void info_end_of_line (WINDOW *window, int count, unsigned char key);
+extern void info_beginning_of_line (WINDOW *window, int count, unsigned char key);
+extern void info_forward_char (WINDOW *window, int count, unsigned char key);
+extern void info_backward_char (WINDOW *window, int count, unsigned char key);
+extern void info_forward_word (WINDOW *window, int count, unsigned char key);
+extern void info_backward_word (WINDOW *window, int count, unsigned char key);
+extern void info_beginning_of_node (WINDOW *window, int count, unsigned char key);
+extern void info_end_of_node (WINDOW *window, int count, unsigned char key);
+extern void info_move_to_prev_xref (WINDOW *window, int count, unsigned char key);
+extern void info_move_to_next_xref (WINDOW *window, int count, unsigned char key);
+
+/* Scrolling text within a window. */
+extern void info_scroll_forward (WINDOW *window, int count, unsigned char key);
+extern void info_scroll_backward (WINDOW *window, int count, unsigned char key);
+extern void info_redraw_display (WINDOW *window, int count, unsigned char key);
+extern void info_toggle_wrap (WINDOW *window, int count, unsigned char key);
+extern void info_toggle_regexp (WINDOW *window, int count, unsigned char key);
+extern void info_move_to_window_line (WINDOW *window, int count,
+ unsigned char key);
+extern void info_up_line (WINDOW *window, int count, unsigned char key);
+extern void info_down_line (WINDOW *window, int count, unsigned char key);
+extern void info_scroll_half_screen_down (WINDOW *window, int count,
+ unsigned char key);
+extern void info_scroll_half_screen_up (WINDOW *window, int count,
+ unsigned char key);
+extern void info_scroll_forward_set_window (WINDOW *window, int count,
+ unsigned char key);
+extern void info_scroll_forward_page_only (WINDOW *window, int count,
+ unsigned char key);
+extern void info_scroll_forward_page_only_set_window (WINDOW *window, int count,
+ unsigned char key);
+extern void info_scroll_backward_set_window (WINDOW *window, int count,
+ unsigned char key);
+extern void info_scroll_backward_page_only (WINDOW *window, int count,
+ unsigned char key);
+extern void info_scroll_backward_page_only_set_window (WINDOW *window, int count,
+ unsigned char key);
+extern void info_scroll_other_window_backward (WINDOW *window, int count,
+ unsigned char key);
+
+/* Manipulating multiple windows. */
+extern void info_split_window (WINDOW *window, int count, unsigned char key);
+extern void info_delete_window (WINDOW *window, int count, unsigned char key);
+extern void info_keep_one_window (WINDOW *window, int count, unsigned char key);
+extern void info_grow_window (WINDOW *window, int count, unsigned char key);
+extern void info_scroll_other_window (WINDOW *window, int count,
+ unsigned char key);
+extern void info_tile_windows (WINDOW *window, int count, unsigned char key);
+extern void info_next_window (WINDOW *window, int count, unsigned char key);
+extern void info_prev_window (WINDOW *window, int count, unsigned char key);
+
+/* Selecting nodes. */
+extern void info_next_node (WINDOW *window, int count, unsigned char key);
+extern void info_prev_node (WINDOW *window, int count, unsigned char key);
+extern void info_up_node (WINDOW *window, int count, unsigned char key);
+extern void info_last_node (WINDOW *window, int count, unsigned char key);
+extern void info_first_node (WINDOW *window, int count, unsigned char key);
+extern void info_history_node (WINDOW *window, int count, unsigned char key);
+extern void info_goto_node (WINDOW *window, int count, unsigned char key);
+extern void info_goto_invocation_node (WINDOW *window, int count,
+ unsigned char key);
+extern void info_top_node (WINDOW *window, int count, unsigned char key);
+extern void info_dir_node (WINDOW *window, int count, unsigned char key);
+extern void info_global_next_node (WINDOW *window, int count, unsigned char key);
+extern void info_global_prev_node (WINDOW *window, int count, unsigned char key);
+extern void info_kill_node (WINDOW *window, int count, unsigned char key);
+extern void info_view_file (WINDOW *window, int count, unsigned char key);
+extern void info_menu_sequence (WINDOW *window, int count, unsigned char key);
+extern NODE *info_follow_menus (NODE *initial_node, char **menus,
+ const char **errstr, char **errarg1, char **errarg2);
+extern void info_man (WINDOW *window, int count, unsigned char key);
+extern void list_visited_nodes (WINDOW *window, int count, unsigned char key);
+extern void select_visited_node (WINDOW *window, int count, unsigned char key);
+
+/* Selecting cross references. */
+extern void info_menu_digit (WINDOW *window, int count, unsigned char key);
+extern void info_menu_item (WINDOW *window, int count, unsigned char key);
+extern void info_xref_item (WINDOW *window, int count, unsigned char key);
+extern void info_find_menu (WINDOW *window, int count, unsigned char key);
+extern void info_select_reference_this_line (WINDOW *window, int count,
+ unsigned char key);
+extern void info_last_menu_item (WINDOW *window, int count, unsigned char key);
+extern void info_visit_menu (WINDOW *window, int count, unsigned char key);
+
+/* Hacking numeric arguments. */
+extern int info_explicit_arg, info_numeric_arg, info_numeric_arg_sign;
+
+extern void info_add_digit_to_numeric_arg (WINDOW *window, int count,
+ unsigned char key);
+extern void info_universal_argument (WINDOW *window, int count,
+ unsigned char key);
+extern void info_initialize_numeric_arg (void);
+extern void info_numeric_arg_digit_loop (WINDOW *window, int count,
+ unsigned char key);
+
+/* Searching commands. */
+extern void info_search (WINDOW *window, int count, unsigned char key);
+extern void isearch_forward (WINDOW *window, int count, unsigned char key);
+extern void isearch_backward (WINDOW *window, int count, unsigned char key);
+extern void info_search_case_sensitively (WINDOW *window, int count,
+ unsigned char key);
+extern void info_search_backward (WINDOW *window, int count, unsigned char key);
+extern void info_search_next (WINDOW *window, int count, unsigned char key);
+extern void info_search_previous (WINDOW *window, int count, unsigned char key);
+
+/* Dumping and printing nodes. */
+extern void info_print_node (WINDOW *window, int count, unsigned char key);
+
+/* Footnotes. */
+extern void info_show_footnotes (WINDOW *window, int count, unsigned char key);
+
+/* Miscellaneous commands. */
+extern void info_abort_key (WINDOW *window, int count, unsigned char key);
+extern void info_quit (WINDOW *window, int count, unsigned char key);
+extern void info_do_lowercase_version (WINDOW *window, int count,
+ unsigned char key);
+
+#endif /* not SESSION_H */
diff --git a/info/signals.c b/info/signals.c
new file mode 100644
index 0000000..0657f97
--- /dev/null
+++ b/info/signals.c
@@ -0,0 +1,294 @@
+/* signals.c -- install and maintain signal handlers.
+ $Id: signals.c,v 1.10 2007/07/01 21:20:31 karl Exp $
+
+ Copyright (C) 1993, 1994, 1995, 1998, 2002, 2003, 2004, 2007
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Originally written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include "signals.h"
+
+void initialize_info_signal_handler (void);
+
+/* **************************************************************** */
+/* */
+/* Pretending That We Have POSIX Signals */
+/* */
+/* **************************************************************** */
+
+#if !defined (HAVE_SIGPROCMASK) && defined (HAVE_SIGSETMASK)
+/* Perform OPERATION on NEWSET, perhaps leaving information in OLDSET. */
+static void
+sigprocmask (int operation, int *newset, int *oldset)
+{
+ switch (operation)
+ {
+ case SIG_UNBLOCK:
+ sigsetmask (sigblock (0) & ~(*newset));
+ break;
+
+ case SIG_BLOCK:
+ *oldset = sigblock (*newset);
+ break;
+
+ case SIG_SETMASK:
+ sigsetmask (*newset);
+ break;
+
+ default:
+ abort ();
+ }
+}
+#endif /* !HAVE_SIGPROCMASK && HAVE_SIGSETMASK */
+
+/* **************************************************************** */
+/* */
+/* Signal Handling for Info */
+/* */
+/* **************************************************************** */
+
+#if defined (HAVE_SIGACTION) || defined (HAVE_SIGPROCMASK) ||\
+ defined (HAVE_SIGSETMASK)
+static void
+mask_termsig (sigset_t *set)
+{
+# if defined (SIGTSTP)
+ sigaddset (set, SIGTSTP);
+ sigaddset (set, SIGTTOU);
+ sigaddset (set, SIGTTIN);
+# endif
+# if defined (SIGWINCH)
+ sigaddset (set, SIGWINCH);
+# endif
+#if defined (SIGQUIT)
+ sigaddset (set, SIGQUIT);
+#endif
+#if defined (SIGINT)
+ sigaddset (set, SIGINT);
+#endif
+# if defined (SIGUSR1)
+ sigaddset (set, SIGUSR1);
+# endif
+}
+#endif /* HAVE_SIGACTION || HAVE_SIGPROCMASK || HAVE_SIGSETMASK */
+
+static RETSIGTYPE info_signal_proc (int sig);
+#if defined (HAVE_SIGACTION)
+typedef struct sigaction signal_info;
+signal_info info_signal_handler;
+
+static void
+set_termsig (int sig, signal_info *old)
+{
+ sigaction (sig, &info_signal_handler, old);
+}
+
+static void
+restore_termsig (int sig, const signal_info *saved)
+{
+ sigaction (sig, saved, NULL);
+}
+#else /* !HAVE_SIGACTION */
+typedef RETSIGTYPE (*signal_info) ();
+#define set_termsig(sig, old) (void)(*(old) = signal (sig, info_signal_proc))
+#define restore_termsig(sig, saved) (void)signal (sig, *(saved))
+#define info_signal_handler info_signal_proc
+static int term_conf_busy = 0;
+#endif /* !HAVE_SIGACTION */
+
+static signal_info old_TSTP, old_TTOU, old_TTIN;
+static signal_info old_WINCH, old_INT, old_USR1;
+static signal_info old_QUIT;
+
+void
+initialize_info_signal_handler (void)
+{
+#ifdef SA_NOCLDSTOP
+ /* (Based on info from Paul Eggert found in coreutils.) Don't use
+ HAVE_SIGACTION to decide whether to use the sa_handler, sa_flags,
+ sa_mask members, as some systems (Solaris 7+) don't define them. Use
+ SA_NOCLDSTOP instead; it's been part of POSIX.1 since day 1 (in 1988). */
+ info_signal_handler.sa_handler = info_signal_proc;
+ info_signal_handler.sa_flags = 0;
+ mask_termsig (&info_signal_handler.sa_mask);
+#endif /* SA_NOCLDSTOP */
+
+#if defined (SIGTSTP)
+ set_termsig (SIGTSTP, &old_TSTP);
+ set_termsig (SIGTTOU, &old_TTOU);
+ set_termsig (SIGTTIN, &old_TTIN);
+#endif /* SIGTSTP */
+
+#if defined (SIGWINCH)
+ set_termsig (SIGWINCH, &old_WINCH);
+#endif
+
+#if defined (SIGQUIT)
+ set_termsig (SIGQUIT, &old_QUIT);
+#endif
+
+#if defined (SIGINT)
+ set_termsig (SIGINT, &old_INT);
+#endif
+
+#if defined (SIGUSR1)
+ /* Used by DJGPP to simulate SIGTSTP on Ctrl-Z. */
+ set_termsig (SIGUSR1, &old_USR1);
+#endif
+}
+
+static void
+redisplay_after_signal (void)
+{
+ 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 void
+reset_info_window_sizes (void)
+{
+ terminal_goto_xy (0, 0);
+ fflush (stdout);
+ terminal_unprep_terminal ();
+ terminal_get_screen_size ();
+ terminal_prep_terminal ();
+ display_initialize_display (screenwidth, screenheight);
+ window_new_screen_size (screenwidth, screenheight);
+ redisplay_after_signal ();
+}
+
+static RETSIGTYPE
+info_signal_proc (int sig)
+{
+ signal_info *old_signal_handler = NULL;
+
+#if !defined (HAVE_SIGACTION)
+ /* best effort: first increment this counter and later block signals */
+ if (term_conf_busy)
+ return;
+ term_conf_busy++;
+#if defined (HAVE_SIGPROCMASK) || defined (HAVE_SIGSETMASK)
+ {
+ sigset_t nvar, ovar;
+ sigemptyset (&nvar);
+ mask_termsig (&nvar);
+ sigprocmask (SIG_BLOCK, &nvar, &ovar);
+ }
+#endif /* HAVE_SIGPROCMASK || HAVE_SIGSETMASK */
+#endif /* !HAVE_SIGACTION */
+ switch (sig)
+ {
+#if defined (SIGTSTP)
+ case SIGTSTP:
+ case SIGTTOU:
+ case SIGTTIN:
+#endif
+#if defined (SIGQUIT)
+ case SIGQUIT:
+#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 defined (SIGQUIT)
+ if (sig == SIGQUIT)
+ old_signal_handler = &old_QUIT;
+#endif /* SIGQUIT */
+#if defined (SIGINT)
+ if (sig == SIGINT)
+ old_signal_handler = &old_INT;
+#endif /* SIGINT */
+
+ /* 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 ();
+ restore_termsig (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 ();
+ set_termsig (sig, old_signal_handler);
+ /* window size might be changed while sleeping */
+ reset_info_window_sizes ();
+ }
+ break;
+
+#if defined (SIGWINCH) || defined (SIGUSR1)
+#ifdef SIGWINCH
+ case SIGWINCH:
+#endif
+#ifdef SIGUSR1
+ case SIGUSR1:
+#endif
+ {
+ /* Turn off terminal IO, tell our parent that the window has changed,
+ then reinitialize the terminal and rebuild our windows. */
+#ifdef SIGWINCH
+ if (sig == SIGWINCH)
+ old_signal_handler = &old_WINCH;
+#endif
+#ifdef SIGUSR1
+ if (sig == SIGUSR1)
+ old_signal_handler = &old_USR1;
+#endif
+ terminal_goto_xy (0, 0);
+ fflush (stdout);
+ terminal_unprep_terminal (); /* needless? */
+ restore_termsig (sig, old_signal_handler);
+ UNBLOCK_SIGNAL (sig);
+ kill (getpid (), sig);
+
+ /* After our old signal handler returns... */
+ set_termsig (sig, old_signal_handler); /* needless? */
+ terminal_prep_terminal ();
+ reset_info_window_sizes ();
+ }
+ break;
+#endif /* SIGWINCH || SIGUSR1 */
+ }
+#if !defined (HAVE_SIGACTION)
+ /* at this time it is safer to perform unblock after decrement */
+ term_conf_busy--;
+#if defined (HAVE_SIGPROCMASK) || defined (HAVE_SIGSETMASK)
+ {
+ sigset_t nvar, ovar;
+ sigemptyset (&nvar);
+ mask_termsig (&nvar);
+ sigprocmask (SIG_UNBLOCK, &nvar, &ovar);
+ }
+#endif /* HAVE_SIGPROCMASK || HAVE_SIGSETMASK */
+#endif /* !HAVE_SIGACTION */
+}
+/* vim: set sw=2 cino={1s>2sn-s^-se-s: */
diff --git a/info/signals.h b/info/signals.h
new file mode 100644
index 0000000..08992ad
--- /dev/null
+++ b/info/signals.h
@@ -0,0 +1,96 @@
+/* signals.h -- header to include system dependent signal definitions.
+ $Id: signals.h,v 1.5 2007/07/01 21:20:31 karl Exp $
+
+ Copyright (C) 1993, 1994, 1995, 1997, 2002, 2004, 2007
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Originally written by Brian Fox (bfox@ai.mit.edu). */
+
+#ifndef INFO_SIGNALS_H
+#define INFO_SIGNALS_H
+
+#include <sys/types.h>
+#include <signal.h>
+
+/* For sysV68 --phdm@info.ucl.ac.be. */
+#if !defined (SIGCHLD) && defined (SIGCLD)
+#define SIGCHLD SIGCLD
+#endif
+
+#if !defined (HAVE_SIGPROCMASK) && !defined (sigmask)
+# define sigmask(x) (1 << ((x)-1))
+#endif /* !HAVE_SIGPROCMASK && !sigmask */
+
+/* Without SA_NOCLDSTOP, sigset_t might end up being undefined even
+ though we have sigprocmask, on older systems, according to Nelson
+ Beebe. The test is from coreutils/sort.c, via Paul Eggert. */
+#if !defined (HAVE_SIGPROCMASK) || !defined (SA_NOCLDSTOP)
+# 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 /* !HAVE_SIGPROCMASK */
+
+#if defined (HAVE_SIGPROCMASK) || defined (HAVE_SIGSETMASK)
+/* 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)
+
+#else /* !HAVE_SIGPROCMASK && !HAVE_SIGSETMASK */
+# define BLOCK_SIGNAL(sig)
+# define UNBLOCK_SIGNAL(sig)
+#endif /* !HAVE_SIGPROCMASK && !HAVE_SIGSETMASK */
+
+#endif /* not INFO_SIGNALS_H */
diff --git a/info/termdep.h b/info/termdep.h
new file mode 100644
index 0000000..d8343d5
--- /dev/null
+++ b/info/termdep.h
@@ -0,0 +1,58 @@
+/* termdep.h -- system things that terminal.c depends on.
+ $Id: termdep.h,v 1.5 2007/07/01 21:20:31 karl Exp $
+
+ Copyright (C) 1993, 1996, 1997, 1998, 2001, 2002, 2007
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#ifndef INFO_TERMDEP_H
+#define INFO_TERMDEP_H
+
+/* NeXT supplies <termios.h> but it is broken. Probably Autoconf should
+ have a separate test, but anyway ... */
+#ifdef NeXT
+#undef HAVE_TERMIOS_H
+#endif
+
+#ifdef HAVE_TERMIOS_H
+# include <termios.h>
+#else
+# if defined (HAVE_TERMIO_H)
+# include <termio.h>
+# if defined (HAVE_SYS_PTEM_H)
+# if defined (M_UNIX) || !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 <sgtty.h>
+# endif /* !HAVE_TERMIO_H */
+#endif /* !HAVE_TERMIOS_H */
+
+#ifdef GWINSZ_IN_SYS_IOCTL
+# include <sys/ioctl.h>
+#endif
+
+#ifdef HAVE_SYS_TTOLD_H
+# include <sys/ttold.h>
+#endif /* HAVE_SYS_TTOLD_H */
+
+#endif /* not INFO_TERMDEP_H */
diff --git a/info/terminal.c b/info/terminal.c
new file mode 100644
index 0000000..660b8e3
--- /dev/null
+++ b/info/terminal.c
@@ -0,0 +1,872 @@
+/* terminal.c -- how to handle the physical terminal for Info.
+ $Id: terminal.c,v 1.7 2008/06/11 09:55:43 gray Exp $
+
+ Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1996, 1997, 1998,
+ 1999, 2001, 2002, 2004, 2007, 2008 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Originally written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include "terminal.h"
+#include "termdep.h"
+
+#include <sys/types.h>
+#include <signal.h>
+
+/* The Unix termcap interface code. */
+#ifdef HAVE_NCURSES_TERMCAP_H
+#include <ncurses/termcap.h>
+#else
+#ifdef HAVE_TERMCAP_H
+#include <termcap.h>
+#else
+/* 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 *BC, *UP;
+char PC; /* Pad character */
+short ospeed; /* Terminal output baud rate */
+extern int tgetnum (), tgetflag (), tgetent ();
+extern char *tgetstr (), *tgoto ();
+extern void tputs ();
+#endif /* not HAVE_TERMCAP_H */
+#endif /* not HAVE_NCURSES_TERMCAP_H */
+
+/* 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 = NULL;
+VFunction *terminal_end_inverse_hook = NULL;
+VFunction *terminal_prep_terminal_hook = NULL;
+VFunction *terminal_unprep_terminal_hook = NULL;
+VFunction *terminal_up_line_hook = NULL;
+VFunction *terminal_down_line_hook = NULL;
+VFunction *terminal_clear_screen_hook = NULL;
+VFunction *terminal_clear_to_eol_hook = NULL;
+VFunction *terminal_get_screen_size_hook = NULL;
+VFunction *terminal_goto_xy_hook = NULL;
+VFunction *terminal_initialize_terminal_hook = NULL;
+VFunction *terminal_new_terminal_hook = NULL;
+VFunction *terminal_put_text_hook = NULL;
+VFunction *terminal_ring_bell_hook = NULL;
+VFunction *terminal_write_chars_hook = NULL;
+VFunction *terminal_scroll_terminal_hook = NULL;
+
+/* **************************************************************** */
+/* */
+/* Terminal and Termcap */
+/* */
+/* **************************************************************** */
+
+/* A buffer which holds onto the current terminal description, and a pointer
+ used to float within it. And the name of the terminal. */
+static char *term_buffer = NULL;
+static char *term_string_buffer = NULL;
+static char *term_name;
+
+/* 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;
+
+static char *term_keypad_on, *term_keypad_off;
+
+/* 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 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;
+
+/* Although I can't find any documentation that says this is supposed to
+ return its argument, all the code I've looked at (termutils, less)
+ does so, so fine. */
+static int
+output_character_function (int c)
+{
+ putc (c, stdout);
+ return c;
+}
+
+/* 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 (void)
+{
+ RETSIGTYPE (*sigsave) (int signum);
+
+ if (term_keypad_on)
+ send_to_terminal (term_keypad_on);
+
+ if (!term_begin_use || !*term_begin_use)
+ return;
+
+#ifdef SIGWINCH
+ sigsave = signal (SIGWINCH, SIG_IGN);
+#endif
+
+ send_to_terminal (term_begin_use);
+ fflush (stdout);
+ if (STREQ (term_name, "sun-cmd"))
+ /* Without this fflush and sleep, running info in a shelltool or
+ cmdtool (TERM=sun-cmd) with scrollbars loses -- the scrollbars are
+ not restored properly.
+ From: strube@physik3.gwdg.de (Hans Werner Strube). */
+ sleep (1);
+
+#ifdef SIGWINCH
+ signal (SIGWINCH, sigsave);
+#endif
+}
+
+/* Tell the terminal that we will not be doing any more cursor
+ addressable motion. */
+static void
+terminal_end_using_terminal (void)
+{
+ RETSIGTYPE (*sigsave) (int signum);
+
+ if (term_keypad_off)
+ send_to_terminal (term_keypad_off);
+
+ if (!term_end_use || !*term_end_use)
+ return;
+
+#ifdef SIGWINCH
+ sigsave = signal (SIGWINCH, SIG_IGN);
+#endif
+
+ send_to_terminal (term_end_use);
+ fflush (stdout);
+ if (STREQ (term_name, "sun-cmd"))
+ /* See comments at other sleep. */
+ sleep (1);
+
+#ifdef SIGWINCH
+ signal (SIGWINCH, sigsave);
+#endif
+}
+
+/* **************************************************************** */
+/* */
+/* 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 = NULL;
+char *term_kd = NULL;
+char *term_kr = NULL;
+char *term_kl = NULL;
+char *term_kP = NULL; /* page-up */
+char *term_kN = NULL; /* page-down */
+char *term_kh = NULL; /* home */
+char *term_ke = NULL; /* end */
+char *term_kD = NULL; /* delete */
+char *term_ki = NULL; /* ins */
+char *term_kx = NULL; /* del */
+
+/* Move the cursor to the terminal location of X and Y. */
+void
+terminal_goto_xy (int x, int 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 (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 (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 (void)
+{
+ 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 (void)
+{
+ 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 (void)
+{
+ 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 (void)
+{
+ 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 (void)
+{
+ 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 (void)
+{
+ 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 (void)
+{
+ 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 (int start, int 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 (int start, int 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 (int start, int end, int 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 (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 (void)
+{
+ 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 nonzero. The variables SCREENHEIGHT and SCREENWIDTH are set
+ to the dimensions that this terminal actually has. The variable
+ TERMINAL_HAS_META_P becomes nonzero if this terminal supports a Meta
+ key. Finally, the terminal screen is cleared. */
+void
+terminal_initialize_terminal (char *terminal_name)
+{
+ char *buffer;
+
+ terminal_is_dumb_p = 0;
+
+ if (terminal_initialize_terminal_hook)
+ {
+ (*terminal_initialize_terminal_hook) (terminal_name);
+ return;
+ }
+
+ term_name = terminal_name ? terminal_name : getenv ("TERM");
+ if (!term_name)
+ term_name = "dumb";
+
+ if (!term_string_buffer)
+ term_string_buffer = xmalloc (2048);
+
+ if (!term_buffer)
+ term_buffer = xmalloc (2048);
+
+ buffer = term_string_buffer;
+
+ term_clrpag = term_cr = term_clreol = NULL;
+
+ /* HP-UX 11.x returns 0 for OK --jeff.hull@state.co.us. */
+ if (tgetent (term_buffer, term_name) < 0)
+ {
+ terminal_is_dumb_p = 1;
+ screenwidth = 80;
+ screenheight = 24;
+ term_cr = "\r";
+ term_up = term_dn = audible_bell = visible_bell = NULL;
+ term_ku = term_kd = term_kl = term_kr = NULL;
+ term_kP = term_kN = NULL;
+ term_kh = term_ke = NULL;
+ term_kD = NULL;
+ return;
+ }
+
+ BC = tgetstr ("pc", &buffer);
+ PC = BC ? *BC : 0;
+
+#if defined (HAVE_TERMIOS_H)
+ {
+ struct termios ti;
+ if (tcgetattr (fileno(stdout), &ti) != -1)
+ ospeed = cfgetospeed (&ti);
+ else
+ ospeed = B9600;
+ }
+#else
+# 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 */
+#endif
+
+ term_cr = tgetstr ("cr", &buffer);
+ term_clreol = tgetstr ("ce", &buffer);
+ term_clrpag = tgetstr ("cl", &buffer);
+ term_goto = tgetstr ("cm", &buffer);
+
+ /* Find out about this terminal's 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 = 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 != NULL);
+ audible_bell = tgetstr ("bl", &buffer);
+ if (!audible_bell)
+ audible_bell = "\007";
+ term_begin_use = tgetstr ("ti", &buffer);
+ term_end_use = tgetstr ("te", &buffer);
+
+ term_keypad_on = tgetstr ("ks", &buffer);
+ term_keypad_off = tgetstr ("ke", &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);
+ }
+ else
+ {
+ term_mm = 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);
+
+ term_kP = tgetstr ("kP", &buffer);
+ term_kN = tgetstr ("kN", &buffer);
+
+#if defined(INFOKEY)
+ term_kh = tgetstr ("kh", &buffer);
+ term_ke = tgetstr ("@7", &buffer);
+ term_ki = tgetstr ("kI", &buffer);
+ term_kx = tgetstr ("kD", &buffer);
+#endif /* defined(INFOKEY) */
+
+ /* Home and end keys. */
+ term_kh = tgetstr ("kh", &buffer);
+ term_ke = tgetstr ("@7", &buffer);
+
+ term_kD = tgetstr ("kD", &buffer);
+
+ /* If this terminal is not cursor addressable, then it is really dumb. */
+ if (!term_goto)
+ terminal_is_dumb_p = 1;
+}
+
+/* How to read characters from the terminal. */
+
+#if defined (HAVE_TERMIOS_H)
+struct termios original_termios, ttybuff;
+#else
+# 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;
+
+# if defined(TIOCGETC) && defined(M_XENIX)
+/* SCO 3.2v5.0.2 defines but does not support TIOCGETC. Gak. Maybe
+ better fix would be to use Posix termios in preference. --gildea,
+ 1jul99. */
+# undef TIOCGETC
+# endif
+
+# 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
+# endif /* !HAVE_TERMIO_H */
+#endif /* !HAVE_TERMIOS_H */
+
+/* Prepare to start using the terminal to read characters singly. */
+void
+terminal_prep_terminal (void)
+{
+ int tty;
+
+ if (terminal_prep_terminal_hook)
+ {
+ (*terminal_prep_terminal_hook) ();
+ return;
+ }
+
+ terminal_begin_using_terminal ();
+
+ tty = fileno (stdin);
+
+#if defined (HAVE_TERMIOS_H)
+ tcgetattr (tty, &original_termios);
+ tcgetattr (tty, &ttybuff);
+#else
+# if defined (HAVE_TERMIO_H)
+ ioctl (tty, TCGETA, &original_termio);
+ ioctl (tty, TCGETA, &ttybuff);
+# endif
+#endif
+
+#if defined (HAVE_TERMIOS_H) || defined (HAVE_TERMIO_H)
+ ttybuff.c_iflag &= (~ISTRIP & ~INLCR & ~IGNCR & ~ICRNL & ~IXON);
+/* These output flags are not part of POSIX, so only use them if they
+ are defined. */
+#ifdef ONLCR
+ ttybuff.c_oflag &= ~ONLCR ;
+#endif
+#ifdef OCRNL
+ ttybuff.c_oflag &= ~OCRNL;
+#endif
+ 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;
+
+#ifdef VLNEXT
+ if (ttybuff.c_cc[VLNEXT] == '\026')
+ ttybuff.c_cc[VLNEXT] = -1;
+#endif /* VLNEXT */
+#endif /* TERMIOS or TERMIO */
+
+/* cf. emacs/src/sysdep.c for being sure output is on. */
+#if defined (HAVE_TERMIOS_H)
+ /* linux kernel 2.2.x needs a TCOFF followed by a TCOON to turn output
+ back on if the user presses ^S at the very beginning; just a TCOON
+ doesn't work. --Kevin Ryde <user42@zip.com.au>, 16jun2000. */
+ tcsetattr (tty, TCSANOW, &ttybuff);
+# ifdef TCOON
+ tcflow (tty, TCOOFF);
+ tcflow (tty, TCOON);
+# endif
+#else
+# if defined (HAVE_TERMIO_H)
+ ioctl (tty, TCSETA, &ttybuff);
+# ifdef TCXONC
+ ioctl (tty, TCXONC, 1);
+# endif
+# endif
+#endif
+
+#if !defined (HAVE_TERMIOS_H) && !defined (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_TERMIOS_H && !HAVE_TERMIO_H */
+}
+
+/* Restore the tty settings back to what they were before we started using
+ this terminal. */
+void
+terminal_unprep_terminal (void)
+{
+ int tty;
+
+ if (terminal_unprep_terminal_hook)
+ {
+ (*terminal_unprep_terminal_hook) ();
+ return;
+ }
+
+ tty = fileno (stdin);
+
+#if defined (HAVE_TERMIOS_H)
+ tcsetattr (tty, TCSANOW, &original_termios);
+#else
+# 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 */
+#endif /* !HAVE_TERMIOS_H */
+ terminal_end_using_terminal ();
+}
+
+#ifdef __MSDOS__
+# include "pcterm.c"
+#endif
diff --git a/info/terminal.h b/info/terminal.h
new file mode 100644
index 0000000..135b3ae
--- /dev/null
+++ b/info/terminal.h
@@ -0,0 +1,129 @@
+/* terminal.h -- The external interface to terminal I/O.
+ $Id: terminal.h,v 1.6 2007/07/01 21:20:31 karl Exp $
+
+ Copyright (C) 1993, 1996, 1997, 2001, 2002, 2004, 2007
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#if !defined (TERMINAL_H)
+#define TERMINAL_H
+
+#include "info.h"
+
+/* 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 (char *terminal_name);
+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 (void);
+extern VFunction *terminal_get_screen_size_hook;
+
+/* Save and restore tty settings. */
+extern void terminal_prep_terminal (void);
+extern void terminal_unprep_terminal (void);
+
+extern VFunction *terminal_prep_terminal_hook;
+extern VFunction *terminal_unprep_terminal_hook;
+
+/* Re-initialize the terminal to TERMINAL_NAME. */
+extern void terminal_new_terminal (char *terminal_name);
+extern VFunction *terminal_new_terminal_hook;
+
+/* Move the cursor to the terminal location of X and Y. */
+extern void terminal_goto_xy (int x, int y);
+extern VFunction *terminal_goto_xy_hook;
+
+/* Print STRING to the terminal at the current position. */
+extern void terminal_put_text (char *string);
+extern VFunction *terminal_put_text_hook;
+
+/* Print NCHARS from STRING to the terminal at the current position. */
+extern void terminal_write_chars (char *string, int nchars);
+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 (void);
+extern VFunction *terminal_clear_to_eol_hook;
+
+/* Clear the entire terminal screen. */
+extern void terminal_clear_screen (void);
+extern VFunction *terminal_clear_screen_hook;
+
+/* Move the cursor up one line. */
+extern void terminal_up_line (void);
+extern VFunction *terminal_up_line_hook;
+
+/* Move the cursor down one line. */
+extern void terminal_down_line (void);
+extern VFunction *terminal_down_line_hook;
+
+/* Turn on reverse video if possible. */
+extern void terminal_begin_inverse (void);
+extern VFunction *terminal_begin_inverse_hook;
+
+/* Turn off reverse video if possible. */
+extern void terminal_end_inverse (void);
+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 (int start, int end, int amount);
+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 (void);
+extern VFunction *terminal_ring_bell_hook;
+
+/* The key sequences output by special keys, if this terminal has any. */
+extern char *term_ku, *term_kd, *term_kr, *term_kl;
+extern char *term_kP, *term_kN;
+extern char *term_ke, *term_kh;
+extern char *term_kx, *term_ki;
+extern char *term_kD;
+
+#endif /* !TERMINAL_H */
diff --git a/info/tilde.c b/info/tilde.c
new file mode 100644
index 0000000..9bd8852
--- /dev/null
+++ b/info/tilde.c
@@ -0,0 +1,340 @@
+/* tilde.c -- tilde expansion code (~/foo := $HOME/foo).
+ $Id: tilde.c,v 1.8 2008/06/11 09:55:43 gray Exp $
+
+ Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1996, 1998, 1999,
+ 2002, 2004, 2006, 2007, 2008 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.h"
+#include "tilde.h"
+
+#if defined (TEST) || defined (STATIC_MALLOC)
+static void *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~", 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", 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. */
+CFunction *tilde_expansion_failure_hook = 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 (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 (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 (IS_SLASH (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 (char *string)
+{
+ char *result;
+ int result_size, result_index;
+
+ result_size = result_index = 0;
+ result = 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 = 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 = 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 = 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 (char *filename)
+{
+ char *dirname = filename ? xstrdup (filename) : NULL;
+
+ if (dirname && *dirname == '~')
+ {
+ char *temp_name;
+ if (!dirname[1] || IS_SLASH (dirname[1]))
+ {
+ /* Prepend $HOME to the rest of the string. */
+ char *temp_home = getenv ("HOME");
+
+ /* If there is no HOME variable, look up the directory in
+ the password database. */
+ if (!temp_home)
+ {
+ struct passwd *entry;
+
+ entry = (struct passwd *) getpwuid (getuid ());
+ if (entry)
+ temp_home = entry->pw_dir;
+ }
+
+ temp_name = xmalloc (1 + strlen (&dirname[1])
+ + (temp_home ? strlen (temp_home) : 0));
+ if (temp_home)
+ strcpy (temp_name, temp_home);
+ else
+ temp_name[0] = 0;
+ strcat (temp_name, &dirname[1]);
+ free (dirname);
+ dirname = xstrdup (temp_name);
+ free (temp_name);
+ }
+ else
+ {
+ struct passwd *user_entry;
+ char *username = xmalloc (257);
+ int i, c;
+
+ for (i = 1; (c = dirname[i]); i++)
+ {
+ if (IS_SLASH (c))
+ break;
+ else
+ username[i - 1] = c;
+ }
+ username[i - 1] = 0;
+
+ if (!(user_entry = (struct passwd *) 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 = (*tilde_expansion_failure_hook) (username);
+
+ if (expansion)
+ {
+ temp_name = xmalloc (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 = xmalloc (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 = xstrdup (temp_name);
+ free (temp_name);
+ }
+
+ endpwent ();
+ free (username);
+ }
+ }
+ 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);
+ }
+ xexit (0);
+}
+
+static void memory_error_and_abort ();
+
+static void *
+xmalloc (bytes)
+ int bytes;
+{
+ void *temp = (void *)malloc (bytes);
+
+ if (!temp)
+ memory_error_and_abort ();
+ return temp;
+}
+
+static void *
+xrealloc (pointer, bytes)
+ void *pointer;
+ int bytes;
+{
+ void *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 ();
+}
+#endif /* TEST */
+
diff --git a/info/tilde.h b/info/tilde.h
new file mode 100644
index 0000000..4287cb5
--- /dev/null
+++ b/info/tilde.h
@@ -0,0 +1,53 @@
+/* tilde.h: Externally available variables and function in libtilde.a.
+ $Id: tilde.h,v 1.6 2007/07/01 21:20:31 karl Exp $
+
+ 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, 2004, 2007
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#ifndef TILDE_H
+#define TILDE_H
+
+#include "info.h"
+
+/* 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 CFunction *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 (char *string);
+
+/* 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 (char *filename);
+
+#endif /* not TILDE_H */
diff --git a/info/variables.c b/info/variables.c
new file mode 100644
index 0000000..292e45d
--- /dev/null
+++ b/info/variables.c
@@ -0,0 +1,319 @@
+/* variables.c -- how to manipulate user visible variables in Info.
+ $Id: variables.c,v 1.10 2008/06/11 09:55:43 gray Exp $
+
+ Copyright (C) 1993, 1997, 2001, 2002, 2004, 2007, 2008
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ 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", NULL };
+
+VARIABLE_ALIST info_variables[] = {
+ { "automatic-footnotes",
+ N_("When \"On\", footnotes appear and disappear automatically"),
+ &auto_footnotes_p, (char **)on_off_choices },
+
+ { "automatic-tiling",
+ N_("When \"On\", creating or deleting a window resizes other windows"),
+ &auto_tiling_p, (char **)on_off_choices },
+
+ { "visible-bell",
+ N_("When \"On\", flash the screen instead of ringing the bell"),
+ &terminal_use_visible_bell_p, (char **)on_off_choices },
+
+ { "errors-ring-bell",
+ N_("When \"On\", errors cause the bell to ring"),
+ &info_error_rings_bell_p, (char **)on_off_choices },
+
+ { "gc-compressed-files",
+ N_("When \"On\", Info garbage collects files which had to be uncompressed"),
+ &gc_compressed_files, (char **)on_off_choices },
+ { "show-index-match",
+ N_("When \"On\", the portion of the matched search string is highlighted"),
+ &show_index_match, (char **)on_off_choices },
+
+ { "scroll-behaviour",
+ N_("Controls what happens when scrolling is requested at the end of a node"),
+ &info_scroll_behaviour, (char **)info_scroll_choices },
+
+ /* Alternate spelling */
+ { "scroll-behavior",
+ N_("Same as scroll-behaviour"),
+ &info_scroll_behaviour, (char **)info_scroll_choices },
+
+ { "scroll-step",
+ N_("The number lines to scroll when the cursor moves out of the window"),
+ &window_scroll_step, NULL },
+
+ { "cursor-movement-scrolls",
+ N_("Controls whether scroll-behavior affects cursor movement commands"),
+ &cursor_movement_scrolls_p, (char **)on_off_choices },
+
+ { "ISO-Latin",
+ N_("When \"On\", Info accepts and displays ISO Latin characters"),
+ &ISO_Latin_p, (char **)on_off_choices },
+
+ { "scroll-last-node",
+ N_("What to do when a scrolling command is issued at the end of the "
+ "last node"),
+ &scroll_last_node, (char**)scroll_last_node_choices },
+
+ { 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 = 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, NULL);
+ 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 = NULL;
+ int array_index = 0;
+ int array_slots = 0;
+
+ for (i = 0; var->choices[i]; i++)
+ {
+ REFERENCE *entry;
+
+ entry = xmalloc (sizeof (REFERENCE));
+ entry->label = xstrdup (var->choices[i]);
+ entry->nodename = NULL;
+ entry->filename = 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 (const 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 NULL;
+ }
+
+ /* User accepted "default"? (There is none.) */
+ if (!*line)
+ {
+ free (line);
+ return 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 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 (void)
+{
+ register int i;
+ REFERENCE **array = NULL;
+ int array_index = 0, array_slots = 0;
+
+ for (i = 0; info_variables[i].name; i++)
+ {
+ REFERENCE *entry;
+
+ entry = xmalloc (sizeof (REFERENCE));
+ entry->label = xstrdup (info_variables[i].name);
+ entry->nodename = NULL;
+ entry->filename = NULL;
+
+ add_pointer_to_array
+ (entry, array_index, array, array_slots, 200, REFERENCE *);
+ }
+
+ return array;
+}
+
+#if defined(INFOKEY)
+
+void
+set_variable_to_value(char *name, char *value)
+{
+ register int i;
+
+ /* Find the variable in our list of variables. */
+ for (i = 0; info_variables[i].name; i++)
+ if (strcmp(info_variables[i].name, name) == 0)
+ break;
+
+ if (!info_variables[i].name)
+ return;
+
+ if (info_variables[i].choices)
+ {
+ register int j;
+
+ /* Find the choice in our list of choices. */
+ for (j = 0; info_variables[i].choices[j]; j++)
+ if (strcmp (info_variables[i].choices[j], value) == 0)
+ break;
+
+ if (info_variables[i].choices[j])
+ *info_variables[i].value = j;
+ }
+ else
+ {
+ *info_variables[i].value = atoi(value);
+ }
+}
+
+#endif /* INFOKEY */
diff --git a/info/variables.h b/info/variables.h
new file mode 100644
index 0000000..309814e
--- /dev/null
+++ b/info/variables.h
@@ -0,0 +1,67 @@
+/* variables.h -- Description of user visible variables in Info.
+ $Id: variables.h,v 1.9 2008/03/04 09:45:27 gray Exp $
+
+ This file is part of GNU Info, a program for reading online documentation
+ stored in Info format.
+
+ Copyright (C) 1993, 1997, 2004, 2007 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#ifndef INFO_VARIABLES_H
+#define INFO_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 (const char *prompt, WINDOW *window);
+
+/* Make an array of REFERENCE which actually contains the names of the
+ variables available in Info. */
+extern REFERENCE **make_variable_completions_array (void);
+
+/* Set the value of an info variable. */
+extern void set_variable (WINDOW *window, int count, unsigned char key);
+extern void describe_variable (WINDOW *window, int count, unsigned char key);
+
+/* 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 cursor_movement_scrolls_p;
+extern int ISO_Latin_p;
+extern int scroll_last_node;
+
+#endif /* not INFO_VARIABLES_H */
diff --git a/info/window.c b/info/window.c
new file mode 100644
index 0000000..fdbeb33
--- /dev/null
+++ b/info/window.c
@@ -0,0 +1,1958 @@
+/* window.c -- windows in Info.
+ $Id: window.c,v 1.17 2008/09/13 10:01:31 gray Exp $
+
+ Copyright (C) 1993, 1997, 1998, 2001, 2002, 2003, 2004, 2007, 2008
+ 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#include "info.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 = NULL;
+
+/* The window which describes the echo area. */
+WINDOW *the_echo_area = NULL;
+
+/* The list of windows in Info. */
+WINDOW *windows = NULL;
+
+/* Pointer to the active window in WINDOW_LIST. */
+WINDOW *active_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)
+
+/* Show malformed multibyte sequences */
+int show_malformed_multibyte_p = 0;
+
+/* 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 (int width, int height)
+{
+ the_screen = xmalloc (sizeof (WINDOW));
+ the_echo_area = xmalloc (sizeof (WINDOW));
+ windows = 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);
+
+ /* 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 = NULL;
+
+void
+window_new_screen_size (int width, int 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 = 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 = 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 = NULL;
+
+ /* 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;
+ }
+ }
+
+ /* One more loop. If any heights or widths have become negative,
+ set them to zero. This can apparently happen with resizing down to
+ very small sizes. Sadly, it is not apparent to me where in the
+ above calculations it goes wrong. */
+ for (win = windows; win; win = win->next)
+ {
+ if (win->height < 0)
+ win->height = 0;
+
+ if (win->width < 0)
+ win->width = 0;
+ }
+}
+
+/* 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)
+{
+ 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 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 = 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;
+ memset (&window->line_map, 0, sizeof (window->line_map));
+ window->modeline = xmalloc (1 + window->width);
+ window->line_starts = 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 *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 (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 = 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)
+{
+ 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 *window, NODE *node)
+{
+ window->node = node;
+ window->pagetop = 0;
+ window->point = 0;
+ recalculate_line_starts (window);
+ window->flags |= W_UpdateWindow;
+ /* The display_pos member is nonzero if we're displaying an anchor. */
+ window->point = node ? node->display_pos : 0;
+ 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 *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 (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 (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 (int character, int hpos)
+{
+ int printable_limit = 127;
+ int width = 1;
+
+ if (ISO_Latin_p)
+ printable_limit = 255;
+
+ 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 (char *string, int hpos)
+{
+ register int i, width, this_char_width;
+
+ for (width = 0, i = 0; string[i]; i++)
+ {
+ /* Support ANSI escape sequences for -R. */
+ if (raw_escapes_p
+ && string[i] == '\033'
+ && string[i+1] == '['
+ && isdigit (string[i+2])
+ && (string[i+3] == 'm'
+ || (isdigit (string[i+3]) && string[i+4] == 'm')))
+ {
+ while (string[i] != 'm')
+ i++;
+ this_char_width = 0;
+ }
+ else
+ 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 that NODE would
+ take to display. This really only counts carriage returns. */
+int
+window_physical_lines (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;
+}
+
+
+struct calc_closure {
+ WINDOW *win;
+ int line_starts_slots; /* FIXME: size_t */
+};
+
+static int
+_calc_line_starts (void *closure, size_t line_index,
+ const char *src_line,
+ char *printed_line, size_t pl_index, size_t pl_count)
+{
+ struct calc_closure *cp = closure;
+ add_pointer_to_array (src_line,
+ cp->win->line_count, cp->win->line_starts,
+ cp->line_starts_slots, 100, char *);
+ return 0;
+}
+
+void
+calculate_line_starts (WINDOW *window)
+{
+ struct calc_closure closure;
+
+ window->line_starts = NULL;
+ window->line_count = 0;
+
+ if (!window->node)
+ return;
+
+ closure.win = window;
+ closure.line_starts_slots = 0;
+ process_node_text (window, window->node->contents, 0,
+ _calc_line_starts, &closure);
+}
+
+/* Given WINDOW, recalculate the line starts for the node it displays. */
+void
+recalculate_line_starts (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 value of 1 produces smooth scrolling. If set
+ to zero, the line containing point is centered within the window. */
+int window_scroll_step = 1;
+
+/* Adjust the pagetop of WINDOW such that the cursor point will be visible. */
+void
+window_adjust_pagetop (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)
+{
+ 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)
+{
+ 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)
+{
+ return window_point_to_column (window, window->point, &window->point);
+}
+
+/* Count the number of characters in LINE that precede the printed column
+ offset of GOAL. */
+int
+window_chars_to_goal (WINDOW *win, int goal)
+{
+ window_compute_line_map (win);
+ if (goal >= win->line_map.used)
+ goal = win->line_map.used - 1;
+ return win->line_map.map[goal] - win->line_map.map[0];
+}
+
+/* Create a modeline for WINDOW, and store it in window->modeline. */
+void
+window_make_modeline (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 = NULL, *filename = "*no file*";
+ char *nodename = "*no node*";
+ const char *update_message = 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 = 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 *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 *window, SEARCH_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 *window, SEARCH_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 = NULL;
+
+/* Make the node of the_echo_area be an empty one. */
+static void
+free_echo_area (void)
+{
+ if (echo_area_node)
+ {
+ maybe_free (echo_area_node->contents);
+ free (echo_area_node);
+ }
+
+ echo_area_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 (void)
+{
+ 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 (const char *format, void *arg1, void *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 = NULL;
+static int old_echo_area_nodes_index = 0;
+static int old_echo_area_nodes_slots = 0;
+
+void
+message_in_echo_area (const char *format, void *arg1, void *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 = NULL;
+ window_message_in_echo_area (format, arg1, arg2);
+}
+
+void
+unmessage_in_echo_area (void)
+{
+ 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 = 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 (int length)
+{
+ if (!message_buffer)
+ {
+ message_buffer_size = length + 1;
+ message_buffer = 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 (const char *format, void *arg1, void *arg2, void *arg3)
+{
+ register int i, len;
+ void *args[3];
+ int arg_index = 0;
+
+ args[0] = arg1;
+ args[1] = arg2;
+ args[2] = arg3;
+
+ 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;
+ const char *fmt_start = format + i;
+ char *fmt;
+ int fmt_len, formatted_len;
+ int paramed = 0;
+
+ format_again:
+ i++;
+ while (format[i] && strchr ("-. +0123456789", format[i]))
+ i++;
+ c = format[i];
+
+ if (c == '\0')
+ abort ();
+
+ if (c == '$') {
+ /* position parameter parameter */
+ /* better to use bprintf from bfox's metahtml? */
+ arg_index = atoi(fmt_start + 1) - 1;
+ if (arg_index < 0)
+ arg_index = 0;
+ if (arg_index >= 2)
+ arg_index = 1;
+ paramed = 1;
+ goto format_again;
+ }
+
+ fmt_len = format + i - fmt_start + 1;
+ fmt = xmalloc (fmt_len + 1);
+ strncpy (fmt, fmt_start, fmt_len);
+ fmt[fmt_len] = '\0';
+
+ if (paramed) {
+ /* removed positioned parameter */
+ char *p;
+ for (p = fmt + 1; *p && *p != '$'; p++) {
+ ;
+ }
+ strcpy(fmt + 1, p + 1);
+ }
+
+ /* If we have "%-98s", maybe 98 calls for a longer string. */
+ if (fmt_len > 2)
+ {
+ int j;
+
+ for (j = fmt_len - 2; j >= 0; j--)
+ if (isdigit (fmt[j]) || fmt[j] == '$')
+ break;
+
+ formatted_len = atoi (fmt + j);
+ }
+ else
+ formatted_len = c == 's' ? 0 : 1; /* %s can produce empty string */
+
+ switch (c)
+ {
+ case '%': /* Insert a percent sign. */
+ message_buffer_resize (len + formatted_len);
+ sprintf
+ (message_buffer + message_buffer_index, fmt, "%");
+ message_buffer_index += formatted_len;
+ break;
+
+ case 's': /* Insert the current arg as a string. */
+ {
+ char *string;
+ int string_len;
+
+ string = (char *)args[arg_index++];
+ string_len = strlen (string);
+
+ if (formatted_len > string_len)
+ string_len = formatted_len;
+ message_buffer_resize (len + string_len);
+ sprintf
+ (message_buffer + message_buffer_index, fmt, string);
+ message_buffer_index += string_len;
+ }
+ break;
+
+ case 'd': /* Insert the current arg as an integer. */
+ {
+ long long_val;
+ int integer;
+
+ long_val = (long)args[arg_index++];
+ integer = (int)long_val;
+
+ message_buffer_resize (len + formatted_len > 32
+ ? formatted_len : 32);
+ sprintf
+ (message_buffer + message_buffer_index, fmt, integer);
+ message_buffer_index = strlen (message_buffer);
+ }
+ break;
+
+ case 'c': /* Insert the current arg as a character. */
+ {
+ long long_val;
+ int character;
+
+ long_val = (long)args[arg_index++];
+ character = (int)long_val;
+
+ message_buffer_resize (len + formatted_len);
+ sprintf
+ (message_buffer + message_buffer_index, fmt, character);
+ message_buffer_index += formatted_len;
+ }
+ break;
+
+ default:
+ abort ();
+ }
+ free (fmt);
+ }
+ }
+ 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 (const char *format, void *arg1, void *arg2)
+{
+ NODE *node;
+
+ message_buffer_index = 0;
+ build_message_buffer (format, arg1, arg2, 0);
+
+ node = message_buffer_to_node ();
+ return node;
+}
+
+/* Convert the contents of the message buffer to a node. */
+NODE *
+message_buffer_to_node (void)
+{
+ NODE *node;
+
+ node = xmalloc (sizeof (NODE));
+ node->filename = NULL;
+ node->parent = NULL;
+ node->nodename = NULL;
+ node->flags = 0;
+ node->display_pos =0;
+
+ /* Make sure that this buffer ends with a newline. */
+ node->nodelen = 1 + strlen (message_buffer);
+ node->contents = 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 (void)
+{
+ message_buffer_index = 0;
+}
+
+/* Print FORMAT with ARG1,2 to the end of the current message buffer. */
+void
+printf_to_message_buffer (const char *format, void *arg1, void *arg2, void *arg3)
+{
+ build_message_buffer (format, arg1, arg2, arg3);
+}
+
+/* Return the current horizontal position of the "cursor" on the most
+ recently output message buffer line. */
+int
+message_buffer_length_this_line (void)
+{
+ 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 (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;
+}
+
+
+#define ITER_SETBYTES(iter,n) ((iter).cur.bytes = n)
+#define ITER_LIMIT(iter) ((iter).limit - (iter).cur.ptr)
+
+/* If ITER points to an ANSI escape sequence, process it, set PLEN to its
+ length in bytes, and return 1.
+ Otherwise, return 0.
+ */
+static int
+ansi_escape (mbi_iterator_t iter, size_t *plen)
+{
+ if (raw_escapes_p && *mbi_cur_ptr (iter) == '\033' && mbi_avail (iter))
+ {
+ mbi_advance (iter);
+ if (*mbi_cur_ptr (iter) == '[' && mbi_avail (iter))
+ {
+ ITER_SETBYTES (iter, 1);
+ mbi_advance (iter);
+ if (isdigit (*mbi_cur_ptr (iter)) && mbi_avail (iter))
+ {
+ ITER_SETBYTES (iter, 1);
+ mbi_advance (iter);
+ if (*mbi_cur_ptr (iter) == 'm')
+ {
+ *plen = 4;
+ return 1;
+ }
+ else if (isdigit (*mbi_cur_ptr (iter)) && mbi_avail (iter))
+ {
+ ITER_SETBYTES (iter, 1);
+ mbi_advance (iter);
+ if (*mbi_cur_ptr (iter) == 'm')
+ {
+ *plen = 5;
+ return 1;
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* If ITER points to an info tag, process it, set PLEN to its
+ length in bytes, and return 1.
+ Otherwise, return 0.
+
+ Collected tag is processed if HANDLE!=0.
+*/
+static int
+info_tag (mbi_iterator_t iter, int handle, size_t *plen)
+{
+ if (*mbi_cur_ptr (iter) == '\0' && mbi_avail (iter))
+ {
+ mbi_advance (iter);
+ if (*mbi_cur_ptr (iter) == '\b' && mbi_avail (iter))
+ {
+ mbi_advance (iter);
+ if (*mbi_cur_ptr (iter) == '[' && mbi_avail (iter))
+ {
+ const char *ptr, *end;
+ mbi_advance (iter);
+ ptr = mbi_cur_ptr (iter);
+ end = memmem (ptr, ITER_LIMIT (iter), "\0\b]", 3);
+ if (end)
+ {
+ size_t len = end - ptr;
+
+ if (handle)
+ {
+ char *elt = xmalloc (len + 1);
+ memcpy (elt, ptr, len);
+ elt[len] = 0;
+ handle_tag (elt);
+ free (elt);
+ }
+ *plen = len + 6;
+ return 1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* Process contents of the current node from WIN, beginning from START, using
+ callback function FUN.
+
+ FUN is called for every line collected from the node. Its arguments:
+
+ int (*fun) (void *closure, size_t line_no,
+ const char *src_line, char *prt_line,
+ size_t prt_bytes, size_t prt_chars)
+
+ closure -- An opaque pointer passed as 5th parameter to process_node_text;
+ line_no -- Number of processed line (starts from 0);
+ src_line -- Pointer to the source line (unmodified);
+ prt_line -- Collected line contents, ready for output;
+ prt_bytes -- Number of bytes in prt_line;
+ prt_chars -- Number of characters in prt_line.
+
+ If FUN returns non zero, process_node_text stops processing and returns
+ immediately.
+
+ If DO_TAGS is not zero, process info tags, otherwise ignore them.
+
+ Return value: number of lines processed.
+*/
+
+size_t
+process_node_text (WINDOW *win, char *start,
+ int do_tags,
+ int (*fun) (void *, size_t, const char *, char *, size_t, size_t),
+ void *closure)
+{
+ char *printed_line; /* Buffer for a printed line. */
+ size_t pl_count = 0; /* Number of *characters* written to PRINTED_LINE */
+ size_t pl_index = 0; /* Index into PRINTED_LINE. */
+ size_t in_index = 0;
+ size_t line_index = 0; /* Number of lines done so far. */
+ size_t allocated_win_width;
+ mbi_iterator_t iter;
+
+ /* 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. */
+ allocated_win_width = win->width + 1;
+ printed_line = xmalloc (allocated_win_width);
+
+ for (mbi_init (iter, start,
+ win->node->contents + win->node->nodelen - start),
+ pl_count = 0;
+ mbi_avail (iter);
+ mbi_advance (iter))
+ {
+ const char *carried_over_ptr;
+ size_t carried_over_len, carried_over_count;
+ const char *cur_ptr = mbi_cur_ptr (iter);
+ int cur_len = mb_len (mbi_cur (iter));
+ int replen;
+ int delim = 0;
+ int rc;
+
+ if (mb_isprint (mbi_cur (iter)))
+ {
+ replen = 1;
+ }
+ else if (cur_len == 1)
+ {
+ if (*cur_ptr == '\r' || *cur_ptr == '\n')
+ {
+ replen = win->width - pl_count;
+ delim = 1;
+ }
+ else if (ansi_escape (iter, &cur_len))
+ {
+ replen = 0;
+ ITER_SETBYTES (iter, cur_len);
+ }
+ else if (info_tag (iter, do_tags, &cur_len))
+ {
+ ITER_SETBYTES (iter, cur_len);
+ continue;
+ }
+ else
+ {
+ if (*cur_ptr == '\t')
+ delim = 1;
+ cur_ptr = printed_representation (cur_ptr, cur_len, pl_count,
+ &cur_len);
+ replen = cur_len;
+ }
+ }
+ else if (show_malformed_multibyte_p || mbi_cur (iter).wc_valid)
+ {
+ /* FIXME: I'm not sure it's the best way to deal with unprintable
+ multibyte characters */
+ cur_ptr = printed_representation (cur_ptr, cur_len, pl_count,
+ &cur_len);
+ replen = cur_len;
+ }
+
+ /* Ensure there is enough space in the buffer */
+ while (pl_index + cur_len + 2 > allocated_win_width - 1)
+ printed_line = x2realloc (printed_line, &allocated_win_width);
+
+ /* If this character can be printed without passing the width of
+ the line, then stuff it into the line. */
+ if (pl_count + replen < win->width)
+ {
+ int i;
+
+ for (i = 0; i < cur_len; i++)
+ printed_line[pl_index++] = cur_ptr[i];
+ pl_count += replen;
+ in_index += mb_len (mbi_cur (iter));
+ }
+ else
+ {
+ /* 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 (delim)
+ {
+ printed_line[pl_index] = '\0';
+ carried_over_ptr = NULL;
+ }
+ else
+ {
+ /* The printed representation of this character extends into
+ the next line. */
+
+ carried_over_count = replen;
+ if (replen == 1)
+ {
+ /* It is a single (possibly multibyte) character */
+ /* FIXME? */
+ carried_over_ptr = cur_ptr;
+ carried_over_len = cur_len;
+ }
+ else
+ {
+ int i;
+
+ /* 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_count < (win->width - 1);
+ pl_count++)
+ printed_line[pl_index++] = cur_ptr[i++];
+
+ carried_over_ptr = cur_ptr + i;
+ carried_over_len = cur_len;
+ }
+
+ /* 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++] = '\\';
+ pl_count++;
+ }
+ printed_line[pl_index] = '\0';
+ }
+
+ rc = fun (closure, line_index, mbi_cur_ptr (iter) - in_index,
+ printed_line, pl_index, pl_count);
+
+ ++line_index;
+
+ /* Reset all data to the start of the line. */
+ pl_index = 0;
+ pl_count = 0;
+ in_index = 0;
+
+ if (rc)
+ break;
+
+ /* If there are bytes carried over, stuff them
+ into the buffer now. */
+ if (carried_over_ptr)
+ {
+ for (; carried_over_len;
+ carried_over_len--, carried_over_ptr++, pl_index++)
+ printed_line[pl_index] = *carried_over_ptr;
+ pl_count += carried_over_count;
+ }
+
+ /* 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)
+ {
+ for (; mbi_avail (iter); mbi_advance (iter))
+ if (mb_len (mbi_cur (iter)) == 1
+ && *mbi_cur_ptr (iter) == '\n')
+ break;
+
+ pl_index = 0;
+ pl_count = 0;
+ in_index = 0;
+ printed_line[0] = 0;
+ }
+ }
+ }
+
+ if (pl_count)
+ fun (closure, line_index, mbi_cur_ptr (iter) - in_index,
+ printed_line, pl_index, pl_count);
+
+ free (printed_line);
+ return line_index;
+}
+
+void
+clean_manpage (char *manpage)
+{
+ mbi_iterator_t iter;
+ size_t len = strlen (manpage);
+ char *newpage = xmalloc (len + 1);
+ char *np = newpage;
+ int prev_len = 0;
+
+ for (mbi_init (iter, manpage, len);
+ mbi_avail (iter);
+ mbi_advance (iter))
+ {
+ const char *cur_ptr = mbi_cur_ptr (iter);
+ int cur_len = mb_len (mbi_cur (iter));
+
+ if (cur_len == 1)
+ {
+ if (*cur_ptr == '\b' || *cur_ptr == '\f')
+ {
+ if (np >= newpage + prev_len)
+ np -= prev_len;
+ }
+ else if (ansi_escape (iter, &cur_len))
+ {
+ memcpy (np, cur_ptr, cur_len);
+ np += cur_len;
+ ITER_SETBYTES (iter, cur_len);
+ }
+ else if (show_malformed_multibyte_p || mbi_cur (iter).wc_valid)
+ *np++ = *cur_ptr;
+ }
+ else
+ {
+ memcpy (np, cur_ptr, cur_len);
+ np += cur_len;
+ }
+ prev_len = cur_len;
+ }
+ *np = 0;
+
+ strcpy (manpage, newpage);
+ free (newpage);
+}
+
+static void
+line_map_init (LINE_MAP *map, NODE *node, int line)
+{
+ map->node = node;
+ map->nline = line;
+ map->used = 0;
+}
+
+static void
+line_map_add (LINE_MAP *map, long pos)
+{
+ if (map->used == map->size)
+ {
+ if (map->size == 0)
+ map->size = 80; /* Initial allocation */
+ map->map = x2nrealloc (map->map,
+ &map->size,
+ sizeof (map->map[0]));
+ }
+
+ map->map[map->used++] = pos;
+}
+
+/* Initialize (clear) WIN's line map. */
+void
+window_line_map_init (WINDOW *win)
+{
+ win->line_map.used = 0;
+}
+
+/* Scan the line number LINE in WIN. If PHYS is true, stop scanning at
+ the end of physical line, i.e. at the newline character. Otherwise,
+ stop it at the end of logical line.
+
+ If FUN is supplied, call it for each processed multibyte character.
+ Arguments of FUN are
+
+ closure - Function-specific data passed as 5th argument to
+ window_scan_line;
+ cpos - Current point value;
+ replen - Size of screen representation of this character, in
+ columns. This value may be 0 (for ANSI sequences and
+ info tags), or > 1 (for tabs).
+ */
+int
+window_scan_line (WINDOW *win, int line, int phys,
+ void (*fun) (void *closure, long cpos, int replen),
+ void *closure)
+{
+ mbi_iterator_t iter;
+ long cpos = win->line_starts[line] - win->node->contents;
+ int delim = 0;
+ char *endp;
+
+ if (!phys && line + 1 < win->line_count)
+ endp = win->line_starts[line + 1];
+ else
+ endp = win->node->contents + win->node->nodelen;
+
+ for (mbi_init (iter,
+ win->line_starts[line],
+ win->node->contents + win->node->nodelen -
+ win->line_starts[line]);
+ !delim && mbi_avail (iter);
+ mbi_advance (iter))
+ {
+ const char *cur_ptr = mbi_cur_ptr (iter);
+ int cur_len = mb_len (mbi_cur (iter));
+ int replen;
+
+ if (cur_ptr >= endp)
+ break;
+
+ if (mb_isprint (mbi_cur (iter)))
+ {
+ replen = 1;
+ }
+ else if (cur_len == 1)
+ {
+ if (*cur_ptr == '\r' || *cur_ptr == '\n')
+ {
+ replen = 1;
+ delim = 1;
+ }
+ else if (ansi_escape (iter, &cur_len))
+ {
+ ITER_SETBYTES (iter, cur_len);
+ replen = 0;
+ }
+ else if (info_tag (iter, 0, &cur_len))
+ {
+ ITER_SETBYTES (iter, cur_len);
+ cpos += cur_len;
+ replen = 0;
+ }
+ else
+ {
+ printed_representation (cur_ptr, cur_len,
+ win->line_map.used,
+ &replen);
+ }
+ }
+ else
+ {
+ /* FIXME: I'm not sure it's the best way to deal with unprintable
+ multibyte characters */
+ printed_representation (cur_ptr, cur_len, win->line_map.used,
+ &replen);
+ }
+
+ if (fun)
+ fun (closure, cpos, replen);
+ cpos += cur_len;
+ }
+ return cpos;
+}
+
+static void
+add_line_map (void *closure, long cpos, int replen)
+{
+ WINDOW *win = closure;
+
+ while (replen--)
+ line_map_add (&win->line_map, cpos);
+}
+
+/* Compute the line map for the current line in WIN. */
+void
+window_compute_line_map (WINDOW *win)
+{
+ int line = window_line_of_point (win);
+
+ if (win->line_map.node == win->node && win->line_map.nline == line
+ && win->line_map.used)
+ return;
+ line_map_init (&win->line_map, win->node, line);
+ if (win->node)
+ window_scan_line (win, line, 0, add_line_map, win);
+}
+
+/* Return offset of the end of current physical line.
+ */
+long
+window_end_of_line (WINDOW *win)
+{
+ int line = window_line_of_point (win);
+ if (win->node)
+ return window_scan_line (win, line, 1, NULL, NULL) - 1;
+ return 0;
+}
+
+/* Translate the value of POINT into a column number. If NP is given
+ store there the value of point corresponding to the beginning of a
+ multibyte character in this column.
+ */
+int
+window_point_to_column (WINDOW *win, long point, long *np)
+{
+ int i;
+
+ window_compute_line_map (win);
+ if (!win->line_map.map || point < win->line_map.map[0])
+ return 0;
+ for (i = 0; i < win->line_map.used; i++)
+ if (win->line_map.map[i] > point)
+ break;
+ if (np)
+ *np = win->line_map.map[i-1];
+ return i - 1;
+}
+
diff --git a/info/window.h b/info/window.h
new file mode 100644
index 0000000..648120d
--- /dev/null
+++ b/info/window.h
@@ -0,0 +1,270 @@
+/* window.h -- Structure and flags used in manipulating Info windows.
+ $Id: window.h,v 1.12 2008/09/13 10:02:01 gray Exp $
+
+ This file is part of GNU Info, a program for reading online documentation
+ stored in Info format.
+
+ Copyright (C) 1993, 1997, 2004, 2007 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.
+
+ Written by Brian Fox (bfox@ai.mit.edu). */
+
+#ifndef INFO_WINDOW_H
+#define INFO_WINDOW_H
+
+#include "infomap.h"
+#include "nodes.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)
+
+/* A line map structure keeps a table of point values corresponding to
+ column offsets within the current line. It is used to convert
+ point values into columns on screen and vice versa. */
+typedef struct line_map_struct
+{
+ NODE *node; /* Node to which this line pertains */
+ size_t nline; /* Line number for which the map is computed. */
+ size_t size; /* Number of elements map can accomodate */
+ size_t used; /* Number of used map slots */
+ long *map; /* The map itself */
+} LINE_MAP;
+
+/* 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
+{
+ struct window_struct *next; /* Next window in this chain. */
+ struct window_struct *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. */
+ LINE_MAP line_map; /* Current line map */
+ 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;
+
+/* 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;
+
+#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. */
+
+extern int show_malformed_multibyte_p; /* Show malformed multibyte sequences */
+
+/* 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 (WINDOW *window);
+
+/* 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 (int width, int height);
+
+/* 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 (NODE *node);
+
+/* 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 (WINDOW *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 (WINDOW *window, NODE *node);
+
+/* 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 (int width, int height);
+
+/* 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 (WINDOW *window, int amount);
+
+/* Adjust the pagetop of WINDOW such that the cursor point will be visible. */
+extern void window_adjust_pagetop (WINDOW *window);
+
+/* 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 (int style);
+
+/* Toggle the state of line wrapping in WINDOW. This can do a bit of fancy
+ redisplay. */
+extern void window_toggle_wrap (WINDOW *window);
+
+/* For every window in CHAIN, set the flags member to have FLAG set. */
+extern void window_mark_chain (WINDOW *chain, int flag);
+
+/* For every window in CHAIN, clear the flags member of FLAG. */
+extern void window_unmark_chain (WINDOW *chain, int flag);
+
+/* Make WINDOW start displaying at PERCENT percentage of its node. */
+extern void window_goto_percentage (WINDOW *window, int percent);
+
+/* Build a new node which has FORMAT printed with ARG1 and ARG2 as the
+ contents. */
+extern NODE *build_message_node (const char *format, void *arg1, void *arg2);
+
+/* Useful functions can be called from outside of window.c. */
+extern void initialize_message_buffer (void);
+
+/* Print FORMAT with ARG1,2 to the end of the current message buffer. */
+extern void printf_to_message_buffer (const char *format, void *arg1, void *arg2,
+ void *arg3);
+
+/* Convert the contents of the message buffer to a node. */
+extern NODE *message_buffer_to_node (void);
+
+/* Return the length of the most recently printed line in message buffer. */
+extern int message_buffer_length_this_line (void);
+
+/* Pad STRING to COUNT characters by inserting blanks. */
+extern int pad_to (int count, char *string);
+
+/* 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 (const char *format, void *arg1, void *arg2);
+
+/* 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 (const char *format, void *arg1, void *arg2);
+extern void unmessage_in_echo_area (void);
+
+/* Clear the echo area, removing any message that is already present.
+ The echo area is cleared immediately. */
+extern void window_clear_echo_area (void);
+
+/* 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 (NODE *node);
+
+/* 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 (WINDOW *window);
+
+/* Given WINDOW, recalculate the line starts for the node it displays. */
+extern void recalculate_line_starts (WINDOW *window);
+
+/* Return the number of characters it takes to display CHARACTER on the
+ screen at HPOS. */
+extern int character_width (int character, int hpos);
+
+/* Return the number of characters it takes to display STRING on the
+ screen at HPOS. */
+extern int string_width (char *string, int hpos);
+
+/* Return the index of the line containing point. */
+extern int window_line_of_point (WINDOW *window);
+
+/* Get and return the goal column for this window. */
+extern int window_get_goal_column (WINDOW *window);
+
+/* Get and return the printed column offset of the cursor in this window. */
+extern int window_get_cursor_column (WINDOW *window);
+
+/* Get and Set the node, pagetop, and point of WINDOW. */
+extern void window_get_state (WINDOW *window, SEARCH_STATE *state);
+extern void window_set_state (WINDOW *window, SEARCH_STATE *state);
+
+/* Count the number of characters in current line of WIN that precede
+ the printed column offset of GOAL. */
+extern int window_chars_to_goal (WINDOW *win, int goal);
+
+extern size_t process_node_text
+ (WINDOW *win, char *start, int do_tags,
+ int (*fun) (void *, size_t, const char *, char *, size_t, size_t),
+ void *closure);
+
+void clean_manpage (char *manpage);
+
+extern void window_compute_line_map (WINDOW *win);
+
+int window_point_to_column (WINDOW *win, long point, long *np);
+
+void window_line_map_init (WINDOW *win);
+
+long window_end_of_line (WINDOW *win);
+
+#endif /* not INFO_WINDOW_H */