Imported from ../bash-3.2.48.tar.gz.
[platform/upstream/bash.git] / bashline.c
index 6cfb1b0..fa4055e 100644 (file)
@@ -1,6 +1,6 @@
 /* bashline.c -- Bash's interface to the readline library. */
 
-/* Copyright (C) 1987,1991 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2006 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
 #  include <grp.h>
 #endif
 
+#if defined (HAVE_NETDB_H)
+#  include <netdb.h>
+#endif
+
 #include <stdio.h>
 #include "chartypes.h"
 #include "bashansi.h"
+#include "bashintl.h"
+
 #include "shell.h"
+#include "input.h"
 #include "builtins.h"
 #include "bashhist.h"
 #include "bashline.h"
 #  include "pcomplete.h"
 #endif
 
+/* These should agree with the defines for emacs_mode and vi_mode in
+   rldefs.h, even though that's not a public readline header file. */
+#ifndef EMACS_EDITING_MODE
+#  define NO_EDITING_MODE      -1
+#  define EMACS_EDITING_MODE    1
+#  define VI_EDITING_MODE       0
+#endif
+
 #if defined (BRACE_COMPLETION)
 extern int bash_brace_completion __P((int, int));
 #endif /* BRACE_COMPLETION */
@@ -85,6 +100,7 @@ static int history_and_alias_expand_line __P((int, int));
 #endif
 
 /* Helper functions for Readline. */
+static void bash_directory_expansion __P((char **));
 static int bash_directory_completion_hook __P((char **));
 static int filename_completion_ignore __P((char **));
 static int bash_push_line __P((void));
@@ -137,10 +153,15 @@ static char **prog_complete_matches;
 #endif
 
 /* Variables used here but defined in other files. */
-extern int current_command_line_count;
+#if defined (BANG_HISTORY)
+extern int hist_verify;
+#endif
+
+extern int current_command_line_count, last_command_exit_value;
 extern int posixly_correct, no_symbolic_links;
 extern char *current_prompt_string, *ps1_prompt;
 extern STRING_INT_ALIST word_token_alist[];
+extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin;
 
 /* SPECIFIC_COMPLETION_FUNCTIONS specifies that we have individual
    completion functions which indicate what type of completion should be
@@ -170,13 +191,18 @@ static int bash_possible_command_completions __P((int, int));
 
 static char *glob_complete_word __P((const char *, int));
 static int bash_glob_completion_internal __P((int));
+static int bash_glob_complete_word __P((int, int));
 static int bash_glob_expand_word __P((int, int));
 static int bash_glob_list_expansions __P((int, int));
+
 #endif /* SPECIFIC_COMPLETION_FUNCTIONS */
 
+static int edit_and_execute_command __P((int, int, int, char *));
 #if defined (VI_MODE)
 static 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 +215,19 @@ int perform_hostname_completion = 1;
 /* If non-zero, we don't do command completion on an empty line. */
 int no_empty_command_completion;
 
+/* Set FORCE_FIGNORE if you want to honor FIGNORE even if it ignores the
+   only possible matches.  Set to 0 if you want to match filenames if they
+   are the only possible matches, even if FIGNORE says to. */
+int force_fignore = 1;
+
 static char *bash_completer_word_break_characters = " \t\n\"'@><=;|&(:";
 static char *bash_nohostname_word_break_characters = " \t\n\"'><=;|&(:";
+/* )) */
 
 static rl_hook_func_t *old_rl_startup_hook = (rl_hook_func_t *)NULL;
 
