#include <stdio.h>
#include <ctype.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "ztypes.h"
#include "xio.h"

static char *charbuf;
static long numchars;
static long char_size;

typedef struct style_t {
    int attr; /* flags are REVERSE, BOLD, EMPHASIS, FIXED_FONT */
    long pos; /* position this style starts at */
} style;

static style *stylelist;
static long numstyles;
static long styles_size;

typedef struct word_t {
    long pos, len;
    long width; /* in pixels */
    int attr;

    long *letterpos; /* if not NULL, an array[0..len] of pixel offsets from wordpos; */
} word;

#define lineflag_Wrapped (1) /* line is a wrap or split from previous line */
#define lineflag_Extra (2) /* the magic extra line on the end */

typedef struct line_t {
    long pos; /* line starts here */
    long posend; /* number of chars. May not be exactly to start of next line, because it won't include the newline or space that ends the line. */
    word *wordlist;
    long numwords;
    int flags;
} lline;

static lline *linelist;
static long numlines;
static long lines_size;

static lline *tmplinelist;
static long tmplines_size;

static long scrollpos; /* character position at top of screen */
static long scrollline; /* number of line at top of screen, after xtext_layout() */
static long lastlineseen; /* last line read before more stuff was output. (-1) to indicate all lines read. */
static long dotpos, dotlen; /* dotpos is in [0..numchars] */
static long lastdotpos = (-1), lastdotlen = 0; /* cached values -- fiddled inside xtext_layout() */

static long dirtybeg, dirtyend; /* mark the limits of what needs to be laid out, [) format */
static long dirtydelta; /* how much the dirty area has grown (or shrunk) */
static long startlay; /* pos of the char that starts the first laid-out line. */

static int textwin_x, textwin_y, textwin_w, textwin_h;
static int scrollwin_x, scrollwin_y, scrollwin_w, scrollwin_h;
static int scrollel_top, scrollel_bot;

typedef struct histunit {
    char *str;
    int len;
} histunit;
static int historynum, historypos;
static histunit *history;

/* these are for xtext editing */
static int buflen;
static char *buffer;
static int *readpos;
static long inputfence;
static int *killflag;
static int originalattr;

#define collapse_dot()  (dotpos += dotlen, dotlen = 0)
#define SIDEMARGIN (4)
#define BARENDHEIGHT (12)
#define BARWIDTH (17)
#define BAREXTRA (4)

static XPoint polydot[3];
static long linesperpage;

#ifdef __STDC__
static void redrawtext(long beg, long num, int clearnum);
static void flip_selection(long dpos, long dlen);
static void find_loc_by_pos(long pos, int *xposret, int *yposret);
static long find_pos_by_loc(int xpos, int ypos);
static long find_line_by_pos(long pos, long guessline);
static void measure_word(lline *curline, word *curword);
static void adjust_elevator();
void xtext_delete_start(long num);
#else
static void redrawtext();
static void flip_selection();
static void find_loc_by_pos();
static long find_pos_by_loc();
static long find_line_by_pos();
static void measure_word();
static void adjust_elevator();
void xtext_delete_start();
#endif


#ifdef __STDC__
void xtext_init()
#else
void xtext_init()
#endif
{
    char_size = 256;
    charbuf = (char *)malloc(sizeof(char) * char_size);
    numchars = 0;

    styles_size = 8;
    stylelist = (style *)malloc(sizeof(style) * styles_size);
    numstyles = 1;
    stylelist[0].pos = 0;
    stylelist[0].attr = 0; /* NORMAL style */

    lines_size = 8;
    linelist = (lline *)malloc(sizeof(lline) * lines_size);
    numlines = 0;

    tmplines_size = 8;
    tmplinelist = (lline *)malloc(sizeof(lline) * tmplines_size);

    historynum = 0;
    history = (histunit *)malloc(prefs.historylength * sizeof(histunit));

    scrollpos = 0;
    scrollline = 0;
    startlay = 0; /* not yet used */

    dirtybeg = 0;
    dirtyend = 0;
    dirtydelta = 0;

    dotpos = 0;
    dotlen = 0;

    polydot[0].x = 0;
    polydot[0].y = 0;
    polydot[1].x = SIDEMARGIN;
    polydot[1].y = 5;
    polydot[2].x = -2*SIDEMARGIN;
    polydot[2].y = 0;

    scrollel_top = (-1); /* indicate elevator is not there */
    scrollel_bot = (-1);

    lastlineseen = 0;
}

#ifdef __STDC__
void xtext_clear_window()
#else
void xtext_clear_window()
#endif
{
    xtext_delete_start(numlines);
}

#ifdef __STDC__
void xtext_resize(int xpos, int ypos, int width, int height)
#else
void xtext_resize(xpos, ypos, width, height)
int xpos;
int ypos;
int width;
int height;
#endif
{
    scrollwin_x = xpos;
    scrollwin_w = BARWIDTH;
    scrollwin_y = ypos+BARENDHEIGHT;
    scrollwin_h = height-2*BARENDHEIGHT;
    textwin_x = xpos+scrollwin_w+SIDEMARGIN+prefs.marginx;
    textwin_y = ypos;
    textwin_w = width-2*SIDEMARGIN-scrollwin_w-2*prefs.marginx;
    textwin_h = height;

    dirtybeg = 0;
    dirtyend = numchars;
    dirtydelta = 0;

    linesperpage = height / lineheight;

    xtext_layout();
}

#ifdef __STDC__
void xtext_redraw()
#else
void xtext_redraw()
#endif
{
    XPoint poly[3];

    /* this assumes that an exposure event will not come in between a data update and an xtext_layout call. (unless the exposure event itself forces xtext_layout first?) */
    /*flip_selection(dotpos, dotlen);*/
    redrawtext(0, -1, -1);
    flip_selection(dotpos, dotlen);

    poly[0].x = scrollwin_x + scrollwin_w/2;
    poly[0].y = textwin_y + 1;
    poly[1].x = scrollwin_x + 0;
    poly[1].y = scrollwin_y - 2;
    poly[2].x = scrollwin_x + scrollwin_w - 1;
    poly[2].y = scrollwin_y - 2;
    XFillPolygon(xiodpy, xiowin, gcgrey, poly, 3, Convex, CoordModeOrigin);

    poly[0].x = scrollwin_x + scrollwin_w/2;
    poly[0].y = textwin_y + textwin_h - 1;
    poly[1].x = scrollwin_x + 0;
    poly[1].y = scrollwin_y + scrollwin_h + 2;
    poly[2].x = scrollwin_x + scrollwin_w - 1;
    poly[2].y = scrollwin_y + scrollwin_h + 2;
    XFillPolygon(xiodpy, xiowin, gcgrey, poly, 3, Convex, CoordModeOrigin);

    XDrawLine(xiodpy, xiowin, gcblack, scrollwin_x+scrollwin_w, textwin_y, scrollwin_x+scrollwin_w, textwin_h);
    scrollel_top = (-1);
    scrollel_bot = (-1);
    XFillRectangle(xiodpy, xiowin, gcgrey, scrollwin_x, scrollwin_y, scrollwin_w, scrollwin_h);
    adjust_elevator();
}

#ifdef __STDC__
static long back_to_white(long pos)
#else
static long back_to_white(pos)
long pos;
#endif
{
    while (pos > 0 && charbuf[pos-1] != ' ' && charbuf[pos-1] != '\n')
	pos--;
    return pos;
}

