/* bashline.c -- Bash's interface to the readline library. */

/* Copyright (C) 1987,1991 Free Software Foundation, Inc.

   This file is part of GNU Bash, the Bourne Again SHell.

   Bash 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 1, or (at your option)
   any later version.

   Bash 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 Bash; see the file COPYING.  If not, write to the Free
   Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */

#include "bashtypes.h"
#include "posixstat.h"

#include <stdio.h>
#include "bashansi.h"
#include <readline/rlconf.h>
#include <readline/readline.h>
#include <readline/history.h>
#include "shell.h"
#include "builtins.h"
#include "builtins/common.h"
#include "bashhist.h"
#include "execute_cmd.h"

#if defined (ALIAS)
#  include "alias.h"
#endif

#if defined (BRACE_EXPANSION)
#  define BRACE_COMPLETION
#endif /* BRACE_EXPANSION */

#if defined (BRACE_COMPLETION)
extern void bash_brace_completion ();
#endif /* BRACE_COMPLETION */

/* Functions bound to keys in Readline for Bash users. */
static void shell_expand_line ();
static void display_shell_version (), operate_and_get_next ();
static void history_expand_line (), bash_ignore_filenames ();

/* Helper functions for Readline. */
static int bash_directory_completion_hook ();
static void filename_completion_ignore ();
static void bash_push_line ();

static char **attempt_shell_completion ();
static char *variable_completion_function ();
static char *hostname_completion_function ();
static char *command_word_completion_function ();
static char *command_subst_completion_function ();

static void snarf_hosts_from_file (), add_host_name ();
static void sort_hostname_list ();

#define DYNAMIC_HISTORY_COMPLETION
#if defined (DYNAMIC_HISTORY_COMPLETION)
static void dynamic_complete_history ();
#endif /* DYNAMIC_HISTORY_COMPLETION */

/* Variables used here but defined in other files. */
extern int posixly_correct, no_symbolic_links;
extern int rl_explicit_arg;
extern char *current_prompt_string, *ps1_prompt;
extern STRING_INT_ALIST word_token_alist[];
extern Function *rl_last_func;
extern int rl_filename_completion_desired;

/* SPECIFIC_COMPLETION_FUNCTIONS specifies that we have individual
   completion functions which indicate what type of completion should be
   done (at or before point) that can be bound to key sequences with
   the readline library. */
#define SPECIFIC_COMPLETION_FUNCTIONS

#if defined (SPECIFIC_COMPLETION_FUNCTIONS)
static void
  bash_specific_completion (),
  bash_complete_filename (), bash_possible_filename_completions (),
  bash_complete_filename_internal (),
  bash_complete_username (), bash_possible_username_completions (),
  bash_complete_username_internal (),
  bash_complete_hostname (), bash_possible_hostname_completions (),
  bash_complete_hostname_internal (),
  bash_complete_variable (), bash_possible_variable_completions (),
  bash_complete_variable_internal (),
  bash_complete_command (), bash_possible_command_completions (),
  bash_complete_command_internal ();
#endif /* SPECIFIC_COMPLETION_FUNCTIONS */

/* Non-zero once initalize_readline () has been called. */
int bash_readline_initialized = 0;

#if defined (VI_MODE)
static void vi_edit_and_execute_command ();
extern char *rl_vi_comment_begin;
#endif

static Function *old_rl_startup_hook = (Function *) NULL;

/* Change the readline VI-mode keymaps into or out of Posix.2 compliance.
   Called when the shell is put into or out of `posix' mode. */
void
posix_readline_initialize (on_or_off)
     int on_or_off;
{
#if defined (VI_MODE)
  if (on_or_off)
    {
      rl_bind_key_in_map (CTRL('I'), rl_insert, vi_insertion_keymap);
      if (rl_vi_comment_begin)
	free (rl_vi_comment_begin);
      rl_vi_comment_begin = savestring ("#");
    }
  else
    rl_bind_key_in_map (CTRL('I'), rl_complete, vi_insertion_keymap);
#endif
} 

