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

char id_mark[] = "$Id: mark.c,v 2.12 1998/10/04 00:45:29 steve Exp $";

#include "elvis.h"


MARK namedmark[26];

/* Allocate a mark, pointing to a specific location in a specific buffer.  As changes are
 * make to the buffer, the mark's offset into that buffer will automatically be updated.
 */
#ifndef DEBUG_ALLOC
MARK markalloc(buffer, offset)
	BUFFER	buffer;	/* the buffer that the new mark will refer to */
	long	offset;	/* position of the mark within buffer */
{
	MARK	newp;

	newp = (MARK)safealloc(1, sizeof(MARKBUF));
#else
MARK _markalloc(file, line, buffer, offset)
	char	*file;	/* source file where allocating */
	int	line;	/* source line where allocating */
	BUFFER	buffer;	/* the buffer that the new mark will refer to */
	long	offset;	/* position of the mark within buffer */
{
	MARK	newp;

	assert(buffer != NULL && offset >= 0 && offset <= o_bufchars(buffer));

	newp = (MARK)_safealloc(file, line, False, 1, sizeof(MARKBUF));
#endif
	newp->buffer = buffer;
	newp->offset = offset;
	newp->next = bufmarks(buffer);
	bufsetmarks(buffer, newp);
	return newp;
}

/* Free a mark which was created by markalloc().  This also adjusts the
 * namedmarks[] array, if necessary.
 */
#ifndef DEBUG_ALLOC
void markfree(mark)
	MARK	mark;	/* the mark to destroy */
{
#else
void _markfree(file, line, mark)
	char	*file;
	int	line;
	MARK	mark;
{
#endif
	MARK	lag;
	int	i;

#ifdef DEBUG_ALLOC
	fprintf(stderr, "markfree(0x%lx) called from %s(%d)\n", (long)mark, file, line);
#endif
	/* remove from buffer's list of marks */
	if (mark == bufmarks(mark->buffer))
	{
		bufsetmarks(mark->buffer, mark->next);
	}
	else
	{
		for (lag = bufmarks(mark->buffer); lag->next != mark; lag = lag->next)
		{
			assert(lag->next);
		}
		lag->next = mark->next;
	}

	/* if in namedmarks, then unset the namedmarks variable */
	for (i = 0; i < QTY(namedmark); i++)
	{
		if (namedmark[i] == mark)
		{
			namedmark[i] = NULL;
			break;
		}
	}

	/* free the mark's resources */
#ifndef DEBUG_ALLOC
	safefree(mark);
#else
	_safefree(file, line, mark);
#endif
}

/* Adjust the offset of every who's offset is greater than "from".  If at "to"
 * or later, add "delta" to it.  Between "from" and "to", add some fraction
 * of "delta" to it.
 *
 * This function is meant to be called only from bufreplace().
 */
void markadjust(from, to, delta)
	MARK	from;	/* old start of text */
	MARK	to;	/* old end of text */
	long	delta;	/* difference between old "to" and new "to" offsets */
{
	MARK	mark;	/* used for scanning the buffer's mark list */
	long	dist;	/* original distance between "from" and "to" */
	long	tooff;	/* original offset of "to" */
	long	fromoff;/* original offset of "from" */

	/* trivial case */
	if (delta == 0)
	{
		return;
	}

	/* Compute some stuff that depends on the "to" mark.  Note that we
	 * must do this before the loop, because during the loop we may adjust
	 * the value of the "to" mark itself!
	 */
	tooff = to->offset;
	fromoff = from->offset;
	dist = tooff - fromoff;
	assert(from->buffer == to->buffer && -delta <= dist && dist >= 0);

	/* for every mark... */
	for (mark = bufmarks(from->buffer); mark; mark = mark->next)
	{
		/* adjust, if affected by mod */
		if (mark->offset > tooff)
		{
			mark->offset += delta;
		}
		else if (mark->offset > fromoff)
		{
			/* NOTE: At this point we know that dist!=0 because
			 * that could only happen if fromoff==tooff, and we
			 * can only get here if this mark's offset <= tooff
			 * but >fromoff.  When tooff==fromoff, that is
			 * impossible!
			 */
			mark->offset += delta * (mark->offset - from->offset) / dist;
		}
	}
}

/* Find the line number of a mark.  This is defined as being the number of
 * newlines preceding the mark, plus 1.  Note that this is not necessarily
 * how the edit mode defines lines.
 */
long markline(mark)
	MARK	mark;	/* mark to be converted */
{
	long	lnum;

	(void)lowoffset(bufbufinfo(markbuffer(mark)), markoffset(mark), NULL, NULL, NULL, &lnum);
	return lnum;
}

/* Adjust a mark so that it points to a specific line, and then return mark.
 * If the requested line doesn't exist, return NULL.
 */
MARK marksetline(mark, linenum)
	MARK	mark;	/* the mark to be adjusted */
	long	linenum;/* the desired line number */
{
	/* if bogus line number, return NULL */
	if (linenum < 1 || linenum > o_buflines(markbuffer(mark)))
	{
		return NULL;
	}

	/* else change the mark's offset & return it */
	mark->offset = lowline(bufbufinfo(markbuffer(mark)), linenum);
	assert(mark->offset < o_bufchars(markbuffer(mark)));
	return mark;
}

/* Change the buffer of a mark.  This involves deleting the mark from the mark
 * list of the old buffer, and then adding it to the list of the new buffer.
 */
void marksetbuffer(mark, buffer)
	MARK	mark;	/* the mark to be moved */
	BUFFER	buffer;	/* the new buffer that the mark should refer to */
{
	MARK	lag;

	/* if no change, then do nothing */
	if (markbuffer(mark) == buffer)
		return;

	/* remove from old buffer's list of marks */
	if (mark == bufmarks(mark->buffer))
	{
		bufsetmarks(mark->buffer, mark->next);
	}
	else
	{
		assert(bufmarks(mark->buffer));
		for (lag = bufmarks(mark->buffer); lag->next != mark; lag = lag->next)
		{
			assert(lag->next->next);
		}
		lag->next = mark->next;
	}

	/* insert it into the new buffer's list */
	mark->buffer = buffer;
	mark->next = bufmarks(buffer);
	bufsetmarks(buffer, mark);
}
