]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - pcomplete.c
Bash-4.3 patch 7
[thirdparty/bash.git] / pcomplete.c
index a1fc04545ac14ed854766c9219f3e8ab15b7a43a..a3327ed533ecb6da1db4063fe94a20452dcf6fa1 100644 (file)
@@ -1,23 +1,22 @@
-/* pcomplete.c - functions to generate lists of matches for programmable
-                completion. */
+/* pcomplete.c - functions to generate lists of matches for programmable completion. */
 
-/* Copyright (C) 1999 Free Software Foundation, Inc.
+/* Copyright (C) 1999-2012 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
-   Bash is free software; you can redistribute it and/or modify it under
-   the terms of the GNU General Public License as published by the Free
-   Software Foundation; either version 2, or (at your option) any later
-   version.
+   Bash is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
 
-   Bash is distributed in the hope that it will be useful, but WITHOUT ANY
-   WARRANTY; without even the implied warranty of MERCHANTABILITY or
-   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-   for more details.
+   Bash is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
-   You should have received a copy of the GNU General Public License along
-   with Bash; see the file COPYING.  If not, write to the Free Software
-   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 #include <config.h>
 
 #if defined (PREFER_STDARG)
 #  include <stdarg.h>
 #else
-#  if defined (PREFER_VARARGS)
-#    include <varargs.h>
-#  endif
+#  include <varargs.h>
 #endif
 
+#include <sys/time.h>
+
 #include <stdio.h>
 #include "bashansi.h"
+#include "bashintl.h"
 
 #include "shell.h"
 #include "pcomplete.h"
 #include "alias.h"
 #include "bashline.h"
+#include "execute_cmd.h"
 #include "pathexp.h"
 
 #if defined (JOB_CONTROL)
 #  include "trap.h"
 #endif
 
+#include "shmbutil.h"
+
 #include "builtins.h"
 #include "builtins/common.h"
+#include "builtins/builtext.h"
 
 #include <glob/glob.h>
-#include <glob/fnmatch.h>
+#include <glob/strmatch.h>
 
 #include <readline/rlconf.h>
 #include <readline/readline.h>
 #include <readline/history.h>
 
+#define PCOMP_RETRYFAIL        256
+
 #ifdef STRDUP
 #  undef STRDUP
 #endif
@@ -78,30 +84,74 @@ typedef SHELL_VAR **SVFUNC ();
 extern char *strpbrk __P((char *, char *));
 #endif
 
-extern int rl_filename_completion_desired;
 extern int array_needs_making;
 extern STRING_INT_ALIST word_token_alist[];
 extern char *signal_names[];
+extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin;
+
+#if defined (DEBUG)
+#if defined (PREFER_STDARG)
+static void debug_printf (const char *, ...)  __attribute__((__format__ (printf, 1, 2)));
+#endif
+#endif /* DEBUG */
+
+static int it_init_joblist __P((ITEMLIST *, int));
+
+static int it_init_aliases __P((ITEMLIST *));
+static int it_init_arrayvars __P((ITEMLIST *));
+static int it_init_bindings __P((ITEMLIST *));
+static int it_init_builtins __P((ITEMLIST *));
+static int it_init_disabled __P((ITEMLIST *));
+static int it_init_enabled __P((ITEMLIST *));
+static int it_init_exported __P((ITEMLIST *));
+static int it_init_functions __P((ITEMLIST *));
+static int it_init_helptopics __P((ITEMLIST *));
+static int it_init_hostnames __P((ITEMLIST *));
+static int it_init_jobs __P((ITEMLIST *));
+static int it_init_running __P((ITEMLIST *));
+static int it_init_stopped __P((ITEMLIST *));
+static int it_init_keywords __P((ITEMLIST *));
+static int it_init_signals __P((ITEMLIST *));
+static int it_init_variables __P((ITEMLIST *));
+static int it_init_setopts __P((ITEMLIST *));
+static int it_init_shopts __P((ITEMLIST *));
+
+static int shouldexp_filterpat __P((char *));
+static char *preproc_filterpat __P((char *, char *));
+
+static void init_itemlist_from_varlist __P((ITEMLIST *, SVFUNC *));
+
+static STRINGLIST *gen_matches_from_itemlist __P((ITEMLIST *, const char *));
+static STRINGLIST *gen_action_completions __P((COMPSPEC *, const char *));
+static STRINGLIST *gen_globpat_matches __P((COMPSPEC *, const char *));
+static STRINGLIST *gen_wordlist_matches __P((COMPSPEC *, const char *));
+static STRINGLIST *gen_shell_function_matches __P((COMPSPEC *, const char *,
+                                                  const char *,
+                                                  char *, int, WORD_LIST *,
+                                                  int, int, int *));
+static STRINGLIST *gen_command_matches __P((COMPSPEC *, const char *,
+                                           const char *,
+                                           char *, int, WORD_LIST *,
+                                           int, int));
+
+static STRINGLIST *gen_progcomp_completions __P((const char *, const char *,
+                                                const char *,
+                                                int, int, int *, int *,
+                                                COMPSPEC **));
+
+static char *pcomp_filename_completion_function __P((const char *, int));
 
-static int it_init_aliases ();
-static int it_init_arrayvars ();
-static int it_init_bindings ();
-static int it_init_builtins ();
-static int it_init_disabled ();
-static int it_init_enabled ();
-static int it_init_exported ();
-static int it_init_functions ();
-static int it_init_hostnames ();
-static int it_init_jobs ();
-static int it_init_running ();
-static int it_init_stopped ();
-static int it_init_keywords ();
-static int it_init_signals ();
-static int it_init_variables ();
-static int it_init_setopts ();
-static int it_init_shopts ();
+#if defined (ARRAY_VARS)
+static SHELL_VAR *bind_comp_words __P((WORD_LIST *));
+#endif
+static void bind_compfunc_variables __P((char *, int, WORD_LIST *, int, int));
+static void unbind_compfunc_variables __P((int));
+static WORD_LIST *build_arg_list __P((char *, const char *, const char *, WORD_LIST *, int));
+static WORD_LIST *command_line_to_word_list __P((char *, int, int, int *, int *));
 
+#ifdef DEBUG
 static int progcomp_debug = 0;
+#endif
 
 int prog_completion_enabled = 1;
 
@@ -115,33 +165,27 @@ ITEMLIST it_directories = { LIST_DYNAMIC };       /* unused */
 ITEMLIST it_disabled = { 0, it_init_disabled, (STRINGLIST *)0 };
 ITEMLIST it_enabled = { 0, it_init_enabled, (STRINGLIST *)0 };
 ITEMLIST it_exports  = { LIST_DYNAMIC, it_init_exported, (STRINGLIST *)0 };
-ITEMLIST it_files = { LIST_DYNAMIC };  /* unused */
+ITEMLIST it_files = { LIST_DYNAMIC };          /* unused */
 ITEMLIST it_functions  = { 0, it_init_functions, (STRINGLIST *)0 };
+ITEMLIST it_helptopics  = { 0, it_init_helptopics, (STRINGLIST *)0 };
 ITEMLIST it_hostnames  = { LIST_DYNAMIC, it_init_hostnames, (STRINGLIST *)0 };
-ITEMLIST it_jobs = { LIST_DYNAMIC, it_init_jobs, (STRINGLIST *)0 };;
+ITEMLIST it_groups = { LIST_DYNAMIC };         /* unused */
+ITEMLIST it_jobs = { LIST_DYNAMIC, it_init_jobs, (STRINGLIST *)0 };
 ITEMLIST it_keywords = { 0, it_init_keywords, (STRINGLIST *)0 };
 ITEMLIST it_running = { LIST_DYNAMIC, it_init_running, (STRINGLIST *)0 };
