ftp.nice.ch/pub/next/unix/editor/vim.3.0.s.tar.gz#/vim-3.0/src/edit.c

This is edit.c in view mode; [Download] [Up]

/* vi:ts=4:sw=4
 *
 * VIM - Vi IMproved		by Bram Moolenaar
 *
 * Read the file "credits.txt" for a list of people who contributed.
 * Read the file "uganda.txt" for copying and usage conditions.
 */

/*
 * edit.c: functions for insert mode
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "param.h"
#include "ops.h"	/* for operator */

extern char_u *get_inserted();
static void start_arrow __ARGS((void));
static void stop_arrow __ARGS((void));
static void stop_insert __ARGS((void));
static int echeck_abbr __ARGS((int));

int arrow_used;				/* Normally FALSE, set to TRUE after hitting
							 * cursor key in insert mode. Used by vgetorpeek()
							 * to decide when to call u_sync() */
int restart_edit = 0;		/* call edit when next command finished */
static char_u	*last_insert = NULL;
							/* the text of the previous insert */
static int		last_insert_skip;
							/* number of chars in front of the previous insert */
static int		new_insert_skip;
							/* number of chars in front of the current insert */

	void
edit(count)
	long count;
{
	int			 c;
	int			 cc;
	char_u		*ptr;
	char_u		*saved_line = NULL;		/* saved line for replace mode */
	linenr_t	 saved_lnum = 0;		/* lnum of saved line */
	int			 saved_char = NUL;		/* char replaced by NL */
	linenr_t	 lnum;
	int 		 temp = 0;
	int			 mode;
	int			 nextc = 0;
	int			 lastc = 0;
	colnr_t		 mincol;
	static linenr_t o_lnum = 0;
	static int	 o_eol = FALSE;
#ifdef WEBB_KEYWORD_COMPL
	FPOS		 complete_pos;
	FPOS		 first_match;
	char_u		 *complete_pat = NULL;
	char_u		 *completion_str = NULL;
	char_u		 *last_completion_str = NULL;
	char_u		 *tmp_ptr;
	int			 previous_c = 0;
	int			 complete_col = 0;			/* init for gcc */
	int			 complete_direction;
	int			 complete_any_word = 0;		/* true -> ^N/^P hit with no prefix
											 * init for gcc */
	int			 done;
	int			 found_error = FALSE;
	char_u		 backup_char = 0;			/* init for gcc */

	c = NUL;
#endif /* WEBB_KEYWORD_COMPL */

	if (restart_edit)
	{
		arrow_used = TRUE;
		restart_edit = 0;
		/*
		 * If the cursor was after the end-of-line before the CTRL-O
		 * and it is now at the end-of-line, put it after the end-of-line
		 * (this is not correct in very rare cases).
		 */
		if (o_eol && curwin->w_cursor.lnum == o_lnum &&
				*((ptr = ml_get(curwin->w_cursor.lnum)) + curwin->w_cursor.col) != NUL &&
				*(ptr + curwin->w_cursor.col + 1) == NUL)
			++curwin->w_cursor.col;
	}
	else
	{
		arrow_used = FALSE;
		o_eol = FALSE;
	}

#ifdef DIGRAPHS
	dodigraph(-1);					/* clear digraphs */
#endif

/*
 * Get the current length of the redo buffer, those characters have to be
 * skipped if we want to get to the inserted characters.
 */

	ptr = get_inserted();
	new_insert_skip = STRLEN(ptr);
	free(ptr);

	old_indent = 0;

	for (;;)
	{
		if (arrow_used)		/* don't repeat insert when arrow key used */
			count = 0;

		if (!arrow_used)
			curwin->w_set_curswant = TRUE;	/* set curwin->w_curswant for next K_DARROW or K_UARROW */
		cursupdate();		/* Figure out where the cursor is based on curwin->w_cursor. */
		showruler(0);
		setcursor();
#ifdef WEBB_KEYWORD_COMPL
		previous_c = c;
#endif /* WEBB_KEYWORD_COMPL */
		if (nextc)			/* character remaining from CTRL-V */
		{
			c = nextc;
			nextc = 0;
		}
		else
		{
			c = vgetc();
			if (c == Ctrl('C'))
				got_int = FALSE;
		}
#ifdef WEBB_KEYWORD_COMPL
		if (previous_c == Ctrl('N') || previous_c == Ctrl('P'))
		{
			/* Show error message from attempted keyword completion (probably
			 * 'Pattern not found') until another key is hit, then go back to
			 * showing what mode we are in.
			 */
			showmode();
			if (c != Ctrl('N') && c != Ctrl('P'))
			{
				/* Get here when we have finished typing a sequence of ^N and
				 * ^P. Free up memory that was used, and make sure we can redo
				 * the insert.
				 */
				if (completion_str != NULL)
					AppendToRedobuff(completion_str);
				free(complete_pat);
				free(completion_str);
				free(last_completion_str);
				complete_pat = completion_str = last_completion_str = NULL;
			}
		}
#endif /* WEBB_KEYWORD_COMPL */
		if (c != Ctrl('D'))			/* remember to detect ^^D and 0^D */
			lastc = c;

/*
 * In replace mode a backspace puts the original text back.
 * We save the current line to be able to do that.
 * If characters are appended to the line, they will be deleted.
 * If we start a new line (with CR) the saved line will be empty, thus
 * the characters will be deleted.
 * If we backspace over the new line, that line will be saved.
 */
		if (State == REPLACE && saved_lnum != curwin->w_cursor.lnum)
		{
			free(saved_line);
			saved_line = strsave(ml_get(curwin->w_cursor.lnum));
			saved_lnum = curwin->w_cursor.lnum;
		}

#ifdef DIGRAPHS
		c = dodigraph(c);
#endif /* DIGRAPHS */

		if (c == Ctrl('V'))
		{
			screen_start();
			screen_outchar('^', curwin->w_row, curwin->w_col);
			AppendToRedobuff((char_u *)"\026");	/* CTRL-V */
			cursupdate();
			setcursor();

			c = get_literal(&nextc);

			insertchar(c);
			continue;
		}
		switch (c)		/* handle character in insert mode */
		{
			  case Ctrl('O'):		/* execute one command */
			    if (echeck_abbr(Ctrl('O') + 0x100))
					break;
			  	count = 0;
				if (State == INSERT)
					restart_edit = 'I';
				else
					restart_edit = 'R';
				o_lnum = curwin->w_cursor.lnum;
				o_eol = (gchar_cursor() == NUL);
				goto doESCkey;

			  case ESC: 			/* an escape ends input mode */
			    if (echeck_abbr(ESC + 0x100))
					break;
				/*FALLTHROUGH*/

			  case Ctrl('C'):
doESCkey:
				if (!arrow_used)
				{
					AppendToRedobuff(ESC_STR);

					if (--count > 0)		/* repeat what was typed */
					{
							(void)start_redo_ins();
							continue;
					}
					stop_insert();
				}
				if (!restart_edit)
					curwin->w_set_curswant = TRUE;

				/*
				 * The cursor should end up on the last inserted character.
				 */
				if (curwin->w_cursor.col != 0 && (!restart_edit || gchar_cursor() == NUL) && !p_ri)
					dec_cursor();
				if (extraspace)			/* did reverse replace in column 0 */
				{
					(void)delchar(FALSE);
					updateline();
					extraspace = FALSE;
				}
				State = NORMAL;
					/* inchar() may have deleted the "INSERT" message */
				if (Recording)
					showmode();
				else if (p_smd)
					MSG("");
				free(saved_line);
				old_indent = 0;
				return;

			  	/*
				 * Insert the previously inserted text.
				 * Last_insert actually is a copy of the redo buffer, so we
				 * first have to remove the command.
				 * For ^@ the trailing ESC will end the insert.
				 */
			  case K_ZERO:
			  case Ctrl('A'):
				stuff_inserted(NUL, 1L, (c == Ctrl('A')));
				break;

			  	/*
				 * insert the contents of a register
				 */
			  case Ctrl('R'):
			  	if (insertbuf(vgetc()) == FAIL)
					beep();
				break;

			  case Ctrl('B'):			/* toggle reverse insert mode */
			  	p_ri = !p_ri;
				showmode();
				break;

				/*
				 * If the cursor is on an indent, ^T/^D insert/delete one
				 * shiftwidth. Otherwise ^T/^D behave like a TAB/backspace.
				 * This isn't completely compatible with
				 * vi, but the difference isn't very noticeable and now you can
				 * mix ^D/backspace and ^T/TAB without thinking about which one
				 * must be used.
				 */
			  case Ctrl('T'):		/* make indent one shiftwidth greater */
			  case Ctrl('D'): 		/* make indent one shiftwidth smaller */
				stop_arrow();
				AppendCharToRedobuff(c);
				if ((lastc == '0' || lastc == '^') && curwin->w_cursor.col)
				{
					--curwin->w_cursor.col;
					(void)delchar(FALSE);			/* delete the '^' or '0' */
					if (lastc == '^')
						old_indent = get_indent();	/* remember current indent */

						/* determine offset from first non-blank */
					temp = curwin->w_cursor.col;
					beginline(TRUE);
					temp -= curwin->w_cursor.col;
					set_indent(0, TRUE);	/* remove all indent */
				}
				else
				{
ins_indent:
						/* determine offset from first non-blank */
					temp = curwin->w_cursor.col;
					beginline(TRUE);
					temp -= curwin->w_cursor.col;

					shift_line(c == Ctrl('D'), TRUE, 1);

						/* try to put cursor on same character */
					temp += curwin->w_cursor.col;
				}
				if (temp <= 0)
					curwin->w_cursor.col = 0;
				else
					curwin->w_cursor.col = temp;
				did_ai = FALSE;
				did_si = FALSE;
				can_si = FALSE;
		  		goto redraw;

			  case BS:
			  case DEL:
nextbs:
				mode = 0;
dodel:
				/* can't delete anything in an empty file */
				/* can't backup past first character in buffer */
				/* can't backup past starting point unless 'backspace' > 1 */
				/* can backup to a previous line if 'backspace' == 0 */
				if (bufempty() || (!p_ri &&
						((curwin->w_cursor.lnum == 1 && curwin->w_cursor.col <= 0) ||
						(p_bs < 2 && (arrow_used ||
							(curwin->w_cursor.lnum == Insstart.lnum &&
							curwin->w_cursor.col <= Insstart.col) ||
							(curwin->w_cursor.col <= 0 && p_bs == 0))))))
				{
					beep();
					goto redraw;
				}

				stop_arrow();
				if (p_ri)
					inc_cursor();
				if (curwin->w_cursor.col <= 0)		/* delete newline! */
				{
					lnum = Insstart.lnum;
					if (curwin->w_cursor.lnum == Insstart.lnum || p_ri)
					{
						if (!u_save((linenr_t)(curwin->w_cursor.lnum - 2), (linenr_t)(curwin->w_cursor.lnum + 1)))
							goto redraw;
						--Insstart.lnum;
						Insstart.col = 0;
						/* this is in xvim, why?
						if (curbuf->b_p_ai)
							for (ptr = ml_get(Insstart.lnum);
											iswhite(*ptr++); Insstart.col++)
								; */
					}
				/* in replace mode, in the line we started replacing, we
														only move the cursor */
					if (State != REPLACE || curwin->w_cursor.lnum > lnum)
					{
						temp = gchar_cursor();		/* remember current char */
						--curwin->w_cursor.lnum;
						(void)dojoin(FALSE, TRUE);
						if (temp == NUL && gchar_cursor() != NUL)
							++curwin->w_cursor.col;
						if (saved_char)				/* restore what NL replaced */
						{
							State = NORMAL;			/* no replace for this char */
							inschar(saved_char);	/* but no showmatch */
							State = REPLACE;
							saved_char = NUL;
							if (!p_ri)
								dec_cursor();
						}
						else if (p_ri)				/* in reverse mode */
							saved_lnum = 0;			/* save this line again */
					}
					else
						dec_cursor();
					did_ai = FALSE;
				}
				else
				{
					if (p_ri && State != REPLACE)
						dec_cursor();
					mincol = 0;
					if (mode == 3 && !p_ri && curbuf->b_p_ai)	/* keep indent */
					{
						temp = curwin->w_cursor.col;
						beginline(TRUE);
						if (curwin->w_cursor.col < temp)
							mincol = curwin->w_cursor.col;
						curwin->w_cursor.col = temp;
					}

					/* delete upto starting point, start of line or previous word */
					do
					{
						if (!p_ri)
							dec_cursor();

								/* start of word? */
						if (mode == 1 && !isspace(gchar_cursor()))
						{
							mode = 2;
							temp = isidchar(gchar_cursor());
						}
								/* end of word? */
						else if (mode == 2 && (isspace(cc = gchar_cursor()) || isidchar(cc) != temp))
						{
							if (!p_ri)
								inc_cursor();
							else if (State == REPLACE)
								dec_cursor();
							break;
						}
						if (State == REPLACE)
						{
							if (saved_line)
							{
								if (extraspace)
								{
									if ((int)STRLEN(ml_get(curwin->w_cursor.lnum)) - 1 > (int)STRLEN(saved_line))
										(void)delchar(FALSE);
									else
									{
										dec_cursor();
										(void)delchar(FALSE);
										extraspace = FALSE;
										pchar_cursor(*saved_line);
									}
								}
								else if (curwin->w_cursor.col < STRLEN(saved_line))
									pchar_cursor(saved_line[curwin->w_cursor.col]);
								else if (!p_ri)
									(void)delchar(FALSE);
							}
						}
						else  /* State != REPLACE */
						{
							(void)delchar(FALSE);
							if (p_ri && gchar_cursor() == NUL)
								break;
						}
						if (mode == 0)		/* just a single backspace */
							break;
						if (p_ri && State == REPLACE && inc_cursor())
							break;
					} while (p_ri || (curwin->w_cursor.col > mincol && (curwin->w_cursor.lnum != Insstart.lnum ||
							curwin->w_cursor.col != Insstart.col)));
					if (extraspace)
						dec_cursor();
				}
				did_si = FALSE;
				can_si = FALSE;
				if (curwin->w_cursor.col <= 1)
					did_ai = FALSE;
				/*
				 * It's a little strange to put backspaces into the redo
				 * buffer, but it makes auto-indent a lot easier to deal
				 * with.
				 */
				AppendCharToRedobuff(c);
				if (vpeekc() == BS)
				{
						c = vgetc();
						goto nextbs;	/* speedup multiple backspaces */
				}
redraw:
				cursupdate();
				updateline();
				break;

			  case Ctrl('W'):		/* delete word before cursor */
			  	mode = 1;
			  	goto dodel;

			  case Ctrl('U'):		/* delete inserted text in current line */
				mode = 3;
			  	goto dodel;

			  case K_LARROW:
			  	if (oneleft() == OK)
				{
					start_arrow();
				}
					/* if 'whichwrap' set for cursor in insert mode may go
					 * to previous line */
				else if ((p_ww & 16) && curwin->w_cursor.lnum > 1)
				{
					start_arrow();
					--(curwin->w_cursor.lnum);
					coladvance(MAXCOL);
					curwin->w_curswant = MAXCOL;	/* so we stay at the end */
				}
				else
					beep();
				break;

			  case K_SLARROW:
			  	if (curwin->w_cursor.lnum > 1 || curwin->w_cursor.col > 0)
				{
					bck_word(1L, 0);
					start_arrow();
				}
				else
					beep();
				break;

			  case K_RARROW:
				if (gchar_cursor() != NUL)
				{
					curwin->w_set_curswant = TRUE;
					start_arrow();
					++curwin->w_cursor.col;
				}
					/* if 'whichwrap' set for cursor in insert mode may go
					 * to next line */
				else if ((p_ww & 16) && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count)
				{
					curwin->w_set_curswant = TRUE;
					start_arrow();
					++curwin->w_cursor.lnum;
					curwin->w_cursor.col = 0;
				}
				else
					beep();
				break;

			  case K_SRARROW:
			  	if (curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count || gchar_cursor() != NUL)
				{
					fwd_word(1L, 0, 0);
					start_arrow();
				}
				else
					beep();
				break;

			  case K_UARROW:
			  	if (oneup(1L))
					start_arrow();
				else
					beep();
				break;

			  case K_SUARROW:
			  	if (onepage(BACKWARD, 1L))
					start_arrow();
				else
					beep();
				break;

			  case K_DARROW:
			  	if (onedown(1L))
					start_arrow();
				else
					beep();
				break;

			  case K_SDARROW:
			  	if (onepage(FORWARD, 1L))
					start_arrow();
				else
					beep();
				break;

			  case TAB:
			    if (echeck_abbr(TAB + 0x100))
					break;
			  	if ((!curbuf->b_p_et && !(p_sta && inindent())) || (p_ri && State == REPLACE))
					goto normalchar;

				AppendToRedobuff((char_u *)"\t");

				if (!curbuf->b_p_et)				/* insert smart tab */
					goto ins_indent;

										/* p_te set: expand a tab into spaces */
				stop_arrow();
				did_ai = FALSE;
				did_si = FALSE;
				can_si = FALSE;
				if (p_sta && inindent())
					temp = (int)curbuf->b_p_sw;
				else
					temp = (int)curbuf->b_p_ts;
				temp -= curwin->w_cursor.col % temp;
				inschar(' ');			/* delete one char in replace mode */
				while (--temp)
					insstr((char_u *)" ");		/* insstr does not delete chars */
				goto redraw;

			  case CR:
			  case NL:
			    if (echeck_abbr(c + 0x100))
					break;
				stop_arrow();
				if (State == REPLACE)
				{
					saved_char = gchar_cursor();
					(void)delchar(FALSE);
				}
				/*
				 * When 'autoindent' set delete white space after the cursor.
				 * Vi does this, although it probably does it implicitly due
				 * to the way it does auto-indent -- webb.
				 */
				if (curbuf->b_p_ai || curbuf->b_p_si)
					while ((c = gchar_cursor()) == ' ' || c == TAB)
						(void)delchar(FALSE);

				AppendToRedobuff(NL_STR);
				if (!Opencmd(FORWARD, TRUE, State == INSERT))
					goto doESCkey;		/* out of memory */
				if (p_ri)
				{
					dec_cursor();
					if (State == REPLACE && curwin->w_cursor.col > 0)
						dec_cursor();
				}
				break;

#ifdef DIGRAPHS
			  case Ctrl('K'):
				screen_start();
				screen_outchar('?', curwin->w_row, curwin->w_col);
				setcursor();
			  	c = vgetc();
				if (c != ESC)
				{
					if (charsize(c) == 1)
					{
						screen_start();
						screen_outchar(c, curwin->w_row, curwin->w_col);
					}
					setcursor();
					cc = vgetc();
					if (cc != ESC)
					{
						AppendToRedobuff((char_u *)"\026");	/* CTRL-V */
						c = getdigraph(c, cc, TRUE);
						goto normalchar;
					}
				}
				updateline();
				goto doESCkey;
#endif /* DIGRAPHS */

#ifdef WEBB_KEYWORD_COMPL
			  case Ctrl('P'):			/* Do previous pattern completion */
			  case Ctrl('N'):			/* Do next pattern completion */
				if (c == Ctrl('P'))
					complete_direction = BACKWARD;
				else
					complete_direction = FORWARD;
				if (previous_c != Ctrl('N') && previous_c != Ctrl('P'))
				{
					/* First time we hit ^N or ^P (in a row, I mean) */
					complete_pos = curwin->w_cursor;
					ptr = ml_get(complete_pos.lnum);
					complete_col = complete_pos.col;
					temp = complete_col - 1;
					if (temp < 0 || !isidchar(ptr[temp]))
					{
						complete_pat = strsave((char_u *)"\\<[a-zA-Z_]");
						complete_any_word = TRUE;
					}
					else
					{
						while (temp >= 0 && isidchar(ptr[temp]))
							temp--;
						temp++;
						complete_pat = alloc(curwin->w_cursor.col - temp + 3);
						if (complete_pat != NULL)
							sprintf((char *)complete_pat, "\\<%.*s",
								(int)(curwin->w_cursor.col - temp), ptr + temp);
						complete_any_word = FALSE;
					}
					last_completion_str = strsave((char_u *)" ");
				}
				else
				{
					/* This is not the first ^N or ^P we have hit in a row */
					while (curwin->w_cursor.col != complete_col)
					{
						curwin->w_cursor.col--;
						delchar(FALSE);
					}
					if (completion_str != NULL)
					{
						free(last_completion_str);
						last_completion_str = strsave(completion_str);
					}
				}
				if (complete_pat == NULL || last_completion_str == NULL)
				{
					found_error = TRUE;
					break;
				}
				if (!complete_any_word)
				{
					ptr = ml_get(curwin->w_cursor.lnum);
					backup_char = ptr[complete_col - 1];
					ptr[complete_col - 1] = ' ';
				}
				done = FALSE;
				found_error = FALSE;
				first_match.lnum = 0;
				keep_old_search_pattern = TRUE;
				while (!done)
				{
					if (complete_direction == BACKWARD)
					{
						ptr = ml_get(complete_pos.lnum);
						while (isidchar(ptr[complete_pos.col]))
							complete_pos.col--;
						complete_pos.col++;
					}
					if (!searchit(&complete_pos, complete_direction,
						complete_pat, 1L, TRUE, TRUE))
					{
						found_error = TRUE;
						break;
					}
					if (complete_any_word)
						ptr = ml_get_pos(&complete_pos);
					else
						ptr = ml_get_pos(&complete_pos) + 1;
					tmp_ptr = ptr;
					temp = 1;
					while (*tmp_ptr != NUL && isidchar(*tmp_ptr++))
						temp++;
					free (completion_str);
					tmp_ptr = completion_str = alloc(temp);
					if (completion_str == NULL)
					{
						found_error = TRUE;
						break;
					}
					while (*ptr != NUL && isidchar(*ptr))
						*(tmp_ptr++) = *(ptr++);
					*tmp_ptr = NUL;
					if (completion_str[0] != NUL &&
							STRCMP(completion_str, last_completion_str) != 0)
						done = TRUE;
					else if (first_match.lnum == 0)
					{
						first_match.lnum = complete_pos.lnum;
						first_match.col = complete_pos.col;
					}
					else if (complete_pos.lnum == first_match.lnum
						 && complete_pos.col == first_match.col)
					{
						if (completion_str[0] == NUL)
							EMSG("Exact match only");
						else
							EMSG("No other matches");
						done = TRUE;
					}
				}
				if (!found_error)
					insstr(completion_str);
				if (!complete_any_word)
				{
					ptr = ml_get(curwin->w_cursor.lnum);
					ptr[complete_col - 1] = backup_char;
				}
				keep_old_search_pattern = FALSE;
				updateline();
				break;
#endif /* WEBB_KEYWORD_COMPL */

			  case Ctrl('Y'):				/* copy from previous line */
				lnum = curwin->w_cursor.lnum - 1;
				goto copychar;

			  case Ctrl('E'):				/* copy from next line */
				lnum = curwin->w_cursor.lnum + 1;
copychar:
				if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count)
				{
					beep();
					break;
				}

				/* try to advance to the cursor column */
				temp = 0;
				ptr = ml_get(lnum);
				while (temp < curwin->w_virtcol && *ptr)
						temp += chartabsize(*ptr++, (long)temp);

				if (temp > curwin->w_virtcol)
						--ptr;
				if ((c = *ptr) == NUL)
				{
					beep();
					break;
				}

				/*FALLTHROUGH*/
			  default:
normalchar:
				/*
				 * do some very smart indenting when entering '{' or '}' or '#'
				 */
				if (curwin->w_cursor.col > 0 && ((can_si && c == '}') || (did_si && c == '{')))
				{
					FPOS	*pos, old_pos;
					int		i;

						/* for '}' set indent equal to matching '{' */
					if (c == '}' && (pos = showmatch('{')) != NULL)
					{
						old_pos = curwin->w_cursor;
						curwin->w_cursor = *pos;
						i = get_indent();
						curwin->w_cursor = old_pos;
						set_indent(i, TRUE);
					}
					else
						shift_line(TRUE, TRUE, 1);
				}
					/* set indent of '#' always to 0 */
				if (curwin->w_cursor.col > 0 && can_si && c == '#')
				{
								/* remember current indent for next line */
					old_indent = get_indent();
					set_indent(0, TRUE);
				}

				if (isidchar(c) || !echeck_abbr(c))
					insertchar(c);
				break;
			}
	}
}

