/* $Header: /home/hyperion/mu/christos/src/sys/tcsh-6.00/RCS/ed.xmap.c,v 3.0 1991/07/04 21:49:28 christos Exp $ */
/*
 * ed.xmap.c: This module contains the procedures for maintaining
 *	      the extended-key map.
 *
 * 	      An extended-key (Xkey) is a sequence of keystrokes
 *	      introduced with an sequence introducer and consisting
 *	      of an arbitrary number of characters.  This module maintains
 *	      a map (the Xmap) to convert these extended-key sequences
 * 	      into input strings or editor functions. It contains the
 *	      following externally visible functions.
 *
 *		int GetXkey(ch,code);
 *		Char *ch;
 *		Char **code;
 *
 *	      Looks up *ch in map and then reads characters until a
 *	      complete match is found or a mismatch occurs.  Returns 1
 *	      for command and 0 for a string. Returns NULL in code for
 *	      no match and 0 as value.  The last character read is returned
 *	      in *ch.
 *
 *		void AddXkey(Xkey, code);
 *		Char *Xkey;
 *		Char * code;
 *
 *		void AddXKeyCmd(Xkey, CmdCode);
 *		Char *Xkey;
 *		Char CmdCode;
 *
 *	      Adds Xkey to the Xmap and associates the code with it.  If
 *	      Xkey is already is in Xmap, the new code is applied to the
 *	      existing Xkey.
 *
 *	        int DeleteXkey(Xkey);
 *	        Char *Xkey;
 *
 *	      Delete the Xkey and all longer Xkeys staring with Xkey, if
 *	      they exists.
 *
 *	      Warning:
 *		If Xkey is a substring of some other Xkeys, then the longer
 *		Xkeys are lost!!  That is, if the Xkeys "abcd" and "abcef"
 *		are in Xmap, adding the key "abc" will cause the first two
 *		definitions to be lost.
 *
 *		void ResetXmap();
 *
 *	      Removes all entries from Xmap and resets the defaults.
 *
 *		void PrintXkey(Xkey);
 *		Char *Xkey;
 *
 *	      Prints all extended keys prefixed by Xkey and their associated
 *	      commands.
 *
 *	      Restrictions:
 *	      -------------
 *	        1) It is not possible to have one Xkey that is a
 *		   substring of another.
 */
/*-
 * Copyright (c) 1980, 1991 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include "config.h"
#if !defined(lint) && !defined(pdp11)
static char *rcsid() 
    { return "$Id: ed.xmap.c,v 3.0 1991/07/04 21:49:28 christos Exp $"; }
#endif

#include "sh.h"
#include "ed.h"
#include "ed.defns.h"

#ifndef NULL
#define NULL 0
#endif

/* Internal Data types and declarations */

/* The Nodes of the Xmap.  The Xmap is a linked list of these node
 * elements
 */
typedef struct Xmapnode {
    Char    ch;			/* single character of Xkey */
    Char   *code;		/* command code or pointer to string, if this
				 * is a leaf */
    struct Xmapnode *next;	/* ptr to next char of this Xkey */
    struct Xmapnode *sibling;	/* ptr to another Xkey with same prefix */
}       XmapNode;

static XmapNode *Xmap = NULL;	/* the current Xmap */

static Char CurCode;
static XmapNode CurCmd = {0, &CurCode, NULL, NULL};

/* Some declarations of procedures */
static	int       	 TraverseMap	__P((XmapNode *, Char *, Char **));
static	int       	 TryNode	__P((XmapNode *, Char *, Char *, int));
static	XmapNode	*GetFreeNode	__P((int));
static	void		 PutFreeNode	__P((XmapNode *));
static	int		 TryDeleteNode	__P((XmapNode **, Char *));


/* ResetXmap():
 *	Takes all nodes on Xmap and puts them on free list.  Then
 *	initializes Xmap with arrow keys
 */
