Imported from ../bash-2.04.tar.gz.
[platform/upstream/bash.git] / bashline.c
index a86e662..cb4de05 100644 (file)
@@ -6,7 +6,7 @@
 
    Bash is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 1, or (at your option)
+   the Free Software Foundation; either version 2, or (at your option)
    any later version.
 
    Bash is distributed in the hope that it will be useful, but WITHOUT
@@ -16,7 +16,7 @@
 
    You should have received a copy of the GNU General Public License
    along with Bash; see the file COPYING.  If not, write to the Free
-   Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
+   Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
 
 #include "config.h"
 
 #  include "alias.h"
 #endif
 
+#if defined (PROGRAMMABLE_COMPLETION)
+#  include "pcomplete.h"
+#endif
+
 #if defined (BRACE_COMPLETION)
 extern void bash_brace_completion ();
 #endif /* BRACE_COMPLETION */
@@ -57,6 +61,7 @@ extern void bash_brace_completion ();
 static void shell_expand_line ();
 static void display_shell_version (), operate_and_get_next ();
 static void bash_ignore_filenames ();
+static void bash_ignore_everything ();
 static void cleanup_expansion_error (), set_up_new_line ();
 
 #if defined (BANG_HISTORY)
@@ -78,7 +83,6 @@ static void bash_push_line ();
 static char **attempt_shell_completion ();
 static char *variable_completion_function ();
 static char *hostname_completion_function ();
-static char *command_word_completion_function ();
 static char *command_subst_completion_function ();
 static void dynamic_complete_history ();
 
@@ -86,7 +90,8 @@ 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 snarf_hosts_from_file ();
+static void add_host_name ();
 
 static char *bash_dequote_filename ();
 static char *bash_quote_filename ();
@@ -95,6 +100,11 @@ static char *bash_quote_filename ();
 static int posix_edit_macros ();
 #endif
 
+#if defined (PROGRAMMABLE_COMPLETION)
+static char **prog_complete_matches;
+static int old_rl_completion_append_character;
+#endif
+
 /* Variables used here but defined in other files. */
 extern int posixly_correct, no_symbolic_links;
 extern int rl_explicit_arg;
@@ -103,10 +113,6 @@ 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
@@ -139,6 +145,9 @@ int bash_readline_initialized = 0;
    host list. */
 int perform_hostname_completion = 1;
 
+/* If non-zero, we don't do command completion on an empty line. */
+int no_empty_command_completion;
+
 static char *bash_completer_word_break_characters = " \t\n\"'@><=;|&(:";
 static char *bash_nohostname_word_break_characters = " \t\n\"'><=;|&(:";
 
@@ -213,7 +222,9 @@ initialize_readline ()
 
 #ifdef ALIAS
   rl_add_defun ("alias-expand-line", (Function *)alias_expand_line, -1);
+#  ifdef BANG_HISTORY
   rl_add_defun ("history-and-alias-expand-line", (Function *)history_and_alias_expand_line, -1);
+#  endif
 #endif
 
   /* Backwards compatibility. */
@@ -237,50 +248,50 @@ initialize_readline ()
 #endif
 
 #if defined (BRACE_COMPLETION)
-  rl_add_defun ("complete-into-braces", bash_brace_completion, -1);
-  rl_bind_key_in_map ('{', bash_brace_completion, emacs_meta_keymap);
+  rl_add_defun ("complete-into-braces", (Function *)bash_brace_completion, -1);
+  rl_bind_key_in_map ('{', (Function *)bash_brace_completion, emacs_meta_keymap);
 #endif /* BRACE_COMPLETION */
 
 #if defined (SPECIFIC_COMPLETION_FUNCTIONS)
