Bash-4.3 distribution sources and documentation
[platform/upstream/bash.git] / lib / readline / histexpand.c
index 0dc179a..4899b9d 100644 (file)
@@ -1,24 +1,23 @@
 /* histexpand.c -- history expansion. */
 
-/* Copyright (C) 1989, 1992 Free Software Foundation, Inc.
+/* Copyright (C) 1989-2012 Free Software Foundation, Inc.
 
-   This file contains the GNU History Library (the Library), a set of
+   This file contains the GNU History Library (History), a set of
    routines for managing the text of previously typed lines.
 
-   The Library is free software; you can redistribute it and/or modify
+   History is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 1, or (at your option)
-   any later version.
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
 
-   The Library 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.
+   History 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.
 
-   The GNU General Public License is often shipped with GNU software, and
-   is generally kept in a file called COPYING or LICENSE.  If you do not
-   have a copy of the license, write to the Free Software Foundation,
-   675 Mass Ave, Cambridge, MA 02139, USA. */
+   You should have received a copy of the GNU General Public License
+   along with History.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 #define READLINE_LIBRARY
 
 #  include <unistd.h>
 #endif
 
-#if defined (HAVE_STRING_H)
-#  include <string.h>
-#else
-#  include <strings.h>
-#endif /* !HAVE_STRING_H */
+#include "rlmbutil.h"
 
 #include "history.h"
 #include "histlib.h"
 
+#include "rlshell.h"
+#include "xmalloc.h"
+
 #define HISTORY_WORD_DELIMITERS                " \t\n;&()|<>"
 #define HISTORY_QUOTE_CHARACTERS       "\"'`"
 
+#define slashify_in_quotes "\\`\"$"
+
+typedef int _hist_search_func_t PARAMS((const char *, int));
+
 static char error_pointer;
 
 static char *subst_lhs;
@@ -60,15 +62,14 @@ static char *subst_rhs;
 static int subst_lhs_len;
 static int subst_rhs_len;
 
-static char *get_history_word_specifier ();
-static char *history_find_word ();
-
-extern int history_offset;
+static char *get_history_word_specifier PARAMS((char *, char *, int *));
+static int history_tokenize_word PARAMS((const char *, int));
+static char **history_tokenize_internal PARAMS((const char *, int, int *));
+static char *history_substring PARAMS((const char *, int, int));
+static void freewords PARAMS((char **, int));
+static char *history_find_word PARAMS((char *, int));
 
-extern char *single_quote ();
-static char *quote_breaks ();
-
-extern char *xmalloc (), *xrealloc ();
+static char *quote_breaks PARAMS((char *));
 
 /* Variables exported by this file. */
 /* The character that represents the start of a history expansion
@@ -93,9 +94,12 @@ char *history_no_expand_chars = " \t\n\r=";
    The default is 0. */
 int history_quotes_inhibit_expansion = 0;
 
+/* Used to split words by history_tokenize_internal. */
+char *history_word_delimiters = HISTORY_WORD_DELIMITERS;
+
 /* If set, this points to a function that is called to verify that a
    particular history expansion should be performed. */
-Function *history_inhibit_expansion_function;
+rl_linebuf_func_t *history_inhibit_expansion_function;
 
 /* **************************************************************** */
 /*                                                                 */
@@ -124,7 +128,7 @@ static char *search_match;
    line = get_history_event ("!echo:p", &index, 0);  */
 char *
 get_history_event (string, caller_index, delimiting_quote)
