/* 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 
 */

/*
 * buffers.c
 *
 * manipulations with redo buffer and stuff buffer
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "param.h"

/*
 * structure used to store one block of the stuff/redo/macro buffers
 */
struct bufblock
{
		struct bufblock *b_next;		/* pointer to next bufblock */
		u_char			b_str[1];		/* contents (actually longer) */
};

#define MINIMAL_SIZE 20 				/* minimal size for b_str */

/*
 * header used for the stuff buffer and the redo buffer
 */
struct buffheader
{
		struct bufblock bh_first;		/* first (dummy) block of list */
		struct bufblock *bh_curr;		/* bufblock for appending */
		int 			bh_index;		/* index for reading */
		int 			bh_space;		/* space in bh_curr for appending */
};

static struct buffheader stuffbuff = {{NULL, {NUL}}, NULL, 0, 0};
static struct buffheader redobuff = {{NULL, {NUL}}, NULL, 0, 0};
static struct buffheader recordbuff = {{NULL, {NUL}}, NULL, 0, 0};

	/*
	 * when block_redo is TRUE redo buffer will not be changed
	 * used by edit() to repeat insertions and 'V' command for redoing
	 */
static int		block_redo = FALSE;

/*
 * structure used for mapping
 */
struct mapblock
{
	struct mapblock *m_next;		/* next mapblock */
	char			*m_keys;		/* mapped from */
	int				 m_keylen;		/* strlen(m_keys) */
	char			*m_str; 		/* mapped to */
	int 			 m_mode;		/* valid mode */
	int				 m_noremap;		/* if non-zero no re-mapping for m_str */
};

static struct mapblock maplist = {NULL, NULL, 0, NULL, 0, 0};
									/* first dummy entry in maplist */

/*
 * variables used by vgetorpeek() and flush_buffers()
 *
 * typestr contains all characters that are not consumed yet.
 * The part in front may contain the result of mappings and @a commands.
 * The lenght of this part is typemaplen.
 * After it are characters that come from the terminal.
 * Some parts of typestr may not be mapped. These parts are remembered in
 * the noremaplist. 
 */
#define MAXMAPLEN 50				/* maximum length of key sequence to be mapped */
									/* must be able to hold an Amiga resize report */
static char		*typestr = NULL;	/* NUL-terminated buffer for typeahead characters */
static char		typebuf[MAXMAPLEN + 3]; /* initial typestr */

static int		typemaplen = 0;		/* number of mapped characters in typestr */

/* 
 * parts int typestr that should not be mapped are remembered with a list
 * of noremap structs. Noremaplist is the first.
 */
struct noremap
{
	int				nr_off;			/* offset to not remappable chars */
	int				nr_len;			/* number of not remappable chars */
	struct noremap	*nr_next;		/* next entry in the list */
};

static struct noremap noremaplist = {0, 0, NULL};

static void		free_buff __ARGS((struct buffheader *));
static u_char	*get_bufcont __ARGS((struct buffheader *, int));
static void		add_buff __ARGS((struct buffheader *, char *));
static void		add_num_buff __ARGS((struct buffheader *, long));
static void		add_char_buff __ARGS((struct buffheader *, int));
static u_char	read_stuff __ARGS((int));
static int		start_stuff __ARGS((void));
static int		read_redo __ARGS((int));
static void		gotchars __ARGS((char *, int));
static void		init_typestr __ARGS((void));
static u_char	vgetorpeek __ARGS((int));
static void		showmap __ARGS((struct mapblock *));

/*
 * free and clear a buffer
 */
	static void
free_buff(buf)
	struct buffheader *buf;
{
		register struct bufblock *p, *np;

		for (p = buf->bh_first.b_next; p != NULL; p = np)
		{
				np = p->b_next;
				free((char *)p);
		}
		buf->bh_first.b_next = NULL;
}

/*
 * return the contents of a buffer as a single string
 */
	static u_char *
get_bufcont(buffer, dozero)
	struct buffheader	*buffer;
	int					dozero;		/* count == zero is not an error */
{
		u_long			count = 0;
		u_char			*p = NULL;
		struct bufblock	*bp;

/* compute the total length of the string */
		for (bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next)
				count += strlen((char *)bp->b_str);

		if ((count || dozero) && (p = (u_char *)lalloc(count + 1, TRUE)) != NULL)
		{
				*p = NUL;
				for (bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next)
						strcat((char *)p, (char *)bp->b_str);
		}
		return (p);
}

