]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - bashline.c
Bash-5.0 patch 11: fix quoted null character removal in operands of conditional ...
[thirdparty/bash.git] / bashline.c
index 6cfb1b063d64d894024463652b8193f9fb5ff47a..824ea9d93996cacfecd7a99e9b2e57e5c90a47d1 100644 (file)
@@ -1,22 +1,22 @@
 /* bashline.c -- Bash's interface to the readline library. */
 
-/* Copyright (C) 1987,1991 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2017 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 2, or (at your option)
-   any later version.
+   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 3 of the License, or
+   (at your option) any later version.
 
-   Bash is distributed in the hope that it will be useful, but WITHOUT
-   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
-   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
-   License for more details.
+   Bash is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with Bash; see the file COPYING.  If not, write to the Free
-   Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 #include "config.h"
 
 #  include <grp.h>
 #endif
 
+#if defined (HAVE_NETDB_H)
+#  include <netdb.h>
+#endif
+
+#include <signal.h>
+
 #include <stdio.h>
 #include "chartypes.h"
 #include "bashansi.h"
+#include "bashintl.h"
+
 #include "shell.h"
+#include "input.h"
+#include "parser.h"
 #include "builtins.h"
 #include "bashhist.h"
 #include "bashline.h"
 #include "execute_cmd.h"
 #include "findcmd.h"
 #include "pathexp.h"
+#include "shmbutil.h"
+#include "trap.h"
+#include "flags.h"
+
+#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR)
+#  include <mbstr.h>           /* mbschr */
+#endif
+
 #include "builtins/common.h"
+
 #include <readline/rlconf.h>
 #include <readline/readline.h>
 #include <readline/history.h>
+#include <readline/rlmbutil.h>
 
 #include <glob/glob.h>
 
 #  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
+
+#define RL_BOOLEAN_VARIABLE_VALUE(s)   ((s)[0] == 'o' && (s)[1] == 'n' && (s)[2] == '\0')
+
 #if defined (BRACE_COMPLETION)
 extern int bash_brace_completion __P((int, int));
 #endif /* BRACE_COMPLETION */
 
+/* To avoid including curses.h/term.h/termcap.h and that whole mess. */
+#ifdef _MINIX
+extern int tputs __P((const char *string, int nlines, void (*outx)(int)));
+#else
+extern int tputs __P((const char *string, int nlines, int (*outx)(int)));
+#endif
+
 /* Forward declarations */
 
 /* Functions bound to keys in Readline for Bash users. */
@@ -84,11 +121,31 @@ static int alias_expand_line __P((int, int));
 static int history_and_alias_expand_line __P((int, int));
 #endif
 
+static int bash_forward_shellword __P((int, int));
+static int bash_backward_shellword __P((int, int));
+static int bash_kill_shellword __P((int, int));
+static int bash_backward_kill_shellword __P((int, int));
+
 /* Helper functions for Readline. */
+static char *restore_tilde __P((char *, char *));
+static char *maybe_restore_tilde __P((char *, char *));
+
+static char *bash_filename_rewrite_hook __P((char *, int));
+
+static void bash_directory_expansion __P((char **));
+static int bash_filename_stat_hook __P((char **));
+static int bash_command_name_stat_hook __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 int executable_completion __P((const char *, int));
+
+static rl_icppfunc_t *save_directory_hook __P((void));
+static void restore_directory_hook __P((rl_icppfunc_t));
+
+static int directory_exists __P((const char *, int));
+
 static void cleanup_expansion_error __P((void));
 static void maybe_make_readline_line __P((char *));
 static void set_up_new_line __P((char *));
@@ -102,6 +159,7 @@ static char *command_subst_completion_function __P((const char *, int));
 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 int bash_dabbrev_expand __P((int, int));
 
 static void initialize_hostname_list __P((void));
 static void add_host_name __P((char *));
@@ -115,8 +173,14 @@ static int return_zero __P((const char *));
 
 static char *bash_dequote_filename __P((char *, int));
 static char *quote_word_break_chars __P((char *));
+static void set_filename_bstab __P((const char *));
 static char *bash_quote_filename __P((char *, int, char *));
 
+#ifdef _MINIX
+static void putx __P((int));
+#else
+static int putx __P((int));
+#endif
 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 *));
@@ -127,19 +191,18 @@ static int set_saved_history __P((void));
 static int posix_edit_macros __P((int, int));
 #endif
 
+static int bash_event_hook __P((void));
+
 #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 *find_cmd_name __P((int, int *, int *));
 static char *prog_complete_return __P((const char *, int));
 
 static char **prog_complete_matches;
 #endif
 
-/* Variables used here but defined in other files. */
-extern int current_command_line_count;
-extern int posixly_correct, no_symbolic_links;
-extern char *current_prompt_string, *ps1_prompt;
+extern int no_symbolic_links;
 extern STRING_INT_ALIST word_token_alist[];
 
 /* SPECIFIC_COMPLETION_FUNCTIONS specifies that we have individual
@@ -168,15 +231,21 @@ 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 int completion_glob_pattern __P((char *));
 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 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;
@@ -189,11 +258,45 @@ 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;
+
+/* Perform spelling correction on directory names during word completion */
+int dircomplete_spelling = 0;
+
+/* Expand directory names during word/filename completion. */
+#if DIRCOMPLETE_EXPAND_DEFAULT
+int dircomplete_expand = 1;
+int dircomplete_expand_relpath = 1;
+#else
+int dircomplete_expand = 0;
+int dircomplete_expand_relpath = 0;
+#endif
+
+/* When non-zero, perform `normal' shell quoting on completed filenames
+   even when the completed name contains a directory name with a shell
+   variable referene, so dollar signs in a filename get quoted appropriately.
+   Set to zero to remove dollar sign (and braces or parens as needed) from
+   the set of characters that will be quoted. */
+int complete_fullquote = 1;
+
 static char *bash_completer_word_break_characters = " \t\n\"'@><=;|&(:";
 static char *bash_nohostname_word_break_characters = " \t\n\"'><=;|&(:";
+/* )) */
+
+static const char *default_filename_quote_characters = " \t\n\\\"'@<>=;|&()#$`?*[!:{~";        /*}*/
+static char *custom_filename_quote_characters = 0;
+static char filename_bstab[256];
 
 static rl_hook_func_t *old_rl_startup_hook = (rl_hook_func_t *)NULL;
 
+static int dot_in_path = 0;
+
+/* Set to non-zero when dabbrev-expand is running */
+static int dabbrev_expand_active = 0;
+
 /* What kind of quoting is performed by bash_quote_filename:
        COMPLETE_DQUOTE = double-quoting the filename
        COMPLETE_SQUOTE = single_quoting the filename
@@ -204,6 +307,9 @@ static rl_hook_func_t *old_rl_startup_hook = (rl_hook_func_t *)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
@@ -213,15 +319,24 @@ 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
+reset_completer_word_break_chars ()
+{
+  rl_completer_word_break_characters = perform_hostname_completion ? savestring (bash_completer_word_break_characters) : savestring (bash_nohostname_word_break_characters);
+}
+
+/* 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;
 
@@ -229,13 +344,60 @@ enable_hostname_completion (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);
@@ -245,6 +407,9 @@ enable_hostname_completion (on_or_off)
 void
 initialize_readline ()
 {
+  rl_command_func_t *func;
+  char kseq[2];
+
   if (bash_readline_initialized)
     return;
 
@@ -263,6 +428,11 @@ initialize_readline ()
   rl_add_defun ("magic-space", tcsh_magic_space, -1);
 #endif
 
+  rl_add_defun ("shell-forward-word", bash_forward_shellword, -1);
+  rl_add_defun ("shell-backward-word", bash_backward_shellword, -1);
+  rl_add_defun ("shell-kill-word", bash_kill_shellword, -1);
+  rl_add_defun ("shell-backward-kill-word", bash_backward_kill_shellword, -1);
+
 #ifdef ALIAS
   rl_add_defun ("alias-expand-line", alias_expand_line, -1);
 #  ifdef BANG_HISTORY
@@ -275,6 +445,7 @@ initialize_readline ()
 
   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);
@@ -291,78 +462,108 @@ initialize_readline ()
   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);
+  rl_add_defun ("dabbrev-expand", bash_dabbrev_expand, -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_in_map (CTRL('E'), shell_expand_line, emacs_meta_keymap);
+  rl_bind_key_if_unbound_in_map (CTRL('E'), shell_expand_line, emacs_meta_keymap);
 
-  /* Bind up our special shell functions. */
 #ifdef BANG_HISTORY
-  rl_bind_key_in_map ('^', history_expand_line, emacs_meta_keymap);
+  rl_bind_key_if_unbound_in_map ('^', history_expand_line, emacs_meta_keymap);
 #endif
 
-  rl_bind_key_in_map (CTRL ('O'), operate_and_get_next, emacs_standard_keymap);
-  rl_bind_key_in_map (CTRL ('V'), 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_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_bind_key_in_map ('/', bash_complete_filename, emacs_meta_keymap);
-  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_bind_key_in_map ('~', bash_complete_username, emacs_meta_keymap);
-  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_bind_key_in_map ('@', bash_complete_hostname, emacs_meta_keymap);
-  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_bind_key_in_map ('$', bash_complete_variable, emacs_meta_keymap);
-  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_bind_key_in_map ('!', bash_complete_command, emacs_meta_keymap);
-  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_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_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 = 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;
+  set_directory_hook ();
+
+  rl_filename_rewrite_hook = bash_filename_rewrite_hook;
+
+  rl_filename_stat_hook = bash_filename_stat_hook;
 
   /* Tell the filename completer we want a chance to ignore some names. */
   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 = "'\"";
@@ -373,30 +574,64 @@ 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 = default_filename_quote_characters;
+  set_filename_bstab (rl_filename_quote_characters);
+
   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;
 }
 
+void
+bashline_reinitialize ()
+{
+  bash_readline_initialized = 0;
+}
+
+void
+bashline_set_event_hook ()
+{
+  rl_signal_event_hook = bash_event_hook;
+}
+
+void
+bashline_reset_event_hook ()
+{
+  rl_signal_event_hook = 0;
+}
+
 /* On Sun systems at least, rl_attempted_completion_function can end up
    getting set to NULL, and rl_completion_entry_function set to do command
    word completion if Bash is interrupted while trying to complete a command
    word.  This just resets all the completion functions to the right thing.
    It's called from throw_to_top_level(). */
 void
-bashline_reinitialize ()
+bashline_reset ()
 {
   tilde_initialize ();
   rl_attempted_completion_function = attempt_shell_completion;
   rl_completion_entry_function = NULL;
-  rl_directory_completion_hook = bash_directory_completion_hook;
   rl_ignore_some_completions_function = filename_completion_ignore;
+  rl_filename_quote_characters = default_filename_quote_characters;
+  set_filename_bstab (rl_filename_quote_characters);
+
+  set_directory_hook ();
+  rl_filename_stat_hook = bash_filename_stat_hook;
+
+  bashline_reset_event_hook ();
+
+  rl_sort_completion_matches = 1;
 }
 
 /* Contains the line to push into readline. */