-     char *string;
+     const char *string;
      int *caller_index;
      int delimiting_quote;
 {
@@ -132,7 +136,7 @@ get_history_event (string, caller_index, delimiting_quote)
   register char c;
   HIST_ENTRY *entry;
   int which, sign, local_index, substring_okay;
-  Function *search_func;
+  _hist_search_func_t *search_func;
   char *temp;
 
   /* The event can be specified in a number of ways.
@@ -201,15 +205,35 @@ get_history_event (string, caller_index, delimiting_quote)
 
   /* Only a closing `?' or a newline delimit a substring search string. */
   for (local_index = i; c = string[i]; i++)
-    if ((!substring_okay && (whitespace (c) || c == ':' ||
-       (history_search_delimiter_chars && member (c, history_search_delimiter_chars)) ||
-       string[i] == delimiting_quote)) ||
-       string[i] == '\n' ||
-       (substring_okay && string[i] == '?'))
-      break;
+    {
+#if defined (HANDLE_MULTIBYTE)
+      if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
+       {
+         int v;
+         mbstate_t ps;
+
+         memset (&ps, 0, sizeof (mbstate_t));
+         /* These produce warnings because we're passing a const string to a
+            function that takes a non-const string. */
+         _rl_adjust_point ((char *)string, i, &ps);
+         if ((v = _rl_get_char_len ((char *)string + i, &ps)) > 1)
+           {
+             i += v - 1;
+             continue;
+           }
+        }
+
+#endif /* HANDLE_MULTIBYTE */
+      if ((!substring_okay && (whitespace (c) || c == ':' ||
+         (history_search_delimiter_chars && member (c, history_search_delimiter_chars)) ||
+         string[i] == delimiting_quote)) ||
+         string[i] == '\n' ||
+         (substring_okay && string[i] == '?'))
+       break;
+    }
 
   which = i - local_index;
-  temp = xmalloc (1 + which);
+  temp = (char *)xmalloc (1 + which);
   if (which)
     strncpy (temp, string + local_index, which);
   temp[which] = '\0';
@@ -221,7 +245,7 @@ get_history_event (string, caller_index, delimiting_quote)
 
 #define FAIL_SEARCH() \
   do { \
-    history_offset = history_length; free (temp) ; return (char *)NULL; \
+    history_offset = history_length; xfree (temp) ; return (char *)NULL; \
   } while (0)
 
   /* If there is no search string, try to use the previous search string,
@@ -230,7 +254,7 @@ get_history_event (string, caller_index, delimiting_quote)
     {
       if (search_string)
         {
-          free (temp);
+          xfree (temp);
           temp = savestring (search_string);
         }
       else
@@ -248,6 +272,8 @@ get_history_event (string, caller_index, delimiting_quote)
       if (local_index == 0 || substring_okay)
        {
          entry = current_history ();
+         if (entry == 0)
+           FAIL_SEARCH ();
          history_offset = history_length;
        
          /* If this was a substring search, then remember the
@@ -261,7 +287,7 @@ get_history_event (string, caller_index, delimiting_quote)
              search_match = history_find_word (entry->line, local_index);
            }
          else
-           free (temp);
+           xfree (temp);
 
          return (entry->line);
        }
@@ -281,16 +307,20 @@ get_history_event (string, caller_index, delimiting_quote)
 /* Extract the contents of STRING as if it is enclosed in single quotes.
    SINDEX, when passed in, is the offset of the character immediately
    following the opening single quote; on exit, SINDEX is left pointing
-   to the closing single quote. */
+   to the closing single quote.  FLAGS currently used to allow backslash
+   to escape a single quote (e.g., for bash $'...'). */
 static void
-hist_string_extract_single_quoted (string, sindex)
+hist_string_extract_single_quoted (string, sindex, flags)
      char *string;
-     int *sindex;
+     int *sindex, flags;
 {
   register int i;
 
   for (i = *sindex; string[i] && string[i] != '\''; i++)
-    ;
+    {
+      if ((flags & 1) && string[i] == '\\' && string[i+1])
+        i++;
+    }
 
   *sindex = i;
 }
@@ -311,7 +341,7 @@ quote_breaks (s)
        len += 2;
     }
 
