aboutsummaryrefslogtreecommitdiffstats
path: root/pcomplete.c
diff options
context:
space:
mode:
Diffstat (limited to 'pcomplete.c')
-rw-r--r--pcomplete.c1461
1 files changed, 1461 insertions, 0 deletions
diff --git a/pcomplete.c b/pcomplete.c
new file mode 100644
index 0000000..a1fc045
--- /dev/null
+++ b/pcomplete.c
@@ -0,0 +1,1461 @@
+/* pcomplete.c - functions to generate lists of matches for programmable
+ completion. */
+
+/* Copyright (C) 1999 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 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. */
+
+#include <config.h>
+
+#if defined (PROGRAMMABLE_COMPLETION)
+
+#include "bashtypes.h"
+#include "posixstat.h"
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include <signal.h>
+
+#if defined (PREFER_STDARG)
+# include <stdarg.h>
+#else
+# if defined (PREFER_VARARGS)
+# include <varargs.h>
+# endif
+#endif
+
+#include <stdio.h>
+#include "bashansi.h"
+
+#include "shell.h"
+#include "pcomplete.h"
+#include "alias.h"
+#include "bashline.h"
+#include "pathexp.h"
+
+#if defined (JOB_CONTROL)
+# include "jobs.h"
+#endif
+
+#if !defined (NSIG)
+# include "trap.h"
+#endif
+
+#include "builtins.h"
+#include "builtins/common.h"
+
+#include <glob/glob.h>
+#include <glob/fnmatch.h>
+
+#include <readline/rlconf.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#ifdef STRDUP
+# undef STRDUP
+#endif
+#define STRDUP(x) ((x) ? savestring (x) : (char *)NULL)
+
+typedef SHELL_VAR **SVFUNC ();
+
+#ifndef HAVE_STRPBRK
+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[];
+
+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 ();
+
+static int progcomp_debug = 0;
+
+int prog_completion_enabled = 1;
+
+/* These are used to manage the arrays of strings for possible completions. */
+ITEMLIST it_aliases = { 0, it_init_aliases, (STRINGLIST *)0 };
+ITEMLIST it_arrayvars = { LIST_DYNAMIC, it_init_arrayvars, (STRINGLIST *)0 };
+ITEMLIST it_bindings = { 0, it_init_bindings, (STRINGLIST *)0 };
+ITEMLIST it_builtins = { 0, it_init_builtins, (STRINGLIST *)0 };
+ITEMLIST it_commands = { LIST_DYNAMIC }; /* unused */
+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_functions = { 0, it_init_functions, (STRINGLIST *)0 };
+ITEMLIST it_hostnames = { LIST_DYNAMIC, it_init_hostnames, (STRINGLIST *)0 };
+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_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_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;
+
+ fprintf (stdout, format, arg1, arg2, arg3, arg4, arg5);
+ fprintf (stdout, "\n");
+ rl_on_new_line ();
+}
+#else
+static void
+#if defined (PREFER_STDARG)
+debug_printf (const char *format, ...)
+#else
+debug_printf (format, va_alist)
+ const char *format;
+ va_dcl
+#endif
+{
+ va_list args;
+
+ if (progcomp_debug == 0)
+ return;
+
+#if defined (PREFER_STDARG)
+ va_start (args, format);
+#else
+ va_start (args);
+#endif
+
+ fprintf (stdout, "DEBUG: ");
+ vfprintf (stdout, format, args);
+ fprintf (stdout, "\n");
+
+ rl_on_new_line ();
+
+ va_end (args);
+}
+#endif /* USE_VARARGS */
+
+/* Functions to manage the item lists */
+
+void
+set_itemlist_dirty (it)
+ ITEMLIST *it;
+{
+ it->flags |= LIST_DIRTY;
+}
+
+void
+initialize_itemlist (itp)
+ ITEMLIST *itp;
+{
+ (*itp->list_getter) (itp);
+ itp->flags |= LIST_INITIALIZED;
+ itp->flags &= ~LIST_DIRTY;
+}
+
+void
+clean_itemlist (itp)
+ ITEMLIST *itp;
+{
+ STRINGLIST *sl;
+
+ sl = itp->slist;
+ if (sl)
+ {
+ if ((itp->flags & (LIST_DONTFREEMEMBERS|LIST_DONTFREE)) == 0)
+ free_array_members (sl->list);
+ if ((itp->flags & LIST_DONTFREE) == 0)
+ free (sl->list);
+ free (sl);
+ }
+ itp->slist = (STRINGLIST *)NULL;
+ 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)
+ char *s;
+{
+ register char *p;
+
+ for (p = s; p && *p; p++)
+ {
+ if (*p == '\\')
+ p++;
+ else if (*p == '&')
+ return 1;
+ }
+ return 0;
+}
+
+/* Replace any instance of `&' in PAT with TEXT. Backslash may be used to
+ quote a `&' and inhibit substitution. Returns a new string. This just
+ calls stringlib.c:strcreplace(). */
+static char *
+preproc_filterpat (pat, text)
+ char *pat;
+ char *text;
+{
+ char *ret;
+
+ ret = strcreplace (pat, '&', text, 1);
+ return ret;
+}
+
+/* Remove any match of FILTERPAT from SL. A `&' in FILTERPAT is replaced by
+ TEXT. A leading `!' in FILTERPAT negates the pattern; in this case
+ any member of SL->list that does *not* match will be removed. This returns
+ a new STRINGLIST with the matching members of SL *copied*. Any
+ non-matching members of SL->list are *freed*. */
+STRINGLIST *
+filter_stringlist (sl, filterpat, text)
+ STRINGLIST *sl;
+ char *filterpat, *text;
+{
+ int i, m, not;
+ STRINGLIST *ret;
+ char *npat, *t;
+
+ if (sl == 0 || sl->list == 0 || sl->list_len == 0)
+ return sl;
+
+ npat = shouldexp_filterpat (filterpat) ? preproc_filterpat (filterpat, text) : filterpat;
+
+ not = (npat[0] == '!');
+ t = not ? npat + 1 : npat;
+
+ ret = alloc_stringlist (sl->list_size);
+ for (i = 0; i < sl->list_len; i++)
+ {
+ m = fnmatch (t, sl->list[i], FNMATCH_EXTFLAG);
+ if ((not && m == FNM_NOMATCH) || (not == 0 && m != FNM_NOMATCH))
+ free (sl->list[i]);
+ else
+ ret->list[ret->list_len++] = sl->list[i];
+ }
+
+ ret->list[ret->list_len] = (char *)NULL;
+ if (npat != filterpat)
+ free (npat);
+
+ 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
+ strings in the list, unless it's the only match). */
+STRINGLIST *
+completions_to_stringlist (matches)
+ char **matches;
+{
+ STRINGLIST *sl;
+ int mlen, i, n;
+
+ mlen = (matches == 0) ? 0 : array_len (matches);
+ sl = alloc_stringlist (mlen + 1);
+
+ if (matches == 0 || matches[0] == 0)
+ return sl;
+
+ if (matches[1] == 0)
+ {
+ sl->list[0] = STRDUP (matches[0]);
+ sl->list[sl->list_len = 1] = (char *)NULL;
+ return sl;
+ }
+
+ for (i = 1, n = 0; i < mlen; i++, n++)
+ sl->list[n] = STRDUP (matches[i]);
+ sl->list_len = n;
+ sl->list[n] = (char *)NULL;
+
+ return sl;
+}
+
+/* Functions to manage the various ITEMLISTs that we populate internally.
+ The caller is responsible for setting ITP->flags correctly. */
+
+static int
+it_init_aliases (itp)
+ ITEMLIST *itp;
+{
+#ifdef ALIAS
+ alias_t **aliases;
+ register int i, n;
+ STRINGLIST *sl;
+
+ aliases = all_aliases ();
+ if (aliases == 0)
+ {
+ itp->slist = (STRINGLIST *)NULL;
+ return 0;
+ }
+ for (n = 0; aliases[n]; n++)
+ ;
+ sl = alloc_stringlist (n+1);
+ for (i = 0; i < n; i++)
+ sl->list[i] = STRDUP (aliases[i]->name);
+ sl->list[n] = (char *)NULL;
+ sl->list_size = sl->list_len = n;
+ itp->slist = sl;
+#else
+ itp->slist = (STRINGLIST *)NULL;
+#endif
+ return 1;
+}
+
+static void
+init_itemlist_from_varlist (itp, svfunc)
+ ITEMLIST *itp;
+ SVFUNC *svfunc;
+{
+ SHELL_VAR **vlist;
+ STRINGLIST *sl;
+ register int i, n;
+
+ vlist = (*svfunc) ();
+ for (n = 0; vlist[n]; n++)
+ ;
+ sl = alloc_stringlist (n+1);
+ for (i = 0; i < n; i++)
+ sl->list[i] = savestring (vlist[i]->name);
+ sl->list[sl->list_len = n] = (char *)NULL;
+ itp->slist = sl;
+}
+
+static int
+it_init_arrayvars (itp)
+ ITEMLIST *itp;
+{
+#if defined (ARRAY_VARS)
+ init_itemlist_from_varlist (itp, all_array_variables);
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+static int
+it_init_bindings (itp)
+ ITEMLIST *itp;
+{
+ char **blist;
+ STRINGLIST *sl;
+
+ /* rl_funmap_names allocates blist, but not its members */
+ blist = rl_funmap_names ();
+ sl = alloc_stringlist (0);
+ sl->list = blist;
+ sl->list_size = 0;
+ sl->list_len = array_len (sl->list);
+ itp->flags |= LIST_DONTFREEMEMBERS;
+ itp->slist = sl;
+
+ return 0;
+}
+
+static int
+it_init_builtins (itp)
+ ITEMLIST *itp;
+{
+ STRINGLIST *sl;
+ char **list;
+ register int i, n;
+
+ sl = alloc_stringlist (num_shell_builtins);
+ for (i = n = 0; i < num_shell_builtins; i++)
+ if (shell_builtins[i].function)
+ 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_enabled (itp)
+ ITEMLIST *itp;
+{
+ STRINGLIST *sl;
+ char **list;
+ register int i, n;
+
+ sl = alloc_stringlist (num_shell_builtins);
+ for (i = n = 0; i < num_shell_builtins; i++)
+ {
+ if (shell_builtins[i].function && (shell_builtins[i].flags & BUILTIN_ENABLED))
+ 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_disabled (itp)
+ ITEMLIST *itp;
+{
+ STRINGLIST *sl;
+ char **list;
+ register int i, n;
+
+ sl = alloc_stringlist (num_shell_builtins);
+ for (i = n = 0; i < num_shell_builtins; i++)
+ {
+ if (shell_builtins[i].function && ((shell_builtins[i].flags & BUILTIN_ENABLED) == 0))
+ 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_exported (itp)
+ ITEMLIST *itp;
+{
+ init_itemlist_from_varlist (itp, all_exported_variables);
+ return 0;
+}
+
+static int
+it_init_functions (itp)
+ ITEMLIST *itp;
+{
+ init_itemlist_from_varlist (itp, all_visible_functions);
+ return 0;
+}
+
+static int
+it_init_hostnames (itp)
+ ITEMLIST *itp;
+{
+ STRINGLIST *sl;
+
+ sl = alloc_stringlist (0);
+ sl->list = get_hostname_list ();
+ sl->list_len = sl->list ? array_len (sl->list) : 0;
+ sl->list_size = sl->list_len;
+ itp->slist = sl;
+ itp->flags |= LIST_DONTFREEMEMBERS|LIST_DONTFREE;
+ return 0;
+}
+
+static int
+it_init_joblist (itp, jstate)
+ ITEMLIST *itp;
+ int jstate;
+{
+#if defined (JOB_CONTROL)
+ STRINGLIST *sl;
+ register int i, n;
+ register PROCESS *p;
+ char *s, *t;
+ JOB_STATE js;
+
+ if (jstate == 0)
+ js = JRUNNING;
+ else if (jstate == 1)
+ js = JSTOPPED;
+
+ sl = alloc_stringlist (job_slots);
+ for (i = job_slots - 1; i >= 0; i--)
+ {
+ if (jobs[i] == 0)
+ continue;
+ p = jobs[i]->pipe;
+ if (jstate == -1 || JOBSTATE(i) == js)
+ {
+ s = savestring (p->command);
+ t = strpbrk (s, " \t\n");
+ if (t)
+ *t = '\0';
+ sl->list[sl->list_len++] = s;
+ }
+ }
+ itp->slist = sl;
+#else
+ itp->slist = (STRINGLIST *)NULL;
+#endif
+ return 0;
+}
+
+static int
+it_init_jobs (itp)
+ ITEMLIST *itp;
+{
+ return (it_init_joblist (itp, -1));
+}
+
+static int
+it_init_running (itp)
+ ITEMLIST *itp;
+{
+ return (it_init_joblist (itp, 0));
+}
+
+static int
+it_init_stopped (itp)
+ ITEMLIST *itp;
+{
+ return (it_init_joblist (itp, 1));
+}
+
+static int
+it_init_keywords (itp)
+ ITEMLIST *itp;
+{
+ STRINGLIST *sl;
+ register int i, n;
+
+ for (n = 0; word_token_alist[n].word; n++)
+ ;
+ sl = alloc_stringlist (n);
+ for (i = 0; i < n; i++)
+ sl->list[i] = word_token_alist[i].word;
+ sl->list[sl->list_len = i] = (char *)NULL;
+ itp->flags |= LIST_DONTFREEMEMBERS;
+ itp->slist = sl;
+ return 0;
+}
+
+static int
+it_init_signals (itp)
+ ITEMLIST *itp;
+{
+ STRINGLIST *sl;
+
+ sl = alloc_stringlist (0);
+ sl->list = signal_names;
+ sl->list_len = array_len (sl->list);
+ itp->flags |= LIST_DONTFREE;
+ itp->slist = sl;
+ return 0;
+}
+
+static int
+it_init_variables (itp)
+ ITEMLIST *itp;
+{
+ init_itemlist_from_varlist (itp, all_visible_variables);
+ return 0;
+}
+
+static int
+it_init_setopts (itp)
+ ITEMLIST *itp;
+{
+ STRINGLIST *sl;
+
+ sl = alloc_stringlist (0);
+ sl->list = get_minus_o_opts ();
+ sl->list_len = array_len (sl->list);
+ itp->slist = sl;
+ itp->flags |= LIST_DONTFREEMEMBERS;
+ return 0;
+}
+
+static int
+it_init_shopts (itp)
+ ITEMLIST *itp;
+{
+ STRINGLIST *sl;
+
+ sl = alloc_stringlist (0);
+ sl->list = get_shopt_options ();
+ sl->list_len = array_len (sl->list);
+ itp->slist = sl;
+ itp->flags |= LIST_DONTFREEMEMBERS;
+ return 0;
+}
+
+/* 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. */
+static STRINGLIST *
+gen_matches_from_itemlist (itp, text)
+ ITEMLIST *itp;
+ char *text;
+{
+ STRINGLIST *ret, *sl;
+ int tlen, i, n;
+
+ if ((itp->flags & (LIST_DIRTY|LIST_DYNAMIC)) ||
+ (itp->flags & LIST_INITIALIZED) == 0)
+ {
+ 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);
+ sl = itp->slist;
+ tlen = STRLEN (text);
+ for (i = n = 0; i < sl->list_len; i++)
+ {
+ if (tlen == 0 || STREQN (sl->list[i], text, tlen))
+ ret->list[n++] = STRDUP (sl->list[i]);
+ }
+ ret->list[ret->list_len = n] = (char *)NULL;
+ return ret;
+}
+
+/* A wrapper for filename_completion_function that dequotes the filename
+ before attempting completions. */
+static char *
+pcomp_filename_completion_function (text, state)
+ char *text;
+ int state;
+{
+ static char *dfn; /* dequoted filename */
+ int qc;
+
+ if (state == 0)
+ {
+ FREE (dfn);
+ /* remove backslashes quoting special characters in filenames. */
+ if (rl_filename_dequoting_function)
+ {
+ qc = (text[0] == '"' || text[0] == '\'') ? text[0] : 0;
+ dfn = (*rl_filename_dequoting_function) (text, qc);
+ }
+ else
+ dfn = savestring (text);
+ }
+
+ return (filename_completion_function (dfn, state));
+}
+
+#define GEN_COMPS(bmap, flag, it, text, glist, tlist) \
+ do { \
+ if (bmap & flag) \
+ { \
+ tlist = gen_matches_from_itemlist (it, text); \
+ glist = append_stringlist (glist, tlist); \
+ free_stringlist (tlist); \
+ } \
+ } while (0)
+
+#define GEN_XCOMPS(bmap, flag, text, func, cmatches, glist, tlist) \
+ do { \
+ if (bmap & flag) \
+ { \
+ cmatches = completion_matches (text, func); \
+ tlist = completions_to_stringlist (cmatches); \
+ glist = append_stringlist (glist, tlist); \
+ free_array (cmatches); \
+ free_stringlist (tlist); \
+ } \
+ } while (0)
+
+/* Functions to generate lists of matches from the actions member of CS. */
+
+static STRINGLIST *
+gen_action_completions (cs, text)
+ COMPSPEC *cs;
+ char *text;
+{
+ STRINGLIST *ret, *tmatches;
+ char **cmatches; /* from completion_matches ... */
+ unsigned long flags;
+
+ ret = tmatches = (STRINGLIST *)NULL;
+ flags = cs->actions;
+
+ GEN_COMPS (flags, CA_ALIAS, &it_aliases, text, ret, tmatches);
+ GEN_COMPS (flags, CA_ARRAYVAR, &it_arrayvars, text, ret, tmatches);
+ GEN_COMPS (flags, CA_BINDING, &it_bindings, text, ret, tmatches);
+ GEN_COMPS (flags, CA_BUILTIN, &it_builtins, text, ret, tmatches);
+ GEN_COMPS (flags, CA_DISABLED, &it_disabled, text, ret, tmatches);
+ 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_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);
+ GEN_COMPS (flags, CA_RUNNING, &it_running, text, ret, tmatches);
+ GEN_COMPS (flags, CA_SETOPT, &it_setopts, text, ret, tmatches);
+ GEN_COMPS (flags, CA_SHOPT, &it_shopts, text, ret, tmatches);
+ GEN_COMPS (flags, CA_SIGNAL, &it_signals, text, ret, tmatches);
+ GEN_COMPS (flags, CA_STOPPED, &it_stopped, text, ret, tmatches);
+ GEN_COMPS (flags, CA_VARIABLE, &it_variables, text, ret, tmatches);
+
+ 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);
+
+ /* And lastly, the special case for directories */
+ if (flags & CA_DIRECTORY)
+ {
+ cmatches = bash_directory_completion_matches (text);
+ tmatches = completions_to_stringlist (cmatches);
+ ret = append_stringlist (ret, tmatches);
+ free_array (cmatches);
+ free_stringlist (tmatches);
+ }
+
+ return ret;
+}
+
+/* Generate a list of matches for CS->globpat. Unresolved: should this use
+ TEXT as a match prefix, or just go without? Currently, the code does not
+ use TEXT, just globs CS->globpat and returns the results. If we do decide
+ to use TEXT, we should call quote_string_for_globbing before the call to
+ glob_filename. */
+static STRINGLIST *
+gen_globpat_matches (cs, text)
+ COMPSPEC *cs;
+ char *text;
+{
+ STRINGLIST *sl;
+ char *t;
+
+ sl = alloc_stringlist (0);
+ sl->list = glob_filename (cs->globpat);
+ if (GLOB_FAILED (sl->list))
+ sl->list = (char **)NULL;
+ if (sl->list)
+ sl->list_len = sl->list_size = array_len (sl->list);
+ return sl;
+}
+
+/* Perform the shell word expansions on CS->words and return the results.
+ Again, this ignores TEXT. */
+static STRINGLIST *
+gen_wordlist_matches (cs, text)
+ COMPSPEC *cs;
+ char *text;
+{
+ WORD_LIST *l, *l2;
+ STRINGLIST *sl;
+ int nw, tlen;
+
+ if (cs->words == 0 || cs->words[0] == '\0')
+ return ((STRINGLIST *)NULL);
+
+ /* 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);
+ if (l == 0)
+ return ((STRINGLIST *)NULL);
+ /* This will jump back to the top level if the expansion fails... */
+ l2 = expand_words_shellexp (l);
+ dispose_words (l);
+
+ nw = list_length (l2);
+ sl = alloc_stringlist (nw + 1);
+ tlen = STRLEN (text);
+
+ 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);
+ }
+ sl->list[sl->list_len = nw] = (char *)NULL;
+
+ return sl;
+}
+
+#ifdef ARRAY_VARS
+
+static SHELL_VAR *
+bind_comp_words (lwords)
+ WORD_LIST *lwords;
+{
+ SHELL_VAR *v;
+
+ v = find_variable ("COMP_WORDS");
+ if (v == 0)
+ v = make_new_array_variable ("COMP_WORDS");
+ if (readonly_p (v))
+ VUNSETATTR (v, att_readonly);
+ if (array_p (v) == 0)
+ v = convert_var_to_array (v);
+ v = assign_array_var_from_word_list (v, lwords);
+ return v;
+}
+#endif /* ARRAY_VARS */
+
+static void
+bind_compfunc_variables (line, ind, lwords, cw, exported)
+ char *line;
+ int ind;
+ WORD_LIST *lwords;
+ int cw, exported;
+{
+ char ibuf[32];
+ char *value;
+ SHELL_VAR *v;
+
+ /* 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);
+ if (v && exported)
+ VSETATTR(v, att_exported);
+
+ value = inttostr (ind, ibuf, 32);
+ v = bind_int_variable ("COMP_POINT", 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);
+ bind_int_variable ("COMP_CWORD", value);
+#endif
+ }
+ else
+ array_needs_making = 1;
+}
+
+static void
+unbind_compfunc_variables (exported)
+ int exported;
+{
+ makunbound ("COMP_LINE", shell_variables);
+ makunbound ("COMP_POINT", shell_variables);
+#ifdef ARRAY_VARS
+ makunbound ("COMP_WORDS", shell_variables);
+ makunbound ("COMP_CWORD", shell_variables);
+#endif
+ if (exported)
+ array_needs_making = 1;
+}
+
+/* Build the list of words to pass to a function or external command
+ as arguments. When the function or command is invoked,
+
+ $0 == function or command being invoked
+ $1 == command name
+ $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. */
+
+static WORD_LIST *
+build_arg_list (cmd, text, lwords, ind)
+ char *cmd;
+ char *text;
+ WORD_LIST *lwords;
+ int ind;
+{
+ WORD_LIST *ret, *cl, *l;
+ WORD_DESC *w;
+ int i;
+
+ ret = (WORD_LIST *)NULL;
+ w = make_word (cmd);
+ ret = make_word_list (w, (WORD_LIST *)NULL);
+
+ w = (lwords && lwords->word) ? copy_word (lwords->word) : make_word ("");
+ cl = ret->next = make_word_list (w, (WORD_LIST *)NULL);
+
+ w = make_word (text);
+ cl->next = make_word_list (w, (WORD_LIST *)NULL);
+ cl = cl->next;
+
+ /* Search lwords for current word */
+ for (l = lwords, i = 1; l && i < ind-1; l = l->next, i++)
+ ;
+ w = (l && l->word) ? copy_word (l->word) : make_word ("");
+ cl->next = make_word_list (w, (WORD_LIST *)NULL);
+
+ return ret;
+}
+
+/* Build a command string with
+ $0 == cs->funcname (function to execute for completion list)
+ $1 == command name (command being completed)
+ $2 = word to be completed (possibly null)
+ $3 = previous word
+ and run in the current shell. The function should put its completion
+ list into the array variable COMPREPLY. We build a STRINGLIST
+ from the results and return it.
+
+ Since the shell function should return its list of matches in an array
+ 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)
+ COMPSPEC *cs;
+ char *text, *line;
+ int ind;
+ WORD_LIST *lwords;
+ int nw, cw;
+{
+ char *funcname;
+ STRINGLIST *sl;
+ SHELL_VAR *f, *v;
+ WORD_LIST *cmdlist;
+ int fval;
+#if defined (ARRAY_VARS)
+ ARRAY *a;
+#endif
+
+ funcname = cs->funcname;
+ f = find_function (funcname);
+ if (f == 0)
+ {
+ fprintf (stderr, "gen_shell_function_matches: function `%s' not found\n", funcname);
+ ding ();
+ rl_on_new_line ();
+ return ((STRINGLIST *)NULL);
+ }
+
+#if !defined (ARRAY_VARS)
+ return ((STRINGLIST *)NULL);
+#else
+
+ /* We pass cw - 1 because command_line_to_word_list returns indices that are
+ 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);
+
+ fval = execute_shell_function (f, cmdlist);
+
+ /* Now clean up and destroy everything. */
+ dispose_words (cmdlist);
+ unbind_compfunc_variables (0);
+
+ /* The list of completions is returned in the array variable COMPREPLY. */
+ v = find_variable ("COMPREPLY");
+ if (v == 0)
+ return ((STRINGLIST *)NULL);
+ if (array_p (v) == 0)
+ v = convert_var_to_array (v);
+
+ a = array_cell (v);
+ if (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->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);
+
+ return (sl);
+#endif
+}
+
+/* Build a command string with
+ $0 == cs->command (command to execute for completion list)
+ $1 == command name (command being completed)
+ $2 = word to be completed (possibly null)
+ $3 = previous word
+ and run in with command substitution. Parse the results, one word
+ per line, with backslashes allowed to escape newlines. Build a
+ STRINGLIST from the results and return it. */
+
+static STRINGLIST *
+gen_command_matches (cs, text, line, ind, lwords, nw, cw)
+ COMPSPEC *cs;
+ char *text, *line;
+ int ind;
+ WORD_LIST *lwords;
+ int nw, cw;
+{
+ char *csbuf, *cscmd, *t;
+ int cmdlen, cmdsize, n, ws, we;
+ WORD_LIST *cmdlist, *cl;
+ STRINGLIST *sl;
+
+ bind_compfunc_variables (line, ind, lwords, cw, 1);
+ cmdlist = build_arg_list (cs->command, text, lwords, cw);
+
+ /* Estimate the size needed for the buffer. */
+ n = strlen (cs->command);
+ cmdsize = n + 1;
+ for (cl = cmdlist->next; cl; cl = cl->next)
+ cmdsize += STRLEN (cl->word->word) + 3;
+ cmdsize += 2;
+
+ /* allocate the string for the command and fill it in. */
+ cscmd = 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 : "");
+ n = strlen (t);
+ RESIZE_MALLOCED_BUFFER (cscmd, cmdlen, n + 2, cmdsize, 64);
+ strcpy (cscmd + cmdlen, t);
+ cmdlen += n;
+ if (cl->next)
+ cscmd[cmdlen++] = ' ';
+ free (t);
+ }
+ cscmd[cmdlen] = '\0';
+
+ csbuf = command_substitute (cscmd, 0);
+
+ /* Now clean up and destroy everything. */
+ dispose_words (cmdlist);
+ free (cscmd);
+ unbind_compfunc_variables (1);
+
+ if (csbuf == 0 || *csbuf == '\0')
+ {
+ FREE (csbuf);
+ return ((STRINGLIST *)NULL);
+ }
+
+ /* 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);
+ for (ws = 0; csbuf[ws]; )
+ {
+ we = ws;
+ while (csbuf[we] && csbuf[we] != '\n')
+ {
+ if (csbuf[we] == '\\' && csbuf[we+1] == '\n')
+ we++;
+ we++;
+ }
+ t = substring (csbuf, ws, we);
+ if (sl->list_len >= sl->list_size - 1)
+ realloc_stringlist (sl, sl->list_size + 16);
+ sl->list[sl->list_len++] = t;
+ while (csbuf[we] == '\n') we++;
+ ws = we;
+ }
+ sl->list[sl->list_len] = (char *)NULL;
+
+ free (csbuf);
+ return (sl);
+}
+
+static WORD_LIST *
+command_line_to_word_list (line, llen, sentinel, nwp, cwp)
+ char *line;
+ int llen, sentinel, *nwp, *cwp;
+{
+ WORD_LIST *ret;
+ char *delims;
+
+ delims = "()<>;&| \t\n"; /* shell metacharacters break words */
+ ret = split_at_delims (line, llen, delims, sentinel, nwp, cwp);
+ return (ret);
+}
+
+/* Evaluate COMPSPEC *cs and return all matches for WORD. */
+
+STRINGLIST *
+gen_compspec_completions (cs, cmd, word, start, end)
+ COMPSPEC *cs;
+ char *cmd;
+ char *word;
+ int start, end;
+{
+ STRINGLIST *ret, *tmatches;
+ char *line, *lword;
+ int llen, nw, cw;
+ WORD_LIST *lwords;
+
+ debug_printf ("programmable_completions (%s, %s, %d, %d)", cmd, word, start, end);
+ debug_printf ("programmable_completions: %s -> 0x%x", cmd, (int)cs);
+ ret = gen_action_completions (cs, word);
+ if (ret && progcomp_debug)
+ {
+ debug_printf ("gen_action_completions (0x%x, %s) -->", (int)cs, word);
+ print_stringlist (ret, "\t");
+ rl_on_new_line ();
+ }
+
+ /* Now we start generating completions based on the other members of CS. */
+ if (cs->globpat)
+ {
+ tmatches = gen_globpat_matches (cs, word);
+ if (tmatches)
+ {
+ if (progcomp_debug)
+ {
+ debug_printf ("gen_globpat_matches (0x%x, %s) -->", (int)cs, word);
+ print_stringlist (tmatches, "\t");
+ rl_on_new_line ();
+ }
+ ret = append_stringlist (ret, tmatches);
+ free_stringlist (tmatches);
+ rl_filename_completion_desired = 1;
+ }
+ }
+
+ if (cs->words)
+ {
+ tmatches = gen_wordlist_matches (cs, word);
+ if (tmatches)
+ {
+ if (progcomp_debug)
+ {
+ debug_printf ("gen_wordlist_matches (0x%x, %s) -->", (int)cs, word);
+ print_stringlist (tmatches, "\t");
+ rl_on_new_line ();
+ }
+ ret = append_stringlist (ret, tmatches);
+ free_stringlist (tmatches);
+ }
+ }
+
+ lwords = (WORD_LIST *)NULL;
+ line = (char *)NULL;
+ if (cs->command || cs->funcname)
+ {
+ /* If we have a command or function to execute, we need to first break
+ the command line into individual words, find the number of words,
+ and find the word in the list containing the word to be completed. */
+ line = substring (rl_line_buffer, start, end);
+ llen = end - start;
+
+ debug_printf ("command_line_to_word_list (%s, %d, %d, 0x%x, 0x%x)",
+ line, llen, rl_point - start, &nw, &cw);
+ lwords = command_line_to_word_list (line, llen, rl_point - start, &nw, &cw);
+ if (lwords == 0 && llen > 0)
+ debug_printf ("ERROR: command_line_to_word_list returns NULL");
+ else if (progcomp_debug)
+ {
+ debug_printf ("command_line_to_word_list -->");
+ printf ("\t");
+ print_word_list (lwords, "!");
+ printf ("\n");
+ fflush(stdout);
+ rl_on_new_line ();
+ }
+ }
+
+ if (cs->funcname)
+ {
+ tmatches = gen_shell_function_matches (cs, word, line, rl_point - start, lwords, nw, cw);
+ if (tmatches)
+ {
+ 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");
+ rl_on_new_line ();
+ }
+ ret = append_stringlist (ret, tmatches);
+ free_stringlist (tmatches);
+ }
+ }
+
+ if (cs->command)
+ {
+ tmatches = gen_command_matches (cs, word, line, rl_point - start, lwords, nw, cw);
+ if (tmatches)
+ {
+ 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");
+ rl_on_new_line ();
+ }
+ ret = append_stringlist (ret, tmatches);
+ free_stringlist (tmatches);
+ }
+ }
+
+ if (cs->command || cs->funcname)
+ {
+ if (lwords)
+ dispose_words (lwords);
+ FREE (line);
+ }
+
+ if (cs->filterpat)
+ {
+ tmatches = filter_stringlist (ret, cs->filterpat, word);
+ if (progcomp_debug)
+ {
+ debug_printf ("filter_stringlist (0x%x, %s, %s) -->", ret, cs->filterpat, word);
+ print_stringlist (tmatches, "\t");
+ rl_on_new_line ();
+ }
+ if (ret && ret != tmatches)
+ {
+ FREE (ret->list);
+ free (ret);
+ }
+ ret = tmatches;
+ }
+
+ if (cs->prefix || cs->suffix)
+ ret = prefix_suffix_stringlist (ret, cs->prefix, cs->suffix);
+
+ 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;
+ int start, end, *foundp;
+{
+ COMPSPEC *cs;
+ STRINGLIST *ret;
+ char **rmatches, *t;
+
+ /* 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)
+ {
+ if (foundp)
+ *foundp = 0;
+ return ((char **)NULL);
+ }
+
+ /* Signal the caller that we found a COMPSPEC for this command. */
+ if (foundp)
+ *foundp = 1;
+
+ ret = gen_compspec_completions (cs, cmd, word, start, end);
+
+ if (ret)
+ {
+ rmatches = ret->list;
+ free (ret);
+ }
+ else
+ rmatches = (char **)NULL;
+
+ return (rmatches);
+}
+
+#endif /* PROGRAMMABLE_COMPLETION */