/************************************************************************
 * 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.						*
 ************************************************************************/

#define NO_PROCDECL	/* Kludge to get this through teensy pdp11 compiler. */

#include "jove.h"

RCS("$Id: util.c,v 14.32.0.11 1994/06/03 04:16:31 tom Exp tom $")

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

DEF_INT( "abort-char", AbortChar, V_CHAR ) = CTL('G');


Line *
lastline(lp)
register Line	*lp;
{
	register Line	*next;

	while (next = lp->l_next)
		lp = next;
	return lp;
}

int	alarmed ZERO;

/*
 * Keystrokes are remembered in their "raw", i.e., unprocessed form in the
 * raw_strokes buffer.  Occasionally these strokes are appended to the
 * prettyprint stroke buffer, and the raw_strokes queue is emptied,
 * whenever `key_strokes' is called. Thus formatting happens only when it
 * is really needed so we save some time.
 */
#define NSTROKES	20

private short	raw_strokes[NSTROKES],
		*raw_p;
private char	printed_strokes[5*NSTROKES],
		*print_p;

void
init_strokes()
{
	alarmed = 0;
	raw_p = raw_strokes;
	print_p = printed_strokes;
}

void
add_stroke(c)
{
	register short	*p = raw_p;

	if (p == &raw_strokes[NSTROKES])
		p -= NSTROKES;
	*p++ = c;
	raw_p = p;
}

char *
key_strokes()
{
	register short	*p = raw_strokes;
	register char	*d = print_p,
			*end = &printed_strokes[sizeof printed_strokes - 5];

	while (d < end && p < raw_p) {
		d += sprintf(d, "%p ", *p++);
	}
	print_p = d;
	raw_p = raw_strokes;

	return printed_strokes;
}

/*
 * [TRH] a nice cheat -- replace contents of keystroke buffer
 */
void
repl_strokes(s)
register const char	*s;
{
	register char	*d = printed_strokes;

	if (alarmed)
		message(s);

	while (*d++ = *s++) ;
	print_p = --d;
	raw_p = raw_strokes;
}

#ifndef TINY
int
pop_stroke()
{
	register int	c = EOF;
	register short	*p = raw_p;

	if (p > raw_strokes) {
		c = *--p;
		raw_p = p;
	}
	return c;
}
#endif

/* Timeout is now handled by getchar (or rawchar if FUNCKEYS) */

void (*timeout_proc)__(( void ));

#define trigger()	{ if (!alarmed) timeout_proc = slowpoke; }

#define end_wait()	{ timeout_proc = NULL; }

private void slowpoke __(( void ));
private void
slowpoke()
{
	alarmed++;
	f_mess("%s", key_strokes());
	end_wait();
}

int
waitchar()
{
	register int	c;

	trigger();
	c = getch();
	if (alarmed)
		message(key_strokes());
	end_wait();

	return c;
}

/* dir > 0 means forward; else means backward. */

#if 0	/* [TRH] unused now... */
char *
StrIndex(dir, buf, charpos, what)
register const char	*buf;
register char	what;
{
	register const char	*cp = &buf[charpos];

	if (dir > 0) {
		while (*cp)
			if (*cp++ == what)
				return (char *)(--cp);
	} else {
		cp++;
		while (cp > buf)
			if (*--cp == what)
				return (char *)(cp);
	}
	return 0;
}
#endif

int
blnkp(buf)
register const char	*buf;
{
	register char	c;

	do {
		if ((c = *buf++) == '\0')
			return YES;
	} while (isspace(c));

	return NO;	/* It's zero if we didn't get to the end of the Line */
}

Line *
next_line(line, num)
Line	*line;
int	num;
{
	register Line	*lp, *np;
	register int	n;

	if (lp = line)
		if ((n = num) < 0)
			while (++n <= 0 && (np = lp->l_prev) != NULL)
				lp = np;
		else
			while (--n >= 0 && (np = lp->l_next) != NULL)
				lp = np;
	return lp;
}

/* prev_line is replaced with a macro. */

/* If new line is != current line, then save current line.  Then set dot
   to line, and if they weren't equal get that line into linebuf.
   [TRH Jun-91] Check that `col' is contained in the line, pad with blanks
   if necessary. This makes life in OverWrite mode a lot easier... */