#ifdef __STDC__
static long fore_to_white(long pos)
#else
static long fore_to_white(pos)
long pos;
#endif
{
    while (pos < numchars && charbuf[pos] != ' ' && charbuf[pos] != '\n')
	pos++;
    return pos;
}

#ifdef __STDC__
static long back_to_nonwhite(long pos)
#else
static long back_to_nonwhite(pos)
long pos;
#endif
{
    while (pos > 0 && (charbuf[pos-1] == ' ' || charbuf[pos-1] == '\n'))
	pos--;
    return pos;
}

#ifdef __STDC__
static long fore_to_nonwhite(long pos)
#else
static long fore_to_nonwhite(pos)
long pos;
#endif
{
    while (pos < numchars && (charbuf[pos] == ' ' || charbuf[pos] == '\n'))
	pos++;
    return pos;
}

/* Coordinates are in screen lines. If num < 0, go to the end. clearnum is the number of lines to clear (may be to a notional line); if 0, don't clear at all; if -1, clear whole window. */
#ifdef __STDC__
static void redrawtext(long beg, long num, int clearnum)
#else
static void redrawtext(beg, num, clearnum)
long beg;
long num;
int clearnum;
#endif
{
    long lx, wx, end, clearend;
    int ypos, ypos2, xpos;
    lline *thisline;
    word *thisword;

    if (num<0)
	end = numlines;
    else {
	end = beg+num;
	if (end > numlines)
	    end = numlines;
    }

    if (beg < scrollline)
	beg = scrollline;

    if (clearnum > 0) {
	clearend = beg+clearnum;
	ypos = textwin_y + (beg-scrollline) * lineheight;
	ypos2 = textwin_y + (clearend-scrollline) * lineheight;
	if (ypos2 > textwin_y+textwin_h) {
	    ypos2 = textwin_y+textwin_h;
	}
	if (ypos != ypos2)
	    XClearArea(xiodpy, xiowin, textwin_x-SIDEMARGIN, ypos, textwin_w+2*SIDEMARGIN, ypos2-ypos, FALSE);
    }
    else if (clearnum < 0) {
	ypos = textwin_y + (beg-scrollline) * lineheight;
	ypos2 = textwin_y+textwin_h;
	if (ypos != ypos2)
	    XClearArea(xiodpy, xiowin, textwin_x-SIDEMARGIN, ypos, textwin_w+2*SIDEMARGIN, ypos2-ypos, FALSE);
    }

    for (lx=beg; lx<end; lx++) {
	thisline = (&linelist[lx]);
	ypos = textwin_y + (lx-scrollline) * lineheight;
	if (ypos + lineheight >= textwin_y + textwin_h)
	    break;
	xpos = textwin_x;
	for (wx=0; wx<thisline->numwords; wx++) {
	    thisword = thisline->wordlist+wx;
	    if (thisword->attr & REVERSE)
		XDrawImageString(xiodpy, xiowin, gcfont[thisword->attr], xpos, ypos+lineheightoff, charbuf+thisline->pos+thisword->pos, thisword->len);
	    else
		XDrawString(xiodpy, xiowin, gcfont[thisword->attr], xpos, ypos+lineheightoff, charbuf+thisline->pos+thisword->pos, thisword->len);
	    xpos += thisword->width;
	}
    }
}

#ifdef __STDC__
static void adjust_elevator()
#else
static void adjust_elevator()
#endif
{
    long newtop, newbot;
    int barheight = (scrollwin_h-2*BAREXTRA);

    if (numlines) {
	newtop = ((barheight*scrollline) / numlines) + BAREXTRA;
	newbot = ((barheight*(scrollline+linesperpage)) / numlines) + BAREXTRA;
	if (newtop < BAREXTRA)
	    newtop = BAREXTRA;
	if (newbot >= scrollwin_h-BAREXTRA)
	    newbot = scrollwin_h-BAREXTRA;
    }
    else {
	newtop = BAREXTRA;
	newbot = scrollwin_h-BAREXTRA;
    }

    if (newtop == scrollel_top && newbot==scrollel_bot)
	return;

    if (scrollel_top != (-1)
	&& (scrollel_top >= newbot || newtop >= scrollel_bot)) {
	/* erase old completely */
	XFillRectangle(xiodpy, xiowin, gcgrey, scrollwin_x, scrollwin_y+scrollel_top, scrollwin_w, scrollel_bot-scrollel_top);
	scrollel_top = (-1);
    }

    if (scrollel_top == (-1)) {
	/* redraw new completely */
	XDrawRectangle(xiodpy, xiowin, gcblack, scrollwin_x, scrollwin_y+newtop, scrollwin_w-1, (newbot-newtop)-1);
	XFillRectangle(xiodpy, xiowin, gcwhite, scrollwin_x+1, scrollwin_y+newtop+1, scrollwin_w-2, (newbot-newtop)-2);
	scrollel_top = newtop;
	scrollel_bot = newbot;
	return;
    }

    /* ok, the old and new overlap */
    if (newtop < scrollel_top) {
	XFillRectangle(xiodpy, xiowin, gcwhite, scrollwin_x+1, scrollwin_y+newtop+1, scrollwin_w-2, scrollel_top-newtop);
    }
    else if (newtop > scrollel_top) {
	XFillRectangle(xiodpy, xiowin, gcgrey, scrollwin_x, scrollwin_y+scrollel_top, scrollwin_w, newtop-scrollel_top);
    }

    if (newbot > scrollel_bot) {
	XFillRectangle(xiodpy, xiowin, gcwhite, scrollwin_x+1, scrollwin_y+scrollel_bot-1, scrollwin_w-2, newbot-scrollel_bot);
    }
    else if (newbot < scrollel_bot) {
	XFillRectangle(xiodpy, xiowin, gcgrey, scrollwin_x, scrollwin_y+newbot, scrollwin_w, scrollel_bot-newbot);
    }

    XDrawRectangle(xiodpy, xiowin, gcblack, scrollwin_x, scrollwin_y+newtop, scrollwin_w-1, (newbot-newtop)-1);
    scrollel_top = newtop;
    scrollel_bot = newbot;
}

#ifdef __STDC__
static void scroll_to(long newscrollline)
#else
static void scroll_to(newscrollline)
long newscrollline;
#endif
{
    long oldscrollline;

    if (newscrollline > numlines-2)
	newscrollline = numlines-2;
    if (newscrollline < 0)
	newscrollline = 0;

    scrollpos = linelist[newscrollline].pos;
    if (scrollline != newscrollline) {
	oldscrollline = scrollline;
	if (!xiobackstore
	    || oldscrollline + linesperpage <= newscrollline
	    || newscrollline + linesperpage <= oldscrollline) {
	    scrollline = newscrollline;
	    redrawtext(scrollline, -1, -1);
	    flip_selection(dotpos, dotlen);
	}
	else {
	    int ypos1, ypos2, yhgt;
	    flip_selection(dotpos, dotlen);
	    scrollline = newscrollline;
	    if (oldscrollline < newscrollline) {
		/* scroll down -- things move up */
		ypos1 = textwin_y + (newscrollline-oldscrollline) * lineheight;
		ypos2 = textwin_y + (0) * lineheight;
		yhgt = (linesperpage-(newscrollline-oldscrollline)) * lineheight;
		XCopyArea(xiodpy, xiowin, xiowin, gcblack, textwin_x-SIDEMARGIN, ypos1, textwin_w+2*SIDEMARGIN, yhgt, textwin_x-SIDEMARGIN, ypos2);
		redrawtext(linesperpage + oldscrollline, (newscrollline-oldscrollline), (newscrollline-oldscrollline));
	    }
	    else {
		/* scroll up -- things move down */
		ypos2 = textwin_y + (oldscrollline-newscrollline) * lineheight;
		ypos1 = textwin_y + (0) * lineheight;
		yhgt = (linesperpage-(oldscrollline-newscrollline)) * lineheight;
		XCopyArea(xiodpy, xiowin, xiowin, gcblack, textwin_x-SIDEMARGIN, ypos1, textwin_w+2*SIDEMARGIN, yhgt, textwin_x-SIDEMARGIN, ypos2);
		redrawtext(newscrollline, (oldscrollline-newscrollline), (oldscrollline-newscrollline));
	    }
	    flip_selection(dotpos, dotlen);
	}
	adjust_elevator();
    }
}

