Imported from ../bash-2.0.tar.gz.
[platform/upstream/bash.git] / bashline.c
index a924b05..7d2e5d7 100644 (file)
    along with Bash; see the file COPYING.  If not, write to the Free
    Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
 
+#include "config.h"
+
+#if defined (READLINE)
+
 #include "bashtypes.h"
 #include "posixstat.h"
 
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
 #include <stdio.h>
 #include "bashansi.h"
-#include <readline/rlconf.h>
-#include <readline/readline.h>
-#include <readline/history.h>
 #include "shell.h"
 #include "builtins.h"
-#include "builtins/common.h"
 #include "bashhist.h"
+#include "bashline.h"
 #include "execute_cmd.h"
+#include "pathexp.h"
+#include "builtins/common.h"
+#include <readline/rlconf.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#include <glob/glob.h>
 
 #if defined (ALIAS)
 #  include "alias.h"
 #endif
 
-#if defined (BRACE_EXPANSION)
-#  define BRACE_COMPLETION
-#endif /* BRACE_EXPANSION */
-
 #if defined (BRACE_COMPLETION)
 extern void bash_brace_completion ();
 #endif /* BRACE_COMPLETION */
@@ -59,14 +67,20 @@ static char *variable_completion_function ();
 static char *hostname_completion_function ();
 static char *command_word_completion_function ();
 static char *command_subst_completion_function ();
+static void dynamic_complete_history ();
+
+static char *glob_complete_word ();
+static void bash_glob_expand_word ();
+static void bash_glob_list_expansions ();
 
 static void snarf_hosts_from_file (), add_host_name ();
-static void sort_hostname_list ();
 
-#define DYNAMIC_HISTORY_COMPLETION
-#if defined (DYNAMIC_HISTORY_COMPLETION)
-static void dynamic_complete_history ();
-#endif /* DYNAMIC_HISTORY_COMPLETION */
+static char *bash_dequote_filename ();
+static char *bash_quote_filename ();
+
+#if defined (ALIAS)
+static int posix_edit_macros ();
+#endif
 
 /* Variables used here but defined in other files. */
 extern int posixly_correct, no_symbolic_links;
@@ -76,6 +90,10 @@ extern STRING_INT_ALIST word_token_alist[];
 extern Function *rl_last_func;
 extern int rl_filename_completion_desired;
 
+/* Helper functions from subst.c */
+extern int char_is_quoted ();
+extern int unclosed_pair ();
+
 /* SPECIFIC_COMPLETION_FUNCTIONS specifies that we have individual
    completion functions which indicate what type of completion should be
    done (at or before point) that can be bound to key sequences with
@@ -83,48 +101,76 @@ extern int rl_filename_completion_desired;
 #define SPECIFIC_COMPLETION_FUNCTIONS
 
 #if defined (SPECIFIC_COMPLETION_FUNCTIONS)
-static void
-  bash_specific_completion (),
-  bash_complete_filename (), bash_possible_filename_completions (),
-  bash_complete_filename_internal (),
-  bash_complete_username (), bash_possible_username_completions (),
-  bash_complete_username_internal (),
-  bash_complete_hostname (), bash_possible_hostname_completions (),
-  bash_complete_hostname_internal (),
-  bash_complete_variable (), bash_possible_variable_completions (),
-  bash_complete_variable_internal (),
-  bash_complete_command (), bash_possible_command_completions (),
-  bash_complete_command_internal ();
+static void bash_specific_completion ();
+static void bash_complete_filename (), bash_possible_filename_completions ();
+static void bash_complete_filename_internal ();
+static void bash_complete_username (), bash_possible_username_completions ();
+static void bash_complete_username_internal ();
+static void bash_complete_hostname (), bash_possible_hostname_completions ();
+static void bash_complete_hostname_internal ();
+static void bash_complete_variable (), bash_possible_variable_completions ();
+static void bash_complete_variable_internal ();
+static void bash_complete_command (), bash_possible_command_completions ();
+static void bash_complete_command_internal ();
 #endif /* SPECIFIC_COMPLETION_FUNCTIONS */
 
-/* Non-zero once initalize_readline () has been called. */
-int bash_readline_initialized = 0;
-
 #if defined (VI_MODE)
 static void vi_edit_and_execute_command ();
-extern char *rl_vi_comment_begin;
 #endif
 
+/* Non-zero once initalize_readline () has been called. */
+int bash_readline_initialized = 0;
+
+/* If non-zero, we do hostname completion, breaking words at `@' and
+   trying to complete the stuff after the `@' from our own internal
+   host list. */
+int perform_hostname_completion = 1;
+
+static char *bash_completer_word_break_characters = " \t\n\"'@><=;|&(:";
+static char *bash_nohostname_word_break_characters = " \t\n\"'><=;|&(:";
+
 static Function *old_rl_startup_hook = (Function *) NULL;
 
+/* What kind of quoting is performed by bash_quote_filename:
+       COMPLETE_DQUOTE = double-quoting the filename
+       COMPLETE_SQUOTE = single_quoting the filename
+       COMPLETE_BSQUOTE = backslash-quoting special chars in the filename
+*/
+#define COMPLETE_DQUOTE  1
+#define COMPLETE_SQUOTE  2
+#define COMPLETE_BSQUOTE 3
+static int completion_quoting_style = COMPLETE_BSQUOTE;
+
 /* Change the readline VI-mode keymaps into or out of Posix.2 compliance.
    Called when the shell is put into or out of `posix' mode. */
 void
 posix_readline_initialize (on_or_off)
      int on_or_off;
 {
+  if (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);
+#endif
+}
+
+void
+enable_hostname_completion (on_or_off)
+     int on_or_off;
+{
   if (on_or_off)
     {
-      rl_bind_key_in_map (CTRL('I'), rl_insert, vi_insertion_keymap);
-      if (rl_vi_comment_begin)
-       free (rl_vi_comment_begin);
-      rl_vi_comment_begin = savestring ("#");
+      perform_hostname_completion = 1;
+      rl_special_prefixes = "$@";
+      rl_completer_word_break_characters = bash_completer_word_break_characters;
     }
   else
-    rl_bind_key_in_map (CTRL('I'), rl_complete, vi_insertion_keymap);
-#endif
-} 
+    {
+      perform_hostname_completion = 0;
+      rl_special_prefixes = "$";
+      rl_completer_word_break_characters = bash_nohostname_word_break_characters;
+    }
+}
 
 /* Called once from parse.y if we are going to use readline. */
 void