/*
 * return the contents of the record buffer as a single string
 *	and clear the record buffer
 */
	u_char *
get_recorded()
{
	u_char *p;

	p = get_bufcont(&recordbuff, TRUE);
	free_buff(&recordbuff);
	return (p);
}

/*
 * return the contents of the redo buffer as a single string
 */
	u_char *
get_inserted()
{
		return(get_bufcont(&redobuff, FALSE));
}

/*
 * add string "s" after the current block of buffer "buf"
 */
	static void
add_buff(buf, s)
	register struct buffheader	*buf;
	char						*s;
{
	struct bufblock *p;
	u_long 			n;
	u_long 			len;

	if ((n = strlen(s)) == 0)				/* don't add empty strings */
		return;

	if (buf->bh_first.b_next == NULL)		/* first add to list */
	{
		buf->bh_space = 0;
		buf->bh_curr = &(buf->bh_first);
	}
	else if (buf->bh_curr == NULL)			/* buffer has already been read */
	{
		emsg("Add to read buffer");
		return;
	}
	else if (buf->bh_index != 0)
		strcpy((char *)buf->bh_first.b_next->b_str, (char *)buf->bh_first.b_next->b_str + buf->bh_index);
	buf->bh_index = 0;

	if (buf->bh_space >= n)
	{
		strcat((char *)buf->bh_curr->b_str, s);
		buf->bh_space -= n;
	}
	else
	{
		if (n < MINIMAL_SIZE)
			len = MINIMAL_SIZE;
		else
			len = n;
		p = (struct bufblock *)lalloc((u_long)(sizeof(struct bufblock) + len), TRUE);
		if (p == NULL)
			return; /* no space, just forget it */
		buf->bh_space = len - n;
		strcpy((char *)p->b_str, s);

		p->b_next = buf->bh_curr->b_next;
		buf->bh_curr->b_next = p;
		buf->bh_curr = p;
	}
	return;
}

	static void
add_num_buff(buf, n)
	struct buffheader *buf;
	long 			  n;
{
		char	number[32];

		sprintf(number, "%ld", n);
		add_buff(buf, number);
}

	static void
add_char_buff(buf, c)
	struct buffheader *buf;
	int 			  c;
{
		char	temp[2];

		temp[0] = c;
		temp[1] = NUL;
		add_buff(buf, temp);
}

/*
 * get one character from the stuff buffer
 * If advance == TRUE go to the next char.
 */
	static u_char
read_stuff(advance)
	int			advance;
{
		register u_char c;
		register struct bufblock *curr;


		if (stuffbuff.bh_first.b_next == NULL)	/* buffer is empty */
			return NUL;

		curr = stuffbuff.bh_first.b_next;
		c = curr->b_str[stuffbuff.bh_index];

		if (advance)
		{
			if (curr->b_str[++stuffbuff.bh_index] == NUL)
			{
				stuffbuff.bh_first.b_next = curr->b_next;
				free((char *)curr);
				stuffbuff.bh_index = 0;
			}
		}
		return c;
}

/*
 * prepare stuff buffer for reading (if it contains something)
 */
	static int
start_stuff()
{
	if (stuffbuff.bh_first.b_next == NULL)
		return FALSE;
	stuffbuff.bh_curr = &(stuffbuff.bh_first);
	stuffbuff.bh_space = 0;
	return TRUE;
}

/*
 * check if the stuff buffer is empty
 */
	int
stuff_empty()
{
	return (stuffbuff.bh_first.b_next == NULL);
}

/*
 * Remove the contents of the stuff buffer and the mapped characters in the
 * typeahead buffer (used in case of an error). If 'typeahead' is true,
 * flush all typeahead characters (used when interrupted by a CTRL-C).
 */
	void
flush_buffers(typeahead)
	int typeahead;
{
	struct noremap *p;

	init_typestr();

	start_stuff();
	while (read_stuff(TRUE) != NUL)
		;

	if (typeahead)			/* remove all typeahead */
	{
			/*
			 * We have to get all characters, because we may delete the first
			 * part of an escape sequence.
			 * In an xterm we get one char at a time and we have to get them all.
			 */
		while (inchar(typestr, MAXMAPLEN, 10))	
			;
		*typestr = NUL;
	}
	else					/* remove mapped characters only */
		strcpy(typestr, typestr + typemaplen);
	typemaplen = 0;
	noremaplist.nr_len = 0;
	noremaplist.nr_off = 0;
	while (noremaplist.nr_next)
	{
		p = noremaplist.nr_next->nr_next;
		free(noremaplist.nr_next);
		noremaplist.nr_next = p;
	}
}

	void
