code:
/* info.c -- Display nodes of Info files in multiple windows.
   $Id$

   Copyright 1993, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
   2004, 2005, 2007, 2008, 2009, 2010, 2011, 2012, 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.  */

#include "info.h"
#include "filesys.h"
#include "info-utils.h"
#include "session.h"
#include "indices.h"
#include "dribble.h"
#include "getopt.h"
#include "man.h"
#include "variables.h"

char *program_name = "info";

/* Non-zero means search all indices for APROPOS_SEARCH_STRING. */
static int apropos_p = 0;

/* Variable containing the string to search for when apropos_p is non-zero. */
static char *apropos_search_string = NULL;

/* Non-zero means search all indices for INDEX_SEARCH_STRING.  Unlike
   apropos, this puts the user at the node, running info. */
static int index_search_p = 0;

/* Non-zero means look for the node which describes the invocation
   and command-line options of the program, and start the info
   session at that node.  */
static int goto_invocation_p = 0;

static char *invocation_program_name = 0;

/* Variable containing the string to search for when index_search_p is
   non-zero. */
static char *index_search_string = NULL;

/* Non-zero means print version info only. */
static int print_version_p = 0;

/* Non-zero means print a short description of the options. */
static int print_help_p = 0;

/* Name of file to start session with.  Default file for --node arguments. */
static char *initial_file = 0;

/* Array of the names of nodes that the user specified with "--node" on the
   command line. */
static char **user_nodenames = NULL;
static size_t user_nodenames_index = 0;
static size_t user_nodenames_slots = 0;

/* References to the nodes to start the session with. */
static REFERENCE **ref_list = NULL;
static size_t ref_slots = 0;
static size_t ref_index = 0;

/* String specifying the first file to load.  This string can only be set
   by the user specifying "--file" on the command line. */
static char *user_filename = NULL;

/* String specifying the name of the file to dump nodes to.  This value is
   filled if the user speficies "--output" on the command line. */
static char *user_output_filename = NULL;

/* Non-zero indicates that when "--output" is specified, all of the menu
   items of the specified nodes (and their subnodes as well) should be
   dumped in the order encountered.  This basically can print a book. */
int dump_subnodes = 0;

/* Non-zero means make default keybindings be loosely modeled on vi(1).  */
int vi_keys_p = 0;

/* Non-zero means don't remove ANSI escape sequences.  */
int raw_escapes_p = 1;

/* Print/visit all matching documents. */
static int all_matches_p = 0;

/* Non-zero means print the absolute location of the file to be loaded.  */
static int print_where_p = 0;

/* Non-zero means don't try to be smart when searching for nodes.  */
int strict_node_location_p = 0;

#if defined(__MSDOS__) || defined(__MINGW32__)
/* Non-zero indicates that screen output should be made 'speech-friendly'.
   Since on MSDOS the usual behavior is to write directly to the video
   memory, speech synthesizer software cannot grab the output.  Therefore,
   we provide a user option which tells us to avoid direct screen output
   and use stdout instead (which loses the color output).  */
int speech_friendly = 0;
#endif

/* Structure describing the options that Info accepts.  We pass this structure
   to getopt_long ().  If you add or otherwise change this structure, you must
   also change the string which follows it. */
#define DRIBBLE_OPTION 2
#define RESTORE_OPTION 3
#define IDXSRCH_OPTION 4
#define INITFLE_OPTION 5
#define VIRTIDX_OPTION 6

static struct option long_options[] = {
  { "all", 0, 0, 'a' },
  { "apropos", 1, 0, 'k' },
  { "debug", 1, 0, 'x' },
  { "directory", 1, 0, 'd' },
  { "dribble", 1, 0, DRIBBLE_OPTION },
  { "file", 1, 0, 'f' },
  { "help", 0, &print_help_p, 1 },
  { "index-search", 1, 0, IDXSRCH_OPTION },
  { "init-file", 1, 0, INITFLE_OPTION },
  { "location", 0, &print_where_p, 1 },
  { "node", 1, 0, 'n' },
  { "output", 1, 0, 'o' },
  { "raw-escapes", 0, &raw_escapes_p, 1 },
  { "no-raw-escapes", 0, &raw_escapes_p, 0 },
  { "show-malformed-multibytes", 0, &show_malformed_multibyte_p, 1 },
  { "no-show-malformed-multibytes", 0, &show_malformed_multibyte_p, 0 },
  { "restore", 1, 0, RESTORE_OPTION },
  { "show-options", 0, 0, 'O' },
  { "strict-node-location", 0, &strict_node_location_p, 1 },
  { "subnodes", 0, &dump_subnodes, 1 },
  { "usage", 0, 0, 'O' },
  { "variable", 1, 0, 'v' },
  { "version", 0, &print_version_p, 1 },
  { "vi-keys", 0, &vi_keys_p, 1 },
  { "where", 0, &print_where_p, 1 },
#if defined(__MSDOS__) || defined(__MINGW32__)
  { "speech-friendly", 0, &speech_friendly, 1 },
#endif
  {NULL, 0, NULL, 0}
};

/* String describing the shorthand versions of the long options found above. */
#if defined(__MSDOS__) || defined(__MINGW32__)
static char *short_options = "ak:d:n:f:ho:ORsv:wbx:";
#else
static char *short_options = "ak:d:n:f:ho:ORv:wsx:";
#endif

/* When non-zero, the Info window system has been initialized. */
int info_windows_initialized_p = 0;

/* Some "forward" declarations. */
static void info_short_help (void);
static void init_messages (void);

/* Find the first file to load (and possibly the first node as well).
   If the --file option is given, use that as the file, otherwise try to
   interpret the first non-option argument, either by looking it up as a dir
   entry, looking for a file by that name, or finding a man page by that name.
   Set INITIAL_FILE to the name of the initial Info file. */
static void
get_initial_file (int *argc, char ***argv, char **error)
{
  REFERENCE *entry;

  /* User used "--file". */
  if (user_filename)
    {
      if (!IS_ABSOLUTE(user_filename) && HAS_SLASH(user_filename)
          && !(user_filename[0] == '.' && IS_SLASH(user_filename[1])))
        {
          /* Prefix "./" to the filename to prevent a lookup
             in INFOPATH.  */
          char *s;
          asprintf (&s, "%s%s", "./", user_filename);
          free (user_filename);
          user_filename = s;
        }
      if (IS_ABSOLUTE(user_filename) || HAS_SLASH(user_filename))
        initial_file = info_add_extension (0, user_filename, 0);
      else
        initial_file = info_find_fullpath (user_filename, 0);

      if (!initial_file)
        {
          if (!filesys_error_number)
            filesys_error_number = ENOENT;
          *error = filesys_error_string (user_filename, filesys_error_number);
        }

      return;
    }

  if (!(*argv)[0])
    {
      /* No more non-option arguments. */
      initial_file = xstrdup("dir");
      return;
    }

  /* If first argument begins with '(', add it as if it were given with
     '--node'.  This is to support invoking like
     "info '(emacs)Buffers'".  If it is a well-formed node spec then
     the rest of the arguments are menu entries to follow, or an
     index entry.  */
  if ((*argv)[0][0] == '(')
    {
      info_parse_node ((*argv)[0]);
      if (info_parsed_filename)
        {
          initial_file = info_find_fullpath (info_parsed_filename, 0);
          if (initial_file)
            {
              add_pointer_to_array (info_new_reference (initial_file,
                                                        info_parsed_nodename),
                                    ref_index, ref_list, ref_slots, 2);
              /* Remove this argument from the argument list. */
              memmove (*argv, *argv + 1, *argc-- * sizeof (char *));
              return;
            }
        }
    }

  /* If there are any more arguments, the initial file is the
     dir entry given by the first one. */
    {
      /* If they say info info (or info -O info, etc.), show them
         info-stnd.texi.  (Get info.texi with info -f info.) */
      if ((*argv)[0] && mbscasecmp ((*argv)[0], "info") == 0)
        (*argv)[0] = "info-stnd";

      entry = lookup_dir_entry ((*argv)[0], 0);
      if (entry)
        {
          initial_file = info_find_fullpath (entry->filename, 0);
          if (initial_file)
            {
              REFERENCE *copy;
              (*argv)++; /* Advance past first remaining argument. */
              (*argc)--;

              copy = info_copy_reference (entry);
              /* Store full path, so that we find the already loaded file in
                 info_find_file, and show the full path if --where is used. */
              free (copy->filename);
              copy->filename = xstrdup (initial_file);
              add_pointer_to_array (copy, ref_index, ref_list, ref_slots, 2);
              return;
            }
        }
    }

  /* File name lookup. */
    {
      /* Try finding a file with this name, in case
         it exists, but wasn't listed in dir. */
      initial_file = info_find_fullpath ((*argv)[0], 0);
      if (initial_file)
        {
          add_pointer_to_array (info_new_reference ((*argv)[0], "Top"),
                                ref_index, ref_list, ref_slots, 2);
          (*argv)++; /* Advance past first remaining argument. */
          (*argc)--;
          return;
        }
      else
        asprintf (error, _("No menu item '%s' in node '%s'."),
            (*argv)[0], "(dir)Top");
    }

  /* Fall back to loading man page. */
    {
      NODE *man_node;

      debug (3, ("falling back to manpage node"));

      man_node = get_manpage_node ((*argv)[0]);
      if (man_node)
        {
          add_pointer_to_array
            (info_new_reference (MANPAGE_FILE_BUFFER_NAME, (*argv)[0]),
             ref_index, ref_list, ref_slots, 2);

          initial_file = MANPAGE_FILE_BUFFER_NAME;
          return;
        }
    }

  /* Inexact dir lookup. */
    {
      entry = lookup_dir_entry ((*argv)[0], 1);
      if (entry)
        {
          initial_file = info_find_fullpath (entry->filename, 0);
          if (initial_file)
            {
              REFERENCE *copy;
              (*argv)++; /* Advance past first remaining argument. */
              (*argc)--;
              /* Clear error message. */
              free (*error);
              *error = 0;

              copy = info_copy_reference (entry);
              /* Store full path, so that we find the already loaded file in
                 info_find_file, and show the full path if --where is used. */
              free (copy->filename);
              copy->filename = initial_file;
              add_pointer_to_array (copy, ref_index, ref_list, ref_slots, 2);
              return;
            }
        }
    }

  return;
}