/* Called once from parse.y if we are going to use readline. */
void
initialize_readline ()
{
  if (bash_readline_initialized)
    return;

  rl_terminal_name = get_string_value ("TERM");
  rl_instream = stdin;
  rl_outstream = stderr;
  rl_special_prefixes = "$@";

  /* Allow conditional parsing of the ~/.inputrc file. */
  rl_readline_name = "Bash";

  /* Bind up our special shell functions. */
  rl_add_defun ("shell-expand-line", (Function *)shell_expand_line, -1);
  rl_bind_key_in_map
    (CTRL('E'), (Function *)shell_expand_line, emacs_meta_keymap);

  /* Bind up our special shell functions. */
  rl_add_defun ("history-expand-line", (Function *)history_expand_line, -1);
  rl_bind_key_in_map ('^', (Function *)history_expand_line, emacs_meta_keymap);

  /* Backwards compatibility. */
  rl_add_defun ("insert-last-argument", rl_yank_last_arg, -1);

  rl_add_defun
    ("operate-and-get-next", (Function *)operate_and_get_next, CTRL('O'));

  rl_add_defun
    ("display-shell-version", (Function *)display_shell_version, -1);

  rl_bind_key_in_map
    (CTRL ('V'), (Function *)display_shell_version, emacs_ctlx_keymap);

  /* In Bash, the user can switch editing modes with "set -o [vi emacs]",
     so it is not necessary to allow C-M-j for context switching.  Turn
     off this occasionally confusing behaviour. */
  rl_unbind_key_in_map (CTRL('J'), emacs_meta_keymap);
  rl_unbind_key_in_map (CTRL('M'), emacs_meta_keymap);
#if defined (VI_MODE)
  rl_unbind_key_in_map (CTRL('E'), vi_movement_keymap);
#endif
  
#if defined (BRACE_COMPLETION)
  rl_add_defun ("complete-into-braces", bash_brace_completion, -1);
  rl_bind_key_in_map ('{', bash_brace_completion, emacs_meta_keymap);
#endif /* BRACE_COMPLETION */

#if defined (SPECIFIC_COMPLETION_FUNCTIONS)
  rl_add_defun ("complete-filename", bash_complete_filename, -1);
  rl_bind_key_in_map ('/', bash_complete_filename, emacs_meta_keymap);
  rl_add_defun ("possible-filename-completions",
		bash_possible_filename_completions, -1);
  rl_bind_key_in_map ('/', bash_possible_filename_completions,
		      emacs_ctlx_keymap);

  rl_add_defun ("complete-username", bash_complete_username, -1);
  rl_bind_key_in_map ('~', bash_complete_username, emacs_meta_keymap);
  rl_add_defun ("possible-username-completions",
		bash_possible_username_completions, -1);
  rl_bind_key_in_map ('~', bash_possible_username_completions,
		      emacs_ctlx_keymap);

  rl_add_defun ("complete-hostname", bash_complete_hostname, -1);
  rl_bind_key_in_map ('@', bash_complete_hostname, emacs_meta_keymap);
  rl_add_defun ("possible-hostname-completions",
		bash_possible_hostname_completions, -1);
  rl_bind_key_in_map ('@', bash_possible_hostname_completions,
		      emacs_ctlx_keymap);

  rl_add_defun ("complete-variable", bash_complete_variable, -1);
  rl_bind_key_in_map ('$', bash_complete_variable, emacs_meta_keymap);
  rl_add_defun ("possible-variable-completions",
		bash_possible_variable_completions, -1);
  rl_bind_key_in_map ('$', bash_possible_variable_completions,
		      emacs_ctlx_keymap);

  rl_add_defun ("complete-command", bash_complete_command, -1);
  rl_bind_key_in_map ('!', bash_complete_command, emacs_meta_keymap);
  rl_add_defun ("possible-command-completions",
		bash_possible_command_completions, -1);
  rl_bind_key_in_map ('!', bash_possible_command_completions,
		      emacs_ctlx_keymap);

#endif /* SPECIFIC_COMPLETION_FUNCTIONS */

#if defined (DYNAMIC_HISTORY_COMPLETION)
  rl_add_defun ("dynamic-complete-history", dynamic_complete_history, -1);
  rl_bind_key_in_map (TAB, dynamic_complete_history, emacs_meta_keymap);
#endif /* DYNAMIC_HISTORY_COMPLETION */

  /* Tell the completer that we want a crack first. */
  rl_attempted_completion_function = (CPPFunction *)attempt_shell_completion;

  /* Tell the completer that we might want to follow symbolic links or
     do other expansion on directory names. */
  rl_directory_completion_hook = bash_directory_completion_hook;

  /* Tell the filename completer we want a chance to ignore some names. */
  rl_ignore_some_completions_function = (Function *)filename_completion_ignore;

#if defined (VI_MODE)
  rl_bind_key_in_map ('v', vi_edit_and_execute_command, vi_movement_keymap);
#endif

  rl_completer_quote_characters = "'\"";
  /* Need to modify this from the default; `$', `{', `\', and ``' are not
     word break characters. */
  rl_completer_word_break_characters = " \t\n\"'@><=;|&("; /**/

  if (posixly_correct)
    posix_readline_initialize (1);

  bash_readline_initialized = 1;
}

/* On Sun systems at least, rl_attempted_completion_function can end up
   getting set to NULL, and rl_completion_entry_function set to do command
   word completion if Bash is interrupted while trying to complete a command
   word.  This just resets all the completion functions to the right thing.
   It's called from throw_to_top_level(). */
void
bashline_reinitialize ()
{
  tilde_initialize ();
  rl_attempted_completion_function = attempt_shell_completion;
  rl_completion_entry_function = (Function *)NULL;
  rl_directory_completion_hook = bash_directory_completion_hook;
  rl_ignore_some_completions_function = (Function *)filename_completion_ignore;
}

/* Contains the line to push into readline. */
static char *push_to_readline = (char *)NULL;

/* Push the contents of push_to_readline into the
   readline buffer. */
static void
bash_push_line ()
{
  if (push_to_readline)
    {
      rl_insert_text (push_to_readline);
      free (push_to_readline);
      push_to_readline = (char *)NULL;
      rl_startup_hook = old_rl_startup_hook;
    }
}

/* Call this to set the initial text for the next line to read
   from readline. */
int
bash_re_edit (line)
     char *line;
{
  if (push_to_readline)
    free (push_to_readline);

  push_to_readline = savestring (line);
  old_rl_startup_hook = rl_startup_hook;
  rl_startup_hook = (Function *)bash_push_line;

  return (0);
}

static void
display_shell_version (count, c)
     int count, c;
{
  crlf ();
  show_shell_version ();
  putc ('\r', rl_outstream);
  fflush (rl_outstream);
  rl_on_new_line ();
  rl_redisplay ();
}

/* **************************************************************** */
/*								    */
/*			     Readline Stuff			    */
/*								    */
/* **************************************************************** */

/* If the user requests hostname completion, then simply build a list
   of hosts, and complete from that forever more. */
#if !defined (ETCHOSTS)
#define ETCHOSTS "/etc/hosts"
#endif

/* The kept list of hostnames. */
static char **hostname_list = (char **)NULL;

/* The physical size of the above list. */
static int hostname_list_size = 0;

/* The length of the above list. */
static int hostname_list_length = 0;

/* Whether or not HOSTNAME_LIST has been initialized. */
int hostname_list_initialized = 0;

/* Non-zero means that HOSTNAME_LIST needs to be sorted. */
static int hostname_list_needs_sorting = 0;

/* Initialize the hostname completion table. */
static void
initialize_hostname_list ()
{
  char *temp;

  temp = get_string_value ("HOSTFILE");
  if (!temp)
    temp = get_string_value ("hostname_completion_file");
  if (!temp)
    temp = ETCHOSTS;

  snarf_hosts_from_file (temp);
  sort_hostname_list ();

  if (hostname_list)
    hostname_list_initialized++;
}

