[comp.sources.amiga] v02i084: stevie - vi editor clone, Part04/04

page@swan.ulowell.edu (Bob Page) (12/02/88)

Submitted-by: grwalter@watcgl.waterloo.edu
Posting-number: Volume 2, Issue 84
Archive-name: editors/stevie.4

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:    Shell Archiver
#	Run the following text with /bin/sh to create:
#	bsd.c
#	bsd.h
#	makefile.amiga.lattice
#	makefile.bsd
#	makefile.os2
#	makefile.tos
#	makefile.usg
#	os2.c
#	os2.h
#	porting.doc
#	tos.c
#	tos.h
#	unix.c
#	unix.h
#	normal.c
#	sendpacket.c
# This archive created: Thu Dec  1 20:32:15 1988
cat << \SHAR_EOF > bsd.c
/*
 * System-dependent routines for BSD 4.3 UNIX 
 */

#include "stevie.h"
#include <sgtty.h>

/*
 * inchar() - get a character from the keyboard 
 */
char
inchar()
{
    char            c;

    fflush(stdout);		/* flush any pending output */

    c = (char) getchar();

    return c;
}

void
outstr(s)
    char           *s;
{
    while (*s)
	outchar(*s++);
}

void
beep()
{
    if (RedrawingDisabled)
	return;

    outchar('\007');
}

void
delay()
{
    sleep(1);
}

static struct sgttyb ostate;

void
windinit()
{
    char           *getenv();
    char           *term;
    struct sgttyb   nstate;

    term = getenv("TERM");
    if (!term) {
	fprintf(stderr, "Invalid terminal type '%s'\n", term);
	exit(1);
    }
    if ((strncmp(term, "vt", 2) != 0) && (strncmp(term, "xterm", 5) != 0)) {
	fprintf(stderr, "Invalid terminal type '%s'\n", term);
	exit(1);
    }
    Columns = 80;
    P(P_LI) = Rows = 24;

    /*
     * Go into cbreak mode 
     */
    ioctl(1, (long) TIOCGETP, (char *) &ostate);
    nstate = ostate;
    nstate.sg_flags = nstate.sg_flags & ~(ECHO | CRMOD) | CBREAK;
    ioctl(1, (long) TIOCSETP, (char *) &nstate);
}

void
windexit(r)
    int             r;
{
    fflush(stdout);

    ioctl(0, (long) TIOCSETP, (char *) &ostate);

    exit(r);
}

void
windgoto(r, c)
    int             c;
    int             r;
{
    r++;
    c++;

    outstr("\033[");
    if (r >= 10)
	outchar((char) (r / 10 + '0'));
    outchar((char) (r % 10 + '0'));
    outchar(';');
    if (c >= 10)
	outchar((char) (c / 10 + '0'));
    outchar((char) (c % 10 + '0'));
    outchar('H');
}

FILE           *
fopenb(fname, mode)
    char           *fname;
    char           *mode;
{
    return fopen(fname, mode);
}
SHAR_EOF
cat << \SHAR_EOF > bsd.h
/*
 * BSD 4.3 Machine-dependent routines. 
 */
char inchar();
#define outchar(c) putchar(c)
void outstr(), beep();
#define remove(path) unlink(path)
int rename();
void windinit(), windexit(), windgoto();
void delay();
SHAR_EOF
cat << \SHAR_EOF > makefile.amiga.lattice
#
# Makefile for Lattice C on Amiga
#

.c.o:
	lc $(CFLAGS) $<

LIBS = /regexp/regexp.lib
LDFLAGS=

CFLAGS = -cu -dAUTO_INDENT
LINKFLAGS = NODEBUG

MACH=	amiga.o raw.o sendpacket.o

OBJ=	main.o edit.o linefunc.o normal.o cmdline.o charset.o \
	updateRealscreen.o misccmds.o help.o dec.o inc.o search.o alloc.o \
	updateNextscreen.o mark.o screen.o fileio.o param.o $(MACH)

all : stevie

stevie : $(OBJ)
	BLINK TO stevie FROM lib:c.o $(OBJ) \
	    LIBRARY $(LIBS) lib:lc.lib lib:amiga.lib \
	    $(LINKFLAGS)

clean :
	delete $(OBJ)
SHAR_EOF
cat << \SHAR_EOF > makefile.bsd
#
# Makefile for BSD 4.3 UNIX
#

LIBS = ../regexp/regexp.o ../regexp/regsub.o
LDFLAGS=

CFLAGS = -I../regexp -pg -g -DDEBUG -DAUTO_INDENT
LINTFLAGS = -I../regexp -DDEBUG -DAUTO_INDENT

MACHOBJ=	bsd.o
MACHSRC=	bsd.c

SRC=	main.c edit.c linefunc.c normal.c cmdline.c charset.c \
	updateRealscreen.c misccmds.c help.c dec.c inc.c search.c alloc.c \
	updateNextscreen.c mark.c screen.c fileio.c param.c $(MACHSRC)

OBJ=	main.o edit.o linefunc.o normal.o cmdline.o charset.o \
	updateRealscreen.o misccmds.o help.o dec.o inc.o search.o alloc.o \
	updateNextscreen.o mark.o screen.o fileio.o param.o $(MACHOBJ)

all : stevie

stevie : $(OBJ)
	$(CC) $(OBJ) $(LIBS) $(CFLAGS) -o stevie

lint:
	lint $(LINTFLAGS) $(SRC)

clean :
	rm -f *.out *.o core stevie *.BAK
SHAR_EOF
cat << \SHAR_EOF > makefile.os2
#
# Makefile for OS/2
#
# The make command with OS/2 is really stupid.
#

LIBS = ..\regexp\regexp.obj ..\regexp\regsub.obj

#
# Compact model lets us edit large files, but keep small model code
#
MODEL= -AC
CFLAGS = $(MODEL) -I..\regexp

MACH=	os2.obj

OBJ=	main.obj edit.obj linefunc.obj normal.obj cmdline.obj charset.obj \
	updateRealscreen.obj \
	misccmds.obj help.obj dec.obj inc.obj search.obj alloc.obj \
	updateNextscreen.obj mark.obj screen.obj fileio.obj param.obj $(MACH)

main.obj:	main.c
	cl -c $(CFLAGS) main.c

updateRealscreen.obj:	nexttoscreen.c
	cl -c $(CFLAGS) updateRealscreen.c

alloc.obj : alloc.c
	cl -c $(CFLAGS) alloc.c

edit.obj : edit.c
	cl -c $(CFLAGS) edit.c

updateNextscreen.obj : filetonext.c
	cl -c $(CFLAGS) updateNextscreen.c

linefunc.obj : linefunc.c
	cl -c $(CFLAGS) linefunc.c

normal.obj : normal.c
	cl -c $(CFLAGS) normal.c

cmdline.obj : cmdline.c
	cl -c $(CFLAGS) cmdline.c

charset.obj : charset.c
	cl -c $(CFLAGS) charset.c

misccmds.obj : misccmds.c
	cl -c $(CFLAGS) misccmds.c

help.obj : help.c
	cl -c $(CFLAGS) help.c

dec.obj : dec.c
	cl -c $(CFLAGS) dec.c

inc.obj : inc.c
	cl -c $(CFLAGS) inc.c

search.obj : search.c
	cl -c $(CFLAGS) search.c

mark.obj : mark.c
	cl -c $(CFLAGS) mark.c

screen.obj : screen.c
	cl -c $(CFLAGS) screen.c

fileio.obj : fileio.c
	cl -c $(CFLAGS) fileio.c

param.obj : param.c
	cl -c $(CFLAGS) param.c

os2.obj : os2.c
	cl -c $(CFLAGS) os2.c

stevie.exe : $(OBJ)
	cl $(MODEL) *.obj $(LIBS) -o stevie.exe
	copy stevie.exe rstevie.exe
	bind rstevie.exe \lib\api.lib \lib\doscalls.lib
SHAR_EOF
cat << \SHAR_EOF > makefile.tos
#
# Makefile for the Atari ST - Megamax C compiler
#

LIBS = \megamax\regexp.lib

CFLAGS = -DMEGAMAX

#	Megamax rule
.c.o:
	mmcc $(CFLAGS) $<
	mmimp $*.o
	mmlib rv vi.lib $*.o

MACH=	tos.o

OBJ=	main.o edit.o linefunc.o normal.o cmdline.o charset.o \
	updateRealscreen.o misccmds.o help.o dec.o inc.o search.o alloc.o \
	updateNextscreen.o mark.o screen.o fileio.o param.o $(MACH)

all : stevie.ttp

stevie.ttp : $(OBJ)
	$(LINKER) vi.lib $(LIBS) -o stevie.ttp

clean :
	$(RM) $(OBJ) vi.lib