-  rl_add_defun ("complete-filename", bash_complete_filename, -1);
-  rl_bind_key_in_map ('/', bash_complete_filename, emacs_meta_keymap);
+  rl_add_defun ("complete-filename", (Function *)bash_complete_filename, -1);
+  rl_bind_key_in_map ('/', (Function *)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);
+               (Function *)bash_possible_filename_completions, -1);
+  rl_bind_key_in_map ('/', (Function *)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 ("complete-username", (Function *)bash_complete_username, -1);
+  rl_bind_key_in_map ('~', (Function *)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);
+               (Function *)bash_possible_username_completions, -1);
+  rl_bind_key_in_map ('~', (Function *)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 ("complete-hostname", (Function *)bash_complete_hostname, -1);
+  rl_bind_key_in_map ('@', (Function *)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);
+               (Function *)bash_possible_hostname_completions, -1);
+  rl_bind_key_in_map ('@', (Function *)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 ("complete-variable", (Function *)bash_complete_variable, -1);
+  rl_bind_key_in_map ('$', (Function *)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);
+               (Function *)bash_possible_variable_completions, -1);
+  rl_bind_key_in_map ('$', (Function *)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 ("complete-command", (Function *)bash_complete_command, -1);
+  rl_bind_key_in_map ('!', (Function *)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);
+               (Function *)bash_possible_command_completions, -1);
+  rl_bind_key_in_map ('!', (Function *)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);
+  rl_add_defun ("glob-expand-word", (Function *)bash_glob_expand_word, -1);
+  rl_add_defun ("glob-list-expansions", (Function *)bash_glob_list_expansions, -1);
+  rl_bind_key_in_map ('*', (Function *)bash_glob_expand_word, emacs_ctlx_keymap);
+  rl_bind_key_in_map ('g', (Function *)bash_glob_list_expansions, emacs_ctlx_keymap);
 
 #endif /* SPECIFIC_COMPLETION_FUNCTIONS */
 
-  rl_add_defun ("dynamic-complete-history", dynamic_complete_history, -1);
-  rl_bind_key_in_map (TAB, dynamic_complete_history, emacs_meta_keymap);
+  rl_add_defun ("dynamic-complete-history", (Function *)dynamic_complete_history, -1);
+  rl_bind_key_in_map (TAB, (Function *)dynamic_complete_history, emacs_meta_keymap);
 
   /* Tell the completer that we want a crack first. */
   rl_attempted_completion_function = (CPPFunction *)attempt_shell_completion;
@@ -293,9 +304,9 @@ initialize_readline ()
   rl_ignore_some_completions_function = (Function *)filename_completion_ignore;
 
 #if defined (VI_MODE)
-  rl_bind_key_in_map ('v', vi_edit_and_execute_command, vi_movement_keymap);
+  rl_bind_key_in_map ('v', (Function *)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_in_map ('@', (Function *)posix_edit_macros, vi_movement_keymap);
 #  endif
 #endif
 
@@ -384,8 +395,10 @@ display_shell_version (count, c)
 /* **************************************************************** */
 
 /* If the user requests hostname completion, then simply build a list
-   of hosts, and complete from that forever more. */
+   of hosts, and complete from that forever more, or at least until
+   HOSTFILE is unset. */
 
+/* THIS SHOULD BE A STRINGLIST. */
 /* The kept list of hostnames. */
 static char **hostname_list = (char **)NULL;
 
@@ -502,6 +515,27 @@ snarf_hosts_from_file (filename)
   fclose (file);
 }
 
+/* Return the hostname list. */
+char **
+get_hostname_list ()
+{
+  if (hostname_list_initialized == 0)
+    initialize_hostname_list ();
+  return (hostname_list);
+}
+
+void
+clear_hostname_list ()
+{
+  register int i;
+
+  if (hostname_list_initialized == 0)
+    return;
+  for (i = 0; i < hostname_list_length; i++)
+    free (hostname_list[i]);
+  hostname_list_length = 0;
+}
+
 /* Return a NULL terminated list of hostnames which begin with TEXT.
    Initialize the hostname list the first time if neccessary.
    The array is malloc ()'ed, but not the individual strings. */