#ifdef __STDC__
static void refiddle_selection(long oldpos, long oldlen, long newpos, long newlen)
#else
static void refiddle_selection(oldpos, oldlen, newpos, newlen)
long oldpos;
long oldlen;
long newpos;
long newlen;
#endif
{
    if (oldlen==0 || newlen==0 || oldpos<0 || newpos<0) {
	flip_selection(oldpos, oldlen);
	flip_selection(newpos, newlen);
	return;
    }

    if (oldpos == newpos) {
	/* start at same place */
	if (oldlen < newlen) {
	    flip_selection(oldpos+oldlen, newlen-oldlen);
	}
	else if (newlen < oldlen) {
	    flip_selection(oldpos+newlen, oldlen-newlen);
	}
	return;
    }
    if (oldpos+oldlen == newpos+newlen) {
	/* end at same place */
	if (oldpos < newpos) {
	    flip_selection(oldpos, newpos-oldpos);
	}
	else if (newpos < oldpos) {
	    flip_selection(newpos, oldpos-newpos);
	}
	return;
    }

    flip_selection(oldpos, oldlen);
    flip_selection(newpos, newlen);
}

#ifdef __STDC__
static void flip_selection(long dpos, long dlen)
#else
static void flip_selection(dpos, dlen)
long dpos;
long dlen;
#endif
{
    int xpos, ypos;
    int xpos2, ypos2;
    long ybody, ybody2;

    if (dpos < 0) {
	return; /* dot hidden */
    }

    if (dlen==0) {
	find_loc_by_pos(dpos, &xpos, &ypos);
	if (ypos < 0 || ypos+lineheight >= textwin_h) {
	    return;
	}
	polydot[0].x = textwin_x + xpos;
	polydot[0].y = textwin_y + ypos + lineheightoff;
	XFillPolygon(xiodpy, xiowin, gcflip, polydot, 3, Convex, CoordModePrevious);
    }
    else {
	find_loc_by_pos(dpos, &xpos, &ypos);
	find_loc_by_pos(dpos+dlen, &xpos2, &ypos2);
	if (ypos==ypos2) {
	    /* within one line */
	    if (xpos!=xpos2 && ypos>=0 && ypos+lineheight<textwin_h) {
		XFillRectangle(xiodpy, xiowin, gcflip, xpos+textwin_x, ypos+textwin_y, xpos2-xpos, lineheight);
	    }
	}
	else {
	    if (xpos < textwin_w && ypos>=0 && ypos+lineheight<textwin_h) {
		/* first partial line */
		XFillRectangle(xiodpy, xiowin, gcflip, xpos+textwin_x, ypos+textwin_y, textwin_w-xpos, lineheight);
	    }
	    ybody = ypos+lineheight;
	    ybody2 = ypos2;
	    if (ybody < ybody2 && ybody2>=0 && ybody+lineheight<textwin_h) {
		if (ybody < 0)
		    ybody = 0;
		if (ybody2+lineheight >= textwin_h)
		    ybody2 = textwin_h;
		/* main body */
		XFillRectangle(xiodpy, xiowin, gcflip, textwin_x, ybody+textwin_y, textwin_w, ybody2-ybody);
	    }
	    if (xpos2 && ypos2>=0 && ypos2+lineheight<textwin_h) {
		/* last partial line */
		XFillRectangle(xiodpy, xiowin, gcflip, textwin_x, ypos2+textwin_y, xpos2, lineheight);
	    }
	}
    }
}

/* push lines from tmplinelist[0..newnum) in place of linelist[oldbeg..oldend) */
#ifdef __STDC__
static void slapover(long newnum, long oldbeg, long oldend)
#else
static void slapover(newnum, oldbeg, oldend)
long newnum;
long oldbeg;
long oldend;
#endif
{
    long wx, lx;
    long newnumlines;

    newnumlines = numlines-(oldend-oldbeg)+newnum;
    if (newnumlines >= lines_size) {
	while (newnumlines >= lines_size)
	    lines_size *= 2;
	linelist = (lline *)realloc(linelist, sizeof(lline) * lines_size);
    }

    /* clobber old */
    for (lx=oldbeg; lx<oldend; lx++) {
	word *thisword;
	/* --- finalize word structure --- */
	for (wx=0, thisword=linelist[lx].wordlist;
	     wx<linelist[lx].numwords;
	     wx++, thisword++) {
	    if (thisword->letterpos) {
		free(thisword->letterpos);
	    }
	}
	free(linelist[lx].wordlist);
	linelist[lx].wordlist = NULL;
    }

    if (oldend < numlines && newnumlines != numlines) {
	memmove(&linelist[oldend+(newnumlines-numlines)],
		&linelist[oldend],
		sizeof(lline) * (numlines-oldend));
    }
    /* ### adjust scrollline by difference too? */
    numlines = newnumlines;

    if (newnum) {
	memcpy(&linelist[oldbeg],
	       &tmplinelist[0],
	       sizeof(lline) * (newnum));
    }
}

/* xpos, ypos are relative to textwin origin */
#ifdef __STDC__
static long find_pos_by_loc(int xpos, int ypos)
#else
static long find_pos_by_loc(xpos, ypos)
int xpos;
int ypos;
#endif
{
    int ix;
    long linenum;
    long wx, atpos, newpos;
    lline *curline;
    word *curword;

    if (ypos < 0) 
	linenum = (-1) - ((-1)-ypos / lineheight);
    else
	linenum = ypos / lineheight;

    linenum += scrollline;

    if (linenum < 0)
	return 0;
    if (linenum >= numlines)
	return numchars;

    curline = (&linelist[linenum]);
    if (xpos < 0) {
	return curline->pos; /* beginning of line */
    }
    atpos = 0;
    for (wx=0; wx<curline->numwords; wx++) {
	newpos = atpos + curline->wordlist[wx].width;
	if (xpos < newpos)
	    break;
	atpos = newpos;
    }
    if (wx==curline->numwords) {
	return curline->posend; /* end of line */
    }

    xpos -= atpos; /* now xpos is relative to word beginning */
    curword = (&curline->wordlist[wx]);
    if (!curword->letterpos)
	measure_word(curline, curword);

    for (ix=0; ix<curword->len; ix++) {
	if (xpos <= (curword->letterpos[ix]+curword->letterpos[ix+1])/2)
	    break;
    }
    return curline->pos + curword->pos + ix;
}