/*
 * Next character is interpreted literally.
 * A one, two or three digit decimal number is interpreted as its byte value.
 * If one or two digits are entered, *nextc is set to the next character.
 */
	int
get_literal(nextc)
	int *nextc;
{
	int			 cc;
	int			 nc;
	int			 oldstate;
	int			 i;

	oldstate = State;
	State = NOMAPPING;		/* next characters not mapped */

	if (got_int)
	{
		*nextc = NUL;
		return Ctrl('C');
	}
	cc = 0;
	for (i = 0; i < 3; ++i)
	{
		nc = vgetc();
		if (!isdigit(nc))
			break;
		cc = cc * 10 + nc - '0';
		nc = 0;
	}
	if (i == 0)		/* no number entered */
	{
		cc = nc;
		nc = 0;
		if (cc == K_ZERO)	/* NUL is stored as NL */
			cc = '\n';
	}
	else if (cc == 0)		/* NUL is stored as NL */
		cc = '\n';

	State = oldstate;
	*nextc = nc;
	got_int = FALSE;		/* CTRL-C typed after CTRL-V is not an interrupt */
	return cc;
}

/*
 * Special characters in this context are those that need processing other
 * than the simple insertion that can be performed here. This includes ESC
 * which terminates the insert, and CR/NL which need special processing to
 * open up a new line. This routine tries to optimize insertions performed by
 * the "redo", "undo" or "put" commands, so it needs to know when it should
 * stop and defer processing to the "normal" mechanism.
 */