@@ -136,15 +182,13 @@ initialize_readline ()
   rl_terminal_name = get_string_value ("TERM");
   rl_instream = stdin;
   rl_outstream = stderr;
-  rl_special_prefixes = "$@";
 
   /* Allow conditional parsing of the ~/.inputrc file. */
   rl_readline_name = "Bash";
 
   /* Bind up our special shell functions. */
   rl_add_defun ("shell-expand-line", (Function *)shell_expand_line, -1);
-  rl_bind_key_in_map
-    (CTRL('E'), (Function *)shell_expand_line, emacs_meta_keymap);
+  rl_bind_key_in_map (CTRL('E'), (Function *)shell_expand_line, emacs_meta_keymap);
 
   /* Bind up our special shell functions. */
   rl_add_defun ("history-expand-line", (Function *)history_expand_line, -1);
@@ -158,7 +202,6 @@ initialize_readline ()
 
   rl_add_defun
     ("display-shell-version", (Function *)display_shell_version, -1);
-
   rl_bind_key_in_map
     (CTRL ('V'), (Function *)display_shell_version, emacs_ctlx_keymap);
 
@@ -170,7 +213,7 @@ initialize_readline ()
 #if defined (VI_MODE)
   rl_unbind_key_in_map (CTRL('E'), vi_movement_keymap);
 #endif
-  
+
 #if defined (BRACE_COMPLETION)
   rl_add_defun ("complete-into-braces", bash_brace_completion, -1);
   rl_bind_key_in_map ('{', bash_brace_completion, emacs_meta_keymap);
@@ -181,43 +224,41 @@ initialize_readline ()
   rl_bind_key_in_map ('/', bash_complete_filename, emacs_meta_keymap);
   rl_add_defun ("possible-filename-completions",
                bash_possible_filename_completions, -1);
-  rl_bind_key_in_map ('/', bash_possible_filename_completions,
-                     emacs_ctlx_keymap);
+  rl_bind_key_in_map ('/', bash_possible_filename_completions, emacs_ctlx_keymap);
 
   rl_add_defun ("complete-username", bash_complete_username, -1);
   rl_bind_key_in_map ('~', bash_complete_username, emacs_meta_keymap);
   rl_add_defun ("possible-username-completions",
                bash_possible_username_completions, -1);
-  rl_bind_key_in_map ('~', bash_possible_username_completions,
-                     emacs_ctlx_keymap);
+  rl_bind_key_in_map ('~', bash_possible_username_completions, emacs_ctlx_keymap);
 
   rl_add_defun ("complete-hostname", bash_complete_hostname, -1);
   rl_bind_key_in_map ('@', bash_complete_hostname, emacs_meta_keymap);
   rl_add_defun ("possible-hostname-completions",
                bash_possible_hostname_completions, -1);
-  rl_bind_key_in_map ('@', bash_possible_hostname_completions,
-                     emacs_ctlx_keymap);
+  rl_bind_key_in_map ('@', bash_possible_hostname_completions, emacs_ctlx_keymap);
 
   rl_add_defun ("complete-variable", bash_complete_variable, -1);
   rl_bind_key_in_map ('$', bash_complete_variable, emacs_meta_keymap);
   rl_add_defun ("possible-variable-completions",
                bash_possible_variable_completions, -1);
-  rl_bind_key_in_map ('$', bash_possible_variable_completions,
-                     emacs_ctlx_keymap);
+  rl_bind_key_in_map ('$', bash_possible_variable_completions, emacs_ctlx_keymap);
 
   rl_add_defun ("complete-command", bash_complete_command, -1);
   rl_bind_key_in_map ('!', bash_complete_command, emacs_meta_keymap);
   rl_add_defun ("possible-command-completions",
                bash_possible_command_completions, -1);
-  rl_bind_key_in_map ('!', bash_possible_command_completions,
-                     emacs_ctlx_keymap);
+  rl_bind_key_in_map ('!', bash_possible_command_completions, emacs_ctlx_keymap);
+
+  rl_add_defun ("glob-expand-word", bash_glob_expand_word, -1);
+  rl_add_defun ("glob-list-expansions", bash_glob_list_expansions, -1);
+  rl_bind_key_in_map ('*', bash_glob_expand_word, emacs_ctlx_keymap);
+  rl_bind_key_in_map ('g', bash_glob_list_expansions, emacs_ctlx_keymap);
 
 #endif /* SPECIFIC_COMPLETION_FUNCTIONS */
 
-#if defined (DYNAMIC_HISTORY_COMPLETION)
   rl_add_defun ("dynamic-complete-history", dynamic_complete_history, -1);
   rl_bind_key_in_map (TAB, dynamic_complete_history, emacs_meta_keymap);
-#endif /* DYNAMIC_HISTORY_COMPLETION */
 
   /* Tell the completer that we want a crack first. */
   rl_attempted_completion_function = (CPPFunction *)attempt_shell_completion;
@@ -231,12 +272,23 @@ initialize_readline ()
 
 #if defined (VI_MODE)
   rl_bind_key_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);