/* returns the last line such that pos >= line.pos. guessline is a guess to start searching at; -1 means end of file. Can return -1 if pos is before the start of the layout. */
#ifdef __STDC__
static long find_line_by_pos(long pos, long guessline)
#else
static long find_line_by_pos(pos, guessline)
long pos;
long guessline;
#endif
{
    long lx;

    if (guessline < 0 || guessline >= numlines)
	guessline = numlines-1;

    if (guessline < numlines-1 && linelist[guessline].pos <= pos) {
	for (lx=guessline; lx<numlines; lx++) {
	    if (linelist[lx].pos > pos)
		break;
	}
	lx--;
    }
    else {
	for (lx=guessline; lx>=0; lx--) {
	    if (linelist[lx].pos <= pos)
		break;
	}
    }

    return lx;
}

/* returns values relative to textwin origin, at top of line. */
#ifdef __STDC__
static void find_loc_by_pos(long pos, int *xposret, int *yposret)
#else
static void find_loc_by_pos(pos, xposret, yposret)
long pos;
int *xposret;
int *yposret;
#endif
{
    long lx;
    long wx, atpos;
    lline *curline;
    word *curword;

    lx = find_line_by_pos(pos, -1);
    if (lx < 0) {
	/* somehow before first line laid out */
	*xposret = 0;
	*yposret = (-scrollline) * lineheight;
	return;
    }
    curline = (&linelist[lx]);

    *yposret = (lx-scrollline) * lineheight;
    atpos = 0;
    for (wx=0; wx<curline->numwords; wx++) {
	if (curline->pos+curline->wordlist[wx].pos+curline->wordlist[wx].len >= pos)
	    break;
	atpos += curline->wordlist[wx].width;
    }
    if (wx==curline->numwords) {
	*xposret = atpos;
	return;
    }

    curword = (&curline->wordlist[wx]);
    if (!curword->letterpos)
	measure_word(curline, curword);

    atpos += curword->letterpos[pos - (curline->pos+curword->pos)];

    *xposret = atpos;
}

#ifdef __STDC__
static void measure_word(lline *curline, word *curword)
#else
static void measure_word(curline, curword)
lline *curline;
word *curword;
#endif
{
    int cx;
    char *buf;
    int direction;
    int ascent, descent;
    XCharStruct overall;
    long *arr;

    if (curword->letterpos)
	free(curword->letterpos);

    arr = (long *)malloc(sizeof(long) * (curword->len+1));

    buf = charbuf+curline->pos+curword->pos;
    arr[0] = 0;
    for (cx=0; cx<curword->len-1; cx++) {
	XTextExtents(fontstr[curword->attr], buf+cx, 1, &direction, &ascent, &descent, &overall);
	arr[cx+1] = arr[cx] + overall.width;
    }
    arr[cx+1] = curword->width;

    curword->letterpos = arr;
}

#ifdef __STDC__
static void strip_garbage(char *buf, int len)
#else
static void strip_garbage(buf, len)
char *buf;
int len;
#endif
{
    int ix;

    for (ix=0; ix<len; ix++, buf++) {
	if (iscntrl(*buf))
	    *buf = ' ';
    }
}

/* pos < 0 means add at end.
 all this is grotesquely inefficient if adding anywhere but the end. */
#ifdef __STDC__
void xtext_add(char ch, long pos)
#else
void xtext_add(ch, pos)
char ch;
long pos;
#endif
{
    if (pos<0)
	pos = numchars;
    xtext_replace(pos, 0, &ch, 1);
}

/* update data, adjusting dot and styles as necessary. */
#ifdef __STDC__
void xtext_replace(long pos, long oldlen, char *buf, long newlen)
#else
void xtext_replace(pos, oldlen, buf, newlen)
long pos;
long oldlen;
char *buf;
long newlen;
#endif
{
    long newnumchars;

    newnumchars = numchars-oldlen+newlen;
    if (newnumchars >= char_size) {
	while (newnumchars >= char_size) 
	    char_size *= 2;
	charbuf = (char *)realloc(charbuf, sizeof(char) * char_size);
    }

    if (pos < dirtybeg || dirtybeg < 0)
	dirtybeg = pos;

    if (newlen != oldlen) {
	if (pos+oldlen != numchars) {
	    memmove(charbuf+pos+newlen, charbuf+pos+oldlen, sizeof(char) * (numchars-(pos+oldlen)));
	}
	if (numchars >= dirtyend)
	    dirtyend = numchars+1;
	dirtydelta += (newlen-oldlen);
    }
    else {
	if (pos+newlen >= dirtyend)
	    dirtyend = pos+newlen+1;
	dirtydelta += (newlen-oldlen);
    }

    /* copy in the new stuff */
    if (newlen)
	memmove(charbuf+pos, buf, sizeof(char) * newlen);

    /* diddle the dot */
    if (dotpos >= pos+oldlen) {
	/* starts after changed region */
	dotpos += (newlen-oldlen);
    }
    else if (dotpos >= pos) {
	/* starts inside changed region */
	if (dotpos+dotlen >= pos+oldlen) {
	    /* ...but ends after it */
	    dotlen = (dotpos+dotlen)-(pos+oldlen);
	    dotpos = pos+newlen;
	}
	else {
	    /* ...and ends inside it */
	    dotpos = pos+newlen;
	    dotlen = 0;
	}
    }
    else {
	/* starts before changed region */
	if (dotpos+dotlen >= pos+oldlen) {
	    /* ...but ends after it */
	    dotlen += (newlen-oldlen);
	}
	else if (dotpos+dotlen >= pos) {
	    /* ...but ends inside it */
	    dotlen = (pos+newlen) - dotpos;
	}	    
    }

    numchars = newnumchars;
}

#ifdef __STDC__
void xtext_setstyle(long pos, int attr)
#else
void xtext_setstyle(pos, attr)
long pos;
int attr;
#endif
{
    long sx;

    if (pos < 0)
	pos = numchars;

    for (sx=numstyles-1; sx>=0; sx--) {
	if (stylelist[sx].pos <= pos) {
	    break;
	}
    }
    if (sx < 0) {
	printf("### oops, went back behind style 0\n");
	return;
    }

    if (stylelist[sx].pos == pos) {
	stylelist[sx].attr = attr;
    }
    else {
	/* insert a style after sx */
	sx++;
	if (numstyles+1 >= styles_size) {
	    styles_size *= 2;
	    stylelist = (style *)realloc(stylelist, sizeof(style) * styles_size);
	}
	numstyles++;
	if (sx < numstyles) {
	    memmove(&stylelist[sx+1], &stylelist[sx], sizeof(style) * (numstyles-sx));
	    stylelist[sx].pos = pos;
	    stylelist[sx].attr = attr;
	}
    }

    if (pos != numchars) {
	/* ### should only go to next style */
	dirtybeg = pos;
	dirtyend = numchars;
	dirtydelta = 0;
	xtext_layout();
    }
}

#ifdef __STDC__
void xtext_set_lastseen()
#else
void xtext_set_lastseen()
#endif
{
    lastlineseen = numlines;
}

#ifdef __STDC__
void xtext_end_visible()
#else
void xtext_end_visible()
#endif
{
    long lx;

    if (lastlineseen < 0 || lastlineseen >= (numlines-linesperpage)-1) {
	/* straight to end */
	if (scrollline < numlines-linesperpage) {
	    scroll_to(numlines-linesperpage);
	}
    }
    else {
	lx = lastlineseen-1;
	while (lx < numlines-linesperpage) {
	    scroll_to(lx);
	    xmess_set_message("[Hit any key to continue.]", TRUE);
	    xio_pause();
	    lx += (linesperpage-1);
	}
	scroll_to(numlines-linesperpage);
	xmess_set_message(NULL, TRUE);
    }

    lastlineseen = (-1);
}

