/* buffer.c */
/* Copyright 1995 by Steve Kirkendall */

char id_buffer[] = "$Id: buffer.c,v 2.90 1998/10/12 18:57:22 steve Exp $";

#include "elvis.h"

#define swaplong(x,y)	{long tmp; tmp = (x); (x) = (y); (y) = tmp;}

#if USE_PROTOTYPES
static void proc(_BLKNO_ bufinfo, long nchars, long nlines, long changes, long prevloc, CHAR *name);
static void freeundo(BUFFER buffer);
static struct undo_s *allocundo(BUFFER buf);
static void bufdo(BUFFER buf, BOOLEAN wipe);
# ifdef DEBUG_ALLOC
static void checkundo(char *where);
static void removeundo(struct undo_s *undo);
# endif
#endif

/* This variable points to the head of a linked list of buffers */
BUFFER elvis_buffers;

/* This is the default buffer.  Its options have been inserted into the
 * list accessible via optset().  This variable should only be changed by
 * the bufoptions() function.
 */
BUFFER bufdefault;

/* This stores the message type that will be used for reporting the number
 * of lines read or written.  It is normally MSG_STATUS so that other messages
 * will be allowed to overwrite it; however, when quitting it is set to
 * MSG_INFO so messages will be queued and eventually displayed somewhere
 * else after the window is closed.  It is also set to MSG_INFO during
 * initialization so the "read..." message appears in the new window.
 */
MSGIMP bufmsgtype = MSG_INFO;

/* This buffer's contents are irrelevent.  The values of its options, though,
 * are significant because the values of its options are used as the default 
 * values of any new buffer.  This buffer is also used as the default buffer
 * during execution of the initialization scripts.
 */
BUFFER bufdefopts;

/* This array describes buffer options */
static OPTDESC bdesc[] =
{
	{"filename", "file",	optsstring,	optisstring	},
	{"bufname", "buffer",	optsstring,	optisstring	},
	{"bufid", "bufferid",	optnstring,	optisnumber	},
	{"buflines", "bl",	optnstring,	optisnumber	},
	{"bufchars", "bc",	optnstring,	optisnumber	},
	{"retain", "ret",	NULL,		NULL		},
	{"modified", "mod",	NULL,		NULL,		},
	{"edited", "samename",	NULL,		NULL,		},
	{"newfile", "new",	NULL,		NULL,		},
	{"readonly", "ro",	NULL,		NULL,		},
	{"autoindent", "ai",	NULL,		NULL,		},
	{"inputtab", "it",	opt1string,	optisoneof,	"tab spaces ex filename identifier"},
	{"autotab", "at",	NULL,		NULL,		},
	{"tabstop", "ts",	optnstring,	optisnumber,	"1:100"},
	{"ccprg", "cp",		optsstring,	optisstring	},
	{"equalprg", "ep",	optsstring,	optisstring	},
	{"keywordprg", "kp",	optsstring,	optisstring	},
	{"makeprg", "mp",	optsstring,	optisstring	},
	{"paragraphs", "para",	optsstring,	optisstring	},
	{"sections", "sect",	optsstring,	optisstring	},
	{"shiftwidth", "sw",	optnstring,	optisnumber	},
	{"undolevels", "ul",	optnstring,	optisnumber	},
	{"textwidth", "tw",	optnstring,	optisnumber	},
	{"internal", "internal",NULL,		NULL		},
	{"bufdisplay", "bd",    optsstring,	optisstring	},
	{"errlines", "errlines",optnstring,	optisnumber	},
	{"readeol", "reol",	opt1string,	optisoneof,	"unix dos mac text binary"},
	{"locked", "lock",	NULL,		NULL		},
	{"partiallastline","pll",NULL,		NULL		},
	{"putstyle", "ps",	opt1string,	optisoneof,	"character line rectangle"}
};

#ifdef DEBUG_ALLOC
/* This are used for maintaining a linked list of all undo versions. */
struct undo_s *undohead, *undotail;

/* This function is called after code which is suspected of leaking memory.
 * It checks all of the undo versions, making sure that each one is still
 * accessible via some buffer.
 */
static void checkundo(where)
	char	*where;
{
	struct undo_s	*scan, *undo;
	BUFFER		buf;

	/* for each undo version... */
	for (scan = undohead; scan; scan = scan->link1)
	{
		/* make sure the buffer still exists */
		for (buf = elvis_buffers; buf != scan->buf; buf = buf->next)
		{
			if (!buf)
				msg(MSG_FATAL, "[s]$1 - buffer disappeared, undo/redo not freed", where);
		}

		/* make sure this is an undo/redo for this buffer */
		if (scan->undoredo == 'l')
		{
			if (scan != buf->undolnptr)
			{
				msg(MSG_FATAL, "[s]$1 - undolnptr version leaked", where);
			}
		}
		else
		{
			for (undo = scan->undoredo=='u' ? buf->undo : buf->redo;
			     undo != scan;
			     undo = undo->next)
			{
				if (!undo)
					msg(MSG_FATAL, "[ss]$1 - $2 version leaked", where, scan->undoredo=='u'?"undo":"redo");
			}
		}
	}
}

static void removeundo(undo)
	struct undo_s	*undo;
{
	if (undo->link1)
		undo->link1->link2 = undo->link2;
	else
		undotail = undo->link2;
	if (undo->link2)
		undo->link2->link1 = undo->link1;
	else
		undohead = undo->link1;
}
#else
# define checkundo(s)
#endif

/* This function is called during session file initialization.  It creates
 * a BUFFER struct for the buffer, and collects the undo versions.
 */
