diff options
author | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
commit | e0c6872cf40896c7be36b11dcc744620f10adf1d (patch) | |
tree | 60335e10d2f4354b0674ec22d7b53f0f8abee672 /macros/texinfo/texinfo/info |
Initial commit
Diffstat (limited to 'macros/texinfo/texinfo/info')
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='[0;31m'; \ + grn='[0;32m'; \ + lgn='[1;32m'; \ + blu='[1;34m'; \ + mgn='[0;35m'; \ + brg='[1m'; \ + std='[m'; \ + 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 Binary files differnew file mode 100644 index 0000000000..b90a696312 --- /dev/null +++ b/macros/texinfo/texinfo/info/t/infodir/index-search.info diff --git a/macros/texinfo/texinfo/info/t/infodir/intera.info b/macros/texinfo/texinfo/info/t/infodir/intera.info Binary files differnew file mode 100644 index 0000000000..71547c6936 --- /dev/null +++ b/macros/texinfo/texinfo/info/t/infodir/intera.info 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 Binary files differnew file mode 100644 index 0000000000..50fcdce8bb --- /dev/null +++ b/macros/texinfo/texinfo/info/t/infodir/split.info-2 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 Binary files differnew file mode 100644 index 0000000000..50fcdce8bb --- /dev/null +++ b/macros/texinfo/texinfo/info/t/infodir/subdir/split-in-subdir.info-2 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 */ |