/* delete num lines from the top */
#ifdef __STDC__
void xtext_delete_start(long num)
#else
void xtext_delete_start(num)
long num;
#endif
{
    long delchars;
    long lx, sx, sx2;
    int origattr;

    if (num > numlines)
	num = numlines;
    if (num < 0)
	num = 0;

    if (num == numlines)
      delchars = numchars;
    else
      delchars = linelist[num].pos;
    if (!delchars)
	return;

    /* lines */
    slapover(0, 0, num);
    for (lx=0; lx<numlines; lx++) {
	linelist[lx].pos -= delchars;
	linelist[lx].posend -= delchars;
    }

    /* styles */
    for (sx=0; sx<numstyles; sx++) {
	if (stylelist[sx].pos > delchars)
	    break;
    }
    if (sx>0) {
	origattr = stylelist[sx-1].attr;
	stylelist[0].pos = 0;
	stylelist[0].attr = origattr;
	for (sx2=1; sx<numstyles; sx++, sx2++) {
	    stylelist[sx2].pos = stylelist[sx].pos - delchars;
	    stylelist[sx2].attr = stylelist[sx].attr;
	}
	numstyles = sx2;
    }

    /* chars */
    if (delchars < numchars)
      memmove(&charbuf[0], &charbuf[delchars], sizeof(char) * (numchars-delchars));
    numchars -= delchars;

    /* adjust, I mean, everything */
    if (dirtybeg != (-1)) {
	dirtybeg -= delchars;
	dirtyend -= delchars;
	if (dirtyend < 0) {
	    dirtybeg = (-1);
	    dirtyend = (-1);
	}
	else if (dirtybeg < 0) {
	    dirtybeg = 0;
	}
    }

    dotpos -= delchars;
    if (dotpos < 0) {
	if (dotpos+dotlen < 0) {
	    dotpos = 0;
	    dotlen = 0;
	}
	else {
	    dotlen += dotpos;
	    dotpos = 0;
	}
    }
    lastdotpos -= delchars;
    if (lastdotpos < 0) {
	if (lastdotpos+lastdotlen < 0) {
	    lastdotpos = 0;
	    lastdotlen = 0;
	}
	else {
	    lastdotlen += lastdotpos;
	    lastdotpos = 0;
	}
    }
    inputfence -= delchars;
    if (inputfence < 0)
	inputfence = 0;

    if (lastlineseen != (-1)) {
	lastlineseen -= num;
	if (lastlineseen < 0)
	    lastlineseen = (-1);
    }

    scrollline -= num;
    scrollpos -= delchars;
    if (scrollline < 0 || scrollpos < 0) {
	scrollline = 0;
	scrollpos = 0;
	redrawtext(0, -1, -1);
	flip_selection(dotpos, dotlen);
	adjust_elevator();
    }
    else {
	adjust_elevator();
    }
}

#ifdef __STDC__
void xtext_layout()
#else
void xtext_layout()
#endif
{
    long ix, jx, ejx, lx;
    long styx, nextstylepos;
    int curstyle;
    long overline, overlineend;
    long tmpl, startpos;
    int prevflags;
    int needwholeredraw;

    int direction;
    int ascent, descent;
    XCharStruct overall;

    static long lastline = 0; /* last line dirtied */

    if (dirtybeg < 0 || dirtyend < 0) {
	if (lastdotpos != dotpos || lastdotlen != dotlen) {
	    refiddle_selection(lastdotpos, lastdotlen, dotpos, dotlen);
	    /*flip_selection(lastdotpos, lastdotlen);*/
	    lastdotpos = dotpos;
	    lastdotlen = dotlen;
	    /*flip_selection(lastdotpos, lastdotlen);*/
	}
	return;
    }

    /* if any text diddling is done, we'll just flip automatically */
    flip_selection(lastdotpos, lastdotlen);
    lastdotpos = dotpos;
    lastdotlen = dotlen;

    if (numlines==0) {
	overline = 0;
	startpos = 0;
    }
    else {
	lx = find_line_by_pos(dirtybeg, lastline);
	/* now lx is the line containing dirtybeg */

	if (lx>0 && lx<numlines && (linelist[lx].flags & lineflag_Wrapped)) {
	    /* do layout from previous line, in case a word from the changed area pops back there. */
	    lx--;
	}
	overline = lx;
	startpos = linelist[overline].pos;
    }

    /* get the first relevant style */
    for (styx=numstyles-1; styx>0; styx--)
	if (stylelist[styx].pos <= startpos)
	    break;
    if (styx==numstyles-1)
	nextstylepos = numchars+10;
    else
	nextstylepos = stylelist[styx+1].pos;
    curstyle = stylelist[styx].attr;

    /* start a-layin' */
    tmpl = 0;
    prevflags = 0;

    while (startpos<numchars && !(startpos >= dirtyend && charbuf[startpos]=='\n')) {
	lline *thisline;
	long tmpw, tmpwords_size;
	long widthsofar, spaceswidth;

	if (tmpl+1 >= tmplines_size) {
	    /* the +1 allows the extra blank line at the end */
	    tmplines_size *= 2;
	    tmplinelist = (lline *)realloc(tmplinelist, sizeof(lline) * tmplines_size);
	}
	thisline = (&tmplinelist[tmpl]);
	thisline->flags = prevflags;
	tmpwords_size = 8;
	thisline->wordlist = (word *)malloc(tmpwords_size * sizeof(word));
	tmpw = 0;

	/*printf("### laying tmpline %d, from charpos %d\n", tmpl, startpos);*/
	tmpl++;

	ix = startpos;
	widthsofar = 0;
	prevflags = 0;

	while (ix<numchars && charbuf[ix]!='\n') {
	    word *thisword;

	    while (ix >= nextstylepos) {
		/* ahead one style */
		styx++;
		if (styx==numstyles-1)
		    nextstylepos = numchars+10;
		else
		    nextstylepos = stylelist[styx+1].pos;
		curstyle = stylelist[styx].attr;
	    }

	    if (tmpw >= tmpwords_size) {
		tmpwords_size *= 2;
		thisline->wordlist = (word *)realloc(thisline->wordlist, tmpwords_size * sizeof(word));
	    }
	    thisword = (&thisline->wordlist[tmpw]);
	    /* --- initialize word structure --- */

	    thisword->letterpos = NULL;
	    for (jx=ix; jx<numchars && jx<nextstylepos && charbuf[jx]!=' ' && charbuf[jx]!='\n'; jx++);

	    XTextExtents(fontstr[curstyle], charbuf+ix, jx-ix, &direction, &ascent, &descent, &overall);
	    if (widthsofar + overall.width > textwin_w) {
		prevflags = lineflag_Wrapped;
		if (tmpw == 0) {
		    /* do something clever -- split the word, put first part in tmplist. */
		    int letx;
		    long wordwidthsofar = 0;
		    for (letx=ix; letx<jx; letx++) {
			XTextExtents(fontstr[curstyle], charbuf+letx, 1, &direction, &ascent, &descent, &overall);
			if (widthsofar + wordwidthsofar+overall.width > textwin_w) {
			    break;
			}
			wordwidthsofar += overall.width;
		    }
		    jx = letx;
		    overall.width = wordwidthsofar;
		    /* spaceswidth and ejx will be 0 */
		    /* don't break */
		}
		else {
		    /* ejx and spaceswidth are properly set from last word, trim them off. */
		    thisword--;
		    thisword->len -= ejx;
		    thisword->width -= spaceswidth;
		    break;
		}
	    }

	    /* figure out trailing whitespace */
	    ejx = 0;
	    while (jx+ejx<numchars && jx+ejx<nextstylepos && charbuf[jx+ejx]==' ') {
		ejx++;
	    }
	    spaceswidth = ejx * spacewidth[curstyle];

	    /* put the word in tmplist */
	    thisword->pos = ix-startpos;
	    thisword->len = jx+ejx-ix;
	    thisword->attr = curstyle;
	    thisword->width = overall.width+spaceswidth;
	    widthsofar += thisword->width;
	    tmpw++; 

	    ix = jx+ejx;
	}
	thisline->pos = startpos;
	if (tmpw) {
	    word *thisword = (&thisline->wordlist[tmpw-1]);
	    thisline->posend = startpos + thisword->pos + thisword->len;
	}
	else {
	    thisline->posend = startpos;
	}

	if (ix<numchars && charbuf[ix]=='\n')
	    ix++;

	thisline->numwords = tmpw;
	if (prefs.fulljustify && prevflags==lineflag_Wrapped && tmpw>1) {
	    /* gonna regret this, I just bet */
	    long extraspace, each;
	    extraspace = textwin_w - widthsofar;
	    each = extraspace / (tmpw-1);
	    extraspace -= (each*(tmpw-1));
	    for (jx=0; jx<extraspace; jx++) {
		thisline->wordlist[jx].width += (each+1);
	    }
	    for (; jx<tmpw-1; jx++) {
		thisline->wordlist[jx].width += each;
	    }
	}
	
	startpos = ix;
    } /* done laying tmp lines */

    if (startpos == numchars && (numchars==0 || charbuf[numchars-1]=='\n')) {
	/* lay one more line! */
	lline *thisline;
	thisline = (&tmplinelist[tmpl]);
	thisline->flags = lineflag_Extra;
	tmpl++;
	
	thisline->wordlist = (word *)malloc(sizeof(word));
	thisline->numwords = 0;
	thisline->pos = startpos;
	thisline->posend = startpos;
    }

    /*printf("### laid %d tmplines, and startpos now %d (delta %d)\n", tmpl, startpos, dirtydelta);*/

    for (lx=overline; lx<numlines && linelist[lx].pos < startpos-dirtydelta; lx++);
    if (lx==numlines-1 && (linelist[lx].flags & lineflag_Extra)) {
	/* account for the extra line */
	lx++;
    }
    overlineend = lx;

    /*printf("### overwrite area is lines [%d..%d) (of %d); replacing with %d lines\n", overline, overlineend, numlines, tmpl);*/

    slapover(tmpl, overline, overlineend);

    lastline = overline+tmpl; /* re-cache value */
    needwholeredraw = FALSE;

    /* diddle scroll stuff */
    if (scrollpos <= dirtybeg) {
	/* disturbance is off bottom of screen -- do nothing */
    }
    else if (scrollpos >= startpos-dirtydelta) {
	/* disturbance is off top of screen -- adjust so that no difference is visible. */
	scrollpos += dirtydelta;
	scrollline += (overline-overlineend) - tmpl;
    }
    else {
	scrollpos += dirtydelta; /* kind of strange, but shouldn't cause trouble */
	if (scrollpos >= numchars)
	    scrollpos = numchars-1;
	if (scrollpos < 0)
	    scrollpos = 0;
	scrollline = find_line_by_pos(scrollpos, scrollline);
	needwholeredraw = TRUE;
    }

    dirtybeg = -1;
    dirtyend = -1;
    dirtydelta = 0;

    if (needwholeredraw) {
	redrawtext(scrollline, -1, -1);
    }
    else if (tmpl == overlineend-overline) {
	redrawtext(overline, tmpl, tmpl);
    }
    else {
	if (overlineend > numlines)
	    redrawtext(overline, -1, overlineend-overline);
	else
	    redrawtext(overline, -1, numlines-overline);
    }

    flip_selection(lastdotpos, lastdotlen);

    adjust_elevator();
}