/* Expand list of nodes to be loaded. */
static void
add_initial_nodes (int argc, char **argv, char **error)
{
  /* Add nodes specified with --node. */
  if (user_nodenames)
    {
      int i;

      /* If any --node arguments were given, the node in ref_list[0] is only
         used to set initial_file. */
      if (user_nodenames_index > 0 && ref_index > 0)
        {
          info_reference_free (ref_list[0]);
          ref_list[0] = 0;
          ref_index = 0;
        }

      for (i = 0; user_nodenames[i]; i++)
        {
          char *node_filename = 0;
          char *node_nodename = 0;

          /* Parse node spec to support invoking
             like info --node "(emacs)Buffers". */
          info_parse_node (user_nodenames[i]);
          if (info_parsed_filename)
            {
              node_filename = info_parsed_filename;
              node_nodename = info_parsed_nodename;
            }
          else
            {
              FILE_BUFFER *file_buffer;
              TAG *tag;
              int j;

              if (!initial_file)
                continue; /* Shouldn't happen. */

              /* Check for a node by this name, and if there isn't one
                 look for an inexact match. */

              node_filename = initial_file;
              node_nodename = 0;

              file_buffer = info_find_file (node_filename);

              /* First look for an exact match. */
              for (j = 0; (tag = file_buffer->tags[j]); j++)
                if (strcmp (user_nodenames[i], tag->nodename) == 0)
                  {
                    node_nodename = tag->nodename;
                    break;
                  }

              if (!node_nodename)
                {
                  int best_guess = -1;
                  int len = strlen (user_nodenames[i]);
                  for (j = 0; (tag = file_buffer->tags[j]); j++)
                    {
                      if (mbscasecmp (user_nodenames[i], tag->nodename) == 0)
                        {
                          /* Exact, case-insensitive match. */
                          node_nodename = tag->nodename;
                          best_guess = -1;
                          break;
                        }
                      else if (best_guess == -1
                               && (mbsncasecmp (user_nodenames[i],
                                                tag->nodename, len) == 0))
                        /* Case-insensitive initial substring. */
                        best_guess = j;
                    }
                  if (best_guess != -1)
                    {
                      node_nodename = file_buffer->tags[best_guess]->nodename;
                    }
                }

              if (!node_nodename)
                {
                  free (*error);
                  asprintf (error, _("Cannot find node '%s'."),
                            user_nodenames[i]);
                  continue;
                }
            }

          if (node_filename && node_nodename)
            add_pointer_to_array
              (info_new_reference (node_filename, node_nodename),
               ref_index, ref_list, ref_slots, 2);
        }
    }

  if (goto_invocation_p)
    {
      NODE *top_node = 0;
      REFERENCE *invoc_ref = 0;

      char *program;

      if (ref_index == 0)
        {
          info_error (_("No program name given."));
          exit (1);
        }

      if (invocation_program_name)
        program = xstrdup (invocation_program_name);
      else if (ref_list[0] && ref_list[0]->filename)
        /* If there's no command-line arguments to
           supply the program name, use the Info file
           name (sans extension and leading directories)
           instead.  */
        program = program_name_from_file_name (ref_list[0]->filename);
      else
        program = xstrdup ("");

      if (ref_index > 0)
        top_node = info_get_node (ref_list[0]->filename,
                                  ref_list[0]->nodename);
      if (top_node)
        invoc_ref = info_intuit_options_node (top_node, program);
      if (invoc_ref)
        {
          info_reference_free (ref_list[0]);
          ref_index = 0;

          add_pointer_to_array (invoc_ref, ref_index, ref_list, ref_slots, 2);
        }
      free (program);
    }

  /* Default is the "Top" node if there were no other nodes. */
  if (ref_index == 0 && initial_file)
    {
       add_pointer_to_array (info_new_reference (initial_file, "Top"),
                             ref_index, ref_list, ref_slots, 2);
    }

  /* If there are arguments remaining, they are the names of menu items
     in sequential info files starting from the first one loaded. */
  if (*argv && ref_index > 0)
    {
      NODE *initial_node; /* Node to start following menus from. */
      NODE *node_via_menus;

      initial_node = info_get_node_with_defaults (ref_list[0]->filename,
                                                  ref_list[0]->nodename, 0);
      if (!initial_node)
        return;

      node_via_menus = info_follow_menus (initial_node, argv, error, 1);
      if (node_via_menus)
        {
          argv += argc; argc = 0;

          info_reference_free (ref_list[0]);
          ref_list[0] = info_new_reference (node_via_menus->fullpath,
                                            node_via_menus->nodename);
          free_history_node (node_via_menus);
        }

      /* If no nodes found, and there is exactly one argument remaining,
         check for it as an index entry. */
      else if (argc == 1 && argv[0])
        {
          FILE_BUFFER *fb;
          REFERENCE *match;

          debug (3, ("looking in indices"));
          fb = info_find_file (ref_list[0]->filename);
          if (fb)
            {
              match = look_in_indices (fb, argv[0], 0);
              if (match)
                {
                  argv += argc; argc = 0;
                  free (*error); *error = 0;

                  info_reference_free (ref_list[0]);
                  ref_list[0] = info_copy_reference (match);
                }
            }
        }

      /* If there are arguments remaining, follow menus inexactly. */
      if (argc != 0)
        {
          initial_node = info_get_node_with_defaults (ref_list[0]->filename,
                                                      ref_list[0]->nodename,
                                                      0);
          free (*error); *error = 0;
          node_via_menus = info_follow_menus (initial_node, argv, error, 0);
          if (node_via_menus)
            {
              if (argc >= 2 || !*error)
                {
                  argv += argc; argc = 0;

                  info_reference_free (ref_list[0]);
                  ref_list[0] = info_new_reference (node_via_menus->fullpath,
                                                    node_via_menus->nodename);
                }
              free_history_node (node_via_menus);
            }
        }

      /* If still no nodes found, and there is exactly one argument remaining,
         look in indices sloppily. */
      if (argc == 1)
        {
          FILE_BUFFER *fb;
          REFERENCE *nearest;

          debug (3, ("looking in indices sloppily"));
          fb = info_find_file (ref_list[0]->filename);
          if (fb)
            {
              nearest = look_in_indices (fb, argv[0], 1);
              if (nearest)
                {
                  argv += argc; argc = 0;
                  free (*error); *error = 0;

                  info_reference_free (ref_list[0]);
                  ref_list[0] = info_copy_reference (nearest);
                }
            }
        }
    }

  return;
}

static void
info_find_matching_files (char *filename)
{
  int i;
  char *searchdir;

  NODE *man_node;

  /* Check for dir entries first. */
  i = 0;
  for (searchdir = infopath_first (&i); searchdir;
       searchdir = infopath_next (&i))
    {
      REFERENCE *new_ref = dir_entry_of_infodir (filename, searchdir);

      if (new_ref)
        add_pointer_to_array (new_ref, ref_index, ref_list, ref_slots, 2);
    }

  /* Look for files with matching names. */
  i = 0;
  while (1)
    {
      char *p;
      int j;

      p = info_file_find_next_in_path (filename, &i, 0);
      if (!p)
        break;

      /* Add to list only if the file is not in the list already (which would
         happen if there was a dir entry with the label and filename both
         being this file). */
      for (j = 0; j < ref_index; j++)
        {
          if (!strcmp (p, ref_list[j]->filename))
            break;
        }

      if (j == ref_index)
        {
          add_pointer_to_array (info_new_reference (p, 0),
            ref_index, ref_list, ref_slots, 2);
        }
      free (p);
    }

  /* Check for man page. */
  man_node = get_manpage_node (filename);
  if (man_node)
    {
      free (man_node);
      add_pointer_to_array
        (info_new_reference (MANPAGE_FILE_BUFFER_NAME, filename),
         ref_index, ref_list, ref_slots, 2);
    }
}

static void
set_debug_level (const char *arg)
{
  char *p;
  long n = strtol (arg, &p, 10);
  if (*p)
    {
      fprintf (stderr, _("invalid number: %s\n"), arg);
      exit (EXIT_FAILURE);
    }
  if (n < 0 || n > UINT_MAX)
    debug_level = UINT_MAX;
  else
    debug_level = n;
}

static void
add_file_directory_to_path (char *filename)
{
  char *directory_name = xstrdup (filename);
  char *temp = filename_non_directory (directory_name);

  if (temp != directory_name)
    {
      if (HAVE_DRIVE (directory_name) && temp == directory_name + 2)
	{
	  /* The directory of "d:foo" is stored as "d:.", to avoid
	     mixing it with "d:/" when a slash is appended.  */
	  *temp = '.';
	  temp += 2;
	}
      temp[-1] = 0;
      infopath_add (directory_name);
    }

  free (directory_name);
}

/* **************************************************************** */
/*                                                                  */
/*                Main Entry Point to the Info Program              */
/*                                                                  */
/* **************************************************************** */

