code:
/* echo-area.c -- how to read a line in the echo area.
   $Id$

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

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

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

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

   Originally written by Brian Fox. */

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

/* Non-zero means that C-g was used to quit reading input. */
int info_aborted_echo_area = 0;

/* Non-zero means that the echo area is being used to read input. */
int echo_area_is_active = 0;

/* The address of the last command executed in the echo area. */
static VFunction *ea_last_executed_command = NULL;

/* Non-zero means that the last command executed while reading input
   killed some text. */
int echo_area_last_command_was_kill = 0;

/* Variables which hold on to the current state of the input line. */
static char input_line[1 + EA_MAX_INPUT]; /* Contents of echo area, including
                                             any prompt. */
static int input_line_point;     /* Offset into input_line of point */
static int input_line_beg;       /* End of prompt, and start of user input. */
static int input_line_end;       /* End of user input. */

static NODE input_line_node = {
  NULL, NULL, NULL, input_line,
  EA_MAX_INPUT, 0, N_IsInternal
};

static void echo_area_initialize_node (void);
static void push_echo_area (void), pop_echo_area (void);
static int echo_area_stack_contains_completions_p (void);

static void ea_kill_text (int from, int to);
static void ea_remove_text (int from, int to);

/* Non-zero means we force the user to complete. */
static int echo_area_must_complete_p = 0;
static int completions_window_p (WINDOW *window);

/* If non-null, this is a window which was specifically created to display
   possible completions output.  We remember it so we can delete it when
   appropriate. */
static WINDOW *echo_area_completions_window = NULL;

/* Variables which keep track of the window which was active prior to
   entering the echo area. */
static WINDOW *calling_window = NULL;
static NODE *calling_window_node = NULL;

/* Remember the node and pertinent variables of the calling window. */
static void
remember_calling_window (WINDOW *window)
{
  /* Only do this if the calling window is not the completions window, or,
     if it is the completions window and there is no other window. */
  if (!completions_window_p (window) ||
      ((window == windows) && !(window->next)))
    {
      calling_window = window;
      calling_window_node = window->node;
    }
}

/* Restore the caller's window so that it shows the node that it was showing
   on entry to info_read_xxx_echo_area (). */
static void
restore_calling_window (void)
{
  register WINDOW *win, *compwin = NULL;

  /* If the calling window is still visible, and it is the window that
     we used for completions output, then restore the calling window. */
  for (win = windows; win; win = win->next)
    {
      if (completions_window_p (win))
        compwin = win;

      if (win == calling_window && win == compwin)
        {
          forget_node (win);
          compwin = NULL;
          break;
        }
    }

  /* Delete the completions window if it is still present, it isn't the
     last window on the screen, and there aren't any prior echo area reads
     pending which created a completions window. */
  if (compwin)
    {
      if ((compwin != windows || windows->next)
          && !echo_area_stack_contains_completions_p ())
        {
          info_delete_window_internal (compwin);
        }
    }
}

/* Set up a new input line with PROMPT. */
static void
initialize_input_line (const char *prompt)
{
  strcpy (input_line, prompt);
  input_line_beg = input_line_end = input_line_point = strlen (prompt);
}

static char *
echo_area_after_read (void)
{
  char *return_value;

  if (info_aborted_echo_area)
    {
      info_aborted_echo_area = 0;
      return_value = NULL;
    }
  else
    {
      if (input_line_beg == input_line_end)
        return_value = xstrdup ("");
      else
        {
          int line_len = input_line_end - input_line_beg;
          return_value = xmalloc (1 + line_len);
          strncpy (return_value, &input_line[input_line_beg], line_len);
          return_value[line_len] = '\0';
        }
    }
  return return_value;
}

static void
read_and_dispatch_in_echo_area (void)
{
  while (1)
    {
      int count;
      VFunction *cmd;
      int lk = 0;

      lk = echo_area_last_command_was_kill;
      echo_area_prep_read ();

      if (!info_any_buffered_input_p ())
        display_update_display ();

      display_cursor_at_point (active_window);

      /* Do the selected command. */
      cmd = read_key_sequence (echo_area_keymap, 0, 0, 1, &count);
      if (cmd)
        {
          (*cmd) (the_echo_area, count);
          ea_last_executed_command = cmd;
        }
      else
        ea_last_executed_command = 0;

      /* Echo area commands that do killing increment the value of
         ECHO_AREA_LAST_COMMAND_WAS_KILL.  Thus, if there is no
         change in the value of this variable, the last command
         executed was not a kill command. */
      if (lk == echo_area_last_command_was_kill)
        echo_area_last_command_was_kill = 0;

      if (cmd == ea_newline || info_aborted_echo_area)
        {
          ea_last_executed_command = NULL;
          break;
        }
    }
}

/* Read a line of text in the echo area.  Return a malloc ()'ed string,
   or NULL if the user aborted out of this read.  PROMPT is the prompt
   to print before reading the line. */
char *
info_read_in_echo_area (const char *prompt)
{
  char *line;

  /* If the echo area is already active, remember the current state. */
  if (echo_area_is_active)
    push_echo_area ();

  /* Initialize our local variables. */
  initialize_input_line (prompt);

  /* Initialize the echo area for the first (but maybe not the last) time. */
  echo_area_initialize_node ();

  /* Save away the original node of this window, and the window itself,
     so echo area commands can temporarily use this window. */
  remember_calling_window (active_window);

  /* Let the rest of Info know that the echo area is active. */
  echo_area_is_active++;
  active_window = the_echo_area;

  /* Read characters in the echo area. */
  read_and_dispatch_in_echo_area ();

  window_clear_echo_area ();

  echo_area_is_active--;

  /* Restore the original active window and show point in it. */
  active_window = calling_window;
  restore_calling_window ();
  display_cursor_at_point (active_window);
  fflush (stdout);

  /* Get the value of the line. */
  line = echo_area_after_read ();

  /* If there is a previous loop waiting for us, restore it now. */
  if (echo_area_is_active)
    pop_echo_area ();

  /* Return the results to the caller. */
  return line;
}

/* (re) Initialize the echo area node. */
static void
echo_area_initialize_node (void)
{
  register int i;

  for (i = input_line_end; (unsigned int) i < sizeof (input_line); i++)
    input_line[i] = ' ';

  input_line[i - 1] = '\n';
  window_set_node_of_window (the_echo_area, &input_line_node);
  input_line[input_line_end] = '\n';
}

/* Prepare to read characters in the echo area.  This can initialize the
   echo area node, but its primary purpose is to side effect the input
   line buffer contents. */
void
echo_area_prep_read (void)
{
  if (the_echo_area->node != &input_line_node)
    echo_area_initialize_node ();

  the_echo_area->point = input_line_point;
  input_line[input_line_end] = '\n';

  /* Mark the line map as invalid.  This causes window_compute_line_map to
     recalculate it when it is called via display_cursor_at_point below.
     Otherwise adding or removing multi-column characters (like tabs) lead
     to incorrect cursor positioning. */
  the_echo_area->line_map.used = 0;

  display_update_one_window (the_echo_area);
  display_cursor_at_point (active_window);
}

/* **************************************************************** */
/*                                                                  */
/*                   Echo Area Movement Commands                    */
/*                                                                  */
/* **************************************************************** */

DECLARE_INFO_COMMAND (ea_forward, _("Move forward a character"))
{
  if (count < 0)
    ea_backward (window, -count);
  else
    {
      mbi_iterator_t iter;
      mbi_init (iter, input_line + input_line_point,
                input_line_end - input_line_point);
      while (mbi_avail (iter) && count--)
        {
          mbi_advance (iter);
          input_line_point = mbi_cur_ptr (iter) - input_line;
          if (input_line_point > input_line_end)
            input_line_point = input_line_end;
        }
    }
}

DECLARE_INFO_COMMAND (ea_backward, _("Move backward a character"))
{
  if (count < 0)
    ea_forward (window, -count);
  else
    {
      char *ptr = input_line + input_line_point;
      while (count--)
        {
          /* Go back one character.  Go back by bytes until we look at a valid
             multi-byte sequence. */
          ptr = input_line + input_line_point;
          while (ptr > input_line)
            {
              ptr--;
              if ((long) mbrlen (ptr,
                                 input_line + input_line_point - ptr, 0) > 0)
                break;
            }
          input_line_point = ptr - input_line;
          if (input_line_point < input_line_beg)
            input_line_point = input_line_beg;
        }
    }
}

DECLARE_INFO_COMMAND (ea_beg_of_line, _("Move to the start of this line"))
{
  input_line_point = input_line_beg;
}

DECLARE_INFO_COMMAND (ea_end_of_line, _("Move to the end of this line"))
{
  input_line_point = input_line_end;
}