SHAR_EOF
cat << \SHAR_EOF > makefile.usg
#
# Makefile for UNIX (System V)
#

LIBS = ../regexp/regexp.a
LDFLAGS=

CFLAGS = -I../regexp -O

MACH=	unix.o

OBJ=	main.o edit.o linefunc.o normal.o cmdline.o charset.o \
	updateRealscreen.o misccmds.o help.o dec.o inc.o search.o alloc.o \
	updateNextscreen.o mark.o screen.o fileio.o param.o $(MACH)

all : stevie

stevie : $(OBJ)
	$(CC) $(OBJ) $(LIBS) -o stevie

clean :
	rm $(OBJ)
SHAR_EOF
cat << \SHAR_EOF > os2.c
/*
 * OS/2 System-dependent routines. 
 */

#include "stevie.h"

/*
 * inchar() - get a character from the keyboard 
 */
char
inchar()
{
    int             c;

    flushbuf();			/* flush any pending output */

    c = getch();
    if (c == EOF)		/* EOF used like \n, so just assign it */
	c = '\n';

    return ((char) c);
}

#define	BSIZE	2048
static char     outbuf[BSIZE];
static int      bpos = 0;

flushbuf()
{
    if (bpos != 0)
	write(1, outbuf, bpos);
    bpos = 0;
}

/*
 * Macro to output a character. Used within this file for speed. 
 */
#define	outone(c)	outbuf[bpos++] = c; if (bpos >= BSIZE) flushbuf()

/*
 * Function version for use outside this file. 
 */
void
outchar(c)
    char            c;
{
    outbuf[bpos++] = c;
    if (bpos >= BSIZE)
	flushbuf();
}

void
outstr(s)
    char           *s;
{
    while (*s) {
	outone(*s++);
    }
}

void
beep()
{
    if (RedrawingDisabled)
	return;

    outone('\007');
}

sleep(n)
    int             n;
{
    extern far pascal DOSSLEEP();

    DOSSLEEP(1000L * n);
}

void
delay()
{
    DOSSLEEP(500L);
}

void
windinit()
{
    Columns = 80;
    P(P_LI) = Rows = 25;
}

void
windexit(r)
    int             r;
{
    flushbuf();
    exit(r);
}

void
windgoto(r, c)
    int             r, c;
{
    r += 1;
    c += 1;

    /*
     * Check for overflow once, to save time. 
     */
    if (bpos + 8 >= BSIZE)
	flushbuf();

    outbuf[bpos++] = '\033';
    outbuf[bpos++] = '[';
    if (r >= 10)
	outbuf[bpos++] = r / 10 + '0';
    outbuf[bpos++] = r % 10 + '0';
    outbuf[bpos++] = ';';
    if (c >= 10)
	outbuf[bpos++] = c / 10 + '0';
    outbuf[bpos++] = c % 10 + '0';
    outbuf[bpos++] = 'H';
}

FILE           *
fopenb(fname, mode)
    char           *fname;
    char           *mode;
{
    FILE           *fopen();
    char            modestr[16];

    sprintf(modestr, "%sb", mode);
    return fopen(fname, modestr);
}
SHAR_EOF
cat << \SHAR_EOF > os2.h
/*
 * OS2 Machine-dependent routines. 
 */
char inchar();
void outchar();
void outstr(), beep();
void windinit(), windexit(), windgoto();
void delay();
void sleep();
SHAR_EOF
cat << \SHAR_EOF > porting.doc

		 Release Notes for STEVIE - Version 3.10a

			       Porting

		     Tony Andrews -  March 6, 1988


	Porting the editor is a relatively simple task. Most of the
code is pretty machine-independent. For each environment, there is
a file of routines that perform various low-level operations that
tend to vary a lot from one machine to another. Another file contains
the escape sequences to be used for each machine.

	The machine-dependent files currently used are:

tos.c:	 Atari ST - ifdef for either Megamax or Alcyon
tos.h

unix.c:	 UNIX System V
unix.h

os2.c:	 Microsoft OS/2
os2.h

amiga.c: Amiga
amiga.h

bsd.c:   BSD 4.3 UNIX
bsd.h

	Each of these files are around 150 lines long and deal with
low-level issues like character I/O to the terminal, terminal
initialization, cursor addressing, and so on. There are different
tradeoffs to be made depending on the environment. For example, the
UNIX version buffers terminal output because of the relatively high
overhead of system calls. A quick look at the files will make it clear
what needs to be done in a new environment.

	Terminal escape sequences are in the file "term.h". These are
defined statically, for the time being. There is some discussion in
term.h regarding which sequences are optional and which are not. The
editor is somewhat flexible in dealing with a lack of terminal
capabilities.

	The character set is in the file "charset.c".

	Because not all C compilers support command line macro definitions,
the #define's for system-specific macros are placed at the beginning of the
file 'stevie.h'. If you port to a new system, add another line there to
define the macro you choose for your port.

	The basic process for doing a new port is:

	1. Come up with a macro name to use when ifdef'ing your system-
	   specific changes. Add a line at the top of 'stevie.h' to define
	   the macro name you've chosen.

	2. Look at amiga.c, bsd.c, unix.c, tos.c, and os2.c and copy the one
	   that comes closest to working on your system. Then modify your new
	   file as needed.

	3. Look at term.h and edit the file appropriately adding a new
	   set of escape sequence definitions for your system.

	4. If you haven't already, get a copy of Henry Spencer's regular
	   expression library and compile it. This has been very simple
	   every time I've done it.

	5. Compiling and debug the editor.
SHAR_EOF
cat << \SHAR_EOF > tos.c
/*
 * System-dependent routines for the Atari ST. 
 */

#include "stevie.h"

#include <osbind.h>

/*
 * The following buffer is used to work around a bug in TOS. It appears that
 * unread console input can cause a crash, but only if console output is
 * going on. The solution is to always grab any unread input before putting
 * out a character. The following buffer holds any characters read in this
 * fashion. The problem can be easily produced because STEVIE can't yet keep
 * up with the normal auto-repeat rate in insert mode. 
 */
#define	IBUFSZ	128

static long     inbuf[IBUFSZ];	/* buffer for unread input */
static long    *inptr = inbuf;	/* where to put next character */

/*
 * inchar() - get a character from the keyboard 
 *
 * Certain special keys are mapped to values above 0x80. These mappings are
 * defined in keymap.h. If the key has a non-zero ascii value, it is simply
 * returned. Otherwise it may be a special key we want to map. 
 *
 * The ST has a bug involving keyboard input that seems to occur when typing
 * quickly, especially typing capital letters. Sometimes a value of
 * 0x02540000 is read. This doesn't correspond to anything on the keyboard,
 * according to my documentation. My solution is to loop when any unknown key
 * is seen. Normally, the bell is rung to indicate the error. If the "bug"
 * value is seen, we ignore it completely. 
 */
char
inchar()
{
    for (;;) {
	long            c, *p;

	/*
	 * Get the next input character, either from the input buffer or
	 * directly from TOS. 
	 */
	if (inptr != inbuf) {	/* input in the buffer, use it */
	    c = inbuf[0];
	    /*
	     * Shift everything else in the buffer down. This would be
	     * cleaner if we used a circular buffer, but it really isn't
	     * worth it. 
	     */
	    inptr--;
	    for (p = inbuf; p < inptr; p++)
		*p = *(p + 1);
	} else
	    c = Crawcin();

	if ((c & 0xff) != 0)
	    return ((char) c);

	switch ((int) (c >> 16) & 0xff) {

	  case 0x62:
	    return K_HELP;
	  case 0x61:
	    return K_UNDO;
	  case 0x52:
	    return K_INSERT;
	  case 0x47:
	    return K_HOME;
	  case 0x48:
	    return K_UARROW;
	  case 0x50:
	    return K_DARROW;
	  case 0x4b:
	    return K_LARROW;
	  case 0x4d:
	    return K_RARROW;
	  case 0x29:
	    return K_CGRAVE;	/* control grave accent */

	    /*
	     * Occurs due to a bug in TOS. 
	     */
	  case 0x54:
	    break;
	    /*
	     * Add the function keys here later if we put in support for
	     * macros. 
	     */

	  default:
	    beep();
	    break;

	}
    }
}

/*
 * get_inchars - snarf away any pending console input 
 *
 * If the buffer overflows, we discard what's left and ring the bell. 
 */
static void
get_inchars()
{
    while (Cconis()) {
	if (inptr >= &inbuf[IBUFSZ]) {	/* no room in buffer? */
	    Crawcin();		/* discard the input */
	    beep();		/* and sound the alarm */
	} else
	    *inptr++ = Crawcin();
    }
}

void
outchar(c)
    char            c;
{
    get_inchars();
    Cconout(c);
}

