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

RCS("$Id: insert.c,v 14.32.0.12 1994/06/24 01:03:15 tom Exp tom $")

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

#ifdef LISP
private Bufpos	*lisp_indent __(( void ));
#endif

/* Make a newline after AFTER in buffer BUF, UNLESS after is 0,
   in which case we insert the newline before after -- in which case
   BUF cannot be NULL */

Line *
listput(buf, after)
register Buffer *buf;
register Line	*after;
{
	register Line	*newline = nbufline();

	if ((newline->l_prev = after) == NULL) { /* Before the first line */
		newline->l_next = buf->b_first;
		buf->b_first = newline;
		if (buf->b_dot == NULL)
			buf->b_dot = newline;
	}
	else {
		newline->l_next = after->l_next;
		after->l_next = newline;
	}
	if (newline->l_next)
		newline->l_next->l_prev = newline;
	else
		if (buf)
			buf->b_last = newline;

	return newline;
}

/* Divide the current line and move the current line to the next one */

void
LineInsert(num)
{
	register Buffer	*cb = curbuf;
	register Line	*newdot = cb->b_dot;
    {
	register int	n;

	if ((n = num) <= 0)
		return;

	modify();
	do {
		newdot = listput(cb, newdot);
		SavLine(newdot, NullStr);
	} while (--n);
    }
	if (cb->b_char != 0) {
		register char	*tail = &linebuf[cb->b_char];
		register char	c = *tail;

		*tail = '\0';		/* shorten this line */
		lsave();
		modify();
		*tail = c;		/* restore first char of tail */
		strcpy(linebuf, tail);	/* and shift tail */
	}
	else {				/* Redisplay optimization */
		register disk_line	null_da = newdot->l_dline;

		newdot->l_dline = cb->b_dot->l_dline;
		cb->b_dot->l_dline = null_da;
	}
	makedirty(cb->b_dot);
	makedirty(newdot);

	IFixMarks(cb->b_dot, cb->b_char, newdot, 0);
	cb->b_dot = newdot;
	cb->b_char = 0;
}

/* Makes the indent of the current line == goal.  If the current indent
   is greater than GOAL it deletes.  If more indent is needed, it uses
   tabs and spaces to get to where it's going. */

void
n_indent(goal)
{
	curchar = 0;
	AdjWtSpace(goal);
}

/*
 * insert the right amount of whitespace (spaces and tabs)
 * to get from current position to goal.
 */
void
AdjWtSpace(goal)
register int	goal;
{
	register int	from,
			nblanks;

	del_whitespace(FORWARD + BACKWARD);

	from = calc_pos(linebuf, curchar);

	/* try to insert tabs first */
	if ((nblanks = (goal - from)) && (from += TabIncr(from)) <= goal) {

		/* 'from' is now aligned on tabstop */
		exp = ((goal - from) / tabstop + 1), Insert('\t');
		nblanks = goal % tabstop;
	}
	/* do remainder with spaces */
	exp = nblanks, Insert(' ');
	exp = 1;			/* kludge */
}

DEF_CMD( "self-insert", SelfInsert, EDIT )
{
	register Buffer	*cb = curbuf;
	register int	c = LastKeyStruck;

#ifdef ABBREV
	if (BufMinorMode(cb, Abbrev) && !isword(c))
		AbbrevExpand();
#endif
	if (BufMinorMode(cb, OverWrite) && c != '\t') {
		register int	num = exp;

		exp = 1;
		while (--num >= 0) {
			if (!eolp(cb) &&
			    (linebuf[cb->b_char] != '\t' ||
			     TabIncr(calc_pos(linebuf, cb->b_char)) == 1)) {
				modify();
				makedirty(cb->b_dot);
				linebuf[cb->b_char++] = c;
			} else
				Insert(c);
		}
	} else
		Insert(c);

	if (BufMinorMode(cb, Fill) &&
	    !isword(c) &&
	    (cb->b_char >= RMargin || calc_pos(linebuf, cb->b_char) >= RMargin))
		DoJustify(cb->b_dot, 0, cb->b_dot, strlen(linebuf), YES,
			  BufMinorMode(cb, Indent) ?
			  get_indent(cb->b_dot) : LMargin);
}

