diff options
Diffstat (limited to 'readline/history.c')
-rw-r--r-- | readline/history.c | 1478 |
1 files changed, 1478 insertions, 0 deletions
diff --git a/readline/history.c b/readline/history.c new file mode 100644 index 00000000000..3c07d11833d --- /dev/null +++ b/readline/history.c @@ -0,0 +1,1478 @@ +/* History.c -- standalone history library */ + +/* Copyright (C) 1989 Free Software Foundation, Inc. + + This file contains the GNU History Library (the Library), a set of + routines for managing the text of previously typed lines. + + The Library 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 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. + + 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. */ + +/* The goal is to make the implementation transparent, so that you + don't have to know what data types are used, just what functions + you can call. I think I have done that. */ + +/* Remove these declarations when we have a complete libgnu.a. */ +#define STATIC_MALLOC +#ifndef STATIC_MALLOC +extern char *xmalloc (), *xrealloc (); +#else +static char *xmalloc (), *xrealloc (); +#endif + +#include <stdio.h> + +#ifdef __GNUC__ +#define alloca __builtin_alloca +#else +#if defined (sparc) && defined (sun) +#include <alloca.h> +#else +extern char *alloca (); +#endif +#endif + +#include "history.h" + +#ifndef savestring +#define savestring(x) (char *)strcpy (xmalloc (1 + strlen (x)), (x)) +#endif + +#ifndef whitespace +#define whitespace(c) (((c) == ' ') || ((c) == '\t')) +#endif + +#ifndef digit +#define digit(c) ((c) >= '0' && (c) <= '9') +#endif + +#ifndef member +#define member(c, s) ((c) ? index ((s), (c)) : 0) +#endif + +/* **************************************************************** */ +/* */ +/* History functions */ +/* */ +/* **************************************************************** */ + +/* An array of HIST_ENTRY. This is where we store the history. */ +static HIST_ENTRY **the_history = (HIST_ENTRY **)NULL; + +/* Non-zero means that we have enforced a limit on the amount of + history that we save. */ +static int history_stifled = 0; + +/* If HISTORY_STIFLED is non-zero, then this is the maximum number of + entries to remember. */ +static int max_input_history; + +/* The current location of the interactive history pointer. Just makes + life easier for outside callers. */ +static int history_offset = 0; + +/* The number of strings currently stored in the input_history list. */ +static int history_length = 0; + +/* The current number of slots allocated to the input_history. */ +static int history_size = 0; + +/* The number of slots to increase the_history by. */ +#define DEFAULT_HISTORY_GROW_SIZE 50 + +/* The character that represents the start of a history expansion + request. This is usually `!'. */ +char history_expansion_char = '!'; + +/* The character that invokes word substitution if found at the start of + a line. This is usually `^'. */ +char history_subst_char = '^'; + +/* During tokenization, if this character is seen as the first character + of a word, then it, and all subsequent characters upto a newline are + ignored. For a Bourne shell, this should be '#'. Bash special cases + the interactive comment character to not be a comment delimiter. */ +char history_comment_char = '\0'; + +/* The list of characters which inhibit the expansion of text if found + immediately following history_expansion_char. */ +char *history_no_expand_chars = " \t\n\r="; + +/* The logical `base' of the history array. It defaults to 1. */ +int history_base = 1; + +/* Begin a session in which the history functions might be used. This + initializes interactive variables. */ +void +using_history () +{ + history_offset = history_length; +} + +/* Place STRING at the end of the history list. The data field + is set to NULL. */ +void +add_history (string) + char *string; +{ + HIST_ENTRY *temp; + + if (history_stifled && (history_length == max_input_history)) { + register int i; + + /* If the history is stifled, and history_length is zero, + and it equals max_input_history, we don't save items. */ + if (!history_length) + return; + + /* If there is something in the slot, then remove it. */ + if (the_history[0]) { + free (the_history[0]->line); + free (the_history[0]); + } + + for (i = 0; i < history_length; i++) + the_history[i] = the_history[i + 1]; + + history_base++; + + } else { + + if (!history_size) { + the_history = + (HIST_ENTRY **)xmalloc ((history_size = DEFAULT_HISTORY_GROW_SIZE) + * sizeof (HIST_ENTRY *)); + history_length = 1; + + } else { + if (history_length == (history_size - 1)) { + the_history = + (HIST_ENTRY **)xrealloc (the_history, + ((history_size += DEFAULT_HISTORY_GROW_SIZE) + * sizeof (HIST_ENTRY *))); + } + history_length++; + } + } + + temp = (HIST_ENTRY *)xmalloc (sizeof (HIST_ENTRY)); + temp->line = savestring (string); + temp->data = (char *)NULL; + + the_history[history_length] = (HIST_ENTRY *)NULL; + the_history[history_length - 1] = temp; +} + +/* Make the history entry at WHICH have LINE and DATA. This returns + the old entry so you can dispose of the data. In the case of an + invalid WHICH, a NULL pointer is returned. */ +HIST_ENTRY * +replace_history_entry (which, line, data) + int which; + char *line; + char *data; +{ + HIST_ENTRY *temp = (HIST_ENTRY *)xmalloc (sizeof (HIST_ENTRY)); + HIST_ENTRY *old_value; + + if (which >= history_length) + return ((HIST_ENTRY *)NULL); + + old_value = the_history[which]; + + temp->line = savestring (line); + temp->data = data; + the_history[which] = temp; + + return (old_value); +} + +/* Returns the magic number which says what history element we are + looking at now. In this implementation, it returns history_offset. */ +int +where_history () +{ + return (history_offset); +} + +/* Search the history for STRING, starting at history_offset. + If DIRECTION < 0, then the search is through previous entries, + else through subsequent. If the string is found, then + current_history () is the history entry, and the value of this function + is the offset in the line of that history entry that the string was + found in. Otherwise, nothing is changed, and a -1 is returned. */ +int +history_search (string, direction) + char *string; + int direction; +{ + register int i = history_offset; + register int reverse = (direction < 0); + register char *line; + register int index; + int string_len = strlen (string); + + /* Take care of trivial cases first. */ + + if (!history_length || ((i == history_length) && !reverse)) + return (-1); + + if (reverse && (i == history_length)) + i--; + + while (1) + { + /* Search each line in the history list for STRING. */ + + /* At limit for direction? */ + if ((reverse && i < 0) || + (!reverse && i == history_length)) + return (-1); + + line = the_history[i]->line; + index = strlen (line); + + /* If STRING is longer than line, no match. */ + if (string_len > index) + goto next_line; + + /* Do the actual search. */ + if (reverse) + { + index -= string_len; + + while (index >= 0) + { + if (strncmp (string, line + index, string_len) == 0) + { + history_offset = i; + return (index); + } + index--; + } + } + else + { + register int limit = (string_len - index) + 1; + index = 0; + + while (index < limit) + { + if (strncmp (string, line + index, string_len) == 0) + { + history_offset = i; + return (index); + } + index++; + } + } + next_line: + if (reverse) + i--; + else + i++; + } +} + +/* Remove history element WHICH from the history. The removed + element is returned to you so you can free the line, data, + and containing structure. */ +HIST_ENTRY * +remove_history (which) + int which; +{ + HIST_ENTRY *return_value; + + if (which >= history_length || !history_length) + return_value = (HIST_ENTRY *)NULL; + else + { + register int i; + return_value = the_history[which]; + + for (i = which; i < history_length; i++) + the_history[i] = the_history[i + 1]; + + history_length--; + } + return (return_value); +} + +/* Stifle the history list, remembering only MAX number of lines. */ +void +stifle_history (max) + int max; +{ + if (history_length > max) + { + register int i, j; + + /* This loses because we cannot free the data. */ + for (i = 0; i < (history_length - max); i++) + { + free (the_history[i]->line); + free (the_history[i]); + } + history_base = i; + for (j = 0, i = history_length - max; j < max; i++, j++) + the_history[j] = the_history[i]; + the_history[j] = (HIST_ENTRY *)NULL; + history_length = j; + } + history_stifled = 1; + max_input_history = max; +} + +/* Stop stifling the history. This returns the previous amount the history + was stifled by. The value is positive if the history was stifled, negative + if it wasn't. */ +int +unstifle_history () +{ + int result = max_input_history; + if (history_stifled) + { + result = - result; + history_stifled = 0; + } + return (result); +} + +/* Return the string that should be used in the place of this + filename. This only matters when you don't specify the + filename to read_history (), or write_history (). */ +static char * +history_filename (filename) + char *filename; +{ + char *return_val = filename ? savestring (filename) : (char *)NULL; + + if (!return_val) + { + char *home = (char *)getenv ("HOME"); + if (!home) home = "."; + return_val = (char *)xmalloc (2 + strlen (home) + strlen (".history")); + strcpy (return_val, home); + strcat (return_val, "/"); + strcat (return_val, ".history"); + } + return (return_val); +} + +/* What to use until the line gets too big. */ +#define TYPICAL_LINE_SIZE 2048 + +/* Add the contents of FILENAME to the history list, a line at a time. + If FILENAME is NULL, then read from ~/.history. Returns 0 if + successful, or errno if not. */ +int +read_history (filename) + char *filename; +{ + char *input = history_filename (filename); + FILE *file = fopen (input, "r"); + char *line = (char *)xmalloc (TYPICAL_LINE_SIZE); + int line_size = TYPICAL_LINE_SIZE; + int done = 0; + + if (!file) + { + extern int errno; + free (line); + return (errno); + } + + while (!done) + { + int c; + int i; + + i = 0; + while (!(done = ((c = getc (file)) == EOF))) + { + if (c == '\n') + break; + + line [i++] = c; + if (i == line_size) + line = (char *)xrealloc (line, line_size += TYPICAL_LINE_SIZE); + } + line[i] = '\0'; + if (line[0]) + add_history (line); + } + free (line); + fclose (file); + return (0); +} + +/* Overwrite FILENAME with the current history. If FILENAME is NULL, + then write the history list to ~/.history. Values returned + are as in read_history ().*/ +int +write_history (filename) + char *filename; +{ + extern int errno; + char *output = history_filename (filename); + FILE *file = fopen (output, "w"); + register int i; + + if (!file) return (errno); + if (!history_length) return (0); + + for (i = 0; i < history_length; i++) + fprintf (file, "%s\n", the_history[i]->line); + + fclose (file); + return (0); +} + +/* Return the history entry at the current position, as determined by + history_offset. If there is no entry there, return a NULL pointer. */ +HIST_ENTRY * +current_history () +{ + if ((history_offset == history_length) || !the_history) + return ((HIST_ENTRY *)NULL); + else + return (the_history[history_offset]); +} + +/* Back up history_offset to the previous history entry, and return + a pointer to that entry. If there is no previous entry then return + a NULL pointer. */ +HIST_ENTRY * +previous_history () +{ + if (!history_offset) + return ((HIST_ENTRY *)NULL); + else + return (the_history[--history_offset]); +} + +/* Move history_offset forward to the next history entry, and return + a pointer to that entry. If there is no next entry then return a + NULL pointer. */ +HIST_ENTRY * +next_history () +{ + if (history_offset == history_length) + return ((HIST_ENTRY *)NULL); + else + return (the_history[++history_offset]); +} + +/* Return the current history array. The caller has to be carefull, since this + is the actual array of data, and could be bashed or made corrupt easily. + The array is terminated with a NULL pointer. */ +HIST_ENTRY ** +history_list () +{ + return (the_history); +} + +/* Return the history entry which is logically at OFFSET in the history array. + OFFSET is relative to history_base. */ +HIST_ENTRY * +history_get (offset) + int offset; +{ + int index = offset - history_base; + + if (index >= history_length || + index < 0 || + !the_history) + return ((HIST_ENTRY *)NULL); + return (the_history[index]); +} + +/* Search for STRING in the history list. DIR is < 0 for searching + backwards. POS is an absolute index into the history list at + which point to begin searching. */ +int +history_search_pos (string, dir, pos) + char *string; + int dir, pos; +{ + int ret, old = where_history (); + history_set_pos (pos); + if (history_search (string, dir) == -1) + { + history_set_pos (old); + return (-1); + } + ret = where_history (); + history_set_pos (old); + return ret; +} + +/* Make the current history item be the one at POS, an absolute index. + Returns zero if POS is out of range, else non-zero. */ +int +history_set_pos (pos) + int pos; +{ + if (pos > history_length || pos < 0 || !the_history) + return (0); + history_offset = pos; + return (1); +} + + +/* **************************************************************** */ +/* */ +/* History Expansion */ +/* */ +/* **************************************************************** */ + +/* Hairy history expansion on text, not tokens. This is of general + use, and thus belongs in this library. */ + +/* The last string searched for in a !?string? search. */ +static char *search_string = (char *)NULL; + +/* Return the event specified at TEXT + OFFSET modifying OFFSET to + point to after the event specifier. Just a pointer to the history + line is returned; NULL is returned in the event of a bad specifier. + You pass STRING with *INDEX equal to the history_expansion_char that + begins this specification. + DELIMITING_QUOTE is a character that is allowed to end the string + specification for what to search for in addition to the normal + characters `:', ` ', `\t', `\n', and sometimes `?'. + So you might call this function like: + line = get_history_event ("!echo:p", &index, 0); */ +char * +get_history_event (string, caller_index, delimiting_quote) + char *string; + int *caller_index; + int delimiting_quote; +{ + register int i = *caller_index; + int which, sign = 1; + HIST_ENTRY *entry; + + /* The event can be specified in a number of ways. + + !! the previous command + !n command line N + !-n current command-line minus N + !str the most recent command starting with STR + !?str[?] + the most recent command containing STR + + All values N are determined via HISTORY_BASE. */ + + if (string[i] != history_expansion_char) + return ((char *)NULL); + + /* Move on to the specification. */ + i++; + + /* Handle !! case. */ + if (string[i] == history_expansion_char) + { + i++; + which = history_base + (history_length - 1); + *caller_index = i; + goto get_which; + } + + /* Hack case of numeric line specification. */ + read_which: + if (string[i] == '-') + { + sign = -1; + i++; + } + + if (digit (string[i])) + { + int start = i; + + /* Get the extent of the digits. */ + for (; digit (string[i]); i++); + + /* Get the digit value. */ + sscanf (string + start, "%d", &which); + + *caller_index = i; + + if (sign < 0) + which = (history_length + history_base) - which; + + get_which: + if (entry = history_get (which)) + return (entry->line); + + return ((char *)NULL); + } + + /* This must be something to search for. If the spec begins with + a '?', then the string may be anywhere on the line. Otherwise, + the string must be found at the start of a line. */ + { + int index; + char *temp; + int substring_okay = 0; + + if (string[i] == '?') + { + substring_okay++; + i++; + } + + for (index = i; string[i]; i++) + if (whitespace (string[i]) || + string[i] == '\n' || + string[i] == ':' || + (substring_okay && string[i] == '?') || + string[i] == delimiting_quote) + break; + + temp = (char *)alloca (1 + (i - index)); + strncpy (temp, &string[index], (i - index)); + temp[i - index] = '\0'; + + if (string[i] == '?') + i++; + + *caller_index = i; + + search_again: + + index = history_search (temp, -1); + + if (index < 0) + search_lost: + { + history_offset = history_length; + return ((char *)NULL); + } + + if (index == 0 || substring_okay || + (strncmp (temp, the_history[history_offset]->line, + strlen (temp)) == 0)) + { + search_won: + entry = current_history (); + history_offset = history_length; + + /* If this was a substring search, then remember the string that + we matched for word substitution. */ + if (substring_okay) + { + if (search_string) + free (search_string); + search_string = savestring (temp); + } + + return (entry->line); + } + + if (history_offset) + history_offset--; + else + goto search_lost; + + goto search_again; + } +} + +/* Expand the string STRING, placing the result into OUTPUT, a pointer + to a string. Returns: + + 0) If no expansions took place (or, if the only change in + the text was the de-slashifying of the history expansion + character) + 1) If expansions did take place + -1) If there was an error in expansion. + + If an error ocurred in expansion, then OUTPUT contains a descriptive + error message. */ +int +history_expand (string, output) + char *string; + char **output; +{ + register int j, l = strlen (string); + int i, word_spec_error = 0; + int cc, modified = 0; + char *word_spec, *event; + int starting_index, only_printing = 0, substitute_globally = 0; + + char *get_history_word_specifier (), *rindex (); + + /* The output string, and its length. */ + int len = 0; + char *result = (char *)NULL; + + /* Used in add_string; */ + char *temp, tt[2], tbl[3]; + + /* Prepare the buffer for printing error messages. */ + result = (char *)xmalloc (len = 255); + + result[0] = tt[1] = tbl[2] = '\0'; + tbl[0] = '\\'; + tbl[1] = history_expansion_char; + + /* Grovel the string. Only backslash can quote the history escape + character. We also handle arg specifiers. */ + + /* Before we grovel forever, see if the history_expansion_char appears + anywhere within the text. */ + + /* The quick substitution character is a history expansion all right. That + is to say, "^this^that^" is equivalent to "!!:s^this^that^", and in fact, + that is the substitution that we do. */ + if (string[0] == history_subst_char) + { + char *format_string = (char *)alloca (10 + strlen (string)); + + sprintf (format_string, "%c%c:s%s", + history_expansion_char, history_expansion_char, + string); + string = format_string; + l += 4; + goto grovel; + } + + /* 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++) + if (string[i] == history_expansion_char) + if (!string[i + 1] || member (string[i + 1], history_no_expand_chars)) + continue; + else + goto grovel; + + free (result); + *output = savestring (string); + return (0); + + grovel: + + for (i = j = 0; i < l; i++) + { + int tchar = string[i]; + if (tchar == history_expansion_char) + tchar = -3; + + switch (tchar) + { + case '\\': + if (string[i + 1] == history_expansion_char) + { + i++; + temp = tbl; + goto do_add; + } + else + goto add_char; + + /* case history_expansion_char: */ + case -3: + starting_index = i + 1; + cc = string[i + 1]; + + /* 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)) + goto add_char; + + /* There is something that is listed as a `word specifier' in csh + documentation which means `the expanded text to this point'. + That is not a word specifier, it is an event specifier. */ + + if (cc == '#') + goto hack_pound_sign; + + /* If it is followed by something that starts a word specifier, + then !! is implied as the event specifier. */ + + if (member (cc, ":$*%^")) + { + char fake_s[3]; + int fake_i = 0; + i++; + fake_s[0] = fake_s[1] = history_expansion_char; + fake_s[2] = '\0'; + event = get_history_event (fake_s, &fake_i, 0); + } + 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); + } + + if (!event) + event_not_found: + { + int l = 1 + (i - starting_index); + + temp = (char *)alloca (1 + l); + strncpy (temp, string + starting_index, l); + temp[l - 1] = 0; + sprintf (result, "%s: %s.", temp, + word_spec_error ? "Bad word specifier" : "Event not found"); + error_exit: + *output = result; + return (-1); + } + + /* If a word specifier is found, then do what that requires. */ + starting_index = i; + + word_spec = get_history_word_specifier (string, event, &i); + + /* There is no such thing as a `malformed word specifier'. However, + it is possible for a specifier that has no match. In that case, + we complain. */ + if (word_spec == (char *)-1) + bad_word_spec: + { + word_spec_error++; + goto event_not_found; + } + + /* If no word specifier, than the thing of interest was the event. */ + if (!word_spec) + temp = event; + else + { + temp = (char *)alloca (1 + strlen (word_spec)); + strcpy (temp, word_spec); + free (word_spec); + } + + /* Perhaps there are other modifiers involved. Do what they say. */ + + hack_specials: + + if (string[i] == ':') + { + char *tstr; + + switch (string[i + 1]) + { + /* :p means make this the last executed line. So we + return an error state after adding this line to the + history. */ + case 'p': + only_printing++; + goto next_special; + + /* :t discards all but the last part of the pathname. */ + case 't': + tstr = rindex (temp, '/'); + if (tstr) + temp = ++tstr; + goto next_special; + + /* :h discards the last part of a pathname. */ + case 'h': + tstr = rindex (temp, '/'); + if (tstr) + *tstr = '\0'; + goto next_special; + + /* :r discards the suffix. */ + case 'r': + tstr = rindex (temp, '.'); + if (tstr) + *tstr = '\0'; + goto next_special; + + /* :e discards everything but the suffix. */ + case 'e': + tstr = rindex (temp, '.'); + if (tstr) + temp = tstr; + goto next_special; + + /* :s/this/that substitutes `this' for `that'. */ + /* :gs/this/that substitutes `this' for `that' globally. */ + case 'g': + if (string[i + 2] == 's') + { + i++; + substitute_globally = 1; + goto substitute; + } + else + + case 's': + substitute: + { + char *this, *that, *new_event; + int delimiter = 0; + int si, l_this, l_that, l_temp = strlen (temp); + + if (i + 2 < strlen (string)) + delimiter = string[i + 2]; + + if (!delimiter) + break; + + i += 3; + + /* Get THIS. */ + for (si = i; string[si] && string[si] != delimiter; si++); + l_this = (si - i); + this = (char *)alloca (1 + l_this); + strncpy (this, string + i, l_this); + this[l_this] = '\0'; + + i = si; + if (string[si]) + i++; + + /* Get THAT. */ + for (si = i; string[si] && string[si] != delimiter; si++); + l_that = (si - i); + that = (char *)alloca (1 + l_that); + strncpy (that, string + i, l_that); + that[l_that] = '\0'; + + i = si; + if (string[si]) i++; + + /* Ignore impossible cases. */ + if (l_this > l_temp) + goto cant_substitute; + + /* Find the first occurrence of THIS in TEMP. */ + si = 0; + for (; (si + l_this) <= l_temp; si++) + if (strncmp (temp + si, this, l_this) == 0) + { + new_event = + (char *)alloca (1 + (l_that - l_this) + l_temp); + strncpy (new_event, temp, si); + strncpy (new_event + si, that, l_that); + strncpy (new_event + si + l_that, + temp + si + l_this, + l_temp - (si + l_this)); + new_event[(l_that - l_this) + l_temp] = '\0'; + temp = new_event; + + if (substitute_globally) + { + si += l_that; + l_temp = strlen (temp); + substitute_globally++; + continue; + } + + goto hack_specials; + } + + cant_substitute: + + if (substitute_globally > 1) + { + substitute_globally = 0; + goto hack_specials; + } + + goto event_not_found; + } + + /* :# is the line so far. Note that we have to + alloca () it since RESULT could be realloc ()'ed + below in add_string. */ + case '#': + hack_pound_sign: + if (result) + { + temp = (char *)alloca (1 + strlen (result)); + strcpy (temp, result); + } + else + temp = ""; + + next_special: + i += 2; + goto hack_specials; + } + + } + /* Believe it or not, we have to back the pointer up by one. */ + --i; + goto add_string; + + /* A regular character. Just add it to the output string. */ + default: + add_char: + tt[0] = string[i]; + temp = tt; + goto do_add; + + add_string: + modified++; + + do_add: + j += strlen (temp); + while (j > len) + result = (char *)xrealloc (result, (len += 255)); + + strcpy (result + (j - strlen (temp)), temp); + } + } + + *output = result; + + if (only_printing) + { + add_history (result); + return (-1); + } + + return (modified != 0); +} + +/* Return a consed string which is the word specified in SPEC, and found + in FROM. NULL is returned if there is no spec. -1 is returned if + the word specified cannot be found. CALLER_INDEX is the offset in + SPEC to start looking; it is updated to point to just after the last + character parsed. */ +char * +get_history_word_specifier (spec, from, caller_index) + char *spec, *from; + int *caller_index; +{ + register int i = *caller_index; + int first, last; + int expecting_word_spec = 0; + char *history_arg_extract (); + + /* The range of words to return doesn't exist yet. */ + first = last = 0; + + /* If we found a colon, then this *must* be a word specification. If + it isn't, then it is an error. */ + if (spec[i] == ':') + i++, expecting_word_spec++; + + /* Handle special cases first. */ + + /* `%' is the word last searched for. */ + if (spec[i] == '%') + { + *caller_index = i + 1; + if (search_string) + return (savestring (search_string)); + else + return (savestring ("")); + } + + /* `*' matches all of the arguments, but not the command. */ + if (spec[i] == '*') + { + char *star_result; + + *caller_index = i + 1; + star_result = history_arg_extract (1, '$', from); + + if (!star_result) + star_result = savestring (""); + + return (star_result); + } + + /* `$' is last arg. */ + if (spec[i] == '$') + { + *caller_index = i + 1; + return (history_arg_extract ('$', '$', from)); + } + + /* Try to get FIRST and LAST figured out. */ + if (spec[i] == '-' || spec[i] == '^') + { + first = 1; + goto get_last; + } + + get_first: + if (digit (spec[i]) && expecting_word_spec) + { + sscanf (spec + i, "%d", &first); + for (; digit (spec[i]); i++); + } + else + return ((char *)NULL); + + get_last: + if (spec[i] == '^') + { + i++; + last = 1; + goto get_args; + } + + if (spec[i] != '-') + { + last = first; + goto get_args; + } + + i++; + + if (digit (spec[i])) + { + sscanf (spec + i, "%d", &last); + for (; digit (spec[i]); i++); + } + else + if (spec[i] == '$') + { + i++; + last = '$'; + } + + get_args: + { + char *result = (char *)NULL; + + *caller_index = i; + + if (last >= first) + result = history_arg_extract (first, last, from); + + if (result) + return (result); + else + return ((char *)-1); + } +} + +/* Extract the args specified, starting at FIRST, and ending at LAST. + The args are taken from STRING. If either FIRST or LAST is < 0, + then make that arg count from the right (subtract from the number of + tokens, so that FIRST = -1 means the next to last token on the line. */ +char * +history_arg_extract (first, last, string) + int first, last; + char *string; +{ + register int i, len; + char *result = (char *)NULL; + int size = 0, offset = 0; + + char **history_tokenize (), **list; + + if (!(list = history_tokenize (string))) + return ((char *)NULL); + + for (len = 0; list[len]; len++); + + if (last < 0) + last = len + last - 1; + + if (first < 0) + first = len + first - 1; + + if (last == '$') + last = len - 1; + + if (first == '$') + first = len - 1; + + last++; + + if (first > len || last > len) + result = ((char *)NULL); + else + { + for (i = first; i < last; i++) + { + int l = strlen (list[i]); + + if (!result) + result = (char *)xmalloc ((size = (2 + l))); + else + result = (char *)xrealloc (result, (size += (2 + l))); + strcpy (result + offset, list[i]); + offset += l; + if (i + 1 < last) + { + strcpy (result + offset, " "); + offset++; + } + } + } + + for (i = 0; i < len; i++) + free (list[i]); + + free (list); + + return (result); +} + +#define slashify_in_quotes "\\`\"$" + +/* Return an array of tokens, much as the shell might. The tokens are + parsed out of STRING. */ +char ** +history_tokenize (string) + char *string; +{ + char **result = (char **)NULL; + register int i, start, result_index, size; + int len; + + i = result_index = size = 0; + + /* Get a token, and stuff it into RESULT. The tokens are split + exactly where the shell would split them. */ + get_token: + + /* Skip leading whitespace. */ + for (; string[i] && whitespace(string[i]); i++); + + start = i; + + if (!string[i] || string[i] == history_comment_char) + return (result); + + if (member (string[i], "()\n")) { + i++; + goto got_token; + } + + if (member (string[i], "<>;&|")) { + int peek = string[i + 1]; + + if (peek == string[i]) { + if (peek == '<') { + if (string[1 + 2] == '-') + i++; + i += 2; + goto got_token; + } + + if (member (peek, ">:&|")) { + i += 2; + goto got_token; + } + } else { + if ((peek == '&' && + (string[i] == '>' || string[i] == '<')) || + ((peek == '>') && + (string[i] == '&'))) { + i += 2; + goto got_token; + } + } + i++; + goto got_token; + } + + /* Get word from string + i; */ + { + int delimiter = 0; + + if (member (string[i], "\"'`")) + delimiter = string[i++]; + + for (;string[i]; i++) { + + if (string[i] == '\\') { + + if (string[i + 1] == '\n') { + i++; + continue; + } else { + if (delimiter != '\'') + if ((delimiter != '"') || + (member (string[i], slashify_in_quotes))) { + i++; + continue; + } + } + } + + if (delimiter && string[i] == delimiter) { + delimiter = 0; + continue; + } + + if (!delimiter && (member (string[i], " \t\n;&()|<>"))) + goto got_token; + + if (!delimiter && member (string[i], "\"'`")) { + delimiter = string[i]; + continue; + } + } + got_token: + + len = i - start; + if (result_index + 2 >= size) { + if (!size) + result = (char **)xmalloc ((size = 10) * (sizeof (char *))); + else + result = + (char **)xrealloc (result, ((size += 10) * (sizeof (char *)))); + } + result[result_index] = (char *)xmalloc (1 + len); + strncpy (result[result_index], string + start, len); + result[result_index][len] = '\0'; + result_index++; + result[result_index] = (char *)NULL; + } + if (string[i]) + goto get_token; + + return (result); +} + +#ifdef STATIC_MALLOC + +/* **************************************************************** */ +/* */ +/* xmalloc and xrealloc () */ +/* */ +/* **************************************************************** */ + +static void memory_error_and_abort (); + +static char * +xmalloc (bytes) + int bytes; +{ + char *temp = (char *)malloc (bytes); + + if (!temp) + memory_error_and_abort (); + return (temp); +} + +static char * +xrealloc (pointer, bytes) + char *pointer; + int bytes; +{ + char *temp = (char *)realloc (pointer, bytes); + + if (!temp) + memory_error_and_abort (); + return (temp); +} + +static void +memory_error_and_abort () +{ + fprintf (stderr, "history: Out of virtual memory!\n"); + abort (); +} +#endif /* STATIC_MALLOC */ + + +/* **************************************************************** */ +/* */ +/* Test Code */ +/* */ +/* **************************************************************** */ +#ifdef TEST +main () +{ + char line[1024], *t; + int done = 0; + + line[0] = 0; + + while (!done) + { + fprintf (stdout, "history%% "); + t = gets (line); + + if (!t) + strcpy (line, "quit"); + + if (line[0]) + { + char *expansion; + int result; + + using_history (); + + result = history_expand (line, &expansion); + strcpy (line, expansion); + free (expansion); + if (result) + fprintf (stderr, "%s\n", line); + + if (result < 0) + continue; + + add_history (line); + } + + if (strcmp (line, "quit") == 0) done = 1; + if (strcmp (line, "save") == 0) write_history (0); + if (strcmp (line, "read") == 0) read_history (0); + if (strcmp (line, "list") == 0) + { + register HIST_ENTRY **the_list = history_list (); + register int i; + + if (the_list) + for (i = 0; the_list[i]; i++) + fprintf (stdout, "%d: %s\n", i + history_base, the_list[i]->line); + } + if (strncmp (line, "delete", strlen ("delete")) == 0) + { + int which; + if ((sscanf (line + strlen ("delete"), "%d", &which)) == 1) + { + HIST_ENTRY *entry = remove_history (which); + if (!entry) + fprintf (stderr, "No such entry %d\n", which); + else + { + free (entry->line); + free (entry); + } + } + else + { + fprintf (stderr, "non-numeric arg given to `delete'\n"); + } + } + } +} + +#endif /* TEST */ + +/* +* Local variables: +* compile-command: "gcc -g -DTEST -o history history.c" +* end: +*/ |