#define ISSPECIAL(c)	((c) < ' ' || (c) >= DEL)

	void
insertchar(c)
	unsigned	c;
{
	int		haveto_redraw = FALSE;
	int		textwidth;

	stop_arrow();

	/*
	 * find out textwidth to be used:
	 *	if 'textwidth' option is set, use it
	 *	else if 'wrapmargin' option is set, use Columns - 'wrapmargin'
	 *	if invalid value, us 0.
	 */
	textwidth = curbuf->b_p_tw;
	if (textwidth == 0 && curbuf->b_p_wm)
		textwidth = Columns - curbuf->b_p_wm;
	if (textwidth < 0)
		textwidth = 0;

	/*
	 * If the cursor is past 'textwidth' and we are inserting a non-space,
	 * try to break the line in two or more pieces. If c == NUL then we have
	 * been called to do formatting only. If textwidth == 0 it does nothing.
	 * Don't do this if an existing character is being replaced.
	 */
	if (c == NUL || !(isspace(c) || (State == REPLACE && *ml_get_cursor() != NUL)))
	{
		while (textwidth && curwin->w_virtcol >= textwidth)
		{
			int		startcol;		/* Cursor column at entry */
			int		wantcol;		/* column at textwidth border */
			int		foundcol;		/* column for start of word */

			if ((startcol = curwin->w_cursor.col) == 0)
				break;
			coladvance(textwidth);			/* find column of textwidth border */
			wantcol = curwin->w_cursor.col;

			curwin->w_cursor.col = startcol - 1;
			foundcol = 0;
			while (curwin->w_cursor.col > 0)			/* find position to break at */
			{
				if (isspace(gchar_cursor()))
				{
					while (curwin->w_cursor.col > 0 && isspace(gchar_cursor()))
						--curwin->w_cursor.col;
					if (curwin->w_cursor.col == 0)	/* only spaces in front of text */
						break;
					foundcol = curwin->w_cursor.col + 1;
					if (curwin->w_cursor.col < wantcol)
						break;
				}
				--curwin->w_cursor.col;
			}

			if (foundcol == 0)			/* no spaces, cannot break line */
			{
				curwin->w_cursor.col = startcol;
				break;
			}
			curwin->w_cursor.col = foundcol;		/* put cursor after pos. to break line */
			startcol -= foundcol;
			Opencmd(FORWARD, FALSE, FALSE);
			while (isspace(gchar_cursor()) && startcol)		/* delete blanks */
			{
				(void)delchar(FALSE);
				--startcol;				/* adjust cursor pos. */
			}
			curwin->w_cursor.col += startcol;
			curs_columns(FALSE);		/* update curwin->w_virtcol */
			haveto_redraw = TRUE;
		}
		if (c == NUL)					/* formatting only */
			return;
		if (haveto_redraw)
		{
			/*
			 * If the cursor ended up just below the screen we scroll up here
			 * to avoid a redraw of the whole screen in the most common cases.
			 */
 			if (curwin->w_cursor.lnum == curwin->w_botline && !curwin->w_empty_rows)
				win_del_lines(curwin, 0, 1, TRUE, TRUE);
			updateScreen(CURSUPD);
		}
	}

	did_ai = FALSE;
	did_si = FALSE;
	can_si = FALSE;

	/*
	 * If there's any pending input, grab up to MAX_COLUMNS at once.
	 * This speeds up normal text input considerably.
	 */
	if (vpeekc() != NUL && State != REPLACE && !p_ri)
	{
		char_u			p[MAX_COLUMNS + 1];
		int 			i;

		p[0] = c;
		i = 1;
		while ((c = vpeekc()) != NUL && !ISSPECIAL(c) && i < MAX_COLUMNS &&
					(textwidth == 0 || (curwin->w_virtcol += charsize(p[i - 1])) < textwidth) &&
					!(!no_abbr && !isidchar(c) && isidchar(p[i - 1])))
			p[i++] = vgetc();
#ifdef DIGRAPHS
		dodigraph(-1);					/* clear digraphs */
		dodigraph(p[i-1]);				/* may be the start of a digraph */
#endif
		p[i] = '\0';
		insstr(p);
		AppendToRedobuff(p);
	}
	else
	{
		inschar(c);
		AppendCharToRedobuff(c);
	}

	/*
	 * TODO: If the cursor has shifted past the end of the screen, should
	 * adjust the screen display. Avoids extra redraw.
	 */

	updateline();
}