@@ -491,13 +726,10 @@ static void
 add_host_name (name)
      char *name;
 {
-  size_t 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);
@@ -590,11 +822,11 @@ clear_hostname_list ()
     return;
   for (i = 0; i < hostname_list_length; i++)
     free (hostname_list[i]);
-  hostname_list_length = 0;
+  hostname_list_length = hostname_list_initialized = 0;
 }
 
 /* Return a NULL terminated list of hostnames which begin with TEXT.
-   Initialize the hostname list the first time if neccessary.
+   Initialize the hostname list the first time if necessary.
    The array is malloc ()'ed, but not the individual strings. */
 static char **
 hostnames_matching (text)
@@ -613,7 +845,7 @@ hostnames_matching (text)
      what is desired. */
   if (*text == '\0')
     {
-      result = alloc_array (1 + hostname_list_length);
+      result = strvec_create (1 + hostname_list_length);
       for (i = 0; i < hostname_list_length; i++)
        result[i] = hostname_list[i];
       result[i] = (char *)NULL;
@@ -632,7 +864,7 @@ hostnames_matching (text)
       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];
@@ -645,12 +877,25 @@ hostnames_matching (text)
 /* The equivalent of the Korn shell C-o operate-and-get-next-history-line
    editing command. */
 static int saved_history_line_to_use = -1;