ResetRedobuff()
{
	if (!block_redo)
		free_buff(&redobuff);
}

	void
AppendToRedobuff(s)
	char		   *s;
{
	if (!block_redo)
		add_buff(&redobuff, s);
}

	void
AppendCharToRedobuff(c)
	int			   c;
{
	if (!block_redo)
		add_char_buff(&redobuff, c);
}

	void
AppendNumberToRedobuff(n)
	long 			n;
{
	if (!block_redo)
		add_num_buff(&redobuff, n);
}

	void
stuffReadbuff(s)
	char		   *s;
{
	add_buff(&stuffbuff, s);
}

	void
stuffcharReadbuff(c)
	int			   c;
{
	add_char_buff(&stuffbuff, c);
}

	void
stuffnumReadbuff(n)
	long	n;
{
	add_num_buff(&stuffbuff, n);
}

/*
 * Read a character from the redo buffer.
 * The redo buffer is left as it is.
 */
	static int
read_redo(init)
	int			init;
{
	static struct bufblock	*bp;
	static u_char			*p;
	int						c;

	if (init)
	{
		if ((bp = redobuff.bh_first.b_next) == NULL)
			return TRUE;
		p = bp->b_str;
		return FALSE;
	}
	if ((c = *p) != NUL)
	{
		if (*++p == NUL && bp->b_next != NULL)
		{
			bp = bp->b_next;
			p = bp->b_str;
		}
	}
	return c;
}

/*
 * copy the rest of the redo buffer into the stuff buffer (could be done faster)
 */
	void
copy_redo()
{
	register int c;

	while ((c = read_redo(FALSE)) != NUL)
		stuffcharReadbuff(c);
}

extern int redo_Visual_busy;		/* this is in normal.c */

/*
 * Stuff the redo buffer into the stuffbuff.
 * Insert the redo count into the command.
 */
	int
start_redo(count)
	long count;
{
	register int c;

	if (read_redo(TRUE))	/* init the pointers; return if nothing to redo */
		return FALSE;

	c = read_redo(FALSE);

/* copy the buffer name, if present */
	if (c == '"')
	{
		add_buff(&stuffbuff, "\"");
		c = read_redo(FALSE);

/* if a numbered buffer is used, increment the number */
		if (c >= '1' && c < '9')
			++c;
		add_char_buff(&stuffbuff, c);
		c = read_redo(FALSE);
	}

	if (c == 'v')	/* redo Visual */
	{
		Visual = Curpos;
		redo_Visual_busy = TRUE;
		c = read_redo(FALSE);
	}

/* try to enter the count (in place of a previous count) */
	if (count)
	{
		while (isdigit(c))		/* skip "old" count */
			c = read_redo(FALSE);
		add_num_buff(&stuffbuff, count);
	}

#ifdef JPFEP
/* forcce turn off Kanji input */
	fep_mode_switch(KanjiInput = FALSE);
#endif

/* copy from the redo buffer into the stuff buffer */
	add_char_buff(&stuffbuff, c);
	copy_redo();
	return TRUE;
}

/*
 * Repeat the last insert (R, o, O, a, A, i or I command) by stuffing
 * the redo buffer into the stuffbuff.
 */
	int
start_redo_ins()
{
	register u_char c;

	if (read_redo(TRUE))
		return FALSE;

	start_stuff();

/* skip the count and the command character */
	while ((c = read_redo(FALSE)) != NUL)
	{
		c = TO_UPPER(c);
		if (strchr("AIRO", c) != NULL)
		{
			if (c == 'O')
				stuffReadbuff(NL_STR);
			break;
		}
	}

#ifdef JPFEP
/* forcce turn off Kanji input */
	fep_mode_switch(KanjiInput = FALSE);
#endif

/* copy the typed text from the redo buffer into the stuff buffer */
	copy_redo();
	block_redo = TRUE;
	return TRUE;
}

	void
set_redo_ins()
{
	block_redo = TRUE;
}

	void
stop_redo_ins()
{
	block_redo = FALSE;
}

/*
 * insert a string in front of the typeahead buffer (for '@' command and vgetorpeek)
 */
	int