/*
 * start_arrow() is called when an arrow key is used in insert mode.
 * It resembles hitting the <ESC> key.
 */
	static void
start_arrow()
{
	if (!arrow_used)		/* something has been inserted */
	{
		AppendToRedobuff(ESC_STR);
		arrow_used = TRUE;		/* this means we stopped the current insert */
		stop_insert();
	}
}

/*
 * stop_arrow() is called before a change is made in insert mode.
 * If an arrow key has been used, start a new insertion.
 */
	static void
stop_arrow()
{
	if (arrow_used)
	{
		u_save_cursor();			/* errors are ignored! */
		Insstart = curwin->w_cursor;		/* new insertion starts here */
		ResetRedobuff();
		AppendToRedobuff((char_u *)"1i");	/* pretend we start an insertion */
		arrow_used = FALSE;
	}
}

/*
 * do a few things to stop inserting
 */
	static void
stop_insert()
{
	stop_redo_ins();

	/*
	 * save the inserted text for later redo with ^@
	 */
	free(last_insert);
	last_insert = get_inserted();
	last_insert_skip = new_insert_skip;

	/*
	 * If we just did an auto-indent, truncate the line, and put
	 * the cursor back.
	 */
	if (did_ai && !arrow_used)
	{
		ml_replace(curwin->w_cursor.lnum, (char_u *)"", TRUE);
		curwin->w_cursor.col = 0;
		if (curwin->w_p_list)			/* the deletion is only seen in list mode */
			updateline();
	}
	did_ai = FALSE;
	did_si = FALSE;
	can_si = FALSE;
}