-  r = ret = xmalloc (len);
+  r = ret = (char *)xmalloc (len);
   *r++ = '\'';
   for (p = s; p && *p; )
     {
@@ -342,7 +372,8 @@ hist_error(s, start, current, errtype)
       char *s;
       int start, current, errtype;
 {
-  char *temp, *emsg;
+  char *temp;
+  const char *emsg;
   int ll, elen;
 
   ll = current - start;
@@ -365,13 +396,17 @@ hist_error(s, start, current, errtype)
       emsg = "unrecognized history modifier";
       elen = 29;
       break;
+    case NO_PREV_SUBST:
+      emsg = "no previous substitution";
+      elen = 24;
+      break;
     default:
       emsg = "unknown expansion error";
       elen = 23;
       break;
     }
 
-  temp = xmalloc (ll + elen + 3);
+  temp = (char *)xmalloc (ll + elen + 3);
   strncpy (temp, s + start, ll);
   temp[ll] = ':';
   temp[ll + 1] = ' ';
@@ -397,17 +432,37 @@ get_subst_pattern (str, iptr, delimiter, is_rhs, lenptr)
      int *iptr, delimiter, is_rhs, *lenptr;
 {
   register int si, i, j, k;
-  char *s = (char *) NULL;
+  char *s;
+#if defined (HANDLE_MULTIBYTE)
+  mbstate_t ps;
+#endif
 
+  s = (char *)NULL;
   i = *iptr;
 
+#if defined (HANDLE_MULTIBYTE)
+  memset (&ps, 0, sizeof (mbstate_t));
+  _rl_adjust_point (str, i, &ps);
+#endif
+
   for (si = i; str[si] && str[si] != delimiter; si++)
-    if (str[si] == '\\' && str[si + 1] == delimiter)
-      si++;
+#if defined (HANDLE_MULTIBYTE)
+    if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
+      {
+       int v;
+       if ((v = _rl_get_char_len (str + si, &ps)) > 1)
+         si += v - 1;
+       else if (str[si] == '\\' && str[si + 1] == delimiter)
+         si++;
+      }
+    else
+#endif /* HANDLE_MULTIBYTE */
+      if (str[si] == '\\' && str[si + 1] == delimiter)
+       si++;
 
   if (si > i || is_rhs)
     {
-      s = xmalloc (si - i + 1);
+      s = (char *)xmalloc (si - i + 1);
       for (j = 0, k = i; k < si; j++, k++)
        {
          /* Remove a backslash quoting the search string delimiter. */
@@ -434,13 +489,13 @@ postproc_subst_rhs ()
   char *new;
   int i, j, new_size;
 
-  new = xmalloc (new_size = subst_rhs_len + subst_lhs_len);
+  new = (char *)xmalloc (new_size = subst_rhs_len + subst_lhs_len);
   for (i = j = 0; i < subst_rhs_len; i++)
     {
       if (subst_rhs[i] == '&')
        {
          if (j + subst_lhs_len >= new_size)
-           new = xrealloc (new, (new_size = new_size * 2 + subst_lhs_len));
+           new = (char *)xrealloc (new, (new_size = new_size * 2 + subst_lhs_len));
          strcpy (new + j, subst_lhs);
          j += subst_lhs_len;
        }
@@ -450,12 +505,12 @@ postproc_subst_rhs ()
          if (subst_rhs[i] == '\\' && subst_rhs[i + 1] == '&')
            i++;
          if (j >= new_size)
-           new = xrealloc (new, new_size *= 2);
+           new = (char *)xrealloc (new, new_size *= 2);
          new[j++] = subst_rhs[i];
        }
     }
   new[j] = '\0';
-  free (subst_rhs);
+  xfree (subst_rhs);
   subst_rhs = new;
   subst_rhs_len = j;
 }
@@ -466,18 +521,23 @@ postproc_subst_rhs ()
    the returned string.  Returns the new index into string in
    *END_INDEX_PTR, and the expanded specifier in *RET_STRING. */
 static int
-history_expand_internal (string, start, end_index_ptr, ret_string, current_line)
+history_expand_internal (string, start, qc, end_index_ptr, ret_string, current_line)
      char *string;
-     int start, *end_index_ptr;
+     int start, qc, *end_index_ptr;
      char **ret_string;
      char *current_line;       /* for !# */
 {
   int i, n, starting_index;
-  int substitute_globally, want_quotes, print_only;
+  int substitute_globally, subst_bywords, want_quotes, print_only;
   char *event, *temp, *result, *tstr, *t, c, *word_spec;
   int result_len;
+#if defined (HANDLE_MULTIBYTE)
+  mbstate_t ps;
 
-  result = xmalloc (result_len = 128);
+  memset (&ps, 0, sizeof (mbstate_t));
+#endif
+
+  result = (char *)xmalloc (result_len = 128);
 
   i = start;
 
@@ -499,22 +559,12 @@ history_expand_internal (string, start, end_index_ptr, ret_string, current_line)
       event = current_line;
     }
   else
-    {
-      int quoted_search_delimiter = 0;
-
-      /* If the character before this `!' is a double or single
-        quote, then this expansion takes place inside of the
-        quoted string.  If we have to search for some text ("!foo"),
-        allow the delimiter to end the search string. */
-      if (i && (string[i - 1] == '\'' || string[i - 1] == '"'))
-       quoted_search_delimiter = string[i - 1];
-      event = get_history_event (string, &i, quoted_search_delimiter);
-    }
+    event = get_history_event (string, &i, qc);
          
   if (event == 0)
     {
       *ret_string = hist_error (string, start, i, EVENT_NOT_FOUND);
-      free (result);
+      xfree (result);
       return (-1);
     }
 
@@ -528,7 +578,7 @@ history_expand_internal (string, start, end_index_ptr, ret_string, current_line)
   if (word_spec == (char *)&error_pointer)
     {
       *ret_string = hist_error (string, starting_index, i, BAD_WORD_SPEC);
-      free (result);
+      xfree (result);
       return (-1);
     }
 
@@ -537,26 +587,32 @@ history_expand_internal (string, start, end_index_ptr, ret_string, current_line)
   FREE (word_spec);
 
   /* Perhaps there are other modifiers involved.  Do what they say. */
-  want_quotes = substitute_globally = print_only = 0;
+  want_quotes = substitute_globally = subst_bywords = print_only = 0;
   starting_index = i;
 
   while (string[i] == ':')
     {
       c = string[i + 1];
 
-      if (c == 'g')
+      if (c == 'g' || c == 'a')
        {
          substitute_globally = 1;
          i++;
          c = string[i + 1];
        }
+      else if (c == 'G')
+       {
+         subst_bywords = 1;
+         i++;
+         c = string[i + 1];
+       }
 
       switch (c)
        {
        default:
          *ret_string = hist_error (string, i+1, i+2, BAD_MODIFIER);
-         free (result);
-         free (temp);
+         xfree (result);
+         xfree (temp);
          return -1;
 
        case 'q':
@@ -581,7 +637,7 @@ history_expand_internal (string, start, end_index_ptr, ret_string, current_line)
            {
              tstr++;
              t = savestring (tstr);
-             free (temp);
+             xfree (temp);
              temp = t;
            }
          break;
@@ -606,7 +662,7 @@ history_expand_internal (string, start, end_index_ptr, ret_string, current_line)
          if (tstr)
            {
              t = savestring (tstr);
-             free (temp);
+             xfree (temp);
              temp = t;
            }
          break;
@@ -620,13 +676,26 @@ history_expand_internal (string, start, end_index_ptr, ret_string, current_line)
        case '&':
        case 's':
          {
-           char *new_event, *t;
-           int delimiter, failed, si, l_temp;
+           char *new_event;
+           int delimiter, failed, si, l_temp, ws, we;
 
            if (c == 's')
              {
                if (i + 2 < (int)strlen (string))
-                 delimiter = string[i + 2];
+                 {
+#if defined (HANDLE_MULTIBYTE)
+                   if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
+                     {
+                       _rl_adjust_point (string, i + 2, &ps);
+                       if (_rl_get_char_len (string + i + 2, &ps) > 1)
+                         delimiter = 0;
+                       else
+                         delimiter = string[i + 2];
+                     }
+                   else
+#endif /* HANDLE_MULTIBYTE */
+                     delimiter = string[i + 2];
+                 }
                else
                  break;        /* no search delimiter */
 
@@ -654,15 +723,6 @@ history_expand_internal (string, start, end_index_ptr, ret_string, current_line)
                      }
                  }
 
-               /* If there is no lhs, the substitution can't succeed. */
-               if (subst_lhs_len == 0)
-                 {
-                   *ret_string = hist_error (string, starting_index, i, SUBST_FAILED);
-                   free (result);
-                   free (temp);
-                   return -1;
-                 }
-
                FREE (subst_rhs);
                subst_rhs = get_subst_pattern (string, &i, delimiter, 1, &subst_rhs_len);
 
@@ -674,44 +734,87 @@ history_expand_internal (string, start, end_index_ptr, ret_string, current_line)
            else
              i += 2;
 
+           /* If there is no lhs, the substitution can't succeed. */
+           if (subst_lhs_len == 0)
+             {
+               *ret_string = hist_error (string, starting_index, i, NO_PREV_SUBST);
+               xfree (result);
+               xfree (temp);
+               return -1;
+             }
+
            l_temp = strlen (temp);
            /* Ignore impossible cases. */
            if (subst_lhs_len > l_temp)
              {
                *ret_string = hist_error (string, starting_index, i, SUBST_FAILED);
-               free (result);
-               free (temp);
+               xfree (result);
+               xfree (temp);
                return (-1);
              }
 
            /* Find the first occurrence of THIS in TEMP. */
-           si = 0;
+           /* Substitute SUBST_RHS for SUBST_LHS in TEMP.  There are three
+              cases to consider:
+
+                1.  substitute_globally == subst_bywords == 0
+                2.  substitute_globally == 1 && subst_bywords == 0
+                3.  substitute_globally == 0 && subst_bywords == 1
+
+              In the first case, we substitute for the first occurrence only.
+              In the second case, we substitute for every occurrence.
+              In the third case, we tokenize into words and substitute the
+              first occurrence of each word. */
+
+           si = we = 0;
            for (failed = 1; (si + subst_lhs_len) <= l_temp; si++)
-             if (STREQN (temp+si, subst_lhs, subst_lhs_len))
-               {
-                 int len = subst_rhs_len - subst_lhs_len + l_temp;
-                 new_event = xmalloc (1 + len);
-                 strncpy (new_event, temp, si);
-                 strncpy (new_event + si, subst_rhs, subst_rhs_len);
-                 strncpy (new_event + si + subst_rhs_len,
-                          temp + si + subst_lhs_len,
-                          l_temp - (si + subst_lhs_len));
-                 new_event[len] = '\0';
-                 free (temp);
-                 temp = new_event;
-
-                 failed = 0;
-
-                 if (substitute_globally)
-                   {
-                     si += subst_rhs_len;
-                     l_temp = strlen (temp);
-                     substitute_globally++;
-                     continue;
-                   }
-                 else
-                   break;
-               }
+             {
+               /* First skip whitespace and find word boundaries if
+                  we're past the end of the word boundary we found
+                  the last time. */
+               if (subst_bywords && si > we)
+                 {
+                   for (; temp[si] && whitespace (temp[si]); si++)
+                     ;
+                   ws = si;
+                   we = history_tokenize_word (temp, si);
+                 }
+
+               if (STREQN (temp+si, subst_lhs, subst_lhs_len))
+                 {
+                   int len = subst_rhs_len - subst_lhs_len + l_temp;
+                   new_event = (char *)xmalloc (1 + len);
+                   strncpy (new_event, temp, si);
+                   strncpy (new_event + si, subst_rhs, subst_rhs_len);
+                   strncpy (new_event + si + subst_rhs_len,
+                            temp + si + subst_lhs_len,
+                            l_temp - (si + subst_lhs_len));
+                   new_event[len] = '\0';
+                   xfree (temp);
+                   temp = new_event;
+
+                   failed = 0;
+
+                   if (substitute_globally)
+                     {
+                       /* Reported to fix a bug that causes it to skip every
+                          other match when matching a single character.  Was
+                          si += subst_rhs_len previously. */
+                       si += subst_rhs_len - 1;
+                       l_temp = strlen (temp);
+                       substitute_globally++;
+                       continue;
+                     }
+                   else if (subst_bywords)
+                     {
+                       si = we;
+                       l_temp = strlen (temp);
+                       continue;
+                     }
+                   else
+                     break;
+                 }
+             }
 
            if (substitute_globally > 1)
              {
@@ -723,14 +826,14 @@ history_expand_internal (string, start, end_index_ptr, ret_string, current_line)
              continue;         /* don't want to increment i */
 
            *ret_string = hist_error (string, starting_index, i, SUBST_FAILED);
-           free (result);
-           free (temp);
+           xfree (result);
+           xfree (temp);
            return (-1);
          }
        }
       i += 2;
     }
-  /* Done with modfiers. */
+  /* Done with modifiers. */
   /* Believe it or not, we have to back the pointer up by one. */
   --i;
 
@@ -739,21 +842,21 @@ history_expand_internal (string, start, end_index_ptr, ret_string, current_line)
       char *x;
 
       if (want_quotes == 'q')
-       x = single_quote (temp);
+       x = sh_single_quote (temp);
       else if (want_quotes == 'x')
        x = quote_breaks (temp);
       else
        x = savestring (temp);
 
-      free (temp);
+      xfree (temp);
       temp = x;
     }
 
   n = strlen (temp);
   if (n >= result_len)
-    result = xrealloc (result, n + 2);
+    result = (char *)xrealloc (result, n + 2);
   strcpy (result, temp);
-  free (temp);
+  xfree (temp);
 
   *end_index_ptr = i;
   *ret_string = result;
@@ -782,7 +885,7 @@ history_expand_internal (string, start, end_index_ptr, ret_string, current_line)
              { \
                while (j >= result_len) \
                  result_len += 128; \
-               result = xrealloc (result, result_len); \
+               result = (char *)xrealloc (result, result_len); \
              } \
            strcpy (result + j - sl, s); \
          } \