ins_typestr(str, noremap)
	char	*str;
	int		noremap;
{
	register char	*s;
	register int	newlen;
	register int	addlen;

	init_typestr();

	/*
	 * In typestr there must always be room for MAXMAPLEN + 3 characters
	 */
	addlen = strlen(str);
	newlen = strlen(typestr) + addlen + MAXMAPLEN + 3;
	if (newlen < 0)				/* string is getting too long */
	{
		emsg(e_toocompl);		/* also calls flush_buffers */
		setcursor();
		return -1;
	}
	s = alloc(newlen);
	if (s == NULL)				/* out of memory */
		return -1;

	strcpy(s, str);
	strcat(s, typestr);
	if (typestr != typebuf)
		free(typestr);
	typestr = s;
	typemaplen += addlen;		/* the inserted string is not typed */
	if (noremap)
	{
		if (noremaplist.nr_off == 0)
			noremaplist.nr_len += addlen;
		else
		{
			struct noremap *p;

			p = (struct noremap *)alloc((int)sizeof(struct noremap));
			if (p != NULL)
			{
				p->nr_next = noremaplist.nr_next;
				p->nr_off = noremaplist.nr_off;
				p->nr_len = noremaplist.nr_len;
				noremaplist.nr_next = p;
				noremaplist.nr_len = addlen;
				noremaplist.nr_off = 0;
			}
		}
	}
	else if (noremaplist.nr_len)
		noremaplist.nr_off += addlen;
	return 0;
}

/*
 * remove "len" characters from the front of typestr
 */
	void
del_typestr(len)
	int	len;
{
	struct noremap *p;

	strcpy(typestr, typestr + len);		/* remove chars from the buffer */
	if ((typemaplen -= len) <= 0)		/* adjust typemaplen */
		typemaplen = 0;
	while (len)							/* adjust noremaplist */
	{
		if (noremaplist.nr_off >= len)
		{
			noremaplist.nr_off -= len;
			break;
		}
		len -= noremaplist.nr_off;
		noremaplist.nr_off = 0;
		if (noremaplist.nr_len > len)
		{
			noremaplist.nr_len -= len;
			break;
		}
		len -= noremaplist.nr_len;
		p = noremaplist.nr_next;
		if (p == NULL)
		{
			noremaplist.nr_len = 0;
			break;
		}
		noremaplist.nr_next = p->nr_next;
		noremaplist.nr_len = p->nr_len;
		noremaplist.nr_off = p->nr_off;
		free(p);
	}
}

extern int arrow_used;			/* this is in edit.c */

/*
 * Write typed characters to script file.
 * If recording is on put the character in the recordbuffer.
 */
	static void
gotchars(s, len)
	char	*s;
	int		len;
{
	while (len--)
	{
		updatescript(*s & 255);

		if (Recording)
			add_char_buff(&recordbuff, (*s & 255));
		++s;
	}

			/* do not sync in insert mode, unless cursor key has been used */
	if (!(State & (INSERT + CMDLINE)) || arrow_used)		
		u_sync();
}

/*
 * Initialize typestr to point to typebuf.
 * Alloc() cannot be used here: In out-of-memory situations it would
 * be impossible to type anything.
 */
	static void
init_typestr()
{
	if (typestr == NULL)
	{
		typestr = typebuf;
		typebuf[0] = NUL;
	}
}

#define NEEDMORET 9999		/* value for incomplete mapping or key-code */

/*
 * get a character: 1. from the stuffbuffer
 *					2. from the typeahead buffer
 *					3. from the user
 *
 * KeyTyped is set to TRUE in the case the user typed the key.
 * If advance is TRUE, we really get the character. Otherwise we just look
 * whether there is a character available.
 */
	static u_char
