/* 
 * cmdPub.c --
 *
 *	This file provides facilities for mapping keystroke sequences
 *	into Tcl commands.  It's left-over from and older version of
 *	Mx where it also included the basic command interpreter.  This
 *	whole file should be replaced by facilities from the X Toolkit.
 *
 * Copyright (C) 1986, 1987, 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: cmdPub.c,v 1.1 88/09/23 12:52:30 mlgray Exp $ SPRITE (Berkeley)";
#endif not lint


#include <X11/Xlib.h>
#include <stdio.h>
#include <string.h>
#include "cmd.h"

/*
 * The following structure defines a keystroke binding.
 */

typedef struct Binding {
    char *sequence;		/* Sequence of keystrokes (dynamically
				 * allocated, NULL terminated). */
    int length;			/* Number of non-NULL chars. in sequence. */
    char *command;		/* Command corresponding to sequence (also
				 * dynamically allocated). */
    struct Binding *nextPtr;	/* Pointer to next binding in list, or NULL
				 * for end of list. */
} Binding;

/*
 * The structure below describes the state of a command interface (typically
 * corresponding to one window).  It holds information about bindings
 * and also keeps a history of recent keystrokes (those that haven't yet
 * been completely bound).
 */

typedef struct Table {
    Binding *bindingPtrs[128];	/* Lists of keystroke bindings.  The list
				 * for a sequence is found by taking the
				 * low-order 7 bits of the first character
				 * of the sequence.  The lists are unsorted.
				 */
    int bindingCount;		/* When we're in the middle of binding a
				 * multi-stroke command, this counts how
				 * many characters (not including the
				 * terminating NULL) are stored in
				 * currentSequence below. */
    char *currentSequence;	/* Keeps track of all characters received
				 * in most recent sequence. */
    int maxChars;		/* Maximum number of non-NULL characters that
				 * may be stored in currentSequence without
				 * growing it. */
} Table;


/*
 *----------------------------------------------------------------------
 *
 * Cmd_TableCreate --
 *
 *	Create a new command stream and initialize its command table.
 *
 * Results:
 *	The return value is a token for a Cmd_Table object, which
 *	is used to hold information about bindings.
 *
 * Side effects:
 *	Memory is allocated for the Cmd_Table.  Initially, the table
 *	contains no bindings.
 *
 *----------------------------------------------------------------------
 */

Cmd_Table
Cmd_TableCreate()
{
    register Table *tablePtr;
    int i;

    tablePtr = (Table *) malloc(sizeof(Table));
    for (i = 0; i < 128; i++) {
	tablePtr->bindingPtrs[i] = NULL;
    }
    tablePtr->bindingCount = 0;
    tablePtr->currentSequence = (char *) malloc(8);
    tablePtr->maxChars = 7;

    return (Cmd_Table) tablePtr;
}

/*
 *----------------------------------------------------------------------
 *
 * Cmd_TableDelete --
 *
 *	Destroys a command table and all information associated with it.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The memory for table is freed up, and none of the command
 *	procedures associated with it will be called again.  The
 *	caller should not use the table token anymore.
 *
 *----------------------------------------------------------------------
 */