/* Add NAME to the list of hosts. */
static void
add_host_name (name)
     char *name;
{
  if (hostname_list_length + 2 > hostname_list_size)
    {
      hostname_list = (char **)
	xrealloc (hostname_list,
		  (1 + (hostname_list_size += 100)) * sizeof (char *));
    }

  hostname_list[hostname_list_length] = savestring (name);
  hostname_list[++hostname_list_length] = (char *)NULL;
  hostname_list_needs_sorting++;
}

/* After you have added some names, you should sort the list of names. */
static void
sort_hostname_list ()
{
  if (hostname_list_needs_sorting && hostname_list)
    sort_char_array (hostname_list);
  hostname_list_needs_sorting = 0;
}

#define cr_whitespace(c) ((c) == '\r' || (c) == '\n' || whitespace(c))

static void
snarf_hosts_from_file (filename)
     char *filename;
{
  FILE *file = fopen (filename, "r");
  char *temp, buffer[256], name[256];
  register int i, start;

  if (!file)
    return;

  while (temp = fgets (buffer, 255, file))
    {
      /* Skip to first character. */
      for (i = 0; buffer[i] && cr_whitespace (buffer[i]); i++);

      /* If comment, ignore. */
      if (buffer[i] == '#')
	continue;

      /* If `preprocessor' directive, do the include. */
      if (strncmp (&buffer[i], "$include ", 9) == 0)
	{
	  char *includefile = &buffer[i + 9];
	  char *t;

	  /* Find start of filename. */
	  while (*includefile && whitespace (*includefile))
	    includefile++;

	  t = includefile;

	  /* Find end of filename. */
	  while (*t && !cr_whitespace (*t))
	    t++;

	  *t = '\0';

	  snarf_hosts_from_file (includefile);
	  continue;
	}

      /* Skip internet address. */
      for (; buffer[i] && !cr_whitespace (buffer[i]); i++);

      /* Gobble up names.  Each name is separated with whitespace. */
      while (buffer[i] && buffer[i] != '#')
	{
	  for (; i && cr_whitespace (buffer[i]); i++);
	  if (buffer[i] ==  '#')
	    continue;
	  for (start = i; buffer[i] && !cr_whitespace (buffer[i]); i++);
	  if ((i - start) == 0)
	    continue;
	  strncpy (name, buffer + start, i - start);
	  name[i - start] = '\0';
	  add_host_name (name);
	}
    }
  fclose (file);
}

/* Return a NULL terminated list of hostnames which begin with TEXT.
   Initialize the hostname list the first time if neccessary.
   The array is malloc ()'ed, but not the individual strings. */
static char **
hostnames_matching (text)
     char *text;
{
  register int i, len = strlen (text);
  register int begin, end;
  int last_search = -1;
  char **result = (char **)NULL;

  if (!hostname_list_initialized)
    {
      initialize_hostname_list ();

      if (!hostname_list_initialized)
	return ((char **)NULL);
    }

  sort_hostname_list ();

  /* The list is sorted.  Do a binary search on it for the first character
     in TEXT, and then grovel the names of interest. */
  begin = 0; end = hostname_list_length;

  /* Special case.  If TEXT consists of nothing, then the whole list is
     what is desired. */
  if (!*text)
    {
      result = (char **)xmalloc ((1 + hostname_list_length) * sizeof (char *));
      for (i = 0; i < hostname_list_length; i++)
	result[i] = hostname_list[i];
      result[i] = (char *)NULL;
      return (result);
    }

  /* Scan until found, or failure. */
  while (end != begin)
    {
      int r = 0;

      i = ((end - begin) / 2) + begin;
      if (i == last_search)
	break;

      if (hostname_list[i] &&
	  (r = strncmp (hostname_list[i], text, len)) == 0)
	{
	  while (strncmp (hostname_list[i], text, len) == 0 && i) i--;
	  if (strncmp (hostname_list[i], text, len) != 0) i++;

	  begin = i;
	  while (hostname_list[i] &&
		 strncmp (hostname_list[i], text, len) == 0) i++;
	  end = i;

	  result = (char **)xmalloc ((1 + (end - begin)) * sizeof (char *));
	  for (i = 0; i + begin < end; i++)
	    result[i] = hostname_list[begin + i];
	  result[i] = (char *)NULL;
	  return (result);
	}

      last_search = i;

      if (r < 0)
	begin = i;
      else
	end = i;
    }
  return ((char **)NULL);
}

/* The equivalent of the K*rn shell C-o operate-and-get-next-history-line
   editing command. */
static int saved_history_line_to_use = 0;

static void
set_saved_history ()
{
  if (saved_history_line_to_use)
    rl_get_previous_history (history_length - saved_history_line_to_use);
  saved_history_line_to_use = 0;
  rl_startup_hook = old_rl_startup_hook;
}  

static void
operate_and_get_next (count, c)
     int count, c;
{
  int where;

  /* Accept the current line. */
  rl_newline ();	

  /* Find the current line, and find the next line to use. */
  where = where_history ();

  if ((history_is_stifled () && (history_length >= max_input_history)) ||
      (where >= history_length - 1))
    saved_history_line_to_use = where;
  else
    saved_history_line_to_use = where + 1;

  old_rl_startup_hook = rl_startup_hook;
  rl_startup_hook = (Function *)set_saved_history;
}

#if defined (VI_MODE)
/* This vi mode command causes VI_EDIT_COMMAND to be run on the current
   command being entered (if no explicit argument is given), otherwise on
   a command from the history file. */

#define VI_EDIT_COMMAND "fc -e ${VISUAL:-${EDITOR:-vi}}"

static void vi_edit_and_execute_command (int count,int c)
{
  char *command;

  /* Accept the current line. */
  rl_newline ();	

  if (rl_explicit_arg)
    {
      command = xmalloc (strlen (VI_EDIT_COMMAND) + 8);
      sprintf (command, "%s %d", VI_EDIT_COMMAND, count);
    }
  else
    {
      /* Take the command we were just editing, add it to the history file,
	 then call fc to operate on it.  We have to add a dummy command to
	 the end of the history because fc ignores the last command (assumes
	 it's supposed to deal with the command before the `fc'). */
      using_history ();
      add_history (rl_line_buffer);
      add_history ("");
      history_lines_this_session++;
      using_history ();
      command = savestring (VI_EDIT_COMMAND);
    }
  parse_and_execute (command, "v", -1);
  rl_line_buffer[0] = '\0';	/* erase pre-edited command */
}
#endif /* VI_MODE */