vgetorpeek(advance)
	int		advance;
{
	register int	c;
	int				n = 0;		/* init for GCC */
	int				len;
#ifdef AMIGA
	char			*s;
#endif
	register struct mapblock *mp;
	int				timedout = FALSE;	/* waited for more than 1 second
												for mapping to complete */
	int				mapdepth = 0;		/* check for recursive mapping */
	int				mode_deleted = FALSE;	/* set when mode has been deleted */

	init_typestr();
	start_stuff();
	if (typemaplen == 0)
		Exec_reg = FALSE;
	do
	{
		c = read_stuff(advance);
		if (c != NUL && !got_int)
			KeyTyped = FALSE;
		else
		{
			/*
			 * Loop until we either find a matching mapped key, or we
			 * are sure that it is not a mapped key.
			 * If a mapped key sequence is found we go back to the start to
			 * try re-mapping.
			 */

			for (;;)
			{
				len = strlen(typestr);
				breakcheck();				/* check for CTRL-C */
				if (got_int)
				{
					c = inchar(typestr, MAXMAPLEN, 0);	/* flush all input */
					/*
					 * If inchar returns TRUE (script file was active) or we are
					 * inside a mapping, get out of insert mode.
					 * Otherwise we behave like having gotten a CTRL-C.
					 * As a result typing CTRL-C in insert mode will
					 * really insert a CTRL-C.
					 */
					if ((c || typemaplen) && (State & (INSERT + CMDLINE)))
						c = ESC;
					else
						c = Ctrl('C');
					flush_buffers(TRUE);		/* flush all typeahead */
					break;
				}
				else if (len > 0)	/* see if we have a mapped key sequence */
				{
					/*
					 * walk through the maplist until we find an
					 * entry that matches (if not timed out).
					 */
					mp = NULL;
					if (!timedout && (typemaplen == 0 || (p_remap &&
							(noremaplist.nr_len == 0 || noremaplist.nr_off != 0)))
							&& !((State & (INSERT + CMDLINE)) && p_paste))
					{
						for (mp = maplist.m_next; mp; mp = mp->m_next)
						{
							if ((mp->m_mode & ABBREV) || !(mp->m_mode & State))
								continue;
							n = mp->m_keylen;
							if (noremaplist.nr_off != 0 && n > noremaplist.nr_off)
								continue;
							if (!strncmp(mp->m_keys, typestr, (size_t)(n > len ? len : n)))
								break;
						}
					}
					if (mp == NULL)					/* no match found */
					{
							/*
							 * check if we have a terminal code, when
							 *	mapping is allowed,
							 *  keys have not been mapped,
							 *	and not an ESC sequence, not in insert mode or
							 *		p_ek is on,
							 *	and when not timed out,
							 */
						if (State != NOMAPPING &&
								typemaplen == 0 &&
								(typestr[0] != ESC || p_ek || !(State & INSERT)) &&
								!timedout)
							n = check_termcode(typestr);
						else
							n = 0;
						if (n == 0)		/* no matching terminal code */
						{
#ifdef AMIGA					/* check for window bounds report */
							if (typemaplen == 0 && (typestr[0] & 0xff) == CSI)
							{
								for (s = typestr + 1; isdigit(*s) || *s == ';' || *s == ' '; ++s)
									;
								if (*s == 'r' || *s == '|')	/* found one */
								{
									strcpy(typestr, s + 1);
									set_winsize(0, 0, FALSE);		/* get size and redraw screen */
									continue;
								}
								if (*s == NUL)		/* need more characters */
									n = -1;
							}
							if (n != -1)			/* got a single character */
#endif
							{
								c = typestr[0] & 255;
								if (typemaplen)
									KeyTyped = FALSE;
								else
								{
									KeyTyped = TRUE;
									if (advance)	/* write char to script file(s) */
										gotchars(typestr, 1);
								}
								if (advance)		/* remove chars from typestr */
									del_typestr(1);
								break;		/* got character, break for loop */
							}
						}
						if (n > 0)		/* full matching terminal code */
							continue;	/* try mapping again */

						/* partial match: get some more characters */
						n = NEEDMORET;
					}
					if (n <= len)		/* complete match */
					{
						if (n > typemaplen)		/* write chars to script file(s) */
							gotchars(typestr + typemaplen, n - typemaplen);

						del_typestr(n);	/* remove the mapped keys */

						/*
						 * Put the replacement string in front of mapstr.
						 * The depth check catches ":map x y" and ":map y x".
						 */
						if (++mapdepth == 1000)
						{
							emsg("recursive mapping");
							if (State == CMDLINE)
								redrawcmdline();
							else
								setcursor();
							flush_buffers(FALSE);
							mapdepth = 0;		/* for next one */
							c = -1;
							break;
						}
						if (ins_typestr(mp->m_str, mp->m_noremap) < 0)
						{
							c = -1;
							break;
						}
						continue;
					}
				}
				/*
				 * special case: if we get an <ESC> in insert mode and there are
				 * no more characters at once, we pretend to go out of insert mode.
				 * This prevents the one second delay after typing an <ESC>.
				 * If we get something after all, we may have to redisplay the
				 * mode. That the cursor is in the wrong place does not matter.
				 */
				c = 0;
				if (advance && len == 1 && typestr[0] == ESC && typemaplen == 0 && (State & INSERT) && (p_timeout || (n == NEEDMORET && p_ttimeout)) && (c = inchar(typestr + len, 2, 0)) == 0)
				{
					if (p_smd)
					{
						delmode();
						mode_deleted = TRUE;
					}
					if (Curscol)		/* move cursor one left if possible */
						--Curscol;
					else if (p_wrap && Curpos.col != 0 && Cursrow)
					{
							--Cursrow;
							Curscol = Columns - 1;
					}
					setcursor();
					flushbuf();
				}
				len += c;

				if (len >= typemaplen + MAXMAPLEN)	/* buffer full, don't map */
				{
					timedout = TRUE;
					continue;
				}
				c = inchar(typestr + len, typemaplen + MAXMAPLEN - len, !advance ? 0 : ((len == 0 || !(p_timeout || (p_ttimeout && n == NEEDMORET))) ? -1 : (int)p_tm));
				if (c <= NUL)		/* no character available */
				{
					if (!advance)
						break;
					if (len)				/* timed out */
					{
						timedout = TRUE;
						continue;
					}
				}
				else
				{
					if (mode_deleted)		/* character entered after ESC */
					{
						showmode();
						mode_deleted = FALSE;
					}
				}
			}		/* for (;;) */
		}		/* if (!character from stuffbuf) */

						/* if advance is FALSE don't loop on NULs */
	} while (c < 0 || (advance && c == NUL));

		/* delete "INSERT" message if we return an ESC */
	if (c == ESC && p_smd && !mode_deleted && (State & INSERT))
		delmode();

	return (u_char) c;
}

	u_char
