Imported from ../bash-3.2.48.tar.gz.
[platform/upstream/bash.git] / bashline.c
index fbd196a..fa4055e 100644 (file)
@@ -1,12 +1,12 @@
 /* bashline.c -- Bash's interface to the readline library. */
 
-/* Copyright (C) 1987,1991 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2006 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)
+   the Free Software Foundation; either version 2, or (at your option)
    any later version.
 
    Bash is distributed in the hope that it will be useful, but WITHOUT
@@ -16,7 +16,7 @@
 
    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. */
+   Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
 
 #include "config.h"
 
 #  include <unistd.h>
 #endif
 
+#if defined (HAVE_GRP_H)
+#  include <grp.h>
+#endif
+
+#if defined (HAVE_NETDB_H)
+#  include <netdb.h>
+#endif
+
 #include <stdio.h>
+#include "chartypes.h"
 #include "bashansi.h"
+#include "bashintl.h"
+
 #include "shell.h"
+#include "input.h"
 #include "builtins.h"
 #include "bashhist.h"
 #include "bashline.h"
 #include "execute_cmd.h"
+#include "findcmd.h"
 #include "pathexp.h"
 #include "builtins/common.h"
 #include <readline/rlconf.h>
 #  include "alias.h"
 #endif
 
+#if defined (PROGRAMMABLE_COMPLETION)
+#  include "pcomplete.h"
+#endif
+
+/* These should agree with the defines for emacs_mode and vi_mode in
+   rldefs.h, even though that's not a public readline header file. */
+#ifndef EMACS_EDITING_MODE
+#  define NO_EDITING_MODE      -1
+#  define EMACS_EDITING_MODE    1
+#  define VI_EDITING_MODE       0
+#endif
+
 #if defined (BRACE_COMPLETION)
-extern void bash_brace_completion ();
+extern int bash_brace_completion __P((int, int));
 #endif /* BRACE_COMPLETION */
 
+/* Forward declarations */
+
 /* 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 ();
+static int shell_expand_line __P((int, int));
+static int display_shell_version __P((int, int));
+static int operate_and_get_next __P((int, int));
+
+static int bash_ignore_filenames __P((char **));
+static int bash_ignore_everything __P((char **));
+
+#if defined (BANG_HISTORY)
+static char *history_expand_line_internal __P((char *));
+static int history_expand_line __P((int, int));
+static int tcsh_magic_space __P((int, int));
+#endif /* BANG_HISTORY */
 #ifdef ALIAS
-static void alias_expand_line ();
+static int alias_expand_line __P((int, int));
+#endif
+#if defined (BANG_HISTORY) && defined (ALIAS)
+static int history_and_alias_expand_line __P((int, int));
 #endif
-static void history_and_alias_expand_line ();
-static void cleanup_expansion_error (), set_up_new_line ();
 
 /* Helper functions for Readline. */
-static int bash_directory_completion_hook ();
-static void filename_completion_ignore ();
-static void bash_push_line ();
+static void bash_directory_expansion __P((char **));
+static int bash_directory_completion_hook __P((char **));
+static int filename_completion_ignore __P((char **));
+static int bash_push_line __P((void));
+
+static void cleanup_expansion_error __P((void));
+static void maybe_make_readline_line __P((char *));
+static void set_up_new_line __P((char *));
+
+static int check_redir __P((int));
+static char **attempt_shell_completion __P((const char *, int, int));
+static char *variable_completion_function __P((const char *, int));
+static char *hostname_completion_function __P((const char *, int));
+static char *command_subst_completion_function __P((const char *, int));
 
-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 dynamic_complete_history ();
+static void build_history_completion_array __P((void));
+static char *history_completion_generator __P((const char *, int));
+static int dynamic_complete_history __P((int, int));
 
-static char *glob_complete_word ();
-static void bash_glob_expand_word ();
-static void bash_glob_list_expansions ();
+static void initialize_hostname_list __P((void));
+static void add_host_name __P((char *));
+static void snarf_hosts_from_file __P((char *));
+static char **hostnames_matching __P((char *));
 
-static void snarf_hosts_from_file (), add_host_name ();
+static void _ignore_completion_names __P((char **, sh_ignore_func_t *));
+static int name_is_acceptable __P((const char *));
+static int test_for_directory __P((const char *));
+static int return_zero __P((const char *));
 
-static char *bash_dequote_filename ();
-static char *bash_quote_filename ();
+static char *bash_dequote_filename __P((char *, int));
+static char *quote_word_break_chars __P((char *));
+static char *bash_quote_filename __P((char *, int, char *));
+
+static int bash_execute_unix_command __P((int, int));
+static void init_unix_command_map __P((void));
+static int isolate_sequence __P((char *, int, int, int *));
+
+static int set_saved_history __P((void));
 
 #if defined (ALIAS)
-static int posix_edit_macros ();
+static int posix_edit_macros __P((int, int));
+#endif
+
+#if defined (PROGRAMMABLE_COMPLETION)
+static int find_cmd_start __P((int));
+static int find_cmd_end __P((int));
+static char *find_cmd_name __P((int));
+static char *prog_complete_return __P((const char *, int));
+
+static char **prog_complete_matches;
 #endif
 
 /* Variables used here but defined in other files. */
+#if defined (BANG_HISTORY)
+extern int hist_verify;
+#endif
+
+extern int current_command_line_count, last_command_exit_value;
 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;
-
-/* Helper functions from subst.c */
-extern int char_is_quoted ();
-extern int unclosed_pair ();
+extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin;
 
 /* SPECIFIC_COMPLETION_FUNCTIONS specifies that we have individual
    completion functions which indicate what type of completion should be
@@ -106,22 +170,39 @@ extern int unclosed_pair ();
 #define SPECIFIC_COMPLETION_FUNCTIONS
 
 #if defined (SPECIFIC_COMPLETION_FUNCTIONS)
-static void bash_specific_completion ();
-static void bash_complete_filename (), bash_possible_filename_completions ();
-static void bash_complete_filename_internal ();
-static void bash_complete_username (), bash_possible_username_completions ();
-static void bash_complete_username_internal ();
-static void bash_complete_hostname (), bash_possible_hostname_completions ();
-static void bash_complete_hostname_internal ();
-static void bash_complete_variable (), bash_possible_variable_completions ();
-static void bash_complete_variable_internal ();
-static void bash_complete_command (), bash_possible_command_completions ();
-static void bash_complete_command_internal ();
+static int bash_specific_completion __P((int, rl_compentry_func_t *));
+
+static int bash_complete_filename_internal __P((int));
+static int bash_complete_username_internal __P((int));
+static int bash_complete_hostname_internal __P((int));
+static int bash_complete_variable_internal __P((int));
+static int bash_complete_command_internal __P((int));
+
+static int bash_complete_filename __P((int, int));
+static int bash_possible_filename_completions __P((int, int));
+static int bash_complete_username __P((int, int));
+static int bash_possible_username_completions __P((int, int));
+static int bash_complete_hostname __P((int, int));
+static int bash_possible_hostname_completions __P((int, int));
+static int bash_complete_variable __P((int, int));
+static int bash_possible_variable_completions __P((int, int));
+static int bash_complete_command __P((int, int));
+static int bash_possible_command_completions __P((int, int));
+
+static char *glob_complete_word __P((const char *, int));
+static int bash_glob_completion_internal __P((int));
+static int bash_glob_complete_word __P((int, int));
+static int bash_glob_expand_word __P((int, int));
+static int bash_glob_list_expansions __P((int, int));
+
 #endif /* SPECIFIC_COMPLETION_FUNCTIONS */
 
+static int edit_and_execute_command __P((int, int, int, char *));
 #if defined (VI_MODE)
-static void vi_edit_and_execute_command ();
+static int vi_edit_and_execute_command __P((int, int));
+static int bash_vi_complete __P((int, int));
 #endif
+static int emacs_edit_and_execute_command __P((int, int));
 
 /* Non-zero once initalize_readline () has been called. */
 int bash_readline_initialized = 0;
@@ -131,10 +212,21 @@ int bash_readline_initialized = 0;
    host list. */
 int perform_hostname_completion = 1;
 
+/* If non-zero, we don't do command completion on an empty line. */
+int no_empty_command_completion;
+
+/* Set FORCE_FIGNORE if you want to honor FIGNORE even if it ignores the
+   only possible matches.  Set to 0 if you want to match filenames if they
+   are the only possible matches, even if FIGNORE says to. */
+int force_fignore = 1;
+
 static char *bash_completer_word_break_characters = " \t\n\"'@><=;|&(:";
 static char *bash_nohostname_word_break_characters = " \t\n\"'><=;|&(:";
+/* )) */
 