static void proc(bufinfo, nchars, nlines, changes, prevloc, name)
	_BLKNO_	bufinfo;	/* block describing the buffer */
	long	nchars;		/* number of characters in buffer */
	long	nlines;		/* number of lines in buffer */
	long	changes;	/* value of "changes" counter */
	long	prevloc;	/* offset of most recent change to buffer */
	CHAR	*name;		/* name of the buffer */
{
	BUFFER		buf;
	BLKNO		tmp;
	struct undo_s	*undo, *scan, *lag;
	BOOLEAN		internal;

	/* try to find a buffer by this name */
	for (buf = elvis_buffers; buf && CHARcmp(o_bufname(buf), name); buf = buf->next)
	{
	}

	/* if no buffer exists yet, then create one and make it use the old
	 * bufinfo block.
	 */
	if (!buf)
	{
		internal = (BOOLEAN)(!CHARncmp(name, toCHAR("Elvis "), 6) &&
				CHARncmp(name, toCHAR("Elvis untitled"), 14));
		buf = bufalloc(name, bufinfo, internal);
		buf->bufinfo = bufinfo;
		o_buflines(buf) = nlines;
		o_bufchars(buf) = nchars;
		buf->changes = changes;
		buf->changepos = prevloc;

		/* guess some values for a few other critical options */
		if (!CHARncmp(name, toCHAR("Elvis "), 6) &&
			CHARncmp(name, toCHAR("Elvis untitled"), 14))
		{
			/* probably an internal buffer */
			optpreset(o_internal(buf), True, OPT_HIDE);
			optpreset(o_modified(buf), False, OPT_HIDE);
		}
		else
		{
			/* the filename is probably the same as the buffer name */
			optpreset(o_filename(buf), CHARdup(name), OPT_FREE|OPT_HIDE);
			optpreset(o_internal(buf), False, OPT_HIDE);

			/* Mark it as readonly so the user will have to think
			 * before clobbering an existing file.
			 */
			o_readonly(buf) = True;

			/* Mark it as modified, so the user has to think
			 * before exitting and losing this session file.
			 */
			optpreset(o_modified(buf), True, OPT_HIDE);
		}
		return;
	}

	/* We found the buffer.  Is this the newest version found so far? */
	if (changes > buf->changes)
	{
		/* yes, this is the newest.  Swap this one with the version
		 * currently in the buf struct.  That will leave this version
		 * (the newest) as the current version, and the current
		 * (second newest) in the arguements and ready to be added
		 * to the undo list.
		 */
		tmp = buf->bufinfo;
		buf->bufinfo = bufinfo;
		bufinfo = tmp;
		swaplong(o_buflines(buf), nlines);
		swaplong(o_bufchars(buf), nchars);
		swaplong(buf->changes, changes);
		swaplong(buf->changepos, prevloc);
	}

	/* insert as an "undo" version */
	undo = (struct undo_s *)safealloc(1, sizeof *undo);
	undo->changes = changes;
	undo->changepos = prevloc;
	undo->buflines = nlines;
	undo->bufchars = nchars;
	undo->bufinfo = bufinfo;
	for (scan = buf->undo, lag = NULL;
	     scan && scan->changes > changes;
	     lag = scan, scan = scan->next)
	{
	} 
	undo->next = scan;
	if (lag)
	{
		lag->next = undo;
	}
	else
	{
		buf->undo = undo;
	}
#ifdef DEBUG_ALLOC
	undo->link1 = undohead;
	undohead = undo;
	undo->link2 = NULL;
	if (undo->link1)
		undo->link1->link2 = undo;
	else
		undotail = undo;
	undo->buf = buf;
	undo->undoredo = 'u';
#endif
}

/* Restart a session */
void bufinit()
{
	assert(BUFOPTQTY == QTY(bdesc));

	/* find any buffers left over from a previous edit */
	lowinit(proc);

	/* create the default options buffer, if it doesn't exist already */
	bufdefopts = bufalloc(toCHAR(DEFAULT_BUF), 0, True);
	bufoptions(bufdefopts);
}

/* Create a buffer with a given name.  The buffer will initially be empty;
 * if it is meant to be associated with a particular file, then the file must
 * be copied into the buffer in a separate operation.  If there is already
 * a buffer with the desired name, it returns a pointer to the old buffer
 * instead of creating a new one.
 */
BUFFER bufalloc(name, bufinfo, internal)
	CHAR	*name;	/* name of the buffer */
	_BLKNO_	bufinfo;/* block number describing the buffer (0 to create) */
	BOOLEAN internal;/* is this supposed to be an internal buffer? */
{
	BUFFER	buffer;
	BUFFER	scan, lag;	/* used for inserting new buffer */
	char	unique[255];	/* name of untitled buffer */
	int	i = 1;		/* for generating a name for untitled buffer */
 static long	bufid = 1;	/* for generating bufid values */

	/* if no name was specified, generate a unique untitled name */
	if (!name)
	{
		do
		{
			sprintf(unique, UNTITLED_BUF, i++);
		} while (buffind(toCHAR(unique)));
		name = toCHAR(unique);
	}

	/* see if there's already a buffer with that name */
	buffer = buffind(name);
	if (buffer)
	{
		return buffer;
	}

	/* allocate buffer struct */
	buffer = (BUFFER)safekept(1, sizeof(*buffer));

	/* create a low-level buffer */
	if (bufinfo)
	{
		buffer->bufinfo = bufinfo;
	}
	else
	{
		buffer->bufinfo = lowalloc(tochar8(name));
	}

	/* initialize the buffer options */
	optpreset(o_readonly(buffer), o_defaultreadonly, OPT_HIDE);
	if (bufdefopts)
	{
		/* copy all options except the following: filename bufname bufid
		 * buflines bufchars modified edited newfile internal autotab
		 * partiallastline putstyle.
		 */
		optpreset(buffer->retain, bufdefopts->retain, OPT_HIDE);
		buffer->autoindent = bufdefopts->autoindent;
		buffer->inputtab = bufdefopts->inputtab;
		buffer->tabstop = bufdefopts->tabstop;
		buffer->shiftwidth = bufdefopts->shiftwidth;
		buffer->undolevels = bufdefopts->undolevels;
		buffer->textwidth = bufdefopts->textwidth;
		buffer->autotab = bufdefopts->autotab;
		buffer->readeol = bufdefopts->readeol;
		buffer->locked = bufdefopts->locked;

		/* Strings are tricky, because we may need to allocate a
		 * duplicate of the value.
		 */
		buffer->cc = bufdefopts->cc;
		if (buffer->cc.flags & OPT_FREE)
			o_cc(buffer) = CHARdup(o_cc(bufdefopts));
		buffer->cc.flags |= OPT_HIDE;
		buffer->equalprg = bufdefopts->equalprg;
		if (buffer->equalprg.flags & OPT_FREE)
			o_equalprg(buffer) = CHARdup(o_equalprg(bufdefopts));
		buffer->keywordprg = bufdefopts->keywordprg;
		if (buffer->keywordprg.flags & OPT_FREE)
			o_keywordprg(buffer) = CHARdup(o_keywordprg(bufdefopts));
		buffer->make = bufdefopts->make;
		if (buffer->make.flags & OPT_FREE)
			o_make(buffer) = CHARdup(o_make(bufdefopts));
		buffer->make.flags |= OPT_HIDE;
		buffer->paragraphs = bufdefopts->paragraphs;
		if (buffer->paragraphs.flags & OPT_FREE)
			o_paragraphs(buffer) = CHARdup(o_paragraphs(bufdefopts));
		buffer->sections = bufdefopts->sections;
		if (buffer->sections.flags & OPT_FREE)
			o_sections(buffer) = CHARdup(o_sections(bufdefopts));
		buffer->bufdisplay = bufdefopts->bufdisplay;
		if (buffer->bufdisplay.flags & OPT_FREE)
			o_bufdisplay(buffer) = CHARdup(o_bufdisplay(bufdefopts));
	}
	else /* no default options -- set them explicitly */
	{
		o_inputtab(buffer) = 't';
		o_autotab(buffer) = True;
		o_tabstop(buffer) = 8;
#ifndef OSCCPRG
# define OSCCPRG "cc ($1?$1:$2)"
#endif
		optpreset(o_cc(buffer), toCHAR(OSCCPRG), OPT_UNSAFE);
		optpreset(o_equalprg(buffer), toCHAR("fmt"), OPT_UNSAFE);
		optpreset(o_keywordprg(buffer), toCHAR("ref $1 file:$2"), OPT_UNSAFE);
#ifndef OSMAKEPRG
# define OSMAKEPRG "make $1"
#endif
		optpreset(o_make(buffer), toCHAR(OSMAKEPRG), OPT_UNSAFE);
		o_paragraphs(buffer) = toCHAR("PPppIPLPQPP");
		o_sections(buffer) = toCHAR("NHSHSSSEse");
		o_shiftwidth(buffer) = 8;
		o_undolevels(buffer) = 0;
		o_bufdisplay(buffer) = toCHAR("normal");
		optflags(o_textwidth(buffer)) = OPT_REDRAW;
		o_readeol(buffer) = 't'; /* text */
		o_locked(buffer) = False;
	}

	/* set the name of this buffer, and limit access to some options */
	optpreset(o_bufname(buffer), CHARkdup(name), OPT_HIDE|OPT_LOCK|OPT_FREE);
	optpreset(o_internal(buffer), internal, OPT_HIDE|OPT_LOCK);
	optflags(o_buflines(buffer)) = OPT_HIDE|OPT_LOCK;
	optflags(o_bufchars(buffer)) = OPT_HIDE|OPT_LOCK;
	optflags(o_bufid(buffer)) = OPT_HIDE|OPT_LOCK;
	if (!internal)
	{
		o_bufid(buffer) = bufid++;
	}
	optpreset(o_partiallastline(buffer), False, OPT_HIDE);
	optpreset(o_putstyle(buffer), 'c', OPT_HIDE|OPT_LOCK);

	/* initialize the "willevent" field to a safe value */
	buffer->willevent = -1;

	/* Add the buffer to the linked list of buffers.  Keep it sorted. */
	for (lag = (BUFFER)0, scan = elvis_buffers;
	     scan && CHARcmp(o_bufname(scan), o_bufname(buffer)) < 0;
	     lag = scan, scan = scan->next)
	{
	}
	buffer->next = scan;
	if (lag)
	{
		lag->next = buffer;
	}
	else
	{
		elvis_buffers = buffer;
	}

	/* return the new buffer */
	return buffer;
}

