/* vi:set ts=4 sw=4:
 *
 * VIM - Vi IMproved		by Bram Moolenaar
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 */

/*
 * getchar.c
 *
 * functions related with getting a character from the user/mapping/redo/...
 *
 * manipulations with redo buffer and stuff buffer
 * mappings and abbreviations
 */

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

/*
 * structure used to store one block of the stuff/redo/macro buffers
 */
struct bufblock
{
		struct bufblock *b_next;		/* pointer to next bufblock */
		char_u			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 old_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_u			*m_keys;		/* mapped from */
	int				 m_keylen;		/* strlen(m_keys) */
	char_u			*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()
 *
 * typebuf[] contains all characters that are not consumed yet.
 * typebuf[typeoff] is the first valid character in typebuf[].
 * typebuf[typeoff + typelen - 1] is the last valid char.
 * typebuf[typeoff + typelen] must be NUL.
 * The part in front may contain the result of mappings, abbreviations and
 * @a commands. The length of this part is typemaplen.
 * After it are characters that come from the terminal.
 * no_abbr_cnt is the number of characters in typebuf that should not be
 * considered for abbreviations.
 * Some parts of typebuf may not be mapped. These parts are remembered in
 * noremapbuf, which is the same length as typebuf and contains TRUE for the
 * characters that are not to be remapped. noremapbuf[typeoff] is the first
 * valid flag.
 * (typebuf has been put in globals.h, because check_termcode() needs it).
 */
static char_u	*noremapbuf = NULL;       /* flags for typeahead characters */
#define TYPELEN_INIT	(3 * (MAXMAPLEN + 3))
static char_u	typebuf_init[TYPELEN_INIT];			/* initial typebuf */
static char_u	noremapbuf_init[TYPELEN_INIT];		/* initial noremapbuf */

static int		typemaplen = 0;		/* nr of mapped characters in typebuf */
static int		no_abbr_cnt = 0;	/* nr of chars without abbrev. in typebuf */
static int		last_recorded_len = 0;	/* number of last recorded chars */

static void		free_buff __ARGS((struct buffheader *));
static char_u	*get_bufcont __ARGS((struct buffheader *, int));
static void		add_buff __ARGS((struct buffheader *, char_u *));
static void		add_num_buff __ARGS((struct buffheader *, long));
static void		add_char_buff __ARGS((struct buffheader *, int));
static int		read_stuff __ARGS((int));
static void		start_stuff __ARGS((void));
static int		read_redo __ARGS((int, int));
static void		copy_redo __ARGS((int));
static void		init_typebuf __ARGS((void));
static void		gotchars __ARGS((char_u *, int));
static void		may_sync_undo __ARGS((void));
static int		vgetorpeek __ARGS((int));
static int		inchar __ARGS((char_u *, int, long));
static void		map_free __ARGS((struct mapblock *));
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;
				vim_free(p);
		}
		buf->bh_first.b_next = NULL;
}

/*
 * return the contents of a buffer as a single string
 */
	static char_u *
get_bufcont(buffer, dozero)
	struct buffheader	*buffer;
	int					dozero;		/* count == zero is not an error */
{
	long_u			count = 0;
	char_u			*p = NULL;
	char_u			*p2;
	char_u			*str;
	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(bp->b_str);

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

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

	p = get_bufcont(&recordbuff, TRUE);
	free_buff(&recordbuff);
	/*
	 * Remove the characters that were added the last time, these must be the
	 * (possibly mapped) characters that stopped recording.
	 */
	len = STRLEN(p);
	if ((int)len >= last_recorded_len)
		p[len - last_recorded_len] = NUL;
	return (p);
}