-static Function *old_rl_startup_hook = (Function *) NULL;
+static rl_hook_func_t *old_rl_startup_hook = (rl_hook_func_t *)NULL;
+
+static int dot_in_path = 0;
 
 /* What kind of quoting is performed by bash_quote_filename:
        COMPLETE_DQUOTE = double-quoting the filename
@@ -146,6 +238,9 @@ static Function *old_rl_startup_hook = (Function *) NULL;
 #define COMPLETE_BSQUOTE 3
 static int completion_quoting_style = COMPLETE_BSQUOTE;
 
+/* Flag values for the final argument to bash_default_completion */
+#define DEFCOMP_CMDPOS         1
+
 /* 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
@@ -155,32 +250,91 @@ posix_readline_initialize (on_or_off)
   if (on_or_off)
     rl_variable_bind ("comment-begin", "#");
 #if defined (VI_MODE)
-  rl_bind_key_in_map (CTRL('I'), on_or_off ? rl_insert : rl_complete, vi_insertion_keymap);
+  rl_bind_key_in_map (CTRL ('I'), on_or_off ? rl_insert : rl_complete, vi_insertion_keymap);
 #endif
 }
 
-void
+/* When this function returns, rl_completer_word_break_characters points to
+   dynamically allocated memory. */
+int
 enable_hostname_completion (on_or_off)
      int on_or_off;
 {
+  int old_value;
+  char *at, *nv, *nval;
+
+  old_value = perform_hostname_completion;
+
   if (on_or_off)
     {
       perform_hostname_completion = 1;
       rl_special_prefixes = "$@";
-      rl_completer_word_break_characters = bash_completer_word_break_characters;
     }
   else
     {
       perform_hostname_completion = 0;
       rl_special_prefixes = "$";
-      rl_completer_word_break_characters = bash_nohostname_word_break_characters;
     }
+
+  /* Now we need to figure out how to appropriately modify and assign
+     rl_completer_word_break_characters depending on whether we want
+     hostname completion on or off. */
+
+  /* If this is the first time this has been called
+     (bash_readline_initialized == 0), use the sames values as before, but
+     allocate new memory for rl_completer_word_break_characters. */
+
+  if (bash_readline_initialized == 0 &&
+      (rl_completer_word_break_characters == 0 || 
+       rl_completer_word_break_characters == rl_basic_word_break_characters))
+    {
+      if (on_or_off)
+       rl_completer_word_break_characters = savestring (bash_completer_word_break_characters);
+      else
+       rl_completer_word_break_characters = savestring (bash_nohostname_word_break_characters);
+    }
+  else
+    {
+      /* See if we have anything to do. */
+      at = strchr (rl_completer_word_break_characters, '@');
+      if ((at == 0 && on_or_off == 0) || (at != 0 && on_or_off != 0))
+        return old_value;
+
+      /* We have something to do.  Do it. */
+      nval = (char *)xmalloc (strlen (rl_completer_word_break_characters) + 1 + on_or_off);
+
+      if (on_or_off == 0)
+       {
+         /* Turn it off -- just remove `@' from word break chars.  We want
+            to remove all occurrences of `@' from the char list, so we loop
+            rather than just copy the rest of the list over AT. */
+         for (nv = nval, at = rl_completer_word_break_characters; *at; )
+           if (*at != '@')
+             *nv++ = *at++;
+           else
+             at++;
+         *nv = '\0';
+       }
+      else
+       {
+         nval[0] = '@';
+         strcpy (nval + 1, rl_completer_word_break_characters);
+        }
+
+      free (rl_completer_word_break_characters);
+      rl_completer_word_break_characters = nval;
+    }
+
+  return (old_value);
 }
 
 /* Called once from parse.y if we are going to use readline. */
 void
 initialize_readline ()
 {
+  rl_command_func_t *func;
+  char kseq[2];
+
   if (bash_readline_initialized)
     return;
 
@@ -191,100 +345,140 @@ initialize_readline ()
   /* 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);
+  /* Add bindable names before calling rl_initialize so they may be
+     referenced in the various inputrc files. */
+  rl_add_defun ("shell-expand-line", shell_expand_line, -1);
+#ifdef BANG_HISTORY
+  rl_add_defun ("history-expand-line", history_expand_line, -1);
+  rl_add_defun ("magic-space", tcsh_magic_space, -1);
+#endif
 
 #ifdef ALIAS
-  rl_add_defun ("alias-expand-line", (Function *)alias_expand_line, -1);
-  rl_add_defun ("history-and-alias-expand-line", (Function *)history_and_alias_expand_line, -1);
+  rl_add_defun ("alias-expand-line", alias_expand_line, -1);
+#  ifdef BANG_HISTORY
+  rl_add_defun ("history-and-alias-expand-line", history_and_alias_expand_line, -1);
+#  endif
 #endif
 
   /* 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 ("operate-and-get-next", operate_and_get_next, -1);
+  rl_add_defun ("display-shell-version", display_shell_version, -1);
+  rl_add_defun ("edit-and-execute-command", emacs_edit_and_execute_command, -1);
+
+#if defined (BRACE_COMPLETION)
+  rl_add_defun ("complete-into-braces", bash_brace_completion, -1);
+#endif
+
+#if defined (SPECIFIC_COMPLETION_FUNCTIONS)
+  rl_add_defun ("complete-filename", bash_complete_filename, -1);
+  rl_add_defun ("possible-filename-completions", bash_possible_filename_completions, -1);
+  rl_add_defun ("complete-username", bash_complete_username, -1);
+  rl_add_defun ("possible-username-completions", bash_possible_username_completions, -1);
+  rl_add_defun ("complete-hostname", bash_complete_hostname, -1);
+  rl_add_defun ("possible-hostname-completions", bash_possible_hostname_completions, -1);
+  rl_add_defun ("complete-variable", bash_complete_variable, -1);
+  rl_add_defun ("possible-variable-completions", bash_possible_variable_completions, -1);
+  rl_add_defun ("complete-command", bash_complete_command, -1);
+  rl_add_defun ("possible-command-completions", bash_possible_command_completions, -1);
+  rl_add_defun ("glob-complete-word", bash_glob_complete_word, -1);
+  rl_add_defun ("glob-expand-word", bash_glob_expand_word, -1);
+  rl_add_defun ("glob-list-expansions", bash_glob_list_expansions, -1);
+#endif
+
+  rl_add_defun ("dynamic-complete-history", dynamic_complete_history, -1);
+
+  /* Bind defaults before binding our custom shell keybindings. */
+  if (RL_ISSTATE(RL_STATE_INITIALIZED) == 0)
+    rl_initialize ();
+
+  /* Bind up our special shell functions. */
+  rl_bind_key_if_unbound_in_map (CTRL('E'), shell_expand_line, emacs_meta_keymap);
+
+#ifdef BANG_HISTORY
+  rl_bind_key_if_unbound_in_map ('^', history_expand_line, emacs_meta_keymap);
+#endif
 
-  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);
+  rl_bind_key_if_unbound_in_map (CTRL ('O'), operate_and_get_next, emacs_standard_keymap);
+  rl_bind_key_if_unbound_in_map (CTRL ('V'), 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);
+  kseq[0] = CTRL('J');
+  kseq[1] = '\0';
+  func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL);
+  if (func == rl_vi_editing_mode)
+    rl_unbind_key_in_map (CTRL('J'), emacs_meta_keymap);
+  kseq[0] = CTRL('M');
+  func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL);
+  if (func == rl_vi_editing_mode)
+    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);
+  rl_bind_key_if_unbound_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_bind_key_if_unbound_in_map ('/', bash_complete_filename, emacs_meta_keymap);
+  rl_bind_key_if_unbound_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);
+  /* Have to jump through hoops here because there is a default binding for
+     M-~ (rl_tilde_expand) */
+  kseq[0] = '~';
+  kseq[1] = '\0';
+  func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL);
+  if (func == 0 || func == rl_tilde_expand)
+    rl_bind_keyseq_in_map (kseq, bash_complete_username, emacs_meta_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_bind_key_if_unbound_in_map ('~', bash_possible_username_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_bind_key_if_unbound_in_map ('@', bash_complete_hostname, emacs_meta_keymap);
+  rl_bind_key_if_unbound_in_map ('@', bash_possible_hostname_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);
+  rl_bind_key_if_unbound_in_map ('$', bash_complete_variable, emacs_meta_keymap);
+  rl_bind_key_if_unbound_in_map ('$', bash_possible_variable_completions, emacs_ctlx_keymap);
 
-  rl_add_defun ("glob-expand-word", bash_glob_expand_word, -1);
-  rl_add_defun ("glob-list-expansions", bash_glob_list_expansions, -1);
-  rl_bind_key_in_map ('*', bash_glob_expand_word, emacs_ctlx_keymap);
-  rl_bind_key_in_map ('g', bash_glob_list_expansions, emacs_ctlx_keymap);
+  rl_bind_key_if_unbound_in_map ('!', bash_complete_command, emacs_meta_keymap);
+  rl_bind_key_if_unbound_in_map ('!', bash_possible_command_completions, emacs_ctlx_keymap);
+
+  rl_bind_key_if_unbound_in_map ('g', bash_glob_complete_word, emacs_meta_keymap);
+  rl_bind_key_if_unbound_in_map ('*', bash_glob_expand_word, emacs_ctlx_keymap);
+  rl_bind_key_if_unbound_in_map ('g', bash_glob_list_expansions, emacs_ctlx_keymap);
 
 #endif /* SPECIFIC_COMPLETION_FUNCTIONS */
 
-  rl_add_defun ("dynamic-complete-history", dynamic_complete_history, -1);
-  rl_bind_key_in_map (TAB, dynamic_complete_history, emacs_meta_keymap);
+  kseq[0] = TAB;
+  kseq[1] = '\0';
+  func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL);
+  if (func == 0 || func == rl_tab_insert)
+    rl_bind_key_in_map (TAB, dynamic_complete_history, emacs_meta_keymap);
 
   /* Tell the completer that we want a crack first. */
-  rl_attempted_completion_function = (CPPFunction *)attempt_shell_completion;
+  rl_attempted_completion_function = 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;
+  rl_ignore_some_completions_function = filename_completion_ignore;
 
+  /* Bind C-xC-e to invoke emacs and run result as commands. */
+  rl_bind_key_if_unbound_in_map (CTRL ('E'), emacs_edit_and_execute_command, emacs_ctlx_keymap);
 #if defined (VI_MODE)
-  rl_bind_key_in_map ('v', vi_edit_and_execute_command, vi_movement_keymap);
+  rl_bind_key_if_unbound_in_map ('v', vi_edit_and_execute_command, vi_movement_keymap);
 #  if defined (ALIAS)
-  rl_bind_key_in_map ('@', posix_edit_macros, vi_movement_keymap);
+  rl_bind_key_if_unbound_in_map ('@', posix_edit_macros, vi_movement_keymap);
 #  endif
+
+  rl_bind_key_in_map ('\\', bash_vi_complete, vi_movement_keymap);
+  rl_bind_key_in_map ('*', bash_vi_complete, vi_movement_keymap);
+  rl_bind_key_in_map ('=', bash_vi_complete, vi_movement_keymap);
 #endif
 
   rl_completer_quote_characters = "'\"";
@@ -295,13 +489,19 @@ initialize_readline ()
   enable_hostname_completion (perform_hostname_completion);
 
   /* characters that need to be quoted when appearing in filenames. */
-  rl_filename_quote_characters = " \t\n\\\"'@<>=;|&()#$`?*[!:";
+  rl_filename_quote_characters = " \t\n\\\"'@<>=;|&()#$`?*[!:{";       /*}*/
   rl_filename_quoting_function = bash_quote_filename;
   rl_filename_dequoting_function = bash_dequote_filename;
   rl_char_is_quoted_p = char_is_quoted;
 
+#if 0
+  /* This is superfluous and makes it impossible to use tab completion in
+     vi mode even when explicitly binding it in ~/.inputrc.  sv_strict_posix()
+     should already have called posix_readline_initialize() when
+     posixly_correct was set. */
   if (posixly_correct)
     posix_readline_initialize (1);
+#endif
 
   bash_readline_initialized = 1;
 }
@@ -316,9 +516,9 @@ bashline_reinitialize ()
 {
   tilde_initialize ();
   rl_attempted_completion_function = attempt_shell_completion;
-  rl_completion_entry_function = (Function *)NULL;
+  rl_completion_entry_function = NULL;
   rl_directory_completion_hook = bash_directory_completion_hook;
-  rl_ignore_some_completions_function = (Function *)filename_completion_ignore;
+  rl_ignore_some_completions_function = filename_completion_ignore;
 }
 
 /* Contains the line to push into readline. */
@@ -326,7 +526,7 @@ static char *push_to_readline = (char *)NULL;
 
 /* Push the contents of push_to_readline into the
    readline buffer. */
-static void
+static int
 bash_push_line ()
 {
   if (push_to_readline)
@@ -336,6 +536,7 @@ bash_push_line ()
       push_to_readline = (char *)NULL;
       rl_startup_hook = old_rl_startup_hook;
     }
+  return 0;
 }
 
 /* Call this to set the initial text for the next line to read
@@ -348,21 +549,22 @@ bash_re_edit (line)
 
   push_to_readline = savestring (line);
   old_rl_startup_hook = rl_startup_hook;
-  rl_startup_hook = (Function *)bash_push_line;
+  rl_startup_hook = bash_push_line;
 
   return (0);
 }
 
-static void
+static int
 display_shell_version (count, c)
      int count, c;
 {
-  crlf ();
+  rl_crlf ();
   show_shell_version (0);
   putc ('\r', rl_outstream);
   fflush (rl_outstream);
   rl_on_new_line ();
   rl_redisplay ();
+  return 0;
 }
 
 /* **************************************************************** */
@@ -372,8 +574,10 @@ display_shell_version (count, c)
 /* **************************************************************** */
 
 /* If the user requests hostname completion, then simply build a list
-   of hosts, and complete from that forever more. */
+   of hosts, and complete from that forever more, or at least until
+   HOSTFILE is unset. */
 
+/* THIS SHOULD BE A STRINGLIST. */
 /* The kept list of hostnames. */
 static char **hostname_list = (char **)NULL;
 