@@ -792,7 +895,7 @@ history_expand_internal (string, start, end_index_ptr, ret_string, current_line)
        do \
          { \
            if (j >= result_len - 1) \
-             result = xrealloc (result, result_len += 64); \
+             result = (char *)xrealloc (result, result_len += 64); \
            result[j++] = c; \
            result[j] = '\0'; \
          } \
@@ -804,16 +907,24 @@ history_expand (hstring, output)
      char **output;
 {
   register int j;
-  int i, r, l, passc, cc, modified, eindex, only_printing;
+  int i, r, l, passc, cc, modified, eindex, only_printing, dquote, squote, flag;
   char *string;
 
   /* The output string, and its length. */
   int result_len;
   char *result;
 
+#if defined (HANDLE_MULTIBYTE)
+  char mb[MB_LEN_MAX];
+  mbstate_t ps;
+#endif
+
   /* Used when adding the string. */
   char *temp;
 
+  if (output == 0)
+    return 0;
+
   /* Setting the history expansion character to 0 inhibits all
      history expansion. */
   if (history_expansion_char == 0)
@@ -823,7 +934,7 @@ history_expand (hstring, output)
     }
     
   /* Prepare the buffer for printing error messages. */
-  result = xmalloc (result_len = 256);
+  result = (char *)xmalloc (result_len = 256);
   result[0] = '\0';
 
   only_printing = modified = 0;