void
outstr(s)
    char           *s;
{
    get_inchars();
    Cconws(s);
}

#define	BGND	0
#define	TEXT	3

/*
 * vbeep() - visual bell 
 */
static void
vbeep()
{
    int             text, bgnd;	/* text and background colors */
    long            l;

    text = Setcolor(TEXT, -1);
    bgnd = Setcolor(BGND, -1);

    Setcolor(TEXT, bgnd);	/* swap colors */
    Setcolor(BGND, text);

    for (l = 0; l < 5000; l++);	/* short pause */

    Setcolor(TEXT, text);	/* restore colors */
    Setcolor(BGND, bgnd);
}

void
beep()
{
    if (RedrawingDisabled)
	return;

    if (P(P_VB))
	vbeep();
    else
	outchar('\007');
}

/*
 * remove(file) - remove a file 
 */
void
remove(file)
    char           *file;
{
    Fdelete(file);
}

/*
 * rename(of, nf) - rename existing file 'of' to 'nf' 
 */
void
rename(of, nf)
    char           *of, *nf;
{
    Fdelete(nf);		/* if 'nf' exists, remove it */
    Frename(0, of, nf);
}

void
windinit()
{
    if (Getrez() == 0)
	Columns = 40;		/* low resolution */
    else
	Columns = 80;		/* medium or high */

    P(P_LI) = Rows = 25;

    Cursconf(1, NULL);
}

void
windexit(r)
    int             r;
{
    exit(r);
}

void
windgoto(r, c)
    int             r, c;
{
    outstr("\033Y");
    outchar(r + 040);
    outchar(c + 040);
}

/*
 * System calls or library routines missing in TOS. 
 */

void
sleep(n)
    int             n;
{
    int             k;

    k = Tgettime();
    while (Tgettime() <= k + n);
}

void
delay()
{
    long            n;

    for (n = 0; n < 8000; n++);
}

int
system(cmd)
    char           *cmd;
{
    char            arg[1];

    arg[0] = (char) 0;		/* no arguments passed to the shell */

    if (Pexec(0, cmd, arg, 0L) < 0)
	return -1;
    else
	return 0;
}

#ifdef	MEGAMAX
char           *
strchr(s, c)
    char           *s;
    int             c;
{
    do {
	if (*s == c)
	    return (s);
    } while (*s++);
    return (NULL);
}
#endif

#ifdef	MEGAMAX

FILE           *
fopenb(fname, mode)
    char           *fname;
    char           *mode;
{
    char            modestr[10];

    sprintf(modestr, "b%s", mode);

    return fopen(fname, modestr);
}

#endif

/*
 * getenv() - get a string from the environment 
 *
 * Both Alcyon and Megamax are missing getenv(). This routine works for both
 * compilers and with the Beckemeyer and Gulam shells. With gulam, the
 * env_style variable should be set to either "mw" or "gu". 
 */
char           *
getenv(name)
    char           *name;
{
    extern long     _base;
    char           *envp, *p;

    envp = *((char **) (_base + 0x2c));

    for (; *envp; envp += strlen(envp) + 1) {
	if (strncmp(envp, name, strlen(name)) == 0) {
	    p = envp + strlen(name);
	    if (*p++ == '=')
		return p;
	}
    }
    return (char *) 0;
}
SHAR_EOF
cat << \SHAR_EOF > tos.h
/*
 * Atari Machine-dependent routines. 
 */
char inchar();
void outchar();
void outstr(), beep();
void remove(), rename();
void windinit(), windexit(), windgoto();
void delay();
void sleep();
SHAR_EOF
cat << \SHAR_EOF > unix.c
/*
 * System-dependent routines for UNIX System V Release 3. 
 */

#include "stevie.h"
/* #include <termio.h> /* System V */
#include <curses.h>		/* BSD */

/*
 * inchar() - get a character from the keyboard 
 */
char
inchar()
{
    char            c;

    flushbuf();			/* flush any pending output */

    while (read(0, &c, 1) != 1);

    return c;
}

#define	BSIZE	2048
static char     outbuf[BSIZE];
static int      bpos = 0;

flushbuf()
{
    if (bpos != 0)
	write(1, outbuf, bpos);
    bpos = 0;
}

/*
 * Macro to output a character. Used within this file for speed. 
 */
#define	outone(c)	outbuf[bpos++] = c; if (bpos >= BSIZE) flushbuf()

/*
 * Function version for use outside this file. 
 */
void
outchar(c)
    char            c;
{
    outbuf[bpos++] = c;
    if (bpos >= BSIZE)
	flushbuf();
}

void
outstr(s)
    char           *s;
{
    while (*s) {
	outone(*s++);
    }
}

void
beep()
{
    if (RedrawingDisabled)
	return;

    outone('\007');
}

/*
 * remove(file) - remove a file 
 */
void
remove(file)
    char           *file;
{
    unlink(file);
}

/*
 * rename(of, nf) - rename existing file 'of' to 'nf' 
 */
void
rename(of, nf)
    char           *of, *nf;
{
    unlink(nf);
    link(of, nf);
    unlink(of);
}

void
delay()
{
    /* not implemented */
}

static struct termio ostate;

void
windinit()
{
    char           *getenv();
    char           *term;
    struct termio   nstate;

    if ((term = getenv("TERM")) == NULL || strcmp(term, "vt100") != 0) {
	fprintf(stderr, "Invalid terminal type '%s'\n", term);
	exit(1);
    }
    Columns = 80;
    P(P_LI) = Rows = 24;

    /*
     * Go into cbreak mode 
     */
    ioctl(0, TCGETA, &ostate);
    nstate = ostate;
    nstate.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL);
    nstate.c_cc[VMIN] = 1;
    nstate.c_cc[VTIME] = 0;
    ioctl(0, TCSETAW, &nstate);
}

void
windexit(r)
    int             r;
{
    /*
     * Restore terminal modes 
     */
    ioctl(0, TCSETAW, &ostate);

    exit(r);
}

#define	outone(c)	outbuf[bpos++] = c; if (bpos >= BSIZE) flushbuf()

void
windgoto(r, c)
    int             r, c;
{
    r += 1;
    c += 1;

    /*
     * Check for overflow once, to save time. 
     */
    if (bpos + 8 >= BSIZE)
	flushbuf();

    outbuf[bpos++] = '\033';
    outbuf[bpos++] = '[';
    if (r >= 10)
	outbuf[bpos++] = r / 10 + '0';
    outbuf[bpos++] = r % 10 + '0';
    outbuf[bpos++] = ';';
    if (c >= 10)
	outbuf[bpos++] = c / 10 + '0';
    outbuf[bpos++] = c % 10 + '0';
    outbuf[bpos++] = 'H';
}

FILE           *
fopenb(fname, mode)
    char           *fname;
    char           *mode;
{
    return fopen(fname, mode);
}
SHAR_EOF
cat << \SHAR_EOF > unix.h
/*
 * Unix Machine-dependent routines. 
 */
char inchar();
void outchar();
void outstr(), beep();
void remove(), rename();
void windinit(), windexit(), windgoto();
void delay();
SHAR_EOF
cat << \SHAR_EOF > normal.c
/*
 * STEVIE - Simply Try this Editor for VI Enthusiasts
 *
 * Code Contributions By : Tim Thompson           twitch!tjt
 *                         Tony Andrews           onecom!wldrdg!tony 
 *                         G. R. (Fred) Walter    watmath!watcgl!grwalter 
 */

/*
 * This file contains the main routine for processing characters in command
 * mode as well as routines for handling the operators. 
 */

#include "stevie.h"

static void
doshift(), dodelete(), doput(), dochange();
static void
                startinsert();
static          bool_t
                dojoin();
static          bool_t
                doyank();

/*
 * Macro evaluates true if char 'c' is a valid identifier character 
 */
#define	IDCHAR(c)	(isalpha(c) || isdigit(c) || (c) == '_')

/*
 * Operators 
 */
#define	NOP	0		/* no pending operation */
#define	DELETE	1
#define	YANK	2
#define	CHANGE	3
#define	LSHIFT	4
#define	RSHIFT	5

#define	CLEAROP	(operator = NOP)/* clear any pending operator */

static int      operator = NOP;	/* current pending operator */

/*
 * When a cursor motion command is made, it is marked as being a character or
 * line oriented motion. Then, if an operator is in effect, the operation
 * becomes character or line oriented accordingly. 
 *
 * Character motions are marked as being inclusive or not. Most char. motions
 * are inclusive, but some (e.g. 'w') are not. 
 *
 * Generally speaking, every command in normal() should either clear any pending
 * operator (with CLEAROP), or set the motion type variable. 
 */

/*
 * Motion types 
 */
