/* vi:ts=4:sw=4
 *
 * VIM - Vi IMproved
 *
 * Code Contributions By:	Bram Moolenaar			mool@oce.nl
 *							Tim Thompson			twitch!tjt
 *							Tony Andrews			onecom!wldrdg!tony 
 *							G. R. (Fred) Walter		watmath!watcgl!grwalter 
 */

/*
 * mark.c: functions for setting marks and jumping to them
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "mark.h"
#include "ops.h"		/* for endop and startop */

/*
 * This file contains routines to maintain and manipulate marks.
 */

#define NMARKS			26			/* max. # of named marks */
#define JUMPLISTSIZE	50			/* max. # of marks in jump list */

static struct mark pcmark;					/* previous context mark */
static struct mark namedm[NMARKS];			/* original vi marks */
static struct filemark namedfm[NMARKS];		/* new marks with file nr */
static struct filemark jumplist[JUMPLISTSIZE];	/* list of old pcmarks */

static int jumplistlen = 0;
static int jumplistidx = 0;

static FPOS *mark2pos __ARGS((struct mark *));

#ifdef NEW
struct markptr
{
	int					mp_ident;		/* 'a' - 'z', 'A' - 'Z' or jumplist */
	struct	filemark	mp_fm;
} marklist[NMARKS + NMARKS + JUMPLISTSIZE];
int marklistlen = 0;

adjustmark(old, new)
{
	max = marklistlen - 1;
	min = 0;
	while (max > min)
	{
		i = (max + min) / 2;
		t = marklist[i].mp_fm.ptr;
		if (t > old)
			max = i - 1;
		else if (t < old)
			min = i + 1;
	}
	if (max == min && marklist[i].mp_fm.ptr == old)
	{
	}
}
#endif

/*
 * setmark(c) - set named mark 'c' at current cursor position
 *
 * Returns TRUE on success, FALSE if no room for mark or bad name given.
 */
	int
setmark(c)
	int			c;
{
	int 			i;

	if (islower(c))
	{
		i = c - 'a';
		namedm[i].ptr = nr2ptr(Curpos.lnum);
		namedm[i].col = Curpos.col;
		return TRUE;
	}
	if (isupper(c))
	{
		i = c - 'A';
		namedfm[i].mark.ptr = nr2ptr(Curpos.lnum);
		namedfm[i].mark.col = Curpos.col;
		namedfm[i].lnum = Curpos.lnum;
		namedfm[i].fnum = 0;
		return TRUE;
	}
	return FALSE;
}

/*
 * setpcmark() - set the previous context mark to the current position
 *				 and insert it into the jump list
 */
	void
setpcmark()
{
	int i;
#ifdef ROTATE
	struct filemark tempmark;
#endif

	pcmark.ptr = nr2ptr(Curpos.lnum);
	pcmark.col = Curpos.col;

#ifndef ROTATE
	/*
	 * simply add the new entry at the end of the list
	 */
	jumplistidx = jumplistlen;
#else
	/*
	 * If last used entry is not at the top, put it at the top by rotating
	 * the stack until it is (the newer entries will be at the bottom).
	 * Keep one entry (the last used one) at the top.
	 */
	if (jumplistidx < jumplistlen)
		++jumplistidx;
	while (jumplistidx < jumplistlen)
	{
		tempmark = jumplist[jumplistlen - 1];
		for (i = jumplistlen - 1; i > 0; --i)
			jumplist[i] = jumplist[i - 1];
		jumplist[0] = tempmark;
		++jumplistidx;
	}
#endif

		/* only add new entry if it differs from the last one */
	if (jumplistlen == 0 || jumplist[jumplistidx - 1].mark.ptr != pcmark.ptr)
	{
			/* if jumplist is full: remove oldest entry */
		if (++jumplistlen > JUMPLISTSIZE)
		{
			jumplistlen = JUMPLISTSIZE;
			for (i = 1; i < jumplistlen; ++i)
				jumplist[i - 1] = jumplist[i];
			--jumplistidx;
		}

		jumplist[jumplistidx].mark = pcmark;
		jumplist[jumplistidx].lnum = Curpos.lnum;
		jumplist[jumplistidx].fnum = 0;
		++jumplistidx;
	}
}

/*
 * move "count" positions in the jump list (count may be negative)
 */
	FPOS *