/* **************************************************************** */
/*								    */
/*			How To Do Shell Completion		    */
/*								    */
/* **************************************************************** */

/* Do some completion on TEXT.  The indices of TEXT in RL_LINE_BUFFER are
   at START and END.  Return an array of matches, or NULL if none. */
static char **
attempt_shell_completion (text, start, end)
     char *text;
     int start, end;
{
  int in_command_position, ti;
  char **matches = (char **)NULL;
  char *command_separator_chars = ";|&{(`";

  rl_ignore_some_completions_function =
    (Function *)filename_completion_ignore;

  /* Determine if this could be a command word.  It is if it appears at
     the start of the line (ignoring preceding whitespace), or if it
     appears after a character that separates commands.  It cannot be a
     command word if we aren't at the top-level prompt. */
  ti = start - 1;

  while ((ti > -1) && (whitespace (rl_line_buffer[ti])))
    ti--;

  in_command_position = 0;
  if (ti < 0)
    {
      /* Only do command completion at the start of a line when we
         are prompting at the top level. */
      if (current_prompt_string == ps1_prompt)
	in_command_position++;
    }
  else if (member (rl_line_buffer[ti], command_separator_chars))
    {
      register int this_char, prev_char;

      in_command_position++;

      /* Handle the two character tokens `>&', `<&', and `>|'.
         We are not in a command position after one of these. */
      this_char = rl_line_buffer[ti];
      prev_char = rl_line_buffer[ti - 1];

      if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) ||
	  (this_char == '|' && prev_char == '>'))
	in_command_position = 0;
      else if (char_is_quoted (rl_line_buffer, ti))
	in_command_position = 0;
    }
  else
    {
      /* This still could be in command position.  It is possible
	 that all of the previous words on the line are variable
	 assignments. */
    }

  /* Special handling for command substitution.  XXX - this should handle
     `$(' as well. */
  if (*text == '`' && unclosed_pair (rl_line_buffer, start, "`"))
    matches = completion_matches (text, command_subst_completion_function);

  /* Variable name? */
  if (!matches && *text == '$')
    matches = completion_matches (text, variable_completion_function);

  /* If the word starts in `~', and there is no slash in the word, then
     try completing this word as a username. */
  if (!matches && *text == '~' && !strchr (text, '/'))
    matches = completion_matches (text, username_completion_function);

  /* Another one.  Why not?  If the word starts in '@', then look through
     the world of known hostnames for completion first. */
  if (!matches && *text == '@')
    matches = completion_matches (text, hostname_completion_function);

  /* And last, (but not least) if this word is in a command position, then
     complete over possible command names, including aliases, functions,
     and command names. */
  if (!matches && in_command_position)
    {
      matches = completion_matches (text, command_word_completion_function);
      /* If we are attempting command completion and nothing matches, we
	 do not want readline to perform filename completion for us.  We
	 still want to be able to complete partial pathnames, so set the
	 completion ignore function to something which will remove filenames
	 and leave directories in the match list. */
      if (!matches)
	rl_ignore_some_completions_function = (Function *)bash_ignore_filenames;
    }

  return (matches);
}

/* This is the function to call when the word to complete is in a position
   where a command word can be found.  It grovels $PATH, looking for commands
   that match.  It also scans aliases, function names, and the shell_builtin
   table. */