void
DotTo(line, col)
Line		*line;
register int	col;
{
	register Buffer	*cb = curbuf;
    {
	register Line	*newdot;

	if ((newdot = line) && (newdot != cb->b_dot)) {
		lsave();
		cb->b_dot = newdot;
		cb->b_char = getline(newdot->l_dline, linebuf);
		/* i.e., length of line */
	}
    }
	if ((col -= cb->b_char) > 0) {
		register char	*cp = &linebuf[cb->b_char];

		do {
			if (*cp++ == '\0') {
				--cp;
				if (BufMinorMode(cb, OverWrite))
					ins_c(' ', cp, 0, col,
					      (int)(&linebuf[LBSIZE] - cp));
				else
					col = 0;
				break;
			}
		} while (--col);
		cb->b_char = cp - linebuf;
	}
	cb->b_char += col;
}

void
SetDot(pos)
Bufpos *pos;
{
	register Bufpos	*bp;

	if (bp = pos)
		DotTo(bp->p_line, bp->p_char);
}

void
ToFirst()
{
	SetLine(curbuf->b_first);
}

void
ToLast()
{
	SetLine(curbuf->b_last);
	Eol();
}

DEF_INT( "mark-threshold", MarkThresh, V_BASE10 ) = 22;
	/* Average screen size ... */

static int	line_diff;

int
LineDist(nextp, endp)
Line	*nextp,
	*endp;
{
	inorder(nextp, 0, endp, 0);
	return line_diff;
}

int
inorder(nextp, char1, endp, char2)
register Line	*nextp,
		*endp;
{
	register Line	*prevp;

	line_diff = 0;
	if (nextp == endp)
		return char1 < char2;

	for (prevp = nextp; ; line_diff++) {
		if (nextp == endp)
			return YES;
		if (prevp == endp)
			return NO;
		if (!nextp) {
			if (!prevp)
				return line_diff = 0, -1;
		} else {
			nextp = nextp->l_next;
			if (!prevp)
				continue;
		}
		prevp = prevp->l_prev;
	}
	/* NOTREACHED */
}

void
PushPntp(line)
Line	*line;
{
	if (LineDist(curline, line) >= MarkThresh)
		set_mark();
}

int
length(line)
Line	*line;
{
	return strlen(lcontents(line));
}

int
lineno(line)
Line	*line;
{
	register int	n = 0;
	register Line	*l;

	if (l = line) do ++n; while (l = l->l_prev);
	return n;
}

/* copy the word at dot in buffer "buf" of size "size" */

void
WordAtDot(buf, size)
register char	*buf;
register int	size;
{
	register char	*cp = &linebuf[curchar];

	while (--cp >= linebuf && isword(*cp))
		;
	cp++;
	while (--size > 0 && isword(*cp))
		*buf++ = *cp++;
	*buf = '\0';
}

/* Are there any modified buffers?  Allp means include B_PROCESS
   buffers in the check. */

int
ModBufs(allp)
{
	register Buffer *b;

	if (b = world) do {
		switch (b->b_type) {

		default:
			if (!allp)
				continue;
						/* fall through... */
		case B_FILE:
			if (IsModified(b))
				return YES;

		case B_SCRATCH:
			continue;
		}
	} while (b = b->b_next);

	return NO;
}

char *
filename(b)
const Buffer	*b;
{
	register const char	*fname;
	return (fname = b->b_fname) ? pr_name(fname) : "[No file]";
}

char *
itos(num)
int	num;
{
	static char	line[1 + 3 * sizeof(int)];

	sprintf(line, "%d", num);
	return line;
}

void
tiewind(w, bp)
register Window *w;
register Buffer *bp;
{
	register Buffer	*old_bp;

	updmodline();	/* Kludge ... but speeds things up considerably */
	w->w_line = bp->b_dot;
	w->w_char = bp->b_char;
	if (old_bp = w->w_bufp)
		old_bp->b_top = w->w_top;
	w->w_bufp = bp;
	SetTop(w, bp->b_top);
}

char *
lcontents(line)
register const Line	*line;
{
	if (line == curline)
		return linebuf;
	else
		return lbptr(line);
}

ltobuf(line, buf)
register Line	*line;
register char	*buf;
{
	if (line == curline) {
		register char	*lb = linebuf;

		return (buf == lb) ? strlen(lb) : appcpy(buf, lb) - buf;
	}
	return getline(line->l_dline, buf);
}

void
DOTsave(bp)
register Bufpos	*bp;
{
	register Buffer	*cb = curbuf;

	bp->p_line = cb->b_dot;
	bp->p_char = cb->b_char;
}

/* Return non-zero if we had to rearrange the order. */

int
fixorder(line1, char1, line2, char2)
register Line	**line1,
		**line2;
register int	*char1,
		*char2;
{
	register Line	*tline;
	register int	tchar;

	if (inorder(*line1, *char1, *line2, *char2))
		return NO;

	tline = *line1;
	tchar = *char1;
	*line1 = *line2;
	*char1 = *char2;
	*line2 = tline;
	*char2 = tchar;

	return YES;
}