#define	MBAD	(-1)		/* 'bad' motion type marks unusable yank buf */
#define	MCHAR	0
#define	MLINE	1

static int      mtype;		/* type of the current cursor motion */
static bool_t   mincl;		/* true if char motion is inclusive */
static int      ybtype = MBAD;
static int      ybcrossline = FALSE;

static LPTR     startop;	/* cursor pos. at start of operator */

/*
 * Operators can have counts either before the operator, or between the
 * operator and the following cursor motion as in: 
 *
 * d3w or 3dw 
 *
 * If a count is given before the operator, it is saved in opnum. If normal() is
 * called with a pending operator, the count in opnum (if present) overrides
 * any count that came later. 
 */
static int      opnum = 0;

#define	DEFAULT1(x)	(((x) == 0) ? 1 : (x))

/*
 * normal 
 *
 * Execute a command in normal mode. 
 */

void
normal(c)
    char            c;
{
    char           *p;
    int             n;
    int             nn;
    bool_t          flag = FALSE;
    int             type = 0;	/* used in some operations to modify type */
    int             dir = FORWARD;	/* search direction */
    char            nchar = NUL;
    bool_t          finish_op;
    LPTR            temp_Curschar;

    last_command = NUL;
    /*
     * If there is an operator pending, then the command we take this time
     * will terminate it. Finish_op tells us to finish the operation before
     * returning this time (unless the operation was cancelled). 
     */
    finish_op = (operator != NOP);

    /*
     * If we're in the middle of an operator AND we had a count before the
     * operator, then that count overrides the current value of Prenum. What
     * this means effectively, is that commands like "3dw" get turned into
     * "d3w" which makes things fall into place pretty neatly. 
     */
    if (finish_op) {
	if (opnum != 0)
	    Prenum = opnum;
    } else
	opnum = 0;

    switch (c) {

      case K_HELP:
	CLEAROP;
	if (help()) {
	    screenclear();
	    updateNextscreen();
	}
	break;

      case CTRL('L'):
	CLEAROP;
	screenclear();
	updateNextscreen();
	break;

      case CTRL('D'):
	CLEAROP;
	if (Prenum)
	    P(P_SS) = (Prenum > Rows - 1) ? Rows - 1 : Prenum;
	scrollup((P(P_SS) < Rows) ? P(P_SS) : Rows - 1);
	onedown((P(P_SS) < Rows) ? P(P_SS) : Rows - 1);
	updateNextscreen();
	break;

      case CTRL('U'):
	CLEAROP;
	if (Prenum)
	    P(P_SS) = (Prenum > Rows - 1) ? Rows - 1 : Prenum;
	scrolldown((P(P_SS) < Rows) ? P(P_SS) : Rows - 1);
	oneup((P(P_SS) < Rows) ? P(P_SS) : Rows - 1);
	updateNextscreen();
	break;

      case CTRL('F'):
	CLEAROP;
	if (nextline(Topchar) == NULL) {
	    beep();
	    break;
	}
	screenclear();
	Prenum = DEFAULT1(Prenum);
	while (Prenum > 0) {
	    *Curschar = *prevline(Botchar);
	    *Topchar = *Curschar;
	    Topchar->index = 0;
	    updateNextscreen();
	    Prenum--;
	}
	beginline(TRUE);
	break;

      case CTRL('B'):
	CLEAROP;
	if (prevline(Topchar) == NULL) {
	    beep();
	    break;
	}
	screenclear();
	Prenum = DEFAULT1(Prenum);
	while (Prenum > 0) {
	    *Curschar = *Topchar;
	    n = Rows - 1;
	    {
		LPTR           *lp = Curschar;
		int             l = 0;

		while ((l < n) && (lp != NULL)) {
		    l += plines(lp);
		    *Topchar = *lp;
		    lp = prevline(lp);
		}
	    }
	    Topchar->index = 0;
	    Prenum--;
	}
	beginline(TRUE);
	updateNextscreen();
	break;

      case CTRL('E'):
	CLEAROP;
	scrollup(DEFAULT1(Prenum));
	updateNextscreen();
	break;

      case CTRL('Y'):
	CLEAROP;
	scrolldown(DEFAULT1(Prenum));
	updateNextscreen();
	break;

      case 'z':
	CLEAROP;
	switch (vgetc()) {
	  case NL:		/* put Curschar at top of screen */
	  case CR:
	    *Topchar = *Curschar;
	    Topchar->index = 0;
	    updateNextscreen();
	    break;

	  case '.':		/* put Curschar in middle of screen */
	    n = Rows / 2;
	    goto dozcmd;

	  case '-':		/* put Curschar at bottom of screen */
	    n = Rows - 1;
	    /* FALLTHROUGH */

    dozcmd:
	    {
		LPTR           *lp = Curschar;
		int             l = 0;

		while ((l < n) && (lp != NULL)) {
		    l += plines(lp);
		    *Topchar = *lp;
		    lp = prevline(lp);
		}
	    }
	    Topchar->index = 0;
	    updateNextscreen();
	    break;

	  default:
	    beep();
	}
	break;

      case CTRL('G'):
	CLEAROP;
	fileinfo();
	break;

      case 'G':
	mtype = MLINE;
	*Curschar = *gotoline(Prenum);
	beginline(TRUE);
	break;

      case 'H':
	mtype = MLINE;
	*Curschar = *Topchar;
	for (n = Prenum; n && onedown(1); n--);
	beginline(TRUE);
	break;

      case 'M':
	mtype = MLINE;
	*Curschar = *Topchar;
	for (n = 0; n < Rows / 2 && onedown(1); n++);
	beginline(TRUE);
	break;

      case 'L':
	mtype = MLINE;
	*Curschar = *prevline(Botchar);
	for (n = Prenum; n && oneup(1); n--);
	beginline(TRUE);
	break;

      case 'l':
      case K_RARROW:
      case ' ':
	mtype = MCHAR;
	mincl = FALSE;
	n = DEFAULT1(Prenum);
	while (n--) {
	    if (!oneright()) {
		if (operator != DELETE && operator != CHANGE) {
		    beep();
		} else {
		    if (lineempty(Curschar)) {
			CLEAROP;
			beep();
		    } else {
			mincl = TRUE;
		    }
		}
		break;
	    }
	}
	set_want_col = TRUE;
	break;

      case 'h':
      case K_LARROW:
      case CTRL('H'):
	mtype = MCHAR;
	mincl = FALSE;
	Prenum = DEFAULT1(Prenum);
	n = Prenum;
	while (n--) {
	    if (!oneleft()) {
		if (operator != DELETE && operator != CHANGE) {
		    beep();
		} else if (Prenum == 1) {
		    CLEAROP;
		    beep();
		}
		break;
	    }
	}
	set_want_col = TRUE;
	break;

      case '-':
	flag = TRUE;
	/* FALLTHROUGH */

      case 'k':
      case K_UARROW:
      case CTRL('P'):
	mtype = MLINE;
	if (!oneup(DEFAULT1(Prenum))) {
	    CLEAROP;
	    beep();
	} else if (flag)
	    beginline(TRUE);
	break;

      case '+':
      case CR:
      case NL:
	flag = TRUE;
	/* FALLTHROUGH */

      case 'j':
      case K_DARROW:
      case CTRL('N'):
	mtype = MLINE;
	if (!onedown(DEFAULT1(Prenum))) {
	    CLEAROP;
	    beep();
	} else if (flag)
	    beginline(TRUE);
	break;

	/*
	 * This is a strange motion command that helps make operators more
	 * logical. It is actually implemented, but not documented in the
	 * real 'vi'. This motion command actually refers to "the current
	 * line". Commands like "dd" and "yy" are really an alternate form of
	 * "d_" and "y_". It does accept a count, so "d3_" works to delete 3
	 * lines. 
	 */
      case '_':
lineop:
	mtype = MLINE;
	if (!onedown(DEFAULT1(Prenum) - 1)) {
	    CLEAROP;
	    beep();
	} else
	    beginline(TRUE);
	break;

      case '|':
	mtype = MCHAR;
	mincl = TRUE;
	beginline(FALSE);
	if (Prenum > 0)
	    *Curschar = *coladvance(Curschar, Prenum - 1);
	Curswant = Prenum - 1;
	break;

      case CTRL(']'):		/* :ta to current identifier */
	CLEAROP;
	{
	    char            ch;
	    LPTR            save;

	    save = *Curschar;
	    /*
	     * First back up to start of identifier. This doesn't match the
	     * real vi but I like it a little better and it shouldn't bother
	     * anyone. 
	     */
	    ch = gchar(Curschar);
	    while (IDCHAR(ch)) {
		if (!oneleft())
		    break;
		ch = gchar(Curschar);
	    }
	    if (!IDCHAR(ch))
		oneright();

	    stuffReadbuff(":ta ");
	    /*
	     * Now grab the chars in the identifier 
	     */
	    ch = gchar(Curschar);
	    while (IDCHAR(ch)) {
		stuffReadbuff(mkstr(ch));
		if (!oneright())
		    break;
		ch = gchar(Curschar);
	    }
	    stuffReadbuff("\n");

	    *Curschar = save;	/* restore, in case of error */
	}
	break;

      case '%':
	mtype = MCHAR;
	mincl = TRUE;
	{
	    LPTR           *pos;

	    if ((pos = showmatch()) == NULL) {
		CLEAROP;
		beep();
	    } else {
		setpcmark();
		*Curschar = *pos;
		set_want_col = TRUE;
	    }
	}
	break;

	/*
	 * Word Motions 
	 */

      case 'B':
	type = 1;
	/* FALLTHROUGH */

      case 'b':
	mtype = MCHAR;
	mincl = FALSE;
	set_want_col = TRUE;
	for (n = DEFAULT1(Prenum); n > 0; n--) {
	    LPTR           *pos;

	    if ((Curschar->linep->prev == NULL) && (Curschar->index == 0)) {
		CLEAROP;
		beep();
		break;
	    }
	    pos = bck_word(Curschar, type);
	    if (pos == NULL)
		*Curschar = *gotoline(1);	/* goto top of file */
	    else
		*Curschar = *pos;
	}
	break;

      case 'W':
	type = 1;
	/* FALLTHROUGH */

      case 'w':
	/*
	 * This is a little strange. To match what the real vi does, we
	 * effectively map 'cw' to 'ce', and 'cW' to 'cE'. This seems
	 * impolite at first, but it's really more what we mean when we say
	 * 'cw'. 
	 */
	if (operator == CHANGE)
	    goto doecmd;

	mtype = MCHAR;
	mincl = FALSE;
	set_want_col = TRUE;
	for (n = DEFAULT1(Prenum); n > 0; n--) {
	    LPTR           *pos;

	    if ((pos = fwd_word(Curschar, type)) == NULL) {
		CLEAROP;
		beep();
		break;
	    } else
		*Curschar = *pos;
	}
	break;

      case 'E':
	type = 1;
	/* FALLTHROUGH */

      case 'e':
doecmd:
	mtype = MCHAR;
	mincl = TRUE;
	set_want_col = TRUE;
	for (n = DEFAULT1(Prenum); n > 0; n--) {
	    LPTR           *pos;

	    if ((pos = end_word(Curschar, type)) == NULL) {
		CLEAROP;
		beep();
		break;
	    } else
		*Curschar = *pos;
	}
	break;

      case '$':
	mtype = MCHAR;
	mincl = TRUE;
	while (oneright());
	Curswant = 999;		/* so we stay at the end */
	break;

      case '^':
	flag = TRUE;
	/* FALLTHROUGH */

      case '0':
	mtype = MCHAR;
	mincl = TRUE;
	beginline(flag);
	break;

      case 'A':
	set_want_col = TRUE;
	while (oneright());
	ResetBuffers();
	AppendToRedobuff("A");
	goto doAPPENDcmd;

      case 'a':
	ResetBuffers();
	AppendToRedobuff("a");

doAPPENDcmd:
	CLEAROP;
	/* Works just like an 'i'nsert on the next character. */
	n = RowNumber(Curschar);
	AppendPositionToUndoUndobuff(Curschar->index, n);
	AppendToUndoUndobuff("a");

	if (!lineempty(Curschar))
	    inc(Curschar);

	n = RowNumber(Curschar);
	AppendPositionToUndobuff(Curschar->index, n);

	startinsert(FALSE);

	CHANGED;
	break;

      case 'I':
	beginline(TRUE);
	ResetBuffers();
	AppendToRedobuff("I");
	goto doINSERTcmd;
	/* FALLTHROUGH */

      case 'i':
      case K_INSERT:
	ResetBuffers();
	AppendToRedobuff("i");

doINSERTcmd:
	CLEAROP;

	n = RowNumber(Curschar);
	AppendPositionToUndobuff(Curschar->index, n);
	AppendPositionToUndoUndobuff(Curschar->index, n);
	AppendToUndoUndobuff("i");

	startinsert(FALSE);

	CHANGED;
	break;

      case 'o':
	CLEAROP;
	ResetBuffers();

	n = RowNumber(Curschar);
	AppendToRedobuff("o");
	AppendPositionToUndobuff(Curschar->index, n);
	AppendPositionToUndoUndobuff(Curschar->index, n);
	AppendToUndoUndobuff("o");

	if (opencmd(FORWARD, TRUE)) {
	    startinsert(TRUE);

	    CHANGED;
	}
	last_command = 'o';
	break;

      case 'O':
	CLEAROP;
	ResetBuffers();

	n = RowNumber(Curschar);
	AppendToRedobuff("O");
	AppendPositionToUndobuff(Curschar->index, n);
	AppendPositionToUndoUndobuff(Curschar->index, n);
	AppendToUndoUndobuff("O");

	if (opencmd(BACKWARD, TRUE)) {
	    startinsert(TRUE);

	    CHANGED;
	}
	last_command = 'O';
	break;

      case 'd':
	if (operator == DELETE)	/* handle 'dd' */
	    goto lineop;
	if (Prenum != 0)
	    opnum = Prenum;
	startop = *Curschar;
	operator = DELETE;
	break;

	/*
	 * Some convenient abbreviations... 
	 */

      case 'x':
	if (Prenum)
	    stuffnumReadbuff(Prenum);
	stuffReadbuff("dl");
	break;

      case 'X':
	if (Prenum)
	    stuffnumReadbuff(Prenum);
	stuffReadbuff("dh");
	break;

      case 'D':
	stuffReadbuff("d$");
	break;

      case 'Y':
	if (Prenum)
	    stuffnumReadbuff(Prenum);
	stuffReadbuff("yy");
	break;

      case 'C':
	stuffReadbuff("c$");
	break;

      case 'c':
	if (operator == CHANGE) {	/* handle 'cc' */
	    CLEAROP;
	    stuffReadbuff("0c$");
	    break;
	}
	if (Prenum != 0)
	    opnum = Prenum;
	startop = *Curschar;
	operator = CHANGE;
	break;

      case 'y':
	if (operator == YANK)	/* handle 'yy' */
	    goto lineop;
	if (Prenum != 0)
	    opnum = Prenum;
	startop = *Curschar;
	operator = YANK;
	break;

      case 'p':
	if (RedrawingDisabled) {
	    RedrawingDisabled = FALSE;
	    updateNextscreen();
	    break;
	} else if (Yankbuffptr != NULL) {
	    doput(FORWARD);

	    if (ybcrossline) {
		n = RowNumber(Curschar);
		stuffnumReadbuff(n);
		stuffReadbuff("G");
		if (ybtype == MCHAR) {
		    stuffnumReadbuff(Curschar->index);
		    if (Curschar->index)
			stuffReadbuff("l");
		} else
		    stuffnumReadbuff(0);
	    }
	    stuffReadbuff("p");
	    RedrawingDisabled = TRUE;
	} else
	    beep();
	break;

      case 'P':
	if (RedrawingDisabled) {
	    RedrawingDisabled = FALSE;
	    updateNextscreen();
	    break;
	} else if (Yankbuffptr != NULL) {
	    doput(BACKWARD);

	    n = RowNumber(Curschar);
	    stuffnumReadbuff(n);
	    stuffReadbuff("G");
	    if (ybtype == MCHAR) {
		stuffnumReadbuff(Curschar->index);
		if (Curschar->index)
		    stuffReadbuff("l");
	    } else
		stuffnumReadbuff(0);
	    stuffReadbuff("P");
	    RedrawingDisabled = TRUE;
	} else
	    beep();
	break;

      case '>':
	if (operator == RSHIFT)	/* handle >> */
	    goto lineop;
	if (operator == LSHIFT) {
	    CLEAROP;
	    beep();
	    break;
	}
	if (Prenum != 0)
	    opnum = Prenum;
	startop = *Curschar;	/* save current position */
	operator = RSHIFT;
	break;

      case '<':
	if (operator == LSHIFT)	/* handle << */
	    goto lineop;
	if (operator == RSHIFT) {
	    CLEAROP;
	    beep();
	    break;
	}
	if (Prenum != 0)
	    opnum = Prenum;
	startop = *Curschar;	/* save current position */
	operator = LSHIFT;
	break;

      case 's':		/* substitute characters */
	if (Prenum)
	    stuffnumReadbuff(Prenum);
	stuffReadbuff("cl");
	break;

      case '?':
      case '/':
      case ':':
	CLEAROP;
	readcmdline(c, (char *) NULL);
	break;

      case 'n':
	mtype = MCHAR;
	mincl = FALSE;
	set_want_col = TRUE;
	repsearch(0);
	break;

      case 'N':
	mtype = MCHAR;
	mincl = FALSE;
	set_want_col = TRUE;
	repsearch(1);
	break;

	/*
	 * Character searches 
	 */
      case 'T':
	dir = BACKWARD;
	/* FALLTHROUGH */

      case 't':
	type = 1;
	goto docsearch;

      case 'F':
	dir = BACKWARD;
	/* FALLTHROUGH */

      case 'f':
docsearch:
	mtype = MCHAR;
	mincl = TRUE;
	set_want_col = TRUE;
	if ((nchar = vgetc()) == ESC)	/* search char */
	    break;
	if (!searchc(nchar, dir, type)) {
	    CLEAROP;
	    beep();
	}
	break;

      case ',':
	flag = 1;
	/* FALLTHROUGH */

      case ';':
	mtype = MCHAR;
	mincl = TRUE;
	set_want_col = TRUE;
	if (!crepsearch(flag)) {
	    CLEAROP;
	    beep();
	}
	break;

	/*
	 * Function searches 
	 */

      case '[':
	dir = BACKWARD;
	/* FALLTHROUGH */

      case ']':
	mtype = MLINE;
	set_want_col = TRUE;
	if (vgetc() != c)
	    beep();

	if (!findfunc(dir)) {
	    CLEAROP;
	    beep();
	}
	break;

	/*
	 * Marks 
	 */

      case 'm':
	CLEAROP;
	if (!setmark(vgetc()))
	    beep();
	break;

      case '\'':
	flag = TRUE;
	/* FALLTHROUGH */

      case '`':
	{
	    LPTR            mtmp, *mark = getmark(vgetc());

	    if (mark == NULL) {
		CLEAROP;
		beep();
	    } else {
		mtmp = *mark;
		setpcmark();
		*Curschar = mtmp;
		if (flag)
		    beginline(TRUE);
	    }
	    mtype = flag ? MLINE : MCHAR;
	    mincl = TRUE;	/* ignored if not MCHAR */
	    set_want_col = TRUE;
	}
	break;

      case 'r':
	CLEAROP;
	if (lineempty(Curschar)) {	/* Nothing to replace */
	    beep();
	    break;
	}
	if ((nchar = vgetc()) == ESC)
	    break;

	Prenum = DEFAULT1(Prenum);
	n = strlen(Curschar->linep->s) - Curschar->index;
	if (n < Prenum) {
	    beep();
	    break;
	}
	ResetBuffers();

	nn = RowNumber(Curschar);
	AppendPositionToUndobuff(Curschar->index, nn);
	AppendPositionToUndoUndobuff(Curschar->index, nn);

	while (Prenum > 0) {
	    AppendToRedobuff("r");
	    AppendToRedobuff(mkstr(nchar));

	    AppendToUndobuff("r");
	    AppendToUndobuff(mkstr(gchar(Curschar)));

	    AppendToUndoUndobuff("r");
	    AppendToUndoUndobuff(mkstr(nchar));

	    pchar(Curschar, nchar);	/* Change current character. */

	    if (Prenum > 1) {
		oneright();
		AppendToRedobuff("l");
		AppendToUndobuff("l");
		AppendToUndoUndobuff("l");
	    }
	    Prenum--;
	}

	CHANGED;
	updateline();
	break;

      case '~':		/* swap case */
	CLEAROP;
	if (lineempty(Curschar)) {
	    beep();
	    break;
	}
	ResetBuffers();

	n = RowNumber(Curschar);
	AppendPositionToUndobuff(Curschar->index, n);
	AppendPositionToUndoUndobuff(Curschar->index, n);

	Prenum = DEFAULT1(Prenum);
	if (Prenum > 0) {
	    AppendNumberToRedobuff(Prenum);
	    AppendNumberToUndobuff(Prenum);
	    AppendNumberToUndoUndobuff(Prenum);
	}
	AppendToRedobuff("~");
	AppendToUndobuff("~");
	AppendToUndoUndobuff("~");

	while (Prenum > 0) {
	    c = gchar(Curschar);
	    if (isalpha(c)) {
		if (islower(c))
		    pchar(Curschar, toupper(c));
		else
		    pchar(Curschar, tolower(c));
	    }
	    if (!oneright())
		break;
	    Prenum--;
	}

	CHANGED;
	updateline();
	break;

      case UNDO_SHIFTJ:
	CLEAROP;
	if (UndoInProgress) {
	    if (dojoin(FALSE, FALSE)) {
		CHANGED;
		updateNextscreen();
	    }
	    break;
	}
	goto doSHIFTJcommand;

      case 'J':
	CLEAROP;
doSHIFTJcommand:
	if (nextline(Curschar) == NULL) {	/* on last line */
	    beep();
	    break;
	}
	ResetBuffers();

	temp_Curschar = *Curschar;
	nn = strlen(Curschar->linep->s);
	if (nn < 0)
	    nn = 0;
	n = RowNumber(&temp_Curschar);

	AppendToRedobuff("J");

	AppendPositionToUndobuff(nn, n);

	AppendPositionToUndoUndobuff(0, n);
	AppendToUndoUndobuff("J");

	if (lineempty(nextline(Curschar))) {
	    AppendToUndobuff("a\n");
	    if (!dojoin(FALSE, TRUE)) {
		beep();
		break;
	    }
	} else if (lineempty(Curschar)) {
	    AppendToUndobuff("i\n");
	    if (!dojoin(FALSE, TRUE)) {
		beep();
		break;
	    }
	} else {
	    AppendToUndobuff("dli\n");
	    if (!dojoin(TRUE, TRUE)) {
		beep();
		break;
	    }
	}

	AppendToUndobuff(ESC_STR);
	AppendPositionToUndobuff(nn, n);

	CHANGED;
	updateNextscreen();
	break;

      case K_CGRAVE:		/* shorthand command */
	CLEAROP;
	stuffReadbuff(":e #\n");
	break;

      case 'Z':		/* write, if changed, and exit */
	if (vgetc() != 'Z') {
	    beep();
	    break;
	}
	if (Changed) {
	    if (Filename != NULL) {
		if (!writeit(Filename, (LPTR *) NULL, (LPTR *) NULL))
		    return;
	    } else {
		emsg("No output file");
		return;
	    }
	}
	getout(0);
	break;

      case '.':
	CLEAROP;
	if (RedrawingDisabled) {
	    RedrawingDisabled = FALSE;
	    updateNextscreen();
	} else if (Redobuffptr != NULL) {
	    stuffReadbuff(Redobuff);
	    stuffReadbuff(".");
	    RedrawingDisabled = TRUE;
	} else
	    beep();
	break;

      case 'u':
      case K_UNDO:
	CLEAROP;
	if (UndoInProgress) {
	    p = UndoUndobuff;
	    UndoUndobuff = Undobuff;
	    Undobuff = p;
	    p = UndoUndobuffptr;
	    UndoUndobuffptr = Undobuffptr;
	    Undobuffptr = p;

	    UndoInProgress = FALSE;
	    RedrawingDisabled = FALSE;
	    updateNextscreen();
	} else if (Undobuffptr != NULL) {
	    stuffReadbuff(Undobuff);
	    stuffReadbuff("u");
	    UndoInProgress = TRUE;
	    RedrawingDisabled = TRUE;
	} else {
	    beep();
	}
	break;

      default:
	CLEAROP;
	beep();
	break;
    }

    /*
     * If an operation is pending, handle it... 
     */
    if (finish_op) {		/* we just finished an operator */
	if (operator == NOP)	/* ... but it was cancelled */
	    return;

	switch (operator) {

	  case LSHIFT:
	  case RSHIFT:
	    ResetBuffers();

	    n = RowNumber(&startop);
	    AppendPositionToUndobuff(startop.index, n);
	    AppendPositionToUndoUndobuff(startop.index, n);
	    if (Prenum != 0) {
		AppendNumberToRedobuff(Prenum);
		AppendNumberToUndobuff(Prenum);
		AppendNumberToUndoUndobuff(Prenum);
	    }
	    AppendToRedobuff((operator == LSHIFT) ? "<" : ">");
	    AppendToUndobuff((operator == LSHIFT) ? ">" : "<");
	    AppendToUndoUndobuff((operator == LSHIFT) ? "<" : ">");
	    AppendToRedobuff(mkstr(c));
	    if (c == '>')
		AppendToUndobuff("<");
	    else if (c == '<')
		AppendToUndobuff(">");
	    else
		AppendToUndobuff(mkstr(c));
	    AppendToUndoUndobuff(mkstr(c));

	    doshift(operator);

	    CHANGED;
	    break;

	  case DELETE:
	    ResetBuffers();

	    n = RowNumber(&startop);
	    AppendPositionToUndoUndobuff(startop.index, n);

	    temp_Curschar = (lt(&startop, Curschar) ? startop : *Curschar);
	    n = RowNumber(&temp_Curschar);
	    if (Prenum != 0) {
		AppendNumberToRedobuff(Prenum);
		AppendNumberToUndoUndobuff(Prenum);
	    }
	    AppendToRedobuff("d");
	    AppendToUndoUndobuff("d");
	    AppendToRedobuff(mkstr(c));
	    AppendToUndoUndobuff(mkstr(c));
	    if (nchar != NUL) {
		AppendToRedobuff(mkstr(nchar));
		AppendToUndoUndobuff(mkstr(nchar));
	    }
	    AppendPositionToUndobuff(temp_Curschar.index, n);

	    dodelete(TRUE, TRUE, TRUE);

	    AppendPositionToUndobuff(temp_Curschar.index, n);

	    CHANGED;
	    break;

	  case YANK:
	    ResetBuffers();	/* no redo/undo/(undo of undo) on yank... */
	    doyank();
	    if (!ybcrossline)
		*Curschar = startop;
	    else if (lt(&startop, Curschar))
		*Curschar = startop;
	    break;

	  case CHANGE:
	    ResetBuffers();

	    n = RowNumber(&startop);
	    AppendPositionToUndoUndobuff(startop.index, n);

	    temp_Curschar = (lt(&startop, Curschar) ? startop : *Curschar);
	    n = RowNumber(&temp_Curschar);
	    if (mtype == MLINE)
		AppendPositionToUndobuff(0, n);
	    else
		AppendPositionToUndobuff(temp_Curschar.index, n);

	    if (Prenum != 0) {
		AppendNumberToRedobuff(Prenum);
		AppendNumberToUndoUndobuff(Prenum);
	    }
	    AppendToRedobuff("c");
	    AppendToUndoUndobuff("c");
	    AppendToRedobuff(mkstr(c));
	    AppendToUndoUndobuff(mkstr(c));
	    if (nchar != NUL) {
		AppendToRedobuff(mkstr(nchar));
		AppendToUndoUndobuff(mkstr(nchar));
	    }
	    dochange();

	    CHANGED;

	    last_command = 'c';
	    break;

	  default:
	    beep();
	}
	operator = NOP;
    }
}