+static int dot_in_path = 0;
+
 /* What kind of quoting is performed by bash_quote_filename:
        COMPLETE_DQUOTE = double-quoting the filename
        COMPLETE_SQUOTE = single_quoting the filename
@@ -204,6 +238,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 +250,18 @@ 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
 }
 
+/* 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 +269,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 +332,9 @@ enable_hostname_completion (on_or_off)
 void
 initialize_readline ()
 {
+  rl_command_func_t *func;
+  char kseq[2];
+
   if (bash_readline_initialized)
     return;
 
@@ -275,6 +365,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,6 +382,7 @@ 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
@@ -302,51 +394,69 @@ initialize_readline ()
     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;
@@ -358,11 +468,17 @@ initialize_readline ()
   /* 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 = "'\"";
@@ -378,8 +494,14 @@ initialize_readline ()
   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;
 }
@@ -491,13 +613,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);
@@ -613,7 +732,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 +751,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];
@@ -680,16 +799,18 @@ 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;
@@ -702,8 +823,8 @@ vi_edit_and_execute_command (count, 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
     {
@@ -716,9 +837,13 @@ vi_edit_and_execute_command (count, c)
       bash_add_history ("");
       history_lines_this_session++;
       using_history ();
-      command = savestring (VI_EDIT_COMMAND);
+      command = savestring (edit_command);
     }
-  r = parse_and_execute (command, "v", SEVAL_NOHIST);
+
+  /* Now, POSIX.1-2001 and SUSv3 say that the commands executed from the
+     temporary file should be placed into the history.  We don't do that
+     yet. */
+  r = parse_and_execute (command, (editing_mode == VI_EDITING_MODE) ? "v" : "C-xC-e", SEVAL_NOHIST);
 
   current_command_line_count = cclc;
 
@@ -734,8 +859,26 @@ vi_edit_and_execute_command (count, c)
 
   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)
@@ -766,6 +909,7 @@ posix_edit_macros (count, key)
 /* **************************************************************** */
 
 #define COMMAND_SEPARATORS ";|&{(`"
+/* )} */ 
 
 static int
 check_redir (ti)