@@ -409,13 +613,10 @@ static void
 add_host_name (name)
      char *name;
 {
-  long size;
-
   if (hostname_list_length + 2 > hostname_list_size)
     {
       hostname_list_size = (hostname_list_size + 32) - (hostname_list_size % 32);
-      size = hostname_list_size * sizeof (char *);
-      hostname_list = (char **)xrealloc (hostname_list, size);
+      hostname_list = strvec_resize (hostname_list, hostname_list_size);
     }
 
   hostname_list[hostname_list_length++] = savestring (name);
@@ -466,7 +667,7 @@ snarf_hosts_from_file (filename)
        }
 
       /* Skip internet address if present. */
-      if (digit (buffer[i]))
+      if (DIGIT (buffer[i]))
        for (; buffer[i] && cr_whitespace (buffer[i]) == 0; i++);
 
       /* Gobble up names.  Each name is separated with whitespace. */
@@ -490,6 +691,27 @@ snarf_hosts_from_file (filename)
   fclose (file);
 }
 
+/* Return the hostname list. */
+char **
+get_hostname_list ()
+{
+  if (hostname_list_initialized == 0)
+    initialize_hostname_list ();
+  return (hostname_list);
+}
+
+void
+clear_hostname_list ()
+{
+  register int i;
+
+  if (hostname_list_initialized == 0)
+    return;
+  for (i = 0; i < hostname_list_length; i++)
+    free (hostname_list[i]);
+  hostname_list_length = 0;
+}
+
 /* 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. */
@@ -510,7 +732,7 @@ hostnames_matching (text)
      what is desired. */
   if (*text == '\0')
     {
-      result = (char **)xmalloc ((1 + hostname_list_length) * sizeof (char *));
+      result = strvec_create (1 + hostname_list_length);
       for (i = 0; i < hostname_list_length; i++)
        result[i] = hostname_list[i];
       result[i] = (char *)NULL;
@@ -523,13 +745,13 @@ hostnames_matching (text)
   for (i = nmatch = rsize = 0; i < hostname_list_length; i++)
     {
       if (STREQN (text, hostname_list[i], len) == 0)
-        continue;
+       continue;
 
       /* OK, it matches.  Add it to the list. */
-      if (nmatch >= rsize)
+      if (nmatch >= (rsize - 1))
        {
          rsize = (rsize + 16) - (rsize % 16);
-         result = (char **)xrealloc (result, rsize * sizeof (char *));
+         result = strvec_resize (result, rsize);
        }
 
       result[nmatch++] = hostname_list[i];
@@ -543,57 +765,66 @@ hostnames_matching (text)
    editing command. */
 static int saved_history_line_to_use = -1;
 
-static void
+static int
 set_saved_history ()
 {
   if (saved_history_line_to_use >= 0)
-    rl_get_previous_history (history_length - saved_history_line_to_use);
+    rl_get_previous_history (history_length - saved_history_line_to_use, 0);
   saved_history_line_to_use = -1;
   rl_startup_hook = old_rl_startup_hook;
+  return (0);
 }
 
-static void
+static int
 operate_and_get_next (count, c)
      int count, c;
 {
   int where;
 
   /* Accept the current line. */
-  rl_newline ();
+  rl_newline (1, c);
 
   /* Find the current line, and find the next line to use. */
   where = where_history ();
 
-  if ((history_is_stifled () && (history_length >= max_input_history)) ||
+  if ((history_is_stifled () && (history_length >= history_max_entries)) ||
       (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;
+  rl_startup_hook = set_saved_history;
+
+  return 0;
 }
 
-#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}}"
+#define VI_EDIT_COMMAND                "fc -e \"${VISUAL:-${EDITOR:-vi}}\""
+#define EMACS_EDIT_COMMAND     "fc -e \"${VISUAL:-${EDITOR:-emacs}}\""
+#define POSIX_VI_EDIT_COMMAND  "fc -e vi"
 
-static void
-vi_edit_and_execute_command (count, c)
-     int count, c;
+static int
+edit_and_execute_command (count, c, editing_mode, edit_command)
+     int count, c, editing_mode;
+     char *edit_command;
 {
   char *command;
+  int r, cclc, rrs;
+
+  rrs = rl_readline_state;
+  cclc = current_command_line_count;
 
   /* Accept the current line. */
-  rl_newline ();
+  rl_newline (1, c);
 
   if (rl_explicit_arg)
     {
-      command = xmalloc (strlen (VI_EDIT_COMMAND) + 8);
-      sprintf (command, "%s %d", VI_EDIT_COMMAND, count);
+      command = (char *)xmalloc (strlen (edit_command) + 8);
+      sprintf (command, "%s %d", edit_command, count);
     }
   else
     {
@@ -606,13 +837,48 @@ vi_edit_and_execute_command (count, c)
       bash_add_history ("");
       history_lines_this_session++;
       using_history ();
-      command = savestring (VI_EDIT_COMMAND);
+      command = savestring (edit_command);
     }
-  parse_and_execute (command, "v", SEVAL_NOHIST);
+
+  /* Now, POSIX.1-2001 and SUSv3 say that the commands executed from the
+     temporary file should be placed into the history.  We don't do that
+     yet. */
+  r = parse_and_execute (command, (editing_mode == VI_EDITING_MODE) ? "v" : "C-xC-e", SEVAL_NOHIST);
+
+  current_command_line_count = cclc;
+
+  /* Now erase the contents of the current line and undo the effects of the
+     rl_accept_line() above.  We don't even want to make the text we just
+     executed available for undoing. */
   rl_line_buffer[0] = '\0';    /* XXX */
+  rl_point = rl_end = 0;
+  rl_done = 0;
+  rl_readline_state = rrs;
+
+  rl_forced_update_display ();
+
+  return r;
+}
+
+#if defined (VI_MODE)
+static int
+vi_edit_and_execute_command (count, c)
+     int count, c;
+{
+  if (posixly_correct)
+    return (edit_and_execute_command (count, c, VI_EDITING_MODE, POSIX_VI_EDIT_COMMAND));
+  else
+    return (edit_and_execute_command (count, c, VI_EDITING_MODE, VI_EDIT_COMMAND));
 }
 #endif /* VI_MODE */
 
+static int
+emacs_edit_and_execute_command (count, c)
+     int count, c;
+{
+  return (edit_and_execute_command (count, c, EMACS_EDITING_MODE, EMACS_EDIT_COMMAND));
+}
+
 #if defined (ALIAS)
 static int
 posix_edit_macros (count, key)
@@ -642,53 +908,142 @@ posix_edit_macros (count, key)
 /*                                                                 */
 /* **************************************************************** */
 
+#define COMMAND_SEPARATORS ";|&{(`"
+/* )} */ 
+
+static int
+check_redir (ti)
+     int ti;
+{
+  register int this_char, prev_char;
+
+  /* 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 == '>'))
+    return (1);
+  else if ((this_char == '{' && prev_char == '$') || /* } */
+          (char_is_quoted (rl_line_buffer, ti)))
+    return (1);
+  return (0);
+}
+
+#if defined (PROGRAMMABLE_COMPLETION)
+/*
+ * XXX - because of the <= start test, and setting os = s+1, this can
+ * potentially return os > start.  This is probably not what we want to
+ * happen, but fix later after 2.05a-release.
+ */
+static int
+find_cmd_start (start)
+     int start;
+{
+  register int s, os;
+
+  os = 0;
+  while (((s = skip_to_delim (rl_line_buffer, os, COMMAND_SEPARATORS)) <= start) &&
+        rl_line_buffer[s])
+    os = s+1;
+  return os;
+}
+
+static int
+find_cmd_end (end)
+     int end;
+{
+  register int e;
+
+  e = skip_to_delim (rl_line_buffer, end, COMMAND_SEPARATORS);
+  return e;
+}
+
+static char *
+find_cmd_name (start)
+     int start;
+{
+  char *name;
+  register int s, e;
+
+  for (s = start; whitespace (rl_line_buffer[s]); s++)
+    ;
+
+  /* skip until a shell break character */
+  e = skip_to_delim (rl_line_buffer, s, "()<>;&| \t\n");
+
+  name = substring (rl_line_buffer, s, e);
+
+  return (name);
+}
+
+static char *
+prog_complete_return (text, matchnum)
+     const char *text;
+     int matchnum;
+{
+  static int ind;
+
+  if (matchnum == 0)
+    ind = 0;
+
+  if (prog_complete_matches == 0 || prog_complete_matches[ind] == 0)
+    return (char *)NULL;
+  return (prog_complete_matches[ind++]);
+}
+
+#endif /* PROGRAMMABLE_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;
+     const char *text;
      int start, end;
 {
-  int in_command_position, ti;
+  int in_command_position, ti, saveti, qc, dflags;
   char **matches, *command_separator_chars;
 
-  command_separator_chars = ";|&{(`";
+  command_separator_chars = COMMAND_SEPARATORS;
   matches = (char **)NULL;
-  rl_ignore_some_completions_function = (Function *)filename_completion_ignore;
+  rl_ignore_some_completions_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;
+  saveti = qc = -1;
 
   while ((ti > -1) && (whitespace (rl_line_buffer[ti])))
     ti--;
 
+#if 1
+  /* If this is an open quote, maybe we're trying to complete a quoted
+     command name. */
+  if (ti >= 0 && (rl_line_buffer[ti] == '"' || rl_line_buffer[ti] == '\''))
+    {
+      qc = rl_line_buffer[ti];
+      saveti = ti--;
+      while (ti > -1 && (whitespace (rl_line_buffer[ti])))
+       ti--;
+    }
+#endif
+      
   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. */
+        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 ((this_char == '{' && prev_char == '$') ||
-              (char_is_quoted (rl_line_buffer, ti)))
+      if (check_redir (ti) == 1)
        in_command_position = 0;
     }
   else
@@ -700,51 +1055,168 @@ attempt_shell_completion (text, start, end)
 
   /* Check that we haven't incorrectly flagged a closed command substitution
      as indicating we're in a command position. */
-  if (in_command_position && rl_line_buffer[ti] == '`' && *text != '`' && 
-       unclosed_pair (rl_line_buffer, 0, "`") == 0)
+  if (in_command_position && ti >= 0 && rl_line_buffer[ti] == '`' &&
+       *text != '`' && unclosed_pair (rl_line_buffer, end, "`") == 0)
     in_command_position = 0;
 
   /* Special handling for command substitution.  If *TEXT is a backquote,
      it can be the start or end of an old-style command substitution, or
      unmatched.  If it's unmatched, both calls to unclosed_pair will
      succeed.  */
-  if (*text == '`' && unclosed_pair (rl_line_buffer, start, "`") &&
-       unclosed_pair (rl_line_buffer, end, "`"))
-    matches = completion_matches (text, command_subst_completion_function);
+  if (*text == '`' && 
+       (in_command_position || (unclosed_pair (rl_line_buffer, start, "`") &&
+                                unclosed_pair (rl_line_buffer, end, "`"))))
+    matches = rl_completion_matches (text, command_subst_completion_function);
+
+#if defined (PROGRAMMABLE_COMPLETION)
+  /* Attempt programmable completion. */
+  if (!matches && in_command_position == 0 && prog_completion_enabled &&
+      (progcomp_size () > 0) && current_prompt_string == ps1_prompt)
+    {
+      int s, e, foundcs;
+      char *n;
+
+      /* XXX - don't free the members */
+      if (prog_complete_matches)
+       free (prog_complete_matches);
+      prog_complete_matches = (char **)NULL;
+
+      s = find_cmd_start (start);
+      e = find_cmd_end (end);
+      n = find_cmd_name (s);
+      if (e > s && assignment (n, 0) == 0)
+       prog_complete_matches = programmable_completions (n, text, s, e, &foundcs);
+      else
+       foundcs = 0;
+      FREE (n);
+      /* XXX - if we found a COMPSPEC for the command, just return whatever
+        the programmable completion code returns, and disable the default
+        filename completion that readline will do unless the COPT_DEFAULT
+        option has been set with the `-o default' option to complete. */
+      if (foundcs)
+       {
+         /* If the user specified that the compspec returns filenames, make
+            sure that readline knows it. */
+         if (foundcs & COPT_FILENAMES)
+           rl_filename_completion_desired = 1;
+         /* If the user doesn't want a space appended, tell readline. */
+         if (foundcs & COPT_NOSPACE)
+           rl_completion_suppress_append = 1;
+         /* Turn what the programmable completion code returns into what
+            readline wants.  I should have made compute_lcd_of_matches
+            external... */
+         matches = rl_completion_matches (text, prog_complete_return);
+         if ((foundcs & COPT_DEFAULT) == 0)
+           rl_attempted_completion_over = 1;   /* no default */
+         if (matches || ((foundcs & COPT_BASHDEFAULT) == 0))
+           return (matches);
+       }
+    }
+#endif
+
+  if (matches == 0)
+    {
+      dflags = 0;
+      if (in_command_position)
+       dflags |= DEFCOMP_CMDPOS;
+      matches = bash_default_completion (text, start, end, qc, dflags);
+    }
+
+  return matches;
+}
+
+char **
+bash_default_completion (text, start, end, qc, compflags)
+     const char *text;
+     int start, end, qc, compflags;
+{
+  char **matches;
+
+  matches = (char **)NULL;
 
-  /* Variable name? */
+  /* New posix-style command substitution or variable name? */
   if (!matches && *text == '$')
-    matches = completion_matches (text, variable_completion_function);
+    {
+      if (qc != '\'' && text[1] == '(') /* ) */
+       matches = rl_completion_matches (text, command_subst_completion_function);
+      else
+       matches = rl_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);
+  if (!matches && *text == '~' && !xstrchr (text, '/'))
+    matches = rl_completion_matches (text, rl_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 && perform_hostname_completion && *text == '@')
-    matches = completion_matches (text, hostname_completion_function);
+    matches = rl_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)
+  if (matches == 0 && (compflags & DEFCOMP_CMDPOS))
     {
-      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;
+      /* If END == START and text[0] == 0, we are trying to complete an empty
+        command word. */
+      if (no_empty_command_completion && end == start && text[0] == '\0')
+       {
+         matches = (char **)NULL;
+         rl_ignore_some_completions_function = bash_ignore_everything;
+       }
+      else
+       {
+#define CMD_IS_DIR(x)  (absolute_pathname(x) == 0 && absolute_program(x) == 0 && *(x) != '~' && test_for_directory (x))
+
+         dot_in_path = 0;
+         matches = rl_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 == (char **)NULL)
+           rl_ignore_some_completions_function = bash_ignore_filenames;
+         else if (matches[1] == 0 && CMD_IS_DIR(matches[0]) && dot_in_path == 0)
+           /* If we found a single match, without looking in the current
+              directory (because it's not in $PATH), but the found name is
+              also a command in the current directory, suppress appending any
+              terminating character, since it's ambiguous. */
+           {
+             rl_completion_suppress_append = 1;
+             rl_filename_completion_desired = 0;
+           }
+         else if (matches[0] && matches[1] && STREQ (matches[0], matches[1]) && CMD_IS_DIR (matches[0]))
+           /* There are multiple instances of the same match (duplicate
+              completions haven't yet been removed).  In this case, all of
+              the matches will be the same, and the duplicate removal code
+              will distill them all down to one.  We turn on
+              rl_completion_suppress_append for the same reason as above.
+              Remember: we only care if there's eventually a single unique
+              completion.  If there are multiple completions this won't
+              make a difference and the problem won't occur. */
+           {
+             rl_completion_suppress_append = 1;
+             rl_filename_completion_desired = 0;
+           }
+       }
     }
 
   /* This could be a globbing pattern, so try to expand it using pathname
      expansion. */
   if (!matches && glob_pattern_p (text))
-    matches = completion_matches (text, glob_complete_word);
+    {
+      matches = rl_completion_matches (text, glob_complete_word);
+      /* A glob expression that matches more than one filename is problematic.
+        If we match more than one filename, punt. */
+      if (matches && matches[1] && rl_completion_type == TAB)
+       {
+         strvec_dispose (matches);
+         matches = (char **)0;
+       }
+    }
 
   return (matches);
 }
@@ -753,32 +1225,40 @@ attempt_shell_completion (text, start, end)
    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 *
+char *
 command_word_completion_function (hint_text, state)
-     char *hint_text;
+     const 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 char *dequoted_hint = (char *)NULL;
+  static int path_index, hint_len, dequoted_len, istate, igncase;
+  static int mapping_over, local_index, searching_path, hint_is_dir;
   static SHELL_VAR **varlist = (SHELL_VAR **)NULL;
 #if defined (ALIAS)
   static alias_t **alias_list = (alias_t **)NULL;
 #endif /* ALIAS */
+  char *temp;
 
   /* 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 (dequoted_hint && dequoted_hint != hint)
+       free (dequoted_hint);
       if (hint)
        free (hint);
 
-      mapping_over = 0;
+      mapping_over = searching_path = 0;
+      hint_is_dir = CMD_IS_DIR (hint_text);
       val = (char *)NULL;
 
+      temp = rl_variable_value ("completion-ignore-case");
+      igncase = strcmp (temp, "on") == 0;
+
       /* 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
@@ -788,13 +1268,27 @@ command_word_completion_function (hint_text, state)
          /* Perform tilde expansion on what's passed, so we don't end up
             passing filenames with tildes directly to stat(). */
          if (*hint_text == '~')
-           hint = bash_tilde_expand (hint_text);
+           hint = bash_tilde_expand (hint_text, 0);
          else
            hint = savestring (hint_text);
-         hint_len = strlen (hint);
+
+         dequoted_hint = hint;
+         /* If readline's completer found a quote character somewhere, but
+            didn't set the quote character, there must have been a quote
+            character embedded in the filename.  It can't be at the start of
+            the filename, so we need to dequote the filename before we look
+            in the file system for it. */
+         if (rl_completion_found_quote && rl_completion_quote_character == 0)
+           {
+             dequoted_hint = bash_dequote_filename (hint, 0);
+             free (hint);
+             hint = dequoted_hint;
+           }
+         dequoted_len = hint_len = strlen (hint);
 
          if (filename_hint)
            free (filename_hint);
+
          filename_hint = savestring (hint);
 
          mapping_over = 4;
@@ -802,11 +1296,17 @@ command_word_completion_function (hint_text, state)
          goto inner;
        }
 