+static int last_saved_history_line = -1;
+
+#define HISTORY_FULL() (history_is_stifled () && history_length >= history_max_entries)
 
 static int
 set_saved_history ()
 {
+  /* XXX - compensate for assumption that history was `shuffled' if it was
+     actually not. */
+  if (HISTORY_FULL () &&
+      hist_last_line_added == 0 &&
+      saved_history_line_to_use < history_length - 1)
+    saved_history_line_to_use++;
+
   if (saved_history_line_to_use >= 0)
-    rl_get_previous_history (history_length - saved_history_line_to_use, 0);
+    {
+     rl_get_previous_history (history_length - saved_history_line_to_use, 0);
+     last_saved_history_line = saved_history_line_to_use;
+    }
   saved_history_line_to_use = -1;
   rl_startup_hook = old_rl_startup_hook;
   return (0);
@@ -666,10 +911,9 @@ operate_and_get_next (count, c)
   rl_newline (1, c);
 
   /* Find the current line, and find the next line to use. */
-  where = where_history ();
+  where = rl_explicit_arg ? count : where_history ();
 
-  if ((history_is_stifled () && (history_length >= history_max_entries)) ||
-      (where >= history_length - 1))
+  if (HISTORY_FULL () || (where >= history_length - 1) || rl_explicit_arg)
     saved_history_line_to_use = where;
   else
     saved_history_line_to_use = where + 1;
@@ -680,30 +924,33 @@ operate_and_get_next (count, c)
   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 int
-vi_edit_and_execute_command (count, c)
-     int count, c;
+edit_and_execute_command (count, c, editing_mode, edit_command)
+     int count, c, editing_mode;
+     char *edit_command;
 {
-  char *command;
-  int r, cclc, rrs;
+  char *command, *metaval;
+  int r, rrs, metaflag;
+  sh_parser_state_t ps;
 
   rrs = rl_readline_state;
-  cclc = current_command_line_count;
+  saved_command_line_count = current_command_line_count;
 
   /* Accept the current line. */
   rl_newline (1, c);
 
   if (rl_explicit_arg)
     {
-      command = (char *)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
     {
@@ -711,16 +958,33 @@ vi_edit_and_execute_command (count, c)
         then call fc to operate on it.  We have to add a dummy command to
         the end of the history because fc ignores the last command (assumes
         it's supposed to deal with the command before the `fc'). */
+      /* This breaks down when using command-oriented history and are not
+        finished with the command, so we should not ignore the last command */
       using_history ();
-      bash_add_history (rl_line_buffer);
+      if (rl_line_buffer[0])
+       {
+         current_command_line_count++; /* for rl_newline above */
+         bash_add_history (rl_line_buffer);
+       }
+      current_command_line_count = 0;  /* for dummy history entry */
       bash_add_history ("");
       history_lines_this_session++;
       using_history ();
-      command = savestring (VI_EDIT_COMMAND);
+      command = savestring (edit_command);
     }
-  r = parse_and_execute (command, "v", SEVAL_NOHIST);
 
-  current_command_line_count = cclc;
+  metaval = rl_variable_value ("input-meta");
+  metaflag = RL_BOOLEAN_VARIABLE_VALUE (metaval);
+  
+  if (rl_deprep_term_function)
+    (*rl_deprep_term_function) ();
+  save_parser_state (&ps);
+  r = parse_and_execute (command, (editing_mode == VI_EDITING_MODE) ? "v" : "C-xC-e", SEVAL_NOHIST);
+  restore_parser_state (&ps);
+  if (rl_prep_term_function)
+    (*rl_prep_term_function) (metaflag);
+
+  current_command_line_count = saved_command_line_count;
 
   /* 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
@@ -730,12 +994,35 @@ vi_edit_and_execute_command (count, c)
   rl_done = 0;
   rl_readline_state = rrs;
 
+#if defined (VI_MODE)
+  if (editing_mode == VI_EDITING_MODE)
+    rl_vi_insertion_mode (1, c);
+#endif
+
   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)
@@ -759,6 +1046,220 @@ posix_edit_macros (count, key)
 }
 #endif
 
+/* Bindable commands that move `shell-words': that is, sequences of
+   non-unquoted-metacharacters. */
+
+#define WORDDELIM(c)   (shellmeta(c) || shellblank(c))
+
+static int
+bash_forward_shellword (count, key)
+     int count, key;
+{
+  size_t slen;
+  int c, p;
+  DECLARE_MBSTATE;
+
+  if (count < 0)
+    return (bash_backward_shellword (-count, key));
+
+  /* The tricky part of this is deciding whether or not the first character
+     we're on is an unquoted metacharacter.  Not completely handled yet. */
+  /* XXX - need to test this stuff with backslash-escaped shell
+     metacharacters and unclosed single- and double-quoted strings. */
+
+  p = rl_point;
+  slen = rl_end;
+
+  while (count)
+    {
+      if (p == rl_end)
+       {
+         rl_point = rl_end;
+         return 0;
+       }
+
+      /* Are we in a quoted string?  If we are, move to the end of the quoted
+         string and continue the outer loop. We only want quoted strings, not
+         backslash-escaped characters, but char_is_quoted doesn't
+         differentiate. */
+      if (char_is_quoted (rl_line_buffer, p) && p > 0 && rl_line_buffer[p-1] != '\\')
+       {
+         do
+           ADVANCE_CHAR (rl_line_buffer, slen, p);
+         while (p < rl_end && char_is_quoted (rl_line_buffer, p));
+         count--;
+         continue;
+       }
+
+      /* Rest of code assumes we are not in a quoted string. */
+      /* Move forward until we hit a non-metacharacter. */
+      while (p < rl_end && (c = rl_line_buffer[p]) && WORDDELIM (c))
+       {
+         switch (c)
+           {
+           default:
+             ADVANCE_CHAR (rl_line_buffer, slen, p);
+             continue;         /* straight back to loop, don't increment p */
+           case '\\':
+             if (p < rl_end && rl_line_buffer[p])
+               ADVANCE_CHAR (rl_line_buffer, slen, p);
+             break;
+           case '\'':
+             p = skip_to_delim (rl_line_buffer, ++p, "'", SD_NOJMP);
+             break;
+           case '"':
+             p = skip_to_delim (rl_line_buffer, ++p, "\"", SD_NOJMP);
+             break;
+           }
+
+         if (p < rl_end)
+           p++;
+       }
+
+      if (rl_line_buffer[p] == 0 || p == rl_end)
+        {
+         rl_point = rl_end;
+         rl_ding ();
+         return 0;
+        }
+       
+      /* Now move forward until we hit a non-quoted metacharacter or EOL */
+      while (p < rl_end && (c = rl_line_buffer[p]) && WORDDELIM (c) == 0)
+       {
+         switch (c)
+           {
+           default:
+             ADVANCE_CHAR (rl_line_buffer, slen, p);
+             continue;         /* straight back to loop, don't increment p */
+           case '\\':
+             if (p < rl_end && rl_line_buffer[p])
+               ADVANCE_CHAR (rl_line_buffer, slen, p);
+             break;
+           case '\'':
+             p = skip_to_delim (rl_line_buffer, ++p, "'", SD_NOJMP);
+             break;
+           case '"':
+             p = skip_to_delim (rl_line_buffer, ++p, "\"", SD_NOJMP);
+             break;
+           }
+
+         if (p < rl_end)
+           p++;
+       }
+
+      if (p == rl_end || rl_line_buffer[p] == 0)
+       {
+         rl_point = rl_end;
+         return (0);
+       }
+
+      count--;      
+    }
+
+  rl_point = p;
+  return (0);
+}
+
+static int
+bash_backward_shellword (count, key)
+     int count, key;
+{
+  size_t slen;
+  int c, p;
+  DECLARE_MBSTATE;
+
+  if (count < 0)
+    return (bash_forward_shellword (-count, key));
+
+  p = rl_point;
+  slen = rl_end;
+
+  if (p == rl_end && p > 0)
+    p--;  
+
+  while (count)
+    {
+      if (p == 0)
+       {
+         rl_point = 0;
+         return 0;
+       }
+
+      /* Move backward until we hit a non-metacharacter. */
+      while (p > 0)
+       {
+         c = rl_line_buffer[p];
+         if (WORDDELIM (c) == 0 || char_is_quoted (rl_line_buffer, p))
+           break;
+         BACKUP_CHAR (rl_line_buffer, slen, p);
+       }
+
+      if (p == 0)
+       {
+         rl_point = 0;
+         return 0;
+       }
+
+      /* Now move backward until we hit a metacharacter or BOL. */
+      while (p > 0)
+       {
+         c = rl_line_buffer[p];
+         if (WORDDELIM (c) && char_is_quoted (rl_line_buffer, p) == 0)
+           break;
+         BACKUP_CHAR (rl_line_buffer, slen, p);
+       }
+
+      count--;
+    }
+
+  rl_point = p;
+  return 0;
+}
+
+static int
+bash_kill_shellword (count, key)
+     int count, key;
+{
+  int p;
+
+  if (count < 0)
+    return (bash_backward_kill_shellword (-count, key));
+
+  p = rl_point;
+  bash_forward_shellword (count, key);
+
+  if (rl_point != p)
+    rl_kill_text (p, rl_point);
+
+  rl_point = p;
+  if (rl_editing_mode == 1)    /* 1 == emacs_mode */
+    rl_mark = rl_point;
+
+  return 0;
+}
+
+static int
+bash_backward_kill_shellword (count, key)
+     int count, key;
+{
+  int p;
+
+  if (count < 0)
+    return (bash_kill_shellword (-count, key));
+
+  p = rl_point;
+  bash_backward_shellword (count, key);
+
+  if (rl_point != p)
+    rl_kill_text (p, rl_point);
+
+  if (rl_editing_mode == 1)    /* 1 == emacs_mode */
+    rl_mark = rl_point;
+
+  return 0;
+}
+
+
 /* **************************************************************** */
 /*                                                                 */
 /*                     How To Do Shell Completion                  */
@@ -766,7 +1267,12 @@ posix_edit_macros (count, key)
 /* **************************************************************** */
 
 #define COMMAND_SEPARATORS ";|&{(`"
+/* )} */ 
+#define COMMAND_SEPARATORS_PLUS_WS ";|&{(` \t"
+/* )} */ 
 
+/* check for redirections and other character combinations that are not
+   command separators */
 static int
 check_redir (ti)
      int ti;
@@ -776,13 +1282,24 @@ check_redir (ti)
   /* 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];
+  prev_char = (ti > 0) ? rl_line_buffer[ti - 1] : 0;
 
   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)))
+  else if (this_char == '{' && prev_char == '$') /*}*/
+    return (1);
+#if 0  /* Not yet */
+  else if (this_char == '(' && prev_char == '$') /*)*/
+    return (1);
+  else if (this_char == '(' && prev_char == '<') /*)*/
+    return (1);
+#if defined (EXTENDED_GLOB)
+  else if (extended_glob && this_char == '(' && prev_char == '!') /*)*/
+    return (1);
+#endif
+#endif
+  else if (char_is_quoted (rl_line_buffer, ti))
     return (1);
   return (0);
 }
@@ -797,12 +1314,26 @@ static int
 find_cmd_start (start)
      int start;
 {
-  register int s, os;
+  register int s, os, ns;
 
   os = 0;
-  while (((s = skip_to_delim (rl_line_buffer, os, COMMAND_SEPARATORS)) <= start) &&
+  /* Flags == SD_NOJMP only because we want to skip over command substitutions
+     in assignment statements.  Have to test whether this affects `standalone'
+     command substitutions as individual words. */
+  while (((s = skip_to_delim (rl_line_buffer, os, COMMAND_SEPARATORS, SD_NOJMP|SD_COMPLETE/*|SD_NOSKIPCMD*/)) <= start) &&
         rl_line_buffer[s])
-    os = s+1;
+    {
+      /* Handle >| token crudely; treat as > not | */
+      if (rl_line_buffer[s] == '|' && rl_line_buffer[s-1] == '>')
+       {
+         ns = skip_to_delim (rl_line_buffer, s+1, COMMAND_SEPARATORS, SD_NOJMP|SD_COMPLETE/*|SD_NOSKIPCMD*/);
+         if (ns > start || rl_line_buffer[ns] == 0)
+           return os;
+         os = ns+1;
+         continue;
+       }
+      os = s+1;
+    }
   return os;
 }
 
@@ -812,13 +1343,14 @@ find_cmd_end (end)
 {
   register int e;
 
-  e = skip_to_delim (rl_line_buffer, end, COMMAND_SEPARATORS);
+  e = skip_to_delim (rl_line_buffer, end, COMMAND_SEPARATORS, SD_NOJMP|SD_COMPLETE);
   return e;
 }
 
 static char *
-find_cmd_name (start)
+find_cmd_name (start, sp, ep)
      int start;
+     int *sp, *ep;
 {
   char *name;
   register int s, e;
@@ -827,10 +1359,15 @@ find_cmd_name (start)
     ;
 
   /* skip until a shell break character */
-  e = skip_to_delim (rl_line_buffer, s, "()<>;&| \t\n");
+  e = skip_to_delim (rl_line_buffer, s, "()<>;&| \t\n", SD_NOJMP|SD_COMPLETE);
 
   name = substring (rl_line_buffer, s, e);
 
+  if (sp)
+    *sp = s;
+  if (ep)
+    *ep = e;
+
   return (name);
 }
 
@@ -851,6 +1388,34 @@ prog_complete_return (text, matchnum)
 
 #endif /* PROGRAMMABLE_COMPLETION */
 
+/* Try and catch completion attempts that are syntax errors or otherwise
+   invalid. */
+static int
+invalid_completion (text, ind)
+     const char *text;
+     int ind;
+{
+  int pind;
+
+  /* If we don't catch these here, the next clause will */
+  if (ind > 0 && rl_line_buffer[ind] == '(' && /*)*/
+                member (rl_line_buffer[ind-1], "$<>"))
+    return 0;
+
+  pind = ind - 1;
+  while (pind > 0 && whitespace (rl_line_buffer[pind]))
+    pind--;
+  /* If we have only whitespace preceding a paren, it's valid */
+  if (ind >= 0 && pind <= 0 && rl_line_buffer[ind] == '(')     /*)*/
+    return 0;
+  /* Flag the invalid completions, which are mostly syntax errors */
+  if (ind > 0 && rl_line_buffer[ind] == '(' && /*)*/
+                member (rl_line_buffer[pind], COMMAND_SEPARATORS) == 0)
+    return 1;
+
+  return 0;
+}
+
 /* 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 **
@@ -858,19 +1423,30 @@ attempt_shell_completion (text, start, end)
      const char *text;
      int start, end;
 {
-  int in_command_position, ti, saveti, qc;
+  int in_command_position, ti, qc, dflags;
   char **matches, *command_separator_chars;
+#if defined (PROGRAMMABLE_COMPLETION)
+  int have_progcomps, was_assignment;
+  COMPSPEC *iw_compspec;
+#endif
 
   command_separator_chars = COMMAND_SEPARATORS;
   matches = (char **)NULL;
   rl_ignore_some_completions_function = filename_completion_ignore;
 
+  rl_filename_quote_characters = default_filename_quote_characters;
+  set_filename_bstab (rl_filename_quote_characters);
+  set_directory_hook ();
+  rl_filename_stat_hook = bash_filename_stat_hook;
+
+  rl_sort_completion_matches = 1;      /* sort by default */
+
   /* 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;
+  qc = -1;
 
   while ((ti > -1) && (whitespace (rl_line_buffer[ti])))
     ti--;
@@ -878,10 +1454,10 @@ attempt_shell_completion (text, start, end)
 #if 1
   /* If this is an open quote, maybe we're trying to complete a quoted
      command name. */
-  if (rl_line_buffer[ti] == '"' || rl_line_buffer[ti] == '\'')
+  if (ti >= 0 && (rl_line_buffer[ti] == '"' || rl_line_buffer[ti] == '\''))
     {
       qc = rl_line_buffer[ti];
-      saveti = ti--;
+      ti--;
       while (ti > -1 && (whitespace (rl_line_buffer[ti])))
        ti--;
     }
@@ -894,6 +1470,8 @@ attempt_shell_completion (text, start, end)
         are prompting at the top level. */
       if (current_prompt_string == ps1_prompt)
        in_command_position++;
+      else if (parser_in_command_position ())
+       in_command_position++;
     }
   else if (member (rl_line_buffer[ti], command_separator_chars))
     {
@@ -909,6 +1487,12 @@ attempt_shell_completion (text, start, end)
         assignments. */
     }
 
+  if (in_command_position && invalid_completion (text, ti))
+    {
+      rl_attempted_completion_over = 1;
+      return ((char **)NULL);
+    }
+
   /* Check that we haven't incorrectly flagged a closed command substitution
      as indicating we're in a command position. */
   if (in_command_position && ti >= 0 && rl_line_buffer[ti] == '`' &&
@@ -918,18 +1502,22 @@ attempt_shell_completion (text, start, end)
   /* 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 == '`' && 
+     succeed.  Don't bother if readline found a single quote and we are
+     completing on the substring.  */
+  if (*text == '`' && rl_completion_quote_character != '\'' &&
        (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 &&
-      (num_progcomps () > 0) && current_prompt_string == ps1_prompt)
+  have_progcomps = prog_completion_enabled && (progcomp_size () > 0);
+  iw_compspec = progcomp_search (INITIALWORD);
+  if (matches == 0 &&
+      (in_command_position == 0 || text[0] == '\0' || (in_command_position && iw_compspec)) &&
+      current_prompt_string == ps1_prompt)
     {
-      int s, e, foundcs;
+      int s, e, s1, e1, os, foundcs;
       char *n;
 
       /* XXX - don't free the members */
@@ -937,67 +1525,189 @@ attempt_shell_completion (text, start, end)
        free (prog_complete_matches);
       prog_complete_matches = (char **)NULL;
 
-      s = find_cmd_start (start);
+      os = start;
+      n = 0;
+      was_assignment = 0;
+      s = find_cmd_start (os);
       e = find_cmd_end (end);
-      n = find_cmd_name (s);
-      if (e > s)
-       prog_complete_matches = programmable_completions (n, text, s, e, &foundcs);
+      do
+       {
+         /* Don't read past the end of rl_line_buffer */
+         if (s > rl_end)
+           {
+             s1 = s = e1;
+             break;
+           }
+         /* Or past point if point is within an assignment statement */
+         else if (was_assignment && s > rl_point)
+           {
+             s1 = s = e1;
+             break;
+           }
+         /* Skip over assignment statements preceding a command name.  If we
+            don't find a command name at all, we can perform command name
+            completion.  If we find a partial command name, we should perform
+            command name completion on it. */
+         FREE (n);
+         n = find_cmd_name (s, &s1, &e1);
+         s = e1 + 1;
+       }
+      while (was_assignment = assignment (n, 0));
+      s = s1;          /* reset to index where name begins */
+
+      /* s == index of where command name begins (reset above)
+        e == end of current command, may be end of line
+         s1 = index of where command name begins
+         e1 == index of where command name ends
+        start == index of where word to be completed begins
+        end == index of where word to be completed ends
+        if (s == start) we are doing command word completion for sure
+        if (e1 == end) we are at the end of the command name and completing it */
+      if (start == 0 && end == 0 && e != 0 && text[0] == '\0') /* beginning of non-empty line */
+        foundcs = 0;
+      else if (start == end && start == s1 && e != 0 && e1 > end)      /* beginning of command name, leading whitespace */
+       foundcs = 0;
+      else if (e == 0 && e == s && text[0] == '\0' && have_progcomps)  /* beginning of empty line */
+        prog_complete_matches = programmable_completions (EMPTYCMD, text, s, e, &foundcs);
+      else if (start == end && text[0] == '\0' && s1 > start && whitespace (rl_line_buffer[start]))
+        foundcs = 0;           /* whitespace before command name */
+      else if (e > s && was_assignment == 0 && e1 == end && rl_line_buffer[e] == 0 && whitespace (rl_line_buffer[e-1]) == 0)
+       {
+         /* not assignment statement, but still want to perform command
+            completion if we are composing command word. */
+         foundcs = 0;
+         in_command_position = s == start && STREQ (n, text);  /* XXX */
+       }
+      else if (e > s && was_assignment == 0 && have_progcomps)
+       {
+         prog_complete_matches = programmable_completions (n, text, s, e, &foundcs);
+         /* command completion if programmable completion fails */
+         /* If we have a completion for the initial word, we can prefer that */
+         in_command_position = s == start && (iw_compspec || STREQ (n, text)); /* XXX */
+         if (iw_compspec && in_command_position)
+           foundcs = 0;
+       }
+      /* empty command name following command separator */
+      else if (s >= e && n[0] == '\0' && text[0] == '\0' && start > 0 &&
+               was_assignment == 0 && member (rl_line_buffer[start-1], COMMAND_SEPARATORS))
+       {
+         foundcs = 0;
+         in_command_position = 1;
+       }
+      else if (s >= e && n[0] == '\0' && text[0] == '\0' && start > 0)
+        {
+          foundcs = 0; /* empty command name following optional assignments */
+          in_command_position += was_assignment;
+        }
+      else if (s == start && e == end && STREQ (n, text) && start > 0)
+        {
+          foundcs = 0; /* partial command name following assignments */
+          in_command_position = 1;
+        }
       else
        foundcs = 0;
+
+      /* If we have defined a compspec for the initial (command) word, call
+        it and process the results like any other programmable completion. */
+      if (in_command_position && have_progcomps && foundcs == 0 && iw_compspec)
+       prog_complete_matches = programmable_completions (INITIALWORD, text, s, e, &foundcs);
+
       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. */
+        option has been set with the `-o default' option to complete or
+        compopt. */
       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;
+         pcomp_set_readline_variables (foundcs, 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 */
-         return (matches);
+         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, *t;
+
+  matches = (char **)NULL;
+
   /* New posix-style command substitution or variable name? */
-  if (!matches && *text == '$')
+  if (*text == '$')
     {
       if (qc != '\'' && text[1] == '(') /* ) */
        matches = rl_completion_matches (text, command_subst_completion_function);
       else
-       matches = rl_completion_matches (text, variable_completion_function);
-    }
+       {
+         matches = rl_completion_matches (text, variable_completion_function);
+         /* If a single match, see if it expands to a directory name and append
+            a slash if it does.  This requires us to expand the variable name,
+            so we don't want to display errors if the variable is unset.  This
+            can happen with dynamic variables whose value has never been
+            requested. */
+         if (matches && matches[0] && matches[1] == 0)
+           {
+             t = savestring (matches[0]);
+             bash_filename_stat_hook (&t);
+             /* doesn't use test_for_directory because that performs tilde
+                expansion */
+             if (file_isdir (t))
+               rl_completion_append_character = '/';
+             free (t);
+           }
+       }
+    }
 
   /* 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, '/'))
+  if (matches == 0 && *text == '~' && mbschr (text, '/') == 0)
     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 == '@')
+  if (matches == 0 && perform_hostname_completion && *text == '@')
     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))
     {
-      if (start == 0 && end == 0 && text[0] == '\0' && no_empty_command_completion)
+      /* 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
@@ -1005,26 +1715,92 @@ attempt_shell_completion (text, start, end)
             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))
+  if (!matches && completion_glob_pattern ((char *)text))
     {
       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])
+      if (matches && matches[1] && rl_completion_type == TAB)
        {
-         free_array (matches);
+         strvec_dispose (matches);
          matches = (char **)0;
        }
+      else if (matches && matches[1] && rl_completion_type == '!')
+       {
+         rl_completion_suppress_append = 1;
+         rl_filename_completion_desired = 0;
+       }
     }
 
   return (matches);
 }
 
+static int
+bash_command_name_stat_hook (name)
+     char **name;
+{
+  char *cname, *result;
+
+  /* If it's not something we're going to look up in $PATH, just call the
+     normal filename stat hook. */
+  if (absolute_program (*name))
+    return (bash_filename_stat_hook (name));
+
+  cname = *name;
+  /* XXX - we could do something here with converting aliases, builtins,
+     and functions into something that came out as executable, but we don't. */
+  result = search_for_command (cname, 0);
+  if (result)
+    {
+      *name = result;
+      return 1;
+    }
+  return 0;
+}
+
+static int
+executable_completion (filename, searching_path)
+     const char *filename;
+     int searching_path;
+{
+  char *f;
+  int r;
+
+  f = savestring (filename);
+  bash_directory_completion_hook (&f);
+  
+  r = searching_path ? executable_file (f) : executable_or_directory (f);
+  free (f);
+  return r;
+}
+
 /* This is the function to call when the word to complete is in a position
    where a command word can be found.  It grovels $PATH, looking for commands
    that match.  It also scans aliases, function names, and the shell_builtin
@@ -1038,51 +1814,123 @@ command_word_completion_function (hint_text, state)
   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 *fnhint = (char *)NULL;
+  static char *dequoted_hint = (char *)NULL;
+  static char *directory_part = (char *)NULL;
+  static char **glob_matches = (char **)NULL;
+  static int path_index, hint_len, istate, igncase;
+  static int mapping_over, local_index, searching_path, hint_is_dir;
+  static int old_glob_ignore_case, globpat;
   static SHELL_VAR **varlist = (SHELL_VAR **)NULL;
 #if defined (ALIAS)
   static alias_t **alias_list = (alias_t **)NULL;
 #endif /* ALIAS */
+  char *temp, *cval;
 
   /* 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 (state == 0)
     {
+      rl_filename_stat_hook = bash_command_name_stat_hook;
+
+      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 = RL_BOOLEAN_VARIABLE_VALUE (temp);
+
+      if (glob_matches)
+       {
+         free (glob_matches);
+         glob_matches = (char **)NULL;
+       }
+
+      globpat = completion_glob_pattern ((char *)hint_text);
+
       /* If this is an absolute program name, do not check it against
         aliases, reserved words, functions or builtins.  We must check
         whether or not it is unique, and, if so, whether that filename
         is executable. */
-      if (absolute_program (hint_text))
+      if (globpat || absolute_program (hint_text))
        {
          /* Perform tilde expansion on what's passed, so we don't end up
-            passing filenames with tildes directly to stat(). */
+            passing filenames with tildes directly to stat().  The rest of
+            the shell doesn't do variable expansion on the word following
+            the tilde, so we don't do it here even if direxpand is set. */
          if (*hint_text == '~')
-           hint = bash_tilde_expand (hint_text);
+           {
+             hint = bash_tilde_expand (hint_text, 0);
+             directory_part = savestring (hint_text);
+             temp = strchr (directory_part, '/');
+             if (temp)
+               *temp = 0;
+             else
+               {
+                 free (directory_part);
+                 directory_part = (char *)NULL;
+               }
+           }
+         else if (dircomplete_expand)
+           {
+             hint = savestring (hint_text);
+             bash_directory_completion_hook (&hint);
+           }
          else
            hint = savestring (hint_text);
+
+         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;
+           }
          hint_len = strlen (hint);
 
          if (filename_hint)
            free (filename_hint);
-         filename_hint = savestring (hint);
 
-         mapping_over = 4;
+         fnhint = filename_hint = savestring (hint);
+
          istate = 0;
-         goto inner;
+
+         if (globpat)
+           {
+             mapping_over = 5;
+             goto globword;
+           }
+         else
+           {
+            if (dircomplete_expand && path_dot_or_dotdot (filename_hint))
+               {
+                 dircomplete_expand = 0;
+                 set_directory_hook ();
+                 dircomplete_expand = 1;
+               }
+             mapping_over = 4;
+             goto inner;
+           }
        }
 
-      hint = savestring (hint_text);
+      dequoted_hint = hint = savestring (hint_text);
       hint_len = strlen (hint);
 
+      if (rl_completion_found_quote && rl_completion_quote_character == 0)
+       dequoted_hint = bash_dequote_filename (hint, 0);
+      
       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;
@@ -1113,7 +1961,9 @@ command_word_completion_function (hint_text, state)
 
          alias = alias_list[local_index++]->name;
 
-         if (STREQN (alias, hint, hint_len))
+         if (igncase == 0 && (STREQN (alias, hint, hint_len)))
+           return (savestring (alias));
+         else if (igncase && strncasecmp (alias, hint, hint_len) == 0)
            return (savestring (alias));
        }
 #endif /* ALIAS */
@@ -1142,7 +1992,10 @@ command_word_completion_function (hint_text, state)
 
          varname = varlist[local_index++]->name;
 
-         if (STREQN (varname, hint, hint_len))
+         /* Honor completion-ignore-case for shell function names. */
+         if (igncase == 0 && (STREQN (varname, hint, hint_len)))
+           return (savestring (varname));
+         else if (igncase && strncasecmp (varname, hint, hint_len) == 0)
            return (savestring (varname));
        }
       local_index = 0;
@@ -1168,6 +2021,60 @@ command_word_completion_function (hint_text, state)
       mapping_over++;
     }
 
+globword:
+  /* Limited support for completing command words with globbing chars.  Only
+     a single match (multiple matches that end up reducing the number of
+     characters in the common prefix are bad) will ever be returned on
+     regular completion. */
+  if (globpat)
+    {
+      if (state == 0)
+       {
+         glob_ignore_case = igncase;
+         glob_matches = shell_glob_filename (hint);
+         glob_ignore_case = old_glob_ignore_case;
+
+         if (GLOB_FAILED (glob_matches) || glob_matches == 0)
+           {
+             glob_matches = (char **)NULL;
+             return ((char *)NULL);
+           }
+
+         local_index = 0;
+               
+         if (glob_matches[1] && rl_completion_type == TAB)     /* multiple matches are bad */
+           return ((char *)NULL);
+       }
+
+      while (val = glob_matches[local_index++])
+        {
+         if (executable_or_directory (val))
+           {
+             if (*hint_text == '~' && directory_part)
+               {
+                 temp = maybe_restore_tilde (val, directory_part);
+                 free (val);
+                 val = temp;
+               }
+             return (val);
+           }
+         free (val);
+        }
+
+      glob_ignore_case = old_glob_ignore_case;
+      return ((char *)NULL);
+    }
+
+  /* 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. */
@@ -1175,16 +2082,17 @@ command_word_completion_function (hint_text, state)
 
   istate = (val != (char *)NULL);
 
-  if (!istate)
+  if (istate == 0)
     {
       char *current_path;
 
       /* Get the next directory from the path.  If there is none, then we
         are all done. */
-      if (!path || !path[path_index] ||
+      if (path == 0 || path[path_index] == 0 ||
          (current_path = extract_colon_unit (path, &path_index)) == 0)
        return ((char *)NULL);
 
+      searching_path = 1;
       if (*current_path == 0)
        {
          free (current_path);
@@ -1195,22 +2103,36 @@ 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 (fnhint && fnhint != filename_hint)
+       free (fnhint);
       if (filename_hint)
        free (filename_hint);
 
-      filename_hint = (char *)xmalloc (2 + strlen (current_path) + hint_len);
-      sprintf (filename_hint, "%s/%s", current_path, hint);
-
-      free (current_path);
+      filename_hint = sh_makepath (current_path, hint, 0);
+      /* Need a quoted version (though it doesn't matter much in most
+        cases) because rl_filename_completion_function dequotes the
+        filename it gets, assuming that it's been quoted as part of
+        the input line buffer. */
+      if (strpbrk (filename_hint, "\"'\\"))
+       fnhint = sh_backslash_quote (filename_hint, filename_bstab, 0);
+      else
+       fnhint = filename_hint;
+      free (current_path);             /* XXX */
     }
 
  inner:
-  val = rl_filename_completion_function (filename_hint, istate);
+  val = rl_filename_completion_function (fnhint, istate);
+  if (mapping_over == 4 && dircomplete_expand)
+    set_directory_hook ();
+
   istate = 1;
 
   if (val == 0)
@@ -1225,23 +2147,18 @@ command_word_completion_function (hint_text, state)
   else
     {
       int match, freetemp;
-      char *temp;
 
       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;
-             vl = strlen (val);
-             tl = strlen (hint_text);
-             l = vl - hint_len;        /* # of chars added */
-             temp = (char *)xmalloc (l + 2 + tl);
-             strcpy (temp, hint_text);
-             strcpy (temp + tl, val + vl - l);
-           }
+           temp = maybe_restore_tilde (val, directory_part);
          else
            temp = savestring (val);
          freetemp = 1;
@@ -1253,7 +2170,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);
            }
@@ -1261,10 +2181,38 @@ command_word_completion_function (hint_text, state)
            freetemp = match = 0;
        }
 
-      /* If we have found a match, and it is an executable file or a
-        directory name, return it. */
-      if (match && executable_or_directory (val))
+      /* 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 0
+      /* If we're not searching $PATH and we have a relative pathname, we
+        need to re-canonicalize it before testing whether or not it's an
+        executable or a directory so the shell treats .. relative to $PWD
+        according to the physical/logical option.  The shell already
+        canonicalizes the directory name in order to tell readline where
+        to look, so not doing it here will be inconsistent. */
+      /* XXX -- currently not used -- will introduce more inconsistency,
+        since shell does not canonicalize ../foo before passing it to
+        shell_execve().  */
+      if (match && searching_path == 0 && *val == '.')
+       {
+         char *t, *t1;
+
+         t = get_working_directory ("command-word-completion");
+         t1 = make_absolute (val, t);
+         free (t);
+         cval = sh_canonpath (t1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
+       }
+      else
+#endif
+       cval = val;
+
+      if (match && executable_completion ((searching_path ? val : cval), searching_path))
        {
+         if (cval != val)
+           free (cval);
          free (val);
          val = "";             /* So it won't be NULL. */
          return (temp);
@@ -1273,6 +2221,8 @@ command_word_completion_function (hint_text, state)
        {
          if (freetemp)
            free (temp);
+         if (cval != val)
+           free (cval);
          free (val);
          goto inner;
        }
@@ -1300,15 +2250,48 @@ command_subst_completion_function (text, state)
        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 = rl_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])
+  if (matches == 0 || matches[cmd_index] == 0)
     {
       rl_filename_quoting_desired = 0; /* disable quoting */
       return ((char *)NULL);
@@ -1338,7 +2321,6 @@ variable_completion_function (text, state)
   static char **varlist = (char **)NULL;
   static int varlist_index;
   static char *varname = (char *)NULL;
-  static int namelen;
   static int first_char, first_char_loc;
 
   if (!state)
@@ -1357,9 +2339,8 @@ variable_completion_function (text, state)
 
       varname = savestring (text + first_char_loc);
 
-      namelen = strlen (varname);
       if (varlist)
-       free_array (varlist);
+       strvec_dispose (varlist);
 
       varlist = all_variables_matching_prefix (varname);
       varlist_index = 0;
@@ -1432,6 +2413,67 @@ hostname_completion_function (text, state)
   return ((char *)NULL);
 }
 
+/*
+ * A completion function for service names from /etc/services (or wherever).
+ */
+char *
+bash_servicename_completion_function (text, state)
+     const char *text;
+     int state;
+{
+#if defined (__WIN32__) || defined (__OPENNT) || !defined (HAVE_GETSERVENT)
+  return ((char *)NULL);
+#else
+  static char *sname = (char *)NULL;
+  static struct servent *srvent;
+  static int snamelen;
+  char *value;
+  char **alist, *aentry;
+  int afound;
+
+  if (state == 0)
+    {
+      FREE (sname);
+
+      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
+}
+
+/*
+ * A completion function for group names from /etc/group (or wherever).
+ */
 char *
 bash_groupname_completion_function (text, state)
      const char *text;
@@ -1482,8 +2524,13 @@ history_expand_line_internal (line)
      char *line;
 {
   char *new_line;
+  int old_verify;
 
+  old_verify = hist_verify;
+  hist_verify = 0;
   new_line = pre_process_line (line, 0, 0);
+  hist_verify = old_verify;
+
   return (new_line == line) ? savestring (line) : new_line;
 }
 #endif
@@ -1494,11 +2541,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 ();
 }
@@ -1510,13 +2566,13 @@ static void
 maybe_make_readline_line (new_line)
      char *new_line;
 {
-  if (strcmp (new_line, rl_line_buffer) != 0)
+  if (new_line && strcmp (new_line, rl_line_buffer) != 0)
     {
       rl_point = rl_end;
 
       rl_add_undo (UNDO_BEGIN, 0, 0, 0);
       rl_delete_text (0, rl_point);
-      rl_point = rl_end = 0;
+      rl_point = rl_end = rl_mark = 0;
       rl_insert_text (new_line);
       rl_add_undo (UNDO_END, 0, 0, 0);
     }
@@ -1615,7 +2671,7 @@ tcsh_magic_space (count, ignore)
   else
     return (1);
 }
-#endif
+#endif /* BANG_HISTORY */
 
 /* History and alias expand the line. */
 static int
@@ -1624,9 +2680,10 @@ history_and_alias_expand_line (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)
@@ -1661,10 +2718,12 @@ shell_expand_line (count, ignore)
 {
   char *new_line;
   WORD_LIST *expanded_string;
+  WORD_DESC *w;
 
-  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)
@@ -1689,9 +2748,19 @@ shell_expand_line (count, ignore)
 
       /* If there is variable expansion to perform, do that as a separate
         operation to be undone. */
+
+#if 1
+      w = alloc_word_desc ();
+      w->word = savestring (rl_line_buffer);
+      w->flags = rl_explicit_arg ? (W_NOPROCSUB|W_NOCOMSUB) : 0;
+      expanded_string = expand_word (w, rl_explicit_arg ? Q_HERE_DOCUMENT : 0);
+      dispose_word (w);
+#else
       new_line = savestring (rl_line_buffer);
       expanded_string = expand_string (new_line, 0);
       FREE (new_line);
+#endif
+
       if (expanded_string == 0)
        {
          new_line = (char *)xmalloc (1);
@@ -1724,10 +2793,6 @@ shell_expand_line (count, ignore)
     }
 }
 
-/* Define NO_FORCE_FIGNORE if you want to match filenames that would
-   otherwise be ignored if they are the only possible matches. */
-/* #define NO_FORCE_FIGNORE */
-
 /* If FIGNORE is set, then don't match files with the given suffixes when
    completing filenames.  If only one of the possibilities has an acceptable
    suffix, delete the others, else just return and let the completer
@@ -1752,10 +2817,8 @@ _ignore_completion_names (names, name_func)
 {
   char **newnames;
   int idx, nidx;
-#ifdef NO_FORCE_FIGNORE
   char **oldnames;
   int oidx;
-#endif
 
   /* 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
@@ -1763,13 +2826,13 @@ _ignore_completion_names (names, name_func)
      if there is only one completion; it is the completion itself. */
   if (names[1] == (char *)0)
     {
-#ifndef NO_FORCE_FIGNORE
-      if ((*name_func) (names[0]) == 0)
-       {
-         free (names[0]);
-         names[0] = (char *)NULL;
-       }
-#endif
+      if (force_fignore)
+       if ((*name_func) (names[0]) == 0)
+         {
+           free (names[0]);
+           names[0] = (char *)NULL;
+         }
+
       return;
     }
 
@@ -1777,23 +2840,23 @@ _ignore_completion_names (names, name_func)
      filenames.  The pointers are copied back to NAMES when done. */
   for (nidx = 1; names[nidx]; nidx++)
     ;
-  newnames = alloc_array (nidx + 1);
-#ifdef NO_FORCE_FIGNORE
-  oldnames = alloc_array (nidx - 1);
-  oidx = 0;
-#endif
+  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
-#ifndef NO_FORCE_FIGNORE
        free (names[idx]);
-#else
-       oldnames[oidx++] = names[idx];
-#endif
     }
 
   newnames[nidx] = (char *)NULL;
@@ -1801,21 +2864,24 @@ _ignore_completion_names (names, name_func)
   /* If none are acceptable then let the completer handle it. */
   if (nidx == 1)
     {
-#ifndef NO_FORCE_FIGNORE
-      free (names[0]);
-      names[0] = (char *)NULL;
-#else
-      free (oldnames);
-#endif
+      if (force_fignore)
+       {
+         free (names[0]);
+         names[0] = (char *)NULL;
+       }
+      else
+       free (oldnames);
+
       free (newnames);
       return;
     }
 
-#ifdef NO_FORCE_FIGNORE
-  while (oidx)
-    free (oldnames[--oidx]);
-  free (oldnames);
-#endif
+  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)
@@ -1879,22 +2945,19 @@ filename_completion_ignore (names)
   return 0;
 }
 
-/* Return 1 if NAME is a directory. */
+/* Return 1 if NAME is a directory.  NAME undergoes tilde expansion. */
 static int
 test_for_directory (name)
      const char *name;
 {
-  struct stat finfo;
   char *fn;
+  int r;
 
-  fn = bash_tilde_expand (name);
-  if (stat (fn, &finfo) != 0)
-    {
-      free (fn);
-      return 0;
-    }
+  fn = bash_tilde_expand (name, 0);
+  r = file_isdir (fn);
   free (fn);
-  return (S_ISDIR (finfo.st_mode));
+
+  return (r);
 }
 
 /* Remove files from NAMES, leaving directories. */
@@ -1921,36 +2984,329 @@ bash_ignore_everything (names)
   return 0;
 }
 
+/* Replace a tilde-prefix in VAL with a `~', assuming the user typed it.  VAL
+   is an expanded filename.  DIRECTORY_PART is the tilde-prefix portion
+   of the un-tilde-expanded version of VAL (what the user typed). */
+static char *
+restore_tilde (val, directory_part)
+     char *val, *directory_part;
+{
+  int l, vl, dl2, xl;
+  char *dh2, *expdir, *ret, *v;
+
+  vl = strlen (val);
+
+  /* We need to duplicate the expansions readline performs on the directory
+     portion before passing it to our completion function. */
+  dh2 = directory_part ? bash_dequote_filename (directory_part, 0) : 0;
+  bash_directory_expansion (&dh2);
+  dl2 = strlen (dh2);
+
+  expdir = bash_tilde_expand (directory_part, 0);
+  xl = strlen (expdir);
+  if (*directory_part == '~' && STREQ (directory_part, expdir))
+    {
+      /* tilde expansion failed, so what should we return? we use what the
+        user typed. */
+      v = mbschr (val, '/');
+      vl = STRLEN (v);
+      ret = (char *)xmalloc (xl + vl + 2);
+      strcpy (ret, directory_part);
+      if (v && *v)
+       strcpy (ret + xl, v);
+
+      free (dh2);
+      free (expdir);
+
+      return ret;
+    }
+  free (expdir);
+
+  /*
+     dh2 = unexpanded but dequoted tilde-prefix
+     dl2 = length of tilde-prefix
+     expdir = tilde-expanded tilde-prefix
+     xl = length of expanded tilde-prefix
+     l = length of remainder after tilde-prefix
+  */
+  l = (vl - xl) + 1;
+  if (l <= 0)
+    {
+      free (dh2);
+      return (savestring (val));               /* XXX - just punt */
+    }
+
+  ret = (char *)xmalloc (dl2 + 2 + l);
+  strcpy (ret, dh2);
+  strcpy (ret + dl2, val + xl);
+
+  free (dh2);
+  return (ret);
+}
+
+static char *
+maybe_restore_tilde (val, directory_part)
+     char *val, *directory_part;
+{
+  rl_icppfunc_t *save;
+  char *ret;
+
+  save = (dircomplete_expand == 0) ? save_directory_hook () : (rl_icppfunc_t *)0;
+  ret = restore_tilde (val, directory_part);
+  if (save)
+    restore_directory_hook (save);
+  return ret;
+}
+
+/* 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))
+    {
+      free (*dirname);
+      *dirname = d;
+    }
+  else 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;
+    }
+}
+
+/* If necessary, rewrite directory entry */
+static char *
+bash_filename_rewrite_hook (fname, fnlen)
+     char *fname;
+     int fnlen;
+{
+  char *conv;
+
+  conv = fnx_fromfs (fname, fnlen);
+  if (conv != fname)
+    conv = savestring (conv);
+  return conv;
+}
+
+/* Functions to save and restore the appropriate directory hook */
+/* This is not static so the shopt code can call it */
+void
+set_directory_hook ()
+{
+  if (dircomplete_expand)
+    {
+      rl_directory_completion_hook = bash_directory_completion_hook;
+      rl_directory_rewrite_hook = (rl_icppfunc_t *)0;
+    }
+  else
+    {
+      rl_directory_rewrite_hook = bash_directory_completion_hook;
+      rl_directory_completion_hook = (rl_icppfunc_t *)0;
+    }
+}
+
+static rl_icppfunc_t *
+save_directory_hook ()
+{
+  rl_icppfunc_t *ret;
+
+  if (dircomplete_expand)
+    {
+      ret = rl_directory_completion_hook;
+      rl_directory_completion_hook = (rl_icppfunc_t *)NULL;
+    }
+  else
+    {
+      ret = rl_directory_rewrite_hook;
+      rl_directory_rewrite_hook = (rl_icppfunc_t *)NULL;
+    }
+
+  return ret;
+}
+
+static void
+restore_directory_hook (hookf)
+     rl_icppfunc_t *hookf;
+{
+  if (dircomplete_expand)
+    rl_directory_completion_hook = hookf;
+  else
+    rl_directory_rewrite_hook = hookf;
+}
+
+/* Check whether not DIRNAME, with any trailing slash removed, exists.  If
+   SHOULD_DEQUOTE is non-zero, we dequote the directory name first. */
+static int
+directory_exists (dirname, should_dequote)
+     const char *dirname;
+     int should_dequote;
+{
+  char *new_dirname;
+  int dirlen, r;
+  struct stat sb;
+
+  /* We save the string and chop the trailing slash because stat/lstat behave
+     inconsistently if one is present. */
+  new_dirname = should_dequote ? bash_dequote_filename ((char *)dirname, rl_completion_quote_character) : savestring (dirname);
+  dirlen = STRLEN (new_dirname);
+  if (new_dirname[dirlen - 1] == '/')
+    new_dirname[dirlen - 1] = '\0';
+#if defined (HAVE_LSTAT)
+  r = lstat (new_dirname, &sb) == 0;
+#else
+  r = stat (new_dirname, &sb) == 0;
+#endif
+  free (new_dirname);
+  return (r);
+}
+  
+/* Expand a filename before the readline completion code passes it to stat(2).
+   The filename will already have had tilde expansion performed. */
+static int
+bash_filename_stat_hook (dirname)
+     char **dirname;
+{
+  char *local_dirname, *new_dirname, *t;
+  int should_expand_dirname, return_value;
+  int global_nounset;
+  WORD_LIST *wl;
+
+  local_dirname = *dirname;
+  should_expand_dirname = return_value = 0;
+  if (t = mbschr (local_dirname, '$'))
+    should_expand_dirname = '$';
+  else if (t = mbschr (local_dirname, '`'))    /* XXX */
+    should_expand_dirname = '`';
+
+  if (should_expand_dirname && directory_exists (local_dirname, 0))
+    should_expand_dirname = 0;
+  
+  if (should_expand_dirname)  
+    {
+      new_dirname = savestring (local_dirname);
+      /* no error messages, and expand_prompt_string doesn't longjmp so we don't
+        have to worry about restoring this setting. */
+      global_nounset = unbound_vars_is_error;
+      unbound_vars_is_error = 0;
+      wl = expand_prompt_string (new_dirname, 0, W_NOCOMSUB|W_NOPROCSUB|W_COMPLETE);   /* does the right thing */
+      unbound_vars_is_error = global_nounset;
+      if (wl)
+       {
+         free (new_dirname);
+         new_dirname = string_list (wl);
+         /* Tell the completer we actually expanded something and change
+            *dirname only if we expanded to something non-null -- stat
+            behaves unpredictably when passed null or empty strings */
+         if (new_dirname && *new_dirname)
+           {
+             free (local_dirname);     /* XXX */
+             local_dirname = *dirname = new_dirname;
+             return_value = STREQ (local_dirname, *dirname) == 0;
+           }
+         else
+           free (new_dirname);
+         dispose_words (wl);
+       }
+      else
+       free (new_dirname);
+    }  
+
+  /* This is very similar to the code in bash_directory_completion_hook below,
+     but without spelling correction and not worrying about whether or not
+     we change relative pathnames. */
+  if (no_symbolic_links == 0 && (local_dirname[0] != '.' || local_dirname[1]))
+    {
+      char *temp1, *temp2;
+
+      t = get_working_directory ("symlink-hook");
+      temp1 = make_absolute (local_dirname, t);
+      free (t);
+      temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
+
+      /* If we can't canonicalize, bail. */
+      if (temp2 == 0)
+       {
+         free (temp1);
+         return return_value;
+       }
+
+      free (local_dirname);
+      *dirname = temp2;
+      free (temp1);
+    }
+
+  return (return_value);
+}
+
 /* Handle symbolic link references and other directory name
-   expansions while hacking completion. */
+   expansions while hacking completion.  This should return 1 if it modifies
+   the DIRNAME argument, 0 otherwise.  It should make sure not to modify
+   DIRNAME if it returns 0. */
 static int
 bash_directory_completion_hook (dirname)
      char **dirname;
 {
   char *local_dirname, *new_dirname, *t;
-  int return_value, should_expand_dirname;
+  int return_value, should_expand_dirname, nextch, closer;
   WORD_LIST *wl;
 
-  return_value = should_expand_dirname = 0;
+  return_value = should_expand_dirname = nextch = closer = 0;
   local_dirname = *dirname;
 
-#if 0
-  should_expand_dirname = strchr (local_dirname, '$') || strchr (local_dirname, '`');
-#else
-  if (strchr (local_dirname, '$'))
-    should_expand_dirname = 1;
+  if (t = mbschr (local_dirname, '$'))
+    {
+      should_expand_dirname = '$';
+      nextch = t[1];
+      /* Deliberately does not handle the deprecated $[...] arithmetic
+        expansion syntax */
+      if (nextch == '(')
+       closer = ')';
+      else if (nextch == '{')
+       closer = '}';
+      else
+       nextch = 0;
+
+      if (closer)
+       {
+         int p;
+         char delims[2];
+
+         delims[0] = closer; delims[1] = 0;
+         p = skip_to_delim (t, 1, delims, SD_NOJMP|SD_COMPLETE);
+         if (t[p] != closer)
+           should_expand_dirname = 0;
+       }
+    }
+  else if (local_dirname[0] == '~')
+    should_expand_dirname = '~';
   else
     {
-      t = strchr (local_dirname, '`');
+      t = mbschr (local_dirname, '`');
       if (t && unclosed_pair (local_dirname, strlen (local_dirname), "`") == 0)
-       should_expand_dirname = 1;
+       should_expand_dirname = '`';
     }
-#endif
+
+  if (should_expand_dirname && directory_exists (local_dirname, 1))
+    should_expand_dirname = 0;
 
   if (should_expand_dirname)  
     {
       new_dirname = savestring (local_dirname);
-      wl = expand_string (new_dirname, 0);
+      wl = expand_prompt_string (new_dirname, 0, W_NOCOMSUB|W_NOPROCSUB|W_COMPLETE);   /* does the right thing */
       if (wl)
        {
          *dirname = string_list (wl);
@@ -1961,6 +3317,24 @@ bash_directory_completion_hook (dirname)
          free (new_dirname);
          dispose_words (wl);
          local_dirname = *dirname;
+         /* XXX - change rl_filename_quote_characters here based on
+            should_expand_dirname/nextch/closer.  This is the only place
+            custom_filename_quote_characters is modified. */
+         if (rl_filename_quote_characters && *rl_filename_quote_characters)
+           {
+             int i, j, c;
+             i = strlen (default_filename_quote_characters);
+             custom_filename_quote_characters = xrealloc (custom_filename_quote_characters, i+1);
+             for (i = j = 0; c = default_filename_quote_characters[i]; i++)
+               {
+                 if (c == should_expand_dirname || c == nextch || c == closer)
+                   continue;
+                 custom_filename_quote_characters[j++] = c;
+               }
+             custom_filename_quote_characters[j] = '\0';
+             rl_filename_quote_characters = custom_filename_quote_characters;
+             set_filename_bstab (rl_filename_quote_characters);
+           }
        }
       else
        {
@@ -1971,34 +3345,90 @@ bash_directory_completion_hook (dirname)
          return 1;
        }
     }
+  else 
+    {
+      /* Dequote the filename even if we don't expand it. */
+      new_dirname = bash_dequote_filename (local_dirname, rl_completion_quote_character);
+      return_value = STREQ (local_dirname, new_dirname) == 0;
+      free (local_dirname);
+      local_dirname = *dirname = new_dirname;
+    }
 
-  if (!no_symbolic_links && (local_dirname[0] != '.' || local_dirname[1]))
+  /* no_symbolic_links == 0 -> use (default) logical view of the file system.
+     local_dirname[0] == '.' && local_dirname[1] == '/' means files in the
+     current directory (./).
+     local_dirname[0] == '.' && local_dirname[1] == 0 means relative pathnames
+     in the current directory (e.g., lib/sh).
+     XXX - should we do spelling correction on these? */
+
+  /* This is test as it was in bash-4.2: skip relative pathnames in current
+     directory.  Change test to
+      (local_dirname[0] != '.' || (local_dirname[1] && local_dirname[1] != '/'))
+     if we want to skip paths beginning with ./ also. */
+  if (no_symbolic_links == 0 && (local_dirname[0] != '.' || local_dirname[1]))
     {
       char *temp1, *temp2;
       int len1, len2;
 
+      /* If we have a relative path
+               (local_dirname[0] != '/' && local_dirname[0] != '.')
+        that is canonical after appending it to the current directory, then
+               temp1 = temp2+'/'
+        That is,
+               strcmp (temp1, temp2) == 0
+        after adding a slash to temp2 below.  It should be safe to not
+        change those.
+      */
       t = get_working_directory ("symlink-hook");
       temp1 = make_absolute (local_dirname, t);
       free (t);
       temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
+
+      /* Try spelling correction if initial canonicalization fails.  Make
+        sure we are set to replace the directory name with the results so
+        subsequent directory checks don't fail. */
+      if (temp2 == 0 && dircomplete_spelling && dircomplete_expand)
+       {
+         temp2 = dirspell (temp1);
+         if (temp2)
+           {
+             free (temp1);
+             temp1 = temp2;
+             temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
+             return_value |= temp2 != 0;
+           }
+       }
       /* If we can't canonicalize, bail. */
       if (temp2 == 0)
        {
          free (temp1);
-         return 1;
+         return return_value;
        }
       len1 = strlen (temp1);
       if (temp1[len1 - 1] == '/')
        {
          len2 = strlen (temp2);
-         temp2 = (char *)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';
+           }
        }
+
+      /* dircomplete_expand_relpath == 0 means we want to leave relative
+        pathnames that are unchanged by canonicalization alone.
+        *local_dirname != '/' && *local_dirname != '.' == relative pathname
+        (consistent with general.c:absolute_pathname())
+        temp1 == temp2 (after appending a slash to temp2) means the pathname
+        is not changed by canonicalization as described above. */
+      if (dircomplete_expand_relpath || ((local_dirname[0] != '/' && local_dirname[0] != '.') && STREQ (temp1, temp2) == 0))
+       return_value |= STREQ (local_dirname, temp2) == 0;
       free (local_dirname);
       *dirname = temp2;
       free (temp1);
     }
+
   return (return_value);
 }
 
@@ -2016,11 +3446,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;
@@ -2033,6 +3459,8 @@ build_history_completion_array ()
   if (hlist)
     {
       for (i = 0; hlist[i]; i++)
+       ;
+      for ( --i; i >= 0; i--)
        {
          /* Separate each token, and place into an array. */
          tokens = history_tokenize (hlist[i]->line);
@@ -2040,11 +3468,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;
@@ -2053,7 +3477,8 @@ build_history_completion_array ()
        }
 
       /* Sort the complete list of tokens. */
-      qsort (history_completion_array, harry_len, sizeof (char *), (QSFUNC *)qsort_string_compare);
+      if (dabbrev_expand_active == 0)
+        qsort (history_completion_array, harry_len, sizeof (char *), (QSFUNC *)strvec_strcmp);
     }
 }
 
@@ -2069,6 +3494,8 @@ history_completion_generator (hint_text, state)
      list of strings to complete over. */
   if (state == 0)
     {
+      if (dabbrev_expand_active)       /* This is kind of messy */
+       rl_completion_suppress_append = 1;
       local_index = 0;
       build_history_completion_array ();
       text = hint_text;
@@ -2077,6 +3504,7 @@ history_completion_generator (hint_text, state)
 
   while (history_completion_array && history_completion_array[local_index])
     {
+      /* XXX - should this use completion-ignore-case? */
       if (strncmp (text, history_completion_array[local_index++], len) == 0)
        return (savestring (history_completion_array[local_index - 1]));
     }
@@ -2088,15 +3516,19 @@ dynamic_complete_history (count, key)
      int count, key;
 {
   int r;
-
   rl_compentry_func_t *orig_func;
   rl_completion_func_t *orig_attempt_func;
+  rl_compignore_func_t *orig_ignore_func;
 
   orig_func = rl_completion_entry_function;
   orig_attempt_func = rl_attempted_completion_function;
+  orig_ignore_func = rl_ignore_some_completions_function;
+
   rl_completion_entry_function = history_completion_generator;
   rl_attempted_completion_function = (rl_completion_func_t *)NULL;
+  rl_ignore_some_completions_function = filename_completion_ignore;
 
+  /* XXX - use rl_completion_mode here? */
   if (rl_last_func == dynamic_complete_history)
     r = rl_complete_internal ('?');
   else
@@ -2104,6 +3536,47 @@ dynamic_complete_history (count, key)
 
   rl_completion_entry_function = orig_func;
   rl_attempted_completion_function = orig_attempt_func;
+  rl_ignore_some_completions_function = orig_ignore_func;
+
+  return r;
+}
+
+static int
+bash_dabbrev_expand (count, key)
+     int count, key;
+{
+  int r, orig_suppress, orig_sort;
+  rl_compentry_func_t *orig_func;
+  rl_completion_func_t *orig_attempt_func;
+  rl_compignore_func_t *orig_ignore_func;
+
+  orig_func = rl_menu_completion_entry_function;
+  orig_attempt_func = rl_attempted_completion_function;
+  orig_ignore_func = rl_ignore_some_completions_function;
+  orig_suppress = rl_completion_suppress_append;
+  orig_sort = rl_sort_completion_matches;
+
+  rl_menu_completion_entry_function = history_completion_generator;
+  rl_attempted_completion_function = (rl_completion_func_t *)NULL;
+  rl_ignore_some_completions_function = filename_completion_ignore;
+  rl_filename_completion_desired = 0;
+  rl_completion_suppress_append = 1;
+  rl_sort_completion_matches = 0;
+
+  /* XXX - use rl_completion_mode here? */
+  dabbrev_expand_active = 1;
+  if (rl_last_func == bash_dabbrev_expand)
+    rl_last_func = rl_menu_complete;
+  r = rl_menu_complete (count, key);
+  dabbrev_expand_active = 0;
+
+  rl_last_func = bash_dabbrev_expand;
+  rl_menu_completion_entry_function = orig_func;
+  rl_attempted_completion_function = orig_attempt_func;
+  rl_ignore_some_completions_function = orig_ignore_func;
+  rl_completion_suppress_append = orig_suppress;
+  rl_sort_completion_matches = orig_sort;
+
   return r;
 }
 
@@ -2112,7 +3585,7 @@ static int
 bash_complete_username (ignore, ignore2)
      int ignore, ignore2;
 {
-  return bash_complete_username_internal (TAB);
+  return bash_complete_username_internal (rl_completion_mode (bash_complete_username));
 }
 
 static int
@@ -2133,7 +3606,7 @@ static int
 bash_complete_filename (ignore, ignore2)
      int ignore, ignore2;
 {
-  return bash_complete_filename_internal (TAB);
+  return bash_complete_filename_internal (rl_completion_mode (bash_complete_filename));
 }
 
 static int
@@ -2150,25 +3623,31 @@ bash_complete_filename_internal (what_to_do)
   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;
+  rl_compignore_func_t *orig_ignore_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_ignore_func = rl_ignore_some_completions_function;
   orig_rl_completer_word_break_characters = rl_completer_word_break_characters;
+
+  orig_dir_func = save_directory_hook ();
+
   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_ignore_some_completions_function = filename_completion_ignore;
   rl_completer_word_break_characters = " \t\n\"\'";
 
   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_ignore_some_completions_function = orig_ignore_func;
   rl_completer_word_break_characters = orig_rl_completer_word_break_characters;
 
+  restore_directory_hook (orig_dir_func);
+
   return r;
 }
 
@@ -2176,7 +3655,7 @@ static int
 bash_complete_hostname (ignore, ignore2)
      int ignore, ignore2;
 {
-  return bash_complete_hostname_internal (TAB);
+  return bash_complete_hostname_internal (rl_completion_mode (bash_complete_hostname));
 }
 
 static int
@@ -2190,7 +3669,7 @@ static int
 bash_complete_variable (ignore, ignore2)
      int ignore, ignore2;
 {
-  return bash_complete_variable_internal (TAB);
+  return bash_complete_variable_internal (rl_completion_mode (bash_complete_variable));
 }
 
 static int
@@ -2204,7 +3683,7 @@ static int
 bash_complete_command (ignore, ignore2)
      int ignore, ignore2;
 {
-  return bash_complete_command_internal (TAB);
+  return bash_complete_command_internal (rl_completion_mode (bash_complete_command));
 }
 
 static int
@@ -2235,6 +3714,64 @@ bash_complete_command_internal (what_to_do)
   return bash_specific_completion (what_to_do, command_word_completion_function);
 }
 
+static int
+completion_glob_pattern (string)
+     char *string;
+{
+  register int c;
+  char *send;
+  int open;
+
+  DECLARE_MBSTATE;
+
+  open = 0;
+  send = string + strlen (string);
+
+  while (c = *string++)
+    {
+      switch (c)
+       {
+       case '?':
+       case '*':
+         return (1);
+
+       case '[':
+         open++;
+         continue;
+
+       case ']':
+         if (open)
+           return (1);
+         continue;
+
+       case '+':
+       case '@':
+       case '!':
+         if (*string == '(')   /*)*/
+           return (1);
+         continue;
+
+       case '\\':
+         if (*string++ == 0)
+           return (0);           
+       }
+
+      /* Advance one fewer byte than an entire multibyte character to
+        account for the auto-increment in the loop above. */
+#ifdef HANDLE_MULTIBYTE
+      string--;
+      ADVANCE_CHAR_P (string, send - string);
+      string++;
+#else
+      ADVANCE_CHAR_P (string, send - string);
+#endif
+    }
+  return (0);
+}
+
+static char *globtext;
+static char *globorig;
+
 static char *
 glob_complete_word (text, state)
      const char *text;
@@ -2242,14 +3779,35 @@ glob_complete_word (text, state)
 {
   static char **matches = (char **)NULL;
   static int ind;
-  char *ret;
+  int glen;
+  char *ret, *ttext;
 
   if (state == 0)
     {
       rl_filename_completion_desired = 1;
-      if (matches)
-       free (matches);
-      matches = shell_glob_filename (text);
+      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;
       ind = 0;
@@ -2267,6 +3825,38 @@ bash_glob_completion_internal (what_to_do)
   return bash_specific_completion (what_to_do, glob_complete_word);
 }
 
+/* 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;
@@ -2288,23 +3878,84 @@ bash_specific_completion (what_to_do, generator)
 {
   rl_compentry_func_t *orig_func;
   rl_completion_func_t *orig_attempt_func;
+  rl_compignore_func_t *orig_ignore_func;
   int r;
 
   orig_func = rl_completion_entry_function;
   orig_attempt_func = rl_attempted_completion_function;
+  orig_ignore_func = rl_ignore_some_completions_function;
   rl_completion_entry_function = generator;
   rl_attempted_completion_function = NULL;
+  rl_ignore_some_completions_function = orig_ignore_func;
 
   r = rl_complete_internal (what_to_do);
 
   rl_completion_entry_function = orig_func;
   rl_attempted_completion_function = orig_attempt_func;
+  rl_ignore_some_completions_function = orig_ignore_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 && completion_glob_pattern (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 unquoted quote characters (single quotes, double
    quotes, and backslashes).  It allows single quotes to appear
@@ -2321,12 +3972,20 @@ bash_dequote_filename (text, quote_char)
   ret = (char *)xmalloc (l + 1);
   for (quoted = quote_char, p = text, r = ret; p && *p; p++)
     {
-      /* Allow backslash-quoted characters to pass through unscathed. */
+      /* Allow backslash-escaped characters to pass through unscathed. */
       if (*p == '\\')
        {
+         /* Backslashes are preserved within single quotes. */
+         if (quoted == '\'')
+           *r++ = *p;
+         /* Backslashes are preserved within double quotes unless the
+            character is one that is defined to be escaped */
+         else if (quoted == '"' && ((sh_syntaxtab[(unsigned char)p[1]] & CBSDQUOTE) == 0))
+           *r++ = *p;
+
          *r++ = *++p;
          if (*p == '\0')
-           break;
+           return ret;         /* XXX - was break; */
          continue;
        }
       /* Close quote. */
@@ -2372,19 +4031,37 @@ 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))
+      if (mbschr (rl_completer_word_break_characters, *s))
        *r++ = '\\';
+      /* XXX -- check for standalone tildes here and backslash-quote them */
+      if (s == text && *s == '~' && file_exists (text))
+        *r++ = '\\';
       *r++ = *s;
     }
   *r = '\0';
   return ret;
 }
 