/* Locate a buffer with a particular name.  (Note that this uses the buffer
 * name, not the filename.)  Returns a pointer to the buffer, or NULL for
 * unknown buffers.
 */
BUFFER buffind(name)
	CHAR	*name;	/* name of the buffer to find */
{
	BUFFER	buffer;
	long	bufid;
	CHAR	*b, *n;

	/* scan through buffers, looking for a match */
	for (buffer = elvis_buffers; buffer && CHARcmp(name, o_bufname(buffer)); buffer = buffer->next)
	{
	}

	/* If no exact match, and the name is a quote and some other char, then
	 * try searching for a cut buffer.
	 */
	if (!buffer && name[0] == '"' && name[1] && !name[2])
	{
		buffer = cutbuffer(name[1], False);
	}

	/* If no exact match, and name is the initials of a buffer, then
	 * use that.
	 */
	if (!buffer && name[0] == '"')
	{
		for (buffer = elvis_buffers; buffer; buffer = buffer->next)
		{
			/* if first char doesn't match, skip it */
			if (*o_bufname(buffer) != name[1])
				continue;

			/* check other chars as initials */
			for (b = o_bufname(buffer) + 1, n = name + 2; *b; b++)
			{
				if (b[-1] == ' ')
				{
					if (*b == *n)
						n++;
					else
						break;
				}
			}

			/* if matched, then stop looking */
			if (!*n && !*b)
				break;
		}
	}

	/* If no exact match, and the name looks like a number, then try
	 * searching by bufid.
	 */
	if (!buffer && (calcnumber(name) || (*name == '#' && calcnumber(++name))))
	{
		for (bufid = atol(tochar8(name)), buffer = elvis_buffers;
		     buffer && o_bufid(buffer) != bufid;
		     buffer = buffer->next)
		{
		}
	}

	return buffer;
}


/* Look up the filename of a buffer, given a reference to a scan variable.
 * The scan variable should be in an existing scan context, and should point
 * to a '#' character.  If the filename is found, the scan variable is left
 * on the last character of the # expression, and the filename is returned.
 * Otherwise it returns NULL and the scan variable is undefined.
 *
 * This is used for expanding # in filenames.
 */
CHAR *buffilenumber(refp)
	CHAR	**refp;
{
	long	id;
	BUFFER	buf;
	MARKBUF	tmp;

	assert(*refp && **refp == '#');

	/* remember which buf we're scanning, so we can fix *refp after NULL */
	buf = markbuffer(scanmark(refp));
	if (buf)
	{
		/* the scanning context is in a buffer */

		/* get the buffer number */
		for (id = 0; scannext(refp) && isdigit(**refp); )
		{
			id = id * 10 + **refp - '0';
		}

		/* move back one character, so *refp points to final char. This
		 * can be tricky if we bumped into the end of the buffer.
		 */
		if (*refp)
			(void)scanprev(refp);
		else
			(void)scanseek(refp, marktmp(tmp, buf, o_bufchars(buf) - 1));
	}
	else
	{
		/* The scanning context is in a string. */

		/* get the buffer number */
		for (id = 0; isdigit(*++*refp); )
		{
			id = id * 10 + **refp - '0';
		}
		--*refp;
	}

	/* if 0, then use alternate file */
	if (id == 0)
		return o_previousfile;

	/* try to find a buffer with that value */
	for (buf = elvis_buffers; buf && o_bufid(buf) != id; buf = buf->next)
	{
	}
	return o_filename(buf);
}



/* Read a text file or filter output into a specific place in the buffer */
BOOLEAN bufread(mark, rfile)
	MARK	mark;	/* where to insert the new next */
	char	*rfile;	/* file to read, or !cmd for filter */
{
	BUFFER	buf;		/* the buffer we're reading into */
	long	offset;		/* offset of mark before inserting text */
	long	origlines;	/* original number of lines in file */
	CHAR	chunk[4096];	/* I/O buffer */
	int	nread;		/* number of bytes in chunk[] */
	BOOLEAN	newbuf;		/* is this a new buffer? */

	/* initialize some vars */
	buf = markbuffer(mark);
	newbuf = (BOOLEAN)(o_bufchars(buf) == 0 && rfile[0] != '!' && (o_verbose || !o_internal(buf)));
	origlines = o_buflines(buf);

	/* open the file/filter */
	if (!ioopen(rfile, 'r', False, False, o_readeol(markbuffer(mark))))
	{
		/* failed -- error message already given */
		return False;
	}

	/* read the text */
	if (newbuf)
		msg(MSG_STATUS, "[s]reading $1", rfile);
	while ((nread = ioread(chunk, QTY(chunk))) > 0)
	{
		if (guipoll(False))
		{
			ioclose();
			return False;
		}
		offset = markoffset(mark);
		bufreplace(mark, mark, chunk, nread);
		marksetoffset(mark, offset + nread);
	}
	ioclose();
	if (newbuf)
		msg(bufmsgtype, "[sdd]read $1, $2 lines, $3 chars", rfile,
					o_buflines(buf), o_bufchars(buf));
	else if (!o_internal(buf))
		msg(bufmsgtype, "[d]read $1 lines", o_buflines(buf) - origlines);

	return True;
}