+ITEMLIST it_services = { LIST_DYNAMIC };       /* unused */
 ITEMLIST it_setopts = { 0, it_init_setopts, (STRINGLIST *)0 };
 ITEMLIST it_shopts = { 0, it_init_shopts, (STRINGLIST *)0 };
 ITEMLIST it_signals = { 0, it_init_signals, (STRINGLIST *)0 };
 ITEMLIST it_stopped = { LIST_DYNAMIC, it_init_stopped, (STRINGLIST *)0 };
-ITEMLIST it_users = { LIST_DYNAMIC };  /* unused */
+ITEMLIST it_users = { LIST_DYNAMIC };          /* unused */
 ITEMLIST it_variables = { LIST_DYNAMIC, it_init_variables, (STRINGLIST *)0 };
 
-/* Debugging code */
-#if !defined (USE_VARARGS)
-static void
-debug_printf (format, arg1, arg2, arg3, arg4, arg5)
-     char *format;
-{
-  if (progcomp_debug == 0)
-    return;
+COMPSPEC *pcomp_curcs;
+const char *pcomp_curcmd;
 
-  fprintf (stdout, format, arg1, arg2, arg3, arg4, arg5);
-  fprintf (stdout, "\n");
-  rl_on_new_line ();
-}
-#else
+#ifdef DEBUG
+/* Debugging code */
 static void
 #if defined (PREFER_STDARG)
 debug_printf (const char *format, ...)
@@ -156,11 +200,7 @@ debug_printf (format, va_alist)
   if (progcomp_debug == 0)
     return;
 
-#if defined (PREFER_STDARG)
-  va_start (args, format);
-#else
-  va_start (args);
-#endif
+  SH_VA_START (args, format);
 
   fprintf (stdout, "DEBUG: ");
   vfprintf (stdout, format, args);
@@ -170,7 +210,7 @@ debug_printf (format, va_alist)
 
   va_end (args);
 }
-#endif /* USE_VARARGS */
+#endif
 
 /* Functions to manage the item lists */
 
@@ -200,7 +240,7 @@ clean_itemlist (itp)
   if (sl)
     {
       if ((itp->flags & (LIST_DONTFREEMEMBERS|LIST_DONTFREE)) == 0)
-       free_array_members (sl->list);
+       strvec_flush (sl->list);
       if ((itp->flags & LIST_DONTFREE) == 0)
        free (sl->list);
       free (sl);
@@ -209,132 +249,6 @@ clean_itemlist (itp)
   itp->flags &= ~(LIST_DONTFREE|LIST_DONTFREEMEMBERS|LIST_INITIALIZED|LIST_DIRTY);
 }
 
-/* Functions to manage the string lists -- lists of matches.  These should
-   really be moved to a separate file. */
-
-/* Allocate a new STRINGLIST, with room for N strings. */
-STRINGLIST *
-alloc_stringlist (n)
-     int n;
-{
-  STRINGLIST *ret;
-  register int i;
-
-  ret = (STRINGLIST *)xmalloc (sizeof (STRINGLIST));
-  if (n)
-    {
-      ret->list = alloc_array (n+1);
-      ret->list_size = n;
-      for (i = 0; i < n; i++)
-       ret->list[i] = (char *)NULL;
-    }
-  else
-    {
-      ret->list = (char **)NULL;
-      ret->list_size = 0;
-    }
-  ret->list_len = 0;
-  return ret;
-}
-
-STRINGLIST *
-realloc_stringlist (sl, n)
-     STRINGLIST *sl;
-     int n;
-{
-  register int i;
-
-  if (n > sl->list_size)
-    {
-      sl->list = (char **)xrealloc (sl->list, (n+1) * sizeof (char *));
-      for (i = sl->list_size; i <= n; i++)
-       sl->list[i] = (char *)NULL;
-      sl->list_size = n;
-    }
-  return sl;
-}
-  
-void
-free_stringlist (sl)
-     STRINGLIST *sl;
-{
-  if (sl == 0)
-    return;
-  if (sl->list)
-    free_array (sl->list);
-  free (sl);
-}
-
-STRINGLIST *
-copy_stringlist (sl)
-     STRINGLIST *sl;
-{
-  STRINGLIST *new;
-  register int i;
-
-  new = alloc_stringlist (sl->list_size);
-  /* I'd like to use copy_array, but that doesn't copy everything. */
-  if (sl->list)
-    {
-      for (i = 0; i < sl->list_size; i++)
-       new->list[i] = STRDUP (sl->list[i]);
-    }
-  new->list_size = sl->list_size;
-  new->list_len = sl->list_len;
-  /* just being careful */
-  if (new->list)
-    new->list[new->list_len] = (char *)NULL;
-  return new;
-}
-
-/* Return a new STRINGLIST with everything from M1 and M2. */
-
-STRINGLIST *
-merge_stringlists (m1, m2)
-     STRINGLIST *m1, *m2;
-{
-  STRINGLIST *sl;
-  int i, n, l1, l2;
-
-  l1 = m1 ? m1->list_len : 0;
-  l2 = m2 ? m2->list_len : 0;
-
-  sl = alloc_stringlist (l1 + l2 + 1);
-  for (i = n = 0; i < l1; i++, n++)
-    sl->list[n] = STRDUP (m1->list[i]);
-  for (i = 0; i < l2; i++, n++)
-    sl->list[n] = STRDUP (m2->list[i]);
-  sl->list_len = n;
-  sl->list[n] = (char *)NULL;
-}
-
-/* Make STRINGLIST M1 contain everything in M1 and M2. */
-STRINGLIST *
-append_stringlist (m1, m2)
-     STRINGLIST *m1, *m2;
-{
-  register int i, n, len1, len2;
-
-  if (m1 == 0)
-    {
-      m1 = copy_stringlist (m2);
-      return m1;
-    }
-
-  len1 = m1->list_len;
-  len2 = m2 ? m2->list_len : 0;
-
-  if (len2)
-    {
-      m1 = realloc_stringlist (m1, len1 + len2 + 1);
-      for (i = 0, n = len1; i < len2; i++, n++)
-       m1->list[n] = STRDUP (m2->list[i]);
-      m1->list[n] = (char *)NULL;
-      m1->list_len = n;
-    }
-
-  return m1;
-}
 
 static int
 shouldexp_filterpat (s)
@@ -388,10 +302,10 @@ filter_stringlist (sl, filterpat, text)
   not = (npat[0] == '!');
   t = not ? npat + 1 : npat;
 
-  ret = alloc_stringlist (sl->list_size);
+  ret = strlist_create (sl->list_size);
   for (i = 0; i < sl->list_len; i++)
     {
-      m = fnmatch (t, sl->list[i], FNMATCH_EXTFLAG);
+      m = strmatch (t, sl->list[i], FNMATCH_EXTFLAG);
       if ((not && m == FNM_NOMATCH) || (not == 0 && m != FNM_NOMATCH))
        free (sl->list[i]);
       else
@@ -405,55 +319,8 @@ filter_stringlist (sl, filterpat, text)
   return ret;
 }
 
