/************************************************************************
 * This program is Copyright (C) 1986 by Jonathan Payne.  JOVE is       *
 * provided to you without charge, and with no warranty.  You may give  *
 * away copies of JOVE, including sources, provided that this notice is *
 * included in all the files.                                           *
 ************************************************************************/

#include "jove.h"

#ifdef ABBREV

RCS("$Id: abbrev.c,v 14.31.0.1 1993/07/07 12:12:56 tom Exp tom $")

#include "ctype.h"
#include "io.h"
#include "re.h"

#define HASHSIZE	19

typedef struct abbrev	Abbr;
struct abbrev {
	unsigned int	a_hash;
	char		*a_abbrev,
			*a_phrase;
	Abbr		*a_next;
	data_obj	*a_cmdhook;
};

#define GLOBAL	NMAJORS
private Abbr	*A_tables[NMAJORS + 1][HASHSIZE] ZERO;

DEF_INT( "auto-case-abbrev", AutoCaseAbbrev, V_BOOL ) _IF(def ABBREV) = YES; _IF(def PRIVATE)
DEF_INT( "save-substituted-abbrevs", SavSAbbrev, V_BOOL ) _IF(def ABBREV) ZERO; _IF(def PRIVATE)

private unsigned int	hash __(( const char *_(abbrev) ));
private Abbr	*lookup __(( int _(table), const char *_(abbrev), unsigned _(hashval) ));
private  char	*crlf __(( char *_(str), int _(cr_or_lf) ));
private void	define __(( int _(table), const char *_(abbrev),
			    const char *_(phrase) )),
		save_abbrevs __(( const char *_(file) )),
		rest_abbrevs __(( const char *_(file) ));

private unsigned int
hash(a)
register const char	*a;
{
	register unsigned int	hashval = *a++;
	register int	c;

	while (c = *a++)
		hashval = (hashval << 2) + c;

	return hashval;
}

private Abbr *
lookup(table, abbrev, hashval)
int		table;
const char	*abbrev;
unsigned int	hashval;
{
	register Abbr		*ap;
	register unsigned int	h = hashval;

#ifdef MSCBUG	/* MSC 4.3 compiler bug!%^#$*# */
	register Abbr	**tp = A_tables[table];
	if (ap = tp[h % HASHSIZE])
#else
	if (ap = A_tables[table][h % HASHSIZE])
#endif
	do {
		if (ap->a_hash == h && strcmp(ap->a_abbrev, abbrev) == 0)
			break;
	} while (ap = ap->a_next);

	return ap;
}

private void
define(table, abbrev, phrase)
int		table;
const char	*abbrev,
		*phrase;
{
	register Abbr	*ap;
	register unsigned int	h = hash(abbrev);

	if ((ap = lookup(table, abbrev, h)) == NULL) {
		register struct	abbrev	**tp = &A_tables[table][h % HASHSIZE];

		ap = (struct abbrev *) emalloc(sizeof *ap);
		ap->a_hash = h;
		ap->a_abbrev = copystr(abbrev);
		ap->a_next = (*tp);
		(*tp) = ap;
		ap->a_cmdhook = NULL;
	} else
		free(ap->a_phrase);
	ap->a_phrase = copystr(phrase);
}