#define alphabetic(c) (islower (c) || isupper (c) || isdigit (c))

/* Move forward a word in the input line. */
DECLARE_INFO_COMMAND (ea_forward_word, _("Move forward a word"))
{
  int c;

  if (count < 0)
    ea_backward_word (window, -count);
  else
    {
      while (count--)
        {
          if (input_line_point == input_line_end)
            return;

          /* If we are not in a word, move forward until we are in one.
             Then, move forward until we hit a non-alphabetic character. */
          c = input_line[input_line_point];

          if (!alphabetic (c))
            {
              while (++input_line_point < input_line_end)
                {
                  c = input_line[input_line_point];
                  if (alphabetic (c))
                    break;
                }
            }

          if (input_line_point == input_line_end)
            return;

          while (++input_line_point < input_line_end)
            {
              c = input_line[input_line_point];
              if (!alphabetic (c))
                break;
            }
        }
    }
}

DECLARE_INFO_COMMAND (ea_backward_word, _("Move backward a word"))
{
  int c;

  if (count < 0)
    ea_forward_word (window, -count);
  else
    {
      while (count--)
        {
          if (input_line_point == input_line_beg)
            return;

          /* Like ea_forward_word (), except that we look at the
             characters just before point. */

          c = input_line[input_line_point - 1];

          if (!alphabetic (c))
            {
              while ((--input_line_point) != input_line_beg)
                {
                  c = input_line[input_line_point - 1];
                  if (alphabetic (c))
                    break;
                }
            }

          while (input_line_point != input_line_beg)
            {
              c = input_line[input_line_point - 1];
              if (!alphabetic (c))
                break;
              else
                --input_line_point;
            }
        }
    }
}

DECLARE_INFO_COMMAND (ea_delete, _("Delete the character under the cursor"))
{
  if (count < 0)
    ea_rubout (window, -count);
  else
    {
      int orig_point;
      if (input_line_point == input_line_end)
        return;

      orig_point = input_line_point;
      ea_forward (window, count);
      if (ea_explicit_arg || count > 1)
        ea_kill_text (orig_point, input_line_point);
      else
        ea_remove_text (orig_point, input_line_point);
      input_line_point = orig_point;
    }
}

DECLARE_INFO_COMMAND (ea_rubout, _("Delete the character behind the cursor"))
{
  if (count < 0)
    ea_delete (window, -count);
  else
    {
      int start;

      if (input_line_point == input_line_beg)
        {
          /* Abort echo area read if backspace at start of area and user input
             is empty. */
          if (input_line_beg == input_line_end)
            info_aborted_echo_area = 1;
          return;
        }

      start = input_line_point;
      ea_backward (window, count);

      if (ea_explicit_arg || count > 1)
        ea_kill_text (start, input_line_point);
      else
        ea_delete (window, count);
    }
}

DECLARE_INFO_COMMAND (ea_abort, _("Cancel or quit operation"))
{
  /* If any text, just discard it, and restore the calling window's node.
     If no text, quit. */
  if (input_line_end != input_line_beg)
    {
      terminal_ring_bell ();
      input_line_end = input_line_point = input_line_beg;
      if (calling_window->node != calling_window_node)
        restore_calling_window ();
    }
  else
    info_aborted_echo_area = 1;
}

DECLARE_INFO_COMMAND (ea_newline, _("Accept (or force completion of) this line"))
{
  /* Stub does nothing.  Simply here to see if it has been executed. */
}

DECLARE_INFO_COMMAND (ea_quoted_insert, _("Insert next character verbatim"))
{
  int character;

  character = get_another_input_key ();

  if (character >= 256 || character < 0)
    return;

  ea_insert (window, count, character);
}

void
ea_insert (WINDOW *window, int count, int key)
{
  register int i;

  if ((input_line_end + 1) == EA_MAX_INPUT)
    {
      terminal_ring_bell ();
      return;
    }

  for (i = input_line_end + 1; i != input_line_point; i--)
    input_line[i] = input_line[i - 1];

  input_line[input_line_point] = key;
  input_line_point++;
  input_line_end++;
  window_line_map_init (window);
}

DECLARE_INFO_COMMAND (ea_tab_insert, _("Insert a TAB character"))
{
  ea_insert (window, count, '\t');
}

/* Swap characters in INPUT_LINE.  The first starts at C1 and ends at C1E, the
   second starts at C2 and ends at C2E, with C1 < C1E <= C2 < C2E. */
static void
ea_swap_chars (int c1, int c1e, int c2, int c2e)
{
  int len1, len2;
  char *tmp;

  len1 = c1e - c1;
  len2 = c2e - c2;

  if (len1 >= len2)
    {
      /* Save first character. */
      tmp = xmalloc (len1);
      memcpy (tmp, input_line + c1, len1);

      /* Move the second character to where the first was. */
      memcpy (input_line + c1, input_line + c2, len2);

      /* Shift the part in between the characters backwards. */
      memmove (input_line + c1 + len2, input_line + c1e, c2 - c1e);

      /* Restore the first character at the end. */
      memcpy (input_line + c2 - (len1 - len2), tmp, len1);
      free (tmp);
    }
  else /* len2 > len1 */
    {
      /* Save second character. */
      tmp = xmalloc (len2);
      memcpy (tmp, input_line + c2, len2);

      /* Move first character to end of second character. */
      memcpy (input_line + c2e - len1, input_line + c1, len1);

      /* Shift the part in between the characters forwards. */
      memmove (input_line + c1e + (len2 - len1), input_line + c1e, c2 - c1e);

      /* Place the second character at the beginning. */
      memcpy (input_line + c1, tmp, len2);
      free (tmp);
    }
}

/* Transpose the characters at point.  If point is at the end of the line,
   then transpose the characters before point. */
DECLARE_INFO_COMMAND (ea_transpose_chars, _("Transpose characters at point"))
{
  while (count)
    {
      if (input_line_point == input_line_end || count < 0)
        {
          /* Swap two characters before point. */
          int c1, c2, c2e;
          c2e = input_line_point;

          ea_backward (window, 1);
          c2 = input_line_point;

          ea_backward (window, 1);
          c1 = input_line_point;

          if (c1 != c2) /* There are two characters in this line. */
            ea_swap_chars (c1, c2, c2, c2e);

          if (count > 0)
            /* Restore point. */
            input_line_point = c2e;
          else
            input_line_point = c1 + c2e - c2;
        }
      else
        {
          int c1, c2, c2e;

          c2 = input_line_point;

          ea_forward (window, 1);
          c2e = input_line_point;
          if (c2e == c2)
            return; /* Shouldn't happen. */

          input_line_point = c2;
          ea_backward (window, 1);
          c1 = input_line_point;
          if (c1 == c2e)
            return; /* Can't go earlier in line. */

          ea_swap_chars (c1, c2, c2, c2e);

          /* Set point is after swapped pair. */
          input_line_point = c2e;
        }

      if (count < 0)
        count++;
      else
        count--;
    }
}

/* **************************************************************** */
/*                                                                  */
/*                   Echo Area Killing and Yanking                  */
/*                                                                  */
/* **************************************************************** */

static char **kill_ring = NULL;
static int kill_ring_index = 0; /* Number of kills appearing in KILL_RING. */
static int kill_ring_slots = 0; /* Number of slots allocated to KILL_RING. */
static int kill_ring_loc = 0;   /* Location of current yank pointer. */

/* The largest number of kills that we remember at one time. */
static int max_retained_kills = 15;

DECLARE_INFO_COMMAND (ea_yank, _("Yank back the contents of the last kill"))
{
  register int i;
  register char *text;

  if (!kill_ring_index)
    {
      inform_in_echo_area (_("Kill ring is empty"));
      return;
    }

  text = kill_ring[kill_ring_loc];

  for (i = 0; text[i]; i++)
    ea_insert (window, 1, text[i]);
}

/* If the last command was yank, or yank_pop, and the text just before
   point is identical to the current kill item, then delete that text
   from the line, rotate the index down, and yank back some other text. */
DECLARE_INFO_COMMAND (ea_yank_pop, _("Yank back a previous kill"))
{
  register int len;

  if (((ea_last_executed_command != (VFunction *) ea_yank) &&
       (ea_last_executed_command != (VFunction *) ea_yank_pop)) ||
      (kill_ring_index == 0))
    return;

  len = strlen (kill_ring[kill_ring_loc]);

  /* Delete the last yanked item from the line. */
  {
    register int i, counter;

    counter = input_line_end - input_line_point;

    for (i = input_line_point - len; counter; i++, counter--)
      input_line[i] = input_line[i + len];

    input_line_end -= len;
    input_line_point -= len;
  }

  /* Get a previous kill, and yank that. */
  kill_ring_loc--;
  if (kill_ring_loc < 0)
    kill_ring_loc = kill_ring_index - 1;

  ea_yank (window, count);
}