@@ -858,7 +1002,7 @@ attempt_shell_completion (text, start, end)
      const char *text;
      int start, end;
 {
-  int in_command_position, ti, saveti, qc;
+  int in_command_position, ti, saveti, qc, dflags;
   char **matches, *command_separator_chars;
 
   command_separator_chars = COMMAND_SEPARATORS;
@@ -878,7 +1022,7 @@ 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--;
@@ -927,7 +1071,7 @@ attempt_shell_completion (text, start, end)
 #if defined (PROGRAMMABLE_COMPLETION)
   /* Attempt programmable completion. */
   if (!matches && in_command_position == 0 && prog_completion_enabled &&
-      (num_progcomps () > 0) && current_prompt_string == ps1_prompt)
+      (progcomp_size () > 0) && current_prompt_string == ps1_prompt)
     {
       int s, e, foundcs;
       char *n;
@@ -940,7 +1084,7 @@ attempt_shell_completion (text, start, end)
       s = find_cmd_start (start);
       e = find_cmd_end (end);
       n = find_cmd_name (s);
-      if (e > s)
+      if (e > s && assignment (n, 0) == 0)
        prog_complete_matches = programmable_completions (n, text, s, e, &foundcs);
       else
        foundcs = 0;
@@ -955,17 +1099,41 @@ attempt_shell_completion (text, start, end)
             sure that readline knows it. */
          if (foundcs & COPT_FILENAMES)
            rl_filename_completion_desired = 1;
+         /* If the user doesn't want a space appended, tell readline. */
+         if (foundcs & COPT_NOSPACE)
+           rl_completion_suppress_append = 1;
          /* Turn what the programmable completion code returns into what
             readline wants.  I should have made compute_lcd_of_matches
             external... */
          matches = rl_completion_matches (text, prog_complete_return);
          if ((foundcs & COPT_DEFAULT) == 0)
            rl_attempted_completion_over = 1;   /* no default */
-         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;
+
+  matches = (char **)NULL;
+
   /* New posix-style command substitution or variable name? */
   if (!matches && *text == '$')
     {
@@ -977,7 +1145,7 @@ attempt_shell_completion (text, start, end)
 
   /* 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 && *text == '~' && !xstrchr (text, '/'))
     matches = rl_completion_matches (text, rl_username_completion_function);
 
   /* Another one.  Why not?  If the word starts in '@', then look through
@@ -988,16 +1156,22 @@ attempt_shell_completion (text, start, end)
   /* 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,6 +1179,28 @@ 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;
+           }
        }
     }
 
@@ -1015,9 +1211,9 @@ attempt_shell_completion (text, start, end)
       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;
        }
     }
@@ -1038,23 +1234,31 @@ 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 *dequoted_hint = (char *)NULL;
+  static int path_index, hint_len, dequoted_len, istate, igncase;
+  static int mapping_over, local_index, searching_path, hint_is_dir;
   static SHELL_VAR **varlist = (SHELL_VAR **)NULL;
 #if defined (ALIAS)
   static alias_t **alias_list = (alias_t **)NULL;
 #endif /* ALIAS */
+  char *temp;
 
   /* We have to map over the possibilities for command words.  If we have
      no state, then make one just for that purpose. */
   if (!state)
     {
+      if (dequoted_hint && dequoted_hint != hint)
+       free (dequoted_hint);
       if (hint)
        free (hint);
 
-      mapping_over = 0;
+      mapping_over = searching_path = 0;
+      hint_is_dir = CMD_IS_DIR (hint_text);
       val = (char *)NULL;
 
+      temp = rl_variable_value ("completion-ignore-case");
+      igncase = strcmp (temp, "on") == 0;
+
       /* If this is an absolute program name, do not check it against
         aliases, reserved words, functions or builtins.  We must check
         whether or not it is unique, and, if so, whether that filename
@@ -1064,13 +1268,27 @@ command_word_completion_function (hint_text, state)
          /* Perform tilde expansion on what's passed, so we don't end up
             passing filenames with tildes directly to stat(). */
          if (*hint_text == '~')
-           hint = bash_tilde_expand (hint_text);
+           hint = bash_tilde_expand (hint_text, 0);
          else
            hint = savestring (hint_text);
-         hint_len = strlen (hint);
+
+         dequoted_hint = hint;
+         /* If readline's completer found a quote character somewhere, but
+            didn't set the quote character, there must have been a quote
+            character embedded in the filename.  It can't be at the start of
+            the filename, so we need to dequote the filename before we look
+            in the file system for it. */
+         if (rl_completion_found_quote && rl_completion_quote_character == 0)
+           {
+             dequoted_hint = bash_dequote_filename (hint, 0);
+             free (hint);
+             hint = dequoted_hint;
+           }
+         dequoted_len = hint_len = strlen (hint);
 
          if (filename_hint)
            free (filename_hint);
+
          filename_hint = savestring (hint);
 
          mapping_over = 4;
@@ -1078,11 +1296,17 @@ command_word_completion_function (hint_text, state)
          goto inner;
        }
 
-      hint = savestring (hint_text);
-      hint_len = strlen (hint);
+      dequoted_hint = hint = savestring (hint_text);
+      dequoted_len = hint_len = strlen (hint);
 
+      if (rl_completion_found_quote && rl_completion_quote_character == 0)
+       {
+         dequoted_hint = bash_dequote_filename (hint, 0);
+         dequoted_len = strlen (dequoted_hint);
+       }
+      
       path = get_string_value ("PATH");
-      path_index = 0;
+      path_index = dot_in_path = 0;
 
       /* Initialize the variables for each type of command word. */
       local_index = 0;
@@ -1168,6 +1392,16 @@ command_word_completion_function (hint_text, state)
       mapping_over++;
     }
 
+  /* If the text passed is a directory in the current directory, return it
+     as a possible match.  Executables in directories in the current
+     directory can be specified using relative pathnames and successfully
+     executed even when `.' is not in $PATH. */
+  if (hint_is_dir)
+    {
+      hint_is_dir = 0; /* only return the hint text once */
+      return (savestring (hint_text));
+    }
+    
   /* Repeatedly call filename_completion_function while we have
      members of PATH left.  Question:  should we stat each file?
      Answer: we call executable_file () on each file. */
@@ -1185,6 +1419,7 @@ command_word_completion_function (hint_text, state)
          (current_path = extract_colon_unit (path, &path_index)) == 0)
        return ((char *)NULL);
 
+      searching_path = 1;
       if (*current_path == 0)
        {
          free (current_path);
@@ -1195,17 +1430,18 @@ command_word_completion_function (hint_text, state)
        {
          char *t;
 
-         t = bash_tilde_expand (current_path);
+         t = bash_tilde_expand (current_path, 0);
          free (current_path);
          current_path = t;
        }
 
+      if (current_path[0] == '.' && current_path[1] == '\0')
+       dot_in_path = 1;
+
       if (filename_hint)
        free (filename_hint);
 
-      filename_hint = (char *)xmalloc (2 + strlen (current_path) + hint_len);
-      sprintf (filename_hint, "%s/%s", current_path, hint);
-
+      filename_hint = sh_makepath (current_path, hint, 0);
       free (current_path);
     }
 