static char *
command_word_completion_function (hint_text, state)
     char *hint_text;
     int state;
{
  static char *hint = (char *)NULL;
  static char *path = (char *)NULL;
  static char *val = (char *)NULL;
  static char *filename_hint = (char *)NULL;
  static int path_index, hint_len, istate;
  static int mapping_over, local_index;
  static SHELL_VAR **varlist = (SHELL_VAR **)NULL;
#if defined (ALIAS)
  static ASSOC **alias_list = (ASSOC **)NULL;
#endif /* ALIAS */

  /* We have to map over the possibilities for command words.  If we have
     no state, then make one just for that purpose. */

  if (!state)
    {
      if (hint)
	free (hint);

      mapping_over = 0;
      val = (char *)NULL;

      /* If this is an absolute program name, do not check it against
	 aliases, reserved words, functions or builtins.  We must check
	 whether or not it is unique, and, if so, whether that filename
	 is executable. */
      if (absolute_program (hint_text))
	{
	  /* Perform tilde expansion on what's passed, so we don't end up
	     passing filenames with tildes directly to stat(). */
	  if (*hint_text == '~')
	    hint = tilde_expand (hint_text);
	  else
	    hint = savestring (hint_text);
	  hint_len = strlen (hint);

	  if (filename_hint)
	    free (filename_hint);
	  filename_hint = savestring (hint);

	  mapping_over = 4;
	  istate = 0;
	  goto inner;
	}

      hint = savestring (hint_text);
      hint_len = strlen (hint);

      path = get_string_value ("PATH");
      path_index = 0;

      /* Initialize the variables for each type of command word. */
      local_index = 0;

      if (varlist)
	free (varlist);

      varlist = all_visible_functions ();

#if defined (ALIAS)
      if (alias_list)
	free (alias_list);

      alias_list = all_aliases ();
#endif /* ALIAS */
    }

  /* mapping_over says what we are currently hacking.  Note that every case
     in this list must fall through when there are no more possibilities. */

  switch (mapping_over)
    {
    case 0:			/* Aliases come first. */
#if defined (ALIAS)
      while (alias_list && alias_list[local_index])
	{
	  register char *alias;

	  alias = alias_list[local_index++]->name;

	  if (STREQN (alias, hint, hint_len))
	    return (savestring (alias));
	}
#endif /* ALIAS */
      local_index = 0;
      mapping_over++;

    case 1:			/* Then shell reserved words. */
      {
	while (word_token_alist[local_index].word)
	  {
	    register char *reserved_word;

	    reserved_word = word_token_alist[local_index++].word;

	    if (STREQN (reserved_word, hint, hint_len))
	      return (savestring (reserved_word));
	  }
	local_index = 0;
	mapping_over++;
      }

    case 2:			/* Then function names. */
      while (varlist && varlist[local_index])
	{
	  register char *varname;

	  varname = varlist[local_index++]->name;

	  if (STREQN (varname, hint, hint_len))
	    return (savestring (varname));
	}
      local_index = 0;
      mapping_over++;

    case 3:			/* Then shell builtins. */
      for (; local_index < num_shell_builtins; local_index++)
	{
	  /* Ignore it if it doesn't have a function pointer or if it
	     is not currently enabled. */
	  if (!shell_builtins[local_index].function ||
	      (shell_builtins[local_index].flags & BUILTIN_ENABLED) == 0)
	    continue;

	  if (STREQN (shell_builtins[local_index].name, hint, hint_len))
	    {
	      int i = local_index++;

	      return (savestring (shell_builtins[i].name));
	    }
	}
      local_index = 0;
      mapping_over++;
    }

  /* Repeatedly call filename_completion_func<tion while we have
     members of PATH left.  Question:  should we stat each file?
     Answer: we call executable_file () on each file. */
 outer:

  istate = (val != (char *)NULL);

  if (!istate)
    {
      char *current_path;

      /* Get the next directory from the path.  If there is none, then we
	 are all done. */
      if (!path || !path[path_index] ||
	  (current_path = extract_colon_unit (path, &path_index)) == 0)
	return ((char *)NULL);

      if (*current_path == 0)
	{
	  free (current_path);
	  current_path = savestring (".");
	}

      if (*current_path == '~')
	{
	  char *t;

	  t = tilde_expand (current_path);
	  free (current_path);
	  current_path = t;
	}

      if (filename_hint)
	free (filename_hint);

      filename_hint = xmalloc (2 + strlen (current_path) + hint_len);
      sprintf (filename_hint, "%s/%s", current_path, hint);

      free (current_path);
    }

 inner:
  val = filename_completion_function (filename_hint, istate);
  istate = 1;

  if (!val)
    {
      /* If the hint text is an absolute program, then don't bother
	 searching through PATH. */
      if (absolute_program (hint))
	return ((char *)NULL);

      goto outer;
    }
  else
    {
      int match;
      char *temp;

      if (absolute_program (hint))
	{
	  match = strncmp (val, hint, hint_len) == 0;
	  /* If we performed tilde expansion, restore the original
	     filename. */
	  if (*hint_text == '~')
	    {
	      int l, tl, vl;
	      vl = strlen (val);
	      tl = strlen (hint_text);
	      l = vl - hint_len;	/* # of chars added */
	      temp = xmalloc (l + 2 + tl);
	      strcpy (temp, hint_text);
	      strcpy (temp + tl, val + vl - l);
	    }
	  else
	    temp = savestring (val);
	}
      else
	{
	  temp = strrchr (val, '/');

	  if (temp)
	    {
	      temp++;
	      match = strncmp (temp, hint, hint_len) == 0;
	      if (match)
		temp = savestring (temp);
	    }
	  else
	    match = 0;
	}

      /* If we have found a match, and it is an executable file, return it. */
      if (match && executable_file (val))
	{
	  free (val);
	  val = "";		/* So it won't be NULL. */
	  return (temp);
	}
      else
	{
	  free (val);
	  goto inner;
	}
    }
}

static char *
command_subst_completion_function (text, state)
     int state;
     char *text;
{
  static char **matches = (char **)NULL;
  static char *orig_start, *filename_text = (char *)NULL;
  static int cmd_index, start_len;

  if (state == 0)
    {
      if (filename_text)
	free (filename_text);
      orig_start = text;
      if (*text == '`')
        text++;
      else if (*text == '$' && text[1] == '(')
        text += 2;
      start_len = text - orig_start;
      filename_text = savestring (text);
      if (matches)
	free (matches);
      matches = completion_matches (filename_text, command_word_completion_function);
      cmd_index = 0;
    }

  if (!matches || !matches[cmd_index])
    {
      rl_filename_quoting_desired = 0;	/* disable quoting */
      return ((char *)NULL);
    }
  else
    {
      char *value;

      value = xmalloc (1 + start_len + strlen (matches[cmd_index]));

      if (start_len == 1)
        value[0] = *orig_start;
      else
        strncpy (value, orig_start, start_len);

      strcpy (value + start_len, matches[cmd_index]);

      cmd_index++;
      return (value);
    }
}

/* Okay, now we write the entry_function for variable completion. */
static char *
variable_completion_function (text, state)
     int state;
     char *text;
{
  register SHELL_VAR *var = (SHELL_VAR *)NULL;
  static SHELL_VAR **varlist = (SHELL_VAR **)NULL;
  static int varlist_index;
  static char *varname = (char *)NULL;
  static int namelen;
  static int first_char, first_char_loc;

  if (!state)
    {
      if (varname)
	free (varname);

      first_char_loc = 0;
      first_char = text[0];

      if (first_char == '$')
	first_char_loc++;

      varname = savestring (text + first_char_loc);

      namelen = strlen (varname);
      if (varlist)
	free (varlist);
      varlist = all_visible_variables ();
      varlist_index = 0;
    }

  while (varlist && varlist[varlist_index])
    {
      var = varlist[varlist_index];

      /* Compare.  You can't do better than Zayre.  No text is also
	 a match.  */
      if (!*varname || (strncmp (varname, var->name, namelen) == 0))
	break;
      varlist_index++;
    }

  if (!varlist || !varlist[varlist_index])
    {
      return ((char *)NULL);
    }
  else
    {
      char *value = xmalloc (2 + strlen (var->name));

      if (first_char_loc)
	*value = first_char;

      strcpy (&value[first_char_loc], var->name);

      varlist_index++;
      return (value);
    }
}

