/* histexpand.c -- history expansion. */
-/* Copyright (C) 1989-2004 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 2, 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,
- 59 Temple Place, Suite 330, Boston, MA 02111 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
#define HISTORY_WORD_DELIMITERS " \t\n;&()|<>"
#define HISTORY_QUOTE_CHARACTERS "\"'`"
+#define HISTORY_EVENT_DELIMITERS "^$*%-"
#define slashify_in_quotes "\\`\"$"
typedef int _hist_search_func_t PARAMS((const char *, int));
-extern int rl_byte_oriented; /* declared in mbutil.c */
-
static char error_pointer;
static char *subst_lhs;
static int subst_lhs_len;
static int subst_rhs_len;
+/* Characters that delimit history event specifications and separate event
+ specifications from word designators. Static for now */
+static char *history_event_delimiter_chars = HISTORY_EVENT_DELIMITERS;
+
static char *get_history_word_specifier PARAMS((char *, char *, int *));
-static char *history_find_word PARAMS((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));
static char *quote_breaks PARAMS((char *));
/* The last string searched for by a !?string? search. */
static char *search_string;
-
/* The last string matched by a !?string? search. */
static char *search_match;
#endif /* HANDLE_MULTIBYTE */
if ((!substring_okay && (whitespace (c) || c == ':' ||
+ (history_event_delimiter_chars && member (c, history_event_delimiter_chars)) ||
(history_search_delimiter_chars && member (c, history_search_delimiter_chars)) ||
string[i] == delimiting_quote)) ||
string[i] == '\n' ||
#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,
{
if (search_string)
{
- free (temp);
+ xfree (temp);
temp = savestring (search_string);
}
else
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
search_match = history_find_word (entry->line, local_index);
}
else
- free (temp);
+ xfree (temp);
return (entry->line);
}
/* 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;
}
}
}
new[j] = '\0';
- free (subst_rhs);
+ xfree (subst_rhs);
subst_rhs = new;
subst_rhs_len = j;
}
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 !# */
{
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 defined (HANDLE_MULTIBYTE)
- if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
- {
- int c, l;
- l = _rl_find_prev_mbchar (string, i, MB_FIND_ANY);
- c = string[l];
- /* XXX - original patch had i - 1 ??? If i == 0 it would fail. */
- if (i && (c == '\'' || c == '"'))
- quoted_search_delimiter = c;
- }
- else
-#endif /* HANDLE_MULTIBYTE */
- 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);
}
if (word_spec == (char *)&error_pointer)
{
*ret_string = hist_error (string, starting_index, i, BAD_WORD_SPEC);
- free (result);
+ xfree (result);
return (-1);
}
{
default:
*ret_string = hist_error (string, i+1, i+2, BAD_MODIFIER);
- free (result);
- free (temp);
+ xfree (result);
+ xfree (temp);
return -1;
case 'q':
{
tstr++;
t = savestring (tstr);
- free (temp);
+ xfree (temp);
temp = t;
}
break;
if (tstr)
{
t = savestring (tstr);
- free (temp);
+ xfree (temp);
temp = t;
}
break;
if (subst_lhs_len == 0)
{
*ret_string = hist_error (string, starting_index, i, NO_PREV_SUBST);
- free (result);
- free (temp);
+ xfree (result);
+ xfree (temp);
return -1;
}
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);
}
temp + si + subst_lhs_len,
l_temp - (si + subst_lhs_len));
new_event[len] = '\0';
- free (temp);
+ xfree (temp);
temp = new_event;
failed = 0;
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;
else
x = savestring (temp);
- free (temp);
+ xfree (temp);
temp = x;
}
if (n >= result_len)
result = (char *)xrealloc (result, n + 2);
strcpy (result, temp);
- free (temp);
+ xfree (temp);
*end_index_ptr = i;
*ret_string = result;
1) If expansions did take place
2) If the `p' modifier was given and the caller should print the result
- If an error ocurred in expansion, then OUTPUT contains a descriptive
+ If an error occurred in expansion, then OUTPUT contains a descriptive
error message. */
#define ADD_STRING(s) \
char **output;
{
register int j;
- int i, r, l, passc, cc, modified, eindex, only_printing, dquote;
+ int i, r, l, passc, cc, modified, eindex, only_printing, dquote, squote, flag;
char *string;
/* The output string, and its length. */
/* `!' followed by one of the characters in history_no_expand_chars
is NOT an expansion. */
- for (i = dquote = 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)
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 &&
+ dquote == 0 &&
(i == 0 || member (string[i - 1], history_word_delimiters)))
{
while (string[i])
}
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
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] == '\\')
{
if (string[i] != history_expansion_char)
{
- free (result);
+ xfree (result);
*output = savestring (string);
return (0);
}
}
/* Extract and perform the substitution. */
- for (passc = dquote = 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)
{
if (strlen (mb) > 1)
{
ADD_STRING (mb);
- break;
+ continue;
}
}
#endif /* HANDLE_MULTIBYTE */
case '\'':
{
/* If history_quotes_inhibit_expansion is set, single quotes
- inhibit history expansion. */
- if (dquote == 0 && 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 = (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]);
}
case -2: /* history_comment_char */
- if (i == 0 || member (string[i - 1], history_word_delimiters))
+ if ((dquote == 0 || history_quotes_inhibit_expansion == 0) &&
+ (i == 0 || member (string[i - 1], history_word_delimiters)))
{
temp = (char *)xmalloc (l - i + 1);
strcpy (temp, string + i);
ADD_STRING (temp);
- free (temp);
+ xfree (temp);
i = l;
}
else
/* 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;
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
modified++;
if (*temp)
ADD_STRING (temp);
- free (temp);
+ xfree (temp);
}
only_printing = r == 1;
i = eindex;
*output = result;
if (string != hstring)
- free (string);
+ xfree (string);
if (only_printing)
{
}
for (i = 0; i < len; i++)
- free (list[i]);
- free (list);
+ xfree (list[i]);
+ xfree (list);
return (result);
}
int ind;
{
register int i;
- int delimiter;
+ int delimiter, nestdelim, delimopen;
i = ind;
- delimiter = 0;
+ delimiter = nestdelim = 0;
if (member (string[i], "()\n"))
{
{
if (peek == '<' && string[i + 2] == '-')
i++;
+ else if (peek == '<' && string[i + 2] == '<')
+ i++;
i += 2;
return i;
}
- else
+ else if ((peek == '&' && (string[i] == '>' || string[i] == '<')) ||
+ (peek == '>' && string[i] == '&'))
{
- if ((peek == '&' && (string[i] == '>' || string[i] == '<')) ||
- (peek == '>' && string[i] == '&') ||
- (peek == '(' && (string[i] == '>' || string[i] == '<')) || /* ) */
- (peek == '(' && string[i] == '$')) /* ) */
- {
- i += 2;
- return 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] != '$')
{
}
}
+ /* 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 (member (string[i], HISTORY_QUOTE_CHARACTERS))
+ if (delimiter == 0 && member (string[i], HISTORY_QUOTE_CHARACTERS))
delimiter = string[i++];
for (; string[i]; 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 && (member (string[i], history_word_delimiters)))
+ if (delimiter == 0 && (member (string[i], history_word_delimiters)))
break;
- if (!delimiter && member (string[i], HISTORY_QUOTE_CHARACTERS))
+ if (delimiter == 0 && member (string[i], HISTORY_QUOTE_CHARACTERS))
delimiter = string[i];
}
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. */
words = history_tokenize_internal (line, ind, &wind);
if (wind == -1 || words == 0)
- return ((char *)NULL);
+ {
+ 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;
}