/* infomap.c -- keymaps for Info.
   $Id: infomap.c 5338 2013-08-22 17:58:30Z karl $

   Copyright 1993, 1997, 1998, 1999, 2001, 2002, 2003, 2004, 2007,
   2008, 2011, 2012, 2013 Free Software Foundation, Inc.

   This program is free software: you can redistribute 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 "infomap.h"
#include "funs.h"
#include "terminal.h"

#if defined(INFOKEY)
#include "infokey.h"
#include "variables.h"
#endif /* INFOKEY */

static int keymap_bind_keyseq (Keymap map, const char *keyseq,
    KEYMAP_ENTRY *keyentry);

/* Return a new keymap which has all the uppercase letters mapped to run
   the function info_do_lowercase_version (). */
Keymap
keymap_make_keymap (void)
{
  int i;
  Keymap keymap;

  keymap = (Keymap)xmalloc (256 * sizeof (KEYMAP_ENTRY));

  for (i = 0; i < 256; i++)
    {
      keymap[i].type = ISFUNC;
      keymap[i].function = NULL;
    }

  for (i = 'A'; i < ('Z' + 1); i++)
    {
      keymap[i].type = ISFUNC;
#if defined(INFOKEY)
      keymap[Meta(i)].type = ISFUNC;
      keymap[Meta(i)].function =
#endif /* INFOKEY */
      keymap[i].function = InfoCmd (info_do_lowercase_version);
    }

  return keymap;
}

#if defined(INFOKEY)
static FUNCTION_KEYSEQ *
find_function_keyseq (Keymap map, int c, Keymap rootmap)
{
  FUNCTION_KEYSEQ *k;

  if (map[c].type != ISFUNC)
    abort ();
  if (map[c].function == NULL)
    return NULL;
  for (k = map[c].function->keys; k; k = k->next)
    {
      const unsigned char *p;
      Keymap m = rootmap;
      if (k->map != rootmap)
        continue;
      for (p = (unsigned char *) k->keyseq; *p && m[*p].type == ISKMAP; p++)
        m = (Keymap)m[*p].function;
      if (*p != c || p[1])
        continue;
      if (m[*p].type != ISFUNC)
        abort ();
      break;
    }
  return k;
}

static void
add_function_keyseq (InfoCommand *function,
    const char *keyseq, Keymap rootmap)
{
  FUNCTION_KEYSEQ *ks;

  if (function == NULL ||
      function == InfoCmd (info_do_lowercase_version) ||
      function == InfoCmd (ea_insert))
    return;
  ks = xmalloc  (sizeof (FUNCTION_KEYSEQ));
  ks->next = function->keys;
  ks->map = rootmap;
  ks->keyseq = xstrdup (keyseq);
  function->keys = ks;
}

static void
remove_function_keyseq (InfoCommand *function,
    const char *keyseq, Keymap rootmap)
{

  FUNCTION_KEYSEQ *k, *kp;

  if (function == NULL ||
      function == InfoCmd (info_do_lowercase_version) ||
      function == InfoCmd (ea_insert))
    return;
  for (kp = NULL, k = function->keys; k; kp = k, k = k->next)
    if (k->map == rootmap && strcmp (k->keyseq, keyseq) == 0)
      break;
  if (!k)
    abort ();
  if (kp)
    kp->next = k->next;
  else
    function->keys = k->next;
}
#endif /* INFOKEY */

/* Return a new keymap which is a copy of MAP. */
Keymap
keymap_copy_keymap (Keymap map, Keymap rootmap, Keymap newroot)
{
  int i;
  Keymap keymap;
#if defined(INFOKEY)
  FUNCTION_KEYSEQ *ks;
#endif /* INFOKEY */

  keymap = keymap_make_keymap ();
  if (!newroot)
    newroot = keymap;

  for (i = 0; i < 256; i++)
    {
      keymap[i].type = map[i].type;
      switch (map[i].type)
        {
        case ISFUNC:
          keymap[i].function = map[i].function;
#if defined(INFOKEY)
          ks = find_function_keyseq (map, i, rootmap);
          if (ks)
            add_function_keyseq (map[i].function, ks->keyseq, newroot);
#endif /* INFOKEY */
          break;
        case ISKMAP:
          keymap[i].function =
	    (InfoCommand *)keymap_copy_keymap ((Keymap)map[i].function,
					       rootmap, NULL);
          break;
        }
    }
  return keymap;
}

/* Free the keymap and its descendants. */
void
keymap_discard_keymap (Keymap map, Keymap rootmap)
{
  int i;

  if (!map)
    return;
  if (!rootmap)
    rootmap = map;

  for (i = 0; i < 256; i++)
    {
#if defined(INFOKEY)
      FUNCTION_KEYSEQ *ks;
#endif /* INFOKEY */
      switch (map[i].type)
        {
        case ISFUNC:
#if defined(INFOKEY)
          ks = find_function_keyseq (map, i, rootmap);
          if (ks)
            remove_function_keyseq (map[i].function, ks->keyseq, rootmap);
#endif /* INFOKEY */
          break;

        case ISKMAP:
          keymap_discard_keymap ((Keymap)map[i].function, rootmap);
          break;

        }
    }
  free (map);
}

/* Conditionally bind key sequence. */
static int
keymap_bind_keyseq (Keymap map,
		    const char *keyseq, KEYMAP_ENTRY *keyentry)
{
  Keymap m = map;
  const unsigned char *s = (unsigned char *) keyseq;
  int c;

  if (s == NULL || *s == '\0') return 0;

  while ((c = *s++) != '\0')
    {
#if defined(INFOKEY)
      FUNCTION_KEYSEQ *ks;
#endif /* INFOKEY */
      switch (m[c].type)
        {
        case ISFUNC:
#if defined(INFOKEY)
          ks = find_function_keyseq (m, c, map);
          if (ks)
            remove_function_keyseq (m[c].function, ks->keyseq, map);
#else /* !INFOKEY */
          if (!(m[c].function == NULL ||
		(m != map &&
		 m[c].function == InfoCmd (info_do_lowercase_version))))
            return 0;
#endif /* !INFOKEY */

          if (*s != '\0')
            {
              m[c].type = ISKMAP;
              /* Here we are casting the Keymap pointer returned from
                 keymap_make_keymap to an InfoCommand pointer.  Ugh.
                 This makes the `function' structure garbage
                 if it's actually interpreted as an InfoCommand.
                 Should really be using a union, and taking steps to
                 avoid the possible error.  */
              m[c].function = (InfoCommand *)keymap_make_keymap ();
            }
          break;

        case ISKMAP:
#if defined(INFOKEY)
          if (*s == '\0')
            keymap_discard_keymap ((Keymap)m[c].function, map);
#else /* !INFOKEY */
          if (*s == '\0')
            return 0;
#endif
          break;
        }
      if (*s != '\0')
        {
          m = (Keymap)m[c].function;
        }
      else
        {
#if defined(INFOKEY)
          add_function_keyseq (keyentry->function, keyseq, map);
#endif /* INFOKEY */
          m[c] = *keyentry;
        }
    }

  return 1;
}