void
ResetXmap(vi)
    int     vi;
{
    static Char strA[] = {033, '[', 'A', '\0'};
    static Char strB[] = {033, '[', 'B', '\0'};
    static Char strC[] = {033, '[', 'C', '\0'};
    static Char strD[] = {033, '[', 'D', '\0'};
    static Char stOA[] = {033, 'O', 'A', '\0'};
    static Char stOB[] = {033, 'O', 'B', '\0'};
    static Char stOC[] = {033, 'O', 'C', '\0'};
    static Char stOD[] = {033, 'O', 'D', '\0'};

    PutFreeNode(Xmap);
    Xmap = NULL;
    AddXKeyCmd(strA, F_UP_HIST);
    AddXKeyCmd(strB, F_DOWN_HIST);
    AddXKeyCmd(strC, F_CHARFWD);
    AddXKeyCmd(strD, F_CHARBACK);
    AddXKeyCmd(stOA, F_UP_HIST);
    AddXKeyCmd(stOB, F_DOWN_HIST);
    AddXKeyCmd(stOC, F_CHARFWD);
    AddXKeyCmd(stOD, F_CHARBACK);
    if (vi) {
	AddXKeyCmd(&strA[1], F_UP_HIST);
	AddXKeyCmd(&strB[1], F_DOWN_HIST);
	AddXKeyCmd(&strC[1], F_CHARFWD);
	AddXKeyCmd(&strD[1], F_CHARBACK);
	AddXKeyCmd(&stOA[1], F_UP_HIST);
	AddXKeyCmd(&stOB[1], F_DOWN_HIST);
	AddXKeyCmd(&stOC[1], F_CHARFWD);
	AddXKeyCmd(&stOD[1], F_CHARBACK);
    }
    return;
}


/* GetXkey():
 *	Calls the recursive function with entry point Xmap
 */
int
GetXkey(ch, code)
    Char   *ch;
    Char  **code;
{
    return (TraverseMap(Xmap, ch, code));
}

/* TraverseMap():
 *	recursively traverses node in tree until match or mismatch is
 * 	found.  May read in more characters.
 */
static int
TraverseMap(ptr, ch, code)
    XmapNode *ptr;
    Char   *ch;
    Char  **code;
{
    Char    tch;

    if (ptr->ch == *ch) {
	/* match found */
	if (ptr->next) {
	    if (ptr->next != &CurCmd) {
		/* Xkey not complete so get next char */
		if (G_N_Char(&tch) != 1) {	/* if EOF or error */
		    *ch = 0;
		    CurCmd.code[0] = F_SEND_EOF;
		    *code = CurCmd.code;
		    return 1;	/* PWP: Pretend we just read an end-of-file */
		}
		*ch = tch;
		return (TraverseMap(ptr->next, ch, code));
	    }
	    else {
		CurCmd.code[0] = (Char) ptr->code;
		*code = CurCmd.code;
		return 1;
	    }
	}
	else {
	    /* next is null so this is leaf node and a string */
	    *ch = 0;
	    *code = ptr->code;
	    return 0;
	}
    }
    else {
	/* no match found here */
	if (ptr->sibling) {
	    /* try next sibling */
	    return (TraverseMap(ptr->sibling, ch, code));
	}
	else {
	    /* no next sibling -- mismatch */
	    *code = NULL;
	    return 0;
	}
    }
}

void
AddXkey(Xkey, code)
    Char   *Xkey;
    Char   *code;
{
    if (Xkey[0] == '\0') {
	xprintf("AddXkey: Null extended-key not allowed.\n");
	return;
    }

    if (Xmap == NULL)
	/* tree is initially empty.  Set up new node to match Xkey[0] */
	Xmap = GetFreeNode(Xkey[0]);	/* it is properly initialized */

    /* Now recurse through Xmap */
    (void) TryNode(Xmap, Xkey, code, 1);	/* string */
    return;
}

void
AddXKeyCmd(Xkey, CmdCode)
    Char   *Xkey;
    int    CmdCode;
{
    /* Gould does not like casts... */
    unsigned int comp_r_bug;

    if (Xkey[0] == '\0') {
	xprintf("AddXKeyCmd: Null extended-key not allowed.\n");
	return;
    }
    if (CmdCode == F_XKEY) {
	xprintf("AddXKeyCmd: sequence-lead-in command not allowed\n");
	return;
    }

    if (Xmap == NULL)
	/* tree is initially empty.  Set up new node to match Xkey[0] */
	Xmap = GetFreeNode(Xkey[0]);	/* it is properly initialized */

    /* Now recurse through Xmap */
    comp_r_bug = (unsigned int) CmdCode;
    (void) TryNode(Xmap, Xkey, (Char *) comp_r_bug, 0);	/* command */
    return;
}