/* Delete the text from point to end of line. */
DECLARE_INFO_COMMAND (ea_kill_line, _("Kill to the end of the line"))
{
  if (count < 0)
    {
      ea_kill_text (input_line_point, input_line_beg);
      input_line_point = input_line_beg;
    }
  else
    ea_kill_text (input_line_point, input_line_end);
}

/* Delete the text from point to beg of line. */
DECLARE_INFO_COMMAND (ea_backward_kill_line,
                      _("Kill to the beginning of the line"))
{
  if (count < 0)
    ea_kill_text (input_line_point, input_line_end);
  else
    {
      ea_kill_text (input_line_point, input_line_beg);
      input_line_point = input_line_beg;
    }
}

/* Delete from point to the end of the current word. */
DECLARE_INFO_COMMAND (ea_kill_word, _("Kill the word following the cursor"))
{
  int orig_point = input_line_point;

  if (count < 0)
    ea_backward_kill_word (window, -count);
  else
    {
      ea_forward_word (window, count);

      if (input_line_point != orig_point)
        ea_kill_text (orig_point, input_line_point);

      input_line_point = orig_point;
    }
  window_line_map_init (window);
}

/* Delete from point to the start of the current word. */
DECLARE_INFO_COMMAND (ea_backward_kill_word,
                      _("Kill the word preceding the cursor"))
{
  int orig_point = input_line_point;

  if (count < 0)
    ea_kill_word (window, -count);
  else
    {
      ea_backward_word (window, count);

      if (input_line_point != orig_point)
        ea_kill_text (orig_point, input_line_point);
    }
  window_line_map_init (window);
}

/* Remove text from offsets FROM to TO.  Unlike 'ea_kill_text' nothing is
   saved in the kill ring. */
static void
ea_remove_text (int from, int to)
{
  int distance, i, counter;
  counter = input_line_end - to;
  distance = to - from;

  for (i = from; counter; i++, counter--)
    input_line[i] = input_line[i + distance];
  input_line_end -= distance;
}

/* The way to kill something.  This appends or prepends to the last
   kill, if the last command was a kill command.  If FROM is less
   than TO, then the killed text is appended to the most recent kill,
   otherwise it is prepended.  If the last command was not a kill command,
   then a new slot is made for this kill. */
static void
ea_kill_text (int from, int to)
{
  register int distance;
  int killing_backwards, slot;
  char *killed_text;

  killing_backwards = (from > to);

  /* If killing backwards, reverse the values of FROM and TO. */
  if (killing_backwards)
    {
      int temp = from;
      from = to;
      to = temp;
    }

  /* Remember the text that we are about to delete. */
  distance = to - from;
  killed_text = xmalloc (1 + distance);
  strncpy (killed_text, &input_line[from], distance);
  killed_text[distance] = '\0';

  /* Actually delete the text from the line. */
  ea_remove_text (from, to);

  /* If the last command was a kill, append or prepend the killed text to
     the last command's killed text. */
  if (echo_area_last_command_was_kill)
    {
      char *old, *new;

      slot = kill_ring_loc;
      old = kill_ring[slot];
      new = xmalloc (1 + strlen (old) + strlen (killed_text));

      if (killing_backwards)
        {
          /* Prepend TEXT to current kill. */
          strcpy (new, killed_text);
          strcat (new, old);
        }
      else
        {
          /* Append TEXT to current kill. */
          strcpy (new, old);
          strcat (new, killed_text);
        }

      free (old);
      free (killed_text);
      kill_ring[slot] = new;
    }
  else
    {
      /* Try to store the kill in a new slot, unless that would cause there
         to be too many remembered kills. */
      slot = kill_ring_index;

      if (slot == max_retained_kills)
        slot = 0;

      if (slot + 1 > kill_ring_slots)
        kill_ring = xrealloc (kill_ring,
			      (kill_ring_slots += max_retained_kills)
			      * sizeof (char *));

      if (slot != kill_ring_index)
        free (kill_ring[slot]);
      else
        kill_ring_index++;

      kill_ring[slot] = killed_text;

      kill_ring_loc = slot;
    }

  /* Notice that the last command was a kill. */
  echo_area_last_command_was_kill++;
}

/* **************************************************************** */
/*                                                                  */
/*                      Echo Area Completion                        */
/*                                                                  */
/* **************************************************************** */

/* Pointer to an array of REFERENCE to complete over. */
REFERENCE **echo_area_completion_items = NULL;

/* Sorted array of REFERENCE * which is the possible completions found in
   the variable echo_area_completion_items.  If there is only one element,
   it is the only possible completion. */
static REFERENCE **completions_found = NULL;
static size_t completions_found_index = 0;
static size_t completions_found_slots = 0;

/* The lowest common denominator found while completing. */
static REFERENCE *LCD_completion;

/* Function to choose which references to offer as completion options. */
static reference_bool_fn completion_exclude_func = 0;

/* Internal functions used by the user calls. */
static void build_completions (void);
static void completions_must_be_rebuilt (void);

/* Variable which holds the output of completions. */
static NODE *possible_completions_output_node = NULL;

static char *compwin_name = "*Completions*";

/* Return non-zero if WINDOW is a window used for completions output. */
static int
completions_window_p (WINDOW *window)
{
  int result = 0;

  if (internal_info_node_p (window->node) &&
      (strcmp (window->node->nodename, compwin_name) == 0))
    result = 1;

  return result;
}

/* Workhorse for completion readers.  If FORCE is non-zero, the user cannot
   exit unless the line read completes, or is empty.  Use EXCLUDE_FUNC to
   exclude items in COMPLETIONS. */
char *
info_read_completing_internal (const char *prompt, REFERENCE **completions,
                               int force, reference_bool_fn exclude_func)
{
  char *line;

  /* If the echo area is already active, remember the current state. */
  if (echo_area_is_active)
    push_echo_area ();

  echo_area_must_complete_p = force;

  /* Initialize our local variables. */
  initialize_input_line (prompt);

  /* Initialize the echo area for the first (but maybe not the last) time. */
  echo_area_initialize_node ();

  /* Save away the original node of this window, and the window itself,
     so echo area commands can temporarily use this window. */
  remember_calling_window (active_window);

  /* Save away the list of items to complete over. */
  echo_area_completion_items = completions;
  completions_must_be_rebuilt ();
  completion_exclude_func = exclude_func;

  active_window = the_echo_area;
  echo_area_is_active++;
  window_line_map_init (active_window);

  /* Read characters in the echo area. */
  while (1)
    {
      read_and_dispatch_in_echo_area ();

      line = echo_area_after_read ();

      /* Force the completion to take place if the user hasn't accepted
         a default or aborted, and if FORCE is active. */
      if (force && line && *line && completions)
        {
          size_t i;

          build_completions ();

          /* If there is only one completion, then make the line be that
             completion. */
          if (completions_found_index == 1)
            {
              free (line);
              line = xstrdup (completions_found[0]->label);
              break;
            }

          /* If one of the completions matches exactly, then that is okay, so
             return the current line. */
          for (i = 0; i < completions_found_index; i++)
            if (mbscasecmp (completions_found[i]->label, line) == 0)
              {
                free (line);
                line = xstrdup (completions_found[i]->label);
                break;
              }

          /* If no match, go back and try again. */
          if (i == completions_found_index)
            {
              if (!completions_found_index)
                inform_in_echo_area (_("No completions"));
              else
                inform_in_echo_area (_("Not complete"));
              continue;
            }
        }
      break;
    }
  echo_area_is_active--;
  window_clear_echo_area ();

  /* Restore the original active window and show point in it. */
  active_window = calling_window;
  restore_calling_window ();
  display_cursor_at_point (active_window);
  fflush (stdout);

  echo_area_completion_items = NULL;
  completions_must_be_rebuilt ();

  /* If there is a previous loop waiting for us, restore it now. */
  if (echo_area_is_active)
    pop_echo_area ();

  return line;
}

/* Read a line in the echo area with completion over COMPLETIONS. */
char *
info_read_completing_in_echo_area (const char *prompt, REFERENCE **completions)
{
  return info_read_completing_internal (prompt, completions, 1, 0);
}

/* Read a line in the echo area allowing completion over COMPLETIONS, but
   not requiring it. */
char *
info_read_maybe_completing (const char *prompt, REFERENCE **completions)
{
  return info_read_completing_internal (prompt, completions, 0, 0);
}

/* Read a line in the echo area with completion over COMPLETIONS, using
   EXCLUDE to exclude items from the completion list. */