+#  endif
 #endif
 
   rl_completer_quote_characters = "'\"";
-  /* Need to modify this from the default; `$', `{', `\', and ``' are not
-     word break characters. */
-  rl_completer_word_break_characters = " \t\n\"'@><=;|&("; /**/
+
+  /* This sets rl_completer_word_break_characters and rl_special_prefixes
+     to the appropriate values, depending on whether or not hostname
+     completion is enabled. */
+  enable_hostname_completion (perform_hostname_completion);
+
+  /* characters that need to be quoted when appearing in filenames. */
+  rl_filename_quote_characters = " \t\n\\\"'@<>=;|&()#$`?*[!";
+  rl_filename_quoting_function = bash_quote_filename;
+  rl_filename_dequoting_function = bash_dequote_filename;
+  rl_char_is_quoted_p = char_is_quoted;
 
   if (posixly_correct)
     posix_readline_initialize (1);
@@ -282,8 +334,7 @@ int
 bash_re_edit (line)
      char *line;
 {
-  if (push_to_readline)
-    free (push_to_readline);
+  FREE (push_to_readline);
 
   push_to_readline = savestring (line);
   old_rl_startup_hook = rl_startup_hook;
@@ -297,7 +348,7 @@ display_shell_version (count, c)
      int count, c;
 {
   crlf ();
-  show_shell_version ();
+  show_shell_version (0);
   putc ('\r', rl_outstream);
   fflush (rl_outstream);
   rl_on_new_line ();
@@ -312,25 +363,19 @@ display_shell_version (count, c)
 
 /* If the user requests hostname completion, then simply build a list
    of hosts, and complete from that forever more. */
-#if !defined (ETCHOSTS)
-#define ETCHOSTS "/etc/hosts"
-#endif
 
 /* The kept list of hostnames. */
 static char **hostname_list = (char **)NULL;
 
 /* The physical size of the above list. */
-static int hostname_list_size = 0;
+static int hostname_list_size;
 
-/* The length of the above list. */
-static int hostname_list_length = 0;
+/* The number of hostnames in the above list. */
+static int hostname_list_length;
 
 /* Whether or not HOSTNAME_LIST has been initialized. */
 int hostname_list_initialized = 0;
 
-/* Non-zero means that HOSTNAME_LIST needs to be sorted. */
-static int hostname_list_needs_sorting = 0;
-
 /* Initialize the hostname completion table. */
 static void
 initialize_hostname_list ()
@@ -338,13 +383,12 @@ initialize_hostname_list ()
   char *temp;
 
   temp = get_string_value ("HOSTFILE");
-  if (!temp)
+  if (temp == 0)
     temp = get_string_value ("hostname_completion_file");
-  if (!temp)
-    temp = ETCHOSTS;
+  if (temp == 0)
+    temp = DEFAULT_HOSTS_FILE;
 
   snarf_hosts_from_file (temp);
-  sort_hostname_list ();
 
   if (hostname_list)
     hostname_list_initialized++;
@@ -355,25 +399,17 @@ static void
 add_host_name (name)
      char *name;
 {
+  long size;
+
   if (hostname_list_length + 2 > hostname_list_size)
     {
-      hostname_list = (char **)
-       xrealloc (hostname_list,
-                 (1 + (hostname_list_size += 100)) * sizeof (char *));
+      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[hostname_list_length] = savestring (name);
-  hostname_list[++hostname_list_length] = (char *)NULL;
-  hostname_list_needs_sorting++;
-}
-
-/* After you have added some names, you should sort the list of names. */
-static void
-sort_hostname_list ()
-{
-  if (hostname_list_needs_sorting && hostname_list)
-    sort_char_array (hostname_list);
-  hostname_list_needs_sorting = 0;
+  hostname_list[hostname_list_length++] = savestring (name);
+  hostname_list[hostname_list_length] = (char *)NULL;
 }
 
 #define cr_whitespace(c) ((c) == '\r' || (c) == '\n' || whitespace(c))
@@ -382,55 +418,59 @@ static void
 snarf_hosts_from_file (filename)
      char *filename;
 {
-  FILE *file = fopen (filename, "r");
+  FILE *file;
   char *temp, buffer[256], name[256];
   register int i, start;
 
-  if (!file)
+  file = fopen (filename, "r");
+  if (file == 0)
     return;
 
   while (temp = fgets (buffer, 255, file))
     {
       /* Skip to first character. */
-      for (i = 0; buffer[i] && cr_whitespace (buffer[i]); i++);
+      for (i = 0; buffer[i] && cr_whitespace (buffer[i]); i++)
+       ;
 
-      /* If comment, ignore. */
-      if (buffer[i] == '#')
+      /* If comment or blank line, ignore. */
+      if (buffer[i] == '\0' || buffer[i] == '#')
        continue;
 
       /* If `preprocessor' directive, do the include. */
-      if (strncmp (&buffer[i], "$include ", 9) == 0)
+      if (strncmp (buffer + i, "$include ", 9) == 0)
        {
-         char *includefile = &buffer[i + 9];
-         char *t;
+         char *incfile, *t;
 
          /* Find start of filename. */
-         while (*includefile && whitespace (*includefile))
-           includefile++;
-
-         t = includefile;
+         for (incfile = buffer + i + 9; *incfile && whitespace (*incfile); incfile++)
+           ;
 
          /* Find end of filename. */
-         while (*t && !cr_whitespace (*t))
-           t++;
+         for (t = incfile; *t && cr_whitespace (*t) == 0; t++)
+           ;
 
          *t = '\0';
 
-         snarf_hosts_from_file (includefile);
+         snarf_hosts_from_file (incfile);
          continue;
        }
 
-      /* Skip internet address. */
-      for (; buffer[i] && !cr_whitespace (buffer[i]); i++);
+      /* Skip internet address if present. */
+      if (digit (buffer[i]))
+       for (; buffer[i] && cr_whitespace (buffer[i]) == 0; i++);
 
       /* Gobble up names.  Each name is separated with whitespace. */
-      while (buffer[i] && buffer[i] != '#')
+      while (buffer[i])
        {
-         for (; i && cr_whitespace (buffer[i]); i++);
-         if (buffer[i] ==  '#')
-           continue;
-         for (start = i; buffer[i] && !cr_whitespace (buffer[i]); i++);
-         if ((i - start) == 0)
+         for (; cr_whitespace (buffer[i]); i++)
+           ;
+         if (buffer[i] == '\0' || buffer[i] ==  '#')
+           break;
+
+         /* Isolate the current word. */
+         for (start = i; buffer[i] && cr_whitespace (buffer[i]) == 0; i++)
+           ;
+         if (i == start)
            continue;
          strncpy (name, buffer + start, i - start);
          name[i - start] = '\0';
@@ -447,28 +487,18 @@ static char **
 hostnames_matching (text)
      char *text;
 {
-  register int i, len = strlen (text);
-  register int begin, end;
-  int last_search = -1;
-  char **result = (char **)NULL;
+  register int i, len, nmatch, rsize;
+  char **result;
 
-  if (!hostname_list_initialized)
-    {
-      initialize_hostname_list ();
+  if (hostname_list_initialized == 0)
+    initialize_hostname_list ();
 
-      if (!hostname_list_initialized)
-       return ((char **)NULL);
-    }
-
-  sort_hostname_list ();
-
-  /* The list is sorted.  Do a binary search on it for the first character
-     in TEXT, and then grovel the names of interest. */
-  begin = 0; end = hostname_list_length;
+  if (hostname_list_initialized == 0)
+    return ((char **)NULL);
 
   /* Special case.  If TEXT consists of nothing, then the whole list is
      what is desired. */
-  if (!*text)
+  if (*text == '\0')
     {
       result = (char **)xmalloc ((1 + hostname_list_length) * sizeof (char *));
       for (i = 0; i < hostname_list_length; i++)
@@ -478,54 +508,39 @@ hostnames_matching (text)
     }
 
   /* Scan until found, or failure. */
-  while (end != begin)
+  len = strlen (text);
+  result = (char **)NULL;
+  for (i = nmatch = rsize = 0; i < hostname_list_length; i++)
     {
-      int r = 0;
-
-      i = ((end - begin) / 2) + begin;
-      if (i == last_search)
-       break;
+      if (STREQN (text, hostname_list[i], len) == 0)
+        continue;
 
-      if (hostname_list[i] &&
-         (r = strncmp (hostname_list[i], text, len)) == 0)
+      /* OK, it matches.  Add it to the list. */
+      if (nmatch >= rsize)
        {
-         while (strncmp (hostname_list[i], text, len) == 0 && i) i--;
-         if (strncmp (hostname_list[i], text, len) != 0) i++;
-
-         begin = i;
-         while (hostname_list[i] &&
-                strncmp (hostname_list[i], text, len) == 0) i++;
-         end = i;
-
-         result = (char **)xmalloc ((1 + (end - begin)) * sizeof (char *));
-         for (i = 0; i + begin < end; i++)
-           result[i] = hostname_list[begin + i];
-         result[i] = (char *)NULL;
-         return (result);
+         rsize = (rsize + 16) - (rsize % 16);
+         result = (char **)xrealloc (result, rsize * sizeof (char *));
        }
 
-      last_search = i;
-
-      if (r < 0)
-       begin = i;
-      else
-       end = i;
+      result[nmatch++] = hostname_list[i];
     }
-  return ((char **)NULL);
+  if (nmatch)
+    result[nmatch] = (char *)NULL;
+  return (result);
 }
 
-/* The equivalent of the K*rn shell C-o operate-and-get-next-history-line
+/* The equivalent of the Korn shell C-o operate-and-get-next-history-line
    editing command. */
-static int saved_history_line_to_use = 0;
+static int saved_history_line_to_use = -1;
 
 static void
 set_saved_history ()
 {
-  if (saved_history_line_to_use)
+  if (saved_history_line_to_use >= 0)
     rl_get_previous_history (history_length - saved_history_line_to_use);
-  saved_history_line_to_use = 0;
+  saved_history_line_to_use = -1;
   rl_startup_hook = old_rl_startup_hook;
-}  
+}
 
 static void
 operate_and_get_next (count, c)
@@ -534,7 +549,7 @@ operate_and_get_next (count, c)
   int where;
 
   /* Accept the current line. */
-  rl_newline ();       
+  rl_newline ();
 
   /* Find the current line, and find the next line to use. */
   where = where_history ();
@@ -558,11 +573,12 @@ operate_and_get_next (count, c)
 
 static void
 vi_edit_and_execute_command (count, c)
+     int count, c;
 {
   char *command;
 
   /* Accept the current line. */
-  rl_newline ();       
+  rl_newline ();
 
   if (rl_explicit_arg)
     {
@@ -583,10 +599,33 @@ vi_edit_and_execute_command (count, c)
       command = savestring (VI_EDIT_COMMAND);
     }
   parse_and_execute (command, "v", -1);
-  rl_line_buffer[0] = '\0';    /* erase pre-edited command */
+  rl_line_buffer[0] = '\0';    /* XXX */
 }
 #endif /* VI_MODE */
 
+#if defined (ALIAS)
+static int
+posix_edit_macros (count, key)
+     int count, key;
+{
+  int c;
+  char alias_name[3], *alias_value, *macro;
+
+  c = rl_read_key ();
+  alias_name[0] = '_';
+  alias_name[1] = c;
+  alias_name[2] = '\0';
+
+  alias_value = get_alias_value (alias_name);
+  if (alias_value && *alias_value)
+    {
+      macro = savestring (alias_value);
+      rl_push_macro_input (macro);
+    }
+  return 0;
+}
+#endif
+
 /* **************************************************************** */
 /*                                                                 */
 /*                     How To Do Shell Completion                  */
@@ -601,11 +640,11 @@ attempt_shell_completion (text, start, end)
      int start, end;
 {
   int in_command_position, ti;
-  char **matches = (char **)NULL;
-  char *command_separator_chars = ";|&{(`";
+  char **matches, *command_separator_chars;
 
-  rl_ignore_some_completions_function =
-    (Function *)filename_completion_ignore;
+  command_separator_chars = ";|&{(`";
+  matches = (char **)NULL;
+  rl_ignore_some_completions_function = (Function *)filename_completion_ignore;
 
   /* Determine if this could be a command word.  It is if it appears at
      the start of the line (ignoring preceding whitespace), or if it
@@ -638,7 +677,8 @@ attempt_shell_completion (text, start, end)
       if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) ||
          (this_char == '|' && prev_char == '>'))
        in_command_position = 0;
-      else if (char_is_quoted (rl_line_buffer, ti))
+      else if ((this_char == '{' && prev_char == '$') ||
+              (char_is_quoted (rl_line_buffer, ti)))
        in_command_position = 0;
     }
   else
@@ -648,8 +688,7 @@ attempt_shell_completion (text, start, end)
         assignments. */
     }
 
-  /* Special handling for command substitution.  XXX - this should handle
-     `$(' as well. */
+  /* Special handling for command substitution. */
   if (*text == '`' && unclosed_pair (rl_line_buffer, start, "`"))
     matches = completion_matches (text, command_subst_completion_function);
 
@@ -664,7 +703,7 @@ attempt_shell_completion (text, start, end)
 
   /* Another one.  Why not?  If the word starts in '@', then look through
      the world of known hostnames for completion first. */
-  if (!matches && *text == '@')
+  if (!matches && perform_hostname_completion && *text == '@')
     matches = completion_matches (text, hostname_completion_function);
 
   /* And last, (but not least) if this word is in a command position, then
@@ -682,6 +721,11 @@ attempt_shell_completion (text, start, end)
        rl_ignore_some_completions_function = (Function *)bash_ignore_filenames;
     }
 
+  /* This could be a globbing pattern, so try to expand it using pathname
+     expansion. */
+  if (!matches && glob_pattern_p (text))
+    matches = completion_matches (text, glob_complete_word);
+
   return (matches);
 }
 
@@ -702,12 +746,11 @@ command_word_completion_function (hint_text, state)
   static int mapping_over, local_index;
   static SHELL_VAR **varlist = (SHELL_VAR **)NULL;
 #if defined (ALIAS)
-  static ASSOC **alias_list = (ASSOC **)NULL;
+  static alias_t **alias_list = (alias_t **)NULL;
 #endif /* ALIAS */
 
   /* We have to map over the possibilities for command words.  If we have
      no state, then make one just for that purpose. */
-
   if (!state)
     {
       if (hint)
@@ -725,7 +768,7 @@ 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 = tilde_expand (hint_text);
+           hint = bash_tilde_expand (hint_text);
          else
            hint = savestring (hint_text);
          hint_len = strlen (hint);
@@ -829,7 +872,7 @@ command_word_completion_function (hint_text, state)
       mapping_over++;
     }
 
-  /* Repeatedly call filename_completion_func<tion while we have
+  /* 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. */
  outer:
@@ -856,7 +899,7 @@ command_word_completion_function (hint_text, state)
        {
          char *t;
 
-         t = tilde_expand (current_path);
+         t = bash_tilde_expand (current_path);
          free (current_path);
          current_path = t;
        }
@@ -874,7 +917,7 @@ command_word_completion_function (hint_text, state)
   val = filename_completion_function (filename_hint, istate);
   istate = 1;
 
-  if (!val)
+  if (val == 0)
     {
       /* If the hint text is an absolute program, then don't bother
         searching through PATH. */
@@ -921,8 +964,9 @@ command_word_completion_function (hint_text, state)
            match = 0;
        }
 
-      /* If we have found a match, and it is an executable file, return it. */
-      if (match && executable_file (val))
+      /* If we have found a match, and it is an executable file or a
+        directory name, return it. */
+      if (match && (executable_file (val) || is_directory (val)))
        {
          free (val);
          val = "";             /* So it won't be NULL. */
@@ -938,12 +982,13 @@ command_word_completion_function (hint_text, state)
 
 static char *
 command_subst_completion_function (text, state)
-     int state;
      char *text;
+     int state;
 {
   static char **matches = (char **)NULL;
   static char *orig_start, *filename_text = (char *)NULL;
   static int cmd_index, start_len;
+  char *value;
 
   if (state == 0)
     {
@@ -969,8 +1014,6 @@ command_subst_completion_function (text, state)
     }
   else
     {
-      char *value;
-
       value = xmalloc (1 + start_len + strlen (matches[cmd_index]));
 
       if (start_len == 1)
@@ -1009,6 +1052,9 @@ variable_completion_function (text, state)
       if (first_char == '$')
        first_char_loc++;
 
+      if (text[first_char_loc] == '{')
+        first_char_loc++;
+
       varname = savestring (text + first_char_loc);
 
       namelen = strlen (varname);
@@ -1035,12 +1081,18 @@ variable_completion_function (text, state)
     }
   else
     {
-      char *value = xmalloc (2 + strlen (var->name));
+      char *value = xmalloc (4 + strlen (var->name));
 
       if (first_char_loc)
-       *value = first_char;
+       {
+         value[0] = first_char;
+         if (first_char_loc == 2)
+           value[1] = '{';
+       }
 
       strcpy (&value[first_char_loc], var->name);
+      if (first_char_loc == 2)
+        strcat (value, "}");
 
       varlist_index++;
       return (value);
@@ -1058,10 +1110,9 @@ hostname_completion_function (text, state)
   static int first_char, first_char_loc;
 
   /* If we don't have any state, make some. */
-  if (!state)
+  if (state == 0)
     {
-      if (list)
-       free (list);
+      FREE (list);
 
       list = (char **)NULL;
 
@@ -1077,15 +1128,16 @@ hostname_completion_function (text, state)
 
   if (list && list[list_index])
     {
-      char *t = xmalloc (2 + strlen (list[list_index]));
+      char *t;
 
+      t = xmalloc (2 + strlen (list[list_index]));
       *t = first_char;
       strcpy (t + first_char_loc, list[list_index]);
       list_index++;
       return (t);
     }
-  else
-    return ((char *)NULL);
+
+  return ((char *)NULL);
 }
 
 /* History and alias expand the line. */
@@ -1183,7 +1235,7 @@ history_expand_line (ignore)
   else
     cleanup_expansion_error ();
 }
-  
+
 /* History and alias expand the line. */
 static void
 history_and_alias_expand_line (ignore)
@@ -1217,6 +1269,7 @@ shell_expand_line (ignore)
      int ignore;
 {
   char *new_line;
+  WORD_LIST *expanded_string;
 
   new_line = pre_process_line (rl_line_buffer, 0, 0);
 
@@ -1243,139 +1296,54 @@ shell_expand_line (ignore)
 
       /* If there is variable expansion to perform, do that as a separate
         operation to be undone. */
-      {
-       WORD_LIST *expanded_string;
-
-       expanded_string = expand_string (rl_line_buffer, 0);
-       if (!expanded_string)
-         new_line = savestring ("");
-       else
-         {
-           new_line = string_list (expanded_string);
-           dispose_words (expanded_string);
-         }
+      expanded_string = expand_string (rl_line_buffer, 0);
+      if (expanded_string == 0)
+       {
+         new_line = xmalloc (1);
+         new_line[0] = '\0';
+       }
+      else
+       {
+         new_line = string_list (expanded_string);
+         dispose_words (expanded_string);
+       }
 
-       maybe_make_readline_line (new_line);
-       free (new_line);
+      maybe_make_readline_line (new_line);
+      free (new_line);
 
-       /* Place rl_point where we think it should go. */
-       if (at_end)
-         rl_point = rl_end;
-       else if (old_point < rl_end)
-         {
-           rl_point = old_point;
-           if (!whitespace (rl_line_buffer[rl_point]))
-             rl_forward_word (1);
-         }
-      }
+      /* Place rl_point where we think it should go. */
+      if (at_end)
+       rl_point = rl_end;
+      else if (old_point < rl_end)
+       {
+         rl_point = old_point;
+         if (!whitespace (rl_line_buffer[rl_point]))
+           rl_forward_word (1);
+       }
     }
   else
     cleanup_expansion_error ();
 }
 
-/* Filename completion ignore.  Emulates the "fignore" facility of
-   tcsh.  If FIGNORE is set, then don't match files with the
-   given suffixes.  If only one of the possibilities has an acceptable
+/* 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
    signal an error.  It is called by the completer when real
    completions are done on filenames by the completer's internal
    function, not for completion lists (M-?) and not on "other"
-   completion types, such as hostnames or commands.
-   It is passed a NULL-terminated array of (char *)'s that must be
-   free()'d if they are deleted.  The first element (names[0]) is the
-   least-common-denominator string of the matching patterns (i.e.
-   u<TAB> produces names[0] = "und", names[1] = "under.c", names[2] =
-   "undun.c", name[3] = NULL).  */
-
-struct ign {
-  char *val;
-  int len;
-};
+   completion types, such as hostnames or commands. */
 
-static struct ign *ignores;    /* Store the ignore strings here */
-static int num_ignores;                /* How many are there? */
-static char *last_fignore;     /* Last value of fignore - cached for speed */
-
-static void
-setup_ignore_patterns ()
+static struct ignorevar fignore =
 {
-  int numitems, maxitems, ptr;
-  char *colon_bit;
-  struct ign *p;
-  
-  char *this_fignore = get_string_value ("FIGNORE");
-
-  /* If nothing has changed then just exit now. */
-  if ((this_fignore &&
-       last_fignore &&
-       strcmp (this_fignore, last_fignore) == 0) ||
-      (!this_fignore && !last_fignore))
-    {
-      return;
-    }
-
-  /* Oops.  FIGNORE has changed.  Re-parse it. */
-  num_ignores = 0;
-
-  if (ignores)
-    {
-      for (p = ignores; p->val; p++) free(p->val);
-      free (ignores);
-      ignores = (struct ign*)NULL;
-    }
-
-  if (last_fignore)
-    {
-      free (last_fignore);
-      last_fignore = (char *)NULL;
-    }
-
-  if (!this_fignore || !*this_fignore)
-    return;
-
-  last_fignore = savestring (this_fignore);
-
-  numitems = maxitems = ptr = 0;
-
-  while (colon_bit = extract_colon_unit (this_fignore, &ptr))
-    {
-      if (numitems + 1 > maxitems)
-       ignores = (struct ign *)
-         xrealloc (ignores, (maxitems += 10) * sizeof (struct ign));
-
-      ignores[numitems].val = colon_bit;
-      ignores[numitems].len = strlen (colon_bit);
-      numitems++;
-    }
-  ignores[numitems].val = NULL;
-  num_ignores = numitems;
-}
-
-static int
-name_is_acceptable (name)
-     char *name;
-{
-  struct ign *p;
-  int nlen = strlen (name);
-
-  for (p = ignores; p->val; p++) 
-    {
-      if (nlen > p->len && p->len > 0 && 
-         strcmp (p->val, &name[nlen - p->len]) == 0)
-       return (0);
-    }
-
-  return (1);
-}
+  "FIGNORE",
+  (struct ign *)0,
+  0,
+  (char *)0,
+  (Function *) 0,
+};
 
-/* Internal function to test whether filenames in NAMES should be
-   ignored.  NAME_FUNC is a pointer to a function to call with each
-   name.  It returns non-zero if the name is acceptable to the particular
-   ignore function which called _ignore_names; zero if the name should
-   be removed from NAMES. */
 static void
-_ignore_names (names, name_func)
+_ignore_completion_names (names, name_func)
      char **names;
      Function *name_func;
 {
@@ -1408,7 +1376,7 @@ _ignore_names (names, name_func)
       if ((*name_func) (names[idx]))
        newnames[nidx++] = names[idx];
       else
-       free (names[idx]);
+        free (names[idx]);
     }
 
   newnames[nidx] = (char *)NULL;
@@ -1431,24 +1399,41 @@ _ignore_names (names, name_func)
       free (newnames);
       return;
     }
-      
+
   /* Copy the acceptable names back to NAMES, set the new array end,
      and return. */
   for (nidx = 1; newnames[nidx]; nidx++)
     names[nidx] = newnames[nidx];
   names[nidx] = (char *)NULL;
+  free (newnames);
+}
+
+static int
+name_is_acceptable (name)
+     char *name;
+{
+  struct ign *p;
+  int nlen;
+
+  for (nlen = strlen (name), p = fignore.ignores; p->val; p++)
+    {
+      if (nlen > p->len && p->len > 0 && STREQ (p->val, &name[nlen - p->len]))
+       return (0);
+    }
+
+  return (1);
 }
 
 static void
 filename_completion_ignore (names)
      char **names;
 {
-  setup_ignore_patterns ();
+  setup_ignore_patterns (&fignore);
 
-  if (num_ignores == 0)
+  if (fignore.num_ignores == 0)
     return;
 
-  _ignore_names (names, name_is_acceptable);
+  _ignore_completion_names (names, name_is_acceptable);
 }
 
 /* Return 1 if NAME is a directory. */
@@ -1459,7 +1444,7 @@ test_for_directory (name)
   struct stat finfo;
   char *fn;
 
-  fn = tilde_expand (name);
+  fn = bash_tilde_expand (name);
   if (stat (fn, &finfo) != 0)
     {
       free (fn);
@@ -1474,7 +1459,7 @@ static void
 bash_ignore_filenames (names)
      char **names;
 {
-  _ignore_names (names, test_for_directory);
+  _ignore_completion_names (names, test_for_directory);
 }
 
 /* Handle symbolic link references and other directory name
@@ -1504,7 +1489,8 @@ bash_directory_completion_hook (dirname)
       else
        {
          free (local_dirname);
-         *dirname = savestring ("");
+         *dirname = xmalloc (1);
+         **dirname = '\0';
          return 1;
        }
     }
@@ -1518,6 +1504,12 @@ bash_directory_completion_hook (dirname)
       temp1 = make_absolute (local_dirname, t);
       free (t);
       temp2 = canonicalize_pathname (temp1);
+      /* If we can't canonicalize, bail. */
+      if (temp2 == 0)
+       {
+         free (temp1);
+         return 1;
+       }
       len1 = strlen (temp1);
       if (temp1[len1 - 1] == '/')
         {
@@ -1533,15 +1525,16 @@ bash_directory_completion_hook (dirname)
   return (return_value);
 }
 
-#if defined (DYNAMIC_HISTORY_COMPLETION)
 static char **history_completion_array = (char **)NULL;
-static int harry_size = 0;
-static int harry_len = 0;
+static int harry_size;
+static int harry_len;
 
 static void
 build_history_completion_array ()
 {
-  register int i;
+  register int i, j;
+  HIST_ENTRY **hlist;
+  char **tokens;
 
   /* First, clear out the current dynamic history completion list. */
   if (harry_size)
@@ -1558,43 +1551,33 @@ build_history_completion_array ()
 
   /* Next, grovel each line of history, making each shell-sized token
      a separate entry in the history_completion_array. */
-  {
-    HIST_ENTRY **hlist;
-
-    hlist = history_list ();
-
-    if (hlist)
-      {
-       register int j;
+  hlist = history_list ();
 
-       for (i = 0; hlist[i]; i++)
-         {
-           char **tokens;
-
-           /* Separate each token, and place into an array. */
-           tokens = history_tokenize (hlist[i]->line);
+  if (hlist)
+    {
+      for (i = 0; hlist[i]; i++)
+       {
+         /* Separate each token, and place into an array. */
+         tokens = history_tokenize (hlist[i]->line);
 
-           for (j = 0; tokens && tokens[j]; j++)
-             {
-               if (harry_len + 2 > harry_size)
+         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 += 10) * sizeof (char *));
-
-               history_completion_array[harry_len++] = tokens[j];
-               history_completion_array[harry_len] = (char *)NULL;
-             }
-           free (tokens);
-         }
+                   (history_completion_array, harry_size * sizeof (char *));
+               }
 
-       /* Sort the complete list of tokens. */
-       qsort (history_completion_array, harry_len, sizeof (char *),
-              (Function *)qsort_string_compare);
+             history_completion_array[harry_len++] = tokens[j];
+             history_completion_array[harry_len] = (char *)NULL;
+           }
+         free (tokens);
+       }
 
-       /* Instead of removing the duplicate entries here, we let the
-          code in the completer handle it. */
-      }
-  }
+      /* Sort the complete list of tokens. */
+      qsort (history_completion_array, harry_len, sizeof (char *), (Function *)qsort_string_compare);
+    }
 }
 
 static char *