@@ -840,7 +951,7 @@ history_expand (hstring, output)
      that is the substitution that we do. */
   if (hstring[0] == history_subst_char)
     {
-      string = xmalloc (l + 5);
+      string = (char *)xmalloc (l + 5);
 
       string[0] = string[1] = history_expansion_char;
       string[2] = ':';
@@ -850,20 +961,37 @@ history_expand (hstring, output)
     }
   else
     {
+#if defined (HANDLE_MULTIBYTE)
+      memset (&ps, 0, sizeof (mbstate_t));
+#endif
+
       string = hstring;
       /* If not quick substitution, still maybe have to do expansion. */
 
       /* `!' followed by one of the characters in history_no_expand_chars
         is NOT an expansion. */
-      for (i = 0; string[i]; i++)
+      for (i = dquote = squote = 0; string[i]; i++)
        {
+#if defined (HANDLE_MULTIBYTE)
+         if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
+           {
+             int v;
+             v = _rl_get_char_len (string + i, &ps);
+             if (v > 1)
+               {
+                 i += v - 1;
+                 continue;
+               }
+           }
+#endif /* HANDLE_MULTIBYTE */
+
          cc = string[i + 1];
-         /* The history_comment_char, if set, appearing that the beginning
+         /* The history_comment_char, if set, appearing at the beginning
             of a word signifies that the rest of the line should not have
             history expansion performed on it.
             Skip the rest of the line and break out of the loop. */
          if (history_comment_char && string[i] == history_comment_char &&
-             (i == 0 || member (string[i - 1], HISTORY_WORD_DELIMITERS)))
+             (i == 0 || member (string[i - 1], history_word_delimiters)))
            {
              while (string[i])
                i++;
@@ -871,7 +999,14 @@ history_expand (hstring, output)
            }
          else if (string[i] == history_expansion_char)
            {
-             if (!cc || member (cc, history_no_expand_chars))
+             if (cc == 0 || member (cc, history_no_expand_chars))
+               continue;
+             /* DQUOTE won't be set unless history_quotes_inhibit_expansion
+                is set.  The idea here is to treat double-quoted strings the
+                same as the word outside double quotes; in effect making the
+                double quote part of history_no_expand_chars when DQUOTE is
+                set. */
+             else if (dquote && cc == '"')
                continue;
              /* If the calling application has set
                 history_inhibit_expansion_function to a function that checks
@@ -884,13 +1019,24 @@ history_expand (hstring, output)
              else
                break;
            }
-         /* XXX - at some point, might want to extend this to handle
-                  double quotes as well. */
-         else if (history_quotes_inhibit_expansion && string[i] == '\'')
+         /* Shell-like quoting: allow backslashes to quote double quotes
+            inside a double-quoted string. */
+         else if (dquote && string[i] == '\\' && cc == '"')
+           i++;
+         /* More shell-like quoting:  if we're paying attention to single
+            quotes and letting them quote the history expansion character,
+            then we need to pay attention to double quotes, because single
+            quotes are not special inside double-quoted strings. */
+         else if (history_quotes_inhibit_expansion && string[i] == '"')
+           {
+             dquote = 1 - dquote;
+           }
+         else if (dquote == 0 && history_quotes_inhibit_expansion && string[i] == '\'')
            {
              /* If this is bash, single quotes inhibit history expansion. */
+             flag = (i > 0 && string[i - 1] == '$');
              i++;
-             hist_string_extract_single_quoted (string, &i);
+             hist_string_extract_single_quoted (string, &i, flag);
            }
          else if (history_quotes_inhibit_expansion && string[i] == '\\')
            {
@@ -899,20 +1045,21 @@ history_expand (hstring, output)
              if (cc == '\'' || cc == history_expansion_char)
                i++;
            }
+         
        }
          
       if (string[i] != history_expansion_char)
        {
-         free (result);
+         xfree (result);
          *output = savestring (string);
          return (0);
        }
     }
 
   /* Extract and perform the substitution. */