char *
info_read_completing_in_echo_area_with_exclusions (const char *prompt,
     REFERENCE **completions, reference_bool_fn exclude)
{
  return info_read_completing_internal (prompt, completions, 1, exclude);
}

DECLARE_INFO_COMMAND (ea_possible_completions, _("List possible completions"))
{
  build_completions ();

  if (!completions_found_index)
    {
      terminal_ring_bell ();
      inform_in_echo_area (_("No completions"));
    }
  else if (completions_found_index == 1)
    {
      inform_in_echo_area (_("Sole completion"));
    }
  else
    {
      size_t i, l;
      size_t limit, iterations, max_label = 0;
      struct text_buffer message;

      text_buffer_init (&message);
      text_buffer_printf (&message, ngettext ("%d completion:\n",
					  "%d completions:\n",
					  completions_found_index),
				completions_found_index);

      /* Find the maximum length of a label. */
      for (i = 0; i < completions_found_index; i++)
        {
          int len = strlen (completions_found[i]->label);
          if (len > max_label)
            max_label = len;
        }

      max_label += 4;

      /* Find out how many columns we should print in. */
      limit = calling_window->width / max_label;
      if (limit != 1 && (limit * max_label == calling_window->width))
        limit--;

      /* Avoid a possible floating exception.  If max_label > width then
         the limit will be 0 and a divide-by-zero fault will result. */
      if (limit == 0)
        limit = 1;

      /* How many iterations of the printing loop? */
      iterations = (completions_found_index + (limit - 1)) / limit;

      /* Watch out for special case.  If the number of completions is less
         than LIMIT, then just do the inner printing loop. */
      if (completions_found_index < limit)
        iterations = 1;

      /* Print the sorted items, up-and-down alphabetically. */
      for (i = 0; i < iterations; i++)
        {
          register int j;

          for (j = 0, l = i; j < limit; j++)
            {
              if (l >= completions_found_index)
                break;
              else
                {
                  char *label;
                  int printed_length, k;

                  label = completions_found[l]->label;
                  printed_length = strlen (label);
                  text_buffer_printf (&message, "%s", label);

                  if (j + 1 < limit)
                    {
                      for (k = 0; k < max_label - printed_length; k++)
                        text_buffer_printf (&message, " ");
                    }
                }
              l += iterations;
            }
          text_buffer_printf (&message, "\n");
        }

      /* Make a new node to hold onto possible completions.  Don't destroy
         dangling pointers. */
      {
        NODE *temp;

        temp = text_buffer_to_node (&message);
        name_internal_node (temp, xstrdup (compwin_name));
        possible_completions_output_node = temp;
        possible_completions_output_node->flags |= N_WasRewritten;
      }

      /* Find a suitable window for displaying the completions output.
         First choice is an existing window showing completions output.
         If there is only one window, and it is large, make another
         (smaller) window, and use that one.  Otherwise, use the caller's
         window. */
      {
        WINDOW *compwin;

        compwin = get_internal_info_window (compwin_name);

        if (!compwin)
          {
            /* If we can split the window to display most of the completion
               items, then do so. */
            if (calling_window->height > (iterations * 2)
		&& calling_window->height / 2 >= WINDOW_MIN_SIZE)
              {
                remember_calling_window (calling_window);

                active_window = calling_window;
                compwin = window_make_window ();
                window_change_window_height
                  (compwin, -(compwin->height - (iterations + 2)));

                echo_area_completions_window = compwin;
                active_window = the_echo_area;
              }
            else
              compwin = calling_window;
          }

        /* Clear any completion nodes already showing from the window history.
           This could happen if the user presses TAB more than once. */
        while (compwin->node && (compwin->node->flags & N_IsInternal)
               && !strcmp (compwin->node->nodename, compwin_name))
          forget_node (compwin);

        info_set_node_of_window (compwin, possible_completions_output_node);

        display_update_display ();
      }
    }
}

DECLARE_INFO_COMMAND (ea_complete, _("Insert completion"))
{
  if (ea_last_executed_command == (VFunction *) ea_complete)
    {
      ea_possible_completions (window, count);
      return;
    }

  input_line_point = input_line_end;
  build_completions ();

  if (!completions_found_index)
    terminal_ring_bell ();
  else if (LCD_completion->label[0] == '\0')
    ea_possible_completions (window, count);
  else
    {
      register int i;
      input_line_point = input_line_end = input_line_beg;
      for (i = 0; LCD_completion->label[i]; i++)
        ea_insert (window, 1, LCD_completion->label[i]);
    }
}

/* Utility REFERENCE used to store possible LCD. */
static REFERENCE LCD_reference = {
    NULL, NULL, NULL, 0, 0, 0
};

static void remove_completion_duplicates (void);

/* Variables which remember the state of the most recent call
   to build_completions (). */
static char *last_completion_request = NULL;
static REFERENCE **last_completion_items = NULL;

/* How to tell the completion builder to reset internal state. */
static void
completions_must_be_rebuilt (void)
{
  free (last_completion_request);
  last_completion_request = NULL;
  last_completion_items = NULL;
}

/* Build a list of possible completions from echo_area_completion_items,
   and the contents of input_line. */
static void
build_completions (void)
{
  size_t i;
  int len;
  register REFERENCE *entry;
  char *request;
  int informed_of_lengthy_job = 0;

  /* If there are no items to complete over, exit immediately. */
  if (!echo_area_completion_items)
    {
      completions_found_index = 0;
      LCD_completion = NULL;
      return;
    }

  /* Check to see if this call to build completions is the same as the last
     call to build completions. */
  len = input_line_end - input_line_beg;
  request = xmalloc (1 + len);
  strncpy (request, &input_line[input_line_beg], len);
  request[len] = '\0';

  if (last_completion_request && last_completion_items &&
      last_completion_items == echo_area_completion_items &&
      (strcmp (last_completion_request, request) == 0))
    {
      free (request);
      return;
    }

  free (last_completion_request);
  last_completion_request = request;
  last_completion_items = echo_area_completion_items;

  /* Always start at the beginning of the list. */
  completions_found_index = 0;
  LCD_completion = NULL;

  for (i = 0; (entry = echo_area_completion_items[i]); i++)
    {
      /* Skip certain items (for example, we might only want
         a list of menu items). */
      if (completion_exclude_func && completion_exclude_func (entry))
        continue;

      if (mbsncasecmp (request, entry->label, len) == 0)
        add_pointer_to_array (entry, completions_found_index,
                              completions_found, completions_found_slots,
                              20);

      if (!informed_of_lengthy_job && completions_found_index > 100)
        {
          informed_of_lengthy_job = 1;
          window_message_in_echo_area (_("Building completions..."));
        }
    }

  if (!completions_found_index)
    return;

  /* Sort and prune duplicate entries from the completions array. */
  remove_completion_duplicates ();

  /* If there is only one completion, just return that. */
  if (completions_found_index == 1)
    {
      LCD_completion = completions_found[0];
      return;
    }

  /* Find the least common denominator. */
  {
    long shortest = 100000;

    for (i = 1; i < completions_found_index; i++)
      {
        register int j;
        int c1, c2;

        for (j = 0;
             (c1 = tolower (completions_found[i - 1]->label[j]))
             && (c2 = tolower (completions_found[i]->label[j]));
             j++)
          if (c1 != c2)
            break;

        if (shortest > j)
          shortest = j;
      }

    free (LCD_reference.label);
    LCD_reference.label = xmalloc (1 + shortest);
    /* Since both the sorting done inside remove_completion_duplicates
       and all the comparisons above are case-insensitive, it's
       possible that the completion we are going to return is
       identical to what the user typed but for the letter-case.  This
       is confusing, since the user could type FOOBAR<TAB> and get her
       string change letter-case for no good reason.  So try to find a
       possible completion whose letter-case is identical, and if so,
       use that.  */
    if (completions_found_index > 1)
      {
	int req_len = strlen (request);

        for (i = 0; i < completions_found_index; i++)
          if (strncmp (request, completions_found[i]->label, req_len) == 0)
            break;
        /* If none of the candidates match exactly, use the first one.  */
        if (i >= completions_found_index)
          i = 0;
      }
    strncpy (LCD_reference.label, completions_found[i]->label, shortest);
    LCD_reference.label[shortest] = '\0';
    LCD_completion = &LCD_reference;
  }

  if (informed_of_lengthy_job)
    echo_area_initialize_node ();
}

/* Function called by qsort. */
static int
compare_references (const void *entry1, const void *entry2)
{
  REFERENCE **e1 = (REFERENCE **) entry1;
  REFERENCE **e2 = (REFERENCE **) entry2;

  return mbscasecmp ((*e1)->label, (*e2)->label);
}