-      hint = savestring (hint_text);
-      hint_len = strlen (hint);
+      dequoted_hint = hint = savestring (hint_text);
+      dequoted_len = hint_len = strlen (hint);
 
+      if (rl_completion_found_quote && rl_completion_quote_character == 0)
+       {
+         dequoted_hint = bash_dequote_filename (hint, 0);
+         dequoted_len = strlen (dequoted_hint);
+       }
+      
       path = get_string_value ("PATH");
-      path_index = 0;
+      path_index = dot_in_path = 0;
 
       /* Initialize the variables for each type of command word. */
       local_index = 0;
@@ -892,6 +1392,16 @@ command_word_completion_function (hint_text, state)
       mapping_over++;
     }
 
+  /* If the text passed is a directory in the current directory, return it
+     as a possible match.  Executables in directories in the current
+     directory can be specified using relative pathnames and successfully
+     executed even when `.' is not in $PATH. */
+  if (hint_is_dir)
+    {
+      hint_is_dir = 0; /* only return the hint text once */
+      return (savestring (hint_text));
+    }
+    
   /* Repeatedly call filename_completion_function while we have
      members of PATH left.  Question:  should we stat each file?
      Answer: we call executable_file () on each file. */
@@ -909,6 +1419,7 @@ command_word_completion_function (hint_text, state)
          (current_path = extract_colon_unit (path, &path_index)) == 0)
        return ((char *)NULL);
 
+      searching_path = 1;
       if (*current_path == 0)
        {
          free (current_path);
@@ -919,22 +1430,23 @@ command_word_completion_function (hint_text, state)
        {
          char *t;
 
-         t = bash_tilde_expand (current_path);
+         t = bash_tilde_expand (current_path, 0);
          free (current_path);
          current_path = t;
        }
 
+      if (current_path[0] == '.' && current_path[1] == '\0')
+       dot_in_path = 1;
+
       if (filename_hint)
        free (filename_hint);
 
-      filename_hint = xmalloc (2 + strlen (current_path) + hint_len);
-      sprintf (filename_hint, "%s/%s", current_path, hint);
-
+      filename_hint = sh_makepath (current_path, hint, 0);
       free (current_path);
     }
 
  inner:
-  val = filename_completion_function (filename_hint, istate);
+  val = rl_filename_completion_function (filename_hint, istate);
   istate = 1;
 
   if (val == 0)