+/* Use characters in STRING to populate the table of characters that should
+   be backslash-quoted.  The table will be used for sh_backslash_quote from
+   this file. */
+static void
+set_filename_bstab (string)
+     const char *string;
+{
+  const char *s;
+
+  memset (filename_bstab, 0, sizeof (filename_bstab));
+  for (s = string; s && *s; s++)
+    filename_bstab[*s] = 1;
+}
+
 /* Quote a filename using double quotes, single quotes, or backslashes
    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;
@@ -2403,10 +4080,6 @@ 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.  We also change
@@ -2414,7 +4087,7 @@ bash_quote_filename (s, rtype, qcp)
      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 && strchr (mtext, '\n'))
+  if (*qcp == '\0' && cs == COMPLETE_BSQUOTE && mbschr (s, '\n'))
     cs = COMPLETE_SQUOTE;
   else if (*qcp == '"')
     cs = COMPLETE_DQUOTE;
@@ -2422,17 +4095,23 @@ bash_quote_filename (s, rtype, 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 && mbschr (s, '!'))
     cs = COMPLETE_BSQUOTE;
 
   if (*qcp == '"' && history_expansion && cs == COMPLETE_DQUOTE &&
-       history_expansion_inhibited == 0 && strchr (mtext, '!'))
+       history_expansion_inhibited == 0 && mbschr (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:
@@ -2442,7 +4121,7 @@ bash_quote_filename (s, rtype, qcp)
       rtext = sh_single_quote (mtext);
       break;
     case COMPLETE_BSQUOTE:
-      rtext = sh_backslash_quote (mtext);
+      rtext = sh_backslash_quote (mtext, complete_fullquote ? 0 : filename_bstab, 0);
       break;
     }
 
@@ -2460,9 +4139,17 @@ 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 = (char *)xmalloc (rlen + 1);
-  strcpy (ret, rtext);
+  if (rtext)
+    {
+      rlen = strlen (rtext);
+      ret = (char *)xmalloc (rlen + 1);
+      strcpy (ret, rtext);
+    }
+  else
+    {
+      ret = (char *)xmalloc (rlen = 1);
+      ret[0] = '\0';
+    }
 
   /* If there are multiple matches, cut off the closing quote. */
   if (rtype == MULT_MATCH && cs != COMPLETE_BSQUOTE)
