code:
/* infomap.c -- keymaps for Info.
   $Id$

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

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

   Originally written by Brian Fox. */

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

void keymap_bind_keyseq (Keymap map, int *keyseq, KEYMAP_ENTRY *keyentry);

/* Return a new Keymap. */
Keymap
keymap_make_keymap (void)
{
  int i;
  Keymap keymap;

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

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

  return keymap;
}

/* Record KEYSEQ, a sequence of keys terminated by 0, in the linked list of
   FUNCTION_KEYSEQ hanging off FUNCTION.  Label it with ROOTMAP so we know
   whether the key sequence is for main operation or for the echo area. */
static void
add_function_keyseq (InfoCommand *function, int *keyseq, Keymap rootmap)
{
  FUNCTION_KEYSEQ *ks, *k;
  int len;

  if (function == NULL ||
      function == InfoCmd (info_do_lowercase_version))
    return;

  /* If there is already a key sequence recorded for this key map,
     don't do anything. */
  for (k = function->keys; k; k = k->next)
    if (k->map == rootmap)
      return;

  ks = xmalloc (sizeof (FUNCTION_KEYSEQ));
  ks->next = function->keys;
  ks->map = rootmap;
  for (len = 0; keyseq[len]; len++);
  ks->keyseq = xmalloc ((len + 1) * sizeof (int));
  memcpy (ks->keyseq, keyseq, (len + 1) * sizeof (int));

  function->keys = ks;
}

/* Bind key sequence.  Don't override already bound key sequences. */
void
keymap_bind_keyseq (Keymap map, int *keyseq, KEYMAP_ENTRY *keyentry)
{
  Keymap m = map;
  int *s = keyseq;
  int c;

  if (!s || *s == 0)
    return;

  while ((c = *s++) != '\0')
    {
      switch (m[c].type)
        {
        case ISFUNC:
          if (m[c].value.function)
            return; /* There is a function here already. */

          if (*s != '\0')
            {
              m[c].type = ISKMAP;
              m[c].value.keymap = keymap_make_keymap ();
            }
          break;

        case ISKMAP:
          if (*s == '\0')
            return; /* The key sequence we were asked to bind is an initial
                       subsequence of an already-bound sequence. */
          break;
        }
      if (*s != '\0')
        {
          m = m[c].value.keymap;
        }
      else
        {
          add_function_keyseq (keyentry->value.function, keyseq, map);
          m[c] = *keyentry;
        }
    }

  return;
}

/* Initialize the standard info keymaps. */

Keymap info_keymap = NULL;
Keymap echo_area_keymap = NULL;

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

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

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

#define NUL     '\0'