/*
 * move cursor to start of line
 * if flag == TRUE move to first non-white
 */
	void
beginline(flag)
	int			flag;
{
	curwin->w_cursor.col = 0;
	if (flag)
	{
		register char_u *ptr;

		for (ptr = ml_get(curwin->w_cursor.lnum); iswhite(*ptr); ++ptr)
			++curwin->w_cursor.col;
	}
	curwin->w_set_curswant = TRUE;
}

/*
 * oneright oneleft onedown oneup
 *
 * Move one char {right,left,down,up}.
 * Return OK when sucessful, FAIL when we hit a line of file boundary.
 */

	int
oneright()
{
	char_u *ptr;

	ptr = ml_get_cursor();
	if (*ptr++ == NUL || *ptr == NUL)
		return FAIL;
	curwin->w_set_curswant = TRUE;
	++curwin->w_cursor.col;
	return OK;
}

	int
oneleft()
{
	if (curwin->w_cursor.col == 0)
		return FAIL;
	curwin->w_set_curswant = TRUE;
	--curwin->w_cursor.col;
	return OK;
}

	int
oneup(n)
	long n;
{
	if (n != 0 && curwin->w_cursor.lnum == 1)
		return FAIL;
	if (n >= curwin->w_cursor.lnum)
		curwin->w_cursor.lnum = 1;
	else
		curwin->w_cursor.lnum -= n;

	if (operator == NOP)
		cursupdate();				/* make sure curwin->w_topline is valid */

	/* try to advance to the column we want to be at */
	coladvance(curwin->w_curswant);
	return OK;
}

	int