@@ -522,7 +556,7 @@ hostnames_matching (text)
      what is desired. */
   if (*text == '\0')
     {
-      result = (char **)xmalloc ((1 + hostname_list_length) * sizeof (char *));
+      result = alloc_array (1 + hostname_list_length);
       for (i = 0; i < hostname_list_length; i++)
        result[i] = hostname_list[i];
       result[i] = (char *)NULL;
@@ -538,7 +572,7 @@ hostnames_matching (text)
         continue;
 
       /* OK, it matches.  Add it to the list. */
-      if (nmatch >= rsize)
+      if (nmatch >= (rsize - 1))
        {
          rsize = (rsize + 16) - (rsize % 16);
          result = (char **)xrealloc (result, rsize * sizeof (char *));
@@ -559,7 +593,7 @@ static void
 set_saved_history ()
 {
   if (saved_history_line_to_use >= 0)
-    rl_get_previous_history (history_length - saved_history_line_to_use);
+    rl_get_previous_history (history_length - saved_history_line_to_use, 0);
   saved_history_line_to_use = -1;
   rl_startup_hook = old_rl_startup_hook;
 }
@@ -571,7 +605,7 @@ operate_and_get_next (count, c)
   int where;
 
   /* Accept the current line. */
-  rl_newline ();
+  rl_newline (1, c);
 
   /* Find the current line, and find the next line to use. */
   where = where_history ();
@@ -600,7 +634,7 @@ vi_edit_and_execute_command (count, c)
   char *command;
 
   /* Accept the current line. */
-  rl_newline ();
+  rl_newline (1, c);
 
   if (rl_explicit_arg)
     {
@@ -654,6 +688,87 @@ posix_edit_macros (count, key)
 /*                                                                 */
 /* **************************************************************** */
 
+#define COMMAND_SEPARATORS ";|&{(`"
+
+static int
+check_redir (ti)
+     int ti;
+{
+  register int this_char, prev_char;
+
+  /* Handle the two character tokens `>&', `<&', and `>|'.
+     We are not in a command position after one of these. */
+  this_char = rl_line_buffer[ti];
+  prev_char = rl_line_buffer[ti - 1];
+
+  if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) ||
+      (this_char == '|' && prev_char == '>'))
+    return (1);
+  else if ((this_char == '{' && prev_char == '$') || /* } */
+          (char_is_quoted (rl_line_buffer, ti)))
+    return (1);
+  return (0);
+}
+
+#if defined (PROGRAMMABLE_COMPLETION)
+static int
+find_cmd_start (start)
+     int start;
+{
+  register int s, os;
+
+  os = 0;
+  while (((s = skip_to_delim (rl_line_buffer, os, COMMAND_SEPARATORS)) <= start) &&
+        rl_line_buffer[s])
+    os = s+1;
+  return os;
+}
+
+static int
+find_cmd_end (end)
+     int end;
+{
+  register int e;
+
+  e = skip_to_delim (rl_line_buffer, end, COMMAND_SEPARATORS);
+  return e;
+}
+
+static char *
+find_cmd_name (start)
+     int start;
+{
+  char *name;
+  register int s, e;
+
+  for (s = start; whitespace (rl_line_buffer[s]); s++)
+    ;
+
+  /* skip until a shell break character */
+  e = skip_to_delim (rl_line_buffer, s, "()<>;&| \t\n");
+
+  name = substring (rl_line_buffer, s, e);
+
+  return (name);
+}
+
+static char *
+prog_complete_return (text, matchnum)
+     char *text;
+     int matchnum;
+{
+  static int ind;
+
+  if (matchnum == 0)
+    ind = 0;
+
+  if (prog_complete_matches == 0 || prog_complete_matches[ind] == 0)
+    return (char *)NULL;
+  return (prog_complete_matches[ind++]);
+}
+
+#endif /* PROGRAMMABLE_COMPLETION */
+
 /* Do some completion on TEXT.  The indices of TEXT in RL_LINE_BUFFER are
    at START and END.  Return an array of matches, or NULL if none. */
 static char **
@@ -661,10 +776,10 @@ attempt_shell_completion (text, start, end)
      char *text;
      int start, end;
 {
-  int in_command_position, ti;
+  int in_command_position, ti, saveti, qc;
   char **matches, *command_separator_chars;
 
-  command_separator_chars = ";|&{(`";
+  command_separator_chars = COMMAND_SEPARATORS;
   matches = (char **)NULL;
   rl_ignore_some_completions_function = (Function *)filename_completion_ignore;
 
@@ -673,10 +788,23 @@ attempt_shell_completion (text, start, end)
      appears after a character that separates commands.  It cannot be a
      command word if we aren't at the top-level prompt. */
   ti = start - 1;
+  saveti = qc = -1;
 
   while ((ti > -1) && (whitespace (rl_line_buffer[ti])))
     ti--;
 
+#if 1
+  /* If this is an open quote, maybe we're trying to complete a quoted
+     command name. */
+  if (rl_line_buffer[ti] == '"' || rl_line_buffer[ti] == '\'')
+    {
+      qc = rl_line_buffer[ti];
+      saveti = ti--;
+      while (ti > -1 && (whitespace (rl_line_buffer[ti])))
+        ti--;
+    }
+#endif
+      
   in_command_position = 0;
   if (ti < 0)
     {
@@ -687,21 +815,10 @@ attempt_shell_completion (text, start, end)
     }
   else if (member (rl_line_buffer[ti], command_separator_chars))
     {
-      register int this_char, prev_char;
-
       in_command_position++;
 
-      /* Handle the two character tokens `>&', `<&', and `>|'.
-         We are not in a command position after one of these. */
-      this_char = rl_line_buffer[ti];
-      prev_char = rl_line_buffer[ti - 1];
-
-      if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) ||
-         (this_char == '|' && prev_char == '>'))
-       in_command_position = 0;
-      else if ((this_char == '{' && prev_char == '$') ||
-              (char_is_quoted (rl_line_buffer, ti)))
-       in_command_position = 0;
+      if (check_redir (ti) == 1)
+        in_command_position = 0;
     }
   else
     {
@@ -713,20 +830,59 @@ attempt_shell_completion (text, start, end)
   /* Check that we haven't incorrectly flagged a closed command substitution
      as indicating we're in a command position. */
   if (in_command_position && ti >= 0 && rl_line_buffer[ti] == '`' &&
-       *text != '`' && unclosed_pair (rl_line_buffer, 0, "`") == 0)
+       *text != '`' && unclosed_pair (rl_line_buffer, end, "`") == 0)
     in_command_position = 0;
 
   /* Special handling for command substitution.  If *TEXT is a backquote,
      it can be the start or end of an old-style command substitution, or
      unmatched.  If it's unmatched, both calls to unclosed_pair will
      succeed.  */