@@ -949,20 +1461,35 @@ command_word_completion_function (hint_text, state)
   else
     {
       int match, freetemp;
-      char *temp;
+#if 0
+      char *temp;              /* shadows previous declaration */
+#endif
 
       if (absolute_program (hint))
        {
-         match = strncmp (val, hint, hint_len) == 0;
+         if (igncase == 0)
+           match = strncmp (val, hint, hint_len) == 0;
+         else
+           match = strncasecmp (val, hint, hint_len) == 0;
+
          /* If we performed tilde expansion, restore the original
             filename. */
          if (*hint_text == '~')
            {
-             int l, tl, vl;
+             int l, tl, vl, dl;
+             char *rd;
              vl = strlen (val);
              tl = strlen (hint_text);
+#if 0
              l = vl - hint_len;        /* # of chars added */
-             temp = xmalloc (l + 2 + tl);
+#else
+             rd = savestring (filename_hint);
+             bash_directory_expansion (&rd);
+             dl = strlen (rd);
+             l = vl - dl;              /* # of chars added */
+             free (rd);
+#endif
+             temp = (char *)xmalloc (l + 2 + tl);
              strcpy (temp, hint_text);
              strcpy (temp + tl, val + vl - l);
            }
@@ -977,7 +1504,10 @@ command_word_completion_function (hint_text, state)
          if (temp)
            {
              temp++;
-             freetemp = match = strncmp (temp, hint, hint_len) == 0;
+             if (igncase == 0)
+               freetemp = match = strncmp (temp, hint, hint_len) == 0;
+             else
+               freetemp = match = strncasecmp (temp, hint, hint_len) == 0;
              if (match)
                temp = savestring (temp);
            }
@@ -985,9 +1515,18 @@ command_word_completion_function (hint_text, state)
            freetemp = match = 0;
        }
 
+#if 0
       /* If we have found a match, and it is an executable file or a
         directory name, return it. */
-      if (match && (executable_file (val) || is_directory (val)))
+      if (match && executable_or_directory (val))
+#else
+      /* If we have found a match, and it is an executable file, return it.
+        We don't return directory names when searching $PATH, since the
+        bash execution code won't find executables in directories which
+        appear in directories in $PATH when they're specified using
+        relative pathnames. */
+      if (match && (searching_path ? executable_file (val) : executable_or_directory (val)))
+#endif
        {
          free (val);
          val = "";             /* So it won't be NULL. */
@@ -1006,11 +1545,12 @@ command_word_completion_function (hint_text, state)
 /* Completion inside an unterminated command substitution. */
 static char *
 command_subst_completion_function (text, state)
-     char *text;
+     const char *text;
      int state;
 {
   static char **matches = (char **)NULL;
-  static char *orig_start, *filename_text = (char *)NULL;
+  static const char *orig_start;
+  static char *filename_text = (char *)NULL;
   static int cmd_index, start_len;
   char *value;
 
@@ -1020,15 +1560,48 @@ command_subst_completion_function (text, state)
        free (filename_text);
       orig_start = text;
       if (*text == '`')
-        text++;
-      else if (*text == '$' && text[1] == '(')
-        text += 2;
+       text++;
+      else if (*text == '$' && text[1] == '(') /* ) */
+       text += 2;
+      /* If the text was quoted, suppress any quote character that the
+        readline completion code would insert. */
+      rl_completion_suppress_quote = 1;
       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;
+
+      /*
+       * At this point we can entertain the idea of re-parsing
+       * `filename_text' into a (possibly incomplete) command name and
+       * arguments, and doing completion based on that.  This is
+       * currently very rudimentary, but it is a small improvement.
+       */
+      for (value = filename_text + strlen (filename_text) - 1; value > filename_text; value--)
+        if (whitespace (*value) || member (*value, COMMAND_SEPARATORS))
+          break;
+      if (value <= filename_text)
+       matches = rl_completion_matches (filename_text, command_word_completion_function);
+      else
+       {
+         value++;
+         start_len += value - filename_text;
+         if (whitespace (value[-1]))
+           matches = rl_completion_matches (value, rl_filename_completion_function);
+         else
+           matches = rl_completion_matches (value, command_word_completion_function);
+       }
+
+      /* If there is more than one match, rl_completion_matches has already
+        put the lcd in matches[0].  Skip over it. */
+      cmd_index = matches && matches[0] && matches[1];
+
+      /* If there's a single match and it's a directory, set the append char
+        to the expected `/'.  Otherwise, don't append anything. */
+      if (matches && matches[0] && matches[1] == 0 && test_for_directory (matches[0]))
+       rl_completion_append_character = '/';
+      else
+       rl_completion_suppress_append = 1;
     }
 
   if (!matches || !matches[cmd_index])
@@ -1038,12 +1611,12 @@ command_subst_completion_function (text, state)
     }
   else
     {
-      value = xmalloc (1 + start_len + strlen (matches[cmd_index]));
+      value = (char *)xmalloc (1 + start_len + strlen (matches[cmd_index]));
 
       if (start_len == 1)
-        value[0] = *orig_start;
+       value[0] = *orig_start;
       else
-        strncpy (value, orig_start, start_len);
+       strncpy (value, orig_start, start_len);
 
       strcpy (value + start_len, matches[cmd_index]);
 
@@ -1055,11 +1628,10 @@ command_subst_completion_function (text, state)
 /* Okay, now we write the entry_function for variable completion. */
 static char *
 variable_completion_function (text, state)
+     const char *text;
      int state;
-     char *text;
 {
-  register SHELL_VAR *var = (SHELL_VAR *)NULL;
-  static SHELL_VAR **varlist = (SHELL_VAR **)NULL;
+  static char **varlist = (char **)NULL;
   static int varlist_index;
   static char *varname = (char *)NULL;
   static int namelen;
@@ -1077,26 +1649,16 @@ variable_completion_function (text, state)
        first_char_loc++;
 
       if (text[first_char_loc] == '{')
-        first_char_loc++;
+       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];
+       strvec_dispose (varlist);
 
-      /* 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++;
+      varlist = all_variables_matching_prefix (varname);
+      varlist_index = 0;
     }
 
   if (!varlist || !varlist[varlist_index])
@@ -1105,7 +1667,9 @@ variable_completion_function (text, state)
     }
   else
     {
-      char *value = xmalloc (4 + strlen (var->name));
+      char *value;
+
+      value = (char *)xmalloc (4 + strlen (varlist[varlist_index]));
 
       if (first_char_loc)
        {
@@ -1114,9 +1678,9 @@ variable_completion_function (text, state)
            value[1] = '{';
        }
 
-      strcpy (&value[first_char_loc], var->name);
+      strcpy (value + first_char_loc, varlist[varlist_index]);
       if (first_char_loc == 2)
-        strcat (value, "}");
+       strcat (value, "}");
 
       varlist_index++;
       return (value);
@@ -1126,8 +1690,8 @@ variable_completion_function (text, state)
 /* How about a completion function for hostnames? */
 static char *
 hostname_completion_function (text, state)
+     const char *text;
      int state;
-     char *text;
 {
   static char **list = (char **)NULL;
   static int list_index = 0;
@@ -1146,7 +1710,7 @@ hostname_completion_function (text, state)
       if (first_char == '@')
        first_char_loc++;
 
-      list = hostnames_matching (&text[first_char_loc]);
+      list = hostnames_matching ((char *)text+first_char_loc);
       list_index = 0;
     }
 
@@ -1154,7 +1718,7 @@ hostname_completion_function (text, state)
     {
       char *t;
 
-      t = xmalloc (2 + strlen (list[list_index]));
+      t = (char *)xmalloc (2 + strlen (list[list_index]));
       *t = first_char;
       strcpy (t + first_char_loc, list[list_index]);
       list_index++;
@@ -1164,31 +1728,126 @@ hostname_completion_function (text, state)
   return ((char *)NULL);
 }
 
-/* History and alias expand the line. */
-static char *
-history_expand_line_internal (line)
-     char *line;
+/*
+ * A completion function for service names from /etc/services (or wherever).
+ */
+char *
+bash_servicename_completion_function (text, state)
+     const char *text;
+     int state;
 {
-  char *new_line;
+#if defined (__WIN32__) || defined (__OPENNT) || !defined (HAVE_GETSERVENT)
+  return ((char *)NULL);
+#else
+  static char *sname = (char *)NULL;
+  static struct servent *srvent;
+  static int snamelen, firstc;
+  char *value;
+  char **alist, *aentry;
+  int afound;
 
-  new_line = pre_process_line (line, 0, 0);
-  return (new_line == line) ? savestring (line) : new_line;
+  if (state == 0)
+    {
+      FREE (sname);
+      firstc = *text;
+
+      sname = savestring (text);
+      snamelen = strlen (sname);
+      setservent (0);
+    }
+
+  while (srvent = getservent ())
+    {
+      afound = 0;
+      if (snamelen == 0 || (STREQN (sname, srvent->s_name, snamelen)))
+       break;
+      /* Not primary, check aliases */
+      for (alist = srvent->s_aliases; *alist; alist++)
+       {
+         aentry = *alist;
+         if (STREQN (sname, aentry, snamelen))
+           {
+             afound = 1;
+             break;
+           }
+       }
+
+      if (afound)
+       break;
+    }
+
+  if (srvent == 0)
+    {
+      endservent ();
+      return ((char *)NULL);
+    }
+
+  value = afound ? savestring (aentry) : savestring (srvent->s_name);
+  return value;
+#endif
 }
 
-#if defined (ALIAS)
-/* Expand aliases in the current readline line. */
-static void
-alias_expand_line (ignore)
-     int ignore;
+/*
+ * A completion function for group names from /etc/group (or wherever).
+ */
+char *
+bash_groupname_completion_function (text, state)
+     const char *text;
+     int state;
+{
+#if defined (__WIN32__) || defined (__OPENNT) || !defined (HAVE_GRP_H)
+  return ((char *)NULL);
+#else
+  static char *gname = (char *)NULL;
+  static struct group *grent;
+  static int gnamelen;
+  char *value;
+
+  if (state == 0)
+    {
+      FREE (gname);
+      gname = savestring (text);
+      gnamelen = strlen (gname);
+
+      setgrent ();
+    }
+
+  while (grent = getgrent ())
+    {
+      if (gnamelen == 0 || (STREQN (gname, grent->gr_name, gnamelen)))
+        break;
+    }
+
+  if (grent == 0)
+    {
+      endgrent ();
+      return ((char *)NULL);
+    }
+
+  value = savestring (grent->gr_name);
+  return (value);
+#endif
+}
+
+/* Functions to perform history and alias expansions on the current line. */
+
+#if defined (BANG_HISTORY)
+/* Perform history expansion on the current line.  If no history expansion
+   is done, pre_process_line() returns what it was passed, so we need to
+   allocate a new line here. */
+static char *
+history_expand_line_internal (line)
+     char *line;
 {
   char *new_line;
+  int old_verify;
 
-  new_line = alias_expand (rl_line_buffer);
+  old_verify = hist_verify;
+  hist_verify = 0;
+  new_line = pre_process_line (line, 0, 0);
+  hist_verify = old_verify;
 
-  if (new_line)
-    set_up_new_line (new_line);
-  else
-    cleanup_expansion_error ();
+  return (new_line == line) ? savestring (line) : new_line;
 }
 #endif
 
@@ -1198,11 +1857,20 @@ static void
 cleanup_expansion_error ()
 {
   char *to_free;
+#if defined (BANG_HISTORY)
+  int old_verify;
+
+  old_verify = hist_verify;
+  hist_verify = 0;
+#endif
 
   fprintf (rl_outstream, "\r\n");
   to_free = pre_process_line (rl_line_buffer, 1, 0);
+#if defined (BANG_HISTORY)
+  hist_verify = old_verify;
+#endif
   if (to_free != rl_line_buffer)
-    free (to_free);
+    FREE (to_free);
   putc ('\r', rl_outstream);
   rl_forced_update_display ();
 }
@@ -1220,7 +1888,7 @@ maybe_make_readline_line (new_line)
 
       rl_add_undo (UNDO_BEGIN, 0, 0, 0);
       rl_delete_text (0, rl_point);
-      rl_point = rl_end = 0;
+      rl_point = rl_end = rl_mark = 0;
       rl_insert_text (new_line);
       rl_add_undo (UNDO_END, 0, 0, 0);
     }
@@ -1231,8 +1899,10 @@ static void
 set_up_new_line (new_line)
      char *new_line;
 {
-  int old_point = rl_point;
-  int at_end = rl_point == rl_end;
+  int old_point, at_end;
+
+  old_point = rl_point;
+  at_end = rl_point == rl_end;
 
   /* If the line was history and alias expanded, then make that
      be one thing to undo. */
@@ -1246,35 +1916,90 @@ set_up_new_line (new_line)
     {
       rl_point = old_point;
       if (!whitespace (rl_line_buffer[rl_point]))
-       rl_forward_word (1);
+       rl_forward_word (1, 0);
     }
 }
 
+#if defined (ALIAS)
+/* Expand aliases in the current readline line. */
+static int
+alias_expand_line (count, ignore)
+     int count, ignore;
+{
+  char *new_line;
+
+  new_line = alias_expand (rl_line_buffer);
+
+  if (new_line)
+    {
+      set_up_new_line (new_line);
+      return (0);
+    }
+  else
+    {
+      cleanup_expansion_error ();
+      return (1);
+    }
+}
+#endif
+
+#if defined (BANG_HISTORY)
 /* History expand the line. */
-static void
-history_expand_line (ignore)
-     int ignore;
+static int
+history_expand_line (count, ignore)
+     int count, ignore;
 {
   char *new_line;
 
   new_line = history_expand_line_internal (rl_line_buffer);
 
   if (new_line)
-    set_up_new_line (new_line);
+    {
+      set_up_new_line (new_line);
+      return (0);
+    }
   else
-    cleanup_expansion_error ();
+    {
+      cleanup_expansion_error ();
+      return (1);
+    }
+}
+
+/* Expand history substitutions in the current line and then insert a
+   space (hopefully close to where we were before). */
+static int
+tcsh_magic_space (count, ignore)
+     int count, ignore;
+{
+  int dist_from_end, old_point;
+
+  old_point = rl_point;
+  dist_from_end = rl_end - rl_point;
+  if (history_expand_line (count, ignore) == 0)
+    {
+      /* Try a simple heuristic from Stephen Gildea <gildea@intouchsys.com>.
+        This works if all expansions were before rl_point or if no expansions
+        were performed. */
+      rl_point = (old_point == 0) ? old_point : rl_end - dist_from_end;
+      rl_insert (1, ' ');
+      return (0);
+    }
+  else
+    return (1);
 }
+#endif /* BANG_HISTORY */
 
 /* History and alias expand the line. */
-static void
-history_and_alias_expand_line (ignore)
-     int ignore;
+static int
+history_and_alias_expand_line (count, ignore)
+     int count, ignore;
 {
   char *new_line;
 
-  new_line = pre_process_line (rl_line_buffer, 0, 0);
-  if (new_line == rl_line_buffer)
-    new_line = savestring (new_line);
+  new_line = 0;
+#if defined (BANG_HISTORY)
+  new_line = history_expand_line_internal (rl_line_buffer);
+#endif
 
 #if defined (ALIAS)
   if (new_line)
@@ -1288,23 +2013,32 @@ history_and_alias_expand_line (ignore)
 #endif /* ALIAS */
 
   if (new_line)
-    set_up_new_line (new_line);
+    {
+      set_up_new_line (new_line);
+      return (0);
+    }
   else
-    cleanup_expansion_error ();
+    {
+      cleanup_expansion_error ();
+      return (1);
+    }
 }
 
 /* History and alias expand the line, then perform the shell word
-   expansions by calling expand_string. */
-static void
-shell_expand_line (ignore)
-     int ignore;
+   expansions by calling expand_string.  This can't use set_up_new_line()
+   because we want the variable expansions as a separate undo'able
+   set of operations. */
+static int
+shell_expand_line (count, ignore)
+     int count, ignore;
 {
   char *new_line;
   WORD_LIST *expanded_string;
 
-  new_line = pre_process_line (rl_line_buffer, 0, 0);
-  if (new_line == rl_line_buffer)
-    new_line = savestring (new_line);
+  new_line = 0;
+#if defined (BANG_HISTORY)
+  new_line = history_expand_line_internal (rl_line_buffer);
+#endif
 
 #if defined (ALIAS)
   if (new_line)
@@ -1334,7 +2068,7 @@ shell_expand_line (ignore)
       FREE (new_line);
       if (expanded_string == 0)
        {
-         new_line = xmalloc (1);
+         new_line = (char *)xmalloc (1);
          new_line[0] = '\0';
        }
       else
@@ -1353,11 +2087,15 @@ shell_expand_line (ignore)
        {
          rl_point = old_point;
          if (!whitespace (rl_line_buffer[rl_point]))
-           rl_forward_word (1);
+           rl_forward_word (1, 0);
        }
+      return 0;
     }
   else
-    cleanup_expansion_error ();
+    {
+      cleanup_expansion_error ();
+      return 1;
+    }
 }
 
 /* If FIGNORE is set, then don't match files with the given suffixes when
@@ -1374,16 +2112,18 @@ static struct ignorevar fignore =
   (struct ign *)0,
   0,
   (char *)0,
-  (Function *) 0,
+  (sh_iv_item_func_t *) 0,
 };
 
 static void
 _ignore_completion_names (names, name_func)
      char **names;
-     Function *name_func;
+     sh_ignore_func_t *name_func;
 {
   char **newnames;
   int idx, nidx;
+  char **oldnames;
+  int oidx;
 
   /* 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
@@ -1391,11 +2131,13 @@ _ignore_completion_names (names, name_func)
      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;
-        }
+      if (force_fignore)
+       if ((*name_func) (names[0]) == 0)
+         {
+           free (names[0]);
+           names[0] = (char *)NULL;
+         }
+
       return;
     }
 
@@ -1403,15 +2145,23 @@ _ignore_completion_names (names, name_func)
      filenames.  The pointers are copied back to NAMES when done. */
   for (nidx = 1; names[nidx]; nidx++)
     ;