int
main (int argc, char *argv[])
{
  int getopt_long_index;       /* Index returned by getopt_long (). */
  char *init_file = 0;         /* Name of init file specified. */
  char *error = 0;             /* Error message to display in mini-buffer. */

#ifdef HAVE_SETLOCALE
  /* Set locale via LC_ALL.  */
  setlocale (LC_ALL, "");
#endif /* HAVE_SETLOCALE */

#ifdef ENABLE_NLS
  /* Set the text message domain.  */
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);
#endif

  init_messages ();
  while (1)
    {
      int option_character;

      option_character = getopt_long (argc, argv, short_options, long_options,
				      &getopt_long_index);

      /* getopt_long returns EOF when there are no more long options. */
      if (option_character == EOF)
        break;

      /* If this is a long option, then get the short version of it. */
      if (option_character == 0 && long_options[getopt_long_index].flag == 0)
        option_character = long_options[getopt_long_index].val;

      /* Case on the option that we have received. */
      switch (option_character)
        {
        case 0:
          break;

	case 'a':
	  all_matches_p = 1;
	  break;

          /* User wants to add a directory. */
        case 'd':
          infopath_add (optarg);
          break;

          /* User is specifying a particular node. */
        case 'n':
          add_pointer_to_array (optarg, user_nodenames_index, user_nodenames,
                                user_nodenames_slots, 10);
          break;

          /* User is specifying a particular Info file. */
        case 'f':
          if (user_filename)
            free (user_filename);

          user_filename = xstrdup (optarg);
          break;

          /* Treat -h like --help. */
        case 'h':
          print_help_p = 1;
          break;

          /* User is specifying the name of a file to output to. */
        case 'o':
          if (user_output_filename)
            free (user_output_filename);
          user_output_filename = xstrdup (optarg);
          break;

         /* User has specified that she wants to find the "Options"
             or "Invocation" node for the program.  */
        case 'O':
          goto_invocation_p = 1;
          break;

	  /* User has specified that she wants the escape sequences
	     in man pages to be passed thru unaltered.  */
        case 'R':
          raw_escapes_p = 1;
          break;

          /* User is specifying that she wishes to dump the subnodes of
             the node that she is dumping. */
        case 's':
          dump_subnodes = 1;
          break;

          /* For compatibility with man, -w is --where.  */
        case 'w':
          print_where_p = 1;
          break;

#if defined(__MSDOS__) || defined(__MINGW32__)
	  /* User wants speech-friendly output.  */
	case 'b':
	  speech_friendly = 1;
	  break;
#endif /* __MSDOS__ || __MINGW32__ */

          /* User has specified a string to search all indices for. */
        case 'k':
          apropos_p = 1;
          free (apropos_search_string);
          apropos_search_string = xstrdup (optarg);
          break;

          /* User has specified a dribble file to receive keystrokes. */
        case DRIBBLE_OPTION:
          close_dribble_file ();
          open_dribble_file (optarg);
          break;

          /* User has specified an alternate input stream. */
        case RESTORE_OPTION:
          info_set_input_from_file (optarg);
          break;

          /* User has specified a string to search all indices for. */
        case IDXSRCH_OPTION:
          index_search_p = 1;
          free (index_search_string);
          index_search_string = xstrdup (optarg);
          break;

          /* User has specified a file to use as the init file. */
        case INITFLE_OPTION:
          init_file = optarg;
          break;

	case 'v':
	  {
            VARIABLE_ALIST *var;
	    char *p;
	    p = strchr (optarg, '=');
	    if (!p)
	      {
		info_error (_("malformed variable assignment: %s"), optarg);
		exit (EXIT_FAILURE);
	      }
	    *p++ = 0;

            if (!(var = variable_by_name (optarg)))
              {
                info_error (_("%s: no such variable"), optarg);
                exit (EXIT_FAILURE);
              }

	    if (!set_variable_to_value (var, p, SET_ON_COMMAND_LINE))
	      {
                info_error (_("value %s is not valid for variable %s"),
                            p, optarg);
		exit (EXIT_FAILURE);
	      }
	  }
	  break;

	case 'x':
	  set_debug_level (optarg);
	  break;

        default:
          fprintf (stderr, _("Try --help for more information.\n"));
          exit (EXIT_FAILURE);
        }
    }

  /* If the output device is not a terminal, and no output filename has been
     specified, make user_output_filename be "-", so that the info is written
     to stdout, and turn on the dumping of subnodes. */
  if ((!isatty (fileno (stdout))) && (user_output_filename == NULL))
    {
      user_output_filename = xstrdup ("-");
      dump_subnodes = 1;
    }

  /* If the user specified --version, then show the version and exit. */
  if (print_version_p)
    {
      printf ("info (GNU %s) %s\n", PACKAGE, VERSION);
      puts ("");
      printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
This is free software: you are free to change and redistribute it.\n\
There is NO WARRANTY, to the extent permitted by law.\n"),
	      "2017");
      exit (EXIT_SUCCESS);
    }

  /* If the `--help' option was present, show the help and exit. */
  if (print_help_p)
    {
      info_short_help ();
      exit (EXIT_SUCCESS);
    }

  argc -= optind;
  argv += optind;

  /* If --file was not used and there is a slash in the first non-option
     argument (e.g. "info subdir/file.info"), do not search the dir files
     for a matching entry. */
  if (!user_filename && argv[0] && HAS_SLASH (argv[0]))
    {
      user_filename = xstrdup (argv[0]);
      argv++; /* Advance past first remaining argument. */
      argc--;
    }

  /* If the user specified a particular filename, add the path of that
     file to the contents of INFOPATH. */
  if (user_filename)
    add_file_directory_to_path (user_filename);

  /* Load custom key mappings and variable settings */
  initialize_terminal_and_keymaps (init_file);

  /* Add extra search directories to any already specified with
     --directory. */
  infopath_init ();

  /* If the user wants to search every known index for a given string,
     do that now, and report the results. */
  if (apropos_p)
    {
      REFERENCE **apropos_list;

      apropos_list = apropos_in_all_indices (apropos_search_string, 0);

      if (!apropos_list)
        info_error (_(APROPOS_NONE), apropos_search_string);
      else
        {
          register int i;
          REFERENCE *entry;

          for (i = 0; (entry = apropos_list[i]); i++)
            fprintf (stdout, "\"(%s)%s\" -- %s\n",
                entry->filename, entry->nodename, entry->label);
        }
      exit (0);
    }

  /* Initialize empty list of nodes to load. */
  add_pointer_to_array (0, ref_index, ref_list, ref_slots, 2);
  ref_index--;

  if (all_matches_p && !index_search_p)
    {
      /* --all */
      if (!user_filename && argv[0])
        {
          user_filename = xstrdup (argv[0]);
          argv++; argc--;
        }
      else if (!user_filename)
        {
          exit (1);
        }
      info_find_matching_files (user_filename);
      /* If only one match, don't start in a menu of matches. */
      if (ref_index == 1)
        all_matches_p = 0;

      /* --where */
      if (print_where_p)
        {
          int i;
          if (!ref_list)
            exit (1);

          for (i = 0; ref_list[i]; i++)
            printf ("%s\n", ref_list[i]->filename);
          exit (0);
        }
    }
  else
    {
      if (goto_invocation_p)
        {
          /* If they said "info --show-options foo bar baz",
             the last of the arguments is the program whose
             options they want to see.  */
          char **p = argv;
          if (*p)
            {
              while (p[1])
                p++;
              invocation_program_name = *p;
            }
        }

      get_initial_file (&argc, &argv, &error);

      /* If the user specified `--index-search=STRING --all', create
         and display the menu of results. */
      if (index_search_p && all_matches_p && initial_file)
        {
          FILE_BUFFER *initial_fb;
          initial_fb = info_find_file (initial_file);
          if (initial_fb)
            {
              NODE *node = create_virtual_index (initial_fb,
                                                 index_search_string);
              if (node)
                {
                  if (user_output_filename)
                    {
                      FILE *output_stream = 0;
                      if (strcmp (user_output_filename, "-") == 0)
                        output_stream = stdout;
                      else
                        output_stream = fopen (user_output_filename, "w");
                      if (output_stream)
                        {
                          write_node_to_stream (node, output_stream);
                        }
                      exit (0);
                    }
                  else
                    {
                      initialize_info_session ();
                      info_set_node_of_window (active_window, node);
                      info_read_and_dispatch ();
                      close_info_session ();
                      exit (0);
                    }
                }
            }
        }

      /* If the user specified `--index-search=STRING',
         start the info session in the node corresponding
         to what they want. */
      else if (index_search_p && initial_file && !user_output_filename)
        {
          FILE_BUFFER *initial_fb;
          initial_fb = info_find_file (initial_file);
          if (initial_fb)
            {
              REFERENCE *result;
              int i, match_offset;

              result = next_index_match (initial_fb, index_search_string, 0, 1,
                                         &i, &match_offset);

              if (result)
                {
                  initialize_info_session ();
                  report_index_match (i, match_offset);
                  info_select_reference (active_window, result);
                  info_read_and_dispatch ();
                  close_info_session ();
                  exit (0);
                }
            }

          fprintf (stderr, _("no index entries found for '%s'\n"),
                   index_search_string);
          close_dribble_file ();
          exit (1);
        }

      /* Add nodes to start with (unless we fell back to the man page). */
      if (!ref_list[0] || strcmp (ref_list[0]->filename,
                                  MANPAGE_FILE_BUFFER_NAME))
        {
          add_initial_nodes (argc, argv, &error);
        }

      /* --where */
      if (print_where_p)
        {
          if (initial_file)
            printf ("%s\n", initial_file);
          exit (0);
        }

    }

  /* --output */
  if (user_output_filename)
    {
      if (error)
        info_error ("%s", error);

      preprocess_nodes_p = 0;
      dump_nodes_to_file (ref_list, user_output_filename, dump_subnodes);
      exit (0);
    }

  if (ref_index == 0)
    {
      if (error)
        {
          info_error ("%s", error);
          exit (1);
        }
      exit (0);
    }

  info_session (ref_list, all_matches_p ? user_filename : 0, error);
  close_info_session ();
  exit (0);
}