@@ -1225,19 +1461,34 @@ command_word_completion_function (hint_text, state)
   else
     {
       int match, freetemp;
-      char *temp;
+#if 0
+      char *temp;              /* shadows previous declaration */
+#endif
 
       if (absolute_program (hint))
        {
-         match = strncmp (val, hint, hint_len) == 0;
+         if (igncase == 0)
+           match = strncmp (val, hint, hint_len) == 0;
+         else
+           match = strncasecmp (val, hint, hint_len) == 0;
+
          /* If we performed tilde expansion, restore the original
             filename. */
          if (*hint_text == '~')
            {
-             int l, tl, vl;
+             int l, tl, vl, dl;
+             char *rd;
              vl = strlen (val);
              tl = strlen (hint_text);
+#if 0
              l = vl - hint_len;        /* # of chars added */
+#else
+             rd = savestring (filename_hint);
+             bash_directory_expansion (&rd);
+             dl = strlen (rd);
+             l = vl - dl;              /* # of chars added */
+             free (rd);
+#endif
              temp = (char *)xmalloc (l + 2 + tl);
              strcpy (temp, hint_text);
              strcpy (temp + tl, val + vl - l);
@@ -1253,7 +1504,10 @@ command_word_completion_function (hint_text, state)
          if (temp)
            {
              temp++;
-             freetemp = match = strncmp (temp, hint, hint_len) == 0;
+             if (igncase == 0)
+               freetemp = match = strncmp (temp, hint, hint_len) == 0;
+             else
+               freetemp = match = strncasecmp (temp, hint, hint_len) == 0;
              if (match)
                temp = savestring (temp);
            }
@@ -1261,9 +1515,18 @@ command_word_completion_function (hint_text, state)
            freetemp = match = 0;
        }
 
+#if 0
       /* If we have found a match, and it is an executable file or a
         directory name, return it. */
       if (match && executable_or_directory (val))
+#else
+      /* If we have found a match, and it is an executable file, return it.
+        We don't return directory names when searching $PATH, since the
+        bash execution code won't find executables in directories which
+        appear in directories in $PATH when they're specified using
+        relative pathnames. */
+      if (match && (searching_path ? executable_file (val) : executable_or_directory (val)))
+#endif
        {
          free (val);
          val = "";             /* So it won't be NULL. */
@@ -1300,12 +1563,45 @@ 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])
@@ -1359,7 +1655,7 @@ variable_completion_function (text, state)
 
       namelen = strlen (varname);
       if (varlist)
-       free_array (varlist);
+       strvec_dispose (varlist);
 
       varlist = all_variables_matching_prefix (varname);
       varlist_index = 0;
@@ -1432,6 +1728,68 @@ 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, firstc;
+  char *value;
+  char **alist, *aentry;
+  int afound;
+
+  if (state == 0)
+    {
+      FREE (sname);
+      firstc = *text;
+
+      sname = savestring (text);
+      snamelen = strlen (sname);
+      setservent (0);
+    }
+
+  while (srvent = getservent ())
+    {
+      afound = 0;
+      if (snamelen == 0 || (STREQN (sname, srvent->s_name, snamelen)))
+       break;
+      /* Not primary, check aliases */
+      for (alist = srvent->s_aliases; *alist; alist++)
+       {
+         aentry = *alist;
+         if (STREQN (sname, aentry, snamelen))
+           {
+             afound = 1;
+             break;
+           }
+       }
+
+      if (afound)
+       break;
+    }
+
+  if (srvent == 0)
+    {
+      endservent ();
+      return ((char *)NULL);
+    }
+
+  value = afound ? savestring (aentry) : savestring (srvent->s_name);
+  return value;
+#endif
+}
+
+/*
+ * A completion function for group names from /etc/group (or wherever).
+ */
 char *
 bash_groupname_completion_function (text, state)
      const char *text;
@@ -1482,8 +1840,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 +1857,20 @@ static void
 cleanup_expansion_error ()
 {
   char *to_free;
+#if defined (BANG_HISTORY)
+  int old_verify;
+
+  old_verify = hist_verify;
+  hist_verify = 0;
+#endif
 
   fprintf (rl_outstream, "\r\n");
   to_free = pre_process_line (rl_line_buffer, 1, 0);
+#if defined (BANG_HISTORY)
+  hist_verify = old_verify;
+#endif
   if (to_free != rl_line_buffer)
-    free (to_free);
+    FREE (to_free);
   putc ('\r', rl_outstream);
   rl_forced_update_display ();
 }