@@ -2474,55 +4161,130 @@ bash_quote_filename (s, rtype, qcp)
 /* Support for binding readline key sequences to Unix commands. */
 static Keymap cmd_xmap;
 
+#ifdef _MINIX
+static void
+#else
+static int
+#endif
+putx(c)
+     int c;
+{
+  int x;
+  x = putc (c, rl_outstream);
+#ifndef _MINIX
+  return x;
+#endif
+}
+  
 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;
+  int type;
+  register int i, r;
+  intmax_t mi;
+  sh_parser_state_t ps;
+  char *cmd, *value, *ce, old_ch;
+  SHELL_VAR *v;
+  char ibuf[INT_STRLEN_BOUND(int) + 1];
 
   /* 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)
+     because we might have already indirected into another keymap, so we
+     have to walk cmd_xmap using the entire key sequence. */
+  cmd = (char *)rl_function_of_keyseq_len (rl_executing_keyseq, rl_key_sequence_length, cmd_xmap, &type);
+    
+  if (cmd == 0 || type != ISMACR)
     {
-      /* 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;
-       }
+      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)
+  ce = rl_get_termcap ("ce");
+  if (ce)      /* clear current line */
     {
-      rl_ding ();
-      return 1;
+#if 0
+      fprintf (rl_outstream, "\r");
+      tputs (ce, 1, putx);
+#else
+      rl_clear_visible_line ();
+#endif
+      fflush (rl_outstream);
+    }
+  else
+    rl_crlf ();        /* move to a new line */
+
+  v = bind_variable ("READLINE_LINE", rl_line_buffer, 0);
+  if (v)
+    VSETATTR (v, att_exported);
+  i = rl_point;
+#if defined (HANDLE_MULTIBYTE)
+  if (MB_CUR_MAX > 1)
+    {
+      old_ch = rl_line_buffer[rl_point];
+      rl_line_buffer[rl_point] = '\0';
+      i = MB_STRLEN (rl_line_buffer);
+      rl_line_buffer[rl_point] = old_ch;
     }
+#endif
+  value = inttostr (i, ibuf, sizeof (ibuf));
+  v = bind_int_variable ("READLINE_POINT", value, 0);
+  if (v)
+    VSETATTR (v, att_exported);
+  array_needs_making = 1;
+
+  save_parser_state (&ps);
+  r = parse_and_execute (savestring (cmd), "bash_execute_unix_command", SEVAL_NOHIST|SEVAL_NOFREE);
+  restore_parser_state (&ps);
+
+  v = find_variable ("READLINE_LINE");
+  maybe_make_readline_line (v ? value_cell (v) : 0);
 
-  rl_crlf ();  /* move to a new line */
+  v = find_variable ("READLINE_POINT");
+  if (v && legal_number (value_cell (v), &mi))
+    {
+      i = mi;
+#if defined (HANDLE_MULTIBYTE)
+      if (i > 0 && MB_CUR_MAX > 1)
+       i = _rl_find_next_mbchar (rl_line_buffer, 0, i, 0);
+#endif
+      if (i != rl_point)
+       {
+         rl_point = i;
+         if (rl_point > rl_end)
+           rl_point = rl_end;
+         else if (rl_point < 0)
+           rl_point = 0;
+       }
+    }      
 
-  cmd = savestring (cmd);
-  parse_and_execute (cmd, "bash_execute_unix_command", 0);
+  check_unbind_variable ("READLINE_LINE");
+  check_unbind_variable ("READLINE_POINT");
+  array_needs_making = 1;
 
   /* and restore the readline buffer and display after command execution. */
-  rl_forced_update_display ();
+  /* If we clear the last line of the prompt above, redraw only that last
+     line.  If the command returns 124, we redraw unconditionally as in
+     previous versions. */
+  if (ce && r != 124)
+    rl_redraw_prompt_last_line ();
+  else
+    rl_forced_update_display ();
+
+  return 0;
+}
+
+int
+print_unix_command_map ()
+{
+  Keymap save;
+
+  save = rl_get_keymap ();
+  rl_set_keymap (cmd_xmap);
+  rl_macro_dumper (1);
+  rl_set_keymap (save);
   return 0;
 }
 