static int default_emacs_like_info_keys[] =
{
  /* Favoured command bindings come first.  We want help to
     report q, not C-x C-c, etc.  */
  'H', NUL,                       A_info_get_help_window,
  'q', NUL,                       A_info_quit,
  KEY_UP_ARROW, NUL,              A_info_prev_line,
  KEY_DOWN_ARROW, NUL,            A_info_next_line,
  KEY_PAGE_UP, NUL,             A_info_scroll_backward,
  KEY_PAGE_DOWN, NUL,           A_info_scroll_forward,
  KEY_HOME, NUL,                  A_info_beginning_of_node,
  KEY_END, NUL,                   A_info_end_of_node,
  '{', NUL,                       A_info_search_previous,
  '}', NUL,                       A_info_search_next,
  CONTROL('g'), NUL,              A_info_abort_key,
  RET, NUL,                       A_info_select_reference_this_line,

  TAB, NUL,                       A_info_move_to_next_xref,
  LFD, NUL,                       A_info_select_reference_this_line,
  CONTROL('a'), NUL,              A_info_beginning_of_line,
  CONTROL('b'), NUL,              A_info_backward_char,
  CONTROL('e'), NUL,              A_info_end_of_line,
  CONTROL('f'), NUL,              A_info_forward_char,
  CONTROL('h'), NUL,              A_info_scroll_backward,
  CONTROL('l'), NUL,              A_info_redraw_display,
  CONTROL('n'), NUL,              A_info_next_line,
  CONTROL('p'), NUL,              A_info_prev_line,
  CONTROL('r'), NUL,              A_isearch_backward,
  CONTROL('s'), NUL,              A_isearch_forward,
  CONTROL('u'), NUL,              A_info_universal_argument,
  CONTROL('v'), NUL,              A_info_scroll_forward_page_only,
  SPC, NUL,                       A_info_scroll_forward,
  ',', NUL,                       A_info_next_index_match,
  '/', NUL,                       A_info_search,
  '0', NUL,                       A_info_last_menu_item,
  '1', NUL,                       A_info_menu_digit,
  '2', NUL,                       A_info_menu_digit,
  '3', NUL,                       A_info_menu_digit,
  '4', NUL,                       A_info_menu_digit,
  '5', NUL,                       A_info_menu_digit,
  '6', NUL,                       A_info_menu_digit,
  '7', NUL,                       A_info_menu_digit,
  '8', NUL,                       A_info_menu_digit,
  '9', NUL,                       A_info_menu_digit,
  '<', NUL,                       A_info_first_node,
  '=', NUL,                       A_info_display_file_info,
  '>', NUL,                       A_info_last_node,
  '?', NUL,                       A_info_search_backward,
  '[', NUL,                       A_info_global_prev_node,
  ']', NUL,                       A_info_global_next_node,
  'b', NUL,                       A_info_beginning_of_node,
  'd', NUL,                       A_info_dir_node,
  'e', NUL,                       A_info_end_of_node,
  'f', NUL,                       A_info_xref_item,
  'g', NUL,                       A_info_goto_node,
  'G', NUL,                       A_info_menu_sequence,
  'h', NUL,                       A_info_get_info_help_node,
  'i', NUL,                       A_info_index_search,
  'I', NUL,                       A_info_virtual_index,
  'l', NUL,                       A_info_history_node,
  'm', NUL,                       A_info_menu_item,
  'n', NUL,                       A_info_next_node,
  'O', NUL,                       A_info_goto_invocation_node,
  'p', NUL,                       A_info_prev_node,
  'r', NUL,                       A_info_xref_item,
  'R', NUL,                       A_info_toggle_regexp,
  's', NUL,                       A_info_search,
  'S', NUL,                       A_info_search_case_sensitively,
  't', NUL,                       A_info_top_node,
  'u', NUL,                       A_info_up_node,
  'x', NUL,                       A_info_delete_window,
  KEYMAP_META('0'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('1'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('2'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('3'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('4'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('5'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('6'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('7'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('8'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('9'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('-'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META(CONTROL('f')), NUL,        A_info_show_footnotes,
  KEYMAP_META(CONTROL('g')), NUL,        A_info_abort_key,
  KEYMAP_META(TAB), NUL,                 A_info_move_to_prev_xref,
  KEYMAP_META(CONTROL('v')), NUL,        A_info_scroll_other_window,
  KEYMAP_META('<'), NUL,                 A_info_beginning_of_node,
  KEYMAP_META('>'), NUL,                 A_info_end_of_node,
  KEYMAP_META('b'), NUL,                 A_info_backward_word,
  KEYMAP_META('f'), NUL,                 A_info_forward_word,
  KEYMAP_META('r'), NUL,                 A_info_move_to_window_line,
  KEYMAP_META('v'), NUL,                 A_info_scroll_backward_page_only,
  KEYMAP_META('x'), NUL,                 A_info_execute_command,
  KEYMAP_META('/'), NUL,                 A_info_tree_search,
  KEYMAP_META('}'), NUL,                 A_info_tree_search_next,
  KEYMAP_META('{'), NUL,                 A_info_tree_search_previous,

  CONTROL('x'), CONTROL('b'), NUL,        A_list_visited_nodes,
  CONTROL('x'), CONTROL('c'), NUL,        A_info_quit,
  CONTROL('x'), CONTROL('f'), NUL,        A_info_view_file,
  CONTROL('x'), CONTROL('g'), NUL,        A_info_abort_key,
  CONTROL('x'), CONTROL('v'), NUL,        A_info_view_file,
  CONTROL('x'), '0', NUL,         A_info_delete_window,
  CONTROL('x'), '1', NUL,         A_info_keep_one_window,
  CONTROL('x'), '2', NUL,         A_info_split_window,
  CONTROL('x'), '^', NUL,         A_info_grow_window,
  CONTROL('x'), 'b', NUL,         A_select_visited_node,
  CONTROL('x'), 'f', NUL,         A_info_all_files,
  CONTROL('x'), 'n', NUL,         A_info_search_next,
  CONTROL('x'), 'N', NUL,         A_info_search_previous,
  CONTROL('x'), 'o', NUL,         A_info_next_window,
  CONTROL('x'), 't', NUL,         A_info_tile_windows,
  CONTROL('x'), 'w', NUL,         A_info_toggle_wrap,

  KEY_RIGHT_ARROW, NUL,         A_info_forward_char,
  KEY_LEFT_ARROW, NUL,          A_info_backward_char,
  KEY_DELETE, NUL,              A_info_scroll_backward,

  ESC, KEY_PAGE_UP, NUL,        A_info_scroll_other_window_backward,
  ESC, KEY_PAGE_DOWN, NUL,      A_info_scroll_other_window,
  ESC, KEY_UP_ARROW, NUL,       A_info_prev_line,
  ESC, KEY_DOWN_ARROW, NUL,     A_info_next_line,
  ESC, KEY_RIGHT_ARROW, NUL,    A_info_forward_word,
  ESC, KEY_LEFT_ARROW, NUL,     A_info_backward_word,
  KEY_BACK_TAB, NUL,            A_info_move_to_prev_xref,

};

static int default_emacs_like_ea_keys[] =
{
  KEYMAP_META('0'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('1'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('2'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('3'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('4'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('5'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('6'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('7'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('8'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('9'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('-'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META(CONTROL('g')), NUL,        A_ea_abort,
  KEYMAP_META(CONTROL('v')), NUL,        A_ea_scroll_completions_window,
  KEYMAP_META('b'), NUL,                 A_ea_backward_word,
  KEYMAP_META('d'), NUL,                 A_ea_kill_word,
  KEYMAP_META('f'), NUL,                 A_ea_forward_word,
  KEYMAP_META('y'), NUL,                 A_ea_yank_pop,
  KEYMAP_META('?'), NUL,                 A_ea_possible_completions,
  KEYMAP_META(TAB), NUL,                 A_ea_tab_insert,
  KEYMAP_META(KEY_DELETE), NUL,                 A_ea_backward_kill_word,
  CONTROL('a'), NUL,              A_ea_beg_of_line,
  CONTROL('b'), NUL,              A_ea_backward,
  CONTROL('d'), NUL,              A_ea_delete,
  CONTROL('e'), NUL,              A_ea_end_of_line,
  CONTROL('f'), NUL,              A_ea_forward,
  CONTROL('g'), NUL,              A_ea_abort,
  ESC, NUL,                       A_ea_abort,
  CONTROL('h'), NUL,              A_ea_rubout,
  CONTROL('k'), NUL,              A_ea_kill_line,
  CONTROL('l'), NUL,              A_info_redraw_display,
  CONTROL('q'), NUL,              A_ea_quoted_insert,
  CONTROL('t'), NUL,              A_ea_transpose_chars,
  CONTROL('u'), NUL,              A_info_universal_argument,
  CONTROL('y'), NUL,              A_ea_yank,
  LFD, NUL,                       A_ea_newline,
  RET, NUL,                       A_ea_newline,
  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...  */
  KEY_DELETE, NUL,                       A_ea_delete,
#else
  KEY_DELETE, NUL,                       A_ea_rubout,
#endif
  CONTROL('x'), 'o', NUL,         A_info_next_window,
  CONTROL('x'), KEY_DELETE, NUL,         A_ea_backward_kill_line,

  KEY_RIGHT_ARROW, NUL,           A_ea_forward,
  KEY_LEFT_ARROW, NUL,            A_ea_backward,
  ESC, KEY_RIGHT_ARROW, NUL,   A_ea_forward_word,
  ESC, KEY_LEFT_ARROW, NUL,    A_ea_backward_word,
  KEY_HOME, NUL,                 A_ea_beg_of_line,
  KEY_END, NUL,                  A_ea_end_of_line,
  ESC, KEY_DELETE, NUL,  A_ea_backward_kill_word,
};

static int default_vi_like_info_keys[] =
{
  /* We want help to report q, not C-x C-c, etc.  */
  'q', NUL,                       A_info_quit,
  'x', NUL,                       A_info_delete_window,
  SPC, NUL,                       A_info_scroll_forward,
  '{', NUL,                       A_info_search_previous,
  '}', NUL,                       A_info_search_next,
  KEY_UP_ARROW, NUL,    A_info_up_line,
  KEY_DOWN_ARROW, NUL,  A_info_down_line,

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

  KEY_PAGE_UP, NUL,             A_info_scroll_backward,
  KEY_PAGE_DOWN, NUL,           A_info_scroll_forward,
  KEY_DELETE, NUL,              A_info_scroll_backward,
  KEY_RIGHT_ARROW, NUL,         A_info_scroll_forward_page_only,
  KEY_LEFT_ARROW, NUL,          A_info_scroll_backward_page_only,
  KEY_HOME, NUL,                A_info_beginning_of_node,
  KEY_END, NUL,                 A_info_end_of_node,
  ESC, KEY_PAGE_DOWN, NUL,      A_info_scroll_other_window,
  ESC, KEY_PAGE_UP, NUL,        A_info_scroll_other_window_backward,
  ESC, KEY_DELETE, NUL,         A_info_scroll_other_window_backward,
  ESC, KEY_UP_ARROW, NUL,       A_info_prev_node,
  ESC, KEY_DOWN_ARROW, NUL,     A_info_next_node,
  ESC, KEY_RIGHT_ARROW, NUL,    A_info_xref_item,
  ESC, KEY_LEFT_ARROW, NUL,     A_info_beginning_of_node,
  CONTROL('x'), KEY_DELETE, NUL, A_ea_backward_kill_line,

};

static int default_vi_like_ea_keys[] =
{
  KEYMAP_META('1'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('2'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('3'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('4'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('5'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('6'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('7'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('8'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('9'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META('-'), NUL,                 A_info_add_digit_to_numeric_arg,
  KEYMAP_META(CONTROL('g')), NUL,        A_ea_abort,
  KEYMAP_META(CONTROL('h')), NUL,        A_ea_backward_kill_word,
  KEYMAP_META(CONTROL('v')), NUL,        A_ea_scroll_completions_window,
  KEYMAP_META('0'), NUL,                 A_ea_beg_of_line,
  KEYMAP_META('$'), NUL,                 A_ea_end_of_line,
  KEYMAP_META('b'), NUL,                 A_ea_backward_word,
  KEYMAP_META('d'), NUL,                 A_ea_kill_word,
  KEYMAP_META('f'), NUL,                 A_ea_forward_word,
  KEYMAP_META('h'), NUL,                 A_ea_backward,
  KEYMAP_META('l'), NUL,                 A_ea_forward,
  KEYMAP_META('w'), NUL,                 A_ea_forward_word,
  KEYMAP_META('x'), NUL,                 A_ea_delete,
  KEYMAP_META('X'), NUL,                 A_ea_kill_word,
  KEYMAP_META('y'), NUL,                 A_ea_yank_pop,
  KEYMAP_META('?'), NUL,                 A_ea_possible_completions,
  KEYMAP_META(TAB), NUL,                 A_ea_tab_insert,
  KEYMAP_META(DEL), NUL,                 A_ea_kill_word,
  CONTROL('a'), NUL,              A_ea_beg_of_line,
  CONTROL('b'), NUL,              A_ea_backward,
  CONTROL('d'), NUL,              A_ea_delete,
  CONTROL('e'), NUL,              A_ea_end_of_line,
  CONTROL('f'), NUL,              A_ea_forward,
  CONTROL('g'), NUL,              A_ea_abort,
  ESC, NUL,                       A_ea_abort,
  CONTROL('h'), NUL,              A_ea_rubout,
  CONTROL('k'), NUL,              A_ea_kill_line,
  CONTROL('l'), NUL,              A_info_redraw_display,
  CONTROL('q'), NUL,              A_ea_quoted_insert,
  CONTROL('t'), NUL,              A_ea_transpose_chars,
  CONTROL('u'), NUL,              A_ea_abort,
  CONTROL('v'), NUL,              A_ea_quoted_insert,
  CONTROL('y'), NUL,              A_ea_yank,
  LFD, NUL,                       A_ea_newline,
  RET, NUL,                       A_ea_newline,
  SPC, NUL,                       A_ea_complete,
  TAB, NUL,                       A_ea_complete,
  '?', NUL,                       A_ea_possible_completions,
  CONTROL('x'), 'o', NUL,         A_info_next_window,

  KEY_RIGHT_ARROW, NUL,         A_ea_forward,
  KEY_LEFT_ARROW, NUL,          A_ea_backward,
  KEY_HOME, NUL,                A_ea_beg_of_line,
  KEY_END, NUL,                 A_ea_end_of_line,
#ifdef __MSDOS__
  KEY_DELETE, NUL,              A_ea_delete,
#else
  KEY_DELETE, NUL,              A_ea_rubout,
#endif
  ESC, KEY_RIGHT_ARROW, NUL,    A_ea_forward_word,
  ESC, KEY_LEFT_ARROW, NUL,     A_ea_backward_word,
  ESC, KEY_DELETE, NUL,         A_ea_kill_word,
  CONTROL('x'), KEY_DELETE, NUL,A_ea_backward_kill_line,
};

/* Whether to suppress the default key bindings. */
static int sup_info, sup_ea;

/* Fetch the contents of the init file at INIT_FILE, or the standard
   infokey file "$HOME/.infokey".  Return non-zero if an init file was
   loaded and read. */
static int
fetch_user_maps (char *init_file)
{
  char *filename = NULL;
  char *homedir;
  FILE *inf;

  /* In infokey.c */
  int compile (FILE *fp, const char *filename, int *, int *);

  /* Find and open file. */
  if (init_file)
    filename = xstrdup (init_file);
  else if ((homedir = getenv ("HOME")) != NULL
#ifdef __MINGW32__
	    || (homedir = getenv ("USERPROFILE")) != NULL
#endif
	  )
    {
      filename = xmalloc (strlen (homedir) + 2 + strlen (INFOKEY_FILE));
      strcpy (filename, homedir);
      strcat (filename, "/");
      strcat (filename, INFOKEY_FILE);
    }
#if defined(__MSDOS__) || defined(__MINGW32__)
  /* Poor baby, she doesn't have a HOME...  */
  else
    filename = xstrdup (INFOKEY_FILE); /* try current directory */
#endif
  inf = fopen (filename, "r");
  if (!inf)
    {
      free (filename);
      if (init_file)
        info_error (_("could not open init file %s"), init_file);
      return 0;
    }

  compile (inf, filename, &sup_info, &sup_ea);

  free (filename);
  return 1;
}

/* Set key bindings in MAP from TABLE, which is of length LEN. */
static void
section_to_keymaps (Keymap map, int *table, unsigned int len)
{
  int k;
  Keymap esc_map;

  int *p;
  int *seq;
  enum { getseq, gotseq, getaction } state = getseq;

  for (p = table; (unsigned int) (p - table) < len; p++)
    {
      switch (state)
	{
	case getseq:
	  if (*p)
	    {
	      seq = p;
	      state = gotseq;
	    }
	  break;

	case gotseq:
	  if (!*p)
            state = getaction;
	  break;

	case getaction:
	  {
	    unsigned int action = *p;
	    KEYMAP_ENTRY ke;

	    state = getseq;

            ke.type = ISFUNC;
            ke.value.function = action < A_NCOMMANDS ?
                                &function_doc_array[action]
                                : NULL;
            keymap_bind_keyseq (map, seq, &ke);
	  }
	  break;
	}
    }
  if (state != getseq)
    abort ();

  /* Go through map and bind ESC x to the same function as M-x if it is not
     bound already. */
  if (!map[ESC].value.function)
    {
      map[ESC].type = ISKMAP;
      map[ESC].value.keymap = keymap_make_keymap ();
    }

  if (map[ESC].type != ISKMAP)
    return; /* ESC is bound to a command. */

  esc_map = map[ESC].value.keymap;
  for (k = 1; k < KEYMAP_META_BASE; k++)
    {
      if (map[k + KEYMAP_META_BASE].type == ISFUNC
          && esc_map[k].value.function == 0)
        {
          esc_map[k].type = ISFUNC;
          esc_map[k].value.function = map[k + KEYMAP_META_BASE].value.function;
        }
    }
  return;
}

/* Read key bindings and variable settings from INIT_FILE.  If INIT_FILE
   is null, look for the init file in the default location. */
void
read_init_file (char *init_file)
{
  int *info_keys, *ea_keys; /* Pointers to keymap tables. */
  long info_keys_len, ea_keys_len; /* Sizes of keymap tables. */

  int i;

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

  if (!vi_keys_p)
    {
      info_keys = default_emacs_like_info_keys;
      info_keys_len = sizeof (default_emacs_like_info_keys)/sizeof (int);
      ea_keys = default_emacs_like_ea_keys;
      ea_keys_len = sizeof (default_emacs_like_ea_keys)/sizeof (int);
    }
  else
    {
      info_keys = default_vi_like_info_keys;
      info_keys_len = sizeof (default_vi_like_info_keys)/sizeof(int);
      ea_keys = default_vi_like_ea_keys;
      ea_keys_len = sizeof (default_vi_like_ea_keys)/sizeof(int);
    }

  /* Get user-defined keys and variables.  */
  if (fetch_user_maps (init_file))
    {
      if (sup_info)
        info_keys = 0; /* Suppress default bindings. */
      if (sup_ea)
        ea_keys = 0;
    }

  /* Apply the default bindings, unless the user says to suppress
     them. */
  if (info_keys)
    section_to_keymaps (info_keymap, info_keys, info_keys_len);
  if (ea_keys)
    section_to_keymaps (echo_area_keymap, ea_keys, ea_keys_len);

  for (i = 'A'; i < ('Z' + 1); i++)
    {
      if (!info_keymap[i].value.function)
        {
          info_keymap[i].type = ISFUNC;
          info_keymap[i].value.function = InfoCmd (info_do_lowercase_version);
        }

      if (!info_keymap[KEYMAP_META(i)].value.function)
        {
          info_keymap[KEYMAP_META(i)].type = ISFUNC;
          info_keymap[KEYMAP_META(i)].value.function
            = InfoCmd (info_do_lowercase_version);
        }
    }
}

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


code:
/* infomap.h -- description of a keymap in Info and related functions.
   $Id$

   Copyright 1993, 1999, 2001, 2002, 2004, 2007, 2013, 2014 Free Software
   Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

   Originaly written by Brian Fox. */

#ifndef INFOMAP_H
#define INFOMAP_H

#include "info.h"

#define ESC '\033'
#define DEL '\177'
#define TAB '\011'
#define RET '\r'
#define LFD '\n'
#define SPC ' '

#define meta_character_threshold (DEL + 1)
#define control_character_threshold (SPC)

#define meta_character_bit 0x80
#define control_character_bit 0x40

#define Meta_p(c) (((c) > meta_character_threshold))
#define Control_p(c) ((c) < control_character_threshold)

#define Meta(c) ((c) | (meta_character_bit))
#define UnMeta(c) ((c) & (~meta_character_bit))
#define Control(c) ((toupper (c)) & (~control_character_bit))
#define UnControl(c) (tolower ((c) | control_character_bit))

/* Structure used to map sequences of bytes to recognized keys. */
typedef struct bytemap_entry
{
  char type;
  int key;
  struct bytemap_entry *next;
} BYTEMAP_ENTRY;

#define BYTEMAP_NONE 0
#define BYTEMAP_KEY 1
#define BYTEMAP_MAP 2
#define BYTEMAP_ESC 3

extern BYTEMAP_ENTRY *byte_seq_to_key;

typedef struct keymap_entry
{
  char type;
  union
  {
    InfoCommand *function;         /* The address of a function. */
    struct keymap_entry *keymap;   /* The address of another Keymap */
  } value;
} KEYMAP_ENTRY;

/* The values that TYPE can have in a keymap entry. */
#define ISFUNC 0
#define ISKMAP 1

/* We use Keymap for a pointer to a block of KEYMAP_SIZE KEYMAP_ENTRY's. */
typedef KEYMAP_ENTRY *Keymap;

extern Keymap info_keymap;
extern Keymap echo_area_keymap;

#define KEY_RIGHT_ARROW		256
#define KEY_LEFT_ARROW		257
#define KEY_UP_ARROW		258
#define KEY_DOWN_ARROW		259
#define KEY_PAGE_UP		260
#define KEY_PAGE_DOWN		261
#define KEY_HOME		262
#define KEY_END			263
#define KEY_DELETE		264
#define KEY_INSERT		265
#define KEY_CTL_LEFT_ARROW	266
#define KEY_CTL_RIGHT_ARROW	267
#define KEY_CTL_DELETE		268
#define KEY_BACK_TAB		269
#define KEY_MOUSE               270

/* Add this to get the offset of the key binding with the meta key. */
#define KEYMAP_META_BASE 271

/* Number of entries in a Keymap: 256 entries for plain byte values plus
   mappings for special keys.  The bindings for the key chords with meta
   follow. */
#define KEYMAP_SIZE (KEYMAP_META_BASE * 2)

#define KEYMAP_META(k) ((k) < KEYMAP_META_BASE ? (k) + KEYMAP_META_BASE : (k))

/* Default "infokey file", where user defs are kept and read by
   Info.  MS-DOS doesn't allow leading dots in file names.  */
#ifdef __MSDOS__
#define INFOKEY_FILE		"_infokey"
#else
#define INFOKEY_FILE		".infokey"
#endif

#define	A_MAX_COMMAND		120
#define	A_INVALID		121

#define	CONTROL(c)		((c) & 0x1f)

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

/* Read init file and initialize the info keymaps. */
extern void read_init_file (char *init_file);

#endif /* not INFOMAP_H */

repo_name: mthl/texinfo

license: mit


code:
/* infopath.c -- INFOPATH handling.
   $Id$

   Copyright 1993, 1997, 1998, 2000, 2002, 2003, 2004, 2007, 2008, 2009, 2011,
   2012, 2013, 2014, 2015 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

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

typedef struct {
  char *name;    /* Path to directory to be searched. */
  dev_t device;  /* Storage device this directory is on. */
  ino_t inode;   /* Inode number, used to detect duplicates. */
} INFO_DIR;

INFO_DIR **infodirs = 0;
size_t infodirs_index = 0;
size_t infodirs_slots = 0;

/* Exclude default file search directories. */
int infopath_no_defaults_p;

static void infopath_add_dir (char *path);
char *extract_colon_unit (char *string, int *idx);

void
infopath_init ()
{
  /* Initialize INFOPATH.
     Highest priority is the environment variable, if set
     Then comes the user's INFODIR from the Makefile.
     The hardwired default settings (filesys.h) are the lowest priority. */
  char *path_from_env = getenv ("INFOPATH");

  if (path_from_env)
    {
      infopath_add (path_from_env);
    }

  if (!infopath_no_defaults_p)
    {
#ifdef INFODIR /* $infodir, set by configure script in Makefile */
      infopath_add (INFODIR);
#ifdef INFODIR2 /* $datadir/info, which could be different. */
      if (!STREQ (INFODIR, INFODIR2))
        infopath_add (INFODIR2);
#endif /* INFODIR2 */
#endif /* INFODIR */
    }

  if (!path_from_env)
    {
      infopath_add (DEFAULT_INFOPATH);
    }
  else
    {
      /* Only insert default path if there is a trailing : on INFOPATH. */

      unsigned len = strlen (path_from_env);
      if (len && path_from_env[len - 1] == PATH_SEP[0])
	{
	  path_from_env[len - 1] = 0;
	  infopath_add (DEFAULT_INFOPATH);
	}
    }
}

/* Return value to be freed by caller. */
char *
infopath_string ()
{
  struct text_buffer path;
  int dir_idx;
  char *this_dir;

  this_dir = infopath_first (&dir_idx);
  if (!this_dir)
    return "";

  text_buffer_init (&path);

  while (1)
    {
      text_buffer_printf (&path, "%s", this_dir);
      this_dir = infopath_next (&dir_idx);
      if (!this_dir)
        break;
      text_buffer_add_char (&path, ':');
    }
  return text_buffer_base (&path);
}

/* For each path element PREFIX/DIR in PATH substitute either
   PREFIX/share/info or PREFIX/info if that directory exists. */
static void
build_infopath_from_path (void)
{
  char *path_from_env, *temp_dirname;
  int dirname_index = 0;
  struct stat finfo;

  path_from_env = getenv ("PATH");

  while ((temp_dirname = extract_colon_unit (path_from_env, &dirname_index)))
    {
      unsigned int i, dir = 0;

      /* Find end of DIRNAME/ (but ignore "/") */
      for (i = 0; temp_dirname[i]; i++)
        if (i && IS_SLASH (temp_dirname[i]))
          dir = i + 1;

      /* Discard path elements ending with "/", "/.", or "/.." */
      if (!temp_dirname[dir] || STREQ (temp_dirname + dir, ".") || STREQ (temp_dirname + dir, "."))
        dir = 0;

      if (dir)
        {
          temp_dirname = xrealloc (temp_dirname, dir + strlen ("share/info") +1);

          /* first try DIRNAME/share/info */
          strcpy (temp_dirname + dir, "share/info");
          if (stat (temp_dirname, &finfo) != 0 || !S_ISDIR (finfo.st_mode))
            {
              /* then try DIRNAME/info */
              strcpy (temp_dirname + dir, "info");
              if (stat (temp_dirname, &finfo) != 0 || !S_ISDIR (finfo.st_mode))
                dir = 0;
            }
        }

      if (dir)
        infopath_add_dir (temp_dirname);
      else
        free (temp_dirname);
    }
}

/* Add directory at PATH to Info search path.  A reference to PATH is retained,
   or PATH is freed. */
static void
infopath_add_dir (char *path)
{
  struct stat dirinfo;
  INFO_DIR *entry;
  int i;

  if (stat (path, &dirinfo) == -1)
    {
      debug (2, ("inaccessible directory %s not added to INFOPATH", path));
      free (path);
      return; /* Doesn't exist, or not accessible. */
    }

  for (i = 0; i < infodirs_index; i++)
    {
      if (   dirinfo.st_ino == infodirs[i]->inode
          && dirinfo.st_dev == infodirs[i]->device
          /* On MS-Windows, `stat' returns zero as the inode, so we
             use file-name comparison instead for that OS.  */
          && (infodirs[i]->inode != 0 || fncmp (path, infodirs[i]->name) == 0))
        {
          debug (2, ("duplicate directory %s not added to INFOPATH", path));
          free (path);
          return; /* We have it already. */
        }
    }

  debug (2, ("adding %s to INFOPATH", path));
  entry = xmalloc (sizeof (INFO_DIR));
  entry->name = path;
  entry->inode = dirinfo.st_ino;
  entry->device = dirinfo.st_dev;
  add_pointer_to_array (entry, infodirs_index, infodirs, infodirs_slots, 8);
}

/* Add PATH to the list of paths found in INFOPATH.  PATH should be allocated
   on the heap and not referenced by the caller after calling this function.
   If PATH is "PATH", add a sequence of path elements derived from the
   environment variable PATH. */
void
infopath_add (char *path)
{
  int idx = 0;
  char *dirname;

  while (dirname = extract_colon_unit (path, &idx))
    {
      if (!strcmp ("PATH", dirname))
        {
          free (dirname);
          build_infopath_from_path ();
        }
      else
        infopath_add_dir (dirname);
    }
}

/* Used to iterate over INFOPATH.  Return value should not be freed
   by caller. */
char *
infopath_next (int *idx)
{
  INFO_DIR *entry;

  if (!infodirs)
   return 0;
  entry = infodirs[(*idx)++];
  if (!entry)
    return 0;
  return entry->name;
}

char *
infopath_first (int *idx)
{
  *idx = 0;
  return infopath_next (idx);
}

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

  if (!string || i >= strlen (string))
    return NULL;

  if (!string[i]) /* end of string */
    return NULL;

  /* Advance to next PATH_SEP.  */
  while (string[i] && string[i] != PATH_SEP[0])
    i++;

  {
    char *value = xmalloc ((i - start) + 1);
    strncpy (value, &string[start], (i - start));
    value[i - start] = 0;

    i++; /* move past PATH_SEP */
    *idx = i;
    return value;
  }
}


code:
/* m-x.c -- Meta-x minibuffer reader.
   $Id$

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

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

   Originally written by Brian Fox. */

#include "info.h"
#include "display.h"
#include "session.h"
#include "echo-area.h"
#include "funs.h"

/* **************************************************************** */
/*                                                                  */
/*                     Reading Named Commands                       */
/*                                                                  */
/* **************************************************************** */

/* Read the name of an Info function in the echo area and return the
   name.  A return value of NULL indicates that no function name could
   be read. */
char *
read_function_name (char *prompt, WINDOW *window)
{
  register int i;
  char *line;
  REFERENCE **array = NULL;
  size_t array_index = 0, array_slots = 0;

  /* Make an array of REFERENCE which actually contains the names of
     the functions available in Info. */
  for (i = 0; function_doc_array[i].func; i++)
    {
      REFERENCE *entry;

      entry = xmalloc (sizeof (REFERENCE));
      entry->label = xstrdup (function_doc_array[i].func_name);
      entry->nodename = NULL;
      entry->filename = NULL;

      add_pointer_to_array (entry, array_index, array, array_slots, 200);
    }

  line = info_read_completing_in_echo_area (prompt, array);
  info_free_references (array);

  return line;
}

DECLARE_INFO_COMMAND (describe_command,
   _("Read the name of an Info command and describe it"))
{
  char *line;

  line = read_function_name (_("Describe command: "), window);

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

  /* Describe the function named in "LINE". */
  if (*line)
    {
      InfoCommand *cmd = named_function (line);

      if (!cmd)
        return;

      window_message_in_echo_area ("%s: %s.",
                                   line, function_documentation (cmd));
    }
  free (line);
}

DECLARE_INFO_COMMAND (info_execute_command,
   _("Read a command name in the echo area and execute it"))
{
  char *line;
  char *keys;
  char *prompt;

  keys = where_is (info_keymap, InfoCmd(info_execute_command));
  /* If the where_is () function thinks that this command doesn't exist,
     there's something very wrong!  */
  if (!keys)
    abort();

  if (info_explicit_arg || count != 1)
    asprintf (&prompt, "%d %s ", count, keys);
  else
    asprintf (&prompt, "%s ", keys);

  /* Ask the completer to read a reference for us. */
  line = read_function_name (prompt, window);
  free (prompt);

  /* User aborted? */
  if (!line)
    {
      info_abort_key (active_window, count);
      return;
    }

  /* User accepted "default"?  (There is none.) */
  if (!*line)
    {
      free (line);
      return;
    }

  /* User wants to execute a named command.  Do it. */
  {
    InfoCommand *command;

    if ((active_window != the_echo_area) &&
        (strncmp (line, "echo-area-", 10) == 0))
      {
        free (line);
        info_error (_("Cannot execute an 'echo-area' command here."));
        return;
      }

    command = named_function (line);
    free (line);

    if (command && command->func)
      (*command->func) (active_window, count, 0);
  }
}

/* Okay, now that we have M-x, let the user set the screen height. */
DECLARE_INFO_COMMAND (set_screen_height,
  _("Set the height of the displayed window"))
{
  int new_height, old_height = screenheight;

  if (info_explicit_arg || count != 1)
    new_height = count;
  else
    {
      char prompt[80];
      char *line;

      new_height = screenheight;

      sprintf (prompt, _("Set screen height to (%d): "), new_height);

      line = info_read_in_echo_area (prompt);

      /* If the user aborted, do that now. */
      if (!line)
        {
          info_abort_key (active_window, count);
          return;
        }

      /* Find out what the new height is supposed to be. */
      if (*line)
        new_height = atoi (line);

      free (line);
    }

  terminal_clear_screen ();
  display_clear_display (the_display);
  screenheight = new_height;
#ifdef SET_SCREEN_SIZE_HELPER
  SET_SCREEN_SIZE_HELPER;
#endif
  if (screenheight == old_height)
    {
      /* Display dimensions didn't actually change, so
	 window_new_screen_size won't do anything, but we've
	 already cleared the display above.  Undo the damage.  */
      window_mark_chain (windows, W_UpdateWindow);
      display_update_display ();
    }
  else
    {
      display_initialize_display (screenwidth, screenheight);
      window_new_screen_size (screenwidth, screenheight);
    }
}


code:
/* makedoc.c -- make doc.c and funs.h from input files.
   $Id$

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

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

   Originally written by Brian Fox. */

/* This program grovels the contents of the source files passed as arguments
   and writes out a file of function pointers and documentation strings, and
   a header file which describes the contents.  This only does the functions
   declared with DECLARE_INFO_COMMAND. */

#include "info.h"
#include "doc.h"

char *program_name = "makedoc";

static void fatal_file_error (char *filename);

/* Name of the header file which receives the declarations of functions. */
static char *funs_filename = "funs.h";

/* Name of the documentation to function pointer file. */
static char *doc_filename = "doc.c";

static char *doc_header[] = {
  "/* doc.c -- Generated structure containing function names and doc strings.",
  "",
  "   This file was automatically made from various source files with the",
  "   command `%s'.",
  "   DO NOT EDIT THIS FILE, only `%s.c'.",
  NULL
};

static char *doc_header_1[] = {
  "   An entry in the array FUNCTION_DOC_ARRAY is made for each command",
  "   found in the above files; each entry consists of a function pointer,",
  "   a string which is the user-visible name of the function,",
  "   and a string which documents its purpose. */",
  "",
  "#include \"info.h\"",
  "#include \"window.h\"",
  "#include \"funs.h\"",
  "",
  "FUNCTION_DOC function_doc_array[] = {",
  "",
  NULL
};

/* How to remember the locations of the functions found so that Emacs
   can use the information in a tag table. */
typedef struct {
  char *name;                   /* Name of the tag. */
  int line;                     /* Line number at which it appears. */
  long char_offset;             /* Character offset at which it appears. */
} EMACS_TAG;

typedef struct {
  char *filename;               /* Name of the file containing entries. */
  long entrylen;                /* Total number of characters in tag block. */
  EMACS_TAG **entries;          /* Entries found in FILENAME. */
  size_t entries_index;
  size_t entries_slots;
} EMACS_TAG_BLOCK;

EMACS_TAG_BLOCK **emacs_tags = NULL;
size_t emacs_tags_index = 0;
size_t emacs_tags_slots = 0;

#define DECLARATION_STRING "\nDECLARE_INFO_COMMAND"

static void process_one_file (char *filename, FILE *doc_stream,
                              FILE *funs_stream);
static void maybe_dump_tags (FILE *stream);
static FILE *must_fopen (char *filename, char *mode);
static void init_func_key (unsigned int val);
static unsigned int next_func_key (void);

int
main (int argc, char **argv)
{
  register int i;
  int tags_only = 0;
  FILE *funs_stream, *doc_stream;

#if STRIP_DOT_EXE
  {
    char *dot = strrchr (argv[0], '.');

    if (dot && FILENAME_CMP (dot, ".exe") == 0)
      *dot = 0;
  }
#endif

  for (i = 1; i < argc; i++)
    if (strcmp (argv[i], "-tags") == 0)
      {
        tags_only++;
        break;
      }

  if (tags_only)
    {
      funs_filename = NULL_DEVICE;
      doc_filename = NULL_DEVICE;
    }

  /* The order of these calls depends exactly on the order in the
     Makefile.{in,am}, or they might fail on filesystems with
     high-precision times; see also the fclose calls below.  */
  funs_stream = must_fopen (funs_filename, "w");
  doc_stream = must_fopen (doc_filename, "w");

  fprintf (funs_stream,
      "/* %s -- Generated declarations for Info commands. */\n\n"
      "#include \"info.h\"\n"
      "#include \"window.h\"\n",
      funs_filename);

  for (i = 0; doc_header[i]; i++)
    {
      fprintf (doc_stream, doc_header[i], argv[0], argv[0]);
      fprintf (doc_stream, "\n");
    }

  fprintf (doc_stream,
           _("   Source files groveled to make this file include:\n\n"));

  for (i = 1; i < argc; i++)
    fprintf (doc_stream, "\t%s\n", argv[i]);

  fprintf (doc_stream, "\n");
  for (i = 0; doc_header_1[i]; i++)
    fprintf (doc_stream, "%s\n", doc_header_1[i]);

  init_func_key(0);

  for (i = 1; i < argc; i++)
    {
      char *curfile;
      curfile = argv[i];

      if (*curfile == '-')
        continue;

      fprintf (doc_stream, "/* Commands found in \"%s\". */\n", curfile);
      fprintf (funs_stream, "\n/* Functions declared in \"%s\". */\n",
               curfile);

      process_one_file (curfile, doc_stream, funs_stream);
    }

  fprintf (doc_stream, "   { NULL, NULL, NULL, NULL }\n};\n");
  fprintf (funs_stream, "\n#define A_NCOMMANDS %u\n", next_func_key());

  /* The order of these calls also depends exactly on the order in the
   * Makefile.{in,am}; see the must_fopen calls above.  */
  fclose (funs_stream);
  fclose (doc_stream);

  if (tags_only)
    maybe_dump_tags (stdout);
  return 0;
}

/* Dumping out the contents of an Emacs tags table. */
static void
maybe_dump_tags (FILE *stream)
{
  size_t i;

  /* Emacs needs its TAGS file to be in Unix text format (i.e., only
     newline at end of every line, no CR), so when we generate a
     TAGS table, we must switch the output stream to binary mode.
     (If the table is written to a terminal, this is obviously not needed.) */
  SET_BINARY (fileno (stream));

  /* Print out the information for each block. */
  for (i = 0; i < emacs_tags_index; i++)
    {
      size_t j;
      register EMACS_TAG_BLOCK *block;
      register EMACS_TAG *etag;
      long block_len;

      block_len = 0;
      block = emacs_tags[i];

      /* Calculate the length of the dumped block first. */
      for (j = 0; j < block->entries_index; j++)
        {
          char digits[30];
          etag = block->entries[j];
          block_len += 3 + strlen (etag->name);
          sprintf (digits, "%d,%ld", etag->line, etag->char_offset);
          block_len += strlen (digits);
        }

      /* Print out the defining line. */
      fprintf (stream, "\f\n%s,%ld\n", block->filename, block_len);

      /* Print out the individual tags. */
      for (j = 0; j < block->entries_index; j++)
        {
          etag = block->entries[j];

          fprintf (stream, "%s,\177%d,%ld\n",
                   etag->name, etag->line, etag->char_offset);
        }
    }
}

/* Keeping track of names, line numbers and character offsets of functions
   found in source files. */
static EMACS_TAG_BLOCK *
make_emacs_tag_block (char *filename)
{
  EMACS_TAG_BLOCK *block;

  block = xmalloc (sizeof (EMACS_TAG_BLOCK));
  block->filename = xstrdup (filename);
  block->entrylen = 0;
  block->entries = NULL;
  block->entries_index = 0;
  block->entries_slots = 0;
  return block;
}

static void
add_tag_to_block (EMACS_TAG_BLOCK *block,
    char *name, int line, long int char_offset)
{
  EMACS_TAG *tag;

  tag = xmalloc (sizeof (EMACS_TAG));
  tag->name = name;
  tag->line = line;
  tag->char_offset = char_offset;
  add_pointer_to_array (tag, block->entries_index, block->entries,
                        block->entries_slots, 50);
}

/* Read the file represented by FILENAME into core, and search it for Info
   function declarations.  Output the declarations in various forms to the
   DOC_STREAM and FUNS_STREAM. */
static void
process_one_file (char *filename, FILE *doc_stream, FILE *funs_stream)
{
  int descriptor, decl_len;
  char *buffer, *decl_str;
  struct stat finfo;
  long offset;
  long file_size;
  EMACS_TAG_BLOCK *block;

  if (stat (filename, &finfo) == -1)
    fatal_file_error (filename);

  descriptor = open (filename, O_RDONLY, 0666);

  if (descriptor == -1)
    fatal_file_error (filename);

  file_size = (long) finfo.st_size;
  buffer = xmalloc (1 + file_size);
  /* On some systems, the buffer will actually contain
     less characters than the full file's size, because
     the CR characters are removed from line endings.  */
  file_size = read (descriptor, buffer, file_size);
  close (descriptor);

  offset = 0;
  decl_str = DECLARATION_STRING;
  decl_len = strlen (decl_str);

  block = make_emacs_tag_block (filename);

  while (1)
    {
      long point = 0;
      long line_start = 0;
      int line_number = 0;

      char *func, *doc;
      char *func_name;

      for (; offset < (file_size - decl_len); offset++)
        {
          if (buffer[offset] == '\n')
            {
              line_number++;
              line_start = offset + 1;
            }

          if (strncmp (buffer + offset, decl_str, decl_len) == 0)
            {
              offset += decl_len;
              point = offset;
              break;
            }
        }

      if (!point)
        break;

      /* Skip forward until we find the open paren. */
      while (point < file_size)
        {
          if (buffer[point] == '\n')
            {
              line_number++;
              line_start = point + 1;
            }
          else if (buffer[point] == '(')
            break;

          point++;
        }

      while (point++ < file_size)
        {
          if (!whitespace_or_newline (buffer[point]))
            break;
          else if (buffer[point] == '\n')
            {
              line_number++;
              line_start = point + 1;
            }
        }

      if (point >= file_size)
        break;

      /* Now looking at name of function.  Get it. */
      for (offset = point; buffer[offset] != ','; offset++);
      func = xmalloc (1 + (offset - point));
      strncpy (func, buffer + point, offset - point);
      func[offset - point] = '\0';

      /* Remember this tag in the current block. */
      {
        char *tag_name;

        tag_name = xmalloc (1 + (offset - line_start));
        strncpy (tag_name, buffer + line_start, offset - line_start);
        tag_name[offset - line_start] = '\0';
        add_tag_to_block (block, tag_name, line_number, point);
      }

      /* Generate the user-visible function name from the function's name. */
      {
        register int i;
        char *name_start;

        name_start = func;

        if (strncmp (name_start, "info_", 5) == 0)
          name_start += 5;

        func_name = xstrdup (name_start);

        /* Fix up "ea" commands. */
        if (strncmp (func_name, "ea_", 3) == 0)
          {
            char *temp_func_name;

            temp_func_name = xmalloc (10 + strlen (func_name));
            strcpy (temp_func_name, "echo_area_");
            strcat (temp_func_name, func_name + 3);
            free (func_name);
            func_name = temp_func_name;
          }

        for (i = 0; func_name[i]; i++)
          if (func_name[i] == '_')
            func_name[i] = '-';
      }

      /* Find doc string. */
      point = offset + 1;

      while (point < file_size)
        {
          if (buffer[point] == '\n')
            {
              line_number++;
              line_start = point + 1;
            }

          if (buffer[point] == '"')
            break;
          else
            point++;
        }

      offset = point + 1;

      while (offset < file_size)
        {
          if (buffer[offset] == '\n')
            {
              line_number++;
              line_start = offset + 1;
            }

          if (buffer[offset] == '\\')
            offset += 2;
          else if (buffer[offset] == '"')
            break;
          else
            offset++;
        }

      offset++;
      if (offset >= file_size)
        break;

      doc = xmalloc (1 + (offset - point));
      strncpy (doc, buffer + point, offset - point);
      doc[offset - point] = '\0';

      fprintf (doc_stream,
          "   { (VFunction *)%s, \"%s\", (FUNCTION_KEYSEQ *)0, %s },\n",
          func, func_name, doc);

      free (func_name);

      fprintf (funs_stream, "#define A_%s %u\n", func, next_func_key());
      fprintf (funs_stream,
          "extern void %s (WINDOW *window, int count);\n",
          func);
      free (func);
      free (doc);
    }
  free (buffer);

  /* If we created any tags, remember this file on our global list.  Otherwise,
     free the memory already allocated to it. */
  if (block->entries)
    add_pointer_to_array (block, emacs_tags_index, emacs_tags,
                          emacs_tags_slots, 10);
  else
    {
      free (block->filename);
      free (block);
    }
}

static void
fatal_file_error (char *filename)
{
  fprintf (stderr, _("Couldn't manipulate the file %s.\n"), filename);
  exit (EXIT_FAILURE);
}

static FILE *
must_fopen (char *filename, char *mode)
{
  FILE *stream;

  stream = fopen (filename, mode);
  if (!stream)
    fatal_file_error (filename);

  return stream;
}

static unsigned int func_key;

static void
init_func_key(unsigned int val)
{
	func_key = val;
}

static unsigned int
next_func_key(void)
{
	return func_key++;
}


code:
/* man.c: How to read and format man files.
   $Id$

   Copyright 1995, 1997, 1998, 1999, 2000, 2002, 2003, 2004, 2005, 2007, 2008,
   2009, 2011, 2012, 2013, 2014, 2015, 2016, 2017 Free Software Foundation,
   Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

   Originally written by Brian Fox Thu May  4 09:17:52 1995. */

#include "info.h"
#include "signals.h"
#if defined (HAVE_SYS_TIME_H)
#include <sys/time.h>
#endif
#if defined (HAVE_SYS_WAIT_H)
#include <sys/wait.h>
#endif

#include "tilde.h"
#include "nodes.h"
#include "man.h"
#include "variables.h"

#if !defined (_POSIX_VERSION)
#define pid_t int
#endif

#if defined (FD_SET)
#  if defined (hpux)
#    define fd_set_cast(x) (int *)(x)
#  else
#    define fd_set_cast(x) (fd_set *)(x)
#  endif /* !hpux */
#endif /* FD_SET */

#if STRIP_DOT_EXE
static char const * const exec_extensions[] = {
  ".exe", ".com", ".bat", ".btm", ".sh", ".ksh", ".pl", ".sed", "", NULL
};
#else
static char const * const exec_extensions[] = { "", NULL };
#endif

static REFERENCE **xrefs_of_manpage (NODE *node);
static char *read_from_fd (int fd);
static char *get_manpage_contents (char *pagename);

/* We store the contents of retrieved man pages in here. */
static NODE **manpage_nodes = 0;
size_t manpage_node_index = 0;
size_t manpage_node_slots = 0;

NODE *
get_manpage_node (char *pagename)
{
  NODE *node = 0, **n, *node2 = 0;
  char *page;

  if (manpage_node_index > 0)
    for (n = manpage_nodes; (node = *n); n++)
      if (!strcmp (node->nodename, pagename))
        break;

  /* Node was not found, so we have to create it. */
  if (!node)
    {
      node = info_create_node ();
      node->fullpath = MANPAGE_FILE_BUFFER_NAME;
      node->nodename = xstrdup (pagename);
      node->flags |= N_HasTagsTable | N_IsManPage;

      /* Save this node. */
      add_pointer_to_array (node, manpage_node_index,
                            manpage_nodes,
                            manpage_node_slots, 100);
    }

  /* Node wasn't found, or its contents were freed since last time. */
  if (!node->contents)
    {
      int plen;

      page = get_manpage_contents (pagename);
      if (!page)
        return 0;
      plen = strlen (page);

      node->contents = page;
      node->nodelen = plen;

      node->body_start = 0;
      node->references = xrefs_of_manpage (node);
      node->up = "(dir)";
    }

  node2 = xmalloc (sizeof (NODE));
  *node2 = *node;
  return node2;
}

/* Scan the list of directories in PATH looking for FILENAME.  If we find
   one that is an executable file, return it as a new string.  Otherwise,
   return a NULL pointer. */
static char *
executable_file_in_path (char *filename, char *path)
{
  struct stat finfo;
  char *temp_dirname;
  int statable, dirname_index;

  dirname_index = 0;

  while ((temp_dirname = extract_colon_unit (path, &dirname_index)))
    {
      char *temp;
      char *temp_end;
      int i;

      /* Expand a leading tilde if one is present. */
      if (*temp_dirname == '~')
        {
          char *expanded_dirname;

          expanded_dirname = tilde_expand_word (temp_dirname);
          free (temp_dirname);
          temp_dirname = expanded_dirname;
        }

      temp = xmalloc (34 + strlen (temp_dirname) + strlen (filename));
      strcpy (temp, temp_dirname);
      if (!IS_SLASH (temp[(strlen (temp)) - 1]))
        strcat (temp, "/");
      strcat (temp, filename);
      temp_end = temp + strlen (temp);

      free (temp_dirname);

      /* Look for FILENAME, possibly with any of the extensions
	 in EXEC_EXTENSIONS[].  */
      for (i = 0; exec_extensions[i]; i++)
	{
	  if (exec_extensions[i][0])
	    strcpy (temp_end, exec_extensions[i]);
	  statable = (stat (temp, &finfo) == 0);

	  /* If we have found a regular executable file, then use it. */
	  if ((statable) && (S_ISREG (finfo.st_mode)) &&
	      (access (temp, X_OK) == 0))
	    return temp;
	}

      free (temp);
    }
  return NULL;
}

/* Return the full pathname of the system man page formatter. */
static char *
find_man_formatter (void)
{
  char *man_command = getenv ("INFO_MAN_COMMAND");
  return man_command ? man_command :
                       executable_file_in_path ("man", getenv ("PATH"));
}

static char *manpage_pagename = NULL;
static char *manpage_section  = NULL;

static void
get_page_and_section (char *pagename)
{
  register int i;

  if (manpage_pagename)
    free (manpage_pagename);

  if (manpage_section)
    free (manpage_section);

  manpage_pagename = NULL;
  manpage_section  = NULL;

  for (i = 0; pagename[i] != '\0' && pagename[i] != '('; i++);

  manpage_pagename = xmalloc (1 + i);
  strncpy (manpage_pagename, pagename, i);
  manpage_pagename[i] = '\0';

  if (pagename[i] == '(')
    {
      int start;

      start = i + 1;

      for (i = start; pagename[i] != '\0' && pagename[i] != ')'; i++);

      manpage_section = xmalloc (1 + (i - start));
      strncpy (manpage_section, pagename + start, (i - start));
      manpage_section[i - start] = '\0';
    }
}

void
clean_manpage (char *manpage)
{
  mbi_iterator_t iter;
  size_t len = strlen (manpage);
  char *newpage = xmalloc (len + 1);
  char *np = newpage;
  int prev_len = 0;

  for (mbi_init (iter, manpage, len);
       mbi_avail (iter);
       mbi_advance (iter))
    {
      const char *cur_ptr = mbi_cur_ptr (iter);
      size_t cur_len = mb_len (mbi_cur (iter));

      if (cur_len == 1)
	{
	  if (*cur_ptr == '\b' || *cur_ptr == '\f')
	    {
	      if (np >= newpage + prev_len)
		np -= prev_len;
	    }
	  else if (ansi_escape (iter, &cur_len))
	    {
	      memcpy (np, cur_ptr, cur_len);
	      np += cur_len;
	      ITER_SETBYTES (iter, cur_len);
	    }
	  else if (show_malformed_multibyte_p || mbi_cur (iter).wc_valid)
	    *np++ = *cur_ptr;
	}
      else
	{
	  memcpy (np, cur_ptr, cur_len);
	  np += cur_len;
	}
      prev_len = cur_len;
    }
  *np = 0;

  strcpy (manpage, newpage);
  free (newpage);
}

static char *get_manpage_from_formatter (char *formatter_args[]);

static char *
get_manpage_contents (char *pagename)
{
  static char *formatter_args[4] = { NULL };
  char *formatted_page;

  if (formatter_args[0] == NULL)
    formatter_args[0] = find_man_formatter ();

  if (formatter_args[0] == NULL)
    return NULL;

  get_page_and_section (pagename);

  if (manpage_section)
    formatter_args[1] = manpage_section;
  else
    formatter_args[1] = "-a";

  formatter_args[2] = manpage_pagename;
  formatter_args[3] = NULL;

  formatted_page = get_manpage_from_formatter (formatter_args);

  /* If there was a section and the page wasn't found, try again
     without the section (e.g. "man 3X curses" versus "man -a curses"). */
  if (!formatted_page && manpage_section)
    {
      formatter_args[1] = "-a";
      formatted_page = get_manpage_from_formatter (formatter_args);
    }

  return formatted_page;
}

static char *
get_manpage_from_formatter (char *formatter_args[])
{
  char *formatted_page = NULL;
  int pipes[2];
  pid_t child;
  int formatter_status = 0;

  /* Open a pipe to this program, read the output, and save it away
     in FORMATTED_PAGE.  The reader end of the pipe is pipes[0]; the
     writer end is pipes[1]. */
#if PIPE_USE_FORK
  pipe (pipes);

  child = fork ();
  if (child == -1)
    return NULL;

  if (child != 0)
    {
      /* In the parent, close the writing end of the pipe, and read from
         the exec'd child. */
      close (pipes[1]);
      formatted_page = read_from_fd (pipes[0]);
      close (pipes[0]);
      wait (&formatter_status); /* Wait for child process to exit. */
    }
  else
    { /* In the child, close the read end of the pipe, make the write end
         of the pipe be stdout, and execute the man page formatter. */
      close (pipes[0]);
      freopen (NULL_DEVICE, "w", stderr);
      freopen (NULL_DEVICE, "r", stdin);
      dup2 (pipes[1], fileno (stdout));

      execv (formatter_args[0], formatter_args);

      /* If we get here, we couldn't exec, so close out the pipe and
         exit. */
      close (pipes[1]);
      exit (EXIT_SUCCESS);
    }
#else  /* !PIPE_USE_FORK */
  /* Cannot fork/exec, but can popen/pclose.  */
  {
    FILE *fpipe;
    char *cmdline;
    size_t cmdlen = 0;
    int save_stderr = dup (fileno (stderr));
    int fd_err = open (NULL_DEVICE, O_WRONLY, 0666);
    int i;

    for (i = 0; formatter_args[i]; i++)
      cmdlen += strlen (formatter_args[i]);
    /* Add-ons: 2 blanks, 2 quotes for the formatter program, 1
       terminating null character.  */
    cmdlen += 2 + 2 + 1;
    cmdline = xmalloc (cmdlen);

    if (fd_err > 2)
      dup2 (fd_err, fileno (stderr)); /* Don't print errors. */
    sprintf (cmdline, "\"%s\" %s %s",
	     formatter_args[0], formatter_args[1], formatter_args[2]);
    fpipe = popen (cmdline, "r");
    free (cmdline);
    if (fd_err > 2)
      close (fd_err);
    dup2 (save_stderr, fileno (stderr));
    if (fpipe == 0)
      return NULL;
    formatted_page = read_from_fd (fileno (fpipe));
    formatter_status = pclose (fpipe);
  }
#endif /* !PIPE_USE_FORK */

  if (!formatted_page)
    return 0;

  /* We could check the exit status of "man -a" to see if it successfully
     output a man page  However:
      * It is possible for "man -a" to output a man page and still to exit with
        a non-zero status.  This was found to happen when duplicate man pages
        were found.
      * "man" was found to exit with a zero status on Solaris 10 even when
        it found nothing.
     Hence, treat it as a success if more than three lines were output.  (A
     small amount of output could be error messages that were sent to standard
     output.) */
  {
    int i;
    char *p;
    p = formatted_page;
    for (i = 0; i < 3; i++)
      {
        p = strchr (p, '\n');
        if (!p)
          {
            free (formatted_page);
            return NULL;
          }
        p++;
      }
  }

  /* If we have the page, then clean it up. */
  clean_manpage (formatted_page);

  return formatted_page;
}

/* Return pointer to bytes read from file descriptor FD.  Return value to be
   freed by caller. */
static char *
read_from_fd (int fd)
{
  struct timeval timeout;
  char *buffer = NULL;
  int bsize = 0;
  int bindex = 0;
  int select_result;
#if defined (FD_SET)
  fd_set read_fds;

  timeout.tv_sec = 15;
  timeout.tv_usec = 0;

  FD_ZERO (&read_fds);
  FD_SET (fd, &read_fds);

  select_result = select (fd + 1, fd_set_cast (&read_fds), 0, 0, &timeout);
#else /* !FD_SET */
  select_result = 1;
#endif /* !FD_SET */

  switch (select_result)
    {
    case 0:
    case -1:
      break;

    default:
      {
        int amount_read;
        int done = 0;

        while (!done)
          {
            while ((bindex + 1024) > (bsize))
              buffer = xrealloc (buffer, (bsize += 1024));
            buffer[bindex] = '\0';

            amount_read = read (fd, buffer + bindex, 1023);

            if (amount_read < 0)
              {
                done = 1;
              }
            else
              {
                bindex += amount_read;
                buffer[bindex] = '\0';
                if (amount_read == 0)
                  done = 1;
              }
          }
      }
    }

  if ((buffer != NULL) && (*buffer == '\0'))
    {
      free (buffer);
      buffer = NULL;
    }

  return buffer;
}

static REFERENCE **
xrefs_of_manpage (NODE *node)
{
  SEARCH_BINDING s;

  REFERENCE **refs = NULL;
  size_t refs_index = 0;
  size_t refs_slots = 0;
  long position;

  /* Initialize reference list to have a single null entry. */
  refs = calloc(1, sizeof (REFERENCE *));
  refs_slots = 1;

  s.buffer = node->contents;
  s.start = 0;
  s.flags = 0;
  s.end = node->nodelen;

  /* Exclude first line, which often looks like:
CAT(1)                           User Commands                          CAT(1)
  */
  s.start = strcspn (node->contents, "\n");

  /* Build a list of references.  A reference is alphabetic characters
     followed by non-whitespace text within parenthesis leading with a digit. */
  while (search_forward ("(", &s, &position) == search_success)
    {
      register int name, name_end;
      int section, section_end;

      name = position;
      if (name == 0)
        goto skip;
      else
        name--;

      /* Go to the start of a sequence of non-whitespace characters,
         checking the characters are those that should appear in a man
         page name. */
      for (; name > 0; name--)
        if (whitespace_or_newline (s.buffer[name])
            || (!isalnum (s.buffer[name])
                && s.buffer[name] != '_'
                && s.buffer[name] != '.'
                && s.buffer[name] != '-'
                && s.buffer[name] != '\033'
                && s.buffer[name] != '['))
          break;

      /* Check if reached start of buffer. */
      if (name == 0)
        goto skip;

      /* Check for invalid sequence in name. */
      if (!whitespace_or_newline (s.buffer[name]))
        goto skip;

      name++;

      if (name == position)
        goto skip; /* Whitespace immediately before '('. */

      /* 'name' is now at the start of a sequence of non-whitespace
         characters.  If we are on an ECMA-48 SGR escape sequence, skip
         past it. */
      if (s.buffer[name] == '\033' && s.buffer[name + 1] == '[')
        {
          name += 2;
          name += strspn (s.buffer + name, "0123456789;");
          if (s.buffer[name] == 'm')
            name++;
          else
            goto skip;
        }

      /* Set name_end to the end of the name, but before any SGR sequence. */
      for (name_end = name; name_end < position; name_end++)
        if (!isalnum (s.buffer[name_end])
            && s.buffer[name_end] != '_'
            && s.buffer[name_end] != '.'
            && s.buffer[name_end] != '-')
          break;

      section = position;
      section_end = 0;

      /* Look for one or two characters within the brackets, the
         first of which must be a non-zero digit and the second a letter. */
      if (!isdigit (s.buffer[section + 1])
          || s.buffer[section + 1] == '0')
        ;
      else if (!s.buffer[section + 2])
        ; /* end of buffer */
      else if (s.buffer[section + 2] == ')')
        section_end = section + 3;
      else if (!isalpha(s.buffer[section + 2]))
        ;
      else if (s.buffer[section + 3] == ')')
        section_end = section + 4;

      if (section_end)
        {
          REFERENCE *entry;
          int len = name_end - name + section_end - section;

          entry = xmalloc (sizeof (REFERENCE));
          entry->label = xcalloc (1, 1 + len);
          strncpy (entry->label, s.buffer + name, name_end - name);
          strncpy (entry->label + strlen (entry->label),
                   s.buffer + section,
                   section_end - section);

          entry->filename = xstrdup (MANPAGE_FILE_BUFFER_NAME);
          entry->nodename = xstrdup (entry->label);
          entry->line_number = 0;
          entry->start = name;
          entry->end = section_end;
          entry->type = REFERENCE_XREF;

          add_pointer_to_array (entry, refs_index, refs, refs_slots, 10);
        }

skip:
      s.start = position + 1;
    }

  return refs;
}


code:
/* man.h: Defines and external function declarations for man.c.
   $Id$

   Copyright 1993, 1997, 2004, 2007, 2013, 2014 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

   Originally written by Brian Fox. */

#ifndef INFO_MAN_H
#define INFO_MAN_H

#define MANPAGE_FILE_BUFFER_NAME "*manpages*"

extern NODE *get_manpage_node (char *pagename);

#endif /* INFO_MAN_H */


code:
/* nodemenu.c -- produce a menu of all visited nodes.
   $Id$

   Copyright 1993, 1997, 1998, 2002, 2003, 2004, 2007, 2008, 2011,
   2013, 2014, 2015, 2016 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

   Originally written by Brian Fox. */

#include "info.h"
#include "session.h"
#include "echo-area.h"
#include "variables.h"

static NODE *get_visited_nodes (void);

/* Return a line describing the format of a node information line. */
static const char *
nodemenu_format_info (void)
{
  /* TRANSLATORS: The "\n* Menu:\n\n" part of this should not be translated, as
     it is part of the Info syntax. */
  return _("\n* Menu:\n\n\
  (File)Node                        Lines   Size   Containing File\n\
  ----------                        -----   ----   ---------------");
}

/* Produce a formatted line of information about NODE.  Here is what we want
   the output listing to look like:

* Menu:
  (File)Node                        Lines   Size   Containing File
  ----------                        -----   ----   ---------------
* (emacs)Buffers::                  48      2230   /usr/gnu/info/emacs/emacs-1
* (autoconf)Writing configure.in::  123     58789  /usr/gnu/info/autoconf/autoconf-1
* (dir)Top::                        40      589    /usr/gnu/info/dir
*/
static char *
format_node_info (NODE *node)
{
  register int i;
  char *containing_file;
  static struct text_buffer line_buffer = { 0 };

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

  if (node->subfile)
    containing_file = node->subfile;
  else
    containing_file = node->fullpath;

  if (!containing_file || !*containing_file)
    text_buffer_printf (&line_buffer, "* %s::", node->nodename);
  else
    text_buffer_printf (&line_buffer, "* (%s)%s::",
                        filename_non_directory (node->fullpath),
                        node->nodename);

  for (i = text_buffer_off (&line_buffer); i < 36; i++)
    text_buffer_add_char (&line_buffer, ' ');

  {
    int lines = 1;

    for (i = 0; i < node->nodelen; i++)
      if (node->contents[i] == '\n')
        lines++;

    text_buffer_printf (&line_buffer, "%d", lines);
  }

  text_buffer_add_char (&line_buffer, ' ');
  for (i = text_buffer_off (&line_buffer); i < 44; i++)
    text_buffer_add_char (&line_buffer, ' ');
  text_buffer_printf (&line_buffer, "%ld", node->nodelen);

  if (containing_file)
    {
      for (i = text_buffer_off (&line_buffer); i < 51; i++)
        text_buffer_add_char (&line_buffer, ' ');
      text_buffer_printf (&line_buffer, containing_file);
    }

  return xstrdup (text_buffer_base (&line_buffer));
}

/* Little string comparison routine for qsort (). */
static int
compare_strings (const void *entry1, const void *entry2)
{
  char **e1 = (char **) entry1;
  char **e2 = (char **) entry2;

  return mbscasecmp (*e1, *e2);
}

/* The name of the nodemenu node. */
static char *nodemenu_nodename = "*Node Menu*";

/* Produce an informative listing of all the visited nodes, and return it
   in a newly allocated node. */
static NODE *
get_visited_nodes (void)
{
  register int i;
  WINDOW *info_win;
  NODE *node;
  char **lines = NULL;
  size_t lines_index = 0, lines_slots = 0;
  struct text_buffer message;

  for (info_win = windows; info_win; info_win = info_win->next)
    {
      for (i = 0; i < info_win->hist_index; i++)
        {
          NODE *history_node = info_win->hist[i]->node;

          /* We skip mentioning "*Node Menu*" nodes. */
          if (strcmp (history_node->nodename, nodemenu_nodename) == 0)
            continue;

          if (history_node)
            {
              char *line;

              line = format_node_info (history_node);
              add_pointer_to_array (line, lines_index, lines, lines_slots, 20);
            }
        }
    }

  /* Sort the array of information lines, if there are any. */
  if (lines)
    {
      register int j, newlen;
      char **temp;

      qsort (lines, lines_index, sizeof (char *), compare_strings);

      /* Delete duplicates. */
      for (i = 0, newlen = 1; i < lines_index - 1; i++)
        {
	  /* Use FILENAME_CMP here, since the most important piece
	     of info in each line is the file name of the node.  */
          if (FILENAME_CMP (lines[i], lines[i + 1]) == 0)
            {
              free (lines[i]);
              lines[i] = NULL;
            }
          else
            newlen++;
        }

      /* We have free ()'d and marked all of the duplicate slots.
         Copy the live slots rather than pruning the dead slots. */
      temp = xmalloc ((1 + newlen) * sizeof (char *));
      for (i = 0, j = 0; i < lines_index; i++)
        if (lines[i])
          temp[j++] = lines[i];

      temp[j] = NULL;
      free (lines);
      lines = temp;
      lines_index = newlen;
    }

  text_buffer_init (&message);

  text_buffer_printf (&message, "\n");
  text_buffer_printf (&message,
    "%s", replace_in_documentation
     (_("Here is the menu of nodes you have recently visited.\n\
Select one from this menu, or use '\\[history-node]' in another window.\n"), 0));

  text_buffer_printf (&message, "%s\n", nodemenu_format_info ());

  for (i = 0; (lines != NULL) && (i < lines_index); i++)
    {
      text_buffer_printf (&message, "%s\n", lines[i]);
      free (lines[i]);
    }

  if (lines)
    free (lines);

  node = text_buffer_to_node (&message);
  scan_node_contents (node, 0, 0);

  return node;
}

DECLARE_INFO_COMMAND (list_visited_nodes,
   _("Make a window containing a menu of all of the currently visited nodes"))
{
  WINDOW *new;
  NODE *node;

  /* If a window is visible and showing the buffer list already, re-use it. */
  for (new = windows; new; new = new->next)
    {
      node = new->node;

      if (internal_info_node_p (node) &&
          (strcmp (node->nodename, nodemenu_nodename) == 0))
        break;
    }

  /* If we couldn't find an existing window, try to use the next window
     in the chain. */
  if (!new)
    {
      if (window->next)
        new = window->next;
      /* If there is more than one window, wrap around. */
      else if (window != windows)
        new = windows;
    }

  /* If we still don't have a window, make a new one to contain the list. */
  if (!new)
    new = window_make_window ();

  /* If we couldn't make a new window, use this one. */
  if (!new)
    new = window;

  /* Lines do not wrap in this window. */
  new->flags |= W_NoWrap;
  node = get_visited_nodes ();
  name_internal_node (node, xstrdup (nodemenu_nodename));
  node->flags |= N_WasRewritten;

  info_set_node_of_window (new, node);
  active_window = new;
}

DECLARE_INFO_COMMAND (select_visited_node,
      _("Select a node which has been previously visited in a visible window"))
{
  char *line;
  NODE *node;

  node = get_visited_nodes ();

  line = info_read_completing_in_echo_area (_("Select visited node: "),
                                            node->references);

  window = active_window;

  if (!line)
    /* User aborts, just quit. */
    info_abort_key (window, 0);
  else if (*line)
    {
      REFERENCE *entry;

      /* Find the selected label in the references. */
      entry = info_get_menu_entry_by_label (node, line, 0);

      if (!entry)
        /* This shouldn't happen, because LINE was in the completion list
           built from the list of references. */
        info_error (_("The reference disappeared! (%s)."), line);
      else
        info_select_reference (window, entry);
    }

  free (line);
  free (node);
}