-  newnames = (char **)xmalloc ((nidx + 1) * (sizeof (char *)));
+  newnames = strvec_create (nidx + 1);
+
+  if (force_fignore == 0)
+    {
+      oldnames = strvec_create (nidx - 1);
+      oidx = 0;
+    }
 
   newnames[0] = names[0];
   for (idx = nidx = 1; names[idx]; idx++)
     {
       if ((*name_func) (names[idx]))
        newnames[nidx++] = names[idx];
+      else if (force_fignore == 0)
+       oldnames[oidx++] = names[idx];
       else
-        free (names[idx]);
+       free (names[idx]);
     }
 
   newnames[nidx] = (char *)NULL;
@@ -1419,12 +2169,25 @@ _ignore_completion_names (names, name_func)
   /* If none are acceptable then let the completer handle it. */
   if (nidx == 1)
     {
-      free (names[0]);
-      names[0] = (char *)NULL;
+      if (force_fignore)
+       {
+         free (names[0]);
+         names[0] = (char *)NULL;
+       }
+      else
+       free (oldnames);
+
       free (newnames);
       return;
     }
 
+  if (force_fignore == 0)
+    {
+      while (oidx)
+       free (oldnames[--oidx]);
+      free (oldnames);
+    }
+
   /* If only one is acceptable, copy it to names[0] and return. */
   if (nidx == 2)
     {
@@ -1445,7 +2208,7 @@ _ignore_completion_names (names, name_func)
 
 static int
 name_is_acceptable (name)
-     char *name;
+     const char *name;
 {
   struct ign *p;
   int nlen;
@@ -1459,27 +2222,43 @@ name_is_acceptable (name)
   return (1);
 }
 
-static void
+#if 0
+static int
+ignore_dot_names (name)
+     char *name;
+{
+  return (name[0] != '.');
+}
+#endif
+
+static int
 filename_completion_ignore (names)
      char **names;
 {
+#if 0
+  if (glob_dot_filenames == 0)
+    _ignore_completion_names (names, ignore_dot_names);
+#endif
+
   setup_ignore_patterns (&fignore);
 
   if (fignore.num_ignores == 0)
-    return;
+    return 0;
 
   _ignore_completion_names (names, name_is_acceptable);
+
+  return 0;
 }
 
 /* Return 1 if NAME is a directory. */
 static int
 test_for_directory (name)
-     char *name;
+     const char *name;
 {
   struct stat finfo;
   char *fn;
 
-  fn = bash_tilde_expand (name);
+  fn = bash_tilde_expand (name, 0);
   if (stat (fn, &finfo) != 0)
     {
       free (fn);
@@ -1490,27 +2269,95 @@ test_for_directory (name)
 }
 
 /* Remove files from NAMES, leaving directories. */
-static void
+static int
 bash_ignore_filenames (names)
      char **names;
 {
   _ignore_completion_names (names, test_for_directory);
+  return 0;
 }
 
+static int
+return_zero (name)
+     const char *name;
+{
+  return 0;
+}
+
+static int
+bash_ignore_everything (names)
+     char **names;
+{
+  _ignore_completion_names (names, return_zero);
+  return 0;
+}
+
+/* Simulate the expansions that will be performed by
+   rl_filename_completion_function.  This must be called with the address of
+   a pointer to malloc'd memory. */
+static void
+bash_directory_expansion (dirname)
+     char **dirname;
+{
+  char *d, *nd;
+
+  d = savestring (*dirname);
+
+  if (rl_directory_rewrite_hook)
+    (*rl_directory_rewrite_hook) (&d);
+
+  if (rl_directory_completion_hook && (*rl_directory_completion_hook) (&d))
+    {
+      free (*dirname);
+      *dirname = d;
+    }
+  else if (rl_completion_found_quote)
+    {
+      nd = bash_dequote_filename (d, rl_completion_quote_character);
+      free (*dirname);
+      free (d);
+      *dirname = nd;
+    }
+}
+  
 /* 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;
+  char *local_dirname, *new_dirname, *t;
+  int return_value, should_expand_dirname;
   WORD_LIST *wl;
+  struct stat sb;
 
+  return_value = should_expand_dirname = 0;
   local_dirname = *dirname;
-  if (strchr (local_dirname, '$') || strchr (local_dirname, '`'))
+
+#if 0
+  should_expand_dirname = xstrchr (local_dirname, '$') || xstrchr (local_dirname, '`');
+#else
+  if (xstrchr (local_dirname, '$'))
+    should_expand_dirname = 1;
+  else
     {
-      wl = expand_string (local_dirname, 0);
+      t = xstrchr (local_dirname, '`');
+      if (t && unclosed_pair (local_dirname, strlen (local_dirname), "`") == 0)
+       should_expand_dirname = 1;
+    }
+#endif
+
+#if defined (HAVE_LSTAT)
+  if (should_expand_dirname && lstat (local_dirname, &sb) == 0)
+#else
+  if (should_expand_dirname && stat (local_dirname, &sb) == 0)
+#endif
+    should_expand_dirname = 0;
+
+  if (should_expand_dirname)  
+    {
+      new_dirname = savestring (local_dirname);
+      wl = expand_prompt_string (new_dirname, 0, W_NOCOMSUB);  /* does the right thing */
       if (wl)
        {
          *dirname = string_list (wl);
@@ -1518,17 +2365,26 @@ bash_directory_completion_hook (dirname)
             actually expanded something. */
          return_value = STREQ (local_dirname, *dirname) == 0;
          free (local_dirname);
+         free (new_dirname);
          dispose_words (wl);
          local_dirname = *dirname;
        }
       else
        {
+         free (new_dirname);
          free (local_dirname);
-         *dirname = xmalloc (1);
+         *dirname = (char *)xmalloc (1);
          **dirname = '\0';
          return 1;
        }
     }
+  else 
+    {
+      /* Dequote the filename even if we don't expand it. */
+      new_dirname = bash_dequote_filename (local_dirname, rl_completion_quote_character);
+      free (local_dirname);
+      local_dirname = *dirname = new_dirname;
+    }
 
   if (!no_symbolic_links && (local_dirname[0] != '.' || local_dirname[1]))
     {
@@ -1538,7 +2394,7 @@ bash_directory_completion_hook (dirname)
       t = get_working_directory ("symlink-hook");
       temp1 = make_absolute (local_dirname, t);
       free (t);
-      temp2 = canonicalize_pathname (temp1);
+      temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
       /* If we can't canonicalize, bail. */
       if (temp2 == 0)
        {
@@ -1547,12 +2403,15 @@ bash_directory_completion_hook (dirname)
        }
       len1 = strlen (temp1);
       if (temp1[len1 - 1] == '/')
-        {
+       {
          len2 = strlen (temp2);
-          temp2 = xrealloc (temp2, len2 + 2);
-          temp2[len2] = '/';
-          temp2[len2 + 1] = '\0';
-        }
+         if (len2 > 2)         /* don't append `/' to `/' or `//' */
+           {
+             temp2 = (char *)xrealloc (temp2, len2 + 2);
+             temp2[len2] = '/';
+             temp2[len2 + 1] = '\0';
+           }
+       }
       free (local_dirname);
       *dirname = temp2;
       free (temp1);
@@ -1574,11 +2433,7 @@ build_history_completion_array ()
   /* 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);
-
+      strvec_dispose (history_completion_array);
       history_completion_array = (char **)NULL;
       harry_size = 0;
       harry_len = 0;
@@ -1598,11 +2453,7 @@ build_history_completion_array ()
          for (j = 0; tokens && tokens[j]; j++)
            {
              if (harry_len + 2 > harry_size)
-               {
-                 harry_size += 10;
-                 history_completion_array = (char **) xrealloc
-                   (history_completion_array, harry_size * sizeof (char *));
-               }
+               history_completion_array = strvec_resize (history_completion_array, harry_size += 10);
 
              history_completion_array[harry_len++] = tokens[j];
              history_completion_array[harry_len] = (char *)NULL;
@@ -1611,17 +2462,17 @@ build_history_completion_array ()
        }
 
       /* Sort the complete list of tokens. */
-      qsort (history_completion_array, harry_len, sizeof (char *), (Function *)qsort_string_compare);
+      qsort (history_completion_array, harry_len, sizeof (char *), (QSFUNC *)strvec_strcmp);
     }
 }
 
 static char *
 history_completion_generator (hint_text, state)