@@ -2545,7 +4307,7 @@ isolate_sequence (string, ind, need_dquote, startp)
   /* 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);
+      builtin_error (_("%s: first non-whitespace character is not `\"'"), string);
       return -1;
     }
 
@@ -2574,7 +4336,7 @@ isolate_sequence (string, ind, need_dquote, startp)
 
   if (delim && string[i] != delim)
     {
-      builtin_error ("%s: no closing `%c'", string, delim);
+      builtin_error (_("no closing `%c' in %s"), delim, string);
       return -1;
     }
 
@@ -2608,13 +4370,17 @@ bind_keyseq_to_unix_command (line)
     ;
   if (line[i] != ':')
     {
-      builtin_error ("%s: missing colon separator", line);
+      builtin_error (_("%s: missing colon separator"), line);
+      FREE (kseq);
       return -1;
     }
 
   i = isolate_sequence (line, i + 1, 0, &kstart);
   if (i < 0)
-    return -1;
+    {
+      FREE (kseq);
+      return -1;
+    }
 
   /* Create the value string containing the command to execute. */
   value = substring (line, kstart, i);
@@ -2624,8 +4390,9 @@ bind_keyseq_to_unix_command (line)
 
   /* and bind the key sequence in the current keymap to a function that
      understands how to execute from CMD_XMAP */
