/* 
 * mxHistory.c --
 *
 *	Contains the basic mechanisms and the Tcl commands to handle
 *	history: remembering recently-invoked Tcl commands.
 *
 * Copyright 1988 Regents of the University of California
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies.  The University of California
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: mxHistory.c,v 1.4 88/07/30 11:54:00 ouster Exp $ SPRITE (Berkeley)";
#endif not lint

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mxInt.h"

/*
 * Forward declarations to procedures declared in this file.
 */

extern void	HistoryAdd();
extern void	HistoryProc();

/*
 *----------------------------------------------------------------------
 *
 * Mx_HistoryCmd --
 *
 *	This is the top-level procedure that implements the
 *	"history" command.  See the man page for details on what it
 *	does and what it returns.
 *
 * Results:
 *	See the man page.
 *
 * Side effects:
 *	See the man page.
 *
 *----------------------------------------------------------------------
 */

int
Mx_HistoryCmd(mxwPtr, interp, argc, argv)
    register MxWindow *mxwPtr;
    register Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int length;

    if (argc < 2) {
	sprintf(interp->result,
		"not enough args: should be \"%.50s option [arg]\"", argv[0]);
	return TCL_ERROR;
    }
    length = strlen(argv[1]);
    if (strncmp(argv[1], "add", length) == 0) {
	if (argc != 3) {
	    sprintf(interp->result,
		    "wrong # args: should be \"%.50s add info\"",
		    argv[0]);
	    return TCL_ERROR;
	}
	HistoryAdd(mxwPtr, argv[2]);
	return TCL_OK;
    } else if (strncmp(argv[1], "clear", length) == 0) {
	if (argc != 2) {
	    sprintf(interp->result,
		    "wrong # args: should be \"%.50s clear\"",
		    argv[0]);
	    return TCL_ERROR;
	}
	mxwPtr->histCurSize = 0;
	*mxwPtr->histBuffer = 0;
	return TCL_OK;
    } else if ((strncmp(argv[1], "ignore", length) == 0)
	     && (length >= 2)) {
	if (argc != 3) {
	    sprintf(interp->result,
		    "wrong # args: should be \"%.50s ignore command\"",
		    argv[0]);
	    return TCL_ERROR;
	}
	return Tcl_Eval(mxwPtr->interp, argv[2], 0, (char **) NULL);
    } else if ((strncmp(argv[1], "info", length) == 0)
	    && (length >= 2)) {
	if (argc != 2) {
	    sprintf(interp->result,
		    "wrong # args: should be \"%.50s info\"",
		    argv[0]);
	    return TCL_ERROR;
	}
	Tcl_Return(interp, mxwPtr->histBuffer, TCL_STATIC);
	return TCL_OK;
    } else if (strncmp(argv[1], "next", length) == 0) {
	if ((argc != 3) && (argc != 4)) {
	    sprintf(interp->result,
		    "wrong # args: should be \"%.50s next varName [command]\"",
		    argv[0]);
	    return TCL_ERROR;
	}
	MxHistoryNext(mxwPtr, argv[2], 0);
	if (argc == 4) {
	    return Tcl_Eval(mxwPtr->interp, argv[3], 0, (char **) NULL);
	}
	return TCL_OK;
    } else if ((strncmp(argv[1], "off", length) == 0) && (length >= 2)) {
	if (argc != 2) {
	    sprintf(interp->result,
		    "too many args: should be \"%.50s off\"",
		    argv[0]);
	    return TCL_ERROR;
	}
	MxHistoryOff(mxwPtr);
	return TCL_OK;
    } else if ((strncmp(argv[1], "on", length) == 0) && (length >= 2)) {
	if (argc != 2) {
	    sprintf(interp->result,
		    "too many args: should be \"%.50s on\"",
		    argv[0]);
	    return TCL_ERROR;
	}
	MxHistoryOn(mxwPtr);
	return TCL_OK;
    }
    sprintf(interp->result, "bad option to \"%.50s\": should be add, clear, ignore, info, next, off, or on",
	    argv[0]);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * MxHistoryInit --
 *
 *	Initialize history-related things for an Mx window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The window is set up to have no history recording in progress.
 *
 *----------------------------------------------------------------------
 */

void
MxHistoryInit(mxwPtr)
    register MxWindow *mxwPtr;		/* Window for which to initialize
					 * history info. */
{
    mxwPtr->histEnabled = 0;
    mxwPtr->histBuffer = malloc(50);
    mxwPtr->histSpace = 50;
    mxwPtr->histCurSize = 0;
    *mxwPtr->histBuffer = 0;
}

/*
 *----------------------------------------------------------------------
 *
 * MxHistoryCleanup --
 *
 *	This procedure is called as part of recycling an Mx window:
 *	it recycles all the dynamically-allocated history memory.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory gets released.
 *
 *----------------------------------------------------------------------
 */

void
MxHistoryCleanup(mxwPtr)
    MxWindow *mxwPtr;		/* Whose history information to clean up. */
{
    free(mxwPtr->histBuffer);
}