/* Produce a scaled down description of the available options to Info. */
static void
info_short_help (void)
{
  printf (_("\
Usage: %s [OPTION]... [MENU-ITEM...]\n\
\n\
Read documentation in Info format.\n"), program_name);
  puts ("");

  puts (_("\
Options:\n\
  -a, --all                    use all matching manuals.\n\
  -k, --apropos=STRING         look up STRING in all indices of all manuals.\n\
  -d, --directory=DIR          add DIR to INFOPATH.\n\
      --dribble=FILE           remember user keystrokes in FILENAME.\n\
  -f, --file=MANUAL            specify Info manual to visit."));

  puts (_("\
  -h, --help                   display this help and exit.\n\
      --index-search=STRING    go to node pointed by index entry STRING.\n\
  -n, --node=NODENAME          specify nodes in first visited Info file.\n\
  -o, --output=FILE            output selected nodes to FILE."));

  puts (_("\
  -R, --raw-escapes            output \"raw\" ANSI escapes (default).\n\
      --no-raw-escapes         output escapes as literal text.\n\
      --restore=FILE           read initial keystrokes from FILE.\n\
  -O, --show-options, --usage  go to command-line options node."));

#if defined(__MSDOS__) || defined(__MINGW32__)
  puts (_("\
  -b, --speech-friendly        be friendly to speech synthesizers."));
#endif

  puts (_("\
      --strict-node-location   (for debugging) use Info file pointers as-is.\n\
      --subnodes               recursively output menu items.\n\
  -v, --variable VAR=VALUE     assign VALUE to Info variable VAR.\n\
      --vi-keys                use vi-like and less-like key bindings.\n\
      --version                display version information and exit.\n\
  -w, --where, --location      print physical location of Info file.\n\
  -x, --debug=NUMBER           set debugging level (-1 for all).\n"));

  puts (_("\n\
The first non-option argument, if present, is the menu entry to start from;\n\
it is searched for in all 'dir' files along INFOPATH.\n\
If it is not present, info merges all 'dir' files and shows the result.\n\
Any remaining arguments are treated as the names of menu\n\
items relative to the initial node visited."));

  puts (_("\n\
For a summary of key bindings, type H within Info."));

  puts (_("\n\
Examples:\n\
  info                       show top-level dir menu\n\
  info info                  show the general manual for Info readers\n\
  info info-stnd             show the manual specific to this Info program\n\
  info emacs                 start at emacs node from top-level dir\n\
  info emacs buffers         select buffers menu entry in emacs manual\n\
  info emacs -n Files        start at Files node within emacs manual\n\
  info '(emacs)Files'        alternative way to start at Files node\n\
  info --show-options emacs  start at node with emacs' command line options\n\
  info --subnodes -o out.txt emacs  dump entire manual to out.txt\n\
  info -f ./foo.info         show file ./foo.info, not searching dir"));

  puts ("");

  puts (_("\
Email bug reports to bug-texinfo@gnu.org,\n\
general questions and discussion to help-texinfo@gnu.org.\n\
Texinfo home page: http://www.gnu.org/software/texinfo/"));

  exit (EXIT_SUCCESS);
}

/* Initialize strings for gettext.  Because gettext doesn't handle N_ or
   _ within macro definitions, we put shared messages into variables and
   use them that way.  This also has the advantage that there's only one
   copy of the strings.  */

const char *msg_cant_find_node;
const char *msg_cant_file_node;
const char *msg_cant_find_window;
const char *msg_cant_find_point;
const char *msg_cant_kill_last;
const char *msg_no_menu_node;
const char *msg_no_foot_node;
const char *msg_no_xref_node;
const char *msg_no_pointer;
const char *msg_unknown_command;
const char *msg_term_too_dumb;
const char *msg_at_node_bottom;
const char *msg_at_node_top;
const char *msg_one_window;
const char *msg_win_too_small;
const char *msg_cant_make_help;

static void
init_messages (void)
{
  msg_cant_find_node   = _("Cannot find node '%s'.");
  msg_cant_file_node   = _("Cannot find node '(%s)%s'.");
  msg_cant_find_window = _("Cannot find a window!");
  msg_cant_find_point  = _("Point doesn't appear within this window's node!");
  msg_cant_kill_last   = _("Cannot delete the last window.");
  msg_no_menu_node     = _("No menu in this node.");
  msg_no_foot_node     = _("No footnotes in this node.");
  msg_no_xref_node     = _("No cross references in this node.");
  msg_no_pointer       = _("No '%s' pointer for this node.");
  msg_unknown_command  = _("Unknown Info command '%c'; try '?' for help.");
  msg_term_too_dumb    = _("Terminal type '%s' is not smart enough to run Info.");
  msg_at_node_bottom   = _("You are already at the last page of this node.");
  msg_at_node_top      = _("You are already at the first page of this node.");
  msg_one_window       = _("Only one window.");
  msg_win_too_small    = _("Resulting window would be too small.");
  msg_cant_make_help   = _("Not enough room for a help window, please delete a window.");
}


code:
/* info.h -- Header file which includes all of the other headers.
   $Id$

   Copyright 1993, 1997, 1998, 1999, 2001, 2002, 2003, 2004, 2007, 2011,
   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/>.

   Originally written by Brian Fox. */

#ifndef INFO_H
#define INFO_H

/* System dependencies.  */
#include "system.h"

/* Some of our other include files use these.  */
typedef int Function ();
typedef void VFunction ();
typedef char *CFunction ();

#include "string.h"
#include "mbiter.h"
#include "mbchar.h"

extern char *program_name;

#if !defined (whitespace)
#  define whitespace(c) ((c == ' ') || (c == '\t'))
#endif /* !whitespace */

#if !defined (whitespace_or_newline)
#  define whitespace_or_newline(c) (whitespace (c) \
                                    || (c == '\n') || (c == '\r'))
#endif /* !whitespace_or_newline */

/* Add ELT to the list of elements found in ARRAY.  SLOTS is the number
   of slots that have already been allocated.  IDX is the index into the
   array where ELT should be added.  MINSLOTS is the number of slots to
   start the array with in case it is empty. */
#define add_pointer_to_array(elt, idx, array, slots, minslots)	\
  do									\
    {									\
       if (idx + 2 >= slots)						\
	 {								\
	   if (slots == 0)						\
	     slots = minslots;						\
	   array = x2nrealloc (array, &slots, sizeof(array[0]));	\
	 }								\
       array[idx++] = elt;						\
       array[idx] = 0; /* null pointer for pointer types */       	\
    }									\
  while (0)

#define add_element_to_array add_pointer_to_array

/* All commands that can be invoked from within info_session () receive
   arguments in the same way.  This simple define declares the header
   of a function named NAME, with associated documentation DOC.  The
   documentation string is groveled out of the source files by the
   utility program `makedoc', which is also responsible for making
   the documentation/function-pointer maps. */
#define DECLARE_INFO_COMMAND(name, doc) \
void name (WINDOW *window, int count)

/* For handling errors.  If you initialize the window system, you should
   also set info_windows_initialized_p to non-zero.  It is used by the
   info_error () function to determine how to format and output errors. */
extern int info_windows_initialized_p;

/* Non-zero means default keybindings are loosely modeled on vi(1).  */
extern int vi_keys_p;

/* Non-zero means don't remove ANSI escape sequences from man pages.  */
extern int raw_escapes_p;

/* Error message defines. */
extern const char *msg_cant_find_node;
extern const char *msg_cant_file_node;
extern const char *msg_cant_find_window;
extern const char *msg_cant_find_point;
extern const char *msg_cant_kill_last;
extern const char *msg_no_menu_node;
extern const char *msg_no_foot_node;
extern const char *msg_no_xref_node;
extern const char *msg_no_pointer;
extern const char *msg_unknown_command;
extern const char *msg_term_too_dumb;
extern const char *msg_at_node_bottom;
extern const char *msg_at_node_top;
extern const char *msg_one_window;
extern const char *msg_win_too_small;
extern const char *msg_cant_make_help;

/* In infopath.c, but also used in man.c. */

/* Given a string containing units of information separated by colons,
   return the next one pointed to by IDX, or NULL if there are no more.
   Advance IDX to the character after the colon. */
char *extract_colon_unit (char *string, int *idx);

#endif /* !INFO_H */

repo_name: mthl/texinfo

license: mit


code:
/* infodoc.c -- functions which build documentation nodes.
   $Id$

   Copyright 1993, 1997, 1998, 1999, 2001, 2002, 2003, 2004, 2006,
   2007, 2008, 2011, 2013, 2014, 2015, 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. */

#include "info.h"
#include "info-utils.h"
#include "filesys.h"
#include "session.h"
#include "doc.h"
#include "funs.h"

/* The name of the node used in the help window. */
static char *info_help_nodename = "*Info Help*";

/* A node containing printed key bindings and their documentation. */
static NODE *internal_info_help_node = NULL;

/* The (more or less) static text which appears in the internal info
   help node.  The actual key bindings are inserted.  Keep the
   underlines (****, etc.) in the same N_ call as  the text lines they
   refer to, so translations can make the number of *'s or -'s match.  */