static int
TryNode(ptr, string, code, IsString)
    XmapNode *ptr;
    Char   *string;
    Char   *code;
    int     IsString;
{
    /*
     * Find a node that matches *string or allocate a new one
     */
    if (ptr->ch != *string) {
	XmapNode *xm;

	for (xm = ptr; xm->sibling != NULL; xm = xm->sibling)
	    if (xm->sibling->ch == *string)
		break;
	if (xm->sibling == NULL)
	    xm->sibling = GetFreeNode(*string);	/* setup new node */
	ptr = xm->sibling;
    }

    if (*++string == '\0') {
	/* we're there */
	if (ptr->next != NULL && ptr->next != &CurCmd) {
	    PutFreeNode(ptr->next);	/* lose longer Xkeys with this prefix */
	    ptr->next = NULL;
	}
	if (ptr->next == NULL && ptr->code)
	    xfree((ptr_t) ptr->code);
	if (IsString) {
	    ptr->next = NULL;
	    ptr->code = Strsave(code);
	}
	else {
	    ptr->next = &CurCmd;
	    ptr->code = code;
	}
    }
    else {
	/* still more chars to go */
	/*
	 * christos: We need to allocate a new XmapNode also if the next
	 * XmapNode is the CurCmd, cause the previous XmapNode definition was
	 * only one char long!
	 */
	if (ptr->next == NULL || ptr->next == &CurCmd)
	    ptr->next = GetFreeNode(*string);	/* setup new node */
	(void) TryNode(ptr->next, string, code, IsString);
    }
    return (0);
}

void
ClearXkey(map, in)
    KEYCMD *map;
    Char   *in;
{
    if ((map[(unsigned char) *in] == F_XKEY) &&
	((map == CcKeyMap && CcAltMap[(unsigned char) *in] != F_XKEY) ||
	 (map == CcAltMap && CcKeyMap[(unsigned char) *in] != F_XKEY)))
	(void) DeleteXkey(in);
}

int
DeleteXkey(Xkey)
    Char   *Xkey;
{
    if (Xkey[0] == '\0') {
	xprintf("DeleteXkey: Null extended-key not allowed.\n");
	return (-1);
    }

    if (Xmap == NULL)
	return (0);

    (void) TryDeleteNode(&Xmap, Xkey);
    return (0);
}

static int
TryDeleteNode(inptr, string)
    XmapNode **inptr;
    Char   *string;
{
    XmapNode *ptr;
    XmapNode *prev_ptr = NULL;

    ptr = *inptr;
    /*
     * Find a node that matches *string or allocate a new one
     */
    if (ptr->ch != *string) {
	XmapNode *xm;

	for (xm = ptr; xm->sibling != NULL; xm = xm->sibling)
	    if (xm->sibling->ch == *string)
		break;
	if (xm->sibling == NULL)
	    return (0);
	prev_ptr = xm;
	ptr = xm->sibling;
    }

    if (*++string == '\0') {
	/* we're there */
	if (prev_ptr == NULL)
	    *inptr = ptr->sibling;
	else
	    prev_ptr->sibling = ptr->sibling;
	ptr->sibling = NULL;
	PutFreeNode(ptr);
	return (1);
    }
    else if (ptr->next != NULL && TryDeleteNode(&ptr->next, string) == 1) {
	if (ptr->next != NULL)
	    return (0);
	if (prev_ptr == NULL)
	    *inptr = ptr->sibling;
	else
	    prev_ptr->sibling = ptr->sibling;
	ptr->sibling = NULL;
	PutFreeNode(ptr);
	return (1);
    }
    else {
	return (0);
    }
}





/* PutFreeNode():
 *	Puts a tree of nodes onto free list using free(3).
 */
static void
PutFreeNode(ptr)
    XmapNode *ptr;
{
    if (ptr == NULL)
	return;

    if (ptr->next && ptr->next != &CurCmd)
	PutFreeNode(ptr->next);
    PutFreeNode(ptr->sibling);
    if (ptr->next == NULL && ptr->code)
	xfree((ptr_t) ptr->code);
    xfree((ptr_t) ptr);
}


/* GetFreeNode():
 *	Returns pointer to an XmapNode for ch.
 */
static XmapNode *
GetFreeNode(ch)
    int    ch;
{
    XmapNode *ptr;

    ptr = (XmapNode *) xmalloc((size_t) sizeof(XmapNode));
    ptr->ch = ch;
    ptr->code = NULL;
    ptr->next = NULL;
    ptr->sibling = NULL;
    return (ptr);
}

/* Now The Print Routine */
#define maxXkey 100		/* max length of a Xkey for print putposes */
static Char printbuf[maxXkey];	/* buffer for printing */

static int Lookup();
static int Enumerate();
static int printOne();
static int u_p_ch();
extern unsigned char *u_p_string();

/* PrintXKey():
 *	Print the binding associated with Xkey key.
 *	Print entire Xmap if null
 */