@@ -1516,7 +1888,7 @@ maybe_make_readline_line (new_line)
 
       rl_add_undo (UNDO_BEGIN, 0, 0, 0);
       rl_delete_text (0, rl_point);
-      rl_point = rl_end = 0;
+      rl_point = rl_end = rl_mark = 0;
       rl_insert_text (new_line);
       rl_add_undo (UNDO_END, 0, 0, 0);
     }
@@ -1615,7 +1987,7 @@ tcsh_magic_space (count, ignore)
   else
     return (1);
 }
-#endif
+#endif /* BANG_HISTORY */
 
 /* History and alias expand the line. */
 static int
@@ -1624,9 +1996,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)
@@ -1662,9 +2035,10 @@ shell_expand_line (count, ignore)
   char *new_line;
   WORD_LIST *expanded_string;
 
-  new_line = pre_process_line (rl_line_buffer, 0, 0);
-  if (new_line == rl_line_buffer)
-    new_line = savestring (new_line);
+  new_line = 0;
+#if defined (BANG_HISTORY)
+  new_line = history_expand_line_internal (rl_line_buffer);
+#endif
 
 #if defined (ALIAS)
   if (new_line)
@@ -1724,10 +2098,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 +2122,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 +2131,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 +2145,23 @@ _ignore_completion_names (names, name_func)
      filenames.  The pointers are copied back to NAMES when done. */
   for (nidx = 1; names[nidx]; nidx++)
     ;
-  newnames = 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 +2169,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)
@@ -1887,7 +2258,7 @@ test_for_directory (name)
   struct stat finfo;
   char *fn;
 
-  fn = bash_tilde_expand (name);
+  fn = bash_tilde_expand (name, 0);
   if (stat (fn, &finfo) != 0)
     {
       free (fn);
@@ -1921,6 +2292,34 @@ bash_ignore_everything (names)
   return 0;
 }
 
+/* Simulate the expansions that will be performed by
+   rl_filename_completion_function.  This must be called with the address of
+   a pointer to malloc'd memory. */
+static void
+bash_directory_expansion (dirname)
+     char **dirname;
+{
+  char *d, *nd;
+
+  d = savestring (*dirname);
+
+  if (rl_directory_rewrite_hook)
+    (*rl_directory_rewrite_hook) (&d);
+
+  if (rl_directory_completion_hook && (*rl_directory_completion_hook) (&d))
+    {
+      free (*dirname);
+      *dirname = d;
+    }
+  else if (rl_completion_found_quote)
+    {
+      nd = bash_dequote_filename (d, rl_completion_quote_character);
+      free (*dirname);
+      free (d);
+      *dirname = nd;
+    }
+}
+  
 /* Handle symbolic link references and other directory name
    expansions while hacking completion. */
 static int
@@ -1930,27 +2329,35 @@ bash_directory_completion_hook (dirname)
   char *local_dirname, *new_dirname, *t;
   int return_value, should_expand_dirname;
   WORD_LIST *wl;
+  struct stat sb;
 
   return_value = should_expand_dirname = 0;
   local_dirname = *dirname;
 
 #if 0
-  should_expand_dirname = strchr (local_dirname, '$') || strchr (local_dirname, '`');
+  should_expand_dirname = xstrchr (local_dirname, '$') || xstrchr (local_dirname, '`');
 #else
-  if (strchr (local_dirname, '$'))
+  if (xstrchr (local_dirname, '$'))
     should_expand_dirname = 1;
   else
     {
-      t = strchr (local_dirname, '`');
+      t = xstrchr (local_dirname, '`');
       if (t && unclosed_pair (local_dirname, strlen (local_dirname), "`") == 0)
        should_expand_dirname = 1;
     }
 #endif
 
+#if defined (HAVE_LSTAT)
+  if (should_expand_dirname && lstat (local_dirname, &sb) == 0)
+#else
+  if (should_expand_dirname && stat (local_dirname, &sb) == 0)
+#endif
+    should_expand_dirname = 0;
+
   if (should_expand_dirname)  
     {
       new_dirname = savestring (local_dirname);
-      wl = expand_string (new_dirname, 0);
+      wl = expand_prompt_string (new_dirname, 0, W_NOCOMSUB);  /* does the right thing */
       if (wl)
        {
          *dirname = string_list (wl);
@@ -1971,6 +2378,13 @@ 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);
+      free (local_dirname);
+      local_dirname = *dirname = new_dirname;
+    }
 
   if (!no_symbolic_links && (local_dirname[0] != '.' || local_dirname[1]))
     {
@@ -1991,9 +2405,12 @@ bash_directory_completion_hook (dirname)
       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';
+           }
        }
       free (local_dirname);
       *dirname = temp2;