/* Fill in the bounds of the current region in Bufpos array `r[2]',
   return whether point is at end of region. */

int
CurRegion(r)
register Bufpos	r[2];
{
	register Mark *m = CurMark();

	DOTsave(&r[0]);
	r[1].p_line = m->m_line;
	r[1].p_char = m->m_char;
	return fixorder(&r[0].p_line, &r[0].p_char, &r[1].p_line, &r[1].p_char);
}

int
inlist(first, what)
Line		*first;
register Line	*what;
{
	register Line	*line;

	if (line = first) do {
		if (line == what)
			return YES;
	} while (line = line->l_next);
	return NO;
}

/* Make `buf' modified and tell the redisplay code to update the modeline
   if it will need to be changed. */

int	ModCount ZERO;

void
modify()
{
	extern int	DOLsave;
	register Buffer	*cb = curbuf;

	if (!cb->b_modified) {
		chk_file(cb->b_fname, "modify");
		cb->b_modified++;
		updmodline();
	}
	DOLsave++;
	if (!Asking)
		ModCount++;
}

DEF_CMD( "make-buffer-unmodified", unmodify, NO )
{
	updmodline();
	curbuf->b_modified = NO;
}

void
len_error(how)
void	(*how)__(( const char *_(fmt), ... ));
{
	(*how)("[line too long]");
}

/* Insert num number of c's at offset atchar in a linebuf of LBSIZE */

void
ins_c(c, buf, atchar, num, maxsize)
char	c, *buf;
{
	register char	*s, *t, *at;

	if (num <= 0)
		return;

	s = at = &buf[atchar];
	while (*s++) ;			/* skip to end of line */

	if ((int)(s - at) + atchar + num > maxsize)	/* incl. terminating \0 */
		len_error(complain);

	/* shift tail num characters forward (by copying backwards) */
	t = s + num;
	while (s > at)
		*--t = *--s;

	/* now fill gap with c's */
	while (s < t)
		*s++ = c;
}

int
TwoBlank()
{
	register Line	*next;

	return (((next = curline->l_next) != NULL) &&
		(*(lcontents(next)) == '\0') &&
		((next = next->l_next) != NULL) &&
		(*(lcontents(next)) == '\0'));
}

void
linecopy(onto, atchar, from)
register char		*onto;
register const char	*from;
{
	register const char	*endp = &onto[LBSIZE - 2];

	onto += atchar;

	while (*onto++ = *from++)
		if (onto > endp)
			len_error(error);
}

char *
syserr()
{
	register int err;

	if (!(err = errno))
		return "Disk full?";	/* an educated guess... */
#ifndef TINY
#   if _ANSI_LIBRARY_
#	if VAXC
	return strerror(err, vaxc$errno);
#	else
	return strerror(err);
#	endif
#   else
    {
#	ifndef sys_nerr
	extern const int	sys_nerr;
#	endif
#	ifndef sys_errlist
	extern const char 	*sys_errlist[];
#	endif

	if ((unsigned) err < sys_nerr)
		return sys_errlist[err];
    }
	return itos(err);
#   endif
#else
	return itos(err);
#endif /* TINY */
}

char *
IOerr(err, file)
const char	*err, *file;
{
#ifdef TINY
	return sprint("Couldn't %s \"%s\".", err, pr_name(file));
#else
	return sprint("Couldn't %s \"%s\". (%s)", err, pr_name(file), syserr());
#endif
}

#if unix
void
pipeclose(p)
register int	*p;
{
	close(*p++);
	close(*p);
}

void
pipeopen(p)
int	p[2];
{
	if (pipe(p) < 0)
		complain("[Pipe failed]");
}
#endif /* unix || vms */

/* NOSTRICT */

void_*
emalloc(size)
size_t	size;
{
	register void_* ptr;

	while ((ptr = malloc(size)) == NULL)
		/* Try garbage collecting lines */
		if (!GCchunks())
			/* Uh ... Oh screw it! */
			error("[Out of memory] ");
	return ptr;
}

/* Get the current time string. */
/* Assumes ctime() returns string with format "Wkd Mon dd hh:mm:ss yyyy\n". */
#ifndef get_ctime
char *
get_ctime()
{
	extern char	*ctime __(( const time_t *_(timep) ));
	extern time_t	now;
	register char	*cp;

	time(&now);			/* be accurate. */
	cp = ctime(&now);
	cp[24] = '\0';			/* get rid of '\n'. */
	return cp;
}
#endif

