code:
#!/bin/sh
# Copyright (C) 2014 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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
. $t/Init-inter.inc

run_ginfo -f intera
# Search without specifying a search string, and check we don't crash.
printf 's\rq' >$PTY_TYPE

. $t/Timeout-test.inc
cleanup


code:
#!/bin/sh
# Copyright (C) 2014 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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
. $t/Init-inter.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

. $t/Timeout-test.inc

# Check we went back to the right node.
grep '^File: search\.info, Node: Top' $GINFO_OUTPUT
RETVAL=$?

cleanup

repo_name: mthl/texinfo

license: mit


code:
#!/bin/sh
# Copyright (C) 2016 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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
. $t/Init-inter.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

. $t/Timeout-test.inc

cleanup


code:
#!/bin/sh
# Copyright (C) 2014 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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,'


code:
#!/bin/sh
# Copyright (C) 2014 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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.'


code:
#!/bin/sh
# Copyright (C) 2014 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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,'


code:
#!/bin/sh
# Copyright (C) 2014, 2015 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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
. $t/Init-inter.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
. $t/Timeout-test.inc

if test ! -f $GINFO_OUTPUT; then
  RETVAL=1
else
  grep 'Node: Top' $GINFO_OUTPUT
  RETVAL=$?
fi

cleanup


code:
#!/bin/sh
# Copyright (C) 2014, 2015 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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
. $t/Init-inter.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
. $t/Timeout-test.inc

cleanup


code:
#!/bin/sh
# Copyright (C) 2014, 2015 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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
. $t/Init-inter.inc

# Follow an index entry in a split file

run_ginfo -f split
printf 'ientry text\r\rDq' >$PTY_TYPE
. $t/Timeout-test.inc

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


code:
#!/bin/sh
# Copyright (C) 2014 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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,'


code:
#!/bin/sh
# Copyright (C) 2014 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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
. $t/Init-inter.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

. $t/Timeout-test.inc

# Return non-zero (test failure) if files differ
diff $GINFO_OUTPUT $t/node-target
RETVAL=$?

cleanup


code:
#!/bin/sh
# Copyright (C) 2014 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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
. $t/Init-inter.inc

run_ginfo -f intera -n 'Argument to tab'
# C-u 2 TAB to go to second link
printf '\0252\t\rDq' >$PTY_TYPE
. $t/Timeout-test.inc

diff $GINFO_OUTPUT $t/node-target
RETVAL=$?

cleanup


code:
#!/bin/sh
# Copyright (C) 2014, 2015 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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
. $t/Init-inter.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
. $t/Timeout-test.inc

cleanup


code:
#!/bin/sh
# Copyright (C) 2014, 2015 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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
. $t/Init-inter.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
. $t/Timeout-test.inc

grep 'Node: Top' $GINFO_OUTPUT
RETVAL=$?

cleanup


code:
#!/bin/sh
# Copyright (C) 2014, 2015 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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
. $t/Init-inter.inc

run_ginfo -f intera
# Tab to first link and follow it
printf '\t\rDq' >$PTY_TYPE
. $t/Timeout-test.inc

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


code:
#!/bin/sh
# Copyright (C) 2014 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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$'


code:
#!/bin/sh
# Copyright (C) 2014 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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
. $t/Init-inter.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
. $t/Timeout-test.inc

grep '^File: dir' $GINFO_OUTPUT
RETVAL=$?

cleanup


code:
#!/bin/sh
# Copyright (C) 2015 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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
. $t/Init-inter.inc

run_ginfo -f intera -n 'Xref target crosses lines'
printf '\t\rDq' >$PTY_TYPE
. $t/Timeout-test.inc

diff $GINFO_OUTPUT $t/node-target
RETVAL=$?

cleanup


code:
#!/bin/sh
# Copyright (C) 2015 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute 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
. $t/Init-inter.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

. $t/Timeout-test.inc

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


code:
/* tag.c -- Functions to handle Info tags (that is, the special
   construct for images, not the "tag table" of starting position.)
   $Id$

   Copyright 2012, 2013, 2014 Free Software Foundation, Inc.

   This program is free software: you can redistribute 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;
}


code:
/* tag.c -- Functions to handle Info tags.
   $Id$

   Copyright 2012, 2013, 2014 Free Software Foundation, Inc.

   This program is free software: you can redistribute 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


code:
/* termdep.h -- system things that terminal.c depends on.
   $Id$

   Copyright 1993, 1996, 1997, 1998, 2001, 2002, 2007, 2012, 2013, 2014, 2016
   Free Software Foundation, Inc.

   This program is free software: you can redistribute 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 */


code:
/* terminal.c -- how to handle the physical terminal for Info.
   $Id$

   Copyright 1988, 1989, 1990, 1991, 1992, 1993, 1996, 1997, 1998,
   1999, 2001, 2002, 2004, 2007, 2008, 2012, 2013, 2014, 2015
   Free Software Foundation, Inc.

   This program is free software: you can redistribute 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


code:
/* terminal.h -- The external interface to terminal I/O.
   $Id$

   Copyright 1993, 1996, 1997, 2001, 2002, 2004, 2007, 2013, 2014, 2015
   Free Software Foundation, Inc.

   This program is free software: you can redistribute 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 */


code:
/* tilde.c -- tilde expansion code (~/foo := $HOME/foo).
   $Id$

   Copyright 1988, 1989, 1990, 1991, 1992, 1993, 1996, 1998, 1999,
   2002, 2004, 2006, 2007, 2008, 2012, 2013, 2017 Free Software
   Foundation, Inc.

   This program is free software: you can redistribute 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;
}


code:
/* tilde.h: tilde expansion.
   $Id$

   Copyright 1988, 1989, 1990, 1991, 1992, 1993, 2004, 2007, 2013, 2017
   Free Software Foundation, Inc.

   This program is free software: you can redistribute 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 */


code:
/* variables.c -- how to manipulate user visible variables in Info.
   $Id$

   Copyright 1993, 1997, 2001, 2002, 2004, 2007, 2008, 2011, 2013,
   2014, 2015, 2017 Free Software Foundation, Inc.

   This program is free software: you can redistribute 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, renditions. */
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;
}


code:
/* variables.h -- Description of user visible variables in Info.
   $Id$

   Copyright 1993, 1997, 2004, 2007, 2011, 2013, 2014, 2015,
   2016, 2017 Free Software Foundation, Inc.

   This program is free software: you can redistribute 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 */