/*
 * return the contents of the redo buffer as a single string
 */
	char_u *
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_u						*s;
{
	struct bufblock *p;
	long_u 			n;
	long_u 			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(buf->bh_first.b_next->b_str,
								 buf->bh_first.b_next->b_str + buf->bh_index);
	buf->bh_index = 0;

	if (buf->bh_space >= (int)n)
	{
		strcat((char *)buf->bh_curr->b_str, (char *)s);
		buf->bh_space -= n;
	}
	else
	{
		if (n < MINIMAL_SIZE)
			len = MINIMAL_SIZE;
		else
			len = n;
		p = (struct bufblock *)lalloc((long_u)(sizeof(struct bufblock) + len), TRUE);
		if (p == NULL)
			return; /* no space, just forget it */
		buf->bh_space = len - n;
		STRCPY(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_u	number[32];

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

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

	/*
	 * translate special key code into three byte sequence
	 */
	if (IS_SPECIAL(c) || c == K_SPECIAL || c == NUL)
	{
		temp[0] = K_SPECIAL;
		temp[1] = K_SECOND(c);
		temp[2] = K_THIRD(c);
		temp[3] = NUL;
	}
	else
	{
		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 int
read_stuff(advance)
	int			advance;
{
	register char_u 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;
			vim_free(curr);
			stuffbuff.bh_index = 0;
		}
	}
	return c;
}

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

/*
 * 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;
{
	init_typebuf();

	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(typebuf, MAXMAPLEN, 10L))	
			;
		typeoff = MAXMAPLEN;
		typelen = 0;
	}
	else					/* remove mapped characters only */
	{
		typeoff += typemaplen;
		typelen -= typemaplen;
	}
	typemaplen = 0;
	no_abbr_cnt = 0;
}

/*
 * The previous contents of the redo buffer is kept in old_redobuffer.
 * This is used for the CTRL-O <.> command in insert mode.
 */
	void
ResetRedobuff()
{
	if (!block_redo)
	{
		free_buff(&old_redobuff);
		old_redobuff = redobuff;
		redobuff.bh_first.b_next = NULL;
	}
}

	void
AppendToRedobuff(s)
	char_u		   *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_u		   *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.
 * if init is TRUE, prepare for redo, return FAIL if nothing to redo, OK
 * otherwise
 * if old is TRUE, use old_redobuff instead of redobuff
 */
	static int
read_redo(init, old_redo)
	int			init;
	int			old_redo;
{
	static struct bufblock	*bp;
	static char_u			*p;
	int						c;

	if (init)
	{
		if (old_redo)
			bp = old_redobuff.bh_first.b_next;
		else
			bp = redobuff.bh_first.b_next;
		if (bp == NULL)
			return FAIL;
		p = bp->b_str;
		return OK;
	}
	if ((c = *p) != NUL)
	{
		if (c == K_SPECIAL)
		{
			c = TO_SPECIAL(p[1], p[2]);
			p += 2;
		}
		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)
 * if old_redo is TRUE, use old_redobuff instead of redobuff
 */
	static void
copy_redo(old_redo)
	int		old_redo;
{
	register int c;

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

/*
 * Stuff the redo buffer into the stuffbuff.
 * Insert the redo count into the command.
 * If 'old_redo' is TRUE, the last but one command is repeated
 * instead of the last command (inserting text). This is used for
 * CTRL-O <.> in insert mode
 *
 * return FAIL for failure, OK otherwise
 */
	int
start_redo(count, old_redo)
	long	count;
	int		old_redo;
{
	register int c;

	if (read_redo(TRUE, old_redo) == FAIL)	/* init the pointers; return if nothing to redo */
		return FAIL;

	c = read_redo(FALSE, old_redo);

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

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

	if (c == 'v')	/* redo Visual */
	{
		VIsual = curwin->w_cursor;
		VIsual_active = TRUE;
		redo_VIsual_busy = TRUE;
		c = read_redo(FALSE, old_redo);
	}

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

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

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

	if (read_redo(TRUE, FALSE) == FAIL)
		return FAIL;
	start_stuff();

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

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

	void
set_redo_ins()
{
	block_redo = TRUE;
}

	void
stop_redo_ins()
{
	block_redo = FALSE;
}

/*
 * Initialize typebuf to point to typebuf_init.
 * Alloc() cannot be used here: In out-of-memory situations it would
 * be impossible to type anything.
 */
	static void
init_typebuf()
{
	if (typebuf == NULL)
	{
		typebuf = typebuf_init;
		noremapbuf = noremapbuf_init;
		typebuflen = TYPELEN_INIT;
		typelen = 0;
		typeoff = 0;
	}
}

/*
 * insert a string in position 'offset' in the typeahead buffer (for "@r"
 * and ":normal" command, vgetorpeek() and check_termcode())
 *
 * If noremap is 0, new string can be mapped again.
 * If noremap is -1, new string cannot be mapped again.
 * If noremap is >0, that many characters of the new string cannot be mapped.
 *
 * If nottyped is TRUE, the string does not return KeyTyped (don't use when
 * offset is non-zero!).
 *
 * return FAIL for failure, OK otherwise
 */
	int
ins_typebuf(str, noremap, offset, nottyped)
	char_u	*str;
	int		noremap;
	int		offset;
	int		nottyped;
{
	register char_u	*s1, *s2;
	register int	newlen;
	register int	addlen;
	register int	i;
	register int	newoff;

	init_typebuf();

	addlen = STRLEN(str);
	/*
	 * Easy case: there is room in front of typebuf[typeoff]
	 */
	if (offset == 0 && addlen <= typeoff)
	{
		typeoff -= addlen;
		vim_memmove(typebuf + typeoff, str, (size_t)addlen);
	}
	/*
	 * Need to allocate new buffer.
	 * In typebuf there must always be room for MAXMAPLEN + 4 characters.
	 * We add some extra room to avoid having to allocate too often.
	 */
	else
	{
		newoff = MAXMAPLEN + 4;
		newlen = typelen + addlen + newoff + 2 * (MAXMAPLEN + 4);
		if (newlen < 0)				/* string is getting too long */
		{
			emsg(e_toocompl);		/* also calls flush_buffers */
			setcursor();
			return FAIL;
		}
		s1 = alloc(newlen);
		if (s1 == NULL)				/* out of memory */
			return FAIL;
		s2 = alloc(newlen);
		if (s2 == NULL)				/* out of memory */
		{
			vim_free(s1);
			return FAIL;
		}
		typebuflen = newlen;

				/* copy the old chars, before the insertion point */
		vim_memmove(s1 + newoff, typebuf + typeoff, (size_t)offset);
				/* copy the new chars */
		vim_memmove(s1 + newoff + offset, str, (size_t)addlen);
				/* copy the old chars, after the insertion point, including
				 * the  NUL at the end */
		vim_memmove(s1 + newoff + offset + addlen, typebuf + typeoff + offset,
											  (size_t)(typelen - offset + 1));
		if (typebuf != typebuf_init)
			vim_free(typebuf);
		typebuf = s1;

		vim_memmove(s2 + newoff, noremapbuf + typeoff, (size_t)offset);
		vim_memmove(s2 + newoff + offset + addlen,
				   noremapbuf + typeoff + offset, (size_t)(typelen - offset));
		if (noremapbuf != noremapbuf_init)
			vim_free(noremapbuf);
		noremapbuf = s2;

		typeoff = newoff;
	}
	typelen += addlen;

	/*
	 * Adjust noremapbuf[] for the new characters:
	 * If noremap  < 0: all the new characters are flagged not remappable
	 * If noremap == 0: all the new characters are flagged mappable
	 * If noremap  > 0: 'noremap' characters are flagged not remappable, the
	 *					rest mappable
	 */
	if (noremap < 0)		/* length not specified */
		noremap = addlen;
	for (i = 0; i < addlen; ++i)
		noremapbuf[typeoff + i + offset] = (noremap-- > 0);

					/* this is only correct for offset == 0! */
	if (nottyped)						/* the inserted string is not typed */
		typemaplen += addlen;
	if (no_abbr_cnt && offset == 0)		/* and not used for abbreviations */
		no_abbr_cnt += addlen;

	return OK;
}

/*
 * Return TRUE if there are no characters in the typeahead buffer that have
 * not been typed (result from a mapping or come from ":normal").
 */
	int
typebuf_typed()
{
	return typemaplen == 0;
}

/*
 * remove "len" characters from typebuf[typeoff + offset]
 */
	void
del_typebuf(len, offset)
	int	len;
	int	offset;
{
	int		i;

	typelen -= len;
	/*
	 * Easy case: Just increase typeoff.
	 */
	if (offset == 0 && typebuflen - (typeoff + len) >= MAXMAPLEN + 3)
		typeoff += len;
	/*
	 * Have to move the characters in typebuf[] and noremapbuf[]
	 */
	else
	{
		i = typeoff + offset;
		/*
		 * Leave some extra room at the end to avoid reallocation.
		 */
		if (typeoff > MAXMAPLEN)
		{
			vim_memmove(typebuf + MAXMAPLEN, typebuf + typeoff, (size_t)offset);
			vim_memmove(noremapbuf + MAXMAPLEN, noremapbuf + typeoff,
															  (size_t)offset);
			typeoff = MAXMAPLEN;
		}
			/* adjust typebuf (include the NUL at the end) */
		vim_memmove(typebuf + typeoff + offset, typebuf + i + len,
											  (size_t)(typelen - offset + 1));
			/* adjust noremapbuf[] */
		vim_memmove(noremapbuf + typeoff + offset, noremapbuf + i + len,
												  (size_t)(typelen - offset));
	}

	if (typemaplen > offset)			/* adjust typemaplen */
	{
		if (typemaplen < offset + len)
			typemaplen = offset;
		else
			typemaplen -= len;
	}
	if (no_abbr_cnt > offset)			/* adjust no_abbr_cnt */
	{
		if (no_abbr_cnt < offset + len)
			no_abbr_cnt = offset;
		else
			no_abbr_cnt -= len;
	}
}

/*
 * Write typed characters to script file.
 * If recording is on put the character in the recordbuffer.
 */
	static void
gotchars(s, len)
	char_u	*s;
	int		len;
{
	int		c;
	char_u	buf[2];

	/* remember how many chars were last recorded */
	if (Recording)
		last_recorded_len += len;

	buf[1] = NUL;
	while (len--)
	{
		c = *s++;
		updatescript(c);

		if (Recording)
		{
			buf[0] = c;
			add_buff(&recordbuff, buf);
		}
	}
	may_sync_undo();
}

/*
 * Sync undo.  Called when typed characters are obtained from the typeahead
 * buffer, or when a menu is used.
 * Do not sync in insert mode, unless cursor key has been used.
 * Also don't sync while reading a script file.
 */
	static void
may_sync_undo()
{
	if ((!(State & (INSERT + CMDLINE)) || arrow_used) &&
												  scriptin[curscript] == NULL)
		u_sync();
}

/*
 * open new script file for ":so!" command
 * return OK on success, FAIL on error
 */
	int
openscript(name)
	char_u *name;
{
	int oldcurscript;

	if (curscript + 1 == NSCRIPT)
	{
		emsg(e_nesting);
		return FAIL;
	}
	else
	{
		if (scriptin[curscript] != NULL)	/* already reading script */
			++curscript;
									/* use NameBuff for expanded name */
		expand_env(name, NameBuff, MAXPATHL);
		if ((scriptin[curscript] = fopen((char *)NameBuff, READBIN)) == NULL)
		{
			emsg2(e_notopen, name);
			if (curscript)
				--curscript;
			return FAIL;
		}
		/*
		 * With command ":g/pat/so! file" we have to execute the
		 * commands from the file now.
		 */
		if (global_busy)
		{
			State = NORMAL;
			oldcurscript = curscript;
			do
			{
				normal();
				vpeekc();			/* check for end of file */
			}
			while (scriptin[oldcurscript]);
			State = CMDLINE;
		}
	}
	return OK;
}

/*
 * updatescipt() is called when a character can be written into the script file
 * or when we have waited some time for a character (c == 0)
 *
 * All the changed memfiles are synced if c == 0 or when the number of typed
 * characters reaches 'updatecount' and 'updatecount' is non-zero.
 */
	void
updatescript(c)
	int c;
{
	static int		count = 0;

	if (c && scriptout)
		putc(c, scriptout);
	if (c == 0 || (p_uc > 0 && ++count >= p_uc))
	{
		ml_sync_all(c == 0, TRUE);
		count = 0;
	}
}

#define K_NEEDMORET -1			/* keylen value for incomplete key-code */
#define M_NEEDMORET -2			/* keylen value for incomplete mapping */

static int old_char = -1;		/* ungotten character */

	int
vgetc()
{
	int		c, c2;

	mod_mask = 0x0;
	last_recorded_len = 0;
	for (;;)					/* this is done twice if there are modifiers */
	{
		if (mod_mask)			/* no mapping after modifier has been read */
		{
			++no_mapping;
			++allow_keys;
		}
		c = vgetorpeek(TRUE);
		if (mod_mask)
		{
			--no_mapping;
			--allow_keys;
		}

		/* Get two extra bytes for special keys */
		if (c == K_SPECIAL)
		{
			++no_mapping;
			c2 = vgetorpeek(TRUE);		/* no mapping for these chars */
			c = vgetorpeek(TRUE);
			--no_mapping;
			if (c2 == KS_MODIFIER)
			{
				mod_mask = c;
				continue;
			}
			c = TO_SPECIAL(c2, c);
		}
#ifdef MSDOS
		/*
		 * If K_NUL was typed, it is replaced by K_NUL, 3 in mch_inchar().
		 * Delete the 3 here.
		 */
		else if (c == K_NUL && vpeekc() == 3)
			(void)vgetorpeek(TRUE);
#endif

		return check_shifted_spec_key(c);
	}
}

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

/*
 * Call vpeekc() without causing anything to be mapped.
 * Return TRUE if a character is available, FALSE otherwise.
 */
	int
char_avail()
{
	int		retval;

	++no_mapping;
	retval = vgetorpeek(FALSE);
	--no_mapping;
	return (retval != NUL);
}

	void
vungetc(c)		/* unget one character (can only be done once!) */
	int		c;
{
	old_char = c;
}

/*
 * get a character: 1. from a previously ungotten character
 *					2. from the stuffbuffer
 *					3. from the typeahead buffer
 *					4. from the user
 *
 * if "advance" is TRUE (vgetc()):
 *		really get the character.
 *		KeyTyped is set to TRUE in the case the user typed the key.
 *		KeyStuffed is TRUE if the character comes from the stuff buffer.
 * if "advance" is FALSE (vpeekc()):
 *		just look whether there is a character available.
 */

	static int
vgetorpeek(advance)
	int		advance;
{
	register int	c, c1;
	int				keylen = 0;				/* init for gcc */
#ifdef AMIGA
	char_u			*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 */
	int				local_State;
	register int	mlen;
	int				max_mlen;
	int				i;
#ifdef USE_GUI
	int				idx;
#endif

	/*
	 * VISUAL state is never set, it is used only here, therefore a check is
	 * made if NORMAL state is actually VISUAL state.
	 */
	local_State = State;
	if ((State & NORMAL) && VIsual_active)
		local_State = VISUAL;

/*
 * get a character: 1. from a previously ungotten character
 */
	if (old_char >= 0)
	{
		c = old_char;
		if (advance)
			old_char = -1;
		return c;
	}

	if (advance)
		KeyStuffed = FALSE;

	init_typebuf();
	start_stuff();
	if (advance && typemaplen == 0)
		Exec_reg = FALSE;
	do
	{
/*
 * get a character: 2. from the stuffbuffer
 */
		c = read_stuff(advance);
		if (c != NUL && !got_int)
		{
			if (advance)
			{
				KeyTyped = FALSE;
				KeyStuffed = TRUE;
			}
			if (no_abbr_cnt == 0)
				no_abbr_cnt = 1;		/* no abbreviations now */
		}
		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 (;;)
			{
				/*
				 * mch_breakcheck() is slow, don't use it too often when
				 * inside a mapping.  But call it each time for typed
				 * characters.
				 */
				if (typemaplen)
					line_breakcheck();
				else
					mch_breakcheck();			/* check for CTRL-C */
				if (got_int)
				{
					c = inchar(typebuf, MAXMAPLEN, 0L);	/* 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 (typelen > 0)	/* check for a mappable key sequence */
				{
					/*
					 * walk through the maplist until we find an
					 * entry that matches.
					 *
					 * Don't look for mappings if:
					 * - timed out
					 * - no_mapping set: mapping disabled (e.g. for CTRL-V)
					 * - typebuf[typeoff] should not be remapped
					 * - in insert or cmdline mode and 'paste' option set
					 * - waiting for "hit return to continue" and CR or SPACE
					 *   typed
					 * - waiting for a char with --more--
					 * - in Ctrl-X mode, and we get a valid char for that mode
					 */
					mp = NULL;
					max_mlen = 0;
					if (!timedout && no_mapping == 0 && (typemaplen == 0 ||
								(p_remap && noremapbuf[typeoff] == FALSE))
							&& !(p_paste && (State & (INSERT + CMDLINE)))
							&& !(State == HITRETURN && (typebuf[typeoff] == CR
								|| typebuf[typeoff] == ' '))
							&& State != ASKMORE
#ifdef INSERT_EXPAND
							&& !(ctrl_x_mode && is_ctrl_x_key(typebuf[typeoff]))
#endif
							)
					{
						c1 = typebuf[typeoff];
#ifdef HAVE_LANGMAP
						LANGMAP_ADJUST(c1, TRUE);
#endif
						for (mp = maplist.m_next; mp; mp = mp->m_next)
						{
							/*
							 * Only consider an entry if
							 * - the first character matches and
							 * - it is not an abbreviation and
							 * - it is for the current state
							 */
							if (		mp->m_keys[0] == c1 &&
										!(mp->m_mode & ABBREV) &&
										(mp->m_mode & local_State))
							{
								int		n;
#ifdef HAVE_LANGMAP
								int		c2;
#endif

									/* find the match length of this mapping */
								for (mlen = 1; mlen < typelen; ++mlen)
								{
#ifdef HAVE_LANGMAP
									c2 = typebuf[typeoff + mlen];
									LANGMAP_ADJUST(c2, TRUE);
									if (mp->m_keys[mlen] != c2)
#else
									if (mp->m_keys[mlen] !=
													typebuf[typeoff + mlen])
#endif
										break;
								}

									/* if one of the typed keys cannot be
									 * remapped, skip the entry */
								for (n = 0; n < mlen; ++n)
									if (noremapbuf[typeoff + n] == TRUE)
										break;
								if (n != mlen)
									continue;

									/* (partly) match found */
								keylen = mp->m_keylen;
								if (mlen == (keylen > typelen ?
													typelen : keylen))
								{
										/* partial match, need more chars */
									if (keylen > typelen)
										keylen = M_NEEDMORET;
									break;	
								}
									/* no match, may have to check for
									 * termcode at next character */
								if (max_mlen < mlen)
									max_mlen = mlen;
							}
						}
					}
					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 ((no_mapping == 0 || allow_keys != 0) &&
														   (typemaplen == 0 ||
								(p_remap && noremapbuf[typeoff] == FALSE)) &&
											!timedout)
							keylen = check_termcode(max_mlen + 1);
						else
							keylen = 0;
						if (keylen == 0)		/* no matching terminal code */
						{
#ifdef AMIGA					/* check for window bounds report */
							if (typemaplen == 0 &&
											(typebuf[typeoff] & 0xff) == CSI)
							{
								for (s = typebuf + typeoff + 1;
										s < typebuf + typeoff + typelen &&
										(isdigit(*s) || *s == ';' || *s == ' ');
										++s)
									;
								if (*s == 'r' || *s == '|')	/* found one */
								{
									del_typebuf((int)(s + 1 -
													   (typebuf + typeoff)), 0);
										/* get size and redraw screen */
									set_winsize(0, 0, FALSE);
									continue;
								}
								if (*s == NUL)		/* need more characters */
									keylen = K_NEEDMORET;
							}
							if (keylen >= 0)
#endif
							{
/*
 * get a character: 3. from the typeahead buffer
 */
								c = typebuf[typeoff] & 255;
								if (advance)	/* remove chars from typebuf */
								{
									if (typemaplen)
										KeyTyped = FALSE;
									else
									{
										KeyTyped = TRUE;
										/* write char to script file(s) */
										gotchars(typebuf + typeoff, 1);
									}
									del_typebuf(1, 0);
								}
								break;		/* got character, break for loop */
							}
						}
						if (keylen > 0)		/* full matching terminal code */
						{
#ifdef USE_GUI
							if (typebuf[typeoff] == K_SPECIAL &&
											  typebuf[typeoff + 1] == KS_MENU)
							{
								/*
								 * Using a menu may cause a break in undo!
								 * It's like using gotchars(), but without
								 * recording or writing to a script file.
								 */
								may_sync_undo();
								del_typebuf(3, 0);
								idx = gui_get_menu_index(current_menu,
																 local_State);
								if (idx != MENU_INDEX_INVALID)
								{
									ins_typebuf(current_menu->strings[idx],
										current_menu->noremap[idx] ? -1 : 0,
										0, TRUE);
								}
							}
#endif /* USE_GUI */
							continue;	/* try mapping again */
						}

						/* partial match: get some more characters */
						keylen = K_NEEDMORET;
					}
						/* complete match */
					if (keylen >= 0 && keylen <= typelen)
					{
										/* write chars to script file(s) */
						if (keylen > typemaplen)
							gotchars(typebuf + typeoff + typemaplen,
														keylen - typemaplen);

						del_typebuf(keylen, 0);	/* 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 >= p_mmd)
						{
							EMSG("recursive mapping");
							if (State == CMDLINE)
								redrawcmdline();
							else
								setcursor();
							flush_buffers(FALSE);
							mapdepth = 0;		/* for next one */
							c = -1;
							break;
						}
						/*
						 * Insert the 'to' part in the typebuf.
						 * If 'from' field is the same as the start of the
						 * 'to' field, don't remap the first character.
						 * If m_noremap is set, don't remap the whole 'to'
						 * part.
						 */
						if (ins_typebuf(mp->m_str, mp->m_noremap ? -1 :
												  STRNCMP(mp->m_str, mp->m_keys,
													  (size_t)keylen) ? 0 : 1,
															   0, TRUE) == FAIL)
						{
							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 && typelen == 1 && typebuf[typeoff] == ESC &&
						 !no_mapping && typemaplen == 0 && (State & INSERT) &&
					   (p_timeout || (keylen == K_NEEDMORET && p_ttimeout)) &&
						(c = inchar(typebuf + typeoff + typelen, 3, 0L)) == 0)
				{
					if (p_smd)
					{
						delmode();
						mode_deleted = TRUE;
					}
					if (curwin->w_cursor.col != 0)	/* move cursor one left if
														possible */
					{
						if (curwin->w_col)
						{
							if (did_ai)
							{
								/*
								 * We are expecting to truncate the trailing
								 * white-space, so find the last non-white
								 * character -- webb
								 */
								colnr_t		col, vcol;
								char_u		*ptr;

								col = vcol = curwin->w_col = 0;
								ptr = ml_get_curline();
								while (col < curwin->w_cursor.col)
								{
									if (!vim_iswhite(ptr[col]))
										curwin->w_col = vcol;
									vcol += lbr_chartabsize(ptr + col,
															   (colnr_t)vcol);
									++col;
								}
  								if (curwin->w_p_nu)
									curwin->w_col += 8;
							}
							else
								--curwin->w_col;
						}
						else if (curwin->w_p_wrap && curwin->w_row)
						{
								--curwin->w_row;
								curwin->w_col = Columns - 1;
						}
					}
					setcursor();
					flushbuf();
				}
				typelen += c;
													/* buffer full, don't map */
				if (typelen >= typemaplen + MAXMAPLEN)
				{
					timedout = TRUE;
					continue;
				}
/*
 * get a character: 4. from the user
 */
				/*
				 * If we have a partial match (and are going to wait for more
				 * input from the user), show the partially matched characters
				 * to the user with showcmd -- webb.
				 */
				i = 0;
				if (typelen > 0 && (State & (NORMAL | INSERT)) && advance)
				{
					push_showcmd();
					while (i < typelen)
						(void)add_to_showcmd(typebuf[typeoff + i++], TRUE);
				}

				c = inchar(typebuf + typeoff + typelen,
						typemaplen + MAXMAPLEN - typelen,
						!advance
							? 0
							: ((typelen == 0 || !(p_timeout || (p_ttimeout &&
										keylen == K_NEEDMORET)))
									? -1L
									: ((keylen == K_NEEDMORET && p_ttm >= 0)
											? p_ttm
											: p_tm)));

				if (i)
					pop_showcmd();

				if (c <= NUL)		/* no character available */
				{
					if (!advance)
						break;
					if (typelen)				/* timed out */
					{
						timedout = TRUE;
						continue;
					}
				}
				else
				{			/* allow mapping for just typed characters */
					while (typebuf[typeoff + typelen] != NUL)
						noremapbuf[typeoff + typelen++] = FALSE;
				}
			}		/* for (;;) */
		}		/* if (!character from stuffbuf) */

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

	/*
	 * The "INSERT" message is taken care of here:
	 *   if we return an ESC to exit insert mode, the message is deleted
	 *   if we don't return an ESC but deleted the message before, redisplay it
	 */
	if (advance && p_smd && (State & INSERT))
	{
		if (c == ESC && !mode_deleted && !no_mapping)
			delmode();
		else if (c != ESC && mode_deleted)
			showmode();
	}

	return c;
}

/*
 * inchar() - get one character from
 *		1. a scriptfile
 *		2. the keyboard
 *
 *  As much characters as we can get (upto 'maxlen') are put in buf and
 *  NUL terminated (buffer length must be 'maxlen' + 1).
 *  Minimum for 'maxlen' is 3!!!!
 *
 *	If we got an interrupt all input is read until none is available.
 *
 *  If wait_time == 0  there is no waiting for the char.
 *  If wait_time == n  we wait for n msec for a character to arrive.
 *  If wait_time == -1 we wait forever for a character to arrive.
 *
 *  Return the number of obtained characters.
 */

	static int
inchar(buf, maxlen, wait_time)
	char_u	*buf;
	int		maxlen;
	long	wait_time;						/* milli seconds */
{
	int				len = 0;			/* init for GCC */
	int				retesc = FALSE;		/* return ESC with gotint */
	register int 	c;
	register int	i;

	if (wait_time == -1L || wait_time > 100L)  /* flush output before waiting */
	{
		cursor_on();
		flushbuf();
	}
	/*
	 * Don't reset these when at the hit-return prompt, otherwise a endless
	 * recursive loop may result (write error in swapfile, hit-return, timeout
	 * on char wait, flush swapfile, write error....).
	 */
	if (State != HITRETURN)
	{
		did_outofmem_msg = FALSE;	/* display out of memory message (again) */
		did_swapwrite_msg = FALSE;	/* display swap file write error again */
	}
	undo_off = FALSE;			/* restart undo now */

/*
 * first try script file
 *	If interrupted: Stop reading script files.
 */
	c = -1;
	while (scriptin[curscript] != NULL && c < 0)
	{
		if (got_int || (c = getc(scriptin[curscript])) < 0)	/* reached EOF */
		{
				/* when reading script file is interrupted, return an ESC to
									get back to normal mode */
			if (got_int)
				retesc = TRUE;
			fclose(scriptin[curscript]);
			scriptin[curscript] = NULL;
			if (curscript > 0)
				--curscript;
		}
		else
		{
			buf[0] = c;
			len = 1;
		}
	}

	if (c < 0)			/* did not get a character from script */
	{
	/*
	 * If we got an interrupt, skip all previously typed characters and
	 * return TRUE if quit reading script file.
	 */
		if (got_int)			/* skip typed characters */
		{
			while (mch_inchar(buf, maxlen, 0L))
				;
			return retesc;
		}
			/* fill up to a third of the buffer, because each character may be
			 * tripled below */
		len = mch_inchar(buf, maxlen / 3, wait_time);
	}

	/*
	 * Two characters are special: NUL and K_SPECIAL.
	 * Replace       NUL by K_SPECIAL KS_ZERO	 K_FILLER
	 * Replace K_SPECIAL by K_SPECIAL KS_SPECIAL K_FILLER
	 * Don't replace K_SPECIAL when reading a script file.
	 */
	for (i = len; --i >= 0; ++buf)
	{
		if (buf[0] == NUL || (buf[0] == K_SPECIAL && c < 0))
		{
			vim_memmove(buf + 3, buf + 1, (size_t)i);
			buf[2] = K_THIRD(buf[0]);
			buf[1] = K_SECOND(buf[0]);
			buf[0] = K_SPECIAL;
			buf += 2;
			len += 2;
		}
	}
	*buf = NUL;								/* add trailing NUL */
	return len;
}

/*
 * 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. Note: keys cannot be a read-only string,
 * it will be modified.
 *
 * for :map	  mode is NORMAL + VISUAL
 * for :map!  mode is INSERT + CMDLINE
 * for :cmap  mode is CMDLINE
 * for :imap  mode is INSERT 
 * for :nmap  mode is NORMAL
 * for :vmap  mode is VISUAL
 * 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
do_map(maptype, keys, mode)
	int		maptype;
	char_u	*keys;
	int		mode;
{
	struct mapblock		*mp, *mprev;
	char_u				*arg;
	char_u				*p;
	int					n;
	int					len = 0;		/* init for GCC */
	char_u				*newstr;
	int					hasarg;
	int					haskey;
	int					did_it = FALSE;
	int					abbrev = 0;
	int					round;
	char_u				*keys_buf = NULL;
	char_u				*arg_buf = NULL;
	int					retval = 0;
	int					do_backslash;

	if (mode & ABBREV)		/* not a mapping but an abbreviation */
	{
		abbrev = ABBREV;
		mode &= ~ABBREV;
	}
/*
 * find end of keys and skip CTRL-Vs (and backslashes) in it
 * Accept backslash like CTRL-V when 'cpoptions' does not contain 'B'.
 * with :unmap white space is included in the keys, no argument possible
 */
	p = keys;
	do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL);
	while (*p && (maptype == 1 || !vim_iswhite(*p)))
	{
		if ((p[0] == Ctrl('V') || (do_backslash && p[0] == '\\')) &&
																  p[1] != NUL)
			++p;				/* skip CTRL-V or backslash */
		++p;
	}
	if (*p != NUL)
		*p++ = NUL;
	p = skipwhite(p);
	arg = p;
	hasarg = (*arg != NUL);
	haskey = (*keys != NUL);

		/* check for :unmap without argument */
	if (maptype == 1 && !haskey)	
	{
		retval = 1;
		goto theend;
	}

	/*
	 * If mapping has been given as ^V<C_UP> say, then replace the term codes
	 * with the appropriate two bytes. If it is a shifted special key, unshift
	 * it too, giving another two bytes.
	 * replace_termcodes() may move the result to allocated memory, which
	 * needs to be freed later (*keys_buf and *arg_buf).
	 * replace_termcodes() also removes CTRL-Vs and sometimes backslashes.
	 */
	if (haskey)
		keys = replace_termcodes(keys, &keys_buf, TRUE);
	if (hasarg)
		arg = replace_termcodes(arg, &arg_buf, FALSE);

/*
 * check arguments and translate function keys
 */
	if (haskey)
	{
		len = STRLEN(keys);
		if (len > MAXMAPLEN)			/* maximum length of MAXMAPLEN chars */
		{
			retval = 1;
			goto theend;
		}

		if (abbrev)
		{
			/*
			 * If an abbreviation ends in a keyword character, the
			 * rest must be all keyword-char or all non-keyword-char.
			 * Otherwise we won't be able to find the start of it in a
			 * vi-compatible way.
			 * An abbrevation cannot contain white space.
			 */
			if (iswordchar(keys[len - 1]))  	/* ends in keyword char */
				for (n = 0; n < len - 2; ++n)
					if (iswordchar(keys[n]) != iswordchar(keys[len - 2]))
					{
						retval = 1;
						goto theend;
					}
			for (n = 0; n < len; ++n)
				if (vim_iswhite(keys[n]))
				{
					retval = 1;
					goto theend;
				}
		}
	}

	if (haskey && hasarg && abbrev)		/* if we will add an abbreviation */
		no_abbr = FALSE;				/* reset flag that indicates there are
															no abbreviations */

	if (!haskey || (maptype != 1 && !hasarg))
		msg_start();
/*
 * 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 && !got_int; ++round)
	{
		for (mp = maplist.m_next, mprev = &maplist; mp && !got_int;
												  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 */
					{
						if (abbrev)				/* for abbreviations that's ok */
							continue;
						retval = 3;
						goto theend;
					}
					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)
							{
								retval = 4;			/* no mem */
								goto theend;
							}
							vim_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 */
					{
						map_free(mprev);
						mp = mprev;				/* continue with next entry */
					}
				}
			}
		}
	}

	if (maptype == 1)						/* delete entry */
	{
		if (!did_it)
			retval = 2;						/* no match */
		goto theend;
	}

	if (!haskey || !hasarg)					/* print entries */
	{
		if (!did_it)
		{
			if (abbrev)
				MSG("No abbreviation found");
			else
				MSG("No mapping found");
		}
		goto theend;						/* listing finished */
	}

	if (did_it)					/* have added the new entry already */
		goto theend;