/*
 * tabinout(shift_type, num) 
 *
 * If shift_type == RSHIFT, add a tab to the begining of the next num lines;
 * otherwise delete a tab from the beginning of the next num lines. 
 */
static void
tabinout(shift_type, num)
    int             shift_type;
    int             num;
{
    LPTR           *p;

    beginline(FALSE);
    while (num-- > 0) {
	beginline(FALSE);
	if (shift_type == RSHIFT)
	    inschar(TAB);
	else {
	    if (gchar(Curschar) == TAB)
		delchar(TRUE, FALSE);
	}
	if (num > 0) {
	    if ((p = nextline(Curschar)) != NULL)
		*Curschar = *p;
	    else
		break;
	}
    }
}

/*
 * doshift - handle a shift operation 
 */
static void
doshift(op)
    int             op;
{
    LPTR            top, bot;
    int             nlines;

    top = startop;
    bot = *Curschar;

    if (lt(&bot, &top))
	pswap(top, bot);

    nlines = cntllines(&top, &bot);
    *Curschar = top;
    tabinout(op, nlines);

    /*
     * The cursor position afterward is the prior of the two positions. 
     */
    *Curschar = top;

    /*
     * If we were on the last char of a line that got shifted left, then move
     * left one so we aren't beyond the end of the line 
     */
    if (gchar(Curschar) == NUL && Curschar->index > 0)
	Curschar->index--;

    if (op == RSHIFT)
	oneright();
    else
	oneleft();

    updateNextscreen();

    if (nlines > P(P_RP))
	smsg("%d lines %ced", nlines, (op == RSHIFT) ? '>' : '<');
}