@@ -1602,13 +1585,12 @@ history_completion_generator (hint_text, state)
      char *hint_text;
      int state;
 {
-  static int local_index = 0;
-  static char *text = (char *)NULL;
-  static int len = 0;
+  static int local_index, len;
+  static char *text;
 
   /* If this is the first call to the generator, then initialize the
      list of strings to complete over. */
-  if (!state)
+  if (state == 0)
     {
       local_index = 0;
       build_history_completion_array ();
@@ -1645,8 +1627,6 @@ dynamic_complete_history (count, key)
   rl_attempted_completion_function = orig_attempt_func;
 }
 
-#endif /* DYNAMIC_HISTORY_COMPLETION */
-
 #if defined (SPECIFIC_COMPLETION_FUNCTIONS)
 static void
 bash_complete_username (ignore, ignore2)
@@ -1775,6 +1755,51 @@ bash_complete_command_internal (what_to_do)
     (what_to_do, (Function *)command_word_completion_function);
 }
 
+static char *
+glob_complete_word (text, state)
+     char *text;
+     int state;
+{
+  static char **matches = (char **)NULL;
+  static int ind;
+  char *ret;
+
+  if (state == 0)
+    {
+      if (matches)
+        free (matches);
+      matches = shell_glob_filename (text);
+      if (GLOB_FAILED (matches))
+        matches = (char **)NULL;
+      ind = 0;
+    }
+
+  ret = matches ? matches[ind] : (char *)NULL;
+  ind++;
+  return ret;
+}
+
+static void
+bash_glob_completion_internal (what_to_do)
+     int what_to_do;
+{
+  bash_specific_completion (what_to_do, (Function *)glob_complete_word);
+}
+
+static void
+bash_glob_expand_word (count, key)
+     int count, key;
+{
+  bash_glob_completion_internal ('*');
+}
+
+static void
+bash_glob_list_expansions (count, key)
+     int count, key;
+{
+  bash_glob_completion_internal ('?');
+}
+
 static void
 bash_specific_completion (what_to_do, generator)
      int what_to_do;