onedown(n)
	long n;
{
	if (n != 0 && curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count)
		return FAIL;
	curwin->w_cursor.lnum += n;
	if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count)
		curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;

	if (operator == NOP)
		cursupdate();				/* make sure curwin->w_topline is valid */

	/* try to advance to the column we want to be at */
	coladvance(curwin->w_curswant);
	return OK;
}

/*
 * move screen 'count' pages up or down and update screen
 *
 * return FAIL for failure, OK otherwise
 */
	int
onepage(dir, count)
	int		dir;
	long	count;
{
	linenr_t		lp;
	long			n;

	if (curbuf->b_ml.ml_line_count == 1)	/* nothing to do */
		return FAIL;
	for ( ; count > 0; --count)
	{
		if (dir == FORWARD ? (curwin->w_topline >= curbuf->b_ml.ml_line_count - 1) : (curwin->w_topline == 1))
		{
			beep();
			return FAIL;
		}
		if (dir == FORWARD)
		{
			if (curwin->w_botline > curbuf->b_ml.ml_line_count)				/* at end of file */
				curwin->w_topline = curbuf->b_ml.ml_line_count;
			else if (plines(curwin->w_botline) >= curwin->w_height - 2 ||	/* next line is big */
					curwin->w_botline - curwin->w_topline <= 3)		/* just three lines on screen */
				curwin->w_topline = curwin->w_botline;
			else
				curwin->w_topline = curwin->w_botline - 2;
			curwin->w_cursor.lnum = curwin->w_topline;
			if (count != 1)
				comp_Botline(curwin);
		}
		else	/* dir == BACKWARDS */
		{
			lp = curwin->w_topline;
			/*
			 * If the first two lines on the screen are not too big, we keep
			 * them on the screen.
			 */
			if ((n = plines(lp)) > curwin->w_height / 2)
				--lp;
			else if (lp < curbuf->b_ml.ml_line_count && n + plines(lp + 1) < curwin->w_height / 2)
				++lp;
			curwin->w_cursor.lnum = lp;
			n = 0;
			while (n <= curwin->w_height && lp >= 1)
			{
				n += plines(lp);
				--lp;
			}
			if (n <= curwin->w_height)				/* at begin of file */
				curwin->w_topline = 1;
			else if (lp >= curwin->w_topline - 2)		/* happens with very long lines */
			{
				--curwin->w_topline;
				comp_Botline(curwin);
				curwin->w_cursor.lnum = curwin->w_botline - 1;
			}
			else
				curwin->w_topline = lp + 2;
		}
	}
	beginline(TRUE);
	updateScreen(VALID);
	return OK;
}

	void
