/* infomap.c -- keymaps for Info.
   $Id: infomap.c,v 1.20 2008/02/29 22:20:53 karl Exp $

   Copyright (C) 1993, 1997, 1998, 1999, 2001, 2002, 2003, 2004, 2007, 2008
   Free Software Foundation, Inc.

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

   Written by Brian Fox (bfox@ai.mit.edu). */

#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 = (InfoCommand *)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 = (FUNCTION_KEYSEQ *)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('n'), NUL,              A_info_next_line,
        CONTROL('p'), NUL,              A_info_prev_line,
        CONTROL('r'), NUL,              A_isearch_backward,
        CONTROL('s'), NUL,              A_isearch_forward,
        CONTROL('u'), NUL,              A_info_universal_argument,
        CONTROL('v'), NUL,              A_info_scroll_forward_page_only,
        ',', 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_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,
        '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'), '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('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('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,
        '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, '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,
        CONTROL('g'), NUL,              A_info_abort_key,
        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);
        }
#ifdef __MSDOS__
        /* 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(filesys_error_string(filename, errno),
                            NULL, NULL);
                        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, NULL);
                else
                        info_error(_("Ignoring invalid infokey file `%s' - too big"),
                                   filename, NULL);
                close(f);
                free(filename);
                return 0;
        }

        /* Read the file into a buffer. */
        buf = (unsigned char *)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, NULL);
                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, NULL);
                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, NULL);
                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, NULL);
                        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, NULL);
                        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(_("Bad data in infokey file -- some key bindings ignored"),
                    NULL, NULL);
        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)
                  {
                    set_variable_to_value((char *) var, (char *) val);
                    state = getvar;
                  }
                break;
              }
          }
      if (state != getvar)
        info_error(_("Bad data in infokey file -- some var settings ignored"),
            NULL, NULL);
}

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: */
