No specific user configuration
[platform/upstream/bash.git] / pcomplete.c
index 311f967..70cbc98 100644 (file)
@@ -1,6 +1,6 @@
 /* pcomplete.c - functions to generate lists of matches for programmable completion. */
 
-/* Copyright (C) 1999-2009 Free Software Foundation, Inc.
+/* Copyright (C) 1999-2012 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -37,6 +37,8 @@
 #  include <varargs.h>
 #endif
 
+#include <sys/time.h>
+
 #include <stdio.h>
 #include "bashansi.h"
 #include "bashintl.h"
 #  include "trap.h"
 #endif
 
+#include "shmbutil.h"
+
 #include "builtins.h"
 #include "builtins/common.h"
+#include "builtins/builtext.h"
 
 #include <glob/glob.h>
 #include <glob/strmatch.h>
@@ -66,6 +71,8 @@
 #include <readline/readline.h>
 #include <readline/history.h>
 
+#define PCOMP_RETRYFAIL        256
+
 #ifdef STRDUP
 #  undef STRDUP
 #endif
@@ -80,6 +87,7 @@ extern char *strpbrk __P((char *, char *));
 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)
@@ -97,6 +105,7 @@ 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 *));
@@ -117,10 +126,18 @@ 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));
-static STRINGLIST *gen_command_matches __P((COMPSPEC *, const char *, char *,
-                                           int, WORD_LIST *, int, int));
+                                                  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));
 
@@ -129,7 +146,7 @@ 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 *, WORD_LIST *, 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
@@ -150,6 +167,7 @@ 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_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_groups = { LIST_DYNAMIC };         /* unused */
 ITEMLIST it_jobs = { LIST_DYNAMIC, it_init_jobs, (STRINGLIST *)0 };
@@ -165,6 +183,7 @@ ITEMLIST it_variables = { LIST_DYNAMIC, it_init_variables, (STRINGLIST *)0 };
 
 COMPSPEC *pcomp_curcs;
 const char *pcomp_curcmd;
+const char *pcomp_curtxt;
 
 #ifdef DEBUG
 /* Debugging code */
@@ -361,6 +380,7 @@ it_init_aliases (itp)
 #else
   itp->slist = (STRINGLIST *)NULL;
 #endif
+  free (alias_list);
   return 1;
 }
 
@@ -490,6 +510,24 @@ 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;
@@ -653,7 +691,7 @@ gen_matches_from_itemlist (itp, text)
   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);
@@ -686,16 +724,29 @@ pcomp_filename_completion_function (text, state)
 {
   static char *dfn;    /* dequoted filename */
   int qc;
+  int iscompgen, iscompleting;
 
   if (state == 0)
     {
       FREE (dfn);
       /* remove backslashes quoting special characters in filenames. */
-#if 1
-      if (RL_ISSTATE (RL_STATE_COMPLETING) && rl_filename_dequoting_function)
-#else
-      if (rl_filename_dequoting_function)
-#endif
+      /* 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)
        {
          /* Use rl_completion_quote_character because any single or
             double quotes have been removed by the time TEXT makes it
@@ -703,6 +754,32 @@ pcomp_filename_completion_function (text, state)
             quoted strings. */
          dfn = (*rl_filename_dequoting_function) ((char *)text, rl_completion_quote_character);
        }
+      /* Intended to solve a mismatched assumption by bash-completion.  If
+        the text to be completed is empty, but bash-completion turns it into
+        a quoted string ('') assuming that this code will dequote it before
+        calling readline, do the dequoting. */
+      else if (iscompgen && iscompleting &&
+              pcomp_curtxt && *pcomp_curtxt == 0 &&
+              text && (*text == '\'' || *text == '"') && text[1] == text[0] && text[2] == 0 && 
+              rl_filename_dequoting_function)
+       dfn = (*rl_filename_dequoting_function) ((char *)text, rl_completion_quote_character);
+      /* Another mismatched assumption by bash-completion.  If compgen is being
+        run as part of bash-completion, and the argument to compgen is not
+        the same as the word originally passed to the programmable completion
+        code, dequote the argument if it has quote characters.  It's an
+        attempt to detect when bash-completion is quoting its filename
+        argument before calling compgen. */
+      /* We could check whether gen_shell_function_matches is in the call
+        stack by checking whether the gen-shell-function-matches tag is in
+        the unwind-protect stack, but there's no function to do that yet.
+        We could simply check whether we're executing in a function by
+        checking variable_context, and may end up doing that. */
+      else if (iscompgen && iscompleting && rl_filename_dequoting_function &&
+              pcomp_curtxt && text &&
+              STREQ (pcomp_curtxt, text) == 0 &&
+              variable_context &&
+              sh_contains_quotes (text))       /* guess */
+       dfn = (*rl_filename_dequoting_function) ((char *)text, rl_completion_quote_character);
       else
        dfn = savestring (text);
     }
@@ -745,6 +822,7 @@ gen_action_completions (cs, text)
   STRINGLIST *ret, *tmatches;
   char **cmatches;     /* from rl_completion_matches ... */
   unsigned long flags;
+  int t;
 
   ret = tmatches = (STRINGLIST *)NULL;
   flags = cs->actions;
@@ -757,6 +835,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);
@@ -776,8 +855,15 @@ gen_action_completions (cs, text)
   /* 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 = strlist_append (ret, tmatches);
       strvec_dispose (cmatches);
@@ -826,8 +912,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... */
@@ -884,6 +973,8 @@ bind_compfunc_variables (line, ind, lwords, cw, exported)
   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). */
@@ -891,7 +982,12 @@ bind_compfunc_variables (line, ind, lwords, cw, exported)
   if (v && exported)
     VSETATTR(v, att_exported);
 