stuff_inserted(c, count, no_esc)
	int		c;
	long	count;
	int		no_esc;
{
	char_u		*esc_ptr = NULL;
	char_u		*ptr;

	if (last_insert == NULL)
	{
		EMSG("No inserted text yet");
		return;
	}
	if (c)
		stuffcharReadbuff(c);
	if (no_esc && (esc_ptr = (char_u *)STRRCHR(last_insert, 27)) != NULL)
		*esc_ptr = NUL;		/* remove the ESC */

			/* skip the command */
	ptr = last_insert + last_insert_skip;

	do
		stuffReadbuff(ptr);
	while (--count > 0);

	if (no_esc && esc_ptr)
		*esc_ptr = 27;		/* put the ESC back */
}

	char_u *
get_last_insert()
{
	if (last_insert == NULL)
		return NULL;
	return last_insert + last_insert_skip;
}

/*
 * Check the word in front of the cursor for an abbreviation.
 * Called when the non-id character "c" has been entered.
 * When an abbreviation is recognized it is removed from the text and
 * the replacement string is inserted in typestr, followed by "c".
 */
	static int
echeck_abbr(c)
	int c;
{
	if (p_paste || no_abbr)			/* no abbreviations or in paste mode */
		return FALSE;

	return check_abbr(c, ml_get(curwin->w_cursor.lnum), curwin->w_cursor.col,
				curwin->w_cursor.lnum == Insstart.lnum ? Insstart.col : 0);
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.