movemark(count)
	int count;
{
	FPOS		*pos;

	if (jumplistlen == 0)			/* nothing to jump to */
		return (FPOS *)NULL;

	if (jumplistidx + count < 0 || jumplistidx + count >= jumplistlen)
		return (FPOS *)NULL;

	/*
	 * if first CTRL-O or CTRL-I command after a jump, add cursor position to list
	 */
	if (jumplistidx == jumplistlen)
	{
		setpcmark();
		--jumplistidx;		/* skip the new entry */
	}

	jumplistidx += count;
	if (jumplist[jumplistidx].mark.ptr == NULL)	/* jump to other file */
	{
		if (getaltfile(jumplist[jumplistidx].fnum - 1, jumplist[jumplistidx].lnum, FALSE))
			return (FPOS *)NULL;
		Curpos.col = jumplist[jumplistidx].mark.col;
		jumplist[jumplistidx].fnum = 0;
		jumplist[jumplistidx].mark.ptr = nr2ptr(Curpos.lnum);
		pos = (FPOS *)-1;
	}
	else
		pos = mark2pos(&jumplist[jumplistidx].mark);
	return pos;
}

/*
 * getmark(c) - find mark for char 'c'
 *
 * Return pointer to FPOS if found
 *        NULL if no such mark.
 *        -1 if mark is in other file (only if changefile is TRUE)
 */
	FPOS *
getmark(c, changefile)
	int			c;
	int			changefile;
{
	FPOS	*posp;

	posp = NULL;
	if (c == '\'' || c == '`')			/* previous context mark */
		posp = mark2pos(&pcmark);
	else if (c == '[')					/* to start of previous operator */
	{
		if (startop.lnum > 0 && startop.lnum <= line_count)
			posp = &startop;
	}
	else if (c == ']')					/* to end of previous operator */
	{
		if (endop.lnum > 0 && endop.lnum <= line_count)
			posp = &endop;
	}
	else if (islower(c))				/* normal named mark */
		posp = mark2pos(&(namedm[c - 'a']));
	else if (isupper(c))				/* named file mark */
	{
		c -= 'A';
		posp = mark2pos(&(namedfm[c].mark));
		if (posp == NULL && namedfm[c].lnum != 0 && (changefile || samealtfile(namedfm[c].fnum - 1)))
		{
			if (!getaltfile(namedfm[c].fnum - 1, namedfm[c].lnum, TRUE))
			{
				Curpos.col = namedfm[c].mark.col;
				namedfm[c].fnum = 0;
				namedfm[c].mark.ptr = nr2ptr(Curpos.lnum);
				posp = (FPOS *)-1;
			}
		}
	}
	return posp;
}

	static FPOS *
mark2pos(markp)
	struct mark *markp;
{
	static FPOS pos;

	if (markp->ptr != NULL && (pos.lnum = ptr2nr(markp->ptr, (linenr_t)1)) != 0)
	{
		pos.col = markp->col;
		return (&pos);
	}
	return (FPOS *)NULL;
}

/*
 * clrallmarks() - clear all marks
 *
 * Used mainly when trashing the entire buffer during ":e" type commands
 */
	void
clrallmarks()
{
	static int 			i = -1;

	if (i == -1)		/* first call ever: initialize */
		for (i = 0; i < NMARKS; i++)
			namedfm[i].lnum = 0;

	for (i = 0; i < NMARKS; i++)
	{
		namedm[i].ptr = NULL;
		namedfm[i].mark.ptr = NULL;
	}
	pcmark.ptr = NULL;
	qf_clrallmarks();
	for (i = 0; i < jumplistlen; ++i)
		jumplist[i].mark.ptr = NULL;
}

/*
 * increment the file number for all filemarks
 * called when adding a file to the file stack
 */
	void
incrmarks()
{
	int			i;

	for (i = 0; i < NMARKS; i++)
		++namedfm[i].fnum;

	for (i = 0; i < jumplistlen; ++i)
	{
#if 0		/* this would take too much time */
		if (jumplist[i].fnum == 0)	/* current file */
			jumplist[i].lnum = ptr2nr(jumplist[i].mark.ptr, 1);
#endif
		++jumplist[i].fnum;
	}
}

/*
 * decrement the file number for the filemarks of the current file
 * called when not adding the current file name to the file stack
 */
	void