/*
 *----------------------------------------------------------------------
 *
 * MxHistoryOn --
 *
 *	Start recording commands in a window (or re-enable recording
 *	if it had been on once but was disabled).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	History recording starts (resumes) if the cumulative number of
 *	calls to MxHistoryOn for the window is greater than the number
 *	of calls to MxHistoryOff.  Note:  only non-history Tcl commands
 *	will be recorded.
 *
 *----------------------------------------------------------------------
 */

void
MxHistoryOn(mxwPtr)
    register MxWindow *mxwPtr;		/* Where to enable recording. */
{
    mxwPtr->histEnabled += 1;
    if (mxwPtr->histEnabled == 1) {
	mxwPtr->histTrace = Tcl_CreateTrace(mxwPtr->interp, 1,
		HistoryProc, (ClientData) mxwPtr);
    } else if (mxwPtr->histEnabled > 1) {
	mxwPtr->histEnabled = 1;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * MxHistoryOff --
 *
 *	Disable command recording for an Mx window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Command recording stops if the cumulative number of calls to
 *	MxHistoryOff is at least as great as the cumulative number of
 *	calls to MxHistoryOn.
 *
 *----------------------------------------------------------------------
 */

void
MxHistoryOff(mxwPtr)
    register MxWindow *mxwPtr;		/* Where to disable recording. */
{
    mxwPtr->histEnabled -= 1;
    if (mxwPtr->histEnabled == 0) {
	Tcl_DeleteTrace(mxwPtr->interp, mxwPtr->histTrace);
	mxwPtr->histTrace = 0;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * MxHistoryNext --
 *
 *	Save all the currently-recorded history information in
 *	variable varName, and clear the history information.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If there is no history information recorded then nothing
 *	happens.  Otherwise varName gets modified.
 *
 *----------------------------------------------------------------------
 */

void
MxHistoryNext(mxwPtr, varName, global)
    register MxWindow *mxwPtr;		/* Window to manipulate. */
    char *varName;			/* Where to save history information.
					 * If NULL, then current history
					 * information isn't saved anywhere. */
    int global;				/* 1 means insist on treating
					 * varName as a global variable. */
{
    if (mxwPtr->histCurSize == 0) {
	return;
    }
    if (varName != NULL) {
	Tcl_SetVar(mxwPtr->interp, varName, mxwPtr->histBuffer, global);
    }
    *mxwPtr->histBuffer = 0;
    mxwPtr->histCurSize = 0;
}

/*
 *----------------------------------------------------------------------
 *
 * HistoryAdd --
 *
 *	Append information onto the history record for a window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	"Command" gets added to the end of the history info for mxwPtr.
 *
 *----------------------------------------------------------------------
 */

void
HistoryAdd(mxwPtr, command)
    register MxWindow *mxwPtr;		/* Window in which to record. */
    char *command;			/* Information to record. */
{
    int length;

    /*
     * Enlarge the buffer if there isn't enough space for the current
     * information. 
     */

    length = strlen(command);
    if ((mxwPtr->histCurSize + length + 2) > mxwPtr->histSpace) {
	char *newBuffer;

	mxwPtr->histSpace = mxwPtr->histCurSize + length + 2;
	mxwPtr->histSpace *= 2;
	newBuffer = malloc((unsigned) mxwPtr->histSpace);
	bcopy(mxwPtr->histBuffer, newBuffer, mxwPtr->histCurSize);
	free(mxwPtr->histBuffer);
	mxwPtr->histBuffer = newBuffer;
    }

    /*
     * Add in the new string.  If this isn't the first command in the
     * buffer, then precede the string with a newline to separate it
     * from the previous command.
     */

    if (mxwPtr->histCurSize != 0) {
	mxwPtr->histBuffer[mxwPtr->histCurSize] = '\n';
	mxwPtr->histCurSize++;
    }
    strcpy(mxwPtr->histBuffer + mxwPtr->histCurSize, command);
    mxwPtr->histCurSize += length;
}

/*
 *----------------------------------------------------------------------
 *
 * HistoryProc --
 *
 *	This is a callback procedure invoked by the Tcl interpreter
 *	to record command info before the commands are executed.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Each call causes the current history information to be
 *	updated.
 *
 *----------------------------------------------------------------------
 */

	/* ARGSUSED */
void
HistoryProc(mxwPtr, interp, level, command, cmdProc, cmdClientData, argc, argv)
    register MxWindow *mxwPtr;		/* Window in which to record. */
    Tcl_Interp *interp;			/* Not used. */
    int level;				/* Not used. */
    char *command;			/* Command to record. */
    int (*cmdProc)();			/* Command procedure:  don't record if
					 * this is Mx_HistoryCmd. */
     ClientData cmdClientData;		/* Not used. */
     int argc;				/* Not used. */
     char **argv;			/* Not used. */
{
    if (cmdProc == Mx_HistoryCmd) {
	return;
    }

    HistoryAdd(mxwPtr, command);
}