/* Prune duplicate entries from COMPLETIONS_FOUND. */
static void
remove_completion_duplicates (void)
{
  size_t i, j;
  REFERENCE **temp;
  int newlen;

  if (!completions_found_index)
    return;

  /* Sort the items. */
  qsort (completions_found, completions_found_index, sizeof (REFERENCE *),
         compare_references);

  for (i = 0, newlen = 1; i < completions_found_index - 1; i++)
    {
      if (strcmp (completions_found[i]->label,
                  completions_found[i + 1]->label) == 0)
        completions_found[i] = NULL;
      else
        newlen++;
    }

  /* We have marked all the dead slots.  It is faster to copy the live slots
     twice than to prune the dead slots one by one. */
  temp = xmalloc ((1 + newlen) * sizeof (REFERENCE *));
  for (i = 0, j = 0; i < completions_found_index; i++)
    if (completions_found[i])
      temp[j++] = completions_found[i];

  for (i = 0; i < newlen; i++)
    completions_found[i] = temp[i];

  completions_found[i] = NULL;
  completions_found_index = newlen;
  free (temp);
}

/* Scroll the "other" window.  If there is a window showing completions, scroll
   that one, otherwise scroll the window which was active on entering the read
   function. */
DECLARE_INFO_COMMAND (ea_scroll_completions_window, _("Scroll the completions window"))
{
  WINDOW *compwin;

  compwin = get_internal_info_window (compwin_name);

  if (!compwin)
    compwin = calling_window;

  /* Let info_scroll_forward () do the work, and print any messages that
     need to be displayed. */
  info_scroll_forward (compwin, count);
}

/* Function which gets called when an Info window is deleted while the
   echo area is active.  WINDOW is the window which has just been deleted. */
void
echo_area_inform_of_deleted_window (WINDOW *window)
{
  /* If this is the calling_window, forget what we remembered about it. */
  if (window == calling_window)
    {
      if (active_window != the_echo_area)
        remember_calling_window (active_window);
      else
        remember_calling_window (windows);
    }

  /* If this window was the echo_area_completions_window, then notice that
     the window has been deleted. */
  if (window == echo_area_completions_window)
    echo_area_completions_window = NULL;
}

/* **************************************************************** */
/*                                                                  */
/*                 Pushing and Popping the Echo Area                */
/*                                                                  */
/* **************************************************************** */

/* Push and Pop the echo area. */
typedef struct {
  char *line;
  REFERENCE **comp_items;
  int point, beg, end;
  int must_complete;
  NODE node;
  WINDOW *compwin;
} PUSHED_EA;

static PUSHED_EA **pushed_echo_areas = NULL;
static size_t pushed_echo_areas_index = 0;
static size_t pushed_echo_areas_slots = 0;

/* Pushing the echo_area has a side effect of zeroing the completion_items. */
static void
push_echo_area (void)
{
  PUSHED_EA *pushed;

  pushed = xmalloc (sizeof (PUSHED_EA));
  pushed->line = xstrdup (input_line);
  pushed->point = input_line_point;
  pushed->beg = input_line_beg;
  pushed->end = input_line_end;
  pushed->node = input_line_node;
  pushed->comp_items = echo_area_completion_items;
  pushed->must_complete = echo_area_must_complete_p;
  pushed->compwin = echo_area_completions_window;

  add_pointer_to_array (pushed, pushed_echo_areas_index, pushed_echo_areas,
                        pushed_echo_areas_slots, 4);

  echo_area_completion_items = NULL;
}

static void
pop_echo_area (void)
{
  PUSHED_EA *popped;

  popped = pushed_echo_areas[--pushed_echo_areas_index];

  strcpy (input_line, popped->line);
  free (popped->line);
  input_line_point = popped->point;
  input_line_beg = popped->beg;
  input_line_end = popped->end;
  input_line_node = popped->node;
  echo_area_completion_items = popped->comp_items;
  echo_area_must_complete_p = popped->must_complete;
  echo_area_completions_window = popped->compwin;
  completions_must_be_rebuilt ();

  /* If the completion window no longer exists, forget about it. */
  if (echo_area_completions_window)
    {
      register WINDOW *win;

      for (win = windows; win; win = win->next)
        if (echo_area_completions_window == win)
          break;

      /* If the window wasn't found, then it has already been deleted. */
      if (!win)
        echo_area_completions_window = NULL;
    }

  free (popped);
}

/* Returns non-zero if any of the prior stacked calls to read in the echo
   area produced a completions window. */
static int
echo_area_stack_contains_completions_p (void)
{
  size_t i;

  for (i = 0; i < pushed_echo_areas_index; i++)
    if (pushed_echo_areas[i]->compwin)
      return 1;

  return 0;
}

/* **************************************************************** */
/*                                                                  */
/*             Error Messages While Reading in Echo Area            */
/*                                                                  */
/* **************************************************************** */

#if defined (HAVE_SYS_TIME_H)
#  include <sys/time.h>
#  define HAVE_STRUCT_TIMEVAL
#endif /* HAVE_SYS_TIME_H */

#if !defined (FD_SET) && defined (__MINGW32__)
#  define WIN32_LEAN_AND_MEAN
#  include <windows.h>
#endif

static void
pause_or_input (void)
{
#ifdef FD_SET
  struct timeval timer;
  fd_set readfds;

  FD_ZERO (&readfds);
  FD_SET (fileno (stdin), &readfds);
  timer.tv_sec = 2;
  timer.tv_usec = 0;
  select (fileno (stdin) + 1, &readfds, NULL, NULL, &timer);
#elif defined (__MINGW32__)
  /* This is signalled on key release, so flush it and wait again. */
  WaitForSingleObject (GetStdHandle (STD_INPUT_HANDLE), 2000);
  FlushConsoleInputBuffer (GetStdHandle (STD_INPUT_HANDLE));
  WaitForSingleObject (GetStdHandle (STD_INPUT_HANDLE), 2000);
#endif /* FD_SET */
}

/* Print MESSAGE right after the end of the current line, and wait
   for input or a couple of seconds, whichever comes first.  Then flush the
   informational message that was printed. */
void
inform_in_echo_area (const char *message)
{
  int i;
  char *text;
  int avail = EA_MAX_INPUT + 1 - input_line_end;

  text = xstrdup (message);
  for (i = 0; text[i] && text[i] != '\n' && i < avail; i++)
    ;
  text[i] = 0;

  echo_area_initialize_node ();
  sprintf (&input_line[input_line_end], "%s[%s]\n",
           echo_area_is_active ? " ": "", text);
  free (text);
  the_echo_area->point = input_line_point;
  display_update_one_window (the_echo_area);
  display_cursor_at_point (active_window);
  fflush (stdout);
  pause_or_input ();
  echo_area_initialize_node ();
}


code:
/* echo-area.h -- Functions used in reading information from the echo area.
   $Id$

   Copyright 1993, 1997, 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. */

#ifndef INFO_ECHO_AREA_H
#define INFO_ECHO_AREA_H

#define EA_MAX_INPUT 256

extern int echo_area_is_active, info_aborted_echo_area;

/* Non-zero means that the last command executed while reading input
   killed some text. */
extern int echo_area_last_command_was_kill;

extern REFERENCE **echo_area_completion_items;

void inform_in_echo_area (const char *message);
void echo_area_inform_of_deleted_window (WINDOW *window);
void echo_area_prep_read (void);

typedef int (*reference_bool_fn) (REFERENCE *);

char *info_read_completing_internal (const char *prompt,
    REFERENCE **completions, int force, reference_bool_fn exclude);

/* Read a line of text in the echo area.  Return a malloc ()'ed string,
   or NULL if the user aborted out of this read.  PROMPT, if
   non-null, is a prompt to print before reading the line. */
char *info_read_in_echo_area (const char *prompt);

/* Read a line in the echo area with completion over COMPLETIONS. */
char *info_read_completing_in_echo_area (const char *prompt,
                                         REFERENCE **completions);

/* Read a line in the echo area allowing completion over COMPLETIONS, but
   not requiring it. */
char *info_read_maybe_completing (const char *prompt, REFERENCE **completions);

/* Read a line in the echo area with completion over COMPLETIONS, using
   EXCLUDE to exclude items from the completion list. */
char *
info_read_completing_in_echo_area_with_exclusions (const char *prompt,
    REFERENCE **completions, reference_bool_fn exclude);