decrmarks()
{
	int			i;

	for (i = 0; i < NMARKS; i++)
		if (namedfm[i].fnum == 1)
			namedfm[i].fnum = 0;

	for (i = 0; i < jumplistlen; ++i)
		if (jumplist[i].fnum == 1)
			jumplist[i].fnum = 0;
}

/*
 * adjustmark: set new ptr for a mark
 * if new == NULL the mark is effectively deleted
 * (this is slow: we have to check about 100 pointers!)
 */
   void
adjustmark(old, new)
		char *old, *new;
{
	register int i, j;

	for (i = 0; i < NMARKS; ++i)
	{
		if (namedm[i].ptr == old)
			namedm[i].ptr = new;
		if (namedfm[i].mark.ptr == old)
		{
			namedfm[i].mark.ptr = new;
			if (new == NULL)
				namedfm[i].lnum = 0;		/* delete this mark */
		}
	}
	if (pcmark.ptr == old)
		pcmark.ptr = new;
	for (i = 0; i < jumplistlen; ++i)
		if (jumplist[i].mark.ptr == old)
		{
			if (new == NULL)				/* delete this mark */
			{
				--jumplistlen;
				if (jumplistidx > jumplistlen)
					--jumplistidx;
				for (j = i; j < jumplistlen; ++j)
					jumplist[j] = jumplist[j + 1];
			}
			else
				jumplist[i].mark.ptr = new;
		}
	qf_adjustmark(old, new);
}

/*
 * get name of file from a filemark (use the occasion to update the lnum)
 */
	char *
fm_getname(fmark)
	struct filemark *fmark;
{
	linenr_t	nr;
	char		*name;

	if (fmark->fnum != 0)						/* maybe not current file */
	{
		name = getaltfname(fmark->fnum - 1);
		if (name == NULL)
			return "-none-";
		if (Filename == NULL || fnamecmp(name, Filename) != 0)	/* not current file */
			return name;
		fmark->fnum = 0;
	}
	if (fmark->mark.ptr == NULL)
	{
		if (fmark->lnum <= line_count)				/* safety check */
			fmark->mark.ptr = nr2ptr(fmark->lnum);	/* update ptr */
	}
	else
	{
		nr = ptr2nr(fmark->mark.ptr, (linenr_t)1);
		if (nr != 0)
			fmark->lnum = nr;					/* update lnum */
	}
	return "-current-";
}

/*
 * print the marks (use the occasion to update the line numbers)
 */
	void
domarks()
{
	int			i;
	char		*name;

#ifdef AMIGA
	settmode(0);		/* set cooked mode, so output can be halted */
#endif
	outstrn("\nmark line  file\n");
	for (i = 0; i < NMARKS; ++i)
	{
		if (namedm[i].ptr != NULL)
		{
			sprintf(IObuff, " %c %5ld\n",
				i + 'a',
				ptr2nr(namedm[i].ptr, (linenr_t)1));
			outstrn(IObuff);
		}
		flushbuf();
	}
	for (i = 0; i < NMARKS; ++i)
	{
		if (namedfm[i].lnum != 0)
		{
			name = fm_getname(&namedfm[i]);
			if (name == NULL)		/* file name not available */
				continue;

			sprintf(IObuff, " %c %5ld  %s\n",
				i + 'A',
				namedfm[i].lnum,
				name);
			outstrn(IObuff);
		}
		flushbuf();
	}
#ifdef AMIGA
	settmode(1);
#endif
	wait_return(TRUE);
}

/*
 * print the jumplist (use the occasion to update the line numbers)
 */
	void
dojumps()
{
	int			i;
	char		*name;

#ifdef AMIGA
	settmode(0);		/* set cooked mode, so output can be halted */
#endif
	outstrn("\n jump line  file\n");
	for (i = 0; i < jumplistlen; ++i)
	{
		if (jumplist[i].lnum != 0)
		{
			name = fm_getname(&jumplist[i]);
			if (name == NULL)		/* file name not available */
				continue;

			sprintf(IObuff, "%c %2d %5ld  %s\n",
				i == jumplistidx ? '>' : ' ',
				i + 1,
				jumplist[i].lnum,
				name);
			outstrn(IObuff);
		}
		flushbuf();
	}
	if (jumplistidx == jumplistlen)
		outstrn(">\n");
#ifdef AMIGA
	settmode(1);
#endif
	wait_return(TRUE);
}


syntax highlighted by Code2HTML, v. 0.9.1