-  if (*text == '`' && unclosed_pair (rl_line_buffer, start, "`") &&
-       unclosed_pair (rl_line_buffer, end, "`"))
+  if (*text == '`' && 
+       (in_command_position || (unclosed_pair (rl_line_buffer, start, "`") &&
+                                unclosed_pair (rl_line_buffer, end, "`"))))
     matches = completion_matches (text, command_subst_completion_function);
 
-  /* Variable name? */
+#if defined (PROGRAMMABLE_COMPLETION)
+  /* Attempt programmable completion. */
+  if (!matches && in_command_position == 0 && prog_completion_enabled &&
+      (num_progcomps () > 0) && current_prompt_string == ps1_prompt)
+    {
+      int s, e, foundcs;
+      char *n;
+
+      /* XXX - don't free the members */
+      if (prog_complete_matches)
+       free (prog_complete_matches);
+      prog_complete_matches = (char **)NULL;
+
+      s = find_cmd_start (start);
+      e = find_cmd_end (end);
+      n = find_cmd_name (s);
+      prog_complete_matches = programmable_completions (n, text, s, e, &foundcs);
+      FREE (n);
+      /* XXX - if we found a COMPSPEC for the command, just return whatever
+        the programmable completion code returns, and disable the default
+        filename completion that readline will do. */
+      if (foundcs)
+       {
+         /* Turn what the programmable completion code returns into what
+            readline wants.  I should have made compute_lcd_of_matches
+            external... */
+         matches = completion_matches (text, prog_complete_return);
+         rl_attempted_completion_over = 1;     /* no default */
+         return (matches);
+       }
+    }
+#endif
+
+  /* New posix-style command substitution or variable name? */
   if (!matches && *text == '$')
-    matches = completion_matches (text, variable_completion_function);
+    {
+      if (qc != '\'' && text[1] == '(') /* ) */
+       matches = completion_matches (text, command_subst_completion_function);
+      else
+       matches = completion_matches (text, variable_completion_function);
+    }
 
   /* If the word starts in `~', and there is no slash in the word, then
      try completing this word as a username. */