-  for (passc = i = j = 0; i < l; i++)
+  for (passc = dquote = squote = i = j = 0; i < l; i++)
     {
-      int tchar = string[i];
+      int qc, tchar = string[i];
 
       if (passc)
        {
@@ -921,6 +1068,30 @@ history_expand (hstring, output)
          continue;
        }
 
+#if defined (HANDLE_MULTIBYTE)
+      if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
+       {
+         int k, c;
+
+         c = tchar;
+         memset (mb, 0, sizeof (mb));
+         for (k = 0; k < MB_LEN_MAX; k++)
+           {
+             mb[k] = (char)c;
+             memset (&ps, 0, sizeof (mbstate_t));
+             if (_rl_get_char_len (mb, &ps) == -2)
+               c = string[++i];
+             else
+               break;
+           }
+         if (strlen (mb) > 1)
+           {
+             ADD_STRING (mb);
+             continue;
+           }
+       }
+#endif /* HANDLE_MULTIBYTE */
+
       if (tchar == history_expansion_char)
        tchar = -3;
       else if (tchar == history_comment_char)
@@ -937,23 +1108,40 @@ history_expand (hstring, output)
          ADD_CHAR (tchar);
          break;
 
+       case '"':
+         dquote = 1 - dquote;
+         ADD_CHAR (tchar);
+         break;
+         
        case '\'':
          {
            /* If history_quotes_inhibit_expansion is set, single quotes
-              inhibit history expansion. */
-           if (history_quotes_inhibit_expansion)
+              inhibit history expansion, otherwise they are treated like
+              double quotes. */
+           if (squote)
+             {
+               squote = 0;
+               ADD_CHAR (tchar);
+             }
+           else if (dquote == 0 && history_quotes_inhibit_expansion)
              {
                int quote, slen;
 
+               flag = (i > 0 && string[i - 1] == '$');
                quote = i++;
-               hist_string_extract_single_quoted (string, &i);
+               hist_string_extract_single_quoted (string, &i, flag);
 
                slen = i - quote + 2;
-               temp = xmalloc (slen);
+               temp = (char *)xmalloc (slen);
                strncpy (temp, string + quote, slen);
                temp[slen - 1] = '\0';
                ADD_STRING (temp);
-               free (temp);
+               xfree (temp);
+             }
+           else if (dquote == 0 && squote == 0 && history_quotes_inhibit_expansion == 0)
+             {
+               squote = 1;
+               ADD_CHAR (string[i]);
              }
            else
              ADD_CHAR (string[i]);
@@ -961,12 +1149,12 @@ history_expand (hstring, output)
          }
 
        case -2:                /* history_comment_char */
-         if (i == 0 || member (string[i - 1], HISTORY_WORD_DELIMITERS))
+         if (i == 0 || member (string[i - 1], history_word_delimiters))
            {
-             temp = xmalloc (l - i + 1);
+             temp = (char *)xmalloc (l - i + 1);
              strcpy (temp, string + i);
              ADD_STRING (temp);
-             free (temp);
+             xfree (temp);
              i = l;
            }
          else
@@ -979,7 +1167,9 @@ history_expand (hstring, output)
          /* If the history_expansion_char is followed by one of the
             characters in history_no_expand_chars, then it is not a
             candidate for expansion of any kind. */
-         if (member (cc, history_no_expand_chars))
+         if (cc == 0 || member (cc, history_no_expand_chars) ||
+                        (dquote && cc == '"') ||
+                        (history_inhibit_expansion_function && (*history_inhibit_expansion_function) (string, i)))
            {
              ADD_CHAR (string[i]);
              break;
@@ -995,23 +1185,23 @@ history_expand (hstring, output)
            {
              if (result)
                {
-                 temp = xmalloc (1 + strlen (result));
+                 temp = (char *)xmalloc (1 + strlen (result));
                  strcpy (temp, result);
                  ADD_STRING (temp);
-                 free (temp);
+                 xfree (temp);
                }
              i++;
              break;
            }
 #endif
-
-         r = history_expand_internal (string, i, &eindex, &temp, result);
+         qc = squote ? '\'' : (dquote ? '"' : 0);
+         r = history_expand_internal (string, i, qc, &eindex, &temp, result);
          if (r < 0)
            {
              *output = temp;
-             free (result);
+             xfree (result);
              if (string != hstring)
-               free (string);
+               xfree (string);
              return -1;
            }
          else
@@ -1021,7 +1211,7 @@ history_expand (hstring, output)
                  modified++;
                  if (*temp)
                    ADD_STRING (temp);
-                 free (temp);
+                 xfree (temp);
                }
              only_printing = r == 1;
              i = eindex;
@@ -1032,11 +1222,13 @@ history_expand (hstring, output)
 
   *output = result;
   if (string != hstring)
-    free (string);
+    xfree (string);
 
   if (only_printing)
     {
+#if 0
       add_history (result);
+#endif
       return (2);
     }
 
@@ -1099,7 +1291,10 @@ get_history_word_specifier (spec, from, caller_index)
   if (spec[i] == '-')
     first = 0;
   else if (spec[i] == '^')
-    first = 1;
+    {
+      first = 1;
+      i++;
+    }
   else if (_rl_digit_p (spec[i]) && expecting_word_spec)
     {
       for (first = 0; _rl_digit_p (spec[i]); i++)
@@ -1129,7 +1324,14 @@ get_history_word_specifier (spec, from, caller_index)
          i++;
          last = '$';
        }
-      else if (!spec[i] || spec[i] == ':')  /* could be modifier separator */
+#if 0
+      else if (!spec[i] || spec[i] == ':')
+       /* check against `:' because there could be a modifier separator */
+#else
+      else
+       /* csh seems to allow anything to terminate the word spec here,
+          leaving it as an abbreviation. */
+#endif
        last = -1;              /* x- abbreviates x-$ omitting word `$' */
     }
 