void
Insert(c)
register int	c;
{
	register Buffer	*cb = curbuf;
	register int	num;

	if ((num = exp) <= 0)
		return;

	if (c == '\n') {
		LineInsert(num);
		return;
	}
	if (c == '\0')
#ifdef NULLCHARS
		c = '\n';
#else
		return;
#endif
	modify();
	makedirty(cb->b_dot);
	ins_c(c, linebuf, cb->b_char, num, LBSIZE);
	IFixMarks(cb->b_dot, cb->b_char, cb->b_dot, cb->b_char + num);
	cb->b_char += num;
}

DEF_INT( "c-indentation-increment", CIndIncrmt, V_BASE10 ); _IF(def PRIVATE)
DEF_INT( "indentation-increment", IndIncrmt, V_BASE10 ); _IF(def PRIVATE)

/* Tab in to the right place for C (and Lisp) mode. */

DEF_CMD( "handle-tab", Tab, EDIT )
{
	register Buffer	*cb = curbuf;

#ifdef ABBREV
	if (BufMinorMode(cb, Abbrev))
		AbbrevExpand();
#endif
#ifdef LISP
	if (BufMajorMode(cb, LISPMODE) && (bolp(cb) || !eolp(cb))) {
		register int	dotchar = cb->b_char;
		register Mark	*m = NULL;

		ToIndent();
		if (dotchar > cb->b_char)
			m = MakeMark(cb->b_dot, dotchar, FLOATER);
		lisp_indent();
		if (m) {
			ToMark(m);
			DelMark(m);
		} else
			ToIndent();
		return;
	}
#endif
    {
	register int	goal = 0,
			incrmt = IndIncrmt,
			num;

	if ((num = exp) < 0)
		return;

	if (C_like_Mode(cb))
		incrmt = CIndIncrmt;
	if (incrmt <= 0)
		incrmt = tabstop;

	if (bolp(cb)) {
		if ((C_like_Mode(cb) &&
		     /* don't indent preprocessor directives. */
		     (linebuf[0] == '#' || c_indent(incrmt))) ||
		    /* use indent of previous line. (Not in fundamental mode) */
		    (!BufMajorMode(cb, FUNDAMENTAL) &&
		     ((goal = get_indent(cb->b_dot->l_prev)) > 0 ||
		      (goal = LMargin) > 0))) {
			/* Indent adjust counts as a single tab,
			   unless an explicit argument is present,
			   then it is not counted at all. */
			if (!exp_p)
				--num;
		}
	}
	if (goal <= 0) {
		skip_blanks();
		goal = calc_pos(linebuf, cb->b_char);
	}
	if (--num >= 0) {
		goal += (incrmt - goal % incrmt) + num * incrmt;
	}
	AdjWtSpace(goal);
    }
}

DEF_INT( "quote-char", QuoteChar, V_CHAR ) = CTL('Q');

DEF_CMD( "quoted-insert", QuotChar, EDIT )
{
	register int	c;

	if ((c = exp) < 0) {		/* use |exp| as character code */
		c = -c & 0xff;
		exp = 1;
	} else {
		c = waitchar();
	}
	LastKeyStruck = c, SelfInsert();
		/* to handle overwrite mode correctly */
}

/* Insert the paren.  If in C mode and c is a '}' then insert the
   '}' in the "right" place for C indentation; that is indented
   the same amount as the matching '{' is indented. */

DEF_INT( "paren-flash-delay", PDelay, V_BASE10 ) = 5; /* 1/2 a second */

extern int SitFor __(( int delay ));

void
Blink(line, col)
register Line	*line;
int	col;
{
	Bufpos	save;
	register Bufpos	*bp = &save;

	DOTsave(bp);
	if (Asking) {
#ifndef TINY
		if (line == bp->p_line) {
			register char	*mp = mesgbuf + Asking,
					*lp = linebuf + bp->p_char;
			register char	c = *--lp;
			register int	offset;

			/* make sure mesgbuf is up to date; scan to the left
			   to count the number of chars recently inserted. */
			do {
				if (*--mp != c)
					Asking++, mp++;
			} while (--lp >= linebuf && *lp == c);
			lp++;
			strcpy(mp, lp);
			/* now, find where the prompt ends. */
			while (lp > linebuf) {
				if (*--lp != *--mp) {
					lp++, mp++;
					break;
				}
			}
			Asking += col - bp->p_char;
			/* reposition the minibuf if necessary. */
			offset = HorWindow(calc_pos(linebuf, col),
					   (int)(lp - linebuf),
					   (int)(mp - mesgbuf));
			if (offset != (int)(lp - linebuf)) {
				offset = how_far(linebuf, offset);
				Asking = col - offset + (int)(mp - mesgbuf);
				strcpy(mp, &linebuf[offset]);
			}
			updmesg();
			SitFor(PDelay);
		}
#endif
		return;		/* sorry, can't oblige you */
	}
	if (in_window(curwind, line) >= 0) {
		DotTo(line, col);
	}
	else {
		register char	*lp = lcontents(line);
		register int	lno = lineno(line);

		if (curwind->w_flags & W_NUMLINES)
			col += 8,
			s_mess("%6d  %s", lno, lp);
		else
			s_mess("%s (l.%d)", lp, lno);
		Asking = col;
	}
	SitFor(PDelay);
	SetDot(bp);
	Asking = 0;
	updmesg();			/* to keep the message line */
}