static long drag_firstbeg, drag_firstend;
static int drag_inscroll; 
static int drag_scrollmode; /* 0 for click in elevator; 1 for dragged in elevator; 2 for endzones; 3 for click in background */
static int drag_hitypos; 
static long drag_origline;

/* got a mouse hit. */
#ifdef __STDC__
void xtext_hitdown(int xpos, int ypos, unsigned int button, unsigned int mods, int clicknum)
#else
void xtext_hitdown(xpos, ypos, button, mods, clicknum)
int xpos;
int ypos;
unsigned int button;
unsigned int mods;
int clicknum;
#endif
{
    long pos;
    long px, px2;

    if (xpos < scrollwin_x+scrollwin_w)
	drag_inscroll = TRUE;
    else
	drag_inscroll = FALSE;

    if (drag_inscroll) {
	drag_origline = scrollline;
	drag_hitypos = ypos-textwin_y;
	switch (button) { /* scrollbar */
	    case Button1:
		if (ypos < scrollwin_y) {
		    drag_scrollmode = 2;
		    xted_scroll(op_ToTop);
		}
		else if (ypos >= scrollwin_y+scrollwin_h) {
		    drag_scrollmode = 2;
		    xted_scroll(op_ToBottom);
		}
		else {
		    if (ypos >= scrollwin_y+scrollel_top
			&& ypos < scrollwin_y+scrollel_bot)
			drag_scrollmode = 0;
		    else
			drag_scrollmode = 3;
		}
		break;
	    case Button3:
		if (ypos < scrollwin_y) {
		    drag_scrollmode = 2;
		    xted_scroll(op_UpLine);
		}
		else if (ypos >= scrollwin_y+scrollwin_h) {
		    drag_scrollmode = 2;
		    xted_scroll(op_DownLine);
		}
		else {
		    if (ypos >= scrollwin_y+scrollel_top
			&& ypos < scrollwin_y+scrollel_bot)
			drag_scrollmode = 0;
		    else
			drag_scrollmode = 3;
		}
		break;
	}
    }
    else {
	switch (button) { /* text window */
	    case Button1:
	    case Button3:
		xpos -= textwin_x;
		ypos -= textwin_y;

		pos = find_pos_by_loc(xpos, ypos);
		if (button==Button1) {
		    if (!(clicknum & 1)) {
			px = back_to_white(pos);
			px2 = fore_to_white(pos);
		    }
		    else {
			px = pos;
			px2 = pos;
		    }
		    dotpos = px;
		    dotlen = px2-px;
		    drag_firstbeg = px;
		    drag_firstend = px2;
		}
		else {
		    if (pos < dotpos+dotlen/2) {
			drag_firstbeg = dotpos+dotlen;
		    }
		    else {
			drag_firstbeg = dotpos;
		    }
		    drag_firstend = drag_firstbeg;
		    if (pos < drag_firstbeg) {
			if (!(clicknum & 1))
			    dotpos = back_to_white(pos);
			else
			    dotpos = pos;
			dotlen = drag_firstend-dotpos;
		    }
		    else if (pos > drag_firstend) {
			dotpos = drag_firstbeg;
			if (!(clicknum & 1))
			    dotlen = fore_to_white(pos)-drag_firstbeg;
			else
			    dotlen = pos-drag_firstbeg;
		    }
		    else {
			dotpos = drag_firstbeg;
			dotlen = drag_firstend-drag_firstbeg;
		    }
		}
		xtext_layout();
		break;
	    default:
		break;
	}
    }
}

