summaryrefslogtreecommitdiff
path: root/macros/texinfo/texinfo/info
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /macros/texinfo/texinfo/info
Initial commit
Diffstat (limited to 'macros/texinfo/texinfo/info')
-rw-r--r--macros/texinfo/texinfo/info/Makefile.am177
-rw-r--r--macros/texinfo/texinfo/info/Makefile.in2636
-rw-r--r--macros/texinfo/texinfo/info/dir.c315
-rw-r--r--macros/texinfo/texinfo/info/display.c1009
-rw-r--r--macros/texinfo/texinfo/info/display.h73
-rw-r--r--macros/texinfo/texinfo/info/doc.h73
-rw-r--r--macros/texinfo/texinfo/info/dribble.c66
-rw-r--r--macros/texinfo/texinfo/info/dribble.h37
-rw-r--r--macros/texinfo/texinfo/info/echo-area.c1543
-rw-r--r--macros/texinfo/texinfo/info/echo-area.h85
-rw-r--r--macros/texinfo/texinfo/info/filesys.c587
-rw-r--r--macros/texinfo/texinfo/info/filesys.h79
-rw-r--r--macros/texinfo/texinfo/info/footnotes.c278
-rw-r--r--macros/texinfo/texinfo/info/footnotes.h41
-rw-r--r--macros/texinfo/texinfo/info/indices.c920
-rw-r--r--macros/texinfo/texinfo/info/indices.h42
-rw-r--r--macros/texinfo/texinfo/info/info-utils.c2063
-rw-r--r--macros/texinfo/texinfo/info/info-utils.h145
-rw-r--r--macros/texinfo/texinfo/info/info.c1197
-rw-r--r--macros/texinfo/texinfo/info/info.h113
-rw-r--r--macros/texinfo/texinfo/info/infodoc.c840
-rw-r--r--macros/texinfo/texinfo/info/infokey.c620
-rw-r--r--macros/texinfo/texinfo/info/infomap.c731
-rw-r--r--macros/texinfo/texinfo/info/infomap.h127
-rw-r--r--macros/texinfo/texinfo/info/infopath.c263
-rw-r--r--macros/texinfo/texinfo/info/m-x.c198
-rw-r--r--macros/texinfo/texinfo/info/makedoc.c494
-rw-r--r--macros/texinfo/texinfo/info/man.c602
-rw-r--r--macros/texinfo/texinfo/info/man.h27
-rw-r--r--macros/texinfo/texinfo/info/nodemenu.c283
-rw-r--r--macros/texinfo/texinfo/info/nodes.c1331
-rw-r--r--macros/texinfo/texinfo/info/nodes.h191
-rw-r--r--macros/texinfo/texinfo/info/pcterm.c1721
-rw-r--r--macros/texinfo/texinfo/info/pseudotty.c208
-rw-r--r--macros/texinfo/texinfo/info/search.c746
-rw-r--r--macros/texinfo/texinfo/info/search.h94
-rw-r--r--macros/texinfo/texinfo/info/session.c5703
-rw-r--r--macros/texinfo/texinfo/info/session.h131
-rw-r--r--macros/texinfo/texinfo/info/signals.c345
-rw-r--r--macros/texinfo/texinfo/info/signals.h97
-rw-r--r--macros/texinfo/texinfo/info/t/Infokey-config11
-rw-r--r--macros/texinfo/texinfo/info/t/Init-test.inc183
-rw-r--r--macros/texinfo/texinfo/info/t/README61
-rw-r--r--macros/texinfo/texinfo/info/t/adjust-anchors.sh43
-rw-r--r--macros/texinfo/texinfo/info/t/all-only.sh34
-rw-r--r--macros/texinfo/texinfo/info/t/anchor-positions.sh28
-rw-r--r--macros/texinfo/texinfo/info/t/body-start.sh35
-rw-r--r--macros/texinfo/texinfo/info/t/c-u-m-x-scroll-forward.sh43
-rw-r--r--macros/texinfo/texinfo/info/t/close-window-after-search.sh28
-rw-r--r--macros/texinfo/texinfo/info/t/dir-3-menus.sh23
-rw-r--r--macros/texinfo/texinfo/info/t/dir-dangling-entry.sh24
-rw-r--r--macros/texinfo/texinfo/info/t/dir-entry-to-subdir.sh29
-rw-r--r--macros/texinfo/texinfo/info/t/dir-file-index.sh22
-rw-r--r--macros/texinfo/texinfo/info/t/dir-file-menu.sh22
-rw-r--r--macros/texinfo/texinfo/info/t/dir-file-no-menu.sh22
-rw-r--r--macros/texinfo/texinfo/info/t/dir-file-node.sh24
-rw-r--r--macros/texinfo/texinfo/info/t/dir-file-sloppily.sh21
-rw-r--r--macros/texinfo/texinfo/info/t/dir-file.sh21
-rw-r--r--macros/texinfo/texinfo/info/t/dir-menus-sloppily.sh23
-rw-r--r--macros/texinfo/texinfo/info/t/dir-merge.sh28
-rw-r--r--macros/texinfo/texinfo/info/t/dir-no-file.sh27
-rw-r--r--macros/texinfo/texinfo/info/t/dir-nondir.sh21
-rw-r--r--macros/texinfo/texinfo/info/t/dir.sh21
-rw-r--r--macros/texinfo/texinfo/info/t/echo-area-no-completions.sh27
-rw-r--r--macros/texinfo/texinfo/info/t/empty-infopath.sh30
-rw-r--r--macros/texinfo/texinfo/info/t/empty.sh25
-rw-r--r--macros/texinfo/texinfo/info/t/end-of-line.sh35
-rw-r--r--macros/texinfo/texinfo/info/t/file-file-with-dot.sh22
-rw-r--r--macros/texinfo/texinfo/info/t/file-index-colons.sh22
-rw-r--r--macros/texinfo/texinfo/info/t/file-index.sh22
-rw-r--r--macros/texinfo/texinfo/info/t/file-node-with-dot.sh23
-rw-r--r--macros/texinfo/texinfo/info/t/file-node.sh23
-rw-r--r--macros/texinfo/texinfo/info/t/file-nodes.sh28
-rw-r--r--macros/texinfo/texinfo/info/t/file-relative-path.sh21
-rw-r--r--macros/texinfo/texinfo/info/t/file.sh34
-rw-r--r--macros/texinfo/texinfo/info/t/gc-split.sh28
-rw-r--r--macros/texinfo/texinfo/info/t/goal-column.sh31
-rw-r--r--macros/texinfo/texinfo/info/t/goto-quoted.sh35
-rw-r--r--macros/texinfo/texinfo/info/t/help.sh21
-rw-r--r--macros/texinfo/texinfo/info/t/inc-sea-bs.sh31
-rw-r--r--macros/texinfo/texinfo/info/t/inc-sea-forward-nonregex.sh36
-rw-r--r--macros/texinfo/texinfo/info/t/inc-sea-forward.sh36
-rw-r--r--macros/texinfo/texinfo/info/t/inc-sea-history.sh40
-rw-r--r--macros/texinfo/texinfo/info/t/inc-sea-insensitive.sh35
-rw-r--r--macros/texinfo/texinfo/info/t/index-apropos.sh31
-rw-r--r--macros/texinfo/texinfo/info/t/index-completing.sh29
-rw-r--r--macros/texinfo/texinfo/info/t/index-long-nodeline.sh34
-rw-r--r--macros/texinfo/texinfo/info/t/index-search.sh30
-rw-r--r--macros/texinfo/texinfo/info/t/index.sh34
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/anchor-positions.info32
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/anchors.info51
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/body-start.info4
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/dir24
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/file-menu.info41
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/file1.info5
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/inc-sea-history.info16
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/index-search.infobin0 -> 248 bytes
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/intera.infobin0 -> 1280 bytes
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/last-node-not-top-level.info15
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/malformed-split.info4
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/no-xref.info4
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/quoting.info19
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/sample.info4
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/search.info17
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/split.info15
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/split.info-18
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/split.info-2bin0 -> 778 bytes
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/subdir/dir16
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/subdir/file-in-subdir.info5
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/subdir/split-in-subdir.info15
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/subdir/split-in-subdir.info-18
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/subdir/split-in-subdir.info-2bin0 -> 778 bytes
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/subfile-initial-match-14
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/subfile-initial-match-104
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/subfile-initial-match.info11
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/tab-skip-node.info16
-rw-r--r--macros/texinfo/texinfo/info/t/infodir/xref_to_anchor.info23
-rw-r--r--macros/texinfo/texinfo/info/t/last-no-history.sh27
-rw-r--r--macros/texinfo/texinfo/info/t/last-node-not-top-level.sh30
-rw-r--r--macros/texinfo/texinfo/info/t/malformed-split.sh27
-rw-r--r--macros/texinfo/texinfo/info/t/menu-sequence.sh33
-rw-r--r--macros/texinfo/texinfo/info/t/multiple-completions.sh30
-rw-r--r--macros/texinfo/texinfo/info/t/next-quoted.sh34
-rw-r--r--macros/texinfo/texinfo/info/t/no-file.sh22
-rw-r--r--macros/texinfo/texinfo/info/t/no-index.sh26
-rw-r--r--macros/texinfo/texinfo/info/t/node-no-file.sh22
-rw-r--r--macros/texinfo/texinfo/info/t/node-target3
-rw-r--r--macros/texinfo/texinfo/info/t/quoted-label-and-target.sh35
-rw-r--r--macros/texinfo/texinfo/info/t/quoted-label-as-target.sh34
-rw-r--r--macros/texinfo/texinfo/info/t/quoted-manual-in-label.sh34
-rw-r--r--macros/texinfo/texinfo/info/t/quoted-manual-in-target.sh35
-rw-r--r--macros/texinfo/texinfo/info/t/quoted-target.sh34
-rw-r--r--macros/texinfo/texinfo/info/t/relative-path.sh22
-rw-r--r--macros/texinfo/texinfo/info/t/relative-reference.sh29
-rw-r--r--macros/texinfo/texinfo/info/t/replace-viewed.sh46
-rw-r--r--macros/texinfo/texinfo/info/t/resize-in-completions.sh33
-rw-r--r--macros/texinfo/texinfo/info/t/search-after-tag.sh35
-rw-r--r--macros/texinfo/texinfo/info/t/search-empty.sh26
-rw-r--r--macros/texinfo/texinfo/info/t/search-skip-screen.sh32
-rw-r--r--macros/texinfo/texinfo/info/t/search-split-after-index.sh28
-rw-r--r--macros/texinfo/texinfo/info/t/spec-file-node.sh20
-rw-r--r--macros/texinfo/texinfo/info/t/spec-menu.sh23
-rw-r--r--macros/texinfo/texinfo/info/t/spec-no-node.sh20
-rw-r--r--macros/texinfo/texinfo/info/t/split-file-menu.sh35
-rw-r--r--macros/texinfo/texinfo/info/t/split-footnotes.sh26
-rw-r--r--macros/texinfo/texinfo/info/t/split-index.sh34
-rw-r--r--macros/texinfo/texinfo/info/t/split.sh22
-rw-r--r--macros/texinfo/texinfo/info/t/star-note-non-whitespace.sh32
-rw-r--r--macros/texinfo/texinfo/info/t/tab-argument.sh28
-rw-r--r--macros/texinfo/texinfo/info/t/tab-no-xref.sh26
-rw-r--r--macros/texinfo/texinfo/info/t/tab-skip-node.sh31
-rw-r--r--macros/texinfo/texinfo/info/t/tab.sh33
-rw-r--r--macros/texinfo/texinfo/info/t/where-dir-file.sh23
-rw-r--r--macros/texinfo/texinfo/info/t/window-split-dir.sh29
-rw-r--r--macros/texinfo/texinfo/info/t/xref-across-lines-2.sh27
-rw-r--r--macros/texinfo/texinfo/info/t/xref-across-lines.sh27
-rw-r--r--macros/texinfo/texinfo/info/t/xref-to-anchor.sh37
-rw-r--r--macros/texinfo/texinfo/info/tag.c243
-rw-r--r--macros/texinfo/texinfo/info/tag.h26
-rw-r--r--macros/texinfo/texinfo/info/termdep.h62
-rw-r--r--macros/texinfo/texinfo/info/terminal.c1297
-rw-r--r--macros/texinfo/texinfo/info/terminal.h180
-rw-r--r--macros/texinfo/texinfo/info/tilde.c105
-rw-r--r--macros/texinfo/texinfo/info/tilde.h29
-rw-r--r--macros/texinfo/texinfo/info/variables.c534
-rw-r--r--macros/texinfo/texinfo/info/variables.h90
-rw-r--r--macros/texinfo/texinfo/info/window.c1331
-rw-r--r--macros/texinfo/texinfo/info/window.h243
168 files changed, 33439 insertions, 0 deletions
diff --git a/macros/texinfo/texinfo/info/Makefile.am b/macros/texinfo/texinfo/info/Makefile.am
new file mode 100644
index 0000000000..73c6d250a4
--- /dev/null
+++ b/macros/texinfo/texinfo/info/Makefile.am
@@ -0,0 +1,177 @@
+# $Id$
+# Makefile.am for texinfo/info.
+#
+# Copyright 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012,
+# 2013, 2014, 2015, 2016, 2017, 2018, 2019 Free Software Foundation, Inc.
+#
+# 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
+transform = s/ginfo/info/; $(program_transform_name)
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/gnulib/lib \
+ -I$(top_builddir)/gnulib/lib \
+ -DLOCALEDIR=\"$(datadir)/locale\" \
+ -DINFODIR=\"$(infodir)\" \
+ -DINFODIR2=\"$(datadir)/info\"
+
+LDADD = $(top_builddir)/gnulib/lib/libgnu.a $(TERMLIBS) $(LIBINTL) $(LIBICONV) $(LIBTHREAD)
+
+EXTRA_DIST = 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 indices.c indices.h \
+ info-utils.c info-utils.h info.c info.h infodoc.c infokey.c \
+ infomap.c infomap.h infopath.c 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 \
+ tag.c tag.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
+
+# The files `doc.c' and `funs.h' are created by ./makedoc run over the
+# source files which contain DECLARE_INFO_COMMAND. `funs.h' is a header file
+# listing the functions found. `doc.c' is a structure containing pointers
+# to those functions along with completable names and documentation strings.
+#
+generated_sources = doc.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: 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
+
+# Test suite
+
+if have_ptys
+check_PROGRAMS = pseudotty
+pseudotty_SOURCES = pseudotty.c
+endif
+
+TESTS = \
+ t/file.sh \
+ t/file-node.sh \
+ t/file-nodes.sh \
+ t/file-node-with-dot.sh \
+ t/file-index.sh \
+ t/file-index-colons.sh \
+ t/file-file-with-dot.sh \
+ t/no-file.sh \
+ t/node-no-file.sh \
+ t/split.sh \
+ t/malformed-split.sh \
+ t/relative-path.sh \
+ t/file-relative-path.sh \
+ t/dir.sh \
+ t/dir-file.sh \
+ t/dir-file-sloppily.sh \
+ t/dir-merge.sh \
+ t/dir-nondir.sh \
+ t/dir-no-file.sh \
+ t/dir-file-menu.sh \
+ t/dir-file-no-menu.sh \
+ t/dir-file-node.sh \
+ t/dir-file-index.sh \
+ t/dir-3-menus.sh \
+ t/dir-menus-sloppily.sh \
+ t/where-dir-file.sh \
+ t/dir-dangling-entry.sh \
+ t/dir-entry-to-subdir.sh \
+ t/spec-file-node.sh \
+ t/spec-no-node.sh \
+ t/spec-menu.sh \
+ t/all-only.sh \
+ t/empty.sh \
+ t/tab.sh \
+ t/tab-argument.sh \
+ t/tab-no-xref.sh \
+ t/tab-skip-node.sh \
+ t/xref-across-lines.sh \
+ t/xref-across-lines-2.sh \
+ t/body-start.sh \
+ t/end-of-line.sh \
+ t/goal-column.sh \
+ t/star-note-non-whitespace.sh \
+ t/c-u-m-x-scroll-forward.sh \
+ t/last-no-history.sh \
+ t/split-footnotes.sh \
+ t/window-split-dir.sh \
+ t/last-node-not-top-level.sh \
+ t/adjust-anchors.sh \
+ t/xref-to-anchor.sh \
+ t/search-after-tag.sh \
+ t/split-file-menu.sh \
+ t/search-split-after-index.sh \
+ t/index.sh \
+ t/no-index.sh \
+ t/index-apropos.sh \
+ t/index-search.sh \
+ t/index-completing.sh \
+ t/split-index.sh \
+ t/index-long-nodeline.sh \
+ t/empty-infopath.sh \
+ t/menu-sequence.sh \
+ t/relative-reference.sh \
+ t/resize-in-completions.sh \
+ t/replace-viewed.sh \
+ t/search-skip-screen.sh \
+ t/search-empty.sh \
+ t/close-window-after-search.sh \
+ t/inc-sea-forward.sh \
+ t/inc-sea-forward-nonregex.sh \
+ t/inc-sea-insensitive.sh \
+ t/inc-sea-history.sh \
+ t/inc-sea-bs.sh \
+ t/gc-split.sh \
+ t/anchor-positions.sh \
+ t/quoted-label-as-target.sh \
+ t/quoted-target.sh \
+ t/quoted-label-and-target.sh \
+ t/quoted-manual-in-target.sh \
+ t/quoted-manual-in-label.sh \
+ t/goto-quoted.sh \
+ t/next-quoted.sh \
+ t/echo-area-no-completions.sh \
+ t/multiple-completions.sh \
+ t/help.sh
+
+EXTRA_DIST += $(TESTS) $(XFAIL_TESTS) \
+ t/README t/infodir \
+ t/Init-test.inc \
+ t/Infokey-config t/node-target
+dist-hook:
+ rm -rf `find $(distdir)/t/infodir -name '*~'`
diff --git a/macros/texinfo/texinfo/info/Makefile.in b/macros/texinfo/texinfo/info/Makefile.in
new file mode 100644
index 0000000000..798940219b
--- /dev/null
+++ b/macros/texinfo/texinfo/info/Makefile.in
@@ -0,0 +1,2636 @@
+# Makefile.in generated by automake 1.16 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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 for texinfo/info.
+#
+# Copyright 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012,
+# 2013, 2014, 2015, 2016, 2017, 2018, 2019 Free Software Foundation, Inc.
+#
+# 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@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@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)
+noinst_PROGRAMS = makedoc$(EXEEXT)
+@have_ptys_TRUE@check_PROGRAMS = pseudotty$(EXEEXT)
+subdir = info
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/gnulib/m4/00gnulib.m4 \
+ $(top_srcdir)/gnulib/m4/__inline.m4 \
+ $(top_srcdir)/gnulib/m4/absolute-header.m4 \
+ $(top_srcdir)/gnulib/m4/alloca.m4 \
+ $(top_srcdir)/gnulib/m4/argz.m4 \
+ $(top_srcdir)/gnulib/m4/btowc.m4 \
+ $(top_srcdir)/gnulib/m4/builtin-expect.m4 \
+ $(top_srcdir)/gnulib/m4/codeset.m4 \
+ $(top_srcdir)/gnulib/m4/dirname.m4 \
+ $(top_srcdir)/gnulib/m4/double-slash-root.m4 \
+ $(top_srcdir)/gnulib/m4/eealloc.m4 \
+ $(top_srcdir)/gnulib/m4/errno_h.m4 \
+ $(top_srcdir)/gnulib/m4/error.m4 \
+ $(top_srcdir)/gnulib/m4/exponentd.m4 \
+ $(top_srcdir)/gnulib/m4/extensions.m4 \
+ $(top_srcdir)/gnulib/m4/extern-inline.m4 \
+ $(top_srcdir)/gnulib/m4/float_h.m4 \
+ $(top_srcdir)/gnulib/m4/getopt.m4 \
+ $(top_srcdir)/gnulib/m4/getprogname.m4 \
+ $(top_srcdir)/gnulib/m4/gettext.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/host-cpu-c-abi.m4 \
+ $(top_srcdir)/gnulib/m4/iconv.m4 \
+ $(top_srcdir)/gnulib/m4/include_next.m4 \
+ $(top_srcdir)/gnulib/m4/intlmacosx.m4 \
+ $(top_srcdir)/gnulib/m4/intmax_t.m4 \
+ $(top_srcdir)/gnulib/m4/inttypes_h.m4 \
+ $(top_srcdir)/gnulib/m4/iswblank.m4 \
+ $(top_srcdir)/gnulib/m4/langinfo_h.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/libunistring-base.m4 \
+ $(top_srcdir)/gnulib/m4/limits-h.m4 \
+ $(top_srcdir)/gnulib/m4/localcharset.m4 \
+ $(top_srcdir)/gnulib/m4/locale-fr.m4 \
+ $(top_srcdir)/gnulib/m4/locale-ja.m4 \
+ $(top_srcdir)/gnulib/m4/locale-zh.m4 \
+ $(top_srcdir)/gnulib/m4/locale_h.m4 \
+ $(top_srcdir)/gnulib/m4/localeconv.m4 \
+ $(top_srcdir)/gnulib/m4/lock.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/mbsinit.m4 \
+ $(top_srcdir)/gnulib/m4/mbslen.m4 \
+ $(top_srcdir)/gnulib/m4/mbstate_t.m4 \
+ $(top_srcdir)/gnulib/m4/mbswidth.m4 \
+ $(top_srcdir)/gnulib/m4/mbtowc.m4 \
+ $(top_srcdir)/gnulib/m4/memchr.m4 \
+ $(top_srcdir)/gnulib/m4/mempcpy.m4 \
+ $(top_srcdir)/gnulib/m4/memrchr.m4 \
+ $(top_srcdir)/gnulib/m4/mmap-anon.m4 \
+ $(top_srcdir)/gnulib/m4/msvc-inval.m4 \
+ $(top_srcdir)/gnulib/m4/msvc-nothrow.m4 \
+ $(top_srcdir)/gnulib/m4/multiarch.m4 \
+ $(top_srcdir)/gnulib/m4/nl_langinfo.m4 \
+ $(top_srcdir)/gnulib/m4/nls.m4 \
+ $(top_srcdir)/gnulib/m4/nocrash.m4 \
+ $(top_srcdir)/gnulib/m4/off_t.m4 $(top_srcdir)/gnulib/m4/po.m4 \
+ $(top_srcdir)/gnulib/m4/printf.m4 \
+ $(top_srcdir)/gnulib/m4/progtest.m4 \
+ $(top_srcdir)/gnulib/m4/pthread_rwlock_rdlock.m4 \
+ $(top_srcdir)/gnulib/m4/regex.m4 \
+ $(top_srcdir)/gnulib/m4/size_max.m4 \
+ $(top_srcdir)/gnulib/m4/ssize_t.m4 \
+ $(top_srcdir)/gnulib/m4/stdarg.m4 \
+ $(top_srcdir)/gnulib/m4/stdbool.m4 \
+ $(top_srcdir)/gnulib/m4/stddef_h.m4 \
+ $(top_srcdir)/gnulib/m4/stdint.m4 \
+ $(top_srcdir)/gnulib/m4/stdint_h.m4 \
+ $(top_srcdir)/gnulib/m4/stdio_h.m4 \
+ $(top_srcdir)/gnulib/m4/stdlib_h.m4 \
+ $(top_srcdir)/gnulib/m4/stpcpy.m4 \
+ $(top_srcdir)/gnulib/m4/strcase.m4 \
+ $(top_srcdir)/gnulib/m4/strcasestr.m4 \
+ $(top_srcdir)/gnulib/m4/strdup.m4 \
+ $(top_srcdir)/gnulib/m4/strerror.m4 \
+ $(top_srcdir)/gnulib/m4/string_h.m4 \
+ $(top_srcdir)/gnulib/m4/strings_h.m4 \
+ $(top_srcdir)/gnulib/m4/strndup.m4 \
+ $(top_srcdir)/gnulib/m4/strnlen.m4 \
+ $(top_srcdir)/gnulib/m4/strstr.m4 \
+ $(top_srcdir)/gnulib/m4/sys_socket_h.m4 \
+ $(top_srcdir)/gnulib/m4/sys_types_h.m4 \
+ $(top_srcdir)/gnulib/m4/threadlib.m4 \
+ $(top_srcdir)/gnulib/m4/unistd_h.m4 \
+ $(top_srcdir)/gnulib/m4/vasnprintf.m4 \
+ $(top_srcdir)/gnulib/m4/vasprintf.m4 \
+ $(top_srcdir)/gnulib/m4/warn-on-use.m4 \
+ $(top_srcdir)/gnulib/m4/wchar_h.m4 \
+ $(top_srcdir)/gnulib/m4/wchar_t.m4 \
+ $(top_srcdir)/gnulib/m4/wcrtomb.m4 \
+ $(top_srcdir)/gnulib/m4/wctype_h.m4 \
+ $(top_srcdir)/gnulib/m4/wcwidth.m4 \
+ $(top_srcdir)/gnulib/m4/wint_t.m4 \
+ $(top_srcdir)/gnulib/m4/xalloc.m4 \
+ $(top_srcdir)/gnulib/m4/xsize.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS) $(noinst_PROGRAMS)
+am_ginfo_OBJECTS = dir.$(OBJEXT) display.$(OBJEXT) dribble.$(OBJEXT) \
+ echo-area.$(OBJEXT) filesys.$(OBJEXT) footnotes.$(OBJEXT) \
+ indices.$(OBJEXT) info-utils.$(OBJEXT) info.$(OBJEXT) \
+ infodoc.$(OBJEXT) infokey.$(OBJEXT) infomap.$(OBJEXT) \
+ infopath.$(OBJEXT) m-x.$(OBJEXT) man.$(OBJEXT) \
+ nodemenu.$(OBJEXT) nodes.$(OBJEXT) search.$(OBJEXT) \
+ session.$(OBJEXT) signals.$(OBJEXT) tag.$(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 = $(top_builddir)/gnulib/lib/libgnu.a \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am_makedoc_OBJECTS = makedoc.$(OBJEXT)
+makedoc_OBJECTS = $(am_makedoc_OBJECTS)
+makedoc_LDADD = $(LDADD)
+makedoc_DEPENDENCIES = $(top_builddir)/gnulib/lib/libgnu.a \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am__pseudotty_SOURCES_DIST = pseudotty.c
+@have_ptys_TRUE@am_pseudotty_OBJECTS = pseudotty.$(OBJEXT)
+pseudotty_OBJECTS = $(am_pseudotty_OBJECTS)
+pseudotty_LDADD = $(LDADD)
+pseudotty_DEPENDENCIES = $(top_builddir)/gnulib/lib/libgnu.a \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/dir.Po ./$(DEPDIR)/display.Po \
+ ./$(DEPDIR)/doc.Po ./$(DEPDIR)/dribble.Po \
+ ./$(DEPDIR)/echo-area.Po ./$(DEPDIR)/filesys.Po \
+ ./$(DEPDIR)/footnotes.Po ./$(DEPDIR)/indices.Po \
+ ./$(DEPDIR)/info-utils.Po ./$(DEPDIR)/info.Po \
+ ./$(DEPDIR)/infodoc.Po ./$(DEPDIR)/infokey.Po \
+ ./$(DEPDIR)/infomap.Po ./$(DEPDIR)/infopath.Po \
+ ./$(DEPDIR)/m-x.Po ./$(DEPDIR)/makedoc.Po ./$(DEPDIR)/man.Po \
+ ./$(DEPDIR)/nodemenu.Po ./$(DEPDIR)/nodes.Po \
+ ./$(DEPDIR)/pseudotty.Po ./$(DEPDIR)/search.Po \
+ ./$(DEPDIR)/session.Po ./$(DEPDIR)/signals.Po \
+ ./$(DEPDIR)/tag.Po ./$(DEPDIR)/terminal.Po \
+ ./$(DEPDIR)/tilde.Po ./$(DEPDIR)/variables.Po \
+ ./$(DEPDIR)/window.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(ginfo_SOURCES) $(nodist_ginfo_SOURCES) $(makedoc_SOURCES) \
+ $(pseudotty_SOURCES)
+DIST_SOURCES = $(ginfo_SOURCES) $(makedoc_SOURCES) \
+ $(am__pseudotty_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__tty_colors_dummy = \
+ mgn= red= grn= lgn= blu= brg= std=; \
+ am__color_tests=no
+am__tty_colors = { \
+ $(am__tty_colors_dummy); \
+ if test "X$(AM_COLOR_TESTS)" = Xno; then \
+ am__color_tests=no; \
+ elif test "X$(AM_COLOR_TESTS)" = Xalways; then \
+ am__color_tests=yes; \
+ elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \
+ am__color_tests=yes; \
+ fi; \
+ if test $$am__color_tests = yes; then \
+ red=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ fi; \
+}
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+am__recheck_rx = ^[ ]*:recheck:[ ]*
+am__global_test_result_rx = ^[ ]*:global-test-result:[ ]*
+am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]*
+# A command that, given a newline-separated list of test names on the
+# standard input, print the name of the tests that are to be re-run
+# upon "make recheck".
+am__list_recheck_tests = $(AWK) '{ \
+ recheck = 1; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ { \
+ if ((getline line2 < ($$0 ".log")) < 0) \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \
+ { \
+ recheck = 0; \
+ break; \
+ } \
+ else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \
+ { \
+ break; \
+ } \
+ }; \
+ if (recheck) \
+ print $$0; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# A command that, given a newline-separated list of test names on the
+# standard input, create the global log from their .trs and .log files.
+am__create_global_log = $(AWK) ' \
+function fatal(msg) \
+{ \
+ print "fatal: making $@: " msg | "cat >&2"; \
+ exit 1; \
+} \
+function rst_section(header) \
+{ \
+ print header; \
+ len = length(header); \
+ for (i = 1; i <= len; i = i + 1) \
+ printf "="; \
+ printf "\n\n"; \
+} \
+{ \
+ copy_in_global_log = 1; \
+ global_test_result = "RUN"; \
+ while ((rc = (getline line < ($$0 ".trs"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".trs"); \
+ if (line ~ /$(am__global_test_result_rx)/) \
+ { \
+ sub("$(am__global_test_result_rx)", "", line); \
+ sub("[ ]*$$", "", line); \
+ global_test_result = line; \
+ } \
+ else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \
+ copy_in_global_log = 0; \
+ }; \
+ if (copy_in_global_log) \
+ { \
+ rst_section(global_test_result ": " $$0); \
+ while ((rc = (getline line < ($$0 ".log"))) != 0) \
+ { \
+ if (rc < 0) \
+ fatal("failed to read from " $$0 ".log"); \
+ print line; \
+ }; \
+ printf "\n"; \
+ }; \
+ close ($$0 ".trs"); \
+ close ($$0 ".log"); \
+}'
+# Restructured Text title.
+am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; }
+# Solaris 10 'make', and several other traditional 'make' implementations,
+# pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it
+# by disabling -e (using the XSI extension "set +e") if it's set.
+am__sh_e_setup = case $$- in *e*) set +e;; esac
+# Default flags passed to test drivers.
+am__common_driver_flags = \
+ --color-tests "$$am__color_tests" \
+ --enable-hard-errors "$$am__enable_hard_errors" \
+ --expect-failure "$$am__expect_failure"
+# To be inserted before the command running the test. Creates the
+# directory for the log if needed. Stores in $dir the directory
+# containing $f, in $tst the test, in $log the log. Executes the
+# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and
+# passes TESTS_ENVIRONMENT. Set up options for the wrapper that
+# will run the test scripts (or their associated LOG_COMPILER, if
+# thy have one).
+am__check_pre = \
+$(am__sh_e_setup); \
+$(am__vpath_adj_setup) $(am__vpath_adj) \
+$(am__tty_colors); \
+srcdir=$(srcdir); export srcdir; \
+case "$@" in \
+ */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \
+ *) am__odir=.;; \
+esac; \
+test "x$$am__odir" = x"." || test -d "$$am__odir" \
+ || $(MKDIR_P) "$$am__odir" || exit $$?; \
+if test -f "./$$f"; then dir=./; \
+elif test -f "$$f"; then dir=; \
+else dir="$(srcdir)/"; fi; \
+tst=$$dir$$f; log='$@'; \
+if test -n '$(DISABLE_HARD_ERRORS)'; then \
+ am__enable_hard_errors=no; \
+else \
+ am__enable_hard_errors=yes; \
+fi; \
+case " $(XFAIL_TESTS) " in \
+ *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \
+ am__expect_failure=yes;; \
+ *) \
+ am__expect_failure=no;; \
+esac; \
+$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT)
+# A shell command to get the names of the tests scripts with any registered
+# extension removed (i.e., equivalently, the names of the test logs, with
+# the '.log' extension removed). The result is saved in the shell variable
+# '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly,
+# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)",
+# since that might cause problem with VPATH rewrites for suffix-less tests.
+# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'.
+am__set_TESTS_bases = \
+ bases='$(TEST_LOGS)'; \
+ bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \
+ bases=`echo $$bases`
+RECHECK_LOGS = $(TEST_LOGS)
+AM_RECURSIVE_TARGETS = check recheck
+TEST_SUITE_LOG = test-suite.log
+TEST_EXTENSIONS = @EXEEXT@ .test
+LOG_DRIVER = $(SHELL) $(top_srcdir)/build-aux/test-driver
+LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS)
+am__set_b = \
+ case '$@' in \
+ */*) \
+ case '$*' in \
+ */*) b='$*';; \
+ *) b=`echo '$@' | sed 's/\.log$$//'`; \
+ esac;; \
+ *) \
+ b='$*';; \
+ esac
+am__test_logs1 = $(TESTS:=.log)
+am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log)
+TEST_LOGS = $(am__test_logs2:.test.log=.log)
+TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/build-aux/test-driver
+TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \
+ $(TEST_LOG_FLAGS)
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(top_srcdir)/build-aux/depcomp \
+ $(top_srcdir)/build-aux/test-driver
+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@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPLE_UNIVERSAL_BUILD = @APPLE_UNIVERSAL_BUILD@
+AR = @AR@
+ARFLAGS = @ARFLAGS@
+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@
+DIFF_A_OPTION = @DIFF_A_OPTION@
+DIFF_U_OPTION = @DIFF_U_OPTION@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EMULTIHOP_HIDDEN = @EMULTIHOP_HIDDEN@
+EMULTIHOP_VALUE = @EMULTIHOP_VALUE@
+ENOLINK_HIDDEN = @ENOLINK_HIDDEN@
+ENOLINK_VALUE = @ENOLINK_VALUE@
+EOVERFLOW_HIDDEN = @EOVERFLOW_HIDDEN@
+EOVERFLOW_VALUE = @EOVERFLOW_VALUE@
+ERRNO_H = @ERRNO_H@
+EXEEXT = @EXEEXT@
+FLOAT_H = @FLOAT_H@
+GAWK = @GAWK@
+GETOPT_CDEFS_H = @GETOPT_CDEFS_H@
+GETOPT_H = @GETOPT_H@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GLIBC21 = @GLIBC21@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GNULIB_ATOLL = @GNULIB_ATOLL@
+GNULIB_BTOWC = @GNULIB_BTOWC@
+GNULIB_CALLOC_POSIX = @GNULIB_CALLOC_POSIX@
+GNULIB_CANONICALIZE_FILE_NAME = @GNULIB_CANONICALIZE_FILE_NAME@
+GNULIB_CHDIR = @GNULIB_CHDIR@
+GNULIB_CHOWN = @GNULIB_CHOWN@
+GNULIB_CLOSE = @GNULIB_CLOSE@
+GNULIB_DPRINTF = @GNULIB_DPRINTF@
+GNULIB_DUP = @GNULIB_DUP@
+GNULIB_DUP2 = @GNULIB_DUP2@
+GNULIB_DUP3 = @GNULIB_DUP3@
+GNULIB_DUPLOCALE = @GNULIB_DUPLOCALE@
+GNULIB_ENVIRON = @GNULIB_ENVIRON@
+GNULIB_EUIDACCESS = @GNULIB_EUIDACCESS@
+GNULIB_EXPLICIT_BZERO = @GNULIB_EXPLICIT_BZERO@
+GNULIB_FACCESSAT = @GNULIB_FACCESSAT@
+GNULIB_FCHDIR = @GNULIB_FCHDIR@
+GNULIB_FCHOWNAT = @GNULIB_FCHOWNAT@
+GNULIB_FCLOSE = @GNULIB_FCLOSE@
+GNULIB_FDATASYNC = @GNULIB_FDATASYNC@
+GNULIB_FDOPEN = @GNULIB_FDOPEN@
+GNULIB_FFLUSH = @GNULIB_FFLUSH@
+GNULIB_FFS = @GNULIB_FFS@
+GNULIB_FFSL = @GNULIB_FFSL@
+GNULIB_FFSLL = @GNULIB_FFSLL@
+GNULIB_FGETC = @GNULIB_FGETC@
+GNULIB_FGETS = @GNULIB_FGETS@
+GNULIB_FOPEN = @GNULIB_FOPEN@
+GNULIB_FPRINTF = @GNULIB_FPRINTF@
+GNULIB_FPRINTF_POSIX = @GNULIB_FPRINTF_POSIX@
+GNULIB_FPURGE = @GNULIB_FPURGE@
+GNULIB_FPUTC = @GNULIB_FPUTC@
+GNULIB_FPUTS = @GNULIB_FPUTS@
+GNULIB_FREAD = @GNULIB_FREAD@
+GNULIB_FREOPEN = @GNULIB_FREOPEN@
+GNULIB_FSCANF = @GNULIB_FSCANF@
+GNULIB_FSEEK = @GNULIB_FSEEK@
+GNULIB_FSEEKO = @GNULIB_FSEEKO@
+GNULIB_FSYNC = @GNULIB_FSYNC@
+GNULIB_FTELL = @GNULIB_FTELL@
+GNULIB_FTELLO = @GNULIB_FTELLO@
+GNULIB_FTRUNCATE = @GNULIB_FTRUNCATE@
+GNULIB_FWRITE = @GNULIB_FWRITE@
+GNULIB_GETC = @GNULIB_GETC@
+GNULIB_GETCHAR = @GNULIB_GETCHAR@
+GNULIB_GETCWD = @GNULIB_GETCWD@
+GNULIB_GETDELIM = @GNULIB_GETDELIM@
+GNULIB_GETDOMAINNAME = @GNULIB_GETDOMAINNAME@
+GNULIB_GETDTABLESIZE = @GNULIB_GETDTABLESIZE@
+GNULIB_GETGROUPS = @GNULIB_GETGROUPS@
+GNULIB_GETHOSTNAME = @GNULIB_GETHOSTNAME@
+GNULIB_GETLINE = @GNULIB_GETLINE@
+GNULIB_GETLOADAVG = @GNULIB_GETLOADAVG@
+GNULIB_GETLOGIN = @GNULIB_GETLOGIN@
+GNULIB_GETLOGIN_R = @GNULIB_GETLOGIN_R@
+GNULIB_GETPAGESIZE = @GNULIB_GETPAGESIZE@
+GNULIB_GETPASS = @GNULIB_GETPASS@
+GNULIB_GETSUBOPT = @GNULIB_GETSUBOPT@
+GNULIB_GETUSERSHELL = @GNULIB_GETUSERSHELL@
+GNULIB_GL_UNISTD_H_GETOPT = @GNULIB_GL_UNISTD_H_GETOPT@
+GNULIB_GRANTPT = @GNULIB_GRANTPT@
+GNULIB_GROUP_MEMBER = @GNULIB_GROUP_MEMBER@
+GNULIB_ISATTY = @GNULIB_ISATTY@
+GNULIB_ISWBLANK = @GNULIB_ISWBLANK@
+GNULIB_ISWCTYPE = @GNULIB_ISWCTYPE@
+GNULIB_LCHOWN = @GNULIB_LCHOWN@
+GNULIB_LINK = @GNULIB_LINK@
+GNULIB_LINKAT = @GNULIB_LINKAT@
+GNULIB_LOCALECONV = @GNULIB_LOCALECONV@
+GNULIB_LOCALENAME = @GNULIB_LOCALENAME@
+GNULIB_LSEEK = @GNULIB_LSEEK@
+GNULIB_MALLOC_POSIX = @GNULIB_MALLOC_POSIX@
+GNULIB_MBRLEN = @GNULIB_MBRLEN@
+GNULIB_MBRTOWC = @GNULIB_MBRTOWC@
+GNULIB_MBSCASECMP = @GNULIB_MBSCASECMP@
+GNULIB_MBSCASESTR = @GNULIB_MBSCASESTR@
+GNULIB_MBSCHR = @GNULIB_MBSCHR@
+GNULIB_MBSCSPN = @GNULIB_MBSCSPN@
+GNULIB_MBSINIT = @GNULIB_MBSINIT@
+GNULIB_MBSLEN = @GNULIB_MBSLEN@
+GNULIB_MBSNCASECMP = @GNULIB_MBSNCASECMP@
+GNULIB_MBSNLEN = @GNULIB_MBSNLEN@
+GNULIB_MBSNRTOWCS = @GNULIB_MBSNRTOWCS@
+GNULIB_MBSPBRK = @GNULIB_MBSPBRK@
+GNULIB_MBSPCASECMP = @GNULIB_MBSPCASECMP@
+GNULIB_MBSRCHR = @GNULIB_MBSRCHR@
+GNULIB_MBSRTOWCS = @GNULIB_MBSRTOWCS@
+GNULIB_MBSSEP = @GNULIB_MBSSEP@
+GNULIB_MBSSPN = @GNULIB_MBSSPN@
+GNULIB_MBSSTR = @GNULIB_MBSSTR@
+GNULIB_MBSTOK_R = @GNULIB_MBSTOK_R@
+GNULIB_MBTOWC = @GNULIB_MBTOWC@
+GNULIB_MEMCHR = @GNULIB_MEMCHR@
+GNULIB_MEMMEM = @GNULIB_MEMMEM@
+GNULIB_MEMPCPY = @GNULIB_MEMPCPY@
+GNULIB_MEMRCHR = @GNULIB_MEMRCHR@
+GNULIB_MKDTEMP = @GNULIB_MKDTEMP@
+GNULIB_MKOSTEMP = @GNULIB_MKOSTEMP@
+GNULIB_MKOSTEMPS = @GNULIB_MKOSTEMPS@
+GNULIB_MKSTEMP = @GNULIB_MKSTEMP@
+GNULIB_MKSTEMPS = @GNULIB_MKSTEMPS@
+GNULIB_NL_LANGINFO = @GNULIB_NL_LANGINFO@
+GNULIB_OBSTACK_PRINTF = @GNULIB_OBSTACK_PRINTF@
+GNULIB_OBSTACK_PRINTF_POSIX = @GNULIB_OBSTACK_PRINTF_POSIX@
+GNULIB_OVERRIDES_WINT_T = @GNULIB_OVERRIDES_WINT_T@
+GNULIB_PCLOSE = @GNULIB_PCLOSE@
+GNULIB_PERROR = @GNULIB_PERROR@
+GNULIB_PIPE = @GNULIB_PIPE@
+GNULIB_PIPE2 = @GNULIB_PIPE2@
+GNULIB_POPEN = @GNULIB_POPEN@
+GNULIB_POSIX_OPENPT = @GNULIB_POSIX_OPENPT@
+GNULIB_PREAD = @GNULIB_PREAD@
+GNULIB_PRINTF = @GNULIB_PRINTF@
+GNULIB_PRINTF_POSIX = @GNULIB_PRINTF_POSIX@
+GNULIB_PTSNAME = @GNULIB_PTSNAME@
+GNULIB_PTSNAME_R = @GNULIB_PTSNAME_R@
+GNULIB_PUTC = @GNULIB_PUTC@
+GNULIB_PUTCHAR = @GNULIB_PUTCHAR@
+GNULIB_PUTENV = @GNULIB_PUTENV@
+GNULIB_PUTS = @GNULIB_PUTS@
+GNULIB_PWRITE = @GNULIB_PWRITE@
+GNULIB_QSORT_R = @GNULIB_QSORT_R@
+GNULIB_RANDOM = @GNULIB_RANDOM@
+GNULIB_RANDOM_R = @GNULIB_RANDOM_R@
+GNULIB_RAWMEMCHR = @GNULIB_RAWMEMCHR@
+GNULIB_READ = @GNULIB_READ@
+GNULIB_READLINK = @GNULIB_READLINK@
+GNULIB_READLINKAT = @GNULIB_READLINKAT@
+GNULIB_REALLOCARRAY = @GNULIB_REALLOCARRAY@
+GNULIB_REALLOC_POSIX = @GNULIB_REALLOC_POSIX@
+GNULIB_REALPATH = @GNULIB_REALPATH@
+GNULIB_REMOVE = @GNULIB_REMOVE@
+GNULIB_RENAME = @GNULIB_RENAME@
+GNULIB_RENAMEAT = @GNULIB_RENAMEAT@
+GNULIB_RMDIR = @GNULIB_RMDIR@
+GNULIB_RPMATCH = @GNULIB_RPMATCH@
+GNULIB_SCANF = @GNULIB_SCANF@
+GNULIB_SECURE_GETENV = @GNULIB_SECURE_GETENV@
+GNULIB_SETENV = @GNULIB_SETENV@
+GNULIB_SETHOSTNAME = @GNULIB_SETHOSTNAME@
+GNULIB_SETLOCALE = @GNULIB_SETLOCALE@
+GNULIB_SLEEP = @GNULIB_SLEEP@
+GNULIB_SNPRINTF = @GNULIB_SNPRINTF@
+GNULIB_SPRINTF_POSIX = @GNULIB_SPRINTF_POSIX@
+GNULIB_STDIO_H_NONBLOCKING = @GNULIB_STDIO_H_NONBLOCKING@
+GNULIB_STDIO_H_SIGPIPE = @GNULIB_STDIO_H_SIGPIPE@
+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_STRERROR_R = @GNULIB_STRERROR_R@
+GNULIB_STRNCAT = @GNULIB_STRNCAT@
+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_STRTOLL = @GNULIB_STRTOLL@
+GNULIB_STRTOULL = @GNULIB_STRTOULL@
+GNULIB_STRVERSCMP = @GNULIB_STRVERSCMP@
+GNULIB_SYMLINK = @GNULIB_SYMLINK@
+GNULIB_SYMLINKAT = @GNULIB_SYMLINKAT@
+GNULIB_SYSTEM_POSIX = @GNULIB_SYSTEM_POSIX@
+GNULIB_TMPFILE = @GNULIB_TMPFILE@
+GNULIB_TOWCTRANS = @GNULIB_TOWCTRANS@
+GNULIB_TRUNCATE = @GNULIB_TRUNCATE@
+GNULIB_TTYNAME_R = @GNULIB_TTYNAME_R@
+GNULIB_UNISTD_H_NONBLOCKING = @GNULIB_UNISTD_H_NONBLOCKING@
+GNULIB_UNISTD_H_SIGPIPE = @GNULIB_UNISTD_H_SIGPIPE@
+GNULIB_UNLINK = @GNULIB_UNLINK@
+GNULIB_UNLINKAT = @GNULIB_UNLINKAT@
+GNULIB_UNLOCKPT = @GNULIB_UNLOCKPT@
+GNULIB_UNSETENV = @GNULIB_UNSETENV@
+GNULIB_USLEEP = @GNULIB_USLEEP@
+GNULIB_VASPRINTF = @GNULIB_VASPRINTF@
+GNULIB_VDPRINTF = @GNULIB_VDPRINTF@
+GNULIB_VFPRINTF = @GNULIB_VFPRINTF@
+GNULIB_VFPRINTF_POSIX = @GNULIB_VFPRINTF_POSIX@
+GNULIB_VFSCANF = @GNULIB_VFSCANF@
+GNULIB_VPRINTF = @GNULIB_VPRINTF@
+GNULIB_VPRINTF_POSIX = @GNULIB_VPRINTF_POSIX@
+GNULIB_VSCANF = @GNULIB_VSCANF@
+GNULIB_VSNPRINTF = @GNULIB_VSNPRINTF@
+GNULIB_VSPRINTF_POSIX = @GNULIB_VSPRINTF_POSIX@
+GNULIB_WCPCPY = @GNULIB_WCPCPY@
+GNULIB_WCPNCPY = @GNULIB_WCPNCPY@
+GNULIB_WCRTOMB = @GNULIB_WCRTOMB@
+GNULIB_WCSCASECMP = @GNULIB_WCSCASECMP@
+GNULIB_WCSCAT = @GNULIB_WCSCAT@
+GNULIB_WCSCHR = @GNULIB_WCSCHR@
+GNULIB_WCSCMP = @GNULIB_WCSCMP@
+GNULIB_WCSCOLL = @GNULIB_WCSCOLL@
+GNULIB_WCSCPY = @GNULIB_WCSCPY@
+GNULIB_WCSCSPN = @GNULIB_WCSCSPN@
+GNULIB_WCSDUP = @GNULIB_WCSDUP@
+GNULIB_WCSFTIME = @GNULIB_WCSFTIME@
+GNULIB_WCSLEN = @GNULIB_WCSLEN@
+GNULIB_WCSNCASECMP = @GNULIB_WCSNCASECMP@
+GNULIB_WCSNCAT = @GNULIB_WCSNCAT@
+GNULIB_WCSNCMP = @GNULIB_WCSNCMP@
+GNULIB_WCSNCPY = @GNULIB_WCSNCPY@
+GNULIB_WCSNLEN = @GNULIB_WCSNLEN@
+GNULIB_WCSNRTOMBS = @GNULIB_WCSNRTOMBS@
+GNULIB_WCSPBRK = @GNULIB_WCSPBRK@
+GNULIB_WCSRCHR = @GNULIB_WCSRCHR@
+GNULIB_WCSRTOMBS = @GNULIB_WCSRTOMBS@
+GNULIB_WCSSPN = @GNULIB_WCSSPN@
+GNULIB_WCSSTR = @GNULIB_WCSSTR@
+GNULIB_WCSTOK = @GNULIB_WCSTOK@
+GNULIB_WCSWIDTH = @GNULIB_WCSWIDTH@
+GNULIB_WCSXFRM = @GNULIB_WCSXFRM@
+GNULIB_WCTOB = @GNULIB_WCTOB@
+GNULIB_WCTOMB = @GNULIB_WCTOMB@
+GNULIB_WCTRANS = @GNULIB_WCTRANS@
+GNULIB_WCTYPE = @GNULIB_WCTYPE@
+GNULIB_WCWIDTH = @GNULIB_WCWIDTH@
+GNULIB_WMEMCHR = @GNULIB_WMEMCHR@
+GNULIB_WMEMCMP = @GNULIB_WMEMCMP@
+GNULIB_WMEMCPY = @GNULIB_WMEMCPY@
+GNULIB_WMEMMOVE = @GNULIB_WMEMMOVE@
+GNULIB_WMEMSET = @GNULIB_WMEMSET@
+GNULIB_WRITE = @GNULIB_WRITE@
+GNULIB__EXIT = @GNULIB__EXIT@
+GREP = @GREP@
+HAVE_ATOLL = @HAVE_ATOLL@
+HAVE_BTOWC = @HAVE_BTOWC@
+HAVE_C99_STDINT_H = @HAVE_C99_STDINT_H@
+HAVE_CANONICALIZE_FILE_NAME = @HAVE_CANONICALIZE_FILE_NAME@
+HAVE_CHOWN = @HAVE_CHOWN@
+HAVE_CRTDEFS_H = @HAVE_CRTDEFS_H@
+HAVE_DECL_ENVIRON = @HAVE_DECL_ENVIRON@
+HAVE_DECL_FCHDIR = @HAVE_DECL_FCHDIR@
+HAVE_DECL_FDATASYNC = @HAVE_DECL_FDATASYNC@
+HAVE_DECL_FPURGE = @HAVE_DECL_FPURGE@
+HAVE_DECL_FSEEKO = @HAVE_DECL_FSEEKO@
+HAVE_DECL_FTELLO = @HAVE_DECL_FTELLO@
+HAVE_DECL_GETDELIM = @HAVE_DECL_GETDELIM@
+HAVE_DECL_GETDOMAINNAME = @HAVE_DECL_GETDOMAINNAME@
+HAVE_DECL_GETLINE = @HAVE_DECL_GETLINE@
+HAVE_DECL_GETLOADAVG = @HAVE_DECL_GETLOADAVG@
+HAVE_DECL_GETLOGIN = @HAVE_DECL_GETLOGIN@
+HAVE_DECL_GETLOGIN_R = @HAVE_DECL_GETLOGIN_R@
+HAVE_DECL_GETPAGESIZE = @HAVE_DECL_GETPAGESIZE@
+HAVE_DECL_GETUSERSHELL = @HAVE_DECL_GETUSERSHELL@
+HAVE_DECL_INITSTATE = @HAVE_DECL_INITSTATE@
+HAVE_DECL_MEMMEM = @HAVE_DECL_MEMMEM@
+HAVE_DECL_MEMRCHR = @HAVE_DECL_MEMRCHR@
+HAVE_DECL_OBSTACK_PRINTF = @HAVE_DECL_OBSTACK_PRINTF@
+HAVE_DECL_SETENV = @HAVE_DECL_SETENV@
+HAVE_DECL_SETHOSTNAME = @HAVE_DECL_SETHOSTNAME@
+HAVE_DECL_SETSTATE = @HAVE_DECL_SETSTATE@
+HAVE_DECL_SNPRINTF = @HAVE_DECL_SNPRINTF@
+HAVE_DECL_STRDUP = @HAVE_DECL_STRDUP@
+HAVE_DECL_STRERROR_R = @HAVE_DECL_STRERROR_R@
+HAVE_DECL_STRNCASECMP = @HAVE_DECL_STRNCASECMP@
+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_TRUNCATE = @HAVE_DECL_TRUNCATE@
+HAVE_DECL_TTYNAME_R = @HAVE_DECL_TTYNAME_R@
+HAVE_DECL_UNSETENV = @HAVE_DECL_UNSETENV@
+HAVE_DECL_VSNPRINTF = @HAVE_DECL_VSNPRINTF@
+HAVE_DECL_WCTOB = @HAVE_DECL_WCTOB@
+HAVE_DECL_WCWIDTH = @HAVE_DECL_WCWIDTH@
+HAVE_DPRINTF = @HAVE_DPRINTF@
+HAVE_DUP2 = @HAVE_DUP2@
+HAVE_DUP3 = @HAVE_DUP3@
+HAVE_DUPLOCALE = @HAVE_DUPLOCALE@
+HAVE_EUIDACCESS = @HAVE_EUIDACCESS@
+HAVE_EXPLICIT_BZERO = @HAVE_EXPLICIT_BZERO@
+HAVE_FACCESSAT = @HAVE_FACCESSAT@
+HAVE_FCHDIR = @HAVE_FCHDIR@
+HAVE_FCHOWNAT = @HAVE_FCHOWNAT@
+HAVE_FDATASYNC = @HAVE_FDATASYNC@
+HAVE_FEATURES_H = @HAVE_FEATURES_H@
+HAVE_FFS = @HAVE_FFS@
+HAVE_FFSL = @HAVE_FFSL@
+HAVE_FFSLL = @HAVE_FFSLL@
+HAVE_FREELOCALE = @HAVE_FREELOCALE@
+HAVE_FSEEKO = @HAVE_FSEEKO@
+HAVE_FSYNC = @HAVE_FSYNC@
+HAVE_FTELLO = @HAVE_FTELLO@
+HAVE_FTRUNCATE = @HAVE_FTRUNCATE@
+HAVE_GETDTABLESIZE = @HAVE_GETDTABLESIZE@
+HAVE_GETGROUPS = @HAVE_GETGROUPS@
+HAVE_GETHOSTNAME = @HAVE_GETHOSTNAME@
+HAVE_GETLOGIN = @HAVE_GETLOGIN@
+HAVE_GETOPT_H = @HAVE_GETOPT_H@
+HAVE_GETPAGESIZE = @HAVE_GETPAGESIZE@
+HAVE_GETPASS = @HAVE_GETPASS@
+HAVE_GETSUBOPT = @HAVE_GETSUBOPT@
+HAVE_GRANTPT = @HAVE_GRANTPT@
+HAVE_GROUP_MEMBER = @HAVE_GROUP_MEMBER@
+HAVE_INTTYPES_H = @HAVE_INTTYPES_H@
+HAVE_ISWBLANK = @HAVE_ISWBLANK@
+HAVE_ISWCNTRL = @HAVE_ISWCNTRL@
+HAVE_LANGINFO_ALTMON = @HAVE_LANGINFO_ALTMON@
+HAVE_LANGINFO_CODESET = @HAVE_LANGINFO_CODESET@
+HAVE_LANGINFO_ERA = @HAVE_LANGINFO_ERA@
+HAVE_LANGINFO_H = @HAVE_LANGINFO_H@
+HAVE_LANGINFO_T_FMT_AMPM = @HAVE_LANGINFO_T_FMT_AMPM@
+HAVE_LANGINFO_YESEXPR = @HAVE_LANGINFO_YESEXPR@
+HAVE_LCHOWN = @HAVE_LCHOWN@
+HAVE_LINK = @HAVE_LINK@
+HAVE_LINKAT = @HAVE_LINKAT@
+HAVE_LONG_LONG_INT = @HAVE_LONG_LONG_INT@
+HAVE_MAX_ALIGN_T = @HAVE_MAX_ALIGN_T@
+HAVE_MBRLEN = @HAVE_MBRLEN@
+HAVE_MBRTOWC = @HAVE_MBRTOWC@
+HAVE_MBSINIT = @HAVE_MBSINIT@
+HAVE_MBSLEN = @HAVE_MBSLEN@
+HAVE_MBSNRTOWCS = @HAVE_MBSNRTOWCS@
+HAVE_MBSRTOWCS = @HAVE_MBSRTOWCS@
+HAVE_MEMCHR = @HAVE_MEMCHR@
+HAVE_MEMPCPY = @HAVE_MEMPCPY@
+HAVE_MKDTEMP = @HAVE_MKDTEMP@
+HAVE_MKOSTEMP = @HAVE_MKOSTEMP@
+HAVE_MKOSTEMPS = @HAVE_MKOSTEMPS@
+HAVE_MKSTEMP = @HAVE_MKSTEMP@
+HAVE_MKSTEMPS = @HAVE_MKSTEMPS@
+HAVE_MSVC_INVALID_PARAMETER_HANDLER = @HAVE_MSVC_INVALID_PARAMETER_HANDLER@
+HAVE_NEWLOCALE = @HAVE_NEWLOCALE@
+HAVE_NL_LANGINFO = @HAVE_NL_LANGINFO@
+HAVE_OS_H = @HAVE_OS_H@
+HAVE_PCLOSE = @HAVE_PCLOSE@
+HAVE_PIPE = @HAVE_PIPE@
+HAVE_PIPE2 = @HAVE_PIPE2@
+HAVE_POPEN = @HAVE_POPEN@
+HAVE_POSIX_OPENPT = @HAVE_POSIX_OPENPT@
+HAVE_PREAD = @HAVE_PREAD@
+HAVE_PTSNAME = @HAVE_PTSNAME@
+HAVE_PTSNAME_R = @HAVE_PTSNAME_R@
+HAVE_PWRITE = @HAVE_PWRITE@
+HAVE_QSORT_R = @HAVE_QSORT_R@
+HAVE_RANDOM = @HAVE_RANDOM@
+HAVE_RANDOM_H = @HAVE_RANDOM_H@
+HAVE_RANDOM_R = @HAVE_RANDOM_R@
+HAVE_RAWMEMCHR = @HAVE_RAWMEMCHR@
+HAVE_READLINK = @HAVE_READLINK@
+HAVE_READLINKAT = @HAVE_READLINKAT@
+HAVE_REALLOCARRAY = @HAVE_REALLOCARRAY@
+HAVE_REALPATH = @HAVE_REALPATH@
+HAVE_RENAMEAT = @HAVE_RENAMEAT@
+HAVE_RPMATCH = @HAVE_RPMATCH@
+HAVE_SECURE_GETENV = @HAVE_SECURE_GETENV@
+HAVE_SETENV = @HAVE_SETENV@
+HAVE_SETHOSTNAME = @HAVE_SETHOSTNAME@
+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_STRCASECMP = @HAVE_STRCASECMP@
+HAVE_STRCASESTR = @HAVE_STRCASESTR@
+HAVE_STRCHRNUL = @HAVE_STRCHRNUL@
+HAVE_STRINGS_H = @HAVE_STRINGS_H@
+HAVE_STRPBRK = @HAVE_STRPBRK@
+HAVE_STRSEP = @HAVE_STRSEP@
+HAVE_STRTOD = @HAVE_STRTOD@
+HAVE_STRTOLL = @HAVE_STRTOLL@
+HAVE_STRTOULL = @HAVE_STRTOULL@
+HAVE_STRUCT_RANDOM_DATA = @HAVE_STRUCT_RANDOM_DATA@
+HAVE_STRVERSCMP = @HAVE_STRVERSCMP@
+HAVE_SYMLINK = @HAVE_SYMLINK@
+HAVE_SYMLINKAT = @HAVE_SYMLINKAT@
+HAVE_SYS_BITYPES_H = @HAVE_SYS_BITYPES_H@
+HAVE_SYS_CDEFS_H = @HAVE_SYS_CDEFS_H@
+HAVE_SYS_INTTYPES_H = @HAVE_SYS_INTTYPES_H@
+HAVE_SYS_LOADAVG_H = @HAVE_SYS_LOADAVG_H@
+HAVE_SYS_PARAM_H = @HAVE_SYS_PARAM_H@
+HAVE_SYS_TYPES_H = @HAVE_SYS_TYPES_H@
+HAVE_UNISTD_H = @HAVE_UNISTD_H@
+HAVE_UNLINKAT = @HAVE_UNLINKAT@
+HAVE_UNLOCKPT = @HAVE_UNLOCKPT@
+HAVE_UNSIGNED_LONG_LONG_INT = @HAVE_UNSIGNED_LONG_LONG_INT@
+HAVE_USLEEP = @HAVE_USLEEP@
+HAVE_VASPRINTF = @HAVE_VASPRINTF@
+HAVE_VDPRINTF = @HAVE_VDPRINTF@
+HAVE_WCHAR_H = @HAVE_WCHAR_H@
+HAVE_WCHAR_T = @HAVE_WCHAR_T@
+HAVE_WCPCPY = @HAVE_WCPCPY@
+HAVE_WCPNCPY = @HAVE_WCPNCPY@
+HAVE_WCRTOMB = @HAVE_WCRTOMB@
+HAVE_WCSCASECMP = @HAVE_WCSCASECMP@
+HAVE_WCSCAT = @HAVE_WCSCAT@
+HAVE_WCSCHR = @HAVE_WCSCHR@
+HAVE_WCSCMP = @HAVE_WCSCMP@
+HAVE_WCSCOLL = @HAVE_WCSCOLL@
+HAVE_WCSCPY = @HAVE_WCSCPY@
+HAVE_WCSCSPN = @HAVE_WCSCSPN@
+HAVE_WCSDUP = @HAVE_WCSDUP@
+HAVE_WCSFTIME = @HAVE_WCSFTIME@
+HAVE_WCSLEN = @HAVE_WCSLEN@
+HAVE_WCSNCASECMP = @HAVE_WCSNCASECMP@
+HAVE_WCSNCAT = @HAVE_WCSNCAT@
+HAVE_WCSNCMP = @HAVE_WCSNCMP@
+HAVE_WCSNCPY = @HAVE_WCSNCPY@
+HAVE_WCSNLEN = @HAVE_WCSNLEN@
+HAVE_WCSNRTOMBS = @HAVE_WCSNRTOMBS@
+HAVE_WCSPBRK = @HAVE_WCSPBRK@
+HAVE_WCSRCHR = @HAVE_WCSRCHR@
+HAVE_WCSRTOMBS = @HAVE_WCSRTOMBS@
+HAVE_WCSSPN = @HAVE_WCSSPN@
+HAVE_WCSSTR = @HAVE_WCSSTR@
+HAVE_WCSTOK = @HAVE_WCSTOK@
+HAVE_WCSWIDTH = @HAVE_WCSWIDTH@
+HAVE_WCSXFRM = @HAVE_WCSXFRM@
+HAVE_WCTRANS_T = @HAVE_WCTRANS_T@
+HAVE_WCTYPE_H = @HAVE_WCTYPE_H@
+HAVE_WCTYPE_T = @HAVE_WCTYPE_T@
+HAVE_WINSOCK2_H = @HAVE_WINSOCK2_H@
+HAVE_WINT_T = @HAVE_WINT_T@
+HAVE_WMEMCHR = @HAVE_WMEMCHR@
+HAVE_WMEMCMP = @HAVE_WMEMCMP@
+HAVE_WMEMCPY = @HAVE_WMEMCPY@
+HAVE_WMEMMOVE = @HAVE_WMEMMOVE@
+HAVE_WMEMSET = @HAVE_WMEMSET@
+HAVE_XLOCALE_H = @HAVE_XLOCALE_H@
+HAVE__BOOL = @HAVE__BOOL@
+HAVE__EXIT = @HAVE__EXIT@
+HELP2MAN = @HELP2MAN@
+INCLUDE_NEXT = @INCLUDE_NEXT@
+INCLUDE_NEXT_AS_FIRST_DIRECTIVE = @INCLUDE_NEXT_AS_FIRST_DIRECTIVE@
+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@
+LIBMULTITHREAD = @LIBMULTITHREAD@
+LIBOBJS = @LIBOBJS@
+LIBPTH = @LIBPTH@
+LIBPTH_PREFIX = @LIBPTH_PREFIX@
+LIBS = @LIBS@
+LIBTHREAD = @LIBTHREAD@
+LIBUNISTRING_UNITYPES_H = @LIBUNISTRING_UNITYPES_H@
+LIBUNISTRING_UNIWIDTH_H = @LIBUNISTRING_UNIWIDTH_H@
+LIMITS_H = @LIMITS_H@
+LN_S = @LN_S@
+LOCALCHARSET_TESTS_ENVIRONMENT = @LOCALCHARSET_TESTS_ENVIRONMENT@
+LOCALE_FR = @LOCALE_FR@
+LOCALE_FR_UTF8 = @LOCALE_FR_UTF8@
+LOCALE_JA = @LOCALE_JA@
+LOCALE_ZH_CN = @LOCALE_ZH_CN@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBMULTITHREAD = @LTLIBMULTITHREAD@
+LTLIBOBJS = @LTLIBOBJS@
+LTLIBPTH = @LTLIBPTH@
+LTLIBTHREAD = @LTLIBTHREAD@
+MAKEINFO = @MAKEINFO@
+MKDIR_P = @MKDIR_P@
+MKINSTALLDIRS = @MKINSTALLDIRS@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NEXT_AS_FIRST_DIRECTIVE_ERRNO_H = @NEXT_AS_FIRST_DIRECTIVE_ERRNO_H@
+NEXT_AS_FIRST_DIRECTIVE_FLOAT_H = @NEXT_AS_FIRST_DIRECTIVE_FLOAT_H@
+NEXT_AS_FIRST_DIRECTIVE_GETOPT_H = @NEXT_AS_FIRST_DIRECTIVE_GETOPT_H@
+NEXT_AS_FIRST_DIRECTIVE_LANGINFO_H = @NEXT_AS_FIRST_DIRECTIVE_LANGINFO_H@
+NEXT_AS_FIRST_DIRECTIVE_LIMITS_H = @NEXT_AS_FIRST_DIRECTIVE_LIMITS_H@
+NEXT_AS_FIRST_DIRECTIVE_LOCALE_H = @NEXT_AS_FIRST_DIRECTIVE_LOCALE_H@
+NEXT_AS_FIRST_DIRECTIVE_STDARG_H = @NEXT_AS_FIRST_DIRECTIVE_STDARG_H@
+NEXT_AS_FIRST_DIRECTIVE_STDDEF_H = @NEXT_AS_FIRST_DIRECTIVE_STDDEF_H@
+NEXT_AS_FIRST_DIRECTIVE_STDINT_H = @NEXT_AS_FIRST_DIRECTIVE_STDINT_H@
+NEXT_AS_FIRST_DIRECTIVE_STDIO_H = @NEXT_AS_FIRST_DIRECTIVE_STDIO_H@
+NEXT_AS_FIRST_DIRECTIVE_STDLIB_H = @NEXT_AS_FIRST_DIRECTIVE_STDLIB_H@
+NEXT_AS_FIRST_DIRECTIVE_STRINGS_H = @NEXT_AS_FIRST_DIRECTIVE_STRINGS_H@
+NEXT_AS_FIRST_DIRECTIVE_STRING_H = @NEXT_AS_FIRST_DIRECTIVE_STRING_H@
+NEXT_AS_FIRST_DIRECTIVE_SYS_TYPES_H = @NEXT_AS_FIRST_DIRECTIVE_SYS_TYPES_H@
+NEXT_AS_FIRST_DIRECTIVE_UNISTD_H = @NEXT_AS_FIRST_DIRECTIVE_UNISTD_H@
+NEXT_AS_FIRST_DIRECTIVE_WCHAR_H = @NEXT_AS_FIRST_DIRECTIVE_WCHAR_H@
+NEXT_AS_FIRST_DIRECTIVE_WCTYPE_H = @NEXT_AS_FIRST_DIRECTIVE_WCTYPE_H@
+NEXT_ERRNO_H = @NEXT_ERRNO_H@
+NEXT_FLOAT_H = @NEXT_FLOAT_H@
+NEXT_GETOPT_H = @NEXT_GETOPT_H@
+NEXT_LANGINFO_H = @NEXT_LANGINFO_H@
+NEXT_LIMITS_H = @NEXT_LIMITS_H@
+NEXT_LOCALE_H = @NEXT_LOCALE_H@
+NEXT_STDARG_H = @NEXT_STDARG_H@
+NEXT_STDDEF_H = @NEXT_STDDEF_H@
+NEXT_STDINT_H = @NEXT_STDINT_H@
+NEXT_STDIO_H = @NEXT_STDIO_H@
+NEXT_STDLIB_H = @NEXT_STDLIB_H@
+NEXT_STRINGS_H = @NEXT_STRINGS_H@
+NEXT_STRING_H = @NEXT_STRING_H@
+NEXT_SYS_TYPES_H = @NEXT_SYS_TYPES_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_DATE = @PACKAGE_DATE@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL = @PERL@
+PERL_EXT_CC = @PERL_EXT_CC@
+PERL_EXT_CFLAGS = @PERL_EXT_CFLAGS@
+PERL_EXT_CPPFLAGS = @PERL_EXT_CPPFLAGS@
+PERL_EXT_LDFLAGS = @PERL_EXT_LDFLAGS@
+POD2MAN = @POD2MAN@
+POSUB = @POSUB@
+PRAGMA_COLUMNS = @PRAGMA_COLUMNS@
+PRAGMA_SYSTEM_HEADER = @PRAGMA_SYSTEM_HEADER@
+PROVE = @PROVE@
+PTRDIFF_T_SUFFIX = @PTRDIFF_T_SUFFIX@
+RANLIB = @RANLIB@
+REPLACE_BTOWC = @REPLACE_BTOWC@
+REPLACE_CALLOC = @REPLACE_CALLOC@
+REPLACE_CANONICALIZE_FILE_NAME = @REPLACE_CANONICALIZE_FILE_NAME@
+REPLACE_CHOWN = @REPLACE_CHOWN@
+REPLACE_CLOSE = @REPLACE_CLOSE@
+REPLACE_DPRINTF = @REPLACE_DPRINTF@
+REPLACE_DUP = @REPLACE_DUP@
+REPLACE_DUP2 = @REPLACE_DUP2@
+REPLACE_DUPLOCALE = @REPLACE_DUPLOCALE@
+REPLACE_FACCESSAT = @REPLACE_FACCESSAT@
+REPLACE_FCHOWNAT = @REPLACE_FCHOWNAT@
+REPLACE_FCLOSE = @REPLACE_FCLOSE@
+REPLACE_FDOPEN = @REPLACE_FDOPEN@
+REPLACE_FFLUSH = @REPLACE_FFLUSH@
+REPLACE_FOPEN = @REPLACE_FOPEN@
+REPLACE_FPRINTF = @REPLACE_FPRINTF@
+REPLACE_FPURGE = @REPLACE_FPURGE@
+REPLACE_FREELOCALE = @REPLACE_FREELOCALE@
+REPLACE_FREOPEN = @REPLACE_FREOPEN@
+REPLACE_FSEEK = @REPLACE_FSEEK@
+REPLACE_FSEEKO = @REPLACE_FSEEKO@
+REPLACE_FTELL = @REPLACE_FTELL@
+REPLACE_FTELLO = @REPLACE_FTELLO@
+REPLACE_FTRUNCATE = @REPLACE_FTRUNCATE@
+REPLACE_GETCWD = @REPLACE_GETCWD@
+REPLACE_GETDELIM = @REPLACE_GETDELIM@
+REPLACE_GETDOMAINNAME = @REPLACE_GETDOMAINNAME@
+REPLACE_GETDTABLESIZE = @REPLACE_GETDTABLESIZE@
+REPLACE_GETGROUPS = @REPLACE_GETGROUPS@
+REPLACE_GETLINE = @REPLACE_GETLINE@
+REPLACE_GETLOGIN_R = @REPLACE_GETLOGIN_R@
+REPLACE_GETPAGESIZE = @REPLACE_GETPAGESIZE@
+REPLACE_GETPASS = @REPLACE_GETPASS@
+REPLACE_ISATTY = @REPLACE_ISATTY@
+REPLACE_ISWBLANK = @REPLACE_ISWBLANK@
+REPLACE_ISWCNTRL = @REPLACE_ISWCNTRL@
+REPLACE_ITOLD = @REPLACE_ITOLD@
+REPLACE_LCHOWN = @REPLACE_LCHOWN@
+REPLACE_LINK = @REPLACE_LINK@
+REPLACE_LINKAT = @REPLACE_LINKAT@
+REPLACE_LOCALECONV = @REPLACE_LOCALECONV@
+REPLACE_LSEEK = @REPLACE_LSEEK@
+REPLACE_MALLOC = @REPLACE_MALLOC@
+REPLACE_MBRLEN = @REPLACE_MBRLEN@
+REPLACE_MBRTOWC = @REPLACE_MBRTOWC@
+REPLACE_MBSINIT = @REPLACE_MBSINIT@
+REPLACE_MBSNRTOWCS = @REPLACE_MBSNRTOWCS@
+REPLACE_MBSRTOWCS = @REPLACE_MBSRTOWCS@
+REPLACE_MBSTATE_T = @REPLACE_MBSTATE_T@
+REPLACE_MBTOWC = @REPLACE_MBTOWC@
+REPLACE_MEMCHR = @REPLACE_MEMCHR@
+REPLACE_MEMMEM = @REPLACE_MEMMEM@
+REPLACE_MKSTEMP = @REPLACE_MKSTEMP@
+REPLACE_NEWLOCALE = @REPLACE_NEWLOCALE@
+REPLACE_NL_LANGINFO = @REPLACE_NL_LANGINFO@
+REPLACE_NULL = @REPLACE_NULL@
+REPLACE_OBSTACK_PRINTF = @REPLACE_OBSTACK_PRINTF@
+REPLACE_PERROR = @REPLACE_PERROR@
+REPLACE_POPEN = @REPLACE_POPEN@
+REPLACE_PREAD = @REPLACE_PREAD@
+REPLACE_PRINTF = @REPLACE_PRINTF@
+REPLACE_PTSNAME = @REPLACE_PTSNAME@
+REPLACE_PTSNAME_R = @REPLACE_PTSNAME_R@
+REPLACE_PUTENV = @REPLACE_PUTENV@
+REPLACE_PWRITE = @REPLACE_PWRITE@
+REPLACE_QSORT_R = @REPLACE_QSORT_R@
+REPLACE_RANDOM_R = @REPLACE_RANDOM_R@
+REPLACE_READ = @REPLACE_READ@
+REPLACE_READLINK = @REPLACE_READLINK@
+REPLACE_READLINKAT = @REPLACE_READLINKAT@
+REPLACE_REALLOC = @REPLACE_REALLOC@
+REPLACE_REALPATH = @REPLACE_REALPATH@
+REPLACE_REMOVE = @REPLACE_REMOVE@
+REPLACE_RENAME = @REPLACE_RENAME@
+REPLACE_RENAMEAT = @REPLACE_RENAMEAT@
+REPLACE_RMDIR = @REPLACE_RMDIR@
+REPLACE_SETENV = @REPLACE_SETENV@
+REPLACE_SETLOCALE = @REPLACE_SETLOCALE@
+REPLACE_SLEEP = @REPLACE_SLEEP@
+REPLACE_SNPRINTF = @REPLACE_SNPRINTF@
+REPLACE_SPRINTF = @REPLACE_SPRINTF@
+REPLACE_STDIO_READ_FUNCS = @REPLACE_STDIO_READ_FUNCS@
+REPLACE_STDIO_WRITE_FUNCS = @REPLACE_STDIO_WRITE_FUNCS@
+REPLACE_STPNCPY = @REPLACE_STPNCPY@
+REPLACE_STRCASESTR = @REPLACE_STRCASESTR@
+REPLACE_STRCHRNUL = @REPLACE_STRCHRNUL@
+REPLACE_STRDUP = @REPLACE_STRDUP@
+REPLACE_STRERROR = @REPLACE_STRERROR@
+REPLACE_STRERROR_R = @REPLACE_STRERROR_R@
+REPLACE_STRNCAT = @REPLACE_STRNCAT@
+REPLACE_STRNDUP = @REPLACE_STRNDUP@
+REPLACE_STRNLEN = @REPLACE_STRNLEN@
+REPLACE_STRSIGNAL = @REPLACE_STRSIGNAL@
+REPLACE_STRSTR = @REPLACE_STRSTR@
+REPLACE_STRTOD = @REPLACE_STRTOD@
+REPLACE_STRTOK_R = @REPLACE_STRTOK_R@
+REPLACE_STRUCT_LCONV = @REPLACE_STRUCT_LCONV@
+REPLACE_SYMLINK = @REPLACE_SYMLINK@
+REPLACE_SYMLINKAT = @REPLACE_SYMLINKAT@
+REPLACE_TMPFILE = @REPLACE_TMPFILE@
+REPLACE_TOWLOWER = @REPLACE_TOWLOWER@
+REPLACE_TRUNCATE = @REPLACE_TRUNCATE@
+REPLACE_TTYNAME_R = @REPLACE_TTYNAME_R@
+REPLACE_UNLINK = @REPLACE_UNLINK@
+REPLACE_UNLINKAT = @REPLACE_UNLINKAT@
+REPLACE_UNSETENV = @REPLACE_UNSETENV@
+REPLACE_USLEEP = @REPLACE_USLEEP@
+REPLACE_VASPRINTF = @REPLACE_VASPRINTF@
+REPLACE_VDPRINTF = @REPLACE_VDPRINTF@
+REPLACE_VFPRINTF = @REPLACE_VFPRINTF@
+REPLACE_VPRINTF = @REPLACE_VPRINTF@
+REPLACE_VSNPRINTF = @REPLACE_VSNPRINTF@
+REPLACE_VSPRINTF = @REPLACE_VSPRINTF@
+REPLACE_WCRTOMB = @REPLACE_WCRTOMB@
+REPLACE_WCSFTIME = @REPLACE_WCSFTIME@
+REPLACE_WCSNRTOMBS = @REPLACE_WCSNRTOMBS@
+REPLACE_WCSRTOMBS = @REPLACE_WCSRTOMBS@
+REPLACE_WCSWIDTH = @REPLACE_WCSWIDTH@
+REPLACE_WCTOB = @REPLACE_WCTOB@
+REPLACE_WCTOMB = @REPLACE_WCTOMB@
+REPLACE_WCWIDTH = @REPLACE_WCWIDTH@
+REPLACE_WRITE = @REPLACE_WRITE@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SIG_ATOMIC_T_SUFFIX = @SIG_ATOMIC_T_SUFFIX@
+SIZE_T_SUFFIX = @SIZE_T_SUFFIX@
+STDARG_H = @STDARG_H@
+STDBOOL_H = @STDBOOL_H@
+STDDEF_H = @STDDEF_H@
+STDINT_H = @STDINT_H@
+STRIP = @STRIP@
+TERMLIBS = @TERMLIBS@
+TEXINFO_DTD_VERSION = @TEXINFO_DTD_VERSION@
+UNDEFINE_STRTOK_R = @UNDEFINE_STRTOK_R@
+UNISTD_H_HAVE_WINSOCK2_H = @UNISTD_H_HAVE_WINSOCK2_H@
+UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS = @UNISTD_H_HAVE_WINSOCK2_H_AND_USE_SOCKETS@
+USE_EXTERNAL_EASTASIANWIDTH = @USE_EXTERNAL_EASTASIANWIDTH@
+USE_EXTERNAL_LIBINTL = @USE_EXTERNAL_LIBINTL@
+USE_EXTERNAL_UNIDECODE = @USE_EXTERNAL_UNIDECODE@
+USE_NLS = @USE_NLS@
+USE_UNICODE = @USE_UNICODE@
+VERSION = @VERSION@
+WCHAR_T_SUFFIX = @WCHAR_T_SUFFIX@
+WINDOWS_64_BIT_OFF_T = @WINDOWS_64_BIT_OFF_T@
+WINDOWS_STAT_INODES = @WINDOWS_STAT_INODES@
+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@
+enable_xs = @enable_xs@
+exec_prefix = @exec_prefix@
+gl_LIBOBJS = @gl_LIBOBJS@
+gl_LTLIBOBJS = @gl_LTLIBOBJS@
+gltests_LIBOBJS = @gltests_LIBOBJS@
+gltests_LTLIBOBJS = @gltests_LTLIBOBJS@
+gltests_WITNESS = @gltests_WITNESS@
+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 = @localedir@
+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@
+AM_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/gnulib/lib \
+ -I$(top_builddir)/gnulib/lib \
+ -DLOCALEDIR=\"$(datadir)/locale\" \
+ -DINFODIR=\"$(infodir)\" \
+ -DINFODIR2=\"$(datadir)/info\"
+
+LDADD = $(top_builddir)/gnulib/lib/libgnu.a $(TERMLIBS) $(LIBINTL) $(LIBICONV) $(LIBTHREAD)
+EXTRA_DIST = pcterm.c $(TESTS) $(XFAIL_TESTS) t/README t/infodir \
+ t/Init-test.inc t/Infokey-config t/node-target
+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 indices.c indices.h \
+ info-utils.c info-utils.h info.c info.h infodoc.c infokey.c \
+ infomap.c infomap.h infopath.c 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 \
+ tag.c tag.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
+
+# The files `doc.c' and `funs.h' are created by ./makedoc run over the
+# source files which contain DECLARE_INFO_COMMAND. `funs.h' is a header file
+# listing the functions found. `doc.c' is a structure containing pointers
+# to those functions along with completable names and documentation strings.
+#
+generated_sources = doc.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
+@have_ptys_TRUE@pseudotty_SOURCES = pseudotty.c
+TESTS = \
+ t/file.sh \
+ t/file-node.sh \
+ t/file-nodes.sh \
+ t/file-node-with-dot.sh \
+ t/file-index.sh \
+ t/file-index-colons.sh \
+ t/file-file-with-dot.sh \
+ t/no-file.sh \
+ t/node-no-file.sh \
+ t/split.sh \
+ t/malformed-split.sh \
+ t/relative-path.sh \
+ t/file-relative-path.sh \
+ t/dir.sh \
+ t/dir-file.sh \
+ t/dir-file-sloppily.sh \
+ t/dir-merge.sh \
+ t/dir-nondir.sh \
+ t/dir-no-file.sh \
+ t/dir-file-menu.sh \
+ t/dir-file-no-menu.sh \
+ t/dir-file-node.sh \
+ t/dir-file-index.sh \
+ t/dir-3-menus.sh \
+ t/dir-menus-sloppily.sh \
+ t/where-dir-file.sh \
+ t/dir-dangling-entry.sh \
+ t/dir-entry-to-subdir.sh \
+ t/spec-file-node.sh \
+ t/spec-no-node.sh \
+ t/spec-menu.sh \
+ t/all-only.sh \
+ t/empty.sh \
+ t/tab.sh \
+ t/tab-argument.sh \
+ t/tab-no-xref.sh \
+ t/tab-skip-node.sh \
+ t/xref-across-lines.sh \
+ t/xref-across-lines-2.sh \
+ t/body-start.sh \
+ t/end-of-line.sh \
+ t/goal-column.sh \
+ t/star-note-non-whitespace.sh \
+ t/c-u-m-x-scroll-forward.sh \
+ t/last-no-history.sh \
+ t/split-footnotes.sh \
+ t/window-split-dir.sh \
+ t/last-node-not-top-level.sh \
+ t/adjust-anchors.sh \
+ t/xref-to-anchor.sh \
+ t/search-after-tag.sh \
+ t/split-file-menu.sh \
+ t/search-split-after-index.sh \
+ t/index.sh \
+ t/no-index.sh \
+ t/index-apropos.sh \
+ t/index-search.sh \
+ t/index-completing.sh \
+ t/split-index.sh \
+ t/index-long-nodeline.sh \
+ t/empty-infopath.sh \
+ t/menu-sequence.sh \
+ t/relative-reference.sh \
+ t/resize-in-completions.sh \
+ t/replace-viewed.sh \
+ t/search-skip-screen.sh \
+ t/search-empty.sh \
+ t/close-window-after-search.sh \
+ t/inc-sea-forward.sh \
+ t/inc-sea-forward-nonregex.sh \
+ t/inc-sea-insensitive.sh \
+ t/inc-sea-history.sh \
+ t/inc-sea-bs.sh \
+ t/gc-split.sh \
+ t/anchor-positions.sh \
+ t/quoted-label-as-target.sh \
+ t/quoted-target.sh \
+ t/quoted-label-and-target.sh \
+ t/quoted-manual-in-target.sh \
+ t/quoted-manual-in-label.sh \
+ t/goto-quoted.sh \
+ t/next-quoted.sh \
+ t/echo-area-no-completions.sh \
+ t/multiple-completions.sh \
+ t/help.sh
+
+all: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .log .o .obj .test .test$(EXEEXT) .trs
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu info/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu info/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__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+
+clean-checkPROGRAMS:
+ -test -z "$(check_PROGRAMS)" || rm -f $(check_PROGRAMS)
+
+clean-noinstPROGRAMS:
+ -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS)
+
+ginfo$(EXEEXT): $(ginfo_OBJECTS) $(ginfo_DEPENDENCIES) $(EXTRA_ginfo_DEPENDENCIES)
+ @rm -f ginfo$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(ginfo_OBJECTS) $(ginfo_LDADD) $(LIBS)
+
+makedoc$(EXEEXT): $(makedoc_OBJECTS) $(makedoc_DEPENDENCIES) $(EXTRA_makedoc_DEPENDENCIES)
+ @rm -f makedoc$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(makedoc_OBJECTS) $(makedoc_LDADD) $(LIBS)
+
+pseudotty$(EXEEXT): $(pseudotty_OBJECTS) $(pseudotty_DEPENDENCIES) $(EXTRA_pseudotty_DEPENDENCIES)
+ @rm -f pseudotty$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(pseudotty_OBJECTS) $(pseudotty_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dir.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/display.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/doc.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dribble.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/echo-area.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/filesys.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/footnotes.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/indices.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/info-utils.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/info.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/infodoc.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/infokey.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/infomap.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/infopath.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/m-x.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/makedoc.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/man.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nodemenu.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nodes.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pseudotty.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/search.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/session.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/signals.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tag.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/terminal.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tilde.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/variables.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/window.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+# Recover from deleted '.trs' file; this should ensure that
+# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create
+# both 'foo.log' and 'foo.trs'. Break the recipe in two subshells
+# to avoid problems with "make -n".
+.log.trs:
+ rm -f $< $@
+ $(MAKE) $(AM_MAKEFLAGS) $<
+
+# Leading 'am--fnord' is there to ensure the list of targets does not
+# expand to empty, as could happen e.g. with make check TESTS=''.
+am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck)
+am--force-recheck:
+ @:
+
+$(TEST_SUITE_LOG): $(TEST_LOGS)
+ @$(am__set_TESTS_bases); \
+ am__f_ok () { test -f "$$1" && test -r "$$1"; }; \
+ redo_bases=`for i in $$bases; do \
+ am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \
+ done`; \
+ if test -n "$$redo_bases"; then \
+ redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \
+ redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \
+ if $(am__make_dryrun); then :; else \
+ rm -f $$redo_logs && rm -f $$redo_results || exit 1; \
+ fi; \
+ fi; \
+ if test -n "$$am__remaking_logs"; then \
+ echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \
+ "recursion detected" >&2; \
+ elif test -n "$$redo_logs"; then \
+ am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \
+ fi; \
+ if $(am__make_dryrun); then :; else \
+ st=0; \
+ errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \
+ for i in $$redo_bases; do \
+ test -f $$i.trs && test -r $$i.trs \
+ || { echo "$$errmsg $$i.trs" >&2; st=1; }; \
+ test -f $$i.log && test -r $$i.log \
+ || { echo "$$errmsg $$i.log" >&2; st=1; }; \
+ done; \
+ test $$st -eq 0 || exit 1; \
+ fi
+ @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \
+ ws='[ ]'; \
+ results=`for b in $$bases; do echo $$b.trs; done`; \
+ test -n "$$results" || results=/dev/null; \
+ all=` grep "^$$ws*:test-result:" $$results | wc -l`; \
+ pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \
+ fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \
+ skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \
+ xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \
+ xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \
+ error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \
+ if test `expr $$fail + $$xpass + $$error` -eq 0; then \
+ success=true; \
+ else \
+ success=false; \
+ fi; \
+ br='==================='; br=$$br$$br$$br$$br; \
+ result_count () \
+ { \
+ if test x"$$1" = x"--maybe-color"; then \
+ maybe_colorize=yes; \
+ elif test x"$$1" = x"--no-color"; then \
+ maybe_colorize=no; \
+ else \
+ echo "$@: invalid 'result_count' usage" >&2; exit 4; \
+ fi; \
+ shift; \
+ desc=$$1 count=$$2; \
+ if test $$maybe_colorize = yes && test $$count -gt 0; then \
+ color_start=$$3 color_end=$$std; \
+ else \
+ color_start= color_end=; \
+ fi; \
+ echo "$${color_start}# $$desc $$count$${color_end}"; \
+ }; \
+ create_testsuite_report () \
+ { \
+ result_count $$1 "TOTAL:" $$all "$$brg"; \
+ result_count $$1 "PASS: " $$pass "$$grn"; \
+ result_count $$1 "SKIP: " $$skip "$$blu"; \
+ result_count $$1 "XFAIL:" $$xfail "$$lgn"; \
+ result_count $$1 "FAIL: " $$fail "$$red"; \
+ result_count $$1 "XPASS:" $$xpass "$$red"; \
+ result_count $$1 "ERROR:" $$error "$$mgn"; \
+ }; \
+ { \
+ echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \
+ $(am__rst_title); \
+ create_testsuite_report --no-color; \
+ echo; \
+ echo ".. contents:: :depth: 2"; \
+ echo; \
+ for b in $$bases; do echo $$b; done \
+ | $(am__create_global_log); \
+ } >$(TEST_SUITE_LOG).tmp || exit 1; \
+ mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \
+ if $$success; then \
+ col="$$grn"; \
+ else \
+ col="$$red"; \
+ test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \
+ fi; \
+ echo "$${col}$$br$${std}"; \
+ echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}"; \
+ echo "$${col}$$br$${std}"; \
+ create_testsuite_report --maybe-color; \
+ echo "$$col$$br$$std"; \
+ if $$success; then :; else \
+ echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \
+ if test -n "$(PACKAGE_BUGREPORT)"; then \
+ echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \
+ fi; \
+ echo "$$col$$br$$std"; \
+ fi; \
+ $$success || exit 1
+
+check-TESTS: $(check_PROGRAMS)
+ @list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list
+ @list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ trs_list=`for i in $$bases; do echo $$i.trs; done`; \
+ log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \
+ exit $$?;
+recheck: all $(check_PROGRAMS)
+ @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+ @set +e; $(am__set_TESTS_bases); \
+ bases=`for i in $$bases; do echo $$i; done \
+ | $(am__list_recheck_tests)` || exit 1; \
+ log_list=`for i in $$bases; do echo $$i.log; done`; \
+ log_list=`echo $$log_list`; \
+ $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \
+ am__force_recheck=am--force-recheck \
+ TEST_LOGS="$$log_list"; \
+ exit $$?
+t/file.sh.log: t/file.sh
+ @p='t/file.sh'; \
+ b='t/file.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/file-node.sh.log: t/file-node.sh
+ @p='t/file-node.sh'; \
+ b='t/file-node.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/file-nodes.sh.log: t/file-nodes.sh
+ @p='t/file-nodes.sh'; \
+ b='t/file-nodes.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/file-node-with-dot.sh.log: t/file-node-with-dot.sh
+ @p='t/file-node-with-dot.sh'; \
+ b='t/file-node-with-dot.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/file-index.sh.log: t/file-index.sh
+ @p='t/file-index.sh'; \
+ b='t/file-index.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/file-index-colons.sh.log: t/file-index-colons.sh
+ @p='t/file-index-colons.sh'; \
+ b='t/file-index-colons.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/file-file-with-dot.sh.log: t/file-file-with-dot.sh
+ @p='t/file-file-with-dot.sh'; \
+ b='t/file-file-with-dot.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/no-file.sh.log: t/no-file.sh
+ @p='t/no-file.sh'; \
+ b='t/no-file.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/node-no-file.sh.log: t/node-no-file.sh
+ @p='t/node-no-file.sh'; \
+ b='t/node-no-file.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/split.sh.log: t/split.sh
+ @p='t/split.sh'; \
+ b='t/split.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/malformed-split.sh.log: t/malformed-split.sh
+ @p='t/malformed-split.sh'; \
+ b='t/malformed-split.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/relative-path.sh.log: t/relative-path.sh
+ @p='t/relative-path.sh'; \
+ b='t/relative-path.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/file-relative-path.sh.log: t/file-relative-path.sh
+ @p='t/file-relative-path.sh'; \
+ b='t/file-relative-path.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/dir.sh.log: t/dir.sh
+ @p='t/dir.sh'; \
+ b='t/dir.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/dir-file.sh.log: t/dir-file.sh
+ @p='t/dir-file.sh'; \
+ b='t/dir-file.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/dir-file-sloppily.sh.log: t/dir-file-sloppily.sh
+ @p='t/dir-file-sloppily.sh'; \
+ b='t/dir-file-sloppily.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/dir-merge.sh.log: t/dir-merge.sh
+ @p='t/dir-merge.sh'; \
+ b='t/dir-merge.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/dir-nondir.sh.log: t/dir-nondir.sh
+ @p='t/dir-nondir.sh'; \
+ b='t/dir-nondir.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/dir-no-file.sh.log: t/dir-no-file.sh
+ @p='t/dir-no-file.sh'; \
+ b='t/dir-no-file.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/dir-file-menu.sh.log: t/dir-file-menu.sh
+ @p='t/dir-file-menu.sh'; \
+ b='t/dir-file-menu.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/dir-file-no-menu.sh.log: t/dir-file-no-menu.sh
+ @p='t/dir-file-no-menu.sh'; \
+ b='t/dir-file-no-menu.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/dir-file-node.sh.log: t/dir-file-node.sh
+ @p='t/dir-file-node.sh'; \
+ b='t/dir-file-node.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/dir-file-index.sh.log: t/dir-file-index.sh
+ @p='t/dir-file-index.sh'; \
+ b='t/dir-file-index.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/dir-3-menus.sh.log: t/dir-3-menus.sh
+ @p='t/dir-3-menus.sh'; \
+ b='t/dir-3-menus.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/dir-menus-sloppily.sh.log: t/dir-menus-sloppily.sh
+ @p='t/dir-menus-sloppily.sh'; \
+ b='t/dir-menus-sloppily.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/where-dir-file.sh.log: t/where-dir-file.sh
+ @p='t/where-dir-file.sh'; \
+ b='t/where-dir-file.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/dir-dangling-entry.sh.log: t/dir-dangling-entry.sh
+ @p='t/dir-dangling-entry.sh'; \
+ b='t/dir-dangling-entry.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/dir-entry-to-subdir.sh.log: t/dir-entry-to-subdir.sh
+ @p='t/dir-entry-to-subdir.sh'; \
+ b='t/dir-entry-to-subdir.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/spec-file-node.sh.log: t/spec-file-node.sh
+ @p='t/spec-file-node.sh'; \
+ b='t/spec-file-node.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/spec-no-node.sh.log: t/spec-no-node.sh
+ @p='t/spec-no-node.sh'; \
+ b='t/spec-no-node.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/spec-menu.sh.log: t/spec-menu.sh
+ @p='t/spec-menu.sh'; \
+ b='t/spec-menu.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/all-only.sh.log: t/all-only.sh
+ @p='t/all-only.sh'; \
+ b='t/all-only.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/empty.sh.log: t/empty.sh
+ @p='t/empty.sh'; \
+ b='t/empty.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/tab.sh.log: t/tab.sh
+ @p='t/tab.sh'; \
+ b='t/tab.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/tab-argument.sh.log: t/tab-argument.sh
+ @p='t/tab-argument.sh'; \
+ b='t/tab-argument.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/tab-no-xref.sh.log: t/tab-no-xref.sh
+ @p='t/tab-no-xref.sh'; \
+ b='t/tab-no-xref.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/tab-skip-node.sh.log: t/tab-skip-node.sh
+ @p='t/tab-skip-node.sh'; \
+ b='t/tab-skip-node.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/xref-across-lines.sh.log: t/xref-across-lines.sh
+ @p='t/xref-across-lines.sh'; \
+ b='t/xref-across-lines.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/xref-across-lines-2.sh.log: t/xref-across-lines-2.sh
+ @p='t/xref-across-lines-2.sh'; \
+ b='t/xref-across-lines-2.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/body-start.sh.log: t/body-start.sh
+ @p='t/body-start.sh'; \
+ b='t/body-start.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/end-of-line.sh.log: t/end-of-line.sh
+ @p='t/end-of-line.sh'; \
+ b='t/end-of-line.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/goal-column.sh.log: t/goal-column.sh
+ @p='t/goal-column.sh'; \
+ b='t/goal-column.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/star-note-non-whitespace.sh.log: t/star-note-non-whitespace.sh
+ @p='t/star-note-non-whitespace.sh'; \
+ b='t/star-note-non-whitespace.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/c-u-m-x-scroll-forward.sh.log: t/c-u-m-x-scroll-forward.sh
+ @p='t/c-u-m-x-scroll-forward.sh'; \
+ b='t/c-u-m-x-scroll-forward.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/last-no-history.sh.log: t/last-no-history.sh
+ @p='t/last-no-history.sh'; \
+ b='t/last-no-history.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/split-footnotes.sh.log: t/split-footnotes.sh
+ @p='t/split-footnotes.sh'; \
+ b='t/split-footnotes.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/window-split-dir.sh.log: t/window-split-dir.sh
+ @p='t/window-split-dir.sh'; \
+ b='t/window-split-dir.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/last-node-not-top-level.sh.log: t/last-node-not-top-level.sh
+ @p='t/last-node-not-top-level.sh'; \
+ b='t/last-node-not-top-level.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/adjust-anchors.sh.log: t/adjust-anchors.sh
+ @p='t/adjust-anchors.sh'; \
+ b='t/adjust-anchors.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/xref-to-anchor.sh.log: t/xref-to-anchor.sh
+ @p='t/xref-to-anchor.sh'; \
+ b='t/xref-to-anchor.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/search-after-tag.sh.log: t/search-after-tag.sh
+ @p='t/search-after-tag.sh'; \
+ b='t/search-after-tag.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/split-file-menu.sh.log: t/split-file-menu.sh
+ @p='t/split-file-menu.sh'; \
+ b='t/split-file-menu.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/search-split-after-index.sh.log: t/search-split-after-index.sh
+ @p='t/search-split-after-index.sh'; \
+ b='t/search-split-after-index.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/index.sh.log: t/index.sh
+ @p='t/index.sh'; \
+ b='t/index.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/no-index.sh.log: t/no-index.sh
+ @p='t/no-index.sh'; \
+ b='t/no-index.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/index-apropos.sh.log: t/index-apropos.sh
+ @p='t/index-apropos.sh'; \
+ b='t/index-apropos.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/index-search.sh.log: t/index-search.sh
+ @p='t/index-search.sh'; \
+ b='t/index-search.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/index-completing.sh.log: t/index-completing.sh
+ @p='t/index-completing.sh'; \
+ b='t/index-completing.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/split-index.sh.log: t/split-index.sh
+ @p='t/split-index.sh'; \
+ b='t/split-index.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/index-long-nodeline.sh.log: t/index-long-nodeline.sh
+ @p='t/index-long-nodeline.sh'; \
+ b='t/index-long-nodeline.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/empty-infopath.sh.log: t/empty-infopath.sh
+ @p='t/empty-infopath.sh'; \
+ b='t/empty-infopath.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/menu-sequence.sh.log: t/menu-sequence.sh
+ @p='t/menu-sequence.sh'; \
+ b='t/menu-sequence.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/relative-reference.sh.log: t/relative-reference.sh
+ @p='t/relative-reference.sh'; \
+ b='t/relative-reference.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/resize-in-completions.sh.log: t/resize-in-completions.sh
+ @p='t/resize-in-completions.sh'; \
+ b='t/resize-in-completions.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/replace-viewed.sh.log: t/replace-viewed.sh
+ @p='t/replace-viewed.sh'; \
+ b='t/replace-viewed.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/search-skip-screen.sh.log: t/search-skip-screen.sh
+ @p='t/search-skip-screen.sh'; \
+ b='t/search-skip-screen.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/search-empty.sh.log: t/search-empty.sh
+ @p='t/search-empty.sh'; \
+ b='t/search-empty.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/close-window-after-search.sh.log: t/close-window-after-search.sh
+ @p='t/close-window-after-search.sh'; \
+ b='t/close-window-after-search.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/inc-sea-forward.sh.log: t/inc-sea-forward.sh
+ @p='t/inc-sea-forward.sh'; \
+ b='t/inc-sea-forward.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/inc-sea-forward-nonregex.sh.log: t/inc-sea-forward-nonregex.sh
+ @p='t/inc-sea-forward-nonregex.sh'; \
+ b='t/inc-sea-forward-nonregex.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/inc-sea-insensitive.sh.log: t/inc-sea-insensitive.sh
+ @p='t/inc-sea-insensitive.sh'; \
+ b='t/inc-sea-insensitive.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/inc-sea-history.sh.log: t/inc-sea-history.sh
+ @p='t/inc-sea-history.sh'; \
+ b='t/inc-sea-history.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/inc-sea-bs.sh.log: t/inc-sea-bs.sh
+ @p='t/inc-sea-bs.sh'; \
+ b='t/inc-sea-bs.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/gc-split.sh.log: t/gc-split.sh
+ @p='t/gc-split.sh'; \
+ b='t/gc-split.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/anchor-positions.sh.log: t/anchor-positions.sh
+ @p='t/anchor-positions.sh'; \
+ b='t/anchor-positions.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/quoted-label-as-target.sh.log: t/quoted-label-as-target.sh
+ @p='t/quoted-label-as-target.sh'; \
+ b='t/quoted-label-as-target.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/quoted-target.sh.log: t/quoted-target.sh
+ @p='t/quoted-target.sh'; \
+ b='t/quoted-target.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/quoted-label-and-target.sh.log: t/quoted-label-and-target.sh
+ @p='t/quoted-label-and-target.sh'; \
+ b='t/quoted-label-and-target.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/quoted-manual-in-target.sh.log: t/quoted-manual-in-target.sh
+ @p='t/quoted-manual-in-target.sh'; \
+ b='t/quoted-manual-in-target.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/quoted-manual-in-label.sh.log: t/quoted-manual-in-label.sh
+ @p='t/quoted-manual-in-label.sh'; \
+ b='t/quoted-manual-in-label.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/goto-quoted.sh.log: t/goto-quoted.sh
+ @p='t/goto-quoted.sh'; \
+ b='t/goto-quoted.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/next-quoted.sh.log: t/next-quoted.sh
+ @p='t/next-quoted.sh'; \
+ b='t/next-quoted.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/echo-area-no-completions.sh.log: t/echo-area-no-completions.sh
+ @p='t/echo-area-no-completions.sh'; \
+ b='t/echo-area-no-completions.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/multiple-completions.sh.log: t/multiple-completions.sh
+ @p='t/multiple-completions.sh'; \
+ b='t/multiple-completions.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+t/help.sh.log: t/help.sh
+ @p='t/help.sh'; \
+ b='t/help.sh'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+.test.log:
+ @p='$<'; \
+ $(am__set_b); \
+ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
+@am__EXEEXT_TRUE@.test$(EXEEXT).log:
+@am__EXEEXT_TRUE@ @p='$<'; \
+@am__EXEEXT_TRUE@ $(am__set_b); \
+@am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \
+@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \
+@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \
+@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT)
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(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 "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$(top_distdir)" distdir="$(distdir)" \
+ dist-hook
+check-am: all-am
+ $(MAKE) $(AM_MAKEFLAGS) $(check_PROGRAMS)
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+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:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+ -test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS)
+ -test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs)
+ -test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG)
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_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-checkPROGRAMS clean-generic \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/dir.Po
+ -rm -f ./$(DEPDIR)/display.Po
+ -rm -f ./$(DEPDIR)/doc.Po
+ -rm -f ./$(DEPDIR)/dribble.Po
+ -rm -f ./$(DEPDIR)/echo-area.Po
+ -rm -f ./$(DEPDIR)/filesys.Po
+ -rm -f ./$(DEPDIR)/footnotes.Po
+ -rm -f ./$(DEPDIR)/indices.Po
+ -rm -f ./$(DEPDIR)/info-utils.Po
+ -rm -f ./$(DEPDIR)/info.Po
+ -rm -f ./$(DEPDIR)/infodoc.Po
+ -rm -f ./$(DEPDIR)/infokey.Po
+ -rm -f ./$(DEPDIR)/infomap.Po
+ -rm -f ./$(DEPDIR)/infopath.Po
+ -rm -f ./$(DEPDIR)/m-x.Po
+ -rm -f ./$(DEPDIR)/makedoc.Po
+ -rm -f ./$(DEPDIR)/man.Po
+ -rm -f ./$(DEPDIR)/nodemenu.Po
+ -rm -f ./$(DEPDIR)/nodes.Po
+ -rm -f ./$(DEPDIR)/pseudotty.Po
+ -rm -f ./$(DEPDIR)/search.Po
+ -rm -f ./$(DEPDIR)/session.Po
+ -rm -f ./$(DEPDIR)/signals.Po
+ -rm -f ./$(DEPDIR)/tag.Po
+ -rm -f ./$(DEPDIR)/terminal.Po
+ -rm -f ./$(DEPDIR)/tilde.Po
+ -rm -f ./$(DEPDIR)/variables.Po
+ -rm -f ./$(DEPDIR)/window.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/dir.Po
+ -rm -f ./$(DEPDIR)/display.Po
+ -rm -f ./$(DEPDIR)/doc.Po
+ -rm -f ./$(DEPDIR)/dribble.Po
+ -rm -f ./$(DEPDIR)/echo-area.Po
+ -rm -f ./$(DEPDIR)/filesys.Po
+ -rm -f ./$(DEPDIR)/footnotes.Po
+ -rm -f ./$(DEPDIR)/indices.Po
+ -rm -f ./$(DEPDIR)/info-utils.Po
+ -rm -f ./$(DEPDIR)/info.Po
+ -rm -f ./$(DEPDIR)/infodoc.Po
+ -rm -f ./$(DEPDIR)/infokey.Po
+ -rm -f ./$(DEPDIR)/infomap.Po
+ -rm -f ./$(DEPDIR)/infopath.Po
+ -rm -f ./$(DEPDIR)/m-x.Po
+ -rm -f ./$(DEPDIR)/makedoc.Po
+ -rm -f ./$(DEPDIR)/man.Po
+ -rm -f ./$(DEPDIR)/nodemenu.Po
+ -rm -f ./$(DEPDIR)/nodes.Po
+ -rm -f ./$(DEPDIR)/pseudotty.Po
+ -rm -f ./$(DEPDIR)/search.Po
+ -rm -f ./$(DEPDIR)/session.Po
+ -rm -f ./$(DEPDIR)/signals.Po
+ -rm -f ./$(DEPDIR)/tag.Po
+ -rm -f ./$(DEPDIR)/terminal.Po
+ -rm -f ./$(DEPDIR)/tilde.Po
+ -rm -f ./$(DEPDIR)/variables.Po
+ -rm -f ./$(DEPDIR)/window.Po
+ -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: all check check-am install install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-TESTS \
+ check-am clean clean-binPROGRAMS clean-checkPROGRAMS \
+ clean-generic clean-noinstPROGRAMS cscopelist-am ctags \
+ ctags-am dist-hook 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 recheck tags tags-am \
+ uninstall uninstall-am uninstall-binPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+# 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: 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)
+dist-hook:
+ rm -rf `find $(distdir)/t/infodir -name '*~'`
+
+# 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/macros/texinfo/texinfo/info/dir.c b/macros/texinfo/texinfo/info/dir.c
new file mode 100644
index 0000000000..49a905c9bc
--- /dev/null
+++ b/macros/texinfo/texinfo/info/dir.c
@@ -0,0 +1,315 @@
+/* dir.c -- how to build a special "dir" node from "localdir" files.
+
+ Copyright 1993-2019 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. */
+
+#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_node (char *contents, size_t size, NODE *node);
+static void insert_text_into_node (NODE *node, long start,
+ char *text, int textlen);
+
+static char *dirs_to_add[] = {
+ "dir", "localdir", NULL
+};
+
+static NODE *dir_node = 0;
+
+static NODE *build_dir_node (void);
+
+/* Return composite directory node. Return value should be freed by caller,
+ but none of its fields should be. */
+NODE *
+get_dir_node (void)
+{
+ NODE *node;
+
+ if (!dir_node)
+ dir_node = build_dir_node ();
+
+ node = xmalloc (sizeof (NODE));
+ *node = *dir_node;
+
+ return node;
+}
+
+static char *dir_contents;
+
+static NODE *
+build_dir_node (void)
+{
+ int path_index;
+ char *this_dir;
+ NODE *node;
+
+ node = info_create_node ();
+ node->nodename = xstrdup ("Top");
+ node->fullpath = xstrdup ("dir");
+ node->contents = xstrdup (
+
+"File: dir, Node: Top, This is the top of the INFO tree.\n"
+"\n"
+"This is the Info main menu (aka directory node).\n"
+"A few useful Info commands:\n"
+"\n"
+" 'q' quits;\n"
+" 'H' lists all Info commands;\n"
+" 'h' starts the Info tutorial;\n"
+" 'mTexinfo RET' visits the Texinfo manual, etc.\n"
+
+ );
+
+ node->nodelen = strlen (node->contents);
+
+ /* 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. */
+ for (this_dir = infopath_first (&path_index); this_dir;
+ this_dir = infopath_next (&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)
+ {
+ 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);
+
+ if (statable && S_ISREG (finfo.st_mode))
+ {
+ size_t filesize;
+ int compressed;
+ char *contents = filesys_read_info_file (fullpath, &filesize,
+ &finfo, &compressed);
+ if (contents)
+ {
+ add_menu_to_node (contents, filesize, node);
+ free (contents);
+ }
+ }
+
+ free (fullpath);
+ }
+ }
+
+ node->flags |= N_IsDir;
+ dir_contents = node->contents;
+ scan_node_contents (node, 0, 0);
+ return node;
+}
+
+/* Given CONTENTS and NODE, add the menu found in CONTENTS to the menu
+ found in NODE->contents. SIZE is the total size of CONTENTS. */
+static void
+add_menu_to_node (char *contents, size_t size, NODE *node)
+{
+ 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 = node->contents;
+ fb_binding.start = 0;
+ fb_binding.end = node->nodelen;
+ fb_binding.flags = S_FoldCase | S_SkipDest;
+
+ /* Move to the start of the menus in CONTENTS and NODE. */
+ if (search_forward (INFO_MENU_LABEL, &contents_binding, &contents_offset)
+ != search_success)
+ /* If there is no menu in CONTENTS, quit now. */
+ 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 NODE, make one. */
+ if (search_forward (INFO_MENU_LABEL, &fb_binding, &fb_offset)
+ != search_success)
+ {
+ fb_binding.start = node->nodelen;
+
+ insert_text_into_node
+ (node, fb_binding.start, INFO_MENU_LABEL, strlen (INFO_MENU_LABEL));
+
+ fb_binding.buffer = node->contents;
+ fb_binding.start = 0;
+ fb_binding.end = node->nodelen;
+ if (search_forward (INFO_MENU_LABEL, &fb_binding, &fb_offset)
+ != search_success)
+ 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 NODE'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_node (node, fb_binding.start, "\n\n", 2);
+ fb_binding.start += 2;
+ }
+ }
+
+ /* Insert the new menu. */
+ insert_text_into_node
+ (node, fb_binding.start, contents + contents_offset, size - contents_offset);
+}
+
+static void
+insert_text_into_node (NODE *node, long start, char *text, int textlen)
+{
+ char *contents;
+ long end;
+
+ end = node->nodelen;
+
+ contents = xmalloc (node->nodelen + textlen + 1);
+ memcpy (contents, node->contents, start);
+ memcpy (contents + start, text, textlen);
+ memcpy (contents + start + textlen, node->contents + start, end - start + 1);
+ free (node->contents);
+ node->contents = contents;
+ node->nodelen += textlen;
+}
+
+/* Return directory entry. Return value should not be freed or modified. */
+REFERENCE *
+lookup_dir_entry (char *label, int sloppy)
+{
+ REFERENCE *entry;
+
+ if (!dir_node)
+ dir_node = build_dir_node ();
+
+ entry = info_get_menu_entry_by_label (dir_node, label, sloppy);
+
+ return entry;
+}
+
+/* Look up entry in "dir" and "localdir" in search directory. Return
+ value is a pointer to a newly allocated REFERENCE. */
+REFERENCE *
+dir_entry_of_infodir (char *label, char *searchdir)
+{
+ int da_index;
+ char *dir_filename;
+ char *dir_fullpath;
+
+ struct stat dummy;
+ char *entry_fullpath;
+
+ NODE *dir_node;
+ REFERENCE *entry;
+
+ for (da_index = 0; (dir_filename = dirs_to_add[da_index]); da_index++)
+ {
+ dir_fullpath = info_add_extension (searchdir, dir_filename, &dummy);
+ if (!dir_fullpath)
+ continue;
+
+ if (!IS_ABSOLUTE(dir_fullpath))
+ {
+ char *tmp;
+ asprintf (&tmp, "./%s", dir_fullpath);
+ free (dir_fullpath);
+ dir_fullpath = tmp;
+ }
+ dir_node = info_get_node (dir_fullpath, "Top");
+ free (dir_fullpath);
+ entry = info_get_menu_entry_by_label (dir_node, label, 1);
+ if (!entry || !entry->filename)
+ {
+ free_history_node (dir_node);
+ continue;
+ /* A dir entry with no filename is unlikely, but not impossible. */
+ }
+
+ entry = info_copy_reference (entry);
+ entry_fullpath = info_add_extension (searchdir, entry->filename, &dummy);
+ if (entry_fullpath)
+ {
+ free (entry->filename);
+ entry->filename = entry_fullpath;
+ }
+
+ free_history_node (dir_node);
+ return entry;
+ }
+ return 0;
+}
+
diff --git a/macros/texinfo/texinfo/info/display.c b/macros/texinfo/texinfo/info/display.c
new file mode 100644
index 0000000000..abd17db537
--- /dev/null
+++ b/macros/texinfo/texinfo/info/display.c
@@ -0,0 +1,1009 @@
+/* display.c -- How to display Info windows.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+#include "display.h"
+#include "session.h"
+#include "tag.h"
+#include "signals.h"
+#include "variables.h"
+
+static void free_display (DISPLAY_LINE **display);
+static DISPLAY_LINE **make_display (int width, int height);
+
+/* 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;
+
+ signal_block_winch ();
+ for (i = 0; display[i]; i++)
+ {
+ display[i]->text[0] = '\0';
+ display[i]->textlen = 0;
+ display[i]->inverse = 0;
+ }
+ signal_unblock_winch ();
+}
+
+/* Non-zero if we didn't completely redisplay a window. */
+int display_was_interrupted_p = 0;
+
+/* Check each window on the screen, and update it if it needs updating. */
+void
+display_update_display (void)
+{
+ register WINDOW *win;
+
+ /* Block window resize signals (SIGWINCH) while accessing the the_display
+ object, because the signal handler may reallocate it out from under our
+ feet. */
+ signal_block_winch ();
+ display_was_interrupted_p = 0;
+
+ for (win = windows; 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);
+ signal_unblock_winch ();
+}
+
+/* Return the screen column of where to write to screen to update line to
+ match A, given that B contains the current state of the line. *PPOS gets
+ the offset into the string A to write from. */
+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;
+ int pos = 0;
+ int first_escape = -1;
+ int escape_pos = -1;
+
+ for (i = 0, mbi_init (itra, a, alen), mbi_init (itrb, b, blen);
+ mbi_avail (itra) && mbi_avail (itrb);
+ i += wcwidth (itra.cur.wc), mbi_advance (itra), mbi_advance (itrb))
+ {
+ if (mb_cmp (mbi_cur (itra), mbi_cur (itrb)))
+ break;
+
+ if (first_escape == -1 && *mbi_cur_ptr (itra) == '\033')
+ {
+ first_escape = i;
+ escape_pos = pos;
+ }
+ pos += mb_len (mbi_cur (itra));
+ }
+
+ if (mbi_avail (itra) || mbi_avail (itrb))
+ {
+ if (first_escape != -1)
+ {
+ *ppos = escape_pos;
+ return first_escape;
+ }
+ else
+ {
+ /* If there was a difference in the line, and there was an escape
+ character, return the position of the escape character, as it could
+ start a terminal escape sequence. */
+ *ppos = pos;
+ return i;
+ }
+ }
+
+ /* Otherwise, no redrawing is required. */
+ return -1;
+}
+
+/* Update line PL_NUM of the screen to be PRINTED_LINE, which is PL_BYTES long
+ and takes up PL_CHARS columns. */
+static int
+display_update_line (long pl_num, char *printed_line,
+ long pl_bytes, long pl_chars)
+{
+ DISPLAY_LINE **display = the_display;
+ DISPLAY_LINE *entry;
+
+ entry = display[pl_num];
+
+ /* 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, or if the entry is marked as
+ invalid, then clear the line from the screen first. */
+ if (entry->inverse)
+ {
+ terminal_goto_xy (0, pl_num);
+ terminal_clear_to_eol ();
+ entry->inverse = 0;
+ entry->text[0] = '\0';
+ entry->textlen = 0;
+ }
+
+ i = find_diff (printed_line, pl_bytes,
+ entry->text, strlen (entry->text), &off);
+
+ /* If the lines differed at all, we must do some redrawing. */
+ if (i != -1)
+ {
+ /* Move to the proper point on the terminal. */
+ terminal_goto_xy (i, pl_num);
+
+ /* If there is any text to print, print it. */
+ terminal_put_text (printed_line + off);
+
+ /* If the printed text didn't extend all the way to the edge
+ of the screen, and text was appearing between here and the
+ edge of the screen, clear from here to the end of the
+ line. */
+ if ((pl_chars < screenwidth && pl_chars < 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_chars;
+
+ /* 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;
+ }
+ return 0;
+}
+
+
+/* Similar to decide_if_in_match, but used for reference highlighting.
+ Given an array REFERENCES with regions, starting at *REF_INDEX decide
+ if we are inside a region at offset OFF. The regions are assumed not
+ to overlap and to be in order. */
+static void
+decide_if_in_reference (long off, int *in_ref, REFERENCE **references,
+ int *ref_index)
+{
+ int i = *ref_index;
+ int m = *in_ref;
+
+ for (; (references[i]); i++)
+ {
+ if (references[i]->start > off)
+ break;
+
+ m = 1;
+
+ if (references[i]->end > off)
+ break;
+
+ m = 0;
+ }
+
+ *ref_index = i;
+ *in_ref = m;
+}
+
+/* Used when processing a line to be displayed from a node. DEFAULT is the
+ value when the line has no special styles like underlined references or
+ highlighted search matches. Otherwise, a line is processed once with
+ COLLECT as the value, and if it differs to what is on the display already,
+ it is processed with WRITEOUT and written to the display. */
+static int writing_out;
+#define DEFAULT 0
+#define COLLECT 1
+#define WRITEOUT 2 /* Values for writing_out global. */
+
+/* Combine rendition masks that are active, in order of priority,
+ then check what's currently active on the display, and output
+ the necessary codes to switch. The list of rendition masks is
+ the complete information about what the style should now be.
+ RENDITION3 takes priority over RENDITION2, which in turn takes
+ priority over RENDITION1. */
+static void
+wrap_terminal_switch_rendition (struct text_buffer *printed_line,
+ RENDITION rendition1,
+ RENDITION rendition2,
+ RENDITION rendition3)
+{
+ long int desired_rendition = 0;
+ desired_rendition = rendition1.value;
+ desired_rendition &= ~rendition2.mask;
+ desired_rendition |= rendition2.value;
+ desired_rendition &= ~rendition3.mask;
+ desired_rendition |= rendition3.value;
+
+ if (writing_out == WRITEOUT)
+ terminal_switch_rendition (desired_rendition);
+ else
+ {
+ /* Guarantee that each byte is non-zero, by having at least one
+ non-zero bit in it. See ZERO1_MASK symbol in display.c. */
+ desired_rendition = ~desired_rendition;
+
+ /* The text added here is only used internally to see when the
+ display has changed, and is not output to the terminal. */
+ text_buffer_add_string (printed_line, "\033", 1);
+ text_buffer_add_string (printed_line, (char *) &desired_rendition,
+ sizeof (long));
+ }
+}
+
+/* Set in display_update_node_text if matches or references are to be
+ distinguished with terminal appearance modes. */
+static MATCH_STATE *matches;
+static REFERENCE **refs;
+static size_t match_index;
+static int ref_index;
+
+/* Number of screen columns output so far in a line. */
+static int pl_chars;
+
+/* Whether we are currently outputting a highlighted reference. This can be
+ carried over from one line to another. */
+static int ref_highlighted;
+
+static int pl_num; /* Number of printed lines done so far. */
+
+RENDITION ref_rendition = {UNDERLINE_MASK, UNDERLINE_MASK};
+RENDITION hl_ref_rendition = {UNDERLINE_MASK, 0};
+RENDITION match_rendition = {STANDOUT_MASK, STANDOUT_MASK};
+
+
+/* Process a line from the node in WIN starting at ITER, and advancing ITER
+ to the end of the line. What is done with the line depends on the value
+ of WRITING_OUT.
+ If the line ends in a newline character, set *DELIM to 1. */
+static void
+display_process_line (WINDOW *win,
+ mbi_iterator_t *iter_inout,
+ struct text_buffer *tb_printed_line,
+ int *delim)
+{
+ mbi_iterator_t iter;
+ const char *cur_ptr;
+ size_t pchars = 0; /* Printed chars */
+ size_t pbytes = 0; /* Bytes to output. */
+ char *rep;
+ int in_match = 0;
+ int in_ref = 0, in_ref_proper = 0;
+ RENDITION empty = {0, 0};
+
+ int point_in_line;
+
+ if (win->point >= win->line_starts[win->pagetop + pl_num]
+ && win->point < win->line_starts[win->pagetop + pl_num + 1])
+ point_in_line = 1;
+ else
+ point_in_line = 0;
+
+ iter = *iter_inout;
+
+ while (1)
+ {
+ int was_in_ref_proper = in_ref_proper;
+ int was_in_match = in_match;
+
+ if (!mbi_avail (iter))
+ break;
+ cur_ptr = mbi_cur_ptr (iter);
+
+ if (matches && matches_ready (matches)
+ && !at_end_of_matches (matches, match_index))
+ {
+ int was_in_match = in_match;
+ decide_if_in_match (cur_ptr - win->node->contents,
+ &in_match, matches, &match_index);
+
+ if (!was_in_match && in_match && writing_out == DEFAULT)
+ writing_out = COLLECT;
+ }
+
+ if (refs && refs[ref_index])
+ {
+ int was_in_ref = in_ref;
+ decide_if_in_reference (cur_ptr - win->node->contents,
+ &in_ref, refs, &ref_index);
+
+ if (was_in_ref && !in_ref)
+ {
+ in_ref_proper = ref_highlighted = 0;
+ }
+ else if (!was_in_ref && in_ref)
+ {
+ if (writing_out == DEFAULT)
+ writing_out = COLLECT;
+
+ /* Decide if this reference should be highlighted. */
+ if (point_in_line && win->point < refs[ref_index]->end)
+ {
+ /* The reference in is the part of the line after
+ the cursor, or the reference contains the cursor. */
+ point_in_line = 0;
+ ref_highlighted = 1;
+ }
+ else if (point_in_line
+ && (!refs[ref_index + 1]
+ || refs[ref_index + 1]->start
+ >= win->line_starts[win->pagetop + pl_num + 1]))
+ {
+ /* The reference label is before the cursor in
+ the current line and none occurs after it in
+ the current line. */
+ point_in_line = 0;
+ ref_highlighted = 1;
+ }
+ else if (win->point >= refs[ref_index]->start
+ && win->point < refs[ref_index]->end)
+ {
+ /* The point is in a cross-reference, but not in the
+ current line. */
+ ref_highlighted = 1;
+ }
+ else if (win->point >= win->line_starts
+ [win->pagetop + pl_num + 1]
+ && win->point < win->line_starts
+ [win->pagetop + pl_num + 2]
+ && refs[ref_index]->end
+ >= win->line_starts[win->pagetop + pl_num + 1]
+ && (!refs[ref_index + 1]
+ || refs[ref_index + 1]->start
+ >= win->line_starts[win->pagetop + pl_num + 2]))
+ {
+ /* Point is in the next line, not inside this reference,
+ but this reference continues onto the next line and
+ no other reference follows it in the line. */
+ ref_highlighted = 1;
+ }
+ }
+ }
+
+ if (in_ref && !in_ref_proper && !strchr (" \t", *cur_ptr))
+ in_ref_proper = 1;
+
+ if (was_in_ref_proper != in_ref_proper || was_in_match != in_match)
+ {
+ /* Calculate the new rendition for output characters, and call
+ the function to switch to it. */
+ RENDITION ref = {0, 0};
+ RENDITION match = {0, 0};
+
+ if (in_ref_proper)
+ ref = ref_highlighted && hl_ref_rendition.mask
+ ? hl_ref_rendition : ref_rendition;
+ if (in_match)
+ match = match_rendition;
+ if (!ref_highlighted)
+ {
+ wrap_terminal_switch_rendition (tb_printed_line,
+ ref, match, empty);
+ }
+ else
+ {
+ wrap_terminal_switch_rendition (tb_printed_line,
+ match, ref, empty);
+ }
+ }
+
+ rep = printed_representation (&iter, delim, pl_chars,
+ &pchars, &pbytes);
+
+ /* If a newline character has been seen, or we have reached the
+ edge of the display. */
+ if (*delim || pl_chars + pchars >= win->width)
+ break;
+
+ if (rep)
+ {
+ if (writing_out != WRITEOUT)
+ text_buffer_add_string (tb_printed_line, rep, pbytes);
+ else
+ terminal_write_chars (rep, pbytes);
+
+ pl_chars += pchars;
+ }
+ mbi_advance (iter);
+ }
+
+ if (writing_out != DEFAULT)
+ wrap_terminal_switch_rendition (tb_printed_line, empty, empty, empty);
+
+ *iter_inout = iter;
+}
+
+/* Update the part of WIN containing text from a node, i.e. not the blank
+ part at the end or a modeline.
+ 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. Return number of lines printed. */
+int
+display_update_node_text (WINDOW *win)
+{
+ static struct text_buffer tb_printed_line; /* Buffer for a printed line. */
+
+ mbi_iterator_t iter; /* Used to iterate through part of node displayed. */
+ mbi_iterator_t bol_iter; /* Keep reference to beginning of each line. */
+ int bol_ref_index = 0, bol_match_index = 0;
+ int bol_ref_highlighted;
+
+ int finish;
+
+ matches = 0;
+ refs = 0;
+ if (match_rendition.mask)
+ matches = &win->matches;
+ if (ref_rendition.mask || hl_ref_rendition.mask)
+ refs = win->node->references;
+
+ pl_num = 0;
+
+ ref_highlighted = 0;
+
+ writing_out = DEFAULT; /* Global variable, declared above. */
+ ref_index = match_index = 0;
+
+ mbi_init (iter, win->node->contents + win->line_starts[win->pagetop],
+ win->node->nodelen - win->line_starts[win->pagetop]);
+ mbi_avail (iter);
+ while (1)
+ {
+ int delim;
+ mbi_copy (&bol_iter, &iter);
+ bol_ref_index = ref_index;
+ bol_match_index = match_index;
+ bol_ref_highlighted = ref_highlighted;
+
+ /* Come back here at end of line when write_out == COLLECT */
+start_of_line:
+ pl_chars = 0;
+
+ text_buffer_reset (&tb_printed_line);
+
+ delim = 0;
+ /* Check if we have processed all the lines in the window. */
+ if (pl_num == win->height)
+ break;
+
+ /* Check if this line of the window is off the screen. This might
+ happen if the screen was resized very small. */
+ if (win->first_row + pl_num >= screenheight)
+ break;
+
+ display_process_line (win, &iter, &tb_printed_line, &delim);
+
+ /* End of printed line. */
+ text_buffer_add_char (&tb_printed_line, '\0');
+
+ finish = 0;
+ /* If there are no highlighted regions in a line, we output the line with
+ display_update_line, which does some optimization of the redisplay.
+ Otherwise, the entire line is output in this function. */
+ if (writing_out == DEFAULT)
+ {
+ finish = display_update_line (win->first_row + pl_num,
+ text_buffer_base (&tb_printed_line),
+ text_buffer_off (&tb_printed_line) - 1,
+ pl_chars);
+ }
+ else if (writing_out == COLLECT)
+ {
+ /* Check if the line differs from what is already on the display,
+ and if so, go back to the start of the line and display it for
+ real. */
+ DISPLAY_LINE *entry = the_display[win->first_row + pl_num];
+ if (strcmp (tb_printed_line.base,
+ the_display[win->first_row + pl_num]->text))
+ {
+ if (tb_printed_line.off > screenwidth)
+ {
+ entry->text = xrealloc (entry->text,
+ tb_printed_line.off + 1);
+ }
+ strcpy (entry->text, tb_printed_line.base);
+ /* Record that the contents of this DISPLAY_LINE isn't
+ literally what is on the display. */
+ entry->textlen = 0;
+ entry->inverse = 1;
+ mbi_copy (&iter, &bol_iter);
+ mbi_avail (bol_iter);
+ ref_index = bol_ref_index;
+ match_index = bol_match_index;
+ terminal_goto_xy (0, win->first_row + pl_num);
+ ref_highlighted = bol_ref_highlighted;
+ writing_out = WRITEOUT;
+ goto start_of_line;
+ }
+ else
+ writing_out = DEFAULT;
+ }
+ else /* writing_out == WRITEOUT */
+ {
+ /* We have just written out this line to the display. */
+ terminal_clear_to_eol ();
+ writing_out = DEFAULT;
+ }
+
+ /* Check if a line continuation character should be displayed.
+ Don't print one on the very last line of the display, as this could
+ cause it to scroll. */
+ if (delim)
+ mbi_advance (iter);
+ else if (win->first_row + pl_num <= the_screen->height - 2)
+ {
+ terminal_goto_xy (win->width - 1, win->first_row + pl_num);
+
+ if (!(win->flags & W_NoWrap))
+ terminal_put_text ("\\");
+ else
+ {
+ terminal_put_text ("$");
+
+ /* If this window has chosen not to wrap lines, skip to the
+ end of the logical line in the buffer, and start a new
+ line here. */
+ for (; mbi_avail (iter); mbi_advance (iter))
+ if (mb_len (mbi_cur (iter)) == 1
+ && *mbi_cur_ptr (iter) == '\n')
+ {
+ mbi_advance (iter);
+ break;
+ }
+ }
+ fflush (stdout);
+ }
+
+ pl_num++;
+ if (finish)
+ break; /* Display was interrupted by typed input. */
+
+ if (!mbi_avail (iter))
+ break;
+ }
+
+ /* Unlike search match highlighting, we always turn reference highlighting
+ off at the end of each line, so the following isn't needed. */
+ /* terminal_end_underline (); */
+
+ return pl_num;
+}
+#undef DEFAULT
+#undef COLLECT
+#undef WRITEOUT /* values for writing_out global */
+
+/* Update one window on the screen. */
+void
+display_update_one_window (WINDOW *win)
+{
+ size_t line_index = 0;
+ DISPLAY_LINE **display = the_display;
+
+ signal_block_winch ();
+
+ /* If display is inhibited, that counts as an interrupted display. */
+ if (display_inhibited)
+ {
+ display_was_interrupted_p = 1;
+ goto funexit;
+ }
+
+ /* If the window has no height, 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)
+ goto funexit;
+
+ /* 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))
+ goto funexit;
+
+ /* If this window has a modeline, it might need to be redisplayed. Do
+ this before the rest of the window to aid in navigation in case the
+ rest of the window is slow to update (for example, if it has lots of
+ search matches to be displayed). */
+ 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))
+ /* Check screen isn't very small. */
+ && line_index < the_screen->height)
+ {
+ 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);
+ }
+ }
+
+ if (win->node)
+ {
+ if (!win->line_starts)
+ calculate_line_starts (win);
+ line_index = display_update_node_text (win);
+
+ if (display_was_interrupted_p)
+ goto funexit;
+ }
+
+ /* 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, or if we don't know what is on the line,
+ clear this line. */
+ if (entry->textlen || entry->inverse)
+ {
+ entry->textlen = 0;
+ entry->text[0] = '\0';
+ entry->inverse = 0;
+
+ terminal_goto_xy (0, win->first_row + line_index);
+ terminal_clear_to_eol ();
+ fflush (stdout);
+
+ if (info_any_buffered_input_p ())
+ {
+ display_was_interrupted_p = 1;
+ goto funexit;
+ }
+ }
+ }
+
+ fflush (stdout);
+
+ /* Okay, this window doesn't need updating anymore. */
+ win->flags &= ~W_UpdateWindow;
+funexit:
+ signal_unblock_winch ();
+}
+
+/* Scroll screen lines from START inclusive to END exclusive down
+ by AMOUNT lines. Negative AMOUNT means move them up. */
+static void
+display_scroll_region (int start, int end, int amount)
+{
+ int i;
+ DISPLAY_LINE *temp;
+
+ /* Do it on the screen. */
+ terminal_scroll_region (start, end, amount);
+
+ /* Now do it in the display buffer so our contents match the screen. */
+ if (amount > 0)
+ {
+ for (i = end - 1; i >= start + amount; i--)
+ {
+ /* Swap rows i and (i - amount). */
+ temp = the_display[i];
+ the_display[i] = the_display[i - amount];
+ the_display[i - amount] = temp;
+ }
+
+ /* Clear vacated lines */
+ for (i = start; i < start + amount && i < end; i++)
+ {
+ the_display[i]->text[0] = '\0';
+ the_display[i]->textlen = 0;
+ the_display[i]->inverse = 0;
+ }
+ }
+ else
+ {
+ amount *= -1;
+ for (i = start; i <= end - 1 - amount; i++)
+ {
+ /* Swap rows i and (i + amount). */
+ temp = the_display[i];
+ the_display[i] = the_display[i + amount];
+ the_display[i + amount] = temp;
+ }
+
+ /* Clear vacated lines */
+ for (i = end - 1; i >= end - amount && i >= start; i--)
+ {
+ the_display[i]->text[0] = '\0';
+ the_display[i]->textlen = 0;
+ the_display[i]->inverse = 0;
+ }
+ }
+}
+
+/* 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 && !terminal_can_scroll_region)
+ 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;
+
+ /* Use scrolling region if we can because it doesn't affect the area
+ below the area we want to scroll. */
+ if (terminal_can_scroll_region)
+ {
+ display_scroll_region (start, end, amount);
+ return;
+ }
+
+ /* Otherwise scroll by deleting and inserting lines. */
+
+ if (amount < 0)
+ start -= amount;
+ else
+ end -= amount;
+
+ /* 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 = 1; 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;
+ }
+ }
+ else
+ {
+ 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,
+ long *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/macros/texinfo/texinfo/info/display.h b/macros/texinfo/texinfo/info/display.h
new file mode 100644
index 0000000000..df9dcbb978
--- /dev/null
+++ b/macros/texinfo/texinfo/info/display.h
@@ -0,0 +1,73 @@
+/* display.h -- How the display in Info is done.
+
+ Copyright 1993-2019 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. */
+
+#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; /* Screen line is either in inverse video, or
+ 'text' does not represent what is on the screen. */
+} 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. */
+void display_initialize_display (int width, int height);
+
+/* Clear all of the lines in DISPLAY making the screen blank. */
+void display_clear_display (DISPLAY_LINE **display);
+
+/* Update the windows on the display. */
+void display_update_display (void);
+
+/* Display WIN on THE_DISPLAY. Unlike display_update_display (), this
+ function only does one window. */
+void display_update_one_window (WINDOW *win);
+
+/* Move the screen cursor to directly over the current character in WINDOW. */
+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. */
+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. */
+void display_scroll_line_starts (WINDOW *window, int old_pagetop,
+ long *old_starts, int old_count);
+
+#endif /* not INFO_DISPLAY_H */
diff --git a/macros/texinfo/texinfo/info/doc.h b/macros/texinfo/texinfo/info/doc.h
new file mode 100644
index 0000000000..931237fd87
--- /dev/null
+++ b/macros/texinfo/texinfo/info/doc.h
@@ -0,0 +1,73 @@
+/* doc.h -- Structures associating function pointers with documentation.
+
+ Copyright 1993-2019 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. */
+
+#if !defined (DOC_H)
+#define DOC_H
+
+#include "info.h"
+
+/* 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;
+ int *keyseq;
+} FUNCTION_KEYSEQ;
+
+/* Structure describing an Info command. */
+typedef struct
+{
+ VFunction *func; /* Pointer to function implementing command. */
+ char *func_name; /* Name of this command. */
+ FUNCTION_KEYSEQ *keys; /* Key sequences that could invoke this command. */
+ char *doc; /* Documentation string. */
+} FUNCTION_DOC;
+
+/* Array of FUNCTION_DOC structures defined in doc.c, generated
+ by the makedoc utility. */
+extern FUNCTION_DOC function_doc_array[];
+
+typedef FUNCTION_DOC InfoCommand;
+#define InfoCmd(fn) (&function_doc_array[A_##fn])
+
+#include "infomap.h" /* for Keymap. */
+
+extern char *function_name (InfoCommand *cmd);
+extern InfoCommand *named_function (char *name);
+
+extern char *function_documentation (InfoCommand *cmd);
+extern char *pretty_keyname (int key);
+extern char *pretty_keyseq (int *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/macros/texinfo/texinfo/info/dribble.c b/macros/texinfo/texinfo/info/dribble.c
new file mode 100644
index 0000000000..43ab39cec8
--- /dev/null
+++ b/macros/texinfo/texinfo/info/dribble.c
@@ -0,0 +1,66 @@
+/* dribble.c -- dribble files for Info.
+
+ Copyright 1993-2019 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. */
+
+#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/macros/texinfo/texinfo/info/dribble.h b/macros/texinfo/texinfo/info/dribble.h
new file mode 100644
index 0000000000..41274982e5
--- /dev/null
+++ b/macros/texinfo/texinfo/info/dribble.h
@@ -0,0 +1,37 @@
+/* dribble.h -- Functions and vars declared in dribble.c.
+
+ Copyright 1993-2019 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. */
+
+#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/macros/texinfo/texinfo/info/echo-area.c b/macros/texinfo/texinfo/info/echo-area.c
new file mode 100644
index 0000000000..89296a7118
--- /dev/null
+++ b/macros/texinfo/texinfo/info/echo-area.c
@@ -0,0 +1,1543 @@
+/* echo-area.c -- how to read a line in the echo area.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+#include "session.h"
+#include "display.h"
+#include "info-utils.h"
+#include "echo-area.h"
+
+/* Non-zero means that C-g was used to quit reading input. */
+int info_aborted_echo_area = 0;
+
+/* Non-zero means that the echo area is being used to read input. */
+int echo_area_is_active = 0;
+
+/* The address of the last command executed in the echo area. */
+static 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]; /* Contents of echo area, including
+ any prompt. */
+static int input_line_point; /* Offset into input_line of point */
+static int input_line_beg; /* End of prompt, and start of user input. */
+static int input_line_end; /* End of user input. */
+
+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);
+static void ea_remove_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;
+
+/* 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;
+ }
+}
+
+/* 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)
+ {
+ forget_node (win);
+ 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 ())
+ {
+ info_delete_window_internal (compwin);
+ }
+ }
+}
+
+/* Set up a new input line with PROMPT. */
+static void
+initialize_input_line (const char *prompt)
+{
+ strcpy (input_line, prompt);
+ 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;
+}
+
+static void
+read_and_dispatch_in_echo_area (void)
+{
+ while (1)
+ {
+ int count;
+ VFunction *cmd;
+ int lk = 0;
+
+ lk = echo_area_last_command_was_kill;
+ echo_area_prep_read ();
+
+ if (!info_any_buffered_input_p ())
+ display_update_display ();
+
+ display_cursor_at_point (active_window);
+
+ /* Do the selected command. */
+ cmd = read_key_sequence (echo_area_keymap, 0, 0, 1, &count);
+ if (cmd)
+ {
+ (*cmd) (the_echo_area, count);
+ ea_last_executed_command = cmd;
+ }
+ else
+ ea_last_executed_command = 0;
+
+ /* 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 (cmd == ea_newline || info_aborted_echo_area)
+ {
+ ea_last_executed_command = NULL;
+ break;
+ }
+ }
+}
+
+/* Read a line of text in the echo area. Return a malloc ()'ed string,
+ or NULL if the user aborted out of this read. PROMPT is the prompt
+ to print before reading the line. */
+char *
+info_read_in_echo_area (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 (active_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. */
+ read_and_dispatch_in_echo_area ();
+
+ window_clear_echo_area ();
+
+ 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';
+
+ /* Mark the line map as invalid. This causes window_compute_line_map to
+ recalculate it when it is called via display_cursor_at_point below.
+ Otherwise adding or removing multi-column characters (like tabs) lead
+ to incorrect cursor positioning. */
+ the_echo_area->line_map.used = 0;
+
+ 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);
+ else
+ {
+ mbi_iterator_t iter;
+ mbi_init (iter, input_line + input_line_point,
+ input_line_end - input_line_point);
+ while (mbi_avail (iter) && count--)
+ {
+ mbi_advance (iter);
+ input_line_point = mbi_cur_ptr (iter) - input_line;
+ 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);
+ else
+ {
+ char *ptr = input_line + input_line_point;
+ while (count--)
+ {
+ /* Go back one character. Go back by bytes until we look at a valid
+ multi-byte sequence. */
+ ptr = input_line + input_line_point;
+ while (ptr > input_line)
+ {
+ ptr--;
+ if ((long) mbrlen (ptr,
+ input_line + input_line_point - ptr, 0) > 0)
+ break;
+ }
+ input_line_point = ptr - input_line;
+ 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);
+ 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);
+ 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"))
+{
+ if (count < 0)
+ ea_rubout (window, -count);
+ else
+ {
+ int orig_point;
+ if (input_line_point == input_line_end)
+ return;
+
+ orig_point = input_line_point;
+ ea_forward (window, count);
+ if (ea_explicit_arg || count > 1)
+ ea_kill_text (orig_point, input_line_point);
+ else
+ ea_remove_text (orig_point, input_line_point);
+ input_line_point = orig_point;
+ }
+}
+
+DECLARE_INFO_COMMAND (ea_rubout, _("Delete the character behind the cursor"))
+{
+ if (count < 0)
+ ea_delete (window, -count);
+ else
+ {
+ int start;
+
+ if (input_line_point == input_line_beg)
+ {
+ /* Abort echo area read if backspace at start of area and user input
+ is empty. */
+ if (input_line_beg == input_line_end)
+ info_aborted_echo_area = 1;
+ return;
+ }
+
+ start = input_line_point;
+ ea_backward (window, count);
+
+ if (ea_explicit_arg || count > 1)
+ ea_kill_text (start, input_line_point);
+ else
+ ea_delete (window, count);
+ }
+}
+
+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"))
+{
+ int character;
+
+ character = get_another_input_key ();
+
+ if (character >= 256 || character < 0)
+ return;
+
+ ea_insert (window, count, character);
+}
+
+void
+ea_insert (WINDOW *window, int count, int key)
+{
+ 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');
+}
+
+/* Swap characters in INPUT_LINE. The first starts at C1 and ends at C1E, the
+ second starts at C2 and ends at C2E, with C1 < C1E <= C2 < C2E. */
+static void
+ea_swap_chars (int c1, int c1e, int c2, int c2e)
+{
+ int len1, len2;
+ char *tmp;
+
+ len1 = c1e - c1;
+ len2 = c2e - c2;
+
+ if (len1 >= len2)
+ {
+ /* Save first character. */
+ tmp = xmalloc (len1);
+ memcpy (tmp, input_line + c1, len1);
+
+ /* Move the second character to where the first was. */
+ memcpy (input_line + c1, input_line + c2, len2);
+
+ /* Shift the part in between the characters backwards. */
+ memmove (input_line + c1 + len2, input_line + c1e, c2 - c1e);
+
+ /* Restore the first character at the end. */
+ memcpy (input_line + c2 - (len1 - len2), tmp, len1);
+ free (tmp);
+ }
+ else /* len2 > len1 */
+ {
+ /* Save second character. */
+ tmp = xmalloc (len2);
+ memcpy (tmp, input_line + c2, len2);
+
+ /* Move first character to end of second character. */
+ memcpy (input_line + c2e - len1, input_line + c1, len1);
+
+ /* Shift the part in between the characters forwards. */
+ memmove (input_line + c1e + (len2 - len1), input_line + c1e, c2 - c1e);
+
+ /* Place the second character at the beginning. */
+ memcpy (input_line + c1, tmp, len2);
+ free (tmp);
+ }
+}
+
+/* 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"))
+{
+ while (count)
+ {
+ if (input_line_point == input_line_end || count < 0)
+ {
+ /* Swap two characters before point. */
+ int c1, c2, c2e;
+ c2e = input_line_point;
+
+ ea_backward (window, 1);
+ c2 = input_line_point;
+
+ ea_backward (window, 1);
+ c1 = input_line_point;
+
+ if (c1 != c2) /* There are two characters in this line. */
+ ea_swap_chars (c1, c2, c2, c2e);
+
+ if (count > 0)
+ /* Restore point. */
+ input_line_point = c2e;
+ else
+ input_line_point = c1 + c2e - c2;
+ }
+ else
+ {
+ int c1, c2, c2e;
+
+ c2 = input_line_point;
+
+ ea_forward (window, 1);
+ c2e = input_line_point;
+ if (c2e == c2)
+ return; /* Shouldn't happen. */
+
+ input_line_point = c2;
+ ea_backward (window, 1);
+ c1 = input_line_point;
+ if (c1 == c2e)
+ return; /* Can't go earlier in line. */
+
+ ea_swap_chars (c1, c2, c2, c2e);
+
+ /* Set point is after swapped pair. */
+ input_line_point = c2e;
+ }
+
+ 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);
+}
+
+/* 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);
+ else
+ {
+ ea_forward_word (window, count);
+
+ 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);
+ else
+ {
+ ea_backward_word (window, count);
+
+ if (input_line_point != orig_point)
+ ea_kill_text (orig_point, input_line_point);
+ }
+ window_line_map_init (window);
+}
+
+/* Remove text from offsets FROM to TO. Unlike 'ea_kill_text' nothing is
+ saved in the kill ring. */
+static void
+ea_remove_text (int from, int to)
+{
+ int distance, i, counter;
+ counter = input_line_end - to;
+ distance = to - from;
+
+ for (i = from; counter; i++, counter--)
+ input_line[i] = input_line[i + distance];
+ input_line_end -= distance;
+}
+
+/* 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 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. */
+ ea_remove_text (from, to);
+
+ /* 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. */
+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 size_t completions_found_index = 0;
+static size_t completions_found_slots = 0;
+
+/* The lowest common denominator found while completing. */
+static REFERENCE *LCD_completion;
+
+/* Function to choose which references to offer as completion options. */
+static reference_bool_fn completion_exclude_func = 0;
+
+/* Internal functions used by the user calls. */
+static void build_completions (void);
+static 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. Use EXCLUDE_FUNC to
+ exclude items in COMPLETIONS. */
+char *
+info_read_completing_internal (const char *prompt, REFERENCE **completions,
+ int force, reference_bool_fn exclude_func)
+{
+ 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 (active_window);
+
+ /* Save away the list of items to complete over. */
+ echo_area_completion_items = completions;
+ completions_must_be_rebuilt ();
+ completion_exclude_func = exclude_func;
+
+ active_window = the_echo_area;
+ echo_area_is_active++;
+ window_line_map_init (active_window);
+
+ /* Read characters in the echo area. */
+ while (1)
+ {
+ read_and_dispatch_in_echo_area ();
+
+ 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)
+ {
+ size_t 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"));
+ free (line);
+ continue;
+ }
+ }
+ break;
+ }
+ echo_area_is_active--;
+ window_clear_echo_area ();
+
+ /* 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 (const char *prompt, REFERENCE **completions)
+{
+ return info_read_completing_internal (prompt, completions, 1, 0);
+}
+
+/* Read a line in the echo area allowing completion over COMPLETIONS, but
+ not requiring it. */
+char *
+info_read_maybe_completing (const char *prompt, REFERENCE **completions)
+{
+ return info_read_completing_internal (prompt, completions, 0, 0);
+}
+
+/* Read a line in the echo area with completion over COMPLETIONS, using
+ EXCLUDE to exclude items from the completion list. */
+char *
+info_read_completing_in_echo_area_with_exclusions (const char *prompt,
+ REFERENCE **completions, reference_bool_fn exclude)
+{
+ return info_read_completing_internal (prompt, completions, 1, exclude);
+}
+
+DECLARE_INFO_COMMAND (ea_possible_completions, _("List possible completions"))
+{
+ build_completions ();
+
+ if (!completions_found_index)
+ {
+ terminal_ring_bell ();
+ inform_in_echo_area (_("No completions"));
+ }
+ else if (completions_found_index == 1)
+ {
+ inform_in_echo_area (_("Sole completion"));
+ }
+ else
+ {
+ size_t i, l;
+ size_t limit, iterations, max_label = 0;
+ struct text_buffer message;
+
+ text_buffer_init (&message);
+ text_buffer_printf (&message, ngettext ("%d completion:\n",
+ "%d completions:\n",
+ completions_found_index),
+ completions_found_index);
+
+ /* 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);
+ text_buffer_printf (&message, "%s", label);
+
+ if (j + 1 < limit)
+ {
+ for (k = 0; k < max_label - printed_length; k++)
+ text_buffer_printf (&message, " ");
+ }
+ }
+ l += iterations;
+ }
+ text_buffer_printf (&message, "\n");
+ }
+
+ /* Make a new node to hold onto possible completions. Don't destroy
+ dangling pointers. */
+ {
+ NODE *temp;
+
+ temp = text_buffer_to_node (&message);
+ name_internal_node (temp, xstrdup (compwin_name));
+ possible_completions_output_node = temp;
+ possible_completions_output_node->flags |= N_WasRewritten;
+ }
+
+ /* 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)
+ {
+ remember_calling_window (calling_window);
+
+ active_window = calling_window;
+ compwin = window_make_window ();
+ window_change_window_height
+ (compwin, -(compwin->height - (iterations + 2)));
+
+ echo_area_completions_window = compwin;
+ active_window = the_echo_area;
+ }
+ else
+ compwin = calling_window;
+ }
+
+ /* Clear any completion nodes already showing from the window history.
+ This could happen if the user presses TAB more than once. */
+ while (compwin->node && (compwin->node->flags & N_IsInternal)
+ && !strcmp (compwin->node->nodename, compwin_name))
+ forget_node (compwin);
+
+ info_set_node_of_window (compwin, possible_completions_output_node);
+
+ display_update_display ();
+ }
+ }
+}
+
+DECLARE_INFO_COMMAND (ea_complete, _("Insert completion"))
+{
+ if (ea_last_executed_command == (VFunction *) ea_complete)
+ {
+ ea_possible_completions (window, count);
+ 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);
+ 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)
+{
+ 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)
+{
+ size_t i;
+ int 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;
+ }
+
+ 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++)
+ {
+ /* Skip certain items (for example, we might only want
+ a list of menu items). */
+ if (completion_exclude_func && completion_exclude_func (entry))
+ continue;
+
+ if (mbsncasecmp (request, entry->label, len) == 0)
+ add_pointer_to_array (entry, completions_found_index,
+ completions_found, completions_found_slots,
+ 20);
+
+ if (!informed_of_lengthy_job && completions_found_index > 100)
+ {
+ informed_of_lengthy_job = 1;
+ window_message_in_echo_area (_("Building completions..."));
+ }
+ }
+
+ if (!completions_found_index)
+ return;
+
+ /* Sort and prune duplicate entries from the completions array. */
+ remove_completion_duplicates ();
+
+ /* If there is only one completion, just return that. */
+ if (completions_found_index == 1)
+ {
+ LCD_completion = completions_found[0];
+ return;
+ }
+
+ /* Find the least common denominator. */
+ {
+ long shortest = 100000;
+
+ for (i = 1; i < completions_found_index; i++)
+ {
+ register int j;
+ int c1, c2;
+
+ for (j = 0;
+ (c1 = tolower (completions_found[i - 1]->label[j]))
+ && (c2 = tolower (completions_found[i]->label[j]));
+ j++)
+ if (c1 != c2)
+ break;
+
+ if (shortest > j)
+ shortest = j;
+ }
+
+ 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)
+{
+ size_t 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;
+
+ compwin = get_internal_info_window (compwin_name);
+
+ if (!compwin)
+ compwin = calling_window;
+
+ /* Let info_scroll_forward () do the work, and print any messages that
+ need to be displayed. */
+ info_scroll_forward (compwin, count);
+}
+
+/* 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;
+ REFERENCE **comp_items;
+ int point, beg, end;
+ int must_complete;
+ NODE node;
+ WINDOW *compwin;
+} PUSHED_EA;
+
+static PUSHED_EA **pushed_echo_areas = NULL;
+static size_t pushed_echo_areas_index = 0;
+static size_t 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->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);
+
+ 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_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)
+{
+ size_t 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 */
+/* */
+/* **************************************************************** */
+
+/* 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/macros/texinfo/texinfo/info/echo-area.h b/macros/texinfo/texinfo/info/echo-area.h
new file mode 100644
index 0000000000..6af6d90ec0
--- /dev/null
+++ b/macros/texinfo/texinfo/info/echo-area.h
@@ -0,0 +1,85 @@
+/* echo-area.h -- Functions used in reading information from the echo area.
+
+ Copyright 1993-2019 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. */
+
+#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 REFERENCE **echo_area_completion_items;
+
+void inform_in_echo_area (const char *message);
+void echo_area_inform_of_deleted_window (WINDOW *window);
+void echo_area_prep_read (void);
+
+typedef int (*reference_bool_fn) (REFERENCE *);
+
+char *info_read_completing_internal (const char *prompt,
+ REFERENCE **completions, int force, reference_bool_fn exclude);
+
+/* Read a line of text in the echo area. Return a malloc ()'ed string,
+ or NULL if the user aborted out of this read. PROMPT, if
+ non-null, is a prompt to print before reading the line. */
+char *info_read_in_echo_area (const char *prompt);
+
+/* Read a line in the echo area with completion over COMPLETIONS. */
+char *info_read_completing_in_echo_area (const char *prompt,
+ REFERENCE **completions);
+
+/* Read a line in the echo area allowing completion over COMPLETIONS, but
+ not requiring it. */
+char *info_read_maybe_completing (const char *prompt, REFERENCE **completions);
+
+/* Read a line in the echo area with completion over COMPLETIONS, using
+ EXCLUDE to exclude items from the completion list. */
+char *
+info_read_completing_in_echo_area_with_exclusions (const char *prompt,
+ REFERENCE **completions, reference_bool_fn exclude);
+
+void ea_insert (WINDOW *window, int count, int key);
+void ea_quoted_insert (WINDOW *window, int count);
+void ea_beg_of_line (WINDOW *window, int count);
+void ea_backward (WINDOW *window, int count);
+void ea_delete (WINDOW *window, int count);
+void ea_end_of_line (WINDOW *window, int count);
+void ea_forward (WINDOW *window, int count);
+void ea_abort (WINDOW *window, int count);
+void ea_rubout (WINDOW *window, int count);
+void ea_complete (WINDOW *window, int count);
+void ea_newline (WINDOW *window, int count);
+void ea_kill_line (WINDOW *window, int count);
+void ea_backward_kill_line (WINDOW *window, int count);
+void ea_transpose_chars (WINDOW *window, int count);
+void ea_yank (WINDOW *window, int count);
+void ea_tab_insert (WINDOW *window, int count);
+void ea_possible_completions (WINDOW *window, int count);
+void ea_backward_word (WINDOW *window, int count);
+void ea_kill_word (WINDOW *window, int count);
+void ea_forward_word (WINDOW *window, int count);
+void ea_yank_pop (WINDOW *window, int count);
+void ea_backward_kill_word (WINDOW *window, int count);
+void ea_scroll_completions_window (WINDOW *window, int count);
+
+#endif /* not INFO_ECHO_AREA_H */
diff --git a/macros/texinfo/texinfo/info/filesys.c b/macros/texinfo/texinfo/info/filesys.c
new file mode 100644
index 0000000000..52b624178a
--- /dev/null
+++ b/macros/texinfo/texinfo/info/filesys.c
@@ -0,0 +1,587 @@
+/* filesys.c -- filesystem specific functions.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+#include "tilde.h"
+#include "filesys.h"
+#include "tag.h"
+#include "session.h"
+
+/* Local to this file. */
+static char *info_file_in_path (char *filename, struct stat *finfo);
+char *info_add_extension (char *dirname, char *fname,
+ struct stat *finfo);
+
+static char *filesys_read_compressed (char *pathname, size_t *filesize);
+
+/* Return the command string that would be used to decompress FILENAME. */
+static char *filesys_decompressor_for_file (char *filename);
+static int compressed_filename_p (char *filename);
+
+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[] = {
+#if STRIP_DOT_EXE
+ { ".gz", "gunzip" },
+ { ".lz", "lunzip" },
+#else
+ { ".gz", "gzip -d" },
+ { ".lz", "lzip -d" },
+#endif
+ { ".xz", "unxz" },
+ { ".bz2", "bunzip2" },
+ { ".z", "gunzip" },
+ { ".lzma", "unlzma" },
+ { ".Z", "uncompress" },
+ { ".Y", "unyabba" },
+#ifdef __MSDOS__
+ { "gz", "gunzip" },
+ { "z", "gunzip" },
+#endif
+ { NULL, NULL }
+};
+
+/* Look for the filename PARTIAL in INFOPATH in order to find the correct file.
+ Return file name and set *FINFO with information about file. If it
+ can't find the file, it returns NULL, and sets filesys_error_number.
+ Return value should be freed by caller. */
+char *
+info_find_fullpath (char *partial, struct stat *finfo)
+{
+ char *fullpath = 0;
+ struct stat dummy;
+
+ debug(1, (_("looking for file \"%s\""), partial));
+
+ if (!finfo)
+ finfo = &dummy;
+
+ filesys_error_number = 0;
+
+ if (!partial || !*partial)
+ return 0;
+
+ /* IS_SLASH and IS_ABSOLUTE defined in ../system.h. */
+
+ /* If path is absolute already, see if it needs an extension. */
+ if (IS_ABSOLUTE (partial)
+ || partial[0] == '.' && IS_SLASH(partial[1]))
+ {
+ fullpath = info_add_extension (0, partial, finfo);
+ }
+
+ /* Tilde expansion. FIXME: Not needed, because done by shell. */
+ else if (partial[0] == '~')
+ {
+ partial = tilde_expand_word (partial);
+ fullpath = info_add_extension (0, partial, finfo);
+ }
+
+ /* If just a simple name element, look for it in the path. */
+ else
+ fullpath = info_file_in_path (partial, finfo);
+
+ if (!fullpath)
+ filesys_error_number = ENOENT;
+
+ return fullpath;
+}
+
+/* Scan the directories in search path looking for FILENAME. If we find
+ one that is a regular file, return it as a new string. Otherwise, return
+ a NULL pointer. Set *FINFO with information about file. */
+char *
+info_file_find_next_in_path (char *filename, int *path_index, struct stat *finfo)
+{
+ struct stat dummy;
+
+ /* Used for output of stat in case the caller doesn't care about
+ its value. */
+ if (!finfo)
+ finfo = &dummy;
+
+ /* 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;
+
+ while (1)
+ {
+ char *dirname, *with_extension = 0;
+
+ dirname = infopath_next (path_index);
+ if (!dirname)
+ break;
+
+ debug(1, (_("looking for file %s in %s"), filename, dirname));
+
+ /* Expand a leading tilde if one is present. */
+ if (*dirname == '~')
+ {
+ char *expanded_dirname = tilde_expand_word (dirname);
+ free (dirname);
+ dirname = expanded_dirname;
+ }
+
+ with_extension = info_add_extension (dirname, filename, finfo);
+
+ if (with_extension)
+ {
+ if (!IS_ABSOLUTE (with_extension))
+ {
+ /* Prefix "./" to it. */
+ char *s;
+ asprintf (&s, "%s%s", "./", with_extension);
+ free (with_extension);
+ return s;
+ }
+ else
+ return with_extension;
+ }
+ }
+ return NULL;
+}
+
+/* Return full path of first Info file known as FILENAME in
+ search path. If relative to current directory, precede it with './'. */
+static char *
+info_file_in_path (char *filename, struct stat *finfo)
+{
+ int i = 0;
+ return info_file_find_next_in_path (filename, &i, finfo);
+}
+
+/* Look for a file called FILENAME in a directory called DIRNAME, adding file
+ extensions if necessary. FILENAME can be an absolute path or a path
+ relative to the current directory, in which case DIRNAME should be
+ null. Return it as a new string; otherwise return a NULL pointer. */
+char *
+info_add_extension (char *dirname, char *filename, struct stat *finfo)
+{
+ char *try_filename;
+ register int i, pre_suffix_length = 0;
+ struct stat dummy;
+
+ if (!finfo)
+ finfo = &dummy;
+
+ if (dirname)
+ pre_suffix_length += strlen (dirname);
+
+ pre_suffix_length += strlen (filename);
+
+ /* Add enough space for any file extensions at end. */
+ try_filename = xmalloc (pre_suffix_length + 30);
+ try_filename[0] = '\0';
+
+ if (dirname)
+ {
+ strcpy (try_filename, dirname);
+ if (!IS_SLASH (try_filename[(strlen (try_filename)) - 1]))
+ {
+ strcat (try_filename, "/");
+ pre_suffix_length++;
+ }
+ }
+
+ strcat (try_filename, filename);
+
+ for (i = 0; info_suffixes[i]; i++)
+ {
+ int statable;
+
+ strcpy (try_filename + pre_suffix_length, info_suffixes[i]);
+ statable = (stat (try_filename, 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))
+ {
+ debug(1, (_("found file %s"), try_filename));
+ return try_filename;
+ }
+ else if (S_ISDIR (finfo->st_mode))
+ {
+ char *newpath, *new_filename;
+
+ newpath = xstrdup (try_filename);
+ new_filename = info_add_extension (newpath, filename, finfo);
+
+ free (newpath);
+ if (new_filename)
+ {
+ free (try_filename);
+ debug(1, (_("found file %s"), new_filename));
+ return new_filename;
+ }
+ }
+ }
+ 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 (try_filename);
+
+ for (j = 0; compress_suffixes[j].suffix; j++)
+ {
+ strcpy (try_filename + pre_compress_suffix_length,
+ compress_suffixes[j].suffix);
+
+ statable = (stat (try_filename, finfo) == 0);
+ if (statable && (S_ISREG (finfo->st_mode)))
+ {
+ debug(1, (_("found file %s"), try_filename));
+ return try_filename;
+ }
+ }
+ }
+ }
+ /* Nothing was found. */
+ free (try_filename);
+ return 0;
+}
+
+#if defined (__MSDOS__) || defined (__MINGW32__)
+/* 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. */
+static 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;
+}
+#endif
+
+/* 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.
+ If the file turns out to be compressed, set IS_COMPRESSED to non-zero.
+ If the file cannot be read, set filesys_error_number and return a NULL
+ pointer. Set *FINFO with information about file. */
+char *
+filesys_read_info_file (char *pathname, size_t *filesize,
+ struct stat *finfo, int *is_compressed)
+{
+ size_t fsize;
+ char *contents;
+
+ fsize = filesys_error_number = 0;
+
+ stat (pathname, finfo);
+ fsize = (long) finfo->st_size;
+
+ if (compressed_filename_p (pathname))
+ {
+ *is_compressed = 1;
+ contents = filesys_read_compressed (pathname, &fsize);
+ }
+ else
+ {
+ int descriptor;
+
+ *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. */
+ contents = xmalloc (1 + fsize);
+ if ((read (descriptor, contents, fsize)) != fsize)
+ {
+ filesys_error_number = errno;
+ close (descriptor);
+ free (contents);
+ return NULL;
+ }
+ contents[fsize] = 0;
+ close (descriptor);
+ }
+
+#if defined (__MSDOS__) || defined (__MINGW32__)
+ /* Old versions of makeinfo on MS-DOS or MS-Windows generated Info files
+ with CR-LF line endings which are only counted as one byte in the file
+ tag table. Convert any of these DOS-style CRLF EOLs into Unix-style NL
+ so that these files can be read correctly on such operating systems.
+
+ Don't do this on GNU/Linux (or other Unix-type operating system), so
+ as not to encourage Info files with CR-LF line endings to be distributed
+ widely beyond their native operating system, which would cause only
+ problems. (If someone really needs to, they can convert the line endings
+ themselves with a separate program.)
+ Also, this will allow any Info files that contain any CR-LF endings by
+ mistake to work as expected (except on MS-DOS/Windows). */
+
+ fsize = convert_eols (contents, fsize);
+
+ /* EOL conversion can shrink the text quite a bit. We don't
+ want to waste storage. */
+ contents = xrealloc (contents, 1 + fsize);
+ contents[fsize] = '\0';
+#endif
+
+ *filesize = fsize;
+
+ 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)
+
+static char *
+filesys_read_compressed (char *pathname, size_t *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 (info_windows_initialized_p)
+ {
+ char *temp;
+
+ temp = xmalloc (5 + strlen (command));
+ sprintf (temp, "%s...", command);
+ message_in_echo_area ("%s", temp);
+ free (temp);
+ }
+
+ stream = popen (command, FOPEN_RBIN);
+ free (command);
+
+ /* Read chunks from this file until there are none left to read. */
+ if (stream)
+ {
+ size_t offset, size;
+ char *chunk;
+
+ offset = size = 0;
+ chunk = xmalloc (FILESYS_PIPE_BUFFER_SIZE);
+
+ while (1)
+ {
+ size_t 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
+ {
+ contents = xrealloc (contents, 1 + offset);
+ contents[offset] = '\0';
+ *filesize = offset;
+ }
+ }
+ else
+ {
+ filesys_error_number = errno;
+ }
+
+ if (info_windows_initialized_p)
+ unmessage_in_echo_area ();
+ return contents;
+}
+
+/* Return non-zero if FILENAME belongs to a compressed file. */
+static 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. */
+static 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;
+
+/* Return string for ERROR_NUM when opening file. Return value should not
+ be freed by caller. */
+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/macros/texinfo/texinfo/info/filesys.h b/macros/texinfo/texinfo/info/filesys.h
new file mode 100644
index 0000000000..2608dc6bd5
--- /dev/null
+++ b/macros/texinfo/texinfo/info/filesys.h
@@ -0,0 +1,79 @@
+/* filesys.h -- external declarations for filesys.c.
+
+ Copyright 1993-2019 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. */
+
+#ifndef INFO_FILESYS_H
+#define INFO_FILESYS_H
+
+/* Return a string describing the search path. */
+extern char *infopath_string ();
+
+/* Initialize INFOPATH */
+void infopath_init (void);
+
+/* Add PATH to the list of paths found in INFOPATH. */
+void infopath_add (char *path);
+
+/* Iterate over INFOPATH */
+char *infopath_first (int *idx);
+char *infopath_next (int *idx);
+
+/* 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. */
+char *info_find_fullpath (char *partial, struct stat *finfo);
+
+/* 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. */
+char *info_file_find_next_in_path (char *filename, int *diridx,
+ struct stat *finfo);
+
+char *info_add_extension (char *dirname, char *filename, struct stat *finfo);
+
+/* Read the contents of PATHNAME, returning a buffer with the contents of
+ that file in it, and returning the size of that buffer in FILESIZE.
+ FINFO is a stat struct which has already been filled in by the caller.
+ If the file cannot be read, return a NULL pointer. */
+char *filesys_read_info_file (char *pathname, size_t *filesize,
+ struct stat *finfo, int *is_compressed);
+
+/* A function which returns a pointer to a static buffer containing
+ an error message for FILENAME and ERROR_NUM. */
+char *filesys_error_string (char *filename, int error_num);
+
+/* The number of the most recent file system error. */
+extern int filesys_error_number;
+
+/* Return true if FILENAME is `dir', with a possible compression suffix. */
+int is_dir_name (char *filename);
+
+/* The default value of INFOPATH. */
+#if !defined (DEFAULT_INFOPATH)
+# define DEFAULT_INFOPATH "PATH:/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/macros/texinfo/texinfo/info/footnotes.c b/macros/texinfo/texinfo/info/footnotes.c
new file mode 100644
index 0000000000..eeedc667e5
--- /dev/null
+++ b/macros/texinfo/texinfo/info/footnotes.c
@@ -0,0 +1,278 @@
+/* footnotes.c -- Some functions for manipulating footnotes.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+#include "session.h"
+#include "info-utils.h"
+#include "footnotes.h"
+
+/* Nonzero means attempt to show footnotes when displaying a new window. */
+int auto_footnotes_p = 0;
+
+static char *footnote_nodename = "*Footnotes*";
+
+/* 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, *footnotes_node = NULL, *result = NULL;
+ long fn_start = -1;
+ char *fnptr;
+
+ /* 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. */
+ {
+ char saved = node->contents[node->nodelen];
+ node->contents[node->nodelen] = '\0';
+ fnptr = strstr (node->contents, FOOTNOTE_LABEL);
+ node->contents[node->nodelen] = saved;
+ }
+ if (fnptr)
+ fn_start = fnptr - node->contents;
+
+ /* If it doesn't, check to see if it has an associated footnotes node. */
+ if (!fnptr)
+ {
+ REFERENCE **refs;
+
+ refs = node->references;
+
+ 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]->type == REFERENCE_XREF
+ && (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]))))
+ {
+ footnotes_node = info_get_node (node->fullpath, refname);
+ if (footnotes_node)
+ {
+ fn_node = footnotes_node;
+ fn_start = 0;
+ }
+ break;
+ }
+
+ free (refname);
+ }
+ }
+
+ /* If we never found the start of a footnotes area, quit now. */
+ if (fn_start == -1)
+ return NULL;
+
+ /* Make the new node. */
+ result = info_create_node ();
+
+ /* Get the size of the footnotes appearing within this node. */
+ {
+ char *header;
+ long text_start = fn_start;
+
+ asprintf (&header,
+ "*** Footnotes appearing in the node '%s' ***\n",
+ 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);
+ result->contents[strlen (header) + fn_node->nodelen - text_start] = '\0';
+
+ /* Copy and adjust references that appear in footnotes section. */
+ {
+ REFERENCE **ref = fn_node->references;
+
+ for (; *ref; ref++)
+ {
+ if ((*ref)->start > text_start)
+ break;
+ }
+
+ result->references = info_copy_references (ref);
+
+ for (ref = result->references; *ref; ref++)
+ {
+ (*ref)->start -= text_start - strlen (header);
+ (*ref)->end -= text_start - strlen (header);
+ }
+ }
+
+ result->nodename = xstrdup (footnote_nodename);
+ result->flags |= N_IsInternal | N_WasRewritten;
+
+ /* Needed in case the user follows a reference in the footnotes window. */
+ result->fullpath = fn_node->fullpath;
+ result->subfile = fn_node->subfile;
+
+ free (header);
+ }
+
+ free_history_node (footnotes_node);
+ 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 = 0;
+
+ fn_win = find_footnotes_window ();
+
+ /* If we are in the footnotes window, change nothing. */
+ if (fn_win == window)
+ return FN_FOUND;
+
+ /* Don't display footnotes for the "*" node (entire contents of file) or
+ for nodes without a name like completion windows. */
+ if (window->node->nodename && strcmp ("*", window->node->nodename))
+ /* Try to find footnotes for this window's node. */
+ new_footnotes = make_footnotes_node (window->node);
+
+ if (!new_footnotes)
+ {
+ /* If there was a window showing footnotes, and there are no footnotes
+ for the current window, delete the old footnote window. */
+ if (fn_win && windows->next)
+ info_delete_window_internal (fn_win);
+ return FN_UNFOUND;
+ }
+
+ /* If there is no window around showing footnotes, try
+ to make a new window. */
+ if (!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 ();
+ active_window = old_active;
+
+ /* If we are hacking automatic footnotes, and there are footnotes
+ but we couldn't display them, print a message to that effect. */
+ if (!fn_win)
+ {
+ if (auto_footnotes_p)
+ info_error (_("Footnotes could not be displayed"));
+ return FN_UNABLE;
+ }
+ }
+
+ /* Note that info_set_node_of_window calls this function
+ (info_get_or_remove_footnotes), but we do not recurse indefinitely
+ because we check if we are in the footnote window above. */
+ info_set_node_of_window (fn_win, new_footnotes);
+ fn_win->flags |= W_TempWindow;
+
+ /* Make the height be the number of lines appearing in the footnotes. */
+ if (new_footnotes)
+ window_change_window_height (fn_win, fn_win->line_count - fn_win->height);
+
+ 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 ("%s", msg_no_foot_node);
+ break;
+
+ case FN_UNABLE:
+ info_error ("%s", msg_win_too_small);
+ break;
+ }
+ }
+}
diff --git a/macros/texinfo/texinfo/info/footnotes.h b/macros/texinfo/texinfo/info/footnotes.h
new file mode 100644
index 0000000000..61107d74a9
--- /dev/null
+++ b/macros/texinfo/texinfo/info/footnotes.h
@@ -0,0 +1,41 @@
+/* footnotes.h -- Some functions for manipulating footnotes.
+
+ Copyright 1993-2019 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. */
+
+#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/macros/texinfo/texinfo/info/indices.c b/macros/texinfo/texinfo/info/indices.c
new file mode 100644
index 0000000000..6f5ef8a1f1
--- /dev/null
+++ b/macros/texinfo/texinfo/info/indices.c
@@ -0,0 +1,920 @@
+/* indices.c -- deal with an Info file index.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+#include "info-utils.h"
+#include "session.h"
+#include "echo-area.h"
+#include "indices.h"
+#include "variables.h"
+
+/* User-visible variable controls the output of info-index-next. */
+int show_index_match = 1;
+
+/* The combined indices of the last file processed by
+ info_indices_of_file_buffer. */
+static REFERENCE **index_index = NULL;
+
+/* The offset of the most recently selected index element. */
+static int index_offset = 0;
+
+/* Whether we are doing initial index search. */
+static int index_initial = 0;
+
+/* Whether we are doing partial index search */
+static int index_partial = 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. Used
+ for reporting to the user which index node an index entry was found
+ in. */
+static INDEX_NAME_ASSOC **index_nodenames = NULL;
+static size_t index_nodenames_index = 0;
+static size_t index_nodenames_slots = 0;
+
+/* Add the name of NODE, and the range of the associated index elements
+ (passed in ARRAY) to index_nodenames. ARRAY must have at least one
+ element. */
+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);
+}
+
+/* Find and concatenate the indices of FILE_BUFFER, saving the result in
+ INDEX_INDEX. 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. */
+static void
+info_indices_of_file_buffer (FILE_BUFFER *file_buffer)
+{
+ register int i;
+ REFERENCE **result = NULL;
+
+ /* No file buffer, no indices. */
+ if (!file_buffer)
+ {
+ free (index_index);
+ index_index = 0;
+ return;
+ }
+
+ /* If the file is the same as the one that we last built an
+ index for, don't do anything. */
+ if (initial_index_filename
+ && FILENAME_CMP (initial_index_filename, file_buffer->filename) == 0)
+ {
+ return;
+ }
+
+ /* Display a message because finding the index entries might take a while. */
+ if (info_windows_initialized_p)
+ window_message_in_echo_area (_("Finding index entries..."));
+
+ /* Reset globals describing where the index was found. */
+ free (initial_index_filename);
+ 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 (strcasestr (tag->nodename, "Index")
+ && tag->cache.nodelen != 0) /* Not an anchor. */
+ {
+ NODE *node;
+ REFERENCE **menu;
+
+ node = info_node_of_tag (file_buffer, &file_buffer->tags[i]);
+
+ if (!node)
+ continue;
+
+ if (!initial_index_filename)
+ {
+ /* Remember the filename and nodename of this index. */
+ initial_index_filename = xstrdup (file_buffer->filename);
+ initial_index_nodename = xstrdup (tag->nodename);
+ }
+
+ menu = node->references;
+
+ /* If we have a non-empty menu, add this index's nodename
+ and range to our list of index_nodenames. */
+ if (menu && menu[0])
+ {
+ add_index_to_index_nodenames (menu, node);
+
+ /* Concatenate the references found so far. */
+ {
+ REFERENCE **old_result = result;
+ result = info_concatenate_references (result, menu);
+ free (old_result);
+ }
+ }
+ free_history_node (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);
+
+ free (index_index);
+ index_index = result;
+
+ if (info_windows_initialized_p)
+ window_clear_echo_area ();
+}
+
+void info_next_index_match (WINDOW *window, int count);
+
+DECLARE_INFO_COMMAND (info_index_search,
+ _("Look up a string in the index for this file"))
+{
+ FILE_BUFFER *fb;
+ char *line;
+ int old_offset;
+
+ fb = file_buffer_of_window (window);
+ if (fb)
+ info_indices_of_file_buffer (fb); /* Sets index_index. */
+
+ if (!fb || !index_index)
+ {
+ info_error (_("No indices found"));
+ return;
+ }
+
+ line = info_read_maybe_completing (_("Index entry: "), index_index);
+
+ /* User aborted? */
+ if (!line)
+ {
+ info_abort_key (window, 1);
+ 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);
+ info_set_node_of_window (window, node);
+ }
+ return;
+ }
+
+ /* Start the search either at the first or last index entry. */
+ if (count < 0)
+ {
+ register int i;
+ for (i = 0; index_index[i]; i++);
+ index_offset = i;
+ }
+ else
+ {
+ index_offset = -1;
+ index_initial = 0;
+ index_partial = 0;
+ }
+
+ old_offset = index_offset;
+
+ /* The "last" string searched for is this one. */
+ free (index_search);
+ index_search = line;
+
+ info_next_index_match (window, count);
+
+ /* If the search failed, return the index offset to where it belongs. */
+ if (index_offset == old_offset)
+ index_offset = 0;
+}
+
+/* Return true if ENT->label matches "S( <[0-9]+>)?", where S stands
+ for the first LEN characters from STR. */
+static int
+index_entry_matches (REFERENCE *ent, const char *str, size_t len)
+{
+ char *p;
+
+ if (strncmp (ent->label, str, len))
+ return 0;
+ p = ent->label + len;
+ if (!*p)
+ return 1;
+ if (p[0] == ' ' && p[1] == '<')
+ {
+ for (p += 2; *p; p++)
+ {
+ if (p[0] == '>' && p[1] == 0)
+ return 1;
+ else if (!isdigit (*p))
+ return 0;
+ }
+ }
+ return 0;
+}
+
+/* Search for the next occurence of STRING in FB's indices starting at OFFSET
+ in direction DIR.
+
+ Try to get an exact match, If no match found, progress onto looking for
+ initial matches, then non-initial substrings, updating the values of
+ INDEX_INITIAL and INDEX_PARTIAL.
+
+ If a match is found, return a pointer to the matching index entry, and
+ set *FOUND_OFFSET to its offset in INDEX_INDEX. Otherwise, return null.
+ If we found a partial match, set *MATCH_OFFSET to the end of the match
+ within the index entry text, else to 0. */
+REFERENCE *
+next_index_match (FILE_BUFFER *fb, char *string, int offset, int dir,
+ int *found_offset, int *match_offset)
+{
+ int i;
+ int partial_match;
+ size_t search_len;
+ REFERENCE *result;
+
+ partial_match = 0;
+ search_len = strlen (string);
+
+ info_indices_of_file_buffer (fb); /* Sets index_index. */
+ if (!index_index)
+ {
+ info_error (_("No indices found."));
+ return 0;
+ }
+
+ if (index_search != string)
+ {
+ free (index_search); index_search = string;
+ }
+
+ if (!index_initial && !index_partial)
+ {
+ /* First try to find an exact match. */
+ for (i = offset + dir; i > -1 && index_index[i]; i += dir)
+ if (index_entry_matches (index_index[i], string, search_len))
+ {
+ *match_offset = 0;
+ break;
+ }
+
+ if (i < 0 || !index_index[i])
+ {
+ offset = 0;
+ index_initial = 1;
+ }
+ }
+
+ if (index_initial)
+ {
+ for (i = offset + dir; i > -1 && index_index[i]; i += dir)
+ if (!index_entry_matches (index_index[i], string, search_len)
+ && !strncmp (index_index[i]->label, string, search_len))
+ {
+ *match_offset = search_len;
+ break;
+ }
+
+ if (i < 0 || !index_index[i])
+ {
+ offset = 0;
+ index_initial = 0;
+ index_partial = 1;
+ }
+ }
+
+ if (index_partial)
+ {
+ /* Look for substrings, excluding case-matching inital matches. */
+ for (i = offset + dir; i > -1 && index_index[i]; i += dir)
+ {
+ if (strncmp (index_index[i]->label, string, search_len) != 0)
+ {
+ partial_match = string_in_line (string, index_index[i]->label);
+ if (partial_match != -1)
+ {
+ *match_offset = partial_match;
+ break;
+ }
+ }
+ }
+ if (partial_match <= 0)
+ index_partial = 0;
+ }
+
+ if (i < 0 || !index_index[i])
+ result = 0;
+ else
+ {
+ index_offset = i;
+ result = index_index[i];
+ }
+
+ *found_offset = i;
+ return result;
+}
+
+/* Display a message saying where the index match was found. */
+void
+report_index_match (int i, int match_offset)
+{
+ 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 (match_offset > 0 && show_index_match)
+ {
+ int k, ls, start, upper;
+
+ ls = strlen (index_search);
+ start = match_offset - ls;
+ upper = isupper (match[start]) ? 1 : 0;
+
+ for (k = 0; k < ls; k++)
+ if (upper)
+ match[k + start] = tolower (match[k + start]);
+ else
+ match[k + start] = 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);
+}
+
+DECLARE_INFO_COMMAND (info_next_index_match,
+ _("Go to the next matching index item from the last '\\[index-search]' command"))
+{
+ int i;
+ int match_offset;
+ int dir;
+ REFERENCE *result;
+
+ /* If there is no previous search string, the user hasn't built an index
+ yet. */
+ if (!index_search)
+ {
+ info_error (_("No previous index search string"));
+ return;
+ }
+
+ /* The direction of this search is controlled by the value of the
+ numeric argument. */
+ if (count < 0)
+ dir = -1;
+ else
+ dir = 1;
+
+ result = next_index_match (file_buffer_of_window (window), index_search,
+ index_offset, dir, &i, &match_offset);
+
+ /* If that failed, print an error. */
+ if (!result)
+ {
+ info_error (index_offset > 0 ?
+ _("No more index entries containing '%s'") :
+ _("No index entries containing '%s'"),
+ index_search);
+ index_offset = 0;
+ return;
+ }
+
+ /* Report to the user on what we have found. */
+ report_index_match (i, match_offset);
+
+ info_select_reference (window, result);
+}
+
+/* Look for the best match of STRING in the indices of FB. If SLOPPY, allow
+ case-insensitive initial substrings to match. Return null if no match is
+ found. Return value should not be freed or modified. This differs from the
+ behaviour of next_index_match in that only _initial_ substrings are
+ considered. */
+REFERENCE *
+look_in_indices (FILE_BUFFER *fb, char *string, int sloppy)
+{
+ REFERENCE **index_ptr;
+ REFERENCE *nearest = 0;
+
+ /* Remember the search string so we can use it as the default for
+ 'virtual-index' or 'next-index-match'. */
+ free (index_search);
+ index_search = xstrdup (string);
+
+ info_indices_of_file_buffer (fb); /* Sets index_index. */
+ if (!index_index)
+ return 0;
+
+ for (index_ptr = index_index; *index_ptr; index_ptr++)
+ {
+ if (!strcmp (string, (*index_ptr)->label))
+ {
+ nearest = *index_ptr;
+ break;
+ }
+ /* Case-insensitive initial substring. */
+ if (sloppy && !nearest && !mbsncasecmp (string, (*index_ptr)->label,
+ mbslen (string)))
+ {
+ nearest = *index_ptr;
+ }
+ }
+ return nearest;
+}
+
+/* **************************************************************** */
+/* */
+/* 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)
+{
+ size_t i, dir_index;
+ REFERENCE **all_indices = NULL;
+ REFERENCE **dir_menu = NULL;
+ NODE *dir_node;
+
+ dir_node = get_dir_node ();
+
+ /* It should be safe to assume that dir nodes do not contain any
+ cross-references, i.e., its references list only contains
+ menu items. */
+ if (dir_node)
+ dir_menu = dir_node->references;
+
+ if (!dir_menu)
+ {
+ free (dir_node);
+ return NULL;
+ }
+
+ /* For every menu item in DIR, get the associated 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;
+ FILE_BUFFER *this_fb, *loaded_file = 0;
+
+ this_item = dir_menu[dir_index];
+ if (!this_item->filename)
+ continue;
+
+ /* 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 (dir_menu[i]->filename
+ && FILENAME_CMP (this_item->filename, dir_menu[i]->filename) == 0)
+ break;
+ if (i < dir_index)
+ continue;
+
+ this_fb = check_loaded_file (this_item->filename);
+
+ if (!this_fb)
+ this_fb = loaded_file = info_find_file (this_item->filename);
+
+ if (!this_fb)
+ continue; /* Couldn't load file. */
+
+ if (this_fb && inform)
+ message_in_echo_area (_("Scanning indices of '%s'..."), this_item->filename);
+
+ info_indices_of_file_buffer (this_fb);
+ this_index = index_index;
+
+ 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. */
+ {
+ REFERENCE **old_indices = all_indices;
+ all_indices = info_concatenate_references (all_indices, this_index);
+ free (old_indices);
+ }
+ }
+
+ /* Try to avoid running out of memory by not loading all of the
+ Info files on the system into memory. This is risky because we
+ may have a pointer into the file buffer, so only free the contents
+ if we have just loaded the file. */
+ if (loaded_file)
+ {
+ free (loaded_file->contents);
+ loaded_file->contents = NULL;
+ }
+ }
+
+ /* Build a list of the references which contain SEARCH_STRING. */
+ if (all_indices)
+ {
+ REFERENCE *entry, **apropos_list = NULL;
+ size_t apropos_list_index = 0;
+ size_t 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);
+ }
+ }
+
+ free (all_indices);
+ all_indices = apropos_list;
+ }
+ free (dir_node);
+ return all_indices;
+}
+
+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, *prompt;
+ REFERENCE **apropos_list;
+ NODE *apropos_node;
+ struct text_buffer message;
+
+ if (index_search)
+ asprintf (&prompt, "%s [%s]: ", _("Index apropos"), index_search);
+ else
+ asprintf (&prompt, "%s: ", _("Index apropos"));
+ line = info_read_in_echo_area (prompt);
+ free (prompt);
+
+ window = active_window;
+
+ /* User aborted? */
+ if (!line)
+ {
+ info_abort_key (window, 1);
+ return;
+ }
+
+ /* User typed something? */
+ if (*line)
+ {
+ free (index_search);
+ index_search = line;
+ }
+ else
+ free (line); /* Try to use the last search string. */
+
+ if (index_search && *index_search)
+ {
+ apropos_list = apropos_in_all_indices (index_search, 1);
+
+ if (!apropos_list)
+ {
+ info_error (_(APROPOS_NONE), index_search);
+ return;
+ }
+ else
+ {
+ /* Create the node. FIXME: Labels and node names taken from the
+ indices of Info files may be in a different character encoding to
+ the one currently being used.
+ This problem is reduced by makeinfo not putting quotation marks
+ from @samp, etc., into node names and index entries. */
+ register int i;
+
+ text_buffer_init (&message);
+ text_buffer_add_char (&message, '\n');
+ text_buffer_printf (&message, _("Index entries containing "
+ "'%s':\n"), index_search);
+ text_buffer_printf (&message, "\n* Menu:");
+ text_buffer_add_string (&message, "\0\b[index\0\b]", 11);
+ text_buffer_add_char (&message, '\n');
+
+ for (i = 0; apropos_list[i]; i++)
+ {
+ int line_start = text_buffer_off (&message);
+ char *filename;
+
+ /* Remove file extension. */
+ filename = program_name_from_file_name
+ (apropos_list[i]->filename);
+
+ /* 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. */
+ text_buffer_printf (&message, "* %s [%s]: ",
+ apropos_list[i]->label, filename);
+
+ while (text_buffer_off (&message) - line_start < 40)
+ text_buffer_add_char (&message, ' ');
+ text_buffer_printf (&message, "(%s)%s.",
+ filename, apropos_list[i]->nodename);
+ text_buffer_printf (&message, " (line %ld)\n",
+ apropos_list[i]->line_number);
+ free (filename);
+ }
+ }
+
+ apropos_node = text_buffer_to_node (&message);
+ {
+ char *old_contents = apropos_node->contents;
+ scan_node_contents (apropos_node, 0, 0);
+ if (old_contents != apropos_node->contents)
+ free (old_contents);
+ }
+
+ name_internal_node (apropos_node, xstrdup (apropos_list_nodename));
+
+ /* Find/Create a window to contain this node. */
+ {
+ WINDOW *new;
+ NODE *node;
+
+ /* 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)
+ new = window_make_window ();
+
+ /* 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;
+
+ info_set_node_of_window (new, apropos_node);
+ active_window = new;
+ }
+ free (apropos_list);
+ }
+}
+
+#define NODECOL 41
+#define LINECOL 62
+
+static void
+format_reference (REFERENCE *ref, const char *filename, struct text_buffer *buf)
+{
+ size_t n;
+
+ n = text_buffer_printf (buf, "* %s: ", ref->label);
+ if (n < NODECOL)
+ n += text_buffer_fill (buf, ' ', NODECOL - n);
+
+ if (ref->filename && strcmp (ref->filename, filename))
+ n += text_buffer_printf (buf, "(%s)", ref->filename);
+ n += text_buffer_printf (buf, "%s. ", ref->nodename);
+
+ if (n < LINECOL)
+ n += text_buffer_fill (buf, ' ', LINECOL - n);
+ else
+ {
+ text_buffer_add_char (buf, '\n');
+ text_buffer_fill (buf, ' ', LINECOL);
+ }
+
+ text_buffer_printf (buf, "(line %4d)\n", ref->line_number);
+}
+
+NODE *
+create_virtual_index (FILE_BUFFER *file_buffer, char *index_search)
+{
+ struct text_buffer text;
+ int i;
+ size_t cnt;
+ NODE *node;
+
+ text_buffer_init (&text);
+ text_buffer_printf (&text,
+ "File: %s, Node: Index for '%s'\n\n",
+ file_buffer->filename, index_search);
+ text_buffer_printf (&text, _("Virtual Index\n"
+ "*************\n\n"
+ "Index entries that match '%s':\n"),
+ index_search);
+ text_buffer_add_string (&text, "\0\b[index\0\b]", 11);
+ text_buffer_printf (&text, "\n* Menu:\n\n");
+
+ cnt = 0;
+
+ index_offset = 0;
+ index_initial = 0;
+ index_partial = 0;
+ while (1)
+ {
+ REFERENCE *result;
+ int match_offset;
+
+ result = next_index_match (file_buffer, index_search, index_offset, 1,
+ &i, &match_offset);
+ if (!result)
+ break;
+ format_reference (index_index[i],
+ file_buffer->filename, &text);
+ cnt++;
+ }
+ text_buffer_add_char (&text, '\0');
+
+ if (cnt == 0)
+ {
+ text_buffer_free (&text);
+ return 0;
+ }
+
+ node = info_create_node ();
+ asprintf (&node->nodename, "Index for '%s'", index_search);
+ node->fullpath = file_buffer->filename;
+ node->contents = text_buffer_base (&text);
+ node->nodelen = text_buffer_off (&text) - 1;
+ node->body_start = strcspn (node->contents, "\n");
+ node->flags |= N_IsInternal | N_WasRewritten;
+
+ scan_node_contents (node, 0, 0);
+
+ return node;
+}
+
+DECLARE_INFO_COMMAND (info_virtual_index,
+ _("List all matches of a string in the index"))
+{
+ char *prompt, *line;
+ FILE_BUFFER *fb;
+ NODE *node;
+
+ fb = file_buffer_of_window (window);
+
+ if (!initial_index_filename ||
+ !fb ||
+ (FILENAME_CMP (initial_index_filename, fb->filename) != 0))
+ {
+ window_message_in_echo_area (_("Finding index entries..."));
+ info_indices_of_file_buffer (fb);
+ }
+
+ if (!index_index)
+ {
+ info_error (_("No indices found."));
+ return;
+ }
+
+ /* Default to last search if there is one. */
+ if (index_search)
+ asprintf (&prompt, "%s [%s]: ", _("Index topic"), index_search);
+ else
+ asprintf (&prompt, "%s: ", _("Index topic"));
+ line = info_read_maybe_completing (prompt, index_index);
+ free (prompt);
+
+ /* User aborted? */
+ if (!line)
+ {
+ info_abort_key (window, 1);
+ return;
+ }
+
+ if (*line)
+ {
+ free (index_search);
+ index_search = line;
+ }
+ else if (!index_search)
+ {
+ free (line);
+ return; /* No previous search string, and no string given. */
+ }
+
+ node = create_virtual_index (fb, index_search);
+ if (!node)
+ {
+ info_error (_("No index entries containing '%s'."), index_search);
+ return;
+ }
+ info_set_node_of_window (window, node);
+}
diff --git a/macros/texinfo/texinfo/info/indices.h b/macros/texinfo/texinfo/info/indices.h
new file mode 100644
index 0000000000..4a27c7bbc8
--- /dev/null
+++ b/macros/texinfo/texinfo/info/indices.h
@@ -0,0 +1,42 @@
+/* indices.h -- Functions defined in indices.c.
+
+ Copyright 1993-2019 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. */
+
+#ifndef INFO_INDICES_H
+#define INFO_INDICES_H
+
+/* User-visible variable controls the output of info-index-next. */
+extern int show_index_match;
+
+/* 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. */
+void info_index_search (WINDOW *window, int count);
+void info_index_apropos (WINDOW *window, int count);
+REFERENCE *next_index_match (FILE_BUFFER *fb, char *string,
+ int offset, int dir,
+ int *found_offset, int *match_offset);
+void report_index_match (int i, int match_offset);
+REFERENCE *look_in_indices (FILE_BUFFER *fb, char *string, int sloppy);
+NODE *create_virtual_index (FILE_BUFFER *file_buffer, char *index_search);
+
+#define APROPOS_NONE \
+ N_("No available info files have '%s' in their indices")
+
+#endif /* not INFO_INDICES_H */
diff --git a/macros/texinfo/texinfo/info/info-utils.c b/macros/texinfo/texinfo/info/info-utils.c
new file mode 100644
index 0000000000..63a2897e53
--- /dev/null
+++ b/macros/texinfo/texinfo/info/info-utils.c
@@ -0,0 +1,2063 @@
+/* info-utils.c -- miscellanous.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+#include "session.h"
+#include "info-utils.h"
+#include "tag.h"
+
+#include <langinfo.h>
+#if HAVE_ICONV
+# include <iconv.h>
+#endif
+#include <wchar.h>
+#ifdef __MINGW32__
+/* MinGW uses a replacement nl_langinfo, see pcterm.c. */
+# define nl_langinfo rpl_nl_langinfo
+extern char * rpl_nl_langinfo (nl_item);
+/* MinGW uses its own replacement wcwidth, see pcterm.c for the
+ reasons. Since Gnulib's wchar.h might redirect wcwidth to
+ rpl_wcwidth, we explicitly undo that here. */
+#undef wcwidth
+#endif
+
+#ifdef __hpux
+#define va_copy(ap1,ap2) memcpy((&ap1),(&ap2),sizeof(va_list))
+#endif
+
+/* 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;
+
+/* Read a filename surrounded by "(" and ")", accounting for matching
+ characters, and place it in *FILENAME if FILENAME is not null. Return
+ length of read filename. On error, set *FILENAME to null and return 0. */
+int
+read_bracketed_filename (char *string, char **filename)
+{
+ register int i = 0;
+ int count = 0; /* Level of nesting. */
+ int first_close = -1; /* First ")" encountered. */
+
+ if (*string != '(')
+ return 0;
+
+ string++;
+ count = 1;
+ for (i = 0; string[i]; i++)
+ {
+ if (string[i] == '(')
+ count++;
+ else if (string[i] == ')')
+ {
+ if (first_close == -1)
+ first_close = i;
+
+ count--;
+ if (count == 0)
+ break;
+ }
+ }
+
+ /* If string ended before brackets were balanced, take the first ")" as
+ terminating the filename. */
+ if (count > 0)
+ {
+ if (first_close == -1)
+ {
+ if (filename)
+ *filename = 0;
+ return 0;
+ }
+ i = first_close;
+ }
+
+ if (filename)
+ {
+ *filename = xcalloc (1, i + 1);
+ memcpy (*filename, string, i);
+ }
+
+ return i + 2; /* Length of filename plus "(" and ")". */
+}
+
+/* Parse the filename and nodename out of STRING, saving in
+ INFO_PARSED_FILENAME and INFO_PARSED_NODENAME. These variables should not
+ be freed by calling code. If either is missing, the relevant variable is
+ set to a null pointer. */
+void
+info_parse_node (char *string)
+{
+ int nodename_len;
+
+ free (info_parsed_filename);
+ free (info_parsed_nodename);
+ info_parsed_filename = 0;
+ info_parsed_nodename = 0;
+
+ /* Special case of nothing passed. Return nothing. */
+ if (!string || !*string)
+ return;
+
+ string += skip_whitespace_and_newlines (string);
+
+ string += read_bracketed_filename (string, &info_parsed_filename);
+
+ /* Parse out nodename. */
+ string += skip_whitespace_and_newlines (string);
+ nodename_len = read_quoted_string (string, "", 0, &info_parsed_nodename);
+
+ if (nodename_len != 0)
+ {
+ canonicalize_whitespace (info_parsed_nodename);
+ }
+}
+
+/* Set *OUTPUT to a copy of the string starting at START and finishing at
+ a character in TERMINATOR, unless START[0] == INFO_QUOTE, in which case
+ copy string from START+1 until the next occurence of INFO_QUOTE. If
+ TERMINATOR is an empty string, finish at a null character. LINES is
+ the number of lines that the string can span. If LINES is zero, there is no
+ limit. Return length of string including any quoting characters. Return
+ 0 if input was invalid. */
+long
+read_quoted_string (char *start, char *terminator, int lines, char **output)
+{
+ long len;
+ char *nl = 0, saved_char;
+
+ if (lines)
+ {
+ int i;
+ nl = start;
+ for (i = 0; i < lines; i++)
+ {
+ nl = strchr (nl, '\n');
+ if (!nl)
+ break; /* End of input string reached. */
+ nl++;
+ }
+ if (nl)
+ {
+ saved_char = *nl;
+ *nl = '\0';
+ }
+ }
+
+ if (start[0] != '\177')
+ {
+ len = strcspn (start, terminator);
+
+ if (*terminator && !start[len])
+ {
+ len = 0;
+ *output = 0;
+ }
+ else
+ {
+ *output = xmalloc (len + 1);
+ strncpy (*output, start, len);
+ (*output)[len] = '\0';
+ }
+ }
+ else
+ {
+ len = strcspn (start + 1, "\177");
+
+ if (*terminator && !(start + 1)[len])
+ {
+ /* No closing 177 byte. */
+ len = 0;
+ *output = 0;
+ }
+ else
+ {
+ *output = xmalloc (len + 1);
+ strncpy (*output, start + 1, len);
+ (*output)[len] = '\0';
+ len += 2; /* Count the two 177 bytes. */
+ }
+
+ }
+
+ if (nl)
+ *nl = saved_char;
+ return len;
+}
+
+
+/* **************************************************************** */
+/* */
+/* Finding and Building Menus */
+/* */
+/* **************************************************************** */
+
+/* Get the entry associated with LABEL in the menu of NODE. Return a
+ pointer to the ENTRY if found, or null. Return value should not
+ be freed by caller. If SLOPPY, allow initial matches, like
+ "Buffers" for a LABEL "buffer". */
+REFERENCE *
+info_get_menu_entry_by_label (NODE *node, char *label, int sloppy)
+{
+ register int i;
+ int best_guess = -1;
+ REFERENCE *entry;
+ REFERENCE **references = node->references;
+
+ if (!references)
+ return 0;
+
+ for (i = 0; (entry = references[i]); i++)
+ {
+ if (entry->type != REFERENCE_MENU_ITEM)
+ continue;
+ if (mbscasecmp (label, entry->label) == 0)
+ return entry; /* Exact, case-insensitive match. */
+ else if (sloppy && best_guess == -1
+ && (mbsncasecmp (entry->label, label, strlen (label)) == 0))
+ best_guess = i;
+ }
+
+ if (sloppy && best_guess != -1)
+ return references[best_guess];
+
+ return 0;
+}
+
+/* A utility function for concatenating REFERENCE **. Returns a new
+ REFERENCE ** which is the concatenation of REF1 and REF2. */
+REFERENCE **
+info_concatenate_references (REFERENCE **ref1, REFERENCE **ref2)
+{
+ register int i, j;
+ REFERENCE **result;
+ int size = 0;
+
+ /* Get the total size of the slots that we will need. */
+ if (ref1)
+ {
+ for (i = 0; ref1[i]; i++);
+ size += i;
+ }
+
+ if (ref2)
+ {
+ for (i = 0; ref2[i]; i++);
+ size += i;
+ }
+
+ result = xmalloc ((1 + size) * sizeof (REFERENCE *));
+
+ /* Copy the contents over. */
+
+ j = 0;
+ if (ref1)
+ {
+ for (i = 0; ref1[i]; i++)
+ result[j++] = ref1[i];
+ }
+
+ if (ref2)
+ {
+ for (i = 0; ref2[i]; i++)
+ result[j++] = ref2[i];
+ }
+
+ result[j] = NULL;
+ return result;
+}
+
+/* Copy a reference structure. Copy each field 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 = src->line_number;
+ dest->type = src->type;
+
+ return dest;
+}
+
+/* Copy a list of references, copying in reference in turn with
+ info_copy_reference. */
+REFERENCE **
+info_copy_references (REFERENCE **ref1)
+{
+ int i;
+ REFERENCE **result;
+ int size;
+
+ if (!ref1)
+ return 0;
+
+ /* Get the total size of the slots that we will need. */
+ for (i = 0; ref1[i]; i++);
+ size = i;
+
+ result = xmalloc ((1 + size) * sizeof (REFERENCE *));
+
+ /* Copy the contents over. */
+ for (i = 0; ref1[i]; i++)
+ result[i] = info_copy_reference (ref1[i]);
+ result[i] = NULL;
+
+ return result;
+}
+
+void
+info_reference_free (REFERENCE *ref)
+{
+ if (ref)
+ {
+ free (ref->label);
+ free (ref->filename);
+ free (ref->nodename);
+ free (ref);
+ }
+}
+
+/* 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++)
+ info_reference_free (entry);
+
+ free (references);
+ }
+}
+
+/* Return new REFERENCE with filename and nodename fields set. */
+REFERENCE *
+info_new_reference (char *filename, char *nodename)
+{
+ REFERENCE *r = xmalloc (sizeof (REFERENCE));
+ r->label = 0;
+ r->filename = filename ? xstrdup (filename) : 0;
+ r->nodename = nodename ? xstrdup (nodename) : 0;
+ r->start = 0;
+ r->end = 0;
+ r->line_number = 0;
+ r->type = 0;
+ return r;
+}
+
+
+/* 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);
+}
+
+/* If ITER points to an ANSI escape sequence, process it, set PLEN to its
+ length in bytes, and return 1.
+ Otherwise, return 0.
+ */
+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;
+}
+
+static struct text_buffer printed_rep = { 0 };
+
+/* Return pointer to string that is the printed representation of character
+ (or other logical unit) at ITER if it were printed at screen column
+ PL_CHARS. Use ITER_SETBYTES (info-utils.h) on ITER if we need to advance
+ past a unit that the multibyte iteractor doesn't know about (like an ANSI
+ escape sequence). If ITER points at an end-of-line character, set *DELIM to
+ this character. *PCHARS gets the number of screen columns taken up by
+ outputting the return value, and *PBYTES the number of bytes in returned
+ string. Return value is not null-terminated. Return value must not be
+ freed by caller. */
+char *
+printed_representation (mbi_iterator_t *iter, int *delim, size_t pl_chars,
+ size_t *pchars, size_t *pbytes)
+{
+ struct text_buffer *rep = &printed_rep;
+
+ char *cur_ptr = (char *) mbi_cur_ptr (*iter);
+ size_t cur_len = mb_len (mbi_cur (*iter));
+
+ text_buffer_reset (&printed_rep);
+
+ if (mb_isprint (mbi_cur (*iter)))
+ {
+ /* cur.wc gives a wchar_t object. See mbiter.h in the
+ gnulib/lib directory. */
+ *pchars = wcwidth ((*iter).cur.wc);
+ *pbytes = cur_len;
+ return cur_ptr;
+ }
+ else if (cur_len == 1)
+ {
+ if (*cur_ptr == '\n' || *cur_ptr == '\r')
+ {
+ /* If this is a CRLF line ending, ignore this character. */
+ if (*cur_ptr == '\r' && cur_ptr[1] == '\n')
+ {
+ *pchars = 0;
+ *pbytes = 0;
+ return cur_ptr;
+ }
+
+ *pchars = 1;
+ *pbytes = cur_len;
+ *delim = *cur_ptr;
+ text_buffer_add_char (rep, ' ');
+ return cur_ptr;
+ }
+ else if (ansi_escape (*iter, &cur_len))
+ {
+ *pchars = 0;
+ *pbytes = cur_len;
+ ITER_SETBYTES (*iter, cur_len);
+
+ return cur_ptr;
+ }
+ else if (*cur_ptr == '\t')
+ {
+ int i = 0;
+
+ *pchars = ((pl_chars + 8) & 0xf8) - pl_chars;
+ *pbytes = *pchars;
+
+ /* We must output spaces instead of the tab because a tab may
+ not clear characters already on the screen. */
+ for (i = 0; i < *pbytes; i++)
+ text_buffer_add_char (rep, ' ');
+ return text_buffer_base (rep);
+ }
+ }
+
+ /* Show CTRL-x as "^X". */
+ if (iscntrl (*cur_ptr) && *(unsigned char *)cur_ptr < 127)
+ {
+ *pchars = 2;
+ *pbytes = 2;
+ text_buffer_add_char (rep, '^');
+ text_buffer_add_char (rep, *cur_ptr | 0x40);
+ return text_buffer_base (rep);
+ }
+ else if (*cur_ptr == DEL)
+ {
+ *pchars = 0;
+ *pbytes = 0;
+ return text_buffer_base (rep);
+ }
+ else
+ {
+ /* Original byte was not recognized as anything. Display its octal
+ value. This could happen in the C locale for bytes above 128,
+ or for bytes 128-159 in an ISO-8859-1 locale. Don't output the bytes
+ as they are, because they could have special meaning to the
+ terminal. */
+ *pchars = 4;
+ *pbytes = 4;
+ text_buffer_printf (rep, "\\%o", *(unsigned char *)cur_ptr);
+ return text_buffer_base (rep);
+ }
+}
+
+
+/* **************************************************************** */
+/* */
+/* Scanning node */
+/* */
+/* **************************************************************** */
+
+/* Whether to strip syntax from the text of nodes. */
+int preprocess_nodes_p;
+
+/* Whether contents of nodes should be rewritten. */
+static int rewrite_p;
+
+/* inptr is moved forward through the body of a node. */
+static char *inptr;
+
+/* Pointer to first byte of node (after node separator). */
+static char *input_start;
+
+/* Number of bytes in node contents. */
+static size_t input_length;
+
+struct text_buffer output_buf;
+
+/* Pointer into a tags table for the file to the anchor we need to adjust as
+ a result of byte counts changing due to character encoding conversion or
+ inserted/deleted text. */
+static TAG **anchor_to_adjust;
+/* Offset within file buffer of first byte of node, used for anchor
+ adjustment. */
+static int node_offset;
+
+/* Difference so far between the number of bytes input in the file and
+ bytes output. Used to adjust the values of anchors in nodes. */
+static long int output_bytes_difference;
+
+/* Whether we are converting the character encoding of the file. */
+static int convert_encoding_p;
+
+#if HAVE_ICONV
+
+/* Whether text in file is encoded in UTF-8. */
+static int file_is_in_utf8;
+
+/* Used for conversion from file encoding to output encoding. */
+static iconv_t iconv_to_output;
+
+/* Conversion from file encoding to UTF-8. */
+static iconv_t iconv_to_utf8;
+
+#endif /* HAVE_ICONV */
+
+void
+init_conversion (FILE_BUFFER *fb)
+{
+ char *target_encoding;
+
+ convert_encoding_p = 0;
+
+ /* Node being processed does not come from an Info file. */
+ if (!fb)
+ return;
+
+#if !HAVE_ICONV
+ return;
+#else
+ file_is_in_utf8 = 0;
+
+ /* Don't process file if encoding is unknown. */
+ if (!fb->encoding)
+ return;
+
+ /* Read name of character encoding from environment locale */
+ target_encoding = nl_langinfo (CODESET);
+
+ /* Don't convert the contents if the locale
+ uses the same character encoding as the file */
+ if (!strcasecmp(target_encoding, fb->encoding))
+ return;
+
+ /* Check if an iconv conversion from file locale to system
+ locale exists */
+ iconv_to_output = iconv_open (target_encoding, fb->encoding);
+ if (iconv_to_output == (iconv_t) -1)
+ return; /* Return if no conversion function implemented */
+
+ if ( !strcasecmp ("UTF8", fb->encoding)
+ || !strcasecmp ("UTF-8", fb->encoding))
+ file_is_in_utf8 = 1;
+
+ if (!file_is_in_utf8)
+ {
+ iconv_to_utf8 = iconv_open ("UTF-8", fb->encoding);
+ if (iconv_to_utf8 == (iconv_t) -1)
+ {
+ /* Return if no conversion function implemented */
+ iconv_close (iconv_to_output);
+ return;
+ }
+ }
+
+ convert_encoding_p = 1;
+ rewrite_p = 1;
+#endif /* HAVE_ICONV */
+}
+
+void close_conversion (void)
+{
+#if HAVE_ICONV
+ if (convert_encoding_p)
+ {
+ iconv_close (iconv_to_output);
+ if (!file_is_in_utf8) iconv_close (iconv_to_utf8);
+ }
+#endif
+}
+
+static void
+init_output_stream (FILE_BUFFER *fb)
+{
+ init_conversion (fb);
+ output_bytes_difference = 0;
+
+ if (rewrite_p)
+ text_buffer_init (&output_buf);
+}
+
+static size_t saved_offset;
+static char *saved_inptr;
+static long saved_difference;
+
+void
+save_conversion_state (void)
+{
+ saved_offset = text_buffer_off (&output_buf);
+ saved_inptr = inptr;
+ saved_difference = output_bytes_difference;
+}
+
+/* Go back to the saved state of the output stream. */
+void
+reset_conversion (void)
+{
+ text_buffer_off (&output_buf) = saved_offset;
+ inptr = saved_inptr;
+ output_bytes_difference = saved_difference;
+}
+
+/* Copy bytes from input to output with no encoding conversion. */
+static void
+copy_direct (long n)
+{
+ text_buffer_add_string (&output_buf, inptr, n);
+ inptr += n;
+}
+
+/* Read one character at *FROM and write out a sequence
+ of bytes representing that character in ASCII. *FROM
+ is advanced past the read character. */
+static int
+degrade_utf8 (char **from, size_t *from_left)
+{
+ static struct encoding_replacement
+ {
+ char *from_string;
+ char *to_string;
+ } er[] = {
+ {"\xE2\x80\x98","'"}, /* Opening single quote */
+ {"\xE2\x80\x99","'"}, /* Closing single quote */
+ {"\xE2\x80\x9C","\""},/* Opening double quote */
+ {"\xE2\x80\x9D","\""},/* Closing double quote */
+ {"\xC2\xA9","(C)"}, /* Copyright symbol */
+ {"\xC2\xBB",">>"}, /* Closing double angle brackets */
+
+ {"\xE2\x86\x92","->"},/* Right arrow */
+ {"\xE2\x87\x92","=>"},/* Right double arrow */
+ {"\xE2\x8A\xA3","-|"},/* Print symbol */
+ {"\xE2\x98\x85","-!-"}, /* Point symbol */
+ {"\xE2\x86\xA6","==>"}, /* Expansion symbol */
+
+ {"\xE2\x80\x90","-"}, /* Hyphen */
+ {"\xE2\x80\x91","-"}, /* Non-breaking hyphen */
+ {"\xE2\x80\x92","-"}, /* Figure dash */
+ {"\xE2\x80\x93","-"}, /* En dash */
+ {"\xE2\x80\x94","--"}, /* Em dash */
+ {"\xE2\x88\x92","-"}, /* Minus sign */
+ {"\xE2\x80\xA6","..."}, /* Ellipsis */
+ {"\xE2\x80\xA2","*"}, /* Bullet */
+
+ {"\xC3\xA0","a`"}, /* Lower case letter a with grave accent */
+ {"\xC3\xA2","a^"}, /* Lower case letter a with circumflex */
+ {"\xC3\xA4","a\""}, /* Lower case letter a with diaeresis */
+ {"\xC3\xA6","ae"}, /* Lower case letter ae ligature */
+ {"\xC3\xA9","e'"}, /* Lower case letter e with acute accent */
+ {"\xC3\xA8","e`"}, /* Lower case letter e with grave accent */
+ {"\xC3\xAA","e^"}, /* Lower case letter e with circumflex */
+ {"\xC3\xAB","e\""}, /* Lower case letter e with diaeresis */
+ {"\xC3\xB6","o\""}, /* Lower case letter o with diaeresis */
+ {"\xC3\xBC","u\""}, /* Lower case letter u with diaeresis */
+ {"\xC3\x84", "A\""}, /* Upper case letter A with diaeresis. */
+ {"\xC3\x96", "O\""}, /* Upper case letter O with diaeresis. */
+ {"\xC3\x9c", "U\""}, /* Upper case letter U with diaeresis. */
+
+ {"\xC3\xB1","n~"}, /* Lower case letter n with tilde */
+ {"\xC3\x87","C,"}, /* Upper case letter C with cedilla */
+ {"\xC3\xA7","c,"}, /* Lower case letter c with cedilla */
+ {"\xC3\x9f","ss"}, /* Lower case letter sharp s */
+
+ {0, 0}
+ };
+
+ struct encoding_replacement *erp;
+
+ for (erp = er; erp->from_string != 0; erp++)
+ {
+ /* Avoid reading past end of input. */
+ int width = strlen (erp->from_string);
+ if (width > *from_left)
+ continue;
+
+ if (!strncmp (erp->from_string, *from, width))
+ {
+ text_buffer_add_string (&output_buf, erp->to_string,
+ strlen(erp->to_string));
+ *from += width;
+ *from_left -= width;
+ return 1;
+ }
+ }
+
+ /* Failing this, just print a question mark. Maybe we should use SUB
+ (^Z) (ASCII substitute character code) instead, or pass through the
+ original bytes. */
+ text_buffer_add_string (&output_buf, "?", 1);
+
+ /* Ideally we would advance one UTF-8 character. This would
+ require knowing its length in bytes. */
+ (*from)++;
+ (*from_left)--;
+
+ return 0;
+}
+
+/* Convert N bytes from input to output encoding and write to
+ output buffer. Return number of bytes over N written. */
+static int
+copy_converting (long n)
+{
+#if !HAVE_ICONV
+ return 0;
+#else
+ size_t bytes_left, orig_bytes_left;
+ int extra_at_end;
+ size_t iconv_ret;
+ long output_start;
+
+ size_t utf8_char_free;
+ char utf8_char[4]; /* Maximum 4 bytes in a UTF-8 character */
+ char *utf8_char_ptr, *orig_inptr;
+ size_t i;
+
+ /* Use n as an estimate of how many bytes will be required
+ in target encoding. */
+ text_buffer_alloc (&output_buf, (size_t) n);
+
+ output_start = text_buffer_off (&output_buf);
+ bytes_left = n;
+ extra_at_end = 0;
+ while (1)
+ {
+ iconv_ret = text_buffer_iconv (&output_buf, iconv_to_output,
+ (ICONV_CONST char **)&inptr, &bytes_left);
+
+ /* Make sure libiconv flushes out the last converted character.
+ This is required when the conversion is stateful, in which
+ case libiconv might not output the last character, waiting to
+ see whether it should be combined with the next one. */
+ if (iconv_ret != (size_t) -1
+ && text_buffer_iconv (&output_buf, iconv_to_output,
+ NULL, NULL) != (size_t) -1)
+ /* Success: all of input converted. */
+ break;
+
+ /* There's been an error while converting. */
+ switch (errno)
+ {
+ case E2BIG:
+ /* Ran out of space in output buffer. Allocate more
+ and try again. */
+ text_buffer_alloc (&output_buf, n);
+ continue;
+ case EINVAL:
+ /* Incomplete byte sequence at end of input buffer. Try to read
+ more. */
+
+ /* input_length - 2 is offset of last-but-one byte within input.
+ This checks if there is at least one more byte within node
+ contents. */
+ if (inptr - input_start + (bytes_left - 1) <= input_length - 2)
+ {
+ bytes_left++;
+ extra_at_end++;
+ }
+ else
+ {
+ copy_direct (bytes_left);
+ bytes_left = 0;
+ }
+ continue;
+ default: /* Unknown error */
+ info_error (_("Error converting file character encoding"));
+
+ /* Skip past current input and hope we don't get an
+ error next time. */
+ inptr += bytes_left;
+ return 0;
+ case EILSEQ:
+ /* Byte sequence in input not recognized. Degrade to ASCII. */
+ break;
+ }
+
+ /* Flush any waiting input in iconv_to_output and enter the
+ default shift state. */
+ text_buffer_iconv (&output_buf, iconv_to_output, NULL, NULL);
+
+ if (file_is_in_utf8)
+ {
+ degrade_utf8 (&inptr, &bytes_left);
+ continue;
+ }
+
+ /* If file is not in UTF-8, we degrade to ASCII in two steps:
+ first convert the character to UTF-8, then look up a replacement
+ string. Note that mixing iconv_to_output and iconv_to_utf8
+ on the same input may not work well if the input encoding
+ is stateful. We could deal with this by always converting to
+ UTF-8 first; then we could mix conversions on the UTF-8 stream. */
+
+ /* We want to read exactly one character. Do this by
+ restricting size of output buffer. */
+ utf8_char_ptr = utf8_char;
+ orig_inptr = inptr;
+ orig_bytes_left = bytes_left;
+ for (i = 1; i <= 4; i++)
+ {
+ utf8_char_free = i;
+ errno = 0;
+ iconv_ret = iconv (iconv_to_utf8, (ICONV_CONST char **)&inptr,
+ &bytes_left, &utf8_char_ptr, &utf8_char_free);
+ if ((iconv_ret == (size_t) -1 && errno != E2BIG)
+ /* If we managed to convert a character: */
+ || utf8_char_ptr > utf8_char)
+ break;
+ }
+
+ /* errno == E2BIG if iconv ran out of output buffer,
+ which is expected. */
+ if (iconv_ret == (size_t) -1 && errno != E2BIG)
+ {
+ /* Character is not recognized. Copy a single byte. */
+ inptr = orig_inptr; /* iconv might have incremented inptr */
+ copy_direct (1);
+ bytes_left = orig_bytes_left - 1;
+ }
+ else
+ {
+ utf8_char_ptr = utf8_char;
+ /* i is width of UTF-8 character */
+ degrade_utf8 (&utf8_char_ptr, &i);
+ /* If we are done, make sure iconv flushes the last character. */
+ if (bytes_left <= 0)
+ {
+ utf8_char_ptr = utf8_char;
+ i = 4;
+ iconv (iconv_to_utf8, NULL, NULL,
+ &utf8_char_ptr, &utf8_char_free);
+ if (utf8_char_ptr > utf8_char)
+ {
+ utf8_char_ptr = utf8_char;
+ degrade_utf8 (&utf8_char_ptr, &i);
+ }
+ }
+ }
+ }
+
+ /* Must cast because the difference between unsigned size_t is always
+ positive. */
+ output_bytes_difference +=
+ n - ((signed long) text_buffer_off (&output_buf) - output_start);
+
+ return extra_at_end;
+#endif /* HAVE_ICONV */
+}
+
+/* Functions below are named from the perspective of the preprocess_nodes_p
+ flag being on. */
+
+/* Copy text from input node contents, possibly converting the
+ character encoding and adjusting anchor offsets at the same time. */
+static void
+copy_input_to_output (long n)
+{
+ if (rewrite_p)
+ {
+ long bytes_left;
+
+ bytes_left = n;
+ while (bytes_left > 0)
+ {
+ if (!convert_encoding_p)
+ {
+ copy_direct (bytes_left);
+ bytes_left = 0;
+ }
+ else
+ {
+ long bytes_to_convert;
+ long extra_written;
+
+ bytes_to_convert = bytes_left;
+
+ if (anchor_to_adjust)
+ {
+ char *first_anchor = input_start
+ + (*anchor_to_adjust)->nodestart - node_offset;
+
+ /* If there is an anchor in the input: */
+ if (first_anchor < inptr + bytes_left)
+ {
+ /* Convert enough to pass the first anchor in input. */
+ bytes_to_convert = first_anchor - inptr + 1;
+
+ /* Shouldn't happen because we should have already
+ have adjusted this anchor. */
+ if (bytes_to_convert < 0)
+ {
+ anchor_to_adjust = 0; /* Abandon anchor adjustment.*/
+ bytes_to_convert = bytes_left;
+ }
+ }
+ }
+
+ /* copy_converting may read more than bytes_to_convert
+ bytes if its input ends in an incomplete byte sequence. */
+ extra_written = copy_converting (bytes_to_convert);
+
+ bytes_left -= bytes_to_convert + extra_written;
+ }
+
+ /* Check if we have gone past any anchors and
+ adjust with output_bytes_difference. */
+ if (anchor_to_adjust)
+ while ((*anchor_to_adjust)->nodestart - node_offset
+ <= inptr - input_start)
+ {
+ (*anchor_to_adjust)->nodestart_adjusted
+ = (*anchor_to_adjust)->nodestart - output_bytes_difference;
+
+ anchor_to_adjust++;
+ if (!*anchor_to_adjust
+ || (*anchor_to_adjust)->cache.nodelen != 0)
+ {
+ anchor_to_adjust = 0;
+ break;
+ }
+ }
+ }
+ }
+ else
+ inptr += n;
+}
+
+static void
+skip_input (long n)
+{
+ if (preprocess_nodes_p)
+ {
+ inptr += n;
+ output_bytes_difference += n;
+ }
+ else if (rewrite_p)
+ {
+ /* We are expanding tags only. Do not skip input. */
+ copy_input_to_output (n);
+ }
+ else
+ {
+ inptr += n;
+ }
+}
+
+static void
+write_extra_bytes_to_output (char *input, long n)
+{
+ if (preprocess_nodes_p)
+ {
+ text_buffer_add_string (&output_buf, input, n);
+ output_bytes_difference -= n;
+ }
+}
+
+/* Like write_extra_bytes_to_output, but writes bytes even when
+ preprocess_nodes=Off. */
+static void
+write_tag_contents (char *input, long n)
+{
+ if (rewrite_p)
+ {
+ text_buffer_add_string (&output_buf, input, n);
+ output_bytes_difference -= n;
+ }
+}
+
+/* Like skip_input, but skip even when !preprocess_nodes_p. */
+static void
+skip_tag_contents (long n)
+{
+ if (rewrite_p)
+ {
+ inptr += n;
+ output_bytes_difference += n;
+ }
+}
+
+/* Read first line of node and set next, prev and up. */
+static void
+parse_top_node_line (NODE *node)
+{
+ char **store_in = 0;
+ char *nodename;
+ char *ptr;
+ int value_length;
+
+ /* If the first line is empty, leave it in. This is the case
+ in the index-apropos window. */
+ if (*node->contents == '\n')
+ return;
+
+ node->next = node->prev = node->up = 0;
+ ptr = node->contents;
+
+ while (1)
+ {
+ store_in = 0;
+
+ ptr += skip_whitespace (ptr);
+
+ /* Check what field we are looking at */
+ if (!strncasecmp (ptr, INFO_FILE_LABEL, strlen(INFO_FILE_LABEL)))
+ {
+ ptr += strlen (INFO_FILE_LABEL);
+ }
+ else if (!strncasecmp (ptr, INFO_NODE_LABEL, strlen(INFO_NODE_LABEL)))
+ {
+ ptr += strlen (INFO_NODE_LABEL);
+ }
+ else if (!strncasecmp (ptr, INFO_PREV_LABEL, strlen(INFO_PREV_LABEL)))
+ {
+ ptr += strlen (INFO_PREV_LABEL);
+ store_in = &node->prev;
+ }
+ else if (!strncasecmp (ptr, INFO_ALTPREV_LABEL,
+ strlen(INFO_ALTPREV_LABEL)))
+ {
+ ptr += strlen (INFO_ALTPREV_LABEL);
+ store_in = &node->prev;
+ }
+ else if (!strncasecmp (ptr, INFO_NEXT_LABEL, strlen(INFO_NEXT_LABEL)))
+ {
+ ptr += strlen (INFO_NEXT_LABEL);
+ store_in = &node->next;
+ }
+ else if (!strncasecmp (ptr, INFO_UP_LABEL, strlen(INFO_UP_LABEL)))
+ {
+ ptr += strlen (INFO_UP_LABEL);
+ store_in = &node->up;
+ }
+ else
+ {
+ store_in = 0;
+ /* Not recognized - code below will skip to next comma */
+ }
+ ptr += skip_whitespace (ptr);
+
+ /* Get length of a bracketed filename component. */
+ if (*ptr != '(')
+ value_length = 0;
+ else
+ value_length = read_bracketed_filename (ptr, 0);
+
+ /* Get length of node name, or filename if following "File:". Note
+ that . is not included in the second argument here in order to
+ support this character in file names. */
+ value_length += read_quoted_string (ptr + value_length,
+ "\n\r\t,", 1, &nodename);
+ if (store_in)
+ {
+ *store_in = xmalloc (value_length + 1);
+ strncpy (*store_in, ptr, value_length);
+ (*store_in)[value_length] = '\0';
+ }
+
+ free (nodename);
+ ptr += value_length;
+
+ if (*ptr == '\n' || !*ptr)
+ break;
+
+ ptr += 1; /* Point after field terminator */
+ }
+}
+
+/* Output, replace or hide text introducing a reference. INPTR starts on
+ the first byte of a sequence introducing a reference and finishes on the
+ first (non-whitespace) byte of the reference label. */
+static int
+scan_reference_marker (REFERENCE *entry, int in_parentheses)
+{
+ /* When preprocess_nodes is Off, we position the cursor on
+ the "*" when moving between references. */
+ if (!preprocess_nodes_p)
+ {
+ if (rewrite_p)
+ entry->start = text_buffer_off(&output_buf);
+ else
+ entry->start = inptr - input_start;
+ }
+
+ /* Check what we found based on first character of match */
+ if (inptr[0] == '\n')
+ {
+ entry->type = REFERENCE_MENU_ITEM;
+ if (!preprocess_nodes_p)
+ entry->start++;
+ }
+ else
+ entry->type = REFERENCE_XREF;
+
+ if (entry->type == REFERENCE_MENU_ITEM)
+ copy_input_to_output (strlen ("\n* "));
+ else
+ {
+ /* Only match "*Note" if it is followed by a whitespace character so that
+ it will not be recognized if, e.g., it is surrounded in inverted
+ commas. */
+ if (!strchr (" \t\r\n", inptr[strlen ("*Note")]))
+ {
+ copy_input_to_output (strlen ("*Note:"));
+ return 0;
+ }
+
+ /* Cross-references can be generated by four different Texinfo
+ commands. @inforef and @xref output "*Note " in Info format,
+ and "See" in HTML and print. @ref and @pxref output "*note "
+ in Info format, and either nothing at all or "see" in HTML
+ and print. Unfortunately, there is no easy way to distinguish
+ between these latter two cases. */
+ /* TODO: Internationalize these strings, but only if we know the
+ language of the document. */
+ if (inptr[1] == 'N')
+ {
+ write_extra_bytes_to_output ("See", 3);
+ in_parentheses = 1;
+ }
+ else if (in_parentheses)
+ {
+ write_extra_bytes_to_output ("see", 3);
+ /* Only output the "see" for input like "(*note ...)", which
+ would have come from a use of @pxref. We used to output "see" for
+ "*note" in more circumstances, with a list of words where to
+ suppress it (to avoid "see *note" turning into "see see"), but
+ such a list can't be complete or reliable. It's better to remove
+ it with more enthusiasm, then if the document writer wants a "see"
+ to appear, they can add one themselves. */
+ }
+
+ skip_input (strlen ("*Note"));
+ if (!in_parentheses)
+ skip_input (skip_whitespace (inptr));
+ }
+
+ /* Copy any white space before label. */
+ copy_input_to_output (skip_whitespace_and_newlines (inptr));
+
+ return 1;
+}
+
+/* Output reference label and update ENTRY. INPTR should be on the first
+ non-whitespace byte of label when this function is called. It is left
+ at the first character after the colon terminating the label. Return 0 if
+ invalid syntax is encountered. */
+static int
+scan_reference_label (REFERENCE *entry, int in_index)
+{
+ int max_lines;
+ int len, label_len = 0;
+
+ /* Handle case of cross-reference like (FILE)NODE::. */
+ if (inptr[0] == '(')
+ label_len = read_bracketed_filename (inptr, &entry->filename);
+
+ /* Search forward to ":" to get label name. Cross-references may have
+ a newline in the middle. */
+ if (entry->type == REFERENCE_MENU_ITEM)
+ max_lines = 1;
+ else
+ max_lines = 2;
+ if (!in_index || inptr[label_len] == '\177')
+ {
+ len = read_quoted_string (inptr + label_len, ":", max_lines,
+ &entry->nodename);
+ canonicalize_whitespace (entry->nodename);
+ if (!len)
+ return 0; /* Input invalid. */
+ label_len += len;
+ }
+ else
+ {
+ /* If in an index node, go forward to the last colon on the line
+ (not preceded by a newline, NUL or DEL). This is in order to
+ support index entries containing colons. This should work fine
+ as long as the node name does not contain a colon as well. */
+
+ char *p;
+ int n, m = 0;
+ p = inptr + label_len;
+
+ while (1)
+ {
+ n = strcspn (p, ":\n\177");
+ if (p[n] == ':')
+ {
+ m += n + 1;
+ p += n + 1;
+ continue;
+ }
+ break;
+ }
+ if (m == 0)
+ return 0; /* no : found */
+ label_len += m - 1;
+ }
+
+ entry->label = xmalloc (label_len + 1);
+ memcpy (entry->label, inptr, label_len);
+ entry->label[label_len] = '\0';
+ canonicalize_whitespace (entry->label);
+
+ if (preprocess_nodes_p)
+ entry->start = text_buffer_off (&output_buf);
+
+ /* Write text of label. */
+ copy_input_to_output (label_len);
+
+ if (rewrite_p)
+ entry->end = text_buffer_off (&output_buf);
+ else
+ entry->end = inptr - input_start;
+
+ /* Colon after label. */
+ skip_input (1);
+ /* Don't mess up the margin of a menu description. */
+ if (entry->type == REFERENCE_MENU_ITEM)
+ write_extra_bytes_to_output (" ", 1);
+
+ return 1;
+}
+
+/* INPTR should be at the first character after the colon
+ terminating the label. Return 0 on syntax error. */
+static int
+scan_reference_target (REFERENCE *entry, NODE *node, int in_parentheses)
+{
+ int i;
+
+ /* This entry continues with a specific target. Parse the
+ file name and node name from the specification. */
+
+ if (entry->type == REFERENCE_XREF)
+ {
+ int length = 0; /* Length of specification */
+ char *target_start = inptr;
+ char *nl_off = 0;
+ int space_at_start_of_line = 0;
+
+ length += skip_whitespace_and_newlines (inptr);
+
+ length += read_bracketed_filename (inptr + length, &entry->filename);
+
+ length += skip_whitespace_and_newlines (inptr + length);
+
+ /* Get the node name. */
+ length += read_quoted_string (inptr + length, ",.", 2, &entry->nodename);
+
+ skip_input (length);
+
+ /* Check if there is a newline in the target. */
+ nl_off = strchr (target_start, '\n');
+ if (nl_off)
+ {
+ if (nl_off < inptr)
+ space_at_start_of_line = skip_whitespace (nl_off + 1);
+ else
+ nl_off = 0;
+ }
+ canonicalize_whitespace (entry->nodename);
+
+ if (entry->filename)
+ {
+ /* Heuristic of whether it's worth outputing a newline before the
+ filename. This checks whether the newline appears more
+ than half way through the text, and therefore which side is
+ longer. */
+ if (nl_off
+ && nl_off < target_start + (length - space_at_start_of_line) / 2)
+ {
+ int i;
+ write_extra_bytes_to_output ("\n", 1);
+
+ for (i = 0; i < space_at_start_of_line; i++)
+ write_extra_bytes_to_output (" ", 1);
+ skip_input (strspn (inptr, " "));
+ nl_off = 0;
+ }
+ else
+
+ if (*inptr != '\n')
+ {
+ write_extra_bytes_to_output (" ", 1);
+ }
+ write_extra_bytes_to_output ("(", 1);
+ write_extra_bytes_to_output (entry->filename,
+ strlen (entry->filename));
+ write_extra_bytes_to_output (" manual)",
+ strlen (" manual)"));
+ }
+
+ /* Hide terminating punctuation if we are in a reference
+ like "(*note Label:(file)node.)". */
+ if (in_parentheses && inptr[0] == '.')
+ skip_input (1);
+
+ /* Copy any terminating punctuation before the optional newline. */
+ copy_input_to_output (strspn (inptr, ".),"));
+
+ /* Output a newline if one is needed. Don't do it at the end of
+ a paragraph. */
+ if (nl_off && *inptr != '\n')
+ {
+ int i;
+
+ write_extra_bytes_to_output ("\n", 1);
+ for (i = 0; i < space_at_start_of_line; i++)
+ write_extra_bytes_to_output (" ", 1);
+ skip_input (strspn (inptr, " "));
+ }
+ }
+ else /* entry->type == REFERENCE_MENU_ITEM */
+ {
+ int line_len;
+ int length = 0; /* Length of specification */
+
+ length = skip_whitespace (inptr);
+ length += read_bracketed_filename (inptr + length, &entry->filename);
+ length += strspn (inptr + length, " ");
+
+ /* Get the node name. */
+ length += read_quoted_string (inptr + length, ",.\t\n", 2,
+ &entry->nodename);
+ if (inptr[length] == '.') /* A '.' terminating the entry. */
+ length++;
+
+ if (node->flags & N_IsDir)
+ {
+ /* Set line_len to length of line so far. */
+
+ char *linestart;
+ linestart = memrchr (input_start, '\n', inptr - input_start);
+ if (!linestart)
+ linestart = input_start;
+ else
+ linestart++; /* Point to first character after newline. */
+ line_len = inptr - linestart;
+ }
+
+ if (node->flags & N_IsIndex)
+ /* Show the name of the node the index entry refers to. */
+ copy_input_to_output (length);
+ else
+ {
+ skip_input (length);
+
+ if ((node->flags & N_IsDir) && inptr[strspn (inptr, " ")] == '\n')
+ {
+ /* For a dir node, if there is no more text in this line,
+ check if there is a menu entry description in the next
+ line to the right of the end of the label, and display it
+ in this line. */
+ skip_input (strspn (inptr, " "));
+ if (line_len <= strspn (inptr + 1, " "))
+ skip_input (1 + line_len);
+ }
+ else
+ {
+ for (i = 0; i < length; i++)
+ write_extra_bytes_to_output (" ", 1);
+ }
+ }
+
+ /* Parse "(line ...)" part of menus, if any. */
+ {
+ char *lineptr = inptr;
+ /* 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. */
+ lineptr += skip_whitespace (inptr);
+ if (*lineptr == '\n')
+ lineptr += 1 + skip_whitespace (lineptr + 1);
+
+ if (!strncmp (lineptr, "(line ", strlen ("(line ")))
+ {
+ lineptr += strlen ("(line ");
+ entry->line_number = strtol (lineptr, 0, 0);
+ }
+ else
+ entry->line_number = 0;
+ }
+ }
+
+ return 1;
+}
+
+/* BASE is earlier in a block of allocated memory than PTR, and the block
+ extends until at least BASE + LEN - 1. Return PTR[INDEX], unless this
+ could be outside the allocated block, in which case return 0. */
+static char
+safe_string_index (char *ptr, long index, char *base, long len)
+{
+ long offset = ptr - base;
+
+ if ( offset + index < 0
+ || offset + index >= len)
+ return 0;
+
+ return ptr[index];
+}
+
+/* Process an in index marker ("^@^H[index^@^H]") or an image marker
+ ("^@^H[image ...^@^H]"). */
+static void
+scan_info_tag (NODE *node, int *in_index, FILE_BUFFER *fb)
+{
+ char *p, *p1;
+ struct text_buffer *expansion = xmalloc (sizeof (struct text_buffer));
+
+ p = inptr;
+ p1 = p;
+
+ text_buffer_init (expansion);
+
+ if (tag_expand (&p1, input_start + input_length, expansion, in_index))
+ {
+ if (*in_index)
+ node->flags |= N_IsIndex;
+
+ if (!rewrite_p)
+ {
+ rewrite_p = 1;
+ init_output_stream (fb);
+
+ /* Put inptr back to start so that
+ copy_input_to_output below gets all
+ preceding contents. */
+ inptr = node->contents;
+ }
+
+ /* Write out up to tag. */
+ copy_input_to_output (p - inptr);
+
+ write_tag_contents (text_buffer_base (expansion),
+ text_buffer_off (expansion));
+ /* Skip past body of tag. */
+ skip_tag_contents (p1 - inptr);
+ }
+ else
+ {
+ /* It was not a valid tag. */
+ copy_input_to_output (p - inptr + 1);
+ }
+
+ text_buffer_free (expansion);
+ free (expansion);
+}
+
+#define looking_at_string(contents, string) \
+ (!strncasecmp (contents, string, strlen (string)))
+
+static char *
+forward_to_info_syntax (char *contents)
+{
+ /* Loop until just before the end of the input. The '- 3' prevents us
+ accessing memory after the end of the input, and none of the strings we
+ are looking for are shorter than 3 bytes. */
+ while (contents < input_start + input_length - 3)
+ {
+ /* Menu entry comes first to optimize for the case of looking through a
+ long index node. */
+ if (looking_at_string (contents, INFO_MENU_ENTRY_LABEL)
+ || looking_at_string (contents, INFO_XREF_LABEL)
+ || !memcmp (contents, "\0\b[", 3))
+ return contents;
+ contents++;
+ }
+ return 0;
+}
+
+/* Scan contents of NODE, recording cross-references and similar.
+
+ Convert character encoding of node contents to that of the user if the two
+ are known to be different. If PREPROCESS_NODES_P == 1, remove Info syntax
+ in contents.
+
+ If FB is non-null, it is the file containing the node, and TAG_PTR is an
+ offset into FB->tags. If the node contents are rewritten, adjust anchors
+ that occur in the node and store adjusted value as TAG->nodestart_adjusted,
+ otherwise simply copy TAG->nodestart to TAG->nodestart_adjusted for each
+ anchor in the node. */
+void
+scan_node_contents (NODE *node, FILE_BUFFER *fb, TAG **tag_ptr)
+{
+ int in_menu = 0;
+ char *match;
+
+ REFERENCE **refs = NULL;
+ size_t refs_index = 0, refs_slots = 0;
+
+ /* Whether an index tag was seen. */
+ int in_index = 0;
+
+ rewrite_p = preprocess_nodes_p;
+
+ init_output_stream (fb);
+
+ if (fb)
+ {
+ char *file_contents;
+
+ /* Set anchor_to_adjust to first anchor in node, if any. */
+ anchor_to_adjust = tag_ptr + 1;
+ if (!*anchor_to_adjust)
+ anchor_to_adjust = 0;
+ else if (*anchor_to_adjust
+ && (*anchor_to_adjust)->cache.nodelen != 0)
+ anchor_to_adjust = 0;
+
+ if (!node->subfile)
+ file_contents = fb->contents;
+ else
+ {
+ FILE_BUFFER *f = info_find_subfile (node->subfile);
+ if (!f)
+ return; /* This shouldn't happen. */
+ file_contents = f->contents;
+ }
+ node_offset = (*tag_ptr)->nodestart
+ + skip_node_separator (file_contents + (*tag_ptr)->nodestart);
+ }
+ else
+ anchor_to_adjust = 0;
+
+ /* Initialize refs to point to array of one null pointer in case
+ there are no results. This way we know if refs has been initialized
+ even if it is empty. */
+ refs = calloc (1, sizeof *refs);
+ refs_slots = 1;
+
+ parse_top_node_line (node);
+
+ /* This should be the only time we assign to inptr in this function -
+ all other assignment should be done with the helper functions above. */
+ inptr = node->contents;
+ input_start = node->contents;
+ input_length = node->nodelen;
+
+
+ while ((match = forward_to_info_syntax (inptr))
+ && match < node->contents + node->nodelen)
+ {
+ int in_parentheses = 0;
+ REFERENCE *entry;
+
+ /* Write out up to match */
+ copy_input_to_output (match - inptr);
+
+ if ((in_menu && match[0] == '\n') || match[0] == '*')
+ {
+ /* Menu entry or cross reference. */
+ /* Create REFERENCE entity. */
+ entry = info_new_reference (0, 0);
+
+ if (safe_string_index (inptr, -1, input_start, input_length) == '('
+ && safe_string_index (inptr, 1, input_start, input_length) == 'n')
+ in_parentheses = 1;
+
+ save_conversion_state ();
+
+ if (!scan_reference_marker (entry, in_parentheses))
+ goto not_a_reference;
+
+ if (!scan_reference_label (entry, in_index))
+ goto not_a_reference;
+
+ /* If this reference entry continues with another ':' then the target
+ of the reference is given by the label. */
+ if (*inptr == ':')
+ {
+ int label_len;
+ skip_input (1);
+ if (entry->type == REFERENCE_MENU_ITEM)
+ write_extra_bytes_to_output (" ", 1);
+
+ /* Remove the DEL bytes from a label like "(FOO)^?BAR^?::". */
+ label_len = strlen (entry->label);
+ if (label_len >= 2 && entry->label[label_len - 1] == 0177)
+ {
+ char *p = strchr (entry->label, '\177');
+ memmove (p, p + 1, label_len - (p - entry->label) - 1);
+ entry->label[label_len - 2] = '\0';
+ }
+ }
+ else
+ {
+ /* Proceed to read the rest of the reference. */
+ /* TODO: we should probably not allow references of the form
+ "(file)node1:node2." or "(file1)node1:(file2)node2", so
+ bail out here if entry->filename is non-null. */
+
+ free (entry->filename); entry->filename = 0;
+ free (entry->nodename); entry->nodename = 0;
+ if (!scan_reference_target (entry, node, in_parentheses))
+ goto not_a_reference;
+ }
+
+ if (0)
+ {
+ char *cur_inptr;
+
+not_a_reference:
+ /* This is not a menu entry or reference. Do not add to our
+ list. */
+ cur_inptr = inptr;
+ reset_conversion ();
+ copy_input_to_output (cur_inptr - inptr);
+
+ info_reference_free (entry);
+ continue;
+ }
+
+ add_pointer_to_array (entry, refs_index, refs, refs_slots, 50);
+ }
+ /* Was "* Menu:" seen? If so, search for menu entries hereafter. */
+ else if (!in_menu && !strncmp (match, INFO_MENU_LABEL,
+ strlen (INFO_MENU_LABEL)))
+ {
+ in_menu = 1;
+ skip_input (strlen ("\n* Menu:"));
+ if (*inptr == '\n')
+ skip_input (strspn (inptr, "\n") - 1); /* Keep one newline. */
+
+ }
+ else if (match[0] == '\0') /* Info tag */
+ {
+ scan_info_tag (node, &in_index, fb);
+ }
+ else
+ copy_input_to_output (1);
+ }
+
+ /* If we haven't accidentally gone past the end of the node, write
+ out the rest of it. */
+ if (inptr < node->contents + node->nodelen)
+ copy_input_to_output ((node->contents + node->nodelen) - inptr);
+
+ /* Null to terminate buffer. */
+ if (rewrite_p)
+ text_buffer_add_string (&output_buf, "\0", 1);
+
+ /* Free resources used in character encoding conversion. */
+ close_conversion ();
+
+ node->references = refs;
+
+ if (rewrite_p)
+ {
+ node->contents = text_buffer_base (&output_buf);
+ node->flags |= N_WasRewritten;
+
+ /* output_buf.off is the offset of the next character to be
+ written. Subtracting 1 gives the offset of our terminating
+ null, that is, the length. */
+ node->nodelen = text_buffer_off (&output_buf) - 1;
+ }
+ else if (fb && tag_ptr)
+ {
+ /* Set nodestart_adjusted for all of the anchors in this node. */
+ tag_ptr++;
+ while (*tag_ptr && (*tag_ptr)->cache.nodelen == 0)
+ {
+ (*tag_ptr)->nodestart_adjusted = (*tag_ptr)->nodestart
+ - output_bytes_difference;
+ tag_ptr++;
+ }
+ }
+}
+
+
+/* Various utility functions */
+
+/* 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->fullpath)
+ return info_find_file (window->node->fullpath);
+
+ return NULL;
+}
+
+/* Return "(FILENAME)NODENAME" for NODE, or just "NODENAME" if NODE's
+ filename is not set. Return value should not be freed. */
+char *
+node_printed_rep (NODE *node)
+{
+ static char *rep;
+
+ if (node->fullpath)
+ {
+ char *filename = filename_non_directory (node->fullpath);
+ rep = xrealloc (rep, 1 + strlen (filename) + 1 + strlen (node->nodename) + 1);
+ sprintf (rep, "(%s)%s", filename, node->nodename);
+ return rep;
+ }
+ else
+ return node->nodename;
+}
+
+
+/* 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)
+{
+ return (node != NULL) && (node->flags & N_IsInternal);
+}
+
+/* Make NODE appear to be one especially created by Info. */
+void
+name_internal_node (NODE *node, char *name)
+{
+ if (!node)
+ return;
+
+ node->fullpath = "";
+ node->subfile = 0;
+ 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;
+}
+
+/* Flexible Text Buffer */
+
+void
+text_buffer_init (struct text_buffer *buf)
+{
+ memset (buf, 0, sizeof *buf);
+}
+
+void
+text_buffer_free (struct text_buffer *buf)
+{
+ free (buf->base);
+}
+
+size_t
+text_buffer_vprintf (struct text_buffer *buf, const char *format, va_list ap)
+{
+ ssize_t n;
+ va_list ap_copy;
+
+ if (!buf->base)
+ {
+ if (buf->size == 0)
+ buf->size = MIN_TEXT_BUF_ALLOC; /* Initial allocation */
+
+ buf->base = xmalloc (buf->size);
+ }
+
+ for (;;)
+ {
+ va_copy (ap_copy, ap);
+ n = vsnprintf (buf->base + buf->off, buf->size - buf->off,
+ format, ap_copy);
+ va_end (ap_copy);
+ if (n < 0 || buf->off + n >= buf->size ||
+ !memchr (buf->base + buf->off, '\0', buf->size - buf->off + 1))
+ {
+ size_t newlen = buf->size * 2;
+ if (newlen < buf->size)
+ xalloc_die ();
+ buf->size = newlen;
+ buf->base = xrealloc (buf->base, buf->size);
+ }
+ else
+ {
+ buf->off += n;
+ break;
+ }
+ }
+ return n;
+}
+
+/* Make sure there are LEN free bytes at end of BUF. */
+void
+text_buffer_alloc (struct text_buffer *buf, size_t len)
+{
+ if (buf->off + len > buf->size)
+ {
+ buf->size = buf->off + len;
+ if (buf->size < MIN_TEXT_BUF_ALLOC)
+ buf->size = MIN_TEXT_BUF_ALLOC;
+ buf->base = xrealloc (buf->base, buf->size);
+ }
+}
+
+/* Return number of bytes that can be written to text buffer without
+ reallocating the text buffer. */
+size_t
+text_buffer_space_left (struct text_buffer *buf)
+{
+ /* buf->size is the offset of the first byte after the allocated space.
+ buf->off is the offset of the first byte to be written to. */
+ return buf->size - buf->off;
+}
+
+#if HAVE_ICONV
+
+/* Run iconv using text buffer as output buffer. */
+size_t
+text_buffer_iconv (struct text_buffer *buf, iconv_t iconv_state,
+ ICONV_CONST char **inbuf, size_t *inbytesleft)
+{
+ size_t out_bytes_left;
+ char *outptr;
+ size_t iconv_ret;
+
+ outptr = text_buffer_base (buf) + text_buffer_off (buf);
+ out_bytes_left = text_buffer_space_left (buf);
+ iconv_ret = iconv (iconv_state, inbuf, inbytesleft,
+ &outptr, &out_bytes_left);
+
+ text_buffer_off (buf) = outptr - text_buffer_base (buf);
+
+ return iconv_ret;
+}
+
+#endif /* HAVE_ICONV */
+
+size_t
+text_buffer_add_string (struct text_buffer *buf, const char *str, size_t len)
+{
+ text_buffer_alloc (buf, len);
+ memcpy (buf->base + buf->off, str, len);
+ buf->off += len;
+ return len;
+}
+
+size_t
+text_buffer_fill (struct text_buffer *buf, int c, size_t len)
+{
+ char *p;
+ int i;
+
+ text_buffer_alloc (buf, len);
+
+ for (i = 0, p = buf->base + buf->off; i < len; i++)
+ *p++ = c;
+ buf->off += len;
+
+ return len;
+}
+
+void
+text_buffer_add_char (struct text_buffer *buf, int c)
+{
+ char ch = c;
+ text_buffer_add_string (buf, &ch, 1);
+}
+
+size_t
+text_buffer_printf (struct text_buffer *buf, const char *format, ...)
+{
+ va_list ap;
+ size_t n;
+
+ va_start (ap, format);
+ n = text_buffer_vprintf (buf, format, ap);
+ va_end (ap);
+ return n;
+}
+
+#if defined(__MSDOS__) || defined(__MINGW32__)
+/* Cannot use FILENAME_CMP here, since that does not consider forward-
+ and back-slash characters equal. */
+int
+fncmp (const char *fn1, const char *fn2)
+{
+ const char *s1 = fn1, *s2 = fn2;
+
+ while (tolower (*s1) == tolower (*s2)
+ || (IS_SLASH (*s1) && IS_SLASH (*s2)))
+ {
+ if (*s1 == 0)
+ return 0;
+ s1++;
+ s2++;
+ }
+
+ return tolower (*s1) - tolower (*s2);
+}
+#endif
+
+struct info_namelist_entry
+{
+ struct info_namelist_entry *next;
+ char name[1];
+};
+
+int
+info_namelist_add (struct info_namelist_entry **ptop, const char *name)
+{
+ struct info_namelist_entry *p;
+
+ for (p = *ptop; p; p = p->next)
+ if (fncmp (p->name, name) == 0)
+ return 1;
+
+ p = xmalloc (sizeof (*p) + strlen (name));
+ strcpy (p->name, name);
+ p->next = *ptop;
+ *ptop = p;
+ return 0;
+}
+
+void
+info_namelist_free (struct info_namelist_entry *top)
+{
+ while (top)
+ {
+ struct info_namelist_entry *next = top->next;
+ free (top);
+ top = next;
+ }
+}
+
diff --git a/macros/texinfo/texinfo/info/info-utils.h b/macros/texinfo/texinfo/info/info-utils.h
new file mode 100644
index 0000000000..75f65de704
--- /dev/null
+++ b/macros/texinfo/texinfo/info/info-utils.h
@@ -0,0 +1,145 @@
+/* info-utils.h -- Exported functions and variables from info-utils.c.
+
+ Copyright 1993-2019 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. */
+
+#ifndef INFO_UTILS_H
+#define INFO_UTILS_H
+
+#include "nodes.h"
+#include "window.h"
+#include "search.h"
+
+#if HAVE_ICONV
+# include <iconv.h>
+#endif
+
+/* 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. */
+void info_parse_node (char *string);
+
+long read_quoted_string (char *start, char *terminator, int lines,
+ char **output);
+
+void scan_node_contents (NODE *node, FILE_BUFFER *fb, TAG **tag_ptr);
+
+/* Get the menu entry associated with LABEL in NODE. Return a
+ pointer to the reference if found, or NULL. If SLOPPY, accept
+ initial substrings and check insensitively to case. */
+REFERENCE *info_get_menu_entry_by_label (NODE *node, char *label,
+ int sloppy);
+
+/* 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. */
+REFERENCE *info_copy_reference (REFERENCE *src);
+
+/* Copy a list of existing references into new memory. */
+REFERENCE **info_copy_references (REFERENCE **ref1);
+
+/* Free the data associated with a single REF */
+void info_reference_free (REFERENCE *ref);
+
+/* Free the data associated with REFERENCES. */
+void info_free_references (REFERENCE **references);
+
+/* Create new REFERENCE structure. */
+REFERENCE *info_new_reference (char *filename, char *nodename);
+
+/* 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);
+
+/* Used with multibyte iterator mbi_iterator_t. */
+#define ITER_SETBYTES(iter,n) ((iter).cur.bytes = n)
+#define ITER_LIMIT(iter) ((iter).limit - (iter).cur.ptr)
+
+int ansi_escape (mbi_iterator_t iter, size_t *plen);
+
+/* Return a pointer to a string which is the printed representation
+ of CHARACTER if it were printed at HPOS. */
+char *printed_representation (mbi_iterator_t *iter,
+ int *delim, size_t pl_chars,
+ size_t *pchars, size_t *pbytes);
+
+FILE_BUFFER *file_buffer_of_window (WINDOW *window);
+
+char *node_printed_rep (NODE *node);
+
+/* Return a pointer to the part of PATHNAME that simply defines the file. */
+char *filename_non_directory (char *pathname);
+
+/* Return non-zero if NODE is one especially created by Info. */
+int internal_info_node_p (NODE *node);
+
+/* Make NODE appear to be one especially created by Info, and give it NAME. */
+void name_internal_node (NODE *node, char *name);
+
+/* Return the window displaying NAME, the name of an internally created
+ Info window. */
+WINDOW *get_internal_info_window (char *name);
+
+struct text_buffer
+{
+ char *base;
+ size_t size;
+ size_t off;
+};
+
+#define MIN_TEXT_BUF_ALLOC 512
+
+void text_buffer_init (struct text_buffer *buf);
+void text_buffer_free (struct text_buffer *buf);
+void text_buffer_alloc (struct text_buffer *buf, size_t len);
+size_t text_buffer_vprintf (struct text_buffer *buf, const char *format,
+ va_list ap);
+size_t text_buffer_space_left (struct text_buffer *buf);
+#if HAVE_ICONV
+size_t text_buffer_iconv (struct text_buffer *buf, iconv_t iconv_state,
+ ICONV_CONST char **inbuf, size_t *inbytesleft);
+#endif
+size_t text_buffer_add_string (struct text_buffer *buf, const char *str,
+ size_t len);
+size_t text_buffer_fill (struct text_buffer *buf, int c, size_t len);
+void text_buffer_add_char (struct text_buffer *buf, int c);
+size_t text_buffer_printf (struct text_buffer *buf, const char *format, ...);
+#define text_buffer_reset(buf) ((buf)->off = 0)
+#define text_buffer_base(buf) ((buf)->base)
+#define text_buffer_off(buf) ((buf)->off)
+
+struct info_namelist_entry;
+int info_namelist_add (struct info_namelist_entry **ptop, const char *name);
+void info_namelist_free (struct info_namelist_entry *top);
+
+#if defined(__MSDOS__) || defined(__MINGW32__)
+int fncmp (const char *fn1, const char *fn2);
+#else
+# define fncmp(s,t) strcmp(s,t)
+#endif
+
+#endif /* not INFO_UTILS_H */
diff --git a/macros/texinfo/texinfo/info/info.c b/macros/texinfo/texinfo/info/info.c
new file mode 100644
index 0000000000..4d2137fda4
--- /dev/null
+++ b/macros/texinfo/texinfo/info/info.c
@@ -0,0 +1,1197 @@
+/* info.c -- Display nodes of Info files in multiple windows.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+#include "filesys.h"
+#include "info-utils.h"
+#include "session.h"
+#include "indices.h"
+#include "dribble.h"
+#include "getopt.h"
+#include "man.h"
+#include "variables.h"
+
+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;
+
+static char *invocation_program_name = 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;
+
+/* Name of file to start session with. Default file for --node arguments. */
+static char *initial_file = 0;
+
+/* Array of the names of nodes that the user specified with "--node" on the
+ command line. */
+static char **user_nodenames = NULL;
+static size_t user_nodenames_index = 0;
+static size_t user_nodenames_slots = 0;
+
+/* References to the nodes to start the session with. */
+static REFERENCE **ref_list = NULL;
+static size_t ref_slots = 0;
+static size_t ref_index = 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;
+
+/* Print/visit all matching documents. */
+static int all_matches_p = 0;
+
+/* Non-zero means print the absolute location of the file to be loaded. */
+static int print_where_p = 0;
+
+/* Non-zero means don't try to be smart when searching for nodes. */
+int strict_node_location_p = 0;
+
+#if defined(__MSDOS__) || defined(__MINGW32__)
+/* 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
+#define INITFLE_OPTION 5
+#define VIRTIDX_OPTION 6
+
+static struct option long_options[] = {
+ { "all", 0, 0, 'a' },
+ { "apropos", 1, 0, 'k' },
+ { "debug", 1, 0, 'x' },
+ { "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 },
+ { "init-file", 1, 0, INITFLE_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' },
+ { "strict-node-location", 0, &strict_node_location_p, 1 },
+ { "subnodes", 0, &dump_subnodes, 1 },
+ { "usage", 0, 0, 'O' },
+ { "variable", 1, 0, 'v' },
+ { "version", 0, &print_version_p, 1 },
+ { "vi-keys", 0, &vi_keys_p, 1 },
+ { "where", 0, &print_where_p, 1 },
+#if defined(__MSDOS__) || defined(__MINGW32__)
+ { "speech-friendly", 0, &speech_friendly, 1 },
+#endif
+ {NULL, 0, NULL, 0}
+};
+
+/* String describing the shorthand versions of the long options found above. */
+#if defined(__MSDOS__) || defined(__MINGW32__)
+static char *short_options = "ak:d:n:f:ho:ORsv:wbx:";
+#else
+static char *short_options = "ak:d:n:f:ho:ORv:wsx:";
+#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);
+
+
+/* Find the first file to load (and possibly the first node as well).
+ If the --file option is given, use that as the file, otherwise try to
+ interpret the first non-option argument, either by looking it up as a dir
+ entry, looking for a file by that name, or finding a man page by that name.
+ Set INITIAL_FILE to the name of the initial Info file. */
+static void
+get_initial_file (int *argc, char ***argv, char **error)
+{
+ REFERENCE *entry;
+
+ /* User used "--file". */
+ if (user_filename)
+ {
+ if (!IS_ABSOLUTE(user_filename) && HAS_SLASH(user_filename)
+ && !(user_filename[0] == '.' && IS_SLASH(user_filename[1])))
+ {
+ /* Prefix "./" to the filename to prevent a lookup
+ in INFOPATH. */
+ char *s;
+ asprintf (&s, "%s%s", "./", user_filename);
+ free (user_filename);
+ user_filename = s;
+ }
+ if (IS_ABSOLUTE(user_filename) || HAS_SLASH(user_filename))
+ initial_file = info_add_extension (0, user_filename, 0);
+ else
+ initial_file = info_find_fullpath (user_filename, 0);
+
+ if (!initial_file)
+ {
+ if (!filesys_error_number)
+ filesys_error_number = ENOENT;
+ *error = filesys_error_string (user_filename, filesys_error_number);
+ }
+
+ return;
+ }
+
+ if (!(*argv)[0])
+ {
+ /* No more non-option arguments. */
+ initial_file = xstrdup("dir");
+ return;
+ }
+
+ /* If first argument begins with '(', add it as if it were given with
+ '--node'. This is to support invoking like
+ "info '(emacs)Buffers'". If it is a well-formed node spec then
+ the rest of the arguments are menu entries to follow, or an
+ index entry. */
+ if ((*argv)[0][0] == '(')
+ {
+ info_parse_node ((*argv)[0]);
+ if (info_parsed_filename)
+ {
+ initial_file = info_find_fullpath (info_parsed_filename, 0);
+ if (initial_file)
+ {
+ add_pointer_to_array (info_new_reference (initial_file,
+ info_parsed_nodename),
+ ref_index, ref_list, ref_slots, 2);
+ /* Remove this argument from the argument list. */
+ memmove (*argv, *argv + 1, *argc-- * sizeof (char *));
+ return;
+ }
+ }
+ }
+
+ /* If there are any more arguments, the initial file is the
+ dir entry given by the first one. */
+ {
+ /* If they say info info (or info -O info, etc.), show them
+ info-stnd.texi. (Get info.texi with info -f info.) */
+ if ((*argv)[0] && mbscasecmp ((*argv)[0], "info") == 0)
+ (*argv)[0] = "info-stnd";
+
+ entry = lookup_dir_entry ((*argv)[0], 0);
+ if (entry)
+ {
+ initial_file = info_find_fullpath (entry->filename, 0);
+ if (initial_file)
+ {
+ REFERENCE *copy;
+ (*argv)++; /* Advance past first remaining argument. */
+ (*argc)--;
+
+ copy = info_copy_reference (entry);
+ /* Store full path, so that we find the already loaded file in
+ info_find_file, and show the full path if --where is used. */
+ free (copy->filename);
+ copy->filename = xstrdup (initial_file);
+ add_pointer_to_array (copy, ref_index, ref_list, ref_slots, 2);
+ return;
+ }
+ }
+ }
+
+ /* File name lookup. */
+ {
+ /* Try finding a file with this name, in case
+ it exists, but wasn't listed in dir. */
+ initial_file = info_find_fullpath ((*argv)[0], 0);
+ if (initial_file)
+ {
+ add_pointer_to_array (info_new_reference ((*argv)[0], "Top"),
+ ref_index, ref_list, ref_slots, 2);
+ (*argv)++; /* Advance past first remaining argument. */
+ (*argc)--;
+ return;
+ }
+ else
+ asprintf (error, _("No menu item '%s' in node '%s'"),
+ (*argv)[0], "(dir)Top");
+ }
+
+ /* Fall back to loading man page. */
+ {
+ NODE *man_node;
+
+ debug (3, ("falling back to manpage node"));
+
+ man_node = get_manpage_node ((*argv)[0]);
+ if (man_node)
+ {
+ add_pointer_to_array
+ (info_new_reference (MANPAGE_FILE_BUFFER_NAME, (*argv)[0]),
+ ref_index, ref_list, ref_slots, 2);
+
+ initial_file = MANPAGE_FILE_BUFFER_NAME;
+ free (man_node);
+ return;
+ }
+ }
+
+ /* Inexact dir lookup. */
+ {
+ entry = lookup_dir_entry ((*argv)[0], 1);
+ if (entry)
+ {
+ initial_file = info_find_fullpath (entry->filename, 0);
+ if (initial_file)
+ {
+ REFERENCE *copy;
+ (*argv)++; /* Advance past first remaining argument. */
+ (*argc)--;
+ /* Clear error message. */
+ free (*error);
+ *error = 0;
+
+ copy = info_copy_reference (entry);
+ /* Store full path, so that we find the already loaded file in
+ info_find_file, and show the full path if --where is used. */
+ free (copy->filename);
+ copy->filename = initial_file;
+ add_pointer_to_array (copy, ref_index, ref_list, ref_slots, 2);
+ return;
+ }
+ }
+ }
+
+ return;
+}
+
+/* Expand list of nodes to be loaded. */
+static void
+add_initial_nodes (int argc, char **argv, char **error)
+{
+ /* Add nodes specified with --node. */
+ if (user_nodenames)
+ {
+ int i;
+
+ /* If any --node arguments were given, the node in ref_list[0] is only
+ used to set initial_file. */
+ if (user_nodenames_index > 0 && ref_index > 0)
+ {
+ info_reference_free (ref_list[0]);
+ ref_list[0] = 0;
+ ref_index = 0;
+ }
+
+ for (i = 0; user_nodenames[i]; i++)
+ {
+ char *node_filename = 0;
+ char *node_nodename = 0;
+
+ /* Parse node spec to support invoking
+ like info --node "(emacs)Buffers". */
+ info_parse_node (user_nodenames[i]);
+ if (info_parsed_filename)
+ {
+ node_filename = info_parsed_filename;
+ node_nodename = info_parsed_nodename;
+ }
+ else
+ {
+ FILE_BUFFER *file_buffer;
+ TAG *tag;
+ int j;
+
+ if (!initial_file)
+ continue; /* Shouldn't happen. */
+
+ /* Check for a node by this name, and if there isn't one
+ look for an inexact match. */
+
+ node_filename = initial_file;
+ node_nodename = 0;
+
+ file_buffer = info_find_file (node_filename);
+ if (!file_buffer)
+ continue;
+
+ /* First look for an exact match. */
+ for (j = 0; (tag = file_buffer->tags[j]); j++)
+ if (strcmp (user_nodenames[i], tag->nodename) == 0)
+ {
+ node_nodename = tag->nodename;
+ break;
+ }
+
+ if (!node_nodename)
+ {
+ int best_guess = -1;
+ int len = strlen (user_nodenames[i]);
+ for (j = 0; (tag = file_buffer->tags[j]); j++)
+ {
+ if (mbscasecmp (user_nodenames[i], tag->nodename) == 0)
+ {
+ /* Exact, case-insensitive match. */
+ node_nodename = tag->nodename;
+ best_guess = -1;
+ break;
+ }
+ else if (best_guess == -1
+ && (mbsncasecmp (user_nodenames[i],
+ tag->nodename, len) == 0))
+ /* Case-insensitive initial substring. */
+ best_guess = j;
+ }
+ if (best_guess != -1)
+ {
+ node_nodename = file_buffer->tags[best_guess]->nodename;
+ }
+ }
+
+ if (!node_nodename)
+ {
+ free (*error);
+ asprintf (error, _("Cannot find node '%s'"),
+ user_nodenames[i]);
+ continue;
+ }
+ }
+
+ if (node_filename && node_nodename)
+ add_pointer_to_array
+ (info_new_reference (node_filename, node_nodename),
+ ref_index, ref_list, ref_slots, 2);
+ }
+ }
+
+ if (goto_invocation_p)
+ {
+ NODE *top_node = 0;
+ REFERENCE *invoc_ref = 0;
+
+ char *program;
+
+ if (ref_index == 0)
+ {
+ info_error (_("No program name given"));
+ exit (1);
+ }
+
+ if (invocation_program_name)
+ program = xstrdup (invocation_program_name);
+ else if (ref_list[0] && ref_list[0]->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 (ref_list[0]->filename);
+ else
+ program = xstrdup ("");
+
+ if (ref_index > 0)
+ top_node = info_get_node (ref_list[0]->filename,
+ ref_list[0]->nodename);
+ if (top_node)
+ invoc_ref = info_intuit_options_node (top_node, program);
+ if (invoc_ref)
+ {
+ info_reference_free (ref_list[0]);
+ ref_index = 0;
+
+ add_pointer_to_array (invoc_ref, ref_index, ref_list, ref_slots, 2);
+ }
+ free (program);
+ }
+
+ /* Default is the "Top" node if there were no other nodes. */
+ if (ref_index == 0 && initial_file)
+ {
+ add_pointer_to_array (info_new_reference (initial_file, "Top"),
+ ref_index, ref_list, ref_slots, 2);
+ }
+
+ /* If there are arguments remaining, they are the names of menu items
+ in sequential info files starting from the first one loaded. */
+ if (*argv && ref_index > 0)
+ {
+ NODE *initial_node; /* Node to start following menus from. */
+ NODE *node_via_menus;
+
+ initial_node = info_get_node_with_defaults (ref_list[0]->filename,
+ ref_list[0]->nodename, 0);
+ if (!initial_node)
+ return;
+
+ node_via_menus = info_follow_menus (initial_node, argv, error, 1);
+ if (node_via_menus)
+ {
+ argv += argc; argc = 0;
+
+ info_reference_free (ref_list[0]);
+ ref_list[0] = info_new_reference (node_via_menus->fullpath,
+ node_via_menus->nodename);
+ free_history_node (node_via_menus);
+ }
+
+ /* If no nodes found, and there is exactly one argument remaining,
+ check for it as an index entry. */
+ else if (argc == 1 && argv[0])
+ {
+ FILE_BUFFER *fb;
+ REFERENCE *match;
+
+ debug (3, ("looking in indices"));
+ fb = info_find_file (ref_list[0]->filename);
+ if (fb)
+ {
+ match = look_in_indices (fb, argv[0], 0);
+ if (match)
+ {
+ argv += argc; argc = 0;
+ free (*error); *error = 0;
+
+ info_reference_free (ref_list[0]);
+ ref_list[0] = info_copy_reference (match);
+ }
+ }
+ }
+
+ /* If there are arguments remaining, follow menus inexactly. */
+ if (argc != 0)
+ {
+ initial_node = info_get_node_with_defaults (ref_list[0]->filename,
+ ref_list[0]->nodename,
+ 0);
+ free (*error); *error = 0;
+ node_via_menus = info_follow_menus (initial_node, argv, error, 0);
+ if (node_via_menus)
+ {
+ if (argc >= 2 || !*error)
+ {
+ argv += argc; argc = 0;
+
+ info_reference_free (ref_list[0]);
+ ref_list[0] = info_new_reference (node_via_menus->fullpath,
+ node_via_menus->nodename);
+ }
+ free_history_node (node_via_menus);
+ }
+ }
+
+ /* If still no nodes found, and there is exactly one argument remaining,
+ look in indices sloppily. */
+ if (argc == 1)
+ {
+ FILE_BUFFER *fb;
+ REFERENCE *nearest;
+
+ debug (3, ("looking in indices sloppily"));
+ fb = info_find_file (ref_list[0]->filename);
+ if (fb)
+ {
+ nearest = look_in_indices (fb, argv[0], 1);
+ if (nearest)
+ {
+ argv += argc; argc = 0;
+ free (*error); *error = 0;
+
+ info_reference_free (ref_list[0]);
+ ref_list[0] = info_copy_reference (nearest);
+ }
+ }
+ }
+ }
+
+ return;
+}
+
+static void
+info_find_matching_files (char *filename)
+{
+ int i;
+ char *searchdir;
+
+ NODE *man_node;
+
+ /* Check for dir entries first. */
+ i = 0;
+ for (searchdir = infopath_first (&i); searchdir;
+ searchdir = infopath_next (&i))
+ {
+ REFERENCE *new_ref = dir_entry_of_infodir (filename, searchdir);
+
+ if (new_ref)
+ add_pointer_to_array (new_ref, ref_index, ref_list, ref_slots, 2);
+ }
+
+ /* Look for files with matching names. */
+ i = 0;
+ while (1)
+ {
+ char *p;
+ int j;
+
+ p = info_file_find_next_in_path (filename, &i, 0);
+ if (!p)
+ break;
+
+ /* Add to list only if the file is not in the list already (which would
+ happen if there was a dir entry with the label and filename both
+ being this file). */
+ for (j = 0; j < ref_index; j++)
+ {
+ if (!strcmp (p, ref_list[j]->filename))
+ break;
+ }
+
+ if (j == ref_index)
+ {
+ add_pointer_to_array (info_new_reference (p, 0),
+ ref_index, ref_list, ref_slots, 2);
+ }
+ free (p);
+ }
+
+ /* Check for man page. */
+ man_node = get_manpage_node (filename);
+ if (man_node)
+ {
+ free (man_node);
+ add_pointer_to_array
+ (info_new_reference (MANPAGE_FILE_BUFFER_NAME, filename),
+ ref_index, ref_list, ref_slots, 2);
+ }
+}
+
+
+static void
+set_debug_level (const char *arg)
+{
+ char *p;
+ long n = strtol (arg, &p, 10);
+ if (*p)
+ {
+ fprintf (stderr, _("invalid number: %s\n"), arg);
+ exit (EXIT_FAILURE);
+ }
+ if (n < 0 || n > UINT_MAX)
+ debug_level = UINT_MAX;
+ else
+ debug_level = n;
+}
+
+static 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;
+ infopath_add (directory_name);
+ }
+
+ free (directory_name);
+}
+
+
+/* **************************************************************** */
+/* */
+/* Main Entry Point to the Info Program */
+/* */
+/* **************************************************************** */
+
+int
+main (int argc, char *argv[])
+{
+ int getopt_long_index; /* Index returned by getopt_long (). */
+ char *init_file = 0; /* Name of init file specified. */
+ char *error = 0; /* Error message to display in mini-buffer. */
+
+#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;
+
+ case 'a':
+ all_matches_p = 1;
+ break;
+
+ /* User wants to add a directory. */
+ case 'd':
+ infopath_add (optarg);
+ break;
+
+ /* User is specifying a particular node. */
+ case 'n':
+ add_pointer_to_array (optarg, user_nodenames_index, user_nodenames,
+ user_nodenames_slots, 10);
+ 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;
+
+#if defined(__MSDOS__) || defined(__MINGW32__)
+ /* User wants speech-friendly output. */
+ case 'b':
+ speech_friendly = 1;
+ break;
+#endif /* __MSDOS__ || __MINGW32__ */
+
+ /* User has specified a string to search all indices for. */
+ case 'k':
+ apropos_p = 1;
+ 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;
+ free (index_search_string);
+ index_search_string = xstrdup (optarg);
+ break;
+
+ /* User has specified a file to use as the init file. */
+ case INITFLE_OPTION:
+ init_file = optarg;
+ break;
+
+ case 'v':
+ {
+ VARIABLE_ALIST *var;
+ char *p;
+ p = strchr (optarg, '=');
+ if (!p)
+ {
+ info_error (_("malformed variable assignment: %s"), optarg);
+ exit (EXIT_FAILURE);
+ }
+ *p++ = 0;
+
+ if (!(var = variable_by_name (optarg)))
+ {
+ info_error (_("%s: no such variable"), optarg);
+ exit (EXIT_FAILURE);
+ }
+
+ if (!set_variable_to_value (var, p, SET_ON_COMMAND_LINE))
+ {
+ info_error (_("value %s is not valid for variable %s"),
+ p, optarg);
+ exit (EXIT_FAILURE);
+ }
+ }
+ break;
+
+ case 'x':
+ set_debug_level (optarg);
+ break;
+
+ default:
+ fprintf (stderr, _("Try --help for more information.\n"));
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ /* 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"),
+ "2019");
+ exit (EXIT_SUCCESS);
+ }
+
+ /* If the `--help' option was present, show the help and exit. */
+ if (print_help_p)
+ {
+ info_short_help ();
+ exit (EXIT_SUCCESS);
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ /* If --file was not used and there is a slash in the first non-option
+ argument (e.g. "info subdir/file.info"), do not search the dir files
+ for a matching entry. */
+ if (!user_filename && argv[0] && HAS_SLASH (argv[0]))
+ {
+ user_filename = xstrdup (argv[0]);
+ argv++; /* Advance past first remaining argument. */
+ argc--;
+ }
+
+ /* 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);
+
+ /* Load custom key mappings and variable settings */
+ initialize_terminal_and_keymaps (init_file);
+
+ /* Add extra search directories to any already specified with
+ --directory. */
+ infopath_init ();
+
+ /* If the user wants to search every known index for a given string,
+ do that now, and report the results. */
+ if (apropos_p)
+ {
+ REFERENCE **apropos_list;
+
+ apropos_list = apropos_in_all_indices (apropos_search_string, 0);
+
+ if (!apropos_list)
+ info_error (_(APROPOS_NONE), apropos_search_string);
+ 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);
+ }
+ exit (0);
+ }
+
+ /* Initialize empty list of nodes to load. */
+ add_pointer_to_array (0, ref_index, ref_list, ref_slots, 2);
+ ref_index--;
+
+ if (all_matches_p && !index_search_p)
+ {
+ /* --all */
+ if (!user_filename && argv[0])
+ {
+ user_filename = xstrdup (argv[0]);
+ argv++; argc--;
+ }
+ else if (!user_filename)
+ {
+ exit (1);
+ }
+ info_find_matching_files (user_filename);
+ /* If only one match, don't start in a menu of matches. */
+ if (ref_index == 1)
+ all_matches_p = 0;
+
+ /* --where */
+ if (print_where_p)
+ {
+ int i;
+ if (!ref_list)
+ exit (1);
+
+ for (i = 0; ref_list[i]; i++)
+ printf ("%s\n", ref_list[i]->filename);
+ exit (0);
+ }
+ }
+ else
+ {
+ if (goto_invocation_p)
+ {
+ /* 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;
+ if (*p)
+ {
+ while (p[1])
+ p++;
+ invocation_program_name = *p;
+ }
+ }
+
+ get_initial_file (&argc, &argv, &error);
+
+ /* If the user specified `--index-search=STRING --all', create
+ and display the menu of results. */
+ if (index_search_p && all_matches_p && initial_file)
+ {
+ FILE_BUFFER *initial_fb;
+ initial_fb = info_find_file (initial_file);
+ if (initial_fb)
+ {
+ NODE *node = create_virtual_index (initial_fb,
+ index_search_string);
+ if (node)
+ {
+ if (user_output_filename)
+ {
+ FILE *output_stream = 0;
+ if (strcmp (user_output_filename, "-") == 0)
+ output_stream = stdout;
+ else
+ output_stream = fopen (user_output_filename, "w");
+ if (output_stream)
+ {
+ write_node_to_stream (node, output_stream);
+ }
+ exit (0);
+ }
+ else
+ {
+ initialize_info_session ();
+ info_set_node_of_window (active_window, node);
+ info_read_and_dispatch ();
+ close_info_session ();
+ exit (0);
+ }
+ }
+ }
+ }
+
+ /* If the user specified `--index-search=STRING',
+ start the info session in the node corresponding
+ to what they want. */
+ else if (index_search_p && initial_file && !user_output_filename)
+ {
+ FILE_BUFFER *initial_fb;
+ initial_fb = info_find_file (initial_file);
+ if (initial_fb)
+ {
+ REFERENCE *result;
+ int i, match_offset;
+
+ result = next_index_match (initial_fb, index_search_string, 0, 1,
+ &i, &match_offset);
+
+ if (result)
+ {
+ initialize_info_session ();
+ report_index_match (i, match_offset);
+ info_select_reference (active_window, result);
+ info_read_and_dispatch ();
+ close_info_session ();
+ exit (0);
+ }
+ }
+
+ fprintf (stderr, _("no index entries found for '%s'\n"),
+ index_search_string);
+ close_dribble_file ();
+ exit (1);
+ }
+
+ /* Add nodes to start with (unless we fell back to the man page). */
+ if (!ref_list[0] || strcmp (ref_list[0]->filename,
+ MANPAGE_FILE_BUFFER_NAME))
+ {
+ add_initial_nodes (argc, argv, &error);
+ }
+
+ /* --where */
+ if (print_where_p)
+ {
+ if (initial_file)
+ printf ("%s\n", initial_file);
+ exit (0);
+ }
+
+ }
+
+ /* --output */
+ if (user_output_filename)
+ {
+ if (error)
+ info_error ("%s", error);
+
+ preprocess_nodes_p = 0;
+ dump_nodes_to_file (ref_list, user_output_filename, dump_subnodes);
+ exit (0);
+ }
+
+ if (ref_index == 0)
+ {
+ if (error)
+ {
+ info_error ("%s", error);
+ exit (1);
+ }
+ exit (0);
+ }
+
+ info_session (ref_list, all_matches_p ? user_filename : 0, error);
+ close_info_session ();
+ exit (0);
+}
+
+
+/* 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 (_("\
+Frequently-used options:\n\
+ -a, --all use all matching manuals\n\
+ -k, --apropos=STRING look up STRING in all indices of all manuals\n\
+ -d, --directory=DIR add DIR to INFOPATH\n\
+ -f, --file=MANUAL specify Info manual 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=FILE output selected nodes to FILE"));
+
+ puts (_("\
+ -O, --show-options, --usage go to command-line options node"));
+
+#if defined(__MSDOS__) || defined(__MINGW32__)
+ puts (_("\
+ -b, --speech-friendly be friendly to speech synthesizers"));
+#endif
+
+ puts (_("\
+ --subnodes recursively output menu items\n\
+ -v, --variable VAR=VALUE assign VALUE to Info variable VAR\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-stnd show the manual for this Info program\n\
+ info emacs start at emacs node from top-level dir\n\
+ info emacs buffers select buffers menu entry in emacs manual\n\
+ info emacs -n Files start at Files node within emacs manual\n\
+ info '(emacs)Files' alternative way to start at Files node\n\
+ info --show-options emacs start at node with emacs' command line options\n\
+ info --subnodes -o out.txt emacs\n\
+ dump entire emacs manual to out.txt\n\
+ info -f ./foo.info show file ./foo.info, not searching dir"));
+
+ puts ("");
+
+ puts (_("\
+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/"));
+
+ exit (EXIT_SUCCESS);
+}
+
+
+/* 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/macros/texinfo/texinfo/info/info.h b/macros/texinfo/texinfo/info/info.h
new file mode 100644
index 0000000000..0280b3b34e
--- /dev/null
+++ b/macros/texinfo/texinfo/info/info.h
@@ -0,0 +1,113 @@
+/* info.h -- Header file which includes all of the other headers.
+
+ Copyright 1993-2019 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. */
+
+#ifndef INFO_H
+#define INFO_H
+
+/* System dependencies. */
+#include "system.h"
+
+/* Some of our other include files use these. */
+typedef int Function ();
+typedef void VFunction ();
+typedef char *CFunction ();
+
+#include "string.h"
+#include "mbiter.h"
+#include "mbchar.h"
+
+extern char *program_name;
+
+#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') || (c == '\r'))
+#endif /* !whitespace_or_newline */
+
+/* Add ELT to the list of elements found in ARRAY. SLOTS is the number
+ of slots that have already been allocated. IDX is the index into the
+ array where ELT should be added. MINSLOTS is the number of slots to
+ start the array with in case it is empty. */
+#define add_pointer_to_array(elt, idx, array, slots, minslots) \
+ do \
+ { \
+ if (idx + 2 >= slots) \
+ { \
+ if (slots == 0) \
+ slots = minslots; \
+ array = x2nrealloc (array, &slots, sizeof(array[0])); \
+ } \
+ array[idx++] = elt; \
+ array[idx] = 0; /* null pointer for pointer types */ \
+ } \
+ while (0)
+
+#define add_element_to_array add_pointer_to_array
+
+/* 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)
+
+
+/* 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 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;
+
+/* 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;
+
+
+/* In infopath.c, but also used in man.c. */
+
+/* Given a string containing units of information separated by colons,
+ return the next one pointed to by IDX, or NULL if there are no more.
+ Advance IDX to the character after the colon. */
+char *extract_colon_unit (char *string, int *idx);
+
+#endif /* !INFO_H */
diff --git a/macros/texinfo/texinfo/info/infodoc.c b/macros/texinfo/texinfo/info/infodoc.c
new file mode 100644
index 0000000000..b9cb00d1d5
--- /dev/null
+++ b/macros/texinfo/texinfo/info/infodoc.c
@@ -0,0 +1,840 @@
+/* infodoc.c -- functions which build documentation nodes.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+#include "info-utils.h"
+#include "filesys.h"
+#include "session.h"
+#include "doc.h"
+#include "funs.h"
+
+/* 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;
+
+/* 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. */
+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[virtual-index] Synthesize menu of matching index entries.\n"),
+ "\n",
+ N_("\\%-10[abort-key] Cancel the current operation.\n"),
+ "\n",
+ NULL
+};
+
+static char *where_is_internal (Keymap map, InfoCommand *cmd);
+
+static void
+dump_map_to_text_buffer (struct text_buffer *tb, int *prefix,
+ int prefix_len, Keymap map)
+{
+ register int i;
+ int *new_prefix = xmalloc ((prefix_len + 2) * sizeof (int));
+
+ memcpy (new_prefix, prefix, prefix_len * sizeof (int));
+ new_prefix[prefix_len + 1] = 0;
+
+ for (i = 0; i < KEYMAP_SIZE; i++)
+ {
+ if (i == 128)
+ i = 256;
+ if (i == 128 + KEYMAP_META_BASE)
+ i = 256 + KEYMAP_META_BASE;
+
+ new_prefix[prefix_len] = i;
+ if (map[i].type == ISKMAP)
+ {
+ dump_map_to_text_buffer (tb, new_prefix, prefix_len + 1,
+ map[i].value.keymap);
+ }
+ else if (map[i].value.function)
+ {
+ long start_of_line = tb->off;
+ register int last;
+ char *doc, *name;
+
+ /* Hide some key mappings. */
+ if (map[i].value.function
+ && (map[i].value.function->func == info_do_lowercase_version))
+ continue;
+
+ doc = function_documentation (map[i].value.function);
+ name = function_name (map[i].value.function);
+
+ if (!*doc)
+ continue;
+
+ /* Find out if there is a series of identical functions, as in
+ add-digit-to-numeric-arg. */
+ for (last = i + 1; last < KEYMAP_SIZE; last++)
+ if ((map[last].type != ISFUNC) ||
+ (map[last].value.function != map[i].value.function))
+ break;
+
+ if (last - 1 != i)
+ {
+ text_buffer_printf (tb, "%s .. ", pretty_keyseq (new_prefix));
+ new_prefix[prefix_len] = last - 1;
+ text_buffer_printf (tb, "%s", pretty_keyseq (new_prefix));
+ i = last - 1;
+ }
+ else
+ text_buffer_printf (tb, "%s", pretty_keyseq (new_prefix));
+
+ while (tb->off - start_of_line < 8)
+ text_buffer_printf (tb, " ");
+
+ /* Print the name of the function, and some padding before the
+ documentation string is printed. */
+ {
+ int length_so_far;
+ int desired_doc_start = 40;
+
+ text_buffer_printf (tb, "(%s)", name);
+ length_so_far = tb->off - start_of_line;
+
+ if ((desired_doc_start + strlen (doc))
+ >= (unsigned int) the_screen->width)
+ text_buffer_printf (tb, "\n ");
+ else
+ {
+ while (length_so_far < desired_doc_start)
+ {
+ text_buffer_printf (tb, " ");
+ length_so_far++;
+ }
+ }
+ }
+ text_buffer_printf (tb, "%s\n", doc);
+ }
+ }
+ 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 *exec_keys;
+
+ int printed_one_mx = 0;
+ struct text_buffer msg;
+ char *infopath_str = infopath_string ();
+
+ text_buffer_init (&msg);
+
+ for (i = 0; info_internal_help_text[i]; i++)
+ text_buffer_printf (&msg, replace_in_documentation
+ (_(info_internal_help_text[i]),
+ help_is_only_window_p), NULL, NULL, NULL);
+
+ text_buffer_printf (&msg, "---------------------\n");
+ text_buffer_printf (&msg, _("This is GNU Info version %s. "), VERSION);
+ text_buffer_printf (&msg, _("The current search path is:\n"));
+ text_buffer_printf (&msg, "%s\n", infopath_str);
+ text_buffer_printf (&msg, "---------------------\n\n");
+ free (infopath_str);
+
+ text_buffer_printf (&msg, _("Commands available in Info windows:\n\n"));
+ dump_map_to_text_buffer (&msg, 0, 0, info_keymap);
+ text_buffer_printf (&msg, "---------------------\n\n");
+ text_buffer_printf (&msg, _("Commands available in the echo area:\n\n"));
+ dump_map_to_text_buffer (&msg, 0, 0, echo_area_keymap);
+
+ /* 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 = &function_doc_array[i];
+
+ if (cmd->func != info_do_lowercase_version
+ && !where_is_internal (info_keymap, cmd)
+ && !where_is_internal (echo_area_keymap, cmd))
+ {
+ if (!printed_one_mx)
+ {
+ text_buffer_printf (&msg, "---------------------\n\n");
+ if (exec_keys && exec_keys[0])
+ text_buffer_printf (&msg,
+ _("The following commands can only be invoked via "
+ "%s:\n\n"),
+ exec_keys);
+ else
+ text_buffer_printf (&msg,
+ _("The following commands cannot be invoked at all:\n\n"));
+ printed_one_mx = 1;
+ }
+
+ text_buffer_printf (&msg,
+ "%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)
+ );
+
+ }
+ }
+
+ free (exec_keys);
+
+ node = text_buffer_to_node (&msg);
+
+ internal_info_help_node = node;
+
+ name_internal_node (internal_info_help_node, xstrdup (info_help_nodename));
+}
+
+/* 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_internal_info_window (info_help_nodename);
+
+ /* Close help window if in it already. */
+ if (help_window && help_window == active_window)
+ {
+ info_delete_window_internal (help_window);
+ return NULL;
+ }
+
+ /* 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)
+ {
+ info_error ("%s", msg_cant_make_help);
+ return NULL;
+ }
+ }
+
+ /* 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 ();
+ info_set_node_of_window (help_window, internal_info_help_node);
+ }
+ else
+ {
+ info_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)
+ {
+ active_window = help_window;
+ }
+ info_set_node_of_window (active_window, internal_info_help_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;
+ }
+}
+
+/* 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->fullpath
+ && !mbscasecmp ("info",
+ filename_non_directory (win->node->fullpath))
+ && (!strcmp (win->node->nodename, "Help")
+ || !strcmp (win->node->nodename, "Help-Small-Screen")))
+ {
+ active_window = win;
+ return;
+ }
+ }
+ }
+
+ /* If there is more than one window on the screen, check if the user typed
+ "H" for help message before typing "h" for tutorial. If so, close help
+ message so the tutorial will not be in a small window. */
+ if (windows->next)
+ {
+ WINDOW *help_window = get_internal_info_window (info_help_nodename);
+ if (help_window && help_window == active_window)
+ {
+ info_delete_window_internal (help_window);
+ }
+ }
+
+ /* 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);
+
+ /* info.info is distributed with Emacs, not Texinfo, so fall back to
+ info-stnd.info if it isn't there. */
+ if (!node)
+ node = info_get_node ("info-stnd", "Top");
+
+ if (!node)
+ {
+ if (info_recent_file_error)
+ info_error ("%s", info_recent_file_error);
+ else
+ info_error (msg_cant_file_node, "info", nodename);
+
+ return;
+ }
+
+ info_set_node_of_window (active_window, node);
+}
+
+/* **************************************************************** */
+/* */
+/* Groveling Info Keymaps and Docs */
+/* */
+/* **************************************************************** */
+
+/* Return the documentation associated with the Info command FUNCTION. */
+char *
+function_documentation (InfoCommand *cmd)
+{
+ char *doc;
+
+ doc = cmd->doc;
+
+ return replace_in_documentation ((strlen (doc) == 0) ? doc : _(doc), 0);
+}
+
+/* Return the user-visible name of the function associated with the
+ Info command FUNCTION. */
+char *
+function_name (InfoCommand *cmd)
+{
+ return cmd->func_name;
+}
+
+/* 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;
+
+ if (!function_doc_array[i].func)
+ return 0;
+ else
+ return &function_doc_array[i];
+}
+
+DECLARE_INFO_COMMAND (describe_key, _("Print documentation for KEY"))
+{
+ int keys[50];
+ int keystroke;
+ int *k = keys;
+ Keymap map = info_keymap;
+
+ *k = '\0';
+
+ for (;;)
+ {
+ message_in_echo_area (_("Describe key: %s"), pretty_keyseq (keys));
+ keystroke = get_input_key ();
+ unmessage_in_echo_area ();
+
+ /* Add the KEYSTROKE to our list. */
+ *k++ = keystroke;
+ *k = '\0';
+
+ if (map[keystroke].value.function == NULL)
+ {
+ message_in_echo_area (_("%s is undefined"), pretty_keyseq (keys));
+ return;
+ }
+ else if (map[keystroke].type == ISKMAP)
+ {
+ map = map[keystroke].value.keymap;
+ continue;
+ }
+ else
+ {
+ char *keyname, *message, *fundoc, *funname = "";
+
+ /* 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 (map[keystroke].value.function
+ && map[keystroke].value.function->func
+ == info_do_lowercase_version)
+ {
+ int lowerkey;
+
+ if (keystroke >= KEYMAP_META_BASE)
+ {
+ lowerkey = keystroke;
+ lowerkey -= KEYMAP_META_BASE;
+ lowerkey = tolower (lowerkey);
+ lowerkey += KEYMAP_META_BASE;
+ }
+ else
+ lowerkey = tolower (keystroke);
+
+ if (map[lowerkey].value.function == NULL)
+ {
+ message_in_echo_area (_("%s is undefined"),
+ pretty_keyseq (keys));
+ return;
+ }
+ }
+
+ keyname = pretty_keyseq (keys);
+
+ funname = function_name (map[keystroke].value.function);
+
+ fundoc = function_documentation (map[keystroke].value.function);
+
+ message = xmalloc
+ (10 + strlen (keyname) + strlen (fundoc) + strlen (funname));
+
+ sprintf (message, "%s (%s): %s.", keyname, funname, fundoc);
+
+ window_message_in_echo_area ("%s", message);
+ free (message);
+ break;
+ }
+ }
+}
+
+/* Return the pretty-printable name of a single key. */
+char *
+pretty_keyname (int key)
+{
+ static char rep_buffer[30];
+ char *rep;
+
+ if (key >= KEYMAP_META_BASE)
+ {
+ char temp[20];
+
+ rep = pretty_keyname (key - KEYMAP_META_BASE);
+
+ sprintf (temp, "M-%s", rep);
+ strcpy (rep_buffer, temp);
+ rep = rep_buffer;
+ }
+ else if (Control_p (key))
+ {
+ switch (key)
+ {
+ case '\n': rep = "LFD"; break;
+ case '\t': rep = "TAB"; break;
+ case '\r': rep = "RET"; break;
+ case ESC: rep = "ESC"; break;
+
+ default:
+ sprintf (rep_buffer, "C-%c", UnControl (key));
+ rep = rep_buffer;
+ }
+ }
+ else if (key >= 256)
+ switch (key)
+ {
+ case KEY_RIGHT_ARROW:
+ rep = "Right"; break;
+ case KEY_LEFT_ARROW:
+ rep = "Left"; break;
+ case KEY_UP_ARROW:
+ rep = "Up"; break;
+ case KEY_DOWN_ARROW:
+ rep = "Down"; break;
+ case KEY_PAGE_UP:
+ rep = "PgUp"; break;
+ case KEY_PAGE_DOWN:
+ rep = "PgDn"; break;
+ case KEY_HOME:
+ rep = "Home"; break;
+ case KEY_END:
+ rep = "End"; break;
+ case KEY_DELETE:
+ rep = "DEL"; break;
+ case KEY_INSERT:
+ rep = "INS"; break;
+ case KEY_BACK_TAB:
+ rep = "BackTab"; break;
+ case KEY_MOUSE:
+ rep = "(mouse event)"; break;
+ default:
+ rep = "(unknown key)"; break; /* This shouldn't be displayed. */
+ }
+ 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. Return
+ value should not be freed by caller. */
+char *
+pretty_keyseq (int *keyseq)
+{
+ static struct text_buffer rep = { 0 };
+
+ if (!text_buffer_base (&rep))
+ text_buffer_init (&rep);
+ else
+ text_buffer_reset (&rep);
+
+ if (!*keyseq)
+ return "";
+
+ while (1)
+ {
+ text_buffer_printf (&rep, "%s", pretty_keyname (keyseq[0]));
+ keyseq++;
+
+ if (!*keyseq)
+ break;
+
+ text_buffer_add_char (&rep, ' ');
+ }
+ return text_buffer_base (&rep);
+}
+
+/* Replace the names of functions with the key that invokes them.
+ Return value should not be freed by caller. */
+char *
+replace_in_documentation (const char *string, int help_is_only_window_p)
+{
+ register int i, start;
+ static struct text_buffer txtresult = {0};
+
+ text_buffer_free (&txtresult);
+ text_buffer_init (&txtresult);
+ text_buffer_alloc (&txtresult, strlen (string));
+
+ 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;
+
+ if(string[j] == '%')
+ {
+ if (string[++j] == '-')
+ j++;
+ if (isdigit(string[j]))
+ {
+ while (isdigit(string[j]))
+ j++;
+ if (string[j] == '.' && isdigit(string[j + 1]))
+ {
+ j += 1;
+ 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] == '[')
+ {
+ char *rep_name, *fun_name, *rep;
+ InfoCommand *command;
+ unsigned replen;
+
+ /* Copy in the old text. */
+ text_buffer_add_string (&txtresult, string + start, i - start);
+ start = j + 1;
+
+ /* 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';
+
+ start = i;
+ if (string[start] == ']')
+ start++;
+
+ fun_name = rep_name;
+ if (strcmp (rep_name, "quit-help") == 0)
+ {
+ /* Special case for help window. 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. */
+
+ fun_name = help_is_only_window_p ? "history-node"
+ : "get-help-window";
+ }
+
+ /* 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 ();
+
+ rep = where_is (info_keymap, command);
+ if (!rep)
+ rep = "N/A";
+ replen = strlen (rep);
+
+ if (fmt)
+ text_buffer_printf (&txtresult, fmt, rep);
+ else
+ text_buffer_add_string (&txtresult, rep, replen);
+ }
+
+ free (fmt);
+ }
+ }
+ text_buffer_add_string (&txtresult,
+ string + start, strlen (string + start) + 1);
+ return text_buffer_base (&txtresult);
+}
+
+/* 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)
+{
+ register FUNCTION_KEYSEQ *k;
+
+ for (k = cmd->keys; k; k = k->next)
+ if (k->map == map)
+ return pretty_keyseq (k->keyseq);
+
+ return NULL;
+}
+
+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);
+ return;
+ }
+
+ if (*command_name)
+ {
+ InfoCommand *command;
+
+ command = named_function (command_name);
+
+ if (command)
+ {
+ char *location;
+
+ location = where_is (info_keymap, command);
+
+ if (!location || !location[0])
+ {
+ info_error (_("'%s' is not on any keys"), command_name);
+ }
+ 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);
+ }
+
+ free (command_name);
+}
diff --git a/macros/texinfo/texinfo/info/infokey.c b/macros/texinfo/texinfo/info/infokey.c
new file mode 100644
index 0000000000..c6e3d428f0
--- /dev/null
+++ b/macros/texinfo/texinfo/info/infokey.c
@@ -0,0 +1,620 @@
+/* infokey.c -- read ~/.infokey
+
+ Copyright 1999-2019 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 Andrew Bettison. */
+
+#include "info.h"
+#include "doc.h"
+#include "session.h"
+#include "funs.h"
+#include "getopt.h"
+#include "variables.h"
+
+extern char *program_name; /* in info.c */
+
+enum sect_e
+ {
+ info = 0,
+ ea = 1,
+ var = 2
+ };
+
+static void syntax_error (const char *filename, unsigned int linenum,
+ const char *fmt, ...) TEXINFO_PRINTFLIKE(3,4);
+
+/* 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 lookup_action (const char *actname);
+
+/* Read the init file. Return true if no error was encountered. Set
+ SUPPRESS_INFO or SUPPRESS_EA to true if the init file specified to ignore
+ default key bindings. */
+int
+compile (FILE *fp, const char *filename, int *suppress_info, int *suppress_ea)
+{
+ int error = 0; /* Set if there was a fatal error in reading init file. */
+ char rescan = 0; /* Whether to reuse the same character when moving onto the
+ next state. */
+ 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 = normal; /* 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;
+ int 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/sizeof(int)) \
+ seq[slen++] = meta ? KEYMAP_META(c) : (c); \
+ else \
+ { \
+ syntax_error(filename, lnum, \
+ _("key sequence too long")); \
+ error = 1; \
+ } \
+ meta = 0; \
+ } while (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))
+ {
+ if (section == info)
+ *suppress_info = 1;
+ else
+ *suppress_ea = 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"));
+ 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"));
+ error = 1;
+ }
+ }
+ break;
+
+ case special_key:
+ switch (c)
+ {
+ case 'u': To_seq (KEY_UP_ARROW); break;
+ case 'd': To_seq (KEY_DOWN_ARROW); break;
+ case 'r': To_seq (KEY_RIGHT_ARROW); break;
+ case 'l': To_seq (KEY_LEFT_ARROW); break;
+ case 'U': To_seq (KEY_PAGE_UP); break;
+ case 'D': To_seq (KEY_PAGE_DOWN); break;
+ case 'h': To_seq (KEY_HOME); break;
+ case 'e': To_seq (KEY_END); break;
+ case 'x': To_seq (KEY_DELETE); break;
+ default: To_seq (c); 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"), c);
+ 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"));
+ error = 1;
+ }
+ else
+ {
+ int keymap_bind_keyseq (Keymap, int *, KEYMAP_ENTRY *);
+
+ act[alen] = '\0';
+ a = lookup_action (act);
+ if (a == A_info_menu_digit)
+ {
+ /* Only allow "1 menu-digit". (This is useful if
+ this default binding is disabled with "#stop".)
+ E.g. do not allow "b menu-digit". */
+ if (seq[0] != '1' || seq[1] != '\0'
+ || section != info)
+ {
+ syntax_error (filename, lnum,
+ _("cannot bind key sequence to menu-digit"));
+ }
+ else
+ {
+ /* Bind each key from '1' to '9' to 'menu-digit'. */
+ KEYMAP_ENTRY ke;
+ int i;
+
+ ke.type = ISFUNC;
+ ke.value.function = &function_doc_array[a];
+
+ for (i = '1'; i <= '9'; i++)
+ {
+ seq[0] = i;
+ keymap_bind_keyseq (info_keymap, seq, &ke);
+ }
+ }
+ }
+ else if (a == -1)
+ {
+ /* Print an error message, but keep going (don't set
+ error = 1) for compatibility with infokey files aimed
+ at future versions which may have different
+ actions. */
+ syntax_error (filename, lnum, _("unknown action `%s'"),
+ act);
+ }
+ else
+ {
+ KEYMAP_ENTRY ke;
+ static InfoCommand invalid_function = { 0 };
+
+ ke.type = ISFUNC;
+ ke.value.function = a != A_INVALID
+ ? &function_doc_array[a]
+ : &invalid_function;
+ To_seq (0);
+
+ if (section == info)
+ keymap_bind_keyseq (info_keymap, seq, &ke);
+ else /* section == ea */
+ keymap_bind_keyseq (echo_area_keymap, seq, &ke);
+ }
+ }
+ }
+ else if (alen < sizeof act - 1)
+ act[alen++] = c;
+ else
+ {
+ syntax_error (filename, lnum, _("action name too long"));
+ 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);
+ error = 1;
+ }
+ break;
+
+ case get_varname:
+ if (c == '=')
+ {
+ if (varlen == 0)
+ {
+ syntax_error (filename, lnum, _("missing variable name"));
+ error = 1;
+ }
+ state = get_value;
+ vallen = 0;
+ }
+ else if (c == '\n' || isspace (c))
+ {
+ syntax_error (filename, lnum,
+ _("missing `=' immediately after variable name"));
+ error = 1;
+ }
+ else if (varlen < sizeof varn - 1)
+ varn[varlen++] = c;
+ else
+ {
+ syntax_error (filename, lnum, _("variable name too long"));
+ error = 1;
+ }
+ break;
+
+ case get_value:
+ if (c == '\n')
+ {
+ VARIABLE_ALIST *v;
+
+ state = start_of_line;
+ varn[varlen] = '\0';
+ val[vallen] = '\0';
+ v = variable_by_name (varn);
+ if (!v)
+ info_error (_("%s: no such variable"), varn);
+ else if (!set_variable_to_value (v, val, SET_IN_CONFIG_FILE))
+ info_error (_("value %s is not valid for variable %s"),
+ val, varn);
+ }
+ else if (vallen < sizeof val - 1)
+ val[vallen++] = c;
+ else
+ {
+ syntax_error (filename, lnum, _("value too long"));
+ error = 1;
+ }
+ break;
+
+ case get_equals:
+ case got_equals:
+ case got_varname:
+ break;
+ }
+ }
+
+#undef To_seq
+
+ return !error;
+}
+
+/* Return the numeric code of an Info command given its name. If not found,
+ return -1. This uses the auto-generated array in doc.c. */
+static int
+lookup_action (const char *name)
+{
+ int i;
+
+ if (!strcmp (name, "invalid"))
+ return A_INVALID;
+ for (i = 0; function_doc_array[i].func_name; i++)
+ if (!strcmp (function_doc_array[i].func_name, name))
+ return i;
+ return -1;
+}
+
+
+
+/* Error handling. */
+
+/* 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, ...)
+{
+ va_list ap;
+
+ fprintf (stderr, "%s: ", program_name);
+ fprintf (stderr, _("\"%s\", line %u: "), filename, linenum);
+ va_start(ap, fmt);
+ vfprintf (stderr, fmt, ap);
+ va_end(ap);
+ fprintf (stderr, "\n");
+}
+
+
diff --git a/macros/texinfo/texinfo/info/infomap.c b/macros/texinfo/texinfo/info/infomap.c
new file mode 100644
index 0000000000..3038994992
--- /dev/null
+++ b/macros/texinfo/texinfo/info/infomap.c
@@ -0,0 +1,731 @@
+/* infomap.c -- keymaps for Info.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+#include "doc.h"
+#include "funs.h"
+#include "session.h"
+#include "terminal.h"
+#include "variables.h"
+
+void keymap_bind_keyseq (Keymap map, int *keyseq, KEYMAP_ENTRY *keyentry);
+
+/* Return a new Keymap. */
+Keymap
+keymap_make_keymap (void)
+{
+ int i;
+ Keymap keymap;
+
+ keymap = (Keymap)xmalloc (KEYMAP_SIZE * sizeof (KEYMAP_ENTRY));
+
+ for (i = 0; i < KEYMAP_SIZE; i++)
+ {
+ keymap[i].type = ISFUNC;
+ keymap[i].value.function = NULL;
+ }
+
+ return keymap;
+}
+
+/* Record KEYSEQ, a sequence of keys terminated by 0, in the linked list of
+ FUNCTION_KEYSEQ hanging off FUNCTION. Label it with ROOTMAP so we know
+ whether the key sequence is for main operation or for the echo area. */
+static void
+add_function_keyseq (InfoCommand *function, int *keyseq, Keymap rootmap)
+{
+ FUNCTION_KEYSEQ *ks, *k;
+ int len;
+
+ if (function == NULL ||
+ function == InfoCmd (info_do_lowercase_version))
+ return;
+
+ /* If there is already a key sequence recorded for this key map,
+ don't do anything. */
+ for (k = function->keys; k; k = k->next)
+ if (k->map == rootmap)
+ return;
+
+ ks = xmalloc (sizeof (FUNCTION_KEYSEQ));
+ ks->next = function->keys;
+ ks->map = rootmap;
+ for (len = 0; keyseq[len]; len++);
+ ks->keyseq = xmalloc ((len + 1) * sizeof (int));
+ memcpy (ks->keyseq, keyseq, (len + 1) * sizeof (int));
+
+ function->keys = ks;
+}
+
+/* Bind key sequence. Don't override already bound key sequences. */
+void
+keymap_bind_keyseq (Keymap map, int *keyseq, KEYMAP_ENTRY *keyentry)
+{
+ Keymap m = map;
+ int *s = keyseq;
+ int c;
+
+ if (!s || *s == 0)
+ return;
+
+ while ((c = *s++) != '\0')
+ {
+ switch (m[c].type)
+ {
+ case ISFUNC:
+ if (m[c].value.function)
+ return; /* There is a function here already. */
+
+ if (*s != '\0')
+ {
+ m[c].type = ISKMAP;
+ m[c].value.keymap = keymap_make_keymap ();
+ }
+ break;
+
+ case ISKMAP:
+ if (*s == '\0')
+ return; /* The key sequence we were asked to bind is an initial
+ subsequence of an already-bound sequence. */
+ break;
+ }
+ if (*s != '\0')
+ {
+ m = m[c].value.keymap;
+ }
+ else
+ {
+ add_function_keyseq (keyentry->value.function, keyseq, map);
+ m[c] = *keyentry;
+ }
+ }
+
+ return;
+}
+
+
+/* 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 int default_emacs_like_info_keys[] =
+{
+ /* Favoured command bindings come first. We want help to
+ report q, not C-x C-c, etc. */
+ 'H', NUL, A_info_get_help_window,
+ 'q', NUL, A_info_quit,
+ KEY_UP_ARROW, NUL, A_info_prev_line,
+ KEY_DOWN_ARROW, NUL, A_info_next_line,
+ KEY_PAGE_UP, NUL, A_info_scroll_backward,
+ KEY_PAGE_DOWN, NUL, A_info_scroll_forward,
+ KEY_HOME, NUL, A_info_beginning_of_node,
+ KEY_END, NUL, A_info_end_of_node,
+ '{', NUL, A_info_search_previous,
+ '}', NUL, A_info_search_next,
+ CONTROL('g'), NUL, A_info_abort_key,
+ RET, NUL, A_info_select_reference_this_line,
+
+ TAB, NUL, A_info_move_to_next_xref,
+ LFD, 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_scroll_backward,
+ 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,
+ SPC, NUL, A_info_scroll_forward,
+ ',', 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_display_file_info,
+ '>', NUL, A_info_last_node,
+ '?', NUL, A_info_search_backward,
+ '[', 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_info_help_node,
+ 'i', NUL, A_info_index_search,
+ 'I', NUL, A_info_virtual_index,
+ '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,
+ 'x', NUL, A_info_delete_window,
+ KEYMAP_META('0'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('1'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('2'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('3'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('4'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('5'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('6'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('7'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('8'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('9'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('-'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META(CONTROL('f')), NUL, A_info_show_footnotes,
+ KEYMAP_META(CONTROL('g')), NUL, A_info_abort_key,
+ KEYMAP_META(TAB), NUL, A_info_move_to_prev_xref,
+ KEYMAP_META(CONTROL('v')), NUL, A_info_scroll_other_window,
+ KEYMAP_META('<'), NUL, A_info_beginning_of_node,
+ KEYMAP_META('>'), NUL, A_info_end_of_node,
+ KEYMAP_META('b'), NUL, A_info_backward_word,
+ KEYMAP_META('f'), NUL, A_info_forward_word,
+ KEYMAP_META('r'), NUL, A_info_move_to_window_line,
+ KEYMAP_META('v'), NUL, A_info_scroll_backward_page_only,
+ KEYMAP_META('x'), NUL, A_info_execute_command,
+ KEYMAP_META('/'), NUL, A_info_tree_search,
+ KEYMAP_META('}'), NUL, A_info_tree_search_next,
+ KEYMAP_META('{'), NUL, A_info_tree_search_previous,
+
+ 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'), 'f', NUL, A_info_all_files,
+ 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,
+
+ KEY_RIGHT_ARROW, NUL, A_info_forward_char,
+ KEY_LEFT_ARROW, NUL, A_info_backward_char,
+ KEY_DELETE, NUL, A_info_scroll_backward,
+
+ ESC, KEY_PAGE_UP, NUL, A_info_scroll_other_window_backward,
+ ESC, KEY_PAGE_DOWN, NUL, A_info_scroll_other_window,
+ ESC, KEY_UP_ARROW, NUL, A_info_prev_line,
+ ESC, KEY_DOWN_ARROW, NUL, A_info_next_line,
+ ESC, KEY_RIGHT_ARROW, NUL, A_info_forward_word,
+ ESC, KEY_LEFT_ARROW, NUL, A_info_backward_word,
+ KEY_BACK_TAB, NUL, A_info_move_to_prev_xref,
+
+};
+
+
+static int default_emacs_like_ea_keys[] =
+{
+ KEYMAP_META('0'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('1'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('2'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('3'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('4'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('5'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('6'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('7'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('8'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('9'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('-'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META(CONTROL('g')), NUL, A_ea_abort,
+ KEYMAP_META(CONTROL('v')), NUL, A_ea_scroll_completions_window,
+ KEYMAP_META('b'), NUL, A_ea_backward_word,
+ KEYMAP_META('d'), NUL, A_ea_kill_word,
+ KEYMAP_META('f'), NUL, A_ea_forward_word,
+ KEYMAP_META('y'), NUL, A_ea_yank_pop,
+ KEYMAP_META('?'), NUL, A_ea_possible_completions,
+ KEYMAP_META(TAB), NUL, A_ea_tab_insert,
+ KEYMAP_META(KEY_DELETE), 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,
+ ESC, NUL, A_ea_abort,
+ CONTROL('h'), NUL, A_ea_rubout,
+ CONTROL('k'), 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,
+ TAB, NUL, A_ea_complete,
+#ifdef __MSDOS__
+ /* PC users will lynch me if I don't give them their usual DEL
+ effect... */
+ KEY_DELETE, NUL, A_ea_delete,
+#else
+ KEY_DELETE, NUL, A_ea_rubout,
+#endif
+ CONTROL('x'), KEY_DELETE, NUL, A_ea_backward_kill_line,
+
+ KEY_RIGHT_ARROW, NUL, A_ea_forward,
+ KEY_LEFT_ARROW, NUL, A_ea_backward,
+ KEY_HOME, NUL, A_ea_beg_of_line,
+ KEY_END, NUL, A_ea_end_of_line,
+};
+
+
+static int default_vi_like_info_keys[] =
+{
+ /* 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,
+ '{', NUL, A_info_search_previous,
+ '}', NUL, A_info_search_next,
+ KEY_UP_ARROW, NUL, A_info_up_line,
+ KEY_DOWN_ARROW, NUL, A_info_down_line,
+
+ '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('c'), NUL, A_info_abort_key,
+ 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('g'), NUL, A_info_display_file_info,
+ 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,
+ KEYMAP_META('0'), NUL, A_info_last_menu_item,
+ KEYMAP_META('1'), NUL, A_info_menu_digit,
+ KEYMAP_META('2'), NUL, A_info_menu_digit,
+ KEYMAP_META('3'), NUL, A_info_menu_digit,
+ KEYMAP_META('4'), NUL, A_info_menu_digit,
+ KEYMAP_META('5'), NUL, A_info_menu_digit,
+ KEYMAP_META('6'), NUL, A_info_menu_digit,
+ KEYMAP_META('7'), NUL, A_info_menu_digit,
+ KEYMAP_META('8'), NUL, A_info_menu_digit,
+ KEYMAP_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_page_only,
+ '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,
+ ':', 'a', NUL, A_info_all_files,
+ '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,
+ KEYMAP_META(CONTROL('f')), NUL, A_info_show_footnotes,
+ KEYMAP_META(CONTROL('g')), NUL, A_info_abort_key,
+ KEYMAP_META(TAB), NUL, A_info_move_to_prev_xref,
+ KEYMAP_META(SPC), NUL, A_info_scroll_forward_page_only,
+ KEYMAP_META(CONTROL('v')), NUL, A_info_scroll_other_window,
+ KEYMAP_META('<'), NUL, A_info_beginning_of_node,
+ KEYMAP_META('>'), NUL, A_info_end_of_node,
+ KEYMAP_META('/'), NUL, A_info_search,
+ KEYMAP_META('?'), NUL, A_info_search_backward,
+ KEYMAP_META('b'), NUL, A_info_beginning_of_node,
+ KEYMAP_META('d'), NUL, A_info_dir_node,
+ KEYMAP_META('e'), NUL, A_info_end_of_node,
+ KEYMAP_META('f'), NUL, A_info_xref_item,
+ KEYMAP_META('g'), NUL, A_info_select_reference_this_line,
+ KEYMAP_META('h'), NUL, A_info_get_info_help_node,
+ KEYMAP_META('I'), NUL, A_info_virtual_index,
+ KEYMAP_META('m'), NUL, A_info_menu_item,
+ KEYMAP_META('n'), NUL, A_info_search,
+ KEYMAP_META('N'), NUL, A_info_search_backward,
+ KEYMAP_META('r'), NUL, A_isearch_backward,
+ KEYMAP_META('s'), NUL, A_isearch_forward,
+ KEYMAP_META('t'), NUL, A_info_top_node,
+ KEYMAP_META('v'), NUL, A_info_scroll_backward_page_only,
+ KEYMAP_META('x'), NUL, A_info_execute_command,
+ 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'), '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,
+
+ KEY_PAGE_UP, NUL, A_info_scroll_backward,
+ KEY_PAGE_DOWN, NUL, A_info_scroll_forward,
+ KEY_DELETE, NUL, A_info_scroll_backward,
+ KEY_RIGHT_ARROW, NUL, A_info_scroll_forward_page_only,
+ KEY_LEFT_ARROW, NUL, A_info_scroll_backward_page_only,
+ KEY_HOME, NUL, A_info_beginning_of_node,
+ KEY_END, NUL, A_info_end_of_node,
+ ESC, KEY_PAGE_DOWN, NUL, A_info_scroll_other_window,
+ ESC, KEY_PAGE_UP, NUL, A_info_scroll_other_window_backward,
+ ESC, KEY_DELETE, NUL, A_info_scroll_other_window_backward,
+ ESC, KEY_UP_ARROW, NUL, A_info_prev_node,
+ ESC, KEY_DOWN_ARROW, NUL, A_info_next_node,
+ ESC, KEY_RIGHT_ARROW, NUL, A_info_xref_item,
+ ESC, KEY_LEFT_ARROW, NUL, A_info_beginning_of_node,
+ CONTROL('x'), KEY_DELETE, NUL, A_ea_backward_kill_line,
+
+};
+
+
+static int default_vi_like_ea_keys[] =
+{
+ KEYMAP_META('1'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('2'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('3'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('4'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('5'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('6'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('7'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('8'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('9'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META('-'), NUL, A_info_add_digit_to_numeric_arg,
+ KEYMAP_META(CONTROL('g')), NUL, A_ea_abort,
+ KEYMAP_META(CONTROL('h')), NUL, A_ea_backward_kill_word,
+ KEYMAP_META(CONTROL('v')), NUL, A_ea_scroll_completions_window,
+ KEYMAP_META('0'), NUL, A_ea_beg_of_line,
+ KEYMAP_META('$'), NUL, A_ea_end_of_line,
+ KEYMAP_META('b'), NUL, A_ea_backward_word,
+ KEYMAP_META('d'), NUL, A_ea_kill_word,
+ KEYMAP_META('f'), NUL, A_ea_forward_word,
+ KEYMAP_META('h'), NUL, A_ea_backward,
+ KEYMAP_META('l'), NUL, A_ea_forward,
+ KEYMAP_META('w'), NUL, A_ea_forward_word,
+ KEYMAP_META('x'), NUL, A_ea_delete,
+ KEYMAP_META('X'), NUL, A_ea_kill_word,
+ KEYMAP_META('y'), NUL, A_ea_yank_pop,
+ KEYMAP_META('?'), NUL, A_ea_possible_completions,
+ KEYMAP_META(TAB), NUL, A_ea_tab_insert,
+ KEYMAP_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,
+ ESC, NUL, A_ea_abort,
+ CONTROL('h'), NUL, A_ea_rubout,
+ CONTROL('k'), 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,
+ TAB, NUL, A_ea_complete,
+
+ KEY_RIGHT_ARROW, NUL, A_ea_forward,
+ KEY_LEFT_ARROW, NUL, A_ea_backward,
+ KEY_HOME, NUL, A_ea_beg_of_line,
+ KEY_END, NUL, A_ea_end_of_line,
+#ifdef __MSDOS__
+ KEY_DELETE, NUL, A_ea_delete,
+#else
+ KEY_DELETE, NUL, A_ea_rubout,
+#endif
+ CONTROL('x'), KEY_DELETE, NUL,A_ea_backward_kill_line,
+};
+
+
+/* Whether to suppress the default key bindings. */
+static int sup_info, sup_ea;
+
+/* Fetch the contents of the init file at INIT_FILE, or the standard
+ infokey file "$HOME/.infokey". Return non-zero if an init file was
+ loaded and read. */
+static int
+fetch_user_maps (char *init_file)
+{
+ char *filename = NULL;
+ char *homedir;
+ FILE *inf;
+
+ /* In infokey.c */
+ int compile (FILE *fp, const char *filename, int *, int *);
+
+ /* Find and open file. */
+ if (init_file)
+ filename = xstrdup (init_file);
+ else if ((homedir = getenv ("HOME")) != NULL
+#ifdef __MINGW32__
+ || (homedir = getenv ("USERPROFILE")) != NULL
+#endif
+ )
+ {
+ filename = xmalloc (strlen (homedir) + 2 + strlen (INFOKEY_FILE));
+ strcpy (filename, homedir);
+ strcat (filename, "/");
+ strcat (filename, INFOKEY_FILE);
+ }
+#if defined(__MSDOS__) || defined(__MINGW32__)
+ /* Poor baby, she doesn't have a HOME... */
+ else
+ filename = xstrdup (INFOKEY_FILE); /* try current directory */
+#endif
+ inf = fopen (filename, "r");
+ if (!inf)
+ {
+ free (filename);
+ if (init_file)
+ info_error (_("could not open init file %s"), init_file);
+ return 0;
+ }
+
+ compile (inf, filename, &sup_info, &sup_ea);
+
+ free (filename);
+ return 1;
+}
+
+
+/* Set key bindings in MAP from TABLE, which is of length LEN. */
+static void
+section_to_keymaps (Keymap map, int *table, unsigned int len)
+{
+ int k;
+ Keymap esc_map;
+
+ int *p;
+ int *seq;
+ enum { getseq, gotseq, getaction } state = getseq;
+
+ for (p = table; (unsigned int) (p - table) < len; p++)
+ {
+ switch (state)
+ {
+ case getseq:
+ if (*p)
+ {
+ seq = p;
+ state = gotseq;
+ }
+ break;
+
+ case gotseq:
+ if (!*p)
+ state = getaction;
+ break;
+
+ case getaction:
+ {
+ unsigned int action = *p;
+ KEYMAP_ENTRY ke;
+
+ state = getseq;
+
+ ke.type = ISFUNC;
+ ke.value.function = action < A_NCOMMANDS ?
+ &function_doc_array[action]
+ : NULL;
+ keymap_bind_keyseq (map, seq, &ke);
+ }
+ break;
+ }
+ }
+ if (state != getseq)
+ abort ();
+
+ /* Go through map and bind ESC x to the same function as M-x if it is not
+ bound already. */
+ if (!map[ESC].value.function)
+ {
+ map[ESC].type = ISKMAP;
+ map[ESC].value.keymap = keymap_make_keymap ();
+ }
+
+ if (map[ESC].type != ISKMAP)
+ return; /* ESC is bound to a command. */
+
+ esc_map = map[ESC].value.keymap;
+ for (k = 1; k < KEYMAP_META_BASE; k++)
+ {
+ if (map[k + KEYMAP_META_BASE].type == ISFUNC
+ && esc_map[k].value.function == 0)
+ {
+ esc_map[k].type = ISFUNC;
+ esc_map[k].value.function = map[k + KEYMAP_META_BASE].value.function;
+ }
+ }
+ return;
+}
+
+/* Read key bindings and variable settings from INIT_FILE. If INIT_FILE
+ is null, look for the init file in the default location. */
+void
+read_init_file (char *init_file)
+{
+ int *info_keys, *ea_keys; /* Pointers to keymap tables. */
+ long info_keys_len, ea_keys_len; /* Sizes of keymap tables. */
+
+ int i;
+
+ if (!info_keymap)
+ {
+ info_keymap = keymap_make_keymap ();
+ echo_area_keymap = keymap_make_keymap ();
+ }
+
+ if (!vi_keys_p)
+ {
+ info_keys = default_emacs_like_info_keys;
+ info_keys_len = sizeof (default_emacs_like_info_keys)/sizeof (int);
+ ea_keys = default_emacs_like_ea_keys;
+ ea_keys_len = sizeof (default_emacs_like_ea_keys)/sizeof (int);
+ }
+ else
+ {
+ info_keys = default_vi_like_info_keys;
+ info_keys_len = sizeof (default_vi_like_info_keys)/sizeof(int);
+ ea_keys = default_vi_like_ea_keys;
+ ea_keys_len = sizeof (default_vi_like_ea_keys)/sizeof(int);
+ }
+
+ /* Get user-defined keys and variables. */
+ if (fetch_user_maps (init_file))
+ {
+ if (sup_info)
+ info_keys = 0; /* Suppress default bindings. */
+ if (sup_ea)
+ ea_keys = 0;
+ }
+
+ /* Apply the default bindings, unless the user says to suppress
+ them. */
+ if (info_keys)
+ section_to_keymaps (info_keymap, info_keys, info_keys_len);
+ if (ea_keys)
+ section_to_keymaps (echo_area_keymap, ea_keys, ea_keys_len);
+
+ for (i = 'A'; i < ('Z' + 1); i++)
+ {
+ if (!info_keymap[i].value.function)
+ {
+ info_keymap[i].type = ISFUNC;
+ info_keymap[i].value.function = InfoCmd (info_do_lowercase_version);
+ }
+
+ if (!info_keymap[KEYMAP_META(i)].value.function)
+ {
+ info_keymap[KEYMAP_META(i)].type = ISFUNC;
+ info_keymap[KEYMAP_META(i)].value.function
+ = InfoCmd (info_do_lowercase_version);
+ }
+ }
+}
+
+/* vim: set sw=2 cino={1s>2sn-s^-se-s: */
diff --git a/macros/texinfo/texinfo/info/infomap.h b/macros/texinfo/texinfo/info/infomap.h
new file mode 100644
index 0000000000..b144023718
--- /dev/null
+++ b/macros/texinfo/texinfo/info/infomap.h
@@ -0,0 +1,127 @@
+/* infomap.h -- description of a keymap in Info and related functions.
+
+ Copyright 1993-2019 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/>.
+
+ Originaly written by Brian Fox. */
+
+#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))
+
+/* Structure used to map sequences of bytes to recognized keys. */
+typedef struct bytemap_entry
+{
+ char type;
+ int key;
+ struct bytemap_entry *next;
+} BYTEMAP_ENTRY;
+
+#define BYTEMAP_NONE 0
+#define BYTEMAP_KEY 1
+#define BYTEMAP_MAP 2
+#define BYTEMAP_ESC 3
+
+extern BYTEMAP_ENTRY *byte_seq_to_key;
+
+typedef struct keymap_entry
+{
+ char type;
+ union
+ {
+ InfoCommand *function; /* The address of a function. */
+ struct keymap_entry *keymap; /* The address of another Keymap */
+ } value;
+} KEYMAP_ENTRY;
+
+/* The values that TYPE can have in a keymap entry. */
+#define ISFUNC 0
+#define ISKMAP 1
+
+/* We use Keymap for a pointer to a block of KEYMAP_SIZE KEYMAP_ENTRY's. */
+typedef KEYMAP_ENTRY *Keymap;
+
+extern Keymap info_keymap;
+extern Keymap echo_area_keymap;
+
+#define KEY_RIGHT_ARROW 256
+#define KEY_LEFT_ARROW 257
+#define KEY_UP_ARROW 258
+#define KEY_DOWN_ARROW 259
+#define KEY_PAGE_UP 260
+#define KEY_PAGE_DOWN 261
+#define KEY_HOME 262
+#define KEY_END 263
+#define KEY_DELETE 264
+#define KEY_INSERT 265
+#define KEY_CTL_LEFT_ARROW 266
+#define KEY_CTL_RIGHT_ARROW 267
+#define KEY_CTL_DELETE 268
+#define KEY_BACK_TAB 269
+#define KEY_MOUSE 270
+
+/* Add this to get the offset of the key binding with the meta key. */
+#define KEYMAP_META_BASE 271
+
+/* Number of entries in a Keymap: 256 entries for plain byte values plus
+ mappings for special keys. The bindings for the key chords with meta
+ follow. */
+#define KEYMAP_SIZE (KEYMAP_META_BASE * 2)
+
+#define KEYMAP_META(k) ((k) < KEYMAP_META_BASE ? (k) + KEYMAP_META_BASE : (k))
+
+/* Default "infokey file", where user defs are kept and read by
+ Info. MS-DOS doesn't allow leading dots in file names. */
+#ifdef __MSDOS__
+#define INFOKEY_FILE "_infokey"
+#else
+#define INFOKEY_FILE ".infokey"
+#endif
+
+#define A_MAX_COMMAND 120
+#define A_INVALID 121
+
+#define CONTROL(c) ((c) & 0x1f)
+
+/* 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);
+
+/* Read init file and initialize the info keymaps. */
+extern void read_init_file (char *init_file);
+
+#endif /* not INFOMAP_H */
diff --git a/macros/texinfo/texinfo/info/infopath.c b/macros/texinfo/texinfo/info/infopath.c
new file mode 100644
index 0000000000..18806d5404
--- /dev/null
+++ b/macros/texinfo/texinfo/info/infopath.c
@@ -0,0 +1,263 @@
+/* infopath.c -- INFOPATH handling.
+
+ Copyright 1993-2019 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/>.
+*/
+
+#include "info.h"
+#include "info-utils.h"
+#include "session.h"
+#include "filesys.h"
+
+typedef struct {
+ char *name; /* Path to directory to be searched. */
+ dev_t device; /* Storage device this directory is on. */
+ ino_t inode; /* Inode number, used to detect duplicates. */
+} INFO_DIR;
+
+INFO_DIR **infodirs = 0;
+size_t infodirs_index = 0;
+size_t infodirs_slots = 0;
+
+/* Exclude default file search directories. */
+int infopath_no_defaults_p;
+
+static void infopath_add_dir (char *path);
+char *extract_colon_unit (char *string, int *idx);
+
+void
+infopath_init ()
+{
+ /* Initialize INFOPATH.
+ Highest priority is the environment variable, if set
+ Then comes the user's INFODIR from the Makefile.
+ The hardwired default settings (filesys.h) are the lowest priority. */
+ char *path_from_env = getenv ("INFOPATH");
+
+ if (path_from_env)
+ {
+ infopath_add (path_from_env);
+ }
+
+ if (!infopath_no_defaults_p)
+ {
+#ifdef INFODIR /* $infodir, set by configure script in Makefile */
+ infopath_add (INFODIR);
+#ifdef INFODIR2 /* $datadir/info, which could be different. */
+ if (!STREQ (INFODIR, INFODIR2))
+ infopath_add (INFODIR2);
+#endif /* INFODIR2 */
+#endif /* INFODIR */
+ }
+
+ if (!path_from_env)
+ {
+ infopath_add (DEFAULT_INFOPATH);
+ }
+ else
+ {
+ /* Only insert default path if there is a trailing : on INFOPATH. */
+
+ unsigned len = strlen (path_from_env);
+ if (len && path_from_env[len - 1] == PATH_SEP[0])
+ {
+ path_from_env[len - 1] = 0;
+ infopath_add (DEFAULT_INFOPATH);
+ }
+ }
+}
+
+/* Return value to be freed by caller. */
+char *
+infopath_string ()
+{
+ struct text_buffer path;
+ int dir_idx;
+ char *this_dir;
+
+ this_dir = infopath_first (&dir_idx);
+ if (!this_dir)
+ return "";
+
+ text_buffer_init (&path);
+
+ while (1)
+ {
+ text_buffer_printf (&path, "%s", this_dir);
+ this_dir = infopath_next (&dir_idx);
+ if (!this_dir)
+ break;
+ text_buffer_add_char (&path, ':');
+ }
+ return text_buffer_base (&path);
+}
+
+/* For each path element PREFIX/DIR in PATH substitute either
+ PREFIX/share/info or PREFIX/info if that directory exists. */
+static void
+build_infopath_from_path (void)
+{
+ char *path_from_env, *temp_dirname;
+ int dirname_index = 0;
+ struct stat finfo;
+
+ path_from_env = getenv ("PATH");
+
+ while ((temp_dirname = extract_colon_unit (path_from_env, &dirname_index)))
+ {
+ unsigned int i, dir = 0;
+
+ /* Find end of DIRNAME/ (but ignore "/") */
+ for (i = 0; temp_dirname[i]; i++)
+ if (i && IS_SLASH (temp_dirname[i]))
+ dir = i + 1;
+
+ /* Discard path elements ending with "/", "/.", or "/.." */
+ if (!temp_dirname[dir] || STREQ (temp_dirname + dir, ".") || STREQ (temp_dirname + dir, "."))
+ dir = 0;
+
+ if (dir)
+ {
+ temp_dirname = xrealloc (temp_dirname, dir + strlen ("share/info") +1);
+
+ /* first try DIRNAME/share/info */
+ strcpy (temp_dirname + dir, "share/info");
+ if (stat (temp_dirname, &finfo) != 0 || !S_ISDIR (finfo.st_mode))
+ {
+ /* then try DIRNAME/info */
+ strcpy (temp_dirname + dir, "info");
+ if (stat (temp_dirname, &finfo) != 0 || !S_ISDIR (finfo.st_mode))
+ dir = 0;
+ }
+ }
+
+ if (dir)
+ infopath_add_dir (temp_dirname);
+ else
+ free (temp_dirname);
+ }
+}
+
+/* Add directory at PATH to Info search path. A reference to PATH is retained,
+ or PATH is freed. */
+static void
+infopath_add_dir (char *path)
+{
+ struct stat dirinfo;
+ INFO_DIR *entry;
+ int i;
+
+ if (stat (path, &dirinfo) == -1)
+ {
+ debug (2, ("inaccessible directory %s not added to INFOPATH", path));
+ free (path);
+ return; /* Doesn't exist, or not accessible. */
+ }
+
+ for (i = 0; i < infodirs_index; i++)
+ {
+ if ( dirinfo.st_ino == infodirs[i]->inode
+ && dirinfo.st_dev == infodirs[i]->device
+ /* On MS-Windows, `stat' returns zero as the inode, so we
+ use file-name comparison instead for that OS. */
+ && (infodirs[i]->inode != 0 || fncmp (path, infodirs[i]->name) == 0))
+ {
+ debug (2, ("duplicate directory %s not added to INFOPATH", path));
+ free (path);
+ return; /* We have it already. */
+ }
+ }
+
+ debug (2, ("adding %s to INFOPATH", path));
+ entry = xmalloc (sizeof (INFO_DIR));
+ entry->name = path;
+ entry->inode = dirinfo.st_ino;
+ entry->device = dirinfo.st_dev;
+ add_pointer_to_array (entry, infodirs_index, infodirs, infodirs_slots, 8);
+}
+
+/* Add PATH to the list of paths found in INFOPATH. PATH should be allocated
+ on the heap and not referenced by the caller after calling this function.
+ If PATH is "PATH", add a sequence of path elements derived from the
+ environment variable PATH. */
+void
+infopath_add (char *path)
+{
+ int idx = 0;
+ char *dirname;
+
+ while (dirname = extract_colon_unit (path, &idx))
+ {
+ if (!strcmp ("PATH", dirname))
+ {
+ free (dirname);
+ build_infopath_from_path ();
+ }
+ else
+ infopath_add_dir (dirname);
+ }
+}
+
+/* Used to iterate over INFOPATH. Return value should not be freed
+ by caller. */
+char *
+infopath_next (int *idx)
+{
+ INFO_DIR *entry;
+
+ if (!infodirs)
+ return 0;
+ entry = infodirs[(*idx)++];
+ if (!entry)
+ return 0;
+ return entry->name;
+}
+
+char *
+infopath_first (int *idx)
+{
+ *idx = 0;
+ return infopath_next (idx);
+}
+
+/* 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;
+ }
+}
diff --git a/macros/texinfo/texinfo/info/m-x.c b/macros/texinfo/texinfo/info/m-x.c
new file mode 100644
index 0000000000..64984bbb34
--- /dev/null
+++ b/macros/texinfo/texinfo/info/m-x.c
@@ -0,0 +1,198 @@
+/* m-x.c -- Meta-x minibuffer reader.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+#include "display.h"
+#include "session.h"
+#include "echo-area.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 (char *prompt, WINDOW *window)
+{
+ register int i;
+ char *line;
+ REFERENCE **array = NULL;
+ size_t 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);
+ }
+
+ line = info_read_completing_in_echo_area (prompt, array);
+ info_free_references (array);
+
+ 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);
+ 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;
+
+ 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)
+ asprintf (&prompt, "%d %s ", count, keys);
+ else
+ asprintf (&prompt, "%s ", keys);
+
+ /* Ask the completer to read a reference for us. */
+ line = read_function_name (prompt, window);
+ free (prompt);
+
+ /* User aborted? */
+ if (!line)
+ {
+ info_abort_key (active_window, count);
+ 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"));
+ return;
+ }
+
+ command = named_function (line);
+ free (line);
+
+ if (command && command->func)
+ (*command->func) (active_window, count, 0);
+ }
+}
+
+/* Okay, now that we have M-x, let the user set the screen height. */
+DECLARE_INFO_COMMAND (set_screen_height,
+ _("Set the height of the displayed window"))
+{
+ int new_height, 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 (prompt);
+
+ /* If the user aborted, do that now. */
+ if (!line)
+ {
+ info_abort_key (active_window, count);
+ return;
+ }
+
+ /* Find out what the new height is supposed to be. */
+ if (*line)
+ new_height = atoi (line);
+
+ 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 ();
+ }
+ else
+ {
+ display_initialize_display (screenwidth, screenheight);
+ window_new_screen_size (screenwidth, screenheight);
+ }
+}
diff --git a/macros/texinfo/texinfo/info/makedoc.c b/macros/texinfo/texinfo/info/makedoc.c
new file mode 100644
index 0000000000..5842cd6986
--- /dev/null
+++ b/macros/texinfo/texinfo/info/makedoc.c
@@ -0,0 +1,494 @@
+/* makedoc.c -- make doc.c and funs.h from input files.
+
+ Copyright 1993-2019 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. */
+
+/* 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 "doc.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 *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,",
+ " a string which is the user-visible name of the function,",
+ " and a string which documents its purpose. */",
+ "",
+ "#include \"info.h\"",
+ "#include \"window.h\"",
+ "#include \"funs.h\"",
+ "",
+ "FUNCTION_DOC function_doc_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. */
+ size_t entries_index;
+ size_t entries_slots;
+} EMACS_TAG_BLOCK;
+
+EMACS_TAG_BLOCK **emacs_tags = NULL;
+size_t emacs_tags_index = 0;
+size_t emacs_tags_slots = 0;
+
+#define DECLARATION_STRING "\nDECLARE_INFO_COMMAND"
+
+static void process_one_file (char *filename, FILE *doc_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;
+
+#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;
+ }
+
+ /* 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");
+ doc_stream = must_fopen (doc_filename, "w");
+
+ fprintf (funs_stream,
+ "/* %s -- Generated declarations for Info commands. */\n\n"
+ "#include \"info.h\"\n"
+ "#include \"window.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 = 1; i < argc; i++)
+ fprintf (doc_stream, "\t%s\n", argv[i]);
+
+ fprintf (doc_stream, "\n");
+ for (i = 0; doc_header_1[i]; i++)
+ fprintf (doc_stream, "%s\n", doc_header_1[i]);
+
+ 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 (funs_stream, "\n/* Functions declared in \"%s\". */\n",
+ curfile);
+
+ process_one_file (curfile, doc_stream, funs_stream);
+ }
+
+ fprintf (doc_stream, " { NULL, NULL, NULL, NULL }\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 (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)
+{
+ size_t 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++)
+ {
+ size_t 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);
+}
+
+/* Read the file represented by FILENAME into core, and search it for Info
+ function declarations. Output the declarations in various forms to the
+ DOC_STREAM and FUNS_STREAM. */
+static void
+process_one_file (char *filename, FILE *doc_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;
+ char *func_name;
+
+ 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);
+ }
+
+ /* 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] = '-';
+ }
+
+ /* 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';
+
+ fprintf (doc_stream,
+ " { (VFunction *)%s, \"%s\", (FUNCTION_KEYSEQ *)0, %s },\n",
+ func, func_name, doc);
+
+ free (func_name);
+
+ fprintf (funs_stream, "#define A_%s %u\n", func, next_func_key());
+ fprintf (funs_stream,
+ "extern void %s (WINDOW *window, int count);\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);
+ else
+ {
+ free (block->filename);
+ free (block);
+ }
+}
+
+static void
+fatal_file_error (char *filename)
+{
+ fprintf (stderr, _("Couldn't manipulate the file %s.\n"), filename);
+ exit (EXIT_FAILURE);
+}
+
+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/macros/texinfo/texinfo/info/man.c b/macros/texinfo/texinfo/info/man.c
new file mode 100644
index 0000000000..8935f27536
--- /dev/null
+++ b/macros/texinfo/texinfo/info/man.c
@@ -0,0 +1,602 @@
+/* man.c: How to read and format man files.
+
+ Copyright 1995-2019 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 "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 <stdio.h>
+
+#include "tilde.h"
+#include "nodes.h"
+#include "man.h"
+#include "variables.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 REFERENCE **xrefs_of_manpage (NODE *node);
+static char *read_from_fd (int fd);
+static char *get_manpage_contents (char *pagename);
+
+/* We store the contents of retrieved man pages in here. */
+static NODE **manpage_nodes = 0;
+size_t manpage_node_index = 0;
+size_t manpage_node_slots = 0;
+
+NODE *
+get_manpage_node (char *pagename)
+{
+ NODE *node = 0, **n, *node2 = 0;
+ char *page;
+
+ if (manpage_node_index > 0)
+ for (n = manpage_nodes; (node = *n); n++)
+ if (!strcmp (node->nodename, pagename))
+ break;
+
+ /* Node was not found, so we have to create it. */
+ if (!node)
+ {
+ node = info_create_node ();
+ node->fullpath = MANPAGE_FILE_BUFFER_NAME;
+ node->nodename = xstrdup (pagename);
+ node->flags |= N_HasTagsTable | N_IsManPage;
+
+ /* Save this node. */
+ add_pointer_to_array (node, manpage_node_index,
+ manpage_nodes,
+ manpage_node_slots, 100);
+ }
+
+ /* Node wasn't found, or its contents were freed since last time. */
+ if (!node->contents)
+ {
+ int plen;
+
+ page = get_manpage_contents (pagename);
+ if (!page)
+ return 0;
+ plen = strlen (page);
+
+ node->contents = page;
+ node->nodelen = plen;
+
+ node->body_start = 0;
+ node->references = xrefs_of_manpage (node);
+ node->up = "(dir)";
+ }
+
+ node2 = xmalloc (sizeof (NODE));
+ *node2 = *node;
+ return node2;
+}
+
+/* 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';
+ }
+}
+
+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);
+ size_t 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 char *get_manpage_from_formatter (char *formatter_args[]);
+
+static char *
+get_manpage_contents (char *pagename)
+{
+ static char *formatter_args[4] = { NULL };
+ char *formatted_page;
+
+ 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[1] = manpage_section;
+ else
+ formatter_args[1] = "-a";
+
+ formatter_args[2] = manpage_pagename;
+ formatter_args[3] = NULL;
+
+ formatted_page = get_manpage_from_formatter (formatter_args);
+
+ /* If there was a section and the page wasn't found, try again
+ without the section (e.g. "man 3X curses" versus "man -a curses"). */
+ if (!formatted_page && manpage_section)
+ {
+ formatter_args[1] = "-a";
+ formatted_page = get_manpage_from_formatter (formatter_args);
+ }
+
+ return formatted_page;
+}
+
+static char *
+get_manpage_from_formatter (char *formatter_args[])
+{
+ char *formatted_page = NULL;
+ int pipes[2];
+ pid_t child;
+ int formatter_status = 0;
+
+ /* 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);
+
+ 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]);
+ wait (&formatter_status); /* Wait for child process to exit. */
+ }
+ 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]);
+ exit (EXIT_SUCCESS);
+ }
+#else /* !PIPE_USE_FORK */
+ /* Cannot fork/exec, but can popen/pclose. */
+ {
+ FILE *fpipe;
+ char *cmdline;
+ size_t cmdlen = 0;
+ int save_stderr = dup (fileno (stderr));
+ int fd_err = open (NULL_DEVICE, O_WRONLY, 0666);
+ int i;
+
+ for (i = 0; formatter_args[i]; i++)
+ cmdlen += strlen (formatter_args[i]);
+ /* Add-ons: 2 blanks, 2 quotes for the formatter program, 1
+ terminating null character. */
+ cmdlen += 2 + 2 + 1;
+ cmdline = xmalloc (cmdlen);
+
+ if (fd_err > 2)
+ dup2 (fd_err, fileno (stderr)); /* Don't print errors. */
+ sprintf (cmdline, "\"%s\" %s %s",
+ formatter_args[0], formatter_args[1], formatter_args[2]);
+ 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));
+ formatter_status = pclose (fpipe);
+ }
+#endif /* !PIPE_USE_FORK */
+
+ if (!formatted_page)
+ return 0;
+
+ /* We could check the exit status of "man -a" to see if it successfully
+ output a man page However:
+ * It is possible for "man -a" to output a man page and still to exit with
+ a non-zero status. This was found to happen when duplicate man pages
+ were found.
+ * "man" was found to exit with a zero status on Solaris 10 even when
+ it found nothing.
+ Hence, treat it as a success if more than three lines were output. (A
+ small amount of output could be error messages that were sent to standard
+ output.) */
+ {
+ int i;
+ char *p;
+ p = formatted_page;
+ for (i = 0; i < 3; i++)
+ {
+ p = strchr (p, '\n');
+ if (!p)
+ {
+ free (formatted_page);
+ return NULL;
+ }
+ p++;
+ }
+ }
+
+ /* If we have the page, then clean it up. */
+ clean_manpage (formatted_page);
+
+ return formatted_page;
+}
+
+/* Return pointer to bytes read from file descriptor FD. Return value to be
+ freed by caller. */
+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 REFERENCE **
+xrefs_of_manpage (NODE *node)
+{
+ SEARCH_BINDING s;
+
+ REFERENCE **refs = NULL;
+ size_t refs_index = 0;
+ size_t refs_slots = 0;
+ long position;
+
+ /* Initialize reference list to have a single null entry. */
+ refs = calloc(1, sizeof (REFERENCE *));
+ refs_slots = 1;
+
+ s.buffer = node->contents;
+ s.start = 0;
+ s.flags = 0;
+ s.end = node->nodelen;
+
+ /* Exclude first line, which often looks like:
+CAT(1) User Commands CAT(1)
+ */
+ s.start = strcspn (node->contents, "\n");
+
+ /* Build a list of references. A reference is alphabetic characters
+ followed by non-whitespace text within parenthesis leading with a digit. */
+ while (search_forward ("(", &s, &position) == search_success)
+ {
+ register int name, name_end;
+ int section, section_end;
+
+ name = position;
+ if (name == 0)
+ goto skip;
+ else
+ name--;
+
+ /* Go to the start of a sequence of non-whitespace characters,
+ checking the characters are those that should appear in a man
+ page name. */
+ for (; name > 0; name--)
+ if (whitespace_or_newline (s.buffer[name])
+ || (!isalnum (s.buffer[name])
+ && s.buffer[name] != '_'
+ && s.buffer[name] != '.'
+ && s.buffer[name] != '-'
+ && s.buffer[name] != '\033'
+ && s.buffer[name] != '['))
+ break;
+
+ /* Check if reached start of buffer. */
+ if (name == 0)
+ goto skip;
+
+ /* Check for invalid sequence in name. */
+ if (!whitespace_or_newline (s.buffer[name]))
+ goto skip;
+
+ name++;
+
+ if (name == position)
+ goto skip; /* Whitespace immediately before '('. */
+
+ /* 'name' is now at the start of a sequence of non-whitespace
+ characters. If we are on an ECMA-48 SGR escape sequence, skip
+ past it. */
+ if (s.buffer[name] == '\033' && s.buffer[name + 1] == '[')
+ {
+ name += 2;
+ name += strspn (s.buffer + name, "0123456789;");
+ if (s.buffer[name] == 'm')
+ name++;
+ else
+ goto skip;
+ }
+
+ /* Set name_end to the end of the name, but before any SGR sequence. */
+ for (name_end = name; name_end < position; name_end++)
+ if (!isalnum (s.buffer[name_end])
+ && s.buffer[name_end] != '_'
+ && s.buffer[name_end] != '.'
+ && s.buffer[name_end] != '-')
+ break;
+
+ section = position;
+ section_end = 0;
+
+ /* Look for one or two characters within the brackets, the
+ first of which must be a non-zero digit and the second a letter. */
+ if (!isdigit (s.buffer[section + 1])
+ || s.buffer[section + 1] == '0')
+ ;
+ else if (!s.buffer[section + 2])
+ ; /* end of buffer */
+ else if (s.buffer[section + 2] == ')')
+ section_end = section + 3;
+ else if (!isalpha(s.buffer[section + 2]))
+ ;
+ else if (s.buffer[section + 3] == ')')
+ section_end = section + 4;
+
+ if (section_end)
+ {
+ REFERENCE *entry;
+ int len = name_end - name + section_end - section;
+
+ entry = xmalloc (sizeof (REFERENCE));
+ entry->label = xcalloc (1, 1 + len);
+ strncpy (entry->label, s.buffer + name, name_end - name);
+ strncpy (entry->label + strlen (entry->label),
+ s.buffer + section,
+ section_end - section);
+
+ entry->filename = xstrdup (MANPAGE_FILE_BUFFER_NAME);
+ entry->nodename = xstrdup (entry->label);
+ entry->line_number = 0;
+ entry->start = name;
+ entry->end = section_end;
+ entry->type = REFERENCE_XREF;
+
+ add_pointer_to_array (entry, refs_index, refs, refs_slots, 10);
+ }
+
+skip:
+ s.start = position + 1;
+ }
+
+ return refs;
+}
diff --git a/macros/texinfo/texinfo/info/man.h b/macros/texinfo/texinfo/info/man.h
new file mode 100644
index 0000000000..cf0e57b363
--- /dev/null
+++ b/macros/texinfo/texinfo/info/man.h
@@ -0,0 +1,27 @@
+/* man.h: Defines and external function declarations for man.c.
+
+ Copyright 1993-2019 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. */
+
+#ifndef INFO_MAN_H
+#define INFO_MAN_H
+
+#define MANPAGE_FILE_BUFFER_NAME "*manpages*"
+
+extern NODE *get_manpage_node (char *pagename);
+
+#endif /* INFO_MAN_H */
diff --git a/macros/texinfo/texinfo/info/nodemenu.c b/macros/texinfo/texinfo/info/nodemenu.c
new file mode 100644
index 0000000000..a870c07642
--- /dev/null
+++ b/macros/texinfo/texinfo/info/nodemenu.c
@@ -0,0 +1,283 @@
+/* nodemenu.c -- produce a menu of all visited nodes.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+#include "session.h"
+#include "echo-area.h"
+#include "variables.h"
+
+static NODE *get_visited_nodes (void);
+
+/* Return a line describing the format of a node information line. */
+static const char *
+nodemenu_format_info (void)
+{
+ /* TRANSLATORS: The "\n* Menu:\n\n" part of this should not be translated, as
+ it is part of the Info syntax. */
+ return _("\n* Menu:\n\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;
+ char *containing_file;
+ static struct text_buffer line_buffer = { 0 };
+
+ if (!text_buffer_base (&line_buffer))
+ text_buffer_init (&line_buffer);
+ else
+ text_buffer_reset (&line_buffer);
+
+ if (node->subfile)
+ containing_file = node->subfile;
+ else
+ containing_file = node->fullpath;
+
+ if (!containing_file || !*containing_file)
+ text_buffer_printf (&line_buffer, "* %s::", node->nodename);
+ else
+ text_buffer_printf (&line_buffer, "* (%s)%s::",
+ filename_non_directory (node->fullpath),
+ node->nodename);
+
+ for (i = text_buffer_off (&line_buffer); i < 36; i++)
+ text_buffer_add_char (&line_buffer, ' ');
+
+ {
+ int lines = 1;
+
+ for (i = 0; i < node->nodelen; i++)
+ if (node->contents[i] == '\n')
+ lines++;
+
+ text_buffer_printf (&line_buffer, "%d", lines);
+ }
+
+ text_buffer_add_char (&line_buffer, ' ');
+ for (i = text_buffer_off (&line_buffer); i < 44; i++)
+ text_buffer_add_char (&line_buffer, ' ');
+ text_buffer_printf (&line_buffer, "%ld", node->nodelen);
+
+ if (containing_file)
+ {
+ for (i = text_buffer_off (&line_buffer); i < 51; i++)
+ text_buffer_add_char (&line_buffer, ' ');
+ text_buffer_printf (&line_buffer, containing_file);
+ }
+
+ return xstrdup (text_buffer_base (&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 newly allocated node. */
+static NODE *
+get_visited_nodes (void)
+{
+ register int i;
+ WINDOW *info_win;
+ NODE *node;
+ char **lines = NULL;
+ size_t lines_index = 0, lines_slots = 0;
+ struct text_buffer message;
+
+ for (info_win = windows; info_win; info_win = info_win->next)
+ {
+ for (i = 0; i < info_win->hist_index; i++)
+ {
+ NODE *history_node = info_win->hist[i]->node;
+
+ /* We skip mentioning "*Node Menu*" nodes. */
+ if (strcmp (history_node->nodename, nodemenu_nodename) == 0)
+ continue;
+
+ if (history_node)
+ {
+ char *line;
+
+ line = format_node_info (history_node);
+ add_pointer_to_array (line, lines_index, lines, lines_slots, 20);
+ }
+ }
+ }
+
+ /* 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;
+ }
+
+ text_buffer_init (&message);
+
+ text_buffer_printf (&message, "\n");
+ text_buffer_printf (&message,
+ "%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));
+
+ text_buffer_printf (&message, "%s\n", nodemenu_format_info ());
+
+ for (i = 0; (lines != NULL) && (i < lines_index); i++)
+ {
+ text_buffer_printf (&message, "%s\n", lines[i]);
+ free (lines[i]);
+ }
+
+ if (lines)
+ free (lines);
+
+ node = text_buffer_to_node (&message);
+ scan_node_contents (node, 0, 0);
+
+ 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;
+
+ /* 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)
+ new = window_make_window ();
+
+ /* 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 ();
+ name_internal_node (node, xstrdup (nodemenu_nodename));
+ node->flags |= N_WasRewritten;
+
+ info_set_node_of_window (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;
+
+ node = get_visited_nodes ();
+
+ line = info_read_completing_in_echo_area (_("Select visited node: "),
+ node->references);
+
+ window = active_window;
+
+ if (!line)
+ /* User aborts, just quit. */
+ info_abort_key (window, 0);
+ else if (*line)
+ {
+ REFERENCE *entry;
+
+ /* Find the selected label in the references. */
+ entry = info_get_menu_entry_by_label (node, line, 0);
+
+ if (!entry)
+ /* This shouldn't happen, because LINE was in the completion list
+ built from the list of references. */
+ info_error (_("The reference disappeared! (%s)."), line);
+ else
+ info_select_reference (window, entry);
+ }
+
+ free (line);
+ free (node);
+}
diff --git a/macros/texinfo/texinfo/info/nodes.c b/macros/texinfo/texinfo/info/nodes.c
new file mode 100644
index 0000000000..47228258f2
--- /dev/null
+++ b/macros/texinfo/texinfo/info/nodes.c
@@ -0,0 +1,1331 @@
+/* nodes.c -- how to get an Info file and node.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+
+#include "nodes.h"
+#include "search.h"
+#include "filesys.h"
+#include "info-utils.h"
+#include "tag.h"
+#include "man.h"
+#include "variables.h"
+
+
+/* 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 files. */
+FILE_BUFFER **info_loaded_files = NULL;
+
+/* Number of loaded files. */
+size_t info_loaded_files_index = 0;
+
+/* The number of slots currently allocated to LOADED_FILES. */
+size_t info_loaded_files_slots = 0;
+
+/* Functions for tag table creation and destruction. */
+
+static void build_tag_table (FILE_BUFFER *file_buffer);
+static void get_nodes_of_tags_table (FILE_BUFFER *file_buffer,
+ SEARCH_BINDING *buffer_binding);
+static void get_tags_of_indirect_tags_table (FILE_BUFFER *file_buffer,
+ SEARCH_BINDING *indirect_binding, SEARCH_BINDING *tags_binding);
+static void free_file_buffer_tags (FILE_BUFFER *file_buffer);
+static void free_info_tag (TAG *tag);
+
+/* 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;
+ long tags_table_begin, tags_table_end;
+
+ 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 = find_file_section (&binding, TAGS_TABLE_END_LABEL);
+ if (position == -1)
+ goto no_tags_table;
+
+ /* If there is a tag table, find the start of it, and grovel over it
+ extracting tag information. */
+
+ /* Remember the end of the tags table. */
+ if (position == 0)
+ goto no_tags_table;
+ else
+ tags_table_end = position - 1;
+
+ /* Locate the start of the tags table. */
+ binding.start = tags_table_end;
+ binding.end = 0;
+ position = find_file_section (&binding, TAGS_TABLE_BEG_LABEL);
+ if (position == -1)
+ goto no_tags_table;
+
+ /* The file contains a valid tags table. Fill the FILE_BUFFER's
+ tags member. */
+ file_buffer->flags |= N_HasTagsTable;
+ tags_table_begin = position;
+
+ position += skip_node_separator (file_buffer->contents + position);
+ position += strlen (TAGS_TABLE_BEG_LABEL);
+ position += strspn (file_buffer->contents + position, "\r\n");
+ if (!looking_at_line (TAGS_TABLE_IS_INDIRECT_LABEL,
+ file_buffer->contents + position))
+ {
+ /* If this isn't an indirect tags table, just remember the nodes
+ described locally in this tags table. */
+ binding.start = tags_table_begin;
+ binding.end = tags_table_end;
+ get_nodes_of_tags_table (file_buffer, &binding);
+ }
+ else
+ {
+ /* This is an indirect tags table. Find the indirect table
+ preceding the tags table. */
+ SEARCH_BINDING indirect;
+
+ indirect.start = tags_table_begin;
+ indirect.end = 0;
+ indirect.buffer = file_buffer->contents;
+ indirect.flags = S_FoldCase;
+
+ position = find_file_section (&indirect, INDIRECT_TABLE_LABEL);
+ if (position == -1)
+ /* This file is malformed. Give up. */
+ return;
+
+ /* Skip "Indirect:" line. */
+ position += skip_node_separator (file_buffer->contents + position);
+ position += strlen (INDIRECT_TABLE_LABEL);
+ position += strspn (file_buffer->contents + position, "\r\n");
+
+ 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;
+
+no_tags_table:
+ /* This file doesn't have a tag table. */
+ build_tag_table (file_buffer);
+}
+
+/* Set fields on new tag table entry. */
+static void
+init_file_buffer_tag (FILE_BUFFER *fb, TAG *entry)
+{
+ if (fb->flags & N_HasTagsTable)
+ {
+ entry->flags |= N_HasTagsTable;
+ entry->filename = fb->fullpath;
+
+ if (fb->flags & N_TagsIndirect)
+ entry->flags |= N_TagsIndirect;
+ }
+}
+
+/* Search through FILE_BUFFER->contents building an array of NODE *,
+ 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
+build_tag_table (FILE_BUFFER *file_buffer)
+{
+ long nodestart;
+ size_t 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;
+ 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);
+
+ /* Record nodename and nodestart. */
+ entry = info_create_tag ();
+ read_quoted_string (nodeline + start, ",\n\r\t", 0, &entry->nodename);
+ if (!entry->nodename || !*entry->nodename)
+ {
+ free (entry);
+ continue;
+ }
+ entry->nodestart = nodestart;
+
+ init_file_buffer_tag (file_buffer, entry);
+
+ if (anchor)
+ entry->cache.nodelen = 0;
+ else
+ /* Record that the length is unknown. */
+ entry->cache.nodelen = -1;
+
+ 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);
+ }
+}
+
+/* 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 s;
+ long position;
+ size_t tags_index = 0;
+
+ /* Copy buffer_binding */
+ s = *buffer_binding;
+
+ /* Find the start of the tags table. */
+ position = buffer_binding->start;
+
+ /* If none, we're all done. */
+ if (position == -1)
+ return;
+
+ /* Move to one character before the start of the actual table. */
+ s.start = position;
+ s.start += skip_node_separator (s.buffer + s.start);
+ s.start += strlen (TAGS_TABLE_BEG_LABEL);
+
+ /* 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 (search_forward ("\n", &s, &position) == search_success)
+ {
+ TAG *entry;
+ char *nodedef;
+ unsigned p;
+ int anchor = 0;
+
+ /* Prepare to skip this line. */
+ s.start = position;
+ s.start++;
+
+ /* Skip past informative "(Indirect)" tags table line. */
+ if (!tags_index && looking_at (TAGS_TABLE_IS_INDIRECT_LABEL, &s))
+ continue;
+
+ /* Find the label preceding the node name. */
+ name_offset = string_in_line (INFO_NODE_LABEL, s.buffer + s.start);
+
+ /* If no node label, maybe it's an anchor. */
+ if (name_offset == -1)
+ {
+ name_offset = string_in_line (INFO_REF_LABEL, s.buffer + s.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 = info_create_tag ();
+
+ init_file_buffer_tag (file_buffer, entry);
+
+ /* Find the beginning of the node definition. */
+ s.start += name_offset;
+ nodedef = s.buffer + s.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->cache.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);
+ }
+}
+
+/* Remember in FILE_BUFFER the nodenames, subfilenames, and offsets within the
+ subfiles of every node which appears in the tags table at TAGS_BINDING. The
+ indirect files list is at INDIRECT_BINDING. */
+static void
+get_tags_of_indirect_tags_table (FILE_BUFFER *file_buffer,
+ SEARCH_BINDING *indirect_binding, SEARCH_BINDING *tags_binding)
+{
+ int i;
+
+ /* 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;
+
+ SUBFILE **subfiles = NULL;
+ size_t subfiles_index = 0, subfiles_slots = 0;
+ TAG *entry;
+
+ /* Remember that tags table was indirect. */
+ file_buffer->flags |= N_TagsIndirect;
+
+ /* 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);
+ if (!file_buffer->tags)
+ return;
+
+ /* 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);
+
+ 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;
+ }
+ }
+
+ /* 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);
+}
+
+/* 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);
+}
+
+/* Functions for retrieving files. */
+
+static FILE_BUFFER *info_load_file (char *fullpath, int get_tags);
+static void get_file_character_encoding (FILE_BUFFER *fb);
+static void forget_info_file (FILE_BUFFER *file_buffer);
+static void info_reload_file_buffer_contents (FILE_BUFFER *fb);
+
+/* Try to find a file in our list of already loaded files. */
+FILE_BUFFER *
+check_loaded_file (char *filename)
+{
+ int is_fullpath, i;
+ FILE_BUFFER *file_buffer;
+
+ /* If full path to the file has been given, we must find it exactly. */
+ is_fullpath = IS_ABSOLUTE (filename)
+ || filename[0] == '.' && IS_SLASH(filename[1]);
+
+ if (info_loaded_files)
+ {
+ for (i = 0; (file_buffer = info_loaded_files[i]); i++)
+ if ( (FILENAME_CMP (filename, file_buffer->fullpath) == 0)
+ || (!is_fullpath
+ && (FILENAME_CMP (filename, file_buffer->filename) == 0)))
+ {
+ struct stat new_info, *old_info;
+
+ old_info = &file_buffer->finfo;
+ if ( stat (file_buffer->fullpath, &new_info) == -1
+ || 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 (file_buffer);
+ break;
+ }
+
+ /* 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 (!file_buffer->tags)
+ build_tags_and_nodes (file_buffer);
+
+ return file_buffer;
+ }
+ }
+ return 0;
+}
+
+/* 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)
+{
+ FILE_BUFFER *file_buffer;
+ char *fullpath;
+ int is_fullpath;
+
+ file_buffer = check_loaded_file (filename);
+ if (file_buffer)
+ return file_buffer;
+
+ /* The file wasn't loaded. Try to load it now. */
+
+ /* Get the full pathname of this file, as known by the info system.
+ That is to say, search along INFOPATH and expand tildes, etc. */
+ is_fullpath = IS_ABSOLUTE (filename)
+ || filename[0] == '.' && IS_SLASH(filename[1]);
+ if (!is_fullpath)
+ fullpath = info_find_fullpath (filename, 0);
+ else
+ fullpath = xstrdup (filename);
+
+ /* If the file wasn't found, give up, returning a NULL pointer. */
+ if (!fullpath)
+ return NULL;
+
+ file_buffer = info_load_file (fullpath, 0);
+
+ free (fullpath);
+ return file_buffer;
+}
+
+/* Find a subfile of a split file. This differs from info_load_file in
+ that it does not fill in a tag table for the file. */
+FILE_BUFFER *
+info_find_subfile (char *fullpath)
+{
+ char *with_extension = 0;
+ int i;
+ FILE_BUFFER *file_buffer = 0;
+ int fullpath_len = strlen (fullpath);
+
+ /* 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++)
+ /* Check if fullpath starts the name of the recorded file (extra
+ extensions like ".info.gz" could be added.) */
+ if (!strncmp (file_buffer->fullpath, fullpath, fullpath_len)
+ && (file_buffer->fullpath[fullpath_len] == '\0'
+ || file_buffer->fullpath[fullpath_len] == '.'))
+ {
+ struct stat new_info, *old_info;
+
+ old_info = &file_buffer->finfo;
+ if ( stat (file_buffer->fullpath, &new_info) == -1
+ || 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 (file_buffer);
+ break;
+ }
+ return file_buffer;
+ }
+ }
+
+ /* The file wasn't loaded. Try to load it now. */
+ with_extension = info_find_fullpath (fullpath, 0);
+ if (with_extension)
+ {
+ file_buffer = info_load_file (with_extension, 1);
+ free (with_extension);
+ }
+ return file_buffer;
+}
+
+/* Load the file with path FULLPATH, and return the information structure
+ describing this file, even if the file was already loaded. IS_SUBFILE
+ says whether this file is the subfile of a split file. If it is, mark
+ the FILE_BUFFER object as such and do not build a list of nodes for
+ this file. */
+static FILE_BUFFER *
+info_load_file (char *fullpath, int is_subfile)
+{
+ char *contents;
+ size_t filesize;
+ struct stat finfo;
+ int compressed;
+ FILE_BUFFER *file_buffer = NULL;
+
+ 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->fullpath = xstrdup (fullpath);
+ file_buffer->filename = filename_non_directory (file_buffer->fullpath);
+ file_buffer->filename = xstrdup (file_buffer->filename);
+ /* Strip off a file extension, so we can find it again in info_find_file. */
+ {
+ char *p = strchr (file_buffer->filename, '.');
+ if (p)
+ *p = '\0';
+ }
+ file_buffer->finfo = finfo;
+ file_buffer->filesize = filesize;
+ file_buffer->contents = contents;
+ if (compressed)
+ file_buffer->flags |= N_IsCompressed;
+
+ /* Find encoding of file, if set */
+ get_file_character_encoding (file_buffer);
+
+ if (!is_subfile)
+ {
+ build_tags_and_nodes (file_buffer);
+ if (!file_buffer->tags)
+ {
+ free (file_buffer->fullpath);
+ free (file_buffer->filename);
+ free (file_buffer->contents);
+ free (file_buffer->encoding);
+ free (file_buffer);
+ return 0;
+ }
+ }
+ else
+ file_buffer->flags |= N_Subfile;
+
+ /* If the file was loaded, remember the name under which it was found. */
+ if (file_buffer)
+ add_pointer_to_array (file_buffer, info_loaded_files_index,
+ info_loaded_files, info_loaded_files_slots, 10);
+
+ return file_buffer;
+}
+
+/* Look for local variables section in FB and set encoding */
+static void
+get_file_character_encoding (FILE_BUFFER *fb)
+{
+ SEARCH_BINDING binding;
+ long position;
+
+ long int enc_start, enc_len;
+ char *enc_string;
+
+ /* See if there is a local variables section in this info file. */
+ binding.buffer = fb->contents;
+ binding.start = fb->filesize;
+ binding.end = binding.start - 1000;
+ if (binding.end < 0)
+ binding.end = 0;
+ binding.flags = S_FoldCase;
+
+ /* Null means the encoding is unknown. */
+ fb->encoding = 0;
+
+ if (search_backward (LOCAL_VARIABLES_LABEL, &binding, &position)
+ != search_success)
+ return;
+
+ binding.start = position;
+ binding.end = fb->filesize;
+
+ if (search_forward (CHARACTER_ENCODING_LABEL, &binding, &enc_start)
+ != search_success)
+ return;
+
+ enc_start += strlen(CHARACTER_ENCODING_LABEL); /* Skip to after "coding:" */
+ enc_start += skip_whitespace(fb->contents + enc_start);
+
+ enc_len = strcspn (fb->contents + enc_start, "\r\n");
+
+ enc_string = xmalloc (enc_len + 1);
+ strncpy (enc_string, fb->contents + enc_start, enc_len);
+ enc_string[enc_len] = '\0';
+
+ fb->encoding = enc_string;
+}
+
+/* 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;
+ file_buffer->encoding = 0;
+
+ return file_buffer;
+}
+
+/* Prevent this file buffer being used again. */
+static void
+forget_info_file (FILE_BUFFER *file_buffer)
+{
+ file_buffer->flags |= N_Gone;
+ file_buffer->filename[0] = '\0';
+ file_buffer->fullpath = "";
+ memset (&file_buffer->finfo, 0, sizeof (struct stat));
+}
+
+/* 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;
+
+ 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;
+}
+
+
+/* Functions for node creation and retrieval. */
+
+static long get_node_length (SEARCH_BINDING *binding);
+static void node_set_body_start (NODE *node);
+static int adjust_nodestart (FILE_BUFFER *file_buffer, TAG *tag);
+
+/* Return a pointer to a newly allocated TAG structure, with
+ fields filled in. */
+TAG *
+info_create_tag (void)
+{
+ TAG *t = xmalloc (sizeof (TAG));
+
+ memset (t, 0, sizeof (TAG));
+ t->filename = 0;
+ t->nodename = 0;
+ t->nodestart = -1;
+ t->nodestart_adjusted = -1;
+ t->cache.nodelen = -1;
+
+ return t;
+}
+/* Return a pointer to a newly allocated NODE structure, with
+ fields filled in. */
+NODE *
+info_create_node (void)
+{
+ NODE *n = xmalloc (sizeof (NODE));
+
+ n->fullpath = 0;
+ n->subfile = 0;
+ n->nodename = 0;
+ n->contents = 0;
+ n->nodelen = -1;
+ n->display_pos = 0;
+ n->body_start = 0;
+ n->flags = 0;
+ n->references = 0;
+ n->up = 0;
+ n->prev = 0;
+ n->next = 0;
+
+ return n;
+}
+
+/* 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;
+}
+
+#define FOLLOW_REMAIN 0
+#define FOLLOW_PATH 1
+
+int follow_strategy;
+
+/* Return a pointer to a NODE structure for the Info node (FILENAME)NODENAME,
+ using DEFAULTS for defaults. If DEFAULTS is null, the defaults are:
+ - 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_with_defaults (char *filename_in, char *nodename_in,
+ NODE *defaults)
+{
+ NODE *node = 0;
+ FILE_BUFFER *file_buffer = NULL;
+ char *filename = 0, *nodename = 0;
+
+ info_recent_file_error = NULL;
+
+ filename = filename_in;
+ if (filename_in)
+ {
+ filename = xstrdup (filename_in);
+ if (follow_strategy == FOLLOW_REMAIN
+ && defaults && defaults->fullpath
+ && filename_in)
+ {
+ /* Find the directory in the filename for defaults, and look in
+ that directory first. */
+ char *file_in_same_dir;
+ char saved_char, *p;
+
+ p = defaults->fullpath + strlen (defaults->fullpath);
+ while (p > defaults->fullpath && !IS_SLASH (*p))
+ p--;
+
+ if (p > defaults->fullpath)
+ {
+ saved_char = *p;
+ *p = 0;
+ file_in_same_dir = info_add_extension (defaults->fullpath,
+ filename, 0);
+ *p = saved_char;
+
+ if (file_in_same_dir)
+ file_buffer = info_find_file (file_in_same_dir);
+ free (file_in_same_dir);
+ }
+ }
+ }
+ else
+ {
+ if (defaults)
+ filename = xstrdup (defaults->fullpath);
+ else
+ filename = xstrdup ("dir");
+ }
+
+ if (nodename_in && *nodename_in)
+ nodename = xstrdup (nodename_in);
+ else
+ /* If NODENAME is not specified, it defaults to "Top". */
+ nodename = xstrdup ("Top");
+
+ /* 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))
+ {
+ node = get_dir_node ();
+ goto cleanup_and_exit;
+ }
+
+ if (mbscasecmp (filename, MANPAGE_FILE_BUFFER_NAME) == 0)
+ {
+ node = get_manpage_node (nodename);
+ goto cleanup_and_exit;
+ }
+
+ if (!file_buffer)
+ file_buffer = info_find_file (filename);
+
+ if (file_buffer)
+ {
+ /* Look for the node. */
+ node = info_get_node_of_file_buffer (file_buffer, nodename);
+ }
+
+ /* If the node not found was "Top", try again with different case. */
+ if (!node && (nodename && mbscasecmp (nodename, "Top") == 0))
+ {
+ node = info_get_node_of_file_buffer (file_buffer, "Top");
+ if (!node)
+ node = info_get_node_of_file_buffer (file_buffer, "top");
+ if (!node)
+ node = info_get_node_of_file_buffer (file_buffer, "TOP");
+ }
+
+cleanup_and_exit:
+ free (filename); free (nodename);
+ return node;
+}
+
+/* Return NODE specified with FILENAME_IN and NODENAME_IN. Return value
+ should be freed by caller, but none of its fields should be. */
+NODE *
+info_get_node (char *filename_in, char *nodename_in)
+{
+ return info_get_node_with_defaults (filename_in, nodename_in, 0);
+}
+
+static void
+node_set_body_start (NODE *node)
+{
+ int n = skip_node_separator (node->contents);
+ node->body_start = strcspn(node->contents + n, "\n");
+ node->body_start += n;
+}
+
+/* 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. Return value should be freed by caller, but none of its
+ fields should be. */
+NODE *
+info_get_node_of_file_buffer (FILE_BUFFER *file_buffer, char *nodename)
+{
+ NODE *node = NULL;
+
+ /* If we are unable to find the file, we have to give up. There isn't
+ anything else we can do. */
+ if (!file_buffer)
+ return NULL;
+
+ /* If the file buffer was gc'ed, reload the contents now. */
+ if (!file_buffer->contents)
+ info_reload_file_buffer_contents (file_buffer);
+
+ /* 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 = info_create_node ();
+ node->fullpath = file_buffer->fullpath;
+ node->nodename = xstrdup ("*");
+ node->contents = file_buffer->contents;
+ node->nodelen = file_buffer->filesize;
+ node->body_start = 0;
+ }
+ /* Search the tags table for an entry which matches the node that we want. */
+ else
+ {
+ 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)
+ {
+ node = info_node_of_tag (file_buffer, &file_buffer->tags[i]);
+ break;
+ }
+ }
+
+ /* Return the results of our node search. */
+ return node;
+}
+
+
+/* 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
+
+/* Find the actual starting memory location of NODE. 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,
+ set N_UpdateTags in NODE->flags. If the node is found, return non-zero.
+ Set NODE->nodestart_adjusted directly on the separator that precedes this
+ node. If the node could not be found, return 0. */
+static int
+adjust_nodestart (FILE_BUFFER *fb, TAG *node)
+{
+ long position = -1;
+ SEARCH_BINDING s;
+
+ /* Try the optimal case first. Who knows? This file may actually be
+ formatted (mostly) correctly. */
+ s.buffer = fb->contents;
+ s.start = node->nodestart;
+ s.end = s.start + 1;
+
+ /* Check that the given nodestart is in fact inside the file buffer. */
+ if (s.start >= 0 && s.start < fb->filesize)
+ {
+ /* Check for node separator at node->nodestart
+ introducting this node. */
+ position = find_node_in_binding (node->nodename, &s);
+ }
+
+ if (position == -1)
+ {
+ if (strict_node_location_p)
+ return 0;
+
+ /* Oh well, I guess we have to try to find it in a larger area. */
+
+ s.start -= DEFAULT_INFO_FUDGE;
+ s.end += DEFAULT_INFO_FUDGE;
+
+ if (s.start < 0)
+ s.start = 0;
+ else if (s.start > fb->filesize)
+ s.start = fb->filesize;
+ if (s.end > fb->filesize)
+ s.end = fb->filesize;
+
+ position = find_node_in_binding (node->nodename, &s);
+
+ /* If the node still couldn't be found, we lose big. */
+ if (position == -1)
+ return 0;
+
+ /* Set the flag in NODE->flags to say that the the tags table could
+ need updating (if we used a tag to get here, that is). */
+ if (node->flags & N_HasTagsTable)
+ node->flags |= N_UpdateTags;
+ }
+
+ node->nodestart_adjusted = s.buffer + position - fb->contents;
+ return 1;
+}
+
+/* Look in the contents of *FB_PTR for a node referred to with TAG. Set
+ the location if found in TAG->nodestart_adjusted.
+
+ PARENT->tags contains the tags table for the whole file. If file is
+ non-split, PARENT should be the same as FB. */
+static int
+find_node_from_tag (FILE_BUFFER *parent, FILE_BUFFER *fb, TAG *tag)
+{
+ int success;
+
+ if (tag->nodestart_adjusted != -1)
+ success = 1;
+ else
+ success = adjust_nodestart (fb, tag);
+
+ if (success)
+ return success;
+ return 0;
+}
+
+/* Calculate the length of the node. */
+static void
+set_tag_nodelen (FILE_BUFFER *subfile, TAG *tag)
+{
+ SEARCH_BINDING node_body;
+
+ node_body.buffer = subfile->contents;
+ node_body.start = tag->nodestart_adjusted;
+ node_body.end = subfile->filesize;
+ node_body.flags = 0;
+ node_body.start += skip_node_separator (node_body.buffer + node_body.start);
+ tag->cache.nodelen = get_node_length (&node_body);
+}
+
+/* Return the node described by *TAG_PTR, retrieving contents from subfile
+ if the file is split. Return 0 on failure. If FAST, don't process the
+ node to find cross-references, a menu, or perform character encoding
+ conversion. */
+static NODE *
+info_node_of_tag_ext (FILE_BUFFER *fb, TAG **tag_ptr, int fast)
+{
+ TAG *tag = *tag_ptr;
+ NODE *node;
+ int is_anchor;
+ TAG *anchor_tag;
+ int node_pos, anchor_pos;
+
+ FILE_BUFFER *parent; /* File containing tag table. */
+ FILE_BUFFER *subfile; /* File containing node. */
+
+ if (!FILENAME_CMP (fb->fullpath, tag->filename))
+ parent = subfile = fb;
+ else
+ {
+ /* This is a split file. */
+ parent = fb;
+ subfile = info_find_subfile (tag->filename);
+ }
+
+ 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. */
+ if (!(tag->nodestart >= 0 && tag->nodestart < subfile->filesize))
+ return NULL;
+
+ node = 0;
+
+ is_anchor = tag->cache.nodelen == 0;
+
+ if (is_anchor)
+ {
+ anchor_pos = tag_ptr - fb->tags;
+
+ /* Look backwards in the tag table for the node preceding
+ the anchor (we're assuming the tags are given in order),
+ skipping over any preceding anchors. */
+ for (node_pos = anchor_pos - 1;
+ node_pos >= 0 && fb->tags[node_pos]->cache.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;
+
+ anchor_tag = tag;
+ tag = fb->tags[node_pos];
+ tag_ptr = &fb->tags[node_pos];
+ }
+
+ /* We haven't checked the entry pointer yet. Look for the node
+ around about it and adjust it if necessary. */
+ if (tag->cache.nodelen == -1)
+ {
+ if (!find_node_from_tag (parent, subfile, tag))
+ return NULL; /* Node not found. */
+
+ set_tag_nodelen (subfile, tag);
+ }
+
+ node = xmalloc (sizeof (NODE));
+ memset (node, 0, sizeof (NODE));
+ if (tag->cache.references)
+ {
+ /* Initialize the node from the cache. */
+ *node = tag->cache;
+ if (!node->contents)
+ {
+ node->contents = subfile->contents + tag->nodestart_adjusted;
+ node->contents += skip_node_separator (node->contents);
+ }
+ }
+ else
+ {
+ /* Data for node has not been generated yet. */
+ node->contents = subfile->contents + tag->nodestart_adjusted;
+ node->contents += skip_node_separator (node->contents);
+ node->nodelen = tag->cache.nodelen;
+ node->nodename = tag->nodename;
+ node->flags = tag->flags;
+
+ node->fullpath = parent->fullpath;
+ if (parent != subfile)
+ node->subfile = tag->filename;
+
+ if (fast)
+ node->flags |= N_Simple;
+ else
+ {
+ /* Read locations of references in node and similar. Strip Info file
+ syntax from node if preprocess_nodes=On. Adjust the offsets of
+ anchors that occur within the node. */
+ scan_node_contents (node, parent, tag_ptr);
+
+ node_set_body_start (node);
+ tag->cache = *node;
+ if (!(node->flags & N_WasRewritten))
+ tag->cache.contents = 0; /* Pointer into file buffer
+ is not saved. */
+ }
+ }
+
+ /* We can't set this when tag table is built, because
+ if file is split, we don't know which of the sub-files
+ are compressed. */
+ if (subfile->flags & N_IsCompressed)
+ node->flags |= N_IsCompressed;
+
+ if (is_anchor)
+ {
+ /* Start displaying the node at the anchor position. */
+
+ node->display_pos = anchor_tag->nodestart_adjusted
+ - (tag->nodestart_adjusted
+ + skip_node_separator (subfile->contents
+ + tag->nodestart_adjusted));
+
+ /* 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;
+ else if (node->display_pos < 0)
+ node->display_pos = 0; /* Shouldn't happen. */
+ }
+
+ return node;
+}
+
+NODE *
+info_node_of_tag (FILE_BUFFER *fb, TAG **tag_ptr)
+{
+ return info_node_of_tag_ext (fb, tag_ptr, 0);
+}
+
+NODE *
+info_node_of_tag_fast (FILE_BUFFER *fb, TAG **tag_ptr)
+{
+ return info_node_of_tag_ext (fb, tag_ptr, 1);
+}
diff --git a/macros/texinfo/texinfo/info/nodes.h b/macros/texinfo/texinfo/info/nodes.h
new file mode 100644
index 0000000000..82b09d8ace
--- /dev/null
+++ b/macros/texinfo/texinfo/info/nodes.h
@@ -0,0 +1,191 @@
+/* nodes.h -- How we represent nodes internally.
+
+ Copyright 1993-2019 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. */
+
+#ifndef NODES_H
+#define NODES_H
+
+#include "info.h"
+
+/* Structure which describes a node reference, such as a menu entry or
+ cross reference. */
+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. */
+ long line_number; /* Specific line number a menu item points to. */
+ int type; /* Whether reference is a xref or a menu item */
+} REFERENCE;
+
+/* Possible values of REFERENCE.type */
+#define REFERENCE_XREF 0
+#define REFERENCE_MENU_ITEM 1
+
+typedef struct {
+ char *fullpath; /* Non-null is the logical file name. */
+ char *subfile; /* File containing node for split files. */
+ char *nodename; /* The name of this node. */
+ char *contents; /* Characters appearing in this node. */
+ long nodelen; /* The length of the CONTENTS member. */
+ long display_pos; /* Where to display at, if nonzero. */
+ long body_start; /* Offset of the actual node body */
+ int flags; /* See immediately below. */
+ REFERENCE **references; /* Cross-references or menu items in node.
+ Null-terminated. references == 0 implies
+ uninitialized, not empty */
+ char *up, *prev, *next; /* Names of nearby nodes. */
+ int active_menu; /* Used for subnodes search. */
+} NODE;
+
+/* Values for NODE.flags or FILE_BUFFER.flags. */
+#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_WasRewritten 0x100 /* NODE->contents can be passed to free(). */
+#define N_IsIndex 0x200 /* An index node. */
+#define N_IsDir 0x400 /* A dir node. */
+#define N_Subfile 0x800 /* File buffer is a subfile of a split file. */
+#define N_Gone 0x1000 /* File is no more. */
+#define N_Simple 0x2000 /* Data about cross-references is missing. */
+#define N_SeenBySearch 0x4000 /* Node has already been seen in a search. */
+
+/* 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 "End Tag Table"
+#define TAGS_TABLE_BEG_LABEL "Tag Table:"
+#define INDIRECT_TABLE_LABEL "Indirect:"
+#define TAGS_TABLE_IS_INDIRECT_LABEL "(Indirect)"
+#define LOCAL_VARIABLES_LABEL "Local Variables"
+#define CHARACTER_ENCODING_LABEL "coding:"
+
+/* 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 simply contains the name of the
+ current 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 value read from the tag table. */
+ long nodestart_adjusted; /* Where the node or anchor actually is. */
+ int flags; /* Same as NODE.flags. */
+ NODE cache; /* Saved information about pointed-to 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. */
+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. */
+ size_t filesize; /* The number of bytes this file expands to. */
+ char **subfiles; /* If non-null, the list of subfiles. */
+ TAG **tags; /* If non-null, the tags table. */
+ size_t tags_slots; /* Number of slots allocated for TAGS. */
+ int flags; /* Various flags. Mimics of N_* flags. */
+ char *encoding; /* Name of character encoding of file. */
+} FILE_BUFFER;
+
+/* Array of FILE_BUFFER * which represents the currently loaded info files. */
+extern FILE_BUFFER **info_loaded_files;
+extern size_t info_loaded_files_index;
+extern size_t 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 *. */
+FILE_BUFFER *info_find_file (char *filename);
+
+FILE_BUFFER *check_loaded_file (char *filename);
+
+FILE_BUFFER *info_find_subfile (char *filename);
+
+TAG *info_create_tag (void);
+
+/* Return a pointer to a new NODE structure. */
+NODE *info_create_node (void);
+
+/* Return a pointer to a NODE structure for the Info node (FILENAME)NODENAME.
+ FILENAME can be passed as NULL, in which case the filename of "dir" is used.
+ NODENAME can be passed as NULL, in which case the nodename of "Top" is used.
+
+ If the node cannot be found, return a NULL pointer. */
+NODE *info_get_node (char *filename, char *nodename);
+
+NODE *info_get_node_with_defaults (char *filename, char *nodename,
+ NODE *defaults);
+
+NODE *info_node_of_tag (FILE_BUFFER *fb, TAG **tag_ptr);
+NODE *info_node_of_tag_fast (FILE_BUFFER *fb, TAG **tag_ptr);
+
+/* 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 (FILE_BUFFER *file_buffer,
+ char *nodename);
+
+/* 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);
+
+void free_history_node (NODE *n);
+
+/* 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. */
+FILE_BUFFER *make_file_buffer (void);
+
+/* Non-zero means don't try to be smart when searching for nodes. */
+extern int strict_node_location_p;
+
+
+/* Found in dir.c */
+NODE *get_dir_node (void);
+REFERENCE *lookup_dir_entry (char *label, int sloppy);
+REFERENCE *dir_entry_of_infodir (char *label, char *searchdir);
+
+#endif /* not NODES_H */
diff --git a/macros/texinfo/texinfo/info/pcterm.c b/macros/texinfo/texinfo/info/pcterm.c
new file mode 100644
index 0000000000..9bf796485b
--- /dev/null
+++ b/macros/texinfo/texinfo/info/pcterm.c
@@ -0,0 +1,1721 @@
+/* pcterm.c -- How to handle the PC terminal for Info under MS-DOS/MS-Windows.
+
+ Copyright 1998-2019 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 and MinGW! However, Borland should come close,
+ and other PC compilers will need minor modifications. */
+
+#ifdef __MSDOS__
+/* 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>
+#endif
+
+#ifdef _WIN32
+#include <io.h>
+#include <conio.h>
+#include <process.h>
+#include <malloc.h> /* for alloca */
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
+#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 4
+#endif
+#ifndef COMMON_LVB_UNDERSCORE
+#define COMMON_LVB_UNDERSCORE 0x8000
+#endif
+
+struct text_info {
+ WORD normattr;
+ WORD attribute;
+ SHORT winleft;
+ SHORT wintop;
+ SHORT winright;
+ SHORT winbottom;
+ SHORT screenheight;
+ SHORT screenwidth;
+ SHORT curx;
+ SHORT cury;
+ COORD bufsize;
+ unsigned char currmode; /* unused and unsupported for Windows */
+};
+
+struct termios {
+ int dummy;
+};
+
+enum text_modes { LASTMODE=-1 };
+
+#define cprintf _cprintf
+#define cputs _cputs
+
+#undef read
+#undef _read
+
+#include "display.h"
+
+void reset_info_window_sizes (void);
+void redisplay_after_signal (void);
+
+#endif
+
+#include "variables.h"
+#include "session.h"
+#include "terminal.h"
+
+extern int speech_friendly; /* defined in info.c */
+
+/* **************************************************************** */
+/* */
+/* PC Terminal Output Functions */
+/* */
+/* **************************************************************** */
+
+static struct text_info outside_info; /* holds screen params outside Info */
+#ifdef _WIN32
+static WORD norm_attr, inv_attr, xref_attr;
+static WORD current_attr;
+static HANDLE hstdin = INVALID_HANDLE_VALUE;
+static HANDLE hstdout = INVALID_HANDLE_VALUE;
+static HANDLE hinfo = INVALID_HANDLE_VALUE;
+static HANDLE hscreen = INVALID_HANDLE_VALUE;
+static DWORD old_inpmode;
+static DWORD old_outpmode;
+static UINT output_cp;
+#else
+static unsigned char norm_attr, inv_attr, xref_attr;
+#endif
+
+static unsigned const char * find_sequence (int);
+
+#ifdef _WIN32
+
+/* Windows-specific initialization and de-initialization. */
+void
+w32_info_prep (void)
+{
+ if (hinfo != INVALID_HANDLE_VALUE)
+ {
+ DWORD new_mode;
+
+ SetConsoleActiveScreenBuffer (hinfo);
+ current_attr = norm_attr;
+ hscreen = hinfo;
+ SetConsoleMode (hstdin, ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT);
+ GetConsoleMode (hscreen, &old_outpmode);
+ new_mode = old_outpmode & ~ENABLE_WRAP_AT_EOL_OUTPUT;
+ SetConsoleMode (hscreen, new_mode);
+ /* Enable underline, if available. */
+ SetConsoleMode (hscreen, new_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
+ }
+}
+
+void
+w32_info_unprep (void)
+{
+ if (hinfo != INVALID_HANDLE_VALUE)
+ {
+ SetConsoleActiveScreenBuffer (hstdout);
+ current_attr = outside_info.normattr;
+ hscreen = hstdout;
+ SetConsoleMode (hstdin, old_inpmode);
+ }
+}
+
+void
+w32_cleanup (void)
+{
+ if (hinfo != INVALID_HANDLE_VALUE)
+ {
+ COORD cursor_pos;
+
+ /* Restore the original position of the cursor. */
+ cursor_pos.X = outside_info.curx;
+ cursor_pos.Y = outside_info.cury;
+ SetConsoleCursorPosition (hstdout, cursor_pos);
+
+ /* Close the input handle we created. */
+ CloseHandle (hinfo);
+ }
+}
+
+static void w32_info_init (void) __attribute__((constructor));
+static void pc_initialize_terminal (char *);
+
+static void
+w32_info_init (void)
+{
+ /* 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;
+}
+
+/* Emulate DJGPP conio functions for Windows. */
+static void
+gettextinfo (struct text_info *ti)
+{
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ static TCHAR errbuf[500];
+ DWORD ignored;
+
+ hstdin = GetStdHandle (STD_INPUT_HANDLE);
+ hstdout = GetStdHandle (STD_OUTPUT_HANDLE);
+
+ if (!GetConsoleMode (hstdin, &ignored))
+ hstdin = INVALID_HANDLE_VALUE;
+
+ if (hstdout != INVALID_HANDLE_VALUE
+ && GetConsoleMode (hstdout, &ignored))
+ {
+ hinfo = CreateConsoleScreenBuffer (GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
+ if (hinfo != INVALID_HANDLE_VALUE
+ && GetConsoleScreenBufferInfo (hstdout, &csbi))
+ {
+ ti->normattr = csbi.wAttributes;
+ ti->winleft = 1;
+ ti->wintop = 1;
+ ti->winright = csbi.srWindow.Right + 1;
+ ti->winbottom = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
+ ti->attribute = csbi.wAttributes;
+ ti->screenheight = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
+ ti->screenwidth = csbi.srWindow.Right - csbi.srWindow.Left + 1;
+ ti->curx = csbi.dwCursorPosition.X;
+ ti->cury = csbi.dwCursorPosition.Y;
+ ti->bufsize = csbi.dwSize;
+
+ atexit (w32_cleanup);
+ }
+ else
+ {
+ DWORD error_no = GetLastError ();
+
+ if (!FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL,
+ error_no,
+ 0, /* choose most suitable language */
+ errbuf, sizeof (errbuf), NULL))
+ sprintf (errbuf, "w32 error %u", error_no);
+ CloseHandle (hinfo);
+ info_error (_("Terminal cannot be initialized: %s\n"), errbuf);
+ exit (1);
+ }
+ }
+ else
+ {
+ /* We were invoked non-interactively. Do the minimum we must. */
+ ti->screenheight = 24;
+ ti->screenwidth = 80;
+ }
+}
+
+void
+textattr (int attr)
+{
+ if (hscreen != INVALID_HANDLE_VALUE)
+ SetConsoleTextAttribute (hscreen, attr);
+}
+
+void
+textmode (int mode)
+{
+ /* Nothing. */
+}
+
+void
+highvideo (void)
+{
+ int attr;
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+
+ GetConsoleScreenBufferInfo (hscreen, &csbi);
+ attr = csbi.wAttributes | FOREGROUND_INTENSITY;
+ attr ^= norm_attr & FOREGROUND_INTENSITY;
+ textattr (attr);
+}
+
+void
+normvideo (void)
+{
+ int attr;
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+
+ GetConsoleScreenBufferInfo (hscreen, &csbi);
+ attr = csbi.wAttributes & ~(FOREGROUND_INTENSITY | BACKGROUND_INTENSITY
+ | COMMON_LVB_UNDERSCORE);
+ attr |= norm_attr & (FOREGROUND_INTENSITY | BACKGROUND_INTENSITY);
+ textattr (attr);
+}
+
+void
+blinkvideo (void)
+{
+ int attr;
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+
+ GetConsoleScreenBufferInfo (hscreen, &csbi);
+ attr = csbi.wAttributes | BACKGROUND_INTENSITY;
+ attr ^= norm_attr & BACKGROUND_INTENSITY;
+ textattr (attr);
+}
+
+void
+underline (void)
+{
+ int attr;
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+
+ GetConsoleScreenBufferInfo (hscreen, &csbi);
+ attr = csbi.wAttributes | COMMON_LVB_UNDERSCORE;
+ textattr (attr);
+}
+
+void
+textcolor (int color)
+{
+ int attr;
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+
+ GetConsoleScreenBufferInfo (hscreen, &csbi);
+ attr = (csbi.wAttributes & (COMMON_LVB_UNDERSCORE | 0xf0)) | (color & 0x0f);
+ textattr (attr);
+}
+
+void
+textbackground (int color)
+{
+ int attr;
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+
+ GetConsoleScreenBufferInfo (hscreen, &csbi);
+ attr = (csbi.wAttributes & (COMMON_LVB_UNDERSCORE | 0x0f)) | ((color & 0x0f) << 4);
+ textattr (attr);
+}
+
+void
+ScreenGetCursor (int *row, int *col)
+{
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+
+ if (hscreen == INVALID_HANDLE_VALUE)
+ *row = *col = 0;
+ else
+ {
+ GetConsoleScreenBufferInfo (hscreen, &csbi);
+ *row = csbi.dwCursorPosition.Y;
+ *col = csbi.dwCursorPosition.X;
+ }
+}
+
+void
+ScreenSetCursor (int row, int col)
+{
+ if (hscreen != INVALID_HANDLE_VALUE)
+ {
+ COORD cursor_pos;
+
+ cursor_pos.X = col;
+ cursor_pos.Y = row;
+
+ SetConsoleCursorPosition (hscreen, cursor_pos);
+ }
+}
+
+void
+ScreenClear (void)
+{
+ if (hscreen != INVALID_HANDLE_VALUE)
+ {
+ DWORD nchars = screenwidth * screenheight;
+ COORD start_pos;
+ DWORD written;
+
+ start_pos.X = start_pos.Y = 0;
+ FillConsoleOutputAttribute (hscreen, norm_attr, nchars, start_pos,
+ &written);
+ FillConsoleOutputCharacter (hscreen, ' ', nchars, start_pos, &written);
+ }
+}
+
+void
+clreol (void)
+{
+ if (hscreen != INVALID_HANDLE_VALUE)
+ {
+ DWORD nchars;
+ COORD start_pos;
+ DWORD written;
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+
+ GetConsoleScreenBufferInfo (hscreen, &csbi);
+ start_pos = csbi.dwCursorPosition;
+ nchars = csbi.dwSize.X - start_pos.X;
+
+ FillConsoleOutputAttribute (hscreen, current_attr, nchars, start_pos,
+ &written);
+ FillConsoleOutputCharacter (hscreen, ' ', nchars, start_pos, &written);
+ }
+}
+
+void
+ScreenVisualBell (void)
+{
+ if (hscreen != INVALID_HANDLE_VALUE)
+ {
+ DWORD nchars = screenwidth * screenheight;
+ COORD start_pos;
+ DWORD written;
+ PWORD attr;
+ DWORD i;
+
+ start_pos.X = start_pos.Y = 0;
+ attr = xmalloc (nchars * sizeof (WORD));
+ ReadConsoleOutputAttribute (hscreen, attr, nchars, start_pos, &written);
+ for (i = 0; i < nchars; ++i)
+ attr[i] ^= norm_attr ^ inv_attr;
+ WriteConsoleOutputAttribute (hscreen, attr, nchars, start_pos, &written);
+ Sleep (50);
+ for (i = 0; i < nchars; ++i)
+ attr[i] ^= norm_attr ^ inv_attr;
+ WriteConsoleOutputAttribute (hscreen, attr, nchars, start_pos, &written);
+ free (attr);
+ }
+ else
+ {
+ printf ("%c", '\a');
+ fflush (stdout);
+ }
+}
+
+int
+movetext(int left, int top, int right, int bottom, int destleft, int desttop)
+{
+ if (hscreen != INVALID_HANDLE_VALUE)
+ {
+ SMALL_RECT src;
+ COORD dest;
+ CHAR_INFO fill;
+
+ src.Left = left - 1;
+ src.Top = top - 1;
+ src.Right = right - 1;
+ src.Bottom = bottom - 1;
+
+ dest.X = destleft - 1;
+ dest.Y = desttop - 1;
+
+ fill.Attributes = norm_attr;
+ fill.Char.AsciiChar = (CHAR)' ';
+
+ return ScrollConsoleScreenBuffer (hscreen, &src , NULL, dest, &fill) != 0;
+ }
+ else
+ return 0;
+}
+
+int
+ScreenRows (void)
+{
+ if (hscreen != INVALID_HANDLE_VALUE)
+ {
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+
+ GetConsoleScreenBufferInfo (hscreen, &csbi);
+ return csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
+ }
+ else
+ return 24;
+}
+
+int
+ScreenCols (void)
+{
+ if (hscreen != INVALID_HANDLE_VALUE)
+ {
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+
+ GetConsoleScreenBufferInfo (hscreen, &csbi);
+ return csbi.srWindow.Right - csbi.srWindow.Left + 1;
+ }
+ else
+ return 80;
+}
+
+void
+_set_screen_lines (int lines)
+{
+ if (hscreen != INVALID_HANDLE_VALUE)
+ {
+ SMALL_RECT window_rectangle;
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ COORD scrbufsize;
+
+ GetConsoleScreenBufferInfo (hscreen, &csbi);
+
+ window_rectangle = csbi.srWindow;
+ window_rectangle.Bottom = window_rectangle.Top + lines - 1;
+ SetConsoleWindowInfo (hscreen, TRUE, &window_rectangle);
+
+ /* Set the screen buffer size to the same dimensions as the window,
+ so that the dysfunctional scroll bar disappears. */
+ scrbufsize.X = window_rectangle.Right - window_rectangle.Left + 1;
+ scrbufsize.Y = window_rectangle.Bottom - window_rectangle.Top + 1;
+ SetConsoleScreenBufferSize (hscreen, scrbufsize);
+ }
+}
+
+void
+w32_set_screen_dimensions (int cols, int rows)
+{
+ if (hscreen != INVALID_HANDLE_VALUE)
+ {
+ SMALL_RECT window_rectangle;
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+
+ GetConsoleScreenBufferInfo (hscreen, &csbi);
+
+ window_rectangle = csbi.srWindow;
+ window_rectangle.Bottom = window_rectangle.Top + rows - 1;
+ window_rectangle.Right = window_rectangle.Left + cols - 1;
+ SetConsoleWindowInfo (hscreen, TRUE, &window_rectangle);
+ }
+}
+
+/* Emulate `sleep'. */
+unsigned
+sleep (unsigned sec)
+{
+ Sleep (sec*1000);
+ return 0;
+}
+
+/* Keyboard input support. */
+
+static int
+w32_our_tty (int fd)
+{
+ /* Is this our tty? */
+ return hstdin != INVALID_HANDLE_VALUE
+ && hstdin == (HANDLE)_get_osfhandle (fd);
+}
+
+/* Translate a Windows key event into the equivalent sequence of bytes
+ to be submitted to Info dispatcher. */
+#define define_seq(p,s1,s2) \
+ do { \
+ if ((ctl & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) != 0) \
+ memcpy (p, s1, sizeof (s1)), p += sizeof (s1) - 1; \
+ else \
+ memcpy (p, s2, sizeof (s2)), p += sizeof (s2) - 1; \
+ } while (0)
+
+static int
+w32keyseq (unsigned char ascii_ch, WORD vkey, DWORD ctl, unsigned char *seq)
+{
+ unsigned char *p = seq;
+
+ switch (ascii_ch)
+ {
+ case '\0':
+ /* Keys with no ASCII code are extended keys, like arrows. */
+ switch (vkey)
+ {
+ case VK_PRIOR:
+ define_seq (p, "\033\061p", "\033v");
+ break;
+ case VK_NEXT:
+ define_seq (p, "\033\061n", "\026");
+ break;
+ case VK_END:
+ define_seq (p, "\033>", "\033>");
+ break;
+ case VK_HOME:
+ define_seq (p, "\033<", "\033<");
+ break;
+ case VK_LEFT:
+ define_seq (p, "\033b", "\033[D");
+ break;
+ case VK_UP:
+ define_seq (p, "\033\061u", "\033[A");
+ break;
+ case VK_RIGHT:
+ define_seq (p, "\033f", "\033[C");
+ break;
+ case VK_DOWN:
+ define_seq (p, "\033\061m", "\033[B");
+ break;
+ case VK_INSERT:
+ define_seq (p, "\033[L", "\033[L");
+ break;
+ case VK_DELETE: /* Delete => Ctrl-d, Alt-Delete => ESC d */
+ if ((ctl & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) != 0)
+ define_seq (p, "\033d", "\033d");
+ else
+ define_seq (p, "\033d", "\004");
+ break;
+ case VK_HELP: /* F1 => Ctrl-h */
+ case VK_F1:
+ *p++ = '\010';
+ break;
+ case 50: /* Ctrl-@ => '\0' */
+ if ((ctl & SHIFT_PRESSED) != 0)
+ *p++ = '\0';
+ break;
+ default:
+ if (0x41 <= vkey && vkey <= 0x5a)
+ {
+ /* Alt-Ctrl-a, Alt-Ctrl-b, etc. */
+ *p++ = '\033';
+ *p++ = '\001' + vkey - 0x41;
+ }
+ }
+ break;
+ case ' ': /* Ctrl-SPC => '\0' */
+ if ((ctl & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) != 0)
+ ascii_ch = '\0';
+ *p++ = ascii_ch;
+ break;
+ case '\t': /* Shift-TAB/Alt-TAB => Esc-TAB */
+ if ((ctl & (SHIFT_PRESSED | LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) != 0)
+ {
+ memcpy (p, "\033\011", sizeof ("\033\011"));
+ p += sizeof ("\033\011") - 1;
+ }
+ else
+ *p++ = '\t';
+ break;
+ case '\b':
+ /* Backspace => DEL. */
+ ascii_ch = '\177';
+ /* FALLTHROUGH */
+ default:
+ if ((ctl & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) != 0)
+ *p++ = '\033';
+ *p++ = ascii_ch;
+ break;
+ }
+ return p - seq;
+}
+
+static unsigned char buffered_chars[512];
+static size_t buf_head;
+static size_t buf_tail;
+
+static ssize_t
+w32_kbd_read (unsigned char *inbuf, size_t n)
+{
+ DWORD nevents, nread;
+ INPUT_RECORD inrec;
+ ssize_t nret = 0;
+
+ do {
+
+ /* Stuff any unread buffered characters. */
+ while (buf_head < buf_tail && n > 0)
+ {
+ *inbuf++ = buffered_chars[buf_head++];
+ nret++;
+ n--;
+ }
+ if (n <= 0)
+ break;
+
+ /* Wait for input. */
+ while (GetNumberOfConsoleInputEvents (hstdin, &nevents)
+ && nevents < 1)
+ Sleep (20);
+
+ while (nevents-- && n > 0)
+ {
+ if (!ReadConsoleInput (hstdin, &inrec, 1, &nread))
+ return -1;
+
+ if (nread > 0)
+ {
+ switch (inrec.EventType)
+ {
+ case KEY_EVENT:
+ if (inrec.Event.KeyEvent.bKeyDown == TRUE
+ && !(inrec.Event.KeyEvent.wVirtualScanCode == 0
+ || inrec.Event.KeyEvent.wVirtualKeyCode == VK_SHIFT
+ || inrec.Event.KeyEvent.wVirtualKeyCode == VK_CONTROL
+ || inrec.Event.KeyEvent.wVirtualKeyCode == VK_MENU))
+ {
+ unsigned char keyseq[10];
+ int count = inrec.Event.KeyEvent.wRepeatCount;
+ unsigned char ch = inrec.Event.KeyEvent.uChar.AsciiChar;
+ WORD vkey = inrec.Event.KeyEvent.wVirtualKeyCode;
+ DWORD ctl_state = inrec.Event.KeyEvent.dwControlKeyState;
+ int nbytes = w32keyseq (ch, vkey, ctl_state, keyseq);
+
+ /* Supply up to N characters to the caller. */
+ while (count && n >= nbytes)
+ {
+ if (nbytes == 1 && keyseq[0] == '\032')
+ {
+ terminal_goto_xy (0, screenheight - 1);
+ terminal_clear_to_eol ();
+ fflush (stdout);
+ terminal_unprep_terminal ();
+ kill (getpid (), 0);
+ terminal_prep_terminal ();
+ reset_info_window_sizes ();
+ }
+ else
+ {
+ memcpy (&inbuf[nret], keyseq, nbytes);
+ nret += nbytes;
+ n -= nbytes;
+ }
+ count--;
+ }
+ /* Buffer the rest. */
+ if (count > 0)
+ {
+ buf_head = buf_tail = 0;
+ while (count--
+ && buf_tail < sizeof(buffered_chars) - nbytes)
+ {
+ memcpy (&buffered_chars[buf_tail], keyseq, nbytes);
+ buf_tail += nbytes;
+ }
+ }
+ }
+ break;
+ case WINDOW_BUFFER_SIZE_EVENT:
+ {
+ int rows, cols;
+
+ /* Note: this event is only sent when the console
+ window's _screen_buffer_ size is changed via
+ the Properties->Layout dialog. */
+ cols = inrec.Event.WindowBufferSizeEvent.dwSize.X;
+ rows = inrec.Event.WindowBufferSizeEvent.dwSize.Y;
+ screenwidth = cols;
+ screenheight = rows;
+ w32_set_screen_dimensions (cols, rows);
+ display_initialize_display (screenwidth, screenheight);
+ window_new_screen_size (screenwidth, screenheight);
+ redisplay_after_signal ();
+ }
+ break;
+ case MOUSE_EVENT:
+ {
+ /* Only vertical wheel support for now. */
+ int wheeled =
+ (inrec.Event.MouseEvent.dwEventFlags & MOUSE_WHEELED) != 0;
+ if (wheeled && mouse_protocol == MP_NORMAL_TRACKING)
+ {
+ extern void info_up_line (WINDOW *, int count);
+ extern void info_down_line (WINDOW *, int count);
+ extern WINDOW *active_window;
+
+ int hiword =
+ HIWORD (inrec.Event.MouseEvent.dwButtonState);
+
+ if ((hiword & 0xFF00) == 0)
+ info_up_line (active_window, 3);
+ else
+ info_down_line (active_window, 3);
+ display_update_display ();
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ } while (n > 0);
+ return nret;
+}
+
+long
+w32_chars_avail (int fd)
+{
+ if (w32_our_tty (fd))
+ return buf_tail - buf_head;
+ else
+ {
+ struct stat st;
+
+ if (fstat (fd, &st) < 0)
+ return 1;
+ else
+ return st.st_size;
+ }
+}
+
+ssize_t
+w32_read (int fd, void *buf, size_t n)
+{
+ if (w32_our_tty (fd))
+ return w32_kbd_read (buf, n);
+ else
+ return _read (fd, buf, n);
+}
+
+/* Write to the console a string of text encoded in UTF-8 or UTF-7. */
+static void
+write_utf (DWORD cp, const char *text, int nbytes)
+{
+ /* MSDN says UTF-7 requires zero in flags. */
+ DWORD flags = (cp == CP_UTF7) ? 0 : MB_ERR_INVALID_CHARS;
+ /* How much space do we need for wide characters? */
+ int wlen = MultiByteToWideChar (cp, flags, text, nbytes, NULL, 0);
+
+ if (wlen)
+ {
+ WCHAR *text_w = alloca (wlen * sizeof (WCHAR));
+ DWORD written;
+
+ if (MultiByteToWideChar (cp, flags, text, nbytes, text_w, wlen) > 0)
+ {
+ WriteConsoleW (hscreen, text_w, (nbytes < 0) ? wlen - 1 : wlen,
+ &written, NULL);
+ return;
+ }
+ }
+ /* Fall back on conio. */
+ if (nbytes < 0)
+ cputs (text);
+ else
+ cprintf ("%.*s", nbytes, text);
+}
+
+/* A replacement for nl_langinfo which does a more accurate job for
+ the console output codeset. Windows can use 3 different encodings
+ at the same time, and the Posix-compliant nl_langinfo simply
+ doesn't know enough to decide which one is needed when CODESET is
+ requested. */
+#undef nl_langinfo
+#include <langinfo.h>
+
+char *
+rpl_nl_langinfo (nl_item item)
+{
+ if (item == CODESET)
+ {
+ static char buf[100];
+
+ /* We need all the help we can get from GNU libiconv, so we
+ request transliteration as well. */
+ sprintf (buf, "CP%u//TRANSLIT", GetConsoleOutputCP ());
+ return buf;
+ }
+ else
+ return nl_langinfo (item);
+}
+
+#ifndef HAVE_WCWIDTH
+/* A replacement for wcwidth. The Gnulib version calls setlocale for
+ every character Info is about to display, which makes display of
+ large nodes annoyingly slow.
+
+ Note that the Gnulib version is still compiled and put into
+ libgnu.a, because the configure script doesn't know about this
+ replacement. But the linker will not pull the Gnulib version into
+ the binary, because it resolves the calls to this replacement
+ function. */
+int
+wcwidth (wchar_t wc)
+{
+ return wc == 0 ? 0 : iswprint (wc) ? 1 : -1;
+}
+#endif
+
+#endif /* _WIN32 */
+
+/* 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);
+}
+
+/* The implementation of the underlined text. The DOS/Windows console
+ doesn't support underlined text (until Win10), so we make it blue instead
+ (blue, because this face is used for hyperlinks). */
+static void
+pc_begin_underline (void)
+{
+ if (xref_attr != COMMON_LVB_UNDERSCORE)
+ textattr (xref_attr);
+ else
+ underline ();
+}
+
+static void
+pc_end_underline (void)
+{
+ if (xref_attr != COMMON_LVB_UNDERSCORE)
+ textattr (norm_attr);
+ else
+ normvideo ();
+}
+
+/* Standout (a.k.a. "high video") text. */
+static void
+pc_begin_standout (void)
+{
+ highvideo ();
+}
+
+static void
+pc_end_standout (void)
+{
+ normvideo ();
+}
+
+static void
+pc_begin_blink (void)
+{
+ blinkvideo ();
+}
+
+static void
+pc_default_color (void)
+{
+ textattr (norm_attr);
+}
+
+/* Info definitions of 8 colors (see terminal.h) are in an order
+ that's different from Windows/DOS console colors. This function
+ unscrambles the order, and also maps 8 standard ANSI colors to the
+ low-intensity shades of the 16 PC colors, so that "standout" works
+ by turning the intensity bit. */
+static int
+convert_color (int terminal_color)
+{
+ /* The terminal.h order is:
+ black, red, green, yellow, blue, magenta, cyan, white. */
+ static int pc_color_map[] = {
+ 0, 4, 2, 6, 1, 5, 3, 7
+ };
+ int intensity = terminal_color & (FOREGROUND_INTENSITY | BACKGROUND_INTENSITY);
+ terminal_color &= ~(FOREGROUND_INTENSITY | BACKGROUND_INTENSITY);
+
+ if (terminal_color >= 0
+ && terminal_color < sizeof(pc_color_map) / sizeof (pc_color_map[0]))
+ return pc_color_map[terminal_color] | intensity;
+ return 7; /* lightgray */
+}
+
+static void
+pc_set_fg_color (int color)
+{
+ textcolor (convert_color (color) | (norm_attr & FOREGROUND_INTENSITY));
+}
+
+static void
+pc_set_bg_color (int color)
+{
+ textbackground (convert_color (color) | (norm_attr & BACKGROUND_INTENSITY));
+}
+
+/* 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);
+#ifdef __MINGW32__
+ else if (hscreen == INVALID_HANDLE_VALUE)
+ fputs (string, stdout);
+ else if (output_cp == CP_UTF8 || output_cp == CP_UTF7)
+ write_utf (output_cp, string, -1);
+#endif
+ 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);
+#ifdef __MINGW32__
+ else if (hscreen == INVALID_HANDLE_VALUE)
+ printf ("%.*s", nchars, string);
+ else if (output_cp == CP_UTF8 || output_cp == CP_UTF7)
+ write_utf (output_cp, string, nchars);
+#endif
+ 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;
+
+#ifdef _WIN32
+ w32_info_prep ();
+#endif
+
+ /* 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;
+
+#ifdef _WIN32
+ w32_info_unprep ();
+#endif
+
+ 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);
+ }
+#ifdef __MSDOS__
+ else
+ pc_clear_to_eol (); /* for text attributes to really take effect */
+#endif
+#ifdef _WIN32
+ if (hscreen != INVALID_HANDLE_VALUE)
+ SetConsoleScreenBufferSize (hstdout, outside_info.bufsize);
+#endif
+
+ /* 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. 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)
+#ifdef __MSDOS__
+ term_name = "pc-dos"; /* ``what's in a name?'' */
+#endif
+#ifdef _WIN32
+ term_name = "w32console";
+#endif
+ }
+
+ /* 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));
+#ifdef __MSDOS__
+ xref_attr = CYAN;
+#endif
+#ifdef _WIN32
+ xref_attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY;
+#endif
+ xref_attr |= outside_info.normattr & 0xf0;
+
+ /* 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;
+ xref_attr = (xref_attr & 0x0f) | (norm_attr & 0xf0);
+ color_desc = strtoul (endp + 1, &endp, 0);
+ if (color_desc <= UCHAR_MAX)
+ inv_attr = (unsigned char)color_desc;
+#ifdef _WIN32
+ if (*endp == 'u')
+ xref_attr = COMMON_LVB_UNDERSCORE;
+ else
+#endif
+ if (*endp != '\0')
+ {
+ color_desc = strtoul (endp + 1, &endp, 0);
+ if (color_desc <= UCHAR_MAX)
+ {
+#ifdef _WIN32
+ if (*endp == 'u')
+ color_desc |= COMMON_LVB_UNDERSCORE;
+ xref_attr = (WORD)color_desc;
+#else
+ xref_attr = (unsigned char)color_desc;
+#endif
+ }
+ }
+ }
+ }
+
+ /* 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 are *certainly* NOT dumb! */
+ terminal_is_dumb_p = 0;
+
+ pc_get_screen_size ();
+
+#ifdef __MINGW32__
+ /* Record the screen output codepage. */
+ output_cp = GetConsoleOutputCP ();
+#endif
+
+#ifdef __MSDOS__
+ /* 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);
+
+ term_kh = (char *)find_sequence (K_Home);
+ term_ke = (char *)find_sequence (K_End);
+ term_ki = (char *)find_sequence (K_Insert);
+ term_kD = (char *)find_sequence (K_Delete);
+#elif defined _WIN32
+ term_kh = "\033<";
+ term_ke = "\033>";
+ term_ki = "\033[L";
+#endif /* __MSDOS__ */
+
+ /* Set all the hooks to our PC-specific functions. */
+ terminal_begin_inverse_hook = pc_begin_inverse;
+ terminal_end_inverse_hook = pc_end_inverse;
+ terminal_begin_standout_hook = pc_begin_standout;
+ terminal_end_standout_hook = pc_end_standout;
+ terminal_begin_underline_hook = pc_begin_underline;
+ terminal_end_underline_hook = pc_end_underline;
+ terminal_begin_bold_hook = pc_begin_standout;
+ terminal_begin_blink_hook = pc_begin_blink;
+ terminal_end_all_modes_hook = pc_default_color;
+ terminal_default_colour_hook = pc_default_color;
+ terminal_set_colour_hook = pc_set_fg_color;
+ terminal_set_bgcolour_hook = pc_set_bg_color;
+ 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"},
+
+ {K_Home, "\033[H"}, /* ...and these are for moving IN a node */
+ {K_End, "\033[F"}, /* they're Numeric-Keypad-Keys, so */
+ {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>"},
+
+ {K_EHome, "\033[H"}, /* these are also for moving IN a node */
+ {K_EEnd, "\033[F"}, /* they're the "extended" (Grey) keys */
+ {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) */
+ {K_Insert, "\033[L"},
+ {K_EInsert, "\033[L"},
+
+ /* 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"
+
+#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);
+ exit (EXIT_FAILURE);
+ case SIGUSR1:
+ /* Simulate SIGTSTP by invoking a subsidiary shell. */
+#ifndef _WIN32
+ pc_goto_xy (0, outside_info.screenheight - 1);
+ pc_clear_to_eol ();
+ pc_write_chars (stopped_msg, sizeof (stopped_msg) - 1);
+#endif
+
+ /* 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);
+#ifdef _WIN32
+ {
+ const char *argv[2];
+ const char *shell = NULL;
+
+ argv[0] = NULL;
+ shell = getenv ("SHELL");
+ if (!shell)
+ {
+ shell = getenv ("COMSPEC");
+ if (!shell)
+ return -1;
+ argv[0] = " /k";
+ }
+ argv[1] = NULL;
+ _spawnvp (_P_WAIT, shell, argv);
+ }
+#else
+ system ("");
+#endif
+ 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. */
+
+int tputs (const char *a, int b, int (*c)(int))
+{
+ perror ("tputs"); return 0; /* here and below, added dummy retvals */
+}
+
+char* tgoto (const char *a, int b, int c)
+{
+ perror ("tgoto"); return 0;
+}
+
+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, const 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/macros/texinfo/texinfo/info/pseudotty.c b/macros/texinfo/texinfo/info/pseudotty.c
new file mode 100644
index 0000000000..9ccbd36274
--- /dev/null
+++ b/macros/texinfo/texinfo/info/pseudotty.c
@@ -0,0 +1,208 @@
+/* pseudotty - open pseudo-terminal and print name of slave device to
+ standard output. Read and ignore any data sent to terminal. This
+ is so we can run tests interactively without messing up the screen.
+
+ Copyright 2014-2019 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 Gavin Smith. */
+
+#define _XOPEN_SOURCE 600
+/* for posix_openpt */
+
+#include <config.h>
+#include <errno.h>
+#include <error.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/select.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if defined __sun || defined __hpux /* Solaris, HP-UX */
+#include <stropts.h>
+#endif
+
+#include "termdep.h"
+
+/* Used by "error" function. */
+const char *program_name = "pseudotty";
+
+int
+main (int argc, char *argv[])
+{
+ int master, slave, control;
+ char *name;
+ fd_set read_set;
+
+ error (0, 0, "getting pty master fd");
+ master = posix_openpt (O_RDWR);
+ if (master == -1)
+ exit (1);
+
+ error (0, 0, "unlocking slave device");
+ if (grantpt (master) < 0 || unlockpt (master) < 0)
+ exit (1);
+ error (0, 0, "getting file name of slave device...");
+ name = ptsname (master);
+ if (!name)
+ exit (1);
+ error (0, 0, "%s", name);
+
+ error (0, 0, "opening slave device");
+ slave = open (name, O_RDWR);
+ if (slave == -1)
+ exit (1);
+
+#if defined __sun || defined __hpux /* Solaris, HP-UX */
+ if (isastream (slave))
+ {
+ error (0, 0, "performing STREAMS ioctl's on slave");
+ if (ioctl (slave, I_PUSH, "ptem") < 0
+ || ioctl (slave, I_PUSH, "ldterm") < 0
+# if defined __sun
+ || ioctl (slave, I_PUSH, "ttcompat") < 0
+# endif
+ )
+ error (1, 0, "STREAMS ioctl's failed");
+ }
+#endif
+
+#if defined (HAVE_TERMIOS_H)
+ {
+ struct termios t;
+ long int disable;
+ disable = fpathconf (slave, _PC_VDISABLE);
+ if (tcgetattr (slave, &t) == -1)
+ error (0, 0, "error calling tcgetattr");
+ else
+ {
+ t.c_cc[VSTART] = disable; /* C-q */
+ t.c_cc[VSTOP] = disable; /* C-s */
+ t.c_cc[VKILL] = disable; /* C-u */
+ t.c_cc[VINTR] = disable; /* C-c */
+ t.c_lflag &= (~ICANON & ~ECHO);
+ t.c_cc[VMIN] = 1;
+ t.c_cc[VTIME] = 0;
+ if (tcsetattr (slave, TCSANOW, &t) == -1)
+ error (0, 0, "error calling tcsetattr");
+ }
+ }
+#endif
+
+#if defined (TIOCSWINSZ)
+ {
+ struct winsize ws;
+ ws.ws_col = ws.ws_row = 0;
+
+ error (0, 0, "attempting to set window size");
+ if (ioctl (master, TIOCSWINSZ, &ws) == 0)
+ error (0, 0, "...succeeded");
+ else
+ error (0, 0, "...failed");
+ }
+#endif
+
+ printf ("%s\n", name);
+ if (fclose (stdout) != 0)
+ error (1, 0, "error closing stdout: aborting");
+
+ error (0, 0, "opening control channel");
+ control = open (argv[1], O_RDONLY);
+ if (control == -1)
+ error (1, 0, "error opening control channel: aborting");
+
+
+ FD_ZERO (&read_set);
+
+ error (0, 0, "entering main loop");
+ while (1)
+ {
+ FD_SET (master, &read_set);
+ FD_SET (control, &read_set);
+
+ select (FD_SETSIZE, &read_set, 0, 0, 0);
+
+ if (FD_ISSET (control, &read_set))
+ {
+ char c;
+ int success;
+ errno = 0;
+ while (1)
+ {
+ error (0, 0, "trying to read");
+ success = read (control, &c, 1);
+ if (success < 0)
+ {
+ if (errno != EINTR)
+ error (1, errno, "read error on control channel");
+ }
+ else if (success == 0)
+ {
+ error (1, 0, "end of file on control channel");
+ }
+ else if (success == 1)
+ {
+ error (0, 0, "read byte 0x%02X", c);
+ break;
+ }
+ }
+
+ /* Feed any read bytes to the program being controlled. */
+ do
+ {
+ success = write (master, &c, 1);
+ if (success == 0)
+ {
+ error (0, 0, "couldn't send byte!");
+ sleep (1);
+ continue;
+ }
+ }
+ while (success == -1 && errno == EINTR);
+
+ if (success != 1)
+ {
+ /* The controlled process has probably exited, or been killed. */
+ error (0, 0, "couldn't send byte (giving up)");
+ sleep (1);
+ }
+ }
+
+ if (FD_ISSET (master, &read_set))
+ {
+ char c;
+ int success;
+ errno = 0;
+ do
+ {
+ success = read (master, &c, 1);
+ }
+ while (success == -1 && errno == EINTR);
+
+ if (success == -1)
+ {
+ /* The controlled process has probably exited, or been killed. */
+ error (0, 0, "read error on master fd");
+ sleep (1);
+ }
+ }
+ }
+
+ return 0; /* NOTREACHED */
+}
diff --git a/macros/texinfo/texinfo/info/search.c b/macros/texinfo/texinfo/info/search.c
new file mode 100644
index 0000000000..9f1d5e26f7
--- /dev/null
+++ b/macros/texinfo/texinfo/info/search.c
@@ -0,0 +1,746 @@
+/* search.c -- searching large bodies of text.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+#include <regex.h>
+
+#include "session.h"
+#include "info-utils.h"
+#include "search.h"
+
+
+/* **************************************************************** */
+/* */
+/* 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. */
+enum search_result
+search (char *string, SEARCH_BINDING *binding, long *poff)
+{
+ enum search_result result;
+
+ /* If the search is backwards, then search backwards, otherwise forwards. */
+ if (binding->start > binding->end)
+ result = search_backward (string, binding, poff);
+ else
+ result = search_forward (string, binding, poff);
+
+ return result;
+}
+
+/* Expand \n and \t in regexp to newlines and tabs */
+static char *
+regexp_expand_newlines_and_tabs (char *regexp)
+{
+ char *unescaped_regexp = xmalloc (1 + strlen (regexp));
+ char *p, *q;
+
+ 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';
+
+ return unescaped_regexp;
+}
+
+/* Escape any special characters in SEARCH_STRING. */
+static char *
+regexp_escape_string (char *search_string)
+{
+ char *special_chars = "\\[]^$.*(){}|+?";
+ char *p, *q;
+
+ char *escaped_string = xmalloc (strlen (search_string) * 2 + 1);
+
+ for (p = search_string, q = escaped_string; *p != '\0'; )
+ {
+ if (strchr (special_chars, *p))
+ {
+ *q++ = '\\';
+ }
+ *q++ = *p++;
+ }
+
+ *q = '\0';
+
+ return escaped_string;
+}
+
+
+static void
+extend_matches (MATCH_STATE *state)
+{
+ regmatch_t *matches = state->matches;
+ size_t match_alloc = state->match_alloc;
+ size_t match_count = state->match_count;
+ char *buffer = state->buffer;
+ size_t buflen = state->buflen;
+
+ regoff_t offset = 0;
+ char saved_char;
+ size_t initial_match_count = match_count;
+
+ if (state->finished)
+ return;
+
+ saved_char = buffer[buflen];
+ buffer[buflen] = '\0';
+
+ if (match_count > 0)
+ {
+ offset = matches[match_count - 1].rm_eo;
+
+ /* move past zero-length match */
+ if (offset == matches[match_count - 1].rm_so)
+ offset++;
+ }
+
+ while (offset < buflen && match_count < initial_match_count + 5)
+ {
+ int result = 0;
+ regmatch_t m;
+
+ result = regexec (&state->regex, &buffer[offset], 1, &m, REG_NOTBOL);
+ if (result == 0)
+ {
+ if (match_count == match_alloc)
+ {
+ /* The match list is full. */
+ if (match_alloc == 0)
+ match_alloc = 50;
+ matches = x2nrealloc
+ (matches, &match_alloc, sizeof matches[0]);
+ }
+
+ matches[match_count] = m;
+ matches[match_count].rm_so += offset;
+ matches[match_count].rm_eo += offset;
+ offset = matches[match_count++].rm_eo;
+
+ if (m.rm_eo == 0)
+ offset++; /* Avoid finding match again for a pattern of "$". */
+ }
+ else
+ {
+ state->finished = 1;
+ break;
+ }
+ }
+ buffer[buflen] = saved_char;
+
+ state->matches = matches;
+ state->match_alloc = match_alloc;
+ state->match_count = match_count;
+}
+
+/* Search BUFFER for REGEXP. If matches are found, pass back the list of
+ matches in MATCH_STATE. */
+enum search_result
+regexp_search (char *regexp, int is_literal, int is_insensitive,
+ char *buffer, size_t buflen,
+ MATCH_STATE *match_state)
+{
+ regex_t preg; /* Compiled pattern buffer for regexp. */
+ int result;
+ char *regexp_str;
+
+ if (!is_literal)
+ regexp_str = regexp_expand_newlines_and_tabs (regexp);
+ else
+ regexp_str = regexp_escape_string (regexp);
+
+ result = regcomp (&preg, regexp_str,
+ REG_EXTENDED | REG_NEWLINE
+ | (is_insensitive ? REG_ICASE : 0));
+ free (regexp_str);
+
+ 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);
+ free (buf);
+ return search_invalid;
+ }
+
+ match_state->matches = 0;
+ match_state->match_count = 0;
+ match_state->match_alloc = 0;
+ match_state->finished = 0;
+ match_state->regex = preg;
+ match_state->buffer = buffer;
+ match_state->buflen = buflen;
+
+ extend_matches (match_state);
+
+ if (match_state->match_count == 0)
+ {
+ free_matches (match_state);
+ return search_not_found;
+ }
+ else
+ return search_success;
+}
+
+/* Search forwards for STRING through the text delimited in BINDING. */
+enum search_result
+search_forward (char *string, SEARCH_BINDING *binding, long *poff)
+{
+ 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;
+ *poff = buff - binding->buffer;
+ return search_success;
+ }
+
+ buff++;
+ }
+
+ if (alternate)
+ free (alternate);
+
+ return search_not_found;
+}
+
+/* Search for STRING backwards through the text delimited in BINDING. */
+enum search_result
+search_backward (char *input_string, SEARCH_BINDING *binding, long *poff)
+{
+ 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;
+ *poff = 1 + buff - binding->buffer;
+ return search_success;
+ }
+
+ buff--;
+ }
+
+ free (string);
+ if (alternate)
+ free (alternate);
+
+ return search_not_found;
+}
+
+/* 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;
+ long offset;
+
+ /* 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;
+
+ if (search_forward (string, &binding, &offset) == search_success)
+ return offset;
+ return -1;
+}
+
+/* Return non-zero if STRING is the first text to appear at BINDING. */
+int
+looking_at (char *string, SEARCH_BINDING *binding)
+{
+ long search_end;
+
+ if (search (string, binding, &search_end) != search_success)
+ return 0;
+
+ /* 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;
+}
+
+/* Return non-zero if POINTER is looking at the text at STRING before an
+ end-of-line. */
+int
+looking_at_line (char *string, char *pointer)
+{
+ int len;
+
+ len = strlen (string);
+ if (strncasecmp (pointer, string, len) != 0)
+ return 0;
+
+ pointer += len;
+ if (*pointer == '\n' || !strncmp (pointer, "\r\n", 2)
+ || *pointer == '\0')
+ return 1;
+ return 0;
+}
+
+/* **************************************************************** */
+/* */
+/* Accessing matches */
+/* */
+/* **************************************************************** */
+/* Search forwards or backwards for entries in MATCHES that start within
+ the search area. The search is forwards if DIR > 0, backward if
+ DIR < 0. Return index of match in *MATCH_INDEX. */
+enum search_result
+match_in_match_list (MATCH_STATE *match_state,
+ long start, long end, int dir,
+ int *match_index)
+{
+ regmatch_t *matches = match_state->matches;
+ size_t match_count = match_state->match_count;
+
+ int i;
+ int index = -1;
+
+ for (i = 0; i < match_count || !match_state->finished; i++)
+ {
+ /* get more matches as we need them */
+ if (i == match_count)
+ {
+ extend_matches (match_state);
+ matches = match_state->matches;
+ match_count = match_state->match_count;
+
+ if (i == match_count)
+ break;
+ }
+
+ if (matches[i].rm_so >= end)
+ break; /* No more matches found in search area. */
+
+ if (matches[i].rm_so >= start)
+ {
+ index = i;
+ if (dir > 0)
+ {
+ *match_index = index;
+ return search_success;
+ }
+ }
+ }
+
+ if (index != -1)
+ {
+ *match_index = index;
+ return search_success;
+ }
+
+ /* not found */
+ return search_not_found;
+}
+
+/* Return match INDEX in STATE. INDEX must be a valid index. */
+regmatch_t
+match_by_index (MATCH_STATE *state, int index)
+{
+ while (state->match_alloc <= index)
+ extend_matches (state);
+ return state->matches[index];
+}
+
+/* Free and clear all data in STATE. */
+void
+free_matches (MATCH_STATE *state)
+{
+ free (state->matches);
+ state->matches = 0;
+ state->match_count = state->match_alloc = state->finished = 0;
+ state->buffer = 0; /* do not free as it is kept elsewhere */
+ state->buflen = 0;
+ regfree (&state->regex);
+}
+
+int
+matches_ready (MATCH_STATE *state)
+{
+ return state->matches ? 1 : 0;
+}
+
+/* Starting at index *MATCH_INDEX, decide if we are inside a match
+ in MATCHES at offset OFF. The matches are assumed not to overlap
+ and to be in order. */
+void
+decide_if_in_match (long off, int *in_match,
+ MATCH_STATE *matches, size_t *match_index)
+{
+ size_t i = *match_index;
+ int m = *in_match;
+
+ for (; !at_end_of_matches (matches, i); i++)
+ {
+ if (match_by_index (matches, i).rm_so > off)
+ break;
+
+ m = 1;
+
+ if (match_by_index (matches, i).rm_eo > off)
+ break;
+
+ m = 0;
+ }
+
+ *match_index = i;
+ *in_match = m;
+}
+
+/* Used for iterating through a match list. */
+int
+at_end_of_matches (MATCH_STATE *state, int index)
+{
+ if (index < state->match_count)
+ return 0;
+ else
+ {
+ if (!state->finished)
+ extend_matches (state);
+
+ if (state->finished)
+ return (state->match_count == index) ? 1 : 0;
+ else
+ return 0;
+ }
+}
+
+
+
+/* **************************************************************** */
+/* */
+/* 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;
+}
+
+/* **************************************************************** */
+/* */
+/* Searching FILE_BUFFER's */
+/* */
+/* **************************************************************** */
+
+/* Return the absolute position of the first occurence of a node separator
+ starting in BINDING->buffer between BINDING->start and BINDING->end
+ inclusive. Return -1 if no node separator was found. */
+long
+find_node_separator (SEARCH_BINDING *binding)
+{
+ register long i;
+ char *body;
+ int dir;
+
+ body = binding->buffer;
+ dir = binding->start < binding->end ? 1 : -1;
+
+ /* A node is started by [^L]^_[^L][\r]\n. That is to say, the C-l's are
+ optional, but the US 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). */
+ i = binding->start;
+ while (1)
+ {
+ /* Note that bytes are read in order from the buffer, so if at any
+ point a null byte is encountered signifying the end of the buffer,
+ no more bytes will be read past that point. */
+ if (body[i] == INFO_COOKIE)
+ {
+ int j = i + 1;
+
+ if (body[j] == INFO_FF)
+ j++;
+ if (body[j] == '\r')
+ j++;
+
+ if (body[j] == '\n')
+ return i;
+ }
+
+ if (i == binding->end)
+ break;
+ i += dir;
+ }
+
+ return -1;
+}
+
+/* Return the length of the node separator characters that BODY is currently
+ pointing at. If it's not pointing at a node separator, return 0. */
+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] == '\r')
+ i++;
+
+ if (body[i++] != '\n')
+ return 0;
+
+ return i;
+}
+
+/* Return the absolute position of the beginning of a section in this file
+ whose first line is LABEL, starting the search at binding->start. */
+long
+find_file_section (SEARCH_BINDING *binding, char *label)
+{
+ SEARCH_BINDING s;
+ long position;
+ int dir;
+
+ s.buffer = binding->buffer;
+ s.start = binding->start;
+ s.end = binding->end;
+ s.flags = S_FoldCase;
+ dir = binding->start < binding->end ? 1 : -1;
+
+ while ((position = find_node_separator (&s)) != -1 )
+ {
+ long offset = position;
+ offset += skip_node_separator (s.buffer + offset);
+ if (looking_at_line (label, s.buffer + offset))
+ return position;
+
+ if (dir > 0)
+ {
+ s.start = offset;
+ if (s.start >= s.end)
+ break;
+ }
+ else
+ {
+ s.start = position - 1;
+ if (s.start <= s.end)
+ break;
+ }
+ }
+ 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;
+ SEARCH_BINDING s;
+
+ s.buffer = binding->buffer;
+ s.start = binding->start;
+ s.end = binding->end;
+ s.flags = 0;
+
+ while ((position = find_node_separator (&s)) != -1)
+ {
+ char *nodename_start;
+ char *read_nodename;
+ int found;
+
+ s.start = position;
+ s.start += skip_node_separator (s.buffer + s.start);
+
+ offset = string_in_line (INFO_NODE_LABEL, s.buffer + s.start);
+
+ if (offset == -1)
+ continue;
+
+ s.start += offset;
+ s.start += skip_whitespace (s.buffer + s.start);
+ nodename_start = s.buffer + s.start;
+ read_quoted_string (nodename_start, "\n\r\t,", 0, &read_nodename);
+ if (!read_nodename)
+ return -1;
+
+ found = !strcmp (read_nodename, nodename);
+ free (read_nodename);
+
+ if (found)
+ return position;
+ }
+ return -1;
+}
diff --git a/macros/texinfo/texinfo/info/search.h b/macros/texinfo/texinfo/info/search.h
new file mode 100644
index 0000000000..16feabdaf7
--- /dev/null
+++ b/macros/texinfo/texinfo/info/search.h
@@ -0,0 +1,94 @@
+/* search.h -- Structure used to search large bodies of text, with bounds.
+
+ Copyright 1993-2019 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. */
+
+/* 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
+
+#include "window.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. */
+
+enum search_result
+ {
+ search_success,
+ search_not_found,
+ search_invalid
+ };
+
+enum search_result search_forward (char *string,
+ SEARCH_BINDING *binding, long *poff);
+enum search_result search_backward (char *input_string,
+ SEARCH_BINDING *binding,
+ long *poff);
+enum search_result search (char *string, SEARCH_BINDING *binding,
+ long *poff);
+enum search_result regexp_search (char *regexp,
+ int is_literal, int is_insensitive,
+ char *buffer, size_t buflen,
+ MATCH_STATE *match_state);
+int looking_at (char *string, SEARCH_BINDING *binding);
+int looking_at_line (char *string, char *pointer);
+
+/* Note that STRING_IN_LINE () always returns the offset of the 1st character
+ after the string. */
+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. */
+int skip_whitespace (char *string);
+int skip_non_whitespace (char *string);
+int skip_whitespace_and_newlines (char *string);
+int skip_node_separator (char *body);
+
+long find_node_separator (SEARCH_BINDING *binding);
+long find_file_section (SEARCH_BINDING *binding, char *label);
+long find_node_in_binding (char *nodename, SEARCH_BINDING *binding);
+
+regmatch_t match_by_index (MATCH_STATE *state, int index);
+enum search_result match_in_match_list (MATCH_STATE *state,
+ long start, long end, int dir, int *match_index);
+
+void free_matches (MATCH_STATE *state);
+int matches_ready (MATCH_STATE *state);
+int at_end_of_matches (MATCH_STATE *state, int index);
+void decide_if_in_match (long off, int *in_match, MATCH_STATE *matches,
+ size_t *match_index);
+
+#endif /* not INFO_SEARCH_H */
diff --git a/macros/texinfo/texinfo/info/session.c b/macros/texinfo/texinfo/info/session.c
new file mode 100644
index 0000000000..694522ed4b
--- /dev/null
+++ b/macros/texinfo/texinfo/info/session.c
@@ -0,0 +1,5703 @@
+/* session.c -- user windowing interface to Info.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+#include "display.h"
+#include "session.h"
+#include "echo-area.h"
+#include "search.h"
+#include "footnotes.h"
+#include "man.h"
+#include "variables.h"
+
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef __MINGW32__
+# define read(f,b,s) w32_read(f,b,s)
+# define _read(f,b,s) w32_read(f,b,s)
+extern ssize_t w32_read (int, void *, size_t);
+#endif
+
+#if defined (HAVE_SYS_TIME_H)
+# include <sys/time.h>
+# define HAVE_STRUCT_TIMEVAL
+#endif /* HAVE_SYS_TIME_H */
+
+/* **************************************************************** */
+/* */
+/* Running an Info Session */
+/* */
+/* **************************************************************** */
+
+static void mouse_event_handler (void);
+
+/* The place that we are reading input from. */
+static FILE *info_input_stream = NULL;
+
+/* Becomes non-zero when 'q' is typed to an Info window. */
+static int quit_info_immediately = 0;
+
+static NODE *allfiles_node = 0;
+
+DECLARE_INFO_COMMAND (info_all_files, _("Show all matching files"))
+{
+ if (!allfiles_node)
+ {
+ info_error (_("No file index"));
+ return;
+ }
+
+ /* FIXME: Copy allfiles_node so it is unique in the window history? */
+ info_set_node_of_window (window, allfiles_node);
+}
+
+static void
+allfiles_create_node (char *term, REFERENCE **fref)
+{
+ int i;
+ struct text_buffer text;
+
+ text_buffer_init (&text);
+
+ text_buffer_printf (&text,
+ "%s File names matching '%s'\n\n"
+ "Info File Index\n"
+ "***************\n\n"
+ "File names that match '%s':\n",
+ INFO_NODE_LABEL,
+ term, term);
+
+ /* Mark as an index so that destinations are never hidden. */
+ text_buffer_add_string (&text, "\0\b[index\0\b]", 11);
+ text_buffer_printf (&text, "\n* Menu:\n\n");
+
+ for (i = 0; fref[i]; i++)
+ {
+ text_buffer_printf (&text, "* %4i: (%s)", i+1, fref[i]->filename);
+ if (fref[i]->nodename)
+ text_buffer_printf (&text, "%s", fref[i]->nodename);
+ text_buffer_printf (&text, ".\n");
+ }
+
+ allfiles_node = info_create_node ();
+ allfiles_node->fullpath = xstrdup ("");
+ allfiles_node->nodename = xstrdup ("*Info File Index*");
+ allfiles_node->contents = text_buffer_base (&text);
+ allfiles_node->nodelen = text_buffer_off (&text);
+ allfiles_node->body_start = strcspn (allfiles_node->contents, "\n");
+
+ scan_node_contents (allfiles_node, 0, 0);
+}
+
+/* Begin an info session finding the nodes specified by REFERENCES. For
+ each loaded node, create a new window. Always split the largest of the
+ available windows. Display ERROR in echo area if non-null. */
+static void
+begin_multiple_window_info_session (REFERENCE **references, char *error)
+{
+ register int i;
+ WINDOW *window = 0;
+
+ for (i = 0; references && references[i]; i++)
+ {
+ if (!window)
+ {
+ window = active_window;
+ info_select_reference (window, references[i]);
+ if (!window->node)
+ window = 0;
+ }
+ 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 ();
+ info_error ("%s", msg_cant_find_window);
+ return;
+ }
+
+ active_window = largest;
+ window = window_make_window ();
+ info_select_reference (window, references[i]);
+
+ if (!window->node)
+ {
+ /* We couldn't find the node referenced. */
+ window_delete_window (window);
+ window = 0;
+ }
+
+ if (window)
+ window_tile_windows (TILE_INTERNALS);
+ else
+ {
+ display_update_display ();
+ info_error ("%s", msg_win_too_small);
+ return;
+ }
+ }
+ }
+
+ /* Load dir node as a back-up if there were no references given, or if
+ none of them were valid. */
+ if (!window)
+ {
+ info_set_node_of_window (active_window, get_dir_node ());
+ return;
+ }
+}
+
+static void
+display_startup_message (void)
+{
+ char *format;
+
+ format = replace_in_documentation
+ /* TRANSLATORS: Try to keep this message (when "expanded") at most 79
+ characters; anything after the 79th character will not actually be
+ displayed on an 80-column terminal. */
+(_("Welcome to Info version %s. Type \\[get-help-window] for help, \\[get-info-help-node] for tutorial."),
+ 0);
+
+ window_message_in_echo_area (format, VERSION, NULL);
+}
+
+/* Run an Info session. If USER_FILENAME is null, create a window for each
+ node referenced in REF_LIST. If USER_FILENAME is not null, "--all" was
+ used on the command line, and display a file index with entries in
+ REF_LIST. ERROR is an optional error message to display at start-up. */
+void
+info_session (REFERENCE **ref_list, char *user_filename, char *error)
+{
+ /* Initialize the Info session. */
+ initialize_info_session ();
+
+ if (!error)
+ display_startup_message ();
+ else
+ show_error_node (error);
+
+ if (!user_filename)
+ begin_multiple_window_info_session (ref_list, error);
+ else
+ {
+ allfiles_create_node (user_filename, ref_list);
+ info_set_node_of_window (active_window, allfiles_node);
+ }
+
+ info_read_and_dispatch ();
+ close_info_session ();
+}
+
+void info_next_line (WINDOW *, int count);
+void info_prev_line (WINDOW *, int count);
+static int info_keyseq_displayed_p;
+
+void
+info_read_and_dispatch (void)
+{
+ VFunction *cmd;
+ int count;
+
+ for (quit_info_immediately = 0; !quit_info_immediately; )
+ {
+ if (!info_any_buffered_input_p ())
+ display_update_display ();
+
+ /* Some redisplay might be necessary if the cursor has moved and
+ a different reference (or no reference) has to be highlighted. */
+ if (hl_ref_rendition.mask)
+ display_update_one_window (active_window);
+
+ display_cursor_at_point (active_window);
+
+ cmd = read_key_sequence (info_keymap, 1, 1, 0, &count);
+ if (cmd)
+ {
+ if (!info_keyseq_displayed_p)
+ window_clear_echo_area ();
+
+ (*cmd) (active_window, count);
+
+ /* Don't change the goal column when going up and down. This
+ means we can go from a long line to a short line and back to
+ a long line and end back in the same column. */
+ if (!(cmd == &info_next_line || cmd == &info_prev_line))
+ active_window->goal_column = -1; /* Goal is current column. */
+ }
+ }
+}
+
+/* Found in signals.c */
+extern void initialize_info_signal_handler (void );
+
+/* Initialize terminal, read configuration file and set key bindings. */
+void
+initialize_terminal_and_keymaps (char *init_file)
+{
+ char *term_name = getenv ("TERM");
+ terminal_initialize_terminal (term_name);
+ read_init_file (init_file);
+}
+
+/* Initialize the first info session by starting the terminal, window,
+ and display systems. */
+void
+initialize_info_session (void)
+{
+ if (!terminal_prep_terminal ())
+ {
+ /* Terminal too dumb to run interactively. */
+ char *term_name = getenv ("TERM");
+ info_error (msg_term_too_dumb, term_name);
+ exit (EXIT_FAILURE);
+ }
+
+ terminal_clear_screen ();
+
+ window_initialize_windows (screenwidth, screenheight);
+ initialize_info_signal_handler ();
+ display_initialize_display (screenwidth, screenheight);
+
+ /* 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;
+}
+
+/* On program exit, leave the cursor at the bottom of the window, and
+ restore the terminal I/O. */
+void
+close_info_session (void)
+{
+ terminal_goto_xy (0, screenheight - 1);
+ terminal_clear_to_eol ();
+ fflush (stdout);
+ terminal_unprep_terminal ();
+ close_dribble_file ();
+}
+
+/* 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;
+}
+
+
+/* **************************************************************** */
+/* */
+/* Input Character Buffering */
+/* */
+/* **************************************************************** */
+
+static void fill_input_buffer (int wait);
+static int info_gather_typeahead (int);
+
+/* Largest number of characters that we can read in advance. */
+#define MAX_INFO_INPUT_BUFFERING 512
+
+static int pop_index = 0; /* Where to remove bytes from input buffer. */
+static int push_index = 0; /* Where to add bytes to input buffer. */
+static unsigned char info_input_buffer[MAX_INFO_INPUT_BUFFERING];
+
+/* 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
+get_byte_from_input_buffer (unsigned char *key)
+{
+ if (push_index == pop_index)
+ return 0;
+
+ *key = info_input_buffer[pop_index++];
+
+ if (pop_index >= MAX_INFO_INPUT_BUFFERING)
+ pop_index = 0;
+
+ return 1;
+}
+
+int
+info_any_buffered_input_p (void)
+{
+ fill_input_buffer (0);
+ return push_index != pop_index;
+}
+
+/* Wrapper around info_gather_typeahead which handles read errors and reaching
+ end-of-file. */
+static void
+fill_input_buffer (int wait)
+{
+ while (1)
+ {
+ int success;
+ do
+ {
+ success = info_gather_typeahead (wait);
+ }
+ while (!success && errno == EINTR); /* Try again if the read was
+ interrupted due to a signal. */
+ if (success || !wait)
+ return;
+
+ /* Reading failed. If we were reading from a dribble file with
+ --restore, switch to standard input. Otherwise quit. */
+ if (info_input_stream != stdin)
+ {
+ fclose (info_input_stream);
+ info_input_stream = stdin;
+ display_inhibited = 0;
+ display_update_display ();
+ display_cursor_at_point (active_window);
+ }
+ else
+ {
+ close_info_session ();
+ exit (EXIT_SUCCESS);
+ }
+ }
+}
+
+/* Read bytes and stuff them into info_input_buffer. If WAIT is true, wait
+ for input; otherwise don't do anything if there is no input waiting.
+ Return 1 on success, 0 on error. ERRNO may be set by read(). */
+static int
+info_gather_typeahead (int wait)
+{
+ 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;
+
+ /* Clear errno. */
+ errno = 0;
+
+ /* There may be characters left over from last time, in which case we don't
+ want to wait for another key to be pressed. */
+ if (wait && pop_index == push_index)
+ {
+ char c;
+ /* Wait until there is a byte waiting, and then stuff it into the input
+ buffer. */
+ if (read (tty, &c, 1) <= 0)
+ return 0;
+
+ if (info_dribble_file)
+ dribble (c);
+
+ info_input_buffer[push_index++] = c;
+ if (push_index >= MAX_INFO_INPUT_BUFFERING)
+ push_index = 0;
+ /* Continue to see if there are more bytes waiting. */
+ }
+
+ /* Get the amount of space available in INFO_INPUT_BUFFER for new chars. */
+ if (pop_index > push_index)
+ space_avail = pop_index - push_index;
+ else
+ space_avail = sizeof (info_input_buffer) - (push_index - pop_index);
+
+ /* 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) && defined (F_GETFL) && defined (F_SETFL)
+ {
+ 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);
+ }
+# else
+# ifdef __MINGW32__
+ {
+ extern long w32_chars_avail (int);
+
+ chars_avail = w32_chars_avail (tty);
+
+ if (chars_avail > space_avail)
+ chars_avail = space_avail;
+ if (chars_avail)
+ chars_avail = read (tty, &input[0], chars_avail);
+ }
+# endif /* _WIN32 */
+# endif/* __DJGPP__ */
+# endif /* O_NDELAY */
+#endif /* !FIONREAD */
+
+ while (i < chars_avail)
+ {
+ if (info_dribble_file)
+ dribble (input[i]);
+
+ /* Add KEY to the buffer of characters to be read. */
+ if (input[i] != Control ('g'))
+ {
+ info_input_buffer[push_index++] = input[i];
+ if (push_index >= MAX_INFO_INPUT_BUFFERING)
+ push_index = 0;
+ }
+ else
+ /* Flush all pending input in the case of C-g pressed. */
+ push_index = pop_index;
+ i++;
+ }
+ /* If wait is true, there is at least one byte left in the input buffer. */
+ if (chars_avail <= 0 && !wait)
+ return 0;
+ return 1;
+}
+
+static int get_input_key_internal (void);
+
+static void _scroll_forward (WINDOW *window, int count, int nodeonly);
+static void _scroll_backward (WINDOW *window, int count, int nodeonly);
+
+/* Whether to process or skip mouse events in the input stream. */
+unsigned char mouse_cb, mouse_cx, mouse_cy;
+
+/* Handle mouse event given that mouse_cb, mouse_cx and mouse_cy contain the
+ data from the event. See the "XTerm Control Sequences" document for their
+ meanings. */
+void
+mouse_event_handler (void)
+{
+ if (mouse_cb & 0x40)
+ {
+ switch (mouse_cb & 0x03)
+ {
+ case 0: /* Mouse button 4 (scroll up). */
+ _scroll_backward (active_window, 3, 1);
+ break;
+ case 1: /* Mouse button 5 (scroll down). */
+ _scroll_forward (active_window, 3, 1);
+ break;
+ }
+ }
+}
+
+/* Return number representing a key that has been pressed, which is an index
+ into info_keymap and echo_area_keymap. */
+int
+get_input_key (void)
+{
+ int ret = -1;
+
+ while (ret == -1)
+ {
+ ret = get_input_key_internal ();
+
+ if (ret == KEY_MOUSE)
+ {
+ get_byte_from_input_buffer (&mouse_cb);
+ get_byte_from_input_buffer (&mouse_cx);
+ get_byte_from_input_buffer (&mouse_cy);
+ }
+ }
+ return ret;
+}
+
+/* Time in milliseconds to wait for the next byte of a byte sequence
+ corresponding to a key or key chord. Settable with the 'key-time' user
+ variable. */
+int key_time = 100;
+
+/* Read bytes from input and return what key has been pressed. Return -1 on
+ reading an unrecognized key. */
+static int
+get_input_key_internal (void)
+{
+ BYTEMAP_ENTRY *b;
+ unsigned char c;
+ int esc_seen = 0;
+ int pop_start;
+ int byte_count = 0;
+ fill_input_buffer (1);
+
+ if (pop_index == push_index)
+ return -1; /* No input waiting. This shouldn't happen. */
+
+ b = byte_seq_to_key;
+ pop_start = pop_index;
+
+ while (pop_index != push_index)
+ {
+ int in_map = 0;
+ int unknown = 0;
+ if (!get_byte_from_input_buffer (&c))
+ break; /* Incomplete byte sequence. */
+ byte_count++;
+
+ switch (b[c].type)
+ {
+ case BYTEMAP_KEY:
+ return b[c].key;
+ case BYTEMAP_ESC:
+ esc_seen = 1;
+ /* Fall through. */
+ case BYTEMAP_MAP:
+ in_map = 1;
+ b = b[c].next;
+ break;
+ case BYTEMAP_NONE:
+ unknown = 1;
+ break;
+ }
+
+ if (unknown)
+ break;
+
+ /* If we read an incomplete byte sequence, pause a short while to
+ see if more bytes follow. */
+ if (in_map && pop_index == push_index)
+ {
+ int ready = 0;
+#if defined (FD_SET)
+ struct timeval timer, *timerp = 0;
+ fd_set readfds;
+
+ FD_ZERO (&readfds);
+ FD_SET (fileno (info_input_stream), &readfds);
+
+ timer.tv_sec = 0;
+ timer.tv_usec = key_time * 1000;
+ timerp = &timer;
+ ready = select (fileno(info_input_stream)+1, &readfds,
+ NULL, NULL, timerp);
+#else
+ ready = 1;
+#endif /* FD_SET */
+ if (ready)
+ fill_input_buffer (0);
+ }
+ }
+
+ /* Incomplete or unknown byte sequence. Start again with the first byte. */
+ pop_index = pop_start;
+
+ if (!esc_seen || (byte_count >= 3 && key_time == 0))
+ {
+ /* If the sequence was incomplete, return the first byte.
+ Also return the first byte for sequences with ESC that are at
+ least three bytes long if 'key_time' is 0, to give some support for
+ specifying byte sequences in .infokey for those sent by unrecognized
+ special keys (which would otherwise be skipped below). */
+ pop_index = pop_start;
+ get_byte_from_input_buffer (&c);
+ return c;
+ }
+ else
+ {
+ get_byte_from_input_buffer (&c); /* Should be ESC */
+
+ /* If there are no more characters, then decide that the escape key
+ itself has been pressed. */
+ if (pop_index == push_index)
+ return 033;
+
+ /* Skip byte sequences that look like they could have come from
+ unrecognized keys, e.g. F3 or C-S-Left, to avoid them as being
+ interpreted as random garbage. These might produce sequences
+ that look like "ESC O R" or "ESC [ 1 ; 6 ~", depending on
+ the terminal. */
+
+ /* Check if the sequence starts ESC O. */
+ get_byte_from_input_buffer (&c);
+ if (c == 'O')
+ {
+ /* If no more bytes, call it M-O. */
+ if (!info_any_buffered_input_p ())
+ return 'O' + KEYMAP_META_BASE;
+
+ /* Otherwise it could be an unrecognized key producing a sequence
+ ESC O (byte). Ignore it, discarding the next byte. */
+ get_byte_from_input_buffer (&c);
+ return -1;
+ }
+
+ /* Unknown CSI-style sequences. */
+ else if (c == '[')
+ {
+ /* If no more bytes, call it M-[. */
+ if (!get_byte_from_input_buffer (&c))
+ return '[' + KEYMAP_META_BASE;
+
+ /* Skip a control sequence as defined by ECMA-48. */
+ while (c >= 0x30 && c <= 0x3f)
+ if (!get_byte_from_input_buffer (&c))
+ break;
+
+ while (c >= 0x20 && c <= 0x2f)
+ if (!get_byte_from_input_buffer (&c))
+ break;
+
+ return -1;
+ }
+
+ else
+ {
+ /* The sequence started with ESC, but wasn't recognized. Treat it
+ as introducing a sequence produced by a key chord with the meta
+ key pressed. */
+ return c + KEYMAP_META_BASE;
+ }
+ }
+}
+
+#if defined (HAVE_SYS_TIME_H)
+# include <sys/time.h>
+# define HAVE_STRUCT_TIMEVAL
+#endif /* HAVE_SYS_TIME_H */
+
+#if !defined (FD_SET) && defined (__MINGW32__)
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+#endif
+
+void
+pause_or_input (void)
+{
+#ifdef FD_SET
+ struct timeval timer;
+ fd_set readfds;
+#endif
+
+ if (pop_index != push_index)
+ return; /* Input is already waiting. */
+
+#ifdef FD_SET
+ FD_ZERO (&readfds);
+ FD_SET (fileno (stdin), &readfds);
+ timer.tv_sec = 2;
+ timer.tv_usec = 0;
+ select (fileno (stdin) + 1, &readfds, NULL, NULL, &timer);
+#elif defined (__MINGW32__)
+ /* This is signalled on key release, so flush it and wait again. */
+ WaitForSingleObject (GetStdHandle (STD_INPUT_HANDLE), 2000);
+ FlushConsoleInputBuffer (GetStdHandle (STD_INPUT_HANDLE));
+ WaitForSingleObject (GetStdHandle (STD_INPUT_HANDLE), 2000);
+#endif /* FD_SET */
+}
+
+
+/* **************************************************************** */
+/* */
+/* Error handling */
+/* */
+/* **************************************************************** */
+
+/* Debugging level */
+unsigned debug_level;
+
+static void
+vinfo_debug (const char *format, va_list ap)
+{
+ fprintf (stderr, "%s: ", program_name);
+ vfprintf (stderr, format, ap);
+ fprintf (stderr, "\n");
+ fflush (stderr);
+}
+
+void
+info_debug (const char *format, ...)
+{
+ va_list ap;
+ va_start (ap, format);
+ vinfo_debug (format, ap);
+ va_end (ap);
+}
+
+/* Non-zero means ring terminal bell on errors. */
+int info_error_rings_bell_p = 1;
+
+/* Print AP according to FORMAT. If the window system was initialized,
+ then the message is printed in the echo area. Otherwise, a message is
+ output to stderr. */
+static void
+vinfo_error (const char *format, va_list ap)
+{
+ if (!info_windows_initialized_p || display_inhibited)
+ {
+ fprintf (stderr, "%s: ", program_name);
+ vfprintf (stderr, format, ap);
+ fprintf (stderr, "\n");
+ fflush (stderr);
+ }
+ else
+ {
+ if (!echo_area_is_active)
+ {
+ if (info_error_rings_bell_p)
+ terminal_ring_bell ();
+ vwindow_message_in_echo_area (format, ap);
+ }
+ else
+ {
+ NODE *temp = build_message_node (format, ap);
+ if (info_error_rings_bell_p)
+ terminal_ring_bell ();
+ inform_in_echo_area (temp->contents);
+ free (temp->contents);
+ free (temp);
+ }
+ }
+}
+
+void
+info_error (const char *format, ...)
+{
+ va_list ap;
+ va_start (ap, format);
+ vinfo_error (format, ap);
+ va_end (ap);
+}
+
+void
+show_error_node (char *error)
+{
+ if (info_error_rings_bell_p)
+ terminal_ring_bell ();
+ if (!info_windows_initialized_p)
+ {
+ info_error ("%s", error);
+ }
+ else if (!echo_area_is_active)
+ {
+ window_message_in_echo_area ("%s", error);
+ }
+ else
+ inform_in_echo_area (error);
+}
+
+
+/* **************************************************************** */
+/* */
+/* Window node history */
+/* */
+/* **************************************************************** */
+
+/* Free a NODE object that is suitable for being placed in a window. */
+void
+free_history_node (NODE *n)
+{
+ if (n && (n->flags & N_IsInternal))
+ {
+ free (n->contents);
+ info_free_references (n->references);
+ free (n->next); free (n->prev); free (n->up);
+ free (n->nodename);
+ }
+ free (n);
+}
+
+static void
+put_node_in_window (WINDOW *win, NODE *node)
+{
+ win->node = node;
+ win->pagetop = 0;
+ win->point = 0;
+ free_matches (&win->matches);
+ free (win->line_starts); win->line_starts = 0;
+ free (win->log_line_no); win->log_line_no = 0;
+ win->flags |= W_UpdateWindow;
+}
+
+/* Go back one in the node history. */
+int
+forget_node_fast (WINDOW *win)
+{
+ int i = win->hist_index;
+ if (i == 0)
+ return 0;
+
+ free_history_node (win->hist[i - 1]->node);
+ free (win->hist[i - 1]);
+ win->hist[i - 1] = 0;
+ i = --win->hist_index;
+
+ if (i == 0)
+ /* Window history is empty. */
+ win->node = 0;
+ else
+ {
+ put_node_in_window (win, win->hist[i - 1]->node);
+ win->point = win->hist[i - 1]->point;
+ }
+ return i;
+}
+
+void
+forget_node (WINDOW *win)
+{
+ int i = forget_node_fast (win);
+
+ if (i == 0)
+ {
+ win->node = 0;
+ return; /* Window history is empty. */
+ }
+
+ window_set_node_of_window (win, win->hist[i - 1]->node);
+ if (auto_footnotes_p)
+ info_get_or_remove_footnotes (win);
+ set_window_pagetop (win, win->hist[i - 1]->pagetop);
+ win->point = win->hist[i - 1]->point;
+ window_compute_line_map (win);
+ win->node->display_pos = win->point;
+}
+
+/* Remove associated list of nodes of WINDOW. */
+void
+forget_window_and_nodes (WINDOW *win)
+{
+ int i;
+ for (i = 0; i < win->hist_index; i++)
+ {
+ free_history_node (win->hist[i]->node);
+ free (win->hist[i]);
+ }
+ free (win->hist);
+}
+
+/* Like info_set_node_of_window, but only do enough so to extend the
+ window history, avoiding calculating line starts. */
+void
+info_set_node_of_window_fast (WINDOW *win, NODE *node)
+{
+ WINDOW_STATE *new;
+
+ if (win->hist_index && win->hist[win->hist_index - 1]->node == win->node)
+ {
+ win->hist[win->hist_index - 1]->pagetop = win->pagetop;
+ win->hist[win->hist_index - 1]->point = win->point;
+ }
+ put_node_in_window (win, node);
+
+ new = xmalloc (sizeof (WINDOW_STATE));
+ new->node = win->node;
+ new->pagetop = win->pagetop;
+ new->point = win->point;
+ add_pointer_to_array (new, win->hist_index, win->hist, win->hist_slots, 16);
+}
+
+/* Set WINDOW to show NODE. Remember the new window in our list of
+ Info windows. If we are doing automatic footnote display, try to display
+ the footnotes for this window. */
+void
+info_set_node_of_window (WINDOW *win, NODE *node)
+{
+ WINDOW_STATE *new;
+
+ /* Remember the current values of pagetop and point if the remembered node
+ is the same as the current one being displayed. */
+ if (win->hist_index && win->hist[win->hist_index - 1]->node == win->node)
+ {
+ win->hist[win->hist_index - 1]->pagetop = win->pagetop;
+ win->hist[win->hist_index - 1]->point = win->point;
+ }
+
+ /* Put this node into the window. */
+ window_set_node_of_window (win, node);
+
+ /* Remember this node, the currently displayed pagetop, and the current
+ location of point in this window. */
+ new = xmalloc (sizeof (WINDOW_STATE));
+ new->node = win->node;
+ new->pagetop = win->pagetop;
+ new->point = win->point;
+ add_pointer_to_array (new, win->hist_index, win->hist, win->hist_slots, 16);
+
+ /* If doing auto-footnote display/undisplay, show the footnotes belonging
+ to this window's node. Don't do that if it is a footnote node itself. */
+ if (auto_footnotes_p
+ && !((win->node->flags & N_IsInternal)
+ && !strcmp (win->node->nodename, "*Footnotes*")))
+ info_get_or_remove_footnotes (win);
+}
+
+
+/* **************************************************************** */
+/* */
+/* Cursor movement within a node */
+/* */
+/* **************************************************************** */
+
+static int forward_move_node_structure (WINDOW *window, int behaviour);
+static int backward_move_node_structure (WINDOW *window, int behaviour);
+
+/* Controls whether scroll-behavior affects line movement commands */
+int cursor_movement_scrolls_p = 1;
+
+/* Variable controlling 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;
+
+/* Used after cursor movement commands. Scroll window so that point is
+ visible, and move the terminal cursor there. */
+static void
+info_show_point (WINDOW *window)
+{
+ if (window_scroll_step == 0)
+ window_adjust_pagetop (window);
+ else
+ {
+ int new_pagetop = window->pagetop;
+ int line = window_line_of_point (window);
+ if (line < window->pagetop)
+ new_pagetop -= window_scroll_step;
+ else if (line >= window->pagetop + window->height)
+ new_pagetop += window_scroll_step;
+
+ /* It's possible that moving by 'scroll-step' still won't show the
+ point. If so, call window_adjust_pagetop as a backup. */
+ if (line >= new_pagetop && line < new_pagetop + window->height)
+ set_window_pagetop (window, new_pagetop);
+ else
+ window_adjust_pagetop (window);
+ }
+
+ if (window->flags & W_UpdateWindow)
+ display_update_one_window (window);
+
+ display_cursor_at_point (window);
+}
+
+/* Advance point of WIN to the beginning of the next logical line. Compute
+ line map of new line. Return 0 if we can't go any further. */
+static int
+point_next_line (WINDOW *win)
+{
+ int line = window_line_of_point (win);
+ if (line + 1 < win->line_count)
+ {
+ win->point = win->line_starts[line + 1];
+ window_compute_line_map (win);
+ return 1;
+ }
+
+ if (cursor_movement_scrolls_p
+ && forward_move_node_structure (win, info_scroll_behaviour) == 0)
+ {
+ win->point = 0;
+ window_compute_line_map (win);
+ return 1;
+ }
+
+ win->point = win->node->nodelen - 1;
+ return 0;
+}
+
+/* Move point of WIN to the end of the previous logical line. Compute
+ line map of new line. Return 0 if we can't go any further. */
+static int
+point_prev_line (WINDOW *win)
+{
+ int line = window_line_of_point (win);
+ if (line > 0)
+ {
+ win->point = win->line_starts[line - 1];
+ window_compute_line_map (win);
+ win->point = win->line_map.map[win->line_map.used - 1];
+ return 1;
+ }
+
+ if (cursor_movement_scrolls_p
+ && backward_move_node_structure (win, info_scroll_behaviour) == 0)
+ {
+ win->point = win->node->nodelen - 1;
+ if (win->line_count > win->height)
+ set_window_pagetop (win, win->line_count - win->height);
+ window_compute_line_map (win);
+ return 1;
+ }
+
+ win->point = 0;
+ return 0;
+}
+
+/* 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';
+}
+
+/* Return true if WIN->point sits on an alphanumeric character. */
+static int
+looking_at_alnum (WINDOW *win)
+{
+ mbi_iterator_t iter;
+ mbi_init (iter, win->node->contents + win->point,
+ win->node->nodelen - win->point);
+ mbi_avail (iter);
+
+ return mbi_cur (iter).wc_valid && iswalnum (mbi_cur (iter).wc);
+}
+
+/* Advance point to the next multibyte character. */
+static void
+point_forward_char (WINDOW *win)
+{
+ long point = win->point;
+ int col;
+
+ /* Find column in the line map after the current one that advances the
+ point. (This may not be the very next character if we are at a
+ double-width character that occupies multiple columns.) */
+ col = window_point_to_column (win, point, 0) + 1;
+ for (; col < win->line_map.used && win->line_map.map[col] == point; col++)
+ ;
+
+ if (col < win->line_map.used)
+ win->point = win->line_map.map[col];
+ else
+ point_next_line (win);
+}
+
+/* Set point to the previous multibyte character. Return 0 if we can't
+ go any further. */
+static int
+point_backward_char (WINDOW *win)
+{
+ long point = win->point;
+ int col;
+
+ /* Find column in the line map before the current one that moves the
+ point backward. */
+ col = window_point_to_column (win, point, 0) - 1;
+ for (; col >= 0 && win->line_map.map[col] == point; col--)
+ ;
+
+ if (col >= 0)
+ {
+ win->point = win->line_map.map[col];
+ return 1;
+ }
+ else
+ return point_prev_line (win);
+}
+
+/* Advance window point to the beginning of the next word. */
+static void
+point_forward_word (WINDOW *win)
+{
+ int col;
+
+ col = window_point_to_column (win, win->point, &win->point);
+
+ /* Skip white space forwards. */
+ while (1)
+ {
+ for (; col < win->line_map.used; col++)
+ {
+ win->point = win->line_map.map[col];
+ if (looking_at_alnum (win))
+ goto skipped_whitespace;
+ }
+ if (!point_next_line (win))
+ return;
+ col = 0;
+ }
+ skipped_whitespace:
+
+ while (1)
+ {
+ for (; col < win->line_map.used; col++)
+ {
+ win->point = win->line_map.map[col];
+ if (!looking_at_alnum (win))
+ return;
+ }
+ if (!point_next_line (win))
+ return;
+ col = 0;
+ }
+}
+
+/* Set window point to the beginning of the previous word. */
+static void
+point_backward_word (WINDOW *win)
+{
+ /* Skip any white space before current cursor position. */
+ while (point_backward_char (win))
+ {
+ if (looking_at_alnum (win))
+ goto back_to_word_start;
+ }
+
+back_to_word_start:
+ while (point_backward_char (win))
+ {
+ if (!looking_at_alnum (win))
+ {
+ point_forward_char (win);
+ return;
+ }
+ }
+}
+
+void info_prev_line (WINDOW *, int count);
+
+/* Move to goal column, or end of line. */
+static void
+move_to_goal_column (WINDOW *window)
+{
+ long goal;
+
+ goal = window->goal_column;
+ if (goal >= window->line_map.used)
+ goal = window->line_map.used - 1;
+ window->point = window->line_map.map[goal];
+ info_show_point (window);
+}
+
+/* Move WINDOW's point down to the next line if possible. */
+DECLARE_INFO_COMMAND (info_next_line, _("Move down to the next line"))
+{
+ if (count < 0)
+ info_prev_line (window, -count);
+ else
+ {
+ if (window->goal_column == -1)
+ window->goal_column = window_get_cursor_column (window);
+ while (count--)
+ point_next_line (window);
+ move_to_goal_column (window);
+ }
+}
+
+/* Move WINDOW's point up to the previous line if possible. */
+DECLARE_INFO_COMMAND (info_prev_line, _("Move up to the previous line"))
+{
+ if (count < 0)
+ info_next_line (window, -count);
+ else
+ {
+ if (window->goal_column == -1)
+ window->goal_column = window_get_cursor_column (window);
+ while (count--)
+ point_prev_line (window);
+ move_to_goal_column (window);
+ }
+}
+
+/* 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];
+ info_show_point (window);
+}
+
+/* Move WINDOW's point to the end of the logical line. */
+DECLARE_INFO_COMMAND (info_end_of_line, _("Move to the end of the line"))
+{
+ long point;
+ if (!window->node)
+ return;
+
+ /* Find physical line with end of logical line in it. */
+ while (!looking_at_newline (window,
+ window->line_map.map[window->line_map.used - 1]))
+ point_next_line (window);
+
+ if (window->line_map.used == 0)
+ return; /* This shouldn't happen. */
+
+ /* Return offset of terminating newline. */
+ point = window->line_map.map[window->line_map.used - 1];
+
+ if (point != window->point)
+ {
+ window->point = point;
+ info_show_point (window);
+ }
+}
+
+/* Move WINDOW's point to the beginning of the logical line. */
+DECLARE_INFO_COMMAND (info_beginning_of_line, _("Move to the start of the line"))
+{
+ int old_point = window->point;
+ int point;
+
+ while (1)
+ {
+ 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;
+}
+
+void info_backward_char (WINDOW *, int count);
+
+/* Move point forward in the node. */
+DECLARE_INFO_COMMAND (info_forward_char, _("Move forward a character"))
+{
+ if (count < 0)
+ info_backward_char (window, -count);
+ else
+ {
+ while (count--)
+ point_forward_char (window);
+ 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);
+ else
+ {
+ while (count--)
+ point_backward_char (window);
+ info_show_point (window);
+ }
+}
+
+void info_backward_word (WINDOW *, int count);
+
+/* 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);
+ return;
+ }
+
+ while (count--)
+ point_forward_word (window);
+
+ info_show_point (window);
+}
+
+DECLARE_INFO_COMMAND (info_backward_word, _("Move backward a word"))
+{
+ if (count < 0)
+ {
+ info_forward_word (window, -count);
+ return;
+ }
+
+ while (count--)
+ point_backward_word (window);
+
+ info_show_point (window);
+}
+
+/* Move to the beginning of the node. */
+DECLARE_INFO_COMMAND (info_beginning_of_node, _("Move to the start of this node"))
+{
+ window->point = 0;
+ info_show_point (window);
+}
+
+/* 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);
+}
+
+
+/* **************************************************************** */
+/* */
+/* Scrolling 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;
+
+static void _scroll_forward (WINDOW *window, int count, int nodeonly);
+static void _scroll_backward (WINDOW *window, int count, int nodeonly);
+
+static void
+_scroll_forward (WINDOW *window, int count, int nodeonly)
+{
+ if (count < 0)
+ _scroll_backward (window, -count, nodeonly);
+ else
+ {
+ if (window->pagetop >= window->line_count - window->height)
+ {
+ if (!nodeonly)
+ {
+ /* If there are no more lines to scroll here, error, or get
+ another node. */
+ if (forward_move_node_structure (window, info_scroll_behaviour)
+ == 0)
+ window->point = 0;
+ else
+ info_end_of_node (window, 1);
+ }
+ return;
+ }
+ set_window_pagetop (window, window->pagetop + count);
+ }
+}
+
+static void
+_scroll_backward (WINDOW *window, int count, int nodeonly)
+{
+ if (count < 0)
+ _scroll_backward (window, -count, nodeonly);
+ else
+ {
+ int desired_top;
+
+ if (window->pagetop <= 0)
+ {
+ if (!nodeonly)
+ {
+ /* If there are no more lines to scroll here, error, or get
+ another node. */
+ if (backward_move_node_structure (window, info_scroll_behaviour)
+ == 0)
+ {
+ info_end_of_node (window, 1);
+ window->point = window->line_starts[window->pagetop];
+ }
+ else
+ window->point = 0;
+ }
+ return;
+ }
+
+ desired_top = window->pagetop - count;
+ if (desired_top < 0)
+ desired_top = 0;
+ set_window_pagetop (window, desired_top);
+ }
+}
+
+/* Lines to scroll by. -1 means scroll by screen size. */
+int default_window_size = -1;
+
+/* Show the next screen of WINDOW's node. */
+DECLARE_INFO_COMMAND (info_scroll_forward, _("Scroll forward in this window"))
+{
+ int lines;
+
+ if (info_explicit_arg)
+ lines = count;
+ else if (default_window_size > 0)
+ lines = default_window_size * count;
+ else
+ lines = (window->height - 2) * count;
+ _scroll_forward (window, lines, 0);
+}
+
+/* Show the previous screen of WINDOW's node. */
+DECLARE_INFO_COMMAND (info_scroll_backward, _("Scroll backward in this window"))
+{
+ info_scroll_forward (window, -count);
+}
+
+/* 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;
+ if (default_window_size < 0)
+ default_window_size *= -1;
+ }
+
+ info_scroll_forward (window, count);
+}
+
+/* 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"))
+{
+ info_scroll_forward_set_window (window, -count);
+}
+
+/* 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"))
+{
+ int lines;
+
+ if (info_explicit_arg)
+ lines = count;
+ else if (default_window_size > 0)
+ lines = default_window_size * count;
+ else
+ lines = (window->height - 2) * count;
+ _scroll_forward (window, lines, 1);
+}
+
+/* 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"))
+{
+ info_scroll_forward_page_only (window, -count);
+}
+
+/* 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"))
+{
+ int lines;
+
+ if (info_explicit_arg)
+ {
+ default_window_size = count;
+ count = 1;
+
+ if (default_window_size < 0)
+ {
+ default_window_size *= -1;
+ count = -1;
+ }
+ }
+
+ if (default_window_size > 0)
+ lines = default_window_size * count;
+ else
+ lines = (window->height - 2) * count;
+
+ _scroll_forward (window, lines, 1);
+}
+
+/* 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"))
+{
+ info_scroll_forward_page_only_set_window (window, -count);
+}
+
+/* Scroll the window forward by N lines. */
+DECLARE_INFO_COMMAND (info_down_line, _("Scroll down by lines"))
+{
+ _scroll_forward (window, count, 0);
+}
+
+/* Scroll the window backward by N lines. */
+DECLARE_INFO_COMMAND (info_up_line, _("Scroll up by lines"))
+{
+ _scroll_backward (window, count, 0);
+}
+
+/* Lines to scroll when using commands that scroll by half screen size
+ by default. 0 means scroll by half screen size. */
+int default_scroll_size = 0;
+
+/* 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"))
+{
+ int lines;
+
+ if (info_explicit_arg)
+ {
+ default_scroll_size = count;
+ count = 1;
+
+ if (default_scroll_size < 0)
+ {
+ default_scroll_size *= -1;
+ count = -1;
+ }
+ }
+
+ if (default_scroll_size != 0)
+ lines = default_scroll_size * count;
+ else
+ lines = (window->height + 1) / 2 * count;
+
+ _scroll_forward (window, lines, 1);
+}
+
+/* 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"))
+{
+ info_scroll_half_screen_down (window, -count);
+}
+
+/* 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 ("%s", msg_one_window);
+ return;
+ }
+
+ other = window->next;
+
+ if (!other)
+ other = window->prev;
+
+ info_scroll_forward (other, count);
+}
+
+/* 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);
+}
+
+
+/* **************************************************************** */
+/* */
+/* Garbage collection */
+/* */
+/* **************************************************************** */
+
+static void
+gc_file_buffers_and_nodes (void)
+{
+ /* Array to record whether each file buffer was referenced or not. */
+ int *fb_referenced = xcalloc (info_loaded_files_index, sizeof (int));
+ WINDOW *win;
+ int i;
+ int fb_index;
+
+ /* Loop over nodes in the history of displayed windows recording
+ which file buffers were referenced. */
+ for (win = windows; win; win = win->next)
+ {
+ if (!win->hist)
+ continue;
+ for (i = 0; win->hist[i]; i++)
+ {
+ NODE *n = win->hist[i]->node;
+
+ /* Loop over file buffers. */
+ for (fb_index = 0; fb_index < info_loaded_files_index; fb_index++)
+ {
+ FILE_BUFFER *fb = info_loaded_files[fb_index];
+
+ /* Each node should match at most one file, either a subfile or a
+ non-split file. */
+ if (fb->flags & N_Subfile)
+ {
+ if (n->subfile && !FILENAME_CMP (fb->fullpath, n->subfile))
+ {
+ fb_referenced[fb_index] = 1;
+ break;
+ }
+ }
+ else if (!(fb->flags & N_TagsIndirect))
+ {
+ if (n->fullpath && !FILENAME_CMP (fb->fullpath, n->fullpath))
+ {
+ fb_referenced[fb_index] = 1;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /* Free unreferenced file buffers. */
+ for (i = 0; i < info_loaded_files_index; i++)
+ {
+ if (!fb_referenced[i])
+ {
+ FILE_BUFFER *fb = info_loaded_files[i];
+ TAG **t;
+
+ if (fb->flags & N_TagsIndirect)
+ continue;
+
+ /* 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;
+
+ /* Don't free file buffers corresponding to files that aren't there
+ any more, because a node may still refer to them. */
+ if (fb->flags & N_Gone)
+ continue;
+
+ free (fb->contents);
+ fb->contents = 0;
+
+ /* Clear pointers into the file contents in the tags table. */
+ if (fb->tags)
+ for (t = fb->tags; (*t); t++)
+ {
+ if (!((*t)->cache.flags & N_WasRewritten))
+ (*t)->cache.contents = 0;
+ }
+ }
+ }
+
+ free (fb_referenced);
+}
+
+
+/* **************************************************************** */
+/* */
+/* Commands for Manipulating Windows */
+/* */
+/* **************************************************************** */
+
+void info_prev_window (WINDOW *, int count);
+
+/* 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);
+ return;
+ }
+
+ /* If no other window, error now. */
+ if (!windows->next || echo_area_is_active)
+ {
+ info_error ("%s", msg_one_window);
+ return;
+ }
+
+ while (count--)
+ {
+ if (window->next)
+ window = window->next;
+ else
+ window = windows;
+ }
+
+ if (active_window != window)
+ {
+ if (auto_footnotes_p)
+ info_get_or_remove_footnotes (window);
+
+ /* Point may not be within part of node displayed in window if there
+ has been resizing of this window since the last time it was active. */
+ window_adjust_pagetop (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);
+ return;
+ }
+
+ /* Only one window? */
+ if (!windows->next || echo_area_is_active)
+ {
+ info_error ("%s", msg_one_window);
+ return;
+ }
+
+ while (count--)
+ {
+
+ if (window != windows && window->prev)
+ window = window->prev;
+ else if (window == windows)
+ {
+ /* If we are in the first window, find the last window in the
+ chain. */
+ while (window->next)
+ window = window->next;
+ }
+ }
+
+ if (active_window != window)
+ {
+ if (auto_footnotes_p)
+ info_get_or_remove_footnotes (window);
+
+ /* Point may not be within part of node displayed in window if there
+ has been resizing of this window since the last time it was active. */
+ window_adjust_pagetop (window);
+ window->flags |= W_UpdateWindow;
+ active_window = window;
+ }
+}
+
+/* Split active 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 = window_make_window ();
+
+ if (!split)
+ info_error ("%s", msg_win_too_small);
+ else
+ {
+ NODE *copy = xmalloc (sizeof (NODE));
+ *copy = *window->node; /* Field-by-field copy of structure. */
+
+ if (copy->flags & N_IsInternal)
+ {
+ /* This allows us to free nodes without checking if these fields
+ are shared by NODE objects in other windows. For non-internal
+ nodes, this data is stored in the tag table. */
+ copy->references = info_copy_references (copy->references);
+ copy->nodename = xstrdup (copy->nodename);
+
+ if (copy->up)
+ copy->up = xstrdup (copy->up);
+ if (copy->next)
+ copy->next = xstrdup (copy->next);
+ if (copy->prev)
+ copy->prev = xstrdup (copy->prev);
+ copy->contents = xstrdup (copy->contents);
+ }
+
+ info_set_node_of_window (split, copy);
+ /* Make sure point still appears in the active window. */
+ info_show_point (window);
+
+ split->pagetop = window->pagetop;
+
+ if (auto_tiling_p)
+ window_tile_windows (DONT_TILE_INTERNALS);
+ else
+ window_adjust_pagetop (split);
+ }
+}
+
+/* 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 ("%s", msg_cant_kill_last);
+ else if (window->flags & W_WindowIsPerm)
+ info_error ("%s", _("Cannot delete a permanent window"));
+ else
+ {
+ info_delete_window_internal (window);
+
+ if (auto_footnotes_p)
+ info_get_or_remove_footnotes (active_window);
+
+ if (auto_tiling_p)
+ window_tile_windows (DONT_TILE_INTERNALS);
+
+ gc_file_buffers_and_nodes ();
+ }
+}
+
+/* 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))
+ {
+ 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;
+ gc_file_buffers_and_nodes ();
+}
+
+/* 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);
+}
+
+
+/* **************************************************************** */
+/* */
+/* Cross-references and menus */
+/* */
+/* **************************************************************** */
+
+/* Using WINDOW for various defaults, select the node referenced by ENTRY
+ in it. If the node is selected, the window and node are remembered.
+ Display an error message if reference couldn't be selected and return 0. */
+int
+info_select_reference (WINDOW *window, REFERENCE *entry)
+{
+ NODE *node;
+ char *file_system_error = NULL;
+
+ /* We need to copy everything from entry because the call to
+ info_get_node_with_defaults can free it if it came from
+ the tag table of a file. */
+ char *filename = entry->filename;
+ char *nodename = entry->nodename;
+ char *label = entry->label;
+ int line_number = entry->line_number;
+
+ node = info_get_node_with_defaults (filename, nodename, window->node);
+
+ /* 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 (nodename && label && !strcmp (nodename, label))
+ {
+ free (file_system_error);
+ file_system_error = NULL;
+
+ node = info_get_node (label, "Top");
+ if (!node && info_recent_file_error)
+ file_system_error = xstrdup (info_recent_file_error);
+ }
+ }
+
+ if (!node)
+ {
+ if (file_system_error)
+ {
+ info_error ("%s", file_system_error);
+ free (file_system_error);
+ }
+ else
+ info_error (msg_cant_find_node, nodename ? nodename : "Top");
+ return 0;
+ }
+
+ /* If in a footnotes window, try to switch to a window containing a
+ node from the file. */
+ if (window->node && (window->node->flags & N_IsInternal)
+ && !strcmp (window->node->nodename, "*Footnotes*"))
+ {
+ WINDOW *w;
+
+ for (w = windows; w; w = windows->next)
+ {
+ if (!strcmp (w->node->fullpath, window->node->fullpath)
+ && !(w->flags & W_TempWindow))
+ {
+ /* Switch to this window. */
+ active_window = window = w;
+ break;
+ }
+ }
+ }
+ info_set_node_of_window (window, node);
+
+ if (line_number > 0)
+ {
+ /* Go to the line given by entry->line_number. */
+ long line = window_log_to_phys_line (window, line_number - 1);
+
+ if (line >= 0 && line < window->line_count)
+ {
+ window->point = window->line_starts[line];
+ window_adjust_pagetop (window);
+ }
+ }
+ return 1;
+}
+
+/* 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);
+ entry.filename = info_parsed_filename;
+ entry.nodename = info_parsed_nodename;
+ entry.line_number = 0;
+ entry.label = "*info-parse-and-select*";
+
+ info_select_reference (window, &entry);
+}
+
+/* Return menu entry indexed by KEY, where '1' is the first menu item, '2' is
+ the second, etc., and '0' is the last. Return value should not be freed. */
+static REFERENCE *
+select_menu_digit (WINDOW *window, unsigned char key)
+{
+ register int i, item;
+ register REFERENCE **menu;
+
+ menu = window->node->references;
+ if (!menu)
+ return 0;
+
+ item = key - '0';
+
+ /* Special case. Item "0" is the last item in this menu. */
+ if (item == 0)
+ {
+ int j;
+ i = -1; /* Not found */
+ for (j = 0; menu[j]; j++)
+ if (menu[j]->type == REFERENCE_MENU_ITEM)
+ i = j;
+ }
+ else
+ {
+ int k = 0;
+ for (i = 0; menu[i]; i++)
+ {
+ if (menu[i]->type == REFERENCE_MENU_ITEM)
+ k++;
+ if (k == item)
+ break;
+ }
+ }
+
+ if (i == -1)
+ return 0;
+
+ return menu[i];
+}
+
+DECLARE_INFO_COMMAND (info_menu_digit, _("Select this menu item"))
+{} /* Declaration only. */
+
+/* Use KEY (a digit) to select the Nth menu item in WINDOW->node. */
+void
+menu_digit (WINDOW *window, int key)
+{
+ int item = key - '0';
+ REFERENCE *entry;
+ REFERENCE **references = window->node->references;
+
+ /* Check if there is a menu in this node. */
+ if (references)
+ {
+ int i;
+ for (i = 0; references[i]; i++)
+ if (references[i]->type == REFERENCE_MENU_ITEM)
+ goto has_menu;
+ }
+
+ info_error ("%s", msg_no_menu_node);
+ return;
+has_menu:
+
+ if (entry = select_menu_digit (window, key))
+ info_select_reference (window, entry);
+ else if (key == '0')
+ /* Don't print "There aren't 0 items in this menu" */
+ info_error ("%s", msg_no_menu_node);
+ else
+ info_error (ngettext ("There isn't %d item in this menu",
+ "There aren't %d items in this menu",
+ item),
+ item);
+ return;
+}
+
+/* Select the last menu item in WINDOW->node. */
+DECLARE_INFO_COMMAND (info_last_menu_item,
+ _("Select the last item in this node's menu"))
+{
+ menu_digit (window, '0');
+}
+
+static int exclude_cross_references (REFERENCE *r)
+{
+ return r->type == REFERENCE_XREF;
+}
+
+static int exclude_menu_items (REFERENCE *r)
+{
+ return r->type == REFERENCE_MENU_ITEM;
+}
+
+static int exclude_nothing (REFERENCE *r)
+{
+ return 1;
+}
+
+/* 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. ASK_P is non-zero if the user should
+ be prompted, or zero to select the item on the current line. MENU_ITEM
+ and XREF control whether menu items and cross-references are eligible
+ for selection. */
+static void
+info_menu_or_ref_item (WINDOW *window, int menu_item, int xref, int ask_p)
+{
+ REFERENCE *defentry = NULL; /* Default link */
+ REFERENCE **refs = window->node->references;
+ REFERENCE *entry;
+
+ /* Name of destination */
+ char *line;
+
+ int line_no;
+ int this_line, next_line;
+
+ int which, closest = -1;
+
+ reference_bool_fn exclude;
+
+ if (!refs)
+ return;
+
+ if (menu_item && !xref)
+ {
+ exclude = &exclude_cross_references;
+ }
+ else if (!menu_item && xref)
+ {
+ exclude = &exclude_menu_items;
+ }
+ else if (menu_item && xref)
+ {
+ exclude = &exclude_nothing;
+ }
+ else /* !menu_item && !xref */
+ return;
+
+ line_no = window_line_of_point (window);
+ this_line = window->line_starts[line_no];
+ if (window->line_starts[line_no + 1])
+ next_line = window->line_starts[line_no + 1];
+ else
+ next_line = window->node->nodelen;
+
+ /* Look for a reference in the current line, preferring one that
+ the point is in, otherwise preferring after the point. */
+ for (which = 0; refs[which]; which++)
+ {
+ /* If we got to the next line without finding an eligible reference. */
+ if (refs[which]->start >= next_line)
+ break;
+
+ /* Check the type of reference is one we are looking for. */
+ if (!( (menu_item && refs[which]->type == REFERENCE_MENU_ITEM)
+ || (xref && refs[which]->type == REFERENCE_XREF)))
+ continue;
+
+ /* Reference is eligible if any part of it is in the line. */
+ if (refs[which]->start >= this_line && refs[which]->start < next_line
+ || refs[which]->start < this_line && refs[which]->end > this_line)
+ {
+ closest = which;
+
+ /* Use the first reference that either contains the point
+ or is after the point. */
+ if (refs[which]->end > window->point)
+ break;
+ }
+ }
+ if (closest != -1)
+ defentry = refs[closest];
+
+ if (ask_p)
+ {
+ char *prompt;
+
+ /* Build the prompt string. */
+ if (menu_item && !xref)
+ {
+ 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_with_exclusions (prompt, refs,
+ exclude);
+ free (prompt);
+
+ window = active_window;
+
+ /* User aborts, just quit. */
+ if (!line)
+ {
+ info_abort_key (window, 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; refs && (ref = refs[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 = refs[best];
+ else
+ entry = NULL;
+ }
+
+ if (!entry && defentry)
+ info_error (_("The reference disappeared! (%s)"), line);
+ else
+ {
+ info_select_reference (window, entry);
+ }
+
+ free (line);
+ }
+}
+
+/* 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"))
+{
+ if (window->node->references)
+ {
+ REFERENCE **r;
+
+ /* Check if there is a menu in this node. */
+ for (r = window->node->references; *r; r++)
+ if ((*r)->type == REFERENCE_MENU_ITEM)
+ break;
+
+ if (*r)
+ {
+ info_menu_or_ref_item (window, 1, 0, 1);
+ return;
+ }
+ }
+
+ info_error ("%s", msg_no_menu_node);
+ return;
+}
+
+/* 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"))
+{
+ if (window->node->references)
+ {
+ REFERENCE **r;
+
+ /* Check if there is a cross-reference in this node. */
+ for (r = window->node->references; *r; r++)
+ if ((*r)->type == REFERENCE_XREF)
+ break;
+
+ if (*r)
+ {
+ info_menu_or_ref_item (window, 0, 1, 1);
+ return;
+ }
+ }
+
+ info_error ("%s", msg_no_xref_node);
+ return;
+}
+
+/* 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;
+
+ if (search (INFO_MENU_LABEL, &binding, &position) == search_success)
+ {
+ window->point = position;
+ window_adjust_pagetop (window);
+ window->flags |= W_UpdateWindow;
+ }
+ else
+ info_error ("%s", msg_no_menu_node);
+}
+
+/* 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 = window->node->references;
+
+ if (!menu)
+ {
+ info_error ("%s", msg_no_menu_node);
+ return;
+ }
+
+ for (i = 0; (entry = menu[i]); i++)
+ {
+ WINDOW *new;
+
+ if (entry->type != REFERENCE_MENU_ITEM) continue;
+
+ new = window_make_window ();
+ info_set_node_of_window (new, window->node);
+ window_tile_windows (TILE_INTERNALS);
+
+ if (!new)
+ {
+ info_error ("%s", msg_win_too_small);
+ break;
+ }
+ else
+ {
+ active_window = new;
+ if (!info_select_reference (new, entry))
+ break;
+ }
+ }
+}
+
+/* Move to the next or previous cross reference in this node. Return 0 if
+ there aren't any. */
+static int
+info_move_to_xref (WINDOW *window, int dir)
+{
+ long placement = -1;
+ NODE *node = window->node;
+ REFERENCE **ref;
+
+ /* Fail if there are no references in node */
+ if (!node->references || !node->references[0])
+ return 0;
+
+ if (dir == 1) /* Search forwards */
+ for (ref = node->references; *ref != 0; ref++)
+ {
+ if ((*ref)->start > window->point)
+ {
+ placement = (*ref)->start;
+ break;
+ }
+ }
+ else /* Search backwards */
+ for (ref = node->references; *ref != 0; ref++)
+ {
+ if ((*ref)->start >= window->point) break;
+ placement = (*ref)->start;
+ }
+
+ if (placement == -1)
+ {
+ /* There was neither a menu or xref entry appearing in this node
+ after point. */
+ return 0;
+ }
+
+ window->point = placement;
+ window_adjust_pagetop (window);
+ return 1;
+}
+
+void info_move_to_next_xref (WINDOW *, int count);
+
+/* Remove history entries from START inclusive to END exclusive.
+ Warning: be careful about removing the last history entry, as
+ info_set_node_of_window includes the currently displayed node in
+ the history. */
+static void
+cleanup_history (WINDOW *window, int start, int end)
+{
+ int i;
+ for (i = start; i < end; i++)
+ {
+ free_history_node (window->hist[i]->node);
+ free (window->hist[i]);
+ }
+ memmove (&window->hist[start], &window->hist[end],
+ (window->hist_index - end) * sizeof (WINDOW_STATE *));
+ window->hist_index -= end - start;
+ window->hist[window->hist_index] = 0;
+}
+
+DECLARE_INFO_COMMAND (info_move_to_prev_xref,
+ _("Move to the previous cross reference"))
+{
+ if (count < 0)
+ info_move_to_next_xref (window, -count);
+ else
+ {
+ size_t last_hist_index, starting_hist_index;
+ char *initial_nodename = window->node->nodename;
+
+ last_hist_index = starting_hist_index = window->hist_index - 1;
+
+ while (count > 0)
+ {
+ if (info_move_to_xref (window, -1))
+ {
+ last_hist_index = window->hist_index - 1;
+ count--;
+ continue;
+ }
+
+ /* When cursor-movement-scrolls=Off, cycle round the node's
+ references. */
+ if (!cursor_movement_scrolls_p)
+ {
+ REFERENCE **r = window->node->references;
+ if (r && r[0])
+ {
+ int i = 0;
+ /* Choose the last menu or xref entry appearing in this
+ node. */
+ while (r[i + 1])
+ i++;
+ window->point = r[i]->start;
+ window_adjust_pagetop (window);
+ count--;
+ continue;
+ }
+
+ info_error ("%s", msg_no_xref_node);
+ return;
+ }
+
+ if (backward_move_node_structure (window, info_scroll_behaviour != 0)
+ || !strcmp (window->node->nodename, initial_nodename))
+ {
+ break; /* No earlier nodes in file, or we are back where we
+ started. */
+ }
+ window->point = window->node->nodelen - 1;
+ }
+
+ /* Go back to the last place a reference was found, or
+ the starting place. */
+ while (window->hist_index > last_hist_index + 1)
+ forget_node (window);
+
+ /* Remove any intermediate nodes. */
+ if (last_hist_index != starting_hist_index)
+ cleanup_history (window, starting_hist_index + 1, last_hist_index);
+ }
+}
+
+DECLARE_INFO_COMMAND (info_move_to_next_xref,
+ _("Move to the next cross reference"))
+{
+ if (count < 0)
+ info_move_to_prev_xref (window, -count);
+ else
+ {
+ size_t last_hist_index, starting_hist_index;
+ char *initial_nodename = window->node->nodename;
+
+ last_hist_index = starting_hist_index = window->hist_index - 1;
+
+ while (count > 0)
+ {
+ if (info_move_to_xref (window, 1))
+ {
+ last_hist_index = window->hist_index - 1;
+ count--;
+ continue;
+ }
+
+ /* When cursor-movement-scrolls=Off, cycle round the node's
+ references. */
+ if (!cursor_movement_scrolls_p)
+ {
+ REFERENCE **r = window->node->references;
+ if (r && r[0])
+ {
+ /* Choose the first menu or xref entry appearing in this
+ node. */
+ window->point = r[0]->start;
+ window_adjust_pagetop (window);
+ count--;
+ continue;
+ }
+
+ info_error ("%s", msg_no_xref_node);
+ return;
+ }
+
+ if (forward_move_node_structure (window, info_scroll_behaviour) != 0
+ || !strcmp (window->node->nodename, initial_nodename))
+ {
+ /*TODO: Print an error. */
+ break; /* No later nodes in file, or we are back where we
+ started. */
+ }
+ }
+
+ /* Go back to the last place a reference was found, or
+ the starting place. */
+ while (window->hist_index > last_hist_index + 1)
+ forget_node (window);
+
+ /* Remove any intermediate nodes. */
+ if (last_hist_index != starting_hist_index)
+ cleanup_history (window, starting_hist_index + 1, last_hist_index);
+ }
+}
+
+/* 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"))
+{
+ REFERENCE **ref = window->node->references;
+
+ if (!ref || !*ref) return; /* No references in node */
+
+ info_menu_or_ref_item (window, 1, 1, 0);
+}
+
+/* Follow the menu list in MENUS (list of strings terminated by a NULL
+ entry) from INITIAL_NODE. If there is an error, place a message
+ in ERROR. STRICT says whether to accept incomplete strings as
+ menu entries, and whether to return the node so far if we can't
+ continue at any point (that might be INITIAL_NODE itself), or to
+ return null. This function frees INITIAL_NODE. */
+NODE *
+info_follow_menus (NODE *initial_node, char **menus, char **error,
+ int strict)
+{
+ NODE *node = NULL;
+
+ for (; *menus; menus++)
+ {
+ REFERENCE *entry;
+ char *arg = *menus; /* Remember the name of the menu entry we want. */
+
+ debug (3, ("looking for %s in %s:%s", arg, initial_node->fullpath,
+ initial_node->nodename));
+
+ if (!initial_node->references)
+ {
+ if (error)
+ {
+ free (*error);
+ asprintf (error, _("No menu in node '%s'"),
+ node_printed_rep (initial_node));
+ }
+ debug (3, ("no menu found"));
+ if (!strict)
+ return initial_node;
+ else
+ {
+ free_history_node (initial_node);
+ return 0;
+ }
+ }
+
+ /* Find the specified menu item. */
+ entry = info_get_menu_entry_by_label (initial_node, arg, !strict);
+
+ /* If we failed to find the reference: */
+ if (!entry)
+ {
+ if (error)
+ {
+ free (*error);
+ asprintf (error, _("No menu item '%s' in node '%s'"),
+ arg, node_printed_rep (initial_node));
+ }
+ debug (3, ("no entry found"));
+ if (!strict)
+ return initial_node;
+ else
+ {
+ free_history_node (initial_node);
+ return 0;
+ }
+ }
+
+ debug (3, ("entry: %s, %s", entry->filename, entry->nodename));
+
+ /* Try to find this node. */
+ node = info_get_node_with_defaults (entry->filename, entry->nodename,
+ initial_node);
+ if (!node)
+ {
+ debug (3, ("no matching node found"));
+ if (error)
+ {
+ free (*error);
+ asprintf (error,
+ _("Unable to find node referenced by '%s' in '%s'"),
+ entry->label,
+ node_printed_rep (initial_node));
+ }
+ if (strict)
+ {
+ free_history_node (initial_node);
+ return 0;
+ }
+ else
+ return initial_node;
+ }
+
+ debug (3, ("node: %s, %s", node->fullpath, node->nodename));
+
+ /* Success. Go round the loop again. */
+ free_history_node (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 (_("Follow menus: "));
+
+ /* If the user aborted, quit now. */
+ if (!line)
+ {
+ info_abort_key (window, 0);
+ return;
+ }
+
+ canonicalize_whitespace (line);
+
+ if (*line)
+ {
+ char *error = 0;
+ NODE *dir_node = get_dir_node ();
+ char **nodes = split_list_of_nodenames (line);
+ NODE *node;
+
+ /* 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)
+ dir_node = info_get_node (window->node->fullpath, 0);
+
+ /* If we still cannot find the starting point, give up. */
+ if (!dir_node)
+ info_error (msg_cant_find_node, "Top");
+ else
+ {
+ node = info_follow_menus (dir_node, nodes, &error, 0);
+ info_set_node_of_window (window, node);
+ if (error)
+ show_error_node (error);
+ }
+
+ free (nodes);
+ }
+ free (line);
+}
+
+
+/* **************************************************************** */
+/* */
+/* Navigation of document structure */
+/* */
+/* **************************************************************** */
+
+/* Get the node pointed to by the LABEL pointer of WINDOW->node into WINDOW.
+ Display error message if there is no such pointer, and return zero. */
+static int
+info_handle_pointer (char *label, WINDOW *window)
+{
+ char *description;
+ NODE *node;
+
+ if (!strcmp (label, "Up"))
+ description = window->node->up;
+ else if (!strcmp (label, "Next"))
+ description = window->node->next;
+ else if (!strcmp (label, "Prev"))
+ description = window->node->prev;
+
+ if (!description)
+ {
+ info_error (msg_no_pointer, label);
+ return 0;
+ }
+
+ info_parse_node (description);
+ node = info_get_node_with_defaults (info_parsed_filename,
+ info_parsed_nodename,
+ window->node);
+ if (!node)
+ {
+ if (info_recent_file_error)
+ info_error ("%s", info_recent_file_error);
+ else
+ info_error (msg_cant_find_node, description);
+ return 0;
+ }
+
+ /* If we are going up, set the cursor position to the last place it
+ was in the node. */
+ if (strcmp (label, "Up") == 0)
+ {
+ int i;
+
+ for (i = window->hist_index - 1; i >= 0; i--)
+ {
+ NODE *p = window->hist[i]->node;
+
+ if (p->fullpath && !strcmp (p->fullpath, node->fullpath)
+ && p->nodename && !strcmp (p->nodename, node->nodename))
+ break;
+ }
+
+ if (i >= 0)
+ node->display_pos = window->hist[i]->point;
+ }
+
+ info_set_node_of_window (window, node);
+ return 1;
+}
+
+/* Make WINDOW display the "Next:" node of the node currently being
+ displayed. */
+DECLARE_INFO_COMMAND (info_next_node, _("Select the Next 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_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_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]->cache.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 ("%s", _("This window has no additional nodes"));
+ else
+ info_set_node_of_window (window, node);
+}
+
+/* Make WINDOW display the first node of this info file. */
+DECLARE_INFO_COMMAND (info_first_node, _("Select the first node in this file"))
+{
+ FILE_BUFFER *fb = file_buffer_of_window (window);
+ NODE *node = 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]->cache.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 ("%s", _("This window has no additional nodes"));
+ else
+ info_set_node_of_window (window, node);
+}
+
+/* Controls what to do when a scrolling command is issued at the end of the
+ last node. */
+int scroll_last_node = SLN_Stop;
+
+/* Move to 1st menu item, Next, Up/Next, or error in this window. Return
+ non-zero on error, 0 on success. Display an error message if there is an
+ error. */
+static int
+forward_move_node_structure (WINDOW *window, int behaviour)
+{
+ if (window->node->flags & (N_IsInternal | N_IsManPage))
+ return 1;
+
+ switch (behaviour)
+ {
+ case IS_PageOnly:
+ info_error ("%s", msg_at_node_bottom);
+ return 1;
+
+ case IS_NextOnly:
+ return !info_handle_pointer ("Next", window);
+ break;
+
+ case IS_Continuous:
+ {
+ /* If this node contains a menu, select its first entry. Indices
+ are an exception, as their menus lead nowhere meaningful. Likewise
+ for dir nodes. */
+ if (!(window->node->flags & N_IsIndex)
+ && !(window->node->flags & N_IsDir))
+ {
+ REFERENCE *entry;
+
+ if (entry = select_menu_digit (window, '1'))
+ {
+ info_select_reference (window, entry);
+ return 0;
+ }
+ }
+
+ /* Okay, this node does not contain a menu. If it contains a
+ "Next:" pointer, use that. */
+ if (window->node->next)
+ {
+ 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;
+ int starting_hist_index = window->hist_index;
+
+ /* 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 (1)
+ {
+ if (window->node->up)
+ {
+ REFERENCE *entry;
+
+ if (!info_handle_pointer ("Up", window))
+ return 1;
+
+ up_counter++;
+
+ /* If no "Next" pointer, keep backing up. */
+ if (!window->node->next)
+ continue;
+
+ /* If this node's first menu item is the same as this node's
+ Next pointer, keep backing up. */
+ entry = select_menu_digit (window, '1');
+ if (entry && !strcmp (window->node->next, entry->nodename))
+ continue;
+
+ /* 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);
+
+ /* Don't include intermediate nodes in the window's
+ history. */
+ cleanup_history (window, starting_hist_index,
+ window->hist_index - 1);
+ return 0;
+ }
+ else
+ {
+ /* No more "Up" pointers. We are at the last node in the
+ file. */
+ register int i;
+
+ for (i = 0; i < up_counter; i++)
+ forget_node (window);
+
+ switch (scroll_last_node)
+ {
+ case SLN_Stop:
+ info_error ("%s",
+ _("No more nodes within this document"));
+ return 1;
+
+ case SLN_Top:
+ info_parse_and_select ("Top", window);
+ return 0;
+
+ default:
+ abort ();
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+/* Move to earlier node in node hierarchy in WINDOW depending on BEHAVIOUR.
+ Display an error message if node wasn't changed. */
+static int
+backward_move_node_structure (WINDOW *window, int behaviour)
+{
+ if (window->node->flags & (N_IsInternal | N_IsManPage))
+ return 1;
+
+ switch (behaviour)
+ {
+ case IS_PageOnly:
+ info_error ("%s", msg_at_node_top);
+ return 1;
+
+ case IS_NextOnly:
+ return !info_handle_pointer ("Prev", window);
+ break;
+
+ case IS_Continuous:
+ if (window->node->up)
+ {
+ /* If up is the dir node, we are at the top node.
+ Don't do anything. */
+ if (!strncasecmp (window->node->up, "(dir)", strlen ("(dir)")))
+ {
+ info_error ("%s", _("No 'Prev' or 'Up' for this node within this document"));
+ return 1;
+ }
+ /* If 'Prev' and 'Up' are the same, we are at the first node
+ of the 'Up' node's menu. Go to up node. */
+ else if (window->node->prev
+ && !strcmp(window->node->prev, window->node->up))
+ {
+ info_handle_pointer ("Up", window);
+ }
+ /* Otherwise, go to 'Prev' node and go down the last entry
+ in the menus as far as possible. */
+ else if (window->node->prev)
+ {
+ int starting_hist_index = window->hist_index;
+ info_handle_pointer ("Prev", window);
+ if (!(window->node->flags & N_IsIndex))
+ {
+ while (1)
+ {
+ REFERENCE *entry = select_menu_digit (window, '0');
+ if (!entry)
+ break;
+ if (!info_select_reference (window, entry))
+ break;
+ }
+ /* Don't include intermediate nodes in the window's
+ history. */
+ cleanup_history (window, starting_hist_index,
+ window->hist_index - 1);
+ }
+ }
+ else /* 'Up' but no 'Prev' */
+ info_handle_pointer ("Up", window);
+ }
+ else if (window->node->prev) /* 'Prev' but no 'Up' */
+ info_handle_pointer ("Prev", window);
+ else
+ {
+ info_error ("%s", _("No 'Prev' or 'Up' for this node within this document"));
+ return 1;
+ }
+
+ break; /* case IS_Continuous: */
+ }
+ return 0;
+}
+
+void info_global_prev_node (WINDOW *, int count);
+
+/* 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);
+ else
+ {
+ while (count)
+ {
+ if (forward_move_node_structure (window, IS_Continuous))
+ break;
+ 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);
+ else
+ {
+ while (count)
+ {
+ if (backward_move_node_structure (window, IS_Continuous))
+ break;
+ count--;
+ }
+ }
+}
+
+
+/* **************************************************************** */
+/* */
+/* Info Node Commands */
+/* */
+/* **************************************************************** */
+
+/* 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;
+ size_t items_index = 0;
+ size_t 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);
+
+ 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);
+ }
+ }
+ }
+ line = info_read_maybe_completing (_("Goto node: "), items);
+ info_free_references (items);
+ }
+#else /* !GOTO_COMPLETES */
+ line = info_read_in_echo_area (_("Goto node: "));
+#endif /* !GOTO_COMPLETES */
+
+ /* If the user aborted, quit now. */
+ if (!line)
+ {
+ info_abort_key (window, 0);
+ return;
+ }
+
+ canonicalize_whitespace (line);
+
+ if (*line)
+ info_parse_and_select (line, window);
+
+ free (line);
+}
+
+static NODE *
+find_invocation_node_by_nodename (FILE_BUFFER *fb, char *program)
+{
+ NODE *node = 0;
+ TAG **n;
+ char *try1, *try2;
+ n = fb->tags;
+ if (!n)
+ return 0;
+
+ asprintf (&try1, "Invoking %s", program);
+ asprintf (&try2, "%s invocation", program);
+ for (; *n; n++)
+ {
+ if ((*n)->nodename
+ && (!strcasecmp ((*n)->nodename, try1)
+ || !strcasecmp ((*n)->nodename, try2)))
+ {
+ node = info_get_node_of_file_buffer (fb, (*n)->nodename);
+ break;
+ }
+ }
+ free (try1); free (try2);
+
+ return node;
+}
+
+/* Find the node in the file with name FILE 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. This
+ function frees NODE. Return value should be freed by caller with
+ info_reference_free. */
+REFERENCE *
+info_intuit_options_node (NODE *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
+ };
+
+ char *filename = node->fullpath;
+ if (!strcmp ("Top", node->nodename))
+ {
+ /* Look through the list of nodes (and anchors) in the file for a node to
+ start at. There may be an invocation node that is not listed in the
+ top-level menu (this is the case for the Bash 4.2 manual), or it may
+ be referred to with an anchor ("Invoking makeinfo" in Texinfo
+ manual). */
+ FILE_BUFFER *fb;
+ NODE *n;
+ fb = info_find_file (filename);
+ if (!fb)
+ return 0;
+ n = find_invocation_node_by_nodename (fb, program);
+ if (n)
+ {
+ free_history_node (node);
+ node = n;
+ }
+ }
+
+ /* 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... */
+ while (1)
+ {
+ const char **try_node;
+ REFERENCE *entry = NULL;
+
+ /* If no menu in this node, stop here. Perhaps this node
+ is the one they need. */
+ if (!node->references)
+ 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 = info_get_menu_entry_by_label
+ (node, nodename, strcmp (*try_node, "%s"));
+ free (nodename);
+ if (entry)
+ break;
+ }
+
+ if (!entry)
+ break;
+
+ /* Go down into menu, and repeat. */
+
+ if (!entry->filename)
+ entry->filename = xstrdup (filename);
+
+ {
+ NODE *node2;
+ node2 = info_get_node (entry->filename, entry->nodename);
+ if (!node2)
+ break;
+ free_history_node (node);
+ node = node2;
+ }
+ }
+
+ {
+ char *n = node->nodename;
+ node->nodename = 0;
+ free_history_node (node);
+ return info_new_reference (filename, n);
+ }
+}
+
+/* Given a name of an Info file, find the name of the package it describes by
+ removing the leading directories and extensions. Return value should be
+ freed by caller. */
+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;
+ REFERENCE *invocation_ref;
+
+ /* 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->fullpath;
+ 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 (prompt);
+ free (prompt);
+ if (!line)
+ {
+ info_abort_key (window, 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, 0);
+ if (!top_node)
+ info_error (msg_cant_find_node, "Top");
+
+ invocation_ref = info_intuit_options_node (top_node, program_name);
+
+ /* We've got our best shot at the invocation node. Now select it. */
+ if (invocation_ref)
+ {
+ info_select_reference (window, invocation_ref);
+ info_reference_free (invocation_ref);
+ }
+
+ free (line);
+ free (default_program_name);
+}
+
+DECLARE_INFO_COMMAND (info_man, _("Read a manpage reference and select it"))
+{
+ char *line;
+
+ line = info_read_in_echo_area (_("Get Manpage: "));
+
+ if (!line)
+ {
+ info_abort_key (window, 0);
+ return;
+ }
+
+ canonicalize_whitespace (line);
+
+ if (*line)
+ {
+ NODE *manpage = info_get_node (MANPAGE_FILE_BUFFER_NAME, line);
+ if (manpage)
+ info_set_node_of_window (window, manpage);
+ }
+
+ free (line);
+}
+
+/* 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);
+}
+
+DECLARE_INFO_COMMAND (info_display_file_info,
+ _("Show full file name of node being displayed"))
+{
+ if (window->node->fullpath && *window->node->fullpath)
+ {
+ int line = window_line_of_point (window);
+ window_message_in_echo_area ("File name: %s, line %d of %ld (%ld%%)",
+ window->node->subfile
+ ? window->node->subfile
+ : window->node->fullpath,
+ line, window->line_count,
+ line * 100 / window->line_count);
+ }
+ else
+ window_message_in_echo_area ("Internal node");
+}
+
+DECLARE_INFO_COMMAND (info_history_node,
+ _("Select the most recently selected node"))
+{
+ if (window->hist_index > 1)
+ forget_node (window);
+ else
+ info_error (_("No earlier node in history"));
+}
+
+/* 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 (_("Find file: "));
+ if (!line)
+ {
+ info_abort_key (active_window, 1);
+ return;
+ }
+
+ if (*line)
+ {
+ NODE *node;
+
+ node = info_get_node (line, "*");
+ if (!node)
+ {
+ if (info_recent_file_error)
+ info_error ("%s", info_recent_file_error);
+ else
+ info_error (_("Cannot find '%s'"), line);
+ }
+ else
+ info_set_node_of_window (window, node);
+
+ free (line);
+ }
+}
+
+/* **************************************************************** */
+/* */
+/* Dumping and Printing Nodes */
+/* */
+/* **************************************************************** */
+
+enum
+ {
+ DUMP_SUCCESS,
+ DUMP_INFO_ERROR,
+ DUMP_SYS_ERROR
+ };
+
+static int dump_node_to_stream (char *filename, char *nodename,
+ FILE *stream, int dump_subnodes);
+static void initialize_dumping (void);
+
+/* Dump the nodes specified with REFERENCES to the file named
+ in OUTPUT_FILENAME. If DUMP_SUBNODES is set, recursively dump
+ the nodes which appear in the menu of each node dumped. */
+void
+dump_nodes_to_file (REFERENCE **references,
+ char *output_filename, int dump_subnodes)
+{
+ int i;
+ FILE *output_stream;
+
+ if (!references)
+ return;
+
+ /* Get the stream to print the nodes to. Special case of an output
+ filename of "-" means to dump the nodes to stdout. */
+ if (strcmp (output_filename, "-") == 0)
+ output_stream = stdout;
+ else
+ output_stream = fopen (output_filename, "w");
+
+ if (!output_stream)
+ {
+ info_error (_("Could not create output file '%s'"), output_filename);
+ return;
+ }
+
+ initialize_dumping ();
+
+ /* Print each node to stream. */
+ for (i = 0; references[i]; i++)
+ {
+ if (dump_node_to_stream (references[i]->filename,
+ references[i]->nodename,
+ output_stream,
+ dump_subnodes) == DUMP_SYS_ERROR)
+ {
+ info_error (_("error writing to %s: %s"), output_filename,
+ strerror (errno));
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ if (output_stream != stdout)
+ fclose (output_stream);
+
+ debug (1, (_("closing %s"), output_filename));
+}
+
+/* A place to remember already dumped nodes. */
+static struct info_namelist_entry *dumped_already;
+
+static void
+initialize_dumping (void)
+{
+ info_namelist_free (dumped_already);
+ dumped_already = NULL;
+}
+
+/* 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 int
+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 ("%s", info_recent_file_error);
+ else
+ {
+ if (filename && *nodename != '(')
+ info_error (msg_cant_file_node,
+ filename_non_directory (filename),
+ nodename);
+ else
+ info_error (msg_cant_find_node, nodename);
+ }
+ return DUMP_INFO_ERROR;
+ }
+
+ /* If we have already dumped this node, don't dump it again. */
+ if (info_namelist_add (&dumped_already, node->nodename))
+ {
+ free_history_node (node);
+ return DUMP_SUCCESS;
+ }
+
+ /* Maybe we should print some information about the node being output. */
+ debug (1, (_("writing node %s..."), node_printed_rep (node)));
+
+ if (write_node_to_stream (node, stream))
+ {
+ free_history_node (node);
+ return DUMP_SYS_ERROR;
+ }
+
+ /* 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 = node->references;
+
+ if (menu)
+ {
+ for (i = 0; menu[i]; i++)
+ {
+ if (REFERENCE_MENU_ITEM != menu[i]->type) continue;
+
+ /* We don't dump Info files which are different than the
+ current one. */
+ if (!menu[i]->filename)
+ if (dump_node_to_stream (filename, menu[i]->nodename,
+ stream, dump_subnodes) == DUMP_SYS_ERROR)
+ {
+ free_history_node (node);
+ return DUMP_SYS_ERROR;
+ }
+ }
+ }
+ }
+
+ free_history_node (node);
+ return DUMP_SUCCESS;
+}
+
+#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"))
+{
+ 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);
+ return;
+ }
+
+ /* Maybe we should print some information about the node being output. */
+ debug (1, (_("printing node %s..."), node_printed_rep (window->node)));
+
+ write_node_to_stream (window->node, printer_pipe);
+ if (piping)
+ pclose (printer_pipe);
+ else
+ fclose (printer_pipe);
+
+ debug (1, (_("finished printing node %s"), node_printed_rep (window->node)));
+}
+
+int
+write_node_to_stream (NODE *node, FILE *stream)
+{
+ return fwrite (node->contents, node->nodelen, 1, stream) != 1;
+}
+
+/* **************************************************************** */
+/* */
+/* 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 char *search_string = NULL;
+static int isearch_is_active = 0;
+
+static int last_search_direction = 0;
+static int last_search_case_sensitive = 0;
+
+/* Whether to use regexps or not for search. */
+static int use_regex = 1;
+
+/* Toggle the usage of regular expressions in searches. */
+DECLARE_INFO_COMMAND (info_toggle_regexp,
+ _("Toggle the usage of regular expressions in searches"))
+{
+ use_regex = !use_regex;
+ window_message_in_echo_area (use_regex
+ ? _("Using regular expressions for searches")
+ : _("Using literal strings for searches"));
+}
+
+/* Search for STRING in NODE starting at START. The DIR argument says which
+ direction to search in. If it is positive, search forward, else backwards.
+
+ If the string was found, return its location in POFF, set the
+ window's node, its point to be the found string, and readjust
+ the window's pagetop. NODE can be retained as a field within WINDOW.
+
+ WINDOW->matches should be a list of matches for NODE->contents, or null.
+ If new matches are calculated, they are saved in WINDOW->matches. */
+static enum search_result
+info_search_in_node_internal (WINDOW *window, NODE *node,
+ char *string, long start,
+ int dir, int case_sensitive,
+ int match_regexp, long *poff)
+{
+ enum search_result result = search_not_found;
+
+ long start1, end1;
+ int match_index;
+ long new_point;
+
+ MATCH_STATE matches;
+
+ /* Check if we need to calculate new results. */
+ if (!matches_ready (&window->matches)
+ || strcmp (window->search_string, string)
+ || window->search_is_case_sensitive != case_sensitive)
+ {
+ free_matches (&window->matches);
+ free (window->search_string);
+ window->search_string = xstrdup (string);
+ window->search_is_case_sensitive = case_sensitive;
+ result = regexp_search (string, !match_regexp, !case_sensitive,
+ node->contents, node->nodelen, &matches);
+ }
+ else
+ {
+ matches = window->matches;
+ result = search_success;
+ }
+
+ if (result != search_success)
+ return result;
+
+ if (node->flags & N_Simple)
+ {
+ /* There are matches in the node, but it hasn't been scanned yet. Get
+ the node again, because its contents may differ. */
+ enum search_result subresult;
+ NODE *full_node;
+
+ free_matches (&matches);
+ full_node = info_get_node (node->fullpath, node->nodename);
+ subresult = info_search_in_node_internal (window, full_node,
+ string, start,
+ dir, case_sensitive,
+ match_regexp, poff);
+ if (window->node != full_node)
+ free (full_node);
+ return subresult;
+ }
+
+ if (dir > 0)
+ {
+ start1 = start;
+ end1 = node->nodelen;
+ }
+ else
+ {
+ start1 = 0;
+ end1 = start + 1; /* include start byte in search area */
+ }
+
+ if (start1 < node->body_start)
+ start1 = node->body_start;
+ if (end1 < node->body_start)
+ end1 = node->body_start;
+
+ result = match_in_match_list (&matches, start1, end1, dir, &match_index);
+ if (result != search_success)
+ return result;
+
+ *poff = match_by_index (&matches, match_index).rm_so;
+
+ window->flags |= W_UpdateWindow;
+ if (window->node != node)
+ info_set_node_of_window (window, node);
+
+ window->matches = matches;
+
+ if (isearch_is_active && dir > 0)
+ new_point = match_by_index (&matches, match_index).rm_eo;
+ else
+ new_point = match_by_index (&matches, match_index).rm_so;
+
+ window->point = new_point;
+
+ return result;
+}
+
+/* Search for STRING starting in WINDOW, starting at *START_OFF.
+
+ 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, return non-zero. *START_OFF is given the start of
+ the found string instance. Set node and point of window to where the string
+ was found. (If the variable ISEARCH_IS_ACTIVE is non-zero and the search is
+ forwards, the point is set to the end of the search.)
+
+ Return non-zero if the search fails. */
+static int
+info_search_internal (char *string, WINDOW *window,
+ int dir, int case_sensitive,
+ long *start_off)
+{
+ register int i;
+ FILE_BUFFER *file_buffer;
+ long start;
+ enum search_result result;
+ int search_other_nodes = 1;
+ int number_of_tags = -1, starting_tag = -1, current_tag = -1;
+ NODE *node = window->node; /* Node to search in. */
+ char *subfile_name = 0;
+ TAG *tag;
+ char *msg = 0;
+ int first_time = 1;
+
+ /* If this node isn't part of a larger file, search this node only. */
+ file_buffer = file_buffer_of_window (window);
+ if (!file_buffer || !file_buffer->tags
+ || !strcmp (window->node->nodename, "*"))
+ search_other_nodes = 0;
+
+ /* Find number of tags and current tag. */
+ if (search_other_nodes)
+ {
+ char *initial_nodename = window->node->nodename;
+
+ for (i = 0; file_buffer->tags[i]; i++)
+ if (strcmp (initial_nodename, file_buffer->tags[i]->nodename) == 0)
+ {
+ starting_tag = i;
+ subfile_name = file_buffer->tags[i]->filename;
+ }
+
+ number_of_tags = i;
+
+ /* Our tag wasn't found. This shouldn't happen. */
+ if (starting_tag == -1)
+ return -1;
+ current_tag = starting_tag;
+ }
+
+ /* Set starting position of search. */
+ start = *start_off;
+
+ /* 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)
+ {
+ result = info_search_in_node_internal (window, node, string, start, dir,
+ case_sensitive, use_regex, start_off);
+
+ if (node != window->node)
+ free_history_node (node);
+
+ if (result == search_invalid)
+ return 1;
+
+ if (result == search_success)
+ {
+ if (!echo_area_is_active)
+ {
+ if (msg)
+ window_message_in_echo_area ("%s", _(msg));
+ else
+ window_clear_echo_area ();
+ }
+ return 0;
+ }
+
+ if (!search_other_nodes)
+ break;
+
+search_next_node:
+ /* If we've searched our starting node twice, there are no matches.
+ Bail out. (We searched the second time in case there were matches
+ before the starting offset.) */
+ if (current_tag == starting_tag && !first_time)
+ break;
+ first_time = 0;
+
+ /* Find the next tag that isn't an anchor. */
+ for (i = current_tag + dir; ; 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->cache.nodelen != 0)
+ break;
+ }
+
+ current_tag = i;
+
+ /* Display message when searching a new subfile. */
+ if (!echo_area_is_active && tag->filename != subfile_name)
+ {
+ subfile_name = tag->filename;
+
+ window_message_in_echo_area
+ (_("Searching subfile %s ..."),
+ filename_non_directory (subfile_name));
+ }
+
+ /* Get a new node to search in. */
+ free_matches (&window->matches);
+
+ node = info_node_of_tag_fast (file_buffer, &tag);
+ if (!node)
+ {
+ /* If not doing i-search... */
+ if (!echo_area_is_active)
+ {
+ if (info_recent_file_error)
+ {
+ info_error ("%s", info_recent_file_error);
+ return -1;
+ }
+ else
+ {
+ info_error (msg_cant_file_node,
+ filename_non_directory (file_buffer->filename),
+ tag->nodename);
+ goto search_next_node;
+ }
+ }
+ }
+
+ if (dir < 0)
+ start = tag->cache.nodelen;
+ else
+ start = 0;
+
+ /* Allow C-g to quit the search, failing it if pressed. */
+ fill_input_buffer (0); \
+ if (info_input_buffer[pop_index] == Control ('g'))
+ goto funexit;
+ }
+
+ /* Not in interactive search. */
+ if (!echo_area_is_active)
+ info_error ("%s", _("Search failed"));
+
+funexit:
+ return -1;
+}
+
+/* Minimal length of a search string */
+int min_search_length = 1;
+
+/* Read a string from the user, storing the result in SEARCH_STRING. Return 0
+ if the user aborted. */
+static int
+ask_for_search_string (int case_sensitive, int use_regex, int direction)
+{
+ char *line, *prompt;
+
+ if (search_string)
+ asprintf (&prompt, _("%s%s%s [%s]: "),
+ use_regex ? _("Regexp search") : _("Search"),
+ case_sensitive ? _(" case-sensitively") : "",
+ direction < 0 ? _(" backward") : "",
+ search_string);
+ else
+ asprintf (&prompt, _("%s%s%s: "),
+ use_regex ? _("Regexp search") : _("Search"),
+ case_sensitive ? _(" case-sensitively") : "",
+ direction < 0 ? _(" backward") : "");
+
+ line = info_read_in_echo_area (prompt);
+ free (prompt);
+
+ if (!line) /* User aborted. */
+ {
+ return 0;
+ }
+ if (!*line)
+ {
+ free (line);
+ return 1;
+ }
+
+ if (mbslen (line) < min_search_length)
+ {
+ info_error ("%s", _("Search string too short"));
+ free (line);
+ return 1;
+ }
+
+ free (search_string);
+ search_string = line;
+
+ return 1;
+}
+
+/* Common entry point for the search functions. Arguments:
+ WINDOW The window to search in
+ COUNT The sign of this argument defines the search
+ direction (negative for searching backwards);
+ its absolute value gives number of repetitions.
+ CASE_SENSITIVE Whether the search is case-sensitive or not.
+*/
+static void
+info_search_1 (WINDOW *window, int count, int case_sensitive)
+{
+ int result;
+ int direction;
+ long start_off;
+ char *p;
+
+ if (count < 0)
+ {
+ direction = -1;
+ count = -count;
+ }
+ else
+ {
+ direction = 1;
+ if (count == 0)
+ count = 1; /* for backward compatibility */
+ }
+
+ if (!ask_for_search_string (case_sensitive, use_regex, direction)
+ || !search_string)
+ return;
+
+ start_off = window->point + direction;
+
+ /* If the search string includes upper-case letters, make the search
+ case-sensitive. */
+ if (case_sensitive == 0)
+ for (p = search_string; *p; p++)
+ if (isupper (*p))
+ {
+ case_sensitive = 1;
+ break;
+ }
+
+ last_search_direction = direction;
+ last_search_case_sensitive = case_sensitive;
+
+ for (result = 0; result == 0 && count--; )
+ result = info_search_internal (search_string,
+ active_window, direction, case_sensitive,
+ &start_off);
+
+ window_adjust_pagetop (window);
+
+ /* Perhaps free the unreferenced file buffers that were searched, but
+ not retained. */
+ gc_file_buffers_and_nodes ();
+}
+
+/* Set *T to an offset within the tags table of the node referenced by R,
+ using WINDOW for defaults. *FB is set to the file buffer structure for
+ the node. */
+static int
+tag_of_reference (REFERENCE *r, WINDOW *window, FILE_BUFFER **fb, TAG ***t)
+{
+ char *filename, *nodename;
+ int i;
+
+ filename = r->filename;
+ nodename = r->nodename;
+ if (!filename)
+ filename = window->node->fullpath;
+ if (!nodename || !*nodename)
+ nodename = "Top";
+
+ *fb = info_find_file (filename);
+ if (!*fb)
+ return 0;
+
+ for (i = 0; *(*t = &(*fb)->tags[i]); i++)
+ if (!strcmp (nodename, (**t)->nodename))
+ goto found_tag;
+ return 0;
+found_tag: ;
+ return 1;
+}
+
+/* Value for NODE.active_menu */
+#define BEFORE_MENU -99
+
+static void tree_search_check_node (WINDOW *window);
+static void tree_search_check_node_backwards (WINDOW *window);
+
+/* Search in WINDOW->node, and nodes reachable by menus from it, for
+ WINDOW->search_string. */
+static void
+tree_search_check_node (WINDOW *window)
+{
+ long start_off;
+ enum search_result result;
+ char *string;
+ int previous_match;
+
+ if (window->node->active_menu != 0)
+ previous_match = 1;
+ else
+ {
+ previous_match = 0;
+ window->node->active_menu = BEFORE_MENU;
+ }
+ string = xstrdup (window->search_string);
+ goto check_node;
+
+check_node:
+ result = info_search_in_node_internal (window, window->node,
+ string,
+ window->point + 1,
+ 1, /* Search forwards */
+ 1, /* Case-sensitive */
+ 0, /* No regular expressions. */
+ &start_off);
+ if (result == search_success)
+ {
+ info_show_point (window);
+ goto funexit;
+ }
+
+ /* Otherwise, try each menu entry in turn. */
+ if (matches_ready (&window->matches))
+ window->point++; /* Find this match again if/when we come back. */
+ goto check_menus;
+
+ /* At this juncture, window->node->active_menu is the index of the last
+ reference in the node to have been checked, plus one. BEFORE_MENU is a
+ special code to say that none of them have been checked. */
+check_menus:
+ if (!(window->node->flags & N_IsIndex)) /* Don't go down menus in index */
+ { /* nodes, because this leads to loops. */
+ REFERENCE *r;
+ int ref_index;
+ if (window->node->active_menu != BEFORE_MENU)
+ ref_index = window->node->active_menu;
+ else
+ ref_index = 0;
+ for (; (r = window->node->references[ref_index]); ref_index++)
+ if (r->type == REFERENCE_MENU_ITEM)
+ {
+ FILE_BUFFER *file_buffer;
+ TAG **tag;
+ NODE *node;
+
+ if (!tag_of_reference (r, window, &file_buffer, &tag))
+ continue;
+
+ if ((*tag)->flags & N_SeenBySearch)
+ continue;
+ (*tag)->flags |= N_SeenBySearch;
+
+ window->node->active_menu = ref_index + 1;
+ node = info_node_of_tag (file_buffer, tag);
+ if (!node)
+ continue;
+ info_set_node_of_window_fast (window, node);
+ window->node->active_menu = BEFORE_MENU;
+ goto check_node;
+ }
+ }
+ goto go_up;
+
+go_up:
+ /* If no more menu entries, try going back. */
+ if (window->hist_index >= 2
+ && window->hist[window->hist_index - 2]->node->active_menu != 0)
+ {
+ forget_node_fast (window);
+ goto check_menus;
+ }
+
+ /* Go back to the final match. */
+ if (previous_match)
+ {
+ message_in_echo_area (_("Going back to last match from %s"),
+ window->node->nodename);
+
+ /* This is a trick.
+ Set active_menu to one more than the number of references,
+ and add an arbitrary node to the window history.
+ When we call tree_search_check_node_backwards, this will go
+ backwards through the tree structure to the last match.
+ Change active_menu back to a valid value afterwards .*/
+ {
+ int n = 0;
+
+ while (window->node->references[n])
+ n++;
+ window->node->active_menu = n + 1;
+
+ info_parse_and_select ("Top", window);
+ /* Check if this worked. */
+ if (strcmp (window->node->nodename, "Top"))
+ {
+ /* Loading "Top" node failed. */
+ window->node->active_menu = 0;
+ goto funexit;
+ }
+ window->node->active_menu = BEFORE_MENU;
+ }
+ window->point = window->node->body_start;
+ tree_search_check_node_backwards (window);
+ }
+ info_error (previous_match ? _("No more matches") : _("Search failed"));
+
+funexit:
+ free (string);
+} /*********** end tree_search_check_node *************/
+
+
+/* The same as tree_search_check_node, but backwards. */
+static void
+tree_search_check_node_backwards (WINDOW *window)
+{
+ long start_off;
+ enum search_result result;
+ char *string;
+ int previous_match;
+
+ previous_match = (window->node->active_menu != 0);
+
+ string = xstrdup (window->search_string);
+ goto check_node;
+
+check_node:
+ result = info_search_in_node_internal (window, window->node,
+ string,
+ window->point - 1,
+ -1, /* Search backwards */
+ 1, /* Case-sensitive */
+ 0, /* No regular expressions. */
+ &start_off);
+ if (result == search_success)
+ {
+ info_show_point (window);
+ goto funexit;
+ }
+
+ goto go_up;
+
+ /* Check through menus in current node, in reverse order.
+ At this juncture, window->node->active_menu is the index of the last
+ reference in the node to have been checked, plus one. BEFORE_MENU is a
+ special code to say that none of them have been checked. */
+check_menus:
+ if (!(window->node->flags & N_IsIndex)) /* Don't go down menus in index */
+ { /* nodes, because this leads to loops. */
+ REFERENCE *r;
+ int ref_index;
+ if (window->node->active_menu == BEFORE_MENU)
+ goto check_node;
+ else
+ ref_index = window->node->active_menu - 2;
+ for (; ref_index >= 0; ref_index--)
+ {
+ r = window->node->references[ref_index];
+ if (r->type == REFERENCE_MENU_ITEM)
+ {
+ TAG **tag;
+ FILE_BUFFER *file_buffer;
+ NODE *node;
+
+ if (!tag_of_reference (r, window, &file_buffer, &tag))
+ continue;
+
+ /* This inverts what is done for the forwards search. It's
+ possible that we will visit the nodes in a different order if
+ there is more than one reference to a node. */
+ if (!((*tag)->flags & N_SeenBySearch))
+ continue;
+
+ node = info_node_of_tag (file_buffer, tag);
+ if (!node)
+ continue;
+ window->node->active_menu = ref_index + 1;
+ info_set_node_of_window_fast (window, node);
+ window->point = window->node->nodelen;
+ {
+ /* Start at the last menu entry in the subordinate node. */
+ int i;
+ i = 0;
+ while(window->node->references[i])
+ i++;
+ window->node->active_menu = i + 1;
+ }
+ goto check_menus;
+ }
+ }
+ }
+ window->node->active_menu = BEFORE_MENU;
+ goto check_node;
+
+ /* Try going back. */
+go_up:
+ if (window->hist_index >= 2
+ && window->hist[window->hist_index - 2]->node->active_menu != 0)
+ {
+ TAG **tag;
+ REFERENCE *r;
+ FILE_BUFFER *file_buffer;
+
+ forget_node_fast (window);
+ r = window->node->references[window->node->active_menu - 1];
+
+ /* Clear the flag to say we've been to the node we just came back
+ from. This reverse the order from the forwards search, where
+ we set this flag just before going down. */
+ if (r && tag_of_reference (r, window, &file_buffer, &tag))
+ {
+ (*tag)->flags &= ~N_SeenBySearch;
+ }
+
+ goto check_menus;
+ }
+
+ /* Otherwise, no result. */
+ info_error (previous_match ? _("No more matches") : _("Search failed"));
+
+funexit:
+ free (string);
+} /*********** end tree_search_check_node_backwards *************/
+
+
+/* Clear N_SeenBySearch for all nodes. */
+void
+wipe_seen_flags (void)
+{
+ int fb_index;
+ TAG **t;
+
+ for (fb_index = 0; fb_index < info_loaded_files_index; fb_index++)
+ {
+ t = info_loaded_files[fb_index]->tags;
+ if (!t)
+ continue; /* Probably a sub-file of a split file. */
+ for (; *t; t++)
+ {
+ (*t)->flags &= ~N_SeenBySearch;
+ }
+ }
+}
+
+DECLARE_INFO_COMMAND (info_tree_search,
+ _("Search this node and subnodes for a string"))
+{
+ char *prompt, *line;
+ int i;
+
+ /* TODO: Display manual name */
+ /* TRANSLATORS: %s is the title of a node. */
+ asprintf (&prompt, _("Search under %s: "),
+ window->node->nodename);
+ line = info_read_in_echo_area (prompt); free (prompt);
+ if (!line)
+ return;
+
+ /* Remove relics of previous tree searches. */
+ wipe_seen_flags ();
+ for (i = 0; i < window->hist_index; i++)
+ window->hist[i]->node->active_menu = 0;
+ window->search_string = line;
+ tree_search_check_node (window);
+}
+
+DECLARE_INFO_COMMAND (info_tree_search_next,
+ _("Go to next match in Info sub-tree"))
+{
+ if (!window->search_string || window->node->active_menu == 0)
+ {
+ info_error (_("No active search"));
+ return;
+ }
+
+ tree_search_check_node (window);
+}
+
+DECLARE_INFO_COMMAND (info_tree_search_previous,
+ _("Go to previous match in Info sub-tree"))
+{
+ if (!window->search_string || window->node->active_menu == 0)
+ {
+ info_error (_("No active search"));
+ return;
+ }
+
+ tree_search_check_node_backwards (window);
+}
+#undef BEFORE_MENU
+
+DECLARE_INFO_COMMAND (info_search_case_sensitively,
+ _("Read a string and search for it case-sensitively"))
+{
+ info_search_1 (window, count, 1);
+}
+
+DECLARE_INFO_COMMAND (info_search, _("Read a string and search for it"))
+{
+ info_search_1 (window, count, 0);
+}
+
+DECLARE_INFO_COMMAND (info_search_backward,
+ _("Read a string and search backward for it"))
+{
+ info_search_1 (window, -count, 0);
+}
+
+int search_skip_screen_p = 0;
+
+DECLARE_INFO_COMMAND (info_search_next,
+ _("Repeat last search in the same direction"))
+{
+ long start_off = window->point + 1;
+ NODE *starting_node = window->node;
+ int result;
+
+ if (!last_search_direction || !search_string)
+ {
+ info_error ("%s", _("No previous search string"));
+ return;
+ }
+
+ if (search_skip_screen_p)
+ {
+ /* Find window bottom */
+ long n = window->height + window->pagetop;
+ if (n < window->line_count)
+ start_off = window->line_starts[n];
+ else
+ start_off = window->node->nodelen;
+ }
+
+ for (result = 0; result == 0 && count--; )
+ result = info_search_internal (search_string,
+ active_window, 1,
+ last_search_case_sensitive,
+ &start_off);
+
+ if (result == 0 && window->node == starting_node && search_skip_screen_p)
+ {
+ long match_line = window_line_of_point (window);
+ long new_pagetop;
+
+ /* Scroll down a whole number of screenfulls to make match visible. */
+ new_pagetop = window->pagetop;
+ new_pagetop += (match_line - window->pagetop) / window->height
+ * window->height;
+
+ set_window_pagetop (window, new_pagetop);
+ }
+ else
+ window_adjust_pagetop (window);
+}
+
+DECLARE_INFO_COMMAND (info_search_previous,
+ _("Repeat last search in the reverse direction"))
+{
+ long start_off = window->point - 1;
+ NODE *starting_node = window->node;
+ int result;
+
+ if (!last_search_direction || !search_string)
+ {
+ info_error ("%s", _("No previous search string"));
+ return;
+ }
+
+ if (search_skip_screen_p)
+ start_off = window->line_starts[window->pagetop] - 1;
+
+ /* start_off can be negative here, in which case info_search_internal
+ will go to the previous node straight away. */
+
+ for (result = 0; result == 0 && count--; )
+ result = info_search_internal (search_string,
+ active_window, -1,
+ last_search_case_sensitive,
+ &start_off);
+
+ if (result == 0 && window->node == starting_node && search_skip_screen_p)
+ {
+ long match_line = window_line_of_point (window);
+ long new_pagetop;
+
+ /* Scroll up a whole number of screenfulls to make match visible.
+ This means if 'info_search_next' was the last command, we'll
+ go back to the same place. */
+ new_pagetop = window->pagetop - window->height;
+ new_pagetop -= (window->pagetop - match_line - 1) / window->height
+ * window->height;
+
+ if (new_pagetop < 0)
+ new_pagetop = 0;
+ set_window_pagetop (window, new_pagetop);
+ }
+ else
+ window_adjust_pagetop (window);
+}
+
+/* If highlight-searches=On, this will clear any highlighted regions on the
+ screen. */
+DECLARE_INFO_COMMAND (info_clear_search,
+ _("Clear displayed search matches"))
+{
+ free_matches (&window->matches);
+ window->flags |= W_UpdateWindow;
+}
+
+
+/* **************************************************************** */
+/* */
+/* Incremental Searching */
+/* */
+/* **************************************************************** */
+
+static void incremental_search (WINDOW *window, int count);
+
+DECLARE_INFO_COMMAND (isearch_forward,
+ _("Search interactively for a string as you type it"))
+{
+ incremental_search (window, count);
+}
+
+DECLARE_INFO_COMMAND (isearch_backward,
+ _("Search interactively for a string as you type it"))
+{
+ incremental_search (window, -count);
+}
+
+/* Structure defining the current state of an incremental search. */
+typedef struct {
+ char *fullpath;
+ char *nodename;
+ long pagetop; /* LINE_STARTS[PAGETOP] is first line in WINDOW. */
+ long point; /* Point in window. */
+ long start; /* Offset in node contents where search started. */
+ int search_index; /* Offset of the last char in the search string. */
+ int direction; /* The direction that this search is heading in. */
+ enum search_result failing; /* Whether or not this search failed. */
+} SEARCH_STATE;
+
+/* 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;
+
+/* Array of search states. */
+static SEARCH_STATE **isearch_states = NULL;
+static size_t isearch_states_index = 0;
+static size_t isearch_states_slots = 0;
+
+/* Get the state of WINDOW, and save it in STATE. */
+static void
+window_get_state (WINDOW *window, SEARCH_STATE *state)
+{
+ state->fullpath = window->node->fullpath;
+ state->nodename = window->node->nodename;
+ state->pagetop = window->pagetop;
+ state->point = window->point;
+}
+
+/* Set the node, pagetop, and point of WINDOW. */
+static void
+window_set_state (WINDOW *window, SEARCH_STATE *state)
+{
+ if (strcmp(window->node->fullpath, state->fullpath)
+ || strcmp(window->node->nodename, state->nodename))
+ {
+ NODE *n = info_get_node (state->fullpath, state->nodename);
+ info_set_node_of_window (window, n);
+ }
+ window->pagetop = state->pagetop;
+ window->point = state->point;
+}
+
+/* Push the state of this search. */
+static void
+push_isearch (WINDOW *window, int search_index, int direction,
+ enum search_result failing, long start_off)
+{
+ SEARCH_STATE *state;
+
+ state = xmalloc (sizeof (SEARCH_STATE));
+ window_get_state (window, state);
+ state->search_index = search_index;
+ state->direction = direction;
+ state->failing = failing;
+ state->start = start_off;
+
+ add_pointer_to_array (state, isearch_states_index, isearch_states,
+ isearch_states_slots, 20);
+}
+
+/* Pop the state of this search to WINDOW, SEARCH_INDEX, and DIRECTION. */
+static void
+pop_isearch (WINDOW *window, int *search_index, int *direction,
+ enum search_result *failing, long *start_off)
+{
+ 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;
+ *start_off = state->start;
+
+ 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,
+ enum search_result failing)
+{
+ 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 != search_success)
+ prompt_len += strlen (_("Failing "));
+ prompt = xmalloc (prompt_len);
+ sprintf (prompt, "%s%s%s",
+ failing != search_success ? _("Failing ") : "",
+ prefix, p_rep ? p_rep : "");
+
+ window_message_in_echo_area ("%s", prompt);
+ free (p_rep);
+ free (prompt);
+ display_cursor_at_point (active_window);
+}
+
+/* Read and dispatch loop for incremental search mode. */
+static void
+incremental_search (WINDOW *window, int count)
+{
+ int key;
+ enum search_result last_search_result, search_result;
+ int dir;
+ SEARCH_STATE orig_state; /* Window state at start of incremental search. */
+ SEARCH_STATE mystate; /* State before each search. */
+ char *p;
+ int case_sensitive;
+ long start_off = window->point;
+ int starting_history_entry = window->hist_index - 1;
+
+ if (count < 0)
+ dir = -1;
+ else
+ dir = 1;
+
+ last_search_result = search_result = search_success;
+
+ window_get_state (window, &orig_state);
+
+ isearch_string_index = 0;
+ if (!isearch_string_size)
+ isearch_string = xmalloc (isearch_string_size = 50);
+
+ isearch_string[isearch_string_index] = '\0';
+
+ isearch_is_active = 1;
+
+ /* Save starting position of search. */
+ push_isearch (window, isearch_string_index, dir, search_result, start_off);
+
+ while (isearch_is_active)
+ {
+ VFunction *func = NULL;
+ int quoted = 0;
+
+ /* Show the search string in the echo area. */
+ show_isearch_prompt (dir, (unsigned char *) isearch_string,
+ search_result);
+
+ /* 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_display ();
+ display_cursor_at_point (active_window);
+ }
+
+ /* Read keys, looking in both keymaps for a recognized key sequence. */
+ {
+ Keymap info_kp, ea_kp;
+
+ info_kp = info_keymap;
+ ea_kp = echo_area_keymap;
+
+ key = get_input_key ();
+
+ while (1)
+ {
+ if (key >= 32 && key < 256)
+ break;
+
+ if (info_kp && info_kp[key].type == ISFUNC)
+ {
+ if (info_kp[key].value.function)
+ func = info_kp[key].value.function->func;
+ if (func == &isearch_forward
+ || func == &isearch_backward
+ || func == &info_abort_key)
+ {
+ goto gotfunc;
+ }
+ else
+ {
+ func = 0;
+ info_kp = 0;
+ }
+ }
+ else if (info_kp) /* ISKMAP */
+ info_kp = info_kp[key].value.keymap;
+
+ if (ea_kp && ea_kp[key].type == ISFUNC)
+ {
+ if (ea_kp[key].value.function)
+ func = ea_kp[key].value.function->func;
+ if (func == &ea_abort
+ || func == &ea_quoted_insert
+ || func == &ea_rubout)
+ {
+ func = ea_kp[key].value.function->func;
+ goto gotfunc;
+ }
+ else
+ {
+ func = 0;
+ ea_kp = 0;
+ }
+ }
+ else if (ea_kp) /* ISKMAP */
+ ea_kp = ea_kp[key].value.keymap;
+
+ if (!info_kp && !ea_kp)
+ break;
+
+ key = get_input_key ();
+ }
+ }
+gotfunc:
+
+ if (func == &ea_quoted_insert)
+ {
+ /* User wants to insert a character. */
+ key = get_input_key ();
+ if (key < 0 || key >= 256)
+ continue; /* The user pressed a key like an arrow key. */
+ quoted = 1;
+ }
+
+ if (quoted || (!func && key >= 32 && key < 256))
+ {
+ push_isearch (window, isearch_string_index, dir,
+ search_result, start_off);
+
+ 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';
+
+ if (search_result != search_success && !use_regex)
+ continue;
+ }
+ else if (func == &ea_rubout)
+ {
+ /* User wants to delete one level of search? */
+ if (!isearch_states_index)
+ {
+ terminal_ring_bell ();
+ continue;
+ }
+ else
+ {
+ int end = isearch_string_index;
+ /* Remove a complete multi-byte character from the end of the
+ search string. */
+ do
+ {
+ pop_isearch (window, &isearch_string_index,
+ &dir, &search_result, &start_off);
+ }
+ while (isearch_string_index > 0
+ && (long) mbrlen (isearch_string + isearch_string_index,
+ end - isearch_string_index, NULL) <= 0);
+
+ isearch_string[isearch_string_index] = '\0';
+
+ if (isearch_string_index == 0)
+ {
+ /* Don't search for an empty string. Clear the search. */
+ free_matches (&window->matches);
+ display_update_one_window (window);
+ continue;
+ }
+ if (search_result != search_success)
+ {
+ display_update_one_window (window);
+ continue;
+ }
+ }
+ }
+ else if (func == &isearch_forward || func == &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 == &isearch_forward && dir > 0
+ || func == &isearch_backward && dir < 0)
+ {
+ /* If the user has typed no characters, then insert the
+ last successful search into the current search string. */
+ if (isearch_string_index == 0)
+ {
+ /* Of course, there must be something to insert. */
+ if (last_isearch_accepted)
+ {
+ if (strlen ((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);
+ }
+ 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 == search_success)
+ {
+ start_off = window->point;
+ if (dir < 0)
+ /* Position before match to avoid finding same match
+ agin. */
+ start_off--;
+ }
+ }
+ }
+ else
+ {
+ /* Reverse the direction of the search. */
+ dir = -dir;
+ }
+ }
+ else if (func == &info_abort_key
+ && isearch_states_index && search_result != search_success)
+ {
+ /* If C-g pressed, and the search is failing, pop the search
+ stack back to the last unfailed search. */
+ terminal_ring_bell ();
+ while (isearch_states_index && search_result != search_success)
+ pop_isearch (window, &isearch_string_index, &dir,
+ &search_result, &start_off);
+ isearch_string[isearch_string_index] = '\0';
+ show_isearch_prompt (dir, (unsigned char *) isearch_string,
+ search_result);
+ continue;
+ }
+ else if (func == &info_abort_key || func == &ea_abort || !func)
+ {
+ /* The character is not printable, or it has a function which is
+ non-null. Exit the search, remembering the search string. */
+ if (isearch_string_index && func != &info_abort_key)
+ {
+ free (last_isearch_accepted);
+ last_isearch_accepted = xstrdup (isearch_string);
+ }
+
+ if (func == &info_abort_key)
+ {
+ if (isearch_states_index)
+ window_set_state (window, &orig_state);
+ }
+
+ if (!echo_area_is_active)
+ window_clear_echo_area ();
+
+ if (auto_footnotes_p)
+ info_get_or_remove_footnotes (active_window);
+
+ isearch_is_active = 0;
+ continue;
+ }
+
+ /* Show the new search string in the prompt. */
+ show_isearch_prompt (dir, (unsigned char *) isearch_string,
+ search_result);
+
+ /* Make the search case-sensitive only if the search string includes
+ upper-case letters. */
+ case_sensitive = 0;
+ for (p = isearch_string; *p; p++)
+ if (isupper (*p))
+ {
+ case_sensitive = 1;
+ break;
+ }
+
+ last_search_result = search_result;
+
+ window_get_state (window, &mystate);
+ search_result = info_search_internal (isearch_string,
+ window, dir, case_sensitive,
+ &start_off);
+
+ /* If this search failed, and we didn't already have a failed search,
+ then ring the terminal bell. */
+ if (search_result != search_success
+ && last_search_result == search_success)
+ {
+ terminal_ring_bell ();
+ }
+ else if (search_result == search_success)
+ {
+ /* Make sure the match is visible, and update the display. */
+
+ if (!strcmp(window->node->fullpath, mystate.fullpath)
+ && !strcmp(window->node->nodename, mystate.nodename)
+ && mystate.pagetop != window->pagetop)
+ {
+ int newtop = window->pagetop;
+ window->pagetop = mystate.pagetop;
+ set_window_pagetop (window, newtop);
+ }
+ window_adjust_pagetop (window);
+
+ /* Call display_update_display to update the window and an automatic
+ footnotes window if present. */
+ display_update_display ();
+ display_cursor_at_point (window);
+ }
+ }
+
+ /* Free the memory used to remember each search state. */
+ free_isearch_states ();
+
+ /* Alter the window history so that we have added at most one node in the
+ incremental search, so that going back once with "l" goes to where we
+ started the incremental search if the match was in a different node. */
+ {
+ int i = window->hist_index - 1;
+ int j = starting_history_entry;
+
+ if (i > j)
+ {
+ if (!strcmp(window->hist[i]->node->nodename,
+ window->hist[j]->node->nodename)
+ && !strcmp(window->hist[j]->node->fullpath,
+ window->hist[i]->node->fullpath))
+ {
+ /* If we end up at the same node we started at, don't extend
+ the history at all. */
+ cleanup_history (window, j, i);
+ }
+ else
+ {
+ cleanup_history (window, j + 1, i);
+ }
+ }
+ }
+
+ /* Perhaps GC some file buffers. */
+ gc_file_buffers_and_nodes ();
+
+ /* After searching, leave the window in the correct state. */
+ if (!echo_area_is_active)
+ window_clear_echo_area ();
+}
+
+
+/* **************************************************************** */
+/* */
+/* 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 ("%s", _("Quit"));
+
+ info_initialize_numeric_arg ();
+}
+
+DECLARE_INFO_COMMAND (info_info_version, _("Display version of Info being run"))
+{
+ window_message_in_echo_area (_("GNU Info version %s"), VERSION);
+}
+
+/* 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 ();
+ }
+ 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);
+ }
+}
+
+/* Exit from info */
+DECLARE_INFO_COMMAND (info_quit, _("Quit using Info"))
+{
+ if (window->next || window->prev)
+ info_delete_window (window, count);
+ else
+ quit_info_immediately = 1;
+}
+
+
+/* **************************************************************** */
+/* */
+/* Reading Keys and Dispatching on Them */
+/* */
+/* **************************************************************** */
+
+DECLARE_INFO_COMMAND (info_do_lowercase_version,
+ _("Run command bound to this key's lowercase variant"))
+{} /* Declaration only. */
+
+static void
+dispatch_error (int *keyseq)
+{
+ char *rep;
+
+ rep = pretty_keyseq (keyseq);
+
+ if (!echo_area_is_active)
+ info_error (_("Unknown command (%s)"), rep);
+ 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 int *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 (int character)
+{
+ if (info_keyseq_index + 2 >= info_keyseq_size)
+ info_keyseq = xrealloc (info_keyseq,
+ sizeof (int) * (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;
+
+ if (!info_keyseq || info_keyseq_index == 0)
+ return;
+
+ 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 another key when keys have already
+ been read as part of the current command (and possibly displayed in status
+ line with display_info_keyseq). */
+int
+get_another_input_key (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 ())
+ {
+#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 get_input_key ();
+}
+
+/* Non-zero means that an explicit argument has been passed to this
+ command, as in C-u C-v. */
+int info_explicit_arg = 0;
+
+/* As above, but used when C-u is typed in the echo area to avoid
+ overwriting this information when "C-u ARG M-x" is typed. */
+int ea_explicit_arg = 0;
+
+void info_universal_argument (WINDOW *, int count);
+void info_add_digit_to_numeric_arg (WINDOW *, int count);
+
+/* Read a key sequence and look up its command in MAP. Handle C-u style
+ numeric args, as well as M--, and M-digits. Return argument in COUNT if it
+ is non-null.
+
+ Some commands can be executed directly, in which case null is returned
+ instead:
+
+ If MENU, call info_menu_digit on ACTIVE_WINDOW if a number key was
+ pressed.
+
+ If MOUSE, call mouse_event_handler if a mouse event occurred.
+
+ If INSERT, call ea_insert if a printable character was input.
+ */
+VFunction *
+read_key_sequence (Keymap map, int menu, int mouse,
+ int insert, int *count)
+{
+ int key;
+ int reading_universal_argument = 0;
+
+ int numeric_arg = 1, numeric_arg_sign = 1, *which_explicit_arg;
+ VFunction *func;
+
+ /* Process the right numeric argument. */
+ if (!echo_area_is_active)
+ which_explicit_arg = &info_explicit_arg;
+ else
+ which_explicit_arg = &ea_explicit_arg;
+
+ *which_explicit_arg = 0;
+
+ initialize_keyseq ();
+
+ key = get_input_key ();
+ if (key == KEY_MOUSE)
+ {
+ if (mouse)
+ mouse_event_handler ();
+ return 0;
+ }
+
+ if (insert
+ && (key >= 040 && key < 0200
+ || ISO_Latin_p && key >= 0200 && key < 0400))
+ {
+ ea_insert (the_echo_area, 1, key);
+ return 0;
+ }
+
+ add_char_to_keyseq (key);
+
+ while (1)
+ {
+ int dash_typed = 0, digit_typed = 0;
+ func = 0;
+
+ if (display_was_interrupted_p && !info_any_buffered_input_p ())
+ display_update_display ();
+
+ if (active_window != the_echo_area)
+ display_cursor_at_point (active_window);
+
+ /* If reading a universal argument, both <digit> and M-<digit> help form
+ the argument. Don't look up the pressed key in the key map. */
+ if (reading_universal_argument)
+ {
+ int k = key;
+ if (k >= KEYMAP_META_BASE)
+ k -= KEYMAP_META_BASE;
+ if (k == '-')
+ {
+ dash_typed = 1;
+ }
+ else if (isdigit (k))
+ {
+ digit_typed = 1;
+ }
+ else
+ /* Note: we may still read another C-u after this. */
+ reading_universal_argument = 0;
+ }
+
+ if (!dash_typed && !digit_typed && map[key].type == ISFUNC)
+ {
+ func = map[key].value.function ? map[key].value.function->func : 0;
+ if (!func)
+ {
+ dispatch_error (info_keyseq);
+ return 0;
+ }
+ }
+
+ if (dash_typed || digit_typed || func == &info_add_digit_to_numeric_arg)
+ {
+ int k = key;
+ if (k > KEYMAP_META_BASE)
+ k -= KEYMAP_META_BASE;
+ reading_universal_argument = 1;
+ if (dash_typed || k == '-')
+ {
+ if (!*which_explicit_arg)
+ {
+ numeric_arg_sign = -1;
+ numeric_arg = 1;
+ }
+
+ }
+ else if (digit_typed || isdigit (k))
+ {
+ if (*which_explicit_arg)
+ numeric_arg = numeric_arg * 10 + (k - '0');
+ else
+ numeric_arg = (k - '0');
+ *which_explicit_arg = 1;
+ }
+ }
+ else if (func == info_do_lowercase_version)
+ {
+ int lowerkey;
+
+ if (key >= KEYMAP_META_BASE)
+ {
+ lowerkey = key;
+ lowerkey -= KEYMAP_META_BASE;
+ lowerkey = tolower (lowerkey);
+ lowerkey += KEYMAP_META_BASE;
+ }
+ else
+ lowerkey = tolower (key);
+
+ if (lowerkey == key)
+ {
+ dispatch_error (info_keyseq);
+ return 0;
+ }
+ key = lowerkey;
+ continue;
+ }
+ else if (func == &info_universal_argument)
+ {
+ /* Multiply by 4. */
+ /* TODO: Maybe C-u should also terminate the universal argument
+ sequence, as in Emacs. (C-u 6 4 C-u 1 inserts 64 1's.) */
+ if (!*which_explicit_arg)
+ numeric_arg *= 4;
+ reading_universal_argument = 1;
+ }
+ else if (menu && func == &info_menu_digit)
+ {
+ /* key can either be digit, or M-digit for --vi-keys. */
+
+ int k = key;
+ if (k > KEYMAP_META_BASE)
+ k -= KEYMAP_META_BASE;
+ window_clear_echo_area ();
+ menu_digit (active_window, k);
+ return 0;
+ }
+ else if (insert
+ && (func == &ea_possible_completions || func == &ea_complete)
+ && !echo_area_completion_items)
+ {
+ ea_insert (the_echo_area, 1, key);
+ return 0;
+ }
+ else if (func)
+ {
+ /* Don't update the key sequence if we have finished reading a key
+ sequence in the echo area. This means that a key sequence like
+ "C-u 2 Left" appears to take effect immediately, instead of there
+ being a delay while the message is displayed. */
+ if (!echo_area_is_active && info_keyseq_displayed_p)
+ display_info_keyseq (0);
+
+ if (count)
+ *count = numeric_arg * numeric_arg_sign;
+
+ /* *which_explicit_arg has not been set yet if only a sequence of
+ C-u's was typed (each of which has multiplied the argument by
+ four). */
+ if (*count != 1 && !*which_explicit_arg)
+ *which_explicit_arg = 1;
+
+ return func;
+ }
+ else if (map[key].type == ISKMAP)
+ {
+ if (map[key].value.keymap != NULL)
+ map = map[key].value.keymap;
+ else
+ {
+ dispatch_error (info_keyseq);
+ return 0;
+ }
+
+ if (info_keyseq_displayed_p)
+ display_info_keyseq (1);
+ }
+
+ do
+ key = get_another_input_key ();
+ while (key == KEY_MOUSE);
+ add_char_to_keyseq (key);
+ }
+
+ return 0;
+}
+
+/* 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"))
+{} /* Declaration only. */
+
+/* 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"))
+{} /* Declaration only. */
+
+/* Create a default argument. */
+void
+info_initialize_numeric_arg (void)
+{
+ if (!echo_area_is_active)
+ {
+ info_explicit_arg = 0;
+ }
+ else
+ {
+ ea_explicit_arg = 0;
+ }
+}
+
diff --git a/macros/texinfo/texinfo/info/session.h b/macros/texinfo/texinfo/info/session.h
new file mode 100644
index 0000000000..4227654623
--- /dev/null
+++ b/macros/texinfo/texinfo/info/session.h
@@ -0,0 +1,131 @@
+/* session.h -- Functions found in session.c.
+
+ Copyright 1993-2019 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. */
+
+#ifndef SESSION_H
+#define SESSION_H
+
+#include "info.h"
+#include "window.h"
+#include "dribble.h"
+
+/* 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;
+
+/* 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;
+
+/* Controls what to do when a scrolling command is issued at the end of the
+ last node. */
+extern int scroll_last_node;
+
+/* Values for scroll_last_node */
+#define SLN_Stop 0 /* Stop at the last node */
+#define SLN_Top 1 /* Go to the top node */
+
+int get_input_key (void);
+int get_another_input_key (void);
+
+VFunction *read_key_sequence (Keymap map, int menu, int mouse,
+ int insert, int *count);
+unsigned char info_input_pending_p (void);
+void info_set_node_of_window (WINDOW *window, NODE *node);
+void info_set_node_of_window_fast (WINDOW *window, NODE *node);
+void initialize_keyseq (void);
+void add_char_to_keyseq (int character);
+FILE_BUFFER *file_buffer_of_window (WINDOW *window);
+int info_select_reference (WINDOW *window, REFERENCE *entry);
+int info_any_buffered_input_p (void);
+void pause_or_input (void);
+
+void dump_nodes_to_file (REFERENCE **references,
+ char *output_filename, int flags);
+int write_node_to_stream (NODE *node, FILE *stream);
+
+char *program_name_from_file_name (char *file_name);
+
+/* Do the physical deletion of WINDOW, and forget this window and
+ associated nodes. */
+void info_delete_window_internal (WINDOW *window);
+
+void forget_window_and_nodes (WINDOW *window);
+void forget_node (WINDOW *win);
+int forget_node_fast (WINDOW *win);
+
+/* Tell Info that input is coming from the file FILENAME. */
+void info_set_input_from_file (char *filename);
+
+/* Error and debugging messages */
+extern unsigned debug_level;
+
+#define debug(n,c) \
+ do \
+ { \
+ if (debug_level >= (n)) \
+ info_debug c; \
+ } \
+ while (0)
+
+void info_debug (const char *format, ...) TEXINFO_PRINTFLIKE(1,2);
+
+/* Print args as per FORMAT. 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, ...) TEXINFO_PRINTFLIKE(1,2);
+
+void initialize_info_session (void);
+void info_read_and_dispatch (void);
+void close_info_session (void);
+void info_session (REFERENCE **ref_list, char *user_filename, char *error);
+void initialize_terminal_and_keymaps (char *init_file);
+REFERENCE *info_intuit_options_node (NODE *initial_node, char *program);
+
+void info_scroll_forward (WINDOW *window, int count);
+void info_abort_key (WINDOW *window, int count);
+
+NODE *info_follow_menus (NODE *initial_node, char **menus,
+ char **error_msg, int strict);
+
+/* Adding numeric arguments. */
+extern int info_explicit_arg;
+extern int ea_explicit_arg;
+void info_initialize_numeric_arg (void);
+
+/* Found in m-x.c. */
+char *read_function_name (char *prompt, WINDOW *window);
+
+void show_error_node (char *error_msg);
+
+#endif /* not SESSION_H */
diff --git a/macros/texinfo/texinfo/info/signals.c b/macros/texinfo/texinfo/info/signals.c
new file mode 100644
index 0000000000..78f30ee84a
--- /dev/null
+++ b/macros/texinfo/texinfo/info/signals.c
@@ -0,0 +1,345 @@
+/* signals.c -- install and maintain signal handlers.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+#include "display.h"
+#include "footnotes.h"
+#include "window.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 (SIGTERM)
+ sigaddset (set, SIGTERM);
+#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_TERM, 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 (SIGTERM)
+ set_termsig (SIGTERM, &old_TERM);
+#endif
+
+#if defined (SIGUSR1)
+ /* Used by DJGPP to simulate SIGTSTP on Ctrl-Z. */
+ set_termsig (SIGUSR1, &old_USR1);
+#endif
+}
+
+void
+redisplay_after_signal (void)
+{
+ terminal_clear_screen ();
+ display_clear_display (the_display);
+ if (auto_footnotes_p)
+ info_get_or_remove_footnotes (active_window);
+ window_mark_chain (windows, W_UpdateWindow);
+ display_update_display ();
+ display_cursor_at_point (active_window);
+ fflush (stdout);
+}
+
+void
+reset_info_window_sizes (void)
+{
+ terminal_get_screen_size ();
+ display_initialize_display (screenwidth, screenheight);
+ window_new_screen_size (screenwidth, screenheight);
+ redisplay_after_signal ();
+}
+
+/* Number of times we were told to ignore SIGWINCH. */
+static int sigwinch_block_count = 0;
+
+void
+signal_block_winch (void)
+{
+#if defined (SIGWINCH)
+ if (sigwinch_block_count == 0)
+ BLOCK_SIGNAL (SIGWINCH);
+ sigwinch_block_count++;
+#endif
+}
+
+void
+signal_unblock_winch (void)
+{
+#if defined (SIGWINCH)
+ sigwinch_block_count--;
+ if (sigwinch_block_count == 0)
+ UNBLOCK_SIGNAL (SIGWINCH);
+#endif
+}
+
+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 (SIGTERM)
+ case SIGTERM:
+#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 */
+#if defined (SIGTERM)
+ if (sig == SIGTERM)
+ old_signal_handler = &old_TERM;
+#endif /* SIGTERM */
+
+ /* 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
+
+ /* This seems risky: what if we receive a (real) signal before
+ the next line is reached? */
+#if 0
+ restore_termsig (sig, old_signal_handler);
+ kill (getpid (), sig);
+#endif
+
+ /* After our old signal handler returns... */
+ set_termsig (sig, old_signal_handler); /* needless? */
+
+ if (sigwinch_block_count != 0)
+ abort ();
+
+ /* Avoid any of the code unblocking the signal too early. This
+ should set the variable to 1 because we shouldn't be here if
+ sigwinch_block_count > 0. */
+ sigwinch_block_count++;
+
+ reset_info_window_sizes ();
+
+ sigwinch_block_count--;
+ /* Don't unblock the signal until after we've finished. */
+ UNBLOCK_SIGNAL (sig);
+ }
+ 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/macros/texinfo/texinfo/info/signals.h b/macros/texinfo/texinfo/info/signals.h
new file mode 100644
index 0000000000..fc954cbd54
--- /dev/null
+++ b/macros/texinfo/texinfo/info/signals.h
@@ -0,0 +1,97 @@
+/* signals.h -- header to include system dependent signal definitions.
+
+ Copyright 1993-2019 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. */
+
+#ifndef INFO_SIGNALS_H
+#define INFO_SIGNALS_H
+
+#include <sys/types.h>
+#include <signal.h>
+
+void signal_block_winch (void);
+void signal_unblock_winch (void);
+
+/* 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/macros/texinfo/texinfo/info/t/Infokey-config b/macros/texinfo/texinfo/info/t/Infokey-config
new file mode 100644
index 0000000000..31b4921f89
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/Infokey-config
@@ -0,0 +1,11 @@
+#info
+? xxx-not-recognized # Check that unknown actions don't break.
+! menu-digit # menu-digit can't be rebound
+D print-node # Dump node to file
+#echo-area
+^x echo-area-abort # as C-g discards typeahead and ESC used for Meta key
+#var
+infopath-no-defaults=On
+automatic-footnotes=On
+key-time=0
+nodeline=print
diff --git a/macros/texinfo/texinfo/info/t/Init-test.inc b/macros/texinfo/texinfo/info/t/Init-test.inc
new file mode 100644
index 0000000000..ec214b92aa
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/Init-test.inc
@@ -0,0 +1,183 @@
+# Copyright (C) 2014-2018 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, 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/>.
+
+# Set up standalone info test environment
+# This file is to be sourced, not to be run directly
+
+# write each expanded command to the *.log file for the test.
+set -x
+
+# Use the second line to run the program under valgrind.
+ginfo="./ginfo --init-file $srcdir/t/Infokey-config"
+#ginfo="valgrind --log-file=$0.val.log --leak-check=full ./ginfo --init-file $srcdir/t/Infokey-config"
+
+# Only look for Info files in our test directory
+INFOPATH=$srcdir/t/infodir; export INFOPATH
+
+t=$srcdir/t; export t
+
+ginfo_output=t/`basename $0.out`
+
+# These are only used for interactive tests
+pipein=t/`basename $0.pipein`
+pty_type=t/`basename $0.pipeout`
+
+# Remove left over file from previous tests
+rm -f $ginfo_output
+
+# File to dump nodes to with M-x print-node
+INFO_PRINT_COMMAND=">$ginfo_output"; export INFO_PRINT_COMMAND
+
+# Not an interactive test
+pty_pid=0
+
+# Get error messages in English
+LC_ALL=C; export LC_ALL
+
+# Make sure that non-interactive operation works even if terminal is dumb
+TERM=dumb; export TERM
+
+# Clean up if the test is interrupted, for example if the user types
+# C-c, to avoid lingering child processes. Signal 2 is SIGINT.
+trap cleanup 2
+
+# Cleanup and exit
+cleanup ()
+{
+ # Delete created files and kill spawned processes if any.
+ test $pty_pid -ne 0 && kill $pty_pid
+
+ rm -f $ginfo_output
+ rm -f $pipein $pty_type
+
+ #killall `basename $0` # see below
+ exit $retval
+}
+
+# Uncomment this line and "killall" above if previous test runs were not
+# cleaned up properly, and rerun "make check".
+
+#cleanup
+
+timeout_test ()
+{
+ wait $ginfo_pid
+ status=$?
+
+ # If the program was not ended by a signal, kill the subshell that
+ # is waiting to send it a signal.
+ test $status -lt 128 && kill $killer_pid
+
+ retval=0
+ if test $status != 0; then
+ retval=1
+ fi
+
+ # Program is gone, so avoid trying to kill it in cleanup
+ ginfo_pid=0
+}
+
+# Functions for interactive tests
+
+
+# we may look up whether a couple of utilities exist.
+
+path_sep=":"
+
+# findprog PROG - Return true if PROG is somewhere in PATH, else false.
+findprog ()
+{
+ saveIFS=$IFS
+ IFS=$path_sep # break path components at the path separator
+ for dir in $PATH; do
+ IFS=$saveIFS
+ # The basic test for an executable is `test -f $f && test -x $f'.
+ # (`test -x' is not enough, because it can also be true for directories.)
+ # We have to try this both for $1 and $1.exe.
+ #
+ # Note: On Cygwin and DJGPP, `test -x' also looks for .exe. On Cygwin,
+ # also `test -f' has this enhancement, but not on DJGPP. (Both are
+ # design decisions, so there is little chance to make them consistent.)
+ # Thusly, it seems to be difficult to make use of these enhancements.
+ #
+ if { test -f "$dir/$1" && test -x "$dir/$1"; } \
+ || { test -f "$dir/$1.exe" && test -x "$dir/$1.exe"; }; then
+ return 0
+ fi
+ done
+ return 1
+}
+
+# Initialize test of interactive operation
+initialization_done=0
+init_interactive_test ()
+{
+ test $initialization_done -eq 1 && return 0
+ initialization_done=1
+
+ # Skip test if pseudotty wasn't built
+ test -x pseudotty || exit 77
+
+ # Avoid ginfo complaining that terminal is too dumb
+ TERM=vt100; export TERM
+
+ # Create named pipes to communicate with pseudotty program, or quit.
+ rm -f $pipein $pty_type # must already be defined
+ if findprog mkfifo; then
+ mkfifo $pipein
+ mkfifo $pty_type
+ #
+ if test ! -r $pipein || test ! -r $pty_type; then
+ echo "$0: could not mkfifo pipes" >&2
+ exit 77
+ fi
+ # ok, we'll proceed with the test.
+ else
+ echo "$0: mkfifo program not found - cannot make named pipes" >&2
+ exit 77
+ fi
+
+ # We can feed input bytes into $pty_type to be passed onto ginfo, as
+ # if they were typed by a user in an interactive session.
+ # We redirect to the FIFO within a subshell, because under NetBSD 6.1.4
+ # it hangs otherwise.
+ (exec ./pseudotty "$pty_type" >$pipein) &
+ pty_pid=$!
+ # Get name of pseudo-terminal slave device
+ read pts_device <$pipein
+
+ # Keeping the FIFO open for writing prevents the select call in
+ # pseudotty returning EOF after a single "printf something >$pty_type".
+ exec 7>$pty_type
+
+ # glibc can kill a running process if it detects a condition like a
+ # double free. This specifies that the message it prints when it does
+ # this should be sent to stderr so it can be recorded in the test *.log
+ # files.
+ LIBC_FATAL_STDERR_=1; export LIBC_FATAL_STDERR
+}
+
+run_ginfo ()
+{
+ init_interactive_test
+
+ (
+ exec 7>&- ; # Close fd from parent shell
+ exec $ginfo "$@" 0<>$pts_device 1<&0 ;
+ ) &
+ ginfo_pid=$!
+ (sleep 3 ; kill $ginfo_pid) &
+ killer_pid=$!
+}
diff --git a/macros/texinfo/texinfo/info/t/README b/macros/texinfo/texinfo/info/t/README
new file mode 100644
index 0000000000..3817ff4dc7
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/README
@@ -0,0 +1,61 @@
+This directory contains shell scripts used as tests for Info. They are
+listed in "info/Makefile.am" and will usually be run with "make check".
+It should be possible to run a test on its own by running it at the
+command line and checking the return value, e.g.
+
+bash $t/goto-quoted.sh
+bash $echo $?
+1
+
+shows a test failure.
+
+The tests fall into two categories: tests of non-interactive and
+interactive operation.
+
+New tests can be created by copying existing ones. Each test should
+start with the lines
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+to allow running at the command line, or with either an in-source or
+out-of-source build with "make check".
+
+Any interactive test should also finish with
+
+cleanup
+
+to cleanup temporary files and spawned processes. This function
+will exit with whatever the value of RETVAL is, so you can set the
+exit status of the script indicating success or failure.
+
+Tests of interactive operation
+------------------------------
+
+These use the "pseudotty" program in the info subdirectory, which
+creates a pseudo-terminal for the input and output of the program.
+This allows the program to happily enter interactive operation (its standard
+file descriptors pass the isatty library function) and avoids affecting
+the output of the terminal the test was invoked from. pseudotty reads and
+discards all input on its stdin, and passes through any bytes read on
+its control channel into the pseudo-terminal. It prints the name of the
+pseudoterminal slave device on standard output.
+
+(test script)
+ ^ |
+ | `-control----> pseudotty (master) <---> (slave) stdin/stdout ginfo
+ `-name of slave-----'
+
+pseudotty will stop running either when killed, or when it exits after
+its control channel is closed.
+
+Since ginfo is reading to and writing from the pseudoterminal slave
+device, if pseudotty exits before ginfo does, ginfo will exit with an
+I/O error.
+
+Many of the tests of interactive operation try to position the cursor
+on a cross-reference by various means, follow the reference, and dump the node
+reached to a file. (It can be compared with a target file in
+info/t/node-target, to check that we ended up where we thought we would.)
+
+
diff --git a/macros/texinfo/texinfo/info/t/adjust-anchors.sh b/macros/texinfo/texinfo/info/t/adjust-anchors.sh
new file mode 100644
index 0000000000..f067d444e4
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/adjust-anchors.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+LC_ALL=en_US.ISO8859-1; export LC_ALL
+# "ISO8859-1" is what it's known as on NetBSD 5.1 (and not "iso-8859-1"
+# or any other variation), although this test is actually skipped on
+# that OS
+
+run_ginfo -f anchors
+
+# Go to anchor in a UTF-8 encoded file when the current character encoding is
+# supposed to be ISO-8859-1
+printf 'ganchor-2\r\002\002\002\002\002\002' >$pty_type
+printf '\002\002\002\002\002\002\002\002\rDq' >$pty_type
+
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ # Return non-zero (test failure) if files differ
+ diff $ginfo_output $t/node-target
+ retval=$?
+fi
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/all-only.sh b/macros/texinfo/texinfo/info/t/all-only.sh
new file mode 100644
index 0000000000..a3a84c7a05
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/all-only.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Try to stop a "man" command finding a man page called "intera"
+MANPATH=.; export MANPATH
+
+# Check that if there is only one file found with --all, we go straight
+# to that file without loading a menu of matching files. This relies
+# on there being no "intera" entry in "dir".
+run_ginfo --all intera
+printf 'Dq' >$pty_type
+timeout_test
+
+grep '^File: intera.info, Node: Top' $ginfo_output
+retval=$?
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/anchor-positions.sh b/macros/texinfo/texinfo/info/t/anchor-positions.sh
new file mode 100644
index 0000000000..0031e7dce2
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/anchor-positions.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Open a UTF-8 file with anchors in various positions. Check that character
+# encoding conversion doesn't cause the program to freeze.
+LANG=en_US.iso8859-1; export LANG
+run_ginfo -f anchor-positions
+printf q >$pty_type
+
+timeout_test
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/body-start.sh b/macros/texinfo/texinfo/info/t/body-start.sh
new file mode 100644
index 0000000000..fca8a8019d
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/body-start.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f body-start
+# Check that node headers aren't included in searches
+printf 'sPotential match\r\rDq' >$pty_type &
+
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ # Return non-zero (test failure) if files differ
+ diff $ginfo_output $t/node-target
+ retval=$?
+fi
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/c-u-m-x-scroll-forward.sh b/macros/texinfo/texinfo/info/t/c-u-m-x-scroll-forward.sh
new file mode 100644
index 0000000000..7a3795581a
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/c-u-m-x-scroll-forward.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Set screen geometry so that scrolling down by screens goes to
+# predictable places. 5 lines of the node should be visible at once, 1
+# status line and 1 line for the echo area.
+# This relies on the TIOCGWINSZ ioctl either failing for the pseudotty,
+# or returning non-positive values.
+# (See terminal_get_screen_size in terminal.c, and the use of
+# TIOCSWINSZ in pseudotty.c.)
+LINES=7; export LINES
+COLUMNS=80; export COLUMNS
+
+run_ginfo -f intera -n 'Scroll four lines'
+
+# C-u M-x scroll-forward should scroll four lines, not four screens
+printf '\025\033xscroll-forward\r' >$pty_type
+printf '\rDq' >$pty_type
+
+timeout_test
+
+# Return non-zero (test failure) if files differ
+diff $ginfo_output $t/node-target
+retval=$?
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/close-window-after-search.sh b/macros/texinfo/texinfo/info/t/close-window-after-search.sh
new file mode 100644
index 0000000000..d7d63df411
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/close-window-after-search.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# Copyright (C) 2017-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera
+
+# Split window, do a search, and then close the window the search took
+# place in.
+printf '\0302sblah\r\0300q' >$pty_type
+
+timeout_test
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/dir-3-menus.sh b/macros/texinfo/texinfo/info/t/dir-3-menus.sh
new file mode 100644
index 0000000000..6125c2db1e
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/dir-3-menus.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Follow several menus in a file to get to desired node
+$ginfo --output - file-menu 'First entry' 'Node 2' 'Node 3' \
+ | grep 'Arrived at Node 3.'
+
diff --git a/macros/texinfo/texinfo/info/t/dir-dangling-entry.sh b/macros/texinfo/texinfo/info/t/dir-dangling-entry.sh
new file mode 100644
index 0000000000..01d33f3a75
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/dir-dangling-entry.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Try to load a dir entry referring to a non-existing file
+$ginfo non-existent 2>&1 | grep 'No menu item'
+retval=$?
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/dir-entry-to-subdir.sh b/macros/texinfo/texinfo/info/t/dir-entry-to-subdir.sh
new file mode 100644
index 0000000000..11cb5b9970
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/dir-entry-to-subdir.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+# Copyright (C) 2015-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Follow a dir entry to a file in a subdirectory of infodir
+run_ginfo subdir
+printf 'Dq' >$pty_type
+timeout_test
+
+cat $ginfo_output
+grep '^File: file-in-subdir.info' $ginfo_output
+retval=$?
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/dir-file-index.sh b/macros/texinfo/texinfo/info/t/dir-file-index.sh
new file mode 100644
index 0000000000..96093ec7cf
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/dir-file-index.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Follow an index entry in a file found through dir
+$ginfo --output - split 'entry text' \
+ | grep 'Node: Target node'
diff --git a/macros/texinfo/texinfo/info/t/dir-file-menu.sh b/macros/texinfo/texinfo/info/t/dir-file-menu.sh
new file mode 100644
index 0000000000..01eca6b732
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/dir-file-menu.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Follow a menu in a file
+$ginfo --output - file-menu 'First entry' | grep 'Arrived at Node 1.'
+
diff --git a/macros/texinfo/texinfo/info/t/dir-file-no-menu.sh b/macros/texinfo/texinfo/info/t/dir-file-no-menu.sh
new file mode 100644
index 0000000000..537596c495
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/dir-file-no-menu.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Try to select a non-existent menu item
+$ginfo --output - file-menu 'Not an entry' 2>&1 | grep 'No menu item'
+
diff --git a/macros/texinfo/texinfo/info/t/dir-file-node.sh b/macros/texinfo/texinfo/info/t/dir-file-node.sh
new file mode 100644
index 0000000000..ccac3f2d28
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/dir-file-node.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Load a node in loaded file using --node, without using --file to
+# specify the desired file.
+
+$ginfo --output - file-menu --node Unreachable \
+ | grep 'not linked to elsewhere'
diff --git a/macros/texinfo/texinfo/info/t/dir-file-sloppily.sh b/macros/texinfo/texinfo/info/t/dir-file-sloppily.sh
new file mode 100644
index 0000000000..156591fd9a
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/dir-file-sloppily.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Follow an reference to "file-menu" in dir without matching label exactly
+$ginfo --output - FiLe-M | grep "^File: file-menu,"
diff --git a/macros/texinfo/texinfo/info/t/dir-file.sh b/macros/texinfo/texinfo/info/t/dir-file.sh
new file mode 100644
index 0000000000..674eaa2c02
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/dir-file.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Follow a reference to file1 in dir
+$ginfo --output - file1
diff --git a/macros/texinfo/texinfo/info/t/dir-menus-sloppily.sh b/macros/texinfo/texinfo/info/t/dir-menus-sloppily.sh
new file mode 100644
index 0000000000..897454ba08
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/dir-menus-sloppily.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Follow several menus in a file inexactly to get to desired node
+$ginfo --output - file-menu first nod nod \
+ | grep 'Arrived at Node 3.'
+
diff --git a/macros/texinfo/texinfo/info/t/dir-merge.sh b/macros/texinfo/texinfo/info/t/dir-merge.sh
new file mode 100644
index 0000000000..bfc1babe35
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/dir-merge.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+INFOPATH=$INFOPATH:$t/infodir/subdir
+
+# Follow a reference to file in merged dir
+run_ginfo -v hide-note-references=On file-in-subdir
+
+printf q >$pty_type
+timeout_test
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/dir-no-file.sh b/macros/texinfo/texinfo/info/t/dir-no-file.sh
new file mode 100644
index 0000000000..71289cc1d3
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/dir-no-file.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Look for a non-existent entry in dir
+
+# Note that the behaviour is different running non-interatively
+# (with --output) and interactively. The latter loads and displays
+# the dir node, but the former does not output the dir node, just an
+# error message.
+
+$ginfo --output - not-a-file 2>&1 | grep 'No menu item'
diff --git a/macros/texinfo/texinfo/info/t/dir-nondir.sh b/macros/texinfo/texinfo/info/t/dir-nondir.sh
new file mode 100644
index 0000000000..5bbfff6b6c
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/dir-nondir.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Load a file despite it not being listed in dir.
+$ginfo --output - sample | grep "This is a simple Info file."
diff --git a/macros/texinfo/texinfo/info/t/dir.sh b/macros/texinfo/texinfo/info/t/dir.sh
new file mode 100644
index 0000000000..2bed697e43
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/dir.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Load top-level dir node
+$ginfo --output -
diff --git a/macros/texinfo/texinfo/info/t/echo-area-no-completions.sh b/macros/texinfo/texinfo/info/t/echo-area-no-completions.sh
new file mode 100644
index 0000000000..e2fc52acd7
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/echo-area-no-completions.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Copyright (C) 2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera -n xrefnode
+# Enter echo area and try to follow a cross-reference that doesn't exist
+
+printf 'fs\r\r\r\r\r\r' >$pty_type
+printf '\030\030q' >$pty_type
+
+timeout_test
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/empty-infopath.sh b/macros/texinfo/texinfo/info/t/empty-infopath.sh
new file mode 100644
index 0000000000..e2be72cdf2
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/empty-infopath.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+# Copyright (C) 2015-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Check that an empty search path doesn't break. There are no search
+# directories here because infopath-no-defaults=On in
+# info/t/Infokey-config.
+INFOPATH=
+export INFOPATH
+run_ginfo
+printf q >$pty_type
+timeout_test
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/empty.sh b/macros/texinfo/texinfo/info/t/empty.sh
new file mode 100644
index 0000000000..edae9e718e
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/empty.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Open an empty file, and check we don't crash. Note that the file is
+# created in $builddir.
+rm -f empty.info
+touch empty.info
+INFOPATH=. $ginfo --output - empty
+rm -f empty.info
diff --git a/macros/texinfo/texinfo/info/t/end-of-line.sh b/macros/texinfo/texinfo/info/t/end-of-line.sh
new file mode 100644
index 0000000000..9c4d31088a
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/end-of-line.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera
+# Check that typing C-e on an empty line doesn't go to previous line
+printf '\x0e\x0e\x0e\x0e\x0e\x05\x0e\rDq' >$pty_type
+
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ # Return non-zero (test failure) if files differ
+ diff $ginfo_output $t/node-target
+ retval=$?
+fi
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/file-file-with-dot.sh b/macros/texinfo/texinfo/info/t/file-file-with-dot.sh
new file mode 100644
index 0000000000..0260a500e6
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/file-file-with-dot.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Follow a menu in a file where the filename contains a full stop.
+$ginfo --output - -f file-menu Target | grep 'File: file1'
+
diff --git a/macros/texinfo/texinfo/info/t/file-index-colons.sh b/macros/texinfo/texinfo/info/t/file-index-colons.sh
new file mode 100644
index 0000000000..d5a17cd6ff
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/file-index-colons.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+# Copyright (C) 2017-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Follow an index entry with a colon in it
+$ginfo --output - --file index-search 'aaa::bbb' \
+ | grep 'Node: Node 2'
diff --git a/macros/texinfo/texinfo/info/t/file-index.sh b/macros/texinfo/texinfo/info/t/file-index.sh
new file mode 100644
index 0000000000..5d8e3ddfd0
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/file-index.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Follow an index entry in a file specified with --file
+$ginfo --output - --file split 'entry text' \
+ | grep 'Node: Target node'
diff --git a/macros/texinfo/texinfo/info/t/file-node-with-dot.sh b/macros/texinfo/texinfo/info/t/file-node-with-dot.sh
new file mode 100644
index 0000000000..bfffccf1b9
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/file-node-with-dot.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Load a node with a full stop in its name in loaded file using --node.
+
+$ginfo --output - --file file-menu --node Has.dot \
+ | grep 'Node: Has.dot'
diff --git a/macros/texinfo/texinfo/info/t/file-node.sh b/macros/texinfo/texinfo/info/t/file-node.sh
new file mode 100644
index 0000000000..0b9dea63f4
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/file-node.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Load a node in loaded file using --node.
+
+$ginfo --output - --file file-menu --node Unreachable \
+ | grep 'not linked to elsewhere'
diff --git a/macros/texinfo/texinfo/info/t/file-nodes.sh b/macros/texinfo/texinfo/info/t/file-nodes.sh
new file mode 100644
index 0000000000..354bab4d8e
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/file-nodes.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Load more than one node in loaded file using --node.
+
+$ginfo --output $ginfo_output --file file-menu --node 'Node 1' --node 'Node 2'
+
+retval=0
+grep 'Arrived at Node 1' $ginfo_output || retval=1
+grep 'Arrived at Node 2' $ginfo_output || retval=1
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/file-relative-path.sh b/macros/texinfo/texinfo/info/t/file-relative-path.sh
new file mode 100644
index 0000000000..32f7c68a6e
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/file-relative-path.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Load a file given relative to the current directory using --file.
+$ginfo --output - --file $t/infodir/sample.info | grep "^File: sample.info,"
diff --git a/macros/texinfo/texinfo/info/t/file.sh b/macros/texinfo/texinfo/info/t/file.sh
new file mode 100644
index 0000000000..b4276f1c0f
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/file.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Check that we can reach a file with --file
+$ginfo --file file-menu >$ginfo_output
+
+# Check that the entire file was dumped, and not just the Top node
+grep 'Node: Top' $ginfo_output \
+ && grep 'Node: Node 1' $ginfo_output \
+ && grep 'Node: Node 2' $ginfo_output \
+ && grep 'Node: Node 3' $ginfo_output \
+ && grep 'Node: Has\.dot' $ginfo_output
+# Don't look for node "Unreachable" which is not in any menus and not dumped
+
+retval=$?
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/gc-split.sh b/macros/texinfo/texinfo/info/t/gc-split.sh
new file mode 100644
index 0000000000..d6f5b04839
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/gc-split.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f split
+# Cause the garbage collector to run in a split file by cancelling an
+# incremental search. Refresh the screen afterwards to increase the
+# chances of trying to access freed file contents.
+printf '\023aa\033[B\014q' >$pty_type
+timeout_test
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/goal-column.sh b/macros/texinfo/texinfo/info/t/goal-column.sh
new file mode 100644
index 0000000000..0f385b04c5
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/goal-column.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera -n 'Goal column'
+
+printf '/^Goal column test\r\016' >$pty_type
+printf '\005\016\016\006\006' >$pty_type
+printf '\rDq' >$pty_type
+timeout_test
+
+diff $ginfo_output $t/node-target
+retval=$?
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/goto-quoted.sh b/macros/texinfo/texinfo/info/t/goto-quoted.sh
new file mode 100644
index 0000000000..2c8a45e07d
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/goto-quoted.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f quoting
+# Go to a node with colons and commas in its name with "g"
+printf 'g\tColo\t\r\t\rDq' >$pty_type
+
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ # Return non-zero (test failure) if files differ
+ diff $ginfo_output $t/node-target
+ retval=$?
+fi
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/help.sh b/macros/texinfo/texinfo/info/t/help.sh
new file mode 100644
index 0000000000..92423e9c32
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/help.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Test --help flag
+$ginfo --help | grep 'index-search'
diff --git a/macros/texinfo/texinfo/info/t/inc-sea-bs.sh b/macros/texinfo/texinfo/info/t/inc-sea-bs.sh
new file mode 100644
index 0000000000..9ef1f70f4f
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/inc-sea-bs.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+# Copyright (C) 2017-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera -n Top
+
+# Do a incremental search and enter a single backslash. This is to
+# test that it doesn't crash when the input is not valid.
+printf '\023' >$pty_type
+printf '\\\n' >$pty_type
+printf q >$pty_type
+
+timeout_test
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/inc-sea-forward-nonregex.sh b/macros/texinfo/texinfo/info/t/inc-sea-forward-nonregex.sh
new file mode 100644
index 0000000000..ebd39d6f9d
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/inc-sea-forward-nonregex.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f search
+
+# Turn off regular expressions, and search for "match" with incremental
+# search. Test deleting characters from search string with backspace. Go
+# forward to match in next node. Test we end up at the end of the match
+# string by trying to wrap to the next line with C-f.
+printf 'R\023matchxy\010\010' >$pty_type
+printf '\023\023\023\r\006\rDq' >$pty_type
+
+timeout_test
+
+# Return non-zero (test failure) if files differ
+diff $ginfo_output $t/node-target
+retval=$?
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/inc-sea-forward.sh b/macros/texinfo/texinfo/info/t/inc-sea-forward.sh
new file mode 100644
index 0000000000..145308c217
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/inc-sea-forward.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f search
+
+# Search for "match" with incremental search. Test deleting characters from
+# search string with backspace. Go forward to match in next node. Test
+# we end up at the end of the match string by trying to wrap to the next line
+# with C-f.
+printf '\023matchxy\010\010' >$pty_type
+printf '\023\023\023\r\006\rDq' >$pty_type
+
+timeout_test
+
+# Return non-zero (test failure) if files differ
+diff $ginfo_output $t/node-target
+retval=$?
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/inc-sea-history.sh b/macros/texinfo/texinfo/info/t/inc-sea-history.sh
new file mode 100644
index 0000000000..cd2938f7f6
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/inc-sea-history.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f inc-sea-history
+
+# Check that we go back to the right node after an incremental search
+printf '\02301234\b4\b\b34\b\bqwerty\b\b\b\b\b\b34' >$pty_type
+printf '\rD' >$pty_type
+
+# Check we reached node with "01234" in it
+sleep 1
+grep 'Node: Four' $ginfo_output
+retval=$?
+
+printf 'lDq' >$pty_type
+timeout_test
+
+if test $retval = 0; then
+ grep 'Node: Top' $ginfo_output
+ retval=$?
+fi
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/inc-sea-insensitive.sh b/macros/texinfo/texinfo/info/t/inc-sea-insensitive.sh
new file mode 100644
index 0000000000..ee06434b77
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/inc-sea-insensitive.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera -n 'Incremental search case-insensitive'
+
+# Search for "match" with incremental search, adding an upper-case character
+# to make the search case-sensitive. Delete it and check the search goes back
+# to being case insensitive by checking that a "Match" is included.
+printf '\023matchX\010' >$pty_type
+printf '\023\023\r\rDq' >$pty_type
+
+timeout_test
+
+# Return non-zero (test failure) if files differ
+diff $ginfo_output $t/node-target
+retval=$?
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/index-apropos.sh b/macros/texinfo/texinfo/info/t/index-apropos.sh
new file mode 100644
index 0000000000..562a98e2a9
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/index-apropos.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo
+
+# Type "M-x index-apropos", look for "link" in indices, select first
+# result. Then type "i" followed by <TAB> to check the indices in the
+# file are still there.
+
+printf '\033xindex-apropos\rlink\r\t\ri\t\030qq' >$pty_type
+
+timeout_test
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/index-completing.sh b/macros/texinfo/texinfo/info/t/index-completing.sh
new file mode 100644
index 0000000000..440e0cceaa
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/index-completing.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f index-search
+# Check that pressing tab to complete works for index search
+printf 'i\t\rDq' >$pty_type
+timeout_test
+
+grep '^Node: Node 2' $ginfo_output
+retval=$?
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/index-long-nodeline.sh b/macros/texinfo/texinfo/info/t/index-long-nodeline.sh
new file mode 100644
index 0000000000..dfdad9f970
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/index-long-nodeline.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f split
+# Follow an menu item from the index node to a node where physical lines
+# don't match logical lines.
+printf 'gIndex\r\t\t\r\rDq' >$pty_type
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ # Return non-zero (test failure) if files differ
+ diff $ginfo_output $t/node-target
+ retval=$?
+fi
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/index-search.sh b/macros/texinfo/texinfo/info/t/index-search.sh
new file mode 100644
index 0000000000..0ac4816f9f
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/index-search.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera
+# Search in indices with 'virtual-index' command. Go down to index
+# entry and select it.
+printf 'Ibc\r\016\016\016\016\016\016\016\016\016\rDq' >$pty_type
+
+timeout_test
+
+# Return non-zero (test failure) if files differ
+diff $ginfo_output $t/node-target
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/index.sh b/macros/texinfo/texinfo/info/t/index.sh
new file mode 100644
index 0000000000..a4d415ff11
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/index.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera
+# Follow an index entry
+printf 'iabc\rDq' >$pty_type
+
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ # Return non-zero (test failure) if files differ
+ diff $ginfo_output $t/node-target
+ retval=$?
+fi
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/infodir/anchor-positions.info b/macros/texinfo/texinfo/info/t/infodir/anchor-positions.info
new file mode 100644
index 0000000000..5facca0a44
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/anchor-positions.info
@@ -0,0 +1,32 @@
+
+File: anchor-positions.info, Node: Top
+
+* Menu:
+
+* First entry::
+
+*note cross-reference::
+
+
+Local Variables:
+coding: utf-8
+End:
+
+Tag Table:
+Node: Top0
+Ref: 4242
+Ref: 4343
+Ref: 4444
+Ref: 5050
+Ref: 5151
+Ref: 5252
+Ref: 5353
+Ref: 5454
+Ref: 6666
+Ref: 6767
+Ref: 6868
+Ref: 6969
+Ref: 7070
+Ref: 7171
+
+End Tag Table
diff --git a/macros/texinfo/texinfo/info/t/infodir/anchors.info b/macros/texinfo/texinfo/info/t/infodir/anchors.info
new file mode 100644
index 0000000000..9aa55c1c55
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/anchors.info
@@ -0,0 +1,51 @@
+
+File: utf8.info, Node: Top, Up: (DIR), Next: Node 2,
+
+This is UTF-8 text.
+x
+‘Text in quotes’. In ISO-8859-1, these should appear as straight
+quotes, because open and closed quotes are not represented.
+
+Symbols without replacements -
+好 - Chinese character "hao3"
+∀ - Mathematical symbol "for all"
+
+“Text in double quotes”.
+
+Sample characters representable in UTF-8 and ISO-8859-1:
+Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Û Ü Ý Þ ß à á
+
+*Note target:(intera)Node 1.
+
+.
+.
+.
+.
+.
+.
+x
+.
+.
+.
+.
+.
+.
+.
+.
+
+Other symbols with ASCII replacements -
+Double closing angular brackets »
+Right arrow →
+Copyright symbol ©
+
+
+Tag Table:
+Node: Top0
+Ref: anchor-177
+Ref: anchor-2534
+
+End Tag Table
+
+Local Variables:
+coding: UTF-8
+End:
diff --git a/macros/texinfo/texinfo/info/t/infodir/body-start.info b/macros/texinfo/texinfo/info/t/infodir/body-start.info
new file mode 100644
index 0000000000..719becd790
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/body-start.info
@@ -0,0 +1,4 @@
+
+File: body-start.info, Node: Top, Up: Potential match
+
+Potential match. *Note Target link:(intera)Node 1.
diff --git a/macros/texinfo/texinfo/info/t/infodir/dir b/macros/texinfo/texinfo/info/t/infodir/dir
new file mode 100644
index 0000000000..532cc638d7
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/dir
@@ -0,0 +1,24 @@
+This is the file .../info/dir, which contains the
+topmost node of the Info hierarchy, called (dir)Top.
+The first time you invoke Info you start off looking at this node.
+
+File: dir, Node: Top This is the top of the INFO tree
+
+ Test Directory node.
+
+ This (the Directory node) gives a menu of major topics.
+ Typing "q" exits, "?" lists all Info commands, "d" returns here,
+ "h" gives a primer for first-timers,
+
+* Menu:
+
+Test Info files
+* file1: (file1).
+* file-menu: (file-menu).
+
+* split: (split).
+
+* non-existent: (non-existent). Dir reference to non-existent file.
+
+* subdir:(subdir/file-in-subdir). File in a subdir.
+* subdir-split:(subdir/split-in-subdir). Split file in a subdir.
diff --git a/macros/texinfo/texinfo/info/t/infodir/file-menu.info b/macros/texinfo/texinfo/info/t/infodir/file-menu.info
new file mode 100644
index 0000000000..b1a88ede39
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/file-menu.info
@@ -0,0 +1,41 @@
+
+File: file-menu, Node: Top
+
+This is file whose Top node contains a menu.
+
+* Menu:
+
+* First entry:Node 1.
+* Has.dot::
+* Target:(file1.info) Top.
+
+
+File: file-menu, Node: Node 1
+
+Arrived at Node 1.
+
+* Menu:
+
+* Node 2::
+
+
+File: file-menu, Node: Node 2
+
+Arrived at Node 2.
+
+* Menu:
+
+* Node 3::
+
+File: file-menu, Node: Node 3
+
+Arrived at Node 3.
+
+
+File: file-menu, Node: Unreachable
+
+This node is not linked to elsewhere.
+
+File: file-menu, Node: Has.dot
+
+This node is not linked to elsewhere.
diff --git a/macros/texinfo/texinfo/info/t/infodir/file1.info b/macros/texinfo/texinfo/info/t/infodir/file1.info
new file mode 100644
index 0000000000..e9396b0025
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/file1.info
@@ -0,0 +1,5 @@
+
+File: file1, Node: Top This is the top of the INFO tree
+
+This is file1.
+
diff --git a/macros/texinfo/texinfo/info/t/infodir/inc-sea-history.info b/macros/texinfo/texinfo/info/t/infodir/inc-sea-history.info
new file mode 100644
index 0000000000..257709f841
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/inc-sea-history.info
@@ -0,0 +1,16 @@
+
+Node: Top
+0
+
+Node: One
+01
+
+Node: Two
+012
+
+Node: Three
+0123
+
+Node: Four
+01234
+
diff --git a/macros/texinfo/texinfo/info/t/infodir/index-search.info b/macros/texinfo/texinfo/info/t/infodir/index-search.info
new file mode 100644
index 0000000000..b90a696312
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/index-search.info
Binary files differ
diff --git a/macros/texinfo/texinfo/info/t/infodir/intera.info b/macros/texinfo/texinfo/info/t/infodir/intera.info
new file mode 100644
index 0000000000..71547c6936
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/intera.info
Binary files differ
diff --git a/macros/texinfo/texinfo/info/t/infodir/last-node-not-top-level.info b/macros/texinfo/texinfo/info/t/infodir/last-node-not-top-level.info
new file mode 100644
index 0000000000..0704900ed4
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/last-node-not-top-level.info
@@ -0,0 +1,15 @@
+
+Node: Top, Next: Second
+
+First node
+
+Node: Second, Prev: Top
+
+* Menu:
+
+* Third::
+
+Node: Third, Up: Second
+
+Last node
+
diff --git a/macros/texinfo/texinfo/info/t/infodir/malformed-split.info b/macros/texinfo/texinfo/info/t/infodir/malformed-split.info
new file mode 100644
index 0000000000..4e0fa7d098
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/malformed-split.info
@@ -0,0 +1,4 @@
+
+Indirect:
+split-1: 1000
+split-10: 2000
diff --git a/macros/texinfo/texinfo/info/t/infodir/no-xref.info b/macros/texinfo/texinfo/info/t/infodir/no-xref.info
new file mode 100644
index 0000000000..d7157c3c25
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/no-xref.info
@@ -0,0 +1,4 @@
+
+Node: Top
+
+This file contains no cross-references or menus.
diff --git a/macros/texinfo/texinfo/info/t/infodir/quoting.info b/macros/texinfo/texinfo/info/t/infodir/quoting.info
new file mode 100644
index 0000000000..39347b636f
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/quoting.info
@@ -0,0 +1,19 @@
+
+File: quoting.info, Node: Top, Next: Colon::in, name,
+
+*Note Colon::in, name::
+*Note Labelled reference:Colon::in, name
+*Note Quoted label...:Colon::in, name
+*Note File in target:(quoting)Colon::in, name
+*Note (quoting)Colon::in, name::
+
+
+File: quoting.info, Node: Colon::in, name
+
+*Note (intera)Node 1::
+
+Tag Table:
+Node: Top0
+Node: Colon::in, name256
+
+End Tag Table
diff --git a/macros/texinfo/texinfo/info/t/infodir/sample.info b/macros/texinfo/texinfo/info/t/infodir/sample.info
new file mode 100644
index 0000000000..78cb6d4c58
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/sample.info
@@ -0,0 +1,4 @@
+
+File: sample.info, Node: Top, Up: (DIR)
+
+This is a simple Info file.
diff --git a/macros/texinfo/texinfo/info/t/infodir/search.info b/macros/texinfo/texinfo/info/t/infodir/search.info
new file mode 100644
index 0000000000..c722d3125e
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/search.info
@@ -0,0 +1,17 @@
+
+File: search.info, Node: Top
+
+File used for testing search commands.
+
+* Menu:
+
+* Node 2::
+
+match
+
+
+File: search.info, Node: Node 2
+matchmatch match
+*Note destination:(intera)Node 1.
+match match match
+match
diff --git a/macros/texinfo/texinfo/info/t/infodir/split.info b/macros/texinfo/texinfo/info/t/infodir/split.info
new file mode 100644
index 0000000000..96396bdb5a
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/split.info
@@ -0,0 +1,15 @@
+
+Indirect:
+split.info-1: 0
+split.info-2: 111
+
+Tag Table:
+(Indirect)
+Node: Top0
+Node: Second111
+Node: Target node214
+Node: Index320
+Node: Long nodeline567
+Ref: Anchor570
+
+End Tag Table
diff --git a/macros/texinfo/texinfo/info/t/infodir/split.info-1 b/macros/texinfo/texinfo/info/t/infodir/split.info-1
new file mode 100644
index 0000000000..d84b4f8b0e
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/split.info-1
@@ -0,0 +1,8 @@
+
+File: split.info, Node: Top, Next: Second
+
+This is the Top node of split.info, contained in 'split.info-1.
+
+* Menu:
+
+* Second:: Node contained in 'split.info-2'
diff --git a/macros/texinfo/texinfo/info/t/infodir/split.info-2 b/macros/texinfo/texinfo/info/t/infodir/split.info-2
new file mode 100644
index 0000000000..50fcdce8bb
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/split.info-2
Binary files differ
diff --git a/macros/texinfo/texinfo/info/t/infodir/subdir/dir b/macros/texinfo/texinfo/info/t/infodir/subdir/dir
new file mode 100644
index 0000000000..6c597e190c
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/subdir/dir
@@ -0,0 +1,16 @@
+This is the file .../info/dir, which contains the
+topmost node of the Info hierarchy, called (dir)Top.
+The first time you invoke Info you start off looking at this node.
+
+File: dir, Node: Top This is the top of the INFO tree
+
+ Test Directory node.
+
+ This (the Directory node) gives a menu of major topics.
+ Typing "q" exits, "?" lists all Info commands, "d" returns here,
+ "h" gives a primer for first-timers,
+
+* Menu:
+
+Test Info files
+* file: (file-in-subdir).
diff --git a/macros/texinfo/texinfo/info/t/infodir/subdir/file-in-subdir.info b/macros/texinfo/texinfo/info/t/infodir/subdir/file-in-subdir.info
new file mode 100644
index 0000000000..a1585baa51
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/subdir/file-in-subdir.info
@@ -0,0 +1,5 @@
+
+File: file-in-subdir.info Node: Top
+
+This file is located within a subdirectory of the filesystem directory
+containing the directory Info file, dir.
diff --git a/macros/texinfo/texinfo/info/t/infodir/subdir/split-in-subdir.info b/macros/texinfo/texinfo/info/t/infodir/subdir/split-in-subdir.info
new file mode 100644
index 0000000000..98b13aca7c
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/subdir/split-in-subdir.info
@@ -0,0 +1,15 @@
+
+Indirect:
+split-in-subdir.info-1: 0
+split-in-subdir.info-2: 111
+
+Tag Table:
+(Indirect)
+Node: Top0
+Node: Second111
+Node: Target node214
+Node: Index320
+Node: Long nodeline567
+Ref: Anchor570
+
+End Tag Table
diff --git a/macros/texinfo/texinfo/info/t/infodir/subdir/split-in-subdir.info-1 b/macros/texinfo/texinfo/info/t/infodir/subdir/split-in-subdir.info-1
new file mode 100644
index 0000000000..d84b4f8b0e
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/subdir/split-in-subdir.info-1
@@ -0,0 +1,8 @@
+
+File: split.info, Node: Top, Next: Second
+
+This is the Top node of split.info, contained in 'split.info-1.
+
+* Menu:
+
+* Second:: Node contained in 'split.info-2'
diff --git a/macros/texinfo/texinfo/info/t/infodir/subdir/split-in-subdir.info-2 b/macros/texinfo/texinfo/info/t/infodir/subdir/split-in-subdir.info-2
new file mode 100644
index 0000000000..50fcdce8bb
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/subdir/split-in-subdir.info-2
Binary files differ
diff --git a/macros/texinfo/texinfo/info/t/infodir/subfile-initial-match-1 b/macros/texinfo/texinfo/info/t/infodir/subfile-initial-match-1
new file mode 100644
index 0000000000..0a5c8392b7
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/subfile-initial-match-1
@@ -0,0 +1,4 @@
+
+Node: One
+
+This is node one.
diff --git a/macros/texinfo/texinfo/info/t/infodir/subfile-initial-match-10 b/macros/texinfo/texinfo/info/t/infodir/subfile-initial-match-10
new file mode 100644
index 0000000000..3e284c0634
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/subfile-initial-match-10
@@ -0,0 +1,4 @@
+
+Node: Two
+
+This is node two.
diff --git a/macros/texinfo/texinfo/info/t/infodir/subfile-initial-match.info b/macros/texinfo/texinfo/info/t/infodir/subfile-initial-match.info
new file mode 100644
index 0000000000..fdac3367ee
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/subfile-initial-match.info
@@ -0,0 +1,11 @@
+
+Indirect:
+subfile-initial-match-1: 1000
+subfile-initial-match-10: 2000
+
+Tag Table:
+(Indirect)
+Node: One1000
+Node: Two2000
+
+End Tag Table
diff --git a/macros/texinfo/texinfo/info/t/infodir/tab-skip-node.info b/macros/texinfo/texinfo/info/t/infodir/tab-skip-node.info
new file mode 100644
index 0000000000..dbd1e8e392
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/tab-skip-node.info
@@ -0,0 +1,16 @@
+
+Node: Top
+
+* Menu:
+* First::
+* Second::
+
+
+Node: First, Up: Top, Next: Second
+
+No xref in this node
+
+
+Node: Second, Up: Top, Prev: First
+
+*Note xxx::
diff --git a/macros/texinfo/texinfo/info/t/infodir/xref_to_anchor.info b/macros/texinfo/texinfo/info/t/infodir/xref_to_anchor.info
new file mode 100644
index 0000000000..87e9daaefd
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/infodir/xref_to_anchor.info
@@ -0,0 +1,23 @@
+
+Node: Top
+
+*note anchor::
+
+
+
+x
+*note (intera)Node 1::
+
+
+
+
+
+
+aaaaaaaaaa
+
+Tag Table:
+Node: Top0
+Ref: anchor32
+
+End Tag Table
+
diff --git a/macros/texinfo/texinfo/info/t/last-no-history.sh b/macros/texinfo/texinfo/info/t/last-no-history.sh
new file mode 100644
index 0000000000..938026d5f9
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/last-no-history.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera
+
+# Try to go back in history when there is no earlier node
+printf lq >$pty_type
+timeout_test
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/last-node-not-top-level.sh b/macros/texinfo/texinfo/info/t/last-node-not-top-level.sh
new file mode 100644
index 0000000000..a7625d0bbd
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/last-node-not-top-level.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f last-node-not-top-level -v scroll-last-node=Top
+
+# Wrap round to top node when last node does not have Top as Up
+printf ']]]Dq' >$pty_type
+timeout_test
+
+grep 'Node: Top' $ginfo_output
+retval=$?
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/malformed-split.sh b/macros/texinfo/texinfo/info/t/malformed-split.sh
new file mode 100644
index 0000000000..a6e5b0a6cb
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/malformed-split.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Copyright (C) 2017-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Load a split file with a malformed or missing tag table.
+
+run_ginfo --file malformed-split --node Two
+printf q >$pty_type
+
+timeout_test
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/menu-sequence.sh b/macros/texinfo/texinfo/info/t/menu-sequence.sh
new file mode 100644
index 0000000000..37d3506a64
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/menu-sequence.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera
+# M-x menu-sequence
+printf '\033xmenu-sequence\rfile-menu,first,no,nod\rDq' >$pty_type
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ grep 'Arrived at Node 3.' $ginfo_output
+ retval=$?
+fi
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/multiple-completions.sh b/macros/texinfo/texinfo/info/t/multiple-completions.sh
new file mode 100644
index 0000000000..ef6104202f
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/multiple-completions.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+# Copyright (C) 2019 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera
+# Ask for completions multiple times, and then cancel. Check we went back
+# to the original node.
+printf 'm\t\t\t\t\030\030Dq' >$pty_type
+timeout_test
+
+grep '^File: intera\.info, *Node: Top' $ginfo_output
+retval=$?
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/next-quoted.sh b/macros/texinfo/texinfo/info/t/next-quoted.sh
new file mode 100644
index 0000000000..6d3d9d11da
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/next-quoted.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f quoting
+# Go to a node with colons and commas in its name with "n"
+printf 'n\t\rDq' >$pty_type
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ # Return non-zero (test failure) if files differ
+ diff $ginfo_output $t/node-target
+ retval=$?
+fi
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/no-file.sh b/macros/texinfo/texinfo/info/t/no-file.sh
new file mode 100644
index 0000000000..a119a4d4b4
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/no-file.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Look for a non-existent entry in INFOPATH.
+
+$ginfo --output - --file not-a-file 2>&1 | grep 'No such file or directory'
diff --git a/macros/texinfo/texinfo/info/t/no-index.sh b/macros/texinfo/texinfo/info/t/no-index.sh
new file mode 100644
index 0000000000..7f52b45816
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/no-index.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f sample
+# Try to search in the indices when there aren't any
+printf 'ia\rq' >$pty_type
+timeout_test
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/node-no-file.sh b/macros/texinfo/texinfo/info/t/node-no-file.sh
new file mode 100644
index 0000000000..048ab58eaf
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/node-no-file.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Ask for a node without saying which file it's in
+$ginfo --output - --node nodename \
+ 2>&1 | grep 'Cannot find node'
diff --git a/macros/texinfo/texinfo/info/t/node-target b/macros/texinfo/texinfo/info/t/node-target
new file mode 100644
index 0000000000..a60b3182c9
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/node-target
@@ -0,0 +1,3 @@
+File: intera.info, Node: Node 1
+
+Arrived at Node 1.
diff --git a/macros/texinfo/texinfo/info/t/quoted-label-and-target.sh b/macros/texinfo/texinfo/info/t/quoted-label-and-target.sh
new file mode 100644
index 0000000000..7567c1c3a6
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/quoted-label-and-target.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f quoting
+
+# Follow a cross-reference with both the label and destination quoted.
+printf '\t\t\t\r\t\rDq' >$pty_type &
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ # Return non-zero (test failure) if files differ
+ diff $ginfo_output $t/node-target
+ retval=$?
+fi
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/quoted-label-as-target.sh b/macros/texinfo/texinfo/info/t/quoted-label-as-target.sh
new file mode 100644
index 0000000000..f8ac2ae651
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/quoted-label-as-target.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f quoting
+# Follow a cross-reference to a node with colons and commas in its name
+printf '\t\r\t\rDq' >$pty_type
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ # Return non-zero (test failure) if files differ
+ diff $ginfo_output $t/node-target
+ retval=$?
+fi
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/quoted-manual-in-label.sh b/macros/texinfo/texinfo/info/t/quoted-manual-in-label.sh
new file mode 100644
index 0000000000..1a8cdcdde0
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/quoted-manual-in-label.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# Copyright (C) 2015-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f quoting
+# Follow a cross-reference with a manual and quoted nodename in the label.
+printf '\t\t\t\t\t\r\t\rDq' >$pty_type
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ # Return non-zero (test failure) if files differ
+ diff $ginfo_output $t/node-target
+ retval=$?
+fi
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/quoted-manual-in-target.sh b/macros/texinfo/texinfo/info/t/quoted-manual-in-target.sh
new file mode 100644
index 0000000000..6ad4ba0656
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/quoted-manual-in-target.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+# Copyright (C) 2015-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f quoting
+# Follow a cross-reference with a manual and quoted nodename in the target,
+# after the colon.
+printf '\t\t\t\t\r\t\rDq' >$pty_type
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ # Return non-zero (test failure) if files differ
+ diff $ginfo_output $t/node-target
+ retval=$?
+fi
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/quoted-target.sh b/macros/texinfo/texinfo/info/t/quoted-target.sh
new file mode 100644
index 0000000000..25ce33649e
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/quoted-target.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f quoting
+# Follow a cross-reference to a node with colons and commas in its name
+printf '\t\t\r\t\r\Dq' >$pty_type
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ # Return non-zero (test failure) if files differ
+ diff $ginfo_output $t/node-target
+ retval=$?
+fi
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/relative-path.sh b/macros/texinfo/texinfo/info/t/relative-path.sh
new file mode 100644
index 0000000000..671835ea96
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/relative-path.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Load a file given relative to the current directory without using
+# --file.
+$ginfo --output - $t/infodir/sample.info | grep "^File: sample.info,"
diff --git a/macros/texinfo/texinfo/info/t/relative-reference.sh b/macros/texinfo/texinfo/info/t/relative-reference.sh
new file mode 100644
index 0000000000..3b2b793d07
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/relative-reference.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Split window with C-x 2, follow menu entry with a slash in the filename.
+# Close window (so destination file will be forgotten), and do it again with
+# the other window.
+run_ginfo -f file-menu
+printf '\0302\t\t\t\r' >$pty_type
+printf '\030o\0301\t\t\t\rq' >$pty_type
+
+timeout_test
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/replace-viewed.sh b/macros/texinfo/texinfo/info/t/replace-viewed.sh
new file mode 100644
index 0000000000..12c841ec35
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/replace-viewed.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+# Copyright (C) 2015-2019 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Test of replacing a file while it is being viewed
+
+rm -f t/replace-viewed.info
+cp $t/infodir/sample.info t/replace-viewed.info
+# We copied from srcdir to builddir here.
+
+run_ginfo -f t/replace-viewed.info
+
+# for debugging - show what node we got to
+printf 'D' >$pty_type
+
+# give ginfo time to load the node
+sleep 1
+cat $ginfo_output
+
+# Now replace the file while it is being viewed.
+rm -f t/replace-viewed.info
+cp $t/infodir/sample.info t/replace-viewed.info
+
+# Do a search. This triggers ginfo's internal garbage collection.
+printf '/what\r' >$pty_type
+printf q >$pty_type
+
+rm -f t/replace-viewed.info
+timeout_test
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/resize-in-completions.sh b/macros/texinfo/texinfo/info/t/resize-in-completions.sh
new file mode 100644
index 0000000000..e827081f46
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/resize-in-completions.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+# Copyright (C) 2014-2019 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Indicate a window resize while showing a completions window
+
+run_ginfo -f file-menu
+
+printf 'g\t' >$pty_type
+sleep 1 # Give ginfo time to process above keystrokes
+kill -s WINCH $ginfo_pid
+sleep 1 # Give ginfo time to process signal
+# C-g to exit completions, q to quit
+printf '\007q' >$pty_type
+
+timeout_test
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/search-after-tag.sh b/macros/texinfo/texinfo/info/t/search-after-tag.sh
new file mode 100644
index 0000000000..e48654fd0d
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/search-after-tag.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera -n Searching
+# Search for text in a node that appears after an Info tag
+printf '/match\r\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\rDq' >$pty_type
+
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ # Return non-zero (test failure) if files differ
+ diff $ginfo_output $t/node-target
+ retval=$?
+fi
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/search-empty.sh b/macros/texinfo/texinfo/info/t/search-empty.sh
new file mode 100644
index 0000000000..e4e1b7b511
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/search-empty.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera
+# Search without specifying a search string, and check we don't crash.
+printf 's\rq' >$pty_type
+
+timeout_test
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/search-skip-screen.sh b/macros/texinfo/texinfo/info/t/search-skip-screen.sh
new file mode 100644
index 0000000000..e59d80c781
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/search-skip-screen.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -v search-skip-screen=Off -f search
+
+# Go back to previous match when search-skip-screen=On
+printf 'smatch\r}{Dq' >$pty_type
+
+timeout_test
+
+# Check we went back to the right node.
+grep '^File: search\.info, Node: Top' $ginfo_output
+retval=$?
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/search-split-after-index.sh b/macros/texinfo/texinfo/info/t/search-split-after-index.sh
new file mode 100644
index 0000000000..569ddb664f
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/search-split-after-index.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# Copyright (C) 2016-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Search for text in a split file after loading an index entry from the
+# command-line.
+run_ginfo -f split 'Link to node'
+printf '/node\r}}}}}q' >$pty_type
+
+timeout_test
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/spec-file-node.sh b/macros/texinfo/texinfo/info/t/spec-file-node.sh
new file mode 100644
index 0000000000..70e121f9c3
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/spec-file-node.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+$ginfo '(file1)Top' | grep '^File: file1,'
diff --git a/macros/texinfo/texinfo/info/t/spec-menu.sh b/macros/texinfo/texinfo/info/t/spec-menu.sh
new file mode 100644
index 0000000000..f86300b78b
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/spec-menu.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Starting at a node given as a specification, follow several menus in a
+# file to get to desired node
+$ginfo '(file-menu)Node 1' 'Node 2' 'Node 3' | grep 'Arrived at Node 3.'
+
diff --git a/macros/texinfo/texinfo/info/t/spec-no-node.sh b/macros/texinfo/texinfo/info/t/spec-no-node.sh
new file mode 100644
index 0000000000..49197c5c0b
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/spec-no-node.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+$ginfo '(file1)' | grep '^File: file1,'
diff --git a/macros/texinfo/texinfo/info/t/split-file-menu.sh b/macros/texinfo/texinfo/info/t/split-file-menu.sh
new file mode 100644
index 0000000000..03cdeaf552
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/split-file-menu.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f split 'Second'
+
+# Follow a menu entry in a split file to a node in a different subfile from
+# the one containing "Top". Check that we arrived properly and remembered
+# that we are in a split file by going back to "Top" with "t".
+printf tDq >$pty_type
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ grep 'Node: Top' $ginfo_output
+ retval=$?
+fi
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/split-footnotes.sh b/macros/texinfo/texinfo/info/t/split-footnotes.sh
new file mode 100644
index 0000000000..7edb75474b
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/split-footnotes.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera -n 'Node with footnotes' -v automatic-footnotes=On
+# Move to automatic footnotes window and split it. Check we don't crash.
+printf '\030o\0302qqqq' >$pty_type
+timeout_test
+
+cleanup
+
diff --git a/macros/texinfo/texinfo/info/t/split-index.sh b/macros/texinfo/texinfo/info/t/split-index.sh
new file mode 100644
index 0000000000..6615c16093
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/split-index.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Follow an index entry in a split file
+
+run_ginfo -f split
+printf 'ientry text\r\rDq' >$pty_type
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ # Return non-zero (test failure) if files differ
+ diff $ginfo_output $t/node-target
+ retval=$?
+fi
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/split.sh b/macros/texinfo/texinfo/info/t/split.sh
new file mode 100644
index 0000000000..673c072609
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/split.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Load a split file
+
+$ginfo -f split | grep '^File: split.info,'
diff --git a/macros/texinfo/texinfo/info/t/star-note-non-whitespace.sh b/macros/texinfo/texinfo/info/t/star-note-non-whitespace.sh
new file mode 100644
index 0000000000..fbc68fe178
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/star-note-non-whitespace.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera -n 'Star note non-whitespace'
+
+# Check that "*note" is not interpreted as a cross-reference marker if
+# it is followed by a non-whitespace character.
+printf '\t\rDq' >$pty_type
+
+timeout_test
+
+# Return non-zero (test failure) if files differ
+diff $ginfo_output $t/node-target
+retval=$?
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/tab-argument.sh b/macros/texinfo/texinfo/info/t/tab-argument.sh
new file mode 100644
index 0000000000..e6c101f85f
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/tab-argument.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera -n 'Argument to tab'
+# C-u 2 TAB to go to second link
+printf '\0252\t\rDq' >$pty_type
+timeout_test
+
+diff $ginfo_output $t/node-target
+retval=$?
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/tab-no-xref.sh b/macros/texinfo/texinfo/info/t/tab-no-xref.sh
new file mode 100644
index 0000000000..fd8469624e
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/tab-no-xref.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -v scroll-last-node=Top -f no-xref.info
+# Check that pressing tab in a file with no cross-references or menus
+# doesn't cause an infinite loop
+printf '\tq' >$pty_type
+timeout_test
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/tab-skip-node.sh b/macros/texinfo/texinfo/info/t/tab-skip-node.sh
new file mode 100644
index 0000000000..1c61554d68
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/tab-skip-node.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f tab-skip-node
+# Check that skipping over a node with no cross-references in it
+# when pressing tab, and then going back in the window history, goes
+# back to the last node that was actually displayed, and not to the
+# skipped node.
+printf '\t\t\tlDq' >$pty_type
+timeout_test
+
+grep 'Node: Top' $ginfo_output
+retval=$?
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/tab.sh b/macros/texinfo/texinfo/info/t/tab.sh
new file mode 100644
index 0000000000..e3c42b97cd
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/tab.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera
+# Tab to first link and follow it
+printf '\t\rDq' >$pty_type
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ # Return non-zero (test failure) if files differ
+ diff $ginfo_output $t/node-target
+ retval=$?
+fi
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/where-dir-file.sh b/macros/texinfo/texinfo/info/t/where-dir-file.sh
new file mode 100644
index 0000000000..8faa7d9ae9
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/where-dir-file.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Follow a reference to file1 in dir and print location
+$ginfo --where file1 \
+ | grep 't/infodir/file1.info$'
+
diff --git a/macros/texinfo/texinfo/info/t/window-split-dir.sh b/macros/texinfo/texinfo/info/t/window-split-dir.sh
new file mode 100644
index 0000000000..42f275e212
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/window-split-dir.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+# Copyright (C) 2014-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+# Load dir node
+run_ginfo
+# Split it in two, go to the new window, and make it the only window.
+printf '\0302\030o\0301Dq' >$pty_type
+timeout_test
+
+grep '^File: dir' $ginfo_output
+retval=$?
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/xref-across-lines-2.sh b/macros/texinfo/texinfo/info/t/xref-across-lines-2.sh
new file mode 100644
index 0000000000..9fd0c5f02d
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/xref-across-lines-2.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Copyright (C) 2017-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera -n "Xref target crosses lines 2"
+printf '\t\rDq' >$pty_type
+timeout_test
+
+grep 'Node: target node two' $ginfo_output
+retval=$?
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/xref-across-lines.sh b/macros/texinfo/texinfo/info/t/xref-across-lines.sh
new file mode 100644
index 0000000000..707247103b
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/xref-across-lines.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Copyright (C) 2015-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f intera -n 'Xref target crosses lines'
+printf '\t\rDq' >$pty_type
+timeout_test
+
+diff $ginfo_output $t/node-target
+retval=$?
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/t/xref-to-anchor.sh b/macros/texinfo/texinfo/info/t/xref-to-anchor.sh
new file mode 100644
index 0000000000..30394807c0
--- /dev/null
+++ b/macros/texinfo/texinfo/info/t/xref-to-anchor.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+# Copyright (C) 2015-2018 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, 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/>.
+
+srcdir=${srcdir:-.}
+. $srcdir/t/Init-test.inc
+
+run_ginfo -f xref_to_anchor
+# Follow cross-reference to an anchor
+printf '\t\r' >$pty_type
+
+# Follow the cross-reference on the line after the anchor
+printf '\6\6\rDq' >$pty_type
+
+timeout_test
+
+if test ! -f $ginfo_output; then
+ retval=1
+else
+ # Return non-zero (test failure) if files differ
+ diff $ginfo_output $t/node-target
+ retval=$?
+fi
+
+cleanup
diff --git a/macros/texinfo/texinfo/info/tag.c b/macros/texinfo/texinfo/info/tag.c
new file mode 100644
index 0000000000..18ef7d13af
--- /dev/null
+++ b/macros/texinfo/texinfo/info/tag.c
@@ -0,0 +1,243 @@
+/* tag.c -- Functions to handle Info tags (that is, the special
+ construct for images, not the "tag table" of starting position.)
+
+ Copyright 2012-2019 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/>. */
+
+#include "info.h"
+#include "tag.h"
+#include "info-utils.h"
+
+struct tag_handler
+{
+ const char *name;
+ size_t len;
+ int (*handler) (char *, struct text_buffer *);
+};
+
+struct info_tag
+{
+ struct info_tag *next;
+ char *kw;
+ char *val;
+};
+
+static void
+info_tag_free (struct info_tag *tag)
+{
+ while (tag)
+ {
+ struct info_tag *next = tag->next;
+ free (tag->kw);
+ free (tag->val);
+ free (tag);
+ tag = next;
+ }
+}
+
+
+/* See if KW is one of the tags in the list starting at TAG. */
+
+static struct info_tag *
+info_tag_find (struct info_tag *tag, const char *kw)
+{
+ for (; tag; tag = tag->next)
+ if (strcmp (tag->kw, kw) == 0)
+ return tag;
+ return NULL;
+}
+
+
+/* Found a keyword when parsing the full tag string: alt, text, etc.
+ Return the new tag, update *TMPBUF_PTR and set *KW. */
+
+static struct info_tag *
+tag_found_keyword (struct text_buffer *tmpbuf_ptr, char **kw)
+{
+ struct info_tag *tag = xmalloc (sizeof (*tag));
+ tag->next = NULL; /* have to update in caller */
+
+ text_buffer_add_char (tmpbuf_ptr, 0);
+ if (*kw != tmpbuf_ptr->base) { /* in case tmpbuf got realloc-ed */
+ *kw = tmpbuf_ptr->base; /* ick */
+ }
+ tag->kw = xstrdup (*kw);
+ tag->val = xstrdup (*kw + strlen(*kw) + 1);
+ text_buffer_reset (tmpbuf_ptr);
+
+ return tag;
+}
+
+/* Handle the image tag. */
+
+static int
+tag_image (char *text, struct text_buffer *outbuf)
+{
+ mbi_iterator_t iter;
+ enum { state_kw, state_val, state_qstr, state_delim } state = state_kw;
+ struct text_buffer tmpbuf;
+ char *kw;
+ struct info_tag *tag_head = NULL, *tag;
+ int escaped = 0;
+
+ text_buffer_init (&tmpbuf);
+ for (mbi_init (iter, text, strlen (text)); mbi_avail (iter);
+ mbi_advance (iter))
+ {
+ const char *cur_ptr;
+ size_t cur_len;
+
+ if (mb_isspace (mbi_cur (iter)))
+ {
+ if (state == state_val)
+ {
+ struct info_tag *new_kw = tag_found_keyword (&tmpbuf, &kw);
+ new_kw->next = tag_head;
+ tag_head = new_kw;
+ state = state_delim;
+ continue;
+ }
+ if (state == state_delim)
+ continue;
+ }
+ else if (state == state_delim)
+ state = state_kw;
+ cur_len = mb_len (mbi_cur (iter));
+ cur_ptr = mbi_cur_ptr (iter);
+
+ if (state == state_qstr && escaped)
+ {
+ escaped = 0;
+ }
+ else if (cur_len == 1)
+ {
+ switch (*cur_ptr)
+ {
+ case '=':
+ if (state != state_kw)
+ break;
+ text_buffer_add_char (&tmpbuf, 0);
+ kw = tmpbuf.base;
+ if (!mbi_avail (iter))
+ break;
+ mbi_advance (iter);
+ state = state_val;
+ cur_len = mb_len (mbi_cur (iter));
+ cur_ptr = mbi_cur_ptr (iter);
+ if (!(cur_len == 1 && *cur_ptr == '"'))
+ break;
+ /* fall through */
+
+ case '"':
+ if (state == state_val)
+ {
+ state = state_qstr;
+ continue;
+ }
+ if (state == state_qstr)
+ {
+ struct info_tag *new_kw = tag_found_keyword (&tmpbuf, &kw);
+ new_kw->next = tag_head;
+ tag_head = new_kw;
+ state = state_delim;
+ continue;
+ }
+ break;
+
+ case '\\':
+ if (state == state_qstr)
+ {
+ escaped = 1;
+ continue;
+ }
+ }
+ }
+ text_buffer_add_string (&tmpbuf, cur_ptr, cur_len);
+ }
+
+ tag = info_tag_find (tag_head, "text");
+ if (!tag)
+ tag = info_tag_find (tag_head, "alt");
+
+ if (tag)
+ {
+ text_buffer_add_string (outbuf, tag->val, strlen (tag->val));
+ }
+
+ text_buffer_free (&tmpbuf);
+ info_tag_free (tag_head);
+ return 0;
+}
+
+
+/* We don't do anything with the index tag; it'll just be ignored. */
+
+static struct tag_handler tagtab[] = {
+ { "image", 5, tag_image },
+ { "index", 5, NULL },
+ { NULL }
+};
+
+static struct tag_handler *
+find_tag_handler (char *tag, size_t taglen)
+{
+ struct tag_handler *tp;
+
+ for (tp = tagtab; tp->name; tp++)
+ if (taglen >= tp->len && strncmp (tp->name, tag, tp->len) == 0)
+ return tp;
+ return NULL;
+}
+
+/* Expand \b[...\b] construct at *INPUT. If encountered, append the
+ expanded text to OUTBUF, advance *INPUT past the tag, and return 1.
+ Otherwise, return 0. If it is an index tag, set IS_INDEX to 1.
+ *INPUT points into a null-terminated area which may however contain other
+ null characters. INPUT_END points to the end of this area. */
+int
+tag_expand (char **input, char *input_end,
+ struct text_buffer *outbuf, int *is_index)
+{
+ char *p = *input;
+ char *q;
+ size_t len;
+ struct tag_handler *tp;
+
+ if (p >= input_end - 3
+ || memcmp(p, "\0\b[", 3) != 0) /* opening magic? */
+ return 0;
+
+ p += 3;
+ q = p + strlen (p);
+ if (q >= input_end - 3
+ || memcmp (q + 1, "\b]", 2)) /* closing magic? */
+ return 0; /* Not a proper tag. */
+
+ /* Output is different for index nodes */
+ if (!strncmp ("index", p, strlen ("index")))
+ *is_index = 1;
+
+ len = strcspn (p, " \t"); /* tag name */
+ tp = find_tag_handler (p, len);
+ if (tp && tp->handler)
+ {
+ while (p[len] == ' ' || p[len] == '\t')
+ ++len; /* move past whitespace */
+
+ tp->handler (p + len, outbuf);
+ }
+ *input = q + 3;
+ return 1;
+}
diff --git a/macros/texinfo/texinfo/info/tag.h b/macros/texinfo/texinfo/info/tag.h
new file mode 100644
index 0000000000..71b03d1fa4
--- /dev/null
+++ b/macros/texinfo/texinfo/info/tag.h
@@ -0,0 +1,26 @@
+/* tag.c -- Functions to handle Info tags.
+
+ Copyright 2012-2019 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/>. */
+
+#ifndef TAG_H
+#define TAG_H
+
+#include "info-utils.h"
+
+int tag_expand (char **input, char *input_end,
+ struct text_buffer *outbuf, int *is_index);
+
+#endif
diff --git a/macros/texinfo/texinfo/info/termdep.h b/macros/texinfo/texinfo/info/termdep.h
new file mode 100644
index 0000000000..a73f36005c
--- /dev/null
+++ b/macros/texinfo/texinfo/info/termdep.h
@@ -0,0 +1,62 @@
+/* termdep.h -- system things that terminal.c depends on.
+
+ Copyright 1993-2019 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. */
+
+#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 */
+# ifndef __MINGW32__
+# include <sgtty.h>
+# endif
+# endif /* !HAVE_TERMIO_H */
+#endif /* !HAVE_TERMIOS_H */
+
+#ifdef HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+
+#ifdef HAVE_SYS_TTOLD_H
+# include <sys/ttold.h>
+#endif /* HAVE_SYS_TTOLD_H */
+
+#ifdef _WIN32
+extern unsigned sleep (unsigned);
+#endif
+
+#endif /* not INFO_TERMDEP_H */
diff --git a/macros/texinfo/texinfo/info/terminal.c b/macros/texinfo/texinfo/info/terminal.c
new file mode 100644
index 0000000000..d3ef1640a2
--- /dev/null
+++ b/macros/texinfo/texinfo/info/terminal.c
@@ -0,0 +1,1297 @@
+/* terminal.c -- how to handle the physical terminal for Info.
+
+ Copyright 1988-2019 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. */
+
+#include "info.h"
+#include "terminal.h"
+#include "termdep.h"
+#include "doc.h"
+#include "variables.h"
+
+#include <sys/types.h>
+#include <signal.h>
+
+/* The Unix termcap interface code. */
+/* With MinGW, if the user has ncurses installed, including termcap.h
+ or ncurses/termcap.h will cause the Info binary depend on the ncurses
+ DLL, just because BC and PC are declared there, although they are
+ never used in the MinGW build. Avoid that useless dependency. */
+#if defined (HAVE_NCURSES_TERMCAP_H) && !defined (__MINGW32__)
+#include <ncurses/termcap.h>
+#elif defined (HAVE_TERMCAP_H) && !defined (__MINGW32__)
+#include <termcap.h>
+#else /* (!HAVE_NCURSES_TERMCAP_H || __MINGW32__) && !HAVE_TERMCAP_H */
+/* 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 int tputs ();
+#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_begin_standout_hook = NULL;
+VFunction *terminal_end_standout_hook = NULL;
+VFunction *terminal_begin_underline_hook = NULL;
+VFunction *terminal_end_underline_hook = NULL;
+VFunction *terminal_begin_bold_hook = NULL;
+VFunction *terminal_begin_blink_hook = NULL;
+VFunction *terminal_end_all_modes_hook = NULL;
+VFunction *terminal_default_colour_hook = NULL;
+VFunction *terminal_set_colour_hook = NULL;
+VFunction *terminal_set_bgcolour_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;
+
+/* User variable 'mouse'. Values can be MP_* constants in terminal.h. */
+int mouse_protocol = MP_NONE;
+
+/* **************************************************************** */
+/* */
+/* 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_cs; /* Set scrolling region. */
+static char *term_SF, *term_SR; /* Scroll forward and in reverse. */
+
+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 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;
+
+/* String introducing a mouse event. */
+static char *term_Km;
+
+/* Strings entering and leaving standout mode. */
+char *term_so, *term_se;
+
+/* Strings entering and leaving underline mode. */
+char *term_us, *term_ue;
+
+/* Set foreground and background colours (terminfo setaf and setab) */
+char *term_AF, *term_AB;
+
+/* Restore original colours, both foreground and background.
+ ("original pair") */
+char *term_op;
+
+/* Turn on bold mode. */
+char *term_md;
+
+/* Turn on blink mode. */
+char *term_mb;
+
+/* Exit all attribute modes. */
+char *term_me;
+
+
+/* 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);
+
+ /* Turn on mouse reporting. This is "normal tracking mode" supported by
+ xterm. The presence of the Km capability may not be a reliable way to
+ tell whether this mode exists, but sending the following sequence is
+ probably harmless if it doesn't. */
+ if (mouse_protocol == MP_NORMAL_TRACKING
+ && term_Km && !strcmp (term_Km, "\033[M"))
+ send_to_terminal ("\033[?1000h");
+ else
+ term_Km = 0;
+
+ 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);
+
+ /* Turn off mouse reporting ("normal tracking mode"). */
+ if (term_Km)
+ send_to_terminal ("\033[?1000l");
+
+ 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 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;
+
+/* Non-zero means that the terminal scroll within a restricted region
+ of lines. */
+int terminal_can_scroll_region = 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_kB = NULL; /* back tab */
+
+/* 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);
+ }
+}
+
+/* Turn on "standout mode" if possible. Likely the same
+ as reverse video. */
+void
+terminal_begin_standout (void)
+{
+ if (terminal_begin_standout_hook)
+ (*terminal_begin_standout_hook) ();
+ else
+ {
+ send_to_terminal (term_so);
+ }
+}
+
+/* Turn off "standout mode" if possible. */
+void
+terminal_end_standout (void)
+{
+ if (terminal_end_standout_hook)
+ (*terminal_end_standout_hook) ();
+ else
+ {
+ send_to_terminal (term_se);
+ }
+}
+
+void
+terminal_begin_underline (void)
+{
+ if (terminal_begin_underline_hook)
+ (*terminal_begin_underline_hook) ();
+ else
+ {
+ send_to_terminal (term_us);
+ }
+}
+
+void
+terminal_end_underline (void)
+{
+ if (terminal_end_underline_hook)
+ (*terminal_end_underline_hook) ();
+ else
+ {
+ send_to_terminal (term_ue);
+ }
+}
+
+void
+terminal_begin_bold (void)
+{
+ if (terminal_begin_bold_hook)
+ (*terminal_begin_bold_hook) ();
+ else
+ {
+ send_to_terminal (term_md);
+ }
+}
+
+void
+terminal_begin_blink (void)
+{
+ if (terminal_begin_blink_hook)
+ (*terminal_begin_blink_hook) ();
+ else
+ {
+ send_to_terminal (term_mb);
+ }
+}
+
+void
+terminal_end_all_modes (void)
+{
+ if (terminal_end_all_modes_hook)
+ (*terminal_end_all_modes_hook) ();
+ else
+ {
+ send_to_terminal (term_me);
+ }
+}
+
+/* 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);
+}
+
+void
+terminal_scroll_region (int start, int end, int amount)
+{
+ /* Any scrolling at all? */
+ if (amount == 0)
+ return;
+
+ if (terminal_scroll_terminal_hook)
+ {
+ (*terminal_scroll_terminal_hook) (start, end, amount);
+ return;
+ }
+
+ if (terminal_can_scroll_region)
+ {
+ /* Set scrolling region. */
+ tputs (tgoto (term_cs, end - 1, start), 0, output_character_function);
+
+ /* Scroll. */
+ if (amount > 0)
+ tputs (tgoto (term_SR, 0, amount), 0, output_character_function);
+ else
+ tputs (tgoto (term_SF, 0, -amount), 0, output_character_function);
+
+ /* Reset scrolling region. */
+ tputs (tgoto (term_cs, screenheight - 1, 0), 0, output_character_function);
+ return;
+ }
+}
+
+/* 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 (amount > 0)
+ {
+ /* If we are scrolling down, delete AMOUNT lines at END. Then insert
+ AMOUNT lines at START. */
+ terminal_delete_lines (end, amount);
+ terminal_insert_lines (start, amount);
+ }
+ else
+ {
+ /* 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). */
+ int abs_amount = -amount;
+ terminal_delete_lines (start - abs_amount, abs_amount);
+ terminal_insert_lines (end - abs_amount, abs_amount);
+ }
+}
+
+/* Revert to the default foreground and background colours. */
+static void
+terminal_default_colour (void)
+{
+ if (terminal_default_colour_hook)
+ (*terminal_default_colour_hook) ();
+ else
+ tputs (term_op, 0, output_character_function);
+}
+
+static void
+terminal_set_colour (int colour)
+{
+ if (terminal_set_colour_hook)
+ (*terminal_set_colour_hook) (colour);
+ else
+ tputs (tgoto (term_AF, 0, colour), 0, output_character_function);
+}
+
+static void
+terminal_set_bgcolour (int colour)
+{
+ if (terminal_set_bgcolour_hook)
+ (*terminal_set_bgcolour_hook) (colour);
+ else
+ tputs (tgoto (term_AB, 0, colour), 0, output_character_function);
+}
+
+/* Information about what styles like colour, underlining, boldface are
+ currently output for text on the screen. All zero represents the default
+ rendition. */
+static unsigned long terminal_rendition;
+
+/* Modes for which there aren't termcap entries for turning them off. */
+#define COMBINED_MODES (BOLD_MASK | BLINK_MASK)
+
+void
+terminal_switch_rendition (unsigned long new)
+{
+ unsigned long old = terminal_rendition;
+
+ if ((old & new & COMBINED_MODES) != (old & COMBINED_MODES))
+ {
+ /* Some modes we can't turn off by themselves, so if we need to turn
+ one of them off, turn back on all the ones that should be on
+ afterwards. */
+ terminal_end_all_modes ();
+ old = 0;
+ }
+ else if (!(new & COLOUR_MASK) && (old & COLOUR_MASK)
+ || !(new & BGCOLOUR_MASK) && (old & BGCOLOUR_MASK))
+ {
+ terminal_default_colour ();
+ old &= ~(COLOUR_MASK|BGCOLOUR_MASK);
+ }
+
+ if ((new & COLOUR_MASK) != (old & COLOUR_MASK))
+ {
+ if ((new & COLOUR_MASK) >= 8)
+ {
+ terminal_set_colour ((new & COLOUR_MASK) - 8);
+ }
+ /* Colour values from 1 to 7 don't do anything right now. */
+ }
+ if ((new & BGCOLOUR_MASK) != (old & BGCOLOUR_MASK))
+ {
+ /* Switch colour. */
+ if ((new & BGCOLOUR_MASK) >> 9 >= 8)
+ {
+ terminal_set_bgcolour (((new & BGCOLOUR_MASK) >> 9) - 8);
+ }
+ /* Colour values from 1 to 7 don't do anything right now. */
+ }
+
+ if ((new & UNDERLINE_MASK) != (old & UNDERLINE_MASK))
+ {
+ if ((new & UNDERLINE_MASK))
+ terminal_begin_underline ();
+ else
+ terminal_end_underline ();
+ }
+ if ((new & STANDOUT_MASK) != (old & STANDOUT_MASK))
+ {
+ if ((new & STANDOUT_MASK))
+ terminal_begin_standout ();
+ else
+ terminal_end_standout ();
+ }
+ if ((new & BOLD_MASK) != (old & BOLD_MASK))
+ {
+ if ((new & BOLD_MASK))
+ terminal_begin_bold ();
+ }
+ if ((new & BLINK_MASK) != (old & BLINK_MASK))
+ {
+ if ((new & BLINK_MASK))
+ terminal_begin_blink ();
+ }
+ terminal_rendition = new;
+}
+
+
+/* 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);
+ }
+}
+
+/* Saved values of the LINES and COLUMNS environmental variables. */
+static char *env_lines, *env_columns;
+
+/* 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)
+ {
+ if (env_columns)
+ screenwidth = atoi (env_columns);
+
+ if (screenwidth <= 0)
+ screenwidth = tgetnum ("co");
+ }
+
+ /* Environment variable LINES overrides setting of "li". */
+ if (screenheight <= 0)
+ {
+ if (env_lines)
+ screenheight = atoi (env_lines);
+
+ if (screenheight <= 0)
+ screenheight = tgetnum ("li");
+ }
+
+ /* If all else fails, default to 80x24 terminal. */
+ if (screenwidth <= 0)
+ screenwidth = 80;
+
+ if (screenheight <= 0)
+ screenheight = 24;
+ }
+}
+
+/* Root of structure representing a mapping from sequences of bytes to named
+ keys. */
+BYTEMAP_ENTRY *byte_seq_to_key;
+
+static void
+add_seq_to_byte_map (int key_id, char *seq)
+{
+ BYTEMAP_ENTRY *b = byte_seq_to_key;
+
+ /* Must consider bytes as unsigned because we use them as array indices. */
+ unsigned char *c = (unsigned char *) seq;
+ for (; *c; c++)
+ {
+ if (c[1] == '\0') /* Last character. */
+ {
+ b[*c].type = BYTEMAP_KEY;
+ b[*c].key = key_id;
+ }
+ else
+ {
+ b[*c].type = BYTEMAP_MAP;
+ b[*c].key = 0;
+ if (!b[*c].next)
+ b[*c].next = xzalloc (256 * sizeof (BYTEMAP_ENTRY));
+ b = b[*c].next;
+ }
+ }
+}
+
+/* When non-zero, various display and input functions handle extended
+ character sets such as ISO Latin or UTF-8 correctly. */
+int ISO_Latin_p = 1;
+
+/* Initialize byte map read in get_input_key. */
+static void
+initialize_byte_map (void)
+{
+ int i;
+
+ static struct special_keys {
+ int key_id;
+ char **byte_seq;
+ } keys[] = {
+ KEY_RIGHT_ARROW, &term_kr,
+ KEY_LEFT_ARROW, &term_kl,
+ KEY_UP_ARROW, &term_ku,
+ KEY_DOWN_ARROW, &term_kd,
+ KEY_PAGE_UP, &term_kP,
+ KEY_PAGE_DOWN, &term_kN,
+ KEY_HOME, &term_kh,
+ KEY_END, &term_ke,
+ KEY_DELETE, &term_kD,
+ KEY_INSERT, &term_ki,
+ KEY_BACK_TAB, &term_kB
+ };
+
+ /* Recognize arrow key sequences with both of the usual prefixes in case they
+ are missing in the termcap entry. */
+ static struct special_keys2 {
+ int key_id;
+ char *byte_seq;
+ } keys2[] = {
+ KEY_RIGHT_ARROW, "\033[C",
+ KEY_RIGHT_ARROW, "\033OC",
+ KEY_LEFT_ARROW, "\033[D",
+ KEY_LEFT_ARROW, "\033OD",
+ KEY_UP_ARROW, "\033[A",
+ KEY_UP_ARROW, "\033OA",
+ KEY_DOWN_ARROW, "\033[B",
+ KEY_DOWN_ARROW, "\033OB"
+ };
+
+ byte_seq_to_key = xmalloc (256 * sizeof (BYTEMAP_ENTRY));
+
+ /* Make each byte represent itself by default. */
+ for (i = 0; i < 128; i++)
+ {
+ byte_seq_to_key[i].type = BYTEMAP_KEY;
+ byte_seq_to_key[i].key = i;
+ byte_seq_to_key[i].next = 0;
+ }
+
+ /* Use 'ISO-Latin' variable to decide whether bytes with the 8th bit set
+ represent the Meta key being pressed. Maybe we should have another
+ variable to enable 8-bit input. If 'ISO-Latin' is set this allows input
+ of non-ASCII characters in the echo area. */
+ if (!ISO_Latin_p)
+ for (i = 128; i < 256; i++)
+ {
+ byte_seq_to_key[i].type = BYTEMAP_KEY;
+ byte_seq_to_key[i].key = (i - 128) + KEYMAP_META_BASE;
+ byte_seq_to_key[i].next = 0;
+ }
+
+ /* Hard-code octal 177 = delete. Either 177 or the term_kD sequence will
+ result in a delete key being registered. */
+ byte_seq_to_key['\177'].type = BYTEMAP_KEY;
+ byte_seq_to_key['\177'].key = KEY_DELETE;
+ byte_seq_to_key['\177'].next = 0;
+
+ /* For each special key, record its byte sequence. */
+ for (i = 0; i < sizeof (keys) / sizeof (*keys); i++)
+ {
+ if (!*keys[i].byte_seq)
+ continue; /* No byte sequence known for this key. */
+
+ add_seq_to_byte_map (keys[i].key_id, *keys[i].byte_seq);
+ }
+
+ /* Hard-coded byte sequences. */
+ for (i = 0; i < sizeof (keys2) / sizeof (*keys2); i++)
+ {
+ add_seq_to_byte_map (keys2[i].key_id, keys2[i].byte_seq);
+ }
+
+ if (term_Km)
+ add_seq_to_byte_map (KEY_MOUSE, term_Km);
+
+ /* Special case for ESC: Can introduce special key sequences, represent the
+ Meta key being pressed, or be a key on its own. */
+ byte_seq_to_key['\033'].type = BYTEMAP_ESC;
+}
+
+/* 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. Get and save various
+ termcap strings. */
+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);
+ initialize_byte_map ();
+ return;
+ }
+
+ term_name = terminal_name ? terminal_name : getenv ("TERM");
+ if (!term_name)
+ term_name = "dumb";
+
+ env_lines = getenv ("LINES");
+ env_columns = getenv ("COLUMNS");
+ /* We save LINES and COLUMNS before the call to tgetent below, because
+ on some openSUSE systems, including openSUSE 12.3, the call to tgetent
+ changes the values returned by getenv for these. */
+
+ 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
+#ifndef __MINGW32__
+ ospeed = B9600;
+#endif
+# 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);
+
+ term_cs = tgetstr ("cs", &buffer);
+ term_SF = tgetstr ("SF", &buffer);
+ term_SR = tgetstr ("SR", &buffer);
+
+ terminal_can_scroll = ((term_AL || term_al) && (term_DL || term_dl));
+ terminal_can_scroll_region = term_cs && term_SF && term_SR;
+
+ term_invbeg = tgetstr ("mr", &buffer);
+ if (term_invbeg)
+ term_invend = tgetstr ("me", &buffer);
+ else
+ term_invend = NULL;
+
+ term_so = tgetstr ("so", &buffer);
+ if (term_so)
+ term_se = tgetstr ("se", &buffer);
+ else
+ term_se = NULL;
+
+ term_us = tgetstr ("us", &buffer);
+ if (term_us)
+ term_ue = tgetstr ("ue", &buffer);
+ else
+ term_ue = NULL;
+
+ term_AF = tgetstr ("AF", &buffer);
+ if (term_AF)
+ term_AB = tgetstr ("AB", &buffer);
+ else
+ term_AB = NULL;
+
+ term_op = tgetstr ("op", &buffer);
+
+ term_md = tgetstr ("md", &buffer);
+ term_mb = tgetstr ("mb", &buffer);
+
+ term_me = tgetstr ("me", &buffer);
+ if (!term_me)
+ term_md = 0; /* Don't use modes if we can't turn them off. */
+
+ 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);
+
+ /* 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);
+
+ term_kh = tgetstr ("kh", &buffer);
+ term_ke = tgetstr ("@7", &buffer);
+ term_ki = tgetstr ("kI", &buffer);
+ term_kD = tgetstr ("kD", &buffer);
+
+ term_kB = tgetstr ("kB", &buffer);
+
+ /* String introducing a mouse event. */
+ term_Km = tgetstr ("Km", &buffer);
+
+ initialize_byte_map ();
+
+ /* 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;
+#ifndef __MINGW32__
+struct sgttyb ttybuff;
+#endif
+
+# 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. Return
+ 0 if terminal is too dumb to run Info interactively. */
+int
+terminal_prep_terminal (void)
+{
+ int tty;
+
+ if (terminal_is_dumb_p)
+ return 0;
+
+ if (terminal_prep_terminal_hook)
+ {
+ (*terminal_prep_terminal_hook) ();
+ return 1;
+ }
+
+ 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) && !defined(__MINGW32__)
+ 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 */
+
+# ifndef __MINGW32__
+ ttybuff.sg_flags &= ~ECHO;
+ ttybuff.sg_flags |= CBREAK;
+ ioctl (tty, TIOCSETN, &ttybuff);
+# endif
+#endif /* !HAVE_TERMIOS_H && !HAVE_TERMIO_H */
+ return 1;
+}
+
+/* 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 */
+# ifndef __MINGW32__
+ ioctl (tty, TIOCGETP, &ttybuff);
+ ttybuff.sg_flags = original_tty_flags;
+ ioctl (tty, TIOCSETN, &ttybuff);
+# endif
+
+# 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 ();
+}
+
+#if defined(__MSDOS__) || defined(__MINGW32__)
+# include "pcterm.c"
+#endif
diff --git a/macros/texinfo/texinfo/info/terminal.h b/macros/texinfo/texinfo/info/terminal.h
new file mode 100644
index 0000000000..120912423c
--- /dev/null
+++ b/macros/texinfo/texinfo/info/terminal.h
@@ -0,0 +1,180 @@
+/* terminal.h -- The external interface to terminal I/O.
+
+ Copyright 1993-2019 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 uWritten by Brian Fox. */
+
+#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 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;
+
+/* Non-zero means that this terminal can scroll within a restricted region. */
+extern int terminal_can_scroll_region;
+
+/* 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. */
+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 int 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;
+
+/* Turn on standout mode if possible. */
+extern void terminal_begin_standout (void);
+extern VFunction *terminal_begin_standout_hook;
+
+/* Turn off standout mode if possible. */
+extern void terminal_end_standout (void);
+extern VFunction *terminal_end_standout_hook;
+
+/* Turn on and off underline mode if possible. */
+void terminal_begin_underline (void);
+extern VFunction *terminal_begin_underline_hook;
+void terminal_end_underline (void);
+extern VFunction *terminal_end_underline_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;
+
+extern void terminal_scroll_region (int start, int end, int amount);
+
+/* 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_kD, *term_ki;
+extern char *term_kB;
+
+extern char *term_so, *term_se;
+
+#define MP_NONE 0
+#define MP_NORMAL_TRACKING 1
+extern int mouse_protocol;
+
+#define COLOUR_MASK 000000000017
+#define COLOUR_BLACK (8 + 0)
+#define COLOUR_RED (8 + 1)
+#define COLOUR_GREEN (8 + 2)
+#define COLOUR_YELLOW (8 + 3)
+#define COLOUR_BLUE (8 + 4)
+#define COLOUR_MAGENTA (8 + 5)
+#define COLOUR_CYAN (8 + 6)
+#define COLOUR_WHITE (8 + 7)
+#define UNDERLINE_MASK 000000000020
+#define STANDOUT_MASK 000000000040
+#define BOLD_MASK 000000000100
+#define ZERO1_MASK 000000000200
+#define BLINK_MASK 000000000400
+#define BGCOLOUR_MASK 000000017000
+#define BGCOLOUR_BLACK ((8 + 0) << 9)
+#define BGCOLOUR_RED ((8 + 1) << 9)
+#define BGCOLOUR_GREEN ((8 + 2) << 9)
+#define BGCOLOUR_YELLOW ((8 + 3) << 9)
+#define BGCOLOUR_BLUE ((8 + 4) << 9)
+#define BGCOLOUR_MAGENTA ((8 + 5) << 9)
+#define BGCOLOUR_CYAN ((8 + 6) << 9)
+#define BGCOLOUR_WHITE ((8 + 7) << 9)
+#define ZERO2_MASK 000000100000
+#define ZERO3_MASK 000040000000
+#define ZERO4_MASK 020000000000
+
+/* ZEROi_MASK are always zero bits. */
+
+void terminal_switch_rendition (unsigned long desired_rendition);
+
+
+#endif /* !TERMINAL_H */
diff --git a/macros/texinfo/texinfo/info/tilde.c b/macros/texinfo/texinfo/info/tilde.c
new file mode 100644
index 0000000000..15056ea9d5
--- /dev/null
+++ b/macros/texinfo/texinfo/info/tilde.c
@@ -0,0 +1,105 @@
+/* tilde.c -- tilde expansion code (~/foo := $HOME/foo).
+
+ Copyright 1988-2019 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. */
+
+#include "info.h"
+#include "tilde.h"
+
+/* Do the work of tilde expansion on FILENAME. FILENAME starts with a
+ tilde. */
+char *
+tilde_expand_word (const 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)
+ {
+#ifndef __MINGW32__
+ struct passwd *entry;
+
+ entry = (struct passwd *) getpwuid (getuid ());
+ if (entry)
+ temp_home = entry->pw_dir;
+#else
+ temp_home = ".";
+#endif
+ }
+
+ 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
+ {
+#ifndef __MINGW32__
+ struct passwd *user_entry;
+#endif
+ 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;
+
+#ifndef __MINGW32__
+ user_entry = (struct passwd *) getpwnam (username);
+ if (user_entry)
+ {
+ temp_name = xmalloc (1 + strlen (user_entry->pw_dir)
+ + strlen (&dirname[i]));
+ strcpy (temp_name, user_entry->pw_dir);
+ strcat (temp_name, &dirname[i]);
+
+ free (dirname);
+ dirname = xstrdup (temp_name);
+ free (temp_name);
+ }
+
+ endpwent ();
+ free (username);
+#else
+ free (dirname);
+ dirname = xstrdup (temp_name);
+ free (temp_name);
+#endif
+ }
+ }
+ return dirname;
+}
diff --git a/macros/texinfo/texinfo/info/tilde.h b/macros/texinfo/texinfo/info/tilde.h
new file mode 100644
index 0000000000..0df7208a2e
--- /dev/null
+++ b/macros/texinfo/texinfo/info/tilde.h
@@ -0,0 +1,29 @@
+/* tilde.h: tilde expansion.
+
+ Copyright 1988-2019 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 (for bash). */
+
+#ifndef TILDE_H
+#define TILDE_H
+
+#include "info.h"
+
+/* Do the work of tilde expansion on FILENAME. FILENAME starts with a
+ tilde. */
+extern char *tilde_expand_word (const char *filename);
+
+#endif /* not TILDE_H */
diff --git a/macros/texinfo/texinfo/info/variables.c b/macros/texinfo/texinfo/info/variables.c
new file mode 100644
index 0000000000..825201784c
--- /dev/null
+++ b/macros/texinfo/texinfo/info/variables.c
@@ -0,0 +1,534 @@
+/* variables.c -- how to manipulate user visible variables in Info.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+#include "session.h"
+#include "echo-area.h"
+#include "variables.h"
+#include "terminal.h"
+#include "display.h"
+
+static VARIABLE_ALIST *read_variable_name (char *prompt, WINDOW *window);
+
+/* **************************************************************** */
+/* */
+/* 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 };
+
+static char *mouse_choices[] = { "Off", "normal-tracking", NULL };
+
+static char *follow_strategy_choices[] = { "remain", "path", NULL };
+
+static char *nodeline_choices[] = { "no", "print", "pointers", NULL };
+
+/* Choices used by the completer when reading a value for the user-visible
+ variable "scroll-behaviour". */
+static char *info_scroll_choices[] = { "Continuous", "Next Only",
+ "Page Only", NULL };
+
+/* Choices for the scroll-last-node variable */
+static char *scroll_last_node_choices[] = { "Stop", "Top", NULL };
+
+/* Choices for, and indicator of, rendition. */
+static char *rendition_choices[] = { "black", "red", "green", "yellow", "blue",
+ "magenta", "cyan", "white", "nocolour", "bgblack", "bgred", "bggreen",
+ "bgyellow", "bgblue", "bgmagenta", "bgcyan", "bgwhite", "nobgcolour",
+ "underline", "nounderline", "standout", "nostandout", "bold", "regular",
+ "blink", "noblink", NULL };
+
+/* Address of this indicates the 'highlight-searches' variable. */
+static int *highlight_searches;
+
+/* Note that the 'where_set' field of each element in the array is
+ not given and defaults to 0. */
+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 },
+
+ { "min-search-length",
+ N_("Minimal length of a search string"),
+ &min_search_length, NULL },
+
+ { "search-skip-screen",
+ N_("Skip current window when searching"),
+ &search_skip_screen_p, (char **)on_off_choices },
+
+ { "infopath-no-defaults",
+ N_("Exclude default directories from file search path"),
+ &infopath_no_defaults_p, (char **)on_off_choices },
+
+ { "hide-note-references",
+ N_("Hide some Info file syntax in the text of nodes"),
+ &preprocess_nodes_p, (char **)on_off_choices },
+
+ { "key-time",
+ N_("Length of time in milliseconds to wait for the next byte in a sequence indicating that a key has been pressed"),
+ &key_time, NULL },
+
+ { "mouse",
+ N_("Method to use to track mouse events"),
+ &mouse_protocol, (char **)mouse_choices },
+
+ { "follow-strategy",
+ N_("How to follow a cross-reference"),
+ &follow_strategy, (char **)follow_strategy_choices },
+
+ { "highlight-searches",
+ N_("Highlight search matches"),
+ &highlight_searches, (char **)on_off_choices },
+
+ { "link-style",
+ N_("Styles for links"),
+ &ref_rendition, (char **)rendition_choices },
+
+ { "active-link-style",
+ N_("Styles for active links"),
+ &hl_ref_rendition, (char **)rendition_choices },
+
+ { "match-style",
+ N_("Styles for search matches"),
+ &match_rendition, (char **)rendition_choices },
+
+ { "nodeline",
+ N_("How to print the information line at the start of a node"),
+ &nodeline_print, (char **)nodeline_choices },
+
+ { NULL }
+};
+
+static char *
+rendition_to_string (RENDITION *rendition)
+{
+ static char string[8 /* nocolour */ + 1 /* comma */
+ + 10 /* nobgcolour */ + 1
+ + 11 /* nounderline */ + 1
+ + 10 /* nostandout */ + 1
+ + 7 /* regular */ + 1
+ + 7 /* noblink */ + 1];
+ unsigned long style;
+ static const char* fg[] = { "black", "red", "green", "yellow", "blue",
+ "magenta", "cyan", "white" };
+ static const char* bg[] = { "bgblack", "bgred", "bggreen", "bgyellow",
+ "bgblue", "bgmagenta", "bgcyan", "bgwhite" };
+
+ *string = '\0';
+
+ if (rendition->mask & BLINK_MASK)
+ strcat (string, rendition->value & BLINK_MASK ? "blink" : "noblink");
+ if (rendition->mask & BOLD_MASK)
+ {
+ if (*string != '\0')
+ strcat (string, ",");
+ strcat (string, rendition->value & BOLD_MASK ? "bold" : "nobold");
+ }
+ if (rendition->mask & STANDOUT_MASK)
+ {
+ if (*string != '\0')
+ strcat (string, ",");
+ strcat (string, rendition->value & STANDOUT_MASK ? "standout" : "nostandout");
+ }
+ if (rendition->mask & UNDERLINE_MASK)
+ {
+ if (*string != '\0')
+ strcat (string, ",");
+ strcat (string, rendition->value & UNDERLINE_MASK ? "underline" : "nounderline");
+ }
+ if (rendition->mask & COLOUR_MASK)
+ {
+ if (*string != '\0')
+ strcat (string, ",");
+ style = rendition->value & COLOUR_MASK;
+ strcat (string, style >= 8 ? fg[style - 8] : "nocolour");
+ }
+ if (rendition->mask & BGCOLOUR_MASK)
+ {
+ if (*string != '\0')
+ strcat (string, ",");
+ style = (rendition->value & BGCOLOUR_MASK) >> 9;
+ strcat (string, style >= 8 ? bg[style - 8] : "nobgcolour");
+ }
+
+ return string;
+}
+
+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;
+
+ if (var->choices)
+ asprintf (&description, "%s (%s): %s.",
+ var->name,
+ var->value == &highlight_searches
+ ? on_off_choices[match_rendition.mask != 0]
+ : var->choices == (char **) &rendition_choices
+ ? rendition_to_string (var->value)
+ : var->choices[*(int *)var->value], _(var->doc));
+ else
+ asprintf (&description, "%s (%d): %s.",
+ var->name, *(int *)var->value, _(var->doc));
+
+ window_message_in_echo_area ("%s", description);
+ free (description);
+}
+
+DECLARE_INFO_COMMAND (set_variable, _("Set the value of an Info variable"))
+{
+ VARIABLE_ALIST *var;
+ char *line;
+ char prompt[100];
+
+ /* Get the variable's name and value. */
+ var = read_variable_name (_("Set variable: "), window);
+
+ if (!var)
+ return;
+
+ /* Read a new value for this variable. */
+
+ if (!var->choices)
+ {
+ int potential_value;
+
+ if (info_explicit_arg || count != 1)
+ potential_value = count;
+ else
+ potential_value = *(int *)(var->value);
+
+ sprintf (prompt, _("Set %s to value (%d): "),
+ var->name, potential_value);
+ line = info_read_in_echo_area (prompt);
+
+ /* User aborted? */
+ if (!line)
+ return;
+
+ /* If the user specified a value, get that, otherwise, we are done. */
+ canonicalize_whitespace (line);
+
+ set_variable_to_value (var, line, SET_IN_SESSION);
+
+ free (line);
+ }
+ else
+ {
+ register int i;
+ REFERENCE **array = NULL;
+ size_t array_index = 0;
+ size_t 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);
+ }
+
+ sprintf (prompt, _("Set %s to value (%s): "),
+ var->name,
+ var->value == &highlight_searches
+ ? on_off_choices[match_rendition.mask != 0]
+ : var->choices == (char **) &rendition_choices
+ ? rendition_to_string (var->value)
+ : var->choices[*(int *)(var->value)]);
+
+ /* Ask the completer to read a variable value for us. */
+ if (var->choices == (char **) &rendition_choices)
+ line = info_read_maybe_completing (prompt, array);
+ else
+ line = info_read_completing_in_echo_area (prompt, array);
+
+ info_free_references (array);
+
+ /* User aborted? */
+ if (!line)
+ {
+ info_abort_key (active_window, 0);
+ return;
+ }
+
+ /* User accepted default choice? If so, no change. */
+ if (!*line)
+ {
+ free (line);
+ return;
+ }
+
+ set_variable_to_value (var, line, SET_IN_SESSION);
+ }
+}
+
+VARIABLE_ALIST *
+variable_by_name (char *name)
+{
+ 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 NULL;
+ else
+ return &info_variables[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. */
+static VARIABLE_ALIST *
+read_variable_name (char *prompt, WINDOW *window)
+{
+ 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 (prompt, variables);
+
+ info_free_references (variables);
+
+ /* User aborted? */
+ if (!line)
+ {
+ info_abort_key (active_window, 0);
+ return NULL;
+ }
+
+ /* User accepted "default"? (There is none.) */
+ if (!*line)
+ {
+ free (line);
+ return NULL;
+ }
+
+ return variable_by_name (line);
+}
+
+/* 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;
+ size_t 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);
+ }
+
+ return array;
+}
+
+/* VALUE is a string that is the value of the variable specified
+ by the user. Update our internal data structure VAR using this
+ information. */
+int
+set_variable_to_value (VARIABLE_ALIST *var, char *value, int where)
+{
+ /* If variable was set elsewhere with a higher priority, don't do
+ anything, but don't indicate an error. */
+ if (var->where_set > where)
+ return 1;
+
+ if (var->choices)
+ {
+ register int j;
+
+ /* "highlight-searches=On" is equivalent to
+ "match-rendition=standout". */
+ if (var->value == &highlight_searches)
+ {
+ if (strcmp (on_off_choices[0], value) == 0)
+ {
+ match_rendition.mask = 0;
+ match_rendition.value = 0;
+ }
+ else
+ {
+ match_rendition.mask = STANDOUT_MASK;
+ match_rendition.value = STANDOUT_MASK;
+ }
+ }
+ else if (var->choices != (char **) &rendition_choices)
+ {
+ /* Find the choice in our list of choices. */
+ for (j = 0; var->choices[j]; j++)
+ if (strcmp (var->choices[j], value) == 0)
+ {
+ *(int *)var->value = j;
+ var->where_set = where;
+ return 1;
+ }
+ }
+ else
+ {
+ static struct {
+ unsigned long mask;
+ unsigned long value;
+ char *name;
+ } styles[] = {
+ COLOUR_MASK, COLOUR_BLACK, "black",
+ COLOUR_MASK, COLOUR_RED, "red",
+ COLOUR_MASK, COLOUR_GREEN, "green",
+ COLOUR_MASK, COLOUR_YELLOW, "yellow",
+ COLOUR_MASK, COLOUR_BLUE, "blue",
+ COLOUR_MASK, COLOUR_MAGENTA, "magenta",
+ COLOUR_MASK, COLOUR_CYAN, "cyan",
+ COLOUR_MASK, COLOUR_WHITE, "white",
+ COLOUR_MASK, 0, "nocolour",
+ COLOUR_MASK, 0, "nocolor",
+ BGCOLOUR_MASK, BGCOLOUR_BLACK, "bgblack",
+ BGCOLOUR_MASK, BGCOLOUR_RED, "bgred",
+ BGCOLOUR_MASK, BGCOLOUR_GREEN, "bggreen",
+ BGCOLOUR_MASK, BGCOLOUR_YELLOW, "bgyellow",
+ BGCOLOUR_MASK, BGCOLOUR_BLUE, "bgblue",
+ BGCOLOUR_MASK, BGCOLOUR_MAGENTA, "bgmagenta",
+ BGCOLOUR_MASK, BGCOLOUR_CYAN, "bgcyan",
+ BGCOLOUR_MASK, BGCOLOUR_WHITE, "bgwhite",
+ BGCOLOUR_MASK, 0, "nobgcolour",
+ BGCOLOUR_MASK, 0, "nobgcolor",
+ UNDERLINE_MASK, UNDERLINE_MASK, "underline",
+ UNDERLINE_MASK, 0, "nounderline",
+ STANDOUT_MASK, STANDOUT_MASK, "standout",
+ STANDOUT_MASK, 0, "nostandout",
+ BOLD_MASK, BOLD_MASK, "bold",
+ BOLD_MASK, 0, "regular",
+ BOLD_MASK, 0, "nobold",
+ BLINK_MASK, BLINK_MASK, "blink",
+ BLINK_MASK, 0, "noblink",
+ };
+ int i;
+ char *component;
+ unsigned long rendition_mask = 0;
+ unsigned long rendition_value = 0;
+
+ component = strtok (value, ",");
+ while (component)
+ {
+ for (i = 0; (styles[i].name); i++)
+ {
+ if (!strcmp (styles[i].name, component))
+ break;
+ }
+ if (styles[i].name)
+ {
+ rendition_mask |= styles[i].mask;
+ rendition_value &= ~styles[i].mask;
+ rendition_value |= styles[i].value;
+ }
+ /* If not found, silently ignore, in case more options are
+ added in the future. */
+
+ component = strtok (0, ",");
+ }
+
+ /* Now all the specified styles are recorded in rendition_value. */
+ ((RENDITION *)var->value)->mask = rendition_mask;
+ ((RENDITION *)var->value)->value = rendition_value;
+ }
+ return 1;
+ }
+ else
+ {
+ char *p;
+ long n = strtol (value, &p, 10);
+ if (*p == 0 && INT_MIN <= n && n <= INT_MAX)
+ {
+ *(int *)var->value = n;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
diff --git a/macros/texinfo/texinfo/info/variables.h b/macros/texinfo/texinfo/info/variables.h
new file mode 100644
index 0000000000..a5f7a93683
--- /dev/null
+++ b/macros/texinfo/texinfo/info/variables.h
@@ -0,0 +1,90 @@
+/* variables.h -- Description of user visible variables in Info.
+
+ Copyright 1993-2019 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. */
+
+#ifndef INFO_VARIABLES_H
+#define INFO_VARIABLES_H
+
+#include "window.h"
+#include "info-utils.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. */
+ void *value; /* Address of value. */
+ char **choices; /* Array of strings or NULL if numeric only. */
+ int where_set; /* Where this variable was set. */
+} VARIABLE_ALIST;
+
+/* Values for VARIABLE_ALIST.where_set, in order of increasing priority. */
+#define SET_BY_DEFAULT 0
+#define SET_IN_CONFIG_FILE 1
+#define SET_ON_COMMAND_LINE 2
+#define SET_IN_SESSION 4
+
+VARIABLE_ALIST *variable_by_name (char *name);
+
+/* Make an array of REFERENCE which actually contains the names of the
+ variables available in Info. */
+REFERENCE **make_variable_completions_array (void);
+
+/* Set the value of an info variable. */
+void set_variable (WINDOW *window, int count);
+int set_variable_to_value (VARIABLE_ALIST *var, char *value, int where);
+
+void describe_variable (WINDOW *window, int count);
+
+/* 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;
+extern int min_search_length;
+extern int search_skip_screen_p;
+extern int infopath_no_defaults_p;
+extern int preprocess_nodes_p;
+extern int key_time;
+extern int mouse_protocol;
+extern int follow_strategy;
+extern int nodeline_print;
+
+typedef struct {
+ unsigned long mask;
+ unsigned long value;
+} RENDITION;
+
+extern RENDITION ref_rendition;
+extern RENDITION hl_ref_rendition;
+extern RENDITION match_rendition;
+
+
+#endif /* not INFO_VARIABLES_H */
diff --git a/macros/texinfo/texinfo/info/window.c b/macros/texinfo/texinfo/info/window.c
new file mode 100644
index 0000000000..49eb2e80ed
--- /dev/null
+++ b/macros/texinfo/texinfo/info/window.c
@@ -0,0 +1,1331 @@
+/* window.c -- windows in Info.
+
+ Copyright 1993-2019 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. */
+
+#include "info.h"
+#include "session.h"
+#include "display.h"
+#include "info-utils.h"
+#include "doc.h"
+#include "tag.h"
+#include "variables.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
+
+/* 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 = xzalloc (sizeof (WINDOW));
+ the_echo_area = xzalloc (sizeof (WINDOW));
+ windows = xzalloc (sizeof (WINDOW));
+ active_window = windows;
+
+ /* 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);
+}
+
+/* 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. */
+void
+window_new_screen_size (int width, int height)
+{
+ register WINDOW *win, *first_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;
+
+ /* The screen has changed height and width. */
+ delta_height = height - the_screen->height;
+ the_screen->height = height;
+ the_screen->width = width;
+
+ /* Set the start of the echo area. */
+ the_echo_area->first_row = height - the_echo_area->height;
+ the_echo_area->width = width;
+
+ /* Count number of windows. */
+ numwins = 0;
+ for (win = windows; win; win = win->next)
+ numwins++;
+
+ if (numwins == 0)
+ return; /* There is nothing to do. */
+
+ /* Divide the change in height among the available windows. */
+ delta_each = delta_height / numwins;
+ delta_leftover = delta_height - (delta_each * 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 - 1 <= WINDOW_MIN_SIZE * numwins)
+ {
+ /* If only one window left, give up. */
+ if (!windows->next)
+ {
+ /* Keep track of the height so that when the screen gets bigger
+ again, it can be resized properly. The -2 is for the window
+ information bar and the echo area. */
+ windows->height = height - 2;
+ windows->width = width;
+ free (windows->modeline);
+ windows->modeline = xmalloc (1 + width);
+ return;
+ }
+
+ /* 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;
+
+ forget_window_and_nodes (win);
+ window_delete_window (win);
+ numwins--;
+ }
+
+ /* Alternate which window we start resizing at, to resize all
+ windows evenly. */
+ {
+ int first_win_num = the_screen->height % numwins;
+ int i;
+ first_win = windows;
+ for (i = 0; i < first_win_num; i++)
+ first_win = first_win->next;
+ }
+
+ /* 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. */
+ win = first_win;
+ do
+ {
+ if ((win->width != width) && ((win->flags & W_InhibitMode) == 0))
+ {
+ win->width = width;
+ free (win->modeline);
+ win->modeline = xmalloc (1 + width);
+ }
+
+ /* Don't resize a window to be smaller than one line. */
+ if (win->height + delta_each >= 1)
+ win->height += delta_each;
+ else
+ delta_leftover += delta_each;
+
+ /* Try to use up the extra space. */
+ if (delta_leftover != 0 && win->height + delta_leftover >= 1)
+ {
+ win->height += delta_leftover;
+ delta_leftover = 0;
+ }
+ /* Go to next window, wrapping round to the start. */
+ win = win->next;
+ if (!win)
+ win = windows;
+ }
+ while (win != first_win);
+
+ for (win = windows; win; win = win->next)
+ {
+ /* If this is not the first window in the chain, set the
+ first row of it by adding one to the location of the
+ previous window's modeline. */
+ if (win->prev)
+ win->first_row = (win->prev->first_row + win->prev->height) + 1;
+
+ if (win->node)
+ {
+ free (win->line_starts);
+ free (win->log_line_no);
+ calculate_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;
+ }
+ }
+
+ /* Make sure point is in displayed part of active window. */
+ window_adjust_pagetop (active_window);
+
+ /* 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 by splitting an existing one. If the window could
+ not be made return a null pointer. The active window is not changed .*/
+WINDOW *
+window_make_window (void)
+{
+ WINDOW *window;
+
+ /* 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 = xzalloc (sizeof (WINDOW));
+ window->width = the_screen->width;
+ window->height = (active_window->height / 2) - 1;
+ window->first_row = active_window->first_row +
+ (active_window->height - window->height);
+ 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;
+
+ /* Adjust the height of the old active window. */
+ active_window->height -= (window->height + 1);
+ active_window->flags |= W_UpdateWindow;
+
+ window_make_modeline (active_window);
+
+ /* 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;
+ 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; \
+ } while (0)
+
+#define grow_me_shrinking_prev(me, prev, diff) \
+ do { \
+ me->height += diff; \
+ prev->height -= diff; \
+ me->first_row -=diff; \
+ } while (0)
+
+#define shrink_me_growing_next(me, next, diff) \
+ do { \
+ me->height -= diff; \
+ next->height += diff; \
+ next->first_row -= diff; \
+ } while (0)
+
+#define shrink_me_growing_prev(me, prev, diff) \
+ do { \
+ me->height -= diff; \
+ prev->height += diff; \
+ me->first_row += diff; \
+ } 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_avail - amount >= prev_avail)
+ grow_me_shrinking_next (window, next, amount);
+ else if (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++;
+ }
+ }
+ }
+ }
+ if (prev)
+ prev->flags |= W_UpdateWindow;
+
+ if (next)
+ next->flags |= W_UpdateWindow;
+
+ window->flags |= W_UpdateWindow;
+}
+
+/* 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)
+ {
+ long *old_starts;
+ long *old_xlat;
+ int old_lines, old_pagetop;
+
+ old_starts = window->line_starts;
+ old_xlat = window->log_line_no;
+ 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);
+ free (old_starts);
+ free (old_xlat);
+ }
+ 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;
+
+ free (window->line_starts);
+ free (window->log_line_no);
+ calculate_line_starts (window);
+ window_compute_line_map (window);
+
+ /* Clear displayed search matches if any. */
+ free_matches (&window->matches);
+
+ window->flags |= W_UpdateWindow;
+ if (node)
+ {
+ /* 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.
+ Be aware that info_delete_window_internal (in session.c) should be called
+ instead if you need to remove the window from the info_windows list. */
+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;
+
+ free (window->line_starts);
+ free (window->log_line_no);
+ free (window->line_map.map);
+ free (window->modeline);
+ free_matches (&window->matches);
+ free (window->search_string);
+
+ if (window == active_window)
+ {
+ WINDOW *new_active = 0;
+
+ /* 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. Try to find an important
+ window to select, e.g. not a footnotes window. */
+ if (next)
+ {
+ new_active = next;
+ while ((new_active->flags & W_TempWindow) && new_active->next)
+ new_active = new_active->next;
+ }
+
+ if ((!new_active || new_active->flags & W_TempWindow) && prev)
+ {
+ new_active = prev;
+ while ((new_active->flags & W_TempWindow) && new_active->prev)
+ new_active = new_active->prev;
+ }
+ active_window = new_active;
+ }
+
+ 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 first physical line corresponding to the logical
+ line LN.
+
+ A logical line can occupy one or more physical lines of output. It
+ occupies more than one physical line if its width is greater than the
+ window width and the flag W_NoWrap is not set for that window.
+ */
+long
+window_log_to_phys_line (WINDOW *window, long ln)
+{
+ size_t i;
+
+ if (ln > window->line_count)
+ return 0;
+ for (i = ln; i < window->line_count && window->log_line_no[i] < ln; i++)
+ ;
+ return i;
+}
+
+/* Change the pagetop of WINDOW to DESIRED_TOP, perhaps scrolling the screen
+ to do so. WINDOW->pagetop should be the currently displayed pagetop. */
+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)
+ {
+ window->point = window->line_starts[window->pagetop];
+ window->goal_column = 0;
+ }
+ else if (point_line >= window->pagetop + window->height)
+ {
+ long bottom = window->pagetop + window->height - 1;
+ window->point = window->line_starts[bottom];
+ window->goal_column = 0;
+ }
+
+ 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.
+
+ To prevent status line blinking when keeping up or down key,
+ scrolling is disabled if the amount to scroll is 1. */
+ if (old_pagetop < desired_top)
+ {
+ int start, end, amount;
+
+ amount = desired_top - old_pagetop;
+
+ if (amount == 1 ||
+ (amount >= window->height) ||
+ (((window->height - amount) * 10) < window->height))
+ return;
+
+ start = 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 == 1 ||
+ (amount >= window->height) ||
+ (((window->height - amount) * 10) < window->height))
+ return;
+
+ start = window->first_row;
+ end = window->first_row + window->height;
+ display_scroll_display (start, end, amount);
+ }
+}
+
+/* Adjust the pagetop of WINDOW such that the cursor point will be visible. */
+void
+window_adjust_pagetop (WINDOW *window)
+{
+ register int line;
+
+ if (!window->node)
+ return;
+
+ line = window_line_of_point (window);
+
+ /* 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)
+ {
+ int new_pagetop = line - ((window->height - 1) / 2);
+
+ if (new_pagetop < 0)
+ new_pagetop = 0;
+ set_window_pagetop (window, new_pagetop);
+ }
+}
+
+/* Return the index of the line containing point. */
+int
+window_line_of_point (WINDOW *window)
+{
+ register int i, start = 0;
+
+ if (!window->line_starts)
+ calculate_line_starts (window);
+
+ /* Check 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->point)
+ start = window->pagetop;
+
+ for (i = start; i < window->line_count; i++)
+ {
+ if (window->line_starts[i] > window->point)
+ break;
+ }
+
+ if (i > 0)
+ return i - 1;
+ else
+ return 0; /* Shouldn't happen */
+}
+
+/* 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);
+}
+
+/* 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 - window->height);
+
+ 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 *nodename = "*no node*";
+ NODE *node = window->node;
+ char *name;
+ int dot;
+
+ if (node && node->nodename)
+ nodename = node->nodename;
+
+ name = filename_non_directory (node->fullpath);
+
+ /* 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 += 3; /* strlen (location_indicator) */
+ modeline_len += strlen (name);
+ if (nodename)
+ modeline_len += strlen (nodename);
+ if (modeline_len < window->width)
+ modeline_len = window->width;
+
+ modeline = xcalloc (1, 1 + modeline_len);
+
+ sprintf (modeline + strlen (modeline), "-----Info: ");
+
+ /* Omit any extension like ".info.gz" from file name. */
+ dot = strcspn (name, ".");
+
+ if (name && strcmp ("", name))
+ {
+ sprintf (modeline + strlen (modeline), "(");
+ strncpy (modeline + strlen (modeline), name, dot);
+ sprintf (modeline + strlen (modeline), ")");
+ }
+ sprintf (modeline + strlen (modeline),
+ "%s, %ld lines --%s",
+ nodename, window->line_count, location_indicator);
+
+ 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->flags |= W_UpdateWindow;
+ window_make_modeline (window);
+}
+
+
+/* A place to buffer echo area messages. */
+static NODE *echo_area_node = NULL;
+
+/* Make the node of the_echo_area be an empty one. */
+void
+free_echo_area (void)
+{
+ if (echo_area_node)
+ {
+ 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);
+}
+
+void
+vwindow_message_in_echo_area (const char *format, va_list ap)
+{
+ free_echo_area ();
+ echo_area_node = build_message_node (format, ap);
+ window_set_node_of_window (the_echo_area, echo_area_node);
+ 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, ...)
+{
+ va_list ap;
+
+ va_start (ap, format);
+ vwindow_message_in_echo_area (format, ap);
+ va_end (ap);
+}
+
+/* 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 size_t old_echo_area_nodes_index = 0;
+static size_t old_echo_area_nodes_slots = 0;
+
+void
+message_in_echo_area (const char *format, ...)
+{
+ va_list ap;
+
+ 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);
+ }
+ echo_area_node = NULL;
+ va_start (ap, format);
+ vwindow_message_in_echo_area (format, ap);
+ va_end (ap);
+}
+
+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);
+}
+
+
+/* Build a new node which has FORMAT printed with ARG1 and ARG2 as the
+ contents. */
+NODE *
+build_message_node (const char *format, va_list ap)
+{
+ struct text_buffer msg;
+
+ text_buffer_init (&msg);
+ text_buffer_vprintf (&msg, format, ap);
+
+ return text_buffer_to_node (&msg);
+}
+
+NODE *
+format_message_node (const char *format, ...)
+{
+ NODE *node;
+ va_list ap;
+
+ va_start (ap, format);
+ node = build_message_node (format, ap);
+ va_end (ap);
+ return node;
+}
+
+NODE *
+text_buffer_to_node (struct text_buffer *tb)
+{
+ NODE *node;
+
+ node = info_create_node ();
+
+ /* Make sure that this buffer ends with a newline. */
+ text_buffer_add_char (tb, '\n');
+ node->nodelen = text_buffer_off (tb);
+ text_buffer_add_char (tb, '\0');
+
+ node->contents = text_buffer_base (tb);
+ node->flags |= N_IsInternal;
+ return node;
+}
+
+/* Used by calculate_line_starts to record line starts in the
+ win->LINE_COUNT and win->LOG_LINE_NO arrays. */
+static void
+collect_line_starts (WINDOW *win, long ll_num, long pl_start)
+{
+ add_element_to_array (pl_start, win->line_count,
+ win->line_starts, win->line_slots, 2);
+
+ /* We cannot do add_element_to_array for this, as this would lead
+ to incrementing cp->win->line_count twice. */
+ win->log_line_no = xrealloc (win->log_line_no,
+ win->line_slots * sizeof (long));
+ win->log_line_no[win->line_count - 1] = ll_num;
+}
+
+#define NO_NODELINE 0
+#define PRINT_NODELINE 1
+#define NODELINE_POINTERS_ONLY 2
+int nodeline_print = 2;
+
+/* Calculate a list of line starts for the node belonging to WINDOW. The
+ line starts are offsets within WINDOW->node->contents.
+
+ Note that this function must agree with what display_update_one_window
+ in display.c does. */
+void
+calculate_line_starts (WINDOW *win)
+{
+ long pl_chars = 0; /* Number of characters in line so far. */
+ long pl_start; /* Offset of start of current physical line. */
+ long ll_num = 0; /* Number of logical lines */
+ mbi_iterator_t iter;
+
+ /* Width of character carried over from one physical line to the next. */
+ size_t carried_over_chars = 0;
+
+ win->line_starts = NULL;
+ win->log_line_no = NULL;
+ win->line_count = 0;
+ win->line_slots = 0;
+
+ if (!win->node)
+ return;
+
+ pl_start = 0;
+ if (nodeline_print != PRINT_NODELINE
+ && !memcmp (win->node->contents, "File:", strlen ("File:")))
+ {
+ char *s = strchr (win->node->contents, '\n');
+ if (s && nodeline_print == NO_NODELINE)
+ {
+ pl_start = s - win->node->contents + 1;
+ }
+ else if (s && nodeline_print == NODELINE_POINTERS_ONLY)
+ {
+ char *s2;
+ char saved = *s;
+ *s = '\0';
+ s2 = strstr (win->node->contents, "Next: ");
+ if (!s2)
+ s2 = strstr (win->node->contents, "Prev: ");
+ if (!s2)
+ s2 = strstr (win->node->contents, "Up: ");
+ if (s2)
+ pl_start = s2 - win->node->contents;
+ *s = saved;
+ }
+ }
+
+ for (mbi_init (iter,
+ win->node->contents + pl_start,
+ win->node->nodelen - pl_start);
+ mbi_avail (iter);
+ mbi_advance (iter))
+ {
+ size_t pchars = 0; /* Screen columns for this character. */
+ size_t pbytes = 0; /* Not used. */
+ int delim = 0;
+
+ /* Set pchars. */
+ (void) printed_representation (&iter, &delim, pl_chars,
+ &pchars, &pbytes);
+
+ /* If this character can be printed without passing the width of
+ the line, then include it in the line. */
+ if (!delim && pl_chars + pchars < win->width)
+ {
+ pl_chars += pchars;
+ continue;
+ }
+
+ /* If this character cannot be printed in this line, we have
+ found the end of this line as it would appear on the screen. */
+
+ carried_over_chars = delim ? 0 : pchars;
+
+ collect_line_starts (win, ll_num, pl_start);
+
+ if (delim == '\r' || delim == '\n')
+ ++ll_num;
+
+ /* Start a new physical line at next character, unless a character
+ was carried over, in which case start there. */
+ pl_start = mbi_cur_ptr (iter) - win->node->contents;
+ if (carried_over_chars == 0)
+ pl_start += mb_len (mbi_cur (iter));
+ pl_chars = 0;
+
+ /* If there is a character carried over, count it now. Expected to be
+ "short", i.e. a representation like "^A". */
+ if (carried_over_chars != 0)
+ {
+ pl_chars = carried_over_chars;
+
+ /* If this window has chosen not to wrap lines, skip to the end
+ of the logical line in the buffer, and start a new line here. */
+ if (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_chars = 0;
+ pl_start = mbi_cur_ptr (iter) + mb_len (mbi_cur (iter))
+ - win->node->contents;
+ }
+ }
+ }
+
+ if (pl_chars)
+ collect_line_starts (win, ll_num++, pl_start);
+
+ /* Have one line start at the end of the node. */
+ collect_line_starts (win, ll_num, mbi_cur_ptr (iter) - win->node->contents);
+ win->line_count--;
+
+ /* Finally, initialize the line map for the current line. */
+ window_line_map_init (win);
+}
+
+
+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;
+}
+
+/* Compute the line map for the current line in WIN. */
+void
+window_compute_line_map (WINDOW *win)
+{
+ int line = window_line_of_point (win);
+ mbi_iterator_t iter;
+ int delim = 0;
+ char *endp;
+ const char *cur_ptr;
+
+ 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)
+ return;
+
+ if (line + 1 < win->line_count)
+ endp = win->node->contents + win->line_starts[line + 1];
+ else
+ endp = win->node->contents + win->node->nodelen;
+
+ for (mbi_init (iter,
+ win->node->contents + win->line_starts[line],
+ win->node->nodelen - win->line_starts[line]);
+ !delim && mbi_avail (iter);
+ mbi_advance (iter))
+ {
+ size_t pchars, pbytes;
+ cur_ptr = mbi_cur_ptr (iter);
+
+ if (cur_ptr >= endp)
+ break;
+
+ /* Set pchars */
+ (void) printed_representation (&iter, &delim, win->line_map.used,
+ &pchars, &pbytes);
+
+ while (pchars--)
+ line_map_add (&win->line_map, cur_ptr - win->node->contents);
+ }
+}
+
+/* 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. If the character at POINT spans
+ multiple columns (e.g. a tab), return the leftmost column it occupies. */
+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];
+ return i;
+}
+
diff --git a/macros/texinfo/texinfo/info/window.h b/macros/texinfo/texinfo/info/window.h
new file mode 100644
index 0000000000..9aa73fa402
--- /dev/null
+++ b/macros/texinfo/texinfo/info/window.h
@@ -0,0 +1,243 @@
+/* window.h -- Structure and flags used in manipulating Info windows.
+
+ Copyright 1993-2019 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. */
+
+#ifndef INFO_WINDOW_H
+#define INFO_WINDOW_H
+
+#include "doc.h"
+#include "nodes.h"
+#include <regex.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. */ \
+ long pagetop; /* LINE_STARTS[PAGETOP] is first line in WINDOW. */ \
+ long point /* Offset within NODE of the cursor position. */
+
+typedef struct {
+ WINDOW_STATE_DECL; /* What gets saved. */
+} WINDOW_STATE;
+
+typedef struct match_struct
+{
+ regmatch_t *matches; /* Array of matches */
+ size_t match_count;
+ size_t match_alloc;
+ int finished; /* Non-zero if all possible matches are stored. */
+ regex_t regex;
+ char *buffer;
+ size_t buflen;
+} MATCH_STATE;
+
+/* 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. */
+ long width; /* Width of this window. */
+ long height; /* Height of this window. */
+ long first_row; /* Offset of the first line in the_screen. */
+ long goal_column; /* Column to place the cursor in when moving it up and
+ down. -1 means the column it is currently in. */
+ WINDOW_STATE_DECL; /* Node, pagetop and point. */
+ LINE_MAP line_map; /* Current line map */
+ char *modeline; /* Calculated text of the modeline for this window. */
+ long *line_starts; /* Offsets of printed line starts in node->contents.*/
+ long *log_line_no; /* Number of logical line corresponding to each
+ physical one. */
+ long line_count; /* Number of printed lines in node. */
+ size_t line_slots; /* Allocated space in LINE_STARTS and LOG_LINE_NO. */
+
+ int flags; /* See below for details. */
+
+ /* Used for highlighting search matches. */
+ char *search_string;
+ int search_is_case_sensitive;
+ MATCH_STATE matches;
+
+ /* History of nodes visited in this window. */
+ WINDOW_STATE **hist; /* Nodes visited in this window, including current. */
+ size_t hist_index; /* Index where to add the next node. */
+ size_t hist_slots; /* Number of slots allocated to HIST. */
+} WINDOW;
+
+#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. */
+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. */
+void window_initialize_windows (int width, int height);
+
+/* Make a new window by splitting an existing one. If the window could
+ not be made return a null pointer. The active window is not changed .*/
+WINDOW *window_make_window (void);
+
+/* 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. */
+void window_delete_window (WINDOW *window);
+
+/* Set WINDOW to display NODE. */
+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. */
+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. */
+void window_change_window_height (WINDOW *window, int amount);
+
+void set_window_pagetop (WINDOW *window, int desired_top);
+
+/* Adjust the pagetop of WINDOW such that the cursor point will be visible. */
+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
+void window_tile_windows (int style);
+
+/* Toggle the state of line wrapping in WINDOW. This can do a bit of fancy
+ redisplay. */
+void window_toggle_wrap (WINDOW *window);
+
+/* For every window in CHAIN, set the flags member to have FLAG set. */
+void window_mark_chain (WINDOW *chain, int flag);
+
+/* For every window in CHAIN, clear the flags member of FLAG. */
+void window_unmark_chain (WINDOW *chain, int flag);
+
+/* Make WINDOW start displaying at PERCENT percentage of its node. */
+void window_goto_percentage (WINDOW *window, int percent);
+
+/* Build a new node which has AP printed according to FORMAT as the
+ contents. */
+NODE *build_message_node (const char *format, va_list ap);
+
+NODE *format_message_node (const char *format, ...)
+ TEXINFO_PRINTFLIKE(1,2);
+
+struct text_buffer;
+NODE *text_buffer_to_node (struct text_buffer *tb);
+
+/* Make a message appear in the echo area, built from arguments formatted
+ according to FORMAT.
+
+ 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, ...)
+ TEXINFO_PRINTFLIKE(1,2);
+
+void vwindow_message_in_echo_area (const char *format, va_list ap);
+
+void free_echo_area (void);
+
+/* Place a temporary message in the echo area built from arguments
+ formatted as per FORMAT.
+
+ The message appears immediately, but does not destroy
+ any existing message. A future call to unmessage_in_echo_area ()
+ restores the old contents. */
+void message_in_echo_area (const char *format, ...)
+ TEXINFO_PRINTFLIKE(1,2);
+
+void unmessage_in_echo_area (void);
+
+/* Clear the echo area, removing any message that is already present.
+ The echo area is cleared immediately. */
+void window_clear_echo_area (void);
+
+/* Return the index of the line containing point. */
+int window_line_of_point (WINDOW *window);
+
+/* Get and return the printed column offset of the cursor in this window. */
+int window_get_cursor_column (WINDOW *window);
+
+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_log_to_phys_line (WINDOW *window, long ln);
+
+void calculate_line_starts (WINDOW *window);
+
+
+#endif /* not INFO_WINDOW_H */