void ea_insert (WINDOW *window, int count, int key);
void ea_quoted_insert (WINDOW *window, int count);
void ea_beg_of_line (WINDOW *window, int count);
void ea_backward (WINDOW *window, int count);
void ea_delete (WINDOW *window, int count);
void ea_end_of_line (WINDOW *window, int count);
void ea_forward (WINDOW *window, int count);
void ea_abort (WINDOW *window, int count);
void ea_rubout (WINDOW *window, int count);
void ea_complete (WINDOW *window, int count);
void ea_newline (WINDOW *window, int count);
void ea_kill_line (WINDOW *window, int count);
void ea_backward_kill_line (WINDOW *window, int count);
void ea_transpose_chars (WINDOW *window, int count);
void ea_yank (WINDOW *window, int count);
void ea_tab_insert (WINDOW *window, int count);
void ea_possible_completions (WINDOW *window, int count);
void ea_backward_word (WINDOW *window, int count);
void ea_kill_word (WINDOW *window, int count);
void ea_forward_word (WINDOW *window, int count);
void ea_yank_pop (WINDOW *window, int count);
void ea_backward_kill_word (WINDOW *window, int count);
void ea_scroll_completions_window (WINDOW *window, int count);

#endif /* not INFO_ECHO_AREA_H */

repo_name: mthl/texinfo

license: mit


code:
/* filesys.c -- filesystem specific functions.
   $Id$

   Copyright 1993, 1997, 1998, 2000, 2002, 2003, 2004, 2007, 2008, 2009, 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 "tilde.h"
#include "filesys.h"
#include "tag.h"
#include "session.h"

/* Local to this file. */
static char *info_file_in_path (char *filename, struct stat *finfo);
char *info_add_extension (char *dirname, char *fname,
                                 struct stat *finfo);

static char *filesys_read_compressed (char *pathname, size_t *filesize);

/* Return the command string that would be used to decompress FILENAME. */
static char *filesys_decompressor_for_file (char *filename);
static int compressed_filename_p (char *filename);

typedef struct
{
  char *suffix;
  char *decompressor;
} COMPRESSION_ALIST;

static char *info_suffixes[] = {
  ".info",
  "-info",
  "/index",
  ".inf",       /* 8+3 file on filesystem which supports long file names */
#ifdef __MSDOS__
  /* 8+3 file names strike again...  */
  ".in",        /* for .inz, .igz etc. */
  ".i",
#endif
  "",
  NULL
};

static COMPRESSION_ALIST compress_suffixes[] = {
#if STRIP_DOT_EXE
  { ".gz", "gunzip" },
  { ".lz", "lunzip" },
#else
  { ".gz", "gzip -d" },
  { ".lz", "lzip -d" },
#endif
  { ".xz", "unxz" },
  { ".bz2", "bunzip2" },
  { ".z", "gunzip" },
  { ".lzma", "unlzma" },
  { ".Z", "uncompress" },
  { ".Y", "unyabba" },
#ifdef __MSDOS__
  { "gz", "gunzip" },
  { "z", "gunzip" },
#endif
  { NULL, NULL }
};

/* Look for the filename PARTIAL in INFOPATH in order to find the correct file.
   Return file name and set *FINFO with information about file.  If it
   can't find the file, it returns NULL, and sets filesys_error_number.
   Return value should be freed by caller. */
char *
info_find_fullpath (char *partial, struct stat *finfo)
{
  char *fullpath = 0;
  struct stat dummy;

  debug(1, (_("looking for file \"%s\""), partial));

  if (!finfo)
    finfo = &dummy;

  filesys_error_number = 0;

  if (!partial || !*partial)
    return 0;

  /* IS_SLASH and IS_ABSOLUTE defined in ../system.h. */

  /* If path is absolute already, see if it needs an extension. */
  if (IS_ABSOLUTE (partial)
      || partial[0] == '.' && IS_SLASH(partial[1]))
    {
      fullpath = info_add_extension (0, partial, finfo);
    }

  /* Tilde expansion.  FIXME: Not needed, because done by shell. */
  else if (partial[0] == '~')
    {
      partial = tilde_expand_word (partial);
      fullpath = info_add_extension (0, partial, finfo);
    }

  /* If just a simple name element, look for it in the path. */
  else
    fullpath = info_file_in_path (partial, finfo);

  if (!fullpath)
    filesys_error_number = ENOENT;

  return fullpath;
}

/* Scan the directories in search path looking for FILENAME.  If we find
   one that is a regular file, return it as a new string.  Otherwise, return
   a NULL pointer.  Set *FINFO with information about file. */
char *
info_file_find_next_in_path (char *filename, int *path_index, struct stat *finfo)
{
  struct stat dummy;

  /* Used for output of stat in case the caller doesn't care about
     its value. */
  if (!finfo)
    finfo = &dummy;

  /* Reject ridiculous cases up front, to prevent infinite recursion
     later on.  E.g., someone might say "info '(.)foo'"...  */
  if (!*filename || STREQ (filename, ".") || STREQ (filename, ".."))
    return NULL;

  while (1)
    {
      char *dirname, *with_extension = 0;

      dirname = infopath_next (path_index);
      if (!dirname)
        break;

      debug(1, (_("looking for file %s in %s"), filename, dirname));

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

      with_extension = info_add_extension (dirname, filename, finfo);

      if (with_extension)
        {
          if (!IS_ABSOLUTE (with_extension))
            {
              /* Prefix "./" to it. */
              char *s;
              asprintf (&s, "%s%s", "./", with_extension);
              free (with_extension);
              return s;
            }
          else
            return with_extension;
        }
    }
  return NULL;
}

/* Return full path of first Info file known as FILENAME in
   search path.  If relative to current directory, precede it with './'. */
static char *
info_file_in_path (char *filename, struct stat *finfo)
{
  int i = 0;
  return info_file_find_next_in_path (filename, &i, finfo);
}

/* Look for a file called FILENAME in a directory called DIRNAME, adding file
   extensions if necessary.  FILENAME can be an absolute path or a path
   relative to the current directory, in which case DIRNAME should be
   null.  Return it as a new string; otherwise return a NULL pointer. */
char *
info_add_extension (char *dirname, char *filename, struct stat *finfo)
{
  char *try_filename;
  register int i, pre_suffix_length = 0;
  struct stat dummy;

  if (!finfo)
    finfo = &dummy;

  if (dirname)
    pre_suffix_length += strlen (dirname);

  pre_suffix_length += strlen (filename);

  /* Add enough space for any file extensions at end. */
  try_filename = xmalloc (pre_suffix_length + 30);
  try_filename[0] = '\0';

  if (dirname)
    {
      strcpy (try_filename, dirname);
      if (!IS_SLASH (try_filename[(strlen (try_filename)) - 1]))
        {
          strcat (try_filename, "/");
          pre_suffix_length++;
        }
    }

  strcat (try_filename, filename);

  for (i = 0; info_suffixes[i]; i++)
    {
      int statable;

      strcpy (try_filename + pre_suffix_length, info_suffixes[i]);
      statable = (stat (try_filename, finfo) == 0);

      /* If we have found a regular file, then use that.  Else, if we
         have found a directory, look in that directory for this file. */
      if (statable)
        {
          if (S_ISREG (finfo->st_mode))
            {
              debug(1, (_("found file %s"), try_filename));
              return try_filename;
            }
          else if (S_ISDIR (finfo->st_mode))
            {
              char *newpath, *new_filename;

              newpath = xstrdup (try_filename);
              new_filename = info_add_extension (newpath, filename, finfo);

              free (newpath);
              if (new_filename)
                {
                  free (try_filename);
                  debug(1, (_("found file %s"), new_filename));
                  return new_filename;
                }
            }
        }
      else
        {
          /* Add various compression suffixes to the name to see if
             the file is present in compressed format. */
          register int j, pre_compress_suffix_length;

          pre_compress_suffix_length = strlen (try_filename);

          for (j = 0; compress_suffixes[j].suffix; j++)
            {
              strcpy (try_filename + pre_compress_suffix_length,
                      compress_suffixes[j].suffix);

              statable = (stat (try_filename, finfo) == 0);
              if (statable && (S_ISREG (finfo->st_mode)))
                {
                  debug(1, (_("found file %s"), try_filename));
                  return try_filename;
                }
            }
        }
    }
  /* Nothing was found. */
  free (try_filename);
  return 0;
}

/* Read the contents of PATHNAME, returning a buffer with the contents of
   that file in it, and returning the size of that buffer in FILESIZE.
   If the file turns out to be compressed, set IS_COMPRESSED to non-zero.
   If the file cannot be read, set filesys_error_number and return a NULL
   pointer.  Set *FINFO with information about file. */
char *
filesys_read_info_file (char *pathname, size_t *filesize,
			struct stat *finfo, int *is_compressed)
{
  size_t fsize;
  char *contents;

  fsize = filesys_error_number = 0;

  stat (pathname, finfo);
  fsize = (long) finfo->st_size;