void
PrintXkey(key)
    Char   *key;
{
    /* do nothing if Xmap is empty and null key specified */
    if (Xmap == NULL && *key == 0)
	return;

    printbuf[0] = '"';
    if (Lookup(key, Xmap, 1) <= -1)
	/* key is not bound */
	xprintf("Unbound extended key \"%s\"\n", short2str(key));
    return;
}

/* Lookup():
 *	look for the string starting at node ptr.
 *	Print if last node
 */
static int
Lookup(string, ptr, cnt)
    int     cnt;
    Char   *string;
    XmapNode *ptr;
{
    int     ncnt;

    if (ptr == NULL)
	return (-1);		/* cannot have null ptr */

    if (*string == 0) {
	/* no more chars in string.  Enumerate from here. */
	(void) Enumerate(ptr, cnt);
	return (0);
    }
    else {
	/* If match put this char into printbuf.  Recurse */
	if (ptr->ch == *string) {
	    /* match found */
	    ncnt = u_p_ch(cnt, ptr->ch);
	    if (ptr->next && ptr->next != &CurCmd)
		/* not yet at leaf */
		return (Lookup(string + 1, ptr->next, ncnt + 1));
	    else {
		/* next node is null  or &CurCmd so key should be complete */
		if (string[1] == 0) {
		    printbuf[ncnt + 1] = '"';
		    printbuf[ncnt + 2] = '\0';
		    (void) printOne(printbuf, ptr->code, ptr->next == NULL);
		    return (0);
		}
		else
		    return (-1);/* mismatch -- string still has chars */
	    }
	}
	else {
	    /* no match found try sibling */
	    if (ptr->sibling)
		return (Lookup(string, ptr->sibling, cnt));
	    else
		return (-1);
	}
    }
}

static int
Enumerate(ptr, cnt)
    int     cnt;
    XmapNode *ptr;
{
    int     ncnt;

    if (cnt >= maxXkey - 5) {	/* buffer too small */
	printbuf[++cnt] = '"';
	printbuf[++cnt] = '\0';
	xprintf("Some extended keys too long for internal print buffer");
	xprintf(" \"%s...\"\n", short2str(printbuf));
	return (0);
    }

    if (ptr == NULL) {
#ifdef DEBUG_EDIT
	xprintf("Enumerate: BUG!! Null ptr passed\n!");
#endif
	return (-1);
    }

    ncnt = u_p_ch(cnt, ptr->ch);	/* put this char at end of string */
    if (ptr->next == NULL || ptr->next == &CurCmd) {
	/* print this Xkey and function */
	printbuf[ncnt + 1] = '"';
	printbuf[ncnt + 2] = '\0';
	(void) printOne(printbuf, ptr->code, ptr->next == NULL);
    }
    else
	(void) Enumerate(ptr->next, ncnt + 1);

    /* go to sibling if there is one */
    if (ptr->sibling)
	(void) Enumerate(ptr->sibling, cnt);
    return (0);
}


/* PrintOne():
 *	Print the specified key and its associated
 *	function specified by code
 */
static int
printOne(key, code, prstring)
    Char   *key;
    Char   *code;
    int     prstring;
{
    struct KeyFuncs *fp;
    unsigned char unparsbuf[200];
    static char *fmt = "%-15s->  %s\n";

    if (code) {
	if (prstring)
	    xprintf(fmt, short2str(key), u_p_string(code, unparsbuf));
	else {
	    for (fp = FuncNames; fp->name; fp++) {
		if ((int) code == fp->func)
		    xprintf(fmt, short2str(key), fp->name);
	    }
	}
    }
    else
	xprintf(fmt, short2str(key), "no input");
    return (0);
}

static int
u_p_ch(cnt, ch)
    int     cnt;
    Char    ch;
{
    if (ch == 0) {
	printbuf[cnt++] = '^';
	printbuf[cnt] = '@';
	return cnt;
    }

    if (Iscntrl(ch)) {
	printbuf[cnt++] = '^';
	if (ch == '\177')
	    printbuf[cnt] = '?';
	else
	    printbuf[cnt] = ch | 0100;
    }
    else if (ch == '^') {
	printbuf[cnt++] = '\\';
	printbuf[cnt] = '^';
    }
    else if (ch == '\\') {
	printbuf[cnt++] = '\\';
	printbuf[cnt] = '\\';
    }
    else if (ch == ' ' || (Isprint(ch) && !Isspace(ch))) {
	printbuf[cnt] = ch;
    }
    else {
	printbuf[cnt++] = '\\';
	printbuf[cnt++] = ((ch >> 6) & 7) + '0';
	printbuf[cnt++] = ((ch >> 3) & 7) + '0';
	printbuf[cnt] = (ch & 7) + '0';
    }
    return cnt;
}