/* How about a completion function for hostnames? */
static char *
hostname_completion_function (text, state)
     int state;
     char *text;
{
  static char **list = (char **)NULL;
  static int list_index = 0;
  static int first_char, first_char_loc;

  /* If we don't have any state, make some. */
  if (!state)
    {
      if (list)
	free (list);

      list = (char **)NULL;

      first_char_loc = 0;
      first_char = *text;

      if (first_char == '@')
	first_char_loc++;

      list = hostnames_matching (&text[first_char_loc]);
      list_index = 0;
    }

  if (list && list[list_index])
    {
      char *t = xmalloc (2 + strlen (list[list_index]));

      *t = first_char;
      strcpy (t + first_char_loc, list[list_index]);
      list_index++;
      return (t);
    }
  else
    return ((char *)NULL);
}

/* History and alias expand the line. */
static char *
history_expand_line_internal (line)
     char *line;
{
  char *new_line;

  new_line = pre_process_line (line, 0, 0);
  return new_line;
}

#if defined (ALIAS)
/* Perform alias expansion on LINE and return the new line. */
static char *
alias_expand_line_internal (line)
     char *line;
{
  char *alias_line;

  alias_line = alias_expand (line);
  return alias_line;
}
#endif

/* There was an error in expansion.  Let the preprocessor print
   the error here. */
static void
cleanup_expansion_error ()
{
  char *to_free;

  fprintf (rl_outstream, "\r\n");
  to_free = pre_process_line (rl_line_buffer, 1, 0);
  free (to_free);
  putc ('\r', rl_outstream);
  rl_forced_update_display ();
}

/* If NEW_LINE differs from what is in the readline line buffer, add an
   undo record to get from the readline line buffer contents to the new
   line and make NEW_LINE the current readline line. */
static void
maybe_make_readline_line (new_line)
     char *new_line;
{
  if (strcmp (new_line, rl_line_buffer) != 0)
    {
      rl_point = rl_end;

      rl_add_undo (UNDO_BEGIN, 0, 0, 0);
      rl_delete_text (0, rl_point);
      rl_point = rl_end = 0;
      rl_insert_text (new_line);
      rl_add_undo (UNDO_END, 0, 0, 0);
    }
}

/* Make NEW_LINE be the current readline line.  This frees NEW_LINE. */
static void
set_up_new_line (new_line)
     char *new_line;
{
  int old_point = rl_point;
  int at_end = rl_point == rl_end;

  /* If the line was history and alias expanded, then make that
     be one thing to undo. */
  maybe_make_readline_line (new_line);
  free (new_line);

  /* Place rl_point where we think it should go. */
  if (at_end)
    rl_point = rl_end;
  else if (old_point < rl_end)
    {
      rl_point = old_point;
      if (!whitespace (rl_line_buffer[rl_point]))
	rl_forward_word (1);
    }
}

/* History expand the line. */
static void
history_expand_line (ignore)
     int ignore;
{
  char *new_line;

  new_line = history_expand_line_internal (rl_line_buffer);

  if (new_line)
    set_up_new_line (new_line);
  else
    cleanup_expansion_error ();
}
  
/* History and alias expand the line. */
static void
history_and_alias_expand_line (ignore)
     int ignore;
{
  char *new_line;

  new_line = pre_process_line (rl_line_buffer, 0, 0);

#if defined (ALIAS)
  if (new_line)
    {
      char *alias_line;

      alias_line = alias_expand (new_line);
      free (new_line);
      new_line = alias_line;
    }
#endif /* ALIAS */

  if (new_line)
    set_up_new_line (new_line);
  else
    cleanup_expansion_error ();
}

/* History and alias expand the line, then perform the shell word
   expansions by calling expand_string. */
static void
shell_expand_line (ignore)
     int ignore;
{
  char *new_line;

  new_line = pre_process_line (rl_line_buffer, 0, 0);

#if defined (ALIAS)
  if (new_line)
    {
      char *alias_line;

      alias_line = alias_expand (new_line);
      free (new_line);
      new_line = alias_line;
    }
#endif /* ALIAS */

  if (new_line)
    {
      int old_point = rl_point;
      int at_end = rl_point == rl_end;

      /* If the line was history and alias expanded, then make that
	 be one thing to undo. */
      maybe_make_readline_line (new_line);
      free (new_line);

      /* If there is variable expansion to perform, do that as a separate
	 operation to be undone. */
      {
	WORD_LIST *expanded_string;

	expanded_string = expand_string (rl_line_buffer, 0);
	if (!expanded_string)
	  new_line = savestring ("");
	else
	  {
	    new_line = string_list (expanded_string);
	    dispose_words (expanded_string);
	  }

	maybe_make_readline_line (new_line);
	free (new_line);

	/* Place rl_point where we think it should go. */
	if (at_end)
	  rl_point = rl_end;
	else if (old_point < rl_end)
	  {
	    rl_point = old_point;
	    if (!whitespace (rl_line_buffer[rl_point]))
	      rl_forward_word (1);
	  }
      }
    }
  else
    cleanup_expansion_error ();
}

/* Filename completion ignore.  Emulates the "fignore" facility of
   tcsh.  If FIGNORE is set, then don't match files with the
   given suffixes.  If only one of the possibilities has an acceptable
   suffix, delete the others, else just return and let the completer
   signal an error.  It is called by the completer when real
   completions are done on filenames by the completer's internal
   function, not for completion lists (M-?) and not on "other"
   completion types, such as hostnames or commands.
 
   It is passed a NULL-terminated array of (char *)'s that must be
   free()'d if they are deleted.  The first element (names[0]) is the
   least-common-denominator string of the matching patterns (i.e.
   u<TAB> produces names[0] = "und", names[1] = "under.c", names[2] =
   "undun.c", name[3] = NULL).  */

struct ign {
  char *val;
  int len;
};

static struct ign *ignores;	/* Store the ignore strings here */
static int num_ignores;		/* How many are there? */
static char *last_fignore;	/* Last value of fignore - cached for speed */