/*
 * dodelete - handle a delete operation 
 */
static void
dodelete(redraw, setup_for_undo, try_to_yank)
    bool_t          redraw;
    bool_t          setup_for_undo;
    bool_t          try_to_yank;
{
    LPTR            top, bot;
    int             nlines;
    int             n;

    /*
     * Do a yank of whatever we're about to delete. If there's too much stuff
     * to fit in the yank buffer, then get a confirmation before doing the
     * delete. This is crude, but simple. And it avoids doing a delete of
     * something we can't put back if we want. 
     */
    if (try_to_yank) {
	if (!doyank()) {
	    msg("yank buffer exceeded: press <y> to confirm");
	    if (vgetc() != 'y') {
		msg("delete aborted");
		*Curschar = startop;
		return;
	    }
	}
    }
    top = startop;
    bot = *Curschar;

    if (lt(&bot, &top))
	pswap(top, bot);

    *Curschar = top;
    nlines = cntllines(&top, &bot);
    cursupdate();

    if (mtype == MLINE) {
	if (operator == CHANGE) {
	    last_command_char = 'a';
	    delline(nlines - 1);
	    Curschar->index = 0;
	    while (delchar(TRUE, FALSE));
	} else {
	    if (bot.linep->next == Fileend->linep)
		last_command_char = 'o';
	    else
		last_command_char = 'O';
	    if (setup_for_undo)
		AppendToUndobuff(mkstr(last_command_char));
	    delline(nlines);
	}
    } else if (top.linep == bot.linep) {	/* del. within line */
	if (!mincl)
	    dec(&bot);

	if (endofline(&bot))
	    last_command_char = 'a';
	else
	    last_command_char = 'i';
	if (setup_for_undo)
	    AppendToUndobuff(mkstr(last_command_char));
	n = bot.index - top.index + 1;
	while (n--)
	    if (!delchar(TRUE, FALSE))
		break;
    } else {			/* del. between lines */
	if (endofline(&top)) {
	    if (nextline(&top)) {
		if (lineempty(nextline(&top)))
		    last_command_char = 'a';
		else
		    last_command_char = 'i';
	    } else {
		last_command_char = 'a';
	    }
	} else {
	    last_command_char = 'i';
	}
	if (setup_for_undo)
	    AppendToUndobuff(mkstr(last_command_char));

	n = Curschar->index;
	while (Curschar->index >= n)
	    if (!delchar(TRUE, FALSE))
		break;

	top = *Curschar;
	*Curschar = *nextline(Curschar);
	delline(nlines - 2);
	Curschar->index = 0;
	n = bot.index;
	if (!mincl)
	    n--;

	while (n-- >= 0)
	    if (!delchar(TRUE, FALSE))
		break;
	*Curschar = top;
	dojoin(FALSE, FALSE);
    }

    if (mtype == MCHAR && nlines == 1 && redraw)
	updateline();
    else
	updateNextscreen();

    if (nlines > P(P_RP))
	smsg("%d fewer lines", nlines);

    if (setup_for_undo) {
	AppendToUndobuff(Yankbuff);
	AppendToUndobuff(ESC_STR);
    }
}