/* get current time, as HH:MM. */
const char *
hh_mm()
{
	static char	HH_MM[6];
	static time_t	last ZERO;
	extern time_t	now;

	/* Only update if a minute or more passed since last time called,
	   or just past a minute. */
	if (now % 60 + last < now) {
		last = now;
		null_ncpy(HH_MM, get_ctime() + 11, 5);
	}
	return HH_MM;
}

#ifndef min
int
min(a, b)
register int	a,
		b;
{
	return (a < b) ? a : b;
}
#endif

#ifndef max
int
max(a, b)
register int	a,
		b;
{
	return (a > b) ? a : b;
}
#endif

int
numcomp(s1, s2)
register const char	*s1,
			*s2;
{
	register const char	*base = s1;

	while (*s1 == *s2++)
		if (*s1++ == '\0') {
			--s1;
			break;
		};
	return s1 - base;
}

char *
copystr(str)
const char	*str;
{
	return strcpy(emalloc(strlen(str) + 1), str);
}

char *
set_str(strptr, value)
register char	**strptr;
const char	*value;
{
	if (*strptr == NULL || strcmp(*strptr, value) != 0) {
		if (!ISSTATIC(*strptr)) {
			free(*strptr);
			*strptr = (char *) NullStr;
		}
		*strptr = copystr(value);
	}
	return *strptr;
}

#ifndef byte_copy
void
byte_copy(from, to, count)
register const char	*from;
register char		*to;
register int		count;
{
	while (--count >= 0)
		*to++ = *from++;
}

/* assume there is no bzero() too */
void
bzero(p, count)
register char	*p;
register int	count;
{
	while (--count >= 0)
		*p++ = 0;
}

/* assume there is no bcmp() too */
int
bcmp(s, t, count)
register const char	*s,
			*t;
register int		count;
{
	while (--count >= 0)
		if (*s++ != *t++)
			return (*--s - *--t);
	return 0;
}
#endif

char *
strlwr(str)
char	*str;
{
	register char	*s = str;
	register char	c;

	while (c = tolower(*s)) {
#if (BPC == 8)	/* "regular" ascii characters only */
		if ((unsigned) c > '\177')
			s++;
		else
#endif
			*s++ = c;
	}
	return str;
}

char *
strupr(str)
char	*str;
{
	register char	*s = str;
	register char	c;

	while (c = toupper(*s)) {
#if (BPC == 8)	/* "regular" ascii characters only */
		if ((unsigned) c > '\177')
			s++;
		else
#endif
			*s++ = c;
	}
	return str;
}

#if 0	/* never used anywhere */
int
casecmp(str1, str2)
register const char	*str1,
			*str2;
{
	while (CEquiv(*str1) == CEquiv(*str2++))
		if (*str1++ == '\0')
			return 0;
	return (*str1 - *--str2);
}
#endif

int
casencmp(str1, str2, n)
register const char	*str1,
			*str2;
register int		n;
{
	while (--n >= 0)
		if (CEquiv(*str1) != CEquiv(*str2++))
			return (*str1 - *--str2);
		else if (*str1++ == '\0')
			break;
	return 0;
}

void
null_ncpy(to, from, n)
register char		*to;
register const char 	*from;
register int		n;
{
	while (--n >= 0)
		if ((*to++ = *from++) == '\0')
			return;
	*to = '\0';
}

char *
appcpy(t, f)
register char		*t;
register const char 	*f;
{
	while (*t++ = *f++)
		;
	return --t;
}

int
sindex(pattern, string)		/* equiv to ANSI strstr() */
const char	*pattern,
		*string;
{
	register const char	*p = pattern,
				*s = string;
	register char		c;

	if ((c = *p++) == '\0')	/* Null pattern matches anything. */
		return 1;
	while (*s != '\0') {
		if (c == *s++ && p[numcomp(p, s)] == '\0')
			return (s - string);
	}
	return NO;
}

/* Return the basename of file F. */

char *
basename(f)
register const char	*f;
{
	register const char	*cp;
	register char		c;
#ifdef DOS
	if (f[0] && f[1] == ':')
		f += 2;
#endif
	for (cp = f; (c = *cp++); ) {
#if vms		/* handle VMS-style filespec. */
		if (c == ']' || c == ':' || c == '>')
			f = cp;
#endif
		if (ISDIRSEP(c))
			f = cp;
	}

	return (char *) f;
}

/* Build a filename from prefix `path' and basename `name' in `buf' */

char *
make_filename(buf, path, name)
register char	*buf;
const char	*path,
		*name;
{
	register const char	*s = path;
	register char		*d = buf;

	while(*d++ = *s++)
		;
	if (--d > buf && !ISDIRSEP(d[-1]))
		*d++ = SLASH;
	s = name;
	while (*d++ = *s++)
		;
	return buf;
}