/* Create a buffer for a given file, and then load the file.  Return a pointer
 * to the buffer.  If the file can't be read for some reason, then complain and
 * leave the buffer empty, but still return the empty buffer.
 *
 * If the buffer already exists and contains text, then the "reload" option
 * can be used to force it to discard that text and reload the buffer; when
 * "reload" is False, it would leave the buffer unchanged instead.
 */
BUFFER bufload(bufname, filename, reload)
	CHAR	*bufname;	/* name of buffer, or NULL to derive from filename */
	char	*filename;	/* name of file to load into a buffer */
	BOOLEAN	reload;		/* load from file even if previously loaded? */
{
	BUFFER	buf;
	MARKBUF	top;
	MARKBUF	end;
	BUFFER	initbuf;	/* buffer containing the initialization script */
	BOOLEAN	oldthen;
	int	i;


	/* Create a buffer, whose name defaults to the same as this file */
	buf = bufalloc(bufname ? bufname : toCHAR(filename), 0, (BOOLEAN)(bufname != NULL));

	/* Does the buffer already contain text? */
	if (o_bufchars(buf) > 0)
	{
		/* If we aren't supposed to reload, then just return the
		 * buffer as-is.
		 */
		if (!reload)
			return buf;

		/* Save the text as an "undo" version, and then delete it */
		if (windefault && markbuffer(windefault->cursor) == buf)
			bufwilldo(windefault->cursor, True);
		else
			bufwilldo(marktmp(top, buf, 0), True);
		bufreplace(marktmp(top, buf, 0), marktmp(end, buf, o_bufchars(buf)), (CHAR *)0, 0);
	}

	/* Set the buffer's options */
	optpreset(o_filename(buf), CHARkdup(filename), OPT_HIDE|OPT_LOCK|OPT_FREE);
	optpreset(o_edited(buf), True, OPT_HIDE);
	o_readonly(buf) = o_defaultreadonly;
	optpreset(o_newfile(buf), False, OPT_HIDE);
	switch (dirperm(filename))
	{
	  case DIR_INVALID:
	  case DIR_BADPATH:
	  case DIR_NOTFILE:
	  case DIR_UNREADABLE:
	  case DIR_READONLY:
		o_readonly(buf) = True;
		break;

	  case DIR_NEW:
		o_newfile(buf) = True;
		break;

	  case DIR_READWRITE:
		/* nothing needed */
		break;
	}

	/* Execute the "before read"  script, if it exists.  If the script
	 * fails, then don't load the newly-created buffer.
	 */
	if (!o_internal(buf))
	{
		initbuf = buffind(toCHAR(BEFOREREAD_BUF));
		if (initbuf)
		{
			/* make the buffer available to :set */
			bufoptions(buf);

			/* execute the script */
			oldthen = exthenflag;
			if (experform(windefault, marktmp(top, initbuf, 0),
				marktmp(end, initbuf, o_bufchars(initbuf))) != RESULT_COMPLETE)
			{
				exthenflag = oldthen;
				return buf;
			}
			exthenflag = oldthen;
		}
	}

	/* read the file's contents into the buffer */
	if (o_newfile(buf))
	{
		msg(bufmsgtype, "[sdd]$1 [NEW FILE]", filename);
	}
	else if (!bufread(marktmp(top, buf, 0), filename))
	{
		o_edited(buf) = False;
		return buf;
	}

	/* if it ends with a partial last line, then add a newline */
	(void)marktmp(end, buf, o_bufchars(buf) - 1);
	if (o_bufchars(buf) > 0L && scanchar(&end) != '\n')
	{
		o_partiallastline(buf) = True;
		marksetoffset(&end, markoffset(&end) + 1);
		bufreplace(&end, &end, toCHAR("\n"), 1L);
	}
	else
		o_partiallastline(buf) = False;

	/* set other options to describe the file */
	o_modified(buf) = False;
	optpreset(o_errlines(buf), o_buflines(buf), OPT_HIDE);
#ifdef PROTOCOL_FTP
	if (!strncmp(filename, "ftp:", 4))
		o_readonly(buf) = (BOOLEAN)(ftpperms == DIR_READONLY);
#endif

	/* Restore the marks to their previous offsets.  Otherwise any marks
	 * which refer to this buffer will be set to the end of the file.
	 * Restoring them isn't perfect, but it beats setting them all to EOF!
	 * (Note: New buffers won't have an "undo" version.)
	 */
	if (buf->undo)
	{
		for (i = 0; i < QTY(buf->undo->offset); i++)
		{
			/* ignore unset marks, or marks into other buffers*/
			if (buf->undo->offset[i] < 0)
				continue;
			assert(markbuffer(namedmark[i]) == buf);

			/* marks past the new end should be freed */
			if (buf->undo->offset[i] >= o_bufchars(buf))
			{
				markfree(namedmark[i]);
				continue;
			}

			/* other marks should have their offsets restored
			 * to what they were before the buffer was loaded
			 */
			marksetoffset(namedmark[i], buf->undo->offset[i]);
		}
	}

	/* execute the file initialization script, if it exists */
	if (!o_internal(buf))
	{
		initbuf = buffind(toCHAR(AFTERREAD_BUF));
		if (initbuf)
		{
			/* make the buffer available to :set */
			bufoptions(buf);

			/* Execute the script's contents. */
			oldthen = exthenflag;
			(void)experform(windefault, marktmp(top, initbuf, 0),
				marktmp(end, initbuf, o_bufchars(initbuf)));
			exthenflag = oldthen;
		}
	}

#ifdef FEATURE_SHOWTAG
	/* load the tag definitions for this file */
	tebuilddef(buf);
#endif

	/* set the initial cursor offset to 0 */
	buf->docursor = 0L;

	return buf;
}


/* This function searches through a path for a file to load.  It then loads
 * that file into a buffer and returns the buffer.  If the file couldn't be
 * located, it returns NULL instead.  If a buffer already exists with the given
 * name, then it returns that buffer without attempting to load anything.
 */
BUFFER bufpath(path, filename, bufname)
	CHAR	*path;		/* path to search through */
	char	*filename;	/* file to search for */
	CHAR	*bufname;	/* name of buffer to store the file */
{
	char	*pathname;	/* full pathname of the loaded file */
	char	pathdup[256];	/* local copy of pathname */
	BUFFER	buf;

	/* if the buffer already exists, return it immediately */
	buf = buffind(bufname);
	if (buf)
	{
		return buf;
	}

	/* try to find the file */
	pathname = iopath(tochar8(path), filename, False);
	if (!pathname)
	{
		return (BUFFER)0;
	}

	/* we need a local copy of the pathname, because bufload() will also
	 * call iopath() to find the "elvis.brf" and "elvis.arf" files, and
	 * iopath() only has a single static buffer that it uses for returning
	 * the found pathname.  We don't want our pathname clobbered.
	 */
	strcpy(pathdup, pathname);

	/* load the file */
	buf = bufload(bufname, pathdup, True);
	return buf;
}