void
Cmd_TableDelete(tableToken)
    Cmd_Table tableToken;		/* Token for table to be deleted. */
{
    register Table *tablePtr =  (Table *) tableToken;
    register Binding *bindingPtr;
    int i;

    for (i = 0; i < 128; i++) {
	for (bindingPtr = tablePtr->bindingPtrs[i]; bindingPtr != NULL;
		bindingPtr = bindingPtr->nextPtr) {
	    free((char *) bindingPtr->sequence);
	    free((char *) bindingPtr->command);
	    free((char *) bindingPtr);
	}
    }
    free((char *) tablePtr->currentSequence);

    free((char *) tablePtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Cmd_BindingCreate --
 *
 *	Associate a command with a particular keystroke or sequence
 *	of keystrokes.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Later calls to Cmd_MapKey will return "command" whenever
 *	"sequence" occurs.  Note: a given keystroke can only
 *	participate in a single successful return from Cmd_Mapkey (
 *	the firstmatching sequence is used).  Thus, if the sequences
 *	"a" and "ab" both have command bindings, the binding for "ab"
 *	will never be used.
 *
 *----------------------------------------------------------------------
 */

void
Cmd_BindingCreate(tableToken, sequence, command)
    Cmd_Table tableToken;	/* Token for table to which binding is
				 * to be added. */
    char *sequence;		/* Sequence of ASCII keystrokes.  If a
				 * binding for this sequence already
				 * exists in table, it is replaced. */
    char *command;		/* Command to associate with sequence. */
{
    register Table *tablePtr = (Table *) tableToken;
    register Binding *bindingPtr;
    int index;

    /*
     * See if the binding already exists.  If so, then just replace it.
     */

    index = sequence[0] & 0177;
    for (bindingPtr = tablePtr->bindingPtrs[index];
	    bindingPtr != NULL; bindingPtr = bindingPtr->nextPtr) {
	if (strcmp(bindingPtr->sequence, sequence) == 0) {
	    free((char *) bindingPtr->command);
	    bindingPtr->command = (char *)
		    malloc((unsigned) (strlen(command) + 1));
	    strcpy(bindingPtr->command, command);
	    return;
	}
    }

    /*
     * This binding doesn't already exist, so make a new one.
     */

    bindingPtr = (Binding *) malloc(sizeof(Binding));
    bindingPtr->length = strlen(sequence);
    bindingPtr->sequence = (char *) malloc((unsigned) (bindingPtr->length + 1));
    strcpy(bindingPtr->sequence, sequence);
    bindingPtr->command = (char *) malloc((unsigned) (strlen(command) + 1));
    strcpy(bindingPtr->command, command);
    bindingPtr->nextPtr = tablePtr->bindingPtrs[index];
    tablePtr->bindingPtrs[index] = bindingPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * Cmd_BindingDelete --
 *
 *	Remove a keystroke-sequence-to-command binding.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The binding for sequence is removed from table, if it
 *	existed.
 *
 *----------------------------------------------------------------------
 */

void
Cmd_BindingDelete(tableToken, sequence)
    Cmd_Table tableToken;	/* Token for command table. */
    char *sequence;		/* Sequence of keystrokes, for which a
				 * binding was previously established by
				 * calling Cmd_BindingCreate. */
{
    register Table *tablePtr = (Table *) tableToken;
    register Binding *bindingPtr, *prevPtr;
    int index;

    index = sequence[0] & 0177;
    for (bindingPtr = tablePtr->bindingPtrs[index], prevPtr = NULL;
	    bindingPtr != NULL;
	    prevPtr = bindingPtr,  bindingPtr = bindingPtr->nextPtr) {
	if (strcmp(bindingPtr->sequence, sequence) != 0) {
	    continue;
	}
	free((char *) bindingPtr->sequence);
	free((char *) bindingPtr->command);
	if (prevPtr != NULL) {
	    prevPtr->nextPtr = bindingPtr->nextPtr;
	} else {
	    tablePtr->bindingPtrs[index] = bindingPtr->nextPtr;
	}
	free((char *) bindingPtr);
	return;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Cmd_BindingGet --
 *
 *	Return information about the command bound to a keystroke
 *	sequence.
 *
 * Results:
 *	The return value is NULL if there is no binding for sequence.
 *	Otherwise, the return value is the command bound to sequence.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

char *
Cmd_BindingGet(tableToken, sequence)
    Cmd_Table tableToken;	/* Token for command table. */
    char *sequence;		/* Sequence of keystrokes, for which a
				 * binding was previously established by
				 * calling Cmd_BindingCreate. */
{
    register Table *tablePtr = (Table *) tableToken;
    register Binding *bindingPtr;
    int index;

    index = sequence[0] & 0177;
    for (bindingPtr = tablePtr->bindingPtrs[index]; bindingPtr != NULL;
	    bindingPtr = bindingPtr->nextPtr) {
	if (strcmp(bindingPtr->sequence, sequence) == 0) {
	    return bindingPtr->command;
	}
    }
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * Cmd_EnumBindings --
 *
 *	For each binding defined in a table, call a procedure.
 *
 * Results:
 *	1 is returned, unless proc returns 0 at some point.
 *	If this happens, then 0 is returned.
 *
 * Side effects:
 *	Proc is called once for each binding in the table.  It must
 *	have the calling sequence
 *
 *	int
 *	proc(clientData, sequence, command)
 *	    ClientData clientData;
 *	    char *sequence;
 *	    char *command;
 *	{
 *	}
 *	
 *	ClientData is the same parameter passed into Cmd_EnumBindings.
 *	Sequence and command describe a keystroke sequence and its
 *	corresponding binding.  Normally, proc should return 1.
 *	If proc ever returns 0, then this procedure returns
 *	immediately with a 0 value.
 *
 *----------------------------------------------------------------------
 */

int
Cmd_EnumBindings(tableToken, proc, clientData)
    Cmd_Table tableToken;	/* Token for command table. */
    int (*proc)();		/* Procedure to call for each binding. */
    ClientData clientData;	/* Value to pass through to proc. */
{
    register Table *tablePtr = (Table *) tableToken;
    register Binding *bindingPtr;
    int index;

    for (index = 0; index <= 0177; index++) {
	for (bindingPtr = tablePtr->bindingPtrs[index]; bindingPtr != NULL;
		bindingPtr = bindingPtr->nextPtr) {
	    if (!(*proc)(clientData, bindingPtr->sequence,
		    bindingPtr->command)) {
		return 0;
	    }
	}
    }
    return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * Cmd_MapKey --
 *
 *	Associate a keystroke with a command by comparing it (and
 *	other recently-typed keys) to the keystroke bindings defined
 *	previously by Cmd_BindingCreate.
 *
 * Results:
 *	One of CMD_OK, CMD_PARTIAL, CMD_UNBOUND.  If CMD_OK is
 *	returned, it means that key was the last keystroke in a
 *	sequence for which a binding exists, and the value at
 *	*stringPtr is set to point to the command for that sequence.
 *	If CMD_PARTIAL is returned, it means that the current
 *	keystroke is part-way through a valid sequence, but more
 *	keystrokes are necessary to complete the sequence.  In this
 *	case, *stringPtr gets set to point to the partial sequence
 *	processed so far.  If CMD_UNBOUND is returned, it means
 *	that there is no binding for this keystroke (and its
 *	predecessors);  *stringPtr is filled in with a pointer to
 *	the unbound sequence.
 *
 * Side effects:
 *	State in the command table is updated to reflect the current
 *	position in the keystroke sequence.
 *
 *----------------------------------------------------------------------
 */

int
Cmd_MapKey(tableToken, key, stringPtr)
    Cmd_Table tableToken;	/* Token for table containing info about
				 * bindings and commands. */
    register char key;		/* ASCII key that was just typed.  If not
				 * ASCII, CMD_UNBOUND is returned and the
				 * sequence recognizer is reset. */
    char **stringPtr;		/* Where to store pointer to command or
				 * current sequence. */
{
    register Table *tablePtr = (Table *) tableToken;
    register Binding *bindingPtr;
    int partial = 0;
    int index;

    /*
     * Accumulate the current sequence in a string in the command table.
     * May have to grow the string if it gets too large.
     */
    
    index = tablePtr->bindingCount;
    if (tablePtr->bindingCount >= tablePtr->maxChars) {
	char *new;
	tablePtr->maxChars *= 2;
	new = (char *) malloc((unsigned) (tablePtr->maxChars + 1));
	strncpy(new, tablePtr->currentSequence, index);
	free((char *) tablePtr->currentSequence);
	tablePtr->currentSequence = new;
    }
    tablePtr->currentSequence[tablePtr->bindingCount] = key;
    tablePtr->bindingCount = index + 1;
    tablePtr->currentSequence[tablePtr->bindingCount] = 0;

    /*
     * Check the characters accumulated so far against the first
     * characters of the bindings in the table.  If they match
     * exactly then we're done.  If they match the first part of
     * some binding, then we have to wait for more characters.
     * Otherwise, this sequence is unbound.
     */

    *stringPtr = tablePtr->currentSequence;
    index = tablePtr->currentSequence[0] & 0177;
    for (bindingPtr = tablePtr->bindingPtrs[index]; bindingPtr != NULL;
	    bindingPtr = bindingPtr->nextPtr) {
	if (strncmp(bindingPtr->sequence, tablePtr->currentSequence,
		tablePtr->bindingCount) == 0) {
	    if (bindingPtr->length == tablePtr->bindingCount) {
		*stringPtr = bindingPtr->command;
		tablePtr->bindingCount = 0;
		return CMD_OK;
	    }
	    partial = 1;
	}
    }
    if (partial) {
	return CMD_PARTIAL;
    }
    tablePtr->bindingCount = 0;
    return CMD_UNBOUND;
}