DEF_CMD( "paren-flash", DoParen, EDIT )
{
	register Buffer	*cb = curbuf;
	register Bufpos *bp = (Bufpos *) -1;
	register int	c = LastKeyStruck;

	if (C_like_Mode(cb)) {
		if (c == '}' && blnkp(linebuf))
			bp = c_indent(0);
	}
#ifdef LISP
	else if (BufMajorMode(cb, LISPMODE)) {
		if (c == ')' && blnkp(linebuf))
			bp = lisp_indent();
	}
#endif
	SelfInsert();
	if (BufMinorMode(cb, ShowMatch) && !charp() && !in_macro() &&
	    !backslashed(linebuf, &linebuf[cb->b_char - 1])) {

		if (bp == (Bufpos *) -1) {
			cb->b_char--;		/* Back onto the ')' */
			bp = m_paren(c, BACKWARD, MP_CAN_STOP|MP_CAN_MISMATCH, (Bufpos *)0);
			cb->b_char++;
		}
		if (bp)
			Blink(bp->p_line, bp->p_char);

		mp_error();	/* display error message */
	}
}

/*
 * in C mode insert hash sign in the first column, and adjust whitespace to
 * the level of the previous preprocessor command, if there is nothing
 * but whitespace between Bol and point.
 * Automagically indent if previous was "if" or "else" command.
 * Automagically de-indent if current is "else" or "endif" command.
 */
DEF_INT( "#-indentation-increment", CprepIncr, V_BASE10 ) = 4; _IF(def PRIVATE)
	/* analogous to CindIncr */

DEF_CMD( "hash-insert", DoHash, EDIT )
{
	static char	re_buf[8 + Bit(BPC-3)];	/* this should be big enough */
	register Buffer	*cb = curbuf;

	if (BufMajorMode(cb, CMODE) && exp_p != YES) {
		register int	here = cb->b_char;
		register int	num;

		if ((num = exp) < 0)
			exp = -num;

		ToIndent();
		if (cb->b_char >= here) {
			register Bufpos	*bp;

			/*
			 * Remove existing `#' to redo indentation.
			 * (it will be re-inserted shortly)
			 */
			if (linebuf[0] == '#')
				DelNChar(), ToIndent();
			/*
			 * kludge: UNdent existing else/endif directives.
			 */
			if (LookingAt("endif\\|else\\|elif", linebuf, cb->b_char))
				exp_p = YES, num = -1;

			cb->b_char = here = 0;

			if (!re_buf[0])
				REcompile("^#[ \t]*", YES, re_buf);

			if (bp = docompiled(BACKWARD, re_buf, (Bufpos *)0, (Line *)0)) {
				register char	*lp = lcontents(bp->p_line);

				here = calc_pos(lp, REeom);
				/*
				 * kludge: indent one more for if/else.  But
				 * line up with if/else if argument is negative.
				 */
				if (LookingAt("if\\|else\\|elif", lp, REeom))
					exp_p += num * YES_NODIGIT;
			}
			if (exp_p > 0)
				here += num * CprepIncr - here % CprepIncr;
			Insert('#');
			/* NOT SelfInsert because of OverWrite mode */
			AdjWtSpace(here);

			return;
		}
		cb->b_char = here;
	}
	SelfInsert();
}

DEF_INT( "delete-blank-tail", DBlnkTail, V_BOOL ) = YES; _IF(def PRIVATE)