/* This function deletes the oldest undo versions of a given buffer */
static void freeundo(buffer)
	BUFFER	buffer;	/* buffer to be cleaned */
{
	struct undo_s *undo, *other;
	int	      i;

	checkundo("before freundo()");

	/* locate the most recent doomed version */
	i = o_undolevels(buffer);
	if (i < 1) i++;
	for (other = NULL, undo = buffer->undo;
	     i > 0 && undo;
	     i--, other = undo, undo = undo->next)
	{
	}

	/* if none are doomed, return now */
	if (!undo)
	{
		return;
	}

	/* Remove the most recent doomed version (and all following versions)
	 * from the linked list of undo versions.
	 */
	if (other)
	{
		other->next = NULL;
	}
	else
	{
		buffer->undo = NULL;
	}

	/* delete each doomed version */
	for (; undo; undo = other)
	{
		other = undo->next;
		/* free the lowbuf and the (struct undo_s) structure */
		lowfree(undo->bufinfo);
#ifdef DEBUG_ALLOC
		removeundo(undo);
#endif
		safefree(undo);
	}
	checkundo("after freeundo");
}



/* Free a buffer which was created via bufalloc(). */
void buffree(buffer)
	BUFFER	buffer;	/* buffer to be destroyed */
{
	BUFFER	scan, lag;
	struct undo_s *undo;

	assert(buffer != bufdefopts);
	checkundo("before buffree");

	/* if any window is editing this buffer, then fail */
	if (wincount(buffer) > 0)
	{
		return;
	}

	/* transfer any marks to the dummy "bufdefopts" buffer */
	while (buffer->marks)
	{
		marksetoffset(buffer->marks, 0L);
		marksetbuffer(buffer->marks, bufdefopts);
	}

#ifdef FEATURE_SHOWTAG
	/* free the array of tag definitions */
	tefreedef(buffer);
#endif

	/* free any undo/redo versions of this buffer */
	while (buffer->undo)
	{
		undo = buffer->undo;
		buffer->undo = undo->next;
		lowfree(undo->bufinfo);
#ifdef DEBUG_ALLOC
		removeundo(undo);
#endif
		safefree(undo);
	}
	while (buffer->redo)
	{
		undo = buffer->redo;
		buffer->redo = undo->next;
		lowfree(undo->bufinfo);
#ifdef DEBUG_ALLOC
		removeundo(undo);
#endif
		safefree(undo);
	}
	if (buffer->undolnptr)
	{
		lowfree(buffer->undolnptr->bufinfo);
#ifdef DEBUG_ALLOC
		removeundo(buffer->undolnptr);
#endif
		safefree(buffer->undolnptr);
		buffer->undolnptr = NULL;
	}
	assert(buffer->undo == NULL && buffer->redo == NULL);

	/* locate the buffer in the linked list */
	for (lag = NULL, scan = elvis_buffers; scan != buffer; lag = scan, scan = scan->next)
	{
		assert(scan->next);
	}

	/* remove it from the linked list */
	if (lag)
	{
		lag->next = scan->next;
	}
	else
	{
		elvis_buffers = scan->next;
	}

	/* free the values of any string options which have been set */
	optfree(QTY(bdesc), &buffer->filename);

	/* free any marks in this buffer */
	while (buffer->marks)
	{
		markfree(buffer->marks);
	}

	/* free the low-level block */
	lowfree(buffer->bufinfo);

	/* free the buffer struct itself */
	safefree(buffer);
	checkundo("after buffree");
}


/* Free a buffer, if possible without losing anything important.  Return
 * True if freed, False if retained.  If "force" is True, it tries harder.
 */
BOOLEAN bufunload(buf, force, save)
	BUFFER	buf;	/* buffer to be unloaded */
	BOOLEAN	force;	/* if True, try harder */
	BOOLEAN	save;	/* if True, maybe save even if noautowrite */
{
	MARKBUF	top, bottom;

	/* if "internal" then retain it for now */
	if (o_internal(buf))
	{
		return False;
	}

	/* if being used by some window, then retain it */
	if (wincount(buf) > 0)
	{
		return False;
	}

	/* If supposed to retain, then keep it unless "force" is True.
	 * If this is a temporary session, then no buffers will be retained,
	 * so we should fail in that situation too.
	 */
	if (o_retain(buf) && !force && !o_tempsession)
	{
		return False;
	}

	/* if not modified, then discard it */
	if (!o_modified(buf))
	{
		buffree(buf);
		return True;
	}

	/* if readonly, or no known filename then free it if "force" or
	 * retain it if not "force"
	 */
	if (o_readonly(buf) || o_filename(buf) == NULL)
	{
		if (force)
		{
			buffree(buf);
		}
		return force;
	}

	/* Try to save the buffer to a file */
	if (save && o_filename(buf)
	 && bufwrite(marktmp(top, buf, 0), marktmp(bottom, buf, o_bufchars(buf)), tochar8(o_filename(buf)), force))
	{
		buffree(buf);
		return True;
	}
	return False;
}


/* Return True if "buf" can be deleted without loosing data, or False if it
 * can't -- in which case it also emits a message describing why.  NOTE THAT
 * YOU ALSO NEED TO MAKE SURE THE BUFFER ISN'T BEING EDITED IN A WINDOW.
 *
 * This function has side-effects.  If the buffer has been modified, it may
 * try to write the buffer out to a file.
 */
BOOLEAN bufsave(buf, force, mustwr)
	BUFFER	buf;	/* the buffer to write */
	BOOLEAN	force;	/* passed to bufwrite() if writing is necessary */
	BOOLEAN	mustwr;	/* write to file even if buffer isn't modified */
{
	MARKBUF	top, bottom;

	/* Can never "save" or delete the internal buffers */
	if (o_internal(buf))
	{
		msg(MSG_ERROR, "[s]$1 is used internally by elvis", o_bufname(buf));
		return False;
	}

	/* If writing wasn't explicitly demanded, and isn't needed, then
	 * return True without doing anything else.
	 */
	if (!mustwr && !o_modified(buf))
	{
		return True;
	}

	/* We know that we need to write this buffer.  If it has no filename
	 * then we can't write it.
	 */
	if (!o_filename(buf))
	{
		msg(MSG_ERROR, "no file name");
		return False;
	}

	/* try to write the buffer out to its file */
	return bufwrite(marktmp(top, buf, 0),
			marktmp(bottom, buf, o_bufchars(buf)),
			tochar8(o_filename(buf)), force);
}



/* Write a buffer, or part of a buffer, out to a file.  Return True if
 * successful, or False if error.
 */