-  rl_set_key (kseq, bash_execute_unix_command, kmap);
-  
+  rl_bind_keyseq_in_map (kseq, bash_execute_unix_command, kmap);
+
+  free (kseq);  
   return 0;
 }
 
@@ -2640,10 +4407,17 @@ bash_directory_completion_matches (text)
   char *dfn;
   int qc;
 
-  qc = (text[0] == '"' || text[0] == '\'') ? text[0] : 0;
-  dfn = bash_dequote_filename ((char *)text, qc);
+  qc = rl_dispatching ? rl_completion_quote_character : 0;  
+  /* If rl_completion_found_quote != 0, rl_completion_matches will call the
+     filename dequoting function, causing the directory name to be dequoted
+     twice. */
+  if (rl_dispatching && rl_completion_found_quote == 0)
+    dfn = bash_dequote_filename ((char *)text, qc);
+  else
+    dfn = (char *)text;
   m1 = rl_completion_matches (dfn, rl_filename_completion_function);
-  free (dfn);
+  if (dfn != text)
+    free (dfn);
 
   if (m1 == 0 || m1[0] == 0)
     return m1;
@@ -2653,4 +4427,35 @@ bash_directory_completion_matches (text)
   (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);
+}
+
+/* This event hook is designed to be called after readline receives a signal
+   that interrupts read(2).  It gives reasonable responsiveness to interrupts
+   and fatal signals without executing too much code in a signal handler
+   context. */
+static int
+bash_event_hook ()
+{
+  /* If we're going to longjmp to top_level, make sure we clean up readline.
+     check_signals will call QUIT, which will eventually longjmp to top_level,
+     calling run_interrupt_trap along the way.  The check for sigalrm_seen is
+     to clean up the read builtin's state. */
+  if (terminating_signal || interrupt_state || sigalrm_seen)
+    rl_cleanup_after_signal ();
+  bashline_reset_event_hook ();
+  check_signals_and_traps ();  /* XXX */
+  return 0;
+}
+
 #endif /* READLINE */