#ifndef DOS
void
make_argv(argv, ap)
register char	*argv[];
va_register va_list ap;
{
	register char	*cp;

	*argv++ = (cp = va_arg(ap, char *));
	*argv++ = basename(cp);
	while (*argv++ = va_arg(ap, char *))
		;
}
#endif

#if unix
/*
 * this one is to catch exit() in crt0.o  -- TRH sep-88
 */
#ifndef PROFILING
#ifdef _P_EXIT_RETURN_TYPE
_P_EXIT_RETURN_TYPE
#else
void
#endif
#ifdef __GNUC__
volatile
#endif
exit(exitval)
int exitval;
{
	_exit(exitval);
	/* NOTREACHED */
}
#endif
#endif /* unix */

#if unix || vms
void
exec_fail(program)
const char	*program;
{
	register const char	*prg;

	if (prg = program) {
		extern int	CapLine, CapCol;
		register int	s_line = CapLine,
				s_col = CapCol;

		f_mess("[%s: exec failed!]", prg);
		Placur(s_line, s_col);		/* as if we did nothing! */
		flusho();
	}
	_exit(-ENOEXEC);
	/* NOTREACHED */
}
#endif /* unix || vms */

/* we need a predictable mktemp for recovery... */

public char *
mktemp(template)
register char	*template;
{
	register char	*s = template;
	register int	i = getpid();

	while (*s++) ;
	--s;
	while (*--s == 'X') {
	    *s = (i % 10) + '0';
	    i /= 10;
	}
	s++;
	i = 'A';
	while (access(template, F_OK) == 0)		/* file exists */
		*s = i++;

	return (template);
}

#if !unix
char *
mktmpe(result, template)
char		*result;
const char	*template;
{
	return mktemp(make_filename(result, TmpFilePath, template));
}
#endif

#ifdef ctime
/* some ctime()s use sprintf with %M.Ns directives which is not supported by
   JOVEs sprintf.  Sigh... */

#   include <time.h>

char *
ctime(time)
time_t	*time;
{
	extern struct tm	*localtime();
	struct tm	*t = localtime(time);
	static char	result[26];
	static char	days[][4] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"},
			months[][4] = {"Jan","Feb","Mar","Apr","May","Jun",
				       "Jul","Aug","Sep","Oct","Nov","Dec"};

	sprintf(result, "%s %s %2d %02d:%02d:%02d %4d\n",
		days[t->tm_wday], months[t->tm_mon], t->tm_mday,
		t->tm_hour, t->tm_min, t->tm_sec, t->tm_year + 1900);
	return result;
}

#endif /* ctime */

/* Like stat() but check if filename exists;
   zero out stat buffer in case of failure. */

int
zstat(filename, st)
const char		*filename;
register struct stat	*st;
{
	register const char	*fname;
	register int		result = 0;

	if ((fname = filename) == NULL || stat(rel_name(fname), st) != 0) {
		bzero(st, sizeof *st);
		--result;
	}
	return result;
}

/*======================================================================
 * $Log: util.c,v $
 * Revision 14.32.0.11  1994/06/03  04:16:31  tom
 * (CurRegion): new function, replaces ad-hoc CurMark(),DOTsave(),fixorder().
 *
 * Revision 14.32.0.10  1994/04/22  18:24:24  tom
 * (make_argv): use `va_register va_list'.
 *
 * Revision 14.32.0.8  1993/11/11  01:47:31  tom
 * (pop_stroke): new function.
 *
 * Revision 14.32  1993/03/22  10:34:52  tom
 * *** empty log message ***
 *
 * Revision 14.31.0.1  1993/03/22  10:34:52  tom
 * fix botch in syserr().
 *
 * Revision 14.31  1993/02/18  01:42:32  tom
 * simplify get_ftime(); add hh_mm() to supply cached HH:MM string;
 * in case[n]cmp(), replace toupper() with CEquiv(); fix type of bcmp;
 * remove (void) casts; lotsa random optimizations.
 *
 * Revision 14.30  1993/02/08  19:16:16  tom
 * cleanup whitespace; some random optimizations; add zstat().
 *
 * Revision 14.29  1992/12/29  13:35:51  tom
 * more portability kludges...
 *
 * Revision 14.28  1992/10/24  01:24:23  tom
 * convert to "port{ansi,defs}.h" conventions.
 *
 * Revision 14.27  1992/09/22  02:03:52  tom
 * exit(): add `volatile' qualifier for Gnu C.
 *
 * Revision 14.26  1992/08/26  23:57:00  tom
 * add RCS directives.
 *
 */