BOOLEAN bufwrite(from, to, wfile, force)
	MARK	from;	/* start of text to write */
	MARK	to;	/* end of text to write */
	char	*wfile;	/* output file, ">>file" to append, "!cmd" to filter */
	BOOLEAN	force;	/* write even if file already exists */
{
	BUFFER	buf = markbuffer(from);
	BUFFER	initbuf;	/* one of the file initialization buffers */
	MARKBUF	top;		/* the endpoints of initbuf */
	MARKBUF	next;		/* used for determining append location */
	CHAR	*cp;		/* used for scanning through file */
	BOOLEAN	append;		/* If True, we're appending */
	BOOLEAN	filter;		/* If True, we're writing to a filter */
	BOOLEAN	wholebuf;	/* if True, we're writing the whole buffer */
	BOOLEAN	samefile;	/* If True, we're writing the buffer to its original file */
	BOOLEAN	oldthen;
	int	bytes;

	assert(from && to && wfile);
	assert(markbuffer(from) == markbuffer(to));
	assert(markoffset(from) >= 0);
	assert(markoffset(from) <= markoffset(to));
	assert(markoffset(to) <= o_bufchars(buf));

	/* Determine some characteristics of this write */
	append = (BOOLEAN)(wfile[0] == '>' && wfile[1] == '>');
	filter = (BOOLEAN)(wfile[0] == '!');
	wholebuf = (BOOLEAN)(markoffset(from) == 0 && markoffset(to) == o_bufchars(buf));
	samefile = (BOOLEAN)(o_filename(buf) && !strcmp(wfile, tochar8(o_filename(buf))));

	/* if we're appending, skip the initial ">>" */
	if (append)
	{
		for (wfile += 2; isspace(*wfile); wfile++)
		{
		}
	}

	/* If writing to the same file, and it is a readonly file, then fail
	 * unless we're forcing a write.
	 */
	if (!filter && wholebuf && samefile && o_readonly(buf) && !force)
	{
		msg(MSG_ERROR, "[s]$1 readonly" , wfile);
		return False;
	}

	/* If this is supposed to be a new file, or we're writing to
	 * a name other than what was originally loaded, and we aren't
	 * forcing a write, then make sure the file doesn't already
	 * exist.
	 */
	if ((o_newfile(buf) || !o_edited(buf) || !samefile || !wholebuf)
	  && !force
	  && !append
	  && !filter
	  && urlperm(wfile) != DIR_NEW)
	{
		msg(MSG_ERROR, "[s]$1 exists", wfile);
		return False;
	}

	/* If we're writing the whole file back over itself, then execute the
	 * "before write" script if it exists.  If this fails and "force" isn't
	 * true, then fail.
	 */
	if (wholebuf && samefile && !filter && !append)
	{
		initbuf = buffind(toCHAR(BEFOREWRITE_BUF));
		if (initbuf)
		{
			/* make the buffer be the default buffer */
			bufoptions(buf);

			/* execute the script */
			oldthen = exthenflag;
			if (experform(windefault, marktmp(top, initbuf, 0),
				marktmp(next, initbuf, o_bufchars(initbuf))) != RESULT_COMPLETE
			    && !force)
			{
				exthenflag = oldthen;
				return False;
			}
			exthenflag = oldthen;
		}
	}

	/* If "partiallastline" is set (indicating that the original file
	 * wasn't terminated with a newline so elvis added one) then we
	 * probably don't want to write that newline.  However, if this is a
	 * text-mode write or the added newline isn't there anymore then we
	 * want to write the whole file.
	 */
	if (wholebuf)
	{
		next = *to;
		marksetoffset(&next, markoffset(&next) - 1);
		if (!o_partiallastline(buf)
		 || (o_writeeol != 'b' && o_readeol(markbuffer(from)) != 'b')
		 || o_bufchars(markbuffer(from)) == 0
		 || scanchar(&next) != '\n')
			o_partiallastline(markbuffer(from)) = False;
		else
			marksetoffset(to, markoffset(&next));
	}

	/* Try to write the file */
	if (ioopen(wfile, append ? 'a' : 'w', False, True,
		o_writeeol == 's' || o_readeol(markbuffer(from)) == 'b'
			? o_readeol(markbuffer(from))
			: o_writeeol))
	{
		if (wholebuf && !filter)
		{
			msg(MSG_STATUS, "[s]writing $1", wfile);
		}
		next = *from;
		scanalloc(&cp, &next);
		assert(cp);
		bytes = 1;
		do
		{
			/* check for ^C */
			if (guipoll(False))
			{
				ioclose();
				scanfree(&cp);
				return False;
			}

			bytes = scanright(&cp);
			if (markoffset(&next) + bytes > markoffset(to))
			{
				bytes = (int)(markoffset(to) - markoffset(&next));
			}
			if (iowrite(cp, bytes) < bytes)
			{
				msg(MSG_ERROR, (wfile[0] == '!') ? "broken pipe" : "disk full");
				ioclose();
				scanfree(&cp);
				return False;
			}
			markaddoffset(&next, bytes);
			scanseek(&cp, &next);
		} while (cp != NULL && markoffset(&next) < markoffset(to));
		ioclose();
		scanfree(&cp);

		if (!filter)
		{
			if (append)
				msg(bufmsgtype, "[ds]appended $1 lines to $2",
					markline(to) - markline(from), wfile);
			else
				msg(bufmsgtype, "[sdd]wrote $1, $2 lines, $3 chars",
					wfile, markline(to) - markline(from),
					markoffset(to) - markoffset(from));
		}
	}
	else
	{
		/* We had an error, and already wrote the error message */
		return False;
	}

	/* Execute the "after write" script. */
	if (wholebuf && samefile && !filter && !append)
	{
		initbuf = buffind(toCHAR(AFTERWRITE_BUF));
		if (initbuf)
		{
			/* make the buffer be the default buffer */
			bufoptions(buf);

			/* execute the script */
			oldthen = exthenflag;
			(void)experform(windefault, marktmp(top, initbuf, 0),
				marktmp(next, initbuf, o_bufchars(initbuf)));
			exthenflag = oldthen;
		}
	}

	/* Writing the whole file has some side-effects on options */
	if (wholebuf && !append && !filter)
	{
		/* if it had no filename before, it has one now */
		if (!o_filename(buf) && wholebuf)
		{
			o_filename(buf) = CHARdup(toCHAR(wfile));
			optflags(o_filename(buf)) |= OPT_FREE;
			buftitle(buf, toCHAR(wfile));
		}

		/* buffer is no longer modified */
		o_modified(buf) = False;
		o_newfile(buf) = False;
		
		/* if the original file was overwritten, then reset the
		 * readonly flag because apparently the file isn't readonly.
		 */
		if (!CHARcmp(o_filename(buf), toCHAR(wfile)))
		{
			o_readonly(buf) = False;
			o_edited(buf) = True;
		}
	}

	/* success! */
	return True;
}


/* Make "buf" be the default buffer.  The default buffer is the one whose
 * options are available to the :set command.
 */
void bufoptions(buf)
	BUFFER	buf;	/* the buffer to become the new default buffer */
{
	/* if same as before, then do nothing */
	if (buf == bufdefault)
	{
		return;
	}

	/* if there is a previous buffer, then delete its options */
	if (bufdefault)
	{
		optdelete(&bufdefault->filename);
	}

	/* make this buffer be the default */
	bufdefault = buf;

	/* if bufdefault is not NULL, then insert its options */
	if (buf)
	{
		optinsert("buf", QTY(bdesc), bdesc, &buf->filename);
	}
}


/* This function changes the name of a buffer.  If the buffer happens to
 * be the main buffer of one or more windows, then it will also retitle
 * those windows.
 */