static void
setup_ignore_patterns ()
{
  int numitems, maxitems, ptr;
  char *colon_bit;
  struct ign *p;
  
  char *this_fignore = get_string_value ("FIGNORE");

  /* If nothing has changed then just exit now. */
  if ((this_fignore &&
       last_fignore &&
       strcmp (this_fignore, last_fignore) == 0) ||
      (!this_fignore && !last_fignore))
    {
      return;
    }

  /* Oops.  FIGNORE has changed.  Re-parse it. */
  num_ignores = 0;

  if (ignores)
    {
      for (p = ignores; p->val; p++) free(p->val);
      free (ignores);
      ignores = (struct ign*)NULL;
    }

  if (last_fignore)
    {
      free (last_fignore);
      last_fignore = (char *)NULL;
    }

  if (!this_fignore || !*this_fignore)
    return;

  last_fignore = savestring (this_fignore);

  numitems = maxitems = ptr = 0;

  while (colon_bit = extract_colon_unit (this_fignore, &ptr))
    {
      if (numitems + 1 > maxitems)
	ignores = (struct ign *)
	  xrealloc (ignores, (maxitems += 10) * sizeof (struct ign));

      ignores[numitems].val = colon_bit;
      ignores[numitems].len = strlen (colon_bit);
      numitems++;
    }
  ignores[numitems].val = NULL;
  num_ignores = numitems;
}

static int
name_is_acceptable (name)
     char *name;
{
  struct ign *p;
  int nlen = strlen (name);

  for (p = ignores; p->val; p++) 
    {
      if (nlen > p->len && p->len > 0 && 
	  strcmp (p->val, &name[nlen - p->len]) == 0)
	return (0);
    }

  return (1);
}

/* Internal function to test whether filenames in NAMES should be
   ignored.  NAME_FUNC is a pointer to a function to call with each
   name.  It returns non-zero if the name is acceptable to the particular
   ignore function which called _ignore_names; zero if the name should
   be removed from NAMES. */
static void
_ignore_names (names, name_func)
     char **names;
     Function *name_func;
{
  char **newnames;
  int idx, nidx;

  /* If there is only one completion, see if it is acceptable.  If it is
     not, free it up.  In any case, short-circuit and return.  This is a
     special case because names[0] is not the prefix of the list of names
     if there is only one completion; it is the completion itself. */
  if (names[1] == (char *)0)
    {
      if ((*name_func) (names[0]) == 0)
        {
          free (names[0]);
          names[0] = (char *)NULL;
        }
      return;
    }

  /* Allocate space for array to hold list of pointers to matching
     filenames.  The pointers are copied back to NAMES when done. */
  for (nidx = 1; names[nidx]; nidx++)
    ;
  newnames = (char **)xmalloc ((nidx + 1) * (sizeof (char *)));

  newnames[0] = names[0];
  for (idx = nidx = 1; names[idx]; idx++)
    {
      if ((*name_func) (names[idx]))
	newnames[nidx++] = names[idx];
      else
	free (names[idx]);
    }

  newnames[nidx] = (char *)NULL;

  /* If none are acceptable then let the completer handle it. */
  if (nidx == 1)
    {
      free (names[0]);
      names[0] = (char *)NULL;
      free (newnames);
      return;
    }

  /* If only one is acceptable, copy it to names[0] and return. */
  if (nidx == 2)
    {
      free (names[0]);
      names[0] = newnames[1];
      names[1] = (char *)NULL;
      free (newnames);
      return;
    }
      
  /* Copy the acceptable names back to NAMES, set the new array end,
     and return. */
  for (nidx = 1; newnames[nidx]; nidx++)
    names[nidx] = newnames[nidx];
  names[nidx] = (char *)NULL;
}

static void
filename_completion_ignore (names)
     char **names;
{
  setup_ignore_patterns ();

  if (num_ignores == 0)
    return;

  _ignore_names (names, name_is_acceptable);
}

/* Return 1 if NAME is a directory. */
static int
test_for_directory (name)
     char *name;
{
  struct stat finfo;
  char *fn;

  fn = tilde_expand (name);
  if (stat (fn, &finfo) != 0)
    {
      free (fn);
      return 0;
    }
  free (fn);
  return (S_ISDIR (finfo.st_mode));
}

/* Remove files from NAMES, leaving directories. */
static void
bash_ignore_filenames (names)
     char **names;
{
  _ignore_names (names, test_for_directory);
}

/* Handle symbolic link references and other directory name
   expansions while hacking completion. */
static int
bash_directory_completion_hook (dirname)
     char **dirname;
{
  char *local_dirname, *t;
  int return_value = 0;
  WORD_LIST *wl;

  local_dirname = *dirname;
  if (strchr (local_dirname, '$') || strchr (local_dirname, '`'))
    {
      wl = expand_string (local_dirname, 0);
      if (wl)
	{
	  *dirname = string_list (wl);
	  /* Tell the completer to replace the directory name only if we
	     actually expanded something. */
	  return_value = STREQ (local_dirname, *dirname) == 0;
	  free (local_dirname);
	  dispose_words (wl);
	  local_dirname = *dirname;
	}
      else
	{
	  free (local_dirname);
	  *dirname = savestring ("");
	  return 1;
	}
    }

  if (!no_symbolic_links && (local_dirname[0] != '.' || local_dirname[1]))
    {
      char *temp1, *temp2;
      int len1, len2;

      t = get_working_directory ("symlink-hook");
      temp1 = make_absolute (local_dirname, t);
      free (t);
      temp2 = canonicalize_pathname (temp1);
      len1 = strlen (temp1);
      if (temp1[len1 - 1] == '/')
        {
	  len2 = strlen (temp2);
          temp2 = xrealloc (temp2, len2 + 2);
          temp2[len2] = '/';
          temp2[len2 + 1] = '\0';
        }
      free (local_dirname);
      *dirname = temp2;
      free (temp1);
    }
  return (return_value);
}

#if defined (DYNAMIC_HISTORY_COMPLETION)
static char **history_completion_array = (char **)NULL;
static int harry_size = 0;
static int harry_len = 0;