@@ -2016,11 +2433,7 @@ build_history_completion_array ()
   /* First, clear out the current dynamic history completion list. */
   if (harry_size)
     {
-      for (i = 0; history_completion_array[i]; i++)
-       free (history_completion_array[i]);
-
-      free (history_completion_array);
-
+      strvec_dispose (history_completion_array);
       history_completion_array = (char **)NULL;
       harry_size = 0;
       harry_len = 0;
@@ -2040,11 +2453,7 @@ build_history_completion_array ()
          for (j = 0; tokens && tokens[j]; j++)
            {
              if (harry_len + 2 > harry_size)
-               {
-                 harry_size += 10;
-                 history_completion_array = (char **)xrealloc
-                   (history_completion_array, harry_size * sizeof (char *));
-               }
+               history_completion_array = strvec_resize (history_completion_array, harry_size += 10);
 
              history_completion_array[harry_len++] = tokens[j];
              history_completion_array[harry_len] = (char *)NULL;
@@ -2053,7 +2462,7 @@ build_history_completion_array ()
        }
 
       /* Sort the complete list of tokens. */
-      qsort (history_completion_array, harry_len, sizeof (char *), (QSFUNC *)qsort_string_compare);
+      qsort (history_completion_array, harry_len, sizeof (char *), (QSFUNC *)strvec_strcmp);
     }
 }
 
@@ -2097,6 +2506,7 @@ dynamic_complete_history (count, key)
   rl_completion_entry_function = history_completion_generator;
   rl_attempted_completion_function = (rl_completion_func_t *)NULL;
 
+  /* XXX - use rl_completion_mode here? */
   if (rl_last_func == dynamic_complete_history)
     r = rl_complete_internal ('?');
   else
@@ -2112,7 +2522,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 +2543,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,7 +2560,7 @@ 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;
+  /*const*/ char *orig_rl_completer_word_break_characters;
   int r;
 
   orig_func = rl_completion_entry_function;
@@ -2176,7 +2586,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 +2600,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 +2614,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 +2645,9 @@ bash_complete_command_internal (what_to_do)
   return bash_specific_completion (what_to_do, command_word_completion_function);
 }
 
+static char *globtext;
+static char *globorig;
+
 static char *
 glob_complete_word (text, state)
      const char *text;
@@ -2242,14 +2655,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 +2701,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;
@@ -2305,6 +2771,63 @@ bash_specific_completion (what_to_do, generator)
 
 #endif /* SPECIFIC_COMPLETION_FUNCTIONS */
 
+#if defined (VI_MODE)
+/* Completion, from vi mode's point of view.  This is a modified version of
+   rl_vi_complete which uses the bash globbing code to implement what POSIX
+   specifies, which is to append a `*' and attempt filename generation (which
+   has the side effect of expanding any globbing characters in the word). */
+static int
+bash_vi_complete (count, key)
+     int count, key;
+{
+#if defined (SPECIFIC_COMPLETION_FUNCTIONS)
+  int p, r;
+  char *t;
+
+  if ((rl_point < rl_end) && (!whitespace (rl_line_buffer[rl_point])))
+    {
+      if (!whitespace (rl_line_buffer[rl_point + 1]))
+       rl_vi_end_word (1, 'E');
+      rl_point++;
+    }
+
+  /* Find boundaries of current word, according to vi definition of a
+     `bigword'. */
+  t = 0;
+  if (rl_point > 0)
+    {
+      p = rl_point;
+      rl_vi_bWord (1, 'B');
+      r = rl_point;
+      rl_point = p;
+      p = r;
+
+      t = substring (rl_line_buffer, p, rl_point);
+    }      
+
+  if (t && glob_pattern_p (t) == 0)
+    rl_explicit_arg = 1;       /* XXX - force glob_complete_word to append `*' */
+  FREE (t);
+
+  if (key == '*')      /* Expansion and replacement. */
+    r = bash_glob_expand_word (count, key);
+  else if (key == '=') /* List possible completions. */
+    r = bash_glob_list_expansions (count, key);
+  else if (key == '\\')        /* Standard completion */
+    r = bash_glob_complete_word (count, key);
+  else
+    r = rl_complete (0, key);
+
+  if (key == '*' || key == '\\')
+    rl_vi_start_inserting (key, 1, 1);
+
+  return (r);
+#else
+  return rl_vi_complete (count, key);
+#endif /* !SPECIFIC_COMPLETION_FUNCTIONS */
+}
+#endif /* VI_MODE */
+
 /* Filename quoting for completion. */
 /* A function to strip unquoted quote characters (single quotes, double
    quotes, and backslashes).  It allows single quotes to appear
@@ -2372,7 +2895,7 @@ 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 (xstrchr (rl_completer_word_break_characters, *s))
        *r++ = '\\';
       *r++ = *s;
     }
@@ -2384,7 +2907,8 @@ quote_word_break_chars (text)
    depending on the value of completion_quoting_style.  If we're
    completing using backslashes, we need to quote some additional
    characters (those that readline treats as word breaks), so we call
-   quote_word_break_chars on the result. */
+   quote_word_break_chars on the result.  This returns newly-allocated
+   memory. */
 static char *
 bash_quote_filename (s, rtype, qcp)
      char *s;