void buftitle(buffer, title)
	BUFFER	buffer;	/* the buffer whose name is to be changed */
	CHAR	*title;	/* the new name of the buffer */
{
	WINDOW	win;

	/* change the name on disk */
	lowtitle(buffer->bufinfo, title);

	/* change the name in RAM */
	safefree(o_bufname(buffer));
	o_bufname(buffer) = CHARkdup(title);

	/* change the window titles, if the gui supports that */
	if (gui->retitle)
	{
		for (win = winofbuf((WINDOW)0, buffer);
		     win;
		     win = winofbuf(win, buffer))
		{
			(*gui->retitle)(win->gw, tochar8(o_bufname(buffer)));
		}
	}
}


/* Set the buffer's flag that will eventually cause an undo version of to
 * be saved.
 */
void bufwilldo(cursor, will)
	MARK	cursor;	/* where to put cursor if we return to this "undo" version */
	BOOLEAN	will;	/* True to set flag, False to merely remember cursor */
{
	if (will && markbuffer(cursor)->willevent != eventcounter)
	{
		markbuffer(cursor)->willdo = True;
		markbuffer(cursor)->willevent = eventcounter;
	}
	markbuffer(cursor)->docursor = markoffset(cursor);
}


/* allocate an "undo" version for a buffer, but don't insert it into the
 * buffer's undo list.  This function is called only from the bufdo() function,
 * which handles any other processing that may be necessary.
 */
static struct undo_s *allocundo(buf)
	BUFFER	buf;
{
	struct undo_s *undo;
	int	i;

	/* allocate a structure */
	undo = (struct undo_s *)safealloc(1, sizeof *undo);
#ifdef DEBUG_ALLOC
	undo->link1 = undohead;
	undohead = undo;
	undo->link2 = NULL;
	if (undo->link1)
		undo->link1->link2 = undo;
	else
		undotail = undo;
	undo->buf = buf;
	undo->undoredo = 'u';
#endif

	/* fill it in */
	undo->changepos = buf->changepos = buf->docursor;
	undo->buflines = o_buflines(buf);
	undo->bufchars = o_bufchars(buf);
	for (i = 0; i < QTY(namedmark); i++)
	{
		if (namedmark[i] && markbuffer(namedmark[i]) == buf)
		{
			undo->offset[i] = markoffset(namedmark[i]);
		}
		else
		{
			undo->offset[i] = -1;
		}
	}
	undo->bufinfo = lowdup(buf->bufinfo);
	undo->next = NULL;

	/* return it */
	return undo;
}


/* Save an "undo" version of a buffer */
static void bufdo(buf, wipe)
	BUFFER	buf;	/* buffer to make an "undo" version for */
	BOOLEAN	wipe;	/* if True, then delete all "redo" versions */
{
	struct undo_s	*undo;
	long		linenum;

	checkundo("before bufdo");

	/* never save an undo version of an internal buffer */
	if (o_internal(buf))
		return;

	/* allocate an undo structure and fill it in */
	undo = allocundo(buf);

	/* insert it into the buffer's "undo" list */
	undo->next = buf->undo;
	buf->undo = undo;

	checkundo("in bufdo, before changing undolnptr");

	/* If this is on a different line from previous change, then allocate
	 * another undo structure to use as the line-undo version.
	 */
	(void)lowoffset(undo->bufinfo, undo->changepos,
		(COUNT *)0, (COUNT *)0, (LBLKNO *)0, &linenum);
	if (linenum != buf->undoline)
	{
		/* free the old line-undo version, if any */
		if (buf->undolnptr)
		{
			lowfree(buf->undolnptr->bufinfo);
#ifdef DEBUG_ALLOC
			removeundo(buf->undolnptr);
#endif
			safefree(buf->undolnptr);
		}

		/* allocate & store the new line-undo version */
		buf->undolnptr = allocundo(buf);
		buf->undoline = linenum;
#ifdef DEBUG_ALLOC
		buf->undolnptr->undoredo = 'l';
#endif
	}

	checkundo("in bufdo, after changing undolnptr");

	/* discard the redo versions, if we're supposed to */
	if (wipe)
	{
		while (buf->redo && buf->redo != buf->undolnptr)
		{
			undo = buf->redo;
			buf->redo = undo->next;
			lowfree(undo->bufinfo);
#ifdef DEBUG_ALLOC
			removeundo(undo);
#endif
			safefree(undo);
		}

		checkundo("in bufdo, after wiping the redo versions");
	}

	/* discard the oldest version[s] from the undo list */
	freeundo(buf);

	/* make sure this is written to disk */
	if (!o_internal(buf) && eventcounter > 2)
		sessync();
}

/* Recall a previous "undo" version of a given buffer.  The "back" argument
 * should be positive to undo, negative to redo, or 0 to undo all changes
 * to the current line.  Returns the cursor offset if successful, or -1 if
 * the requested undo version doesn't exist.
 */
long bufundo(cursor, back)
	MARK	cursor;	/* buffer to be undone, plus cursor offset of current version */
	long	back;	/* number of versions back (negative to redo) */
{
	struct undo_s	*undo;
	struct undo_s	*tmp;
	long		i;
	BUFFER		buffer;
	MARKBUF		from, to;
	long		delta;
	long		origulev;

	checkundo("before bufundo");

	/* locate the desired undo version */
	buffer = markbuffer(cursor);
	if (back == 0)
	{
		/* line undo */
		undo = buffer->undolnptr;
	}
	else if (o_undolevels(buffer) == 0)
	{
		/* Can only oscillate between previous version and this one,
		 * but it has the advantage that <u> and <^R> both do exactly
		 * the same thing.
		 */
		if (buffer->redo)
			undo = buffer->redo, back = -1;
		else
			undo = buffer->undo, back = 1;
	}
	else if (back > 0)
	{
		/* undo */
		for (i = back, undo = buffer->undo; undo && i > 1; i--, undo = undo->next)
		{
		}
	}
	else
	{
		/* redo */
		for (i = -back, undo = buffer->redo; undo && i > 1; i--, undo = undo->next)
		{
		}
	}

	/* if the requested version doesn't exist, then fail */
	if (!undo)
	{
		return -1;
	}

	/* save the current version as either an undo version or a redo version,
	 * so we can revert to it.  Note that we increase the number of undo
	 * levels temporarily, so the oldest undo version won't be discarded
	 * yet.
	 */
	origulev = o_undolevels(buffer);
	o_undolevels(buffer)++;
	if (o_undolevels(buffer) < 2)
		o_undolevels(buffer) = 2;
	bufwilldo(marktmp(from, buffer, undo->changepos), True);
	bufdo(buffer, False);
	if (back > 0)
	{
		/* undoing: move from "undo" to "redo" */
		while (buffer->undo != undo)
		{
			assert(buffer->undo);
			tmp = buffer->undo;
			buffer->undo = tmp->next;
			tmp->next = buffer->redo;
			buffer->redo = tmp;
#ifdef DEBUG_ALLOC
			tmp->undoredo = 'r';
#endif
		}

		/* remove the selected version from the "undo" list */
		buffer->undo = undo->next;
	}
	else if (back < 0)
	{
		/* redoing: move from "redo" to "undo" */
		while (buffer->redo != undo)
		{
			assert(buffer->redo);
			tmp = buffer->redo;
			buffer->redo = tmp->next;
			tmp->next = buffer->undo;
			buffer->undo = tmp;
#ifdef DEBUG_ALLOC
			tmp->undoredo = 'u';
#endif
		}

		/* remove the selected version from the "redo" list */
		buffer->redo = undo->next;
	}
	else
	{
		/* line-undo: Remove the selected undo version from the
		 * undolnptr pointer.
		 */
		buffer->undolnptr = NULL;
		buffer->undoline = 0;
	}

	/* replace the current version with the selected undo version */
	lowfree(buffer->bufinfo);
	buffer->bufinfo = undo->bufinfo;
	delta = undo->bufchars - o_bufchars(buffer);
	buffer->changepos = undo->changepos;
	o_buflines(buffer) = undo->buflines;
	o_bufchars(buffer) = undo->bufchars;
	buffer->changes++;
	o_modified(buffer) = (BOOLEAN)!o_internal(buffer);
	/*!!! But since internal buffers never store any undo versions, if
	 * we get here we know that o_internal(buffer) is False.
	 */

	/* Adjust the values of any marks.  Most marks can be fixed just by
	 * calling markadjust(), but the named marks' old values are stored in
	 * the undo buffer and we can recall them exactly.
	 */
	if (delta < 0)
	{
		markadjust(marktmp(from, buffer, undo->changepos),
			marktmp(to, buffer, undo->changepos - delta),
			delta);
	}
	else
	{
		markadjust(marktmp(from, buffer, undo->changepos), &from,delta);
	}
	for (i = 0; i < QTY(namedmark); i++)
	{
		if (undo->offset[i] >= 0
		 && (!namedmark[i] || markbuffer(namedmark[i]) == buffer))
		{
			if (namedmark[i])
				markfree(namedmark[i]);
			namedmark[i] = markalloc(buffer, undo->offset[i]);
		}
	}
#ifdef DISPLAY_MARKUP
	dmmuadjust(marktmp(from, buffer, 0), marktmp(to, buffer, o_bufchars(buffer)), 0);
#endif

	/* We can free the undo_s structure now. */
#ifdef DEBUG_ALLOC
	removeundo(undo);
#endif
	safefree(undo);

	/* Okay to free the oldest "undo" version now */
	o_undolevels(buffer) = origulev;
	freeundo(buffer);	/* Is this really necessary? */

	/* Return the offset of the last change, so the cursor can be moved
	 * there.  Never return a point past the end of the buffer, though.
	 */
	checkundo("after bufundo");
	if (o_bufchars(buffer) == 0)
		buffer->changepos = 0;
	else if (buffer->changepos >= o_bufchars(buffer))
		buffer->changepos = o_bufchars(buffer) - 1;
	return buffer->changepos;
}

