+/***************************************************************************
+ * This program is Copyright (C) 1986, 1987, 1988 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"
+#include "ctype.h"
+#include "list.h"
+#include "chars.h"
+#include "disp.h"
+
+private int
+ newchunk proto((void));
+private void
+ DoNewline proto((int indentp)),
+ init_specials proto((void));
+
+/* Make a newline after AFTER in buffer BUF, UNLESS after is 0,
+ in which case we insert the newline before after. */
+
+Line *
+listput(buf, after)
+register Buffer *buf;
+register Line *after;
+{
+ register Line *newline = nbufline();
+
+ if (after == 0) { /* Before the first line */
+ newline->l_next = buf->b_first;
+ newline->l_prev = 0;
+ buf->b_first = newline;
+ } else {
+ newline->l_prev = after;
+ 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;
+ if (buf && buf->b_dot == 0)
+ buf->b_dot = newline;
+ return newline;
+}
+
+/* Divide the current line and move the current line to the next one */
+
+void
+LineInsert(num)
+register int num;
+{
+ char newline[LBSIZE];
+ register Line *newdot,
+ *olddot;
+ int oldchar;
+
+ olddot = curline;
+ oldchar = curchar;
+
+ newdot = curline;
+ while (--num >= 0) {
+ newdot = listput(curbuf, newdot);
+ SavLine(newdot, NullStr);
+ }
+
+ modify();
+ if (curchar != 0) {
+ strcpy(newline, &linebuf[curchar]);
+ linebuf[curchar] = '\0'; /* Shorten this line */
+ SavLine(curline, linebuf);
+ strcpy(linebuf, newline);
+ } else { /* Redisplay optimization */
+ newdot->l_dline = curline->l_dline;
+ SavLine(curline, NullStr);
+ }
+
+ makedirty(curline);
+ curline = newdot;
+ curchar = 0;
+ makedirty(curline);
+ IFixMarks(olddot, oldchar, curline, curchar);
+}
+
+/* Inserts tabs and spaces to move the cursor to column GOAL. It
+ Uses the most optimal number of tabs and spaces no matter what
+ was there before hand. */
+
+void
+n_indent(goal)
+register int goal;
+{
+ int dotcol,
+ incrmt;
+
+ DelWtSpace();
+ dotcol = calc_pos(linebuf, curchar);
+
+ for (;;) {
+ incrmt = (tabstop - (dotcol % tabstop));
+ if (dotcol + incrmt > goal)
+ break;
+ insert_c('\t', 1);
+ dotcol += incrmt;
+ }
+ if (dotcol != goal)
+ insert_c(' ', (goal - dotcol));
+}
+
+#ifdef ABBREV
+void
+MaybeAbbrevExpand()
+{
+ if (MinorMode(Abbrev) && !ismword(LastKeyStruck) &&
+ !bolp() && ismword(linebuf[curchar - 1]))
+ AbbrevExpand();
+}
+#endif
+
+void
+SelfInsert()
+{
+#ifdef ABBREV
+ MaybeAbbrevExpand();
+#endif
+ if (LastKeyStruck != CTL('J') && MinorMode(OverWrite)) {
+ register int num,
+ i;
+
+ for (i = 0, num = arg_value(); i < num; i++) {
+ int pos = calc_pos(linebuf, curchar);
+
+ if (!eolp()) {
+ if (linebuf[curchar] == '\t') {
+ if ((pos + 1) == ((pos + tabstop) - (pos % tabstop)))
+ del_char(FORWARD, 1, NO);
+ } else
+ del_char(FORWARD, 1, NO);
+ }
+ insert_c(LastKeyStruck, 1);
+ }
+ } else
+ Insert(LastKeyStruck);
+
+ if (MinorMode(Fill) && (curchar >= RMargin ||
+ (calc_pos(linebuf, curchar) >= RMargin))) {
+ int margin;
+ Bufpos save;
+
+ if (MinorMode(Indent)) {
+ DOTsave(&save);
+ ToIndent();
+ margin = calc_pos(linebuf, curchar);
+ SetDot(&save);
+ } else
+ margin = LMargin;
+ DoJustify(curline, 0, curline,
+ curchar + (int)strlen(&linebuf[curchar]), 1, margin);
+ }
+}
+
+void
+Insert(c)
+int c;
+{
+ if (c == CTL('J'))
+ LineInsert(arg_value());
+ else
+ insert_c(c, arg_value());
+}
+
+/* insert character C N times at point */
+void
+insert_c(c, n)
+int c,
+ n;
+{
+ if (n <= 0)
+ return;
+ modify();
+ makedirty(curline);
+ ins_c(c, linebuf, curchar, n, LBSIZE);
+ IFixMarks(curline, curchar, curline, curchar + n);
+ curchar += n;
+}
+
+/* Tab in to the right place for C mode */
+
+void
+Tab()
+{
+#ifdef LISP
+ if (MajorMode(LISPMODE) && (bolp() || !eolp())) {
+ int dotchar = curchar;
+ Mark *m = 0;
+
+ ToIndent();
+ if (dotchar > curchar)
+ m = MakeMark(curline, dotchar, M_FLOATER);
+ (void) lisp_indent();
+ if (m) {
+ ToMark(m);
+ DelMark(m);
+ } else
+ ToIndent();
+ return;
+ }
+#endif
+ if (MajorMode(CMODE)) {
+ if (within_indent())
+ (void) c_indent(NO);
+ else {
+ int curpos,
+ tabbed_pos;
+
+ skip_wht_space();
+ curpos = calc_pos(linebuf, curchar);
+ tabbed_pos = curpos + (CIndIncrmt - (curpos % CIndIncrmt));
+ n_indent(tabbed_pos);
+ }
+ } else
+ SelfInsert();
+}
+
+void
+QuotChar()
+{
+ int c,
+ slow = NO;
+
+ c = waitchar(&slow);
+ if (c != CTL('@'))
+ Insert(c);
+}
+
+/* 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. */
+
+int PDelay = 5, /* 1/2 a second */
+ CIndIncrmt = 8;
+
+void
+DoParen()
+{
+ Bufpos *bp;
+ int tried = NO,
+ nx,
+ c = LastKeyStruck;
+
+ if (!isclosep(c)) {
+ SelfInsert();
+ return;
+ }
+
+ if (MajorMode(CMODE) && c == '}' && within_indent()) {
+ bp = c_indent(YES);
+ tried = TRUE;
+ }
+#ifdef LISP
+ if (MajorMode(LISPMODE) && c == ')' && blnkp(linebuf)) {
+ bp = lisp_indent();
+ tried = TRUE;
+ }
+#endif
+ SelfInsert();
+#ifdef MAC
+ if (MinorMode(ShowMatch) && !in_macro()) {
+#else
+ if (MinorMode(ShowMatch) && !charp() && !in_macro()) {
+#endif
+ b_char(1); /* Back onto the ')' */
+ if (!tried)
+ bp = m_paren(c, BACKWARD, NO, YES);
+ f_char(1);
+ if (bp != 0) {
+ nx = in_window(curwind, bp->p_line);
+ if (nx != -1) { /* is visible */
+ Bufpos b;
+
+ DOTsave(&b);
+ SetDot(bp);
+ SitFor(PDelay);
+ SetDot(&b);
+ } else
+ s_mess("%s", lcontents(bp->p_line));
+ }
+ mp_error(); /* display error message */
+ }
+}
+
+void
+LineAI()
+{
+ DoNewline(TRUE);
+}
+
+void
+Newline()
+{
+ DoNewline(MinorMode(Indent));
+}
+
+private void
+DoNewline(indentp)
+int indentp;
+{
+ Bufpos save;
+ int indent;
+
+ /* first we calculate the indent of the current line */
+ DOTsave(&save);
+ ToIndent();
+ indent = calc_pos(linebuf, curchar);
+ SetDot(&save);
+
+#ifdef ABBREV
+ MaybeAbbrevExpand();
+#endif
+#ifdef LISP
+ if (MajorMode(LISPMODE))
+ DelWtSpace();
+ else
+#endif
+ if (indentp || blnkp(linebuf))
+ DelWtSpace();
+
+ /* If there is more than 2 blank lines in a row then don't make
+ a newline, just move down one. */
+ if (arg_value() == 1 && eolp() && TwoBlank())
+ SetLine(curline->l_next);
+ else
+ LineInsert(arg_value());
+
+ if (indentp)
+#ifdef LISP
+ if (MajorMode(LISPMODE))
+ (void) lisp_indent();
+ else
+#endif
+ {
+ Bol();
+ n_indent((LMargin == 0) ? indent : LMargin);
+ }
+}
+
+void
+ins_str(str, ok_nl)
+register char *str;
+int ok_nl;
+{
+ register char c;
+ Bufpos save;
+ int llen;
+
+ if (*str == 0)
+ return; /* ain't nothing to insert! */
+ DOTsave(&save);
+ llen = strlen(linebuf);
+ while ((c = *str++) != '\0') {
+ if (c == '\n' || (ok_nl && llen >= LBSIZE - 2)) {
+ IFixMarks(save.p_line, save.p_char, curline, curchar);
+ modify();
+ makedirty(curline);
+ LineInsert(1);
+ DOTsave(&save);
+ llen = strlen(linebuf);
+ }
+ if (c != '\n') {
+ ins_c(c, linebuf, curchar++, 1, LBSIZE);
+ llen += 1;
+ }
+ }
+ IFixMarks(save.p_line, save.p_char, curline, curchar);
+ modify();
+ makedirty(curline);
+}
+
+void
+open_lines(n)
+int n;
+{
+ Bufpos dot;
+
+ DOTsave(&dot);
+ LineInsert(n); /* Open the lines... */
+ SetDot(&dot);
+}
+
+void
+OpenLine()
+{
+ open_lines(arg_value());
+}
+
+/* Take the region FLINE/FCHAR to TLINE/TCHAR and insert it at
+ ATLINE/ATCHAR in WHATBUF. */
+
+Bufpos *
+DoYank(fline, fchar, tline, tchar, atline, atchar, whatbuf)
+Line *fline,
+ *tline,
+ *atline;
+int fchar,
+ tchar,
+ atchar;
+Buffer *whatbuf;
+{
+ register Line *newline;
+ static Bufpos bp;
+ char save[LBSIZE],
+ buf[LBSIZE];
+ Line *startline = atline;
+ int startchar = atchar;
+
+ lsave();
+ if (whatbuf)
+ modify();
+ (void) ltobuf(atline, genbuf);
+ strcpy(save, &genbuf[atchar]);
+
+ (void) ltobuf(fline, buf);
+ if (fline == tline)
+ buf[tchar] = '\0';
+
+ linecopy(genbuf, atchar, &buf[fchar]);
+ atline->l_dline = putline(genbuf);
+ makedirty(atline);
+
+ fline = fline->l_next;
+ while (fline != tline->l_next) {
+ newline = listput(whatbuf, atline);
+ newline->l_dline = fline->l_dline;
+ makedirty(newline);
+ fline = fline->l_next;
+ atline = newline;
+ atchar = 0;
+ }
+
+ getline(atline->l_dline, genbuf);
+ atchar += tchar;
+ linecopy(genbuf, atchar, save);
+ atline->l_dline = putline(genbuf);
+ makedirty(atline);
+ 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;
+}
+
+void
+YankPop()
+{
+ Line *line,
+ *last;
+ Mark *mp = CurMark();
+ Bufpos *dot;
+ int dir = -1; /* Direction to rotate the ring */
+
+ if (last_cmd != YANKCMD)
+ complain("Yank something first!");
+
+ lfreelist(reg_delete(mp->m_line, mp->m_char, curline, curchar));
+
+ /* Now must find a recently killed region. */
+
+ if (arg_value() < 0)
+ dir = 1;
+
+ killptr += dir;
+ for (;;) {
+ if (killptr < 0)
+ killptr = NUMKILLS - 1;
+ else if (killptr >= NUMKILLS)
+ killptr = 0;
+ if (killbuf[killptr])
+ break;
+ killptr += dir;
+ }
+
+ this_cmd = YANKCMD;
+
+ line = killbuf[killptr];
+ last = lastline(line);
+ dot = DoYank(line, 0, last, length(last), curline, curchar, curbuf);
+ MarkSet(CurMark(), curline, curchar);
+ SetDot(dot);
+}
+
+/* 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). */
+ Line *c_block; /* Chunk of memory */
+ struct chunk *c_nextfree; /* Next chunk of lines */
+};
+
+private struct chunk *fchunk = 0;
+private Line *ffline = 0; /* First free line */
+
+void
+freeline(line)
+register Line *line;
+{
+ line->l_dline = 0;
+ line->l_next = ffline;
+ if (ffline)
+ ffline->l_prev = line;
+ line->l_prev = 0;
+ ffline = line;
+}
+
+void
+lfreelist(first)
+register Line *first;
+{
+ if (first)
+ lfreereg(first, lastline(first));
+}
+
+/* Append region from line1 to line2 onto the free list of lines */
+
+void
+lfreereg(line1, line2)
+register Line *line1,
+ *line2;
+{
+ register Line *next,
+ *last = line2->l_next;
+
+ while (line1 != last) {
+ next = line1->l_next;
+ freeline(line1);
+ line1 = next;
+ }
+}
+
+private int
+newchunk()
+{
+ register Line *newline;
+ register int i;
+ struct chunk *f;
+ int nlines = CHUNKSIZE;
+
+ f = (struct chunk *) emalloc(sizeof (struct chunk));
+ if (f == 0)
+ return 0;
+
+ if ((f->c_block = (Line *) malloc((unsigned) (sizeof (Line) * nlines))) == 0) {
+ while (nlines > 0) {
+ f->c_block = (Line *) malloc((unsigned) (sizeof (Line) * nlines));
+ if (f->c_block != 0)
+ break;
+ nlines /= 2;
+ }
+ }
+
+ if (nlines <= 0)
+ return 0;
+
+ f->c_nlines = nlines;
+ for (i = 0, newline = f->c_block; i < nlines; newline++, i++)
+ freeline(newline);
+ f->c_nextfree = fchunk;
+ fchunk = f;
+ return 1;
+}
+
+/* New BUFfer LINE */
+
+Line *
+nbufline()
+{
+ register Line *newline;
+
+ if (ffline == 0) /* No free list */
+ if (newchunk() == 0)
+ complain("[Out of lines] ");
+ newline = ffline;
+ ffline = ffline->l_next;
+ if (ffline)
+ ffline->l_prev = 0;
+ return newline;
+}
+
+/* Remove the free lines, in chunk c, from the free list because they are
+ no longer free. */
+
+private void
+remfreelines(c)
+register struct chunk *c;
+{
+ register Line *lp;
+ register int i;
+
+ for (lp = c->c_block, i = 0; i < c->c_nlines; i++, lp++) {
+ 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;
+ }
+}
+
+/* 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()". */
+
+void
+GCchunks()
+{
+ register struct chunk *cp;
+ struct chunk *prev = 0,
+ *next = 0;
+ register int i;
+ register Line *newline;
+
+ for (cp = fchunk; cp != 0; cp = next) {
+ for (i = 0, newline = cp->c_block; i < cp->c_nlines; newline++, i++)
+ if (newline->l_dline != 0)
+ break;
+
+ next = cp->c_nextfree;
+
+ if (i == cp->c_nlines) { /* Unlink it!!! */
+ if (prev)
+ prev->c_nextfree = cp->c_nextfree;
+ else
+ fchunk = cp->c_nextfree;
+ remfreelines(cp);
+ free((char *) cp->c_block);
+ free((char *) cp);
+ } else
+ prev = cp;
+ }
+}
+
+#ifdef LISP
+
+#include "re.h"
+
+/* Grind S-Expr */
+
+void
+GSexpr()
+{
+ Bufpos dot,
+ end;
+
+ if (linebuf[curchar] != '(')
+ complain((char *) 0);
+ DOTsave(&dot);
+ FSexpr();
+ DOTsave(&end);
+ SetDot(&dot);
+ for (;;) {
+ if (curline == end.p_line)
+ break;
+ line_move(FORWARD, 1, NO);
+ if (!blnkp(linebuf))
+ (void) 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). */
+
+private List *specials = NIL;
+
+private void
+init_specials()
+{
+ static char *const words[] = {
+ "case",
+ "def",
+ "dolist",
+ "fluid-let",
+ "lambda",
+ "let",
+ "lexpr",
+ "macro",
+ "named-l", /* named-let and named-lambda */
+ "nlambda",
+ "prog",
+ "selectq",
+ 0
+ };
+ char *const *wordp = words;
+
+ while (*wordp)
+ list_push(&specials, (Element *) *wordp++);
+}
+
+void
+AddSpecial()
+{
+ char *word;
+ register List *lp;
+
+ if (specials == NIL)
+ init_specials();
+ word = ask((char *) 0, ProcFmt);
+ for (lp = specials; lp != NIL; lp = list_next(lp))
+ if (strcmp((char *) list_data(lp), word) == 0)
+ return; /* already in list */
+ (void) list_push(&specials, (Element *) copystr(word));
+}
+
+Bufpos *
+lisp_indent()
+{
+ Bufpos *bp,
+ savedot;
+ int goal;
+
+ bp = m_paren(')', BACKWARD, NO, YES);
+
+ if (bp == NULL)
+ return NULL;
+
+ /* We want to end up
+
+ (atom atom atom ...
+ ^ here.
+ */
+
+ DOTsave(&savedot);
+ SetDot(bp);
+ f_char(1);
+ if (linebuf[curchar] != '(') {
+ register List *lp;
+
+ if (specials == NIL)
+ init_specials();
+ for (lp = specials; lp != NIL; lp = list_next(lp))
+ if (casencmp((char *) list_data(lp),
+ &linebuf[curchar],
+ strlen((char *) list_data(lp))) == 0)
+ break;
+ if (lp == NIL) { /* not special */
+ int c_char = curchar;
+
+ WITH_TABLE(curbuf->b_major)
+ f_word(1);
+ END_TABLE();
+ if (LookingAt("[ \t]*;\\|[ \t]*$", linebuf, curchar))
+ curchar = c_char;
+ else while (linebuf[curchar] == ' ')
+ curchar += 1;
+ } else
+ curchar += 1;
+ }
+ goal = calc_pos(linebuf, curchar);
+ SetDot(&savedot);
+ Bol();
+ n_indent(goal);
+
+ return bp;
+}
+#endif /* LISP */