DEF_CMD( "expand-abbrev", AbbrevExpand, NO ) _IF(def ABBREV)
{
	register char		*wp = &genbuf[LBSIZE - 1];
	int			Cap_count = 0;
    {
	/* copy last typed word to buffer */
	register int		c;
	register const char	*cp = &linebuf[curchar];

	*wp = '\0';
	while (cp > linebuf && isword(c = *--cp)) {
		if (isupper(c)) {
			Cap_count++;
		}
		*--wp = c;
	}
	if (*wp == '\0')	/* not adjacent to word */
		return;
    }
    {
	register const Abbr	*ap;
      {
	register unsigned int	h;

	/* Try to match literal abbrev first; if this fails, then
	   try again as lowercase abbrev if auto-capitalize is on,
	   but only if the literal contained some uppercase characters. */

	if ((ap = lookup(curbuf->b_major, wp, h = hash(wp))) ||
	    (ap = lookup(GLOBAL, wp, h)))
		Cap_count = 0;
	else if (!(Cap_count && True(AutoCaseAbbrev) &&
		   ((ap = lookup(curbuf->b_major, wp, h = hash(strlwr(wp)))) ||
		    (ap = lookup(GLOBAL, wp, h)))))
		return;
      }
      {
	register int	c;
	register int	o_exp = exp;		/* save numeric argument! */

	/*
	 * use DelNChar with negative argument since DelPChar behaves funny
	 * in OverWrite mode. It also sets mark so that you can change your
	 * mind about the abbrev expansion. `this_cmd' is cleared to prevent
	 * subsequent kills to be appended to killed abbrev.
	 */
	exp = wp - genbuf - (LBSIZE - 1), exp_p = SavSAbbrev, DelNChar();
	this_cmd = 0;
	wp = ap->a_phrase;
	if (*wp) {
		set_mark();
		if (Cap_count) {
			c = *wp++;
			exp = 1, Insert(toupper(c));
			if (--Cap_count) {
				while (*wp) {
					if (isword(c))
						c = *wp++;
					else
						c = toupper(*wp++);
					Insert(c);
				}
			}
		}
		ins_str(wp, NO);
	}

	if (ap->a_cmdhook)
		DoTimes(ExecCmd(ap->a_cmdhook), 1);

	exp = o_exp;	/* restore numeric argument (oh well...) */
      }
    }
}

extern const char * const MajorName[];

private const char
	hdr_pat[] = "^------\\([^ ]* \\)\\{Mode ,\\}abbrevs------$",
	/* (match optional "Mode" for backward compatibility) */
	hdr_fmt[] = "------%sabbrevs------\n";
/*
 * replace linefeeds to CR or vice versa
 * this kludge to allow embedded newlines in abbrevs
 */
private char *
crlf(str, from)
char		*str;
register int	from;
{
	register char	*s = str;

	while (s = index(s, from))
		*s++ = (from == '\n') ? '\r' : '\n';

	return str;
}

private void
save_abbrevs(file)
const char	*file;
{
	register File		*fp;
	register const Abbr	*ap;
	register int		mode,
				i,
				count = 0;

	fp = open_file(file, iobuff, F_WRITE|F_TEXT|F_COMPLAIN);
	{ mode = 0; } do {
		fprintf(fp, hdr_fmt, MajorName[mode]);
		{ i = HASHSIZE - 1; } do {
			if (ap = A_tables[mode][i]) do {
				fprintf(fp, "%s:%s\n", ap->a_abbrev,
					crlf(strcpy(genbuf, ap->a_phrase), '\n'));
				count++;
			} while (ap = ap->a_next);
		} while (--i >= 0);
	} while (++mode <= GLOBAL);
	f_close(fp);
	add_mess(" %d written.", count);
}

/*
 * The format of abbrev files recognized has been extended:
 * everything before the first mode header line is taken as a Global Abbrev,
 * and abbrevs following an unrecognized mode header line are silently ignored.
 */
private void
rest_abbrevs(file)
const char	*file;
{
	register int	mode = GLOBAL;
	register char	*phrase_p;
	register char	*line = genbuf;
	register File	*fp;

	REcompile(hdr_pat, YES, compbuf);
	fp = open_file(file, iobuff, F_READ|F_TEXT|F_COMPLAIN|F_QUIET);
	while (f_gets(fp, line, LBSIZE) == 0 /*!EOF*/) {
		if (re_sindex(line, 0, compbuf)) {	/* a header line */
			putmatch(1, line, LBSIZE);
			mode = match(MajorName, line);
			continue;
		}
		if (mode < 0)		/* ignore unknown abbrev modes */
			continue;
		if (!(phrase_p = index(line, ':')) || phrase_p == line)
			complain("\"%s\":%d: Abbrev. format error.", file, io_lines);

		*phrase_p++ = '\0';	/* Null terminate the abbrev. */
		define(mode, line, crlf(phrase_p, '\r'));
	}
	f_close(fp);
	message(NullStr);
}