/* This function replaces part of a buffer with new text.  In addition to
 * replacement, it can also be used to implement insertion (by having "from"
 * and "to" be identical) or deletion (by having "newlen" be zero).
 *
 * It uses markadjust() to automatically update marks.  If the buffer's
 * "willdo" flag is set, then it will automatically create an "undo" version
 * of the buffer before making the change.
 */
void bufreplace(from, to, newp, newlen)
	MARK	from;	/* starting position of old text */
	MARK	to;	/* ending position of old text */
	CHAR	*newp;	/* pointer to new text (in RAM) */
	long	newlen;	/* length of new text */
{
	long	chglines;
	long	chgchars;


	assert(markbuffer(from) == markbuffer(to) && newlen >= 0);

	/* if the destination's "willdo" flag is set, then save an "undo"
	 * version of it before doing the change
	 */
	if (markbuffer(from)->willdo)
	{
		bufdo(markbuffer(from), True);
		markbuffer(from)->willdo = False;
	}

	/* make the change to the buffer contents */
	if (markoffset(from) == markoffset(to))
	{
		/* maybe we aren't really changing anything? */
		if (newlen == 0)
		{
			return;
		}

		/* we're inserting */
		chglines = lowinsert(markbuffer(from)->bufinfo, markoffset(from), newp, newlen);
	}
	else if (newlen == 0)
	{
		/* we're deleting */
		chglines = lowdelete(markbuffer(from)->bufinfo, markoffset(from), markoffset(to));
	}
	else
	{
		/* we're replacing */
		chglines = lowreplace(markbuffer(from)->bufinfo, markoffset(from), markoffset(to), newp, newlen);
	}

	/* adjust the buffer totals */
	chgchars = newlen - (markoffset(to) - markoffset(from));
	o_buflines(markbuffer(from)) += chglines;
	o_bufchars(markbuffer(from)) += chgchars;
	o_modified(markbuffer(from)) = (BOOLEAN)!o_internal(markbuffer(from));
	markbuffer(from)->changes++;

	/* adjust the marks */
#ifdef DISPLAY_MARKUP
	dmmuadjust(from, to, chgchars);
#endif
	markadjust(from, to, chgchars);
}

/* Copy part of one buffer into another.  "dst" is the destination (where
 * the pasted text will be inserted), and "from" and "to" describe the
 * portion of the source buffer to insert.
 *
 * This calls markadjust() to automatically adjust marks.  If the destination
 * buffer's "willdo" flag is set, it will save an "undo" version before
 * making the change.
 */
void bufpaste(dst, from, to)
	MARK	dst;	/* destination */
	MARK	from;	/* start of source */
	MARK	to;	/* end of source */
{
	long	chglines;
	long	chgchars;


	assert(markbuffer(from) == markbuffer(to));

	/* if the destination's "willdo" flag is set, then save an "undo"
	 * version of it before doing the paste
	 */
	if (markbuffer(dst)->willdo)
	{
		bufdo(markbuffer(dst), True);
		markbuffer(dst)->willdo = False;
	}

	/* make the change to the buffer's contents */
	chglines = lowpaste(markbuffer(dst)->bufinfo, markoffset(dst),
		markbuffer(from)->bufinfo, markoffset(from), markoffset(to));

	/* adjust the destination's counters */
	chgchars = markoffset(to) - markoffset(from);
	o_buflines(markbuffer(dst)) += chglines;
	o_bufchars(markbuffer(dst)) += chgchars;
	o_modified(markbuffer(dst)) = (BOOLEAN)!o_internal(markbuffer(dst));
	markbuffer(dst)->changes++;

	/* adjust marks */
	markadjust(dst, dst, chgchars);
}


/* Copy a section of some buffer into dynamically-allocated RAM, and append
 * a NUL to the end of the copy.  The calling function must call safefree()
 * on the returned memory.
 */
CHAR *bufmemory(from, to)
	MARK	from, to;	/* the section of the buffer to fetch */
{
	CHAR	*memory;	/* the allocated memory */
	CHAR	*scan, *build;	/* used for copying text from buffer to memory */
	long	i;

	assert(markbuffer(from) == markbuffer(to)
		&& markoffset(from) <= markoffset(to));

	/* allocate space for the copy */
	i = markoffset(to) - markoffset(from);
	memory = (CHAR *)safealloc((int)(i + 1), sizeof(CHAR));

	/* copy the text into it */
	for (scanalloc(&scan, from), build = memory; i > 0; scannext(&scan), i--)
	{
		assert(scan);
		*build++ = *scan;
	}
	scanfree(&scan);
	*build = '\0';

	return memory;
}