@@ -1149,7 +1351,7 @@ get_history_word_specifier (spec, from, caller_index)
 char *
 history_arg_extract (first, last, string)
      int first, last;
-     char *string;
+     const char *string;
 {
   register int i, len;
   char *result;
@@ -1185,7 +1387,7 @@ history_arg_extract (first, last, string)
     {
       for (size = 0, i = first; i < last; i++)
        size += strlen (list[i]) + 1;
-      result = xmalloc (size + 1);
+      result = (char *)xmalloc (size + 1);
       result[0] = '\0';
 
       for (i = first, offset = 0; i < last; i++)
@@ -1201,13 +1403,154 @@ history_arg_extract (first, last, string)
     }
 
   for (i = 0; i < len; i++)
-    free (list[i]);
-  free (list);
+    xfree (list[i]);
+  xfree (list);
 
   return (result);
 }
 
-#define slashify_in_quotes "\\`\"$"
+static int
+history_tokenize_word (string, ind)
+     const char *string;
+     int ind;
+{
+  register int i;
+  int delimiter, nestdelim, delimopen;
+
+  i = ind;
+  delimiter = nestdelim = 0;
+
+  if (member (string[i], "()\n"))
+    {
+      i++;
+      return i;
+    }
+
+  if (member (string[i], "<>;&|$"))
+    {
+      int peek = string[i + 1];
+
+      if (peek == string[i] && peek != '$')
+       {
+         if (peek == '<' && string[i + 2] == '-')
+           i++;
+         else if (peek == '<' && string[i + 2] == '<')
+           i++;
+         i += 2;
+         return i;
+       }
+      else if ((peek == '&' && (string[i] == '>' || string[i] == '<')) ||
+               (peek == '>' && string[i] == '&'))
+       {
+         i += 2;
+         return i;
+       }
+      /* XXX - separated out for later -- bash-4.2 */
+      else if ((peek == '(' && (string[i] == '>' || string[i] == '<')) || /* ) */
+              (peek == '(' && string[i] == '$')) /*)*/
+       {
+         i += 2;
+         delimopen = '(';
+         delimiter = ')';
+         nestdelim = 1;
+         goto get_word;
+       }
+#if 0
+      else if (peek == '\'' && string[i] == '$')
+        {
+         i += 2;       /* XXX */
+         return i;
+        }
+#endif
+
+      if (string[i] != '$')
+       {
+         i++;
+         return i;
+       }
+    }
+
+  /* same code also used for $(...)/<(...)/>(...) above */
+  if (member (string[i], "!@?+*"))
+    {
+      int peek = string[i + 1];
+
+      if (peek == '(')         /*)*/
+       {
+         /* Shell extended globbing patterns */
+         i += 2;
+         delimopen = '(';
+         delimiter = ')';      /* XXX - not perfect */
+         nestdelim = 1;
+       }
+    }
+
+get_word:
+  /* Get word from string + i; */
+
+  if (delimiter == 0 && member (string[i], HISTORY_QUOTE_CHARACTERS))
+    delimiter = string[i++];
+
+  for (; string[i]; i++)
+    {
+      if (string[i] == '\\' && string[i + 1] == '\n')
+       {
+         i++;
+         continue;
+       }
+
+      if (string[i] == '\\' && delimiter != '\'' &&
+         (delimiter != '"' || member (string[i], slashify_in_quotes)))
+       {
+         i++;
+         continue;
+       }
+
+      /* delimiter must be set and set to something other than a quote if
+        nestdelim is set, so these tests are safe. */
+      if (nestdelim && string[i] == delimopen)
+       {
+         nestdelim++;
+         continue;
+       }
+      if (nestdelim && string[i] == delimiter)
+       {
+         nestdelim--;
+         if (nestdelim == 0)
+           delimiter = 0;
+         continue;
+       }
+      
+      if (delimiter && string[i] == delimiter)
+       {
+         delimiter = 0;
+         continue;
+       }
+
+      if (delimiter == 0 && (member (string[i], history_word_delimiters)))
+       break;
+
+      if (delimiter == 0 && member (string[i], HISTORY_QUOTE_CHARACTERS))
+       delimiter = string[i];
+    }
+
+  return i;
+}
+
+static char *
+history_substring (string, start, end)
+     const char *string;
+     int start, end;
+{
+  register int len;
+  register char *result;
+
+  len = end - start;
+  result = (char *)xmalloc (len + 1);
+  strncpy (result, string + start, len);
+  result[len] = '\0';
+  return result;
+}
 
 /* Parse STRING into tokens and return an array of strings.  If WIND is
    not -1 and INDP is not null, we also want the word surrounding index
@@ -1215,19 +1558,21 @@ history_arg_extract (first, last, string)
    *INDP. */
 static char **
 history_tokenize_internal (string, wind, indp)