static char *info_internal_help_text[] = {
  N_("Basic Info command keys\n"),
  "\n",
  N_("\\%-10[quit-help]  Close this help window.\n"),
  N_("\\%-10[quit]  Quit Info altogether.\n"),
  N_("\\%-10[get-info-help-node]  Invoke the Info tutorial.\n"),
  "\n",
  N_("\\%-10[prev-line]  Move up one line.\n"),
  N_("\\%-10[next-line]  Move down one line.\n"),
  N_("\\%-10[scroll-backward]  Scroll backward one screenful.\n"),
  N_("\\%-10[scroll-forward]  Scroll forward one screenful.\n"),
  N_("\\%-10[beginning-of-node]  Go to the beginning of this node.\n"),
  N_("\\%-10[end-of-node]  Go to the end of this node.\n"),
  "\n",
  N_("\\%-10[move-to-next-xref]  Skip to the next hypertext link.\n"),
  N_("\\%-10[select-reference-this-line]  Follow the hypertext link under the cursor.\n"),
  N_("\\%-10[history-node]  Go back to the last node seen in this window.\n"),
  "\n",
  N_("\\%-10[global-prev-node]  Go to the previous node in the document.\n"),
  N_("\\%-10[global-next-node]  Go to the next node in the document.\n"),
  N_("\\%-10[prev-node]  Go to the previous node on this level.\n"),
  N_("\\%-10[next-node]  Go to the next node on this level.\n"),
  N_("\\%-10[up-node]  Go up one level.\n"),
  N_("\\%-10[top-node]  Go to the top node of this document.\n"),
  N_("\\%-10[dir-node]  Go to the main 'directory' node.\n"),
  "\n",
  N_("1...9       Pick the first...ninth item in this node's menu.\n"),
  N_("\\%-10[last-menu-item]  Pick the last item in this node's menu.\n"),
  N_("\\%-10[menu-item]  Pick a menu item specified by name.\n"),
  N_("\\%-10[xref-item]  Follow a cross reference specified by name.\n"),
  N_("\\%-10[goto-node]  Go to a node specified by name.\n"),
  "\n",
  N_("\\%-10[search]  Search forward for a specified string.\n"),
  N_("\\%-10[search-previous]  Search for previous occurrence.\n"),
  N_("\\%-10[search-next]  Search for next occurrence.\n"),
  N_("\\%-10[index-search]  Search for a specified string in the index, and\n\
              select the node referenced by the first entry found.\n"),
  N_("\\%-10[virtual-index]  Synthesize menu of matching index entries.\n"),
  "\n",
  N_("\\%-10[abort-key]  Cancel the current operation.\n"),
  "\n",
  NULL
};

static char *where_is_internal (Keymap map, InfoCommand *cmd);

static void
dump_map_to_text_buffer (struct text_buffer *tb, int *prefix,
                         int prefix_len, Keymap map)
{
  register int i;
  int *new_prefix = xmalloc ((prefix_len + 2) * sizeof (int));

  memcpy (new_prefix, prefix, prefix_len * sizeof (int));
  new_prefix[prefix_len + 1] = 0;

  for (i = 0; i < KEYMAP_SIZE; i++)
    {
      if (i == 128)
        i = 256;
      if (i == 128 + KEYMAP_META_BASE)
        i = 256 + KEYMAP_META_BASE;

      new_prefix[prefix_len] = i;
      if (map[i].type == ISKMAP)
        {
          dump_map_to_text_buffer (tb, new_prefix, prefix_len + 1,
                                   map[i].value.keymap);
        }
      else if (map[i].value.function)
        {
          long start_of_line = tb->off;
          register int last;
          char *doc, *name;

          /* Hide some key mappings. */
          if (map[i].value.function
              && (map[i].value.function->func == info_do_lowercase_version))
            continue;

          doc = function_documentation (map[i].value.function);
          name = function_name (map[i].value.function);

          if (!*doc)
            continue;

          /* Find out if there is a series of identical functions, as in
             add-digit-to-numeric-arg. */
          for (last = i + 1; last < KEYMAP_SIZE; last++)
            if ((map[last].type != ISFUNC) ||
                (map[last].value.function != map[i].value.function))
              break;

          if (last - 1 != i)
            {
              text_buffer_printf (tb, "%s .. ", pretty_keyseq (new_prefix));
              new_prefix[prefix_len] = last - 1;
              text_buffer_printf (tb, "%s", pretty_keyseq (new_prefix));
              i = last - 1;
            }
          else
            text_buffer_printf (tb, "%s", pretty_keyseq (new_prefix));

          while (tb->off - start_of_line < 8)
            text_buffer_printf (tb, " ");

          /* Print the name of the function, and some padding before the
             documentation string is printed. */
          {
            int length_so_far;
            int desired_doc_start = 40;

            text_buffer_printf (tb, "(%s)", name);
            length_so_far = tb->off - start_of_line;

            if ((desired_doc_start + strlen (doc))
                >= (unsigned int) the_screen->width)
              text_buffer_printf (tb, "\n     ");
            else
              {
                while (length_so_far < desired_doc_start)
                  {
                    text_buffer_printf (tb, " ");
                    length_so_far++;
                  }
              }
          }
          text_buffer_printf (tb, "%s\n", doc);
        }
    }
  free (new_prefix);
}

/* How to create internal_info_help_node.  HELP_IS_ONLY_WINDOW_P says
   whether we're going to end up in a second (or more) window of our
   own, or whether there's only one window and we're going to usurp it.
   This determines how to quit the help window.  Maybe we should just
   make q do the right thing in both cases.  */

static void
create_internal_info_help_node (int help_is_only_window_p)
{
  register int i;
  NODE *node;
  char *exec_keys;

  int printed_one_mx = 0;
  struct text_buffer msg;
  char *infopath_str = infopath_string ();

  text_buffer_init (&msg);

  for (i = 0; info_internal_help_text[i]; i++)
    text_buffer_printf (&msg, replace_in_documentation
                        (_(info_internal_help_text[i]),
                         help_is_only_window_p), NULL, NULL, NULL);

  text_buffer_printf (&msg, "---------------------\n");
  text_buffer_printf (&msg, _("This is GNU Info version %s.  "), VERSION);
  text_buffer_printf (&msg, _("The current search path is:\n"));
  text_buffer_printf (&msg, "%s\n", infopath_str);
  text_buffer_printf (&msg, "---------------------\n\n");
  free (infopath_str);

  text_buffer_printf (&msg, _("Commands available in Info windows:\n\n"));
  dump_map_to_text_buffer (&msg, 0, 0, info_keymap);
  text_buffer_printf (&msg, "---------------------\n\n");
  text_buffer_printf (&msg, _("Commands available in the echo area:\n\n"));
  dump_map_to_text_buffer (&msg, 0, 0, echo_area_keymap);

  /* Get a list of commands which have no keystroke equivs. */
  exec_keys = where_is (info_keymap, InfoCmd(info_execute_command));
  if (exec_keys)
    exec_keys = xstrdup (exec_keys);
  for (i = 0; function_doc_array[i].func; i++)
    {
      InfoCommand *cmd = &function_doc_array[i];

      if (cmd->func != info_do_lowercase_version
          && !where_is_internal (info_keymap, cmd)
          && !where_is_internal (echo_area_keymap, cmd))
        {
          if (!printed_one_mx)
            {
              text_buffer_printf (&msg, "---------------------\n\n");
              if (exec_keys && exec_keys[0])
                text_buffer_printf (&msg,
                    _("The following commands can only be invoked via "
                      "%s:\n\n"),
                    exec_keys);
              else
                text_buffer_printf (&msg,
                   _("The following commands cannot be invoked at all:\n\n"));
              printed_one_mx = 1;
            }

          text_buffer_printf (&msg,
             "%s %s\n     %s\n",
             exec_keys,
             function_doc_array[i].func_name,
             replace_in_documentation (strlen (function_doc_array[i].doc)
               ? _(function_doc_array[i].doc) : "", 0)
            );

        }
    }

  free (exec_keys);

  node = text_buffer_to_node (&msg);

  internal_info_help_node = node;

  name_internal_node (internal_info_help_node, xstrdup (info_help_nodename));
}

/* Return a window which is the window showing help in this Info. */

/* If the eligible window's height is >= this, split it to make the help
   window.  Otherwise display the help window in the current window.  */
#define HELP_SPLIT_SIZE 24

static WINDOW *
info_find_or_create_help_window (void)
{
  int help_is_only_window_p;
  WINDOW *eligible = NULL;
  WINDOW *help_window = get_internal_info_window (info_help_nodename);

  /* Close help window if in it already. */
  if (help_window && help_window == active_window)
    {
      info_delete_window_internal (help_window);
      return NULL;
    }

  /* If we couldn't find the help window, then make it. */
  if (!help_window)
    {
      WINDOW *window;
      int max = 0;

      for (window = windows; window; window = window->next)
        {
          if (window->height > max)
            {
              max = window->height;
              eligible = window;
            }
        }

      if (!eligible)
        {
          info_error ("%s", msg_cant_make_help);
          return NULL;
        }
    }

  /* Make sure that we have a node containing the help text.  The
     argument is false if help will be the only window (so l must be used
     to quit help), true if help will be one of several visible windows
     (so CTRL-x 0 must be used to quit help).  */
  help_is_only_window_p = ((help_window && !windows->next)
        || (!help_window && eligible->height < HELP_SPLIT_SIZE));
  create_internal_info_help_node (help_is_only_window_p);

  /* Either use the existing window to display the help node, or create
     a new window if there was no existing help window. */
  if (!help_window)
    { /* Split the largest window into 2 windows, and show the help text
         in that window. */
      if (eligible->height >= HELP_SPLIT_SIZE)
        {
          active_window = eligible;
          help_window = window_make_window ();
          info_set_node_of_window (help_window, internal_info_help_node);
        }
      else
        {
          info_set_node_of_window (active_window, internal_info_help_node);
          help_window = active_window;
        }
    }
  else
    { /* Case where help node always gets regenerated, and we have an
         existing window in which to place the node. */
      if (active_window != help_window)
        {
          active_window = help_window;
        }
      info_set_node_of_window (active_window, internal_info_help_node);
    }
  return help_window;
}