static void
build_history_completion_array ()
{
  register int i;

  /* First, clear out the current dynamic history completion list. */
  if (harry_size)
    {
      for (i = 0; history_completion_array[i]; i++)
	free (history_completion_array[i]);

      free (history_completion_array);

      history_completion_array = (char **)NULL;
      harry_size = 0;
      harry_len = 0;
    }

  /* Next, grovel each line of history, making each shell-sized token
     a separate entry in the history_completion_array. */
  {
    HIST_ENTRY **hlist;

    hlist = history_list ();

    if (hlist)
      {
	register int j;

	for (i = 0; hlist[i]; i++)
	  {
	    char **tokens;

	    /* Separate each token, and place into an array. */
	    tokens = history_tokenize (hlist[i]->line);

	    for (j = 0; tokens && tokens[j]; j++)
	      {
		if (harry_len + 2 > harry_size)
		  history_completion_array = (char **) xrealloc
		    (history_completion_array,
		     (harry_size += 10) * sizeof (char *));

		history_completion_array[harry_len++] = tokens[j];
		history_completion_array[harry_len] = (char *)NULL;
	      }
	    free (tokens);
	  }

	/* Sort the complete list of tokens. */
	qsort (history_completion_array, harry_len, sizeof (char *),
	       (Function *)qsort_string_compare);

	/* Instead of removing the duplicate entries here, we let the
	   code in the completer handle it. */
      }
  }
}

static char *
history_completion_generator (hint_text, state)
     char *hint_text;
     int state;
{
  static int local_index = 0;
  static char *text = (char *)NULL;
  static int len = 0;

  /* If this is the first call to the generator, then initialize the
     list of strings to complete over. */
  if (!state)
    {
      local_index = 0;
      build_history_completion_array ();
      text = hint_text;
      len = strlen (text);
    }

  while (history_completion_array && history_completion_array[local_index])
    {
      if (strncmp (text, history_completion_array[local_index++], len) == 0)
	return (savestring (history_completion_array[local_index - 1]));
    }
  return ((char *)NULL);
}

static void
dynamic_complete_history (count, key)
     int count, key;
{
  Function *orig_func;
  CPPFunction *orig_attempt_func;

  orig_func = rl_completion_entry_function;
  orig_attempt_func = rl_attempted_completion_function;
  rl_completion_entry_function = (Function *)history_completion_generator;
  rl_attempted_completion_function = (CPPFunction *)NULL;

  if (rl_last_func == (Function *)dynamic_complete_history)
    rl_complete_internal ('?');
  else
    rl_complete_internal (TAB);

  rl_completion_entry_function = orig_func;
  rl_attempted_completion_function = orig_attempt_func;
}

#endif /* DYNAMIC_HISTORY_COMPLETION */

#if defined (SPECIFIC_COMPLETION_FUNCTIONS)
static void
bash_complete_username (ignore, ignore2)
     int ignore, ignore2;
{
  bash_complete_username_internal (TAB);
}

static void
bash_possible_username_completions (ignore, ignore2)
     int ignore, ignore2;
{
  bash_complete_username_internal ('?');
}

static void
bash_complete_username_internal (what_to_do)
     int what_to_do;
{
  bash_specific_completion
    (what_to_do, (Function *)username_completion_function);
}

static void
bash_complete_filename (ignore, ignore2)
     int ignore, ignore2;
{
  bash_complete_filename_internal (TAB);
}

static void
bash_possible_filename_completions (ignore, ignore2)
     int ignore, ignore2;
{
  bash_complete_filename_internal ('?');
}

static void
bash_complete_filename_internal (what_to_do)
     int what_to_do;
{
  Function  *orig_func, *orig_dir_func;
  CPPFunction *orig_attempt_func;
  char *orig_rl_completer_word_break_characters;

  orig_func = rl_completion_entry_function;
  orig_attempt_func = rl_attempted_completion_function;
  orig_dir_func = rl_directory_completion_hook;
  orig_rl_completer_word_break_characters = rl_completer_word_break_characters;
  rl_completion_entry_function = (Function *)filename_completion_function;
  rl_attempted_completion_function = (CPPFunction *)NULL;
  rl_directory_completion_hook = (Function *)NULL;
  rl_completer_word_break_characters = " \t\n\"\'";

  rl_complete_internal (what_to_do);

  rl_completion_entry_function = orig_func;
  rl_attempted_completion_function = orig_attempt_func;
  rl_directory_completion_hook = orig_dir_func;
  rl_completer_word_break_characters = orig_rl_completer_word_break_characters;
}

static void
bash_complete_hostname (ignore, ignore2)
     int ignore, ignore2;
{
  bash_complete_hostname_internal (TAB);
}

static void
bash_possible_hostname_completions (ignore, ignore2)
     int ignore, ignore2;
{
  bash_complete_hostname_internal ('?');
}

static void
bash_complete_variable (ignore, ignore2)
     int ignore, ignore2;
{
  bash_complete_variable_internal (TAB);
}

static void
bash_possible_variable_completions (ignore, ignore2)
     int ignore, ignore2;
{
  bash_complete_variable_internal ('?');
}

static void
bash_complete_command (ignore, ignore2)
     int ignore, ignore2;
{
  bash_complete_command_internal (TAB);
}

static void
bash_possible_command_completions (ignore, ignore2)
     int ignore, ignore2;
{
  bash_complete_command_internal ('?');
}

static void
bash_complete_hostname_internal (what_to_do)
     int what_to_do;
{
  bash_specific_completion
    (what_to_do, (Function *)hostname_completion_function);
}

static void
bash_complete_variable_internal (what_to_do)
     int what_to_do;
{
  bash_specific_completion
    (what_to_do, (Function *)variable_completion_function);
}

static void
bash_complete_command_internal (what_to_do)
     int what_to_do;
{
  bash_specific_completion
    (what_to_do, (Function *)command_word_completion_function);
}

static void
bash_specific_completion (what_to_do, generator)
     int what_to_do;
     Function *generator;
{
  Function *orig_func;
  CPPFunction *orig_attempt_func;

  orig_func = rl_completion_entry_function;
  orig_attempt_func = rl_attempted_completion_function;
  rl_completion_entry_function = generator;
  rl_attempted_completion_function = (CPPFunction *)NULL;

  rl_complete_internal (what_to_do);

  rl_completion_entry_function = orig_func;
  rl_attempted_completion_function = orig_attempt_func;
}

#endif	/* SPECIFIC_COMPLETION_FUNCTIONS */