#ifdef __STDC__
void xtext_hitmove(int xpos, int ypos, unsigned int button, unsigned int mods, int clicknum)
#else
void xtext_hitmove(xpos, ypos, button, mods, clicknum)
int xpos;
int ypos;
unsigned int button;
unsigned int mods;
int clicknum;
#endif
{
    long pos, px;

    if (drag_inscroll) {
	if (drag_scrollmode==0 || drag_scrollmode==1) {
	    drag_scrollmode = 1;
	    px = ((ypos - drag_hitypos)*numlines) / (scrollwin_h-2*BAREXTRA);
	    scroll_to(drag_origline+px);
	}
    }
    else {
	xpos -= textwin_x;
	ypos -= textwin_y;

	switch (button) {
	    case Button1:
	    case Button3:
		pos = find_pos_by_loc(xpos, ypos);
		if (pos < drag_firstbeg) {
		    if (!(clicknum & 1))
			dotpos = back_to_white(pos);
		    else
			dotpos = pos;
		    dotlen = drag_firstend-dotpos;
		}
		else if (pos > drag_firstend) {
		    dotpos = drag_firstbeg;
		    if (!(clicknum & 1))
			dotlen = fore_to_white(pos)-drag_firstbeg;
		    else
			dotlen = pos-drag_firstbeg;
		}
		else {
		    dotpos = drag_firstbeg;
		    dotlen = drag_firstend-drag_firstbeg;
		}
		xtext_layout();
		break;
	    default:
		break;
	}
    }
}

#ifdef __STDC__
void xtext_hitup(int xpos, int ypos, unsigned int button, unsigned int mods, int clicknum)
#else
void xtext_hitup(xpos, ypos, button, mods, clicknum)
int xpos;
int ypos;
unsigned int button;
unsigned int mods;
int clicknum;
#endif
{
    int px;

    if (drag_inscroll && (drag_scrollmode==0 || drag_scrollmode==3)) {
	switch (button) { /* scrollbar */
	    case Button1:
		px = (ypos - textwin_y) / lineheight;
		scroll_to(scrollline+px);
		break;
	    case Button3:
		px = (ypos - textwin_y) / lineheight;
		scroll_to(scrollline-px);
		break;
	}
    }
}

/* editing functions... */

#ifdef __STDC__
void xted_init(int vbuflen, char *vbuffer, int *vreadpos, int *vkillflag, 
  int firsttime)
#else
void xted_init(vbuflen, vbuffer, vreadpos, vkillflag, firsttime)
int vbuflen;
char *vbuffer;
int *vreadpos;
int *vkillflag;
int firsttime;
#endif
{
    killflag = vkillflag;
    *killflag = (-1);
    buflen = vbuflen;
    buffer = vbuffer;
    readpos = vreadpos;

    if (*readpos) {
      if (firsttime) {
	/* Z-machine has already entered the text into the buffer. */
	inputfence = numchars - (*readpos);
	originalattr = stylelist[numstyles-1].attr;
	xtext_setstyle(inputfence, prefs.inputattr);
      }
      else {
	/* The terp has to enter the text. */
	inputfence = numchars;
	originalattr = stylelist[numstyles-1].attr;
	xtext_setstyle(-1, prefs.inputattr);
	xtext_replace(dotpos, 0, buffer, *readpos);
	xtext_layout();
      }
    }
    else {
	inputfence = numchars;
	originalattr = stylelist[numstyles-1].attr;
	xtext_setstyle(-1, prefs.inputattr);
    }

    historypos = historynum;
}

#ifdef __STDC__
void xted_insert(int ch)
#else
void xted_insert(ch)
int ch;
#endif
{
    if (iscntrl(ch))
	ch = ' ';

    if (dotpos < inputfence) {
	dotpos = numchars;
	dotlen = 0;
    }
    else {
	collapse_dot();
    }

    xtext_add(ch, dotpos);
    xtext_layout();
    xtext_end_visible();
}

#ifdef __STDC__
void xted_delete(int op)
#else
void xted_delete(op)
int op;
#endif
{
    long pos;

    if (dotpos < inputfence)
	return;
    collapse_dot();

    switch (op) {
	case op_BackChar:
	    if (dotpos <= inputfence)
		return;
	    xtext_replace(dotpos-1, 1, "", 0);
	    break;
	case op_ForeChar:
	    if (dotpos < inputfence || dotpos >= numchars)
		return;
	    xtext_replace(dotpos, 1, "", 0);
	    break;
	case op_BackWord:
	    pos = back_to_nonwhite(dotpos);
	    pos = back_to_white(pos);
	    if (pos < inputfence)
		pos = inputfence;
	    if (pos >= dotpos)
		return;
	    xtext_replace(pos, dotpos-pos, "", 0);
	    break;
	case op_ForeWord:
	    pos = fore_to_nonwhite(dotpos);
	    pos = fore_to_white(pos);
	    if (pos < inputfence)
		pos = inputfence;
	    if (pos <= dotpos)
		return;
	    xtext_replace(dotpos, pos-dotpos, "", 0);
	    break;
    }
    xtext_layout();
}

#ifdef __STDC__
void xted_enter(int op)
#else
void xted_enter(op)
int op;
#endif
{
    int len;

    if (op != op_Enter)
	return;

    if (killflag)
	*killflag = '\n';
    xtext_setstyle(-1, originalattr);

    len = numchars-inputfence;
    if (len > buflen)
	len = buflen;
    memmove(buffer, charbuf+inputfence, len*sizeof(char));
    *readpos = len;

    if (len) {
	/* add to history */
	if (historynum==prefs.historylength) {
	    free(history[0].str);
	    memmove(&history[0], &history[1], (prefs.historylength-1) * (sizeof(histunit)));
	}
	else
	    historynum++;
	history[historynum-1].str = malloc(len*sizeof(char));
	memmove(history[historynum-1].str, charbuf+inputfence, len*sizeof(char));
	history[historynum-1].len = len;
    }

    xtext_add('\n', -1);
    dotpos = numchars;
    dotlen = 0;
    xtext_layout();

    /* a somewhat strange place to put the buffer trimmer, but what the heck. The status line shrinker too. */
    xstat_reset_window_size(op_Shrink);
    if (numchars > prefs.buffersize + prefs.bufferslack) {
	long lx;
	for (lx=0; lx<numlines; lx++)
	    if (linelist[lx].pos > (numchars-prefs.buffersize))
		break;
	if (lx) {
	    xtext_delete_start(lx);
	}
    }
}

#ifdef __STDC__
void xtext_line_timeout()
#else
void xtext_line_timeout()
#endif
{
    int len;

    /* same as xted_enter(), but skip the unnecessary stuff.
     We don't need to add to history, collapse the dot, xtext_layout, trim the buffer, or shrink the status window. */
    
    len = numchars-inputfence;
    if (len > buflen)
	len = buflen;
    memmove(buffer, charbuf+inputfence, len*sizeof(char));
    *readpos = len;

    if (len) {
	xtext_replace(inputfence, len, "", 0);
	dotpos = numchars;
	dotlen = 0;
	xtext_layout();
    }

    xtext_setstyle(-1, originalattr);
}