/* Create or move to the help window. */
DECLARE_INFO_COMMAND (info_get_help_window, _("Display help message"))
{
  WINDOW *help_window;

  help_window = info_find_or_create_help_window ();
  if (help_window)
    {
      active_window = help_window;
    }
}

/* Show the Info help node.  This means that the "info" file is installed
   where it can easily be found on your system. */
DECLARE_INFO_COMMAND (info_get_info_help_node, _("Visit Info node '(info)Help'"))
{
  NODE *node;
  char *nodename;

  /* If there is a window on the screen showing the node "(info)Help" or
     the node "(info)Help-Small-Screen", simply select that window. */
  {
    WINDOW *win;

    for (win = windows; win; win = win->next)
      {
        if (win->node && win->node->fullpath
            && !mbscasecmp ("info",
                            filename_non_directory (win->node->fullpath))
            && (!strcmp (win->node->nodename, "Help")
                || !strcmp (win->node->nodename, "Help-Small-Screen")))
          {
            active_window = win;
            return;
          }
      }
  }

  /* If there is more than one window on the screen, check if the user typed
     "H" for help message before typing "h" for tutorial.  If so, close help
     message so the tutorial will not be in a small window. */
  if (windows->next)
    {
      WINDOW *help_window = get_internal_info_window (info_help_nodename);
      if (help_window && help_window == active_window)
        {
          info_delete_window_internal (help_window);
        }
    }

  /* If the current window is small, show the small screen help. */
  if (active_window->height < 24)
    nodename = "Help-Small-Screen";
  else
    nodename = "Help";

  /* Try to get the info file for Info. */
  node = info_get_node ("info", nodename);

  /* info.info is distributed with Emacs, not Texinfo, so fall back to
     info-stnd.info if it isn't there. */
  if (!node)
    node = info_get_node ("info-stnd", "Top");

  if (!node)
    {
      if (info_recent_file_error)
        info_error ("%s", info_recent_file_error);
      else
        info_error (msg_cant_file_node, "info", nodename);

      return;
    }

  info_set_node_of_window (active_window, node);
}

/* **************************************************************** */
/*                                                                  */
/*                   Groveling Info Keymaps and Docs                */
/*                                                                  */
/* **************************************************************** */

/* Return the documentation associated with the Info command FUNCTION. */
char *
function_documentation (InfoCommand *cmd)
{
  char *doc;

  doc = cmd->doc;

  return replace_in_documentation ((strlen (doc) == 0) ? doc : _(doc), 0);
}

/* Return the user-visible name of the function associated with the
   Info command FUNCTION. */
char *
function_name (InfoCommand *cmd)
{
  return cmd->func_name;
}

/* Return a pointer to the info command for function NAME. */
InfoCommand *
named_function (char *name)
{
  register int i;

  for (i = 0; function_doc_array[i].func; i++)
    if (strcmp (function_doc_array[i].func_name, name) == 0)
      break;

  if (!function_doc_array[i].func)
    return 0;
  else
    return &function_doc_array[i];
}

DECLARE_INFO_COMMAND (describe_key, _("Print documentation for KEY"))
{
  int keys[50];
  int keystroke;
  int *k = keys;
  Keymap map = info_keymap;

  *k = '\0';

  for (;;)
    {
      message_in_echo_area (_("Describe key: %s"), pretty_keyseq (keys));
      keystroke = get_input_key ();
      unmessage_in_echo_area ();

      /* Add the KEYSTROKE to our list. */
      *k++ = keystroke;
      *k = '\0';

      if (map[keystroke].value.function == NULL)
        {
          message_in_echo_area (_("%s is undefined."), pretty_keyseq (keys));
          return;
        }
      else if (map[keystroke].type == ISKMAP)
        {
          map = map[keystroke].value.keymap;
          continue;
        }
      else
        {
          char *keyname, *message, *fundoc, *funname = "";

          /* If the key is bound to do-lowercase-version, but its
             lower-case variant is undefined, say that this key is
             also undefined.  This is especially important for unbound
             edit keys that emit an escape sequence: it's terribly
             confusing to see a message "Home (do-lowercase-version)"
             or some such when Home is unbound.  */
          if (map[keystroke].value.function
              && map[keystroke].value.function->func
                 == info_do_lowercase_version)
            {
              int lowerkey;

              if (keystroke >= KEYMAP_META_BASE)
                {
                  lowerkey = keystroke;
                  lowerkey -= KEYMAP_META_BASE;
                  lowerkey = tolower (lowerkey);
                  lowerkey += KEYMAP_META_BASE;
                }
              else
                lowerkey = tolower (keystroke);

              if (map[lowerkey].value.function == NULL)
                {
                  message_in_echo_area (_("%s is undefined."),
					pretty_keyseq (keys));
                  return;
                }
            }

          keyname = pretty_keyseq (keys);

          funname = function_name (map[keystroke].value.function);

          fundoc = function_documentation (map[keystroke].value.function);

          message = xmalloc
            (10 + strlen (keyname) + strlen (fundoc) + strlen (funname));

          sprintf (message, "%s (%s): %s.", keyname, funname, fundoc);

          window_message_in_echo_area ("%s", message);
          free (message);
          break;
        }
    }
}

/* Return the pretty-printable name of a single key. */
char *
pretty_keyname (int key)
{
  static char rep_buffer[30];
  char *rep;

  if (key >= KEYMAP_META_BASE)
    {
      char temp[20];

      rep = pretty_keyname (key - KEYMAP_META_BASE);

      sprintf (temp, "M-%s", rep);
      strcpy (rep_buffer, temp);
      rep = rep_buffer;
    }
  else if (Control_p (key))
    {
      switch (key)
        {
        case '\n': rep = "LFD"; break;
        case '\t': rep = "TAB"; break;
        case '\r': rep = "RET"; break;
        case ESC:  rep = "ESC"; break;

        default:
          sprintf (rep_buffer, "C-%c", UnControl (key));
          rep = rep_buffer;
        }
    }
  else if (key >= 256)
    switch (key)
      {
      case KEY_RIGHT_ARROW:
        rep = "Right"; break;
      case KEY_LEFT_ARROW:
        rep = "Left"; break;
      case KEY_UP_ARROW:
        rep = "Up"; break;
      case KEY_DOWN_ARROW:
        rep = "Down"; break;
      case KEY_PAGE_UP:
        rep = "PgUp"; break;
      case KEY_PAGE_DOWN:
        rep = "PgDn"; break;
      case KEY_HOME:
        rep = "Home"; break;
      case KEY_END:
        rep = "End"; break;
      case KEY_DELETE:
        rep = "DEL"; break;
      case KEY_INSERT:
        rep = "INS"; break;
      case KEY_BACK_TAB:
        rep = "BackTab"; break;
      case KEY_MOUSE:
        rep = "(mouse event)"; break;
      default:
        rep = "(unknown key)"; break; /* This shouldn't be displayed. */
      }
  else
    {
      switch (key)
        {
        case ' ': rep = "SPC"; break;
        case DEL: rep = "DEL"; break;
        default:
          rep_buffer[0] = key;
          rep_buffer[1] = '\0';
          rep = rep_buffer;
        }
    }
  return rep;
}

/* Return the pretty printable string which represents KEYSEQ.  Return
   value should not be freed by caller. */
char *
pretty_keyseq (int *keyseq)
{
  static struct text_buffer rep = { 0 };

  if (!text_buffer_base (&rep))
    text_buffer_init (&rep);
  else
    text_buffer_reset (&rep);

  if (!*keyseq)
    return "";

  while (1)
    {
      text_buffer_printf (&rep, "%s", pretty_keyname (keyseq[0]));
      keyseq++;

      if (!*keyseq)
        break;

      text_buffer_add_char (&rep, ' ');
    }
  return text_buffer_base (&rep);
}

/* Return a pointer to the last character in s that is found in f. */
static const char *
strrpbrk (const char *s, const char *f)
{
  register const char *e = s + strlen(s);
  register const char *t;

  while (e-- != s)
    {
      for (t = f; *t; t++)
        if (*e == *t)
          return e;
    }
  return NULL;
}