vgetc()
{
	return (vgetorpeek(TRUE));
}

	u_char
vpeekc()
{
	return (vgetorpeek(FALSE));
}

/*
 * map[!]					: show all key mappings
 * map[!] {lhs}				: show key mapping for {lhs}
 * map[!] {lhs} {rhs}		: set key mapping for {lhs} to {rhs}
 * noremap[!] {lhs} {rhs}	: same, but no remapping for {rhs}
 * unmap[!] {lhs}			: remove key mapping for {lhs}
 * abbr						: show all abbreviations
 * abbr {lhs}				: show abbreviations for {lhs}
 * abbr {lhs} {rhs}			: set abbreviation for {lhs} to {rhs}
 * noreabbr {lhs} {rhs}		: same, but no remapping for {rhs}
 * unabbr {lhs}				: remove abbreviation for {lhs}
 *
 * maptype == 1 for unmap command, 2 for noremap command.
 *
 * keys is pointer to any arguments.
 *
 * for :map	  mode is NORMAL 
 * for :map!  mode is INSERT + CMDLINE
 * for :cmap  mode is CMDLINE
 * for :imap  mode is INSERT 
 * for :abbr  mode is INSERT + CMDLINE + ABBREV
 * for :iabbr mode is INSERT + ABBREV
 * for :cabbr mode is CMDLINE + ABBREV
 * 
 * Return 0 for success
 *		  1 for invalid arguments
 *		  2 for no match
 *		  3 for ambiguety
 *		  4 for out of mem
 */
	int