private const char	GlobalPrompt[] = "Global Abbrev: ";
#define			AbbrPrompt	(GlobalPrompt + 7)	/* cheat */
private const char	AbbrDelim[] = " \r\n";

DEF_CMD( "define-mode-word-abbrev", DefAbbrev, ARG(NO) ); _IF(def ABBREV)
DEF_CMD( "define-global-word-abbrev", DefAbbrev, ARG(YES) ) _IF(def ABBREV)
{
	char			abbrbuf[LBSIZE];
	register char		*abbrev = abbrbuf;
	register const char	*prompt = AbbrPrompt;
	register int		mode = curbuf->b_major;
	register Abbr		*ap;

	if ((LastCmd->Type & ARG(YES)) || exp_p) {	/* global-abbrev */
		prompt = GlobalPrompt;
		mode = GLOBAL;
	}
	strcpy(abbrev, do_ask(AbbrDelim, (int (*)()) 0, (char *) 0, prompt));

#   define phrase	((!InJoverc && \
			  (ap = lookup(mode, abbrev, hash(abbrev)))) ? \
				ap->a_phrase : (char *) 0)
	define(mode, abbrev,
	       crlf(ask(phrase, "%s%s phrase: ", prompt, abbrev), '\r'));
#   undef phrase
}

DEF_CMD( "write-word-abbrev-file", AbbrevIO, ARG(WRITE) );	_IF(def ABBREV)
DEF_CMD( "read-word-abbrev-file",  AbbrevIO, ARG(READ) )	_IF(def ABBREV)
{
	char		filebuf[FILESIZE];
	register char	*abbr_file = ask_file((char *) 0, (char *) 0, filebuf);
	register void	(*what)__(( const char *_(filename) )) = rest_abbrevs;

	if (LastCmd->Type & ARG(WRITE))
		what = save_abbrevs;

	(*what)(abbr_file);
}

#ifndef TINY
DEF_CMD( "edit-word-abbrevs", EditAbbrevs, NO ) _IF(def ABBREV)_IF(ndef TINY)
{
	EditParam(save_abbrevs, rest_abbrevs, "Edit Abbreviations");
}
#endif

DEF_CMD( "bind-macro-to-word-abbrev", BindMtoW, NO ) _IF(def ABBREV)
{
	register Abbr		*ap;
	register const char	*abbrev;
	register unsigned int	h;

	abbrev = do_ask(AbbrDelim, (int (*)()) 0, (char *) 0, AbbrPrompt);

	if ((ap = lookup(curbuf->b_major, abbrev, h = hash(abbrev))) == NULL &&
	    (ap = lookup(GLOBAL, abbrev, h)) == NULL)
	    	complain("%s: unknown abbrev.", abbrev);

	ap->a_cmdhook = findmac("Macro: ");
}

#endif /* ABBREV */

/*======================================================================
 * $Log: abbrev.c,v $
 * Revision 14.31.0.1  1993/07/07  12:12:56  tom
 * (F_TEXT): new option for f_open et al.
 *
 * Revision 14.31  1993/02/18  02:56:34  tom
 * add hash parameter to lookup() to avoid unnecessary computation of hash
 * value; lookup literal abbrev, and expand it without auto-casing, before
 * trying lowercased abbrev if "auto-case-abbrev" is in effect; remove
 * implicit abbrev size limit.
 *
 * Revision 14.30  1993/02/06  00:48:32  tom
 * cleanup whitespace; some random optimizations.
 *
 * Revision 14.26  1992/08/26  23:56:49  tom
 * add "expand-abbrev" command; PRIVATE-ized some Variable defs;
 * add RCS directives.
 *
 */