DEF_CMD( "newline", Newline, EDIT|ARG(NO) );
DEF_CMD( "newline-and-indent", Newline, EDIT|ARG(Indent) )
{
	register Buffer	*cb = curbuf;
	register int	indent = 0;

	/* first we calculate the indent of the current line (if we need it) */
	if ((LastCmd->Type & ARG(Indent)) || BufMinorMode(cb, Indent)) {
		if ((indent = LMargin) == 0 &&
		    (indent = get_indent(cb->b_dot)) == 0)
			--indent;	/* non-zero to indicate indent mode. */
	}
#ifdef ABBREV
	if (BufMinorMode(cb, Abbrev))
		AbbrevExpand();
#endif
#ifdef LISP
	if (BufMajorMode(cb, LISPMODE))
		del_whitespace(FORWARD + BACKWARD);
	else
#endif
	if (!(LastCmd->Type & ARG(~Indent))) {	/* not "newline-and-backup" */
		register int	direction = FORWARD + BACKWARD;

		if (indent || blnkp(&linebuf[cb->b_char]) ||
		    /* otherwise, leave blanks AFTER point untouched. */
		    (direction -= FORWARD, True(DBlnkTail))) {
			del_whitespace(direction);
		}
	}

	/* If there is more than 2 blank lines in a row then don't make
	   a newline, just move down one. (unless explicitly requested) */
	if (exp_p == NO && eolp(cb) && TwoBlank())
		SetLine(cb->b_dot->l_next);
	else
		LineInsert(exp);

	if (indent) {
#ifdef LISP
		if (BufMajorMode(cb, LISPMODE)) {
			lisp_indent();
			return;
		}
#endif
		if (C_like_Mode(cb)) {
			register int incrmt;

			/* don't indent preprocessor directives */
			if (linebuf[0] == '#')
				return;
			if ((incrmt = CIndIncrmt) <= 0)
				incrmt = tabstop;
			if (c_indent(incrmt))
				return;
		}
		n_indent(indent);
	}
}

DEF_CMD( "newline-and-backup", OpenLine, EDIT|ARG(~Indent) )
{
	Bufpos	save;
	register Bufpos	*dot = &save;

	DOTsave(dot);
	++exp_p, Newline();	/* Open the lines... */
	SetDot(dot);
}

void
ins_str(str, ok_nl)
const char	*str;
{
	register Buffer	*cb = curbuf;
	register char	*s = (char *) str,
			*p,
			*tail = &linebuf[cb->b_char],
			*s_lim;
	register size_t	s_len,		/* length of substring to be inserted */
			l_len,		/* length of line in linebuf incl. \0 */
			max_len;	/* max length of substring */

	if (*s == '\0')
		return;

	for (p = tail; *p++; ) ;	/* skip to end of line */

	for (l_len = p - linebuf ;; str = s, p = (tail = linebuf) + l_len) {

		max_len = LBSIZE - l_len;
		l_len -= cb->b_char;

#ifdef NULLCHARS
	    if (ok_nl < 0) {	/* Kludge: interpret \n as \0 */
		while (*s++) ;
		--s;
	    }
	    else
#endif
		while (*s != '\n' && *++s) ;
				/* skip to next linefeed in string */

		if (s_len = s - (char *) str) {
			modify();
			makedirty(cb->b_dot);

			if (s_len > max_len) {
				/* adjust length of substring */
				s = (char *)&str[s_len = max_len];
			}
			s_lim = s;		/* remember... */
			for (s = p + s_len;  p > tail; )
				*--s = *--p;	/* shift tail */
			for (s = (char *) str;  s < s_lim; )
				*p++ = *s++;	/* insert substring */

			/* and here `s' has its original value */

			IFixMarks(cb->b_dot, cb->b_char, cb->b_dot, cb->b_char + s_len);
			cb->b_char += s_len;
		}
		if (*s == '\0')
			break;		/* end of insertion string */
#ifdef NULLCHARS
	    if (!(ok_nl < 0))
#endif
		if (*s++ != '\n') {	/* skip newline */
			--s;
			if (!ok_nl)	/* truncated string */
				len_error(complain);
		}
		LineInsert(1);

		if (*s == '\0')
			break;
	}
}

/* Take the region FLINE/FCHAR to TLINE/TCHAR and insert it at
   ATLINE/ATCHAR in WHATBUF.
   (which should be either `curbuf', or NULL if yanking to the kill ring)

   [TRH] Some optimizations in an attempt to minimize disk I/O
 */