/*
 * 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)
	{
		retval = 4;			/* no mem */
		goto theend;
	}
	mp->m_keys = strsave(keys);
	mp->m_str = strsave(arg);
	if (mp->m_keys == NULL || mp->m_str == NULL)
	{
		vim_free(mp->m_keys);
		vim_free(mp->m_str);
		vim_free(mp);
		retval = 4;		/* no mem */
		goto theend;
	}
	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;

theend:
	vim_free(keys_buf);
	vim_free(arg_buf);
	return retval;
}

/*
 * Delete one entry from the maplist.
 * The argument is a pointer to the PREVIOUS entry!
 */
	static void
map_free(mprev)
	struct mapblock		*mprev;
{
	struct mapblock		*mp;
	
	mp = mprev->m_next;
	vim_free(mp->m_keys);
	vim_free(mp->m_str);
	mprev->m_next = mp->m_next;
	vim_free(mp);
}

/*
 * Clear all mappings or abbreviations.
 * 'abbr' should be FALSE for mappings, TRUE for abbreviations.
 */
	void
map_clear(modec, forceit, abbr)
	int		modec;
	int		forceit;
	int		abbr;
{
	struct mapblock		*mp;
	int		mode;

	if (forceit)			/* :mapclear! */
		mode = INSERT + CMDLINE;
	else if (modec == 'i')
		mode = INSERT;
	else if (modec == 'n')
		mode = NORMAL;
	else if (modec == 'c')
		mode = CMDLINE;
	else if (modec == 'v')
		mode = VISUAL;
	else
		mode = VISUAL + NORMAL;

	for (mp = &maplist; mp->m_next != NULL; )
	{
		if (abbr != !(mp->m_next->m_mode & ABBREV) && mp->m_next->m_mode & mode)
		{
			mp->m_next->m_mode &= ~mode;
			if ((mp->m_next->m_mode & ~ABBREV) == 0) /* entry can be deleted */
			{
				map_free(mp);
				continue;
			}
		}
		mp = mp->m_next;
	}
}

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

	if (msg_didout)
		msg_outchar('\n');
	if ((mp->m_mode & (INSERT + CMDLINE)) == INSERT + CMDLINE)
		MSG_OUTSTR("! ");
	else if (mp->m_mode & INSERT)
		MSG_OUTSTR("i ");
	else if (mp->m_mode & CMDLINE)
		MSG_OUTSTR("c ");
	else if (!(mp->m_mode & VISUAL))
		MSG_OUTSTR("n ");
	else if (!(mp->m_mode & NORMAL))
		MSG_OUTSTR("v ");
	else
		MSG_OUTSTR("  ");
	/* Get length of what we write */
	len = msg_outtrans_special(mp->m_keys, TRUE);
	do
	{
		msg_outchar(' ');				/* padd with blanks */
		++len;
	} while (len < 12);
	if (mp->m_noremap)
		msg_outchar('*');
	else
		msg_outchar(' ');
	/* Use FALSE below if we only want things like <Up> to show up as such on
	 * the rhs, and not M-x etc, TRUE gets both -- webb
	 */
	msg_outtrans_special(mp->m_str, TRUE);
	flushbuf();							/* show one line at a time */
}