/* Replace the names of functions with the key that invokes them. */
char *
replace_in_documentation (const char *string, int help_is_only_window_p)
{
  unsigned reslen = strlen (string);
  register int i, start, next;
  static char *result = NULL;

  free (result);
  result = xmalloc (1 + reslen);

  next = start = 0;

  /* Skip to the beginning of a replaceable function. */
  for (i = start; string[i]; i++)
    {
      int j = i + 1;

      /* Is this the start of a replaceable function name? */
      if (string[i] == '\\')
        {
          char *fmt = NULL;
          unsigned min = 0;
          unsigned max = 0;

          if(string[j] == '%')
            {
              if (string[++j] == '-')
                j++;
              if (isdigit(string[j]))
                {
                  min = atoi(string + j);
                  while (isdigit(string[j]))
                    j++;
                  if (string[j] == '.' && isdigit(string[j + 1]))
                    {
                      j += 1;
                      max = atoi(string + j);
                      while (isdigit(string[j]))
                        j++;
                    }
                  fmt = xmalloc (j - i + 2);
                  strncpy (fmt, string + i + 1, j - i);
                  fmt[j - i - 1] = 's';
                  fmt[j - i] = '\0';
                }
              else
                j = i + 1;
            }
          if (string[j] == '[')
            {
              unsigned arg = 0;
              char *argstr = NULL;
              char *rep_name, *fun_name, *rep;
              InfoCommand *command;
              char *repstr = NULL;
              unsigned replen;

              /* Copy in the old text. */
              strncpy (result + next, string + start, i - start);
              next += (i - start);
              start = j + 1;

              /* Look for an optional numeric arg. */
              i = start;
              if (isdigit(string[i])
                  || (string[i] == '-' && isdigit(string[i + 1])) )
                {
                  arg = atoi(string + i);
                  if (string[i] == '-')
                    i++;
                  while (isdigit(string[i]))
                    i++;
                }
              start = i;

              /* Move to the end of the function name. */
              for (i = start; string[i] && (string[i] != ']'); i++);

              rep_name = xmalloc (1 + i - start);
              strncpy (rep_name, string + start, i - start);
              rep_name[i - start] = '\0';

            /* If we have only one window (because the window size was too
               small to split it), we have to quit help by going back one
               node in the history list, not deleting the window.  */
              if (strcmp (rep_name, "quit-help") == 0)
                fun_name = help_is_only_window_p ? "history-node"
                                                 : "get-help-window";
              else
                fun_name = rep_name;

              /* Find a key which invokes this function in the info_keymap. */
              command = named_function (fun_name);

              free (rep_name);

              /* If the internal documentation string fails, there is a
                 serious problem with the associated command's documentation.
                 We croak so that it can be fixed immediately. */
              if (!command)
                abort ();

              if (arg)
                {
                  char *argrep;
		  const char *p;

                  argrep = where_is (info_keymap, InfoCmd(info_add_digit_to_numeric_arg));
                  p = argrep ? strrpbrk (argrep, "0123456789-") : NULL;
                  if (p)
                    {
                      argstr = xmalloc (p - argrep + 21);
                      strncpy (argstr, argrep, p - argrep);
                      sprintf (argstr + (p - argrep), "%d", arg);
                    }
                  else
                    command = NULL;
                }
              rep = command ? where_is (info_keymap, command) : NULL;
              if (!rep)
                rep = "N/A";
              replen = (argstr ? strlen (argstr) : 0) + strlen (rep) + 1;
              repstr = xmalloc (replen);
              repstr[0] = '\0';
              if (argstr)
                {
                  strcat(repstr, argstr);
                  strcat(repstr, " ");
                  free (argstr);
                }
              strcat(repstr, rep);

              if (fmt)
                {
                  if (replen > max)
                    replen = max;
                  if (replen < min)
                    replen = min;
                }
              if (next + replen > reslen)
                {
                  reslen = next + replen + 1;
                  result = xrealloc (result, reslen + 1);
                }

              if (fmt)
                  sprintf (result + next, fmt, repstr);
              else
                  strcpy (result + next, repstr);

              next = strlen (result);
              free (repstr);

              start = i;
              if (string[i])
                start++;
            }

          free (fmt);
        }
    }
  strcpy (result + next, string + start);
  return result;
}

/* Return a string of characters which could be typed from the keymap
   MAP to invoke FUNCTION. */
static char *where_is_rep = NULL;
static int where_is_rep_index = 0;
static int where_is_rep_size = 0;

char *
where_is (Keymap map, InfoCommand *cmd)
{
  char *rep;

  if (!where_is_rep_size)
    where_is_rep = xmalloc (where_is_rep_size = 100);
  where_is_rep_index = 0;

  rep = where_is_internal (map, cmd);

  /* If it couldn't be found, return "M-x Foo" (or equivalent). */
  if (!rep)
    {
      char *name;

      name = function_name (cmd);
      if (!name)
        return NULL; /* no such function */

      rep = where_is_internal (map, InfoCmd(info_execute_command));
      if (!rep)
        return ""; /* function exists but can't be got to by user */

      sprintf (where_is_rep, "%s %s", rep, name);

      rep = where_is_rep;
    }
  return rep;
}

/* Return the printed rep of the keystrokes that invoke FUNCTION,
   as found in MAP, or NULL. */
static char *
where_is_internal (Keymap map, InfoCommand *cmd)
{
  register FUNCTION_KEYSEQ *k;

  for (k = cmd->keys; k; k = k->next)
    if (k->map == map)
      return pretty_keyseq (k->keyseq);

  return NULL;
}

DECLARE_INFO_COMMAND (info_where_is,
   _("Show what to type to execute a given command"))
{
  char *command_name;

  command_name = read_function_name (_("Where is command: "), window);

  if (!command_name)
    {
      info_abort_key (active_window, count);
      return;
    }

  if (*command_name)
    {
      InfoCommand *command;

      command = named_function (command_name);

      if (command)
        {
          char *location;

          location = where_is (info_keymap, command);

          if (!location || !location[0])
            {
              info_error (_("'%s' is not on any keys"), command_name);
            }
          else
            {
              if (strstr (location, function_name (command)))
                window_message_in_echo_area
                  (_("%s can only be invoked via %s."),
                   command_name, location);
              else
                window_message_in_echo_area
                  (_("%s can be invoked via %s."),
                   command_name, location);
            }
        }
      else
        info_error (_("There is no function named '%s'"), command_name);
    }

  free (command_name);
}


code:
/* infokey.c -- compile ~/.infokey to ~/.info.
   $Id$

   Copyright 1999, 2001, 2002, 2003, 2004, 2005, 2007, 2008, 2009,
   2010, 2011, 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/>.

   Originally written by Andrew Bettison. */

#include "info.h"
#include "doc.h"
#include "session.h"
#include "funs.h"
#include "getopt.h"
#include "variables.h"

extern char *program_name;  /* in info.c */

enum sect_e
  {
    info = 0,
    ea = 1,
    var = 2
  };

static void syntax_error (const char *filename, unsigned int linenum,
			  const char *fmt, ...) TEXINFO_PRINTFLIKE(3,4);

/* Compilation - the real work.

	Source file syntax
	------------------
	The source file is a line-based text file with the following
	structure:

		# comments
		# more comments

		#info
		u	prev-line
		d	next-line
		^a	invalid		# just beep
		\ku	prev-line
		#stop
		\kd	next-line
		q	quit		# of course!

		#echo-area
		^a	echo-area-beg-of-line
		^e	echo-area-end-of-line
		\kr	echo-area-forward
		\kl	echo-area-backward
		\kh	echo-area-beg-of-line
		\ke	echo-area-end-of-line

		#var
		scroll-step=1
		ISO-Latin=Off

	Lines starting with '#' are comments, and are ignored.  Blank
	lines are ignored.  Each section is introduced by one of the
	following lines:

		#info
		#echo-area
		#var

	The sections may occur in any order.  Each section may be
	omitted completely.  If the 'info' section is the first in the
	file, its '#info' line may be omitted.

	The 'info' and 'echo-area' sections
	-----------------------------------
	Each line in the 'info' or 'echo-area' sections has the
	following syntax:

		key-sequence SPACE action-name [ SPACE [ # comment ] ] \n

	Where SPACE is one or more white space characters excluding
	newline, "action-name" is the name of a GNU Info command,
	"comment" is any sequence of characters excluding newline, and
	"key-sequence" is a concatenation of one or more key definitions
	using the following syntax:

	   1.	A carat ^ followed by one character indicates a single
	   	control character;

	   2.	A backslash \ followed by one, two, or three octal
		digits indicates a single character having that ASCII
		code;

	   3.	\n indicates a single NEWLINE;
		\e indicates a single ESC;
		\r indicates a single CR;
		\t indicates a single TAB;
		\b indicates a single BACKSPACE;

	   4.	\ku indicates the Up Arrow key;
	   	\kd indicates the Down Arrow key;
	   	\kl indicates the Left Arrow key;
	   	\kr indicates the Right Arrow key;
	   	\kP indicates the Page Up (PRIOR) key;
	   	\kN indicates the Page Down (NEXT) key;
	   	\kh indicates the Home key;
	   	\ke indicates the End key;
	   	\kx indicates the DEL key;
		\k followed by any other character indicates a single
		control-K, and the following character is interpreted
		as in rules 1, 2, 3, 5 and 6.

	   5.	\m followed by any sequence defined in rules 1, 2, 3, 4
		or 6 indicates the "Meta" modification of that key.

	   6.	A backslash \ followed by any character not described
	   	above indicates that character itself.  In particular:
		\\ indicates a single backslash \,
		\  (backslash-space) indicates a single space,
		\^ indicates a single caret ^,

	If the following line:

		#stop

	occurs anywhere in an 'info' or 'echo-area' section, that
	indicates to GNU Info to suppress all of its default key
	bindings in that context.

	The 'var' section
	-----------------
	Each line in the 'var' section has the following syntax:

		variable-name = value \n

	Where "variable-name" is the name of a GNU Info variable and
	"value" is the value that GNU Info will assign to that variable
	when commencing execution.  There must be no white space in the
	variable name, nor between the variable name and the '='.  All
	characters immediately following the '=', up to but not
	including the terminating newline, are considered to be the
	value that will be assigned.  In other words, white space
	following the '=' is not ignored.
 */

static int lookup_action (const char *actname);

/* Read the init file.  Return true if no error was encountered.  Set
   SUPPRESS_INFO or SUPPRESS_EA to true if the init file specified to ignore
   default key bindings. */