domap(maptype, keys, mode)
	int		maptype;
	char	*keys;
	int		mode;
{
	struct mapblock		*mp, *mprev;
	char				*arg;
	char				*p;
	int					n = 0;			/* init for GCC */
	int					len = 0;		/* init for GCC */
	char				*newstr;
	int					hasarg;
	int					haskey;
	int					did_it = FALSE;
	int					abbrev = 0;
	int					round;

	if (mode & ABBREV)		/* not a mapping but an abbreviation */
	{
		abbrev = ABBREV;
		mode &= ~ABBREV;
	}
/*
 * find end of keys and remove CTRL-Vs in it
 */
	p = keys;
	while (*p && *p != ' ' && *p != '\t')
	{
		if (*p == Ctrl('V') && p[1] != NUL)
			strcpy(p, p + 1);			/* remove CTRL-V */
		++p;
	}
	if (*p != NUL)
		*p++ = NUL;
	skipspace(&p);
	arg = p;
	hasarg = (*arg != NUL);
	haskey = (*keys != NUL);

		/* check for :unmap with not one argument */
	if (maptype == 1 && (!haskey || hasarg))	
		return 1;

/*
 * remove CTRL-Vs from argument
 */
	while (*p)
	{
		if (*p == Ctrl('V') && p[1] != NUL)
			strcpy(p, p + 1);			/* remove CTRL-V */
		++p;
	}

/*
 * check arguments and translate function keys
 */
	if (haskey)
	{
		if (*keys == '#' && isdigit(*(keys + 1)))	/* function key */
		{
			if (*++keys == '0')
				*(u_char *)keys = K_F10;
			else
				*keys += K_F1 - '1';
		}
		len = strlen(keys);
		if (len > MAXMAPLEN)			/* maximum lenght of MAXMAPLEN chars */
			return 2;
	}

	if (haskey && hasarg && abbrev)		/* will add an abbreviation */
		no_abbr = FALSE;

#ifdef AMIGA
	if (!haskey || (maptype != 1 && !hasarg))
		settmode(0);				/* set cooked mode so output can be halted */
#endif
/*
 * Find an entry in the maplist that matches.
 * For :unmap we may loop two times: once to try to unmap an entry with a
 * matching 'from' part, a second time, if the first fails, to unmap an
 * entry with a matching 'to' part. This was done to allow ":ab foo bar" to be
 * unmapped by typing ":unab foo", where "foo" will be replaced by "bar" because
 * of the abbreviation.
 */
	for (round = 0; (round == 0 || maptype == 1) && round <= 1 && !did_it; ++round)
	{
		for (mp = maplist.m_next, mprev = &maplist; mp; mprev = mp, mp = mp->m_next)
		{
										/* skip entries with wrong mode */
			if (!(mp->m_mode & mode) || (mp->m_mode & ABBREV) != abbrev)
				continue;
			if (!haskey)						/* show all entries */
			{
				showmap(mp);
				did_it = TRUE;
			}
			else								/* do we have a match? */
			{
				if (round)		/* second round: try 'to' string for unmap */
				{
					n = strlen(mp->m_str);
					p = mp->m_str;
				}
				else
				{
					n = mp->m_keylen;
					p = mp->m_keys;
				}
				if (!strncmp(p, keys, (size_t)(n < len ? n : len)))
				{
					if (maptype == 1)			/* delete entry */
					{
						if (n != len)			/* not a full match */
							continue;
						/*
						 * We reset the indicated mode bits. If nothing is left the
						 * entry is deleted below.
						 */
						mp->m_mode &= (~mode | ABBREV);
						did_it = TRUE;			/* remember that we did something */
					}
					else if (!hasarg)			/* show matching entry */
					{
						showmap(mp);
						did_it = TRUE;
					}
					else if (n != len)			/* new entry is ambigious */
					{
						return 3;
					}
					else
					{
						mp->m_mode &= (~mode | ABBREV);		/* remove mode bits */
						if (!(mp->m_mode & ~ABBREV) && !did_it)	/* reuse existing entry */
						{
							newstr = strsave(arg);
							if (newstr == NULL)
								return 4;			/* no mem */
							free(mp->m_str);
							mp->m_str = newstr;
							mp->m_noremap = maptype;
							mp->m_mode = mode + abbrev;
							did_it = TRUE;
						}
					}
					if (!(mp->m_mode & ~ABBREV))		/* entry can be deleted */
					{
						free(mp->m_keys);
						free(mp->m_str);
						mprev->m_next = mp->m_next;
						free(mp);
						mp = mprev;					/* continue with next entry */
					}
				}
			}
		}
	}

	if (maptype == 1)						/* delete entry */
	{
		if (did_it)
			return 0;						/* removed OK */
		else
			return 2;						/* no match */
	}

	if (!haskey || !hasarg)					/* print entries */
	{
#ifdef AMIGA
		settmode(1);
#endif
		if (did_it)
			wait_return(TRUE);
		else if (abbrev)
			msg("No abbreviation found");
		else
			msg("No mapping found");
		return 0;							/* listing finished */
	}

	if (did_it)					/* have added the new entry already */
		return 0;
/*
 * get here when we have to add a new entry
 */
		/* allocate a new entry for the maplist */
	mp = (struct mapblock *)alloc((unsigned)sizeof(struct mapblock));
	if (mp == NULL)
		return 4;			/* no mem */
	mp->m_keys = strsave(keys);
	mp->m_str = strsave(arg);
	if (mp->m_keys == NULL || mp->m_str == NULL)
	{
			free(mp->m_keys);
			free(mp->m_str);
			free(mp);
			return 4;		/* no mem */
	}
	mp->m_keylen = strlen(mp->m_keys);
	mp->m_noremap = maptype;
	mp->m_mode = mode + abbrev;

	/* add the new entry in front of the maplist */
	mp->m_next = maplist.m_next;
	maplist.m_next = mp;

	return 0;				/* added OK */
}

	static void
showmap(mp)
	struct mapblock *mp;
{
	int len;