-STRINGLIST *
-prefix_suffix_stringlist (sl, prefix, suffix)
-     STRINGLIST *sl;
-     char *prefix, *suffix;
-{
-  int plen, slen, tlen, llen, i;
-  char *t;
-
-  if (sl == 0 || sl->list == 0 || sl->list_len == 0)
-    return sl;
-
-  plen = STRLEN (prefix);
-  slen = STRLEN (suffix);
-
-  if (plen == 0 && slen == 0)
-    return (sl);
-
-  for (i = 0; i < sl->list_len; i++)
-    {
-      llen = STRLEN (sl->list[i]);
-      tlen = plen + llen + slen + 1;
-      t = xmalloc (tlen + 1);
-      if (plen)
-       strcpy (t, prefix);
-      strcpy (t + plen, sl->list[i]);
-      if (slen)
-       strcpy (t + plen + llen, suffix);
-      free (sl->list[i]);
-      sl->list[i] = t;
-    }
-
-  return (sl);  
-}
-   
-void
-print_stringlist (sl, prefix)
-     STRINGLIST *sl;
-     char *prefix;
-{
-  register int i;
-
-  if (sl == 0)
-    return;
-  for (i = 0; i < sl->list_len; i++)
-    printf ("%s%s\n", prefix ? prefix : "", sl->list[i]);
-}
-
-/* Turn an array of strings returned by completion_matches into a STRINGLIST.
-   This understands how completion_matches sets matches[0] (the lcd of the
+/* Turn an array of strings returned by rl_completion_matches into a STRINGLIST.
+   This understands how rl_completion_matches sets matches[0] (the lcd of the
    strings in the list, unless it's the only match). */
 STRINGLIST *
 completions_to_stringlist (matches)
@@ -462,8 +329,8 @@ completions_to_stringlist (matches)
   STRINGLIST *sl;
   int mlen, i, n;
 
-  mlen = (matches == 0) ? 0 : array_len (matches);
-  sl = alloc_stringlist (mlen + 1);
+  mlen = (matches == 0) ? 0 : strvec_len (matches);
+  sl = strlist_create (mlen + 1);
 
   if (matches == 0 || matches[0] == 0)
     return sl;
@@ -491,27 +358,28 @@ it_init_aliases (itp)
      ITEMLIST *itp;
 {
 #ifdef ALIAS
-  alias_t **aliases;
+  alias_t **alias_list;
   register int i, n;
   STRINGLIST *sl;
 
-  aliases = all_aliases ();
-  if (aliases == 0)
+  alias_list = all_aliases ();
+  if (alias_list == 0)
     {
       itp->slist = (STRINGLIST *)NULL;
       return 0;
     }
-  for (n = 0; aliases[n]; n++)
+  for (n = 0; alias_list[n]; n++)
     ;
-  sl = alloc_stringlist (n+1);
+  sl = strlist_create (n+1);
   for (i = 0; i < n; i++)
-    sl->list[i] = STRDUP (aliases[i]->name);
+    sl->list[i] = STRDUP (alias_list[i]->name);
   sl->list[n] = (char *)NULL;
   sl->list_size = sl->list_len = n;
   itp->slist = sl;
 #else
   itp->slist = (STRINGLIST *)NULL;
 #endif
+  free (alias_list);
   return 1;
 }
 
@@ -525,9 +393,14 @@ init_itemlist_from_varlist (itp, svfunc)
   register int i, n;
 
   vlist = (*svfunc) ();
+  if (vlist == 0)
+    {
+      itp->slist = (STRINGLIST *)NULL;
+      return;
+    }    
   for (n = 0; vlist[n]; n++)
     ;
-  sl = alloc_stringlist (n+1);
+  sl = strlist_create (n+1);
   for (i = 0; i < n; i++)
     sl->list[i] = savestring (vlist[i]->name);
   sl->list[sl->list_len = n] = (char *)NULL;
@@ -554,11 +427,11 @@ it_init_bindings (itp)
   STRINGLIST *sl;
 
   /* rl_funmap_names allocates blist, but not its members */
-  blist = rl_funmap_names ();
-  sl = alloc_stringlist (0);
+  blist = (char **)rl_funmap_names (); /* XXX fix const later */
+  sl = strlist_create (0);
   sl->list = blist;
   sl->list_size = 0;
-  sl->list_len = array_len (sl->list);
+  sl->list_len = strvec_len (sl->list);
   itp->flags |= LIST_DONTFREEMEMBERS;
   itp->slist = sl;
 