@@ -743,14 +899,22 @@ attempt_shell_completion (text, start, end)
      and command names. */
   if (!matches && in_command_position)
     {
-      matches = completion_matches (text, command_word_completion_function);
-      /* If we are attempting command completion and nothing matches, we
-        do not want readline to perform filename completion for us.  We
-        still want to be able to complete partial pathnames, so set the
-        completion ignore function to something which will remove filenames
-        and leave directories in the match list. */
-      if (!matches)
-       rl_ignore_some_completions_function = (Function *)bash_ignore_filenames;
+      if (start == 0 && end == 0 && text[0] == '\0' && no_empty_command_completion)
+       {
+         matches = (char **)NULL;
+         rl_ignore_some_completions_function = (Function *)bash_ignore_everything;
+       }
+      else
+       {
+         matches = completion_matches (text, command_word_completion_function);
+         /* If we are attempting command completion and nothing matches, we
+            do not want readline to perform filename completion for us.  We
+            still want to be able to complete partial pathnames, so set the
+            completion ignore function to something which will remove
+            filenames and leave directories in the match list. */
+         if (matches == (char **)NULL)
+           rl_ignore_some_completions_function = (Function *)bash_ignore_filenames;
+       }
     }
 
   /* This could be a globbing pattern, so try to expand it using pathname
@@ -774,7 +938,7 @@ attempt_shell_completion (text, start, end)
    where a command word can be found.  It grovels $PATH, looking for commands
    that match.  It also scans aliases, function names, and the shell_builtin
    table. */
-static char *
+char *
 command_word_completion_function (hint_text, state)
      char *hint_text;
      int state;
@@ -1079,8 +1243,7 @@ variable_completion_function (text, state)
      int state;
      char *text;
 {
-  register SHELL_VAR *var = (SHELL_VAR *)NULL;
-  static SHELL_VAR **varlist = (SHELL_VAR **)NULL;
+  static char **varlist = (char **)NULL;
   static int varlist_index;
   static char *varname = (char *)NULL;
   static int namelen;
@@ -1104,20 +1267,10 @@ variable_completion_function (text, state)
 
       namelen = strlen (varname);
       if (varlist)
-       free (varlist);
-      varlist = all_visible_variables ();
-      varlist_index = 0;
-    }
-
-  while (varlist && varlist[varlist_index])
-    {
-      var = varlist[varlist_index];
+       free_array (varlist);
 
-      /* Compare.  You can't do better than Zayre.  No text is also
-        a match.  */
-      if (!*varname || (strncmp (varname, var->name, namelen) == 0))
-       break;
-      varlist_index++;
+      varlist = all_variables_matching_prefix (varname);
+      varlist_index = 0;
     }
 
   if (!varlist || !varlist[varlist_index])
@@ -1126,7 +1279,7 @@ variable_completion_function (text, state)
     }
   else
     {
-      char *value = xmalloc (4 + strlen (var->name));
+      char *value = xmalloc (4 + strlen (varlist[varlist_index]));
 
       if (first_char_loc)
        {
@@ -1135,7 +1288,7 @@ variable_completion_function (text, state)
            value[1] = '{';
        }
 
-      strcpy (&value[first_char_loc], var->name);
+      strcpy (value + first_char_loc, varlist[varlist_index]);
       if (first_char_loc == 2)
         strcat (value, "}");
 
@@ -1256,7 +1409,7 @@ set_up_new_line (new_line)
     {
       rl_point = old_point;
       if (!whitespace (rl_line_buffer[rl_point]))
-       rl_forward_word (1);
+       rl_forward_word (1, 0);
     }
 }
 
@@ -1417,7 +1570,7 @@ shell_expand_line (ignore)
        {
          rl_point = old_point;
          if (!whitespace (rl_line_buffer[rl_point]))
-           rl_forward_word (1);
+           rl_forward_word (1, 0);
        }
     }
   else
@@ -1477,9 +1630,9 @@ _ignore_completion_names (names, name_func)
      filenames.  The pointers are copied back to NAMES when done. */
   for (nidx = 1; names[nidx]; nidx++)
     ;
-  newnames = (char **)xmalloc ((nidx + 1) * (sizeof (char *)));
+  newnames = alloc_array (nidx + 1);
 #ifdef NO_FORCE_FIGNORE
-  oldnames = (char **)xmalloc ((nidx - 1) * (sizeof (char *)));
+  oldnames = alloc_array (nidx - 1);
   oidx = 0;
 #endif
 
@@ -1551,10 +1704,24 @@ name_is_acceptable (name)
   return (1);
 }
 
+#if 0
+static int
+ignore_dot_names (name)
+     char *name;
+{
+  return (name[0] != '.');
+}
+#endif
+
 static void
 filename_completion_ignore (names)
      char **names;
 {
+#if 0
+  if (glob_dot_filenames == 0)
+    _ignore_completion_names (names, ignore_dot_names);
+#endif
+
   setup_ignore_patterns (&fignore);
 
   if (fignore.num_ignores == 0)
@@ -1589,20 +1756,50 @@ bash_ignore_filenames (names)
   _ignore_completion_names (names, test_for_directory);
 }
 