  if (compressed_filename_p (pathname))
    {
      *is_compressed = 1;
      contents = filesys_read_compressed (pathname, &fsize);
    }
  else
    {
      int descriptor;

      *is_compressed = 0;
      descriptor = open (pathname, O_RDONLY | O_BINARY, 0666);

      /* If the file couldn't be opened, give up. */
      if (descriptor < 0)
        {
          filesys_error_number = errno;
          return NULL;
        }

      /* Try to read the contents of this file. */
      contents = xmalloc (1 + fsize);
      if ((read (descriptor, contents, fsize)) != fsize)
        {
	  filesys_error_number = errno;
	  close (descriptor);
	  free (contents);
	  return NULL;
        }
      contents[fsize] = 0;
      close (descriptor);
    }

  *filesize = fsize;

  return contents;
}

/* Typically, pipe buffers are 4k. */
#define BASIC_PIPE_BUFFER (4 * 1024)

/* We use some large multiple of that. */
#define FILESYS_PIPE_BUFFER_SIZE (16 * BASIC_PIPE_BUFFER)

static char *
filesys_read_compressed (char *pathname, size_t *filesize)
{
  FILE *stream;
  char *command, *decompressor;
  char *contents = NULL;

  *filesize = filesys_error_number = 0;

  decompressor = filesys_decompressor_for_file (pathname);

  if (!decompressor)
    return NULL;

  command = xmalloc (15 + strlen (pathname) + strlen (decompressor));
  /* Explicit .exe suffix makes the diagnostics of `popen'
     better on systems where COMMAND.COM is the stock shell.  */
  sprintf (command, "%s%s < %s",
	   decompressor, STRIP_DOT_EXE ? ".exe" : "", pathname);

  if (info_windows_initialized_p)
    {
      char *temp;

      temp = xmalloc (5 + strlen (command));
      sprintf (temp, "%s...", command);
      message_in_echo_area ("%s", temp);
      free (temp);
    }

  stream = popen (command, FOPEN_RBIN);
  free (command);

  /* Read chunks from this file until there are none left to read. */
  if (stream)
    {
      size_t offset, size;
      char *chunk;

      offset = size = 0;
      chunk = xmalloc (FILESYS_PIPE_BUFFER_SIZE);

      while (1)
        {
          size_t bytes_read;

          bytes_read = fread (chunk, 1, FILESYS_PIPE_BUFFER_SIZE, stream);

          if (bytes_read + offset >= size)
            contents = xrealloc
              (contents, size += (2 * FILESYS_PIPE_BUFFER_SIZE));

          memcpy (contents + offset, chunk, bytes_read);
          offset += bytes_read;
          if (bytes_read != FILESYS_PIPE_BUFFER_SIZE)
            break;
        }

      free (chunk);
      if (pclose (stream) == -1)
	{
	  if (contents)
	    free (contents);
	  contents = NULL;
	  filesys_error_number = errno;
	}
      else
	{
	  contents = xrealloc (contents, 1 + offset);
	  contents[offset] = '\0';
	  *filesize = offset;
	}
    }
  else
    {
      filesys_error_number = errno;
    }

  if (info_windows_initialized_p)
    unmessage_in_echo_area ();
  return contents;
}

/* Return non-zero if FILENAME belongs to a compressed file. */
static int
compressed_filename_p (char *filename)
{
  char *decompressor;

  /* Find the final extension of this filename, and see if it matches one
     of our known ones. */
  decompressor = filesys_decompressor_for_file (filename);

  if (decompressor)
    return 1;
  else
    return 0;
}

/* Return the command string that would be used to decompress FILENAME. */
static char *
filesys_decompressor_for_file (char *filename)
{
  register int i;
  char *extension = NULL;

  /* Find the final extension of FILENAME, and see if it appears in our
     list of known compression extensions. */
  for (i = strlen (filename) - 1; i > 0; i--)
    if (filename[i] == '.')
      {
        extension = filename + i;
        break;
      }

  if (!extension)
    return NULL;

  for (i = 0; compress_suffixes[i].suffix; i++)
    if (FILENAME_CMP (extension, compress_suffixes[i].suffix) == 0)
      return compress_suffixes[i].decompressor;

#if defined (__MSDOS__)
  /* If no other suffix matched, allow any extension which ends
     with `z' to be decompressed by gunzip.  Due to limited 8+3 DOS
     file namespace, we can expect many such cases, and supporting
     every weird suffix thus produced would be a pain.  */
  if (extension[strlen (extension) - 1] == 'z' ||
      extension[strlen (extension) - 1] == 'Z')
    return "gunzip";
#endif

  return NULL;
}

/* The number of the most recent file system error. */
int filesys_error_number = 0;

/* A function which returns a pointer to a static buffer containing
   an error message for FILENAME and ERROR_NUM. */
static char *errmsg_buf = NULL;
static int errmsg_buf_size = 0;

/* Return string for ERROR_NUM when opening file.  Return value should not
   be freed by caller. */
char *
filesys_error_string (char *filename, int error_num)
{
  int len;
  char *result;

  if (error_num == 0)
    return NULL;

  result = strerror (error_num);

  len = 4 + strlen (filename) + strlen (result);
  if (len >= errmsg_buf_size)
    errmsg_buf = xrealloc (errmsg_buf, (errmsg_buf_size = 2 + len));

  sprintf (errmsg_buf, "%s: %s", filename, result);
  return errmsg_buf;
}

/* Check for "dir" with all the possible info and compression suffixes,
   in combination.  */

int
is_dir_name (char *filename)
{
  unsigned i;

  for (i = 0; info_suffixes[i]; i++)
    {
      unsigned c;
      char trydir[50];
      strcpy (trydir, "dir");
      strcat (trydir, info_suffixes[i]);

      if (mbscasecmp (filename, trydir) == 0)
        return 1;

      for (c = 0; compress_suffixes[c].suffix; c++)
        {
          char dir_compressed[50]; /* can be short */
          strcpy (dir_compressed, trydir);
          strcat (dir_compressed, compress_suffixes[c].suffix);
          if (mbscasecmp (filename, dir_compressed) == 0)
            return 1;
        }
    }

  return 0;
}


code:
/* filesys.h -- external declarations for filesys.c.
   $Id$

   Copyright 1993, 1997, 1998, 2002, 2004, 2005, 2007, 2009, 2012, 2013,
   2014, 2016 Free Software Foundation, Inc.

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

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

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

   Originally written by Brian Fox. */

#ifndef INFO_FILESYS_H
#define INFO_FILESYS_H

/* Return a string describing the search path. */
extern char *infopath_string ();

/* Initialize INFOPATH */
void infopath_init (void);

/* Add PATH to the list of paths found in INFOPATH. */
void infopath_add (char *path);

/* Iterate over INFOPATH */
char *infopath_first (int *idx);
char *infopath_next (int *idx);

/* Expand the filename in PARTIAL to make a real name for this operating
   system.  This looks in INFO_PATHS in order to find the correct file.
   If it can't find the file, it returns NULL. */
char *info_find_fullpath (char *partial, struct stat *finfo);

/* Scan the list of directories in PATH looking for FILENAME.  If we find
   one that is a regular file, return it as a new string.  Otherwise, return
   a NULL pointer. */
char *info_file_find_next_in_path (char *filename, int *diridx,
                                   struct stat *finfo);

char *info_add_extension (char *dirname, char *filename, struct stat *finfo);

/* Read the contents of PATHNAME, returning a buffer with the contents of
   that file in it, and returning the size of that buffer in FILESIZE.
   FINFO is a stat struct which has already been filled in by the caller.
   If the file cannot be read, return a NULL pointer. */
char *filesys_read_info_file (char *pathname, size_t *filesize,
    struct stat *finfo, int *is_compressed);

/* A function which returns a pointer to a static buffer containing
   an error message for FILENAME and ERROR_NUM. */
char *filesys_error_string (char *filename, int error_num);

/* The number of the most recent file system error. */
extern int filesys_error_number;

/* Return true if FILENAME is `dir', with a possible compression suffix.  */
int is_dir_name (char *filename);

/* The default value of INFOPATH. */
#if !defined (DEFAULT_INFOPATH)
#  define DEFAULT_INFOPATH "PATH:/usr/local/info:/usr/info:/usr/local/lib/info:/usr/lib/info:/usr/local/gnu/info:/usr/local/gnu/lib/info:/usr/gnu/info:/usr/gnu/lib/info:/opt/gnu/info:/usr/share/info:/usr/share/lib/info:/usr/local/share/info:/usr/local/share/lib/info:/usr/gnu/lib/emacs/info:/usr/local/gnu/lib/emacs/info:/usr/local/lib/emacs/info:/usr/local/emacs/info:."
#endif /* !DEFAULT_INFOPATH */

#if !defined (S_ISREG) && defined (S_IFREG)
#  define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif /* !S_ISREG && S_IFREG */