int
compile (FILE *fp, const char *filename, int *suppress_info, int *suppress_ea)
{
  int error = 0; /* Set if there was a fatal error in reading init file. */
  char rescan = 0; /* Whether to reuse the same character when moving onto the
                      next state. */
  unsigned int lnum = 0;
  int c = 0;

  /* This parser is a true state machine, with no sneaky fetching
     of input characters inside the main loop.  In other words, all
     state is fully represented by the following variables:
   */
  enum
    {
      start_of_line,
      start_of_comment,
      in_line_comment,
      in_trailing_comment,
      get_keyseq,
      got_keyseq,
      get_action,
      got_action,
      get_varname,
      got_varname,
      get_equals,
      got_equals,
      get_value
    }
  state = start_of_line;
  enum sect_e section = info;
  enum
    {
      normal,
      slosh,
      control,
      octal,
      special_key
    }
  seqstate = normal;	/* used if state == get_keyseq */
  char meta = 0;
  char ocnt = 0;	/* used if state == get_keyseq && seqstate == octal */

  /* Data is accumulated in the following variables.  The code
     avoids overflowing these strings, and throws an error
     where appropriate if a string limit is exceeded.  These string
     lengths are arbitrary (and should be large enough) and their
     lengths are not hard-coded anywhere else, so increasing them
     here will not break anything.  */
  char oval = 0;
  char comment[10];
  unsigned int clen = 0;
  int seq[20];
  unsigned int slen = 0;
  char act[80];
  unsigned int alen = 0;
  char varn[80];
  unsigned int varlen = 0;
  char val[80];
  unsigned int vallen = 0;

#define	To_seq(c) \
		  do { \
		    if (slen < sizeof seq/sizeof(int)) \
		      seq[slen++] = meta ? KEYMAP_META(c) : (c); \
		    else \
		      { \
			syntax_error(filename, lnum, \
				     _("key sequence too long")); \
			error = 1; \
		      } \
		    meta = 0; \
		  } while (0)

  while (!error && (rescan || (c = fgetc (fp)) != EOF))
    {
      rescan = 0;
      switch (state)
	{
	case start_of_line:
	  lnum++;
	  if (c == '#')
	    state = start_of_comment;
	  else if (c != '\n')
	    {
	      switch (section)
		{
		case info:
		case ea:
		  state = get_keyseq;
		  seqstate = normal;
		  slen = 0;
		  break;
		case var:
		  state = get_varname;
		  varlen = 0;
		  break;
		}
	      rescan = 1;
	    }
	  break;

	case start_of_comment:
	  clen = 0;
	  state = in_line_comment;
	  /* fall through */
	case in_line_comment:
	  if (c == '\n')
	    {
	      state = start_of_line;
	      comment[clen] = '\0';
	      if (strcmp (comment, "info") == 0)
		section = info;
	      else if (strcmp (comment, "echo-area") == 0)
		section = ea;
	      else if (strcmp (comment, "var") == 0)
		section = var;
	      else if (strcmp (comment, "stop") == 0
		       && (section == info || section == ea))
                {
                  if (section == info)
                    *suppress_info = 1;
                  else
                    *suppress_ea = 1;
                }
	    }
	  else if (clen < sizeof comment - 1)
	    comment[clen++] = c;
	  break;

	case in_trailing_comment:
	  if (c == '\n')
	    state = start_of_line;
	  break;

	case get_keyseq:
	  switch (seqstate)
	    {
	    case normal:
	      if (c == '\n' || isspace (c))
		{
		  state = got_keyseq;
		  rescan = 1;
		  if (slen == 0)
		    {
		      syntax_error (filename, lnum, _("missing key sequence"));
		      error = 1;
		    }
		}
	      else if (c == '\\')
		seqstate = slosh;
	      else if (c == '^')
		seqstate = control;
	      else
		To_seq (c);
	      break;

	    case slosh:
	      switch (c)
		{
		case '0': case '1': case '2': case '3':
		case '4': case '5': case '6': case '7':
		  seqstate = octal;
		  oval = c - '0';
		  ocnt = 1;
		  break;
		case 'b':
		  To_seq ('\b');
		  seqstate = normal;
		  break;
		case 'e':
		  To_seq ('\033');
		  seqstate = normal;
		  break;
		case 'n':
		  To_seq ('\n');
		  seqstate = normal;
		  break;
		case 'r':
		  To_seq ('\r');
		  seqstate = normal;
		  break;
		case 't':
		  To_seq ('\t');
		  seqstate = normal;
		  break;
		case 'm':
		  meta = 1;
		  seqstate = normal;
		  break;
		case 'k':
		  seqstate = special_key;
		  break;
		default:
		  /* Backslash followed by any other char
		     just means that char.  */
		  To_seq (c);
		  seqstate = normal;
		  break;
		}
	      break;

	    case octal:
	      switch (c)
		{
		case '0': case '1': case '2': case '3':
		case '4': case '5': case '6': case '7':
		  if (++ocnt <= 3)
		    oval = oval * 8 + c - '0';
		  if (ocnt == 3)
		    seqstate = normal;
		  break;
		default:
		  ocnt = 4;
		  seqstate = normal;
		  rescan = 1;
		  break;
		}
	      if (seqstate != octal)
		{
		  if (oval)
		    To_seq (oval);
		  else
		    {
		      syntax_error (filename, lnum,
				    _("NUL character (\\000) not permitted"));
		      error = 1;
		    }
		}
	      break;

	    case special_key:
	      switch (c)
		{
		case 'u': To_seq (KEY_UP_ARROW); break;
		case 'd': To_seq (KEY_DOWN_ARROW); break;
		case 'r': To_seq (KEY_RIGHT_ARROW); break;
		case 'l': To_seq (KEY_LEFT_ARROW); break;
		case 'U': To_seq (KEY_PAGE_UP); break;
		case 'D': To_seq (KEY_PAGE_DOWN); break;
		case 'h': To_seq (KEY_HOME); break;
		case 'e': To_seq (KEY_END); break;
		case 'x': To_seq (KEY_DELETE); break;
		default:  To_seq (c); rescan = 1; break;
		}
	      seqstate = normal;
	      break;

	    case control:
	      if (CONTROL (c))
		To_seq (CONTROL (c));
	      else
		{
		  syntax_error (filename, lnum,
				_("NUL character (^%c) not permitted"), c);
		  error = 1;
		}
	      seqstate = normal;
	      break;
	    }
	  break;

	case got_keyseq:
	  if (isspace (c) && c != '\n')
	    break;
	  state = get_action;
	  alen = 0;
	  /* fall through */
	case get_action:
	  if (c == '\n' || isspace (c))
	    {
	      int a;

	      state = got_action;
	      rescan = 1;
	      if (alen == 0)
		{
		  syntax_error (filename, lnum, _("missing action name"));
		  error = 1;
		}
	      else
		{
		  act[alen] = '\0';
		  a = lookup_action (act);
                  if (a == A_info_menu_digit)
		    {
                      /* It does not make sense for menu-digit to be anything
                         other than '0' .. '9'. */
		      syntax_error (filename, lnum,
                        _("cannot bind key sequence to menu-digit"));
		    }
		  else if (a == -1)
		    {
                      /* Print an error message, but keep going (don't set
                         error = 1) for compatibility with infokey files aimed
                         at future versions which may have different
                         actions. */
		      syntax_error (filename, lnum, _("unknown action `%s'"),
				    act);
		    }
                  else
		    {
                      int keymap_bind_keyseq (Keymap, int *, KEYMAP_ENTRY *);

                      KEYMAP_ENTRY ke;
                      static InfoCommand invalid_function = { 0 };

                      ke.type = ISFUNC;
                      ke.value.function = a != A_INVALID
                                            ? &function_doc_array[a]
                                            : &invalid_function;
                      To_seq (0);

                      if (section == info)
                        keymap_bind_keyseq (info_keymap, seq, &ke);
                      else /* section == ea */
                        keymap_bind_keyseq (echo_area_keymap, seq, &ke);
		    }
		}
	    }
	  else if (alen < sizeof act - 1)
	    act[alen++] = c;
	  else
	    {
	      syntax_error (filename, lnum, _("action name too long"));
	      error = 1;
	    }
	  break;

	case got_action:
	  if (c == '#')
	    state = in_trailing_comment;
	  else if (c == '\n')
	    state = start_of_line;
	  else if (!isspace (c))
	    {
	      syntax_error (filename, lnum,
			    _("extra characters following action `%s'"),
			    act);
	      error = 1;
	    }
	  break;

	case get_varname:
	  if (c == '=')
	    {
	      if (varlen == 0)
		{
		  syntax_error (filename, lnum, _("missing variable name"));
		  error = 1;
		}
	      state = get_value;
	      vallen = 0;
	    }
	  else if (c == '\n' || isspace (c))
	    {
	      syntax_error (filename, lnum,
			    _("missing `=' immediately after variable name"));
	      error = 1;
	    }
	  else if (varlen < sizeof varn - 1)
	    varn[varlen++] = c;
	  else
	    {
	      syntax_error (filename, lnum, _("variable name too long"));
	      error = 1;
	    }
	  break;

	case get_value:
	  if (c == '\n')
	    {
              VARIABLE_ALIST *v;

              state = start_of_line;
              varn[varlen] = '\0';
              val[vallen] = '\0';
              v = variable_by_name (varn);
              if (!v)
                info_error (_("%s: no such variable"), varn);
              else if (!set_variable_to_value (v, val, SET_IN_CONFIG_FILE))
                info_error (_("value %s is not valid for variable %s"),
                              val, varn);
	    }
	  else if (vallen < sizeof val - 1)
	    val[vallen++] = c;
	  else
	    {
	      syntax_error (filename, lnum, _("value too long"));
	      error = 1;
	    }
	  break;

        case get_equals:
        case got_equals:
        case got_varname:
          break;
	}
    }

#undef To_seq

  return !error;
}

/* Return the numeric code of an Info command given its name.  If not found,
   return -1.  This uses the auto-generated array in doc.c. */
static int
lookup_action (const char *name)
{
  int i;

  if (!strcmp (name, "invalid"))
    return A_INVALID;
  for (i = 0; function_doc_array[i].func_name; i++)
    if (!strcmp (function_doc_array[i].func_name, name))
      return i;
  return -1;
}

/* Error handling. */

/* Give the user a generic error message in the form
	progname: message
 */
static void
syntax_error (const char *filename,
	      unsigned int linenum, const char *fmt, ...)
{
  va_list ap;

  fprintf (stderr, "%s: ", program_name);
  fprintf (stderr, _("\"%s\", line %u: "), filename, linenum);
  va_start(ap, fmt);
  vfprintf (stderr, fmt, ap);
  va_end(ap);
  fprintf (stderr, "\n");
}