	if ((mp->m_mode & (INSERT + CMDLINE)) == INSERT + CMDLINE)
		outstr("! ");
	else if (mp->m_mode & INSERT)
		outstr("i ");
	else if (mp->m_mode & CMDLINE)
		outstr("c ");
	len = outtrans(mp->m_keys, -1);	/* get length of what we have written */
	do
	{
		outchar(' ');				/* padd with blanks */
		++len;
	} while (len < 12);
	if (mp->m_noremap)
		outchar('*');
	else
		outchar(' ');
	outtrans(mp->m_str, -1);
	outchar('\n');
	flushbuf();
}

/*
 * Check for an abbreviation.
 * Cursor is at ptr[col]. When inserting, mincol is where insert started.
 * "c" is the character typed before check_abbr was called.
 */
	int
check_abbr(c, ptr, col, mincol)
	int		c;
	char	*ptr;
	int		col;
	int		mincol;
{
	int				len;
	int				j;
	char			tb[3];
	struct mapblock *mp;

	for (len = col; len > 0 && isidchar(ptr[len - 1]); --len)
		;
	if (len < mincol)
		len = mincol;
	if (len < col)				/* there is a word in front of the cursor */
	{
		ptr += len;
		len = col - len;
		for (mp = maplist.m_next; mp; mp = mp->m_next)
		{
					/* find entries with right mode and keys */
			if ((mp->m_mode & ABBREV) == ABBREV &&
						(mp->m_mode & State) &&
						mp->m_keylen == len &&
						!strncmp(mp->m_keys, ptr, (size_t)len))
				break;
		}
		if (mp)								/* found a match */
		{
			j = 0;
			if (c < 0x100 && (c < ' ' || c > '~'))
				tb[j++] = Ctrl('V');		/* special char needs CTRL-V */
			tb[j++] = c;
			tb[j] = NUL;
			ins_typestr(tb, TRUE);			/* insert the last typed char */
			ins_typestr(mp->m_str, mp->m_noremap);	/* insert the to string */
			while (len--)
				ins_typestr("\b", TRUE);	/* delete the from string */
			return TRUE;
		}
	}
	return FALSE;
}

/*
 * Write map commands for the current mappings to an .exrc file.
 * Return 1 on error.
 */
	int
makemap(fd)
	FILE *fd;
{
	struct mapblock *mp;
	char			c1;
	char 			*p;

	for (mp = maplist.m_next; mp; mp = mp->m_next)
	{
		c1 = NUL;
		p = "map";
		switch (mp->m_mode)
		{
		case NORMAL:
			break;
		case CMDLINE + INSERT:
			p = "map!";
			break;
		case CMDLINE:
			c1 = 'c';
			break;
		case INSERT:
			c1 = 'i';
			break;
		case INSERT + CMDLINE + ABBREV:
			p = "abbr";
			break;
		case CMDLINE + ABBREV:
			c1 = 'c';
			p = "abbr";
			break;
		case INSERT + ABBREV:
			c1 = 'i';
			p = "abbr";
			break;
		default:
			emsg("makemap: Illegal mode");
			return 1;
		}
		if (c1 && putc(c1, fd) < 0)
			return 1;
		if (mp->m_noremap && fprintf(fd, "nore") < 0)
			return 1;
		if (fprintf(fd, p) < 0)
			return 1;

		if (	putc(' ', fd) < 0 || putescstr(fd, mp->m_keys, FALSE) < 0 ||
				putc(' ', fd) < 0 || putescstr(fd, mp->m_str, FALSE) < 0 ||
#ifdef MSDOS
				putc('\r', fd) < 0 ||
#endif
				putc('\n', fd) < 0)
			return 1;
	}
	return 0;
}

	int
putescstr(fd, str, set)
	FILE		*fd;
	char		*str;
	int			set;		/* TRUE for makeset, FALSE for makemap */
{
	for ( ; *str; ++str)
	{
		/*
		 * some characters have to be escaped with CTRL-V to
		 * prevent them from misinterpreted in DoOneCmd().
		 * A space has to be escaped with a backslash to
		 * prevent it to be misinterpreted in doset().
		 */
		if (*str < ' ' || *str > '~' || (*str == ' ' && !set))
		{
			if (putc(Ctrl('V'), fd) < 0)
				return -1;
		}
		else if ((set && *str == ' ') || *str == '|')
		{
			if (putc('\\', fd) < 0)
				return -1;
		}
		if (putc(*str, fd) < 0)
			return -1;
	}
	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1