/*
 * 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.
 *
 * Historic vi practice: The last character of an abbreviation must be an id
 * character ([a-zA-Z0-9_]). The characters in front of it must be all id
 * characters or all non-id characters. This allows for abbr. "#i" to
 * "#include".
 *
 * Vim addition: Allow for abbreviations that end in a non-keyword character.
 * Then there must be white space before the abbr.
 *
 * return TRUE if there is an abbreviation, FALSE if not
 */
	int
check_abbr(c, ptr, col, mincol)
	int		c;
	char_u	*ptr;
	int		col;
	int		mincol;
{
	int				len;
	int				j;
	char_u			tb[4];
	struct mapblock *mp;
	int				is_id = TRUE;
	int				vim_abbr;

	if (no_abbr_cnt)		/* abbrev. are not recursive */
		return FALSE;

	/*
	 * Check for word before the cursor: If it ends in a keyword char all
	 * chars before it must be al keyword chars or non-keyword chars, but not
	 * white space. If it ends in a non-keyword char we accept any characters
	 * before it except white space.
	 */
	if (col == 0)								/* cannot be an abbr. */
		return FALSE;

	if (!iswordchar(ptr[col - 1]))
		vim_abbr = TRUE;						/* Vim added abbr. */
	else
	{
		vim_abbr = FALSE;						/* vi compatible abbr. */
		if (col > 1)
			is_id = iswordchar(ptr[col - 2]);
	}
	for (len = col - 1; len > 0 && !vim_isspace(ptr[len - 1]) &&
					   (vim_abbr || is_id == iswordchar(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:
			 * Insert the rest of the abbreviation in typebuf[].
			 * This goes from end to start.
			 *
			 * Characters 0x000 - 0x100: normal chars, may need CTRL-V,
			 * except K_SPECIAL: Becomes K_SPECIAL KS_SPECIAL K_FILLER
			 * Characters where IS_SPECIAL() == TRUE: key codes, need
			 * K_SPECIAL. Other characters (with ABBR_OFF): don't use CTRL-V.
			 */
			j = 0;
											/* special key code, split up */
			if (IS_SPECIAL(c) || c == K_SPECIAL)
			{
				tb[j++] = K_SPECIAL;
				tb[j++] = K_SECOND(c);
				c = K_THIRD(c);
			}
			else if (c < 0x100  && (c < ' ' || c > '~'))
				tb[j++] = Ctrl('V');		/* special char needs CTRL-V */
			tb[j++] = c;
			tb[j] = NUL;
											/* insert the last typed char */
			(void)ins_typebuf(tb, TRUE, 0, TRUE);
											/* insert the to string */
			(void)ins_typebuf(mp->m_str, mp->m_noremap ? -1 : 0, 0, TRUE);
											/* no abbrev. for these chars */
			no_abbr_cnt += STRLEN(mp->m_str) + j + 1;

			tb[0] = Ctrl('H');
			tb[1] = NUL;
			while (len--)					/* delete the from string */
				(void)ins_typebuf(tb, TRUE, 0, TRUE);
			return TRUE;
		}
	}
	return FALSE;
}

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

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

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

/*
 * write escape string to file
 *
 * return FAIL for failure, OK otherwise
 */
	int
putescstr(fd, str, set)
	FILE		*fd;
	char_u		*str;
	int			set;		/* TRUE for makeset, FALSE for makemap */
{
	int		c;
	int		modifiers;

	for ( ; *str; ++str)
	{
		c = *str;
		/*
		 * Special key codes have to be translated to be able to make sense
		 * when they are read back.
		 */
		if (c == K_SPECIAL && !set)
		{
			modifiers = 0x0;
			if (str[1] == KS_MODIFIER)
			{
				modifiers = str[2];
				str += 3;
				c = *str;
			}
			if (c == K_SPECIAL)
			{
				c = TO_SPECIAL(str[1], str[2]);
				str += 2;
			}
			if (IS_SPECIAL(c) || modifiers)		/* special key */
			{
				fprintf(fd, (char *)get_special_key_name(c, modifiers));
				continue;
			}
		}
		/*
		 * A '\n' in a map command should be written as <NL>.
		 * A '\n' in a set command should be written as \^V^J.
		 */
		if (c == NL)
		{
			if (set)
				fprintf(fd, "\\\026\n");
			else
				fprintf(fd, "<NL>");
			continue;
		}
		/*
		 * some characters have to be escaped with CTRL-V to
		 * prevent them from misinterpreted in DoOneCmd().
		 * A space, Tab and '"' has to be escaped with a backslash to
		 * prevent it to be misinterpreted in do_set().
		 */
		if (set && (vim_iswhite(c) || c == '"' || c == '\\'))
		{
			if (putc('\\', fd) < 0)
				return FAIL;
		}
		else if (c < ' ' || c > '~' || c == '|')
		{
			if (putc(Ctrl('V'), fd) < 0)
				return FAIL;
		}
		if (putc(c, fd) < 0)
			return FAIL;
	}
	return OK;
}

/*
 * Check all mappings for the presence of special key codes.
 * Used after ":set term=xxx".
 */
	void
check_map_keycodes()
{
	struct mapblock *mp;
	char_u 			*p;
	int				i;
	char_u			buf[3];
	char_u			*save_name;

	save_name = sourcing_name;
	sourcing_name = (char_u *)"mappings";/* don't give error messages */
	for (mp = maplist.m_next; mp != NULL; mp = mp->m_next)
	{
		for (i = 0; i <= 1; ++i)		/* do this twice */
		{
			if (i == 0)
				p = mp->m_keys;			/* once for the "from" part */
			else
				p = mp->m_str;			/* and once for the "to" part */
			while (*p)
			{
				if (*p == K_SPECIAL)
				{
					++p;
					if (*p < 128)		/* only for "normal" termcap entries */
					{
						buf[0] = p[0];
						buf[1] = p[1];
						buf[2] = NUL;
						(void)add_termcap_entry(buf, FALSE);
					}
					++p;
				}
				++p;
			}
		}
	}
	sourcing_name = save_name;
}