Bufpos *
DoYank(fline, fchar, tline, tchar, atline, atchar, whatbuf)
register Line	*fline;
Line		*tline;
register Line	*atline;
Buffer		*whatbuf;
{
	static Bufpos	bp;
	char		buf[LBSIZE];
	register char	*from;
#define tail	from	/* alias !! */
	Line		*startline = atline;
	int		startchar = atchar;
	disk_line	start_dline;

	ltobuf(atline, genbuf);
	from = &lcontents(fline)[fchar];

	lsave();
	if (whatbuf)
		modify();

	start_dline = atline->l_dline;

	if (fline == tline) {

		/* special case: affects single line. */
		strcpy(buf, from);
		buf[tchar -= fchar] = '\0';	/* truncate line... */
		linecopy(genbuf, atchar, buf);	/* append... */
		genbuf[atchar + tchar] = from[tchar];
						/* for optimizing check below */
		tail = &lcontents(atline)[atchar];
		atchar += tchar;

	} else {
		/* [TRH] optimizations */
		strcpy(buf, &genbuf[atchar]);

		if (atchar == 0 && fchar == 0) {
			/* replace entire line; use fline unmodified */
			atline->l_dline = fline->l_dline;
		}
		else if (buf[0] == '\0' && from[0] == '\0') {
			/* append null string; leave atline unmodified */
		}
		else {
			linecopy(genbuf, atchar, from);
			SavLine(atline, genbuf);
		}

		/* just copy the line list of the body */
		do {
			makedirty(atline);
			fline = fline->l_next;
			atline = listput(whatbuf, atline);
			atline->l_dline = fline->l_dline;
		} while (fline != tline);

		ltobuf(atline, genbuf);
		tail = buf;
		atchar = tchar;
		fchar = 0;		/* for optimizing check below */
	}
	/* these optimizations are also OK for the special case above */
	if (startchar == 0 && tchar == 0) {
		/* insert null string: use original first line */
		atline->l_dline = start_dline;
	}
	else if (fchar == 0 && tail[0] == '\0' && genbuf[tchar] == '\0') {
		/* append null tail to tline==fline; just copy disk address */
		atline->l_dline = fline->l_dline;
	}
	else {
		/* put modified last line */
		linecopy(genbuf, atchar, tail);
		SavLine(atline, genbuf);
	}
	makedirty(atline);
	if (whatbuf) {
		IFixMarks(startline, startchar, atline, atchar);
	}
	bp.p_line = atline;
	bp.p_char = atchar;
	this_cmd = YANKCMD;
	getDOT();			/* Whatever used to be in linebuf */
	return &bp;
}

/* (from misc.c)
 * [TRH Nov-88] allow argument on Yank:
 * abs(exp)	Number of times to Yank kill buffer. The entire portion of
 *		yanked text becomes the new Region
 * sign(exp)	Controls placement of Point: at the end of the yanked region
 *		if positive, at the beginning if negative
 * [TRH Jun-91] allow repeat count on YankPop (like Yank, except sign).
 */
private void do_yank __(( Line *start ));
private void
do_yank(start)			/* common code for Yank and YankPop */
Line	*start;
{
	register int	num;
	Bufpos		save;
	register Bufpos	*dot = &save;
	register Line	*last;
	register int	llen;

	if ((num = exp) < 0)
		num = -num;

	last = lastline(start);
	llen = length(last);

	DOTsave(dot);
	while (--num >= 0)
		dot = DoYank(start, 0, last, llen, dot->p_line, dot->p_char, curbuf);
	SetDot(dot);

	this_cmd = YANKCMD;	/* even if exp == 0 */
}

DEF_CMD( "yank", Yank, EDIT )
{
	register Line	*start;

	if ((start = *killptr) == NULL)
		complain("[Nothing to yank!]");

	set_mark();
	do_yank(start);
	if (exp < 0)
		PtToMark();
}