#if !defined (S_ISDIR) && defined (S_IFDIR)
#  define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif /* !S_ISDIR && S_IFDIR */

#endif /* not INFO_FILESYS_H */


code:
/* footnotes.c -- Some functions for manipulating footnotes.
   $Id$

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

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

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

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

   Originally written by Brian Fox. */

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

/* Nonzero means attempt to show footnotes when displaying a new window. */
int auto_footnotes_p = 0;

static char *footnote_nodename = "*Footnotes*";

/* Find the window currently showing footnotes. */
static WINDOW *
find_footnotes_window (void)
{
  WINDOW *win;

  /* Try to find an existing window first. */
  for (win = windows; win; win = win->next)
    if (internal_info_node_p (win->node) &&
        (strcmp (win->node->nodename, footnote_nodename) == 0))
      break;

  return win;
}

/* Manufacture a node containing the footnotes of this node, and
   return the manufactured node.  If NODE has no footnotes, return a
   NULL pointer. */
NODE *
make_footnotes_node (NODE *node)
{
  NODE *fn_node, *footnotes_node = NULL, *result = NULL;
  long fn_start = -1;
  char *fnptr;

  /* Make the initial assumption that the footnotes appear as simple
     text within this windows node. */
  fn_node = node;

  /* See if this node contains the magic footnote label. */
    {
      char saved = node->contents[node->nodelen];
      node->contents[node->nodelen] = '\0';
      fnptr = strstr (node->contents, FOOTNOTE_LABEL);
      node->contents[node->nodelen] = saved;
    }
  if (fnptr)
    fn_start = fnptr - node->contents;

  /* If it doesn't, check to see if it has an associated footnotes node. */
  if (!fnptr)
    {
      REFERENCE **refs;

      refs = node->references;

      if (refs)
        {
          register int i;
          char *refname;
          int reflen = strlen ("-Footnotes") + strlen (node->nodename);

          refname = xmalloc (reflen + 1);

          strcpy (refname, node->nodename);
          strcat (refname, "-Footnotes");

          for (i = 0; refs[i]; i++)
            if (refs[i]->type == REFERENCE_XREF
                && (refs[i]->nodename != NULL)
                /* Support both the older "foo-Footnotes" and the new
                   style "foo-Footnote-NN" references.  */
                && (strcmp (refs[i]->nodename, refname) == 0 ||
                 (strncmp (refs[i]->nodename, refname, reflen - 1) == 0 &&
                  refs[i]->nodename[reflen - 1] == '-' &&
                  isdigit (refs[i]->nodename[reflen]))))
              {
                footnotes_node = info_get_node (node->fullpath, refname);
                if (footnotes_node)
                  {
                    fn_node = footnotes_node;
                    fn_start = 0;
                  }
                break;
              }

          free (refname);
        }
    }

  /* If we never found the start of a footnotes area, quit now. */
  if (fn_start == -1)
    return NULL;

  /* Make the new node. */
  result = info_create_node ();

  /* Get the size of the footnotes appearing within this node. */
  {
    char *header;
    long text_start = fn_start;

    asprintf (&header,
              "*** Footnotes appearing in the node '%s' ***\n",
              node->nodename);

    /* Move the start of the displayed text to right after the first line.
       This effectively skips either "---- footno...", or "File: foo...". */
    while (text_start < fn_node->nodelen)
      if (fn_node->contents[text_start++] == '\n')
        break;

    result->nodelen = strlen (header) + fn_node->nodelen - text_start;

    /* Set the contents of this node. */
    result->contents = xmalloc (1 + result->nodelen);
    sprintf (result->contents, "%s", header);
    memcpy (result->contents + strlen (header),
            fn_node->contents + text_start, fn_node->nodelen - text_start);
    result->contents[strlen (header) + fn_node->nodelen - text_start] = '\0';

   /* Copy and adjust references that appear in footnotes section. */
    {
      REFERENCE **ref = fn_node->references;

      for (; *ref; ref++)
        {
          if ((*ref)->start > text_start)
            break;
        }

      result->references = info_copy_references (ref);

      for (ref = result->references; *ref; ref++)
        {
          (*ref)->start -= text_start - strlen (header);
          (*ref)->end -= text_start - strlen (header);
        }
    }

    result->nodename = xstrdup (footnote_nodename);
    result->flags |= N_IsInternal | N_WasRewritten;

    /* Needed in case the user follows a reference in the footnotes window. */
    result->fullpath = fn_node->fullpath;
    result->subfile = fn_node->subfile;

    free (header);
  }

  free_history_node (footnotes_node);
  return result;
}

/* Create or delete the footnotes window depending on whether footnotes
   exist in WINDOW's node or not.  Returns FN_FOUND if footnotes were found
   and displayed.  Returns FN_UNFOUND if there were no footnotes found
   in WINDOW's node.  Returns FN_UNABLE if there were footnotes, but the
   window to show them couldn't be made. */
int
info_get_or_remove_footnotes (WINDOW *window)
{
  WINDOW *fn_win;
  NODE *new_footnotes = 0;

  fn_win = find_footnotes_window ();

  /* If we are in the footnotes window, change nothing. */
  if (fn_win == window)
    return FN_FOUND;

  /* Don't display footnotes for the "*" node (entire contents of file) or
     for nodes without a name like completion windows. */
  if (window->node->nodename && strcmp ("*", window->node->nodename))
    /* Try to find footnotes for this window's node. */
    new_footnotes = make_footnotes_node (window->node);

  if (!new_footnotes)
    {
      /* If there was a window showing footnotes, and there are no footnotes
         for the current window, delete the old footnote window. */
      if (fn_win && windows->next)
        info_delete_window_internal (fn_win);
      return FN_UNFOUND;
    }

  /* If there is no window around showing footnotes, try
     to make a new window. */
  if (!fn_win)
    {
      WINDOW *old_active;
      WINDOW *last, *win;

      /* Always make this window be the last one appearing in the list.  Find
         the last window in the chain. */
      for (win = windows, last = windows; win; last = win, win = win->next);

      /* Try to split this window, and make the split window the one to
         contain the footnotes. */
      old_active = active_window;
      active_window = last;
      fn_win = window_make_window ();
      active_window = old_active;

      /* If we are hacking automatic footnotes, and there are footnotes
         but we couldn't display them, print a message to that effect. */
      if (!fn_win)
        {
          if (auto_footnotes_p)
            info_error (_("Footnotes could not be displayed"));
          return FN_UNABLE;
        }
    }

  /* Note that info_set_node_of_window calls this function
     (info_get_or_remove_footnotes), but we do not recurse indefinitely
     because we check if we are in the footnote window above. */
  info_set_node_of_window (fn_win, new_footnotes);
  fn_win->flags |= W_TempWindow;

  /* Make the height be the number of lines appearing in the footnotes. */
  if (new_footnotes)
    window_change_window_height (fn_win, fn_win->line_count - fn_win->height);

  return FN_FOUND;
}

/* Show the footnotes associated with this node in another window. */
DECLARE_INFO_COMMAND (info_show_footnotes,
   _("Show the footnotes associated with this node in another window"))
{
  /* A negative argument means just make the window go away. */
  if (count < 0)
    {
      WINDOW *fn_win = find_footnotes_window ();

      /* If there is an old footnotes window, and it isn't the only window
         on the screen, delete it. */
      if (fn_win && windows->next)
        info_delete_window_internal (fn_win);
    }
  else
    {
      int result;

      result = info_get_or_remove_footnotes (window);

      switch (result)
        {
        case FN_UNFOUND:
          info_error ("%s", msg_no_foot_node);
          break;

        case FN_UNABLE:
          info_error ("%s", msg_win_too_small);
          break;
        }
    }
}


code:
/* footnotes.h -- Some functions for manipulating footnotes.
   $Id$

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

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

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

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

   Originally written by Brian Fox. */

#ifndef INFO_FOOTNOTES_H
#define INFO_FOOTNOTES_H

/* Magic string which indicates following text is footnotes. */
#define FOOTNOTE_LABEL N_("---------- Footnotes ----------")

#define FN_FOUND   0
#define FN_UNFOUND 1
#define FN_UNABLE  2

/* Create or delete the footnotes window depending on whether footnotes
   exist in WINDOW's node or not.  Returns FN_FOUND if footnotes were found
   and displayed.  Returns FN_UNFOUND if there were no footnotes found
   in WINDOW's node.  Returns FN_UNABLE if there were footnotes, but the
   window to show them couldn't be made. */
extern int info_get_or_remove_footnotes (WINDOW *window);

/* Non-zero means attempt to show footnotes when displaying a new window. */
extern int auto_footnotes_p;

#endif /* not INFO_FOOTNOTES_H */