/*
 * dochange - handle a change operation 
 */
static void
dochange()
{
    bool_t          doappend;	/* true if we should do append, not insert */

    doappend = lt(Curschar, &startop);	/* needed for some compilers */
    doappend = endofline(doappend ? &startop : Curschar);

    dodelete(FALSE, FALSE, TRUE);

    if (doappend && !lineempty(Curschar))
	inc(Curschar);

    startinsert(FALSE);
}

static          bool_t
doyank()
{
    LPTR            top, bot;
    char           *ybend = &Yankbuff[YANKSIZE - 1];
    int             nlines;

    Yankbuffptr = Yankbuff;

    top = startop;
    bot = *Curschar;

    if (lt(&bot, &top))
	pswap(top, bot);

    nlines = cntllines(&top, &bot);

    ybtype = mtype;		/* set the yank buffer type */
    ybcrossline = FALSE;
    if (LINEOF(&top) != LINEOF(&bot))
	ybcrossline = TRUE;

    if (mtype == MLINE) {
	ybcrossline = TRUE;
	top.index = 0;
	bot.index = strlen(bot.linep->s);
	/*
	 * The following statement checks for the special case of yanking a
	 * blank line at the beginning of the file. If not handled right, we
	 * yank an extra char (a newline). 
	 */
	if (dec(&bot) == -1) {
	    *Yankbuff = NUL;
	    Yankbuffptr = NULL;
	    return TRUE;
	}
    } else {
	if (!mincl)
	    if (!equal(&top, &bot))
		dec(&bot);
    }

    for (; ltoreq(&top, &bot); inc(&top)) {
	*Yankbuffptr = (gchar(&top) != NUL) ? gchar(&top) : NL;
	Yankbuffptr++;
	if (Yankbuffptr >= ybend) {
	    *Yankbuffptr = NUL;
	    msg("yank too big for buffer");
	    ybtype = MBAD;
	    return FALSE;
	}
    }

    *Yankbuffptr = NUL;

    if (operator == YANK)
	if (nlines > P(P_RP))
	    smsg("%d lines yanked", nlines);

    return TRUE;
}