#ifdef __STDC__
void xted_scroll(int op)
#else
void xted_scroll(op)
int op;
#endif
{
    switch (op) {
	case op_UpLine:
	    scroll_to(scrollline-1);
	    break;
	case op_DownLine:
	    scroll_to(scrollline+1);
	    break;
	case op_UpPage:
	    scroll_to(scrollline-(linesperpage-1));
	    break;
	case op_DownPage:
	    scroll_to(scrollline+(linesperpage-1));
	    break;
	case op_ToTop:
	    scroll_to(0);
	    break;
	case op_ToBottom:
	    scroll_to(numlines);
	    break;
    }
}

#ifdef __STDC__
void xted_movecursor(int op)
#else
void xted_movecursor(op)
int op;
#endif
{
    long pos;

    switch (op) {
	case op_BackChar:
	    collapse_dot();
	    if (dotpos > 0)
		dotpos--;
	    break;
	case op_ForeChar:
	    collapse_dot();
	    if (dotpos < numchars)
		dotpos++;
	    break;
	case op_BackWord:
	    collapse_dot();
	    dotpos = back_to_nonwhite(dotpos);
	    dotpos = back_to_white(dotpos);
	    break;
	case op_ForeWord:
	    collapse_dot();
	    dotpos = fore_to_nonwhite(dotpos);
	    dotpos = fore_to_white(dotpos);
	    break;
	case op_BeginLine:
	    if (dotlen) {
		dotlen = 0;
	    }
	    else {
		if (dotpos >= inputfence)
		    dotpos = inputfence;
		else {
		    pos = dotpos;
		    while (pos > 0 && charbuf[pos-1] != '\n')
			pos--;
		    dotpos = pos;
		}
	    }
	    break;
	case op_EndLine:
	    if (dotlen) {
		collapse_dot();
	    }
	    else {
		if (dotpos >= inputfence)
		    dotpos = numchars;
		else {
		    pos = dotpos;
		    while (pos < numchars && charbuf[pos] != '\n')
			pos++;
		    dotpos = pos;
		}
	    }
	    break;
    }
    xtext_layout();
}

#ifdef __STDC__
void xted_cutbuf(int op)
#else
void xted_cutbuf(op)
int op;
#endif
{
    char *cx;
    int num;
    long tmppos;

    switch (op) {
	case op_Copy:
	    if (dotlen) {
		XRotateBuffers(xiodpy, 1);
		XStoreBytes(xiodpy, charbuf+dotpos, sizeof(char)*dotlen);
	    }
	    break;
	case op_Wipe:
	    if (dotlen) {
		XRotateBuffers(xiodpy, 1);
		XStoreBytes(xiodpy, charbuf+dotpos, sizeof(char)*dotlen);
		if (dotpos >= inputfence) {
		    xtext_replace(dotpos, dotlen, "", 0);
		    xtext_layout();
		}
	    }
	    break;
	case op_Yank:
	    collapse_dot();
	    if (dotpos < inputfence)
		dotpos = numchars;
	    cx = XFetchBytes(xiodpy, &num);
	    strip_garbage(cx, num);
	    if (cx && num) {
		tmppos = dotpos;
		xtext_replace(tmppos, 0, cx, num);
		dotpos = tmppos;
		dotlen = num;
		free(cx);
	    }
	    xtext_layout();
	    break;
	case op_Untype:
	    if (numchars == inputfence)
		break;
	    dotpos = inputfence;
	    dotlen = numchars-inputfence;
	    XRotateBuffers(xiodpy, 1);
	    XStoreBytes(xiodpy, charbuf+dotpos, sizeof(char)*dotlen);
	    xtext_replace(dotpos, dotlen, "", 0);
	    xtext_layout();
	    break;
	case op_Kill:
	    if (dotpos < inputfence) {
		/* maybe extend to end-of-line and copy? */
		break;
	    }
	    dotlen = numchars-dotpos;
	    XRotateBuffers(xiodpy, 1);
	    XStoreBytes(xiodpy, charbuf+dotpos, sizeof(char)*dotlen);
	    xtext_replace(dotpos, dotlen, "", 0);
	    xtext_layout();
	    break;
    }
}

#ifdef __STDC__
void xted_history(int op)
#else
void xted_history(op)
int op;
#endif
{
    long pos, len;

    switch (op) {
	case op_BackLine:
	    if (historypos > 0) {
		if (dotpos < inputfence) {
		    dotpos = numchars;
		    dotlen = 0;
		}
		historypos--;
		pos = dotpos;
		xtext_replace(pos, dotlen, history[historypos].str, history[historypos].len);
		dotpos = pos;
		dotlen = history[historypos].len;
		xtext_layout();
	    }
	    break;
	case op_ForeLine:
	    if (historypos < historynum) {
		if (dotpos < inputfence) {
		    dotpos = numchars;
		    dotlen = 0;
		}
		historypos++;
		if (historypos < historynum) {
		    pos = dotpos;
		    xtext_replace(dotpos, dotlen, history[historypos].str, history[historypos].len);
		    dotpos = pos;
		    dotlen = history[historypos].len;
		}
		else {
		    pos = dotpos;
		    xtext_replace(dotpos, dotlen, "", 0);
		    dotpos = pos;
		    dotlen = 0;
		}
		xtext_layout();
	    }
    }
}

#ifdef __STDC__
void xted_define_macro(int keynum)
#else
void xted_define_macro(keynum)
int keynum;
#endif
{
    static cmdentry *macrocommand = NULL;
    char buf[256];
    char *cx, *cx2;

    if (!macrocommand) {
	macrocommand = xkey_find_cmd_by_name("macro");
	if (!macrocommand) {
	    xmess_set_message("Error: unable to find macro command entry.", FALSE);
	    return;
	}
    }
    if (keycmds[keynum] != macrocommand) {
	cx = xkey_get_key_name(keynum);
	sprintf(buf, "Key <%s> is not bound to the macro command.", cx);
	xmess_set_message(buf, FALSE);
	return;
    }

    if (dotlen == 0) {
	xmess_set_message("You must highlight a string to define this macro to.", FALSE);
	return;
    }

    cx2 = (char *)malloc(sizeof(char) * (dotlen+1));
    memcpy(cx2, charbuf+dotpos, dotlen * sizeof(char));
    cx2[dotlen] = '\0';
    strip_garbage(cx2, dotlen);

    if (keycmdargs[keynum])
	free(keycmdargs[keynum]);
    keycmdargs[keynum] = cx2;
    cx = xkey_get_key_name(keynum);
    if (!cx2 || !cx2[0])
	sprintf(buf, "Macro <%s> is not defined.", cx);
    else if (strlen(cx2) > (sizeof(buf)-64))
	sprintf(buf, "Macro <%s> is defined to something too long to display.", cx);
    else
	sprintf(buf, "Macro <%s> defined to \"%s\".", cx, cx2);
    xmess_set_message(buf, FALSE);
}

#ifdef __STDC__
void xted_macro(int op)
#else
void xted_macro(op)
int op;
#endif
{
    char *str, *cx;

    str = keycmdargs[op];
    if (!str || !str[0]) {
	char buf[128];
	cx = xkey_get_key_name(op);
	sprintf(buf, "Macro <%s> is not defined.", cx);
	xmess_set_message(buf, FALSE);
	return;
    }

    if (dotpos < inputfence) {
	dotpos = numchars;
	dotlen = 0;
    }
    else {
	collapse_dot();
    }

    xtext_replace(dotpos, 0, str, strlen(str));

    xtext_layout();
    xtext_end_visible();
}

#ifdef __STDC__
void xted_noop(int op)
#else
void xted_noop(op)
int op;
#endif
{
    /* good for debugging */
}