-     char *hint_text;
+     const char *hint_text;
      int state;
 {
   static int local_index, len;
-  static char *text;
+  static const char *text;
 
   /* If this is the first call to the generator, then initialize the
      list of strings to complete over. */
@@ -1641,171 +2492,200 @@ history_completion_generator (hint_text, state)
   return ((char *)NULL);
 }
 
-static void
+static int
 dynamic_complete_history (count, key)
      int count, key;
 {
-  Function *orig_func;
-  CPPFunction *orig_attempt_func;
+  int r;
+
+  rl_compentry_func_t *orig_func;
+  rl_completion_func_t *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;
+  rl_completion_entry_function = history_completion_generator;
+  rl_attempted_completion_function = (rl_completion_func_t *)NULL;
 
-  if (rl_last_func == (Function *)dynamic_complete_history)
-    rl_complete_internal ('?');
+  /* XXX - use rl_completion_mode here? */
+  if (rl_last_func == dynamic_complete_history)
+    r = rl_complete_internal ('?');
   else
-    rl_complete_internal (TAB);
+    r = rl_complete_internal (TAB);
 
   rl_completion_entry_function = orig_func;
   rl_attempted_completion_function = orig_attempt_func;
+  return r;
 }
 
 #if defined (SPECIFIC_COMPLETION_FUNCTIONS)
-static void
+static int
 bash_complete_username (ignore, ignore2)
      int ignore, ignore2;
 {
-  bash_complete_username_internal (TAB);
+  return bash_complete_username_internal (rl_completion_mode (bash_complete_username));
 }
 
-static void
+static int
 bash_possible_username_completions (ignore, ignore2)
      int ignore, ignore2;
 {
-  bash_complete_username_internal ('?');
+  return bash_complete_username_internal ('?');
 }
 
-static void
+static int
 bash_complete_username_internal (what_to_do)
      int what_to_do;
 {
-  bash_specific_completion
-    (what_to_do, (Function *)username_completion_function);
+  return bash_specific_completion (what_to_do, rl_username_completion_function);
 }
 
-static void
+static int
 bash_complete_filename (ignore, ignore2)
      int ignore, ignore2;
 {
-  bash_complete_filename_internal (TAB);
+  return bash_complete_filename_internal (rl_completion_mode (bash_complete_filename));
 }
 
-static void
+static int
 bash_possible_filename_completions (ignore, ignore2)
      int ignore, ignore2;
 {
-  bash_complete_filename_internal ('?');
+  return bash_complete_filename_internal ('?');
 }
 
-static void
+static int
 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;
+  rl_compentry_func_t *orig_func;
+  rl_completion_func_t *orig_attempt_func;
+  rl_icppfunc_t *orig_dir_func;
+  /*const*/ char *orig_rl_completer_word_break_characters;
+  int r;
 
   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_completion_entry_function = rl_filename_completion_function;
+  rl_attempted_completion_function = (rl_completion_func_t *)NULL;
+  rl_directory_completion_hook = (rl_icppfunc_t *)NULL;
   rl_completer_word_break_characters = " \t\n\"\'";
 
-  rl_complete_internal (what_to_do);
+  r = 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;
+
+  return r;
 }
 
-static void
+static int
 bash_complete_hostname (ignore, ignore2)
      int ignore, ignore2;
 {
-  bash_complete_hostname_internal (TAB);
+  return bash_complete_hostname_internal (rl_completion_mode (bash_complete_hostname));
 }
 
-static void
+static int
 bash_possible_hostname_completions (ignore, ignore2)
      int ignore, ignore2;
 {
-  bash_complete_hostname_internal ('?');
+  return bash_complete_hostname_internal ('?');
 }
 
-static void
+static int
 bash_complete_variable (ignore, ignore2)
      int ignore, ignore2;
 {
-  bash_complete_variable_internal (TAB);
+  return bash_complete_variable_internal (rl_completion_mode (bash_complete_variable));
 }
 
-static void
+static int
 bash_possible_variable_completions (ignore, ignore2)
      int ignore, ignore2;
 {
-  bash_complete_variable_internal ('?');
+  return bash_complete_variable_internal ('?');
 }
 
-static void
+static int
 bash_complete_command (ignore, ignore2)
      int ignore, ignore2;
 {
-  bash_complete_command_internal (TAB);
+  return bash_complete_command_internal (rl_completion_mode (bash_complete_command));
 }
 
-static void
+static int
 bash_possible_command_completions (ignore, ignore2)
      int ignore, ignore2;
 {
-  bash_complete_command_internal ('?');
+  return bash_complete_command_internal ('?');
 }
 
-static void
+static int
 bash_complete_hostname_internal (what_to_do)
      int what_to_do;
 {
-  bash_specific_completion
-    (what_to_do, (Function *)hostname_completion_function);
+  return bash_specific_completion (what_to_do, hostname_completion_function);
 }
 
-static void
+static int
 bash_complete_variable_internal (what_to_do)
      int what_to_do;
 {
-  bash_specific_completion
-    (what_to_do, (Function *)variable_completion_function);
+  return bash_specific_completion (what_to_do, variable_completion_function);
 }
 
-static void
+static int
 bash_complete_command_internal (what_to_do)
      int what_to_do;
 {
-  bash_specific_completion
-    (what_to_do, (Function *)command_word_completion_function);
+  return bash_specific_completion (what_to_do, command_word_completion_function);
 }
 
+static char *globtext;
+static char *globorig;
+
 static char *
 glob_complete_word (text, state)
-     char *text;
+     const char *text;
      int state;
 {
   static char **matches = (char **)NULL;
   static int ind;
-  char *ret;
+  int glen;
+  char *ret, *ttext;
 
   if (state == 0)
     {
-      if (matches)
-        free (matches);
-      matches = shell_glob_filename (text);
+      rl_filename_completion_desired = 1;
+      FREE (matches);
+      if (globorig != globtext)
+       FREE (globorig);
+      FREE (globtext);
+
+      ttext = bash_tilde_expand (text, 0);
+
+      if (rl_explicit_arg)
+       {
+         globorig = savestring (ttext);
+         glen = strlen (ttext);
+         globtext = (char *)xmalloc (glen + 2);
+         strcpy (globtext, ttext);
+         globtext[glen] = '*';
+         globtext[glen+1] = '\0';
+       }
+      else
+        globtext = globorig = savestring (ttext);
+
+      if (ttext != text)
+       free (ttext);
+
+      matches = shell_glob_filename (globtext);
       if (GLOB_FAILED (matches))
-        matches = (char **)NULL;
+       matches = (char **)NULL;
       ind = 0;
     }
 
@@ -1814,61 +2694,154 @@ glob_complete_word (text, state)
   return ret;
 }
 
-static void
+static int
 bash_glob_completion_internal (what_to_do)
      int what_to_do;
 {
-  bash_specific_completion (what_to_do, (Function *)glob_complete_word);
+  return bash_specific_completion (what_to_do, glob_complete_word);
 }
 
-static void
+/* A special quoting function so we don't end up quoting globbing characters
+   in the word if there are no matches or multiple matches. */
+static char *
+bash_glob_quote_filename (s, rtype, qcp)
+     char *s;
+     int rtype;
+     char *qcp;
+{
+  if (globorig && qcp && *qcp == '\0' && STREQ (s, globorig))
+    return (savestring (s));
+  else
+    return (bash_quote_filename (s, rtype, qcp));
+}
+
+static int
+bash_glob_complete_word (count, key)
+     int count, key;
+{
+  int r;
+  rl_quote_func_t *orig_quoting_function;
+
+  if (rl_editing_mode == EMACS_EDITING_MODE)
+    rl_explicit_arg = 1;       /* force `*' append */
+  orig_quoting_function = rl_filename_quoting_function;
+  rl_filename_quoting_function = bash_glob_quote_filename;
+  
+  r = bash_glob_completion_internal (rl_completion_mode (bash_glob_complete_word));
+
+  rl_filename_quoting_function = orig_quoting_function;
+  return r;
+}
+
+static int
 bash_glob_expand_word (count, key)
      int count, key;
 {
-  bash_glob_completion_internal ('*');
+  return bash_glob_completion_internal ('*');
 }
 
-static void
+static int
 bash_glob_list_expansions (count, key)
      int count, key;
 {
-  bash_glob_completion_internal ('?');
+  return bash_glob_completion_internal ('?');
 }
 
-static void
+static int
 bash_specific_completion (what_to_do, generator)
      int what_to_do;
-     Function *generator;
+     rl_compentry_func_t *generator;
 {
-  Function *orig_func;
-  CPPFunction *orig_attempt_func;
+  rl_compentry_func_t *orig_func;
+  rl_completion_func_t *orig_attempt_func;
+  int r;
 
   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_attempted_completion_function = NULL;
 
-  rl_complete_internal (what_to_do);
+  r = rl_complete_internal (what_to_do);
 
   rl_completion_entry_function = orig_func;
   rl_attempted_completion_function = orig_attempt_func;
+
+  return r;
 }
 
 #endif /* SPECIFIC_COMPLETION_FUNCTIONS */
 
+#if defined (VI_MODE)
+/* Completion, from vi mode's point of view.  This is a modified version of
+   rl_vi_complete which uses the bash globbing code to implement what POSIX
+   specifies, which is to append a `*' and attempt filename generation (which
+   has the side effect of expanding any globbing characters in the word). */
+static int
+bash_vi_complete (count, key)
+     int count, key;
+{
+#if defined (SPECIFIC_COMPLETION_FUNCTIONS)
+  int p, r;
+  char *t;
+
+  if ((rl_point < rl_end) && (!whitespace (rl_line_buffer[rl_point])))
+    {
+      if (!whitespace (rl_line_buffer[rl_point + 1]))
+       rl_vi_end_word (1, 'E');
+      rl_point++;
+    }
+
+  /* Find boundaries of current word, according to vi definition of a
+     `bigword'. */
+  t = 0;
+  if (rl_point > 0)
+    {
+      p = rl_point;
+      rl_vi_bWord (1, 'B');
+      r = rl_point;
+      rl_point = p;
+      p = r;
+
+      t = substring (rl_line_buffer, p, rl_point);
+    }      
+
+  if (t && glob_pattern_p (t) == 0)
+    rl_explicit_arg = 1;       /* XXX - force glob_complete_word to append `*' */
+  FREE (t);
+
+  if (key == '*')      /* Expansion and replacement. */
+    r = bash_glob_expand_word (count, key);
+  else if (key == '=') /* List possible completions. */
+    r = bash_glob_list_expansions (count, key);
+  else if (key == '\\')        /* Standard completion */
+    r = bash_glob_complete_word (count, key);
+  else
+    r = rl_complete (0, key);
+
+  if (key == '*' || key == '\\')
+    rl_vi_start_inserting (key, 1, 1);
+
+  return (r);
+#else
+  return rl_vi_complete (count, key);
+#endif /* !SPECIFIC_COMPLETION_FUNCTIONS */
+}
+#endif /* VI_MODE */
+
 /* Filename quoting for completion. */