@@ -2403,10 +2927,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 +2934,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 && xstrchr (s, '\n'))
     cs = COMPLETE_SQUOTE;
   else if (*qcp == '"')
     cs = COMPLETE_DQUOTE;
@@ -2422,17 +2942,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 && xstrchr (s, '!'))
     cs = COMPLETE_BSQUOTE;
 
   if (*qcp == '"' && history_expansion && cs == COMPLETE_DQUOTE &&
-       history_expansion_inhibited == 0 && strchr (mtext, '!'))
+       history_expansion_inhibited == 0 && xstrchr (s, '!'))
     {
       cs = COMPLETE_BSQUOTE;
       *qcp = '\0';
     }
 #endif
 
+  /* Don't tilde-expand backslash-quoted filenames, since only single and
+     double quotes inhibit tilde expansion. */
+  mtext = s;
+  if (mtext[0] == '~' && rtype == SINGLE_MATCH && cs != COMPLETE_BSQUOTE)
+    mtext = bash_tilde_expand (s, 0);
+
   switch (cs)
     {
     case COMPLETE_DQUOTE:
@@ -2483,6 +3009,7 @@ bash_execute_unix_command (count, key)
   Keymap xkmap;                /* unix command executing keymap */
   register int i;
   char *cmd;
+  sh_parser_state_t ps;
 
   /* First, we need to find the right command to execute.  This is tricky,
      because we might have already indirected into another keymap. */
@@ -2500,7 +3027,7 @@ bash_execute_unix_command (count, key)
       else
        {
          rl_crlf ();
-         internal_error ("bash_execute_unix_command: cannot find keymap for command");
+         internal_error (_("bash_execute_unix_command: cannot find keymap for command"));
          rl_forced_update_display ();
          return 1;
        }
@@ -2518,8 +3045,12 @@ bash_execute_unix_command (count, key)
 
   rl_crlf ();  /* move to a new line */
 
+  save_parser_state (&ps);
+
   cmd = savestring (cmd);
-  parse_and_execute (cmd, "bash_execute_unix_command", 0);
+  parse_and_execute (cmd, "bash_execute_unix_command", SEVAL_NOHIST);
+
+  restore_parser_state (&ps);
 
   /* and restore the readline buffer and display after command execution. */
   rl_forced_update_display ();
@@ -2545,7 +3076,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 +3105,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,7 +3139,7 @@ bind_keyseq_to_unix_command (line)
     ;
   if (line[i] != ':')
     {
-      builtin_error ("%s: missing colon separator", line);
+      builtin_error (_("%s: missing colon separator"), line);
       return -1;
     }
 
@@ -2624,7 +3155,7 @@ 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);
   
   return 0;
 }
@@ -2640,7 +3171,7 @@ bash_directory_completion_matches (text)
   char *dfn;
   int qc;
 
-  qc = (text[0] == '"' || text[0] == '\'') ? text[0] : 0;
+  qc = rl_dispatching ? rl_completion_quote_character : 0;  
   dfn = bash_dequote_filename ((char *)text, qc);
   m1 = rl_completion_matches (dfn, rl_filename_completion_function);
   free (dfn);
@@ -2653,4 +3184,16 @@ 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);
+}
 #endif /* READLINE */