1097 lines
30 KiB
C
1097 lines
30 KiB
C
/*
|
||
* tclHistory.c --
|
||
*
|
||
* This module implements history as an optional addition to Tcl.
|
||
* It can be called to record commands ("events") before they are
|
||
* executed, and it provides a command that may be used to perform
|
||
* history substitutions.
|
||
*
|
||
* Copyright (c) 1990-1993 The Regents of the University of California.
|
||
* Copyright (c) 1994-1995 Sun Microsystems, Inc.
|
||
*
|
||
* See the file "license.terms" for information on usage and redistribution
|
||
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||
*
|
||
* SCCS: @(#) tclHistory.c 1.40 96/02/15 11:50:24
|
||
*/
|
||
|
||
#include "tclInt.h"
|
||
#include "tclPort.h"
|
||
|
||
/*
|
||
* This history stuff is mostly straightforward, except for one thing
|
||
* that makes everything very complicated. Suppose that the following
|
||
* commands get executed:
|
||
* echo foo
|
||
* history redo
|
||
* It's important that the history event recorded for the second command
|
||
* be "echo foo", not "history redo". Otherwise, if another "history redo"
|
||
* command is typed, it will result in infinite recursions on the
|
||
* "history redo" command. Thus, the actual recorded history must be
|
||
* echo foo
|
||
* echo foo
|
||
* To do this, the history command revises recorded history as part of
|
||
* its execution. In the example above, when "history redo" starts
|
||
* execution, the current event is "history redo", but the history
|
||
* command arranges for the current event to be changed to "echo foo".
|
||
*
|
||
* There are three additional complications. The first is that history
|
||
* substitution may only be part of a command, as in the following
|
||
* command sequence:
|
||
* echo foo bar
|
||
* echo [history word 3]
|
||
* In this case, the second event should be recorded as "echo bar". Only
|
||
* part of the recorded event is to be modified. Fortunately, Tcl_Eval
|
||
* helps with this by recording (in the evalFirst and evalLast fields of
|
||
* the intepreter) the location of the command being executed, so the
|
||
* history module can replace exactly the range of bytes corresponding
|
||
* to the history substitution command.
|
||
*
|
||
* The second complication is that there are two ways to revise history:
|
||
* replace a command, and replace the result of a command. Consider the
|
||
* two examples below:
|
||
* format {result is %d} $num | format {result is %d} $num
|
||
* print [history redo] | print [history word 3]
|
||
* Recorded history for these two cases should be as follows:
|
||
* format {result is %d} $num | format {result is %d} $num
|
||
* print [format {result is %d} $num] | print $num
|
||
* In the left case, the history command was replaced with another command
|
||
* to be executed (the brackets were retained), but in the case on the
|
||
* right the result of executing the history command was replaced (i.e.
|
||
* brackets were replaced too).
|
||
*
|
||
* The third complication is that there could potentially be many
|
||
* history substitutions within a single command, as in:
|
||
* echo [history word 3] [history word 2]
|
||
* There could even be nested history substitutions, as in:
|
||
* history subs abc [history word 2]
|
||
* If history revisions were made immediately during each "history" command
|
||
* invocations, it would be very difficult to produce the correct cumulative
|
||
* effect from several substitutions in the same command. To get around
|
||
* this problem, the actual history revision isn't made during the execution
|
||
* of the "history" command. Information about the changes is just recorded,
|
||
* in xxx records, and the actual changes are made during the next call to
|
||
* Tcl_RecordHistory (when we know that execution of the previous command
|
||
* has finished).
|
||
*/
|
||
|
||
/*
|
||
* Default space allocation for command strings:
|
||
*/
|
||
|
||
#define INITIAL_CMD_SIZE 40
|
||
|
||
/*
|
||
* Forward declarations for procedures defined later in this file:
|
||
*/
|
||
|
||
static void DoRevs _ANSI_ARGS_((Interp *iPtr));
|
||
static HistoryEvent * GetEvent _ANSI_ARGS_((Interp *iPtr, char *string));
|
||
static char * GetWords _ANSI_ARGS_((Interp *iPtr, char *command,
|
||
char *words));
|
||
static void InitHistory _ANSI_ARGS_((Interp *iPtr));
|
||
static void InsertRev _ANSI_ARGS_((Interp *iPtr,
|
||
HistoryRev *revPtr));
|
||
static void MakeSpace _ANSI_ARGS_((HistoryEvent *hPtr, int size));
|
||
static void RevCommand _ANSI_ARGS_((Interp *iPtr, char *string));
|
||
static void RevResult _ANSI_ARGS_((Interp *iPtr, char *string));
|
||
static int SubsAndEval _ANSI_ARGS_((Interp *iPtr, char *cmd,
|
||
char *old, char *new));
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* InitHistory --
|
||
*
|
||
* Initialize history-related state in an interpreter.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* History info is initialized in iPtr.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static void
|
||
InitHistory(iPtr)
|
||
register Interp *iPtr; /* Interpreter to initialize. */
|
||
{
|
||
int i;
|
||
|
||
if (iPtr->numEvents != 0) {
|
||
return;
|
||
}
|
||
iPtr->numEvents = 20;
|
||
iPtr->events = (HistoryEvent *)
|
||
ckalloc((unsigned) (iPtr->numEvents * sizeof(HistoryEvent)));
|
||
for (i = 0; i < iPtr->numEvents; i++) {
|
||
iPtr->events[i].command = (char *) ckalloc(INITIAL_CMD_SIZE);
|
||
*iPtr->events[i].command = 0;
|
||
iPtr->events[i].bytesAvl = INITIAL_CMD_SIZE;
|
||
}
|
||
iPtr->curEvent = 0;
|
||
iPtr->curEventNum = 0;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* Tcl_RecordAndEval --
|
||
*
|
||
* This procedure adds its command argument to the current list of
|
||
* recorded events and then executes the command by calling
|
||
* Tcl_Eval.
|
||
*
|
||
* Results:
|
||
* The return value is a standard Tcl return value, the result of
|
||
* executing cmd.
|
||
*
|
||
* Side effects:
|
||
* The command is recorded and executed. In addition, pending history
|
||
* revisions are carried out, and information is set up to enable
|
||
* Tcl_Eval to identify history command ranges. This procedure also
|
||
* initializes history information for the interpreter, if it hasn't
|
||
* already been initialized.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
Tcl_RecordAndEval(interp, cmd, flags)
|
||
Tcl_Interp *interp; /* Token for interpreter in which command
|
||
* will be executed. */
|
||
char *cmd; /* Command to record. */
|
||
int flags; /* Additional flags. TCL_NO_EVAL means
|
||
* only record: don't execute command.
|
||
* TCL_EVAL_GLOBAL means use Tcl_GlobalEval
|
||
* instead of Tcl_Eval. */
|
||
{
|
||
register Interp *iPtr = (Interp *) interp;
|
||
register HistoryEvent *eventPtr;
|
||
int length, result;
|
||
|
||
if (iPtr->numEvents == 0) {
|
||
InitHistory(iPtr);
|
||
}
|
||
DoRevs(iPtr);
|
||
|
||
/*
|
||
* Don't record empty commands.
|
||
*/
|
||
|
||
while (isspace(UCHAR(*cmd))) {
|
||
cmd++;
|
||
}
|
||
if (*cmd == '\0') {
|
||
Tcl_ResetResult(interp);
|
||
return TCL_OK;
|
||
}
|
||
|
||
iPtr->curEventNum++;
|
||
iPtr->curEvent++;
|
||
if (iPtr->curEvent >= iPtr->numEvents) {
|
||
iPtr->curEvent = 0;
|
||
}
|
||
eventPtr = &iPtr->events[iPtr->curEvent];
|
||
|
||
/*
|
||
* Chop off trailing newlines before recording the command.
|
||
*/
|
||
|
||
length = strlen(cmd);
|
||
while (cmd[length-1] == '\n') {
|
||
length--;
|
||
}
|
||
MakeSpace(eventPtr, length + 1);
|
||
strncpy(eventPtr->command, cmd, (size_t) length);
|
||
eventPtr->command[length] = 0;
|
||
|
||
/*
|
||
* Execute the command. Note: history revision isn't possible after
|
||
* a nested call to this procedure, because the event at the top of
|
||
* the history list no longer corresponds to what's going on when
|
||
* a nested call here returns. Thus, must leave history revision
|
||
* disabled when we return.
|
||
*/
|
||
|
||
result = TCL_OK;
|
||
if (!(flags & TCL_NO_EVAL)) {
|
||
iPtr->historyFirst = cmd;
|
||
iPtr->revDisables = 0;
|
||
iPtr->evalFlags = (flags & ~TCL_EVAL_GLOBAL) | TCL_RECORD_BOUNDS;
|
||
if (flags & TCL_EVAL_GLOBAL) {
|
||
result = Tcl_GlobalEval(interp, cmd);
|
||
} else {
|
||
result = Tcl_Eval(interp, cmd);
|
||
}
|
||
}
|
||
iPtr->revDisables = 1;
|
||
return result;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* Tcl_HistoryCmd --
|
||
*
|
||
* This procedure is invoked to process the "history" Tcl command.
|
||
* See the user documentation for details on what it does.
|
||
*
|
||
* Results:
|
||
* A standard Tcl result.
|
||
*
|
||
* Side effects:
|
||
* See the user documentation.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
/* ARGSUSED */
|
||
int
|
||
Tcl_HistoryCmd(dummy, interp, argc, argv)
|
||
ClientData dummy; /* Not used. */
|
||
Tcl_Interp *interp; /* Current interpreter. */
|
||
int argc; /* Number of arguments. */
|
||
char **argv; /* Argument strings. */
|
||
{
|
||
register Interp *iPtr = (Interp *) interp;
|
||
register HistoryEvent *eventPtr;
|
||
size_t length;
|
||
int c;
|
||
|
||
if (iPtr->numEvents == 0) {
|
||
InitHistory(iPtr);
|
||
}
|
||
|
||
/*
|
||
* If no arguments, treat the same as "history info".
|
||
*/
|
||
|
||
if (argc == 1) {
|
||
goto infoCmd;
|
||
}
|
||
|
||
c = argv[1][0];
|
||
length = strlen(argv[1]);
|
||
|
||
if ((c == 'a') && (strncmp(argv[1], "add", length)) == 0) {
|
||
if ((argc != 3) && (argc != 4)) {
|
||
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
|
||
" add event ?exec?\"", (char *) NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
if (argc == 4) {
|
||
if (strncmp(argv[3], "exec", strlen(argv[3])) != 0) {
|
||
Tcl_AppendResult(interp, "bad argument \"", argv[3],
|
||
"\": should be \"exec\"", (char *) NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
return Tcl_RecordAndEval(interp, argv[2], 0);
|
||
}
|
||
return Tcl_RecordAndEval(interp, argv[2], TCL_NO_EVAL);
|
||
} else if ((c == 'c') && (strncmp(argv[1], "change", length)) == 0) {
|
||
if ((argc != 3) && (argc != 4)) {
|
||
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
|
||
" change newValue ?event?\"", (char *) NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
if (argc == 3) {
|
||
eventPtr = &iPtr->events[iPtr->curEvent];
|
||
iPtr->revDisables += 1;
|
||
while (iPtr->revPtr != NULL) {
|
||
HistoryRev *nextPtr;
|
||
|
||
ckfree(iPtr->revPtr->newBytes);
|
||
nextPtr = iPtr->revPtr->nextPtr;
|
||
ckfree((char *) iPtr->revPtr);
|
||
iPtr->revPtr = nextPtr;
|
||
}
|
||
} else {
|
||
eventPtr = GetEvent(iPtr, argv[3]);
|
||
if (eventPtr == NULL) {
|
||
return TCL_ERROR;
|
||
}
|
||
}
|
||
MakeSpace(eventPtr, (int) strlen(argv[2]) + 1);
|
||
strcpy(eventPtr->command, argv[2]);
|
||
return TCL_OK;
|
||
} else if ((c == 'e') && (strncmp(argv[1], "event", length)) == 0) {
|
||
if (argc > 3) {
|
||
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
|
||
" event ?event?\"", (char *) NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
eventPtr = GetEvent(iPtr, argc==2 ? "-1" : argv[2]);
|
||
if (eventPtr == NULL) {
|
||
return TCL_ERROR;
|
||
}
|
||
RevResult(iPtr, eventPtr->command);
|
||
Tcl_SetResult(interp, eventPtr->command, TCL_VOLATILE);
|
||
return TCL_OK;
|
||
} else if ((c == 'i') && (strncmp(argv[1], "info", length)) == 0) {
|
||
int count, indx, i;
|
||
char *newline;
|
||
|
||
if ((argc != 2) && (argc != 3)) {
|
||
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
|
||
" info ?count?\"", (char *) NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
infoCmd:
|
||
if (argc == 3) {
|
||
if (Tcl_GetInt(interp, argv[2], &count) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
if (count > iPtr->numEvents) {
|
||
count = iPtr->numEvents;
|
||
}
|
||
} else {
|
||
count = iPtr->numEvents;
|
||
}
|
||
newline = "";
|
||
for (i = 0, indx = iPtr->curEvent + 1 + iPtr->numEvents - count;
|
||
i < count; i++, indx++) {
|
||
char *cur, *next, savedChar;
|
||
char serial[20];
|
||
|
||
if (indx >= iPtr->numEvents) {
|
||
indx -= iPtr->numEvents;
|
||
}
|
||
cur = iPtr->events[indx].command;
|
||
if (*cur == '\0') {
|
||
continue; /* No command recorded here. */
|
||
}
|
||
sprintf(serial, "%6d ", iPtr->curEventNum + 1 - (count - i));
|
||
Tcl_AppendResult(interp, newline, serial, (char *) NULL);
|
||
newline = "\n";
|
||
|
||
/*
|
||
* Tricky formatting here: for multi-line commands, indent
|
||
* the continuation lines.
|
||
*/
|
||
|
||
while (1) {
|
||
next = strchr(cur, '\n');
|
||
if (next == NULL) {
|
||
break;
|
||
}
|
||
next++;
|
||
savedChar = *next;
|
||
*next = 0;
|
||
Tcl_AppendResult(interp, cur, "\t", (char *) NULL);
|
||
*next = savedChar;
|
||
cur = next;
|
||
}
|
||
Tcl_AppendResult(interp, cur, (char *) NULL);
|
||
}
|
||
return TCL_OK;
|
||
} else if ((c == 'k') && (strncmp(argv[1], "keep", length)) == 0) {
|
||
int count, i, src;
|
||
HistoryEvent *events;
|
||
|
||
if (argc != 3) {
|
||
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
|
||
" keep number\"", (char *) NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
if (Tcl_GetInt(interp, argv[2], &count) != TCL_OK) {
|
||
return TCL_ERROR;
|
||
}
|
||
if ((count <= 0) || (count > 1000)) {
|
||
Tcl_AppendResult(interp, "illegal keep count \"", argv[2],
|
||
"\"", (char *) NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
/*
|
||
* Create a new history array and copy as much existing history
|
||
* as possible from the old array.
|
||
*/
|
||
|
||
events = (HistoryEvent *)
|
||
ckalloc((unsigned) (count * sizeof(HistoryEvent)));
|
||
if (count < iPtr->numEvents) {
|
||
src = iPtr->curEvent + 1 - count;
|
||
if (src < 0) {
|
||
src += iPtr->numEvents;
|
||
}
|
||
} else {
|
||
src = iPtr->curEvent + 1;
|
||
}
|
||
for (i = 0; i < count; i++, src++) {
|
||
if (src >= iPtr->numEvents) {
|
||
src = 0;
|
||
}
|
||
if (i < iPtr->numEvents) {
|
||
events[i] = iPtr->events[src];
|
||
iPtr->events[src].command = NULL;
|
||
} else {
|
||
events[i].command = (char *) ckalloc(INITIAL_CMD_SIZE);
|
||
events[i].command[0] = 0;
|
||
events[i].bytesAvl = INITIAL_CMD_SIZE;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Throw away everything left in the old history array, and
|
||
* substitute the new one for the old one.
|
||
*/
|
||
|
||
for (i = 0; i < iPtr->numEvents; i++) {
|
||
if (iPtr->events[i].command != NULL) {
|
||
ckfree(iPtr->events[i].command);
|
||
}
|
||
}
|
||
ckfree((char *) iPtr->events);
|
||
iPtr->events = events;
|
||
if (count < iPtr->numEvents) {
|
||
iPtr->curEvent = count-1;
|
||
} else {
|
||
iPtr->curEvent = iPtr->numEvents-1;
|
||
}
|
||
iPtr->numEvents = count;
|
||
return TCL_OK;
|
||
} else if ((c == 'n') && (strncmp(argv[1], "nextid", length)) == 0) {
|
||
if (argc != 2) {
|
||
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
|
||
" nextid\"", (char *) NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
sprintf(iPtr->result, "%d", iPtr->curEventNum+1);
|
||
return TCL_OK;
|
||
} else if ((c == 'r') && (strncmp(argv[1], "redo", length)) == 0) {
|
||
if (argc > 3) {
|
||
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
|
||
" redo ?event?\"", (char *) NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
eventPtr = GetEvent(iPtr, argc==2 ? "-1" : argv[2]);
|
||
if (eventPtr == NULL) {
|
||
return TCL_ERROR;
|
||
}
|
||
RevCommand(iPtr, eventPtr->command);
|
||
return Tcl_Eval(interp, eventPtr->command);
|
||
} else if ((c == 's') && (strncmp(argv[1], "substitute", length)) == 0) {
|
||
if ((argc > 5) || (argc < 4)) {
|
||
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
|
||
" substitute old new ?event?\"", (char *) NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
eventPtr = GetEvent(iPtr, argc==4 ? "-1" : argv[4]);
|
||
if (eventPtr == NULL) {
|
||
return TCL_ERROR;
|
||
}
|
||
return SubsAndEval(iPtr, eventPtr->command, argv[2], argv[3]);
|
||
} else if ((c == 'w') && (strncmp(argv[1], "words", length)) == 0) {
|
||
char *words;
|
||
|
||
if ((argc != 3) && (argc != 4)) {
|
||
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
|
||
" words num-num/pat ?event?\"", (char *) NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
eventPtr = GetEvent(iPtr, argc==3 ? "-1" : argv[3]);
|
||
if (eventPtr == NULL) {
|
||
return TCL_ERROR;
|
||
}
|
||
words = GetWords(iPtr, eventPtr->command, argv[2]);
|
||
if (words == NULL) {
|
||
return TCL_ERROR;
|
||
}
|
||
RevResult(iPtr, words);
|
||
iPtr->result = words;
|
||
iPtr->freeProc = TCL_DYNAMIC;
|
||
return TCL_OK;
|
||
}
|
||
|
||
Tcl_AppendResult(interp, "bad option \"", argv[1],
|
||
"\": must be add, change, event, info, keep, nextid, ",
|
||
"redo, substitute, or words", (char *) NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* MakeSpace --
|
||
*
|
||
* Given a history event, make sure it has enough space for
|
||
* a string of a given length (enlarge the string area if
|
||
* necessary).
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* More memory may get allocated.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static void
|
||
MakeSpace(hPtr, size)
|
||
HistoryEvent *hPtr;
|
||
int size; /* # of bytes needed in hPtr. */
|
||
{
|
||
if (hPtr->bytesAvl < size) {
|
||
ckfree(hPtr->command);
|
||
hPtr->command = (char *) ckalloc((unsigned) size);
|
||
hPtr->bytesAvl = size;
|
||
}
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* InsertRev --
|
||
*
|
||
* Add a new revision to the list of those pending for iPtr.
|
||
* Do it in a way that keeps the revision list sorted in
|
||
* increasing order of firstIndex. Also, eliminate revisions
|
||
* that are subsets of other revisions.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* RevPtr is added to iPtr's revision list.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static void
|
||
InsertRev(iPtr, revPtr)
|
||
Interp *iPtr; /* Interpreter to use. */
|
||
register HistoryRev *revPtr; /* Revision to add to iPtr's list. */
|
||
{
|
||
register HistoryRev *curPtr;
|
||
register HistoryRev *prevPtr;
|
||
|
||
for (curPtr = iPtr->revPtr, prevPtr = NULL; curPtr != NULL;
|
||
prevPtr = curPtr, curPtr = curPtr->nextPtr) {
|
||
/*
|
||
* If this revision includes the new one (or vice versa) then
|
||
* just eliminate the one that is a subset of the other.
|
||
*/
|
||
|
||
if ((revPtr->firstIndex <= curPtr->firstIndex)
|
||
&& (revPtr->lastIndex >= curPtr->firstIndex)) {
|
||
curPtr->firstIndex = revPtr->firstIndex;
|
||
curPtr->lastIndex = revPtr->lastIndex;
|
||
curPtr->newSize = revPtr->newSize;
|
||
ckfree(curPtr->newBytes);
|
||
curPtr->newBytes = revPtr->newBytes;
|
||
ckfree((char *) revPtr);
|
||
return;
|
||
}
|
||
if ((revPtr->firstIndex >= curPtr->firstIndex)
|
||
&& (revPtr->lastIndex <= curPtr->lastIndex)) {
|
||
ckfree(revPtr->newBytes);
|
||
ckfree((char *) revPtr);
|
||
return;
|
||
}
|
||
|
||
if (revPtr->firstIndex < curPtr->firstIndex) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Insert revPtr just after prevPtr.
|
||
*/
|
||
|
||
if (prevPtr == NULL) {
|
||
revPtr->nextPtr = iPtr->revPtr;
|
||
iPtr->revPtr = revPtr;
|
||
} else {
|
||
revPtr->nextPtr = prevPtr->nextPtr;
|
||
prevPtr->nextPtr = revPtr;
|
||
}
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* RevCommand --
|
||
*
|
||
* This procedure is invoked by the "history" command to record
|
||
* a command revision. See the comments at the beginning of the
|
||
* file for more information about revisions.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* Revision information is recorded.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static void
|
||
RevCommand(iPtr, string)
|
||
register Interp *iPtr; /* Interpreter in which to perform the
|
||
* substitution. */
|
||
char *string; /* String to substitute. */
|
||
{
|
||
register HistoryRev *revPtr;
|
||
|
||
if ((iPtr->evalFirst == NULL) || (iPtr->revDisables > 0)) {
|
||
return;
|
||
}
|
||
revPtr = (HistoryRev *) ckalloc(sizeof(HistoryRev));
|
||
revPtr->firstIndex = iPtr->evalFirst - iPtr->historyFirst;
|
||
revPtr->lastIndex = iPtr->evalLast - iPtr->historyFirst;
|
||
revPtr->newSize = strlen(string);
|
||
revPtr->newBytes = (char *) ckalloc((unsigned) (revPtr->newSize+1));
|
||
strcpy(revPtr->newBytes, string);
|
||
InsertRev(iPtr, revPtr);
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* RevResult --
|
||
*
|
||
* This procedure is invoked by the "history" command to record
|
||
* a result revision. See the comments at the beginning of the
|
||
* file for more information about revisions.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* Revision information is recorded.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static void
|
||
RevResult(iPtr, string)
|
||
register Interp *iPtr; /* Interpreter in which to perform the
|
||
* substitution. */
|
||
char *string; /* String to substitute. */
|
||
{
|
||
register HistoryRev *revPtr;
|
||
char *evalFirst, *evalLast;
|
||
char *argv[2];
|
||
|
||
if ((iPtr->evalFirst == NULL) || (iPtr->revDisables > 0)) {
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* Expand the replacement range to include the brackets that surround
|
||
* the command. If there aren't any brackets (i.e. this command was
|
||
* invoked at top-level) then don't do any revision. Also, if there
|
||
* are several commands in brackets, of which this is just one,
|
||
* then don't do any revision.
|
||
*/
|
||
|
||
evalFirst = iPtr->evalFirst;
|
||
evalLast = iPtr->evalLast + 1;
|
||
while (1) {
|
||
if (evalFirst == iPtr->historyFirst) {
|
||
return;
|
||
}
|
||
evalFirst--;
|
||
if (*evalFirst == '[') {
|
||
break;
|
||
}
|
||
if (!isspace(UCHAR(*evalFirst))) {
|
||
return;
|
||
}
|
||
}
|
||
if (*evalLast != ']') {
|
||
return;
|
||
}
|
||
|
||
revPtr = (HistoryRev *) ckalloc(sizeof(HistoryRev));
|
||
revPtr->firstIndex = evalFirst - iPtr->historyFirst;
|
||
revPtr->lastIndex = evalLast - iPtr->historyFirst;
|
||
argv[0] = string;
|
||
revPtr->newBytes = Tcl_Merge(1, argv);
|
||
revPtr->newSize = strlen(revPtr->newBytes);
|
||
InsertRev(iPtr, revPtr);
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* DoRevs --
|
||
*
|
||
* This procedure is called to apply the history revisions that
|
||
* have been recorded in iPtr.
|
||
*
|
||
* Results:
|
||
* None.
|
||
*
|
||
* Side effects:
|
||
* The most recent entry in the history for iPtr may be modified.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static void
|
||
DoRevs(iPtr)
|
||
register Interp *iPtr; /* Interpreter whose history is to
|
||
* be modified. */
|
||
{
|
||
register HistoryRev *revPtr;
|
||
register HistoryEvent *eventPtr;
|
||
char *newCommand, *p;
|
||
unsigned int size;
|
||
int bytesSeen, count;
|
||
|
||
if (iPtr->revPtr == NULL) {
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* The revision is done in two passes. The first pass computes the
|
||
* amount of space needed for the revised event, and the second pass
|
||
* pieces together the new event and frees up the revisions.
|
||
*/
|
||
|
||
eventPtr = &iPtr->events[iPtr->curEvent];
|
||
size = strlen(eventPtr->command) + 1;
|
||
for (revPtr = iPtr->revPtr; revPtr != NULL; revPtr = revPtr->nextPtr) {
|
||
size -= revPtr->lastIndex + 1 - revPtr->firstIndex;
|
||
size += revPtr->newSize;
|
||
}
|
||
|
||
newCommand = (char *) ckalloc(size);
|
||
p = newCommand;
|
||
bytesSeen = 0;
|
||
for (revPtr = iPtr->revPtr; revPtr != NULL; ) {
|
||
HistoryRev *nextPtr = revPtr->nextPtr;
|
||
|
||
count = revPtr->firstIndex - bytesSeen;
|
||
if (count > 0) {
|
||
strncpy(p, eventPtr->command + bytesSeen, (size_t) count);
|
||
p += count;
|
||
}
|
||
strncpy(p, revPtr->newBytes, (size_t) revPtr->newSize);
|
||
p += revPtr->newSize;
|
||
bytesSeen = revPtr->lastIndex+1;
|
||
ckfree(revPtr->newBytes);
|
||
ckfree((char *) revPtr);
|
||
revPtr = nextPtr;
|
||
}
|
||
strcpy(p, eventPtr->command + bytesSeen);
|
||
|
||
/*
|
||
* Replace the command in the event.
|
||
*/
|
||
|
||
ckfree(eventPtr->command);
|
||
eventPtr->command = newCommand;
|
||
eventPtr->bytesAvl = size;
|
||
iPtr->revPtr = NULL;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* GetEvent --
|
||
*
|
||
* Given a textual description of an event (see the manual page
|
||
* for legal values) find the corresponding event and return its
|
||
* command string.
|
||
*
|
||
* Results:
|
||
* The return value is a pointer to the event named by "string".
|
||
* If no such event exists, then NULL is returned and an error
|
||
* message is left in iPtr.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static HistoryEvent *
|
||
GetEvent(iPtr, string)
|
||
register Interp *iPtr; /* Interpreter in which to look. */
|
||
char *string; /* Description of event. */
|
||
{
|
||
int eventNum, index;
|
||
register HistoryEvent *eventPtr;
|
||
int length;
|
||
|
||
/*
|
||
* First check for a numeric specification of an event.
|
||
*/
|
||
|
||
if (isdigit(UCHAR(*string)) || (*string == '-')) {
|
||
if (Tcl_GetInt((Tcl_Interp *) iPtr, string, &eventNum) != TCL_OK) {
|
||
return NULL;
|
||
}
|
||
if (eventNum < 0) {
|
||
eventNum += iPtr->curEventNum;
|
||
}
|
||
if (eventNum > iPtr->curEventNum) {
|
||
Tcl_AppendResult((Tcl_Interp *) iPtr, "event \"", string,
|
||
"\" hasn't occurred yet", (char *) NULL);
|
||
return NULL;
|
||
}
|
||
if ((eventNum <= iPtr->curEventNum-iPtr->numEvents)
|
||
|| (eventNum <= 0)) {
|
||
Tcl_AppendResult((Tcl_Interp *) iPtr, "event \"", string,
|
||
"\" is too far in the past", (char *) NULL);
|
||
return NULL;
|
||
}
|
||
index = iPtr->curEvent + (eventNum - iPtr->curEventNum);
|
||
if (index < 0) {
|
||
index += iPtr->numEvents;
|
||
}
|
||
return &iPtr->events[index];
|
||
}
|
||
|
||
/*
|
||
* Next, check for an event that contains the string as a prefix or
|
||
* that matches the string in the sense of Tcl_StringMatch.
|
||
*/
|
||
|
||
length = strlen(string);
|
||
for (index = iPtr->curEvent - 1; ; index--) {
|
||
if (index < 0) {
|
||
index += iPtr->numEvents;
|
||
}
|
||
if (index == iPtr->curEvent) {
|
||
break;
|
||
}
|
||
eventPtr = &iPtr->events[index];
|
||
if ((strncmp(eventPtr->command, string, (size_t) length) == 0)
|
||
|| Tcl_StringMatch(eventPtr->command, string)) {
|
||
return eventPtr;
|
||
}
|
||
}
|
||
|
||
Tcl_AppendResult((Tcl_Interp *) iPtr, "no event matches \"", string,
|
||
"\"", (char *) NULL);
|
||
return NULL;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* SubsAndEval --
|
||
*
|
||
* Generate a new command by making a textual substitution in
|
||
* the "cmd" argument. Then execute the new command.
|
||
*
|
||
* Results:
|
||
* The return value is a standard Tcl error.
|
||
*
|
||
* Side effects:
|
||
* History gets revised if the substitution is occurring on
|
||
* a recorded command line. Also, the re-executed command
|
||
* may produce side-effects.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static int
|
||
SubsAndEval(iPtr, cmd, old, new)
|
||
register Interp *iPtr; /* Interpreter in which to execute
|
||
* new command. */
|
||
char *cmd; /* Command in which to substitute. */
|
||
char *old; /* String to search for in command. */
|
||
char *new; /* Replacement string for "old". */
|
||
{
|
||
char *src, *dst, *newCmd;
|
||
int count, oldLength, newLength, length, result;
|
||
|
||
/*
|
||
* Figure out how much space it will take to hold the
|
||
* substituted command (and complain if the old string
|
||
* doesn't appear in the original command).
|
||
*/
|
||
|
||
oldLength = strlen(old);
|
||
newLength = strlen(new);
|
||
src = cmd;
|
||
count = 0;
|
||
while (1) {
|
||
src = strstr(src, old);
|
||
if (src == NULL) {
|
||
break;
|
||
}
|
||
src += oldLength;
|
||
count++;
|
||
}
|
||
if (count == 0) {
|
||
Tcl_AppendResult((Tcl_Interp *) iPtr, "\"", old,
|
||
"\" doesn't appear in event", (char *) NULL);
|
||
return TCL_ERROR;
|
||
}
|
||
length = strlen(cmd) + count*(newLength - oldLength);
|
||
|
||
/*
|
||
* Generate a substituted command.
|
||
*/
|
||
|
||
newCmd = (char *) ckalloc((unsigned) (length + 1));
|
||
dst = newCmd;
|
||
while (1) {
|
||
src = strstr(cmd, old);
|
||
if (src == NULL) {
|
||
strcpy(dst, cmd);
|
||
break;
|
||
}
|
||
strncpy(dst, cmd, (size_t) (src-cmd));
|
||
dst += src-cmd;
|
||
strcpy(dst, new);
|
||
dst += newLength;
|
||
cmd = src + oldLength;
|
||
}
|
||
|
||
RevCommand(iPtr, newCmd);
|
||
result = Tcl_Eval((Tcl_Interp *) iPtr, newCmd);
|
||
ckfree(newCmd);
|
||
return result;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* GetWords --
|
||
*
|
||
* Given a command string, return one or more words from the
|
||
* command string.
|
||
*
|
||
* Results:
|
||
* The return value is a pointer to a dynamically-allocated
|
||
* string containing the words of command specified by "words".
|
||
* If the word specifier has improper syntax then an error
|
||
* message is placed in iPtr->result and NULL is returned.
|
||
*
|
||
* Side effects:
|
||
* Memory is allocated. It is the caller's responsibilty to
|
||
* free the returned string..
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static char *
|
||
GetWords(iPtr, command, words)
|
||
register Interp *iPtr; /* Tcl interpreter in which to place
|
||
* an error message if needed. */
|
||
char *command; /* Command string. */
|
||
char *words; /* Description of which words to extract
|
||
* from the command. Either num[-num] or
|
||
* a pattern. */
|
||
{
|
||
char *result;
|
||
char *start, *end, *dst;
|
||
register char *next;
|
||
int first; /* First word desired. -1 means last word
|
||
* only. */
|
||
int last; /* Last word desired. -1 means use everything
|
||
* up to the end. */
|
||
int index; /* Index of current word. */
|
||
char *pattern;
|
||
|
||
/*
|
||
* Figure out whether we're looking for a numerical range or for
|
||
* a pattern.
|
||
*/
|
||
|
||
pattern = NULL;
|
||
first = 0;
|
||
last = -1;
|
||
if (*words == '$') {
|
||
if (words[1] != '\0') {
|
||
goto error;
|
||
}
|
||
first = -1;
|
||
} else if (isdigit(UCHAR(*words))) {
|
||
first = strtoul(words, &start, 0);
|
||
if (*start == 0) {
|
||
last = first;
|
||
} else if (*start == '-') {
|
||
start++;
|
||
if (*start == '$') {
|
||
start++;
|
||
} else if (isdigit(UCHAR(*start))) {
|
||
last = strtoul(start, &start, 0);
|
||
} else {
|
||
goto error;
|
||
}
|
||
if (*start != 0) {
|
||
goto error;
|
||
}
|
||
}
|
||
if ((first > last) && (last != -1)) {
|
||
goto error;
|
||
}
|
||
} else {
|
||
pattern = words;
|
||
}
|
||
|
||
/*
|
||
* Scan through the words one at a time, copying those that are
|
||
* relevant into the result string. Allocate a result area large
|
||
* enough to hold all the words if necessary.
|
||
*/
|
||
|
||
result = (char *) ckalloc((unsigned) (strlen(command) + 1));
|
||
dst = result;
|
||
for (next = command; isspace(UCHAR(*next)); next++) {
|
||
/* Empty loop body: just find start of first word. */
|
||
}
|
||
for (index = 0; *next != 0; index++) {
|
||
start = next;
|
||
end = TclWordEnd(next, 0, (int *) NULL);
|
||
if (*end != 0) {
|
||
end++;
|
||
for (next = end; isspace(UCHAR(*next)); next++) {
|
||
/* Empty loop body: just find start of next word. */
|
||
}
|
||
}
|
||
if ((first > index) || ((first == -1) && (*next != 0))) {
|
||
continue;
|
||
}
|
||
if ((last != -1) && (last < index)) {
|
||
continue;
|
||
}
|
||
if (pattern != NULL) {
|
||
int match;
|
||
char savedChar = *end;
|
||
|
||
*end = 0;
|
||
match = Tcl_StringMatch(start, pattern);
|
||
*end = savedChar;
|
||
if (!match) {
|
||
continue;
|
||
}
|
||
}
|
||
if (dst != result) {
|
||
*dst = ' ';
|
||
dst++;
|
||
}
|
||
strncpy(dst, start, (size_t) (end-start));
|
||
dst += end-start;
|
||
}
|
||
*dst = 0;
|
||
|
||
/*
|
||
* Check for an out-of-range argument index.
|
||
*/
|
||
|
||
if ((last >= index) || (first >= index)) {
|
||
ckfree(result);
|
||
Tcl_AppendResult((Tcl_Interp *) iPtr, "word selector \"", words,
|
||
"\" specified non-existent words", (char *) NULL);
|
||
return NULL;
|
||
}
|
||
return result;
|
||
|
||
error:
|
||
Tcl_AppendResult((Tcl_Interp *) iPtr, "bad word selector \"", words,
|
||
"\": should be num-num or pattern", (char *) NULL);
|
||
return NULL;
|
||
}
|