+static int
+return_zero (name)
+     char *name;
+{
+  return 0;
+}
+
+static void
+bash_ignore_everything (names)
+     char **names;
+{
+  _ignore_completion_names (names, return_zero);
+}
+
 /* Handle symbolic link references and other directory name
    expansions while hacking completion. */
 static int
 bash_directory_completion_hook (dirname)
      char **dirname;
 {
-  char *local_dirname, *t;
-  int return_value = 0;
+  char *local_dirname, *new_dirname, *t;
+  int return_value, should_expand_dirname;
   WORD_LIST *wl;
 
+  return_value = should_expand_dirname = 0;
   local_dirname = *dirname;
-  if (strchr (local_dirname, '$') || strchr (local_dirname, '`'))
+
+#if 0
+  should_expand_dirname = strchr (local_dirname, '$') || strchr (local_dirname, '`');
+#else
+  if (strchr (local_dirname, '$'))
+    should_expand_dirname = 1;
+  else
     {
-      wl = expand_string (local_dirname, 0);
+      t = strchr (local_dirname, '`');
+      if (t && unclosed_pair (local_dirname, strlen (local_dirname), "`") == 0)
+       should_expand_dirname = 1;
+    }
+#endif
+
+  if (should_expand_dirname)  
+    {
+      new_dirname = savestring (local_dirname);
+      wl = expand_string (new_dirname, 0);
       if (wl)
        {
          *dirname = string_list (wl);
@@ -1610,11 +1807,13 @@ bash_directory_completion_hook (dirname)
             actually expanded something. */
          return_value = STREQ (local_dirname, *dirname) == 0;
          free (local_dirname);
+         free (new_dirname);
          dispose_words (wl);
          local_dirname = *dirname;
        }
       else
        {
+         free (new_dirname);
          free (local_dirname);
          *dirname = xmalloc (1);
          **dirname = '\0';
@@ -1950,9 +2149,9 @@ 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. */
+/* A function to strip unquoted quote characters (single quotes, double
+   quotes, and backslashes).  It allows single quotes to appear
+   within double quotes, and vice versa.  It should be smarter. */
 static char *
 bash_dequote_filename (text, quote_char)
      char *text;
@@ -2052,8 +2251,14 @@ bash_quote_filename (s, rtype, qcp)
 
   cs = completion_quoting_style;
   /* Might need to modify the default completion style based on *qcp,
-     since it's set to any user-provided opening quote. */
-  if (*qcp == '"')
+     since it's set to any user-provided opening quote.  We also change
+     to single-quoting if there is no user-provided opening quote and
+     the word being completed contains newlines, since those are not
+     quoted correctly using backslashes (a backslash-newline pair is
+     special to the shell parser). */
+  if (*qcp == '\0' && cs == COMPLETE_BSQUOTE && strchr (mtext, '\n'))
+    cs = COMPLETE_SQUOTE;
+  else if (*qcp == '"')
     cs = COMPLETE_DQUOTE;
   else if (*qcp == '\'')
     cs = COMPLETE_SQUOTE;
@@ -2108,4 +2313,186 @@ bash_quote_filename (s, rtype, qcp)
   return ret;
 }
 
+/* Support for binding readline key sequences to Unix commands. */
+static Keymap cmd_xmap;
+
+static int
+bash_execute_unix_command (count, key)
+     int count;        /* ignored */
+     int key;
+{
+  Keymap ckmap;                /* current keymap */
+  Keymap xkmap;                /* unix command executing keymap */
+  register int i;
+  char *cmd;
+
+  /* First, we need to find the right command to execute.  This is tricky,
+     because we might have already indirected into another keymap. */
+  ckmap = rl_get_keymap ();
+  if (ckmap != rl_executing_keymap)
+    {
+      /* bogus.  we have to search.  only handle one level of indirection. */
+      for (i = 0; i < KEYMAP_SIZE; i++)
+       {
+         if (ckmap[i].type == ISKMAP && (Keymap)ckmap[i].function == rl_executing_keymap)
+           break;
+       }
+      if (i < KEYMAP_SIZE)
+        xkmap = (Keymap)cmd_xmap[i].function;
+      else
+       {
+         crlf ();
+          internal_error ("bash_execute_unix_command: cannot find keymap for command");
+          rl_forced_update_display ();
+          return 1;
+       }
+    }
+  else
+    xkmap = cmd_xmap;
+
+  cmd = (char *)xkmap[key].function;
+
+  if (cmd == 0)
+    {
+      ding ();
+      return 1;
+    }
+
+  crlf ();     /* move to a new line */
+
+  cmd = savestring (cmd);
+  parse_and_execute (cmd, "bash_execute_unix_command", 0);
+
+  /* and restore the readline buffer and display after command execution. */
+  rl_forced_update_display ();
+  return 0;
+}
+
+static void
+init_unix_command_map ()
+{
+  cmd_xmap = rl_make_bare_keymap ();
+}
+
+static int
+isolate_sequence (string, ind, need_dquote, startp)
+     char *string;
+     int ind, need_dquote, *startp;
+{
+  register int i;
+  int c, passc, delim;
+
+  for (i = ind; string[i] && whitespace (string[i]); i++)
+    ;
+  /* NEED_DQUOTE means that the first non-white character *must* be `"'. */
+  if (need_dquote && string[i] != '"')
+    {
+      builtin_error ("%s: first non-whitespace character is not `\"'", string);
+      return -1;
+    }
+
+  /* We can have delimited strings even if NEED_DQUOTE == 0, like the command
+     string to bind the key sequence to. */
+  delim = (string[i] == '"' || string[i] == '\'') ? string[i] : 0;
+    
+  if (startp)
+    *startp = delim ? ++i : i;
+
+  for (passc = 0; c = string[i]; i++)
+    {
+      if (passc)
+       {
+         passc = 0;
+         continue;
+       }
+      if (c == '\\')
+       {
+         passc++;
+         continue;
+       }
+      if (c == delim)
+        break;
+    }
+
+  if (delim && string[i] != delim)
+    {
+      builtin_error ("%s: no closing `%c'", string, delim);
+      return -1;
+    }
+
+  return i;
+}
+
+int
+bind_keyseq_to_unix_command (line)
+     char *line;
+{
+  Keymap kmap;
+  char *kseq, *value;
+  int i, kstart, len, ok;
+
+  if (cmd_xmap == 0)
+    init_unix_command_map ();
+
+  kmap = rl_get_keymap ();
+
+  /* We duplicate some of the work done by rl_parse_and_bind here, but
+     this code only has to handle `"keyseq": ["]command["]' and can
+     generate an error for anything else. */
+  i = isolate_sequence (line, 0, 1, &kstart);
+  if (i < 0)
+    return -1;
+
+  /* Create the key sequence string to pass to rl_generic_bind */
+  kseq = substring (line, kstart, i);
+
+  for ( ; line[i] && line[i] != ':'; i++)
+    ;
+  if (line[i] != ':')
+    {
+      builtin_error ("%s: missing colon separator", line);
+      return -1;
+    }
+
+  i = isolate_sequence (line, i + 1, 0, &kstart);
+  if (i < 0)
+    return -1;
+
+  /* Create the value string containing the command to execute. */
+  value = substring (line, kstart, i);
+
+  /* Save the command to execute and the key sequence in the CMD_XMAP */
+  rl_generic_bind (ISMACR, kseq, value, cmd_xmap);
+
+  /* and bind the key sequence in the current keymap to a function that
+     understands how to execute from CMD_XMAP */
+  rl_set_key (kseq, (Function *)bash_execute_unix_command, kmap);
+  
+  return 0;
+}
+
+/* Used by the programmable completion code.  Complete TEXT as a filename,
+   but return only directories as matches.  Dequotes the filename before
+   attempting to find matches. */
+char **
+bash_directory_completion_matches (text)
+     char *text;
+{
+  char **m1;
+  char *dfn;
+  int qc;
+
+  qc = (text[0] == '"' || text[0] == '\'') ? text[0] : 0;
+  dfn = bash_dequote_filename (text, qc);
+  m1 = completion_matches (dfn, filename_completion_function);
+  free (dfn);
+
+  if (m1 == 0 || m1[0] == 0)
+    return m1;
+  /* We don't bother recomputing the lcd of the matches, because it will just
+     get thrown away by the programmable completion code and recomputed
+     later. */
+  (void)bash_ignore_filenames (m1);
+  return m1;
+}
 #endif /* READLINE */