@@ -1795,3 +1820,111 @@ bash_specific_completion (what_to_do, generator)
 }
 
 #endif /* SPECIFIC_COMPLETION_FUNCTIONS */
+
+/* Filename quoting for completion. */
+/* A function to strip quotes that are not protected by backquotes.  It
+   allows single quotes to appear within double quotes, and vice versa.
+   It should be smarter. */
+static char *
+bash_dequote_filename (text, quote_char)
+     char *text;
+{
+  char *ret, *p, *r;
+  int l, quoted;
+
+  l = strlen (text);
+  ret = xmalloc (l + 1);
+  for (quoted = quote_char, p = text, r = ret; p && *p; p++)
+    {
+      /* Allow backslash-quoted characters to pass through unscathed. */
+      if (*p == '\\')
+       {
+         *r++ = *++p;
+         if (*p == '\0')
+           break;
+         continue;
+       }
+      /* Close quote. */
+      if (quoted && *p == quoted)
+        {
+          quoted = 0;
+          continue;
+        }
+      /* Open quote. */
+      if (quoted == 0 && (*p == '\'' || *p == '"'))
+        {
+          quoted = *p;
+          continue;
+        }
+      *r++ = *p;
+    }
+  *r = '\0';
+  return ret;
+}
+
+/* Quote a filename using double quotes. */
+static char *
+bash_quote_filename (s, rtype, qcp)
+     char *s;
+     int rtype;
+     char *qcp;
+{
+  char *rtext, *mtext, *ret;
+  int rlen, cs;
+
+  rtext = (char *)NULL;
+
+  /* If RTYPE == MULT_MATCH, it means that there is
+     more than one match.  In this case, we do not add
+     the closing quote or attempt to perform tilde
+     expansion.  If RTYPE == SINGLE_MATCH, we try
+     to perform tilde expansion, because single and double
+     quotes inhibit tilde expansion by the shell. */
+
+  mtext = s;
+  if (mtext[0] == '~' && rtype == SINGLE_MATCH)
+    mtext = bash_tilde_expand (s);
+
+  cs = completion_quoting_style;
+  /* Might need to modify the default completion style based on *qcp,
+     since it's set to any user-provided opening quote. */
+  if (*qcp == '"')
+    cs = COMPLETE_DQUOTE;
+  else if (*qcp == '\'')
+    cs = COMPLETE_SQUOTE;
+#if defined (BANG_HISTORY)
+  else if (*qcp == '\0' && history_expansion && cs == COMPLETE_DQUOTE &&
+          history_expansion_inhibited == 0 && strchr (mtext, '!'))
+    cs = COMPLETE_BSQUOTE;
+#endif
+
+  switch (cs)
+    {
+    case COMPLETE_DQUOTE:
+      rtext = double_quote (mtext);
+      break;
+    case COMPLETE_SQUOTE:
+      rtext = single_quote (mtext);
+      break;
+    case COMPLETE_BSQUOTE:
+      rtext = backslash_quote (mtext);
+      break;
+    }
+
+  if (mtext != s)
+    free (mtext);
+
+  /* Leave the opening quote intact.  The readline completion code takes
+     care of avoiding doubled opening quotes. */
+  rlen = strlen (rtext);
+  ret = xmalloc (rlen + 1);
+  strcpy (ret, rtext);
+
+  /* If there are multiple matches, cut off the closing quote. */
+  if (rtype == MULT_MATCH && cs != COMPLETE_BSQUOTE)
+    ret[rlen - 1] = '\0';
+  free (rtext);
+  return ret;
+}
+
+#endif /* READLINE */