DEF_CMD( "yank-pop", YankPop, EDIT )
{
	if (last_cmd != YANKCMD) {
	    /*-	complain("Yank something first!"); -*/
		Yank();
		return;
	}
	/* First delete the region */
    {
	register Buffer	*cb = curbuf;
	register Mark	*mp = CurMark();
	reg_delete(mp->m_line, mp->m_char, cb->b_dot, cb->b_char);
    }
	/* Now must find a recently killed region. */
    {
	register Line	*start;
	register Line	**kp = killptr;
	do {
		if (exp >= 0) {			/* normal: rotate backwards */
			if (kp == &killbuf[0])
				kp += NUMKILLS;
			*--kp;
		} else {
			*kp++;
			if (kp == &killbuf[NUMKILLS])
				kp -= NUMKILLS;
		}
	} while ((start = *kp) == NULL);
	killptr = kp;

	do_yank(start);
    }
}

/* This is an attempt to reduce the amount of memory taken up by each line.
   Without this each malloc of a line uses sizeof (Line) + sizeof(HEADER)
   where line is 3 words and HEADER is 1 word.
   This is going to allocate memory in chucks of CHUNKSIZE * sizeof (Line)
   and divide each chuck into Lines.  A line is free in a chunk when its
   line->l_dline == 0, so freeline sets dline to 0. */

#define CHUNKSIZE	300

struct chunk {
	int	c_nlines;	/* Number of lines in this chunk (so they
				   don't all have to be CHUNKSIZE long). */
	struct chunk
		*c_nextfree;	/* Next chunk of lines */
	Line	c_block[1];	/* rest of lines get appended to chunk */
};

static struct chunk
		*fchunk = NULL;
static Line	*ffline = NULL;	/* First free line */

#define TOTCHUNKSIZE(n)	(sizeof(struct chunk)-sizeof(Line) + (n)*sizeof(Line))

#define FROM_CACHE	0x8000	/* ORed in with c_nlines if block were
				   stolen from the disk cache */

private void freeline __(( Line *_(line) ));
private void
freeline(line)
register Line	*line;
{
	register Line	*head;

	line->l_dline = 0;
	line->l_prev = NULL;
	if (head = ffline)
		head->l_prev = line;
	line->l_next = head;

	ffline = line;
}

void
lfreelist(first)
register Line	*first;
{
	lfreereg(first, (Line *)0);
}

/* Append region from line1 to line2 onto the free list of lines */

void
lfreereg(line1, line2)
register Line	*line1;
Line	*line2;
{
	register Line	*next,
			*last;

	if ((last = line2) != NULL)
		last = last->l_next;

	while (line1 != last) {
		next = line1->l_next;
		freeline(line1);
		line1 = next;
	}
}

#include "temp.h"	/* for definition of `Block' */

private void newchunk __(( void ));
private void
newchunk()
{
	register Line	*newline;
	register struct chunk	*f;
	register int	nlines = CHUNKSIZE;

	if (!(f = (struct chunk *) malloc(TOTCHUNKSIZE(CHUNKSIZE)))) {
		/*
		 * try to steal a block from the cache.
		 * if that doesn't work, try to obtain a smaller chunk.
		 */
		if (f = (struct chunk *) cache_block((Block *)0))
			nlines = (sizeof(Block) - TOTCHUNKSIZE(0)) /
				  sizeof(Line) | FROM_CACHE;
		else do {
			if ((nlines >>= 1) == 0)
				complain("[out of lines] ");
		} while (!(f = (struct chunk *) malloc(TOTCHUNKSIZE(nlines))));
	}
	f->c_nlines = nlines;
	nlines &= ~FROM_CACHE;
	newline = f->c_block;
	do  freeline(newline);  while (++newline, --nlines);
	f->c_nextfree = fchunk;
	fchunk = f;
}

/* New BUFfer LINE */

Line *
nbufline()
{
	register Line	*newline,
			*next;

	if (!(newline = ffline)) {		/* No free list */
		newchunk();
		newline = ffline;
	}
	if (next = newline->l_next)
		next->l_prev = NULL;
	ffline = next;
	return newline;
}

/* This is used to garbage collect the chunks of lines when malloc fails
   and we are NOT looking for a new buffer line.  This goes through each
   chunk, and if every line in a given chunk is not allocated, the entire
   chunk is `free'd by "free()". */