@@ -570,10 +443,9 @@ it_init_builtins (itp)
      ITEMLIST *itp;
 {
   STRINGLIST *sl;
-  char **list;
   register int i, n;
 
-  sl = alloc_stringlist (num_shell_builtins);
+  sl = strlist_create (num_shell_builtins);
   for (i = n = 0; i < num_shell_builtins; i++)
     if (shell_builtins[i].function)
       sl->list[n++] = shell_builtins[i].name;
@@ -588,10 +460,9 @@ it_init_enabled (itp)
      ITEMLIST *itp;
 {
   STRINGLIST *sl;
-  char **list;
   register int i, n;
 
-  sl = alloc_stringlist (num_shell_builtins);
+  sl = strlist_create (num_shell_builtins);
   for (i = n = 0; i < num_shell_builtins; i++)
     {
       if (shell_builtins[i].function && (shell_builtins[i].flags & BUILTIN_ENABLED))
@@ -608,10 +479,9 @@ it_init_disabled (itp)
      ITEMLIST *itp;
 {
   STRINGLIST *sl;
-  char **list;
   register int i, n;
 
-  sl = alloc_stringlist (num_shell_builtins);
+  sl = strlist_create (num_shell_builtins);
   for (i = n = 0; i < num_shell_builtins; i++)
     {
       if (shell_builtins[i].function && ((shell_builtins[i].flags & BUILTIN_ENABLED) == 0))
@@ -639,15 +509,33 @@ it_init_functions (itp)
   return 0;
 }
 
+/* Like it_init_builtins, but includes everything the help builtin looks at,
+   not just builtins with an active implementing function. */
+static int
+it_init_helptopics (itp)
+     ITEMLIST *itp;
+{
+  STRINGLIST *sl;
+  register int i, n;
+
+  sl = strlist_create (num_shell_builtins);
+  for (i = n = 0; i < num_shell_builtins; i++)
+    sl->list[n++] = shell_builtins[i].name;
+  sl->list[sl->list_len = n] = (char *)NULL;
+  itp->flags |= LIST_DONTFREEMEMBERS;
+  itp->slist = sl;
+  return 0;
+}
+
 static int
 it_init_hostnames (itp)
      ITEMLIST *itp;
 {
   STRINGLIST *sl;
 
-  sl = alloc_stringlist (0);
+  sl = strlist_create (0);
   sl->list = get_hostname_list ();
-  sl->list_len = sl->list ? array_len (sl->list) : 0;
+  sl->list_len = sl->list ? strvec_len (sl->list) : 0;
   sl->list_size = sl->list_len;
   itp->slist = sl;
   itp->flags |= LIST_DONTFREEMEMBERS|LIST_DONTFREE;
@@ -661,29 +549,32 @@ it_init_joblist (itp, jstate)
 {
 #if defined (JOB_CONTROL)
   STRINGLIST *sl;
-  register int i, n;
+  register int i;
   register PROCESS *p;
   char *s, *t;
-  JOB_STATE js;
+  JOB *j;
+  JOB_STATE ws;                /* wanted state */
 
+  ws = JNONE;
   if (jstate == 0)
-    js = JRUNNING;
+    ws = JRUNNING;
   else if (jstate == 1)
-    js = JSTOPPED;
+    ws = JSTOPPED;
 
-  sl = alloc_stringlist (job_slots);
-  for (i = job_slots - 1; i >= 0; i--)
+  sl = strlist_create (js.j_jobslots);
+  for (i = js.j_jobslots - 1; i >= 0; i--)
     {
-      if (jobs[i] == 0)
+      j = get_job_by_jid (i);
+      if (j == 0)
        continue;
-      p = jobs[i]->pipe;
-      if (jstate == -1 || JOBSTATE(i) == js)
+      p = j->pipe;
+      if (jstate == -1 || JOBSTATE(i) == ws)
        {
          s = savestring (p->command);
          t = strpbrk (s, " \t\n");
          if (t)
            *t = '\0';
-       sl->list[sl->list_len++] = s;
+         sl->list[sl->list_len++] = s;
        }
     }
   itp->slist = sl;
@@ -723,7 +614,7 @@ it_init_keywords (itp)
 
   for (n = 0; word_token_alist[n].word; n++)
     ;
-  sl = alloc_stringlist (n);
+  sl = strlist_create (n);
   for (i = 0; i < n; i++)
     sl->list[i] = word_token_alist[i].word;
   sl->list[sl->list_len = i] = (char *)NULL;
@@ -738,9 +629,9 @@ it_init_signals (itp)
 {
   STRINGLIST *sl;
 
-  sl = alloc_stringlist (0);
+  sl = strlist_create (0);
   sl->list = signal_names;
-  sl->list_len = array_len (sl->list);
+  sl->list_len = strvec_len (sl->list);
   itp->flags |= LIST_DONTFREE;
   itp->slist = sl;
   return 0;
@@ -760,9 +651,9 @@ it_init_setopts (itp)
 {
   STRINGLIST *sl;
 
-  sl = alloc_stringlist (0);
+  sl = strlist_create (0);
   sl->list = get_minus_o_opts ();
-  sl->list_len = array_len (sl->list);
+  sl->list_len = strvec_len (sl->list);
   itp->slist = sl;
   itp->flags |= LIST_DONTFREEMEMBERS;
   return 0;
@@ -774,9 +665,9 @@ it_init_shopts (itp)
 {
   STRINGLIST *sl;
 
-  sl = alloc_stringlist (0);
+  sl = strlist_create (0);
   sl->list = get_shopt_options ();
-  sl->list_len = array_len (sl->list);
+  sl->list_len = strvec_len (sl->list);
   itp->slist = sl;
   itp->flags |= LIST_DONTFREEMEMBERS;
   return 0;
@@ -785,59 +676,88 @@ it_init_shopts (itp)
 /* Generate a list of all matches for TEXT using the STRINGLIST in itp->slist
    as the list of possibilities.  If the itemlist has been marked dirty or
    it should be regenerated every time, destroy the old STRINGLIST and make a
-   new one before trying the match. */
+   new one before trying the match.  TEXT is dequoted before attempting a
+   match. */
 static STRINGLIST *
 gen_matches_from_itemlist (itp, text)
      ITEMLIST *itp;
-     char *text;
+     const char *text;
 {
   STRINGLIST *ret, *sl;
   int tlen, i, n;
+  char *ntxt;
 
   if ((itp->flags & (LIST_DIRTY|LIST_DYNAMIC)) ||
       (itp->flags & LIST_INITIALIZED) == 0)
     {
-      if (itp->flags & (LIST_DIRTY | LIST_DYNAMIC))
+      if (itp->flags & (LIST_DIRTY|LIST_DYNAMIC))
        clean_itemlist (itp);
       if ((itp->flags & LIST_INITIALIZED) == 0)
        initialize_itemlist (itp);
     }
-  ret = alloc_stringlist (itp->slist->list_len+1);
+  if (itp->slist == 0)
+    return ((STRINGLIST *)NULL);
+  ret = strlist_create (itp->slist->list_len+1);
   sl = itp->slist;
-  tlen = STRLEN (text);
+
+  ntxt = bash_dequote_text (text);
+  tlen = STRLEN (ntxt);
+
   for (i = n = 0; i < sl->list_len; i++)
     {
-      if (tlen == 0 || STREQN (sl->list[i], text, tlen))
+      if (tlen == 0 || STREQN (sl->list[i], ntxt, tlen))
        ret->list[n++] = STRDUP (sl->list[i]);
     }
   ret->list[ret->list_len = n] = (char *)NULL;
+
+  FREE (ntxt);
   return ret;
 }
 
-/* A wrapper for filename_completion_function that dequotes the filename
+/* A wrapper for rl_filename_completion_function that dequotes the filename
    before attempting completions. */
 static char *
 pcomp_filename_completion_function (text, state)
-     char *text;
+     const char *text;
      int state;
 {
   static char *dfn;    /* dequoted filename */
   int qc;
+  int iscompgen, iscompleting;
 
   if (state == 0)
     {
       FREE (dfn);
       /* remove backslashes quoting special characters in filenames. */
-      if (rl_filename_dequoting_function)
+      /* There are roughly three paths we can follow to get here:
+               1.  complete -f
+               2.  compgen -f "$word" from a completion function
+               3.  compgen -f "$word" from the command line
+        They all need to be handled.
+
+        In the first two cases, readline will run the filename dequoting
+        function in rl_filename_completion_function if it found a filename
+        quoting character in the word to be completed
+        (rl_completion_found_quote).  We run the dequoting function here
+        if we're running compgen, we're not completing, and the
+        rl_filename_completion_function won't dequote the filename
+        (rl_completion_found_quote == 0). */
+      iscompgen = this_shell_builtin == compgen_builtin;
+      iscompleting = RL_ISSTATE (RL_STATE_COMPLETING);
+      if (iscompgen && iscompleting == 0 && rl_completion_found_quote == 0
+         && rl_filename_dequoting_function)
        {
-         qc = (text[0] == '"' || text[0] == '\'') ? text[0] : 0;
-         dfn = (*rl_filename_dequoting_function) (text, qc);
+         /* Use rl_completion_quote_character because any single or
+            double quotes have been removed by the time TEXT makes it
+            here, and we don't want to remove backslashes inside
+            quoted strings. */
+         dfn = (*rl_filename_dequoting_function) ((char *)text, rl_completion_quote_character);
        }
       else
        dfn = savestring (text);
     }
 
-  return (filename_completion_function (dfn, state));
+  return (rl_filename_completion_function (dfn, state));
 }
 
 #define GEN_COMPS(bmap, flag, it, text, glist, tlist) \
@@ -845,8 +765,11 @@ pcomp_filename_completion_function (text, state)
     if (bmap & flag) \
       { \
        tlist = gen_matches_from_itemlist (it, text); \
-       glist = append_stringlist (glist, tlist); \
-       free_stringlist (tlist); \
+       if (tlist) \
+         { \
+           glist = strlist_append (glist, tlist); \
+           strlist_dispose (tlist); \
+         } \
       } \
   } while (0)
 
@@ -854,11 +777,11 @@ pcomp_filename_completion_function (text, state)
   do { \
     if (bmap & flag) \
       { \
-       cmatches = completion_matches (text, func); \
+       cmatches = rl_completion_matches (text, func); \
        tlist = completions_to_stringlist (cmatches); \
-       glist = append_stringlist (glist, tlist); \
-       free_array (cmatches); \
-       free_stringlist (tlist); \
+       glist = strlist_append (glist, tlist); \
+       strvec_dispose (cmatches); \
+       strlist_dispose (tlist); \
       } \
   } while (0)
 
@@ -867,11 +790,12 @@ pcomp_filename_completion_function (text, state)
 static STRINGLIST *
 gen_action_completions (cs, text)
      COMPSPEC *cs;
-     char *text;
+     const char *text;
 {
   STRINGLIST *ret, *tmatches;
-  char **cmatches;     /* from completion_matches ... */
+  char **cmatches;     /* from rl_completion_matches ... */
   unsigned long flags;
+  int t;
 
   ret = tmatches = (STRINGLIST *)NULL;
   flags = cs->actions;
@@ -884,6 +808,7 @@ gen_action_completions (cs, text)
   GEN_COMPS (flags, CA_ENABLED, &it_enabled, text, ret, tmatches);
   GEN_COMPS (flags, CA_EXPORT, &it_exports, text, ret, tmatches);
   GEN_COMPS (flags, CA_FUNCTION, &it_functions, text, ret, tmatches);
+  GEN_COMPS (flags, CA_HELPTOPIC, &it_helptopics, text, ret, tmatches);
   GEN_COMPS (flags, CA_HOSTNAME, &it_hostnames, text, ret, tmatches);
   GEN_COMPS (flags, CA_JOB, &it_jobs, text, ret, tmatches);
   GEN_COMPS (flags, CA_KEYWORD, &it_keywords, text, ret, tmatches);
@@ -896,16 +821,26 @@ gen_action_completions (cs, text)
 
   GEN_XCOMPS(flags, CA_COMMAND, text, command_word_completion_function, cmatches, ret, tmatches);
   GEN_XCOMPS(flags, CA_FILE, text, pcomp_filename_completion_function, cmatches, ret, tmatches);
-  GEN_XCOMPS(flags, CA_USER, text, username_completion_function, cmatches, ret, tmatches);
+  GEN_XCOMPS(flags, CA_USER, text, rl_username_completion_function, cmatches, ret, tmatches);
+  GEN_XCOMPS(flags, CA_GROUP, text, bash_groupname_completion_function, cmatches, ret, tmatches);
+  GEN_XCOMPS(flags, CA_SERVICE, text, bash_servicename_completion_function, cmatches, ret, tmatches);
 
   /* And lastly, the special case for directories */
   if (flags & CA_DIRECTORY)
     {
+      t = rl_filename_completion_desired;
+      rl_completion_mark_symlink_dirs = 1;     /* override user preference */
       cmatches = bash_directory_completion_matches (text);
+      /* If we did not want filename completion before this, and there are
+        no matches, turn off rl_filename_completion_desired so whatever
+        matches we get are not treated as filenames (it gets turned on by
+        rl_filename_completion_function unconditionally). */
+      if (t == 0 && cmatches == 0 && rl_filename_completion_desired == 1)
+        rl_filename_completion_desired = 0;
       tmatches = completions_to_stringlist (cmatches);
-      ret = append_stringlist (ret, tmatches);
-      free_array (cmatches);
-      free_stringlist (tmatches);
+      ret = strlist_append (ret, tmatches);
+      strvec_dispose (cmatches);
+      strlist_dispose (tmatches);
     }
 
   return ret;
@@ -919,17 +854,16 @@ gen_action_completions (cs, text)
 static STRINGLIST *
 gen_globpat_matches (cs, text)
       COMPSPEC *cs;
-      char *text;
+      const char *text;
 {
   STRINGLIST *sl;
-  char *t;
 
-  sl = alloc_stringlist (0);
-  sl->list = glob_filename (cs->globpat);
+  sl = strlist_create (0);
+  sl->list = glob_filename (cs->globpat, 0);
   if (GLOB_FAILED (sl->list))
     sl->list = (char **)NULL;
   if (sl->list)
-    sl->list_len = sl->list_size = array_len (sl->list);
+    sl->list_len = sl->list_size = strvec_len (sl->list);
   return sl;
 }
 
@@ -938,11 +872,12 @@ gen_globpat_matches (cs, text)
 static STRINGLIST *
 gen_wordlist_matches (cs, text)
      COMPSPEC *cs;
-     char *text;
+     const char *text;
 {
   WORD_LIST *l, *l2;
   STRINGLIST *sl;
   int nw, tlen;
+  char *ntxt;          /* dequoted TEXT to use in comparisons */
 
   if (cs->words == 0 || cs->words[0] == '\0')
     return ((STRINGLIST *)NULL);
@@ -950,8 +885,11 @@ gen_wordlist_matches (cs, text)
   /* This used to be a simple expand_string(cs->words, 0), but that won't
      do -- there's no way to split a simple list into individual words
      that way, since the shell semantics say that word splitting is done
-     only on the results of expansion. */
-  l = split_at_delims (cs->words, strlen (cs->words), (char *)NULL, -1, (int *)NULL, (int *)NULL);
+     only on the results of expansion.  split_at_delims also handles embedded
+     quoted strings and preserves the quotes for the expand_words_shellexp
+     function call that follows. */
+  /* XXX - this is where this function spends most of its time */
+  l = split_at_delims (cs->words, strlen (cs->words), (char *)NULL, -1, 0, (int *)NULL, (int *)NULL);
   if (l == 0)
     return ((STRINGLIST *)NULL);
   /* This will jump back to the top level if the expansion fails... */
@@ -959,16 +897,20 @@ gen_wordlist_matches (cs, text)
   dispose_words (l);
 
   nw = list_length (l2);
-  sl = alloc_stringlist (nw + 1);
-  tlen = STRLEN (text);
+  sl = strlist_create (nw + 1);
+
+  ntxt = bash_dequote_text (text);
+  tlen = STRLEN (ntxt);
 
   for (nw = 0, l = l2; l; l = l->next)
     {
-      if (tlen == 0 || STREQN (l->word->word, text, tlen))
-        sl->list[nw++] = STRDUP (l->word->word);
+      if (tlen == 0 || STREQN (l->word->word, ntxt, tlen))
+       sl->list[nw++] = STRDUP (l->word->word);
     }
   sl->list[sl->list_len = nw] = (char *)NULL;
 
+  dispose_words (l2);
+  FREE (ntxt);
   return sl;
 }
 
@@ -987,7 +929,9 @@ bind_comp_words (lwords)
     VUNSETATTR (v, att_readonly);
   if (array_p (v) == 0)
     v = convert_var_to_array (v);
-  v = assign_array_var_from_word_list (v, lwords);
+  v = assign_array_var_from_word_list (v, lwords, 0);
+
+  VUNSETATTR (v, att_invisible);
   return v;
 }
 #endif /* ARRAY_VARS */
@@ -999,28 +943,45 @@ bind_compfunc_variables (line, ind, lwords, cw, exported)
      WORD_LIST *lwords;
      int cw, exported;
 {
-  char ibuf[32];
+  char ibuf[INT_STRLEN_BOUND(int) + 1];
   char *value;
   SHELL_VAR *v;
+  size_t llen;
+  int c;
 
   /* Set the variables that the function expects while it executes.  Maybe
      these should be in the function environment (temporary_env). */
-  v = bind_variable ("COMP_LINE", line);
+  v = bind_variable ("COMP_LINE", line, 0);
   if (v && exported)
     VSETATTR(v, att_exported);
 
-  value = inttostr (ind, ibuf, 32);
+  /* Post bash-4.2: COMP_POINT is characters instead of bytes. */
+  c = line[ind];
+  line[ind] = '\0';
+  llen = MB_STRLEN (line);
+  line[ind] = c;
+  value = inttostr (llen, ibuf, sizeof(ibuf));
   v = bind_int_variable ("COMP_POINT", value);
   if (v && exported)
     VSETATTR(v, att_exported);
 
+  value = inttostr (rl_completion_type, ibuf, sizeof (ibuf));
+  v = bind_int_variable ("COMP_TYPE", value);
+  if (v && exported)
+    VSETATTR(v, att_exported);
+
+  value = inttostr (rl_completion_invoking_key, ibuf, sizeof (ibuf));
+  v = bind_int_variable ("COMP_KEY", value);
+  if (v && exported)
+    VSETATTR(v, att_exported);
+
   /* Since array variables can't be exported, we don't bother making the
      array of words. */
   if (exported == 0)
     {
 #ifdef ARRAY_VARS
       v = bind_comp_words (lwords);
-      value = inttostr (cw, ibuf, 32);
+      value = inttostr (cw, ibuf, sizeof(ibuf));
       bind_int_variable ("COMP_CWORD", value);
 #endif
     }
@@ -1032,11 +993,13 @@ static void
 unbind_compfunc_variables (exported)
      int exported;
 {
-  makunbound ("COMP_LINE", shell_variables);
-  makunbound ("COMP_POINT", shell_variables);
+  unbind_variable ("COMP_LINE");
+  unbind_variable ("COMP_POINT");
+  unbind_variable ("COMP_TYPE");
+  unbind_variable ("COMP_KEY");
 #ifdef ARRAY_VARS
-  makunbound ("COMP_WORDS", shell_variables);
-  makunbound ("COMP_CWORD", shell_variables);
+  unbind_variable ("COMP_WORDS");
+  unbind_variable ("COMP_CWORD");
 #endif
   if (exported)
     array_needs_making = 1;
@@ -1047,16 +1010,18 @@ unbind_compfunc_variables (exported)
 
        $0 == function or command being invoked
        $1 == command name
-       $2 = word to be completed (possibly null)
-       $3 = previous word
+       $2 == word to be completed (possibly null)
+       $3 == previous word
 
    Functions can access all of the words in the current command line
-   with the COMP_WORDS array.  External commands cannot. */
+   with the COMP_WORDS array.  External commands cannot; they have to
+   make do with the COMP_LINE and COMP_POINT variables. */
 
 static WORD_LIST *
-build_arg_list (cmd, text, lwords, ind)
+build_arg_list (cmd, cname, text, lwords, ind)
      char *cmd;
-     char *text;
+     const char *cname;
+     const char *text;
      WORD_LIST *lwords;
      int ind;
 {
@@ -1066,13 +1031,13 @@ build_arg_list (cmd, text, lwords, ind)
 
   ret = (WORD_LIST *)NULL;
   w = make_word (cmd);
-  ret = make_word_list (w, (WORD_LIST *)NULL);
+  ret = make_word_list (w, (WORD_LIST *)NULL); /* $0 */
 
-  w = (lwords && lwords->word) ? copy_word (lwords->word) : make_word ("");
+  w = make_word (cname);                       /* $1 */
   cl = ret->next = make_word_list (w, (WORD_LIST *)NULL);
 
   w = make_word (text);
-  cl->next = make_word_list (w, (WORD_LIST *)NULL);
+  cl->next = make_word_list (w, (WORD_LIST *)NULL);    /* $2 */
   cl = cl->next;
 
   /* Search lwords for current word */
@@ -1097,28 +1062,37 @@ build_arg_list (cmd, text, lwords, ind)
    variable, this does nothing if arrays are not compiled into the shell. */
 
 static STRINGLIST *
-gen_shell_function_matches (cs, text, line, ind, lwords, nw, cw)
+gen_shell_function_matches (cs, cmd, text, line, ind, lwords, nw, cw, foundp)
      COMPSPEC *cs;
-     char *text, *line;
+     const char *cmd;
+     const char *text;
+     char *line;
      int ind;
      WORD_LIST *lwords;
      int nw, cw;
+     int *foundp;
 {
   char *funcname;
   STRINGLIST *sl;
   SHELL_VAR *f, *v;
   WORD_LIST *cmdlist;
-  int fval;
+  int fval, found;
+  sh_parser_state_t ps;
+  sh_parser_state_t * restrict pps;
 #if defined (ARRAY_VARS)
   ARRAY *a;
 #endif
 
+  found = 0;
+  if (foundp)
+    *foundp = found;
+
   funcname = cs->funcname;
   f = find_function (funcname);
   if (f == 0)
     {
-      fprintf (stderr, "gen_shell_function_matches: function `%s' not found\n", funcname);
-      ding ();
+      internal_error (_("completion: function `%s' not found"), funcname);
+      rl_ding ();
       rl_on_new_line ();
       return ((STRINGLIST *)NULL);
     }
@@ -1131,10 +1105,26 @@ gen_shell_function_matches (cs, text, line, ind, lwords, nw, cw)
      1-based, while bash arrays are 0-based. */
   bind_compfunc_variables (line, ind, lwords, cw - 1, 0);
 
-  cmdlist = build_arg_list (funcname, text, lwords, cw);
-  
+  cmdlist = build_arg_list (funcname, cmd, text, lwords, cw);
+
+  pps = &ps;
+  save_parser_state (pps);
+  begin_unwind_frame ("gen-shell-function-matches");
+  add_unwind_protect (restore_parser_state, (char *)pps);
+  add_unwind_protect (dispose_words, (char *)cmdlist);
+  add_unwind_protect (unbind_compfunc_variables, (char *)0);
+
   fval = execute_shell_function (f, cmdlist);  
 
+  discard_unwind_frame ("gen-shell-function-matches");
+  restore_parser_state (pps);
+
+  found = fval != EX_NOTFOUND;
+  if (fval == EX_RETRYFAIL)
+    found |= PCOMP_RETRYFAIL;
+  if (foundp)
+    *foundp = found;
+
   /* Now clean up and destroy everything. */
   dispose_words (cmdlist);
   unbind_compfunc_variables (0);
@@ -1146,20 +1136,22 @@ gen_shell_function_matches (cs, text, line, ind, lwords, nw, cw)
   if (array_p (v) == 0)
     v = convert_var_to_array (v);
 
+  VUNSETATTR (v, att_invisible);
+
   a = array_cell (v);
-  if (a == 0 || array_empty (a))
+  if (found == 0 || (found & PCOMP_RETRYFAIL) || a == 0 || array_empty (a))
     sl = (STRINGLIST *)NULL;
   else
     {
       /* XXX - should we filter the list of completions so only those matching
         TEXT are returned?  Right now, we do not. */
-      sl = alloc_stringlist (0);
+      sl = strlist_create (0);
       sl->list = array_to_argv (a);
       sl->list_len = sl->list_size = array_num_elements (a);
     }
 
   /* XXX - should we unbind COMPREPLY here? */
-  makunbound ("COMPREPLY", shell_variables);
+  unbind_variable ("COMPREPLY");
 
   return (sl);
 #endif
@@ -1175,9 +1167,11 @@ gen_shell_function_matches (cs, text, line, ind, lwords, nw, cw)
    STRINGLIST from the results and return it. */
 
 static STRINGLIST *
-gen_command_matches (cs, text, line, ind, lwords, nw, cw)
+gen_command_matches (cs, cmd, text, line, ind, lwords, nw, cw)
      COMPSPEC *cs;
-     char *text, *line;
+     const char *cmd;
+     const char *text;
+     char *line;
      int ind;
      WORD_LIST *lwords;
      int nw, cw;
@@ -1185,10 +1179,11 @@ gen_command_matches (cs, text, line, ind, lwords, nw, cw)
   char *csbuf, *cscmd, *t;
   int cmdlen, cmdsize, n, ws, we;
   WORD_LIST *cmdlist, *cl;
+  WORD_DESC *tw;
   STRINGLIST *sl;
 
   bind_compfunc_variables (line, ind, lwords, cw, 1);
-  cmdlist = build_arg_list (cs->command, text, lwords, cw);
+  cmdlist = build_arg_list (cs->command, cmd, text, lwords, cw);
 
   /* Estimate the size needed for the buffer. */
   n = strlen (cs->command);
@@ -1198,14 +1193,14 @@ gen_command_matches (cs, text, line, ind, lwords, nw, cw)
   cmdsize += 2;
 
   /* allocate the string for the command and fill it in. */
-  cscmd = xmalloc (cmdsize + 1);
+  cscmd = (char *)xmalloc (cmdsize + 1);
 
   strcpy (cscmd, cs->command);                 /* $0 */
   cmdlen = n;
   cscmd[cmdlen++] = ' ';
   for (cl = cmdlist->next; cl; cl = cl->next)   /* $1, $2, $3, ... */
     {
-      t = single_quote (cl->word->word ? cl->word->word : "");
+      t = sh_single_quote (cl->word->word ? cl->word->word : "");
       n = strlen (t);
       RESIZE_MALLOCED_BUFFER (cscmd, cmdlen, n + 2, cmdsize, 64);
       strcpy (cscmd + cmdlen, t);
@@ -1216,7 +1211,10 @@ gen_command_matches (cs, text, line, ind, lwords, nw, cw)
     }
   cscmd[cmdlen] = '\0';
 
-  csbuf = command_substitute (cscmd, 0);
+  tw = command_substitute (cscmd, 0);
+  csbuf = tw ? tw->word : (char *)NULL;
+  if (tw)
+    dispose_word_desc (tw);
 
   /* Now clean up and destroy everything. */
   dispose_words (cmdlist);
@@ -1231,7 +1229,7 @@ gen_command_matches (cs, text, line, ind, lwords, nw, cw)
 
   /* Now break CSBUF up at newlines, with backslash allowed to escape a
      newline, and put the individual words into a STRINGLIST. */
-  sl = alloc_stringlist (16);
+  sl = strlist_create (16);
   for (ws = 0; csbuf[ws]; )
     {
       we = ws;
@@ -1243,7 +1241,7 @@ gen_command_matches (cs, text, line, ind, lwords, nw, cw)
        }
       t = substring (csbuf, ws, we);
       if (sl->list_len >= sl->list_size - 1)
-       realloc_stringlist (sl, sl->list_size + 16);
+       strlist_resize (sl, sl->list_size + 16);
       sl->list[sl->list_len++] = t;
       while (csbuf[we] == '\n') we++;
       ws = we;
@@ -1262,34 +1260,47 @@ command_line_to_word_list (line, llen, sentinel, nwp, cwp)
   WORD_LIST *ret;
   char *delims;
 
+#if 0
   delims = "()<>;&| \t\n";     /* shell metacharacters break words */
-  ret = split_at_delims (line, llen, delims, sentinel, nwp, cwp);
+#else
+  delims = rl_completer_word_break_characters;
+#endif
+  ret = split_at_delims (line, llen, delims, sentinel, SD_NOQUOTEDELIM, nwp, cwp);
   return (ret);
 }
 
 /* Evaluate COMPSPEC *cs and return all matches for WORD. */
 
 STRINGLIST *
-gen_compspec_completions (cs, cmd, word, start, end)
+gen_compspec_completions (cs, cmd, word, start, end, foundp)
      COMPSPEC *cs;
-     char *cmd;
-     char *word;
+     const char *cmd;
+     const char *word;
      int start, end;
+     int *foundp;
 {
   STRINGLIST *ret, *tmatches;
-  char *line, *lword;
-  int llen, nw, cw;
+  char *line;
+  int llen, nw, cw, found, foundf;
   WORD_LIST *lwords;
+  WORD_DESC *lw;
+  COMPSPEC *tcs;
 
-  debug_printf ("programmable_completions (%s, %s, %d, %d)", cmd, word, start, end);
-  debug_printf ("programmable_completions: %s -> 0x%x", cmd, (int)cs);
+  found = 1;
+
+#ifdef DEBUG
+  debug_printf ("gen_compspec_completions (%s, %s, %d, %d)", cmd, word, start, end);
+  debug_printf ("gen_compspec_completions: %s -> %p", cmd, cs);
+#endif
   ret = gen_action_completions (cs, word);
+#ifdef DEBUG
   if (ret && progcomp_debug)
     {
-      debug_printf ("gen_action_completions (0x%x, %s) -->", (int)cs, word);
-      print_stringlist (ret, "\t");
+      debug_printf ("gen_action_completions (%p, %s) -->", cs, word);
+      strlist_print (ret, "\t");
       rl_on_new_line ();
     }
+#endif
 
   /* Now we start generating completions based on the other members of CS. */
   if (cs->globpat)
@@ -1297,14 +1308,16 @@ gen_compspec_completions (cs, cmd, word, start, end)
       tmatches = gen_globpat_matches (cs, word);
       if (tmatches)
        {
+#ifdef DEBUG
          if (progcomp_debug)
            {
-             debug_printf ("gen_globpat_matches (0x%x, %s) -->", (int)cs, word);
-             print_stringlist (tmatches, "\t");
+             debug_printf ("gen_globpat_matches (%p, %s) -->", cs, word);
+             strlist_print (tmatches, "\t");
              rl_on_new_line ();
            }
-         ret = append_stringlist (ret, tmatches);
-         free_stringlist (tmatches);
+#endif
+         ret = strlist_append (ret, tmatches);
+         strlist_dispose (tmatches);
          rl_filename_completion_desired = 1;
        }
     }
@@ -1314,14 +1327,16 @@ gen_compspec_completions (cs, cmd, word, start, end)
       tmatches = gen_wordlist_matches (cs, word);
       if (tmatches)
        {
+#ifdef DEBUG
          if (progcomp_debug)
            {
-             debug_printf ("gen_wordlist_matches (0x%x, %s) -->", (int)cs, word);
-             print_stringlist (tmatches, "\t");
+             debug_printf ("gen_wordlist_matches (%p, %s) -->", cs, word);
+             strlist_print (tmatches, "\t");
              rl_on_new_line ();
            }
-         ret = append_stringlist (ret, tmatches);
-         free_stringlist (tmatches);
+#endif
+         ret = strlist_append (ret, tmatches);
+         strlist_dispose (tmatches);
        }
     }
 
@@ -1335,9 +1350,20 @@ gen_compspec_completions (cs, cmd, word, start, end)
       line = substring (rl_line_buffer, start, end);
       llen = end - start;
 
-      debug_printf ("command_line_to_word_list (%s, %d, %d, 0x%x, 0x%x)",
+#ifdef DEBUG
+      debug_printf ("command_line_to_word_list (%s, %d, %d, %p, %p)",
                line, llen, rl_point - start, &nw, &cw);
+#endif
       lwords = command_line_to_word_list (line, llen, rl_point - start, &nw, &cw);
+      /* If we skipped a NULL word at the beginning of the line, add it back */
+      if (lwords && lwords->word && cmd[0] == 0 && lwords->word->word[0] != 0)
+       {
+         lw = make_bare_word (cmd);
+         lwords = make_word_list (lw, lwords);
+         nw++;
+         cw++;
+       }
+#ifdef DEBUG
       if (lwords == 0 && llen > 0)
        debug_printf ("ERROR: command_line_to_word_list returns NULL");
       else if (progcomp_debug)
@@ -1349,37 +1375,45 @@ gen_compspec_completions (cs, cmd, word, start, end)
          fflush(stdout);
          rl_on_new_line ();
        }
+#endif
     }
 
   if (cs->funcname)
     {
-      tmatches = gen_shell_function_matches (cs, word, line, rl_point - start, lwords, nw, cw);
+      foundf = 0;
+      tmatches = gen_shell_function_matches (cs, cmd, word, line, rl_point - start, lwords, nw, cw, &foundf);
+      if (foundf != 0)
+       found = foundf;
       if (tmatches)
        {
+#ifdef DEBUG
          if (progcomp_debug)
            {
-             debug_printf ("gen_shell_function_matches (0x%x, %s, 0x%x, %d, %d) -->", (int)cs, word, lwords, nw, cw);
-             print_stringlist (tmatches, "\t");
+             debug_printf ("gen_shell_function_matches (%p, %s, %s, %p, %d, %d) -->", cs, cmd, word, lwords, nw, cw);
+             strlist_print (tmatches, "\t");
              rl_on_new_line ();
            }
-         ret = append_stringlist (ret, tmatches);
-         free_stringlist (tmatches);
+#endif
+         ret = strlist_append (ret, tmatches);
+         strlist_dispose (tmatches);
        }
     }
 
   if (cs->command)
     {
-      tmatches = gen_command_matches (cs, word, line, rl_point - start, lwords, nw, cw);
+      tmatches = gen_command_matches (cs, cmd, word, line, rl_point - start, lwords, nw, cw);
       if (tmatches)
        {
+#ifdef DEBUG
          if (progcomp_debug)
            {
-             debug_printf ("gen_command_matches (0x%x, %s, 0x%x, %d, %d) -->", (int)cs, word, lwords, nw, cw);
-             print_stringlist (tmatches, "\t");
+             debug_printf ("gen_command_matches (%p, %s, %s, %p, %d, %d) -->", cs, cmd, word, lwords, nw, cw);
+             strlist_print (tmatches, "\t");
              rl_on_new_line ();
            }
-         ret = append_stringlist (ret, tmatches);
-         free_stringlist (tmatches);
+#endif
+         ret = strlist_append (ret, tmatches);
+         strlist_dispose (tmatches);
        }
     }
 
@@ -1390,15 +1424,26 @@ gen_compspec_completions (cs, cmd, word, start, end)
       FREE (line);
     }
 
+  if (foundp)
+    *foundp = found;
+
+  if (found == 0 || (found & PCOMP_RETRYFAIL))
+    {
+      strlist_dispose (ret);
+      return NULL;
+    }
+
   if (cs->filterpat)
     {
       tmatches = filter_stringlist (ret, cs->filterpat, word);
+#ifdef DEBUG
       if (progcomp_debug)
        {
-         debug_printf ("filter_stringlist (0x%x, %s, %s) -->", ret, cs->filterpat, word);
-         print_stringlist (tmatches, "\t");
+         debug_printf ("filter_stringlist (%p, %s, %s) -->", ret, cs->filterpat, word);
+         strlist_print (tmatches, "\t");
          rl_on_new_line ();
        }
+#endif
       if (ret && ret != tmatches)
        {
          FREE (ret->list);
@@ -1408,44 +1453,164 @@ gen_compspec_completions (cs, cmd, word, start, end)
     }
 
   if (cs->prefix || cs->suffix)
-    ret = prefix_suffix_stringlist (ret, cs->prefix, cs->suffix);
+    ret = strlist_prefix_suffix (ret, cs->prefix, cs->suffix);
+
+  /* If no matches have been generated and the user has specified that
+      directory completion should be done as a default, call
+      gen_action_completions again to generate a list of matching directory
+      names. */
+  if ((ret == 0 || ret->list_len == 0) && (cs->options & COPT_DIRNAMES))
+    {
+      tcs = compspec_create ();
+      tcs->actions = CA_DIRECTORY;
+      FREE (ret);
+      ret = gen_action_completions (tcs, word);
+      compspec_dispose (tcs);
+    }
+  else if (cs->options & COPT_PLUSDIRS)
+    {
+      tcs = compspec_create ();
+      tcs->actions = CA_DIRECTORY;
+      tmatches = gen_action_completions (tcs, word);
+      ret = strlist_append (ret, tmatches);
+      strlist_dispose (tmatches);
+      compspec_dispose (tcs);
+    }
 
   return (ret);
 }
 
+void
+pcomp_set_readline_variables (flags, nval)
+     int flags, nval;
+{
+  /* If the user specified that the compspec returns filenames, make
+     sure that readline knows it. */
+  if (flags & COPT_FILENAMES)
+    rl_filename_completion_desired = nval;
+  /* If the user doesn't want a space appended, tell readline. */
+  if (flags & COPT_NOSPACE)
+    rl_completion_suppress_append = nval;
+  /* The value here is inverted, since the default is on and the `noquote'
+     option is supposed to turn it off */
+  if (flags & COPT_NOQUOTE)
+    rl_filename_quoting_desired = 1 - nval;
+}
+
+/* Set or unset FLAGS in the options word of the current compspec.
+   SET_OR_UNSET is 1 for setting, 0 for unsetting. */
+void
+pcomp_set_compspec_options (cs, flags, set_or_unset)
+     COMPSPEC *cs;
+     int flags, set_or_unset;
+{
+  if (cs == 0 && ((cs = pcomp_curcs) == 0))
+    return;
+  if (set_or_unset)
+    cs->options |= flags;
+  else
+    cs->options &= ~flags;
+}
+
+static STRINGLIST *
+gen_progcomp_completions (ocmd, cmd, word, start, end, foundp, retryp, lastcs)
+     const char *ocmd;
+     const char *cmd;
+     const char *word;
+     int start, end;
+     int *foundp, *retryp;
+     COMPSPEC **lastcs;
+{
+  COMPSPEC *cs, *oldcs;
+  const char *oldcmd;
+  STRINGLIST *ret;
+
+  cs = progcomp_search (ocmd);
+
+  if (cs == 0 || cs == *lastcs)
+    {
+#if 0
+      if (foundp)
+       *foundp = 0;
+#endif
+      return (NULL);
+    }
+
+  if (*lastcs)
+    compspec_dispose (*lastcs);
+  cs->refcount++;      /* XXX */
+  *lastcs = cs;
+
+  cs = compspec_copy (cs);
+
+  oldcs = pcomp_curcs;
+  oldcmd = pcomp_curcmd;
+
+  pcomp_curcs = cs;
+  pcomp_curcmd = cmd;
+
+  ret = gen_compspec_completions (cs, cmd, word, start, end, foundp);
+
+  pcomp_curcs = oldcs;
+  pcomp_curcmd = oldcmd;
+
+  /* We need to conditionally handle setting *retryp here */
+  if (retryp)
+    *retryp = foundp && (*foundp & PCOMP_RETRYFAIL);           
+
+  if (foundp)
+    {
+      *foundp &= ~PCOMP_RETRYFAIL;
+      *foundp |= cs->options;
+    }
+
+  compspec_dispose (cs);
+  return ret;  
+}
+
 /* The driver function for the programmable completion code.  Returns a list
    of matches for WORD, which is an argument to command CMD.  START and END
    bound the command currently being completed in rl_line_buffer. */
 char **
 programmable_completions (cmd, word, start, end, foundp)
-     char *cmd, *word;
+     const char *cmd;
+     const char *word;
      int start, end, *foundp;
 {
-  COMPSPEC *cs;
+  COMPSPEC *cs, *lastcs;
   STRINGLIST *ret;
   char **rmatches, *t;
+  int found, retry, count;
 
-  /* We look at the basename of CMD if the full command does not have
-     an associated COMPSPEC. */
-  cs = find_compspec (cmd);
-  if (cs == 0)
-    {
-      t = strrchr (cmd, '/');
-      if (t)
-       cs = find_compspec (++t);
-    }
-  if (cs == 0)
+  lastcs = 0;
+  found = count = 0;
+
+  do
     {
-      if (foundp)
-       *foundp = 0;
-      return ((char **)NULL);
-    }
+      retry = 0;
 
-  /* Signal the caller that we found a COMPSPEC for this command. */
-  if (foundp)
-    *foundp = 1;
+      /* We look at the basename of CMD if the full command does not have
+        an associated COMPSPEC. */
+      ret = gen_progcomp_completions (cmd, cmd, word, start, end, &found, &retry, &lastcs);
+      if (found == 0)
+       {
+         t = strrchr (cmd, '/');
+         if (t && *(++t))
+           ret = gen_progcomp_completions (t, cmd, word, start, end, &found, &retry, &lastcs);
+       }
+
+      if (found == 0)
+       ret = gen_progcomp_completions (DEFAULTCMD, cmd, word, start, end, &found, &retry, &lastcs);
 
-  ret = gen_compspec_completions (cs, cmd, word, start, end);
+      count++;
+
+      if (count > 32)
+       {
+         internal_warning ("programmable_completion: %s: possible retry loop", cmd);
+         break;
+       }
+    }
+  while (retry);
 
   if (ret)
     {
@@ -1455,6 +1620,12 @@ programmable_completions (cmd, word, start, end, foundp)
   else
     rmatches = (char **)NULL;
 
+  if (foundp)
+    *foundp = found;
+
+  if (lastcs)  /* XXX - should be while? */
+    compspec_dispose (lastcs);
+
   return (rmatches);
 }