-/* A function to strip quotes that are not protected by backquotes.  It
-   allows single quotes to appear within double quotes, and vice versa.
-   It should be smarter. */
+/* A function to strip unquoted quote characters (single quotes, double
+   quotes, and backslashes).  It allows single quotes to appear
+   within double quotes, and vice versa.  It should be smarter. */
 static char *
 bash_dequote_filename (text, quote_char)
      char *text;
+     int quote_char;
 {
   char *ret, *p, *r;
   int l, quoted;
 
   l = strlen (text);
-  ret = xmalloc (l + 1);
+  ret = (char *)xmalloc (l + 1);
   for (quoted = quote_char, p = text, r = ret; p && *p; p++)
     {
       /* Allow backslash-quoted characters to pass through unscathed. */
@@ -1881,16 +2854,16 @@ bash_dequote_filename (text, quote_char)
        }
       /* Close quote. */
       if (quoted && *p == quoted)
-        {
-          quoted = 0;
-          continue;
-        }
+       {
+         quoted = 0;
+         continue;
+       }
       /* Open quote. */
       if (quoted == 0 && (*p == '\'' || *p == '"'))
-        {
-          quoted = *p;
-          continue;
-        }
+       {
+         quoted = *p;
+         continue;
+       }
       *r++ = *p;
     }
   *r = '\0';
@@ -1908,7 +2881,7 @@ quote_word_break_chars (text)
   int l;
 
   l = strlen (text);
-  ret = xmalloc ((2 * l) + 1);
+  ret = (char *)xmalloc ((2 * l) + 1);
   for (s = text, r = ret; *s; s++)
     {
       /* Pass backslash-quoted characters through, including the backslash. */
@@ -1922,8 +2895,8 @@ quote_word_break_chars (text)
        }
       /* OK, we have an unquoted character.  Check its presence in
         rl_completer_word_break_characters. */
-      if (strchr (rl_completer_word_break_characters, *s))
-        *r++ = '\\';
+      if (xstrchr (rl_completer_word_break_characters, *s))
+       *r++ = '\\';
       *r++ = *s;
     }
   *r = '\0';
@@ -1934,7 +2907,8 @@ quote_word_break_chars (text)
    depending on the value of completion_quoting_style.  If we're
    completing using backslashes, we need to quote some additional
    characters (those that readline treats as word breaks), so we call
-   quote_word_break_chars on the result. */
+   quote_word_break_chars on the result.  This returns newly-allocated
+   memory. */
 static char *
 bash_quote_filename (s, rtype, qcp)
      char *s;
@@ -1953,40 +2927,48 @@ bash_quote_filename (s, rtype, qcp)
      to perform tilde expansion, because single and double
      quotes inhibit tilde expansion by the shell. */
 
-  mtext = s;
-  if (mtext[0] == '~' && rtype == SINGLE_MATCH)
-    mtext = bash_tilde_expand (s);
-
   cs = completion_quoting_style;
   /* Might need to modify the default completion style based on *qcp,
-     since it's set to any user-provided opening quote. */
-  if (*qcp == '"')
+     since it's set to any user-provided opening quote.  We also change
+     to single-quoting if there is no user-provided opening quote and
+     the word being completed contains newlines, since those are not
+     quoted correctly using backslashes (a backslash-newline pair is
+     special to the shell parser). */
+  if (*qcp == '\0' && cs == COMPLETE_BSQUOTE && xstrchr (s, '\n'))
+    cs = COMPLETE_SQUOTE;
+  else if (*qcp == '"')
     cs = COMPLETE_DQUOTE;
   else if (*qcp == '\'')
     cs = COMPLETE_SQUOTE;
 #if defined (BANG_HISTORY)
   else if (*qcp == '\0' && history_expansion && cs == COMPLETE_DQUOTE &&
-          history_expansion_inhibited == 0 && strchr (mtext, '!'))
+          history_expansion_inhibited == 0 && xstrchr (s, '!'))
     cs = COMPLETE_BSQUOTE;
 
   if (*qcp == '"' && history_expansion && cs == COMPLETE_DQUOTE &&
-        history_expansion_inhibited == 0 && strchr (mtext, '!'))
+       history_expansion_inhibited == 0 && xstrchr (s, '!'))
     {
       cs = COMPLETE_BSQUOTE;
       *qcp = '\0';
     }
 #endif
 
+  /* Don't tilde-expand backslash-quoted filenames, since only single and
+     double quotes inhibit tilde expansion. */
+  mtext = s;
+  if (mtext[0] == '~' && rtype == SINGLE_MATCH && cs != COMPLETE_BSQUOTE)
+    mtext = bash_tilde_expand (s, 0);
+
   switch (cs)
     {
     case COMPLETE_DQUOTE:
-      rtext = double_quote (mtext);
+      rtext = sh_double_quote (mtext);
       break;
     case COMPLETE_SQUOTE:
-      rtext = single_quote (mtext);
+      rtext = sh_single_quote (mtext);
       break;
     case COMPLETE_BSQUOTE:
-      rtext = backslash_quote (mtext);
+      rtext = sh_backslash_quote (mtext);
       break;
     }
 
@@ -2005,7 +2987,7 @@ bash_quote_filename (s, rtype, qcp)
   /* Leave the opening quote intact.  The readline completion code takes
      care of avoiding doubled opening quotes. */
   rlen = strlen (rtext);
-  ret = xmalloc (rlen + 1);
+  ret = (char *)xmalloc (rlen + 1);
   strcpy (ret, rtext);
 
   /* If there are multiple matches, cut off the closing quote. */
@@ -2015,4 +2997,203 @@ bash_quote_filename (s, rtype, qcp)
   return ret;
 }
 
+/* Support for binding readline key sequences to Unix commands. */
+static Keymap cmd_xmap;
+
+static int
+bash_execute_unix_command (count, key)
+     int count;        /* ignored */
+     int key;
+{
+  Keymap ckmap;                /* current keymap */
+  Keymap xkmap;                /* unix command executing keymap */
+  register int i;
+  char *cmd;
+  sh_parser_state_t ps;
+
+  /* First, we need to find the right command to execute.  This is tricky,
+     because we might have already indirected into another keymap. */
+  ckmap = rl_get_keymap ();
+  if (ckmap != rl_executing_keymap)
+    {
+      /* bogus.  we have to search.  only handle one level of indirection. */
+      for (i = 0; i < KEYMAP_SIZE; i++)
+       {
+         if (ckmap[i].type == ISKMAP && (Keymap)ckmap[i].function == rl_executing_keymap)
+           break;
+       }
+      if (i < KEYMAP_SIZE)
+       xkmap = (Keymap)cmd_xmap[i].function;
+      else
+       {
+         rl_crlf ();
+         internal_error (_("bash_execute_unix_command: cannot find keymap for command"));
+         rl_forced_update_display ();
+         return 1;
+       }
+    }
+  else
+    xkmap = cmd_xmap;
+
+  cmd = (char *)xkmap[key].function;
+
+  if (cmd == 0)
+    {
+      rl_ding ();
+      return 1;
+    }
+
+  rl_crlf ();  /* move to a new line */
+
+  save_parser_state (&ps);
+
+  cmd = savestring (cmd);
+  parse_and_execute (cmd, "bash_execute_unix_command", SEVAL_NOHIST);
+
+  restore_parser_state (&ps);
+
+  /* and restore the readline buffer and display after command execution. */
+  rl_forced_update_display ();
+  return 0;
+}
+
+static void
+init_unix_command_map ()
+{
+  cmd_xmap = rl_make_bare_keymap ();
+}
+
+static int
+isolate_sequence (string, ind, need_dquote, startp)
+     char *string;
+     int ind, need_dquote, *startp;
+{
+  register int i;
+  int c, passc, delim;
+
+  for (i = ind; string[i] && whitespace (string[i]); i++)
+    ;
+  /* NEED_DQUOTE means that the first non-white character *must* be `"'. */
+  if (need_dquote && string[i] != '"')
+    {
+      builtin_error (_("%s: first non-whitespace character is not `\"'"), string);
+      return -1;
+    }
+
+  /* We can have delimited strings even if NEED_DQUOTE == 0, like the command
+     string to bind the key sequence to. */
+  delim = (string[i] == '"' || string[i] == '\'') ? string[i] : 0;
+    
+  if (startp)
+    *startp = delim ? ++i : i;
+
+  for (passc = 0; c = string[i]; i++)
+    {
+      if (passc)
+       {
+         passc = 0;
+         continue;
+       }
+      if (c == '\\')
+       {
+         passc++;
+         continue;
+       }
+      if (c == delim)
+       break;
+    }
+
+  if (delim && string[i] != delim)
+    {
+      builtin_error (_("no closing `%c' in %s"), delim, string);
+      return -1;
+    }
+
+  return i;
+}
+
+int
+bind_keyseq_to_unix_command (line)
+     char *line;
+{
+  Keymap kmap;
+  char *kseq, *value;
+  int i, kstart;
+
+  if (cmd_xmap == 0)
+    init_unix_command_map ();
+
+  kmap = rl_get_keymap ();
+
+  /* We duplicate some of the work done by rl_parse_and_bind here, but
+     this code only has to handle `"keyseq": ["]command["]' and can
+     generate an error for anything else. */
+  i = isolate_sequence (line, 0, 1, &kstart);
+  if (i < 0)
+    return -1;
+
+  /* Create the key sequence string to pass to rl_generic_bind */
+  kseq = substring (line, kstart, i);
+
+  for ( ; line[i] && line[i] != ':'; i++)
+    ;
+  if (line[i] != ':')
+    {
+      builtin_error (_("%s: missing colon separator"), line);
+      return -1;
+    }
+
+  i = isolate_sequence (line, i + 1, 0, &kstart);
+  if (i < 0)
+    return -1;
+
+  /* Create the value string containing the command to execute. */
+  value = substring (line, kstart, i);
+
+  /* Save the command to execute and the key sequence in the CMD_XMAP */
+  rl_generic_bind (ISMACR, kseq, value, cmd_xmap);
+
+  /* and bind the key sequence in the current keymap to a function that
+     understands how to execute from CMD_XMAP */
+  rl_bind_keyseq_in_map (kseq, bash_execute_unix_command, kmap);
+  
+  return 0;
+}
+
+/* Used by the programmable completion code.  Complete TEXT as a filename,
+   but return only directories as matches.  Dequotes the filename before
+   attempting to find matches. */
+char **
+bash_directory_completion_matches (text)
+     const char *text;
+{
+  char **m1;
+  char *dfn;
+  int qc;
+
+  qc = rl_dispatching ? rl_completion_quote_character : 0;  
+  dfn = bash_dequote_filename ((char *)text, qc);
+  m1 = rl_completion_matches (dfn, rl_filename_completion_function);
+  free (dfn);
+
+  if (m1 == 0 || m1[0] == 0)
+    return m1;
+  /* We don't bother recomputing the lcd of the matches, because it will just
+     get thrown away by the programmable completion code and recomputed
+     later. */
+  (void)bash_ignore_filenames (m1);
+  return m1;
+}
+
+char *
+bash_dequote_text (text)
+     const char *text;
+{
+  char *dtxt;
+  int qc;
+
+  qc = (text[0] == '"' || text[0] == '\'') ? text[0] : 0;
+  dtxt = bash_dequote_filename ((char *)text, qc);
+  return (dtxt);
+}
 #endif /* READLINE */