GCchunks()
{
	register Line	*lp;
	register int	i;
	register struct chunk	*cp,
			*prev = NULL,
			*next;
	register int	did_some = 0;

	if (cp = fchunk) do {
		next = cp->c_nextfree;
		lp = cp->c_block, i = cp->c_nlines & ~FROM_CACHE;
		do {
			if (lp->l_dline)
				break;
		} while (++lp, --i);
		if (i) {
			prev = cp;
			continue;
		}
		if (prev)
			prev->c_nextfree = next;
		else
			fchunk = next;
		/*
		 * Remove the free lines, in chunk cp, from the free list
		 * because they are no longer free.
		 */
		lp = cp->c_block, i = cp->c_nlines & ~FROM_CACHE;
		do {
			if (lp->l_prev)
				lp->l_prev->l_next = lp->l_next;
			else
				ffline = lp->l_next;
			if (lp->l_next)
				lp->l_next->l_prev = lp->l_prev;
		} while (++lp, --i);

		if (cp->c_nlines & FROM_CACHE)
			cache_block((Block *) cp);
		else {
			free((void_*) cp);
			did_some++;
		}
	} while (cp = next);

	return did_some;
}

#ifdef LISP

/* Grind S-Expr */

DEF_CMD( "grind-s-expr", GSexpr, EDIT ) _IF(def LISP)
{
	register Buffer	*cb = curbuf;
	Bufpos	dot,
		end;

	if (linebuf[cb->b_char] != '(')
		complain((char *) 0);
	DOTsave(&dot);
	FSexpr();
	DOTsave(&end);
	exp = 1;
	SetDot(&dot);
	while (cb->b_dot != end.p_line) {
		line_move(FORWARD);
		if (!blnkp(linebuf))
			lisp_indent();
	}
 	SetDot(&dot);
}

/* lisp_indent() indents a new line in Lisp Mode, according to where
   the matching close-paren would go if we typed that (sort of). */

#include "table.h"

private const char *init_specials[] = {
	"case",
	"def",
	"dolist",
	"fluid-let",
	"lambda",
	"let",
	"lexpr",
	"macro",
	"named-l",	/* named-let and named-lambda */
	"nlambda",
	"prog",
	"selectq"
};

private Table specials = {
	init_specials,
	sizeof init_specials / sizeof init_specials[0],
	0,
	casencmp,
};

DEF_CMD( "add-lisp-special", AddSpecial, NO ) _IF(def LISP)
{
	add_word(&specials, ask((char *) 0, ProcFmt));
}

private Bufpos *
lisp_indent()
{
	register Buffer	*cb = curbuf;
	register Bufpos	*bp;
	Bufpos		savedot;
	register int	goal;
#define c_char	goal	/* alias! */

	if ((bp = m_paren(')', BACKWARD, MP_CAN_STOP, (Bufpos *)0)) == NULL)
		return NULL;

	/* We want to end up

		(atom atom atom ...
		      ^ here.
	 */

	DOTsave(&savedot);
	SetDot(bp);
	/* we are at the '(' so this is safe. */
	if (linebuf[c_char = cb->b_char + 1] != '(') {
		if (word_in_table(&specials, &linebuf[c_char]))
			c_char++;
		else if (exp = FORWARD, ForWord(),
			 !LookingAt("[ \t]*;\\|[ \t]*$", linebuf, cb->b_char))
			for (c_char = cb->b_char; isspace(linebuf[c_char]); )
				c_char++;
	}
	goal = calc_pos(linebuf, c_char);
	SetDot(&savedot);
	n_indent(goal);

	return bp;
#undef c_char
}
#endif /* LISP */

/*
 * [TRH] Adjust indent level of lines in region.
 * The increment is given by the argument count (which can be negative
 * in order to decrement the indent level).
 * If no argument is given, the increment equals the value of the
 * tabstop appropriate for the current mode. (i.e. c-indent-increment
 * in C mode, tabstop in other modes {{ lisp mode?? }}
 * If the argument is a single minus sign the tabstop value is used
 * as a decrement.
 */