static void
doput(dir)
    int             dir;
{
    bool_t          type;

    if (ybtype == MBAD) {
	beep();
	return;
    }
    type = (ybtype == MCHAR);
    if (dir == FORWARD)
	stuffReadbuff(type ? "a" : "o");
    else
	stuffReadbuff(type ? "i" : "O");

    stuffReadbuff(Yankbuff);
    stuffReadbuff(ESC_STR);
}

static void
startinsert(startln)
    int             startln;	/* if set, insert at start of line */
{
    *Insstart = *Curschar;
    if (startln) {
	Insstart->index = 0;
    }
    Insbuffptr = Insbuff;
    *Insbuffptr = NUL;
    State = INSERT;
    if (P(P_MO))
	msg("Insert Mode");
}

void
ResetBuffers()
{
    if (UndoInProgress)
	return;

    *Redobuff = NUL;
    Redobuffptr = NULL;

    *Undobuff = NUL;
    Undobuffptr = NULL;

    *UndoUndobuff = NUL;
    UndoUndobuffptr = NULL;
}

void
AppendToRedobuff(s)
    char           *s;
{
    if (UndoInProgress)
	return;

    if (Redobuffptr == NULL) {
	if ((strlen(s) + 1) < REDO_UNDO_SIZE) {
	    strcpy(Redobuff, s);
	    Redobuffptr = Redobuff;
	} else
	    emsg("Couldn't AppendToRedobuff() - should never happen");
	return;
    }
    if ((strlen(Redobuff) + strlen(s) + 1) < REDO_UNDO_SIZE) {
	strcat(Redobuff, s);
	return;
    }
    emsg("Couldn't AppendToRedobuff() - should never happen");
}

void
AppendNumberToRedobuff(n)
    int             n;
{
    char            buf[32];

    if (UndoInProgress)
	return;

    sprintf(buf, "%d", n);
    AppendToRedobuff(buf);
}

void
AppendToUndobuff(s)
    char           *s;
{
    if (UndoInProgress)
	return;

    if (Undobuffptr == NULL) {
	if ((strlen(s) + 1) < REDO_UNDO_SIZE) {
	    strcpy(Undobuff, s);
	    Undobuffptr = Undobuff;
	} else
	    emsg("Couldn't AppendToUndobuff() - should never happen");
	return;
    }
    if ((strlen(Undobuff) + strlen(s) + 1) < REDO_UNDO_SIZE) {
	strcat(Undobuff, s);
	return;
    }
    emsg("Couldn't AppendToUndobuff() - should never happen");
}

void
AppendNumberToUndobuff(n)
    int             n;
{
    char            buf[32];

    if (UndoInProgress)
	return;

    sprintf(buf, "%d", n);
    AppendToUndobuff(buf);
}

void
AppendPositionToUndobuff(column, row)
    int             column;
    int             row;
{
    if (UndoInProgress)
	return;

    AppendNumberToUndobuff(row);
    AppendToUndobuff("G");
    AppendNumberToUndobuff(column);
    if (column)
	AppendToUndobuff("l");
}

void
AppendToUndoUndobuff(s)
    char           *s;
{
    if (UndoInProgress)
	return;

    if (UndoUndobuffptr == NULL) {
	if ((strlen(s) + 1) < REDO_UNDO_SIZE) {
	    strcpy(UndoUndobuff, s);
	    UndoUndobuffptr = Undobuff;
	} else
	    emsg("Couldn't AppendToUndoUndobuff() - should never happen");
	return;
    }
    if ((strlen(UndoUndobuff) + strlen(s) + 1) < REDO_UNDO_SIZE) {
	strcat(UndoUndobuff, s);
	return;
    }
    emsg("Couldn't AppendToUndoUndobuff() - should never happen");
}

void
AppendNumberToUndoUndobuff(n)
    int             n;
{
    char            buf[32];

    if (UndoInProgress)
	return;

    sprintf(buf, "%d", n);
    AppendToUndoUndobuff(buf);
}

void
AppendPositionToUndoUndobuff(column, row)
    int             column;
    int             row;
{
    if (UndoInProgress)
	return;

    AppendNumberToUndoUndobuff(row);
    AppendToUndoUndobuff("G");
    AppendNumberToUndoUndobuff(column);
    if (column)
	AppendToUndoUndobuff("l");
}

static          bool_t
dojoin(leading_space, strip_leading_spaces)
    bool_t          leading_space;
    bool_t          strip_leading_spaces;
{
    int             scol;	/* save cursor column */
    int             currsize;	/* size of the current line */
    int             nextsize;	/* size of the next line */

    if (nextline(Curschar) == NULL)	/* on last line */
	return FALSE;

    if (!canincrease(nextsize = strlen(Curschar->linep->next->s)))
	return FALSE;

    currsize = strlen(Curschar->linep->s);

    while (oneright());		/* to end of line */

    strcat(Curschar->linep->s, Curschar->linep->next->s);

    /*
     * Delete the following line. To do this we move the cursor there
     * briefly, and then move it back. Don't back up if the delete made us
     * the last line. 
     */
    Curschar->linep = Curschar->linep->next;
    scol = Curschar->index;

    if (nextline(Curschar) != NULL) {
	delline(1);
	Curschar->linep = Curschar->linep->prev;
    } else
	delline(1);

    Curschar->index = scol;

    if (currsize)
	oneright();		/* go to first char. of joined line */

    if (nextsize != 0 && strip_leading_spaces) {
	/*
	 * Delete leading white space on the joined line and insert a single
	 * space. 
	 */
	while (gchar(Curschar) == ' ' || gchar(Curschar) == TAB) {
	    delchar(TRUE, TRUE);
	}
	if (leading_space)
	    inschar(' ');
    }
    return TRUE;
}

char           *
mkstr(c)
    char            c;
{
    static char     s[2];

    s[0] = c;
    s[1] = NUL;

    return s;
}
SHAR_EOF
cat << \SHAR_EOF > sendpacket.c
/*
 * Sendpacket.c 
 *
 * An invaluable addition to your Amiga.lib file. This code sends a packet the
 * given message port. This makes working around DOS lots easier. 
 *
 * Note, I didn't write this, those wonderful folks at CBM did. I do suggest
 * however that you may wish to add it to Amiga.Lib, to do so, compile it and
 * say 'oml lib:amiga.lib -r sendpacket.o' 
 */

#include <exec/types.h>
#include <exec/ports.h>
#include <exec/memory.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>

/*
 * Function - SendPacket written by Phil Lindsay, Carolyn Scheppner, and Andy
 * Finkel. This function will send a packet of the given type to the Message
 * Port supplied. 
 */

long
SendPacket(pid, action, args, nargs)
    struct MsgPort *pid;	/* process indentifier ... (handlers message
				 * port ) */
    long            action,	/* packet type ... (what you want handler to
				 * do )   */
                    args[],	/* a pointer to a argument list */
                    nargs;	/* number of arguments in list  */
{
    struct MsgPort *replyport;
    struct StandardPacket *packet;

    long            count, *pargs, res1;

    replyport = (struct MsgPort *) CreatePort(NULL, 0);
    if (!replyport)
	return (0);

    /* Allocate space for a packet, make it public and clear it */
    packet = (struct StandardPacket *)
	AllocMem((long) sizeof(struct StandardPacket), MEMF_PUBLIC | MEMF_CLEAR);
    if (!packet) {
	DeletePort(replyport);
	return (0);
    }
    packet->sp_Msg.mn_Node.ln_Name = (char *) &(packet->sp_Pkt);
    packet->sp_Pkt.dp_Link = &(packet->sp_Msg);
    packet->sp_Pkt.dp_Port = replyport;
    packet->sp_Pkt.dp_Type = action;

    /* copy the args into the packet */
    pargs = &(packet->sp_Pkt.dp_Arg1);	/* address of first argument */
    for (count = 0; count < nargs; count++)
	pargs[count] = args[count];

    PutMsg(pid, packet);	/* send packet */

    WaitPort(replyport);
    GetMsg(replyport);

    res1 = packet->sp_Pkt.dp_Res1;

    FreeMem(packet, (long) sizeof(struct StandardPacket));
    DeletePort(replyport);

    return (res1);
}
SHAR_EOF
#	End of shell archive
exit 0
-- 
Bob Page, U of Lowell CS Dept.  page@swan.ulowell.edu  ulowell!page
Have five nice days.