/* Initialize the standard info keymaps. */

Keymap info_keymap = NULL;
Keymap echo_area_keymap = NULL;

/* Make sure that we don't have too many command codes defined. */

#if A_NCOMMANDS > A_MAX_COMMAND + 1
#error "too many commands defined"
#endif

/* Initialize the keymaps from the .info keymap file. */

#define NUL     '\0'

static unsigned char default_emacs_like_info_keys[] =
{
  0,      /* suppress-default-keybindings flag */
  TAB, NUL,                       A_info_move_to_next_xref,
  LFD, NUL,                       A_info_select_reference_this_line,
  RET, NUL,                       A_info_select_reference_this_line,
  CONTROL('a'), NUL,              A_info_beginning_of_line,
  CONTROL('b'), NUL,              A_info_backward_char,
  CONTROL('e'), NUL,              A_info_end_of_line,
  CONTROL('f'), NUL,              A_info_forward_char,
  CONTROL('h'), NUL,              A_info_get_help_window,
  CONTROL('l'), NUL,              A_info_redraw_display,
  CONTROL('p'), NUL,              A_info_prev_line,
  CONTROL('r'), NUL,              A_isearch_backward,
  CONTROL('s'), NUL,              A_isearch_forward,
  CONTROL('u'), NUL,              A_info_universal_argument,
  CONTROL('v'), NUL,              A_info_scroll_forward_page_only,
  ',', NUL,                       A_info_next_index_match,
  '/', NUL,                       A_info_search,
  '0', NUL,                       A_info_last_menu_item,
  '1', NUL,                       A_info_menu_digit,
  '2', NUL,                       A_info_menu_digit,
  '3', NUL,                       A_info_menu_digit,
  '4', NUL,                       A_info_menu_digit,
  '5', NUL,                       A_info_menu_digit,
  '6', NUL,                       A_info_menu_digit,
  '7', NUL,                       A_info_menu_digit,
  '8', NUL,                       A_info_menu_digit,
  '9', NUL,                       A_info_menu_digit,
  '<', NUL,                       A_info_first_node,
  '=', NUL,                       A_info_display_file_info,
  '>', NUL,                       A_info_last_node,
  '?', NUL,                       A_info_get_help_window,
  '[', NUL,                       A_info_global_prev_node,
  ']', NUL,                       A_info_global_next_node,
  'b', NUL,                       A_info_beginning_of_node,
  'd', NUL,                       A_info_dir_node,
  'e', NUL,                       A_info_end_of_node,
  'f', NUL,                       A_info_xref_item,
  'g', NUL,                       A_info_goto_node,
  'G', NUL,                       A_info_menu_sequence,
  'h', NUL,                       A_info_get_help_window,
  'H', NUL,                       A_info_get_info_help_node,
  'i', NUL,                       A_info_index_search,
  'I', NUL,                       A_info_virtual_index,
  'l', NUL,                       A_info_history_node,
  'm', NUL,                       A_info_menu_item,
  'n', NUL,                       A_info_next_node,
  'O', NUL,                       A_info_goto_invocation_node,
  'p', NUL,                       A_info_prev_node,
  'r', NUL,                       A_info_xref_item,
  'R', NUL,                       A_info_toggle_regexp,
  's', NUL,                       A_info_search,
  'S', NUL,                       A_info_search_case_sensitively,
  't', NUL,                       A_info_top_node,
  'u', NUL,                       A_info_up_node,
  ESC, '0', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '1', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '2', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '3', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '4', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '5', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '6', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '7', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '8', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '9', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '-', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, CONTROL('f'), NUL,         A_info_show_footnotes,
  ESC, CONTROL('g'), NUL,         A_info_abort_key,
  ESC, TAB, NUL,                  A_info_move_to_prev_xref,
  ESC, CONTROL('v'), NUL,         A_info_scroll_other_window,
  ESC, '<', NUL,                  A_info_beginning_of_node,
  ESC, '>', NUL,                  A_info_end_of_node,
  ESC, 'b', NUL,                  A_info_backward_word,
  ESC, 'f', NUL,                  A_info_forward_word,
  ESC, 'r', NUL,                  A_info_move_to_window_line,
  ESC, 'v', NUL,                  A_info_scroll_backward_page_only,
  Meta('0'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('1'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('2'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('3'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('4'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('5'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('6'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('7'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('8'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('9'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('-'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta(CONTROL('f')), NUL,        A_info_show_footnotes,
  Meta(CONTROL('g')), NUL,        A_info_abort_key,
  Meta(TAB), NUL,                 A_info_move_to_prev_xref,
  Meta(CONTROL('v')), NUL,        A_info_scroll_other_window,
  Meta('<'), NUL,                 A_info_beginning_of_node,
  Meta('>'), NUL,                 A_info_end_of_node,
  Meta('b'), NUL,                 A_info_backward_word,
  Meta('f'), NUL,                 A_info_forward_word,
  Meta('r'), NUL,                 A_info_move_to_window_line,
  Meta('v'), NUL,                 A_info_scroll_backward_page_only,
#if defined (NAMED_FUNCTIONS)
  ESC, 'x', NUL,                  A_info_execute_command,
  Meta('x'), NUL,                 A_info_execute_command,
#endif /* NAMED_FUNCTIONS */

  CONTROL('x'), CONTROL('b'), NUL,        A_list_visited_nodes,
  CONTROL('x'), CONTROL('c'), NUL,        A_info_quit,
  CONTROL('x'), CONTROL('f'), NUL,        A_info_view_file,
  CONTROL('x'), CONTROL('g'), NUL,        A_info_abort_key,
  CONTROL('x'), CONTROL('v'), NUL,        A_info_view_file,
  CONTROL('x'), '0', NUL,         A_info_delete_window,
  CONTROL('x'), '1', NUL,         A_info_keep_one_window,
  CONTROL('x'), '2', NUL,         A_info_split_window,
  CONTROL('x'), '^', NUL,         A_info_grow_window,
  CONTROL('x'), 'b', NUL,         A_select_visited_node,
  CONTROL('x'), 'f', NUL,         A_info_all_files,
  CONTROL('x'), 'k', NUL,         A_info_kill_node,
  CONTROL('x'), 'n', NUL,         A_info_search_next,
  CONTROL('x'), 'N', NUL,         A_info_search_previous,
  CONTROL('x'), 'o', NUL,         A_info_next_window,
  CONTROL('x'), 't', NUL,         A_info_tile_windows,
  CONTROL('x'), 'w', NUL,         A_info_toggle_wrap,
  
/*      Arrow key bindings for info keymaps.  It seems that some
        terminals do not match their termcap entries, so it's best to just
        define everything with both of the usual prefixes.  */

  SK_ESCAPE, SK_PAGE_UP, NUL,             A_info_scroll_backward,
  SK_ESCAPE, SK_PAGE_DOWN, NUL,           A_info_scroll_forward,
  '\033', 'O', 'A', NUL,                  A_info_prev_line,
  '\033', '[', 'A', NUL,                  A_info_prev_line,
  '\033', 'O', 'B', NUL,                  A_info_next_line,
  '\033', '[', 'B', NUL,                  A_info_next_line,
  SK_ESCAPE, SK_RIGHT_ARROW, NUL,         A_info_forward_char,
  '\033', 'O', 'C', NUL,                  A_info_forward_char,
  '\033', '[', 'C', NUL,                  A_info_forward_char,
  SK_ESCAPE, SK_LEFT_ARROW, NUL,          A_info_backward_char,
  '\033', 'O', 'D', NUL,                  A_info_backward_char,
  '\033', '[', 'D', NUL,                  A_info_backward_char,
  SK_ESCAPE, SK_HOME, NUL,                A_info_beginning_of_node,
  SK_ESCAPE, SK_END, NUL,                 A_info_end_of_node,
  SK_ESCAPE, SK_DELETE, NUL,              A_info_scroll_backward,
  
  ESC, SK_ESCAPE, SK_PAGE_UP, NUL,        A_info_scroll_other_window_backward,
  ESC, SK_ESCAPE, SK_PAGE_DOWN, NUL,      A_info_scroll_other_window,
  ESC, SK_ESCAPE, SK_UP_ARROW, NUL,       A_info_prev_line,
  ESC, '\033', 'O', 'A', NUL,             A_info_prev_line,
  ESC, '\033', '[', 'A', NUL,             A_info_prev_line,
  ESC, SK_ESCAPE, SK_DOWN_ARROW, NUL,     A_info_next_line,
  ESC, '\033', 'O', 'B', NUL,             A_info_next_line,
  ESC, '\033', '[', 'B', NUL,             A_info_next_line,
  ESC, SK_ESCAPE, SK_RIGHT_ARROW, NUL,    A_info_forward_word,
  ESC, '\033', 'O', 'C', NUL,             A_info_forward_word,
  ESC, '\033', '[', 'C', NUL,             A_info_forward_word,
  ESC, SK_ESCAPE, SK_LEFT_ARROW, NUL,     A_info_backward_word,
  ESC, '\033', 'O', 'D', NUL,             A_info_backward_word,
  ESC, '\033', '[', 'D', NUL,             A_info_backward_word,
  
  /* We want help to report q, not C-x C-c, etc.  */
  'q', NUL,                       A_info_quit,
  'x', NUL,                       A_info_delete_window,
  SPC, NUL,                       A_info_scroll_forward,
  DEL, NUL,                       A_info_scroll_backward,
  '{', NUL,                       A_info_search_previous,
  '}', NUL,                       A_info_search_next,
  CONTROL('g'), NUL,              A_info_abort_key,
  SK_ESCAPE, SK_UP_ARROW, NUL,    A_info_prev_line,
  SK_ESCAPE, SK_DOWN_ARROW, NUL,  A_info_next_line,
};


static unsigned char default_emacs_like_ea_keys[] =
{
  0,      /* suppress-default-keybindings flag */
  ESC, '0', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '1', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '2', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '3', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '4', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '5', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '6', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '7', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '8', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '9', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '-', NUL,                  A_info_add_digit_to_numeric_arg,
  Meta('0'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('1'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('2'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('3'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('4'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('5'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('6'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('7'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('8'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('9'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('-'), NUL,                 A_info_add_digit_to_numeric_arg,
  ESC, CONTROL('g'), NUL,         A_ea_abort,
  ESC, CONTROL('v'), NUL,         A_ea_scroll_completions_window,
  ESC, 'b', NUL,                  A_ea_backward_word,
  ESC, 'd', NUL,                  A_ea_kill_word,
  ESC, 'f', NUL,                  A_ea_forward_word,
  ESC, 'y', NUL,                  A_ea_yank_pop,
  ESC, '?', NUL,                  A_ea_possible_completions,
  ESC, TAB, NUL,                  A_ea_tab_insert,
  ESC, DEL, NUL,                  A_ea_backward_kill_word,
  Meta(CONTROL('g')), NUL,        A_ea_abort,
  Meta(CONTROL('v')), NUL,        A_ea_scroll_completions_window,
  Meta('b'), NUL,                 A_ea_backward_word,
  Meta('d'), NUL,                 A_ea_kill_word,
  Meta('f'), NUL,                 A_ea_forward_word,
  Meta('y'), NUL,                 A_ea_yank_pop,
  Meta('?'), NUL,                 A_ea_possible_completions,
  Meta(TAB), NUL,                 A_ea_tab_insert,
  Meta(DEL), NUL,                 A_ea_backward_kill_word,
  CONTROL('a'), NUL,              A_ea_beg_of_line,
  CONTROL('b'), NUL,              A_ea_backward,
  CONTROL('d'), NUL,              A_ea_delete,
  CONTROL('e'), NUL,              A_ea_end_of_line,
  CONTROL('f'), NUL,              A_ea_forward,
  CONTROL('g'), NUL,              A_ea_abort,
  CONTROL('h'), NUL,              A_ea_rubout,
/*      CONTROL('k') */
  SK_ESCAPE, SK_LITERAL, NUL,     A_ea_kill_line,
  CONTROL('l'), NUL,              A_info_redraw_display,
  CONTROL('q'), NUL,              A_ea_quoted_insert,
  CONTROL('t'), NUL,              A_ea_transpose_chars,
  CONTROL('u'), NUL,              A_info_universal_argument,
  CONTROL('y'), NUL,              A_ea_yank,
  LFD, NUL,                       A_ea_newline,
  RET, NUL,                       A_ea_newline,
  SPC, NUL,                       A_ea_complete,
  TAB, NUL,                       A_ea_complete,
  '?', NUL,                       A_ea_possible_completions,
#ifdef __MSDOS__
  /* PC users will lynch me if I don't give them their usual DEL
     effect...  */
  DEL, NUL,                       A_ea_delete,
#else
  DEL, NUL,                       A_ea_rubout,
#endif
#if defined (NAMED_FUNCTIONS)
  /*    ESC, 'x', NUL,                  A_info_execute_command, */
  /*    Meta('x'), NUL,                 A_info_execute_command, */
#endif /* NAMED_FUNCTIONS */
  CONTROL('x'), 'o', NUL,         A_info_next_window,
  CONTROL('x'), DEL, NUL,         A_ea_backward_kill_line,

/*      Arrow key bindings for echo area keymaps.  It seems that some
        terminals do not match their termcap entries, so it's best to just
        define everything with both of the usual prefixes.  */

  SK_ESCAPE, SK_RIGHT_ARROW, NUL,         A_ea_forward,
  '\033', 'O', 'C', NUL,                  A_ea_forward,
  '\033', '[', 'C', NUL,                  A_ea_forward,
  SK_ESCAPE, SK_LEFT_ARROW, NUL,          A_ea_backward,
  '\033', 'O', 'D', NUL,                  A_ea_backward,
  '\033', '[', 'D', NUL,                  A_ea_backward,
  ESC, SK_ESCAPE, SK_RIGHT_ARROW, NUL,    A_ea_forward_word,
  ESC, '\033', 'O', 'C', NUL,             A_ea_forward_word,
  ESC, '\033', '[', 'C', NUL,             A_ea_forward_word,
  ESC, SK_ESCAPE, SK_LEFT_ARROW, NUL,     A_ea_backward_word,
  ESC, '\033', 'O', 'D', NUL,             A_ea_backward_word,
  ESC, '\033', '[', 'D', NUL,             A_ea_backward_word,
#ifdef __MSDOS__
  SK_ESCAPE, SK_DELETE, NUL,              A_ea_delete,
#else
  SK_ESCAPE, SK_DELETE, NUL,              A_ea_rubout,
#endif
  SK_ESCAPE, SK_HOME, NUL,                A_ea_beg_of_line,
  SK_ESCAPE, SK_END, NUL,                 A_ea_end_of_line,
  ESC, SK_ESCAPE, SK_DELETE, NUL,         A_ea_backward_kill_word,
  CONTROL('x'), SK_ESCAPE, SK_DELETE, NUL,A_ea_backward_kill_line,
};


static unsigned char default_vi_like_info_keys[] =
{
  0,      /* suppress-default-keybindings flag */
  '0', NUL,                       A_info_add_digit_to_numeric_arg,
  '1', NUL,                       A_info_add_digit_to_numeric_arg,
  '2', NUL,                       A_info_add_digit_to_numeric_arg,
  '3', NUL,                       A_info_add_digit_to_numeric_arg,
  '4', NUL,                       A_info_add_digit_to_numeric_arg,
  '5', NUL,                       A_info_add_digit_to_numeric_arg,
  '6', NUL,                       A_info_add_digit_to_numeric_arg,
  '7', NUL,                       A_info_add_digit_to_numeric_arg,
  '8', NUL,                       A_info_add_digit_to_numeric_arg,
  '9', NUL,                       A_info_add_digit_to_numeric_arg,
  '-', NUL,                       A_info_add_digit_to_numeric_arg,
  TAB, NUL,                       A_info_move_to_next_xref,
  LFD, NUL,                       A_info_down_line,
  RET, NUL,                       A_info_down_line,
  CONTROL('a'), NUL,              A_info_beginning_of_line,
  CONTROL('b'), NUL,              A_info_scroll_backward_page_only,
  CONTROL('c'), NUL,              A_info_abort_key,
  CONTROL('d'), NUL,              A_info_scroll_half_screen_down,
  CONTROL('e'), NUL,              A_info_down_line,
  CONTROL('f'), NUL,              A_info_scroll_forward_page_only,
  CONTROL('g'), NUL,              A_info_display_file_info,
  CONTROL('k'), NUL,              A_info_up_line,
  CONTROL('l'), NUL,              A_info_redraw_display,
  CONTROL('n'), NUL,              A_info_down_line,
  CONTROL('p'), NUL,              A_info_up_line,
  CONTROL('r'), NUL,              A_info_redraw_display,
  CONTROL('s'), NUL,              A_isearch_forward,
  CONTROL('u'), NUL,              A_info_scroll_half_screen_up,
  CONTROL('v'), NUL,              A_info_scroll_forward_page_only,
  CONTROL('y'), NUL,              A_info_up_line,
  ',', NUL,                       A_info_next_index_match,
  '/', NUL,                       A_info_search,
  ESC, '0', NUL,                  A_info_last_menu_item,
  ESC, '1', NUL,                  A_info_menu_digit,
  ESC, '2', NUL,                  A_info_menu_digit,
  ESC, '3', NUL,                  A_info_menu_digit,
  ESC, '4', NUL,                  A_info_menu_digit,
  ESC, '5', NUL,                  A_info_menu_digit,
  ESC, '6', NUL,                  A_info_menu_digit,
  ESC, '7', NUL,                  A_info_menu_digit,
  ESC, '8', NUL,                  A_info_menu_digit,
  ESC, '9', NUL,                  A_info_menu_digit,
  Meta('0'), NUL,                 A_info_last_menu_item,
  Meta('1'), NUL,                 A_info_menu_digit,
  Meta('2'), NUL,                 A_info_menu_digit,
  Meta('3'), NUL,                 A_info_menu_digit,
  Meta('4'), NUL,                 A_info_menu_digit,
  Meta('5'), NUL,                 A_info_menu_digit,
  Meta('6'), NUL,                 A_info_menu_digit,
  Meta('7'), NUL,                 A_info_menu_digit,
  Meta('8'), NUL,                 A_info_menu_digit,
  Meta('9'), NUL,                 A_info_menu_digit,
  '<', NUL,                       A_info_first_node,
  '>', NUL,                       A_info_last_node,
  '?', NUL,                       A_info_search_backward,
  '[', NUL,                       A_info_global_prev_node,
  ']', NUL,                       A_info_global_next_node,
  '\'', NUL,                      A_info_history_node,
  'b', NUL,                       A_info_scroll_backward,
  'd', NUL,                       A_info_scroll_half_screen_down,
  'e', NUL,                       A_info_down_line,
  'E', NUL,                       A_info_view_file,
  ':', 'e', NUL,                  A_info_view_file,
  'f', NUL,                       A_info_scroll_forward_page_only,
  'F', NUL,                       A_info_scroll_forward_page_only,
  'g', NUL,                       A_info_first_node,
  'G', NUL,                       A_info_last_node,
  'h', NUL,                       A_info_get_help_window,
  'H', NUL,                       A_info_get_help_window,
  'i', NUL,                       A_info_index_search,
  'I', NUL,                       A_info_goto_invocation_node,
  'j', NUL,                       A_info_next_line,
  'k', NUL,                       A_info_prev_line,
  'l', NUL,                       A_info_history_node,
  'm', NUL,                       A_info_menu_item,
  'n', NUL,                       A_info_search_next,
  ':', 'a', NUL,                  A_info_all_files,
  'N', NUL,                       A_info_search_previous,
  'O', NUL,                       A_info_goto_invocation_node,
  'p', NUL,                       A_info_prev_node,
  'Q', NUL,                       A_info_quit,
  ':', 'q', NUL,                  A_info_quit,
  ':', 'Q', NUL,                  A_info_quit,
  'Z', 'Z', NUL,                  A_info_quit,
  'r', NUL,                       A_info_redraw_display,
  'R', NUL,                       A_info_toggle_regexp,
  's', NUL,                       A_info_search,
  'S', NUL,                       A_info_search_case_sensitively,
  't', NUL,                       A_info_top_node,
  'u', NUL,                       A_info_scroll_half_screen_up,
  'w', NUL,                       A_info_scroll_backward_page_only_set_window,
  'y', NUL,                       A_info_up_line,
  'z', NUL,                       A_info_scroll_forward_page_only_set_window,
  ESC, CONTROL('f'), NUL,         A_info_show_footnotes,
  ESC, CONTROL('g'), NUL,         A_info_abort_key,
  ESC, TAB, NUL,                  A_info_move_to_prev_xref,
  ESC, SPC, NUL,                  A_info_scroll_forward_page_only,
  ESC, CONTROL('v'), NUL,         A_info_scroll_other_window,
  ESC, '<', NUL,                  A_info_beginning_of_node,
  ESC, '>', NUL,                  A_info_end_of_node,
  ESC, '/', NUL,                  A_info_search,
  ESC, '?', NUL,                  A_info_search_backward,
  ESC, 'b', NUL,                  A_info_beginning_of_node,
  ESC, 'd', NUL,                  A_info_dir_node,
  ESC, 'e', NUL,                  A_info_end_of_node,
  ESC, 'f', NUL,                  A_info_xref_item,
  ESC, 'g', NUL,                  A_info_select_reference_this_line,
  ESC, 'h', NUL,                  A_info_get_info_help_node,
  ESC, 'I', NUL,                  A_info_virtual_index,
  ESC, 'm', NUL,                  A_info_menu_item,
  ESC, 'n', NUL,                  A_info_search,
  ESC, 'N', NUL,                  A_info_search_backward,
  ESC, 'r', NUL,                  A_isearch_backward,
  ESC, 's', NUL,                  A_isearch_forward,
  ESC, 't', NUL,                  A_info_top_node,
  ESC, 'v', NUL,                  A_info_scroll_backward_page_only,
#if defined (NAMED_FUNCTIONS)
  ESC, 'x', NUL,                  A_info_execute_command,
  Meta('x'), NUL,                 A_info_execute_command,
#endif /* NAMED_FUNCTIONS */
  ESC, DEL, NUL,                  A_info_scroll_other_window_backward,
  CONTROL('x'), CONTROL('b'), NUL,        A_list_visited_nodes,
  CONTROL('x'), CONTROL('c'), NUL,        A_info_quit,
  CONTROL('x'), CONTROL('f'), NUL,        A_info_view_file,
  CONTROL('x'), CONTROL('g'), NUL,        A_info_abort_key,
  CONTROL('x'), CONTROL('v'), NUL,        A_info_view_file,
  CONTROL('x'), LFD, NUL,         A_info_select_reference_this_line,
  CONTROL('x'), RET, NUL,         A_info_select_reference_this_line,
  CONTROL('x'), '0', NUL,         A_info_delete_window,
  CONTROL('x'), '1', NUL,         A_info_keep_one_window,
  CONTROL('x'), '2', NUL,         A_info_split_window,
  CONTROL('x'), '^', NUL,         A_info_grow_window,
  CONTROL('x'), 'b', NUL,         A_select_visited_node,
  CONTROL('x'), 'g', NUL,         A_info_goto_node,
  CONTROL('x'), 'i', NUL,         A_info_index_search,
  CONTROL('x'), 'I', NUL,         A_info_goto_invocation_node,
  CONTROL('x'), 'k', NUL,         A_info_kill_node,
  CONTROL('x'), 'n', NUL,         A_info_next_node,
  CONTROL('x'), 'o', NUL,         A_info_next_window,
  CONTROL('x'), 'O', NUL,         A_info_goto_invocation_node,
  CONTROL('x'), 'p', NUL,         A_info_prev_node,
  CONTROL('x'), 'r', NUL,         A_info_xref_item,
  CONTROL('x'), 't', NUL,         A_info_tile_windows,
  CONTROL('x'), 'u', NUL,         A_info_up_node,
  CONTROL('x'), 'w', NUL,         A_info_toggle_wrap,
  CONTROL('x'), ',', NUL,         A_info_next_index_match,

/*      Arrow key bindings for info keymaps.  It seems that some
        terminals do not match their termcap entries, so it's best to just
        define everything with both of the usual prefixes.  */
  
  SK_ESCAPE, SK_PAGE_UP, NUL,             A_info_scroll_backward,
  SK_ESCAPE, SK_PAGE_DOWN, NUL,           A_info_scroll_forward,
  '\033', 'O', 'A', NUL,                  A_info_up_line,
  '\033', '[', 'A', NUL,                  A_info_up_line,
  '\033', 'O', 'B', NUL,                  A_info_down_line,
  '\033', '[', 'B', NUL,                  A_info_down_line,
  SK_ESCAPE, SK_RIGHT_ARROW, NUL,         A_info_scroll_forward_page_only,
  '\033', 'O', 'C', NUL,                  A_info_scroll_forward_page_only,
  '\033', '[', 'C', NUL,                  A_info_scroll_forward_page_only,
  SK_ESCAPE, SK_LEFT_ARROW, NUL,          A_info_scroll_backward_page_only,
  '\033', 'O', 'D', NUL,                  A_info_scroll_backward_page_only,
  '\033', '[', 'D', NUL,                  A_info_scroll_backward_page_only,
  SK_ESCAPE, SK_HOME, NUL,                A_info_beginning_of_node,
  SK_ESCAPE, SK_END, NUL,                 A_info_end_of_node,
  ESC, SK_ESCAPE, SK_PAGE_DOWN, NUL,      A_info_scroll_other_window,
  ESC, SK_ESCAPE, SK_PAGE_UP, NUL,        A_info_scroll_other_window_backward,
  ESC, SK_ESCAPE, SK_DELETE, NUL,         A_info_scroll_other_window_backward,
  ESC, SK_ESCAPE, SK_UP_ARROW, NUL,       A_info_prev_node,
  ESC, '\033', 'O', 'A', NUL,             A_info_prev_node,
  ESC, '\033', '[', 'A', NUL,             A_info_prev_node,
  ESC, SK_ESCAPE, SK_DOWN_ARROW, NUL,     A_info_next_node,
  ESC, '\033', 'O', 'B', NUL,             A_info_next_node,
  ESC, '\033', '[', 'B', NUL,             A_info_next_node,
  ESC, SK_ESCAPE, SK_RIGHT_ARROW, NUL,    A_info_xref_item,
  ESC, '\033', 'O', 'C', NUL,             A_info_xref_item,
  ESC, '\033', '[', 'C', NUL,             A_info_xref_item,
  ESC, SK_ESCAPE, SK_LEFT_ARROW, NUL,     A_info_beginning_of_node,
  ESC, '\033', 'O', 'D', NUL,             A_info_beginning_of_node,
  ESC, '\033', '[', 'D', NUL,             A_info_beginning_of_node,
  CONTROL('x'), SK_ESCAPE, SK_DELETE, NUL,A_ea_backward_kill_line,
  
  /* We want help to report q, not C-x C-c, etc.  */
  'q', NUL,                       A_info_quit,
  'x', NUL,                       A_info_delete_window,
  SPC, NUL,                       A_info_scroll_forward,
  DEL, NUL,                       A_info_scroll_backward,
  '{', NUL,                       A_info_search_previous,
  '}', NUL,                       A_info_search_next,
  SK_ESCAPE, SK_UP_ARROW, NUL,    A_info_up_line,
  SK_ESCAPE, SK_DOWN_ARROW, NUL,  A_info_down_line,
};


static unsigned char default_vi_like_ea_keys[] =
{
  0,      /* suppress-default-keybindings flag */
  ESC, '1', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '2', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '3', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '4', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '5', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '6', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '7', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '8', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '9', NUL,                  A_info_add_digit_to_numeric_arg,
  ESC, '-', NUL,                  A_info_add_digit_to_numeric_arg,
  Meta('1'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('2'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('3'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('4'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('5'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('6'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('7'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('8'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('9'), NUL,                 A_info_add_digit_to_numeric_arg,
  Meta('-'), NUL,                 A_info_add_digit_to_numeric_arg,
  ESC, CONTROL('g'), NUL,         A_ea_abort,
  ESC, CONTROL('h'), NUL,         A_ea_backward_kill_word,
  ESC, CONTROL('v'), NUL,         A_ea_scroll_completions_window,
  ESC, '0', NUL,                  A_ea_beg_of_line,
  ESC, '$', NUL,                  A_ea_end_of_line,
  ESC, 'b', NUL,                  A_ea_backward_word,
  ESC, 'd', NUL,                  A_ea_kill_word,
  ESC, 'f', NUL,                  A_ea_forward_word,
  ESC, 'h', NUL,                  A_ea_forward,
  ESC, 'l', NUL,                  A_ea_backward,
  ESC, 'w', NUL,                  A_ea_forward_word,
  ESC, 'x', NUL,                  A_ea_delete,
  ESC, 'X', NUL,                  A_ea_kill_word,
  ESC, 'y', NUL,                  A_ea_yank_pop,
  ESC, '?', NUL,                  A_ea_possible_completions,
  ESC, TAB, NUL,                  A_ea_tab_insert,
  ESC, DEL, NUL,                  A_ea_kill_word,
  Meta(CONTROL('g')), NUL,        A_ea_abort,
  Meta(CONTROL('h')), NUL,        A_ea_backward_kill_word,
  Meta(CONTROL('v')), NUL,        A_ea_scroll_completions_window,
  Meta('0'), NUL,                 A_ea_beg_of_line,
  Meta('$'), NUL,                 A_ea_end_of_line,
  Meta('b'), NUL,                 A_ea_backward_word,
  Meta('d'), NUL,                 A_ea_kill_word,
  Meta('f'), NUL,                 A_ea_forward_word,
  Meta('h'), NUL,                 A_ea_forward,
  Meta('l'), NUL,                 A_ea_backward,
  Meta('w'), NUL,                 A_ea_forward_word,
  Meta('x'), NUL,                 A_ea_delete,
  Meta('X'), NUL,                 A_ea_kill_word,
  Meta('y'), NUL,                 A_ea_yank_pop,
  Meta('?'), NUL,                 A_ea_possible_completions,
  Meta(TAB), NUL,                 A_ea_tab_insert,
  Meta(DEL), NUL,                 A_ea_kill_word,
  CONTROL('a'), NUL,              A_ea_beg_of_line,
  CONTROL('b'), NUL,              A_ea_backward,
  CONTROL('d'), NUL,              A_ea_delete,
  CONTROL('e'), NUL,              A_ea_end_of_line,
  CONTROL('f'), NUL,              A_ea_forward,
  CONTROL('g'), NUL,              A_ea_abort,
  CONTROL('h'), NUL,              A_ea_rubout,
  /*      CONTROL('k') */
  SK_ESCAPE, SK_LITERAL, NUL,     A_ea_kill_line,
  CONTROL('l'), NUL,              A_info_redraw_display,
  CONTROL('q'), NUL,              A_ea_quoted_insert,
  CONTROL('t'), NUL,              A_ea_transpose_chars,
  CONTROL('u'), NUL,              A_ea_abort,
  CONTROL('v'), NUL,              A_ea_quoted_insert,
  CONTROL('y'), NUL,              A_ea_yank,
  LFD, NUL,                       A_ea_newline,
  RET, NUL,                       A_ea_newline,
  SPC, NUL,                       A_ea_complete,
  TAB, NUL,                       A_ea_complete,
  '?', NUL,                       A_ea_possible_completions,
#ifdef __MSDOS__
  /* PC users will lynch me if I don't give them their usual DEL
     effect...  */
  DEL, NUL,                       A_ea_delete,
#else
        DEL, NUL,                       A_ea_rubout,
#endif
  CONTROL('x'), 'o', NUL,         A_info_next_window,
  CONTROL('x'), DEL, NUL,         A_ea_backward_kill_line,
  
  /* Arrow key bindings for echo area keymaps.  It seems that some
     terminals do not match their termcap entries, so it's best to just
     define everything with both of the usual prefixes.  */

  SK_ESCAPE, SK_RIGHT_ARROW, NUL,         A_ea_forward,
  '\033', 'O', 'C', NUL,                  A_ea_forward,
  '\033', '[', 'C', NUL,                  A_ea_forward,
  SK_ESCAPE, SK_LEFT_ARROW, NUL,          A_ea_backward,
  '\033', 'O', 'D', NUL,                  A_ea_backward,
  '\033', '[', 'D', NUL,                  A_ea_backward,
  SK_ESCAPE, SK_HOME, NUL,                A_ea_beg_of_line,
  SK_ESCAPE, SK_END, NUL,                 A_ea_end_of_line,
#ifdef __MSDOS__
  SK_ESCAPE, SK_DELETE, NUL,              A_ea_delete,
#else
  SK_DELETE, SK_DELETE, NUL,              A_ea_rubout,
#endif
  ESC, SK_ESCAPE, SK_RIGHT_ARROW, NUL,    A_ea_forward_word,
  ESC, '\033', 'O', 'C', NUL,             A_ea_forward_word,
  ESC, '\033', '[', 'C', NUL,             A_ea_forward_word,
  ESC, SK_ESCAPE, SK_LEFT_ARROW, NUL,     A_ea_backward_word,
  ESC, '\033', 'O', 'D', NUL,             A_ea_backward_word,
  ESC, '\033', '[', 'D', NUL,             A_ea_backward_word,
  ESC, SK_ESCAPE, SK_DELETE, NUL,         A_ea_kill_word,
  CONTROL('x'), SK_ESCAPE, SK_DELETE, NUL,A_ea_backward_kill_line,
};


static unsigned char *user_info_keys;
static unsigned int user_info_keys_len;
static unsigned char *user_ea_keys;
static unsigned int user_ea_keys_len;
static unsigned char *user_vars;
static unsigned int user_vars_len;

/*
 * Return the size of a file, or 0 if the size can't be determined.
 */
static unsigned long
filesize (int f)
{
  long pos = lseek (f, 0L, SEEK_CUR);
  long sz = -1L;
  if (pos != -1L)
    {
      sz = lseek (f, 0L, SEEK_END);
      lseek (f, pos, SEEK_SET);
    }
  return sz == -1L ? 0L : sz;
}

/* Get an integer from a infokey file.
   Integers are stored as two bytes, low order first, in radix INFOKEY_RADIX.
 */
static int
getint (unsigned char **sp)
{
  int n;
  
  if ( !((*sp)[0] < INFOKEY_RADIX && (*sp)[1] < INFOKEY_RADIX) )
    return -1;
  n = (*sp)[0] + (*sp)[1] * INFOKEY_RADIX;
  *sp += 2;
  return n;
}


/* Fetch the contents of the standard infokey file "$HOME/.info".  Return
   true if ok, false if not.  */
static int
fetch_user_maps (void)
{
  char *filename = NULL;
  char *homedir;
  int f;
  unsigned char *buf;
  unsigned long len;
  long nread;
  unsigned char *p;
  int n;
  
  /* Find and open file. */
  if ((filename = getenv ("INFOKEY")) != NULL)
    filename = xstrdup (filename);
  else if ((homedir = getenv ("HOME")) != NULL)
    {
      filename = xmalloc (strlen (homedir) + 2 + strlen (INFOKEY_FILE));
      strcpy (filename, homedir);
      strcat (filename, "/");
      strcat (filename, INFOKEY_FILE);
    }
#if defined(__MSDOS__) || defined(__MINGW32__)
  /* Poor baby, she doesn't have a HOME...  */
  else
    filename = xstrdup (INFOKEY_FILE); /* try current directory */
#endif
  if (filename == NULL || (f = open (filename, O_RDONLY)) == -1)
    {
      if (filename && errno != ENOENT)
	{
	  info_error ("%s", filesys_error_string (filename, errno));
	  free (filename);
	}
      return 0;
    }
  SET_BINARY (f);

  /* Ensure that the file is a reasonable size. */
  len = filesize (f);
  if (len < INFOKEY_NMAGIC + 2 || len > 100 * 1024)
    {
      /* Bad file (a valid file must have at least 9 chars, and
	 more than 100 KB is a problem). */
      if (len < INFOKEY_NMAGIC + 2)
	info_error (_("Ignoring invalid infokey file `%s' - too small"),
		   filename);
      else
	info_error (_("Ignoring invalid infokey file `%s' - too big"),
		   filename);
      close (f);
      free (filename);
      return 0;
    }
  
  /* Read the file into a buffer. */
  buf = xmalloc ((int)len);
  nread = read (f, buf, (unsigned int) len);
  close (f);
  if ((unsigned int) nread != len)
    {
      info_error (_("Error reading infokey file `%s' - short read"),
                           filename);
      free (buf);
      free (filename);
      return 0;
    }
  
  /* Check the header, trailer, and version of the file to increase
     our confidence that the contents are valid.  */
  if (buf[0] != INFOKEY_MAGIC_S0 ||
      buf[1] != INFOKEY_MAGIC_S1 ||
      buf[2] != INFOKEY_MAGIC_S2 ||
      buf[3] != INFOKEY_MAGIC_S3 ||
      buf[len - 4] != INFOKEY_MAGIC_E0 ||
      buf[len - 3] != INFOKEY_MAGIC_E1 ||
      buf[len - 2] != INFOKEY_MAGIC_E2 ||
      buf[len - 1] != INFOKEY_MAGIC_E3)
    {
      info_error (_("Invalid infokey file `%s' (bad magic numbers) -- run infokey to update it"),
		 filename);
      free (filename);
      return 0;
    }
  if (len < INFOKEY_NMAGIC + strlen (VERSION) + 1 ||
      strcmp (VERSION, (char *) (buf + 4)) != 0)
    {
      info_error (_("Your infokey file `%s' is out of date -- run infokey to update it"),
		 filename);
      free (filename);
      return 0;
    }
  
  /* Extract the pieces.  */
  for (p = buf + 4 + strlen (VERSION) + 1;
       (unsigned int) (p - buf) < len - 4;
       p += n)
    {
      int s = *p++;
      
      n = getint (&p);
      if (n < 0 || (unsigned int) n > len - 4 - (p - buf))
	{
	  info_error (_("Invalid infokey file `%s' (bad section length) -- run infokey to update it"),
		     filename);
	  free (filename);
	  return 0;
	}
      
      switch (s)
	{
	case INFOKEY_SECTION_INFO:
	  user_info_keys = p;
	  user_info_keys_len = n;
	  break;
	  
	case INFOKEY_SECTION_EA:
	  user_ea_keys = p;
	  user_ea_keys_len = n;
	  break;
	  
	case INFOKEY_SECTION_VAR:
	  user_vars = p;
	  user_vars_len = n;
	  break;
	  
	default:
	  info_error (_("Invalid infokey file `%s' (bad section code) -- run infokey to update it"),
		     filename);
	  free (filename);
	  return 0;
	}
    }
  
  free (filename);
  return 1;
}

/* Decode special key sequences from the infokey file.  Return zero
   if the key sequence includes special keys which the terminal
   doesn't define.
 */
static int
decode_keys (unsigned char *src, unsigned int slen,
	    unsigned char *dst, unsigned int dlen)
{
  unsigned char *s = src;
  unsigned char *d = dst;

#define To_dst(c) do { \
    if ((unsigned int) (d - dst) < dlen) *d++ = (c);	\
} while (0)

  while ((unsigned int) (s - src) < slen)
    {
      unsigned char c = ISMETA (*s) ? UNMETA (*s) : *s;
      
      if (c == SK_ESCAPE)
	{
	  char *t;
	  static char lit[] = { SK_ESCAPE, NUL };
	  
	  switch ((unsigned int) (s + 1 - src) < slen ? s[1] : '\0')
	    {
	    case SK_RIGHT_ARROW:    t = term_kr; break;
	    case SK_LEFT_ARROW:     t = term_kl; break;
	    case SK_UP_ARROW:       t = term_ku; break;
	    case SK_DOWN_ARROW:     t = term_kd; break;
	    case SK_PAGE_UP:        t = term_kP; break;
	    case SK_PAGE_DOWN:      t = term_kN; break;
	    case SK_HOME:           t = term_kh; break;
	    case SK_END:            t = term_ke; break;
	    case SK_DELETE:         t = term_kx; break;
	    case SK_INSERT:         t = term_ki; break;
	    case SK_LITERAL:
	    default:                t = lit; break;
	    }
	  if (t == NULL)
	    return 0;
	  while (*t)
	    To_dst (ISMETA (*s) ? Meta (*t++) : *t++);
	  s += 2;
	}
      else
	{
	  if (ISMETA (*s))
	    To_dst (Meta (*s++));
	  else
	    To_dst (*s++);
	}
    }
  
  To_dst ('\0');
  
  return 1;
  
#undef To_dst

}

/* Convert an infokey file section to keymap bindings.  Return false if
   the default bindings are to be suppressed.  */
static int
section_to_keymaps (Keymap map, unsigned char *table, unsigned int len)
{
  int stop;
  unsigned char *p;
  unsigned char *seq = NULL;
  unsigned int seqlen = 0;
  enum { getseq, gotseq, getaction } state = getseq;
  
  stop = len > 0 ? table[0] : 0;
  
  for (p = table + 1; (unsigned int) (p - table) < len; p++)
    {
      switch (state)
	{
	case getseq:
	  if (*p)
	    {
	      seq = p;
	      state = gotseq;
	    }
	  break;
	  
	case gotseq:
	  if (!*p)
	    {
	      seqlen = p - seq;
	      state = getaction;
	    }
	  break;
	  
	case getaction:
	  {
	    unsigned int action = *p;
	    unsigned char keyseq[256];
	    KEYMAP_ENTRY ke;
	    
	    state = getseq;
	    /* If decode_keys returns zero, it means that seq includes keys
	       which the terminal doesn't support, like PageDown.  In that
	       case, don't bind the key sequence.  */
	    if (decode_keys (seq, seqlen, keyseq, sizeof keyseq))
	      {
		keyseq[sizeof keyseq - 1] = '\0';
		ke.type = ISFUNC;
		ke.function = action < A_NCOMMANDS ?
		                    &function_doc_array[action]
		                    : NULL;
		keymap_bind_keyseq (map, (const char *) keyseq, &ke);
	      }
	  }
	  break;
	}
    }
  if (state != getseq)
    info_error ("%s", _("Bad data in infokey file -- some key bindings ignored"));
  return !stop;
}

/* Convert an infokey file section to variable settings.
 */
static void
section_to_vars (unsigned char *table, unsigned int len)
{
  enum { getvar, gotvar, getval, gotval } state = getvar;
  unsigned char *var = NULL;
  unsigned char *val = NULL;
  unsigned char *p;
  
  for (p = table; (unsigned int) (p - table) < len; p++)
    {
      switch (state)
	{
	case getvar:
	  if (*p)
	    {
	      var = p;
	      state = gotvar;
	    }
	  break;
	  
	case gotvar:
	  if (!*p)
	    state = getval;
	  break;
	  
	case getval:
	  if (*p)
	    {
	      val = p;
	      state = gotval;
	    }
	  break;
	  
	case gotval:
	  if (!*p)
	    {
	      if (set_variable_to_value ((char *) var, (char *) val))
		{
		  switch (errno)
		    {
		    case ENOENT:
		      info_error (_("%s: no such variable"), var);
		      break;
		      
		    case EINVAL:
		      info_error (_("value %s is not valid for variable %s"),
				  val, var);
		      break;
		      
		    default:
		      abort ();
		    }
		}	
	      state = getvar;
	    }
	  break;
	}
    }
  if (state != getvar)
    info_error ("%s", _("Bad data in infokey file -- some var settings ignored"));
}

void
initialize_info_keymaps (void)
{
  int i;
  int suppress_info_default_bindings = 0;
  int suppress_ea_default_bindings = 0;

  if (!info_keymap)
    {
      info_keymap = keymap_make_keymap ();
      echo_area_keymap = keymap_make_keymap ();
    }

  /* Bind the echo area insert routines. */
  for (i = 0; i < 256; i++)
    if (isprint (i))
      echo_area_keymap[i].function = InfoCmd (ea_insert);

  /* Get user-defined keys and variables.  */
  if (fetch_user_maps ())
    {
      if (user_info_keys_len && user_info_keys[0])
        suppress_info_default_bindings = 1;
      if (user_ea_keys_len && user_ea_keys[0])
        suppress_ea_default_bindings = 1;
    }

  /* Apply the default bindings, unless the user says to suppress
     them.  */
  if (vi_keys_p)
    {
      if (!suppress_info_default_bindings)
        section_to_keymaps (info_keymap, default_vi_like_info_keys,
                           sizeof (default_vi_like_info_keys));
      if (!suppress_ea_default_bindings)
          section_to_keymaps (echo_area_keymap, default_vi_like_ea_keys,
                             sizeof (default_vi_like_ea_keys));
    }
  else
    {
      if (!suppress_info_default_bindings)
        section_to_keymaps (info_keymap, default_emacs_like_info_keys,
                           sizeof (default_emacs_like_info_keys));
      if (!suppress_ea_default_bindings)
          section_to_keymaps (echo_area_keymap, default_emacs_like_ea_keys,
                             sizeof (default_emacs_like_ea_keys));
    }

  /* If the user specified custom bindings, apply them on top of the
     default ones.  */
  if (user_info_keys_len)
    section_to_keymaps (info_keymap, user_info_keys, user_info_keys_len);

  if (user_ea_keys_len)
    section_to_keymaps (echo_area_keymap, user_ea_keys, user_ea_keys_len);

  if (user_vars_len)
    section_to_vars (user_vars, user_vars_len);
}

/* vim: set sw=2 cino={1s>2sn-s^-se-s: */