-  value = inttostr (ind, ibuf, sizeof(ibuf));
+  /* 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);
@@ -941,15 +1037,17 @@ 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;
+     const char *cname;
      const char *text;
      WORD_LIST *lwords;
      int ind;
@@ -960,13 +1058,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 */
@@ -991,24 +1089,31 @@ 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;
+     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)
@@ -1027,11 +1132,25 @@ 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);
 
-  save_parser_state (&ps);  
   fval = execute_shell_function (f, cmdlist);  
-  restore_parser_state (&ps);
+
+  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);
@@ -1047,7 +1166,7 @@ gen_shell_function_matches (cs, text, line, ind, lwords, nw, cw)
   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
     {
@@ -1075,8 +1194,9 @@ 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;
+     const char *cmd;
      const char *text;
      char *line;
      int ind;
@@ -1090,7 +1210,7 @@ gen_command_matches (cs, text, line, ind, lwords, nw, cw)
   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);
@@ -1120,7 +1240,8 @@ gen_command_matches (cs, text, line, ind, lwords, nw, cw)
 
   tw = command_substitute (cscmd, 0);
   csbuf = tw ? tw->word : (char *)NULL;
-  dispose_word_desc (tw);
+  if (tw)
+    dispose_word_desc (tw);
 
   /* Now clean up and destroy everything. */
   dispose_words (cmdlist);
@@ -1171,25 +1292,29 @@ command_line_to_word_list (line, llen, sentinel, nwp, cwp)
 #else
   delims = rl_completer_word_break_characters;
 #endif
-  ret = split_at_delims (line, llen, delims, sentinel, nwp, cwp);
+  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;
      const char *cmd;
      const char *word;
      int start, end;
+     int *foundp;
 {
   STRINGLIST *ret, *tmatches;
   char *line;
-  int llen, nw, cw;
+  int llen, nw, cw, found, foundf;
   WORD_LIST *lwords;
+  WORD_DESC *lw;
   COMPSPEC *tcs;
 
+  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);
@@ -1257,6 +1382,14 @@ gen_compspec_completions (cs, cmd, word, start, end)
                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");
@@ -1274,13 +1407,16 @@ gen_compspec_completions (cs, cmd, word, start, end)
 
   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 (%p, %s, %p, %d, %d) -->", cs, word, lwords, nw, cw);
+             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 ();
            }
@@ -1292,13 +1428,13 @@ gen_compspec_completions (cs, cmd, word, start, end)
 
   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 (%p, %s, %p, %d, %d) -->", cs, word, lwords, nw, cw);
+             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 ();
            }
@@ -1315,6 +1451,15 @@ 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);
@@ -1345,6 +1490,7 @@ gen_compspec_completions (cs, cmd, word, start, end)
     {
       tcs = compspec_create ();
       tcs->actions = CA_DIRECTORY;
+      FREE (ret);
       ret = gen_action_completions (tcs, word);
       compspec_dispose (tcs);
     }
@@ -1372,6 +1518,10 @@ pcomp_set_readline_variables (flags, 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.
@@ -1389,55 +1539,108 @@ pcomp_set_compspec_options (cs, flags, set_or_unset)
     cs->options &= ~flags;
 }
 
-/* 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)
+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, *foundp;
+     int start, end;
+     int *foundp, *retryp;
+     COMPSPEC **lastcs;
 {
   COMPSPEC *cs, *oldcs;
+  const char *oldcmd, *oldtxt;
   STRINGLIST *ret;
-  char **rmatches, *t;
-  const char *oldcmd;
 
-  /* We look at the basename of CMD if the full command does not have
-     an associated COMPSPEC. */
-  cs = progcomp_search (cmd);
-  if (cs == 0)
-    {
-      t = strrchr (cmd, '/');
-      if (t)
-       cs = progcomp_search (++t);
-    }
-  if (cs == 0)
+  cs = progcomp_search (ocmd);
+
+  if (cs == 0 || cs == *lastcs)
     {
+#if 0
       if (foundp)
        *foundp = 0;
-      return ((char **)NULL);
+#endif
+      return (NULL);
     }
 
+  if (*lastcs)
+    compspec_dispose (*lastcs);
+  cs->refcount++;      /* XXX */
+  *lastcs = cs;
+
   cs = compspec_copy (cs);
 
   oldcs = pcomp_curcs;
   oldcmd = pcomp_curcmd;
+  oldtxt = pcomp_curtxt;
 
   pcomp_curcs = cs;
   pcomp_curcmd = cmd;
+  pcomp_curtxt = word;
 
-  /* Signal the caller that we found a COMPSPEC for this command, and pass
-     back any meta-options associated with the compspec. */
-  if (foundp)
-    *foundp = 1|cs->options;
-
-  ret = gen_compspec_completions (cs, cmd, word, start, end);
+  ret = gen_compspec_completions (cs, cmd, word, start, end, foundp);
 
   pcomp_curcs = oldcs;
   pcomp_curcmd = oldcmd;
+  pcomp_curtxt = oldtxt;
+
+  /* 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)
+     const char *cmd;
+     const char *word;
+     int start, end, *foundp;
+{
+  COMPSPEC *cs, *lastcs;
+  STRINGLIST *ret;
+  char **rmatches, *t;
+  int found, retry, count;
+
+  lastcs = 0;
+  found = count = 0;
+
+  do
+    {
+      retry = 0;
+
+      /* 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);
+
+      count++;
+
+      if (count > 32)
+       {
+         internal_warning ("programmable_completion: %s: possible retry loop", cmd);
+         break;
+       }
+    }
+  while (retry);
 
   if (ret)
     {
@@ -1447,6 +1650,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);
 }