-     char *string;
+     const char *string;
      int wind, *indp;
 {
   char **result;
   register int i, start, result_index, size;
-  int len, delimiter;
+
+  /* If we're searching for a string that's not part of a word (e.g., " "),
+     make sure we set *INDP to a reasonable value. */
+  if (indp && wind != -1)
+    *indp = -1;
 
   /* Get a token, and stuff it into RESULT.  The tokens are split
      exactly where the shell would split them. */
   for (i = result_index = size = 0, result = (char **)NULL; string[i]; )
     {
-      delimiter = 0;
-
       /* Skip leading whitespace. */
       for (; string[i] && whitespace (string[i]); i++)
        ;
@@ -1235,88 +1580,30 @@ history_tokenize_internal (string, wind, indp)
        return (result);
 
       start = i;
-      
-      if (member (string[i], "()\n"))
-       {
-         i++;
-         goto got_token;
-       }
-
-      if (member (string[i], "<>;&|$"))
-       {
-         int peek = string[i + 1];
-
-         if (peek == string[i] && peek != '$')
-           {
-             if (peek == '<' && string[i + 2] == '-')
-               i++;
-             i += 2;
-             goto got_token;
-           }
-         else
-           {
-             if ((peek == '&' && (string[i] == '>' || string[i] == '<')) ||
-                 ((peek == '>') && (string[i] == '&')) ||
-                 ((peek == '(') && (string[i] == '$')))
-               {
-                 i += 2;
-                 goto got_token;
-               }
-           }
-         if (string[i] != '$')
-           {
-             i++;
-             goto got_token;
-           }
-       }
 
-      /* Get word from string + i; */
+      i = history_tokenize_word (string, start);
 
-      if (member (string[i], HISTORY_QUOTE_CHARACTERS))
-       delimiter = string[i++];
-
-      for (; string[i]; i++)
+      /* If we have a non-whitespace delimiter character (which would not be
+        skipped by the loop above), use it and any adjacent delimiters to
+        make a separate field.  Any adjacent white space will be skipped the
+        next time through the loop. */
+      if (i == start && history_word_delimiters)
        {
-         if (string[i] == '\\' && string[i + 1] == '\n')
-           {
-             i++;
-             continue;
-           }
-
-         if (string[i] == '\\' && delimiter != '\'' &&
-             (delimiter != '"' || member (string[i], slashify_in_quotes)))
-           {
-             i++;
-             continue;
-           }
-
-         if (delimiter && string[i] == delimiter)
-           {
-             delimiter = 0;
-             continue;
-           }
-
-         if (!delimiter && (member (string[i], HISTORY_WORD_DELIMITERS)))
-           break;
-
-         if (!delimiter && member (string[i], HISTORY_QUOTE_CHARACTERS))
-           delimiter = string[i];
+         i++;
+         while (string[i] && member (string[i], history_word_delimiters))
+           i++;
        }
 
-    got_token:
-
       /* If we are looking for the word in which the character at a
         particular index falls, remember it. */
       if (indp && wind != -1 && wind >= start && wind < i)
         *indp = result_index;
 
-      len = i - start;
       if (result_index + 2 >= size)
        result = (char **)xrealloc (result, ((size += 10) * sizeof (char *)));
-      result[result_index] = xmalloc (1 + len);
-      strncpy (result[result_index], string + start, len);
-      result[result_index][len] = '\0';
-      result[++result_index] = (char *)NULL;
+
+      result[result_index++] = history_substring (string, start, i);
+      result[result_index] = (char *)NULL;
     }
 
   return (result);
@@ -1326,11 +1613,23 @@ history_tokenize_internal (string, wind, indp)
    parsed out of STRING. */
 char **
 history_tokenize (string)
-     char *string;
+     const char *string;
 {
   return (history_tokenize_internal (string, -1, (int *)NULL));
 }
 
+/* Free members of WORDS from START to an empty string */
+static void
+freewords (words, start)
+     char **words;
+     int start;
+{
+  register int i;
+
+  for (i = start; words[i]; i++)
+    xfree (words[i]);
+}
+
 /* Find and return the word which contains the character at index IND
    in the history line LINE.  Used to save the word matched by the
    last history !?string? search. */
@@ -1343,13 +1642,17 @@ history_find_word (line, ind)
   int i, wind;
 
   words = history_tokenize_internal (line, ind, &wind);
-  if (wind == -1)
-    return ((char *)NULL);
+  if (wind == -1 || words == 0)
+    {
+      if (words)
+       freewords (words, 0);
+      FREE (words);
+      return ((char *)NULL);
+    }
   s = words[wind];
   for (i = 0; i < wind; i++)
-    free (words[i]);
-  for (i = wind + 1; words[i]; i++)
-    free (words[i]);
-  free (words);
+    xfree (words[i]);
+  freewords (words, wind + 1);
+  xfree (words);
   return s;
 }