DEF_CMD( "shift-region-left",  RegAdjIndent, EDIT|NEGATE );
DEF_CMD( "shift-region-right", RegAdjIndent, EDIT )
{
	register Buffer	*cb = curbuf;
	Bufpos		r[2];
	int		swapped = CurRegion(r);
	int		incrmt;

	if ((incrmt = exp) == 0)
		return;

	if (exp_p != YES) {
		register int	step = IndIncrmt;

		if (C_like_Mode(cb))
			step = CIndIncrmt;
		if (step <= 0)
			step = tabstop;
		incrmt *= step;
	}
    {
	register  Line	*lp = r[0].p_line,
			*end_lp = r[1].p_line;

	if (r[1].p_char > 0)
		end_lp = end_lp->l_next;
	if (r[0].p_char > 0)
		lp = lp->l_next;
	/*
	 * loop termination guaranteed: r[0].p_line == r[1].p_line
	 * implies r[0].p_char <= r[1].p_char (since we fixorder()ed),
	 * also r[0].p_char > 0 implies r[1].p_char > 0, and still lp == end_lp
	 * after handling the boundary conditions.
	 */
	for (; lp != end_lp; lp = lp->l_next) {
		SetLine(lp);
		if (linebuf[0] == '\0')	/* empty line */
			continue;
		if (linebuf[0] == '#' && BufMajorMode(cb, CMODE))
			continue;	/* don't indent preproc. directives */
		ToIndent();
		AdjWtSpace(calc_pos(linebuf, cb->b_char) + incrmt);
	}
	SetDot(&r[swapped]);
    }
}

/* Tab to next column, as defined by the previous (non-empty) line. */

#ifndef TINY

DEF_INT( "column-tabstop", ColTabstop, V_BASE10 ) _IF(ndef TINY) = 8; _IF(def PRIVATE)

DEF_STR( "column-format", ColFmt, 40, V_REGEXP ) _IF(ndef TINY)_IF(def PRIVATE)  =
    "[^\t ]*[\t ]+";

DEF_CMD( "tab-to-next-column", TabCol, EDIT ) _IF(ndef TINY)
{
	register const char	*lp;
	register Buffer		*cb = curbuf;
	int			num;

	if ((num = exp) <= 0)
		return;

	/* find a non-empty line before current one. */
    {
	register Line	*line = cb->b_dot;

	do {
		if ((line = line->l_prev) == NULL) {
			lp = linebuf;
			break;
		}
	} while (blnkp(lp = lcontents(line)));
    }
	/* tab to next column, as defined by the "column-format" RE pattern. */
    {
	register int	goal = how_far(lp, calc_pos(linebuf, cb->b_char));

	if (goal >= strlen(lp)) {
		lp = linebuf;
		goal = cb->b_char;
	}

	while (--num >= 0 && LookingAt(ColFmt, lp, goal)) {
		goal = REeom;
	}
	++num;

	goal = calc_pos(lp, goal);

#	define tabstop	ColTabstop		/* for TabIncr */
	while (--num >= 0) {
		goal += TabIncr(goal);
	}
#	undef tabstop

	AdjWtSpace(goal);
    }
}
#endif /* !TINY */

/*======================================================================
 * $Log: insert.c,v $
 * Revision 14.32.0.12  1994/06/24  01:03:15  tom
 * (Tab): don't use indent of previous line in Fundamental mode;
 * (Blink): move AUTOSCROLL handling to SitFor().
 *
 * Revision 14.32.0.11  1994/06/13  01:30:24  tom
 * (listput,LineInsert,Insert,DoHash,NewLine,YankPop): code squeeze;
 * (SelfInsert): reformat; ("indentation-increment"): new Variable;
 * (Tab,NewLine,YankPop,RegAdjIndent): use it;
 * (Tab): adjust indent in all modes if bolp -- not just C mode, don't rely
 *  on SelfInsert;
 * (NewLine): move Abbrev expansion to top;
 * (DoYank): IFixMarks only if whatbuf != NULL -- this was potential bug;
 * (RegAdjIndent): use CurRegion().
 *
 * Revision 14.32  1993/06/09  01:02:19  tom
 * (ins_str): `ok_nl' < 0 interprets \n as null characters instead of newline.
 *
 * Revision 14.31  1993/02/18  06:16:22  tom
 * replace spurious DelWtSpace() with del_whitespace(); remove (void) casts;
 * lotsa random optimizations.
 *
 * Revision 14.30  1993/02/06  00:48:32  tom
 * cleanup whitespace; some random optimizations; be more careful to call
 * modify() before actual modification (so buffer remains unchanged if
 * confirmation of modification fails); handle "delete-blank-tail" correctly.
 *
 * Revision 14.28  1992/10/24  01:24:19  tom
 * convert to "port{ansi,defs}.h" conventions.
 *
 * Revision 14.27  1992/09/21  13:16:02  tom
 * add "quote-char" variable.
 *
 * Revision 14.26  1992/08/26  23:56:53  tom
 * make lisp_indent() private; PRIVATE-ized some Variable defs;
 * add RCS directives.
 *
 */
